@ohos-ports/curlconverter 4.12.0-beta.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (270) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +153 -0
  3. package/dist/src/Headers.d.ts +23 -0
  4. package/dist/src/Headers.js +287 -0
  5. package/dist/src/Headers.js.map +1 -0
  6. package/dist/src/Query.d.ts +8 -0
  7. package/dist/src/Query.js +136 -0
  8. package/dist/src/Query.js.map +1 -0
  9. package/dist/src/Request.d.ts +200 -0
  10. package/dist/src/Request.js +1191 -0
  11. package/dist/src/Request.js.map +1 -0
  12. package/dist/src/Warnings.d.ts +15 -0
  13. package/dist/src/Warnings.js +111 -0
  14. package/dist/src/Warnings.js.map +1 -0
  15. package/dist/src/cli.d.ts +2 -0
  16. package/dist/src/cli.js +333 -0
  17. package/dist/src/cli.js.map +1 -0
  18. package/dist/src/curl/auth.d.ts +12 -0
  19. package/dist/src/curl/auth.js +40 -0
  20. package/dist/src/curl/auth.js.map +1 -0
  21. package/dist/src/curl/form.d.ts +28 -0
  22. package/dist/src/curl/form.js +218 -0
  23. package/dist/src/curl/form.js.map +1 -0
  24. package/dist/src/curl/opts.d.ts +2052 -0
  25. package/dist/src/curl/opts.js +1000 -0
  26. package/dist/src/curl/opts.js.map +1 -0
  27. package/dist/src/curl/url.d.ts +14 -0
  28. package/dist/src/curl/url.js +218 -0
  29. package/dist/src/curl/url.js.map +1 -0
  30. package/dist/src/generators/ansible.d.ts +5 -0
  31. package/dist/src/generators/ansible.js +282 -0
  32. package/dist/src/generators/ansible.js.map +1 -0
  33. package/dist/src/generators/c.d.ts +12 -0
  34. package/dist/src/generators/c.js +1230 -0
  35. package/dist/src/generators/c.js.map +1 -0
  36. package/dist/src/generators/cfml.d.ts +5 -0
  37. package/dist/src/generators/cfml.js +134 -0
  38. package/dist/src/generators/cfml.js.map +1 -0
  39. package/dist/src/generators/clojure.d.ts +7 -0
  40. package/dist/src/generators/clojure.js +436 -0
  41. package/dist/src/generators/clojure.js.map +1 -0
  42. package/dist/src/generators/csharp.d.ts +8 -0
  43. package/dist/src/generators/csharp.js +356 -0
  44. package/dist/src/generators/csharp.js.map +1 -0
  45. package/dist/src/generators/dart.d.ts +5 -0
  46. package/dist/src/generators/dart.js +273 -0
  47. package/dist/src/generators/dart.js.map +1 -0
  48. package/dist/src/generators/elixir.d.ts +8 -0
  49. package/dist/src/generators/elixir.js +296 -0
  50. package/dist/src/generators/elixir.js.map +1 -0
  51. package/dist/src/generators/go.d.ts +5 -0
  52. package/dist/src/generators/go.js +228 -0
  53. package/dist/src/generators/go.js.map +1 -0
  54. package/dist/src/generators/har.d.ts +7 -0
  55. package/dist/src/generators/har.js +115 -0
  56. package/dist/src/generators/har.js.map +1 -0
  57. package/dist/src/generators/http.d.ts +7 -0
  58. package/dist/src/generators/http.js +165 -0
  59. package/dist/src/generators/http.js.map +1 -0
  60. package/dist/src/generators/httpie.d.ts +5 -0
  61. package/dist/src/generators/httpie.js +446 -0
  62. package/dist/src/generators/httpie.js.map +1 -0
  63. package/dist/src/generators/java/httpurlconnection.d.ts +5 -0
  64. package/dist/src/generators/java/httpurlconnection.js +123 -0
  65. package/dist/src/generators/java/httpurlconnection.js.map +1 -0
  66. package/dist/src/generators/java/java.d.ts +8 -0
  67. package/dist/src/generators/java/java.js +247 -0
  68. package/dist/src/generators/java/java.js.map +1 -0
  69. package/dist/src/generators/java/jsoup.d.ts +5 -0
  70. package/dist/src/generators/java/jsoup.js +152 -0
  71. package/dist/src/generators/java/jsoup.js.map +1 -0
  72. package/dist/src/generators/java/okhttp.d.ts +5 -0
  73. package/dist/src/generators/java/okhttp.js +232 -0
  74. package/dist/src/generators/java/okhttp.js.map +1 -0
  75. package/dist/src/generators/javascript/axios.d.ts +5 -0
  76. package/dist/src/generators/javascript/axios.js +332 -0
  77. package/dist/src/generators/javascript/axios.js.map +1 -0
  78. package/dist/src/generators/javascript/got.d.ts +5 -0
  79. package/dist/src/generators/javascript/got.js +277 -0
  80. package/dist/src/generators/javascript/got.js.map +1 -0
  81. package/dist/src/generators/javascript/http.d.ts +8 -0
  82. package/dist/src/generators/javascript/http.js +183 -0
  83. package/dist/src/generators/javascript/http.js.map +1 -0
  84. package/dist/src/generators/javascript/javascript.d.ts +37 -0
  85. package/dist/src/generators/javascript/javascript.js +676 -0
  86. package/dist/src/generators/javascript/javascript.js.map +1 -0
  87. package/dist/src/generators/javascript/jquery.d.ts +15 -0
  88. package/dist/src/generators/javascript/jquery.js +297 -0
  89. package/dist/src/generators/javascript/jquery.js.map +1 -0
  90. package/dist/src/generators/javascript/ky.d.ts +7 -0
  91. package/dist/src/generators/javascript/ky.js +245 -0
  92. package/dist/src/generators/javascript/ky.js.map +1 -0
  93. package/dist/src/generators/javascript/request.d.ts +5 -0
  94. package/dist/src/generators/javascript/request.js +113 -0
  95. package/dist/src/generators/javascript/request.js.map +1 -0
  96. package/dist/src/generators/javascript/superagent.d.ts +10 -0
  97. package/dist/src/generators/javascript/superagent.js +301 -0
  98. package/dist/src/generators/javascript/superagent.js.map +1 -0
  99. package/dist/src/generators/javascript/xhr.d.ts +8 -0
  100. package/dist/src/generators/javascript/xhr.js +147 -0
  101. package/dist/src/generators/javascript/xhr.js.map +1 -0
  102. package/dist/src/generators/json.d.ts +45 -0
  103. package/dist/src/generators/json.js +197 -0
  104. package/dist/src/generators/json.js.map +1 -0
  105. package/dist/src/generators/julia.d.ts +8 -0
  106. package/dist/src/generators/julia.js +365 -0
  107. package/dist/src/generators/julia.js.map +1 -0
  108. package/dist/src/generators/kotlin.d.ts +8 -0
  109. package/dist/src/generators/kotlin.js +325 -0
  110. package/dist/src/generators/kotlin.js.map +1 -0
  111. package/dist/src/generators/lua.d.ts +5 -0
  112. package/dist/src/generators/lua.js +192 -0
  113. package/dist/src/generators/lua.js.map +1 -0
  114. package/dist/src/generators/matlab/common.d.ts +16 -0
  115. package/dist/src/generators/matlab/common.js +238 -0
  116. package/dist/src/generators/matlab/common.js.map +1 -0
  117. package/dist/src/generators/matlab/httpinterface.d.ts +2 -0
  118. package/dist/src/generators/matlab/httpinterface.js +211 -0
  119. package/dist/src/generators/matlab/httpinterface.js.map +1 -0
  120. package/dist/src/generators/matlab/matlab.d.ts +5 -0
  121. package/dist/src/generators/matlab/matlab.js +29 -0
  122. package/dist/src/generators/matlab/matlab.js.map +1 -0
  123. package/dist/src/generators/matlab/webservices.d.ts +2 -0
  124. package/dist/src/generators/matlab/webservices.js +191 -0
  125. package/dist/src/generators/matlab/webservices.js.map +1 -0
  126. package/dist/src/generators/objectivec.d.ts +8 -0
  127. package/dist/src/generators/objectivec.js +269 -0
  128. package/dist/src/generators/objectivec.js.map +1 -0
  129. package/dist/src/generators/ocaml.d.ts +8 -0
  130. package/dist/src/generators/ocaml.js +150 -0
  131. package/dist/src/generators/ocaml.js.map +1 -0
  132. package/dist/src/generators/perl.d.ts +9 -0
  133. package/dist/src/generators/perl.js +209 -0
  134. package/dist/src/generators/perl.js.map +1 -0
  135. package/dist/src/generators/php/guzzle.d.ts +5 -0
  136. package/dist/src/generators/php/guzzle.js +328 -0
  137. package/dist/src/generators/php/guzzle.js.map +1 -0
  138. package/dist/src/generators/php/php.d.ts +8 -0
  139. package/dist/src/generators/php/php.js +217 -0
  140. package/dist/src/generators/php/php.js.map +1 -0
  141. package/dist/src/generators/php/requests.d.ts +5 -0
  142. package/dist/src/generators/php/requests.js +89 -0
  143. package/dist/src/generators/php/requests.js.map +1 -0
  144. package/dist/src/generators/powershell.d.ts +11 -0
  145. package/dist/src/generators/powershell.js +377 -0
  146. package/dist/src/generators/powershell.js.map +1 -0
  147. package/dist/src/generators/python/http.d.ts +5 -0
  148. package/dist/src/generators/python/http.js +128 -0
  149. package/dist/src/generators/python/http.js.map +1 -0
  150. package/dist/src/generators/python/python.d.ts +22 -0
  151. package/dist/src/generators/python/python.js +1693 -0
  152. package/dist/src/generators/python/python.js.map +1 -0
  153. package/dist/src/generators/r/httr.d.ts +10 -0
  154. package/dist/src/generators/r/httr.js +298 -0
  155. package/dist/src/generators/r/httr.js.map +1 -0
  156. package/dist/src/generators/r/httr2.d.ts +5 -0
  157. package/dist/src/generators/r/httr2.js +214 -0
  158. package/dist/src/generators/r/httr2.js.map +1 -0
  159. package/dist/src/generators/ruby/httparty.d.ts +6 -0
  160. package/dist/src/generators/ruby/httparty.js +308 -0
  161. package/dist/src/generators/ruby/httparty.js.map +1 -0
  162. package/dist/src/generators/ruby/ruby.d.ts +14 -0
  163. package/dist/src/generators/ruby/ruby.js +515 -0
  164. package/dist/src/generators/ruby/ruby.js.map +1 -0
  165. package/dist/src/generators/rust.d.ts +8 -0
  166. package/dist/src/generators/rust.js +177 -0
  167. package/dist/src/generators/rust.js.map +1 -0
  168. package/dist/src/generators/swift.d.ts +8 -0
  169. package/dist/src/generators/swift.js +219 -0
  170. package/dist/src/generators/swift.js.map +1 -0
  171. package/dist/src/generators/wget.d.ts +8 -0
  172. package/dist/src/generators/wget.js +472 -0
  173. package/dist/src/generators/wget.js.map +1 -0
  174. package/dist/src/index.d.ts +53 -0
  175. package/dist/src/index.js +54 -0
  176. package/dist/src/index.js.map +1 -0
  177. package/dist/src/parse.d.ts +13 -0
  178. package/dist/src/parse.js +43 -0
  179. package/dist/src/parse.js.map +1 -0
  180. package/dist/src/shell/Parser.d.ts +4 -0
  181. package/dist/src/shell/Parser.js +6 -0
  182. package/dist/src/shell/Parser.js.map +1 -0
  183. package/dist/src/shell/Word.d.ts +48 -0
  184. package/dist/src/shell/Word.js +486 -0
  185. package/dist/src/shell/Word.js.map +1 -0
  186. package/dist/src/shell/tokenizer.d.ts +3 -0
  187. package/dist/src/shell/tokenizer.js +610 -0
  188. package/dist/src/shell/tokenizer.js.map +1 -0
  189. package/dist/src/shell/webParser.d.ts +4 -0
  190. package/dist/src/shell/webParser.js +15 -0
  191. package/dist/src/shell/webParser.js.map +1 -0
  192. package/dist/src/utils.d.ts +6 -0
  193. package/dist/src/utils.js +21 -0
  194. package/dist/src/utils.js.map +1 -0
  195. package/dist/tools/compare-requests.d.ts +2 -0
  196. package/dist/tools/compare-requests.js +448 -0
  197. package/dist/tools/compare-requests.js.map +1 -0
  198. package/dist/tools/gen-test.d.ts +2 -0
  199. package/dist/tools/gen-test.js +114 -0
  200. package/dist/tools/gen-test.js.map +1 -0
  201. package/dist/tree-sitter-bash.wasm +0 -0
  202. package/index.d.ts +3 -0
  203. package/package.json +97 -0
  204. package/src/Headers.ts +334 -0
  205. package/src/Query.ts +147 -0
  206. package/src/Request.ts +1649 -0
  207. package/src/Warnings.ts +154 -0
  208. package/src/cli.ts +561 -0
  209. package/src/curl/auth.ts +45 -0
  210. package/src/curl/form.ts +297 -0
  211. package/src/curl/opts.ts +1417 -0
  212. package/src/curl/url.ts +241 -0
  213. package/src/generators/ansible.ts +364 -0
  214. package/src/generators/c.ts +1371 -0
  215. package/src/generators/cfml.ts +155 -0
  216. package/src/generators/clojure.ts +521 -0
  217. package/src/generators/csharp.ts +383 -0
  218. package/src/generators/dart.ts +293 -0
  219. package/src/generators/elixir.ts +349 -0
  220. package/src/generators/go.ts +269 -0
  221. package/src/generators/har.ts +147 -0
  222. package/src/generators/http.ts +194 -0
  223. package/src/generators/httpie.ts +506 -0
  224. package/src/generators/java/httpurlconnection.ts +151 -0
  225. package/src/generators/java/java.ts +280 -0
  226. package/src/generators/java/jsoup.ts +172 -0
  227. package/src/generators/java/okhttp.ts +257 -0
  228. package/src/generators/javascript/axios.ts +395 -0
  229. package/src/generators/javascript/got.ts +362 -0
  230. package/src/generators/javascript/http.ts +232 -0
  231. package/src/generators/javascript/javascript.ts +825 -0
  232. package/src/generators/javascript/jquery.ts +360 -0
  233. package/src/generators/javascript/ky.ts +315 -0
  234. package/src/generators/javascript/request.ts +152 -0
  235. package/src/generators/javascript/superagent.ts +373 -0
  236. package/src/generators/javascript/xhr.ts +192 -0
  237. package/src/generators/json.ts +293 -0
  238. package/src/generators/julia.ts +398 -0
  239. package/src/generators/kotlin.ts +355 -0
  240. package/src/generators/lua.ts +209 -0
  241. package/src/generators/matlab/common.ts +281 -0
  242. package/src/generators/matlab/httpinterface.ts +284 -0
  243. package/src/generators/matlab/matlab.ts +39 -0
  244. package/src/generators/matlab/webservices.ts +263 -0
  245. package/src/generators/objectivec.ts +297 -0
  246. package/src/generators/ocaml.ts +168 -0
  247. package/src/generators/perl.ts +237 -0
  248. package/src/generators/php/guzzle.ts +368 -0
  249. package/src/generators/php/php.ts +239 -0
  250. package/src/generators/php/requests.ts +103 -0
  251. package/src/generators/powershell.ts +430 -0
  252. package/src/generators/python/http.ts +167 -0
  253. package/src/generators/python/python.ts +1934 -0
  254. package/src/generators/r/httr.ts +328 -0
  255. package/src/generators/r/httr2.ts +268 -0
  256. package/src/generators/ruby/httparty.ts +351 -0
  257. package/src/generators/ruby/ruby.ts +564 -0
  258. package/src/generators/rust.ts +243 -0
  259. package/src/generators/swift.ts +235 -0
  260. package/src/generators/wget.ts +533 -0
  261. package/src/index.ts +89 -0
  262. package/src/parse.ts +72 -0
  263. package/src/shell/Parser.ts +8 -0
  264. package/src/shell/Word.ts +531 -0
  265. package/src/shell/tokenizer.ts +736 -0
  266. package/src/shell/webParser.ts +18 -0
  267. package/src/utils.ts +25 -0
  268. package/tools/compare-requests.ts +571 -0
  269. package/tools/extract_curl_args.py +511 -0
  270. package/tools/gen-test.ts +129 -0
package/package.json ADDED
@@ -0,0 +1,97 @@
1
+ {
2
+ "name": "@ohos-ports/curlconverter",
3
+ "version": "4.12.0-beta.0",
4
+ "description": "convert curl commands to Python, JavaScript, Go, PHP and more",
5
+ "homepage": "https://curlconverter.com",
6
+ "author": {
7
+ "name": "Nick Carneiro",
8
+ "email": "nickc@trillworks.com",
9
+ "url": "https://trillworks.com"
10
+ },
11
+ "repository": {
12
+ "type": "git",
13
+ "url": "https://github.com/ohos-ports/ohos-ports.git",
14
+ "directory": "ports/curlconverter/4.12.0"
15
+ },
16
+ "license": "MIT",
17
+ "keywords": [
18
+ "curl",
19
+ "http",
20
+ "requests",
21
+ "python",
22
+ "converter"
23
+ ],
24
+ "main": "dist/src/index.js",
25
+ "types": "dist/src/index.d.ts",
26
+ "type": "module",
27
+ "dependencies": {
28
+ "jsesc": "^3.0.2",
29
+ "lossless-json": "^4.0.2",
30
+ "@ohos-ports/tree-sitter": "^0.25.2-beta.0",
31
+ "web-tree-sitter": "^0.24.3",
32
+ "yamljs": "^0.3.0",
33
+ "@ohos-ports/tree-sitter-bash": "^0.23.3-beta.0"
34
+ },
35
+ "devDependencies": {
36
+ "@eslint/eslintrc": "^3.1.0",
37
+ "@eslint/js": "^9.12.0",
38
+ "@types/diff": "^5.2.3",
39
+ "@types/har-format": "^1.2.16",
40
+ "@types/jsesc": "^3.0.3",
41
+ "@types/nunjucks": "^3.2.6",
42
+ "@types/tape": "^5.6.4",
43
+ "@types/yamljs": "^0.2.34",
44
+ "@types/yargs": "^17.0.33",
45
+ "@typescript-eslint/eslint-plugin": "^8.9.0",
46
+ "@typescript-eslint/parser": "^8.9.0",
47
+ "c8": "^10.1.2",
48
+ "colors": "^1.4.0",
49
+ "cross-env": "^7.0.3",
50
+ "diff": "^7.0.0",
51
+ "eslint": "^9.12.0",
52
+ "eslint-config-prettier": "^9.1.0",
53
+ "glob": "^11.0.0",
54
+ "husky": "^9.1.6",
55
+ "lint-staged": "^15.2.10",
56
+ "prettier": "^3.3.3",
57
+ "tape": "^5.9.0",
58
+ "@ohos-ports/tree-sitter-cli": "^0.27.0-beta.0",
59
+ "typescript": "^5.6.3",
60
+ "yargs": "^17.7.2"
61
+ },
62
+ "scripts": {
63
+ "compile": "tsc",
64
+ "start": "tsc --watch --preserveWatchOutput -p ./",
65
+ "pretest": "cross-env TSC_COMPILE_ON_ERROR=true npm run compile || exit 0",
66
+ "test": "cross-env NODE_OPTIONS=\"--enable-source-maps --unhandled-rejections=strict\" npx tape dist/test/test.js",
67
+ "coverage": "c8 --check-coverage npm run test",
68
+ "prelint": "npm run compile",
69
+ "lint": "eslint src",
70
+ "format": "prettier --write .",
71
+ "prefix": "npm run compile",
72
+ "fix": "eslint src --fix",
73
+ "prepare": "npx husky && npm run compile && npx tree-sitter build --wasm node_modules/tree-sitter-bash --output dist/tree-sitter-bash.wasm",
74
+ "pregen-test": "npm run compile",
75
+ "gen-test": "node --enable-source-maps ./dist/tools/gen-test.js",
76
+ "compare-requests": "npm run compile && node --enable-source-maps ./dist/tools/compare-requests.js",
77
+ "compare-request": "npm run compile && node --enable-source-maps ./dist/tools/compare-requests.js"
78
+ },
79
+ "lint-staged": {
80
+ "**/*": "prettier --write --ignore-unknown"
81
+ },
82
+ "bin": "dist/src/cli.js",
83
+ "browser": {
84
+ "./dist/src/shell/Parser.js": "./dist/src/shell/webParser.js"
85
+ },
86
+ "bugs": {
87
+ "url": "https://github.com/ohos-ports/ohos-ports/issues"
88
+ },
89
+ "files": [
90
+ "dist",
91
+ "src",
92
+ "tools",
93
+ "index.d.ts",
94
+ "README.md",
95
+ "LICENSE"
96
+ ]
97
+ }
package/src/Headers.ts ADDED
@@ -0,0 +1,334 @@
1
+ import { Word, eq, joinWords } from "./shell/Word.js";
2
+ import type { Warnings } from "./Warnings.js";
3
+
4
+ // https://en.wikipedia.org/wiki/List_of_HTTP_header_fields#Standard_request_fields
5
+ // and then searched for "#" in the RFCs that define each header
6
+ const COMMA_SEPARATED = new Set(
7
+ [
8
+ "A-IM",
9
+ "Accept",
10
+ "Accept-Charset",
11
+ // "Accept-Datetime",
12
+ "Accept-Encoding",
13
+ "Accept-Language",
14
+ // "Access-Control-Request-Method",
15
+ "Access-Control-Request-Headers",
16
+ // TODO: auth-scheme [ 1*SP ( token68 / #auth-param ) ]
17
+ // "Authorization",
18
+ "Cache-Control",
19
+ "Connection",
20
+ "Content-Encoding",
21
+ // "Content-Length",
22
+ // "Content-MD5",
23
+ // "Content-Type", // semicolon
24
+ // "Cookie", // semicolon
25
+ // "Date",
26
+ "Expect",
27
+ "Forwarded",
28
+ // "From",
29
+ // "Host",
30
+ // "HTTP2-Settings",
31
+ "If-Match",
32
+ // "If-Modified-Since",
33
+ "If-None-Match",
34
+ // "If-Range",
35
+ // "If-Unmodified-Since",
36
+ // "Max-Forwards",
37
+ // "Origin",
38
+ // "Pragma",
39
+ // "Prefer", // semicolon
40
+ // "Proxy-Authorization",
41
+ "Range",
42
+ // "Referer",
43
+ "TE",
44
+ "Trailer",
45
+ "Transfer-Encoding",
46
+ // "User-Agent",
47
+ "Upgrade",
48
+ "Via",
49
+ "Warning",
50
+ ].map((h) => h.toLowerCase()),
51
+ );
52
+ const SEMICOLON_SEPARATED = new Set(
53
+ ["Content-Type", "Cookie", "Prefer"].map((h) => h.toLowerCase()),
54
+ );
55
+
56
+ export class Headers implements Iterable<[Word, Word | null]> {
57
+ // null means the header must not be sent, for example
58
+ // curl -H 'Host:' example.com
59
+ readonly headers: [Word, Word | null][];
60
+ readonly lowercase: boolean;
61
+
62
+ constructor(
63
+ headerArgs?: Word[],
64
+ warnings: Warnings = [],
65
+ // Used for warnings
66
+ argName = "--header/H",
67
+ ) {
68
+ let headers: [Word, Word | null][] = [];
69
+
70
+ if (headerArgs) {
71
+ for (const header of headerArgs) {
72
+ if (header.startsWith("@")) {
73
+ warnings.push([
74
+ "header-file",
75
+ "passing a file for " +
76
+ argName +
77
+ " is not supported: " +
78
+ JSON.stringify(header.toString()),
79
+ ]);
80
+ continue;
81
+ }
82
+
83
+ if (header.includes(":")) {
84
+ const [name, value] = header.split(":", 2);
85
+ // Passing -H 'Header-Name:' disables sending of that header.
86
+ // If the colon is followed by just spaces they're ignored
87
+ // unless it's -H 'Host: '
88
+ // https://github.com/curl/curl/issues/12782
89
+ const hasValue =
90
+ value && (eq(name, "Host") ? value : value.trim()).toBool();
91
+ const headerValue = hasValue ? value.removeFirstChar(" ") : null;
92
+ headers.push([name, headerValue]);
93
+ } else if (header.includes(";")) {
94
+ const [name] = header.split(";", 2);
95
+ headers.push([name, new Word()]);
96
+ } else {
97
+ // TODO: warn that this header arg is ignored?
98
+ }
99
+ }
100
+ }
101
+ this.lowercase =
102
+ headers.length > 0 && headers.every((h) => eq(h[0], h[0].toLowerCase()));
103
+
104
+ // Handle repeated headers
105
+ // For Cookie and Accept, merge the values using ';' and ',' respectively
106
+ // For other headers, warn about the repeated header
107
+ const uniqueHeaders: { [key: string]: [Word, Word | null][] } = {};
108
+ for (const [name, value] of headers) {
109
+ // TODO: something better, at least warn that variable is ignored
110
+ const lowerName = name.toLowerCase().toString();
111
+ if (!uniqueHeaders[lowerName]) {
112
+ uniqueHeaders[lowerName] = [];
113
+ }
114
+ uniqueHeaders[lowerName].push([name, value]);
115
+ }
116
+
117
+ headers = [];
118
+ for (const [lowerName, repeatedHeaders] of Object.entries(uniqueHeaders)) {
119
+ if (repeatedHeaders.length === 1) {
120
+ headers.push(repeatedHeaders[0]);
121
+ continue;
122
+ }
123
+ // If they're all null, just use the first one
124
+ if (repeatedHeaders.every((h) => h[1] === null)) {
125
+ const lastRepeat = repeatedHeaders[repeatedHeaders.length - 1];
126
+ // Warn users if some are capitalized differently
127
+ if (new Set(repeatedHeaders.map((h) => h[0])).size > 1) {
128
+ warnings.push([
129
+ "repeated-header",
130
+ `"${lastRepeat[0]}" header unset ${repeatedHeaders.length} times`,
131
+ ]);
132
+ }
133
+ headers.push(lastRepeat);
134
+ continue;
135
+ }
136
+ // Otherwise there's at least one non-null value, so we can ignore the nulls
137
+ // TODO: if the values of the repeated headers are the same, just use the first one
138
+ // 'content-type': 'application/json; application/json',
139
+ // doesn't really make sense
140
+ const nonEmptyHeaders = repeatedHeaders.filter((h) => h[1] !== null);
141
+ if (nonEmptyHeaders.length === 1) {
142
+ headers.push(nonEmptyHeaders[0]);
143
+ continue;
144
+ }
145
+ let mergeChar = "";
146
+ if (COMMA_SEPARATED.has(lowerName)) {
147
+ mergeChar = ", ";
148
+ } else if (SEMICOLON_SEPARATED.has(lowerName)) {
149
+ mergeChar = "; ";
150
+ }
151
+ if (mergeChar) {
152
+ const merged = joinWords(
153
+ nonEmptyHeaders.map((h) => h[1]) as Word[],
154
+ mergeChar,
155
+ );
156
+ warnings.push([
157
+ "repeated-header",
158
+ `merged ${nonEmptyHeaders.length} "${
159
+ nonEmptyHeaders[nonEmptyHeaders.length - 1][0]
160
+ }" headers together with "${mergeChar.trim()}"`,
161
+ ]);
162
+ headers.push([nonEmptyHeaders[0][0], merged]);
163
+ continue;
164
+ }
165
+
166
+ warnings.push([
167
+ "repeated-header",
168
+ `found ${nonEmptyHeaders.length} "${
169
+ nonEmptyHeaders[nonEmptyHeaders.length - 1][0]
170
+ }" headers, only the last one will be sent`,
171
+ ]);
172
+ headers = headers.concat(nonEmptyHeaders);
173
+ }
174
+
175
+ this.headers = headers;
176
+ }
177
+
178
+ get length(): number {
179
+ return this.headers.length;
180
+ }
181
+
182
+ *[Symbol.iterator](): Iterator<[Word, Word | null]> {
183
+ for (const h of this.headers) {
184
+ yield h;
185
+ }
186
+ }
187
+
188
+ // Gets the first header, matching case-insensitively
189
+ get(header: string): Word | null | undefined {
190
+ const lookup = header.toLowerCase();
191
+ for (const [h, v] of this.headers) {
192
+ if (h.toLowerCase().toString() === lookup) {
193
+ return v;
194
+ }
195
+ }
196
+ return undefined;
197
+ }
198
+
199
+ getContentType(): string | null | undefined {
200
+ const contentTypeHeader = this.get("content-type");
201
+ if (!contentTypeHeader) {
202
+ return contentTypeHeader;
203
+ }
204
+ return contentTypeHeader.split(";")[0].trim().toString();
205
+ }
206
+
207
+ has(header: Word | string): boolean {
208
+ const lookup = header.toLowerCase();
209
+ for (const h of this.headers) {
210
+ if (eq(h[0].toLowerCase(), lookup)) {
211
+ return true;
212
+ }
213
+ }
214
+ return false;
215
+ }
216
+
217
+ // Doesn't overwrite existing headers
218
+ setIfMissing(header: string, value: Word | string): boolean {
219
+ if (this.has(header)) {
220
+ return false;
221
+ }
222
+
223
+ if (this.lowercase) {
224
+ header = header.toLowerCase();
225
+ }
226
+ const k = typeof header === "string" ? new Word(header) : header;
227
+ const v = typeof value === "string" ? new Word(value) : value;
228
+ this.headers.push([k, v]);
229
+ return true;
230
+ }
231
+
232
+ prependIfMissing(header: string, value: Word | string) {
233
+ if (this.has(header)) {
234
+ return false;
235
+ }
236
+
237
+ if (this.lowercase) {
238
+ header = header.toLowerCase();
239
+ }
240
+ const k = typeof header === "string" ? new Word(header) : header;
241
+ const v = typeof value === "string" ? new Word(value) : value;
242
+ this.headers.unshift([k, v]);
243
+ return true;
244
+ }
245
+
246
+ set(header: string, value: Word | string) {
247
+ if (this.lowercase) {
248
+ header = header.toLowerCase();
249
+ }
250
+ const k = typeof header === "string" ? new Word(header) : header;
251
+ const v = typeof value === "string" ? new Word(value) : value;
252
+
253
+ // keep it in the same place if we overwrite
254
+ const searchHeader = k.toLowerCase().toString();
255
+ for (let i = 0; i < this.headers.length; i++) {
256
+ if (eq(this.headers[i][0].toLowerCase(), searchHeader)) {
257
+ this.headers[i][1] = v;
258
+ return;
259
+ }
260
+ }
261
+
262
+ this.headers.push([k, v]);
263
+ }
264
+
265
+ delete(header: string) {
266
+ const lookup = header.toLowerCase();
267
+ for (let i = this.headers.length - 1; i >= 0; i--) {
268
+ if (this.headers[i][0].toLowerCase().toString() === lookup) {
269
+ this.headers.splice(i, 1);
270
+ }
271
+ }
272
+ }
273
+
274
+ // TODO: doesn't this skip the next element after deleting?
275
+ clearNulls() {
276
+ for (let i = this.headers.length - 1; i >= 0; i--) {
277
+ if (this.headers[i][1] === null) {
278
+ this.headers.splice(i, 1);
279
+ }
280
+ }
281
+ }
282
+
283
+ // TODO: shouldn't be used
284
+ count(header: string) {
285
+ let count = 0;
286
+ const lookup = header.toLowerCase();
287
+ for (const h of this.headers || []) {
288
+ if (h[0].toLowerCase().toString() === lookup) {
289
+ count += 1;
290
+ }
291
+ }
292
+ return count;
293
+ }
294
+
295
+ toBool(): boolean {
296
+ return this.headers.length > 0 && this.headers.some((h) => h[1] !== null);
297
+ }
298
+ }
299
+
300
+ // Cookie is a type of header.
301
+ export type Cookie = [Word, Word];
302
+ export type Cookies = Array<Cookie>;
303
+
304
+ export function parseCookiesStrict(cookieString: Word): Cookies | null {
305
+ const cookies: Cookies = [];
306
+ for (let cookie of cookieString.split(";")) {
307
+ cookie = cookie.replace(/^ /, "");
308
+ const [name, value] = cookie.split("=", 2);
309
+ if (value === undefined) {
310
+ return null;
311
+ }
312
+ cookies.push([name, value]);
313
+ }
314
+ if (new Set(cookies.map((c) => c[0])).size !== cookies.length) {
315
+ return null;
316
+ }
317
+ return cookies;
318
+ }
319
+
320
+ export function parseCookies(cookieString: Word): Cookies | null {
321
+ const cookies: Cookies = [];
322
+ for (let cookie of cookieString.split(";")) {
323
+ cookie = cookie.trim();
324
+ if (!cookie) {
325
+ continue;
326
+ }
327
+ const [name, value] = cookie.split("=", 2);
328
+ cookies.push([name.trim(), (value || "").trim()]);
329
+ }
330
+ if (new Set(cookies.map((c) => c[0])).size !== cookies.length) {
331
+ return null;
332
+ }
333
+ return cookies;
334
+ }
package/src/Query.ts ADDED
@@ -0,0 +1,147 @@
1
+ import { Word, eq } from "./shell/Word.js";
2
+ import { UTF8encoder } from "./utils.js";
3
+
4
+ export type QueryList = Array<[Word, Word]>;
5
+ export type QueryDict = Array<[Word, Word | Array<Word>]>;
6
+
7
+ // Can't have a QueryDict without a QueryList
8
+ export type Query = [QueryList, QueryDict | null] | [null, null];
9
+
10
+ // Match Python's urllib.parse.quote() behavior
11
+ // https://github.com/python/cpython/blob/3.11/Lib/urllib/parse.py#L826
12
+ // curl and Python let you send non-ASCII characters by encoding each UTF-8 byte.
13
+ // TODO: ignore hex case?
14
+ function _percentEncode(s: string): string {
15
+ return [...UTF8encoder.encode(s)]
16
+ .map((b) => {
17
+ if (
18
+ // A-Z
19
+ (b >= 0x41 && b <= 0x5a) ||
20
+ // a-z
21
+ (b >= 0x61 && b <= 0x7a) ||
22
+ // 0-9
23
+ (b >= 0x30 && b <= 0x39) ||
24
+ // -._~
25
+ b === 0x2d ||
26
+ b === 0x2e ||
27
+ b === 0x5f ||
28
+ b === 0x7e
29
+ ) {
30
+ return String.fromCharCode(b);
31
+ }
32
+ return "%" + b.toString(16).toUpperCase().padStart(2, "0");
33
+ })
34
+ .join("");
35
+ }
36
+
37
+ export function percentEncode(s: Word): Word {
38
+ const newTokens = [];
39
+ for (const token of s.tokens) {
40
+ if (typeof token === "string") {
41
+ newTokens.push(_percentEncode(token));
42
+ } else {
43
+ newTokens.push(token);
44
+ }
45
+ }
46
+ return new Word(newTokens);
47
+ }
48
+ export function percentEncodePlus(s: Word): Word {
49
+ const newTokens = [];
50
+ for (const token of s.tokens) {
51
+ if (typeof token === "string") {
52
+ newTokens.push(_percentEncode(token).replace(/%20/g, "+"));
53
+ } else {
54
+ newTokens.push(token);
55
+ }
56
+ }
57
+ return new Word(newTokens);
58
+ }
59
+
60
+ // Reimplements decodeURIComponent but ignores variables/commands
61
+ export function wordDecodeURIComponent(s: Word): Word {
62
+ const newTokens = [];
63
+ for (const token of s.tokens) {
64
+ if (typeof token === "string") {
65
+ newTokens.push(decodeURIComponent(token));
66
+ } else {
67
+ newTokens.push(token);
68
+ }
69
+ }
70
+ return new Word(newTokens);
71
+ }
72
+
73
+ // if url is 'example.com?' the s is ''
74
+ // if url is 'example.com' the s is null
75
+ export function parseQueryString(s: Word | null): Query {
76
+ if (!s || s.isEmpty()) {
77
+ return [null, null];
78
+ }
79
+
80
+ const asList: QueryList = [];
81
+ for (const param of s.split("&")) {
82
+ // Most software libraries don't let you distinguish between a=&b= and a&b,
83
+ // so if we get an `a&b`-type query string, don't bother.
84
+ if (!param.includes("=")) {
85
+ return [null, null];
86
+ }
87
+
88
+ const [key, val] = param.split("=", 2);
89
+ let decodedKey;
90
+ let decodedVal;
91
+ try {
92
+ // https://url.spec.whatwg.org/#urlencoded-parsing
93
+ // recommends replacing + with space before decoding.
94
+ decodedKey = wordDecodeURIComponent(key.replace(/\+/g, " "));
95
+ decodedVal = wordDecodeURIComponent(val.replace(/\+/g, " "));
96
+ } catch (e) {
97
+ if (e instanceof URIError) {
98
+ // Query string contains invalid percent encoded characters,
99
+ // we cannot properly convert it.
100
+ return [null, null];
101
+ }
102
+ throw e;
103
+ }
104
+ // If the query string doesn't round-trip, we cannot properly convert it.
105
+ // TODO: this is a bit Python-specific, ideally we would check how each runtime/library
106
+ // percent-encodes query strings. For example, a %27 character in the input query
107
+ // string will be decoded to a ' but won't be re-encoded into a %27 by encodeURIComponent
108
+ const roundTripKey = percentEncode(decodedKey);
109
+ const roundTripVal = percentEncode(decodedVal);
110
+ // If the original data used %20 instead of + (what requests will send), that's close enough
111
+ if (
112
+ (!eq(roundTripKey, key) && !eq(roundTripKey.replace(/%20/g, "+"), key)) ||
113
+ (!eq(roundTripVal, val) && !eq(roundTripVal.replace(/%20/g, "+"), val))
114
+ ) {
115
+ return [null, null];
116
+ }
117
+ asList.push([decodedKey, decodedVal]);
118
+ }
119
+
120
+ // Group keys
121
+ const keyWords: { [key: string]: Word } = {};
122
+ const uniqueKeys: { [key: string]: Array<Word> } = {};
123
+ let prevKey = null;
124
+ for (const [key, val] of asList) {
125
+ const keyStr = key.toString(); // TODO: do this better
126
+ if (prevKey === keyStr) {
127
+ uniqueKeys[keyStr].push(val);
128
+ } else if (!Object.prototype.hasOwnProperty.call(uniqueKeys, keyStr)) {
129
+ uniqueKeys[keyStr] = [val];
130
+ keyWords[keyStr] = key;
131
+ } else {
132
+ // If there's a repeated key with a different key between
133
+ // one of its repetitions, there is no way to represent
134
+ // this query string as a dictionary.
135
+ return [asList, null];
136
+ }
137
+ prevKey = keyStr;
138
+ }
139
+
140
+ // Convert lists with 1 element to the element
141
+ const asDict: QueryDict = [];
142
+ for (const [keyStr, val] of Object.entries(uniqueKeys)) {
143
+ asDict.push([keyWords[keyStr], val.length === 1 ? val[0] : val]);
144
+ }
145
+
146
+ return [asList, asDict];
147
+ }