UNPKG

curlconverter

Version:

convert curl commands to Python, JavaScript, Go, PHP and more

361 lines (326 loc) 10.4 kB
import { Word, eq } from "../../shell/Word.js"; import { parse, getFirst, COMMON_SUPPORTED_ARGS } from "../../parse.js"; import type { Request, Warnings } from "../../parse.js"; import { parseQueryString } from "../../Query.js"; import type { Query } from "../../Query.js"; import type { FormParam } from "../../curl/form.js"; import { repr, reprObj, asParseFloatTimes1000, type JSImports, addImport, reprImports, } from "./javascript.js"; export const supportedArgs = new Set([ ...COMMON_SUPPORTED_ARGS, "form", "form-string", "max-time", ]); export function dedent(s: string): string { return s.replace(/^ {2}/gm, ""); } export function indent(s: string, indent = 1): string { const indentation = " ".repeat(indent); return s.split("\n").join("\n" + indentation); } export function commentOut(s: string, indent = 0): string { const indentation = " ".repeat(indent); return s.split("\n").join("\n" + indentation + "// "); } export function serializeQuery( query: Query, imports: JSImports, ): [string, boolean] { const [queryList, queryDict] = query; let code = ""; let traditional = false; if (queryDict) { code += "{\n"; for (const [key, value] of queryDict) { code += " " + repr(key, imports) + ": "; if (Array.isArray(value)) { code += "[" + value.map((v) => repr(v, imports)).join(", ") + "]"; traditional = true; } else { code += repr(value, imports); } code += ",\n"; } if (code.endsWith(",\n")) { code = code.slice(0, -2); } code += "\n}"; } else if (queryList) { code += "[\n"; for (const [key, value] of queryList) { code += " { name: " + repr(key, imports) + ", value: " + repr(value, imports) + " },\n"; } if (code.endsWith(",\n")) { code = code.slice(0, -2); } code += "\n]"; } else { // shouldn't happen return ["null", false]; } return [code, traditional]; } // TODO: @ function _getDataString( data: Word, contentType: string | null | undefined, exactContentType: Word | null | undefined, imports: JSImports, ): [Word | null | undefined, string, string | null, boolean] { let traditional = false; const originalStringRepr = repr(data, imports); if (contentType === "application/json" && data.isString()) { const dataStr = data.toString(); const parsed = JSON.parse(dataStr); // Only convert arrays and {} to JavaScript objects if (typeof parsed !== "object" || parsed === null) { return [exactContentType, originalStringRepr, null, traditional]; } const roundtrips = JSON.stringify(parsed) === dataStr; const jsonAsJavaScript = "JSON.stringify(" + reprObj(parsed, 1) + ")"; return [ exactContentType, jsonAsJavaScript, roundtrips ? null : originalStringRepr, traditional, ]; } if (contentType === "application/x-www-form-urlencoded") { const [queryList, queryDict] = parseQueryString(data); if (queryList) { if ( eq(exactContentType, "application/x-www-form-urlencoded; charset=utf-8") ) { exactContentType = null; } let queryObj; [queryObj, traditional] = serializeQuery([queryList, queryDict], imports); // TODO: check roundtrip, add a comment return [exactContentType, indent(queryObj), null, traditional]; } } return [exactContentType, originalStringRepr, null, traditional]; } export function getDataString( data: Word, contentType: string | null | undefined, exactContentType: Word | null | undefined, imports: JSImports, ): [Word | null | undefined, string, string | null, boolean] { let dataString: string | null = null; let commentedOutDataString: string | null = null; let traditional = false; try { [exactContentType, dataString, commentedOutDataString, traditional] = _getDataString(data, contentType, exactContentType, imports); } catch {} if (!dataString) { dataString = repr(data, imports); } return [exactContentType, dataString, commentedOutDataString, traditional]; } export function getFormString( multipartUploads: FormParam[], imports: JSImports, ): string { let code = "const form = new FormData();\n"; for (const m of multipartUploads) { code += "form.append(" + repr(m.name, imports) + ", "; if ("contentFile" in m) { // TODO: no fs in browser addImport(imports, "* as fs", "fs"); if (eq(m.contentFile, "-")) { code += "fs.readFileSync(0).toString()"; } else { code += "fs.readFileSync(" + repr(m.contentFile, imports) + ")"; } if ("filename" in m && m.filename) { code += ", " + repr(m.filename, imports); } } else { code += repr(m.content, imports); } code += ");\n"; } code += "\n"; return code; } export function _toJavaScriptJquery( requests: Request[], warnings: Warnings = [], ): string { const request = getFirst(requests, warnings); const imports: JSImports = []; let code = ""; // data: passed with these methods will be added to the URL instead const nonDataMethods = ["GET", "HEAD"]; const method = request.urls[0].method; const methodStr = method.toString(); if (!eq(request.urls[0].method.toUpperCase(), method)) { warnings.push([ "method-case", "jQuery uppercases the method, so it will be changed to " + JSON.stringify(method.toUpperCase().toString()), ]); } const contentType = request.headers.getContentType(); let exactContentType = request.headers.get("content-type"); request.headers.delete("content-type"); let url = request.urls[0].url; let dataString: string | null = null; let commentedOutDataString: string | null = null; let traditional = false; if (request.multipartUploads) { code += getFormString(request.multipartUploads, imports); dataString = "form"; warnings.push([ "multipart-form", // TODO: remove this when jQuery supports FormData "jQuery doesn't support sending FormData yet", ]); } else if (request.data) { // can delete content-type header by returning null for exactContentType [exactContentType, dataString, commentedOutDataString, traditional] = getDataString(request.data, contentType, exactContentType, imports); if (commentedOutDataString) { commentedOutDataString = " // data: " + commentedOutDataString + ",\n"; } } if (nonDataMethods.includes(methodStr)) { if (request.urls[0].queryList) { if (dataString) { warnings.push([ "data-with-get", "jQuery doesn't allow sending data in the body with GET or HEAD requests", ]); if (commentedOutDataString) { commentedOutDataString += " // data: " + indent(commentOut(dedent(dataString))) + ",\n"; } else { commentedOutDataString = " // data: " + indent(commentOut(dedent(dataString))) + ",\n"; } } [dataString, traditional] = serializeQuery( [request.urls[0].queryList, request.urls[0].queryDict ?? null], imports, ); dataString = indent(dataString); url = request.urls[0].urlWithoutQueryList; } else if (request.data || request.multipartUploads) { warnings.push([ "data-with-get", "jQuery doesn't allow sending data with GET or HEAD requests. The data will be sent in the URL instead", ]); } } const needsConfig = !!( request.headers.length || request.urls[0].auth || ((request.multipartUploads || request.data) && nonDataMethods.includes(methodStr)) || request.timeout || (exactContentType !== null && exactContentType !== undefined) || commentedOutDataString || traditional ); let done = ""; done += ".done(function(response) {\n"; done += " console.log(response);\n"; done += "});"; if ( !needsConfig && method.isString() && ["GET", "POST"].includes(methodStr) ) { // TODO: .getJSON() const fn = methodStr === "GET" ? "get" : "post"; code += "$." + fn + "("; code += repr(url, imports); if (dataString) { code += ", " + dedent(dataString); } code += ")\n"; code += " " + indent(done) + "\n"; } else { code += "$.ajax({\n"; code += " url: " + repr(url, imports) + ",\n"; code += " crossDomain: true,\n"; if (methodStr !== "GET") { // jQuery uppercases methods const sentMethod = eq(request.urls[0].method.toUpperCase(), method) ? method.toLowerCase() : method; code += " method: " + repr(sentMethod, imports) + ",\n"; } if (request.headers.length) { code += " headers: {\n"; for (const [key, value] of request.headers) { if (value === null) { continue; } code += " " + repr(key, imports) + ": " + repr(value, imports) + ",\n"; } if (code.endsWith(",\n")) { code = code.slice(0, -2); code += "\n"; } code += " },\n"; } if (exactContentType) { code += " contentType: " + repr(exactContentType, imports) + ",\n"; } if (request.urls[0].auth) { const [username, password] = request.urls[0].auth; code += " username: " + repr(username, imports) + ",\n"; code += " password: " + repr(password, imports) + ",\n"; } if (commentedOutDataString) { code += commentedOutDataString; } if (dataString) { code += " data: " + dataString + ",\n"; } if (traditional) { code += " traditional: true,\n"; } if (request.timeout) { code += " timeout: " + asParseFloatTimes1000(request.timeout, imports) + ",\n"; } if (code.endsWith(",\n")) { code = code.slice(0, -2); code += "\n"; } code += "})" + done + "\n"; } let importCode = reprImports(imports); if (importCode) { importCode += "\n"; } return importCode + code; } export function toJavaScriptJqueryWarn( curlCommand: string | string[], warnings: Warnings = [], ): [string, Warnings] { const requests = parse(curlCommand, supportedArgs, warnings); const jquery = _toJavaScriptJquery(requests, warnings); return [jquery, warnings]; } export function toJavaScriptJquery(curlCommand: string | string[]): string { return toJavaScriptJqueryWarn(curlCommand)[0]; }