acrylblends 0.3.0 → 0.4.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 (315) hide show
  1. package/dist/index.d.ts +6 -1
  2. package/dist/index.js +28 -3
  3. package/dist/index.js.map +1 -1
  4. package/package.json +1 -1
  5. package/plugins/acryl-inspect/plugin.json +2 -1
  6. package/plugins/acryl-inspect/src/report.ts +16 -13
  7. package/plugins/acryl-inspect/src/service.ts +9 -7
  8. package/plugins/acryl-inspect/tests/report.spec.ts +7 -8
  9. package/plugins/acryl-inspect/tests/service.spec.ts +11 -10
  10. package/plugins/acryl-inspect/upstream.json +2 -2
  11. package/plugins/acryl-node-server/LICENSE +26 -0
  12. package/plugins/acryl-node-server/plugin.json +2 -1
  13. package/plugins/acryl-node-server/src/adapter/conninfo.ts +23 -0
  14. package/plugins/acryl-node-server/src/adapter/early-hints.ts +55 -0
  15. package/plugins/acryl-node-server/src/adapter/error.ts +11 -0
  16. package/plugins/acryl-node-server/src/adapter/headers.ts +249 -0
  17. package/plugins/acryl-node-server/src/adapter/index.ts +6 -0
  18. package/plugins/acryl-node-server/src/adapter/listener.ts +540 -0
  19. package/plugins/acryl-node-server/src/adapter/request.ts +797 -0
  20. package/plugins/acryl-node-server/src/adapter/response.ts +186 -0
  21. package/plugins/acryl-node-server/src/adapter/serve-static.ts +238 -0
  22. package/plugins/acryl-node-server/src/adapter/server.ts +36 -0
  23. package/plugins/acryl-node-server/src/adapter/types.ts +82 -0
  24. package/plugins/acryl-node-server/src/adapter/url.ts +42 -0
  25. package/plugins/acryl-node-server/src/adapter/utils/response/constants.ts +1 -0
  26. package/plugins/acryl-node-server/src/adapter/utils/response.ts +4 -0
  27. package/plugins/acryl-node-server/src/adapter/utils/stream.ts +88 -0
  28. package/plugins/acryl-node-server/src/adapter/utils.ts +96 -0
  29. package/plugins/acryl-node-server/src/adapter/websocket-types.ts +45 -0
  30. package/plugins/acryl-node-server/src/adapter/websocket.ts +375 -0
  31. package/plugins/acryl-node-server/src/index.ts +19 -6
  32. package/plugins/acryl-node-server/tests/adapter/app.ts +76 -0
  33. package/plugins/acryl-node-server/tests/adapter/assets/.static/plain.txt +1 -0
  34. package/plugins/acryl-node-server/tests/adapter/assets/favicon.ico +0 -0
  35. package/plugins/acryl-node-server/tests/adapter/assets/secret.txt +1 -0
  36. package/plugins/acryl-node-server/tests/adapter/assets/static/acryl.html +1 -0
  37. package/plugins/acryl-node-server/tests/adapter/assets/static/admin/secret.txt +1 -0
  38. package/plugins/acryl-node-server/tests/adapter/assets/static/data.json +5 -0
  39. package/plugins/acryl-node-server/tests/adapter/assets/static/extensionless +1 -0
  40. package/plugins/acryl-node-server/tests/adapter/assets/static/foo..bar.txt +0 -0
  41. package/plugins/acryl-node-server/tests/adapter/assets/static/index.html +1 -0
  42. package/plugins/acryl-node-server/tests/adapter/assets/static/plain.txt +1 -0
  43. package/plugins/acryl-node-server/tests/adapter/assets/static-with-context-path/plain.txt +1 -0
  44. package/plugins/acryl-node-server/tests/adapter/assets/static-with-precompressed/hello.bin +1 -0
  45. package/plugins/acryl-node-server/tests/adapter/assets/static-with-precompressed/hello.bin.br +1 -0
  46. package/plugins/acryl-node-server/tests/adapter/assets/static-with-precompressed/hello.txt +1 -0
  47. package/plugins/acryl-node-server/tests/adapter/assets/static-with-precompressed/hello.txt.br +1 -0
  48. package/plugins/acryl-node-server/tests/adapter/assets/static-with-precompressed/hello.txt.zst +1 -0
  49. package/plugins/acryl-node-server/tests/adapter/conninfo.test.ts +34 -0
  50. package/plugins/acryl-node-server/tests/adapter/content-length.test.ts +248 -0
  51. package/plugins/acryl-node-server/tests/adapter/early-hints.test.ts +457 -0
  52. package/plugins/acryl-node-server/tests/adapter/fixtures/keys/agent1-cert.pem +23 -0
  53. package/plugins/acryl-node-server/tests/adapter/fixtures/keys/agent1-key.pem +27 -0
  54. package/plugins/acryl-node-server/tests/adapter/headers.test.ts +252 -0
  55. package/plugins/acryl-node-server/tests/adapter/helpers/request.ts +190 -0
  56. package/plugins/acryl-node-server/tests/adapter/listener.test.ts +727 -0
  57. package/plugins/acryl-node-server/tests/adapter/request-client-disconnect.test.ts +189 -0
  58. package/plugins/acryl-node-server/tests/adapter/request.test.ts +1230 -0
  59. package/plugins/acryl-node-server/tests/adapter/response.test.ts +285 -0
  60. package/plugins/acryl-node-server/tests/adapter/serve-static.test.ts +849 -0
  61. package/plugins/acryl-node-server/tests/adapter/server-socket.test.ts +790 -0
  62. package/plugins/acryl-node-server/tests/adapter/server.test.ts +1302 -0
  63. package/plugins/acryl-node-server/tests/adapter/setup.ts +16 -0
  64. package/plugins/acryl-node-server/tests/adapter/url.test.ts +76 -0
  65. package/plugins/acryl-node-server/tests/adapter/utils/response.test.ts +23 -0
  66. package/plugins/acryl-node-server/tests/adapter/utils/stream.test.ts +75 -0
  67. package/plugins/acryl-node-server/tests/adapter/utils.test.ts +235 -0
  68. package/plugins/acryl-node-server/tests/adapter/websocket.test.ts +260 -0
  69. package/plugins/acryl-node-server/tests/server.spec.ts +44 -8
  70. package/plugins/acryl-node-server/upstream.json +3 -3
  71. package/plugins/acryl-router/LICENSE +26 -0
  72. package/plugins/acryl-router/plugin.json +2 -1
  73. package/plugins/acryl-router/src/client.ts +2 -0
  74. package/plugins/acryl-router/src/connection.ts +42 -0
  75. package/plugins/acryl-router/src/core/app-base.ts +544 -0
  76. package/plugins/acryl-router/src/core/app.test.ts +3833 -0
  77. package/plugins/acryl-router/src/core/app.ts +34 -0
  78. package/plugins/acryl-router/src/core/client/client.test.ts +2313 -0
  79. package/plugins/acryl-router/src/core/client/client.ts +249 -0
  80. package/plugins/acryl-router/src/core/client/fetch-result-please.ts +95 -0
  81. package/plugins/acryl-router/src/core/client/index.ts +17 -0
  82. package/plugins/acryl-router/src/core/client/types.test.ts +149 -0
  83. package/plugins/acryl-router/src/core/client/types.ts +393 -0
  84. package/plugins/acryl-router/src/core/client/utils.test.ts +405 -0
  85. package/plugins/acryl-router/src/core/client/utils.ts +117 -0
  86. package/plugins/acryl-router/src/core/compose.test.ts +751 -0
  87. package/plugins/acryl-router/src/core/compose.ts +73 -0
  88. package/plugins/acryl-router/src/core/context.test.ts +629 -0
  89. package/plugins/acryl-router/src/core/context.ts +781 -0
  90. package/plugins/acryl-router/src/core/helper/accepts/accepts.test.ts +344 -0
  91. package/plugins/acryl-router/src/core/helper/accepts/accepts.ts +96 -0
  92. package/plugins/acryl-router/src/core/helper/accepts/index.ts +6 -0
  93. package/plugins/acryl-router/src/core/helper/adapter/index.test.ts +55 -0
  94. package/plugins/acryl-router/src/core/helper/adapter/index.ts +90 -0
  95. package/plugins/acryl-router/src/core/helper/conninfo/index.ts +6 -0
  96. package/plugins/acryl-router/src/core/helper/conninfo/types.ts +45 -0
  97. package/plugins/acryl-router/src/core/helper/cookie/index.test.ts +510 -0
  98. package/plugins/acryl-router/src/core/helper/cookie/index.ts +146 -0
  99. package/plugins/acryl-router/src/core/helper/factory/index.test.ts +434 -0
  100. package/plugins/acryl-router/src/core/helper/factory/index.ts +375 -0
  101. package/plugins/acryl-router/src/core/helper/html/index.test.ts +78 -0
  102. package/plugins/acryl-router/src/core/helper/html/index.ts +57 -0
  103. package/plugins/acryl-router/src/core/helper/route/index.test.ts +259 -0
  104. package/plugins/acryl-router/src/core/helper/route/index.ts +144 -0
  105. package/plugins/acryl-router/src/core/helper/streaming/index.ts +9 -0
  106. package/plugins/acryl-router/src/core/helper/streaming/sse.ts +101 -0
  107. package/plugins/acryl-router/src/core/helper/streaming/stream.test.ts +128 -0
  108. package/plugins/acryl-router/src/core/helper/streaming/stream.ts +45 -0
  109. package/plugins/acryl-router/src/core/helper/streaming/text.test.ts +34 -0
  110. package/plugins/acryl-router/src/core/helper/streaming/text.ts +15 -0
  111. package/plugins/acryl-router/src/core/helper/streaming/utils.ts +11 -0
  112. package/plugins/acryl-router/src/core/helper/testing/index.test.ts +42 -0
  113. package/plugins/acryl-router/src/core/helper/testing/index.ts +28 -0
  114. package/plugins/acryl-router/src/core/helper/websocket/index.test.ts +124 -0
  115. package/plugins/acryl-router/src/core/helper/websocket/index.ts +140 -0
  116. package/plugins/acryl-router/src/core/http-exception.test.ts +45 -0
  117. package/plugins/acryl-router/src/core/http-exception.ts +77 -0
  118. package/plugins/acryl-router/src/core/index.ts +53 -0
  119. package/plugins/acryl-router/src/core/middleware/basic-auth/index.test.ts +434 -0
  120. package/plugins/acryl-router/src/core/middleware/basic-auth/index.ts +152 -0
  121. package/plugins/acryl-router/src/core/middleware/bearer-auth/index.test.ts +1018 -0
  122. package/plugins/acryl-router/src/core/middleware/bearer-auth/index.ts +220 -0
  123. package/plugins/acryl-router/src/core/middleware/body-limit/index.test.ts +273 -0
  124. package/plugins/acryl-router/src/core/middleware/body-limit/index.ts +110 -0
  125. package/plugins/acryl-router/src/core/middleware/combine/index.test.ts +355 -0
  126. package/plugins/acryl-router/src/core/middleware/combine/index.ts +171 -0
  127. package/plugins/acryl-router/src/core/middleware/compress/index.test.ts +535 -0
  128. package/plugins/acryl-router/src/core/middleware/compress/index.ts +142 -0
  129. package/plugins/acryl-router/src/core/middleware/context-storage/index.test.ts +43 -0
  130. package/plugins/acryl-router/src/core/middleware/context-storage/index.ts +58 -0
  131. package/plugins/acryl-router/src/core/middleware/cors/index.test.ts +532 -0
  132. package/plugins/acryl-router/src/core/middleware/cors/index.ts +163 -0
  133. package/plugins/acryl-router/src/core/middleware/csrf/index.test.ts +534 -0
  134. package/plugins/acryl-router/src/core/middleware/csrf/index.ts +150 -0
  135. package/plugins/acryl-router/src/core/middleware/etag/digest.test.ts +21 -0
  136. package/plugins/acryl-router/src/core/middleware/etag/digest.ts +95 -0
  137. package/plugins/acryl-router/src/core/middleware/etag/index.test.ts +503 -0
  138. package/plugins/acryl-router/src/core/middleware/etag/index.ts +135 -0
  139. package/plugins/acryl-router/src/core/middleware/ip-restriction/index.test.ts +217 -0
  140. package/plugins/acryl-router/src/core/middleware/ip-restriction/index.ts +278 -0
  141. package/plugins/acryl-router/src/core/middleware/jwk/index.test.ts +1135 -0
  142. package/plugins/acryl-router/src/core/middleware/jwk/index.ts +1 -0
  143. package/plugins/acryl-router/src/core/middleware/jwk/jwk.ts +195 -0
  144. package/plugins/acryl-router/src/core/middleware/jwk/keys.test.json +48 -0
  145. package/plugins/acryl-router/src/core/middleware/jwt/index.test.ts +802 -0
  146. package/plugins/acryl-router/src/core/middleware/jwt/index.ts +9 -0
  147. package/plugins/acryl-router/src/core/middleware/jwt/jwt.ts +189 -0
  148. package/plugins/acryl-router/src/core/middleware/language/index.test.ts +552 -0
  149. package/plugins/acryl-router/src/core/middleware/language/index.ts +12 -0
  150. package/plugins/acryl-router/src/core/middleware/language/language.ts +314 -0
  151. package/plugins/acryl-router/src/core/middleware/logger/index.test.ts +204 -0
  152. package/plugins/acryl-router/src/core/middleware/logger/index.ts +95 -0
  153. package/plugins/acryl-router/src/core/middleware/method-not-allowed/index.test.ts +292 -0
  154. package/plugins/acryl-router/src/core/middleware/method-not-allowed/index.ts +141 -0
  155. package/plugins/acryl-router/src/core/middleware/method-override/index.test.ts +221 -0
  156. package/plugins/acryl-router/src/core/middleware/method-override/index.ts +147 -0
  157. package/plugins/acryl-router/src/core/middleware/powered-by/index.test.ts +37 -0
  158. package/plugins/acryl-router/src/core/middleware/powered-by/index.ts +35 -0
  159. package/plugins/acryl-router/src/core/middleware/pretty-json/index.test.ts +218 -0
  160. package/plugins/acryl-router/src/core/middleware/pretty-json/index.ts +67 -0
  161. package/plugins/acryl-router/src/core/middleware/request-id/index.test.ts +168 -0
  162. package/plugins/acryl-router/src/core/middleware/request-id/index.ts +7 -0
  163. package/plugins/acryl-router/src/core/middleware/request-id/request-id.ts +59 -0
  164. package/plugins/acryl-router/src/core/middleware/secure-headers/index.test.ts +671 -0
  165. package/plugins/acryl-router/src/core/middleware/secure-headers/index.ts +8 -0
  166. package/plugins/acryl-router/src/core/middleware/secure-headers/permissions-policy.ts +101 -0
  167. package/plugins/acryl-router/src/core/middleware/secure-headers/secure-headers.ts +340 -0
  168. package/plugins/acryl-router/src/core/middleware/serve-static/index.test.ts +450 -0
  169. package/plugins/acryl-router/src/core/middleware/serve-static/index.ts +135 -0
  170. package/plugins/acryl-router/src/core/middleware/serve-static/path.test.ts +50 -0
  171. package/plugins/acryl-router/src/core/middleware/serve-static/path.ts +25 -0
  172. package/plugins/acryl-router/src/core/middleware/timeout/index.test.ts +70 -0
  173. package/plugins/acryl-router/src/core/middleware/timeout/index.ts +58 -0
  174. package/plugins/acryl-router/src/core/middleware/timing/index.test.ts +310 -0
  175. package/plugins/acryl-router/src/core/middleware/timing/index.ts +8 -0
  176. package/plugins/acryl-router/src/core/middleware/timing/timing.ts +256 -0
  177. package/plugins/acryl-router/src/core/middleware/trailing-slash/index.test.ts +381 -0
  178. package/plugins/acryl-router/src/core/middleware/trailing-slash/index.ts +156 -0
  179. package/plugins/acryl-router/src/core/request/constants.ts +1 -0
  180. package/plugins/acryl-router/src/core/request.test.ts +751 -0
  181. package/plugins/acryl-router/src/core/request.ts +502 -0
  182. package/plugins/acryl-router/src/core/router/common.case.test.ts +1132 -0
  183. package/plugins/acryl-router/src/core/router/linear-router/index.ts +6 -0
  184. package/plugins/acryl-router/src/core/router/linear-router/router.test.ts +80 -0
  185. package/plugins/acryl-router/src/core/router/linear-router/router.ts +152 -0
  186. package/plugins/acryl-router/src/core/router/pattern-router/index.ts +6 -0
  187. package/plugins/acryl-router/src/core/router/pattern-router/router.test.ts +46 -0
  188. package/plugins/acryl-router/src/core/router/pattern-router/router.ts +54 -0
  189. package/plugins/acryl-router/src/core/router/reg-exp-router/index.ts +7 -0
  190. package/plugins/acryl-router/src/core/router/reg-exp-router/matcher.ts +33 -0
  191. package/plugins/acryl-router/src/core/router/reg-exp-router/node.ts +170 -0
  192. package/plugins/acryl-router/src/core/router/reg-exp-router/prepared-router.test.ts +154 -0
  193. package/plugins/acryl-router/src/core/router/reg-exp-router/prepared-router.ts +165 -0
  194. package/plugins/acryl-router/src/core/router/reg-exp-router/router.test.ts +265 -0
  195. package/plugins/acryl-router/src/core/router/reg-exp-router/router.ts +174 -0
  196. package/plugins/acryl-router/src/core/router/reg-exp-router/trie.ts +84 -0
  197. package/plugins/acryl-router/src/core/router/smart-router/index.ts +6 -0
  198. package/plugins/acryl-router/src/core/router/smart-router/router.test.ts +13 -0
  199. package/plugins/acryl-router/src/core/router/smart-router/router.ts +70 -0
  200. package/plugins/acryl-router/src/core/router/trie-router/index.ts +6 -0
  201. package/plugins/acryl-router/src/core/router/trie-router/node.test.ts +896 -0
  202. package/plugins/acryl-router/src/core/router/trie-router/node.ts +221 -0
  203. package/plugins/acryl-router/src/core/router/trie-router/router.test.ts +8 -0
  204. package/plugins/acryl-router/src/core/router/trie-router/router.ts +18 -0
  205. package/plugins/acryl-router/src/core/router/utils.ts +1 -0
  206. package/plugins/acryl-router/src/core/router.ts +103 -0
  207. package/plugins/acryl-router/src/core/types.test.ts +4134 -0
  208. package/plugins/acryl-router/src/core/types.ts +2782 -0
  209. package/plugins/acryl-router/src/core/utils/accept.test.ts +299 -0
  210. package/plugins/acryl-router/src/core/utils/accept.ts +268 -0
  211. package/plugins/acryl-router/src/core/utils/basic-auth.test.ts +56 -0
  212. package/plugins/acryl-router/src/core/utils/basic-auth.ts +26 -0
  213. package/plugins/acryl-router/src/core/utils/body.test.ts +496 -0
  214. package/plugins/acryl-router/src/core/utils/body.ts +263 -0
  215. package/plugins/acryl-router/src/core/utils/buffer.test.ts +147 -0
  216. package/plugins/acryl-router/src/core/utils/buffer.ts +117 -0
  217. package/plugins/acryl-router/src/core/utils/color.test.ts +23 -0
  218. package/plugins/acryl-router/src/core/utils/color.ts +56 -0
  219. package/plugins/acryl-router/src/core/utils/compress.ts +10 -0
  220. package/plugins/acryl-router/src/core/utils/concurrent.test.ts +69 -0
  221. package/plugins/acryl-router/src/core/utils/concurrent.ts +55 -0
  222. package/plugins/acryl-router/src/core/utils/constants.ts +4 -0
  223. package/plugins/acryl-router/src/core/utils/cookie.test.ts +460 -0
  224. package/plugins/acryl-router/src/core/utils/cookie.ts +281 -0
  225. package/plugins/acryl-router/src/core/utils/crypto.test.ts +43 -0
  226. package/plugins/acryl-router/src/core/utils/crypto.ts +58 -0
  227. package/plugins/acryl-router/src/core/utils/encode.test.ts +61 -0
  228. package/plugins/acryl-router/src/core/utils/encode.ts +34 -0
  229. package/plugins/acryl-router/src/core/utils/filepath.test.ts +78 -0
  230. package/plugins/acryl-router/src/core/utils/filepath.ts +60 -0
  231. package/plugins/acryl-router/src/core/utils/handler.ts +15 -0
  232. package/plugins/acryl-router/src/core/utils/headers.ts +355 -0
  233. package/plugins/acryl-router/src/core/utils/html.test.ts +16 -0
  234. package/plugins/acryl-router/src/core/utils/html.ts +182 -0
  235. package/plugins/acryl-router/src/core/utils/http-status.ts +72 -0
  236. package/plugins/acryl-router/src/core/utils/ipaddr.test.ts +149 -0
  237. package/plugins/acryl-router/src/core/utils/ipaddr.ts +374 -0
  238. package/plugins/acryl-router/src/core/utils/jwt/index.ts +7 -0
  239. package/plugins/acryl-router/src/core/utils/jwt/jwa.test.ts +86 -0
  240. package/plugins/acryl-router/src/core/utils/jwt/jwa.ts +38 -0
  241. package/plugins/acryl-router/src/core/utils/jwt/jws.ts +235 -0
  242. package/plugins/acryl-router/src/core/utils/jwt/jwt.test.ts +1788 -0
  243. package/plugins/acryl-router/src/core/utils/jwt/jwt.ts +292 -0
  244. package/plugins/acryl-router/src/core/utils/jwt/types.ts +157 -0
  245. package/plugins/acryl-router/src/core/utils/jwt/utf8.ts +7 -0
  246. package/plugins/acryl-router/src/core/utils/mime.test.ts +64 -0
  247. package/plugins/acryl-router/src/core/utils/mime.ts +94 -0
  248. package/plugins/acryl-router/src/core/utils/stream.test.ts +212 -0
  249. package/plugins/acryl-router/src/core/utils/stream.ts +107 -0
  250. package/plugins/acryl-router/src/core/utils/types.test.ts +295 -0
  251. package/plugins/acryl-router/src/core/utils/types.ts +123 -0
  252. package/plugins/acryl-router/src/core/utils/url.test.ts +384 -0
  253. package/plugins/acryl-router/src/core/utils/url.ts +337 -0
  254. package/plugins/acryl-router/src/core/validator/index.ts +8 -0
  255. package/plugins/acryl-router/src/core/validator/utils.test.ts +82 -0
  256. package/plugins/acryl-router/src/core/validator/utils.ts +69 -0
  257. package/plugins/acryl-router/src/core/validator/validator.test.ts +1488 -0
  258. package/plugins/acryl-router/src/core/validator/validator.ts +185 -0
  259. package/plugins/acryl-router/src/helpers/index.ts +13 -3
  260. package/plugins/acryl-router/src/helpers/schema-validator.ts +35 -0
  261. package/plugins/acryl-router/src/index.ts +6 -6
  262. package/plugins/acryl-router/src/middleware/index.ts +25 -6
  263. package/plugins/acryl-router/src/middleware/loopback-only.ts +19 -13
  264. package/plugins/acryl-router/src/service.ts +256 -64
  265. package/plugins/acryl-router/tests/docs-examples.spec.ts +152 -0
  266. package/plugins/acryl-router/tests/live-install.spec.ts +2 -2
  267. package/plugins/acryl-router/tests/loader.spec.ts +3 -2
  268. package/plugins/acryl-router/tests/service.spec.ts +264 -66
  269. package/plugins/acryl-router/tests/shadow-state.spec.ts +79 -0
  270. package/plugins/acryl-router/upstream.json +2 -2
  271. package/plugins/acryl-router-http/plugin.json +2 -1
  272. package/plugins/acryl-router-http/src/bridge.ts +3 -1
  273. package/plugins/acryl-router-http/tests/adapter.spec.ts +16 -12
  274. package/plugins/acryl-router-http/tests/surfaces.spec.ts +12 -10
  275. package/plugins/acryl-router-http/upstream.json +3 -3
  276. package/plugins/acryl-state/NOTICE +30 -0
  277. package/plugins/acryl-state/plugin.json +1 -1
  278. package/plugins/acryl-state/src/cell.ts +1 -1
  279. package/plugins/acryl-state/src/service.ts +33 -15
  280. package/plugins/acryl-state/upstream.json +6 -4
  281. package/plugins/builder/script.ts +4 -7
  282. package/plugins/extensions/index.ts +2 -2
  283. package/plugins/extensions/resolve.ts +28 -3
  284. package/plugins/openai/prompt.ts +9 -7
  285. package/plugins/tsconfig.json +5 -4
  286. package/templates/agent/src/routes.ts +37 -27
  287. package/templates/agent/test/model.test.ts +2 -1
  288. package/templates/agent/tsconfig.json +8 -5
  289. package/templates/blank/src/routes.ts +6 -4
  290. package/templates/blank/tsconfig.json +2 -4
  291. package/templates/todo/src/page.ts +1 -1
  292. package/templates/todo/src/todo.ts +56 -29
  293. package/templates/todo/tsconfig.json +2 -4
  294. package/templates/todo/upstream.json +2 -2
  295. package/plugins/acryl-router/src/compose.ts +0 -21
  296. package/plugins/acryl-router/src/context.ts +0 -59
  297. package/plugins/acryl-router/src/helpers/read-body.ts +0 -59
  298. package/plugins/acryl-router/src/helpers/sse.ts +0 -83
  299. package/plugins/acryl-router/src/helpers/validate.ts +0 -44
  300. package/plugins/acryl-router/src/http-error.ts +0 -21
  301. package/plugins/acryl-router/src/middleware/body-limit.ts +0 -25
  302. package/plugins/acryl-router/src/middleware/cors.ts +0 -46
  303. package/plugins/acryl-router/src/middleware/logger.ts +0 -21
  304. package/plugins/acryl-router/src/middleware/request-id.ts +0 -24
  305. package/plugins/acryl-router/src/middleware/secure-headers.ts +0 -23
  306. package/plugins/acryl-router/src/pattern.ts +0 -89
  307. package/plugins/acryl-router/src/registrar.ts +0 -90
  308. package/plugins/acryl-router/src/table.ts +0 -156
  309. package/plugins/acryl-router/src/types.ts +0 -73
  310. package/plugins/acryl-router/src/util.ts +0 -23
  311. package/plugins/acryl-router/tests/api.spec.ts +0 -223
  312. package/plugins/acryl-router/tests/conformance.ts +0 -170
  313. package/plugins/acryl-router/tests/helpers.spec.ts +0 -172
  314. package/plugins/acryl-router/tests/middleware.spec.ts +0 -183
  315. package/plugins/acryl-router/tests/table.spec.ts +0 -47
@@ -0,0 +1,299 @@
1
+ import { parseAccept } from './accept.ts'
2
+
3
+ describe('parseAccept Comprehensive Tests', () => {
4
+ describe('Basic Functionality', () => {
5
+ test('parses simple accept header', () => {
6
+ const header = 'text/html,application/json;q=0.9'
7
+ expect(parseAccept(header)).toEqual([
8
+ { type: 'text/html', params: {}, q: 1 },
9
+ { type: 'application/json', params: { q: '0.9' }, q: 0.9 },
10
+ ])
11
+ })
12
+
13
+ test('handles missing header', () => {
14
+ expect(parseAccept('')).toEqual([])
15
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
16
+ expect(parseAccept(undefined as any)).toEqual([])
17
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
18
+ expect(parseAccept(null as any)).toEqual([])
19
+ })
20
+
21
+ test('handles whitespace-only header', () => {
22
+ expect(parseAccept(' ')).toEqual([])
23
+ expect(parseAccept(' \t\n ')).toEqual([])
24
+ })
25
+ })
26
+
27
+ describe('Quality Values', () => {
28
+ test('handles extreme q values', () => {
29
+ const header = 'a;q=999999,b;q=-99999,c;q=Infinity,d;q=-Infinity,e;q=NaN'
30
+ const result = parseAccept(header)
31
+ // Each side clamps to the bound it is past, so a negative q sorts below a real one
32
+ // rather than above it -- `-99999` lands where `-Infinity` already did. Asserted as
33
+ // pairs because the clamp makes the sequence non-monotonic, which sorts the result.
34
+ expect(result.map((x) => [x.type, x.q])).toEqual([
35
+ ['a', 1],
36
+ ['c', 1],
37
+ ['b', 0],
38
+ ['d', 0],
39
+ ['e', 0],
40
+ ])
41
+ })
42
+
43
+ test('a negative q does not outrank a valid one', () => {
44
+ const result = parseAccept('application/json;q=0.9,text/html;q=-1')
45
+ expect(result.map((x) => [x.type, x.q])).toEqual([
46
+ ['application/json', 0.9],
47
+ ['text/html', 0],
48
+ ])
49
+ // The sort is by descending q, so the refused type must not come first.
50
+ expect(result[0].type).toBe('application/json')
51
+ })
52
+
53
+ test('handles malformed q values', () => {
54
+ const header = 'a;q=,b;q=invalid,c;q=1.2.3,d;q=true,e;q="0.5"'
55
+ const result = parseAccept(header)
56
+ expect(result.every((x) => x.q >= 0 && x.q <= 1)).toBe(true)
57
+ })
58
+
59
+ test('preserves original q string in params', () => {
60
+ const header = 'type;q=invalid'
61
+ const result = parseAccept(header)
62
+ expect(result[0].params.q).toBe('invalid')
63
+ expect(result[0].q).toBe(1) // Normalized q value
64
+ })
65
+
66
+ test('treats the q parameter name as case-insensitive', () => {
67
+ // RFC 9110 §12.4.2: the "q" parameter is case-insensitive.
68
+ expect(parseAccept('text/html;Q=0.5')).toEqual([
69
+ { type: 'text/html', params: { Q: '0.5' }, q: 0.5 },
70
+ ])
71
+ expect(parseAccept('text/html;Q=0')[0].q).toBe(0)
72
+ })
73
+
74
+ test('sorts by quality when the q parameter is uppercase', () => {
75
+ const result = parseAccept('text/html;Q=0.5,application/json;q=0.9')
76
+ expect(result.map((x) => x.type)).toEqual(['application/json', 'text/html'])
77
+ })
78
+ })
79
+
80
+ describe('Parameter Handling', () => {
81
+ test('handles complex parameters', () => {
82
+ const header = 'type;a=1;b="2";c=\'3\';d="semi;colon";e="nested"quoted""'
83
+ const result = parseAccept(header)
84
+ expect(result[0].params).toEqual({
85
+ a: '1',
86
+ b: '2',
87
+ c: "'3'",
88
+ d: 'semi;colon',
89
+ })
90
+ })
91
+
92
+ test('handles malformed parameters', () => {
93
+ const header = 'type;=value;;key=;=;====;key====value'
94
+ const result = parseAccept(header)
95
+ expect(result[0].type).toBe('type')
96
+ expect(Object.keys(result[0].params).length).toBe(0)
97
+ })
98
+
99
+ test('handles duplicate parameters', () => {
100
+ const header = 'type;key=1;key=2;KEY=3'
101
+ const result = parseAccept(header)
102
+ expect(result[0].params.key).toBe('2')
103
+ expect(result[0].params.KEY).toBe('3')
104
+ })
105
+
106
+ test('treats `__proto__` as a normal parameter without changing the prototype', () => {
107
+ const result = parseAccept('text/html;__proto__=x')
108
+ expect(Object.getPrototypeOf(result[0].params)).toBeNull()
109
+ expect(result[0].params['__proto__']).toBe('x')
110
+ })
111
+ })
112
+
113
+ describe('Media Type Edge Cases', () => {
114
+ test('handles malformed media types', () => {
115
+ const headers = [
116
+ '*/html',
117
+ 'text/*mal/formed',
118
+ '/partial',
119
+ 'missing/',
120
+ 'inv@lid/type',
121
+ 'text/(html)',
122
+ 'text/html?invalid',
123
+ ]
124
+ headers.forEach((header) => {
125
+ const result = parseAccept(header)
126
+ expect(result[0].type).toBe(header)
127
+ })
128
+ })
129
+
130
+ test('handles extremely long types', () => {
131
+ const longType = 'a'.repeat(10000) + '/' + 'b'.repeat(10000)
132
+ const result = parseAccept(longType)
133
+ expect(result[0].type).toBe(longType)
134
+ })
135
+ })
136
+
137
+ describe('Delimiter Edge Cases', () => {
138
+ test('handles multiple consecutive delimiters', () => {
139
+ const header = 'a,,,,b;q=0.9,,,,c;q=0.8,,,,'
140
+ const result = parseAccept(header)
141
+ expect(result.map((x) => x.type)).toEqual(['a', 'b', 'c'])
142
+ })
143
+
144
+ test('handles unusual whitespace', () => {
145
+ const header = '\n\t a \t\n ; \n\t q=0.9 \t\n , \n\t b \t\n'
146
+ const result = parseAccept(header)
147
+ expect(result.map((x) => x.type)).toEqual(['b', 'a'])
148
+ })
149
+
150
+ test('handles comma inside quoted parameter value', () => {
151
+ const header = 'text/plain;meta="a,b";q=0.8,application/json;q=0.7'
152
+ const result = parseAccept(header)
153
+ expect(result).toEqual([
154
+ {
155
+ type: 'text/plain',
156
+ params: {
157
+ meta: 'a,b',
158
+ q: '0.8',
159
+ },
160
+ q: 0.8,
161
+ },
162
+ {
163
+ type: 'application/json',
164
+ params: {
165
+ q: '0.7',
166
+ },
167
+ q: 0.7,
168
+ },
169
+ ])
170
+ })
171
+
172
+ test('handles escaped quote and semicolon inside quoted parameter', () => {
173
+ const header = 'text/plain;meta="a\\\";b";q=0.5'
174
+ const result = parseAccept(header)
175
+ expect(result).toEqual([
176
+ {
177
+ type: 'text/plain',
178
+ params: {
179
+ meta: 'a";b',
180
+ q: '0.5',
181
+ },
182
+ q: 0.5,
183
+ },
184
+ ])
185
+ })
186
+
187
+ test('handles escaped character inside quoted parameter', () => {
188
+ const header = 'text/plain;meta="a\\\\z;c";q=0.3'
189
+ const result = parseAccept(header)
190
+ expect(result).toEqual([
191
+ {
192
+ type: 'text/plain',
193
+ params: {
194
+ meta: 'a\\z;c',
195
+ q: '0.3',
196
+ },
197
+ q: 0.3,
198
+ },
199
+ ])
200
+ })
201
+
202
+ test('skips invalid param without swallowing next media type', () => {
203
+ const header = 'a;foo, b;q=0.5'
204
+ const result = parseAccept(header)
205
+ expect(result).toEqual([
206
+ { type: 'a', params: {}, q: 1 },
207
+ { type: 'b', params: { q: '0.5' }, q: 0.5 },
208
+ ])
209
+ })
210
+
211
+ test('skips malformed quoted param tail without creating bogus media type', () => {
212
+ const header = 'a;foo="x"bar,b'
213
+ const result = parseAccept(header)
214
+ expect(result).toEqual([
215
+ { type: 'a', params: {}, q: 1 },
216
+ { type: 'b', params: {}, q: 1 },
217
+ ])
218
+ })
219
+
220
+ test('parses params after quoted value with trailing whitespace', () => {
221
+ const header = 'a;foo="x" ;q=0.5,b;q=0.4'
222
+ const result = parseAccept(header)
223
+ expect(result).toEqual([
224
+ { type: 'a', params: { foo: 'x', q: '0.5' }, q: 0.5 },
225
+ { type: 'b', params: { q: '0.4' }, q: 0.4 },
226
+ ])
227
+ })
228
+
229
+ test('handles quoted param followed immediately by comma', () => {
230
+ const header = 'a;foo="x",b'
231
+ const result = parseAccept(header)
232
+ expect(result).toEqual([
233
+ { type: 'a', params: { foo: 'x' }, q: 1 },
234
+ { type: 'b', params: {}, q: 1 },
235
+ ])
236
+ })
237
+
238
+ test('skips empty media type that starts with semicolon', () => {
239
+ const header = ';q=0.5,b;q=0.4'
240
+ const result = parseAccept(header)
241
+ expect(result).toEqual([{ type: 'b', params: { q: '0.4' }, q: 0.4 }])
242
+ })
243
+ })
244
+
245
+ describe('Security Cases', () => {
246
+ test('handles potential injection patterns', () => {
247
+ const headers = [
248
+ 'type;q=0.9--',
249
+ 'type;q=0.9;drop table users',
250
+ 'type;__|;q=0.9',
251
+ 'text/html"><script>alert(1)</script>',
252
+ 'application/json${process.env}',
253
+ ]
254
+ headers.forEach((header) => {
255
+ expect(() => parseAccept(header)).not.toThrow()
256
+ })
257
+ })
258
+
259
+ test('handles many semicolons with an unbalanced quote', () => {
260
+ const header = `text/plain;${'a;'.repeat(8000)}"`
261
+ const result = parseAccept(header)
262
+ expect(result[0].type).toBe('text/plain')
263
+ })
264
+
265
+ test('handles extremely large input', () => {
266
+ const header = 'a;q=0.9,'.repeat(100000)
267
+ expect(() => parseAccept(header)).not.toThrow()
268
+ })
269
+ })
270
+
271
+ describe('Unicode and Special Characters', () => {
272
+ test('handles unicode in types and parameters', () => {
273
+ const header = '🌐/😊;param=🔥;q=0.9'
274
+ const result = parseAccept(header)
275
+ expect(result[0].type).toBe('🌐/😊')
276
+ expect(result[0].params.param).toBe('🔥')
277
+ })
278
+
279
+ test('handles special characters', () => {
280
+ const header = 'type;param=\x00\x01\x02\x03'
281
+ const result = parseAccept(header)
282
+ expect(result[0].params.param).toBe('\x00\x01\x02\x03')
283
+ })
284
+ })
285
+
286
+ describe('Sort Stability', () => {
287
+ test('maintains stable sort for equal q values', () => {
288
+ const header = 'a;q=0.9,b;q=0.9,c;q=0.9,d;q=0.9'
289
+ const result = parseAccept(header)
290
+ expect(result.map((x) => x.type)).toEqual(['a', 'b', 'c', 'd'])
291
+ })
292
+
293
+ test('handles mixed priorities correctly', () => {
294
+ const header = 'd;q=0.8,b;q=0.9,c;q=0.8,a;q=0.9'
295
+ const result = parseAccept(header)
296
+ expect(result.map((x) => x.type)).toEqual(['b', 'a', 'd', 'c'])
297
+ })
298
+ })
299
+ })
@@ -0,0 +1,268 @@
1
+ export interface Accept {
2
+ type: string
3
+ params: Record<string, string>
4
+ q: number
5
+ }
6
+
7
+ const isWhitespace = (char: number): boolean =>
8
+ char === 32 || char === 9 || char === 10 || char === 13
9
+
10
+ const consumeWhitespace = (acceptHeader: string, startIndex: number): number => {
11
+ while (startIndex < acceptHeader.length) {
12
+ if (!isWhitespace(acceptHeader.charCodeAt(startIndex))) {
13
+ break
14
+ }
15
+ startIndex++
16
+ }
17
+ return startIndex
18
+ }
19
+
20
+ const ignoreTrailingWhitespace = (acceptHeader: string, startIndex: number): number => {
21
+ while (startIndex > 0) {
22
+ if (!isWhitespace(acceptHeader.charCodeAt(startIndex - 1))) {
23
+ break
24
+ }
25
+ startIndex--
26
+ }
27
+ return startIndex
28
+ }
29
+
30
+ const skipInvalidParam = (acceptHeader: string, startIndex: number): [number, boolean] => {
31
+ while (startIndex < acceptHeader.length) {
32
+ const char = acceptHeader.charCodeAt(startIndex)
33
+ if (char === 59) {
34
+ // ';' => next param
35
+ return [startIndex + 1, true]
36
+ }
37
+ if (char === 44) {
38
+ // ',' => next accept value
39
+ return [startIndex + 1, false]
40
+ }
41
+ startIndex++
42
+ }
43
+ return [startIndex, false]
44
+ }
45
+
46
+ const skipInvalidAcceptValue = (acceptHeader: string, startIndex: number): number => {
47
+ let i = startIndex
48
+ let inQuotes = false
49
+ while (i < acceptHeader.length) {
50
+ const char = acceptHeader.charCodeAt(i)
51
+ if (inQuotes && char === 92) {
52
+ // '\' => escape
53
+ i++
54
+ } else if (char === 34) {
55
+ // '"' => toggle quotes
56
+ inQuotes = !inQuotes
57
+ } else if (!inQuotes && char === 44) {
58
+ // ',' => next accept value
59
+ return i + 1
60
+ }
61
+ i++
62
+ }
63
+ return i
64
+ }
65
+
66
+ const getNextParam = (
67
+ acceptHeader: string,
68
+ startIndex: number
69
+ ): [number, string | undefined, string | undefined, boolean] => {
70
+ startIndex = consumeWhitespace(acceptHeader, startIndex)
71
+ let i = startIndex
72
+ let key: string | undefined
73
+ let value: string | undefined
74
+ let hasNext = false
75
+ while (i < acceptHeader.length) {
76
+ const char = acceptHeader.charCodeAt(i)
77
+ if (char === 61) {
78
+ // '=' => end of key
79
+ key = acceptHeader.slice(startIndex, ignoreTrailingWhitespace(acceptHeader, i))
80
+ i++
81
+ break
82
+ }
83
+ if (char === 59) {
84
+ // ';' => invalid empty param, continue parsing params
85
+ return [i + 1, undefined, undefined, true]
86
+ }
87
+ if (char === 44) {
88
+ // ',' => invalid empty param, move to next accept value
89
+ return [i + 1, undefined, undefined, false]
90
+ }
91
+ i++
92
+ }
93
+ if (key === undefined) {
94
+ return [i, undefined, undefined, false]
95
+ }
96
+
97
+ i = consumeWhitespace(acceptHeader, i)
98
+ if (acceptHeader.charCodeAt(i) === 61) {
99
+ // '=' is invalid as a value, so return undefined
100
+ const skipResult = skipInvalidParam(acceptHeader, i + 1)
101
+ return [skipResult[0], key, undefined, skipResult[1]]
102
+ }
103
+
104
+ let inQuotes = false
105
+ const paramStartIndex = i
106
+ while (i < acceptHeader.length) {
107
+ const char = acceptHeader.charCodeAt(i)
108
+
109
+ if (inQuotes && char === 92) {
110
+ // '\' => escape
111
+ i++
112
+ } else if (char === 34) {
113
+ // '"' => start of quotes
114
+ if (inQuotes) {
115
+ let nextIndex = consumeWhitespace(acceptHeader, i + 1)
116
+ const nextChar = acceptHeader.charCodeAt(nextIndex)
117
+ if (nextIndex < acceptHeader.length && !(nextChar === 59 || nextChar === 44)) {
118
+ // not ';' or ',' => invalid trailing chars
119
+ const skipResult = skipInvalidParam(acceptHeader, nextIndex)
120
+ return [skipResult[0], key, undefined, skipResult[1]]
121
+ }
122
+ value = acceptHeader.slice(paramStartIndex + 1, i)
123
+ if (value.includes('\\')) {
124
+ value = value.replace(/\\(.)/g, '$1')
125
+ }
126
+ if (nextChar === 44) {
127
+ // ',' => end of accept value
128
+ return [nextIndex + 1, key, value, false]
129
+ }
130
+ if (nextChar === 59) {
131
+ // ';' => has next param
132
+ hasNext = true
133
+ nextIndex++
134
+ }
135
+ i = nextIndex
136
+ break
137
+ }
138
+ inQuotes = true
139
+ } else if (!inQuotes && (char === 59 || char === 44)) {
140
+ // ';' or ',' => end of value
141
+ value = acceptHeader.slice(paramStartIndex, ignoreTrailingWhitespace(acceptHeader, i))
142
+ if (char === 59) {
143
+ // ';' => has next param
144
+ hasNext = true
145
+ }
146
+ i++
147
+ break
148
+ }
149
+ i++
150
+ }
151
+ return [
152
+ i,
153
+ key,
154
+ value ?? acceptHeader.slice(paramStartIndex, ignoreTrailingWhitespace(acceptHeader, i)),
155
+ hasNext,
156
+ ]
157
+ }
158
+
159
+ const getNextAcceptValue = (
160
+ acceptHeader: string,
161
+ startIndex: number
162
+ ): [number, Accept | undefined] => {
163
+ const accept: Accept = {
164
+ type: '',
165
+ params: Object.create(null),
166
+ q: 1,
167
+ }
168
+ startIndex = consumeWhitespace(acceptHeader, startIndex)
169
+ let i = startIndex
170
+ while (i < acceptHeader.length) {
171
+ const char = acceptHeader.charCodeAt(i)
172
+ if (char === 59 || char === 44) {
173
+ // ';' or ',' => end of type
174
+ accept.type = acceptHeader.slice(startIndex, ignoreTrailingWhitespace(acceptHeader, i))
175
+ i++
176
+ if (char === 44) {
177
+ // ',' => end of value
178
+ return [i, accept.type ? accept : undefined]
179
+ }
180
+ if (!accept.type) {
181
+ return [skipInvalidAcceptValue(acceptHeader, i), undefined]
182
+ }
183
+ break // parse params
184
+ }
185
+ i++
186
+ }
187
+ if (!accept.type) {
188
+ accept.type = acceptHeader.slice(
189
+ startIndex,
190
+ ignoreTrailingWhitespace(acceptHeader, acceptHeader.length)
191
+ )
192
+ return [acceptHeader.length, accept.type ? accept : undefined]
193
+ }
194
+
195
+ let param: string | undefined
196
+ let value: string | undefined
197
+ let hasNext: boolean
198
+ while (i < acceptHeader.length) {
199
+ ;[i, param, value, hasNext] = getNextParam(acceptHeader, i)
200
+ if (param && value) {
201
+ accept.params[param] = value
202
+ }
203
+ if (!hasNext) {
204
+ break
205
+ }
206
+ }
207
+
208
+ return [i, accept] as [number, Accept]
209
+ }
210
+
211
+ export const parseAccept = (acceptHeader: string): Accept[] => {
212
+ if (!acceptHeader) {
213
+ return []
214
+ }
215
+
216
+ const values: Accept[] = []
217
+ let i = 0
218
+ let accept: Accept | undefined
219
+ let requiresSort = false // in many cases, accept values are already sorted by quality (e.g. "text/html, application/json;q=0.9, */*;q=0.8")
220
+ let lastAccept: Accept | undefined
221
+ while (i < acceptHeader.length) {
222
+ ;[i, accept] = getNextAcceptValue(acceptHeader, i)
223
+ if (accept) {
224
+ // The "q" parameter is case-insensitive (RFC 9110 §12.4.2). `params` keeps
225
+ // the name as it was sent, so both spellings are checked here.
226
+ accept.q = parseQuality(accept.params.q ?? accept.params.Q)
227
+ values.push(accept)
228
+ if (lastAccept && lastAccept.q < accept.q) {
229
+ // find higher quality accept value, so we need to sort
230
+ requiresSort = true
231
+ }
232
+ lastAccept = accept
233
+ }
234
+ }
235
+ if (requiresSort) {
236
+ values.sort((a, b) => b.q - a.q)
237
+ }
238
+
239
+ return values
240
+ }
241
+
242
+ const parseQuality = (qVal?: string): number => {
243
+ if (qVal === undefined) {
244
+ return 1
245
+ }
246
+ if (qVal === '') {
247
+ return 1
248
+ }
249
+ if (qVal === 'NaN') {
250
+ return 0
251
+ }
252
+
253
+ const num = Number(qVal)
254
+ if (Number.isNaN(num)) {
255
+ return 1
256
+ }
257
+ // A qvalue is 0-1 (RFC 9110 12.4.2). Clamp rather than reject, but clamp each side to the
258
+ // bound it is past: sending a negative q below every real one instead of above them, which
259
+ // is what `-Infinity` already did.
260
+ if (num < 0) {
261
+ return 0
262
+ }
263
+ if (num > 1) {
264
+ return 1
265
+ }
266
+
267
+ return num
268
+ }
@@ -0,0 +1,56 @@
1
+ import { auth } from './basic-auth.ts'
2
+
3
+ describe('auth', () => {
4
+ it('auth() - not include Authorization Header', () => {
5
+ const res = auth(new Request('http://localhost/auth'))
6
+ expect(res).toBeUndefined()
7
+ })
8
+
9
+ it('auth() - invalid Authorization Header format', () => {
10
+ const res = auth(
11
+ new Request('http://localhost/auth', {
12
+ headers: { Authorization: 'InvalidAuthHeader' },
13
+ })
14
+ )
15
+ expect(res).toBeUndefined()
16
+ })
17
+
18
+ it('auth() - invalid Base64 string in Authorization Header', () => {
19
+ const res = auth(
20
+ new Request('http://localhost/auth', {
21
+ headers: { Authorization: 'Basic InvalidBase64' },
22
+ })
23
+ )
24
+ expect(res).toBeUndefined()
25
+ })
26
+
27
+ it('auth() - valid Authorization Header', () => {
28
+ const validBase64 = btoa('username:password')
29
+ const res = auth(
30
+ new Request('http://localhost/auth', {
31
+ headers: { Authorization: `Basic ${validBase64}` },
32
+ })
33
+ )
34
+ expect(res).toEqual({ username: 'username', password: 'password' })
35
+ })
36
+
37
+ it('auth() - empty username', () => {
38
+ const validBase64 = btoa(':password')
39
+ const res = auth(
40
+ new Request('http://localhost/auth', {
41
+ headers: { Authorization: `Basic ${validBase64}` },
42
+ })
43
+ )
44
+ expect(res).toEqual({ username: '', password: 'password' })
45
+ })
46
+
47
+ it('auth() - empty password', () => {
48
+ const validBase64 = btoa('username:')
49
+ const res = auth(
50
+ new Request('http://localhost/auth', {
51
+ headers: { Authorization: `Basic ${validBase64}` },
52
+ })
53
+ )
54
+ expect(res).toEqual({ username: 'username', password: '' })
55
+ })
56
+ })
@@ -0,0 +1,26 @@
1
+ import { decodeBase64 } from './encode.ts'
2
+
3
+ const CREDENTIALS_REGEXP = /^ *(?:[Bb][Aa][Ss][Ii][Cc]) +([A-Za-z0-9._~+/-]+=*) *$/
4
+ const USER_PASS_REGEXP = /^([^:]*):(.*)$/
5
+ const utf8Decoder = new TextDecoder()
6
+
7
+ export type Auth = (req: Request) => { username: string; password: string } | undefined
8
+
9
+ export const auth: Auth = (req: Request) => {
10
+ const match = CREDENTIALS_REGEXP.exec(req.headers.get('Authorization') || '')
11
+ if (!match) {
12
+ return undefined
13
+ }
14
+
15
+ let userPass = undefined
16
+ // If an invalid string is passed to atob(), it throws a `DOMException`.
17
+ try {
18
+ userPass = USER_PASS_REGEXP.exec(utf8Decoder.decode(decodeBase64(match[1])))
19
+ } catch {} // Do nothing
20
+
21
+ if (!userPass) {
22
+ return undefined
23
+ }
24
+
25
+ return { username: userPass[1], password: userPass[2] }
26
+ }