mirror of
https://github.com/curlconverter/curlconverter.git
synced 2022-05-22 02:35:29 +03:00
handle --form more correctly (#385)
This commit is contained in:
committed by
GitHub
parent
733f110e56
commit
5c2f1ab054
@@ -71,20 +71,20 @@ export const _toCFML = (request: Request): string => {
|
||||
|
||||
if (request.data || request.multipartUploads) {
|
||||
if (request.multipartUploads) {
|
||||
for (const [multipartKey, multipartValue] of request.multipartUploads) {
|
||||
if (multipartValue.charAt(0) === "@") {
|
||||
for (const { name, content, contentFile } of request.multipartUploads) {
|
||||
if (contentFile) {
|
||||
cfmlCode +=
|
||||
'httpService.addParam(type="file", name="' +
|
||||
quote(multipartKey) +
|
||||
quote(name) +
|
||||
'", file="#expandPath("' +
|
||||
quote(multipartValue.substring(1)) +
|
||||
quote(contentFile) +
|
||||
'")#");\n';
|
||||
} else {
|
||||
cfmlCode +=
|
||||
'httpService.addParam(type="formfield", name="' +
|
||||
quote(multipartKey) +
|
||||
quote(name) +
|
||||
'", value="' +
|
||||
quote(multipartValue) +
|
||||
quote(content as string) +
|
||||
'");\n';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -106,12 +106,11 @@ function getFormDataString(request: Request): string {
|
||||
|
||||
let fileArgs: string[] | string = [];
|
||||
let dataArgs: string[] | string = [];
|
||||
for (const [multipartKey, multipartValue] of request.multipartUploads) {
|
||||
if (multipartValue.startsWith("@")) {
|
||||
const fileName = multipartValue.slice(1);
|
||||
fileArgs.push(` {:file, ~s|${fileName}|}`);
|
||||
for (const { name, content, contentFile } of request.multipartUploads) {
|
||||
if (contentFile) {
|
||||
fileArgs.push(` {:file, ~s|${contentFile}|}`);
|
||||
} else {
|
||||
dataArgs.push(` {${repr(multipartKey)}, ${repr(multipartValue)}}`);
|
||||
dataArgs.push(` {${repr(name)}, ${repr(content as string)}}`);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -82,12 +82,11 @@ function getFilesString(
|
||||
data: {},
|
||||
};
|
||||
|
||||
for (const [multipartKey, multipartValue] of request.multipartUploads) {
|
||||
if (multipartValue.startsWith("@")) {
|
||||
const fileName = multipartValue.slice(1);
|
||||
data.files[multipartKey] = fileName;
|
||||
for (const { name, content, contentFile } of request.multipartUploads) {
|
||||
if (contentFile) {
|
||||
data.files[name] = contentFile;
|
||||
} else {
|
||||
data.data[multipartKey] = multipartValue;
|
||||
data.data[name] = content as string;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -109,9 +109,17 @@ const prepareMultipartUploads = (request: Request): string | null => {
|
||||
let response = null;
|
||||
if (request.multipartUploads) {
|
||||
const params: [string, string][] = [];
|
||||
for (const [key, value] of request.multipartUploads) {
|
||||
const fileProvider = prepareDataProvider(value, null, "", 1);
|
||||
params.push([repr(key), fileProvider as string]); // TODO: can this be not a string?
|
||||
for (const { name, content, contentFile } of request.multipartUploads) {
|
||||
const value = contentFile ? "@" + contentFile : (content as string); // TODO: something nicer
|
||||
const fileProvider = prepareDataProvider(
|
||||
value,
|
||||
null,
|
||||
"",
|
||||
1,
|
||||
true,
|
||||
!contentFile
|
||||
);
|
||||
params.push([repr(name), fileProvider as string]); // TODO: can this be not a string?
|
||||
}
|
||||
response = callFunction("body", "MultipartFormProvider", params);
|
||||
}
|
||||
|
||||
@@ -63,20 +63,20 @@ export const _toPhp = (request: Request): string => {
|
||||
let requestDataCode = "";
|
||||
if (request.multipartUploads) {
|
||||
requestDataCode = "[\n";
|
||||
for (const [multipartKey, multipartValue] of request.multipartUploads) {
|
||||
if (multipartValue.charAt(0) === "@") {
|
||||
for (const { name, content, contentFile } of request.multipartUploads) {
|
||||
if (contentFile) {
|
||||
requestDataCode +=
|
||||
" '" +
|
||||
quote(multipartKey) +
|
||||
quote(name) +
|
||||
"' => new CURLFile('" +
|
||||
quote(multipartValue.substring(1)) +
|
||||
quote(contentFile) +
|
||||
"'),\n";
|
||||
} else {
|
||||
requestDataCode +=
|
||||
" '" +
|
||||
quote(multipartKey) +
|
||||
quote(name) +
|
||||
"' => '" +
|
||||
quote(multipartValue) +
|
||||
quote(content as string) +
|
||||
"',\n";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -183,15 +183,30 @@ function getFilesString(request: Request): string | undefined {
|
||||
return undefined;
|
||||
}
|
||||
const multipartUploads = request.multipartUploads.map((m) => {
|
||||
let multipartValue;
|
||||
if (m[1].startsWith("@")) {
|
||||
const fileName = m[1].slice(1);
|
||||
multipartValue =
|
||||
"(" + repr(fileName) + ", open(" + repr(fileName) + ", 'rb'))";
|
||||
} else {
|
||||
multipartValue = "(None, " + repr(m[1]) + ")";
|
||||
// https://github.com/psf/requests/blob/2d5517682b3b38547634d153cea43d48fbc8cdb5/requests/models.py#L117
|
||||
//
|
||||
// Requests's multipart syntax looks like this:
|
||||
// (name/filename, content)
|
||||
// (name, open(filename/contentFile))
|
||||
// (name, (filename, open(contentFile))
|
||||
// (name, (filename, open(contentFile), contentType, headers)) // this isn't parsed from --form yet
|
||||
const { filename, content, contentFile } = m;
|
||||
const name = m.name ? repr(m.name) : "None";
|
||||
const sentFilename = filename ? repr(filename) : "None";
|
||||
if (contentFile) {
|
||||
if (contentFile === filename) {
|
||||
return [name, "open(" + repr(contentFile) + ", 'rb')"];
|
||||
}
|
||||
return [
|
||||
name,
|
||||
"(" + sentFilename + ", open(" + repr(contentFile) + ", 'rb'))",
|
||||
];
|
||||
}
|
||||
return [m[0], multipartValue];
|
||||
// We should always either have .content or .contentFile
|
||||
if (filename && name === filename) {
|
||||
return [name, repr(content as string)];
|
||||
}
|
||||
return [name, "(" + sentFilename + ", " + repr(content as string) + ")"];
|
||||
});
|
||||
|
||||
const multipartUploadsAsDict = Object.fromEntries(multipartUploads);
|
||||
@@ -200,15 +215,13 @@ function getFilesString(request: Request): string | undefined {
|
||||
if (Object.keys(multipartUploadsAsDict).length === multipartUploads.length) {
|
||||
filesString += "{\n";
|
||||
for (const [multipartKey, multipartValue] of multipartUploads) {
|
||||
filesString +=
|
||||
" " + repr(multipartKey) + ": " + multipartValue + ",\n";
|
||||
filesString += " " + multipartKey + ": " + multipartValue + ",\n";
|
||||
}
|
||||
filesString += "}\n";
|
||||
} else {
|
||||
filesString += "[\n";
|
||||
for (const [multipartKey, multipartValue] of multipartUploads) {
|
||||
filesString +=
|
||||
" (" + repr(multipartKey) + ", " + multipartValue + "),\n";
|
||||
filesString += " (" + multipartKey + ", " + multipartValue + "),\n";
|
||||
}
|
||||
filesString += "]\n";
|
||||
}
|
||||
|
||||
@@ -53,16 +53,14 @@ function getFilesString(request: Request): string | undefined {
|
||||
let filesString = "files = list(\n";
|
||||
filesString += request.multipartUploads
|
||||
.map((m) => {
|
||||
const [multipartKey, multipartValue] = m;
|
||||
const { name, content, contentFile } = m;
|
||||
let fileParam;
|
||||
if (multipartValue.startsWith("@")) {
|
||||
const fileName = multipartValue.slice(1);
|
||||
if (contentFile) {
|
||||
// filesString += ' ' + reprn(multipartKey) + ' (' + repr(fileName) + ', upload_file(' + repr(fileName) + '))'
|
||||
fileParam =
|
||||
" " + reprn(multipartKey) + " = upload_file(" + repr(fileName) + ")";
|
||||
" " + reprn(name) + " = upload_file(" + repr(contentFile) + ")";
|
||||
} else {
|
||||
fileParam =
|
||||
" " + reprn(multipartKey) + " = " + repr(multipartValue) + "";
|
||||
fileParam = " " + reprn(name) + " = " + repr(content as string) + "";
|
||||
}
|
||||
return fileParam;
|
||||
})
|
||||
|
||||
@@ -49,16 +49,11 @@ export const _toRust = (request: Request) => {
|
||||
if (request.multipartUploads) {
|
||||
lines.push(indent("let form = multipart::Form::new()"));
|
||||
const parts = request.multipartUploads.map((m) => {
|
||||
const [partType, partValue] = m;
|
||||
switch (partType) {
|
||||
case "image":
|
||||
case "file": {
|
||||
const path = partValue.split("@")[1];
|
||||
return indent(`.file("${partType}", "${quote(path)}")?`, 2);
|
||||
}
|
||||
default:
|
||||
return indent(`.text("${partType}", "${quote(partValue)}")`, 2);
|
||||
const { name, content, contentFile } = m;
|
||||
if (contentFile) {
|
||||
return indent(`.file("${name}", "${quote(contentFile)}")?`, 2);
|
||||
}
|
||||
return indent(`.text("${name}", "${quote(content as string)}")`, 2);
|
||||
});
|
||||
parts[parts.length - 1] += ";";
|
||||
lines.push(...parts, "");
|
||||
|
||||
45
src/util.ts
45
src/util.ts
@@ -73,6 +73,8 @@ type Headers = Array<[string, string | null]>;
|
||||
type Cookie = [string, string];
|
||||
type Cookies = Array<Cookie>;
|
||||
|
||||
type FormParam = { value: string; type: "string" | "form" };
|
||||
|
||||
interface ParsedArguments {
|
||||
request?: string; // the HTTP method
|
||||
data?: string[];
|
||||
@@ -81,9 +83,20 @@ interface ParsedArguments {
|
||||
"data-raw"?: string[];
|
||||
"data-urlencode"?: string[];
|
||||
json?: string[];
|
||||
form?: FormParam[];
|
||||
[key: string]: any;
|
||||
}
|
||||
|
||||
function pushArgValue(obj: ParsedArguments, argName: string, value: string) {
|
||||
if (argName === "form-string") {
|
||||
return pushProp(obj, "form", { value, type: "string" });
|
||||
} else if (argName === "form") {
|
||||
return pushProp(obj, "form", { value, type: "form" });
|
||||
}
|
||||
// TODO: --data-*
|
||||
return pushProp(obj, argName, value);
|
||||
}
|
||||
|
||||
interface Request {
|
||||
url: string;
|
||||
urlWithoutQuery: string;
|
||||
@@ -93,7 +106,12 @@ interface Request {
|
||||
headers?: Headers;
|
||||
stdin?: string;
|
||||
input?: string;
|
||||
multipartUploads?: [string, string][];
|
||||
multipartUploads?: {
|
||||
name: string;
|
||||
filename?: string;
|
||||
content?: string;
|
||||
contentFile?: string;
|
||||
}[];
|
||||
auth?: [string, string];
|
||||
cookies?: Cookies;
|
||||
compressed?: boolean;
|
||||
@@ -1102,7 +1120,7 @@ const parseArgs = (args: string[], opts?: [LongOpts, ShortOpts]) => {
|
||||
if (longArg.type === "string") {
|
||||
if (i + 1 < args.length) {
|
||||
i++;
|
||||
pushProp(parsedArguments, longArg.name, args[i]);
|
||||
pushArgValue(parsedArguments, longArg.name, args[i]);
|
||||
} else {
|
||||
throw new CCError("option " + arg + ": requires parameter");
|
||||
}
|
||||
@@ -1159,7 +1177,7 @@ const parseArgs = (args: string[], opts?: [LongOpts, ShortOpts]) => {
|
||||
} else {
|
||||
throw new CCError("option " + argRepr + ": requires parameter");
|
||||
}
|
||||
pushProp(parsedArguments, longArg.name, val);
|
||||
pushArgValue(parsedArguments, longArg.name, val as string);
|
||||
} else {
|
||||
// Use shortFor because -N is short for --no-buffer
|
||||
// and we want to end up with {buffer: false}
|
||||
@@ -1168,7 +1186,7 @@ const parseArgs = (args: string[], opts?: [LongOpts, ShortOpts]) => {
|
||||
}
|
||||
}
|
||||
} else {
|
||||
pushProp(parsedArguments, "url", arg);
|
||||
pushArgValue(parsedArguments, "url", arg);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1471,11 +1489,22 @@ function buildRequest(parsedArguments: ParsedArguments): Request {
|
||||
} else if (parsedArguments.form) {
|
||||
request.multipartUploads = [];
|
||||
for (const multipartArgument of parsedArguments.form) {
|
||||
// -F is the most complicated option, we just assume it looks
|
||||
// like key=value and some generators handle value being @filepath
|
||||
// TODO: https://curl.se/docs/manpage.html#-F
|
||||
const [key, value] = multipartArgument.split(/=(.*)/s, 2);
|
||||
request.multipartUploads.push([key, value || ""]);
|
||||
// -F is the most complicated option, we only handle
|
||||
// name=value and name=@file and name=<file
|
||||
const [name, value] = multipartArgument.value.split(/=(.*)/s, 2);
|
||||
const isString = multipartArgument.type === "string";
|
||||
|
||||
let filename, content, contentFile;
|
||||
if (value.charAt(0) === "@" && !isString) {
|
||||
filename = value.slice(1);
|
||||
contentFile = filename;
|
||||
} else if (value.charAt(0) === "<" && !isString) {
|
||||
contentFile = value.slice(1);
|
||||
} else {
|
||||
content = value;
|
||||
}
|
||||
request.multipartUploads.push({ name, filename, content, contentFile });
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
2
test/fixtures/curl_commands/get_with_form.sh
vendored
2
test/fixtures/curl_commands/get_with_form.sh
vendored
@@ -1,6 +1,6 @@
|
||||
curl -s --user 'test' \
|
||||
http://localhost:28139/v3 \
|
||||
-F from='test@tester.com' \
|
||||
-F to='devs@tester.net' \
|
||||
--form-string to='devs@tester.net' \
|
||||
-F subject='Hello' \
|
||||
-F text='Testing the converter!'
|
||||
|
||||
2
test/fixtures/python/multipart_post.py
generated
vendored
2
test/fixtures/python/multipart_post.py
generated
vendored
@@ -6,7 +6,7 @@ headers = {
|
||||
|
||||
files = {
|
||||
'attributes': (None, '{"name":"tigers.jpeg", "parent":{"id":"11446498"}}'),
|
||||
'file': ('myfile.jpg', open('myfile.jpg', 'rb')),
|
||||
'file': open('myfile.jpg', 'rb'),
|
||||
}
|
||||
|
||||
response = requests.post('https://localhost:28139/api/2.0/files/content', headers=headers, files=files)
|
||||
|
||||
2
test/fixtures/python/post_image.py
generated
vendored
2
test/fixtures/python/post_image.py
generated
vendored
@@ -1,7 +1,7 @@
|
||||
import requests
|
||||
|
||||
files = {
|
||||
'image': ('image.jpg', open('image.jpg', 'rb')),
|
||||
'image': open('image.jpg', 'rb'),
|
||||
}
|
||||
|
||||
response = requests.post('http://localhost:28139/targetservice', files=files)
|
||||
|
||||
2
test/fixtures/python/post_with_extra_whitespace.py
generated
vendored
2
test/fixtures/python/post_with_extra_whitespace.py
generated
vendored
@@ -7,7 +7,7 @@ headers = {
|
||||
}
|
||||
|
||||
files = {
|
||||
'files': ('47.htz', open('47.htz', 'rb')),
|
||||
'files': open('47.htz', 'rb'),
|
||||
'name': (None, '47'),
|
||||
'oldMediaId': (None, '47'),
|
||||
'updateInLayouts': (None, '1'),
|
||||
|
||||
Reference in New Issue
Block a user