curlparser.ts 7.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236
  1. import * as URL from "url"
  2. import * as querystring from "querystring"
  3. import * as cookie from "cookie"
  4. import parser from "yargs-parser"
  5. /**
  6. * given this: [ 'msg1=value1', 'msg2=value2' ]
  7. * output this: 'msg1=value1&msg2=value2'
  8. * @param dataArguments
  9. */
  10. const joinDataArguments = (dataArguments: string[]) => {
  11. let data = ""
  12. dataArguments.forEach((argument, i) => {
  13. if (i === 0) {
  14. data += argument
  15. } else {
  16. data += `&${argument}`
  17. }
  18. })
  19. return data
  20. }
  21. const parseDataFromArguments = (parsedArguments: any) => {
  22. if (parsedArguments.data) {
  23. return {
  24. data: Array.isArray(parsedArguments.data)
  25. ? joinDataArguments(parsedArguments.data)
  26. : parsedArguments.data,
  27. dataArray: Array.isArray(parsedArguments.data)
  28. ? parsedArguments.data
  29. : null,
  30. isDataBinary: false,
  31. }
  32. } else if (parsedArguments["data-binary"]) {
  33. return {
  34. data: Array.isArray(parsedArguments["data-binary"])
  35. ? joinDataArguments(parsedArguments["data-binary"])
  36. : parsedArguments["data-binary"],
  37. dataArray: Array.isArray(parsedArguments["data-binary"])
  38. ? parsedArguments["data-binary"]
  39. : null,
  40. isDataBinary: true,
  41. }
  42. } else if (parsedArguments.d) {
  43. return {
  44. data: Array.isArray(parsedArguments.d)
  45. ? joinDataArguments(parsedArguments.d)
  46. : parsedArguments.d,
  47. dataArray: Array.isArray(parsedArguments.d) ? parsedArguments.d : null,
  48. isDataBinary: false,
  49. }
  50. } else if (parsedArguments["data-ascii"]) {
  51. return {
  52. data: Array.isArray(parsedArguments["data-ascii"])
  53. ? joinDataArguments(parsedArguments["data-ascii"])
  54. : parsedArguments["data-ascii"],
  55. dataArray: Array.isArray(parsedArguments["data-ascii"])
  56. ? parsedArguments["data-ascii"]
  57. : null,
  58. isDataBinary: false,
  59. }
  60. }
  61. }
  62. const parseCurlCommand = (curlCommand: string) => {
  63. const newlineFound = /\\/gi.test(curlCommand)
  64. if (newlineFound) {
  65. // remove '\' and newlines
  66. curlCommand = curlCommand.replace(/\\/gi, "")
  67. curlCommand = curlCommand.replace(/\n/g, "")
  68. }
  69. // yargs parses -XPOST as separate arguments. just prescreen for it.
  70. curlCommand = curlCommand.replace(/ -XPOST/, " -X POST")
  71. curlCommand = curlCommand.replace(/ -XGET/, " -X GET")
  72. curlCommand = curlCommand.replace(/ -XPUT/, " -X PUT")
  73. curlCommand = curlCommand.replace(/ -XPATCH/, " -X PATCH")
  74. curlCommand = curlCommand.replace(/ -XDELETE/, " -X DELETE")
  75. curlCommand = curlCommand.trim()
  76. const parsedArguments = parser(curlCommand)
  77. let cookieString
  78. let cookies
  79. let url = parsedArguments._[1]
  80. if (!url) {
  81. for (const argName in parsedArguments) {
  82. if (typeof parsedArguments[argName] === "string") {
  83. if (["http", "www."].includes(parsedArguments[argName])) {
  84. url = parsedArguments[argName]
  85. }
  86. }
  87. }
  88. }
  89. let headers: any
  90. const parseHeaders = (headerFieldName: string) => {
  91. if (parsedArguments[headerFieldName]) {
  92. if (!headers) {
  93. headers = {}
  94. }
  95. if (!Array.isArray(parsedArguments[headerFieldName])) {
  96. parsedArguments[headerFieldName] = [parsedArguments[headerFieldName]]
  97. }
  98. parsedArguments[headerFieldName].forEach((header: string) => {
  99. if (header.includes("Cookie")) {
  100. // stupid javascript tricks: closure
  101. cookieString = header
  102. } else {
  103. const colonIndex = header.indexOf(":")
  104. const headerName = header.substring(0, colonIndex)
  105. const headerValue = header.substring(colonIndex + 1).trim()
  106. headers[headerName] = headerValue
  107. }
  108. })
  109. }
  110. }
  111. parseHeaders("H")
  112. parseHeaders("header")
  113. if (parsedArguments.A) {
  114. if (!headers) {
  115. headers = []
  116. }
  117. headers["User-Agent"] = parsedArguments.A
  118. } else if (parsedArguments["user-agent"]) {
  119. if (!headers) {
  120. headers = []
  121. }
  122. headers["User-Agent"] = parsedArguments["user-agent"]
  123. }
  124. if (parsedArguments.b) {
  125. cookieString = parsedArguments.b
  126. }
  127. if (parsedArguments.cookie) {
  128. cookieString = parsedArguments.cookie
  129. }
  130. const multipartUploads: Record<string, string> = {}
  131. if (parsedArguments.F) {
  132. if (!Array.isArray(parsedArguments.F)) {
  133. parsedArguments.F = [parsedArguments.F]
  134. }
  135. parsedArguments.F.forEach((multipartArgument: string) => {
  136. // input looks like key=value. value could be json or a file path prepended with an @
  137. const [key, value] = multipartArgument.split("=", 2)
  138. multipartUploads[key] = value
  139. })
  140. }
  141. if (cookieString) {
  142. const cookieParseOptions = {
  143. decode: (s: any) => s,
  144. }
  145. // separate out cookie headers into separate data structure
  146. // note: cookie is case insensitive
  147. cookies = cookie.parse(
  148. cookieString.replace(/^Cookie: /gi, ""),
  149. cookieParseOptions
  150. )
  151. }
  152. let method
  153. if (parsedArguments.X === "POST") {
  154. method = "post"
  155. } else if (parsedArguments.X === "PUT" || parsedArguments.T) {
  156. method = "put"
  157. } else if (parsedArguments.X === "PATCH") {
  158. method = "patch"
  159. } else if (parsedArguments.X === "DELETE") {
  160. method = "delete"
  161. } else if (parsedArguments.X === "OPTIONS") {
  162. method = "options"
  163. } else if (
  164. (parsedArguments.d ||
  165. parsedArguments.data ||
  166. parsedArguments["data-ascii"] ||
  167. parsedArguments["data-binary"] ||
  168. parsedArguments.F ||
  169. parsedArguments.form) &&
  170. !(parsedArguments.G || parsedArguments.get)
  171. ) {
  172. method = "post"
  173. } else if (parsedArguments.I || parsedArguments.head) {
  174. method = "head"
  175. } else {
  176. method = "get"
  177. }
  178. const compressed = !!parsedArguments.compressed
  179. let urlObject = URL.parse(url) // eslint-disable-line
  180. // if GET request with data, convert data to query string
  181. // NB: the -G flag does not change the http verb. It just moves the data into the url.
  182. if (parsedArguments.G || parsedArguments.get) {
  183. urlObject.query = urlObject.query ? urlObject.query : ""
  184. const option =
  185. "d" in parsedArguments ? "d" : "data" in parsedArguments ? "data" : null
  186. if (option) {
  187. let urlQueryString = ""
  188. if (!url.includes("?")) {
  189. url += "?"
  190. } else {
  191. urlQueryString += "&"
  192. }
  193. if (typeof parsedArguments[option] === "object") {
  194. urlQueryString += parsedArguments[option].join("&")
  195. } else {
  196. urlQueryString += parsedArguments[option]
  197. }
  198. urlObject.query += urlQueryString
  199. url += urlQueryString
  200. delete parsedArguments[option]
  201. }
  202. }
  203. const query = querystring.parse(urlObject.query!, null as any, null as any, {
  204. maxKeys: 10000,
  205. })
  206. urlObject.search = null // Clean out the search/query portion.
  207. const request = {
  208. url,
  209. urlWithoutQuery: URL.format(urlObject),
  210. compressed,
  211. query,
  212. headers,
  213. method,
  214. cookies,
  215. cookieString: cookieString?.replace("Cookie: ", ""),
  216. multipartUploads,
  217. ...parseDataFromArguments(parsedArguments),
  218. auth: parsedArguments.u,
  219. user: parsedArguments.user,
  220. }
  221. return request
  222. }
  223. export default parseCurlCommand