diff --git a/src/PSProxmoxVE.Core/Client/PveHttpClient.cs b/src/PSProxmoxVE.Core/Client/PveHttpClient.cs
index 4982231..84d1209 100644
--- a/src/PSProxmoxVE.Core/Client/PveHttpClient.cs
+++ b/src/PSProxmoxVE.Core/Client/PveHttpClient.cs
@@ -99,7 +99,7 @@ namespace PSProxmoxVE.Core.Client
{
var request = BuildRequest(HttpMethod.Post, resource, mutating: true);
if (data != null)
- request.Content = new FormUrlEncodedContent(data);
+ request.Content = BuildFormContent(data);
return await SendAsync(request, resource, "POST").ConfigureAwait(false);
}
@@ -111,7 +111,7 @@ namespace PSProxmoxVE.Core.Client
{
var request = BuildRequest(HttpMethod.Put, resource, mutating: true);
if (data != null)
- request.Content = new FormUrlEncodedContent(data);
+ request.Content = BuildFormContent(data);
return await SendAsync(request, resource, "PUT").ConfigureAwait(false);
}
@@ -124,6 +124,52 @@ namespace PSProxmoxVE.Core.Client
return await SendAsync(request, resource, "DELETE").ConfigureAwait(false);
}
+ // -------------------------------------------------------------------------
+ // Form body encoding
+ // -------------------------------------------------------------------------
+
+ ///
+ /// Builds form-encoded content using minimal encoding that matches curl behavior.
+ /// .NET's over-encodes characters like
+ /// : and ! which PVE's internal API consumers (e.g. cluster join)
+ /// do not properly URL-decode.
+ ///
+ private static StringContent BuildFormContent(Dictionary data)
+ {
+ var sb = new StringBuilder();
+ foreach (var kvp in data)
+ {
+ if (sb.Length > 0) sb.Append('&');
+ sb.Append(EncodeFormValue(kvp.Key));
+ sb.Append('=');
+ sb.Append(EncodeFormValue(kvp.Value));
+ }
+ return new StringContent(sb.ToString(), Encoding.UTF8, "application/x-www-form-urlencoded");
+ }
+
+ ///
+ /// Encodes a form value with minimal percent-encoding — only characters that
+ /// would break form parsing are encoded. This matches curl's -d behavior
+ /// and avoids over-encoding that PVE does not handle correctly.
+ ///
+ private static string EncodeFormValue(string value)
+ {
+ var sb = new StringBuilder(value.Length);
+ foreach (char c in value)
+ {
+ switch (c)
+ {
+ case '&': sb.Append("%26"); break;
+ case '=': sb.Append("%3D"); break;
+ case '+': sb.Append("%2B"); break;
+ case ' ': sb.Append('+'); break;
+ case '%': sb.Append("%25"); break;
+ default: sb.Append(c); break;
+ }
+ }
+ return sb.ToString();
+ }
+
// -------------------------------------------------------------------------
// Synchronous wrappers
// -------------------------------------------------------------------------