fix(importer-curl): keep --data-urlencode whole and survive a stray percent (#597)

This commit is contained in:
Ngo Quoc Viet
2026-08-20 08:41:03 -07:00
committed by GitHub
parent 4e835cf7e8
commit cb295b7102
2 changed files with 147 additions and 4 deletions
+46 -4
View File
@@ -506,7 +506,17 @@ function importCommand(parseEntries: string[], workspaceId: string) {
form: multipartFormDataFromRaw,
};
} else if (dataParameters.length > 0 && bodyAsGET) {
urlParameters.push(...dataParameters);
// `-G` moves the data into the query string, and Yaak encodes url
// parameters on send exactly as it encodes the form body below, so this
// needs the same decode -- otherwise a `--data-urlencode` value arrives
// here already encoded and goes out encoded twice.
urlParameters.push(
...dataParameters.map((parameter) => ({
...parameter,
name: decodePercentEncoding(parameter.name),
value: decodePercentEncoding(parameter.value),
})),
);
} else if (
dataParameters.length > 0 &&
(mimeType == null || mimeType === "application/x-www-form-urlencoded")
@@ -515,8 +525,8 @@ function importCommand(parseEntries: string[], workspaceId: string) {
body = {
form: dataParameters.map((parameter) => ({
...parameter,
name: decodeURIComponent(parameter.name || ""),
value: decodeURIComponent(parameter.value || ""),
name: decodePercentEncoding(parameter.name),
value: decodePercentEncoding(parameter.value),
})),
};
filteredHeaders.push({
@@ -593,6 +603,34 @@ interface DataParameter {
enabled?: boolean;
}
/**
* Decode a percent-encoded form value, keeping it as-is when it is not one.
*
* Yaak's form editor holds decoded values and re-encodes them on send, so a
* `-d` value has to be decoded on the way in. But curl sends that value
* verbatim and does not require it to be valid percent-encoding: `a=100%` is
* an ordinary form value, and `decodeURIComponent` throws URIError on it,
* which failed the whole import rather than that one parameter.
*/
function decodePercentEncoding(value: string | undefined): string {
const text = value || "";
try {
return decodeURIComponent(text);
} catch {
// Mixed: some of it is percent-encoded and some of it is a stray `%`.
// Returning the whole string untouched would leave the encoded part to be
// encoded a second time on send, so decode each valid run on its own and
// leave the stray byte alone. A run rather than a single escape, because a
// non-ASCII character is several escapes that only decode together.
return text.replace(/(%[0-9A-Fa-f]{2})+/g, (run) => {
try {
return decodeURIComponent(run);
} catch {
return run;
}
});
}
}
function pairsToDataParameters(keyedPairs: FlagsByName): DataParameter[] {
const dataParameters: DataParameter[] = [];
@@ -605,7 +643,11 @@ function pairsToDataParameters(keyedPairs: FlagsByName): DataParameter[] {
for (const p of pairs) {
if (typeof p !== "string") continue;
const params = p.split("&");
// `-d` content really is `&`-separated, so splitting it is right. But
// `--data-urlencode` encodes its whole argument — an `&` inside it is
// data curl percent-encodes, not a separator, so splitting there turned
// one parameter into several and changed what the request sends.
const params = flagName === "data-urlencode" ? [p] : p.split("&");
for (const param of params) {
const [name, value] = splitOnce(param, "=");
if (param.startsWith("@")) {