handle --form more correctly (#385)

This commit is contained in:
Boris Verkhovskiy
2022-04-14 18:26:13 -07:00
committed by GitHub
parent 733f110e56
commit 5c2f1ab054
13 changed files with 105 additions and 64 deletions

View File

@@ -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';
}
}

View File

@@ -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)}}`);
}
}

View File

@@ -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;
}
}

View File

@@ -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);
}

View File

@@ -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";
}
}

View File

@@ -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";
}

View File

@@ -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;
})

View File

@@ -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, "");

View File

@@ -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 });
}
}

View File

@@ -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!'

View File

@@ -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
View File

@@ -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)

View File

@@ -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'),