From 65d46c661f346e1f5b59005c3c293165528de550 Mon Sep 17 00:00:00 2001 From: Will Toozs Date: Mon, 3 Jun 2024 18:24:55 +0200 Subject: [PATCH 01/15] [edit]CLDSRV-527: add necessary packages --- package.json | 3 +- yarn.lock | 1109 ++++++++++++++++++++++++++++---------------------- 2 files changed, 627 insertions(+), 485 deletions(-) diff --git a/package.json b/package.json index df035922b4..0e3fd95804 100644 --- a/package.json +++ b/package.json @@ -20,11 +20,12 @@ "homepage": "https://github.com/scality/S3#readme", "dependencies": { "@hapi/joi": "^17.1.0", - "arsenal": "git+https://github.com/scality/arsenal#7.70.29", + "arsenal": "git+https://github.com/scality/arsenal#a643a3e6ccbc49327339a285de1d4cb17afcd171", "async": "~2.5.0", "aws-sdk": "2.905.0", "azure-storage": "^2.1.0", "bucketclient": "scality/bucketclient#7.10.6", + "busboy": "^1.6.0", "commander": "^2.9.0", "cron-parser": "^2.11.0", "diskusage": "1.1.3", diff --git a/yarn.lock b/yarn.lock index 9db7ce143e..ceabf1822b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2,10 +2,10 @@ # yarn lockfile v1 -"@colors/colors@1.5.0": - version "1.5.0" - resolved "https://registry.yarnpkg.com/@colors/colors/-/colors-1.5.0.tgz#bb504579c1cae923e6576a4f5da43d25f97bdbd9" - integrity sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ== +"@colors/colors@1.6.0", "@colors/colors@^1.6.0": + version "1.6.0" + resolved "https://registry.yarnpkg.com/@colors/colors/-/colors-1.6.0.tgz#ec6cd237440700bc23ca23087f513c75508958b0" + integrity sha512-Ir+AOibqzrIsL6ajt3Rz3LskB7OiMVHqltZmspbW/TJuTVuyOMirVqAkjfY6JISiLHgyNqicAC8AyHHGzNd/dA== "@dabh/diagnostics@^2.0.2": version "2.0.3" @@ -33,7 +33,7 @@ resolved "https://registry.yarnpkg.com/@hapi/formula/-/formula-2.0.0.tgz#edade0619ed58c8e4f164f233cda70211e787128" integrity sha512-V87P8fv7PI0LH7LiVi8Lkf3x+KCO7pQozXRssAHNXXL9L1K+uyu4XypLXwxqVDKgyQai6qj3/KteNlrqDx4W5A== -"@hapi/hoek@^9.0.0": +"@hapi/hoek@^9.0.0", "@hapi/hoek@^9.3.0": version "9.3.0" resolved "https://registry.yarnpkg.com/@hapi/hoek/-/hoek-9.3.0.tgz#8368869dcb735be2e7f5cb7647de78e167a251fb" integrity sha512-/c6rf4UJlmHlC9b5BaNvzAcFv7HZ2QHaV0D4/HNlBdvFnvQq8RI4kYdhyPCl7Xj+oWvTWQ8ujhqS53LIgAe6KQ== @@ -54,7 +54,7 @@ resolved "https://registry.yarnpkg.com/@hapi/pinpoint/-/pinpoint-2.0.1.tgz#32077e715655fc00ab8df74b6b416114287d6513" integrity sha512-EKQmr16tM8s16vTT3cA5L0kZZcTMU5DUOZTuvpnY738m+jyP3JIUj+Mm1xc1rsLkGBQ/gVnfKYPwOmPg1tUR4Q== -"@hapi/topo@^5.0.0": +"@hapi/topo@^5.0.0", "@hapi/topo@^5.1.0": version "5.1.0" resolved "https://registry.yarnpkg.com/@hapi/topo/-/topo-5.1.0.tgz#dc448e332c6c6e37a4dc02fd84ba8d44b9afb012" integrity sha512-foQZKJig7Ob0BMAYBfcJk8d77QtOe7Wo4ox7ff1lQYoNNAb6jwcY1ncdoy2e9wQZzvNy7ODZCYJkK8kzmcAnAg== @@ -83,16 +83,16 @@ rimraf "^3.0.2" "@senx/warp10@^1.0.14": - version "1.1.2" - resolved "https://registry.yarnpkg.com/@senx/warp10/-/warp10-1.1.2.tgz#b12ff40a203bf52b560a944e62ea4e992658119f" - integrity sha512-7ooeoNwbqAeaq+dxgQwM9PvEsFsC0VEfnKxTt634Q7287wf3J2ErtbFtW+BmivuOL1AyDR5rBfHz4MhvX2I04Q== + version "1.1.4" + resolved "https://registry.yarnpkg.com/@senx/warp10/-/warp10-1.1.4.tgz#2674e62e05bbfea5e7811a6f171115025161024d" + integrity sha512-2UmvM6HP4sxNgPimjYwIzVqhOFPGli7JeeFsdiwrF39RAEZxxSBpj9OCiZf5rLml/XeNnumVObajAopuuc+wuw== dependencies: - dayjs "^1.10.5" + dayjs "^1.11.10" -"@sideway/address@^4.1.3": - version "4.1.4" - resolved "https://registry.yarnpkg.com/@sideway/address/-/address-4.1.4.tgz#03dccebc6ea47fdc226f7d3d1ad512955d4783f0" - integrity sha512-7vwq+rOHVWjyXxVlR76Agnvhy8I9rpzjosTESvmhNeXOXdZZB15Fl+TI9x1SiHZH5Jv2wTGduSxFDIaq0m3DUw== +"@sideway/address@^4.1.5": + version "4.1.5" + resolved "https://registry.yarnpkg.com/@sideway/address/-/address-4.1.5.tgz#4bc149a0076623ced99ca8208ba780d65a99b9d5" + integrity sha512-IqO/DUQHUkPeixNQ8n0JA6102hT9CmaljNTPmQ1u8MEhBo/R4Q8eKLN/vGZxuebwOroDB4cbpjheD4+/sKFK4Q== dependencies: "@hapi/hoek" "^9.0.0" @@ -113,24 +113,17 @@ dependencies: type-detect "4.0.8" -"@sinonjs/commons@^2.0.0": - version "2.0.0" - resolved "https://registry.yarnpkg.com/@sinonjs/commons/-/commons-2.0.0.tgz#fd4ca5b063554307e8327b4564bd56d3b73924a3" - integrity sha512-uLa0j859mMrg2slwQYdO/AkrOfmH+X6LTVmNTS9CqexuE2IvVORIkSpJLqePAbEnKJ77aMmCwr1NUZ57120Xcg== - dependencies: - type-detect "4.0.8" - "@sinonjs/commons@^3.0.0": - version "3.0.0" - resolved "https://registry.yarnpkg.com/@sinonjs/commons/-/commons-3.0.0.tgz#beb434fe875d965265e04722ccfc21df7f755d72" - integrity sha512-jXBtWAF4vmdNmZgD5FoKsVLv3rPgDnLgPbU84LIJ3otV44vJlDRokVng5v8NFJdCf/da9legHcKaRuZs4L7faA== + version "3.0.1" + resolved "https://registry.yarnpkg.com/@sinonjs/commons/-/commons-3.0.1.tgz#1029357e44ca901a615585f6d27738dbc89084cd" + integrity sha512-K3mCHKQ9sVh8o1C9cxkwxaOmXoAMlDxC1mYyHrjqOWEcBjYr76t96zL2zlj5dUGZ3HSw240X1qgH3Mjf1yJWpQ== dependencies: type-detect "4.0.8" -"@sinonjs/fake-timers@^10.0.2": - version "10.3.0" - resolved "https://registry.yarnpkg.com/@sinonjs/fake-timers/-/fake-timers-10.3.0.tgz#55fdff1ecab9f354019129daf4df0dd4d923ea66" - integrity sha512-V4BG07kuYSUkTCSBHG8G8TNhM+F19jXFWnQtzj+we8DrkpSBCee9Z3Ms8yiGer/dlmhe35/Xdgyo3/0rQKg7YA== +"@sinonjs/fake-timers@^11.2.2": + version "11.2.2" + resolved "https://registry.yarnpkg.com/@sinonjs/fake-timers/-/fake-timers-11.2.2.tgz#50063cc3574f4a27bd8453180a04171c85cc9699" + integrity sha512-G2piCSxQ7oWOxwGSAyFHfPIsyeJGXYtc6mFbnFA+kRXkiEnTl8c/8jul2S329iFBnDI9HGoeWWAZvuvOkZccgw== dependencies: "@sinonjs/commons" "^3.0.0" @@ -150,15 +143,15 @@ lodash.get "^4.4.2" type-detect "^4.0.8" -"@sinonjs/text-encoding@^0.7.1": +"@sinonjs/text-encoding@^0.7.2": version "0.7.2" resolved "https://registry.yarnpkg.com/@sinonjs/text-encoding/-/text-encoding-0.7.2.tgz#5981a8db18b56ba38ef0efb7d995b12aa7b51918" integrity sha512-sXXKG+uL9IrKqViTtao2Ws6dy0znu9sOaP1di/jKGW1M6VssO8vlpXCQcpZ+jisQ1tTFAC5Jo/EOzFbggBagFQ== "@socket.io/component-emitter@~3.1.0": - version "3.1.0" - resolved "https://registry.yarnpkg.com/@socket.io/component-emitter/-/component-emitter-3.1.0.tgz#96116f2a912e0c02817345b3c10751069920d553" - integrity sha512-+9jVqKhRSpsc591z5vX+X5Yyw+he/HCB4iQ/RYxw35CEPaY1gnsNE43nf9n9AaYjAQrTiI/mOwKUKdUs9vf7Xg== + version "3.1.2" + resolved "https://registry.yarnpkg.com/@socket.io/component-emitter/-/component-emitter-3.1.2.tgz#821f8442f4175d8f0467b9daf26e3a18e2d02af2" + integrity sha512-9BCxFwvbGg/RsZK9tjXd8s4UcwR0MWeFQ1XEKIQVVvAGJyINdrqKMcTRyLoK8Rse1GjzLV9cwjWV1olXRWEXVA== "@tootallnate/once@1": version "1.1.2" @@ -166,9 +159,9 @@ integrity sha512-RbzJvlNzmRq5c3O09UipeuXno4tA1FE6ikOjxZK0tuxVv3412l64l5t1W5pj4+rJq9vpkm/kwiR07aZXnsKPxw== "@types/async@^3.2.12": - version "3.2.20" - resolved "https://registry.yarnpkg.com/@types/async/-/async-3.2.20.tgz#53517caaa68c94f99da1c4e986cf7f2954981515" - integrity sha512-6jSBQQugzyX1aWto0CbvOnmxrU9tMoXfA9gc4IrLEtvr3dTwSg5GLGoWiZnGLI6UG/kqpB3JOQKQrqnhUWGKQA== + version "3.2.24" + resolved "https://registry.yarnpkg.com/@types/async/-/async-3.2.24.tgz#3a96351047575bbcf2340541b2d955a35339608f" + integrity sha512-8iHVLHsCCOBKjCF2KwFe0p9Z3rfM9mL+sSP8btyR5vTjJRAqpBYD28/ZLgXPf0pjG1VxOvtCV/BgXkQbpSe8Hw== "@types/cookie@^0.4.1": version "0.4.1" @@ -176,26 +169,28 @@ integrity sha512-XW/Aa8APYr6jSVVA1y/DEIZX0/GMKLEVekNG727R8cs56ahETkRAy/3DR7+fJyh7oUgGwNQaRfXCun0+KbWY7Q== "@types/cors@^2.8.12": - version "2.8.13" - resolved "https://registry.yarnpkg.com/@types/cors/-/cors-2.8.13.tgz#b8ade22ba455a1b8cb3b5d3f35910fd204f84f94" - integrity sha512-RG8AStHlUiV5ysZQKq97copd2UmVYw3/pRMLefISZ3S1hK104Cwm7iLQ3fTKx+lsUH2CE8FlLaYeEA2LSeqYUA== + version "2.8.17" + resolved "https://registry.yarnpkg.com/@types/cors/-/cors-2.8.17.tgz#5d718a5e494a8166f569d986794e49c48b216b2b" + integrity sha512-8CGDvrBj1zgo2qE+oS3pOCyYNqCPryMWY2bGfwA0dcfopWGgxs+78df0Rs3rc9THP4JkOhLsAa+15VdpAqkcUA== dependencies: "@types/node" "*" "@types/node@*", "@types/node@>=10.0.0": - version "20.4.4" - resolved "https://registry.yarnpkg.com/@types/node/-/node-20.4.4.tgz#c79c7cc22c9d0e97a7944954c9e663bcbd92b0cb" - integrity sha512-CukZhumInROvLq3+b5gLev+vgpsIqC2D0deQr/yS1WnxvmYLlJXZpaQrQiseMY+6xusl79E04UjWoqyr+t1/Ew== + version "20.12.7" + resolved "https://registry.yarnpkg.com/@types/node/-/node-20.12.7.tgz#04080362fa3dd6c5822061aa3124f5c152cff384" + integrity sha512-wq0cICSkRLVaf3UGLMGItu/PtdY7oaXaI/RVU+xliKVOtRna3PRY57ZDfztpDL0n11vfymMUnXv8QwYCO7L1wg== + dependencies: + undici-types "~5.26.4" "@types/triple-beam@^1.3.2": - version "1.3.2" - resolved "https://registry.yarnpkg.com/@types/triple-beam/-/triple-beam-1.3.2.tgz#38ecb64f01aa0d02b7c8f4222d7c38af6316fef8" - integrity sha512-txGIh+0eDFzKGC25zORnswy+br1Ha7hj5cMVwKIU7+s0U2AxxJru/jZSMU6OC9MJWP6+pc/hc6ZjyZShpsyY2g== + version "1.3.5" + resolved "https://registry.yarnpkg.com/@types/triple-beam/-/triple-beam-1.3.5.tgz#74fef9ffbaa198eb8b588be029f38b00299caa2c" + integrity sha512-6WaYesThRMCl19iryMYP7/x2OVgCtbIVflDGFpWnb9irXI3UjYE4AzmYuiUKY1AJstGijoY+MgUszMgRxIYTYw== "@types/utf8@^3.0.1": - version "3.0.1" - resolved "https://registry.yarnpkg.com/@types/utf8/-/utf8-3.0.1.tgz#bf081663d4fff05ee63b41f377a35f8b189f7e5b" - integrity sha512-1EkWuw7rT3BMz2HpmcEOr/HL61mWNA6Ulr/KdbXR9AI0A55wD4Qfv8hizd8Q1DnknSIzzDvQmvvY/guvX7jjZA== + version "3.0.3" + resolved "https://registry.yarnpkg.com/@types/utf8/-/utf8-3.0.3.tgz#e153ada15157477a9e0d2bcc638c34284a1d82ea" + integrity sha512-+lqLGxWZsEe4Z6OrzBI7Ym4SMUTaMS5yOrHZ0/IL0bpIye1Qbs4PpobJL2mLDbftUXlPFZR7fu6d1yM+bHLX1w== JSONStream@^1.0.0: version "1.3.5" @@ -316,12 +311,10 @@ agent-base@^4.3.0: es6-promisify "^5.0.0" agentkeepalive@^4.1.3, agentkeepalive@^4.1.4: - version "4.3.0" - resolved "https://registry.yarnpkg.com/agentkeepalive/-/agentkeepalive-4.3.0.tgz#bb999ff07412653c1803b3ced35e50729830a255" - integrity sha512-7Epl1Blf4Sy37j4v9f9FjICCh4+KAQOyXgHEwlyBiAQLbhKdq/i2QQU3amQalS/wPhdPzDXPL5DMR5bkn+YeWg== + version "4.5.0" + resolved "https://registry.yarnpkg.com/agentkeepalive/-/agentkeepalive-4.5.0.tgz#2673ad1389b3c418c5a20c5d7364f93ca04be923" + integrity sha512-5GG/5IbQQpC9FpkRGsSvZI5QYeSCzlJHdpBQntCsuTOxhKD8lqKhrleg2Yi7yvMIf82Ycmmqln9U8V9qwEiJew== dependencies: - debug "^4.1.0" - depd "^2.0.0" humanize-ms "^1.2.1" aggregate-error@^3.0.0: @@ -429,29 +422,31 @@ argparse@^2.0.1: resolved "https://registry.yarnpkg.com/argparse/-/argparse-2.0.1.tgz#246f50f3ca78a3240f6c997e8a9bd1eac49e4b38" integrity sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q== -array-buffer-byte-length@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/array-buffer-byte-length/-/array-buffer-byte-length-1.0.0.tgz#fabe8bc193fea865f317fe7807085ee0dee5aead" - integrity sha512-LPuwb2P+NrQw3XhxGc36+XSvuBPopovXYTR9Ew++Du9Yb/bx5AzBfrIsBoj0EZUifjQU+sHL21sseZ3jerWO/A== +array-buffer-byte-length@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/array-buffer-byte-length/-/array-buffer-byte-length-1.0.1.tgz#1e5583ec16763540a27ae52eed99ff899223568f" + integrity sha512-ahC5W1xgou+KTXix4sAO8Ki12Q+jf4i0+tmk3sC+zgcynshkHxzpXdImBehiUYKKKDwvfFiJl1tZt6ewscS1Mg== dependencies: - call-bind "^1.0.2" - is-array-buffer "^3.0.1" + call-bind "^1.0.5" + is-array-buffer "^3.0.4" array-flatten@1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/array-flatten/-/array-flatten-1.1.1.tgz#9a5f699051b1e7073328f2a008968b64ea2955d2" integrity sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg== -arraybuffer.prototype.slice@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/arraybuffer.prototype.slice/-/arraybuffer.prototype.slice-1.0.1.tgz#9b5ea3868a6eebc30273da577eb888381c0044bb" - integrity sha512-09x0ZWFEjj4WD8PDbykUwo3t9arLn8NIzmmYEJFpYekOAQjpkGSyrQhNoRTcwwcFRu+ycWF78QZ63oWTqSjBcw== - dependencies: - array-buffer-byte-length "^1.0.0" - call-bind "^1.0.2" - define-properties "^1.2.0" - get-intrinsic "^1.2.1" - is-array-buffer "^3.0.2" +arraybuffer.prototype.slice@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/arraybuffer.prototype.slice/-/arraybuffer.prototype.slice-1.0.3.tgz#097972f4255e41bc3425e37dc3f6421cf9aefde6" + integrity sha512-bMxMKAjg13EBSVscxTaYA4mRc5t1UAXa2kXiGTNfZ079HIWXEkKmkgFrh/nJqamaLSrXO5H4WFFkPEaLJWbs3A== + dependencies: + array-buffer-byte-length "^1.0.1" + call-bind "^1.0.5" + define-properties "^1.2.1" + es-abstract "^1.22.3" + es-errors "^1.2.1" + get-intrinsic "^1.2.3" + is-array-buffer "^3.0.4" is-shared-array-buffer "^1.0.2" arraybuffer.slice@~0.0.7: @@ -499,7 +494,7 @@ arraybuffer.slice@~0.0.7: optionalDependencies: ioctl "^2.0.2" -"arsenal@git+https://github.com/scality/arsenal#7.70.29": +"arsenal@git+https://github.com/scality/arsenal#a643a3e6ccbc49327339a285de1d4cb17afcd171": version "7.70.29" resolved "git+https://github.com/scality/arsenal#a643a3e6ccbc49327339a285de1d4cb17afcd171" dependencies: @@ -570,9 +565,9 @@ async@^2.1.4, async@^2.3.0: lodash "^4.17.14" async@^3.2.0, async@^3.2.3: - version "3.2.4" - resolved "https://registry.yarnpkg.com/async/-/async-3.2.4.tgz#2d22e00f8cddeb5fde5dd33522b56d1cf569a81c" - integrity sha512-iAB+JbDEGXhyIUavoDl9WP/Jj106Kz9DEn1DPgYw5ruDn0e3Wgi3sKFm55sASdGBNOQB8F59d9qQ7deqrHA8wQ== + version "3.2.5" + resolved "https://registry.yarnpkg.com/async/-/async-3.2.5.tgz#ebd52a8fdaf7a2289a24df399f8d8485c8a46b66" + integrity sha512-baNZyqaaLhyLVKm/DlvdW051MSgO6b8eVfIezl9E5PqWxFgzLm/wQntEW4zOytVburDEr0JlALEpdOFwvErLsg== async@~2.1.5: version "2.1.5" @@ -593,10 +588,12 @@ asynckit@^0.4.0: resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79" integrity sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q== -available-typed-arrays@^1.0.5: - version "1.0.5" - resolved "https://registry.yarnpkg.com/available-typed-arrays/-/available-typed-arrays-1.0.5.tgz#92f95616501069d07d10edb2fc37d3e1c65123b7" - integrity sha512-DMD0KiN46eipeziST1LPP/STfDU0sufISXmjSgvVsoU2tqxctQeASejWcfNtxYKqETM1UxQ8sp2OrSBWpHY6sw== +available-typed-arrays@^1.0.7: + version "1.0.7" + resolved "https://registry.yarnpkg.com/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz#a5cc375d6a03c2efc87a553f3e0b1522def14846" + integrity sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ== + dependencies: + possible-typed-array-names "^1.0.0" aws-sdk@2.905.0: version "2.905.0" @@ -614,9 +611,9 @@ aws-sdk@2.905.0: xml2js "0.4.19" aws-sdk@^2.1005.0: - version "2.1420.0" - resolved "https://registry.yarnpkg.com/aws-sdk/-/aws-sdk-2.1420.0.tgz#ae49a7b8e54fbcd72bfdd4a94473ccf25ca8c984" - integrity sha512-BvGzkKGgOXNj9WAeazvOLu48sCbU52mKLGIY5AjBKX4EuY8APcKKvl1R54kdUoJDnMwv0HwQpGvrVvLTDdOv/A== + version "2.1608.0" + resolved "https://registry.yarnpkg.com/aws-sdk/-/aws-sdk-2.1608.0.tgz#8d5936f65c75aa729ee566eaa9af4c50c273a2f4" + integrity sha512-qqmKS6PRNTRO+O3ZVp9+tvB6asy5uRYDpR6AhSrnhu46JtDpI47aB/O9vyykqQf3JsFu0loinDJjl2hxQoal9A== dependencies: buffer "4.9.2" events "1.1.1" @@ -627,7 +624,7 @@ aws-sdk@^2.1005.0: url "0.10.3" util "^0.12.4" uuid "8.0.0" - xml2js "0.5.0" + xml2js "0.6.2" aws-sign2@~0.7.0: version "0.7.0" @@ -842,25 +839,7 @@ bluebird@^3.3.1: resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.7.2.tgz#9f229c15be272454ffa973ace0dbee79a1b0c36f" integrity sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg== -body-parser@1.20.1: - version "1.20.1" - resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.20.1.tgz#b1812a8912c195cd371a3ee5e66faa2338a5c668" - integrity sha512-jWi7abTbYwajOytWCQc37VulmWiRae5RyTpaCyDcS5/lMdtwSz5lOpDE67srw/HYe35f1z3fDQw+3txg7gNtWw== - dependencies: - bytes "3.1.2" - content-type "~1.0.4" - debug "2.6.9" - depd "2.0.0" - destroy "1.2.0" - http-errors "2.0.0" - iconv-lite "0.4.24" - on-finished "2.4.1" - qs "6.11.0" - raw-body "2.5.1" - type-is "~1.6.18" - unpipe "1.0.0" - -body-parser@^1.18.3, body-parser@^1.19.0: +body-parser@1.20.2, body-parser@^1.18.3, body-parser@^1.19.0: version "1.20.2" resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.20.2.tgz#6feb0e21c4724d06de7ff38da36dad4f57a747fd" integrity sha512-ml9pReCu3M61kGlqoTm2umSXTlRTuGTx0bfYj+uIUKKYycG5NtSbeetV3faSU6R7ajOPw0g/J1PvK4qNy7s5bA== @@ -939,6 +918,13 @@ buffer@^5.1.0, buffer@^5.5.0, buffer@^5.6.0: base64-js "^1.3.1" ieee754 "^1.1.13" +busboy@^1.6.0: + version "1.6.0" + resolved "https://registry.yarnpkg.com/busboy/-/busboy-1.6.0.tgz#966ea36a9502e43cdb9146962523b92f531f6893" + integrity sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA== + dependencies: + streamsearch "^1.1.0" + byte-size@^7.0.0: version "7.0.1" resolved "https://registry.yarnpkg.com/byte-size/-/byte-size-7.0.1.tgz#b1daf3386de7ab9d706b941a748dbfc71130dee3" @@ -988,13 +974,16 @@ cacache@^15.2.0: tar "^6.0.2" unique-filename "^1.1.1" -call-bind@^1.0.0, call-bind@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/call-bind/-/call-bind-1.0.2.tgz#b1d4e89e688119c3c9a903ad30abb2f6a919be3c" - integrity sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA== +call-bind@^1.0.0, call-bind@^1.0.2, call-bind@^1.0.5, call-bind@^1.0.6, call-bind@^1.0.7: + version "1.0.7" + resolved "https://registry.yarnpkg.com/call-bind/-/call-bind-1.0.7.tgz#06016599c40c56498c18769d2730be242b6fa3b9" + integrity sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w== dependencies: - function-bind "^1.1.1" - get-intrinsic "^1.0.2" + es-define-property "^1.0.0" + es-errors "^1.3.0" + function-bind "^1.1.2" + get-intrinsic "^1.2.4" + set-function-length "^1.2.1" caller-path@^0.1.0: version "0.1.0" @@ -1186,9 +1175,9 @@ component-emitter@1.2.1: integrity sha512-jPatnhd33viNplKjqXKRkGU345p263OIWzDL2wH3LGIGp5Kojo+uXizHmOADRvhGFFTnJqX3jBAKP6vvmSDKcA== component-emitter@~1.3.0: - version "1.3.0" - resolved "https://registry.yarnpkg.com/component-emitter/-/component-emitter-1.3.0.tgz#16e4070fba8ae29b679f2215853ee181ab2eabc0" - integrity sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg== + version "1.3.1" + resolved "https://registry.yarnpkg.com/component-emitter/-/component-emitter-1.3.1.tgz#ef1d5796f7d93f135ee6fb684340b26403c97d17" + integrity sha512-T0+barUSQRTUQASh8bx02dl+DhF54GtIDY13Y3m9oWTklKbb3Wv974meRpeZ3lp1JpLVECWWNHC4vaG2XHXouQ== component-inherit@0.0.3: version "0.0.3" @@ -1237,10 +1226,10 @@ cookie@0.3.1: resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.3.1.tgz#e7e0a1f9ef43b4c8ba925c5c5a96e806d16873bb" integrity sha512-+IJOX0OqlHCszo2mBUq+SrEbCj6w7Kpffqx60zYbPTFaO4+yYgRjHwcZNpWvaTylDHaV7PPmBHzSecZiMhtPgw== -cookie@0.5.0: - version "0.5.0" - resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.5.0.tgz#d1f5d71adec6558c58f389987c366aa47e994f8b" - integrity sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw== +cookie@0.6.0: + version "0.6.0" + resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.6.0.tgz#2798b04b071b0ecbff0dbb62a505a8efa4e19051" + integrity sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw== cookie@~0.4.1: version "0.4.2" @@ -1294,13 +1283,13 @@ crypt@0.0.2, crypt@~0.0.1: resolved "https://registry.yarnpkg.com/crypt/-/crypt-0.0.2.tgz#88d7ff7ec0dfb86f713dc87bbb42d044d3e6c41b" integrity sha512-mCxBlsHFYh9C+HVpiEacem8FEBnMXgU9gy4zmNC+SXAZNB/1idgp/aulFJ4FgCi7GPEVbfyng092GqL2k2rmow== -d@1, d@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/d/-/d-1.0.1.tgz#8698095372d58dbee346ffd0c7093f99f8f9eb5a" - integrity sha512-m62ShEObQ39CfralilEQRjH6oAMtNCV1xJyEx5LpRYUVN+EviphDgUc/F3hnYbADmkiNs67Y+3ylmlG7Lnu+FA== +d@1, d@^1.0.1, d@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/d/-/d-1.0.2.tgz#2aefd554b81981e7dccf72d6842ae725cb17e5de" + integrity sha512-MOqHvMWF9/9MX6nza0KgvFH4HpMU0EF5uUDXqX/BtxtU8NfB0QzRtJ8Oe/6SuS4kbhyzVJwjd97EA4PKrzJ8bw== dependencies: - es5-ext "^0.10.50" - type "^1.0.1" + es5-ext "^0.10.64" + type "^2.7.2" dag-map@~1.0.0: version "1.0.2" @@ -1314,10 +1303,37 @@ dashdash@^1.12.0: dependencies: assert-plus "^1.0.0" -dayjs@^1.10.5: - version "1.11.9" - resolved "https://registry.yarnpkg.com/dayjs/-/dayjs-1.11.9.tgz#9ca491933fadd0a60a2c19f6c237c03517d71d1a" - integrity sha512-QvzAURSbQ0pKdIye2txOzNaHmxtUBXerpY0FJsFXUMKbIZeFm5ht1LS/jFsrncjnmtv8HsG0W2g6c0zUjZWmpA== +data-view-buffer@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/data-view-buffer/-/data-view-buffer-1.0.1.tgz#8ea6326efec17a2e42620696e671d7d5a8bc66b2" + integrity sha512-0lht7OugA5x3iJLOWFhWK/5ehONdprk0ISXqVFn/NFrDu+cuc8iADFrGQz5BnRK7LLU3JmkbXSxaqX+/mXYtUA== + dependencies: + call-bind "^1.0.6" + es-errors "^1.3.0" + is-data-view "^1.0.1" + +data-view-byte-length@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/data-view-byte-length/-/data-view-byte-length-1.0.1.tgz#90721ca95ff280677eb793749fce1011347669e2" + integrity sha512-4J7wRJD3ABAzr8wP+OcIcqq2dlUKp4DVflx++hs5h5ZKydWMI6/D/fAot+yh6g2tHh8fLFTvNOaVN357NvSrOQ== + dependencies: + call-bind "^1.0.7" + es-errors "^1.3.0" + is-data-view "^1.0.1" + +data-view-byte-offset@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/data-view-byte-offset/-/data-view-byte-offset-1.0.0.tgz#5e0bbfb4828ed2d1b9b400cd8a7d119bca0ff18a" + integrity sha512-t/Ygsytq+R995EJ5PZlD4Cu56sWa8InXySaViRzw9apusqsOO2bQP+SbYzAhR0pFKoB+43lYy8rWban9JSuXnA== + dependencies: + call-bind "^1.0.6" + es-errors "^1.3.0" + is-data-view "^1.0.1" + +dayjs@^1.11.10: + version "1.11.11" + resolved "https://registry.yarnpkg.com/dayjs/-/dayjs-1.11.11.tgz#dfe0e9d54c5f8b68ccf8ca5f72ac603e7e5ed59e" + integrity sha512-okzr3f11N6WuqYtZSvm+F776mB41wRZMhKP+hc34YdW+KmtYYK9iqvHSwo2k9FEH3fhGXvOPV6yz2IcSrfRUDg== debug@2.2.0: version "2.2.0" @@ -1333,7 +1349,7 @@ debug@2.6.9, debug@^2.1.1, debug@^2.2.0, debug@^2.6.8, debug@~2.6.9: dependencies: ms "2.0.0" -debug@4, debug@^4.1.0, debug@^4.1.1, debug@^4.3.1, debug@^4.3.3, debug@~4.3.1, debug@~4.3.2: +debug@4, debug@^4.1.1, debug@^4.3.1, debug@^4.3.3, debug@~4.3.1, debug@~4.3.2, debug@~4.3.4: version "4.3.4" resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.4.tgz#1319f6579357f2338d3337d2cdd4914bb5dcc865" integrity sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ== @@ -1388,11 +1404,21 @@ deferred-leveldown@~5.3.0: abstract-leveldown "~6.2.1" inherits "^2.0.3" -define-properties@^1.1.3, define-properties@^1.1.4, define-properties@^1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/define-properties/-/define-properties-1.2.0.tgz#52988570670c9eacedd8064f4a990f2405849bd5" - integrity sha512-xvqAVKGfT1+UAvPwKTVw/njhdQ8ZhXK4lI0bCIuCMrp2up9nPnaDftrLtmpTazqd1o+UY4zgzU+avtMbDP+ldA== +define-data-property@^1.0.1, define-data-property@^1.1.4: + version "1.1.4" + resolved "https://registry.yarnpkg.com/define-data-property/-/define-data-property-1.1.4.tgz#894dc141bb7d3060ae4366f6a0107e68fbe48c5e" + integrity sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A== + dependencies: + es-define-property "^1.0.0" + es-errors "^1.3.0" + gopd "^1.0.1" + +define-properties@^1.1.3, define-properties@^1.2.0, define-properties@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/define-properties/-/define-properties-1.2.1.tgz#10781cc616eb951a80a034bafcaa7377f6af2b6c" + integrity sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg== dependencies: + define-data-property "^1.0.1" has-property-descriptors "^1.0.0" object-keys "^1.1.1" @@ -1411,7 +1437,7 @@ denque@^1.1.0, denque@^1.4.1: resolved "https://registry.yarnpkg.com/denque/-/denque-1.5.1.tgz#07f670e29c9a78f8faecb2566a1e2c11929c5cbf" integrity sha512-XwE+iZ4D6ZUB7mfYRMb5wByE8L74HCn30FBN7sWnXksWc1LO1bPDl67pBR9o/kC4z/xSNAwkMYcGgqDV3BE3Hw== -depd@2.0.0, depd@^2.0.0: +depd@2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/depd/-/depd-2.0.0.tgz#b696163cc757560d09cf22cc8fad1571b79e76df" integrity sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw== @@ -1434,11 +1460,11 @@ diff@1.4.0: integrity sha512-VzVc42hMZbYU9Sx/ltb7KYuQ6pqAw+cbFWVy4XKdkuEL2CFaRLGEnISPs7YdzaUGpi+CpIqvRmu7hPQ4T7EQ5w== diff@^5.0.0: - version "5.1.0" - resolved "https://registry.yarnpkg.com/diff/-/diff-5.1.0.tgz#bc52d298c5ea8df9194800224445ed43ffc87e40" - integrity sha512-D+mk+qE8VC/PAUrlAU34N+VfXev0ghe5ywmpqrawphmVZc1bEfn56uo9qpyGp1p4xpzOHkSW4ztBd6L7Xx4ACw== + version "5.2.0" + resolved "https://registry.yarnpkg.com/diff/-/diff-5.2.0.tgz#26ded047cd1179b78b9537d5ef725503ce1ae531" + integrity sha512-uIFDxqpRZGZ6ThOk84hEfqWoHx2devRFvpTZcTHur85vImfaxUbTW9Ryh4CpCuDnToOP1CEtXKIgytHBPVff5A== -diskusage@1.1.3, diskusage@^1.1.1, diskusage@^1.1.3: +diskusage@1.1.3: version "1.1.3" resolved "https://registry.yarnpkg.com/diskusage/-/diskusage-1.1.3.tgz#680d7dbf1b679168a195c9240eb3552cbd2c067b" integrity sha512-EAyaxl8hy4Ph07kzlzGTfpbZMNAAAHXSZtNEMwdlnSd1noHzvA6HsgKt4fEMSvaEXQYLSphe5rPMxN4WOj0hcQ== @@ -1446,6 +1472,14 @@ diskusage@1.1.3, diskusage@^1.1.1, diskusage@^1.1.3: es6-promise "^4.2.5" nan "^2.14.0" +diskusage@^1.1.1, diskusage@^1.1.3: + version "1.2.0" + resolved "https://registry.yarnpkg.com/diskusage/-/diskusage-1.2.0.tgz#3e8ae42333d5d7e0c7d93e055d7fea9ea841bc88" + integrity sha512-2u3OG3xuf5MFyzc4MctNRUKjjwK+UkovRYdD2ed/NZNZPrt0lqHnLKxGhlFVvAb4/oufIgQG3nWgwmeTbHOvXA== + dependencies: + es6-promise "^4.2.8" + nan "^2.18.0" + doctrine@^1.2.2: version "1.5.0" resolved "https://registry.yarnpkg.com/doctrine/-/doctrine-1.5.0.tgz#379dce730f6166f76cefa4e6707a159b02c5a6fa" @@ -1607,59 +1641,85 @@ error-ex@^1.3.1: dependencies: is-arrayish "^0.2.1" -es-abstract@^1.19.0, es-abstract@^1.20.4: - version "1.22.1" - resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.22.1.tgz#8b4e5fc5cefd7f1660f0f8e1a52900dfbc9d9ccc" - integrity sha512-ioRRcXMO6OFyRpyzV3kE1IIBd4WG5/kltnzdxSCqoP8CMGs/Li+M1uF5o7lOkZVFjDs+NLesthnF66Pg/0q0Lw== - dependencies: - array-buffer-byte-length "^1.0.0" - arraybuffer.prototype.slice "^1.0.1" - available-typed-arrays "^1.0.5" - call-bind "^1.0.2" - es-set-tostringtag "^2.0.1" +es-abstract@^1.22.1, es-abstract@^1.22.3, es-abstract@^1.23.0, es-abstract@^1.23.2: + version "1.23.3" + resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.23.3.tgz#8f0c5a35cd215312573c5a27c87dfd6c881a0aa0" + integrity sha512-e+HfNH61Bj1X9/jLc5v1owaLYuHdeHHSQlkhCBiTK8rBvKaULl/beGMxwrMXjpYrv4pz22BlY570vVePA2ho4A== + dependencies: + array-buffer-byte-length "^1.0.1" + arraybuffer.prototype.slice "^1.0.3" + available-typed-arrays "^1.0.7" + call-bind "^1.0.7" + data-view-buffer "^1.0.1" + data-view-byte-length "^1.0.1" + data-view-byte-offset "^1.0.0" + es-define-property "^1.0.0" + es-errors "^1.3.0" + es-object-atoms "^1.0.0" + es-set-tostringtag "^2.0.3" es-to-primitive "^1.2.1" - function.prototype.name "^1.1.5" - get-intrinsic "^1.2.1" - get-symbol-description "^1.0.0" + function.prototype.name "^1.1.6" + get-intrinsic "^1.2.4" + get-symbol-description "^1.0.2" globalthis "^1.0.3" gopd "^1.0.1" - has "^1.0.3" - has-property-descriptors "^1.0.0" - has-proto "^1.0.1" + has-property-descriptors "^1.0.2" + has-proto "^1.0.3" has-symbols "^1.0.3" - internal-slot "^1.0.5" - is-array-buffer "^3.0.2" + hasown "^2.0.2" + internal-slot "^1.0.7" + is-array-buffer "^3.0.4" is-callable "^1.2.7" - is-negative-zero "^2.0.2" + is-data-view "^1.0.1" + is-negative-zero "^2.0.3" is-regex "^1.1.4" - is-shared-array-buffer "^1.0.2" + is-shared-array-buffer "^1.0.3" is-string "^1.0.7" - is-typed-array "^1.1.10" + is-typed-array "^1.1.13" is-weakref "^1.0.2" - object-inspect "^1.12.3" + object-inspect "^1.13.1" object-keys "^1.1.1" - object.assign "^4.1.4" - regexp.prototype.flags "^1.5.0" - safe-array-concat "^1.0.0" - safe-regex-test "^1.0.0" - string.prototype.trim "^1.2.7" - string.prototype.trimend "^1.0.6" - string.prototype.trimstart "^1.0.6" - typed-array-buffer "^1.0.0" - typed-array-byte-length "^1.0.0" - typed-array-byte-offset "^1.0.0" - typed-array-length "^1.0.4" + object.assign "^4.1.5" + regexp.prototype.flags "^1.5.2" + safe-array-concat "^1.1.2" + safe-regex-test "^1.0.3" + string.prototype.trim "^1.2.9" + string.prototype.trimend "^1.0.8" + string.prototype.trimstart "^1.0.8" + typed-array-buffer "^1.0.2" + typed-array-byte-length "^1.0.1" + typed-array-byte-offset "^1.0.2" + typed-array-length "^1.0.6" unbox-primitive "^1.0.2" - which-typed-array "^1.1.10" + which-typed-array "^1.1.15" -es-set-tostringtag@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/es-set-tostringtag/-/es-set-tostringtag-2.0.1.tgz#338d502f6f674301d710b80c8592de8a15f09cd8" - integrity sha512-g3OMbtlwY3QewlqAiMLI47KywjWZoEytKr8pf6iTC8uJq5bIAH52Z9pnQ8pVL6whrCto53JZDuUIsifGeLorTg== +es-define-property@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/es-define-property/-/es-define-property-1.0.0.tgz#c7faefbdff8b2696cf5f46921edfb77cc4ba3845" + integrity sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ== dependencies: - get-intrinsic "^1.1.3" - has "^1.0.3" - has-tostringtag "^1.0.0" + get-intrinsic "^1.2.4" + +es-errors@^1.2.1, es-errors@^1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/es-errors/-/es-errors-1.3.0.tgz#05f75a25dab98e4fb1dcd5e1472c0546d5057c8f" + integrity sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw== + +es-object-atoms@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/es-object-atoms/-/es-object-atoms-1.0.0.tgz#ddb55cd47ac2e240701260bc2a8e31ecb643d941" + integrity sha512-MZ4iQ6JwHOBQjahnjwaC1ZtIBH+2ohjamzAO3oaHcXYup7qxjF2fixyH+Q71voWHeOkI2q/TnJao/KfXYIZWbw== + dependencies: + es-errors "^1.3.0" + +es-set-tostringtag@^2.0.3: + version "2.0.3" + resolved "https://registry.yarnpkg.com/es-set-tostringtag/-/es-set-tostringtag-2.0.3.tgz#8bb60f0a440c2e4281962428438d58545af39777" + integrity sha512-3T8uNMC3OQTHkFUsFq8r/BwAXLHvU/9O9mE0fBc/MY5iq/8H7ncvO947LmYA6ldWw9Uh8Yhf25zu6n7nML5QWQ== + dependencies: + get-intrinsic "^1.2.4" + has-tostringtag "^1.0.2" + hasown "^2.0.1" es-to-primitive@^1.2.1: version "1.2.1" @@ -1670,13 +1730,14 @@ es-to-primitive@^1.2.1: is-date-object "^1.0.1" is-symbol "^1.0.2" -es5-ext@^0.10.35, es5-ext@^0.10.46, es5-ext@^0.10.50, es5-ext@^0.10.62, es5-ext@~0.10.14: - version "0.10.62" - resolved "https://registry.yarnpkg.com/es5-ext/-/es5-ext-0.10.62.tgz#5e6adc19a6da524bf3d1e02bbc8960e5eb49a9a5" - integrity sha512-BHLqn0klhEpnOKSrzn/Xsz2UIW8j+cGmo9JLzr8BiUapV8hPL9+FliFqjwr9ngW7jWdnxv6eO+/LqyhJVqgrjA== +es5-ext@^0.10.35, es5-ext@^0.10.46, es5-ext@^0.10.62, es5-ext@^0.10.64, es5-ext@~0.10.14: + version "0.10.64" + resolved "https://registry.yarnpkg.com/es5-ext/-/es5-ext-0.10.64.tgz#12e4ffb48f1ba2ea777f1fcdd1918ef73ea21714" + integrity sha512-p2snDhiLaXe6dahss1LddxqEm+SkuDvV8dnIQG0MWjyHpcMNfXKPE+/Cc0y+PhxJX3A4xGNeFCj5oc0BUh6deg== dependencies: es6-iterator "^2.0.3" es6-symbol "^3.1.3" + esniff "^2.0.1" next-tick "^1.1.0" es6-iterator@^2.0.3, es6-iterator@~2.0.1, es6-iterator@~2.0.3: @@ -1700,7 +1761,7 @@ es6-map@^0.1.3: es6-symbol "~3.1.1" event-emitter "~0.3.5" -es6-promise@^4.0.3, es6-promise@^4.2.5: +es6-promise@^4.0.3, es6-promise@^4.2.5, es6-promise@^4.2.8: version "4.2.8" resolved "https://registry.yarnpkg.com/es6-promise/-/es6-promise-4.2.8.tgz#4eb21594c972bc40553d276e510539143db53e0a" integrity sha512-HJDGx5daxeIvxdBxvG2cb9g4tEvwIk3i8+nhX0yGrYmZUzbkdg8QbDevheDB8gd0//uPj4c1EQua8Q+MViT0/w== @@ -1725,12 +1786,12 @@ es6-set@~0.1.5: type "^2.7.2" es6-symbol@^3.1.1, es6-symbol@^3.1.3, es6-symbol@~3.1.1: - version "3.1.3" - resolved "https://registry.yarnpkg.com/es6-symbol/-/es6-symbol-3.1.3.tgz#bad5d3c1bcdac28269f4cb331e431c78ac705d18" - integrity sha512-NJ6Yn3FuDinBaBRWl/q5X/s4koRHBrgKAu+yGI6JCBeiu3qrcbJhwT2GeR/EXVfylRk8dpQVJoLEFhK+Mu31NA== + version "3.1.4" + resolved "https://registry.yarnpkg.com/es6-symbol/-/es6-symbol-3.1.4.tgz#f4e7d28013770b4208ecbf3e0bf14d3bcb557b8c" + integrity sha512-U9bFFjX8tFiATgtkJ1zg25+KviIXpgRvRHS8sau3GfhVzThRQrOeksPeT0BWW2MNZs1OEWJ1DPXOQMn0KKRkvg== dependencies: - d "^1.0.1" - ext "^1.1.2" + d "^1.0.2" + ext "^1.7.0" es6-weak-map@^2.0.1: version "2.0.3" @@ -1818,6 +1879,16 @@ eslint@^2.4.0: text-table "~0.2.0" user-home "^2.0.0" +esniff@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/esniff/-/esniff-2.0.1.tgz#a4d4b43a5c71c7ec51c51098c1d8a29081f9b308" + integrity sha512-kTUIGKQ/mDPFoJ0oVfcmyJn4iBDRptjNVIzwIFR7tqWXdVI9xfA2RMwY/gbSpJG3lkdWNEjLap/NqVHZiJsdfg== + dependencies: + d "^1.0.1" + es5-ext "^0.10.62" + event-emitter "^0.3.5" + type "^2.7.2" + espree@^3.1.6: version "3.5.4" resolved "https://registry.yarnpkg.com/espree/-/espree-3.5.4.tgz#b0f447187c8a8bed944b815a660bddf5deb5d1a7" @@ -1887,16 +1958,16 @@ exit-hook@^1.0.0: integrity sha512-MsG3prOVw1WtLXAZbM3KiYtooKR1LvxHh3VHsVtIy0uiUu8usxgB/94DP2HxtD/661lLdB6yzQ09lGJSQr6nkg== express@^4.17.1: - version "4.18.2" - resolved "https://registry.yarnpkg.com/express/-/express-4.18.2.tgz#3fabe08296e930c796c19e3c516979386ba9fd59" - integrity sha512-5/PsL6iGPdfQ/lKM1UuielYgv3BUoJfz1aUwU9vHZ+J7gyvwdQXFEBIEIaxeGf0GIcreATNyBExtalisDbuMqQ== + version "4.19.2" + resolved "https://registry.yarnpkg.com/express/-/express-4.19.2.tgz#e25437827a3aa7f2a827bc8171bbbb664a356465" + integrity sha512-5T6nhjsT+EOMzuck8JjBHARTHfMht0POzlA60WV2pMD3gyXw2LZnZ+ueGdNxG+0calOJcWKbpFcuzLZ91YWq9Q== dependencies: accepts "~1.3.8" array-flatten "1.1.1" - body-parser "1.20.1" + body-parser "1.20.2" content-disposition "0.5.4" content-type "~1.0.4" - cookie "0.5.0" + cookie "0.6.0" cookie-signature "1.0.6" debug "2.6.9" depd "2.0.0" @@ -1923,7 +1994,7 @@ express@^4.17.1: utils-merge "1.0.1" vary "~1.1.2" -ext@^1.1.2: +ext@^1.7.0: version "1.7.0" resolved "https://registry.yarnpkg.com/ext/-/ext-1.7.0.tgz#0ea4383c0103d60e70be99e9a7f11027a33c4f5f" integrity sha512-6hxeJYaL110a9b5TEJSj0gojyHQAmA2ch5Os+ySCiA1QGdS697XWY1pzsrSjqA9LDEEgdB/KypIlR59RcLuHYw== @@ -2059,9 +2130,9 @@ follow-redirects@1.5.10: debug "=3.1.0" follow-redirects@^1.0.0: - version "1.15.2" - resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.2.tgz#b460864144ba63f2681096f274c4e57026da2c13" - integrity sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA== + version "1.15.6" + resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.6.tgz#7f815c0cda4249c74ff09e95ef97c23b5fd0399b" + integrity sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA== for-each@^0.3.3: version "0.3.3" @@ -2106,27 +2177,27 @@ fs.realpath@^1.0.0: resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" integrity sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw== -function-bind@^1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d" - integrity sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A== +function-bind@^1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.2.tgz#2c02d864d97f3ea6c8830c464cbd11ab6eab7a1c" + integrity sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA== -function.prototype.name@^1.1.5: - version "1.1.5" - resolved "https://registry.yarnpkg.com/function.prototype.name/-/function.prototype.name-1.1.5.tgz#cce0505fe1ffb80503e6f9e46cc64e46a12a9621" - integrity sha512-uN7m/BzVKQnCUF/iW8jYea67v++2u7m5UgENbHRtdDVclOUP+FMPlCNdmk0h/ysGyo2tavMJEDqJAkJdRa1vMA== +function.prototype.name@^1.1.6: + version "1.1.6" + resolved "https://registry.yarnpkg.com/function.prototype.name/-/function.prototype.name-1.1.6.tgz#cdf315b7d90ee77a4c6ee216c3c3362da07533fd" + integrity sha512-Z5kx79swU5P27WEayXM1tBi5Ze/lbIyiNgU3qyXUOf9b2rgXYyF9Dy9Cx+IQv/Lc8WCG6L82zwUPpSS9hGehIg== dependencies: call-bind "^1.0.2" - define-properties "^1.1.3" - es-abstract "^1.19.0" - functions-have-names "^1.2.2" + define-properties "^1.2.0" + es-abstract "^1.22.1" + functions-have-names "^1.2.3" functional-red-black-tree@~1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz#1b0ab3bd553b2a0d6399d29c0e3ea0b252078327" integrity sha512-dsKNQNdj6xA3T+QlADDA7mOSlX0qiMINjn0cgr+eGHGsbSHzTabcIogz2+p/iqP1Xs6EP/sS2SbqH+brGTbq0g== -functions-have-names@^1.2.2, functions-have-names@^1.2.3: +functions-have-names@^1.2.3: version "1.2.3" resolved "https://registry.yarnpkg.com/functions-have-names/-/functions-have-names-1.2.3.tgz#0404fe4ee2ba2f607f0e0ec3c80bae994133b834" integrity sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ== @@ -2191,23 +2262,25 @@ get-folder-size@^2.0.1: gar "^1.0.4" tiny-each-async "2.0.3" -get-intrinsic@^1.0.2, get-intrinsic@^1.1.1, get-intrinsic@^1.1.3, get-intrinsic@^1.2.0, get-intrinsic@^1.2.1: - version "1.2.1" - resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.2.1.tgz#d295644fed4505fc9cde952c37ee12b477a83d82" - integrity sha512-2DcsyfABl+gVHEfCOaTrWgyt+tb6MSEGmKq+kI5HwLbIYgjgmMcV8KQ41uaKz1xxUcn9tJtgFbQUEVcEbd0FYw== +get-intrinsic@^1.1.3, get-intrinsic@^1.2.1, get-intrinsic@^1.2.3, get-intrinsic@^1.2.4: + version "1.2.4" + resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.2.4.tgz#e385f5a4b5227d449c3eabbad05494ef0abbeadd" + integrity sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ== dependencies: - function-bind "^1.1.1" - has "^1.0.3" + es-errors "^1.3.0" + function-bind "^1.1.2" has-proto "^1.0.1" has-symbols "^1.0.3" + hasown "^2.0.0" -get-symbol-description@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/get-symbol-description/-/get-symbol-description-1.0.0.tgz#7fdb81c900101fbd564dd5f1a30af5aadc1e58d6" - integrity sha512-2EmdH1YvIQiZpltCNgkuiUnyukzxM/R6NDJX31Ke3BG1Nq5b0S2PhX59UKi9vZpPDQVdqn+1IcaAwnzTT5vCjw== +get-symbol-description@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/get-symbol-description/-/get-symbol-description-1.0.2.tgz#533744d5aa20aca4e079c8e5daf7fd44202821f5" + integrity sha512-g0QYk1dZBxGwk+Ngc+ltRH2IBp2f7zBkBMBJZCDerh6EhlhSR6+9irMCuT/09zD6qkarHUSn529sK/yL4S27mg== dependencies: - call-bind "^1.0.2" - get-intrinsic "^1.1.1" + call-bind "^1.0.5" + es-errors "^1.3.0" + get-intrinsic "^1.2.4" getpass@^0.1.1: version "0.1.7" @@ -2319,12 +2392,12 @@ gtoken@^2.3.0: pify "^4.0.0" handlebars@^4.0.3: - version "4.7.7" - resolved "https://registry.yarnpkg.com/handlebars/-/handlebars-4.7.7.tgz#9ce33416aad02dbd6c8fafa8240d5d98004945a1" - integrity sha512-aAcXm5OAfE/8IXkcZvCepKU3VzW1/39Fb5ZuqMtgI/hT8X2YgoMvBY5dLhq/cpOvw7Lk1nK/UF71aLG/ZnVYRA== + version "4.7.8" + resolved "https://registry.yarnpkg.com/handlebars/-/handlebars-4.7.8.tgz#41c42c18b1be2365439188c77c6afae71c0cd9e9" + integrity sha512-vafaFqs8MZkRrSX7sFVUdo3ap/eNiLnb4IakshzvP56X5Nr1iGKAIqdX6tMlm6HcNRIkr6AxO5jFEoJzzpT8aQ== dependencies: minimist "^1.2.5" - neo-async "^2.6.0" + neo-async "^2.6.2" source-map "^0.6.1" wordwrap "^1.0.0" optionalDependencies: @@ -2382,42 +2455,35 @@ has-flag@^4.0.0: resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-4.0.0.tgz#944771fd9c81c81265c4d6941860da06bb59479b" integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ== -has-property-descriptors@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/has-property-descriptors/-/has-property-descriptors-1.0.0.tgz#610708600606d36961ed04c196193b6a607fa861" - integrity sha512-62DVLZGoiEBDHQyqG4w9xCuZ7eJEwNmJRWw2VY84Oedb7WFcA27fiEVe8oUQx9hAUJ4ekurquucTGwsyO1XGdQ== +has-property-descriptors@^1.0.0, has-property-descriptors@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz#963ed7d071dc7bf5f084c5bfbe0d1b6222586854" + integrity sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg== dependencies: - get-intrinsic "^1.1.1" + es-define-property "^1.0.0" -has-proto@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/has-proto/-/has-proto-1.0.1.tgz#1885c1305538958aff469fef37937c22795408e0" - integrity sha512-7qE+iP+O+bgF9clE5+UoBFzE65mlBiVj3tKCrlNQ0Ogwm0BjpT/gK4SlLYDMybDh5I3TCTKnPPa0oMG7JDYrhg== +has-proto@^1.0.1, has-proto@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/has-proto/-/has-proto-1.0.3.tgz#b31ddfe9b0e6e9914536a6ab286426d0214f77fd" + integrity sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q== has-symbols@^1.0.2, has-symbols@^1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.3.tgz#bb7b2c4349251dce87b125f7bdf874aa7c8b39f8" integrity sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A== -has-tostringtag@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/has-tostringtag/-/has-tostringtag-1.0.0.tgz#7e133818a7d394734f941e73c3d3f9291e658b25" - integrity sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ== +has-tostringtag@^1.0.0, has-tostringtag@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/has-tostringtag/-/has-tostringtag-1.0.2.tgz#2cdc42d40bef2e5b4eeab7c01a73c54ce7ab5abc" + integrity sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw== dependencies: - has-symbols "^1.0.2" + has-symbols "^1.0.3" has-unicode@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/has-unicode/-/has-unicode-2.0.1.tgz#e0e6fe6a28cf51138855e086d1691e771de2a8b9" integrity sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ== -has@^1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/has/-/has-1.0.3.tgz#722d7cbfc1f6aa8241f16dd814e011e1f41e8796" - integrity sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw== - dependencies: - function-bind "^1.1.1" - hash-base@^3.0.0: version "3.1.0" resolved "https://registry.yarnpkg.com/hash-base/-/hash-base-3.1.0.tgz#55c381d9e06e1d2997a883b4a3fddfe7f0d3af33" @@ -2427,6 +2493,13 @@ hash-base@^3.0.0: readable-stream "^3.6.0" safe-buffer "^5.2.0" +hasown@^2.0.0, hasown@^2.0.1, hasown@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/hasown/-/hasown-2.0.2.tgz#003eaf91be7adc372e84ec59dc37252cedb80003" + integrity sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ== + dependencies: + function-bind "^1.1.2" + hdclient@scality/hdclient#1.1.0: version "1.1.0" resolved "https://codeload.github.com/scality/hdclient/tar.gz/dd6d952e39ca89133019bafbb33eef6c418689f8" @@ -2595,13 +2668,13 @@ inquirer@^0.12.0: strip-ansi "^3.0.0" through "^2.3.6" -internal-slot@^1.0.5: - version "1.0.5" - resolved "https://registry.yarnpkg.com/internal-slot/-/internal-slot-1.0.5.tgz#f2a2ee21f668f8627a4667f309dc0f4fb6674986" - integrity sha512-Y+R5hJrzs52QCG2laLn4udYVnxsfny9CpOhNhUvk/SSSVyF6T27FzRbF0sroPidSu3X8oEAkOn2K804mjpt6UQ== +internal-slot@^1.0.7: + version "1.0.7" + resolved "https://registry.yarnpkg.com/internal-slot/-/internal-slot-1.0.7.tgz#c06dcca3ed874249881007b0a5523b172a190802" + integrity sha512-NGnrKwXzSms2qUUih/ILZ5JBqNTSa1+ZmP6flaIp6KmSElgE9qdndzS3cqjrDovwFdmwsGsLdeFgB6suw+1e9g== dependencies: - get-intrinsic "^1.2.0" - has "^1.0.3" + es-errors "^1.3.0" + hasown "^2.0.0" side-channel "^1.0.4" invariant@^2.2.2: @@ -2651,10 +2724,13 @@ ioredis@^4.28.0, ioredis@^4.28.5: redis-parser "^3.0.0" standard-as-callback "^2.1.0" -ip@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/ip/-/ip-2.0.0.tgz#4cf4ab182fee2314c75ede1276f8c80b479936da" - integrity sha512-WKa+XuLG1A1R0UWhl2+1XQSi+fZWMsYKffMZTTYsiZaUD8k2yDAj5atimTUD2TZkyCkNEeYE5NhFZmupOGtjYQ== +ip-address@^9.0.5: + version "9.0.5" + resolved "https://registry.yarnpkg.com/ip-address/-/ip-address-9.0.5.tgz#117a960819b08780c3bd1f14ef3c1cc1d3f3ea5a" + integrity sha512-zHtQzGojZXTwZTHQqra+ETKd4Sn3vgi7uBmlPoXVWZqYvuKmtI0l/VZTjqGmJY9x88GGOaZ9+G9ES8hC4T4X8g== + dependencies: + jsbn "1.1.0" + sprintf-js "^1.1.3" ipaddr.js@1.9.1: version "1.9.1" @@ -2669,14 +2745,13 @@ is-arguments@^1.0.4: call-bind "^1.0.2" has-tostringtag "^1.0.0" -is-array-buffer@^3.0.1, is-array-buffer@^3.0.2: - version "3.0.2" - resolved "https://registry.yarnpkg.com/is-array-buffer/-/is-array-buffer-3.0.2.tgz#f2653ced8412081638ecb0ebbd0c41c6e0aecbbe" - integrity sha512-y+FyyR/w8vfIRq4eQcM1EYgSTnmHXPqaF+IgzgraytCFq5Xh8lllDVmAZolPJiZttZLeFSINPYMaEJ7/vWUa1w== +is-array-buffer@^3.0.4: + version "3.0.4" + resolved "https://registry.yarnpkg.com/is-array-buffer/-/is-array-buffer-3.0.4.tgz#7a1f92b3d61edd2bc65d24f130530ea93d7fae98" + integrity sha512-wcjaerHw0ydZwfhiKbXJWLDY8A7yV7KhjQOpb83hGgGfId/aQa4TOvwyzn2PuswW2gPCYEL/nEAiSVpdOj1lXw== dependencies: call-bind "^1.0.2" - get-intrinsic "^1.2.0" - is-typed-array "^1.1.10" + get-intrinsic "^1.2.1" is-arrayish@^0.2.1: version "0.2.1" @@ -2718,12 +2793,19 @@ is-callable@^1.1.3, is-callable@^1.1.4, is-callable@^1.2.7: resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.2.7.tgz#3bc2a85ea742d9e36205dcacdd72ca1fdc51b055" integrity sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA== -is-core-module@^2.11.0: - version "2.12.1" - resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.12.1.tgz#0c0b6885b6f80011c71541ce15c8d66cf5a4f9fd" - integrity sha512-Q4ZuBAe2FUsKtyQJoQHlvP8OvBERxO3jEmy1I7hcRXcJBGGHFh/aJBswbXuS9sgrDH2QUO8ilkwNPHvHMd8clg== +is-core-module@^2.13.0: + version "2.13.1" + resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.13.1.tgz#ad0d7532c6fea9da1ebdc82742d74525c6273384" + integrity sha512-hHrIjvZsftOsvKSn2TRYl63zvxsgE0K+0mYMoH6gD4omR5IWB2KynivBQczo3+wF1cCkjzvptnI9Q0sPU66ilw== dependencies: - has "^1.0.3" + hasown "^2.0.0" + +is-data-view@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/is-data-view/-/is-data-view-1.0.1.tgz#4b4d3a511b70f3dc26d42c03ca9ca515d847759f" + integrity sha512-AHkaJrsUVW6wq6JS8y3JnM/GJF/9cf+k20+iDzlSaJrinEo5+7vRiteOSwBhHRiAyQATN1AmY4hwzxJKPmYf+w== + dependencies: + is-typed-array "^1.1.13" is-date-object@^1.0.1: version "1.0.5" @@ -2809,10 +2891,10 @@ is-nan@^1.3.0: call-bind "^1.0.0" define-properties "^1.1.3" -is-negative-zero@^2.0.2: - version "2.0.2" - resolved "https://registry.yarnpkg.com/is-negative-zero/-/is-negative-zero-2.0.2.tgz#7bf6f03a28003b8b3965de3ac26f664d765f3150" - integrity sha512-dqJvarLawXsFbNDeJW7zAz8ItJ9cd28YufuuFzh0G8pNHjJMnY08Dv7sYX2uF5UpQOwieAeOExEYAWWfu7ZZUA== +is-negative-zero@^2.0.3: + version "2.0.3" + resolved "https://registry.yarnpkg.com/is-negative-zero/-/is-negative-zero-2.0.3.tgz#ced903a027aca6381b777a5743069d7376a49747" + integrity sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw== is-number-object@^1.0.4: version "1.0.7" @@ -2839,12 +2921,12 @@ is-resolvable@^1.0.0: resolved "https://registry.yarnpkg.com/is-resolvable/-/is-resolvable-1.1.0.tgz#fb18f87ce1feb925169c9a407c19318a3206ed88" integrity sha512-qgDYXFSR5WvEfuS5dMj6oTMEbrrSaM0CrFk2Yiq/gXnBvD9pMa2jGXxyhGLfvhZpuMZe18CJpFxAt3CRs42NMg== -is-shared-array-buffer@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/is-shared-array-buffer/-/is-shared-array-buffer-1.0.2.tgz#8f259c573b60b6a32d4058a1a07430c0a7344c79" - integrity sha512-sqN2UDu1/0y6uvXyStCOzyhAjCSlHceFoMKJW8W9EU9cvic/QdsZ0kEU93HEy3IUEFZIiH/3w+AH/UQbPHNdhA== +is-shared-array-buffer@^1.0.2, is-shared-array-buffer@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/is-shared-array-buffer/-/is-shared-array-buffer-1.0.3.tgz#1237f1cba059cdb62431d378dcc37d9680181688" + integrity sha512-nA2hv5XIhLR3uVzDDfCIknerhx8XUKnstuOERPNNIinXG7v9u+ohXF67vxm4TPTEPU6lm61ZkwP3c9PCB97rhg== dependencies: - call-bind "^1.0.2" + call-bind "^1.0.7" is-stream@^2.0.0: version "2.0.1" @@ -2865,12 +2947,12 @@ is-symbol@^1.0.2, is-symbol@^1.0.3: dependencies: has-symbols "^1.0.2" -is-typed-array@^1.1.10, is-typed-array@^1.1.3, is-typed-array@^1.1.9: - version "1.1.12" - resolved "https://registry.yarnpkg.com/is-typed-array/-/is-typed-array-1.1.12.tgz#d0bab5686ef4a76f7a73097b95470ab199c57d4a" - integrity sha512-Z14TF2JNG8Lss5/HMqt0//T9JeHXttXy5pH/DBU4vi98ozO2btxzq9MwYDZYnKwU8nRsz/+GVFVRDq3DkVuSPg== +is-typed-array@^1.1.13, is-typed-array@^1.1.3: + version "1.1.13" + resolved "https://registry.yarnpkg.com/is-typed-array/-/is-typed-array-1.1.13.tgz#d6c5ca56df62334959322d7d7dd1cca50debe229" + integrity sha512-uZ25/bUAlUY5fR4OKT4rZQEBrzQWYV9ZJYGGsUmEJ6thodVJ1HX64ePQ6Z0qPWP+m+Uq6e9UugrE38jeYsDSMw== dependencies: - which-typed-array "^1.1.11" + which-typed-array "^1.1.14" is-typedarray@^1.0.0, is-typedarray@~1.0.0: version "1.0.0" @@ -3046,13 +3128,13 @@ jmespath@0.16.0: integrity sha512-9FzQjJ7MATs1tSpnco1K6ayiYE3figslrXA72G2HQ/n76RzvYlofyi5QM+iX4YRs/pu3yzxlVQSST23+dMDknw== joi@^17.6.0: - version "17.9.2" - resolved "https://registry.yarnpkg.com/joi/-/joi-17.9.2.tgz#8b2e4724188369f55451aebd1d0b1d9482470690" - integrity sha512-Itk/r+V4Dx0V3c7RLFdRh12IOjySm2/WGPMubBT92cQvRfYZhPM2W0hZlctjj72iES8jsRCwp7S/cRmWBnJ4nw== + version "17.13.0" + resolved "https://registry.yarnpkg.com/joi/-/joi-17.13.0.tgz#b6f340b8029ee7af2397f821d17a4f03bf34b043" + integrity sha512-9qcrTyoBmFZRNHeVP4edKqIUEgFzq7MHvTNSDuHSqkpOPtiBkgNgcmTSqmiw1kw9tdKaiddvIDv/eCJDxmqWCA== dependencies: - "@hapi/hoek" "^9.0.0" - "@hapi/topo" "^5.0.0" - "@sideway/address" "^4.1.3" + "@hapi/hoek" "^9.3.0" + "@hapi/topo" "^5.1.0" + "@sideway/address" "^4.1.5" "@sideway/formula" "^3.0.1" "@sideway/pinpoint" "^2.0.0" @@ -3074,6 +3156,11 @@ js-yaml@3.x, js-yaml@^3.13.1, js-yaml@^3.14.0, js-yaml@^3.5.1, js-yaml@^3.7.0: argparse "^1.0.7" esprima "^4.0.0" +jsbn@1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/jsbn/-/jsbn-1.1.0.tgz#b01307cb29b618a1ed26ec79e911f803c4da0040" + integrity sha512-4bYVV3aAMtDTTu4+xsDYa6sy9GyJ69/amsu9sYF2zqjiEoZA5xJi3BrfX3uY+/IekIu7MwdObdbDWpoZdBv3/A== + jsbn@~0.1.0: version "0.1.1" resolved "https://registry.yarnpkg.com/jsbn/-/jsbn-0.1.1.tgz#a5e654c2e5a2deb5f201d96cefbca80c0ef2f513" @@ -3121,11 +3208,14 @@ json-schema@0.4.0, json-schema@~0.4.0: integrity sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA== json-stable-stringify@^1.0.0, json-stable-stringify@^1.0.1: - version "1.0.2" - resolved "https://registry.yarnpkg.com/json-stable-stringify/-/json-stable-stringify-1.0.2.tgz#e06f23128e0bbe342dc996ed5a19e28b57b580e0" - integrity sha512-eunSSaEnxV12z+Z73y/j5N37/In40GK4GmsSy+tEHJMxknvqnA7/djeYtAgW0GsWHUfg+847WJjKaEylk2y09g== + version "1.1.1" + resolved "https://registry.yarnpkg.com/json-stable-stringify/-/json-stable-stringify-1.1.1.tgz#52d4361b47d49168bcc4e564189a42e5a7439454" + integrity sha512-SU/971Kt5qVQfJpyDveVhQ/vya+5hvrjClFOcr8c0Fq5aODJjMwutrOfCU+eCnVD5gpx1Q3fEqkyom77zH1iIg== dependencies: + call-bind "^1.0.5" + isarray "^2.0.5" jsonify "^0.0.1" + object-keys "^1.1.1" json-stringify-safe@~5.0.1: version "5.0.1" @@ -3178,10 +3268,10 @@ jsprim@^1.2.2: json-schema "0.4.0" verror "1.10.0" -just-extend@^4.0.2: - version "4.2.1" - resolved "https://registry.yarnpkg.com/just-extend/-/just-extend-4.2.1.tgz#ef5e589afb61e5d66b24eca749409a8939a8c744" - integrity sha512-g3UB796vUFIY90VIv/WX3L2c8CS2MdWUww3CNrYmqza1Fg0DURc2K/O4YrnklBdQarSJ/y8JnJYDGc+1iumQjg== +just-extend@^6.2.0: + version "6.2.0" + resolved "https://registry.yarnpkg.com/just-extend/-/just-extend-6.2.0.tgz#b816abfb3d67ee860482e7401564672558163947" + integrity sha512-cYofQu2Xpom82S6qD778jBDpwvvy39s1l/hrYij2u9AMdQcGRpaBu6kY4mVhuno5kJVi1DAz4aiphA2WI1/OAw== jwa@^1.4.1: version "1.4.1" @@ -3447,11 +3537,11 @@ lodash@^4.0.0, lodash@^4.14.0, lodash@^4.17.13, lodash@^4.17.14, lodash@^4.17.15 integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== logform@^2.3.2, logform@^2.4.0: - version "2.5.1" - resolved "https://registry.yarnpkg.com/logform/-/logform-2.5.1.tgz#44c77c34becd71b3a42a3970c77929e52c6ed48b" - integrity sha512-9FyqAm9o9NKKfiAKfZoYo9bGXXuwMkxQiQttkT4YjjVtQVIQtK6LmVtlxmCaFswo6N4AfEkHqZTV0taDtPotNg== + version "2.6.0" + resolved "https://registry.yarnpkg.com/logform/-/logform-2.6.0.tgz#8c82a983f05d6eaeb2d75e3decae7a768b2bf9b5" + integrity sha512-1ulHeNPp6k/LD8H91o7VYFBng5i1BDE7HoKxVbZiGFidS1Rj65qcywLxX+pVfAPoQJEjRdvKcusKwOupHCVOVQ== dependencies: - "@colors/colors" "1.5.0" + "@colors/colors" "1.6.0" "@types/triple-beam" "^1.3.2" fecha "^4.2.0" ms "^2.1.1" @@ -3807,16 +3897,16 @@ mocha@^2.3.4: to-iso-string "0.0.2" moment-timezone@^0.5.31: - version "0.5.43" - resolved "https://registry.yarnpkg.com/moment-timezone/-/moment-timezone-0.5.43.tgz#3dd7f3d0c67f78c23cd1906b9b2137a09b3c4790" - integrity sha512-72j3aNyuIsDxdF1i7CEgV2FfxM1r6aaqJyLB2vwb33mXYyoyLly+F1zbWqhA3/bVIoJ4szlUoMbUnVdid32NUQ== + version "0.5.45" + resolved "https://registry.yarnpkg.com/moment-timezone/-/moment-timezone-0.5.45.tgz#cb685acd56bac10e69d93c536366eb65aa6bcf5c" + integrity sha512-HIWmqA86KcmCAhnMAN0wuDOARV/525R2+lOLotuGFzn4HO+FH+/645z2wx0Dt3iDv6/p61SIvKnDstISainhLQ== dependencies: moment "^2.29.4" moment@^2.26.0, moment@^2.29.4: - version "2.29.4" - resolved "https://registry.yarnpkg.com/moment/-/moment-2.29.4.tgz#3dbe052889fe7c1b2ed966fcb3a77328964ef108" - integrity sha512-5LC9SOxjSc2HF6vO2CyuTDNivEdoz2IvyJJGj6X8DJ0eFyfszE0QiEd+iXmBvUP3WHxSjFH/vIsA0EN00cgr8w== + version "2.30.1" + resolved "https://registry.yarnpkg.com/moment/-/moment-2.30.1.tgz#f8c91c07b7a786e30c59926df530b4eac96974ae" + integrity sha512-uEmtNhbDOrWPFS+hdjFCBfy9f2YoyzRpwcl+DqpC6taX21FzsTLQVbMV/W7PzNSX6x/bhC1zA3c2UQ5NzH6how== mongodb@^3.0.1: version "3.7.4" @@ -3856,10 +3946,10 @@ mute-stream@0.0.5: resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-0.0.5.tgz#8fbfabb0a98a253d3184331f9e8deb7372fac6c0" integrity sha512-EbrziT4s8cWPmzr47eYVW3wimS4HsvlnV5ri1xw1aR6JQo/OrJX5rkl32K/QQHdxeabJETtfeaROGhd8W7uBgg== -nan@^2.14.0, nan@^2.3.2: - version "2.17.0" - resolved "https://registry.yarnpkg.com/nan/-/nan-2.17.0.tgz#c0150a2368a182f033e9aa5195ec76ea41a199cb" - integrity sha512-2ZTgtl0nJsO0KQCjEpxcIr5D+Yv90plTitZt9JBfQvVJDS5seMl3FOvsh3+9CoYWXf/1l5OaZzzF6nDm4cagaQ== +nan@^2.14.0, nan@^2.18.0, nan@^2.3.2: + version "2.19.0" + resolved "https://registry.yarnpkg.com/nan/-/nan-2.19.0.tgz#bb58122ad55a6c5bc973303908d5b16cfdd5a8c0" + integrity sha512-nO1xXxfh/RWNxfd/XPfbIfFk5vgLsAxUR9y5O0cHMJu/AW9U95JLXqthYHjEp+8gQ5p96K9jUp8nbVOxCdRbtw== napi-macros@~2.0.0: version "2.0.0" @@ -3880,7 +3970,7 @@ negotiator@0.6.3, negotiator@^0.6.2: resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.3.tgz#58e323a72fedc0d6f9cd4d31fe49f51479590ccd" integrity sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg== -neo-async@^2.6.0: +neo-async@^2.6.2: version "2.6.2" resolved "https://registry.yarnpkg.com/neo-async/-/neo-async-2.6.2.tgz#b4aafb93e3aeb2d8174ca53cf163ab7d7308305f" integrity sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw== @@ -3896,20 +3986,20 @@ nice-try@^1.0.4: integrity sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ== nise@^5.1.1: - version "5.1.4" - resolved "https://registry.yarnpkg.com/nise/-/nise-5.1.4.tgz#491ce7e7307d4ec546f5a659b2efe94a18b4bbc0" - integrity sha512-8+Ib8rRJ4L0o3kfmyVCL7gzrohyDe0cMFTBa2d364yIrEGMEoetznKJx899YxjybU6bL9SQkYPSBBs1gyYs8Xg== + version "5.1.9" + resolved "https://registry.yarnpkg.com/nise/-/nise-5.1.9.tgz#0cb73b5e4499d738231a473cd89bd8afbb618139" + integrity sha512-qOnoujW4SV6e40dYxJOb3uvuoPHtmLzIk4TFo+j0jPJoC+5Z9xja5qH5JZobEPsa8+YYphMrOSwnrshEhG2qww== dependencies: - "@sinonjs/commons" "^2.0.0" - "@sinonjs/fake-timers" "^10.0.2" - "@sinonjs/text-encoding" "^0.7.1" - just-extend "^4.0.2" - path-to-regexp "^1.7.0" + "@sinonjs/commons" "^3.0.0" + "@sinonjs/fake-timers" "^11.2.2" + "@sinonjs/text-encoding" "^0.7.2" + just-extend "^6.2.0" + path-to-regexp "^6.2.1" node-fetch@^2.3.0: - version "2.6.12" - resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.12.tgz#02eb8e22074018e3d5a83016649d04df0e348fba" - integrity sha512-C/fGU2E8ToujUivIO0H+tpQ6HWo4eEmchoPIoXtxCrVghxdKq+QOHqEZW7tuP3KlV3bC8FRMO5nMCC7Zm1VP6g== + version "2.7.0" + resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.7.0.tgz#d0f0fa6e3e2dc1d27efcd8ad99d550bda94d187d" + integrity sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A== dependencies: whatwg-url "^5.0.0" @@ -4055,23 +4145,23 @@ object-component@0.0.3: resolved "https://registry.yarnpkg.com/object-component/-/object-component-0.0.3.tgz#f0c69aa50efc95b866c186f400a33769cb2f1291" integrity sha512-S0sN3agnVh2SZNEIGc0N1X4Z5K0JeFbGBrnuZpsxuUh5XLF0BnvWkMjRXo/zGKLd/eghvNIKcx1pQkmUjXIyrA== -object-inspect@^1.12.3, object-inspect@^1.9.0: - version "1.12.3" - resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.12.3.tgz#ba62dffd67ee256c8c086dfae69e016cd1f198b9" - integrity sha512-geUvdk7c+eizMNUDkRpW1wJwgfOiOeHbxBR/hLXK1aT6zmVSO0jsQcs7fj6MGw89jC/cjGfLcNOrtMYtGqm81g== +object-inspect@^1.13.1: + version "1.13.1" + resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.13.1.tgz#b96c6109324ccfef6b12216a956ca4dc2ff94bc2" + integrity sha512-5qoj1RUiKOMsCCNLV1CBiPYE10sziTsnmNxkAI/rZhiD63CF7IqdFGC/XzjWjpSgLf0LxXX3bDFIh0E18f6UhQ== object-keys@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/object-keys/-/object-keys-1.1.1.tgz#1c47f272df277f3b1daf061677d9c82e2322c60e" integrity sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA== -object.assign@^4.1.4: - version "4.1.4" - resolved "https://registry.yarnpkg.com/object.assign/-/object.assign-4.1.4.tgz#9673c7c7c351ab8c4d0b516f4343ebf4dfb7799f" - integrity sha512-1mxKf0e58bvyjSCtKYY4sRe9itRk3PJpquJOjeIkz885CczcI4IvJJDLPS72oowuSh+pBxUFROpX+TU++hxhZQ== +object.assign@^4.1.5: + version "4.1.5" + resolved "https://registry.yarnpkg.com/object.assign/-/object.assign-4.1.5.tgz#3a833f9ab7fdb80fc9e8d2300c803d216d8fdbb0" + integrity sha512-byy+U7gp+FVwmyzKPYhW2h5l3crpmGsxl7X2s8y43IgxvG4g3QZ6CffDtsNQy1WsmZpQbO+ybo0AlW7TY6DcBQ== dependencies: - call-bind "^1.0.2" - define-properties "^1.1.4" + call-bind "^1.0.5" + define-properties "^1.2.1" has-symbols "^1.0.3" object-keys "^1.1.1" @@ -4204,12 +4294,10 @@ path-to-regexp@0.1.7: resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-0.1.7.tgz#df604178005f522f15eb4490e7247a1bfaa67f8c" integrity sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ== -path-to-regexp@^1.7.0: - version "1.8.0" - resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-1.8.0.tgz#887b3ba9d84393e87a0a0b9f4cb756198b53548a" - integrity sha512-n43JRhlUKUAlibEJhPeir1ncUID16QnEjNpwzNdO3Lm4ywrBpBZ5oLD0I6br9evr1Y9JTqwRtAh7JLoOzAQdVA== - dependencies: - isarray "0.0.1" +path-to-regexp@^6.2.1: + version "6.2.2" + resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-6.2.2.tgz#324377a83e5049cbecadc5554d6a63a9a4866b36" + integrity sha512-GQX3SSMokngb36+whdpRXE+3f9V8UzyAorlYvOGx87ufGHehNTn5lCxrKtLyZ4Yl/wEKnNnr98ZzOwwDZV5ogw== path-type@^3.0.0: version "3.0.0" @@ -4243,6 +4331,11 @@ pluralize@^1.2.1: resolved "https://registry.yarnpkg.com/pluralize/-/pluralize-1.2.1.tgz#d1a21483fd22bb41e58a12fa3421823140897c45" integrity sha512-TH+BeeL6Ct98C7as35JbZLf8lgsRzlNJb5gklRIGHKaPkGl1esOKBc5ALUMd+q08Sr6tiEKM+Icbsxg5vuhMKQ== +possible-typed-array-names@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/possible-typed-array-names/-/possible-typed-array-names-1.0.0.tgz#89bb63c6fada2c3e90adc4a647beeeb39cc7bf8f" + integrity sha512-d7Uw+eZoloe0EHDIYoe+bQ5WXnGMOpmiZFTuMWCwpjzzkL2nTjcKiAk4hh8TjnGye2TwWOk3UXucZ+3rbmBa8Q== + prelude-ls@~1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.1.2.tgz#21932a549f5e52ffd9a827f570e04be62a97da54" @@ -4360,9 +4453,9 @@ punycode@1.3.2: integrity sha512-RofWgt/7fL5wP1Y7fxE7/EmTLzQVnB0ycyibJ0OOHIlJqTNzglYFxVwETOcIoJqJmpDXJ9xImDv+Fq34F/d4Dw== punycode@^2.1.0, punycode@^2.1.1: - version "2.3.0" - resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.3.0.tgz#f67fa67c94da8f4d0cfff981aee4118064199b8f" - integrity sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA== + version "2.3.1" + resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.3.1.tgz#027422e2faec0b25e1549c3e1bd8309b9133b6e5" + integrity sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg== qs@6.11.0: version "6.11.0" @@ -4386,16 +4479,6 @@ range-parser@~1.2.1: resolved "https://registry.yarnpkg.com/range-parser/-/range-parser-1.2.1.tgz#3cf37023d199e1c24d1a55b84800c2f3e6468031" integrity sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg== -raw-body@2.5.1: - version "2.5.1" - resolved "https://registry.yarnpkg.com/raw-body/-/raw-body-2.5.1.tgz#fe1b1628b181b700215e5fd42389f98b71392857" - integrity sha512-qqJBtEyVgS0ZmPGdCFPWJ3FreoqvG4MVQln/kCgF7Olq95IbOp0/BWyMwbdtn4VTvkM8Y7khCQ2Xgk/tcrCXig== - dependencies: - bytes "3.1.2" - http-errors "2.0.0" - iconv-lite "0.4.24" - unpipe "1.0.0" - raw-body@2.5.2: version "2.5.2" resolved "https://registry.yarnpkg.com/raw-body/-/raw-body-2.5.2.tgz#99febd83b90e08975087e8f1f9419a149366b68a" @@ -4483,14 +4566,15 @@ regenerator-runtime@^0.11.0: resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz#be05ad7f9bf7d22e056f9726cee5017fbf19e2e9" integrity sha512-MguG95oij0fC3QV3URf4V2SDYGJhJnJGqvIIgdECeODCT98wSWDAJ94SSuVpYQUoTcGUIL6L4yNB7j1DFFHSBg== -regexp.prototype.flags@^1.5.0: - version "1.5.0" - resolved "https://registry.yarnpkg.com/regexp.prototype.flags/-/regexp.prototype.flags-1.5.0.tgz#fe7ce25e7e4cca8db37b6634c8a2c7009199b9cb" - integrity sha512-0SutC3pNudRKgquxGoRGIz946MZVHqbNfPjBdxeOhBrdgDKlRoXmYLQN9xRbrR09ZXWeGAdPuif7egofn6v5LA== +regexp.prototype.flags@^1.5.2: + version "1.5.2" + resolved "https://registry.yarnpkg.com/regexp.prototype.flags/-/regexp.prototype.flags-1.5.2.tgz#138f644a3350f981a858c44f6bb1a61ff59be334" + integrity sha512-NcDiDkTLuPR+++OCKB0nWafEmhg/Da8aUPLPMQbK+bxKKCm1/S5he+AqYa4PlMCVBalb4/yxIRub6qkEx5yJbw== dependencies: - call-bind "^1.0.2" - define-properties "^1.2.0" - functions-have-names "^1.2.3" + call-bind "^1.0.6" + define-properties "^1.2.1" + es-errors "^1.3.0" + set-function-name "^2.0.1" repeating@^2.0.0: version "2.0.1" @@ -4549,11 +4633,11 @@ resolve-from@^1.0.0: integrity sha512-kT10v4dhrlLNcnO084hEjvXCI1wUG9qZLoz2RogxqDQQYy7IxjI/iMUkOtQTNEh6rzHxvdQWHsJyel1pKOVCxg== resolve@^1.10.0: - version "1.22.2" - resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.2.tgz#0ed0943d4e301867955766c9f3e1ae6d01c6845f" - integrity sha512-Sb+mjNHOULsBv818T40qSPeRiuWLyaGMa5ewydRLFimneixmVy2zdivRl+AF6jaYPC8ERxGDmFSiqui6SfPd+g== + version "1.22.8" + resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.8.tgz#b6c87a9f2aa06dfab52e3d70ac8cde321fa5a48d" + integrity sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw== dependencies: - is-core-module "^2.11.0" + is-core-module "^2.13.0" path-parse "^1.0.7" supports-preserve-symlinks-flag "^1.0.0" @@ -4608,13 +4692,13 @@ rx-lite@^3.1.2: resolved "https://registry.yarnpkg.com/rx-lite/-/rx-lite-3.1.2.tgz#19ce502ca572665f3b647b10939f97fd1615f102" integrity sha512-1I1+G2gteLB8Tkt8YI1sJvSIfa0lWuRtC8GjvtyPBcLSF5jBCCJJqKrpER5JU5r6Bhe+i9/pK3VMuUcXu0kdwQ== -safe-array-concat@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/safe-array-concat/-/safe-array-concat-1.0.0.tgz#2064223cba3c08d2ee05148eedbc563cd6d84060" - integrity sha512-9dVEFruWIsnie89yym+xWTAYASdpw3CJV7Li/6zBewGf9z2i1j31rP6jnY0pHEO4QZh6N0K11bFjWmdR8UGdPQ== +safe-array-concat@^1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/safe-array-concat/-/safe-array-concat-1.1.2.tgz#81d77ee0c4e8b863635227c721278dd524c20edb" + integrity sha512-vj6RsCsWBCf19jIeHEfkRMw8DPiBb+DMXklQ/1SGDHOMlHdPUkZXFQ2YdplS23zESTijAcurb1aSgJA3AgMu1Q== dependencies: - call-bind "^1.0.2" - get-intrinsic "^1.2.0" + call-bind "^1.0.7" + get-intrinsic "^1.2.4" has-symbols "^1.0.3" isarray "^2.0.5" @@ -4638,13 +4722,13 @@ safe-json-stringify@^1.0.3, safe-json-stringify@^1.2.0: resolved "https://registry.yarnpkg.com/safe-json-stringify/-/safe-json-stringify-1.2.0.tgz#356e44bc98f1f93ce45df14bcd7c01cda86e0afd" integrity sha512-gH8eh2nZudPQO6TytOvbxnuhYBOvDBBLW52tz5q6X58lJcd/tkmqFR+5Z9adS8aJtURSXWThWy/xJtJwixErvg== -safe-regex-test@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/safe-regex-test/-/safe-regex-test-1.0.0.tgz#793b874d524eb3640d1873aad03596db2d4f2295" - integrity sha512-JBUUzyOgEwXQY1NuPtvcj/qcBDbDmEvWufhlnXZIm75DEHp+afM1r1ujJpJsV/gSM4t59tpDyPi1sd6ZaPFfsA== +safe-regex-test@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/safe-regex-test/-/safe-regex-test-1.0.3.tgz#a5b4c0f06e0ab50ea2c395c14d8371232924c377" + integrity sha512-CdASjNJPvRa7roO6Ra/gLYBTzYzzPyyBXxIMdGW3USQLyjWEls2RgW5UBTXaQVp+OrpeCK3bLem8smtmheoRuw== dependencies: - call-bind "^1.0.2" - get-intrinsic "^1.1.3" + call-bind "^1.0.6" + es-errors "^1.3.0" is-regex "^1.1.4" safe-stable-stringify@^2.3.1: @@ -4675,9 +4759,9 @@ sax@1.2.1: integrity sha512-8I2a3LovHTOpm7NV5yOyO8IHqgVsfK4+UuySrXU8YXkSRX7k6hCV9b3HrkKCr3nMpgj+0bmocaJJWpvp1oc7ZA== sax@>=0.6.0, sax@^1.2.4: - version "1.2.4" - resolved "https://registry.yarnpkg.com/sax/-/sax-1.2.4.tgz#2816234e2378bddc4e5354fab5caa895df7100d9" - integrity sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw== + version "1.3.0" + resolved "https://registry.yarnpkg.com/sax/-/sax-1.3.0.tgz#a5dbe77db3be05c9d1ee7785dbd3ea9de51593d0" + integrity sha512-0s+oAmw9zLl1V1cS9BtZN7JAd0cW5e0QH4W3LWEK6a4LaLEA2OTpGYWDY+6XasBLtz6wkm3u1xRw95mRuJ59WA== "semver@2 || 3 || 4 || 5", semver@^5.3.0, semver@^5.5.0, semver@^5.6.0: version "5.7.2" @@ -4685,9 +4769,9 @@ sax@>=0.6.0, sax@^1.2.4: integrity sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g== semver@^7.3.5: - version "7.5.4" - resolved "https://registry.yarnpkg.com/semver/-/semver-7.5.4.tgz#483986ec4ed38e1c6c48c34894a9182dbff68a6e" - integrity sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA== + version "7.6.0" + resolved "https://registry.yarnpkg.com/semver/-/semver-7.6.0.tgz#1a46a4db4bffcccd97b743b5005c8325f23d4e2d" + integrity sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg== dependencies: lru-cache "^6.0.0" @@ -4730,6 +4814,28 @@ set-blocking@^2.0.0: resolved "https://registry.yarnpkg.com/set-blocking/-/set-blocking-2.0.0.tgz#045f9782d011ae9a6803ddd382b24392b3d890f7" integrity sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw== +set-function-length@^1.2.1: + version "1.2.2" + resolved "https://registry.yarnpkg.com/set-function-length/-/set-function-length-1.2.2.tgz#aac72314198eaed975cf77b2c3b6b880695e5449" + integrity sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg== + dependencies: + define-data-property "^1.1.4" + es-errors "^1.3.0" + function-bind "^1.1.2" + get-intrinsic "^1.2.4" + gopd "^1.0.1" + has-property-descriptors "^1.0.2" + +set-function-name@^2.0.1: + version "2.0.2" + resolved "https://registry.yarnpkg.com/set-function-name/-/set-function-name-2.0.2.tgz#16a705c5a0dc2f5e638ca96d8a8cd4e1c2b90985" + integrity sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ== + dependencies: + define-data-property "^1.1.4" + es-errors "^1.3.0" + functions-have-names "^1.2.3" + has-property-descriptors "^1.0.2" + setprototypeof@1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.2.0.tgz#66c9a24a73f9fc28cbe66b09fed3d33dcaf1b424" @@ -4758,13 +4864,14 @@ shelljs@^0.6.0: integrity sha512-B1vvzXQlJ77SURr3SIUQ/afh+LwecDKAVKE1wqkBlr2PCHoZDaF6MFD+YX1u9ddQjR4z2CKx1tdqvS2Xfs5h1A== side-channel@^1.0.4: - version "1.0.4" - resolved "https://registry.yarnpkg.com/side-channel/-/side-channel-1.0.4.tgz#efce5c8fdc104ee751b25c58d4290011fa5ea2cf" - integrity sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw== + version "1.0.6" + resolved "https://registry.yarnpkg.com/side-channel/-/side-channel-1.0.6.tgz#abd25fb7cd24baf45466406b1096b7831c9215f2" + integrity sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA== dependencies: - call-bind "^1.0.0" - get-intrinsic "^1.0.2" - object-inspect "^1.9.0" + call-bind "^1.0.7" + es-errors "^1.3.0" + get-intrinsic "^1.2.4" + object-inspect "^1.13.1" sigmund@~1.0.0: version "1.0.1" @@ -4821,10 +4928,11 @@ socket.io-adapter@~1.1.0: integrity sha512-WzZRUj1kUjrTIrUKpZLEzFZ1OLj5FwLlAFQs9kuZJzJi5DKdU7FsWc36SNmA8iDOtwBQyT8FkrriRM8vXLYz8g== socket.io-adapter@~2.5.2: - version "2.5.2" - resolved "https://registry.yarnpkg.com/socket.io-adapter/-/socket.io-adapter-2.5.2.tgz#5de9477c9182fdc171cd8c8364b9a8894ec75d12" - integrity sha512-87C3LO/NOMc+eMcpcxUBebGjkpMDkNBS9tf7KJqcDsmL936EChtVva71Dw2q4tQcuVC+hAUy4an2NO/sYXmwRA== + version "2.5.4" + resolved "https://registry.yarnpkg.com/socket.io-adapter/-/socket.io-adapter-2.5.4.tgz#4fdb1358667f6d68f25343353bd99bd11ee41006" + integrity sha512-wDNHGXGewWAjQPt3pyeYBtpWSq9cLE5UW1ZUPL/2eGK9jtse/FpXib7epSTsz0Q0m+6sg6Y4KtcFTlah1bdOVg== dependencies: + debug "~4.3.4" ws "~8.11.0" socket.io-client@2.3.0: @@ -4934,11 +5042,11 @@ socks-proxy-agent@^6.0.0: socks "^2.6.2" socks@^2.6.2: - version "2.7.1" - resolved "https://registry.yarnpkg.com/socks/-/socks-2.7.1.tgz#d8e651247178fde79c0663043e07240196857d55" - integrity sha512-7maUZy1N7uo6+WVEX6psASxtNlKaNVMlGQKkG/63nEDdLOWNbiUMoLK7X4uYoLhQstau72mLgfEWcXcwsaHbYQ== + version "2.8.3" + resolved "https://registry.yarnpkg.com/socks/-/socks-2.8.3.tgz#1ebd0f09c52ba95a09750afe3f3f9f724a800cb5" + integrity sha512-l5x7VUUWbjVFbafGLxPWkYsHIhEvmF85tbIeFZWc8ZPtoMyybuEhL7Jye/ooC4/d48FgOjSJXgsF/AJPYCW8Zw== dependencies: - ip "^2.0.0" + ip-address "^9.0.5" smart-buffer "^4.2.0" sorted-array-functions@^1.3.0: @@ -4972,9 +5080,9 @@ spdx-correct@^3.0.0: spdx-license-ids "^3.0.0" spdx-exceptions@^2.1.0: - version "2.3.0" - resolved "https://registry.yarnpkg.com/spdx-exceptions/-/spdx-exceptions-2.3.0.tgz#3f28ce1a77a00372683eade4a433183527a2163d" - integrity sha512-/tTrYOC7PPI1nUAgx34hUpqXuyJG+DTHJTnIULG4rDygi4xu/tfgmq1e1cIRwRzwZgo4NLySi+ricLkZkw4i5A== + version "2.5.0" + resolved "https://registry.yarnpkg.com/spdx-exceptions/-/spdx-exceptions-2.5.0.tgz#5d607d27fc806f66d7b64a766650fa890f04ed66" + integrity sha512-PiU42r+xO4UbUS1buo3LPJkjlO7430Xn5SVAhdpzzsPHsjbYVflnnFdATgabnLude+Cqu25p6N+g2lw/PFsa4w== spdx-expression-parse@^3.0.0: version "3.0.1" @@ -4985,9 +5093,14 @@ spdx-expression-parse@^3.0.0: spdx-license-ids "^3.0.0" spdx-license-ids@^3.0.0: - version "3.0.13" - resolved "https://registry.yarnpkg.com/spdx-license-ids/-/spdx-license-ids-3.0.13.tgz#7189a474c46f8d47c7b0da4b987bb45e908bd2d5" - integrity sha512-XkD+zwiqXHikFZm4AX/7JSCXA98U5Db4AFd5XUg/+9UNtnH75+Z9KxtpYiJZx36mUDVOwH83pl7yvCer6ewM3w== + version "3.0.17" + resolved "https://registry.yarnpkg.com/spdx-license-ids/-/spdx-license-ids-3.0.17.tgz#887da8aa73218e51a1d917502d79863161a93f9c" + integrity sha512-sh8PWc/ftMqAAdFiBu6Fy6JUOYjqDJBJvIhpfDMyHrr0Rbp5liZqd4TjtQ/RgfLjKFZb+LMx5hpml5qOWy0qvg== + +sprintf-js@^1.1.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.1.3.tgz#4914b903a2f8b685d17fdf78a70e917e872e444a" + integrity sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA== sprintf-js@~1.0.2: version "1.0.3" @@ -5002,9 +5115,9 @@ sprintf-js@~1.0.2: werelogs scality/werelogs#8.1.0 sshpk@^1.7.0: - version "1.17.0" - resolved "https://registry.yarnpkg.com/sshpk/-/sshpk-1.17.0.tgz#578082d92d4fe612b13007496e543fa0fbcbe4c5" - integrity sha512-/9HIEs1ZXGhSPE8X6Ccm7Nam1z8KcoCqPdI7ecm1N33EzAetWahvQWVqLZtaZQ+IDKX4IyA2o0gBzqIMkAagHQ== + version "1.18.0" + resolved "https://registry.yarnpkg.com/sshpk/-/sshpk-1.18.0.tgz#1663e55cddf4d688b86a46b77f0d5fe363aba028" + integrity sha512-2p2KJZTSqQ/I3+HX42EpYOa2l3f8Erv8MWKsy2I9uf4wA7yFIkXRffYdsx86y6z4vHtV8u7g+pPlr8/4ouAxsQ== dependencies: asn1 "~0.2.3" assert-plus "^1.0.0" @@ -5046,6 +5159,11 @@ stream-to-pull-stream@^1.7.1: looper "^3.0.0" pull-stream "^3.2.3" +streamsearch@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/streamsearch/-/streamsearch-1.1.0.tgz#404dd1e2247ca94af554e841a8ef0eaa238da764" + integrity sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg== + string-width@^1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/string-width/-/string-width-1.0.2.tgz#118bdf5b8cdc51a2a7e70d211e07e2b0b9b107d3" @@ -5073,40 +5191,42 @@ string-width@^2.0.0: strip-ansi "^4.0.0" string.prototype.padend@^3.0.0: - version "3.1.4" - resolved "https://registry.yarnpkg.com/string.prototype.padend/-/string.prototype.padend-3.1.4.tgz#2c43bb3a89eb54b6750de5942c123d6c98dd65b6" - integrity sha512-67otBXoksdjsnXXRUq+KMVTdlVRZ2af422Y0aTyTjVaoQkGr3mxl2Bc5emi7dOQ3OGVVQQskmLEWwFXwommpNw== + version "3.1.6" + resolved "https://registry.yarnpkg.com/string.prototype.padend/-/string.prototype.padend-3.1.6.tgz#ba79cf8992609a91c872daa47c6bb144ee7f62a5" + integrity sha512-XZpspuSB7vJWhvJc9DLSlrXl1mcA2BdoY5jjnS135ydXqLoqhs96JjDtCkjJEQHvfqZIp9hBuBMgI589peyx9Q== dependencies: - call-bind "^1.0.2" - define-properties "^1.1.4" - es-abstract "^1.20.4" + call-bind "^1.0.7" + define-properties "^1.2.1" + es-abstract "^1.23.2" + es-object-atoms "^1.0.0" -string.prototype.trim@^1.2.7: - version "1.2.7" - resolved "https://registry.yarnpkg.com/string.prototype.trim/-/string.prototype.trim-1.2.7.tgz#a68352740859f6893f14ce3ef1bb3037f7a90533" - integrity sha512-p6TmeT1T3411M8Cgg9wBTMRtY2q9+PNy9EV1i2lIXUN/btt763oIfxwN3RR8VU6wHX8j/1CFy0L+YuThm6bgOg== +string.prototype.trim@^1.2.9: + version "1.2.9" + resolved "https://registry.yarnpkg.com/string.prototype.trim/-/string.prototype.trim-1.2.9.tgz#b6fa326d72d2c78b6df02f7759c73f8f6274faa4" + integrity sha512-klHuCNxiMZ8MlsOihJhJEBJAiMVqU3Z2nEXWfWnIqjN0gEFS9J9+IxKozWWtQGcgoa1WUZzLjKPTr4ZHNFTFxw== dependencies: - call-bind "^1.0.2" - define-properties "^1.1.4" - es-abstract "^1.20.4" + call-bind "^1.0.7" + define-properties "^1.2.1" + es-abstract "^1.23.0" + es-object-atoms "^1.0.0" -string.prototype.trimend@^1.0.6: - version "1.0.6" - resolved "https://registry.yarnpkg.com/string.prototype.trimend/-/string.prototype.trimend-1.0.6.tgz#c4a27fa026d979d79c04f17397f250a462944533" - integrity sha512-JySq+4mrPf9EsDBEDYMOb/lM7XQLulwg5R/m1r0PXEFqrV0qHvl58sdTilSXtKOflCsK2E8jxf+GKC0T07RWwQ== +string.prototype.trimend@^1.0.8: + version "1.0.8" + resolved "https://registry.yarnpkg.com/string.prototype.trimend/-/string.prototype.trimend-1.0.8.tgz#3651b8513719e8a9f48de7f2f77640b26652b229" + integrity sha512-p73uL5VCHCO2BZZ6krwwQE3kCzM7NKmis8S//xEC6fQonchbum4eP6kR4DLEjQFO3Wnj3Fuo8NM0kOSjVdHjZQ== dependencies: - call-bind "^1.0.2" - define-properties "^1.1.4" - es-abstract "^1.20.4" + call-bind "^1.0.7" + define-properties "^1.2.1" + es-object-atoms "^1.0.0" -string.prototype.trimstart@^1.0.6: - version "1.0.6" - resolved "https://registry.yarnpkg.com/string.prototype.trimstart/-/string.prototype.trimstart-1.0.6.tgz#e90ab66aa8e4007d92ef591bbf3cd422c56bdcf4" - integrity sha512-omqjMDaY92pbn5HOX7f9IccLA+U1tA9GvtU4JrodiXFfYB7jPzzHpRzpglLAjtUV6bB557zwClJezTqnAiYnQA== +string.prototype.trimstart@^1.0.8: + version "1.0.8" + resolved "https://registry.yarnpkg.com/string.prototype.trimstart/-/string.prototype.trimstart-1.0.8.tgz#7ee834dda8c7c17eff3118472bb35bfedaa34dde" + integrity sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg== dependencies: - call-bind "^1.0.2" - define-properties "^1.1.4" - es-abstract "^1.20.4" + call-bind "^1.0.7" + define-properties "^1.2.1" + es-object-atoms "^1.0.0" string_decoder@^1.1.1: version "1.3.0" @@ -5214,9 +5334,9 @@ table@^3.7.8: string-width "^2.0.0" tar@^6.0.2, tar@^6.1.2: - version "6.1.15" - resolved "https://registry.yarnpkg.com/tar/-/tar-6.1.15.tgz#c9738b0b98845a3b344d334b8fa3041aaba53a69" - integrity sha512-/zKt9UyngnxIT/EAGYuxaMYgOIJiP81ab9ZfkILq4oNLPFX50qyYmu7jRj9qeXoxmJHjGlbH0+cm2uy1WCs10A== + version "6.2.1" + resolved "https://registry.yarnpkg.com/tar/-/tar-6.2.1.tgz#717549c541bc3c2af15751bea94b1dd068d4b03a" + integrity sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A== dependencies: chownr "^2.0.0" fs-minipass "^2.0.0" @@ -5286,9 +5406,13 @@ tr46@~0.0.3: integrity sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw== traverse@~0.6.6: - version "0.6.7" - resolved "https://registry.yarnpkg.com/traverse/-/traverse-0.6.7.tgz#46961cd2d57dd8706c36664acde06a248f1173fe" - integrity sha512-/y956gpUo9ZNCb99YjxG7OaslxZWHfCHAUUfshwqOXmxUIvqLjVO581BT+gM59+QV9tFe6/CGG53tsA1Y7RSdg== + version "0.6.9" + resolved "https://registry.yarnpkg.com/traverse/-/traverse-0.6.9.tgz#76cfdbacf06382d460b76f8b735a44a6209d8b81" + integrity sha512-7bBrcF+/LQzSgFmT0X5YclVqQxtv7TDJ1f8Wj7ibBu/U6BMLeOpUxuZjV7rMc44UtKxlnMFigdhFAIszSX1DMg== + dependencies: + gopd "^1.0.1" + typedarray.prototype.slice "^1.0.3" + which-typed-array "^1.1.15" trim-right@^1.0.1: version "1.0.1" @@ -5337,54 +5461,54 @@ type-is@^1.6.4, type-is@~1.6.18: media-typer "0.3.0" mime-types "~2.1.24" -type@^1.0.1: - version "1.2.0" - resolved "https://registry.yarnpkg.com/type/-/type-1.2.0.tgz#848dd7698dafa3e54a6c479e759c4bc3f18847a0" - integrity sha512-+5nt5AAniqsCnu2cEQQdpzCAh33kVx8n0VoFidKpB1dVVLAN/F+bgVOqOJqOnEnrhp222clB5p3vUlD+1QAnfg== - type@^2.7.2: version "2.7.2" resolved "https://registry.yarnpkg.com/type/-/type-2.7.2.tgz#2376a15a3a28b1efa0f5350dcf72d24df6ef98d0" integrity sha512-dzlvlNlt6AXU7EBSfpAscydQ7gXB+pPGsPnfJnZpiNJBDj7IaJzQlBZYGdEi4R9HmPdBv2XmWJ6YUtoTa7lmCw== -typed-array-buffer@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/typed-array-buffer/-/typed-array-buffer-1.0.0.tgz#18de3e7ed7974b0a729d3feecb94338d1472cd60" - integrity sha512-Y8KTSIglk9OZEr8zywiIHG/kmQ7KWyjseXs1CbSo8vC42w7hg2HgYTxSWwP0+is7bWDc1H+Fo026CpHFwm8tkw== +typed-array-buffer@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/typed-array-buffer/-/typed-array-buffer-1.0.2.tgz#1867c5d83b20fcb5ccf32649e5e2fc7424474ff3" + integrity sha512-gEymJYKZtKXzzBzM4jqa9w6Q1Jjm7x2d+sh19AdsD4wqnMPDYyvwpsIc2Q/835kHuo3BEQ7CjelGhfTsoBb2MQ== dependencies: - call-bind "^1.0.2" - get-intrinsic "^1.2.1" - is-typed-array "^1.1.10" + call-bind "^1.0.7" + es-errors "^1.3.0" + is-typed-array "^1.1.13" -typed-array-byte-length@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/typed-array-byte-length/-/typed-array-byte-length-1.0.0.tgz#d787a24a995711611fb2b87a4052799517b230d0" - integrity sha512-Or/+kvLxNpeQ9DtSydonMxCx+9ZXOswtwJn17SNLvhptaXYDJvkFFP5zbfU/uLmvnBJlI4yrnXRxpdWH/M5tNA== +typed-array-byte-length@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/typed-array-byte-length/-/typed-array-byte-length-1.0.1.tgz#d92972d3cff99a3fa2e765a28fcdc0f1d89dec67" + integrity sha512-3iMJ9q0ao7WE9tWcaYKIptkNBuOIcZCCT0d4MRvuuH88fEoEH62IuQe0OtraD3ebQEoTRk8XCBoknUNc1Y67pw== dependencies: - call-bind "^1.0.2" + call-bind "^1.0.7" for-each "^0.3.3" - has-proto "^1.0.1" - is-typed-array "^1.1.10" + gopd "^1.0.1" + has-proto "^1.0.3" + is-typed-array "^1.1.13" -typed-array-byte-offset@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/typed-array-byte-offset/-/typed-array-byte-offset-1.0.0.tgz#cbbe89b51fdef9cd6aaf07ad4707340abbc4ea0b" - integrity sha512-RD97prjEt9EL8YgAgpOkf3O4IF9lhJFr9g0htQkm0rchFp/Vx7LW5Q8fSXXub7BXAODyUQohRMyOc3faCPd0hg== +typed-array-byte-offset@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/typed-array-byte-offset/-/typed-array-byte-offset-1.0.2.tgz#f9ec1acb9259f395093e4567eb3c28a580d02063" + integrity sha512-Ous0vodHa56FviZucS2E63zkgtgrACj7omjwd/8lTEMEPFFyjfixMZ1ZXenpgCFBBt4EC1J2XsyVS2gkG0eTFA== dependencies: - available-typed-arrays "^1.0.5" - call-bind "^1.0.2" + available-typed-arrays "^1.0.7" + call-bind "^1.0.7" for-each "^0.3.3" - has-proto "^1.0.1" - is-typed-array "^1.1.10" + gopd "^1.0.1" + has-proto "^1.0.3" + is-typed-array "^1.1.13" -typed-array-length@^1.0.4: - version "1.0.4" - resolved "https://registry.yarnpkg.com/typed-array-length/-/typed-array-length-1.0.4.tgz#89d83785e5c4098bec72e08b319651f0eac9c1bb" - integrity sha512-KjZypGq+I/H7HI5HlOoGHkWUUGq+Q0TPhQurLbyrVrvnKTBgzLhIJ7j6J/XTQOi0d1RjyZ0wdas8bKs2p0x3Ng== +typed-array-length@^1.0.6: + version "1.0.6" + resolved "https://registry.yarnpkg.com/typed-array-length/-/typed-array-length-1.0.6.tgz#57155207c76e64a3457482dfdc1c9d1d3c4c73a3" + integrity sha512-/OxDN6OtAk5KBpGb28T+HZc2M+ADtvRxXrKKbUwtsLgdoxgX13hyy7ek6bFRl5+aBs2yZzB0c4CnQfAtVypW/g== dependencies: - call-bind "^1.0.2" + call-bind "^1.0.7" for-each "^0.3.3" - is-typed-array "^1.1.9" + gopd "^1.0.1" + has-proto "^1.0.3" + is-typed-array "^1.1.13" + possible-typed-array-names "^1.0.0" typedarray-to-buffer@~3.1.5: version "3.1.5" @@ -5393,6 +5517,18 @@ typedarray-to-buffer@~3.1.5: dependencies: is-typedarray "^1.0.0" +typedarray.prototype.slice@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/typedarray.prototype.slice/-/typedarray.prototype.slice-1.0.3.tgz#bce2f685d3279f543239e4d595e0d021731d2d1a" + integrity sha512-8WbVAQAUlENo1q3c3zZYuy5k9VzBQvp8AX9WOtbvyWlLM1v5JaSRmjubLjzHF4JFtptjH/5c/i95yaElvcjC0A== + dependencies: + call-bind "^1.0.7" + define-properties "^1.2.1" + es-abstract "^1.23.0" + es-errors "^1.3.0" + typed-array-buffer "^1.0.2" + typed-array-byte-offset "^1.0.2" + typedarray@^0.0.6: version "0.0.6" resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777" @@ -5440,6 +5576,11 @@ underscore@^1.12.1: resolved "https://registry.yarnpkg.com/underscore/-/underscore-1.13.6.tgz#04786a1f589dc6c09f761fc5f45b89e935136441" integrity sha512-+A5Sja4HP1M08MaXya7p5LvjuM7K6q/2EaC0+iovj/wOcMsTzMvDFbasi/oSapiwOlt252IqsKqPjCl7huKS0A== +undici-types@~5.26.4: + version "5.26.5" + resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-5.26.5.tgz#bcd539893d00b56e964fd2657a4866b221a65617" + integrity sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA== + unique-filename@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/unique-filename/-/unique-filename-1.1.1.tgz#1d69769369ada0583103a1e6ae87681b56573230" @@ -5566,9 +5707,9 @@ validate-npm-package-license@^3.0.1: spdx-expression-parse "^3.0.0" validator@^13.0.0, validator@^13.6.0, validator@^13.7.0: - version "13.9.0" - resolved "https://registry.yarnpkg.com/validator/-/validator-13.9.0.tgz#33e7b85b604f3bbce9bb1a05d5c3e22e1c2ff855" - integrity sha512-B+dGG8U3fdtM0/aNK4/X8CXq/EcxU2WPrPEkJGslb47qyHsxmbggTWK0yEA4qnYVNF+nxNlN88o14hIcPmSIEA== + version "13.11.0" + resolved "https://registry.yarnpkg.com/validator/-/validator-13.11.0.tgz#23ab3fd59290c61248364eabf4067f04955fbb1b" + integrity sha512-Ii+sehpSfZy+At5nPdnyMhx78fEoPDkR2XW/zimHEL3MyGJQOCQ7WeP20jPYRz7ZCpcKLB21NxuXHF3bxjStBQ== vary@^1, vary@~1.1.2: version "1.1.2" @@ -5648,16 +5789,16 @@ which-boxed-primitive@^1.0.2: is-string "^1.0.5" is-symbol "^1.0.3" -which-typed-array@^1.1.10, which-typed-array@^1.1.11, which-typed-array@^1.1.2: - version "1.1.11" - resolved "https://registry.yarnpkg.com/which-typed-array/-/which-typed-array-1.1.11.tgz#99d691f23c72aab6768680805a271b69761ed61a" - integrity sha512-qe9UWWpkeG5yzZ0tNYxDmd7vo58HDBc39mZ0xWWpolAGADdFOzkfamWLDxkOWcvHQKVmdTyQdLD4NOfjLWTKew== +which-typed-array@^1.1.14, which-typed-array@^1.1.15, which-typed-array@^1.1.2: + version "1.1.15" + resolved "https://registry.yarnpkg.com/which-typed-array/-/which-typed-array-1.1.15.tgz#264859e9b11a649b388bfaaf4f767df1f779b38d" + integrity sha512-oV0jmFtUky6CXfkqehVvBP/LSWJ2sy4vWMioiENyJLePrBO/yKyV9OyJySfAKosh+RYkIl5zJCNZ8/4JncrpdA== dependencies: - available-typed-arrays "^1.0.5" - call-bind "^1.0.2" + available-typed-arrays "^1.0.7" + call-bind "^1.0.7" for-each "^0.3.3" gopd "^1.0.1" - has-tostringtag "^1.0.0" + has-tostringtag "^1.0.2" which@^1.1.1, which@^1.2.9: version "1.3.1" @@ -5680,21 +5821,21 @@ wide-align@^1.1.5: dependencies: string-width "^1.0.2 || 2 || 3 || 4" -winston-transport@^4.5.0: - version "4.5.0" - resolved "https://registry.yarnpkg.com/winston-transport/-/winston-transport-4.5.0.tgz#6e7b0dd04d393171ed5e4e4905db265f7ab384fa" - integrity sha512-YpZzcUzBedhlTAfJg6vJDlyEai/IFMIVcaEZZyl3UXIl4gmqRpU7AE89AHLkbzLUsv0NVmw7ts+iztqKxxPW1Q== +winston-transport@^4.7.0: + version "4.7.0" + resolved "https://registry.yarnpkg.com/winston-transport/-/winston-transport-4.7.0.tgz#e302e6889e6ccb7f383b926df6936a5b781bd1f0" + integrity sha512-ajBj65K5I7denzer2IYW6+2bNIVqLGDHqDw3Ow8Ohh+vdW+rv4MZ6eiDvHoKhfJFZ2auyN8byXieDDJ96ViONg== dependencies: logform "^2.3.2" readable-stream "^3.6.0" triple-beam "^1.3.0" winston@^3.2.1: - version "3.10.0" - resolved "https://registry.yarnpkg.com/winston/-/winston-3.10.0.tgz#d033cb7bd3ced026fed13bf9d92c55b903116803" - integrity sha512-nT6SIDaE9B7ZRO0u3UvdrimG0HkB7dSTAgInQnNR2SOPJ4bvq5q79+pXLftKmP52lJGW15+H5MCK0nM9D3KB/g== + version "3.13.0" + resolved "https://registry.yarnpkg.com/winston/-/winston-3.13.0.tgz#e76c0d722f78e04838158c61adc1287201de7ce3" + integrity sha512-rwidmA1w3SE4j0E5MuIufFhyJPBDG7Nu71RkZor1p2+qHvJSZ9GYDA81AyleQcZbh/+V6HjeBdfnTZJm9rSeQQ== dependencies: - "@colors/colors" "1.5.0" + "@colors/colors" "^1.6.0" "@dabh/diagnostics" "^2.0.2" async "^3.2.3" is-stream "^2.0.0" @@ -5704,7 +5845,7 @@ winston@^3.2.1: safe-stable-stringify "^2.3.1" stack-trace "0.0.x" triple-beam "^1.3.0" - winston-transport "^4.5.0" + winston-transport "^4.7.0" word-wrap@~1.2.3: version "1.2.5" @@ -5753,10 +5894,10 @@ xml2js@0.4.19: sax ">=0.6.0" xmlbuilder "~9.0.1" -xml2js@0.5.0: - version "0.5.0" - resolved "https://registry.yarnpkg.com/xml2js/-/xml2js-0.5.0.tgz#d9440631fbb2ed800203fad106f2724f62c493b7" - integrity sha512-drPFnkQJik/O+uPKpqSgr22mpuFHqKdbS835iAQrUC73L2F5WkboIRd63ai/2Yg6I1jzifPFKH2NTK+cfglkIA== +xml2js@0.6.2: + version "0.6.2" + resolved "https://registry.yarnpkg.com/xml2js/-/xml2js-0.6.2.tgz#dd0b630083aa09c161e25a4d0901e2b2a929b499" + integrity sha512-T4rieHaC1EXcES0Kxxj4JWgaUQHDk+qwHcYOCFHfiwKz7tOVPLq7Hjq9dM1WCMhylqMEfP7hMcOIChvotiZegA== dependencies: sax ">=0.6.0" xmlbuilder "~11.0.0" From fd6298dd632f184801b6e172f7ecfd50e9ea7304 Mon Sep 17 00:00:00 2001 From: Will Toozs Date: Mon, 3 Jun 2024 18:28:44 +0200 Subject: [PATCH 02/15] CLDSRV-527: accomodate POST obj API call - Use busboy to separate out form data fields - Handle POST Object separately from other POST API calls - Update the API call waterfall with this new logic --- lib/api/api.js | 142 +++++++++++++++++++++++++++++++++++++------------ 1 file changed, 107 insertions(+), 35 deletions(-) diff --git a/lib/api/api.js b/lib/api/api.js index 23039807da..d55c0bc342 100644 --- a/lib/api/api.js +++ b/lib/api/api.js @@ -52,6 +52,7 @@ const objectGetRetention = require('./objectGetRetention'); const objectGetTagging = require('./objectGetTagging'); const objectHead = require('./objectHead'); const objectPut = require('./objectPut'); +const objectPost = require('./objectPost'); const objectPutACL = require('./objectPutACL'); const objectPutLegalHold = require('./objectPutLegalHold'); const objectPutTagging = require('./objectPutTagging'); @@ -68,6 +69,10 @@ const validateQueryAndHeaders = require('../utilities/validateQueryAndHeaders'); const parseCopySource = require('./apiUtils/object/parseCopySource'); const { tagConditionKeyAuth } = require('./apiUtils/authorization/tagConditionKeys'); const checkHttpHeadersSize = require('./apiUtils/object/checkHttpHeadersSize'); +const { decryptToken } = require('./apiUtils/object/continueToken'); +const busboy = require('busboy'); + + const monitoringMap = policies.actionMaps.actionMonitoringMapS3; @@ -184,8 +189,98 @@ const api = { } return { returnTagCount, isImplicitDeny }; } + let bb; + let fileEventData = null; + + if (apiMethod === 'objectPost' && request.headers['content-type'].includes('multipart/form-data')) { + bb = busboy({ headers: request.headers }); + } return async.waterfall([ + next => { + if (apiMethod === 'objectPut' || apiMethod === 'objectPutPart') { + return next(null); + } + if (apiMethod === 'objectPost' && request.headers['content-type'].includes('multipart/form-data')) { + writeContinue(request, response); + + let algoOK = false; + let credOK = false; + let dateOK = false; + let sigOK = false; + let policyOK = false; + request.formData = {}; + bb.on('field', (fieldname, val) => { + request.formData[fieldname] = val; + if (request.formData.Policy) { + request.formData.decryptedPolicy = JSON.parse(decryptToken(request.formData.Policy)); + } + + // TODO - put content type field for file in request + if (fieldname === 'X-Amz-Algorithm') { + algoOK = true; + } + if (fieldname === 'X-Amz-Credential') { + credOK = true; + } + if (fieldname === 'X-Amz-Date') { + dateOK = true; + } + if (fieldname === 'X-Amz-Signature') { + sigOK = true; + } + if (fieldname === 'Policy') { + policyOK = true; + } + }); + + bb.on('file', (fieldname, file, filename, encoding, mimetype) => { + fileEventData = { fieldname, file, filename, encoding, mimetype }; + if (algoOK && credOK && dateOK && sigOK && policyOK) { + return next(null); + } + }); + + bb.on('finish', () => { + // if authorization field is not found, return error + if (!algoOK || !credOK || !dateOK || !sigOK || !policyOK) { + return next(errors.InvalidRequest); + } + }); + request.pipe(bb); + } else { + // issue 100 Continue to the client + writeContinue(request, response); + const MAX_POST_LENGTH = request.method === 'POST' ? + 1024 * 1024 : 1024 * 1024 / 2; // 1 MB or 512 KB + const post = []; + let postLength = 0; + request.on('data', chunk => { + postLength += chunk.length; + // Sanity check on post length + if (postLength <= MAX_POST_LENGTH) { + post.push(chunk); + } + }); + + request.on('error', err => { + log.trace('error receiving request', { + error: err, + }); + return next(errors.InternalError); + }); + + request.on('end', () => { + if (postLength > MAX_POST_LENGTH) { + log.error('body length is too long for request type', + { postLength }); + return next(errors.InvalidRequest); + } + return next(null); + }); + } + return undefined; + }, next => auth.server.doAuth( request, log, (err, userInfo, authorizationResults, streamingV4Params) => { if (err) { @@ -200,41 +295,7 @@ const api = { authNames.userName = userInfo.getIAMdisplayName(); } log.addDefaultFields(authNames); - if (apiMethod === 'objectPut' || apiMethod === 'objectPutPart') { - return next(null, userInfo, authorizationResults, streamingV4Params); - } - // issue 100 Continue to the client - writeContinue(request, response); - const MAX_POST_LENGTH = request.method === 'POST' ? - 1024 * 1024 : 1024 * 1024 / 2; // 1 MB or 512 KB - const post = []; - let postLength = 0; - request.on('data', chunk => { - postLength += chunk.length; - // Sanity check on post length - if (postLength <= MAX_POST_LENGTH) { - post.push(chunk); - } - }); - - request.on('error', err => { - log.trace('error receiving request', { - error: err, - }); - return next(errors.InternalError); - }); - - request.on('end', () => { - if (postLength > MAX_POST_LENGTH) { - log.error('body length is too long for request type', - { postLength }); - return next(errors.InvalidRequest); - } - // Convert array of post buffers into one string - request.post = Buffer.concat(post, postLength).toString(); - return next(null, userInfo, authorizationResults, streamingV4Params); - }); - return undefined; + return next(null, userInfo, authorizationResults, streamingV4Params); }, // Tag condition keys require information from CloudServer for evaluation (userInfo, authorizationResults, streamingV4Params, next) => tagConditionKeyAuth( @@ -244,6 +305,10 @@ const api = { apiMethod, log, (err, authResultsWithTags) => { + // TODO CLDSRV-527 remove ignore for POST object here + if (apiMethod === 'objectPost') { + return next(null, userInfo, authorizationResults, streamingV4Params); + } if (err) { log.trace('tag authentication error', { error: err }); return next(err); @@ -271,6 +336,12 @@ const api = { return acc; }, {}); } + if (apiMethod === 'objectPost' && fileEventData) { + request._response = response; + request.file = fileEventData.file; + return this[apiMethod](userInfo, request, streamingV4Params, + log, callback, authorizationResults); + } if (apiMethod === 'objectPut' || apiMethod === 'objectPutPart') { request._response = response; return this[apiMethod](userInfo, request, streamingV4Params, @@ -337,6 +408,7 @@ const api = { objectCopy, objectHead, objectPut, + objectPost, objectPutACL, objectPutLegalHold, objectPutTagging, From 4913ab044fa4d46d3b7100ae7157399543f93b68 Mon Sep 17 00:00:00 2001 From: Will Toozs Date: Mon, 3 Jun 2024 18:32:59 +0200 Subject: [PATCH 03/15] CLDSRV-527: add POST Object API Handler --- lib/api/api.js | 2 - lib/api/objectPost.js | 232 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 232 insertions(+), 2 deletions(-) create mode 100644 lib/api/objectPost.js diff --git a/lib/api/api.js b/lib/api/api.js index d55c0bc342..b69b2e83ec 100644 --- a/lib/api/api.js +++ b/lib/api/api.js @@ -72,8 +72,6 @@ const checkHttpHeadersSize = require('./apiUtils/object/checkHttpHeadersSize'); const { decryptToken } = require('./apiUtils/object/continueToken'); const busboy = require('busboy'); - - const monitoringMap = policies.actionMaps.actionMonitoringMapS3; auth.setHandler(vault); diff --git a/lib/api/objectPost.js b/lib/api/objectPost.js new file mode 100644 index 0000000000..217c8e2ccc --- /dev/null +++ b/lib/api/objectPost.js @@ -0,0 +1,232 @@ +const async = require('async'); +const { errors, versioning } = require('arsenal'); +const { PassThrough } = require('stream'); + +const { getObjectSSEConfiguration } = require('./apiUtils/bucket/bucketEncryption'); +const collectCorsHeaders = require('../utilities/collectCorsHeaders'); +const createAndStoreObject = require('./apiUtils/object/createAndStoreObject'); +const { standardMetadataValidateBucketAndObj } = require('../metadata/metadataUtils'); +const { pushMetric } = require('../utapi/utilities'); +const { validateHeaders } = require('./apiUtils/object/objectLockHelpers'); +const kms = require('../kms/wrapper'); +const { config } = require('../Config'); +const { setExpirationHeaders } = require('./apiUtils/object/expirationHeaders'); +const monitoring = require('../utilities/metrics'); + +const writeContinue = require('../utilities/writeContinue'); +const { overheadField } = require('../../constants'); + + +const versionIdUtils = versioning.VersionID; + + +/** + * POST Object in the requested bucket. Steps include: + * validating metadata for authorization, bucket and object existence etc. + * store object data in datastore upon successful authorization + * store object location returned by datastore and + * object's (custom) headers in metadata + * return the result in final callback + * + * @param {AuthInfo} authInfo - Instance of AuthInfo class with requester's info + * @param {request} request - request object given by router, + * includes normalized headers + * @param {object | undefined } streamingV4Params - if v4 auth, + * object containing accessKey, signatureFromRequest, region, scopeDate, + * timestamp, and credentialScope + * (to be used for streaming v4 auth if applicable) + * @param {object} log - the log request + * @param {object} fileInfo - object containing file stream and filename + * @param {Function} callback - final callback to call with the result + * @return {undefined} + */ +function objectPost(authInfo, request, streamingV4Params, log, callback) { + const { + headers, + method, + } = request; + let parsedContentLength = 0; + + const passThroughStream = new PassThrough(); + + // TODO CLDSRV-527 add acl header check + // if (!aclUtils.checkGrantHeaderValidity(headers)) { + // log.trace('invalid acl header'); + // monitoring.promMetrics('PUT', request.bucketName, 400, + // 'putObject'); + // return callback(errors.InvalidArgument); + // } + // TODO CLDSRV-527 add check for versionId + // const queryContainsVersionId = checkQueryVersionId(query); + // if (queryContainsVersionId instanceof Error) { + // return callback(queryContainsVersionId); + // } + const invalidSSEError = errors.InvalidArgument.customizeDescription( + 'The encryption method specified is not supported'); + const requestType = request.apiMethods || 'objectPost'; + + const valParams = { authInfo, bucketName: request.formData.bucket, objectKey: request.formData.key, requestType, request }; + + const canonicalID = authInfo.getCanonicalID(); + + // TODO CLDSRV-527 add check for non-printable characters? + // if (hasNonPrintables(objectKey)) { + // return callback(errors.InvalidInput.customizeDescription( + // 'object keys cannot contain non-printable characters', + // )); + // } + + // TODO CLDSRV-527 add checksum header check + // const checksumHeaderErr = validateChecksumHeaders(headers); + // if (checksumHeaderErr) { + // return callback(checksumHeaderErr); + // } + + log.trace('owner canonicalID to send to data', { canonicalID }); + + return standardMetadataValidateBucketAndObj(valParams, request.actionImplicitDenies, log, + (err, bucket, objMD) => { + const responseHeaders = collectCorsHeaders(headers.origin, + method, bucket); + + if (err && !err.AccessDenied) { + log.trace('error processing request', { + error: err, + method: 'metadataValidateBucketAndObj', + }); + monitoring.promMetrics('POST', request.bucketName, err.code, 'postObject'); + return callback(err, responseHeaders); + } + if (bucket.hasDeletedFlag() && canonicalID !== bucket.getOwner()) { + log.trace('deleted flag on bucket and request ' + + 'from non-owner account'); + monitoring.promMetrics('POST', request.bucketName, 404, 'postObject'); + return callback(errors.NoSuchBucket); + } + + return async.waterfall([ + function countPOSTFileSize(next) { + request.file.on('data', (chunk) => { + parsedContentLength += chunk.length; + passThroughStream.write(chunk); + + }); + + request.file.on('end', () => { + // Here totalBytes will have the total size of the file + passThroughStream.end(); + request.file = passThroughStream; + request.parsedContentLength = parsedContentLength; + return next(); + }); + return undefined; + }, + // TODO CLDSRV-527 add this back? + // function handleTransientOrDeleteBuckets(next) { + // if (bucket.hasTransientFlag() || bucket.hasDeletedFlag()) { + // return cleanUpBucket(bucket, canonicalID, log, next); + // } + // return next(); + // }, + function getSSEConfig(next) { + return getObjectSSEConfiguration(headers, bucket, log, + (err, sseConfig) => { + if (err) { + log.error('error getting server side encryption config', { err }); + return next(invalidSSEError); + } + return next(null, sseConfig); + } + ); + }, + function createCipherBundle(serverSideEncryptionConfig, next) { + if (serverSideEncryptionConfig) { + return kms.createCipherBundle( + serverSideEncryptionConfig, log, next); + } + return next(null, null); + }, + function objectCreateAndStore(cipherBundle, next) { + const objectLockValidationError + = validateHeaders(bucket, headers, log); + if (objectLockValidationError) { + return next(objectLockValidationError); + } + writeContinue(request, request._response); + return createAndStoreObject(request.bucketName, + bucket, request.formData.key, objMD, authInfo, canonicalID, cipherBundle, + request, false, streamingV4Params, overheadField, log, next); + }, + ], (err, storingResult) => { + if (err) { + monitoring.promMetrics('POST', request.bucketName, err.code, + 'postObject'); + return callback(err, responseHeaders); + } + // ingestSize assumes that these custom headers indicate + // an ingestion PUT which is a metadata only operation. + // Since these headers can be modified client side, they + // should be used with caution if needed for precise + // metrics. + const ingestSize = (request.headers['x-amz-meta-mdonly'] + && !Number.isNaN(request.headers['x-amz-meta-size'])) + ? Number.parseInt(request.headers['x-amz-meta-size'], 10) : null; + const newByteLength = parsedContentLength; + + setExpirationHeaders(responseHeaders, { + lifecycleConfig: bucket.getLifecycleConfiguration(), + objectParams: { + key: request.key, + date: storingResult.lastModified, + tags: storingResult.tags, + }, + }); + + // Utapi expects null or a number for oldByteLength: + // * null - new object + // * 0 or > 0 - existing object with content-length 0 or > 0 + // objMD here is the master version that we would + // have overwritten if there was an existing version or object + // + // TODO: Handle utapi metrics for null version overwrites. + const oldByteLength = objMD && objMD['content-length'] + !== undefined ? objMD['content-length'] : null; + if (storingResult) { + // ETag's hex should always be enclosed in quotes + responseHeaders.ETag = `"${storingResult.contentMD5}"`; + } + const vcfg = bucket.getVersioningConfiguration(); + const isVersionedObj = vcfg && vcfg.Status === 'Enabled'; + if (isVersionedObj) { + if (storingResult && storingResult.versionId) { + responseHeaders['x-amz-version-id'] = + versionIdUtils.encode(storingResult.versionId, + config.versionIdEncodingType); + } + } + + // Only pre-existing non-versioned objects get 0 all others use 1 + const numberOfObjects = !isVersionedObj && oldByteLength !== null ? 0 : 1; + + // only the bucket owner's metrics should be updated, regardless of + // who the requester is + pushMetric('postObject', log, { + authInfo, + canonicalID: bucket.getOwner(), + bucket: request.bucketName, + keys: [request.key], + newByteLength, + oldByteLength: isVersionedObj ? null : oldByteLength, + versionId: isVersionedObj && storingResult ? storingResult.versionId : undefined, + location: bucket.getLocationConstraint(), + numberOfObjects, + }); + monitoring.promMetrics('POST', request.bucketName, '204', + 'postObject', newByteLength, oldByteLength, isVersionedObj, + null, ingestSize); + return callback(null, responseHeaders); + }); + }); +} + +module.exports = objectPost; From 587f5bfa8986ea6395643d26b03350c1c826b416 Mon Sep 17 00:00:00 2001 From: Will Toozs Date: Mon, 3 Jun 2024 18:33:35 +0200 Subject: [PATCH 04/15] CLDSRV-527: accomodate POST Object in extraneous code --- lib/api/apiUtils/object/createAndStoreObject.js | 4 ++++ lib/api/apiUtils/object/prepareStream.js | 2 +- lib/metadata/metadataUtils.js | 11 ++++++----- 3 files changed, 11 insertions(+), 6 deletions(-) diff --git a/lib/api/apiUtils/object/createAndStoreObject.js b/lib/api/apiUtils/object/createAndStoreObject.js index 7dc84089bf..ce592f215d 100644 --- a/lib/api/apiUtils/object/createAndStoreObject.js +++ b/lib/api/apiUtils/object/createAndStoreObject.js @@ -210,6 +210,10 @@ function createAndStoreObject(bucketName, bucketMD, objectKey, objMD, authInfo, metadataStoreParams.contentMD5 = constants.emptyFileMd5; return next(null, null, null); } + if (request.apiMethod === 'objectPost') { + return dataStore(objectKeyContext, cipherBundle, request.file, size, + streamingV4Params, backendInfo, log, next); + } return dataStore(objectKeyContext, cipherBundle, request, size, streamingV4Params, backendInfo, log, next); }, diff --git a/lib/api/apiUtils/object/prepareStream.js b/lib/api/apiUtils/object/prepareStream.js index 7d436dd96b..91484efa1f 100644 --- a/lib/api/apiUtils/object/prepareStream.js +++ b/lib/api/apiUtils/object/prepareStream.js @@ -13,7 +13,7 @@ const V4Transform = require('../../../auth/streamingV4/V4Transform'); * the type of request requires them */ function prepareStream(stream, streamingV4Params, log, errCb) { - if (stream.headers['x-amz-content-sha256'] === + if (stream.headers && stream.headers['x-amz-content-sha256'] === 'STREAMING-AWS4-HMAC-SHA256-PAYLOAD') { if (typeof streamingV4Params !== 'object') { // this might happen if the user provided a valid V2 diff --git a/lib/metadata/metadataUtils.js b/lib/metadata/metadataUtils.js index 16c1a1f91f..397dd6b8fd 100644 --- a/lib/metadata/metadataUtils.js +++ b/lib/metadata/metadataUtils.js @@ -231,11 +231,12 @@ function standardMetadataValidateBucketAndObj(params, actionImplicitDenies, log, }, (bucket, objMD, next) => { const canonicalID = authInfo.getCanonicalID(); - if (!isObjAuthorized(bucket, objMD, requestType, canonicalID, authInfo, log, request, - actionImplicitDenies)) { - log.debug('access denied for user on object', { requestType }); - return next(errors.AccessDenied, bucket); - } + // TODO CLDSRV-527 reinstate this check isObjAuthorized + // if (!isObjAuthorized(bucket, objMD, requestType, canonicalID, authInfo, log, request, + // actionImplicitDenies)) { + // log.debug('access denied for user on object', { requestType }); + // return next(errors.AccessDenied, bucket); + // } return next(null, bucket, objMD); }, ], (err, bucket, objMD) => { From 77c69cccfc9401fa5bc72f3979b6f5a65020b793 Mon Sep 17 00:00:00 2001 From: Will Toozs Date: Mon, 3 Jun 2024 18:34:14 +0200 Subject: [PATCH 05/15] [WIP]CLDSRV-527: add test files --- .../aws-node-sdk/test/object/post.js | 415 +++++++++ tests/unit/api/objectPost.js | 817 ++++++++++++++++++ 2 files changed, 1232 insertions(+) create mode 100644 tests/functional/aws-node-sdk/test/object/post.js create mode 100644 tests/unit/api/objectPost.js diff --git a/tests/functional/aws-node-sdk/test/object/post.js b/tests/functional/aws-node-sdk/test/object/post.js new file mode 100644 index 0000000000..2dc8f265d3 --- /dev/null +++ b/tests/functional/aws-node-sdk/test/object/post.js @@ -0,0 +1,415 @@ +const assert = require('assert'); + +const withV4 = require('../support/withV4'); +const BucketUtility = require('../../lib/utility/bucket-util'); +const provideRawOutput = require('../../lib/utility/provideRawOutput'); +const { taggingTests, generateMultipleTagQuery } + = require('../../lib/utility/tagging'); +const genMaxSizeMetaHeaders + = require('../../lib/utility/genMaxSizeMetaHeaders'); +const changeObjectLock = require('../../../../utilities/objectLock-util'); + +const bucket = 'bucket2putstuffin4324242'; +const object = 'object2putstuffin'; + +function _checkError(err, code, statusCode) { + assert(err, 'Expected error but found none'); + assert.strictEqual(err.code, code); + assert.strictEqual(err.statusCode, statusCode); +} + +describe('PUT object', () => { + withV4(sigCfg => { + let bucketUtil; + let s3; + + beforeEach(() => { + bucketUtil = new BucketUtility('default', sigCfg); + s3 = bucketUtil.s3; + return s3.createBucket({ Bucket: bucket }).promise() + .catch(err => { + process.stdout.write(`Error creating bucket: ${err}\n`); + throw err; + }); + }); + + afterEach(() => { + process.stdout.write('Emptying bucket'); + return bucketUtil.empty(bucket) + .then(() => { + process.stdout.write('Deleting bucket'); + return bucketUtil.deleteOne(bucket); + }) + .catch(err => { + process.stdout.write('Error in afterEach'); + throw err; + }); + }); + + it('should put an object and set the acl via query param', + done => { + const params = { Bucket: bucket, Key: 'key', + ACL: 'public-read', StorageClass: 'STANDARD' }; + const url = s3.getSignedUrl('putObject', params); + provideRawOutput(['-verbose', '-X', 'PUT', url, + '--upload-file', 'uploadFile'], httpCode => { + assert.strictEqual(httpCode, '200 OK'); + s3.getObjectAcl({ Bucket: bucket, Key: 'key' }, + (err, result) => { + assert.equal(err, null, 'Expected success, ' + + `got error ${JSON.stringify(err)}`); + assert.deepStrictEqual(result.Grants[1], { Grantee: + { Type: 'Group', URI: + 'http://acs.amazonaws.com/groups/global/AllUsers', + }, Permission: 'READ' }); + done(); + }); + }); + }); + + it('should put an object with key slash', + done => { + const params = { Bucket: bucket, Key: '/' }; + s3.putObject(params, err => { + assert.equal(err, null, 'Expected success, ' + + `got error ${JSON.stringify(err)}`); + done(); + }); + }); + + it('should return error if putting object w/ > 2KB user-defined md', + done => { + const metadata = genMaxSizeMetaHeaders(); + const params = { Bucket: bucket, Key: '/', Metadata: metadata }; + s3.putObject(params, err => { + assert.strictEqual(err, null, `Unexpected err: ${err}`); + // add one more byte to be over the limit + metadata.header0 = `${metadata.header0}${'0'}`; + s3.putObject(params, err => { + assert(err, 'Expected err but did not find one'); + assert.strictEqual(err.code, 'MetadataTooLarge'); + assert.strictEqual(err.statusCode, 400); + done(); + }); + }); + }); + + it('should return InvalidRequest error if putting object with ' + + 'object lock retention date and mode when object lock is not ' + + 'enabled on the bucket', done => { + const date = new Date(2050, 10, 10); + const params = { + Bucket: bucket, + Key: 'key', + ObjectLockRetainUntilDate: date, + ObjectLockMode: 'GOVERNANCE', + }; + s3.putObject(params, err => { + const expectedErrMessage + = 'Bucket is missing ObjectLockConfiguration'; + assert.strictEqual(err.code, 'InvalidRequest'); + assert.strictEqual(err.message, expectedErrMessage); + done(); + }); + }); + + it('should return Not Implemented error for obj. encryption using ' + + 'customer-provided encryption keys', done => { + const params = { Bucket: bucket, Key: 'key', + SSECustomerAlgorithm: 'AES256' }; + s3.putObject(params, err => { + assert.strictEqual(err.code, 'NotImplemented'); + done(); + }); + }); + + it('should return InvalidRedirectLocation if putting object ' + + 'with x-amz-website-redirect-location header that does not start ' + + 'with \'http://\', \'https://\' or \'/\'', done => { + const params = { Bucket: bucket, Key: 'key', + WebsiteRedirectLocation: 'google.com' }; + s3.putObject(params, err => { + assert.strictEqual(err.code, 'InvalidRedirectLocation'); + assert.strictEqual(err.statusCode, 400); + done(); + }); + }); + + describe('Put object with tag set', () => { + taggingTests.forEach(taggingTest => { + it(taggingTest.it, done => { + const key = encodeURIComponent(taggingTest.tag.key); + const value = encodeURIComponent(taggingTest.tag.value); + const tagging = `${key}=${value}`; + const params = { Bucket: bucket, Key: object, + Tagging: tagging }; + s3.putObject(params, err => { + if (taggingTest.error) { + _checkError(err, taggingTest.error, 400); + return done(); + } + assert.equal(err, null, 'Expected success, ' + + `got error ${JSON.stringify(err)}`); + return s3.getObjectTagging({ Bucket: bucket, + Key: object }, (err, data) => { + assert.equal(err, null, 'Expected success, ' + + `got error ${JSON.stringify(err)}`); + assert.deepStrictEqual(data.TagSet[0], { + Key: taggingTest.tag.key, + Value: taggingTest.tag.value }); + done(); + }); + }); + }); + }); + + it('should be able to put object with 10 tags', + done => { + const taggingConfig = generateMultipleTagQuery(10); + s3.putObject({ Bucket: bucket, Key: object, + Tagging: taggingConfig }, err => { + assert.equal(err, null, 'Expected success, ' + + `got error ${JSON.stringify(err)}`); + done(); + }); + }); + + it('should be able to put an empty Tag set', done => { + s3.putObject({ Bucket: bucket, Key: object, + Tagging: '', + }, err => { + assert.equal(err, null, 'Expected success, ' + + `got error ${JSON.stringify(err)}`); + done(); + }); + }); + + it('should be able to put object with empty tags', + done => { + s3.putObject({ Bucket: bucket, Key: object, + Tagging: '&&&&&&&&&&&&&&&&&key1=value1' }, err => { + assert.equal(err, null, 'Expected success, ' + + `got error ${JSON.stringify(err)}`); + done(); + }); + }); + + it('should allow putting 50 tags', done => { + const taggingConfig = generateMultipleTagQuery(50); + s3.putObject({ Bucket: bucket, Key: object, + Tagging: taggingConfig }, done); + }); + + it('should return BadRequest if putting more that 50 tags', + done => { + const taggingConfig = generateMultipleTagQuery(51); + s3.putObject({ Bucket: bucket, Key: object, + Tagging: taggingConfig }, err => { + _checkError(err, 'BadRequest', 400); + done(); + }); + }); + + it('should return InvalidArgument if using the same key twice', + done => { + s3.putObject({ Bucket: bucket, Key: object, + Tagging: 'key1=value1&key1=value2' }, err => { + _checkError(err, 'InvalidArgument', 400); + done(); + }); + }); + + it('should return InvalidArgument if using the same key twice ' + + 'and empty tags', done => { + s3.putObject({ Bucket: bucket, Key: object, + Tagging: '&&&&&&&&&&&&&&&&&key1=value1&key1=value2' }, + err => { + _checkError(err, 'InvalidArgument', 400); + done(); + }); + }); + + it('should return InvalidArgument if tag with no key', done => { + s3.putObject({ Bucket: bucket, Key: object, + Tagging: '=value1', + }, err => { + _checkError(err, 'InvalidArgument', 400); + done(); + }); + }); + + it('should return InvalidArgument putting object with ' + + 'bad encoded tags', done => { + s3.putObject({ Bucket: bucket, Key: object, Tagging: + 'key1==value1' }, err => { + _checkError(err, 'InvalidArgument', 400); + done(); + }); + }); + + it('should return InvalidArgument putting object tag with ' + + 'invalid characters: %', done => { + const value = 'value1%'; + s3.putObject({ Bucket: bucket, Key: object, Tagging: + `key1=${value}` }, err => { + _checkError(err, 'InvalidArgument', 400); + done(); + }); + }); + }); + }); +}); + +describe('PUT object with object lock', () => { + withV4(sigCfg => { + let bucketUtil; + let s3; + + beforeEach(() => { + bucketUtil = new BucketUtility('default', sigCfg); + s3 = bucketUtil.s3; + return s3.createBucket({ + Bucket: bucket, + ObjectLockEnabledForBucket: true, + }).promise() + .catch(err => { + process.stdout.write(`Error creating bucket: ${err}\n`); + throw err; + }); + }); + + afterEach(() => { + process.stdout.write('Emptying bucket'); + return bucketUtil.empty(bucket) + .then(() => { + process.stdout.write('Deleting bucket'); + return bucketUtil.deleteOne(bucket); + }) + .catch(err => { + process.stdout.write('Error in afterEach'); + throw err; + }); + }); + + it('should put object with valid object lock retention date and ' + + 'mode when object lock is enabled on the bucket', done => { + const date = new Date(2050, 10, 10); + const params = { + Bucket: bucket, + Key: 'key1', + ObjectLockRetainUntilDate: date, + ObjectLockMode: 'COMPLIANCE', + }; + s3.putObject(params, (err, res) => { + assert.ifError(err); + changeObjectLock( + [{ bucket, key: 'key1', versionId: res.VersionId }], '', done); + }); + }); + + it('should put object with valid object lock retention date and ' + + 'mode when object lock is enabled on the bucket', done => { + const date = new Date(2050, 10, 10); + const params = { + Bucket: bucket, + Key: 'key2', + ObjectLockRetainUntilDate: date, + ObjectLockMode: 'GOVERNANCE', + }; + s3.putObject(params, (err, res) => { + assert.ifError(err); + changeObjectLock( + [{ bucket, key: 'key2', versionId: res.VersionId }], '', done); + }); + }); + + it('should error with invalid object lock mode header', done => { + const date = new Date(2050, 10, 10); + const params = { + Bucket: bucket, + Key: 'key3', + ObjectLockMode: 'Governance', + ObjectLockRetainUntilDate: date, + }; + s3.putObject(params, err => { + assert.strictEqual(err.code, 'InvalidArgument'); + assert.strictEqual(err.message, 'Unknown wormMode directive'); + done(); + }); + }); + + it('should put object with valid legal hold status ON', done => { + const params = { + Bucket: bucket, + Key: 'key4', + ObjectLockLegalHoldStatus: 'ON', + }; + s3.putObject(params, (err, res) => { + assert.ifError(err); + changeObjectLock( + [{ bucket, key: 'key4', versionId: res.VersionId }], '', done); + }); + }); + + it('should put object with valid legal hold status OFF', done => { + const params = { + Bucket: bucket, + Key: 'key5', + ObjectLockLegalHoldStatus: 'OFF', + }; + s3.putObject(params, err => { + assert.ifError(err); + done(); + }); + }); + + it('should error with invalid legal hold status', done => { + const params = { + Bucket: bucket, + Key: 'key6', + ObjectLockLegalHoldStatus: 'on', + }; + s3.putObject(params, err => { + assert.strictEqual(err.code, 'InvalidArgument'); + assert.strictEqual(err.message, + 'Legal hold status must be one of "ON", "OFF"'); + done(); + }); + }); + + it('should return error when object lock retain until date header is ' + + 'provided but object lock mode header is missing', done => { + const date = new Date(2050, 10, 10); + const params = { + Bucket: bucket, + Key: 'key7', + ObjectLockRetainUntilDate: date, + }; + s3.putObject(params, err => { + const expectedErrMessage + = 'x-amz-object-lock-retain-until-date and ' + + 'x-amz-object-lock-mode must both be supplied'; + assert.strictEqual(err.code, 'InvalidArgument'); + assert.strictEqual(err.message, expectedErrMessage); + done(); + }); + }); + + it('should return error when object lock mode header is provided ' + + 'but object lock retain until date header is missing', done => { + const params = { + Bucket: bucket, + Key: 'key8', + ObjectLockMode: 'GOVERNANCE', + }; + s3.putObject(params, err => { + const expectedErrMessage + = 'x-amz-object-lock-retain-until-date and ' + + 'x-amz-object-lock-mode must both be supplied'; + assert.strictEqual(err.code, 'InvalidArgument'); + assert.strictEqual(err.message, expectedErrMessage); + done(); + }); + }); + }); +}); diff --git a/tests/unit/api/objectPost.js b/tests/unit/api/objectPost.js new file mode 100644 index 0000000000..81ced2fed7 --- /dev/null +++ b/tests/unit/api/objectPost.js @@ -0,0 +1,817 @@ +const assert = require('assert'); +const async = require('async'); +const moment = require('moment'); +const { errors, s3middleware } = require('arsenal'); +const sinon = require('sinon'); + +const { bucketPut } = require('../../../lib/api/bucketPut'); +const bucketPutObjectLock = require('../../../lib/api/bucketPutObjectLock'); +const bucketPutACL = require('../../../lib/api/bucketPutACL'); +const bucketPutVersioning = require('../../../lib/api/bucketPutVersioning'); +const { parseTagFromQuery } = s3middleware.tagging; +const { cleanup, DummyRequestLogger, makeAuthInfo, versioningTestUtils } + = require('../helpers'); +const { ds } = require('arsenal').storage.data.inMemory.datastore; +const metadata = require('../metadataswitch'); +const objectPost = require('../../../lib/api/objectPost'); +const { objectLockTestUtils } = require('../helpers'); +const DummyRequest = require('../DummyRequest'); +const mpuUtils = require('../utils/mpuUtils'); +const { lastModifiedHeader } = require('../../../constants'); +const { createPresignedPost } = require('@aws-sdk/s3-presigned-post'); + +const any = sinon.match.any; + +const log = new DummyRequestLogger(); +const canonicalID = 'accessKey1'; +const authInfo = makeAuthInfo(canonicalID); +const namespace = 'default'; +const bucketName = 'bucketname'; +// const postBody = Buffer.from('I am a body', 'utf8'); +// const correctMD5 = 'be747eb4b75517bf6b3cf7c5fbb62f3a'; +// const mockDate = new Date(2050, 10, 12); +const testPutBucketRequest = new DummyRequest({ + bucketName, + namespace, + headers: { host: `${bucketName}.s3.amazonaws.com` }, + url: '/', +}); +// const testPutBucketRequestLock = new DummyRequest({ +// bucketName, +// namespace, +// headers: { +// 'host': `${bucketName}.s3.amazonaws.com`, +// 'x-amz-bucket-object-lock-enabled': 'true', +// }, +// url: '/', +// }); + +// const originalputObjectMD = metadata.putObjectMD; +const objectName = 'objectName'; + +// let testPutObjectRequest; +// const enableVersioningRequest = +// versioningTestUtils.createBucketPutVersioningReq(bucketName, 'Enabled'); +// const suspendVersioningRequest = +// versioningTestUtils.createBucketPutVersioningReq(bucketName, 'Suspended'); + +// function testAuth(bucketOwner, authUser, bucketPutReq, log, cb) { +// bucketPut(bucketOwner, bucketPutReq, log, () => { +// bucketPutACL(bucketOwner, testPutBucketRequest, log, err => { +// assert.strictEqual(err, undefined); +// objectPut(authUser, testPutObjectRequest, undefined, +// log, (err, resHeaders) => { +// assert.strictEqual(err, null); +// assert.strictEqual(resHeaders.ETag, `"${correctMD5}"`); +// cb(); +// }); +// }); +// }); +// } + +// describe('parseTagFromQuery', () => { +// const invalidArgument = { status: 'InvalidArgument', statusCode: 400 }; +// const invalidTag = { status: 'InvalidTag', statusCode: 400 }; +// const allowedChar = '+- =._:/'; +// const tests = [ +// { tagging: 'key1=value1', result: { key1: 'value1' } }, +// { tagging: `key1=${encodeURIComponent(allowedChar)}`, +// result: { key1: allowedChar } }, +// { tagging: 'key1=value1=value2', error: invalidArgument }, +// { tagging: '=value1', error: invalidArgument }, +// { tagging: 'key1%=value1', error: invalidArgument }, +// { tagging: `${'w'.repeat(129)}=value1`, error: invalidTag }, +// { tagging: `key1=${'w'.repeat(257)}`, error: invalidTag }, +// { tagging: `${'w'.repeat(129)}=value1`, error: invalidTag }, +// { tagging: `key1=${'w'.repeat(257)}`, error: invalidTag }, +// ]; +// tests.forEach(test => { +// const behavior = test.error ? 'fail' : 'pass'; +// it(`should ${behavior} if tag set: "${test.tagging}"`, done => { +// const result = parseTagFromQuery(test.tagging); +// if (test.error) { +// assert(result.is[test.error.status]); +// assert.strictEqual(result.code, test.error.statusCode); +// } else { +// assert.deepStrictEqual(result, test.result); +// } +// done(); +// }); +// }); +// }); + +async function generatePresignedPost(options, conditions) { + // Define the conditions for the presigned POST + // const tagging = "Tag NameTag Value"; + // const conditions = [ + // {"bucket": bucketName}, + // { key: key }, // Ensure the key matches + // { "Content-Type": contentType }, // Enforce specific content type + // ]; + + // const fields = { + // "Content-Type": contentType, // Pre-filled field for content type + // "tagging": tagging + // }; + + // const options = { + // Bucket: bucketName, + // Fields: fields, + // Expires: 300, // Time in seconds before the presigned POST expires + // Conditions: conditions, + // Key: key, + // }; + try { + // Generate the presigned POST + // The presigned POST contains the URL and fields to upload the object + // It will generate the Policy, x-amz-signature, x-amz-credential and x-amz-date for you + const presignedPost = await createPresignedPost(s3, options); + return presignedPost; + } catch (error) { + console.error("Error creating presigned post:", error); + throw error; + } +} + +async function uploadFileUsingPresignedPost(presignedPostData, fileContent) { + const formData = new FormData(); + Object.entries(presignedPostData.fields).forEach(([key, value]) => { + formData.append(key, value); + }); + formData.append('file', fileContent, 'filename.txt'); + console.log('formdata: ', formData) + const response = + await axios({ + method: 'post', + url: presignedPostData.url, + data: formData, + headers: { + 'Content-Type': `multipart/form-data; boundary=${formData._boundary}`, + }, + }); + + return response; +} + +describe('objectPost API', () => { + // beforeEach(() => { + // cleanup(); + // sinon.spy(metadata, 'putObjectMD'); + // testPutObjectRequest = new DummyRequest({ + // bucketName, + // namespace, + // objectKey: objectName, + // headers: { host: `${bucketName}.s3.amazonaws.com` }, + // url: '/', + // }, postBody); + // }); + + // afterEach(() => { + // sinon.restore(); + // metadata.putObjectMD = originalputObjectMD; + // }); + +// it('should return an error if the bucket does not exist', done => { +// objectPut(authInfo, testPutObjectRequest, undefined, log, err => { +// assert.deepStrictEqual(err, errors.NoSuchBucket); +// done(); +// }); +// }); + +// it('should return an error if user is not authorized', done => { +// const putAuthInfo = makeAuthInfo('accessKey2'); +// bucketPut(putAuthInfo, testPutBucketRequest, +// log, () => { +// objectPut(authInfo, testPutObjectRequest, +// undefined, log, err => { +// assert.deepStrictEqual(err, errors.AccessDenied); +// done(); +// }); +// }); +// }); + + // NOTE: likely not feasible on POST + // it('should put object if user has FULL_CONTROL grant on bucket', done => { + // const bucketOwner = makeAuthInfo('accessKey2'); + // const authUser = makeAuthInfo('accessKey3'); + // testPutBucketRequest.headers['x-amz-grant-full-control'] = + // `id=${authUser.getCanonicalID()}`; + // testAuth(bucketOwner, authUser, testPutBucketRequest, log, done); + // }); + + // it('should put object if user has WRITE grant on bucket', done => { + // const bucketOwner = makeAuthInfo('accessKey2'); + // const authUser = makeAuthInfo('accessKey3'); + // testPutBucketRequest.headers['x-amz-grant-write'] = + // `id=${authUser.getCanonicalID()}`; + + // testAuth(bucketOwner, authUser, testPutBucketRequest, log, done); + // }); + + // it('should put object in bucket with public-read-write acl', done => { + // const bucketOwner = makeAuthInfo('accessKey2'); + // const authUser = makeAuthInfo('accessKey3'); + // testPutBucketRequest.headers['x-amz-acl'] = 'public-read-write'; + + // testAuth(bucketOwner, authUser, testPutBucketRequest, log, done); + // }); + + it('should successfully post an object', done => { + const testPutObjectRequest = new DummyRequest({ + bucketName, + namespace, + objectKey: objectName, + headers: {}, + url: `/${bucketName}/${objectName}`, + calculatedHash: 'vnR+tLdVF79rPPfF+7YvOg==', + }, postBody); + + bucketPut(authInfo, testPutBucketRequest, log, () => { + objectPost(authInfo, testPostObjectRequest, undefined, log, + (err, resHeaders) => { + assert.strictEqual(resHeaders.ETag, `"${correctMD5}"`); + metadata.getObjectMD(bucketName, objectName, + {}, log, (err, md) => { + assert(md); + assert + .strictEqual(md['content-md5'], correctMD5); + done(); + }); + }); + }); + }); + + // const mockModes = ['GOVERNANCE', 'COMPLIANCE']; + // mockModes.forEach(mockMode => { + // it(`should put an object with valid date & ${mockMode} mode`, done => { + // const testPutObjectRequest = new DummyRequest({ + // bucketName, + // namespace, + // objectKey: objectName, + // headers: { + // 'x-amz-object-lock-retain-until-date': mockDate, + // 'x-amz-object-lock-mode': mockMode, + // }, + // url: `/${bucketName}/${objectName}`, + // calculatedHash: 'vnR+tLdVF79rPPfF+7YvOg==', + // }, postBody); + // bucketPut(authInfo, testPutBucketRequestLock, log, () => { + // objectPut(authInfo, testPutObjectRequest, undefined, log, + // (err, headers) => { + // assert.ifError(err); + // assert.strictEqual(headers.ETag, `"${correctMD5}"`); + // metadata.getObjectMD(bucketName, objectName, {}, log, + // (err, md) => { + // const mode = md.retentionMode; + // const retainUntilDate = md.retentionDate; + // assert.ifError(err); + // assert(md); + // assert.strictEqual(mode, mockMode); + // assert.strictEqual(retainUntilDate, mockDate); + // done(); + // }); + // }); + // }); + // }); + // }); + + // const formatTime = time => time.slice(0, 20); + + // const testObjectLockConfigs = [ + // { + // testMode: 'COMPLIANCE', + // val: 30, + // type: 'Days', + // }, + // { + // testMode: 'GOVERNANCE', + // val: 5, + // type: 'Years', + // }, + // ]; + // testObjectLockConfigs.forEach(config => { + // const { testMode, type, val } = config; + // it('should put an object with default retention if object does not ' + + // 'have retention configuration but bucket has', done => { + // const testPutObjectRequest = new DummyRequest({ + // bucketName, + // namespace, + // objectKey: objectName, + // headers: {}, + // url: `/${bucketName}/${objectName}`, + // calculatedHash: 'vnR+tLdVF79rPPfF+7YvOg==', + // }, postBody); + + // const testObjLockRequest = { + // bucketName, + // headers: { host: `${bucketName}.s3.amazonaws.com` }, + // post: objectLockTestUtils.generateXml(testMode, val, type), + // }; + + // bucketPut(authInfo, testPutBucketRequestLock, log, () => { + // bucketPutObjectLock(authInfo, testObjLockRequest, log, () => { + // objectPut(authInfo, testPutObjectRequest, undefined, log, + // (err, headers) => { + // assert.ifError(err); + // assert.strictEqual(headers.ETag, `"${correctMD5}"`); + // metadata.getObjectMD(bucketName, objectName, {}, + // log, (err, md) => { + // const mode = md.retentionMode; + // const retainDate = md.retentionDate; + // const date = moment(); + // const days + // = type === 'Days' ? val : val * 365; + // const expectedDate + // = date.add(days, 'days'); + // assert.ifError(err); + // assert.strictEqual(mode, testMode); + // assert.strictEqual(formatTime(retainDate), + // formatTime(expectedDate.toISOString())); + // done(); + // }); + // }); + // }); + // }); + // }); + // }); + + + // it('should successfully put an object with legal hold ON', done => { + // const request = new DummyRequest({ + // bucketName, + // namespace, + // objectKey: objectName, + // headers: { + // 'x-amz-object-lock-legal-hold': 'ON', + // }, + // url: `/${bucketName}/${objectName}`, + // calculatedHash: 'vnR+tLdVF79rPPfF+7YvOg==', + // }, postBody); + + // bucketPut(authInfo, testPutBucketRequestLock, log, () => { + // objectPut(authInfo, request, undefined, log, (err, headers) => { + // assert.ifError(err); + // assert.strictEqual(headers.ETag, `"${correctMD5}"`); + // metadata.getObjectMD(bucketName, objectName, {}, log, + // (err, md) => { + // assert.ifError(err); + // assert.strictEqual(md.legalHold, true); + // done(); + // }); + // }); + // }); + // }); + + // it('should successfully put an object with legal hold OFF', done => { + // const request = new DummyRequest({ + // bucketName, + // namespace, + // objectKey: objectName, + // headers: { + // 'x-amz-object-lock-legal-hold': 'OFF', + // }, + // url: `/${bucketName}/${objectName}`, + // calculatedHash: 'vnR+tLdVF79rPPfF+7YvOg==', + // }, postBody); + + // bucketPut(authInfo, testPutBucketRequestLock, log, () => { + // objectPut(authInfo, request, undefined, log, (err, headers) => { + // assert.ifError(err); + // assert.strictEqual(headers.ETag, `"${correctMD5}"`); + // metadata.getObjectMD(bucketName, objectName, {}, log, + // (err, md) => { + // assert.ifError(err); + // assert(md); + // assert.strictEqual(md.legalHold, false); + // done(); + // }); + // }); + // }); + // }); + + // it('should successfully put an object with user metadata', done => { + // const testPutObjectRequest = new DummyRequest({ + // bucketName, + // namespace, + // objectKey: objectName, + // headers: { + // // Note that Node will collapse common headers into one + // // (e.g. "x-amz-meta-test: hi" and "x-amz-meta-test: + // // there" becomes "x-amz-meta-test: hi, there") + // // Here we are not going through an actual http + // // request so will not collapse properly. + // 'x-amz-meta-test': 'some metadata', + // 'x-amz-meta-test2': 'some more metadata', + // 'x-amz-meta-test3': 'even more metadata', + // }, + // url: `/${bucketName}/${objectName}`, + // calculatedHash: 'vnR+tLdVF79rPPfF+7YvOg==', + // }, postBody); + + // bucketPut(authInfo, testPutBucketRequest, log, () => { + // objectPut(authInfo, testPutObjectRequest, undefined, log, + // (err, resHeaders) => { + // assert.strictEqual(resHeaders.ETag, `"${correctMD5}"`); + // metadata.getObjectMD(bucketName, objectName, {}, log, + // (err, md) => { + // assert(md); + // assert.strictEqual(md['x-amz-meta-test'], + // 'some metadata'); + // assert.strictEqual(md['x-amz-meta-test2'], + // 'some more metadata'); + // assert.strictEqual(md['x-amz-meta-test3'], + // 'even more metadata'); + // done(); + // }); + // }); + // }); + // }); + + // it('If testingMode=true and the last-modified header is given, should set last-modified accordingly', done => { + // const imposedLastModified = '2024-07-19'; + // const testPutObjectRequest = new DummyRequest({ + // bucketName, + // namespace, + // objectKey: objectName, + // headers: { + // [lastModifiedHeader]: imposedLastModified, + // }, + // url: `/${bucketName}/${objectName}`, + // calculatedHash: 'vnR+tLdVF79rPPfF+7YvOg==', + // }, postBody); + + // bucketPut(authInfo, testPutBucketRequest, log, () => { + // const config = require('../../../lib/Config'); + // config.config.testingMode = true; + // objectPut(authInfo, testPutObjectRequest, undefined, log, + // (err, resHeaders) => { + // assert.strictEqual(resHeaders.ETag, `"${correctMD5}"`); + // metadata.getObjectMD(bucketName, objectName, {}, log, + // (err, md) => { + // assert(md); + + // const lastModified = md['last-modified']; + // const lastModifiedDate = lastModified.split('T')[0]; + // // last-modified date should be the one set by the last-modified header + // assert.strictEqual(lastModifiedDate, imposedLastModified); + + // // The header should be removed after being treated. + // assert(md[lastModifiedHeader] === undefined); + + // config.config.testingMode = false; + // done(); + // }); + // }); + // }); + // }); + + // it('should not take into acccount the last-modified header when testingMode=false', done => { + // const imposedLastModified = '2024-07-19'; + + // const testPutObjectRequest = new DummyRequest({ + // bucketName, + // namespace, + // objectKey: objectName, + // headers: { + // 'x-amz-meta-x-scal-last-modified': imposedLastModified, + // }, + // url: `/${bucketName}/${objectName}`, + // calculatedHash: 'vnR+tLdVF79rPPfF+7YvOg==', + // }, postBody); + + // bucketPut(authInfo, testPutBucketRequest, log, () => { + // const config = require('../../../lib/Config'); + // config.config.testingMode = false; + // objectPut(authInfo, testPutObjectRequest, undefined, log, + // (err, resHeaders) => { + // assert.strictEqual(resHeaders.ETag, `"${correctMD5}"`); + // metadata.getObjectMD(bucketName, objectName, {}, log, + // (err, md) => { + // assert(md); + // assert.strictEqual(md['x-amz-meta-x-scal-last-modified'], + // imposedLastModified); + // const lastModified = md['last-modified']; + // const lastModifiedDate = lastModified.split('T')[0]; + // const currentTs = new Date().toJSON(); + // const currentDate = currentTs.split('T')[0]; + // assert.strictEqual(lastModifiedDate, currentDate); + // done(); + // }); + // }); + // }); + // }); + + // it('should put an object with user metadata but no data', done => { + // const postBody = ''; + // const correctMD5 = 'd41d8cd98f00b204e9800998ecf8427e'; + // const testPutObjectRequest = new DummyRequest({ + // bucketName, + // namespace, + // objectKey: objectName, + // headers: { + // 'content-length': '0', + // 'x-amz-meta-test': 'some metadata', + // 'x-amz-meta-test2': 'some more metadata', + // 'x-amz-meta-test3': 'even more metadata', + // }, + // parsedContentLength: 0, + // url: `/${bucketName}/${objectName}`, + // calculatedHash: 'd41d8cd98f00b204e9800998ecf8427e', + // }, postBody); + + // bucketPut(authInfo, testPutBucketRequest, log, () => { + // objectPut(authInfo, testPutObjectRequest, undefined, log, + // (err, resHeaders) => { + // assert.strictEqual(resHeaders.ETag, `"${correctMD5}"`); + // assert.deepStrictEqual(ds, []); + // metadata.getObjectMD(bucketName, objectName, {}, log, + // (err, md) => { + // assert(md); + // assert.strictEqual(md.location, null); + // assert.strictEqual(md['x-amz-meta-test'], + // 'some metadata'); + // assert.strictEqual(md['x-amz-meta-test2'], + // 'some more metadata'); + // assert.strictEqual(md['x-amz-meta-test3'], + // 'even more metadata'); + // done(); + // }); + // }); + // }); + // }); + + // it('should not leave orphans in data when overwriting an object', done => { + // const testPutObjectRequest2 = new DummyRequest({ + // bucketName, + // namespace, + // objectKey: objectName, + // headers: {}, + // url: `/${bucketName}/${objectName}`, + // }, Buffer.from('I am another body', 'utf8')); + + // bucketPut(authInfo, testPutBucketRequest, log, () => { + // objectPut(authInfo, testPutObjectRequest, + // undefined, log, () => { + // objectPut(authInfo, testPutObjectRequest2, undefined, + // log, + // () => { + // // orphan objects don't get deleted + // // until the next tick + // // in memory + // setImmediate(() => { + // // Data store starts at index 1 + // assert.strictEqual(ds[0], undefined); + // assert.strictEqual(ds[1], undefined); + // assert.deepStrictEqual(ds[2].value, + // Buffer.from('I am another body', 'utf8')); + // done(); + // }); + // }); + // }); + // }); + // }); + + // it('should not leave orphans in data when overwriting an multipart upload object', done => { + // bucketPut(authInfo, testPutBucketRequest, log, () => { + // mpuUtils.createMPU(namespace, bucketName, objectName, log, + // (err, testUploadId) => { + // objectPut(authInfo, testPutObjectRequest, undefined, log, err => { + // assert.ifError(err); + // sinon.assert.calledWith(metadata.putObjectMD, + // any, any, any, sinon.match({ oldReplayId: testUploadId }), any, any); + // done(); + // }); + // }); + // }); + // }); + + // it('should not put object with retention configuration if object lock ' + + // 'is not enabled on the bucket', done => { + // const testPutObjectRequest = new DummyRequest({ + // bucketName, + // namespace, + // objectKey: objectName, + // headers: { + // 'x-amz-object-lock-retain-until-date': mockDate, + // 'x-amz-object-lock-mode': 'GOVERNANCE', + // }, + // url: `/${bucketName}/${objectName}`, + // calculatedHash: 'vnR+tLdVF79rPPfF+7YvOg==', + // }, postBody); + + // bucketPut(authInfo, testPutBucketRequest, log, () => { + // objectPut(authInfo, testPutObjectRequest, undefined, log, err => { + // assert.deepStrictEqual(err, errors.InvalidRequest + // .customizeDescription( + // 'Bucket is missing ObjectLockConfiguration')); + // done(); + // }); + // }); + // }); + // it('should forward a 400 back to client on metadata 408 response', () => { + // metadata.putObjectMD = + // (bucketName, objName, objVal, params, log, cb) => + // cb({ httpCode: 408 }); + + // bucketPut(authInfo, testPutBucketRequest, log, () => { + // objectPut(authInfo, testPutObjectRequest, undefined, log, + // err => { + // assert.strictEqual(err.code, 400); + // }); + // }); + // }); + + // it('should forward a 502 to the client for 4xx != 408', () => { + // metadata.putObjectMD = + // (bucketName, objName, objVal, params, log, cb) => + // cb({ httpCode: 412 }); + + // bucketPut(authInfo, testPutBucketRequest, log, () => { + // objectPut(authInfo, testPutObjectRequest, undefined, log, + // err => { + // assert.strictEqual(err.code, 502); + // }); + // }); + // }); + + + // it('should pass overheadField to metadata.putObjectMD for a non-versioned request', done => { + // const testPutObjectRequest = new DummyRequest({ + // bucketName, + // namespace, + // objectKey: objectName, + // headers: {}, + // url: `/${bucketName}/${objectName}`, + // contentMD5: correctMD5, + // }, postBody); + + // bucketPut(authInfo, testPutBucketRequest, log, () => { + // objectPut(authInfo, testPutObjectRequest, undefined, log, + // err => { + // assert.ifError(err); + // sinon.assert.calledWith(metadata.putObjectMD.lastCall, + // bucketName, objectName, any, sinon.match({ overheadField: sinon.match.array }), any, any); + // done(); + // }); + // }); + // }); + + // it('should pass overheadField to metadata.putObjectMD for a versioned request', done => { + // const testPutObjectRequest = versioningTestUtils + // .createPutObjectRequest(bucketName, objectName, Buffer.from('I am another body', 'utf8')); + // bucketPut(authInfo, testPutBucketRequest, log, () => { + // bucketPutVersioning(authInfo, enableVersioningRequest, log, () => { + // objectPut(authInfo, testPutObjectRequest, undefined, log, + // err => { + // assert.ifError(err); + // sinon.assert.calledWith(metadata.putObjectMD.lastCall, + // bucketName, objectName, any, sinon.match({ overheadField: sinon.match.array }), any, any); + // done(); + // } + // ); + // }); + // }); + // }); + + // it('should pass overheadField to metadata.putObjectMD for a version-suspended request', done => { + // const testPutObjectRequest = versioningTestUtils + // .createPutObjectRequest(bucketName, objectName, Buffer.from('I am another body', 'utf8')); + // bucketPut(authInfo, testPutBucketRequest, log, () => { + // bucketPutVersioning(authInfo, suspendVersioningRequest, log, () => { + // objectPut(authInfo, testPutObjectRequest, undefined, log, + // err => { + // assert.ifError(err); + // sinon.assert.calledWith(metadata.putObjectMD.lastCall, + // bucketName, objectName, any, sinon.match({ overheadField: sinon.match.array }), any, any); + // done(); + // } + // ); + // }); + // }); + // }); +}); + + + + +// describe('objectPut API with versioning', () => { +// beforeEach(() => { +// cleanup(); +// }); + +// const objData = ['foo0', 'foo1', 'foo2'].map(str => +// Buffer.from(str, 'utf8')); +// const testPutObjectRequests = objData.map(data => versioningTestUtils +// .createPutObjectRequest(bucketName, objectName, data)); + +// it('should delete latest version when creating new null version ' + +// 'if latest version is null version', done => { +// async.series([ +// callback => bucketPut(authInfo, testPutBucketRequest, log, +// callback), +// // putting null version by putting obj before versioning configured +// callback => objectPut(authInfo, testPutObjectRequests[0], undefined, +// log, err => { +// versioningTestUtils.assertDataStoreValues(ds, [objData[0]]); +// callback(err); +// }), +// callback => bucketPutVersioning(authInfo, suspendVersioningRequest, +// log, callback), +// // creating new null version by putting obj after ver suspended +// callback => objectPut(authInfo, testPutObjectRequests[1], +// undefined, log, err => { +// // wait until next tick since mem backend executes +// // deletes in the next tick +// setImmediate(() => { +// // old null version should be deleted +// versioningTestUtils.assertDataStoreValues(ds, +// [undefined, objData[1]]); +// callback(err); +// }); +// }), +// // create another null version +// callback => objectPut(authInfo, testPutObjectRequests[2], +// undefined, log, err => { +// setImmediate(() => { +// // old null version should be deleted +// versioningTestUtils.assertDataStoreValues(ds, +// [undefined, undefined, objData[2]]); +// callback(err); +// }); +// }), +// ], done); +// }); + +// describe('when null version is not the latest version', () => { +// const objData = ['foo0', 'foo1', 'foo2'].map(str => +// Buffer.from(str, 'utf8')); +// const testPutObjectRequests = objData.map(data => versioningTestUtils +// .createPutObjectRequest(bucketName, objectName, data)); +// beforeEach(done => { +// async.series([ +// callback => bucketPut(authInfo, testPutBucketRequest, log, +// callback), +// // putting null version: put obj before versioning configured +// callback => objectPut(authInfo, testPutObjectRequests[0], +// undefined, log, callback), +// callback => bucketPutVersioning(authInfo, +// enableVersioningRequest, log, callback), +// // put another version: +// callback => objectPut(authInfo, testPutObjectRequests[1], +// undefined, log, callback), +// callback => bucketPutVersioning(authInfo, +// suspendVersioningRequest, log, callback), +// ], err => { +// if (err) { +// return done(err); +// } +// versioningTestUtils.assertDataStoreValues(ds, +// objData.slice(0, 2)); +// return done(); +// }); +// }); + +// it('should still delete null version when creating new null version', +// done => { +// objectPut(authInfo, testPutObjectRequests[2], undefined, +// log, err => { +// assert.ifError(err, `Unexpected err: ${err}`); +// setImmediate(() => { +// // old null version should be deleted after putting +// // new null version +// versioningTestUtils.assertDataStoreValues(ds, +// [undefined, objData[1], objData[2]]); +// done(err); +// }); +// }); +// }); +// }); + +// it('should return BadDigest error and not leave orphans in data when ' + +// 'contentMD5 and completedHash do not match', done => { +// const testPutObjectRequest = new DummyRequest({ +// bucketName, +// namespace, +// objectKey: objectName, +// headers: {}, +// url: `/${bucketName}/${objectName}`, +// contentMD5: 'vnR+tLdVF79rPPfF+7YvOg==', +// }, Buffer.from('I am another body', 'utf8')); + +// bucketPut(authInfo, testPutBucketRequest, log, () => { +// objectPut(authInfo, testPutObjectRequest, undefined, log, +// err => { +// assert.deepStrictEqual(err, errors.BadDigest); +// // orphan objects don't get deleted +// // until the next tick +// // in memory +// setImmediate(() => { +// // Data store starts at index 1 +// assert.strictEqual(ds[0], undefined); +// assert.strictEqual(ds[1], undefined); +// done(); +// }); +// }); +// }); +// }); +// }); From aadef76ceb9b17853b66515be94c9d43e7053c0a Mon Sep 17 00:00:00 2001 From: Will Toozs Date: Tue, 4 Jun 2024 12:41:41 +0200 Subject: [PATCH 06/15] fixup: packages --- package.json | 2 +- yarn.lock | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package.json b/package.json index 0e3fd95804..fbef39da90 100644 --- a/package.json +++ b/package.json @@ -20,7 +20,7 @@ "homepage": "https://github.com/scality/S3#readme", "dependencies": { "@hapi/joi": "^17.1.0", - "arsenal": "git+https://github.com/scality/arsenal#a643a3e6ccbc49327339a285de1d4cb17afcd171", + "arsenal": "git+https://github.com/scality/arsenal#eebc71dfb0f46b60dcbce285391f41892974a0d2", "async": "~2.5.0", "aws-sdk": "2.905.0", "azure-storage": "^2.1.0", diff --git a/yarn.lock b/yarn.lock index ceabf1822b..26ad3627e1 100644 --- a/yarn.lock +++ b/yarn.lock @@ -494,9 +494,9 @@ arraybuffer.slice@~0.0.7: optionalDependencies: ioctl "^2.0.2" -"arsenal@git+https://github.com/scality/arsenal#a643a3e6ccbc49327339a285de1d4cb17afcd171": +"arsenal@git+https://github.com/scality/arsenal#eebc71dfb0f46b60dcbce285391f41892974a0d2": version "7.70.29" - resolved "git+https://github.com/scality/arsenal#a643a3e6ccbc49327339a285de1d4cb17afcd171" + resolved "git+https://github.com/scality/arsenal#eebc71dfb0f46b60dcbce285391f41892974a0d2" dependencies: "@js-sdsl/ordered-set" "^4.4.2" "@types/async" "^3.2.12" From 77a4fa03344b71ae8f6d6cc27b7ffa2b99f20a39 Mon Sep 17 00:00:00 2001 From: Will Toozs Date: Wed, 12 Jun 2024 16:15:24 +0200 Subject: [PATCH 07/15] first functional tests are fucntional --- .../aws-node-sdk/test/object/post.js | 393 +++++++++++++----- 1 file changed, 289 insertions(+), 104 deletions(-) diff --git a/tests/functional/aws-node-sdk/test/object/post.js b/tests/functional/aws-node-sdk/test/object/post.js index 2dc8f265d3..78027ad6f9 100644 --- a/tests/functional/aws-node-sdk/test/object/post.js +++ b/tests/functional/aws-node-sdk/test/object/post.js @@ -1,126 +1,311 @@ +const AWS = require('aws-sdk'); + +const axios = require('axios'); +const fs = require('fs'); +const path = require('path'); +const crypto = require('crypto'); +const FormData = require('form-data'); const assert = require('assert'); -const withV4 = require('../support/withV4'); -const BucketUtility = require('../../lib/utility/bucket-util'); -const provideRawOutput = require('../../lib/utility/provideRawOutput'); -const { taggingTests, generateMultipleTagQuery } - = require('../../lib/utility/tagging'); -const genMaxSizeMetaHeaders - = require('../../lib/utility/genMaxSizeMetaHeaders'); -const changeObjectLock = require('../../../../utilities/objectLock-util'); - -const bucket = 'bucket2putstuffin4324242'; -const object = 'object2putstuffin'; - -function _checkError(err, code, statusCode) { - assert(err, 'Expected error but found none'); - assert.strictEqual(err.code, code); - assert.strictEqual(err.statusCode, statusCode); -} - -describe('PUT object', () => { - withV4(sigCfg => { - let bucketUtil; - let s3; +// const filename = 'test-file.txt'; +// const bucketName = 'your-bucket-name'; +// const url = `http://localhost:8000/${bucketName}/`; + +const generateBucketName = () => `test-bucket-${crypto.randomBytes(8).toString('hex')}`; +let bucketName; +const filename = 'test-file.txt'; + +let url; +const s3 = new AWS.S3(); + +const calculateFields = (additionalConditions) => { + const ak = process.env.AWS_ACCESS_KEY_ID; + const sk = process.env.AWS_SECRET_ACCESS_KEY; + const region = 'us-east-1'; + const service = 's3'; + + const now = new Date(); + const formattedDate = now.toISOString().replace(/[:-]|\.\d{3}/g, ''); + const formatDate = (date) => { + const year = date.getUTCFullYear(); + const month = (date.getUTCMonth() + 1).toString().padStart(2, '0'); + const day = date.getUTCDate().toString().padStart(2, '0'); + return `${year}${month}${day}`; + }; + const shortFormattedDate = formatDate(now); + + const credential = `${ak}/${shortFormattedDate}/${region}/${service}/aws4_request`; + const conditionsFields = [ + { bucket: bucketName }, + { key: filename }, + { 'x-amz-credential': credential }, + { 'x-amz-algorithm': 'AWS4-HMAC-SHA256' }, + { 'x-amz-date': formattedDate }, + ]; + if (additionalConditions) { + additionalConditions.forEach(field => { + const key = Object.keys(field)[0]; + const value = field[key]; + conditionsFields.push({ [key]: value }); + }); + } + const policy = { + expiration: new Date(new Date().getTime() + 60000).toISOString(), + conditions: conditionsFields, + }; + const policyBase64 = Buffer.from(JSON.stringify(policy)).toString('base64'); + + const getSignatureKey = (key, dateStamp, regionName, serviceName) => { + const kDate = crypto.createHmac('sha256', `AWS4${key}`).update(dateStamp).digest(); + const kRegion = crypto.createHmac('sha256', kDate).update(regionName).digest(); + const kService = crypto.createHmac('sha256', kRegion).update(serviceName).digest(); + const kSigning = crypto.createHmac('sha256', kService).update('aws4_request').digest(); + return kSigning; + }; + const signingKey = getSignatureKey(sk, shortFormattedDate, region, service); + const signature = crypto.createHmac('sha256', signingKey).update(policyBase64).digest('hex'); + + const returnFields = [ + { name: 'X-Amz-Credential', value: credential }, + { name: 'X-Amz-Algorithm', value: 'AWS4-HMAC-SHA256' }, + { name: 'X-Amz-Signature', value: signature }, + { name: 'X-Amz-Date', value: formattedDate }, + { name: 'Policy', value: policyBase64 }, + { name: 'bucket', value: bucketName }, + { name: 'key', value: filename }, + ]; + if (!additionalConditions) { + return returnFields; + } + if (additionalConditions) { + additionalConditions.forEach(field => { + const key = Object.keys(field)[0]; + const value = field[key]; + returnFields.push({ name: key, value: value }); + }); + } + + return returnFields; +}; + +describe('AWS S3 POST Object with Policy', function () { + this.timeout(10000); // Increase timeout for potentially slow operations + + beforeEach(done => { + bucketName = generateBucketName(); + url = `https://${bucketName}.s3.amazonaws.com/`; + const filePath = path.join(__dirname, 'test-file.txt'); + const fileContent = 'This is a test file'; + + fs.writeFile(filePath, fileContent, err => { + if (err) { + return done(err); + } + + // Create the bucket + s3.createBucket({ Bucket: bucketName }, (err) => { + if (err) { + return done(err); + } + done(); + }); + }); + }); - beforeEach(() => { - bucketUtil = new BucketUtility('default', sigCfg); - s3 = bucketUtil.s3; - return s3.createBucket({ Bucket: bucket }).promise() - .catch(err => { - process.stdout.write(`Error creating bucket: ${err}\n`); - throw err; + afterEach(done => { + const filePath = path.join(__dirname, 'test-file.txt'); + + // Delete the file + fs.unlink(filePath, err => { + if (err) { + return done(err); + } + + // Delete the bucket and its contents + const deleteBucket = () => { + s3.deleteBucket({ Bucket: bucketName }, (err) => { + if (err && err.code !== 'NoSuchBucket') { + return done(err); + } + done(); + }); + }; + + // List objects in the bucket + s3.listObjects({ Bucket: bucketName }, (err, data) => { + if (err && err.code === 'NoSuchBucket') { + return done(); // Ignore the error if the bucket does not exist + } else if (err) { + return done(err); + } + + if (data.Contents.length === 0) { + // Bucket is already empty + return deleteBucket(); + } + + // Delete all objects in the bucket + const objects = data.Contents.map(item => ({ Key: item.Key })); + s3.deleteObjects({ + Bucket: bucketName, + Delete: { Objects: objects } + }, (err) => { + if (err) { + return done(err); + } + deleteBucket(); + }); }); }); + }); - afterEach(() => { - process.stdout.write('Emptying bucket'); - return bucketUtil.empty(bucket) - .then(() => { - process.stdout.write('Deleting bucket'); - return bucketUtil.deleteOne(bucket); + it('should successfully upload an object to S3 using a POST form', done => { + const fields = calculateFields(); + const formData = new FormData(); + + fields.forEach(field => { + formData.append(field.name, field.value); + }); + + formData.append('file', fs.createReadStream(path.join(__dirname, 'test-file.txt'))); + + formData.getLength((err, length) => { + if (err) { + return done(err); + } + + axios.post(url, formData, { + headers: { + ...formData.getHeaders(), + 'Content-Length': length, + }, }) - .catch(err => { - process.stdout.write('Error in afterEach'); - throw err; - }); + .then(response => { + assert.equal(response.status, 204); + done(); + }) + .catch(err => { + done(err); + }); }); + }); - it('should put an object and set the acl via query param', - done => { - const params = { Bucket: bucket, Key: 'key', - ACL: 'public-read', StorageClass: 'STANDARD' }; - const url = s3.getSignedUrl('putObject', params); - provideRawOutput(['-verbose', '-X', 'PUT', url, - '--upload-file', 'uploadFile'], httpCode => { - assert.strictEqual(httpCode, '200 OK'); - s3.getObjectAcl({ Bucket: bucket, Key: 'key' }, - (err, result) => { - assert.equal(err, null, 'Expected success, ' + - `got error ${JSON.stringify(err)}`); - assert.deepStrictEqual(result.Grants[1], { Grantee: - { Type: 'Group', URI: - 'http://acs.amazonaws.com/groups/global/AllUsers', - }, Permission: 'READ' }); - done(); + it('should successfully upload a larger file to S3 using a POST form', done => { + const largeFilePath = path.join(__dirname, 'large-test-file.txt'); + const largeFileContent = 'This is a larger test file'.repeat(10000); // Simulate a larger file + + fs.writeFile(largeFilePath, largeFileContent, err => { + if (err) { + return done(err); + } + + const fields = calculateFields(); + const formData = new FormData(); + + fields.forEach(field => { + formData.append(field.name, field.value); + }); + + formData.append('file', fs.createReadStream(largeFilePath)); + + formData.getLength((err, length) => { + if (err) { + return done(err); + } + + axios.post(url, formData, { + headers: { + ...formData.getHeaders(), + 'Content-Length': length, + }, + }) + .then(response => { + assert.equal(response.status, 204); + fs.unlink(largeFilePath, done); // Clean up the large file + }) + .catch(err => { + fs.unlink(largeFilePath, () => done(err)); // Clean up and propagate the error }); - }); }); + }); + }); - it('should put an object with key slash', - done => { - const params = { Bucket: bucket, Key: '/' }; - s3.putObject(params, err => { - assert.equal(err, null, 'Expected success, ' + - `got error ${JSON.stringify(err)}`); + it('should handle error when bucket does not exist', done => { + bucketName = generateBucketName(); + const tempUrl = `https://${bucketName}.s3.amazonaws.com/`; + const fields = calculateFields(); + const formData = new FormData(); + + fields.forEach(field => { + formData.append(field.name, field.value); + }); + + formData.append('file', fs.createReadStream(path.join(__dirname, 'test-file.txt'))); + + formData.getLength((err, length) => { + if (err) { + return done(err); + } + + axios.post(tempUrl, formData, { + headers: { + ...formData.getHeaders(), + 'Content-Length': length, + }, + }) + .then(() => { + done(new Error('Expected error but got success response')); + }) + .catch(err => { + + assert.equal(err.response.status, 404); done(); }); - }); + }); + }); - it('should return error if putting object w/ > 2KB user-defined md', - done => { - const metadata = genMaxSizeMetaHeaders(); - const params = { Bucket: bucket, Key: '/', Metadata: metadata }; - s3.putObject(params, err => { - assert.strictEqual(err, null, `Unexpected err: ${err}`); - // add one more byte to be over the limit - metadata.header0 = `${metadata.header0}${'0'}`; - s3.putObject(params, err => { - assert(err, 'Expected err but did not find one'); - assert.strictEqual(err.code, 'MetadataTooLarge'); - assert.strictEqual(err.statusCode, 400); + it('should upload an object with additional metadata', done => { + const additionalConditions = [ + { 'x-amz-meta-test-meta': 'test-value' }, + ]; + const fields = calculateFields(additionalConditions); + const formData = new FormData(); + + fields.forEach(field => { + formData.append(field.name, field.value); + }); + + formData.append('file', fs.createReadStream(path.join(__dirname, 'test-file.txt'))); + + formData.getLength((err, length) => { + if (err) { + return done(err); + } + + axios.post(url, formData, { + headers: { + ...formData.getHeaders(), + 'Content-Length': length, + }, + }) + .then(response => { + assert.equal(response.status, 204); + + // Verify metadata + s3.headObject({ Bucket: bucketName, Key: filename }, (err, data) => { + if (err) { + return done(err); + } + assert.equal(data.Metadata['test-meta'], 'test-value'); done(); }); + }) + .catch(err => { + done(err); }); - }); - - it('should return InvalidRequest error if putting object with ' + - 'object lock retention date and mode when object lock is not ' + - 'enabled on the bucket', done => { - const date = new Date(2050, 10, 10); - const params = { - Bucket: bucket, - Key: 'key', - ObjectLockRetainUntilDate: date, - ObjectLockMode: 'GOVERNANCE', - }; - s3.putObject(params, err => { - const expectedErrMessage - = 'Bucket is missing ObjectLockConfiguration'; - assert.strictEqual(err.code, 'InvalidRequest'); - assert.strictEqual(err.message, expectedErrMessage); - done(); - }); }); - - it('should return Not Implemented error for obj. encryption using ' + - 'customer-provided encryption keys', done => { - const params = { Bucket: bucket, Key: 'key', - SSECustomerAlgorithm: 'AES256' }; - s3.putObject(params, err => { - assert.strictEqual(err.code, 'NotImplemented'); - done(); - }); + }); +}); }); it('should return InvalidRedirectLocation if putting object ' + From 7bd862393a21e2017c19a1cc7833291fe5991a88 Mon Sep 17 00:00:00 2001 From: Will Toozs Date: Fri, 14 Jun 2024 12:11:17 +0200 Subject: [PATCH 08/15] update package --- package.json | 5 ++++- yarn.lock | 4 ++-- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/package.json b/package.json index fbef39da90..2693da102c 100644 --- a/package.json +++ b/package.json @@ -20,7 +20,7 @@ "homepage": "https://github.com/scality/S3#readme", "dependencies": { "@hapi/joi": "^17.1.0", - "arsenal": "git+https://github.com/scality/arsenal#eebc71dfb0f46b60dcbce285391f41892974a0d2", + "arsenal": "git+https://github.com/scality/arsenal#3363bb4bc0f52d11c568b25b4fe566dbec880262", "async": "~2.5.0", "aws-sdk": "2.905.0", "azure-storage": "^2.1.0", @@ -61,6 +61,7 @@ }, "scripts": { "ft_awssdk": "cd tests/functional/aws-node-sdk && mocha --reporter mocha-multi-reporters --reporter-options configFile=$INIT_CWD/tests/reporter-config.json test/", + "ft_post": "cd tests/functional/aws-node-sdk && mocha --reporter mocha-multi-reporters --reporter-options configFile=$INIT_CWD/tests/reporter-config.json test/object/post.js", "ft_awssdk_aws": "cd tests/functional/aws-node-sdk && AWS_ON_AIR=true mocha --reporter mocha-multi-reporters --reporter-options configFile=$INIT_CWD/tests/reporter-config.json test/", "ft_awssdk_buckets": "cd tests/functional/aws-node-sdk && mocha --reporter mocha-multi-reporters --reporter-options configFile=$INIT_CWD/tests/reporter-config.json test/bucket", "ft_awssdk_objects_misc": "cd tests/functional/aws-node-sdk && mocha --reporter mocha-multi-reporters --reporter-options configFile=$INIT_CWD/tests/reporter-config.json test/legacy test/object test/service test/support", @@ -91,6 +92,8 @@ "utapi_replay": "node lib/utapi/utapiReplay.js", "utapi_reindex": "node lib/utapi/utapiReindex.js", "test": "CI=true S3BACKEND=mem mocha --reporter mocha-multi-reporters --reporter-options configFile=$INIT_CWD/tests/reporter-config.json --recursive tests/unit", + "test_post_unit": "CI=true S3BACKEND=mem mocha --reporter mocha-multi-reporters --reporter-options configFile=$INIT_CWD/tests/reporter-config.json --recursive tests/unit/api/objectPost.js", + "test_versionid_base62": "VERSION_ID_ENCODING_TYPE=base62 CI=true S3BACKEND=mem mocha --reporter mocha-multi-reporters --reporter-options configFile=$INIT_CWD/tests/reporter-config.json --recursive tests/unit/api", "test_legacy_location": "CI=true S3_LOCATION_FILE=tests/locationConfig/locationConfigLegacy.json S3BACKEND=mem mocha --reporter mocha-multi-reporters --reporter-options configFile=$INIT_CWD/tests/reporter-config.json --recursive tests/unit", "test_utapi_v2": "mocha --reporter mocha-multi-reporters --reporter-options configFile=$INIT_CWD/tests/reporter-config.json --recursive tests/utapi", diff --git a/yarn.lock b/yarn.lock index 26ad3627e1..14f4e5dea3 100644 --- a/yarn.lock +++ b/yarn.lock @@ -494,9 +494,9 @@ arraybuffer.slice@~0.0.7: optionalDependencies: ioctl "^2.0.2" -"arsenal@git+https://github.com/scality/arsenal#eebc71dfb0f46b60dcbce285391f41892974a0d2": +"arsenal@git+https://github.com/scality/arsenal#3363bb4bc0f52d11c568b25b4fe566dbec880262": version "7.70.29" - resolved "git+https://github.com/scality/arsenal#eebc71dfb0f46b60dcbce285391f41892974a0d2" + resolved "git+https://github.com/scality/arsenal#3363bb4bc0f52d11c568b25b4fe566dbec880262" dependencies: "@js-sdsl/ordered-set" "^4.4.2" "@types/async" "^3.2.12" From c95aa64056e841c682d1b59340e3f3e70bad98c2 Mon Sep 17 00:00:00 2001 From: Will Toozs Date: Fri, 14 Jun 2024 12:15:04 +0200 Subject: [PATCH 09/15] update func tests --- .../aws-node-sdk/test/object/post.js | 1525 ++++++++++++++--- 1 file changed, 1248 insertions(+), 277 deletions(-) diff --git a/tests/functional/aws-node-sdk/test/object/post.js b/tests/functional/aws-node-sdk/test/object/post.js index 78027ad6f9..ae8849e52c 100644 --- a/tests/functional/aws-node-sdk/test/object/post.js +++ b/tests/functional/aws-node-sdk/test/object/post.js @@ -6,32 +6,49 @@ const path = require('path'); const crypto = require('crypto'); const FormData = require('form-data'); const assert = require('assert'); +const xml2js = require('xml2js'); + // const filename = 'test-file.txt'; // const bucketName = 'your-bucket-name'; // const url = `http://localhost:8000/${bucketName}/`; const generateBucketName = () => `test-bucket-${crypto.randomBytes(8).toString('hex')}`; -let bucketName; const filename = 'test-file.txt'; +const region = 'us-east-1'; +const ak = process.env.AWS_ACCESS_KEY_ID; +const sk = process.env.AWS_SECRET_ACCESS_KEY; + +const s3 = new AWS.S3({ + accessKeyId: ak, + secretAccessKey: sk, + region: 'us-east-1', +}); let url; -const s3 = new AWS.S3(); +let bucketName; + + +const formatDate = (date) => { + const year = date.getUTCFullYear(); + const month = (date.getUTCMonth() + 1).toString().padStart(2, '0'); + const day = date.getUTCDate().toString().padStart(2, '0'); + return `${year}${month}${day}`; +}; + +const getSignatureKey = (key, dateStamp, regionName, serviceName) => { + const kDate = crypto.createHmac('sha256', `AWS4${key}`).update(dateStamp).digest(); + const kRegion = crypto.createHmac('sha256', kDate).update(regionName).digest(); + const kService = crypto.createHmac('sha256', kRegion).update(serviceName).digest(); + const kSigning = crypto.createHmac('sha256', kService).update('aws4_request').digest(); + return kSigning; +}; -const calculateFields = (additionalConditions) => { - const ak = process.env.AWS_ACCESS_KEY_ID; - const sk = process.env.AWS_SECRET_ACCESS_KEY; - const region = 'us-east-1'; +const calculateFields = (ak, sk, additionalConditions) => { const service = 's3'; const now = new Date(); const formattedDate = now.toISOString().replace(/[:-]|\.\d{3}/g, ''); - const formatDate = (date) => { - const year = date.getUTCFullYear(); - const month = (date.getUTCMonth() + 1).toString().padStart(2, '0'); - const day = date.getUTCDate().toString().padStart(2, '0'); - return `${year}${month}${day}`; - }; const shortFormattedDate = formatDate(now); const credential = `${ak}/${shortFormattedDate}/${region}/${service}/aws4_request`; @@ -46,7 +63,12 @@ const calculateFields = (additionalConditions) => { additionalConditions.forEach(field => { const key = Object.keys(field)[0]; const value = field[key]; - conditionsFields.push({ [key]: value }); + const index = conditionsFields.findIndex(condition => condition.hasOwnProperty(key)); + if (index !== -1) { + conditionsFields[index][key] = value; + } else { + conditionsFields.push({ [key]: value }); + } }); } const policy = { @@ -55,13 +77,6 @@ const calculateFields = (additionalConditions) => { }; const policyBase64 = Buffer.from(JSON.stringify(policy)).toString('base64'); - const getSignatureKey = (key, dateStamp, regionName, serviceName) => { - const kDate = crypto.createHmac('sha256', `AWS4${key}`).update(dateStamp).digest(); - const kRegion = crypto.createHmac('sha256', kDate).update(regionName).digest(); - const kService = crypto.createHmac('sha256', kRegion).update(serviceName).digest(); - const kSigning = crypto.createHmac('sha256', kService).update('aws4_request').digest(); - return kSigning; - }; const signingKey = getSignatureKey(sk, shortFormattedDate, region, service); const signature = crypto.createHmac('sha256', signingKey).update(policyBase64).digest('hex'); @@ -81,7 +96,12 @@ const calculateFields = (additionalConditions) => { additionalConditions.forEach(field => { const key = Object.keys(field)[0]; const value = field[key]; - returnFields.push({ name: key, value: value }); + const index = returnFields.findIndex(f => f.name === key); + if (index !== -1) { + returnFields[index].value = value; + } else { + returnFields.push({ name: key, value: value }); + } }); } @@ -160,7 +180,7 @@ describe('AWS S3 POST Object with Policy', function () { }); it('should successfully upload an object to S3 using a POST form', done => { - const fields = calculateFields(); + const fields = calculateFields(ak, sk); const formData = new FormData(); fields.forEach(field => { @@ -199,7 +219,7 @@ describe('AWS S3 POST Object with Policy', function () { return done(err); } - const fields = calculateFields(); + const fields = calculateFields(ak, sk); const formData = new FormData(); fields.forEach(field => { @@ -233,7 +253,7 @@ describe('AWS S3 POST Object with Policy', function () { it('should handle error when bucket does not exist', done => { bucketName = generateBucketName(); const tempUrl = `https://${bucketName}.s3.amazonaws.com/`; - const fields = calculateFields(); + const fields = calculateFields(ak, sk); const formData = new FormData(); fields.forEach(field => { @@ -268,7 +288,7 @@ describe('AWS S3 POST Object with Policy', function () { const additionalConditions = [ { 'x-amz-meta-test-meta': 'test-value' }, ]; - const fields = calculateFields(additionalConditions); + const fields = calculateFields(ak, sk, additionalConditions); const formData = new FormData(); fields.forEach(field => { @@ -305,296 +325,1247 @@ describe('AWS S3 POST Object with Policy', function () { }); }); }); -}); - }); - it('should return InvalidRedirectLocation if putting object ' + - 'with x-amz-website-redirect-location header that does not start ' + - 'with \'http://\', \'https://\' or \'/\'', done => { - const params = { Bucket: bucket, Key: 'key', - WebsiteRedirectLocation: 'google.com' }; - s3.putObject(params, err => { - assert.strictEqual(err.code, 'InvalidRedirectLocation'); - assert.strictEqual(err.statusCode, 400); - done(); - }); + it('should handle error when policy is invalid', done => { + const fields = calculateFields(ak, sk); + fields.push({ name: 'Policy', value: 'invalid-policy' }); + const formData = new FormData(); + + fields.forEach(field => { + formData.append(field.name, field.value); }); - describe('Put object with tag set', () => { - taggingTests.forEach(taggingTest => { - it(taggingTest.it, done => { - const key = encodeURIComponent(taggingTest.tag.key); - const value = encodeURIComponent(taggingTest.tag.value); - const tagging = `${key}=${value}`; - const params = { Bucket: bucket, Key: object, - Tagging: tagging }; - s3.putObject(params, err => { - if (taggingTest.error) { - _checkError(err, taggingTest.error, 400); - return done(); - } - assert.equal(err, null, 'Expected success, ' + - `got error ${JSON.stringify(err)}`); - return s3.getObjectTagging({ Bucket: bucket, - Key: object }, (err, data) => { - assert.equal(err, null, 'Expected success, ' + - `got error ${JSON.stringify(err)}`); - assert.deepStrictEqual(data.TagSet[0], { - Key: taggingTest.tag.key, - Value: taggingTest.tag.value }); - done(); - }); - }); - }); - }); + formData.append('file', fs.createReadStream(path.join(__dirname, 'test-file.txt'))); - it('should be able to put object with 10 tags', - done => { - const taggingConfig = generateMultipleTagQuery(10); - s3.putObject({ Bucket: bucket, Key: object, - Tagging: taggingConfig }, err => { - assert.equal(err, null, 'Expected success, ' + - `got error ${JSON.stringify(err)}`); - done(); - }); - }); + formData.getLength((err, length) => { + if (err) { + return done(err); + } - it('should be able to put an empty Tag set', done => { - s3.putObject({ Bucket: bucket, Key: object, - Tagging: '', - }, err => { - assert.equal(err, null, 'Expected success, ' + - `got error ${JSON.stringify(err)}`); + axios.post(url, formData, { + headers: { + ...formData.getHeaders(), + 'Content-Length': length, + }, + }) + .then(() => { + done(new Error('Expected error but got success response')); + }) + .catch(err => { + assert.equal(err.response.status, 403); done(); }); - }); + }); + }); - it('should be able to put object with empty tags', - done => { - s3.putObject({ Bucket: bucket, Key: object, - Tagging: '&&&&&&&&&&&&&&&&&key1=value1' }, err => { - assert.equal(err, null, 'Expected success, ' + - `got error ${JSON.stringify(err)}`); - done(); - }); - }); + it('should handle error when signature is invalid', done => { + const fields = calculateFields(ak, sk); + fields.push({ name: 'X-Amz-Signature', value: 'invalid-signature' }); + const formData = new FormData(); - it('should allow putting 50 tags', done => { - const taggingConfig = generateMultipleTagQuery(50); - s3.putObject({ Bucket: bucket, Key: object, - Tagging: taggingConfig }, done); - }); + fields.forEach(field => { + formData.append(field.name, field.value); + }); - it('should return BadRequest if putting more that 50 tags', - done => { - const taggingConfig = generateMultipleTagQuery(51); - s3.putObject({ Bucket: bucket, Key: object, - Tagging: taggingConfig }, err => { - _checkError(err, 'BadRequest', 400); - done(); - }); - }); + formData.append('file', fs.createReadStream(path.join(__dirname, 'test-file.txt'))); - it('should return InvalidArgument if using the same key twice', - done => { - s3.putObject({ Bucket: bucket, Key: object, - Tagging: 'key1=value1&key1=value2' }, err => { - _checkError(err, 'InvalidArgument', 400); - done(); - }); - }); + formData.getLength((err, length) => { + if (err) { + return done(err); + } - it('should return InvalidArgument if using the same key twice ' + - 'and empty tags', done => { - s3.putObject({ Bucket: bucket, Key: object, - Tagging: '&&&&&&&&&&&&&&&&&key1=value1&key1=value2' }, - err => { - _checkError(err, 'InvalidArgument', 400); + axios.post(url, formData, { + headers: { + ...formData.getHeaders(), + 'Content-Length': length, + }, + }) + .then(() => { + done(new Error('Expected error but got success response')); + }) + .catch(err => { + assert.equal(err.response.status, 403); done(); }); - }); + }); + }); - it('should return InvalidArgument if tag with no key', done => { - s3.putObject({ Bucket: bucket, Key: object, - Tagging: '=value1', - }, err => { - _checkError(err, 'InvalidArgument', 400); - done(); - }); - }); + it('should be able to post an empty file and verify its existence', done => { + const emptyFilePath = path.join(__dirname, 'empty-file.txt'); - it('should return InvalidArgument putting object with ' + - 'bad encoded tags', done => { - s3.putObject({ Bucket: bucket, Key: object, Tagging: - 'key1==value1' }, err => { - _checkError(err, 'InvalidArgument', 400); - done(); - }); + // Create an empty file + fs.writeFile(emptyFilePath, '', err => { + if (err) { + return done(err); + } + + const fields = calculateFields(ak, sk); + const formData = new FormData(); + + fields.forEach(field => { + formData.append(field.name, field.value); }); - it('should return InvalidArgument putting object tag with ' + - 'invalid characters: %', done => { - const value = 'value1%'; - s3.putObject({ Bucket: bucket, Key: object, Tagging: - `key1=${value}` }, err => { - _checkError(err, 'InvalidArgument', 400); - done(); - }); + formData.append('file', fs.createReadStream(emptyFilePath)); + + formData.getLength((err, length) => { + if (err) { + return done(err); + } + + axios.post(url, formData, { + headers: { + ...formData.getHeaders(), + 'Content-Length': length, + }, + }) + .then(response => { + assert.equal(response.status, 204); + + // Initialize AWS SDK with credentials and region + const s3 = new AWS.S3({ + accessKeyId: process.env.AWS_ACCESS_KEY_ID, + secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY, + region: 'us-east-1' + }); + + // Check if the object exists + s3.headObject({ Bucket: bucketName, Key: filename }, (err, data) => { + if (err) { + return done(err); + } + + // Verify the object size is 0 + assert.equal(data.ContentLength, 0); + + // Clean up: delete the empty file locally and from S3 + fs.unlink(emptyFilePath, err => { + if (err) { + return done(err); + } + + s3.deleteObject({ Bucket: bucketName, Key: filename }, (err, data) => { + if (err) { + return done(err); + } + + done(); + }); + }); + }); + }) + .catch(err => { + done(err); + }); }); }); }); -}); -describe('PUT object with object lock', () => { - withV4(sigCfg => { - let bucketUtil; - let s3; - - beforeEach(() => { - bucketUtil = new BucketUtility('default', sigCfg); - s3 = bucketUtil.s3; - return s3.createBucket({ - Bucket: bucket, - ObjectLockEnabledForBucket: true, - }).promise() - .catch(err => { - process.stdout.write(`Error creating bucket: ${err}\n`); - throw err; - }); + it('should handle error when file is missing', done => { + const fields = calculateFields(ak, sk); + const formData = new FormData(); + + fields.forEach(field => { + formData.append(field.name, field.value); }); - afterEach(() => { - process.stdout.write('Emptying bucket'); - return bucketUtil.empty(bucket) - .then(() => { - process.stdout.write('Deleting bucket'); - return bucketUtil.deleteOne(bucket); + formData.getLength((err, length) => { + if (err) { + return done(err); + } + + axios.post(url, formData, { + headers: { + ...formData.getHeaders(), + 'Content-Length': length, + }, }) - .catch(err => { - process.stdout.write('Error in afterEach'); - throw err; - }); + .then(() => { + done(new Error('Expected error but got success response')); + }) + .catch(err => { + assert.equal(err.response.status, 400); + done(); + }); }); + }); - it('should put object with valid object lock retention date and ' + - 'mode when object lock is enabled on the bucket', done => { - const date = new Date(2050, 10, 10); - const params = { - Bucket: bucket, - Key: 'key1', - ObjectLockRetainUntilDate: date, - ObjectLockMode: 'COMPLIANCE', - }; - s3.putObject(params, (err, res) => { - assert.ifError(err); - changeObjectLock( - [{ bucket, key: 'key1', versionId: res.VersionId }], '', done); - }); - }); + it('should upload an object with key slash', done => { + const fields = calculateFields(ak, sk, [{ key: 'key', value: '/' }]); - it('should put object with valid object lock retention date and ' + - 'mode when object lock is enabled on the bucket', done => { - const date = new Date(2050, 10, 10); - const params = { - Bucket: bucket, - Key: 'key2', - ObjectLockRetainUntilDate: date, - ObjectLockMode: 'GOVERNANCE', - }; - s3.putObject(params, (err, res) => { - assert.ifError(err); - changeObjectLock( - [{ bucket, key: 'key2', versionId: res.VersionId }], '', done); - }); - }); + const formData = new FormData(); - it('should error with invalid object lock mode header', done => { - const date = new Date(2050, 10, 10); - const params = { - Bucket: bucket, - Key: 'key3', - ObjectLockMode: 'Governance', - ObjectLockRetainUntilDate: date, - }; - s3.putObject(params, err => { - assert.strictEqual(err.code, 'InvalidArgument'); - assert.strictEqual(err.message, 'Unknown wormMode directive'); - done(); - }); + fields.forEach(field => { + formData.append(field.name, field.value); }); - it('should put object with valid legal hold status ON', done => { - const params = { - Bucket: bucket, - Key: 'key4', - ObjectLockLegalHoldStatus: 'ON', - }; - s3.putObject(params, (err, res) => { - assert.ifError(err); - changeObjectLock( - [{ bucket, key: 'key4', versionId: res.VersionId }], '', done); - }); + formData.append('file', fs.createReadStream(path.join(__dirname, 'test-file.txt'))); + + formData.getLength((err, length) => { + if (err) { + return done(err); + } + + axios.post(url, formData, { + headers: { + ...formData.getHeaders(), + 'Content-Length': length, + }, + }) + .then(response => { + assert.equal(response.status, 204); + done(); + }) + .catch(err => { + done(err); + }); }); + }); - it('should put object with valid legal hold status OFF', done => { - const params = { - Bucket: bucket, - Key: 'key5', - ObjectLockLegalHoldStatus: 'OFF', - }; - s3.putObject(params, err => { - assert.ifError(err); - done(); - }); + it('should return InvalidRedirectLocation if posting object with x-amz-website-redirect-location header that does not start with "http://", "https://", or "/"', done => { + const fields = calculateFields(ak, sk, [{ 'x-amz-website-redirect-location': 'invalid-url' }]); + const formData = new FormData(); + + fields.forEach(field => { + formData.append(field.name, field.value); }); - it('should error with invalid legal hold status', done => { - const params = { - Bucket: bucket, - Key: 'key6', - ObjectLockLegalHoldStatus: 'on', - }; - s3.putObject(params, err => { - assert.strictEqual(err.code, 'InvalidArgument'); - assert.strictEqual(err.message, - 'Legal hold status must be one of "ON", "OFF"'); - done(); - }); + formData.append('file', fs.createReadStream(path.join(__dirname, 'test-file.txt'))); + + formData.getLength((err, length) => { + if (err) { + return done(err); + } + + axios.post(url, formData, { + headers: { + ...formData.getHeaders(), + 'Content-Length': length, + }, + }) + .then(() => { + done(new Error('Expected InvalidRedirectLocation error but got success response')); + }) + .catch(err => { + assert.equal(err.response.status, 400); + done(); + }); }); + }); - it('should return error when object lock retain until date header is ' + - 'provided but object lock mode header is missing', done => { - const date = new Date(2050, 10, 10); - const params = { - Bucket: bucket, - Key: 'key7', - ObjectLockRetainUntilDate: date, - }; - s3.putObject(params, err => { - const expectedErrMessage - = 'x-amz-object-lock-retain-until-date and ' + - 'x-amz-object-lock-mode must both be supplied'; - assert.strictEqual(err.code, 'InvalidArgument'); - assert.strictEqual(err.message, expectedErrMessage); - done(); - }); + it('should successfully upload object with valid x-amz-website-redirect-location header and verify it', done => { + const validRedirectLocation = 'http://example.com'; + const fields = calculateFields(ak, sk, [{ 'x-amz-website-redirect-location': validRedirectLocation }]); + const formData = new FormData(); + + fields.forEach(field => { + formData.append(field.name, field.value); }); - it('should return error when object lock mode header is provided ' + - 'but object lock retain until date header is missing', done => { - const params = { - Bucket: bucket, - Key: 'key8', - ObjectLockMode: 'GOVERNANCE', - }; - s3.putObject(params, err => { - const expectedErrMessage - = 'x-amz-object-lock-retain-until-date and ' + - 'x-amz-object-lock-mode must both be supplied'; - assert.strictEqual(err.code, 'InvalidArgument'); - assert.strictEqual(err.message, expectedErrMessage); - done(); - }); + formData.append('file', fs.createReadStream(path.join(__dirname, 'test-file.txt'))); + + formData.getLength((err, length) => { + if (err) { + return done(err); + } + + axios.post(url, formData, { + headers: { + ...formData.getHeaders(), + 'Content-Length': length, + }, + }) + .then(response => { + assert.equal(response.status, 204); + + // Get the object's website redirect location + s3.getObject({ Bucket: bucketName, Key: filename }, (err, data) => { + if (err) { + return done(err); + } + assert.equal(data.WebsiteRedirectLocation, validRedirectLocation); + done(); + }); + }) + .catch(err => { + done(err); + }); }); }); -}); + + + it('should be able to post object with 10 tags and verify them', done => { + const tags = Array.from({ length: 9 }, (_, i) => `Tag${i + 1}Value${i + 1}`).join(''); + const taggingXML = `${tags}`; + const fields = calculateFields(ak, sk, [{ 'tagging': taggingXML }]); + + const formData = new FormData(); + + fields.forEach(field => { + formData.append(field.name, field.value); + }); + + formData.append('file', fs.createReadStream(path.join(__dirname, 'test-file.txt'))); + + formData.getLength((err, length) => { + if (err) { + return done(err); + } + + axios.post(url, formData, { + headers: { + ...formData.getHeaders(), + 'Content-Length': length, + }, + }) + .then(response => { + assert.equal(response.status, 204); + + // Initialize AWS SDK with credentials and region + const s3 = new AWS.S3({ + accessKeyId: process.env.AWS_ACCESS_KEY_ID, + secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY, + region: 'us-east-1' + }); + + // Get the object tags + s3.getObjectTagging({ Bucket: bucketName, Key: filename }, (err, data) => { + if (err) { + return done(err); + } + + const tags = data.TagSet; + assert.equal(tags.length, 9); + + done(); + }); + }) + .catch(err => { + done(err); + }); + }); + }); + + it('should refuse invalid tagging (wrong XML)', done => { + const invalidTaggingXML = `Tag1Value1`; // Missing closing + const fields = calculateFields(ak, sk, [{ tagging: invalidTaggingXML }]); + + const formData = new FormData(); + + fields.forEach(field => { + formData.append(field.name, field.value); + }); + + formData.append('file', fs.createReadStream(path.join(__dirname, 'test-file.txt'))); + + formData.getLength((err, length) => { + if (err) { + return done(err); + } + + axios.post(url, formData, { + headers: { + ...formData.getHeaders(), + 'Content-Length': length, + }, + }) + .then(() => { + done(new Error('Expected error but got success response')); + }) + .catch(err => { + assert.equal(err.response.status, 400); // 400 Bad Request for invalid XML + done(); + }); + }); + }); + + it('should return the specified success_action_status code', done => { + const successActionStatus = 201; + const fields = calculateFields(ak, sk, [{ 'success_action_status': successActionStatus.toString() }]); + + const formData = new FormData(); + + fields.forEach(field => { + formData.append(field.name, field.value); + }); + + formData.append('file', fs.createReadStream(path.join(__dirname, 'test-file.txt'))); + + formData.getLength((err, length) => { + if (err) { + return done(err); + } + + axios.post(url, formData, { + headers: { + ...formData.getHeaders(), + 'Content-Length': length, + }, + }) + .then(response => { + assert.equal(response.status, successActionStatus); + done(); + }) + .catch(err => { + done(err); + }); + }); + }); + + it('should return an error if form data (excluding file) exceeds 20KB', done => { + const fields = calculateFields(ak, sk); + + // Add additional fields to make form data exceed 20KB + const largeValue = 'A'.repeat(1024); // 1KB value + for (let i = 0; i < 21; i++) { // Add 21 fields of 1KB each to exceed 20KB + fields.push({ name: `field${i}`, value: largeValue }); + } + + const formData = new FormData(); + + fields.forEach(field => { + formData.append(field.name, field.value); + }); + + formData.append('file', fs.createReadStream(path.join(__dirname, 'test-file.txt'))); + + formData.getLength((err, length) => { + if (err) { + return done(err); + } + + axios.post(url, formData, { + headers: { + ...formData.getHeaders(), + 'Content-Length': length, + }, + }) + .then(response => { + done(new Error('Request should not succeed with form data exceeding 20KB')); + }) + .catch(err => { + assert.ok(err.response, 'Error should be returned by axios'); + + // Parse the XML error response + xml2js.parseString(err.response.data, (err, result) => { + if (err) { + return done(err); + } + + const error = result.Error; + assert.equal(error.Code[0], 'MaxPostPreDataLengthExceeded'); + assert.equal(error.Message[0], 'Your POST request fields preceeding the upload file was too large.'); + done(); + }); + }); + }); + }); + + + it('should return an error if there is a discrepancy between policy and form fields', done => { + let fields = calculateFields(ak, sk); + + // Find and replace the 'key' field with an invalid name + fields = fields.map(field => { + if (field.name === 'key') { + return { name: 'key', value: 'invalid-key-name' }; + } + return field; + }); + const formData = new FormData(); + + fields.forEach(field => { + formData.append(field.name, field.value); + }); + + formData.append('file', fs.createReadStream(path.join(__dirname, 'test-file.txt'))); + + formData.getLength((err, length) => { + if (err) { + return done(err); + } + + axios.post(url, formData, { + headers: { + ...formData.getHeaders(), + 'Content-Length': length, + }, + }) + .then(response => { + done(new Error('Request should not succeed with a policy and form field discrepancy')); + }) + .catch(err => { + assert.ok(err.response, 'Error should be returned by axios'); + + // Parse the XML error response + xml2js.parseString(err.response.data, (parseErr, result) => { + if (parseErr) { + return done(parseErr); + } + + const error = result.Error; + assert.equal(error.Code[0], 'AccessDenied', 'Expected PolicyConditionFailed error code'); + assert.ok( + error.Message[0].includes('Invalid according to Policy: Policy Condition failed'), + 'Expected error message to include policy condition failure details' + ); + done(); + }); + }); + }); + }); + + + it('should return an error for invalid keys', done => { + const invalidAccessKeyId = 'INVALIDACCESSKEY'; + const invalidSecretAccessKey = 'INVALIDSECRETKEY'; + let fields = calculateFields(invalidAccessKeyId, invalidSecretAccessKey); + + // Modify the signature to be invalid + fields = fields.map(field => { + if (field.name === 'X-Amz-Signature') { + return { name: 'X-Amz-Signature', value: 'invalid-signature' }; + } + return field; + }); + + const formData = new FormData(); + + fields.forEach(field => { + formData.append(field.name, field.value); + }); + + formData.append('file', fs.createReadStream(path.join(__dirname, 'test-file.txt'))); + + formData.getLength((err, length) => { + if (err) { + return done(err); + } + + axios.post(url, formData, { + headers: { + ...formData.getHeaders(), + 'Content-Length': length, + }, + }) + .then(response => { + done(new Error('Request should not succeed with an invalid keys')); + }) + .catch(err => { + assert.ok(err.response, 'Error should be returned by axios'); + + // Parse the XML error response + xml2js.parseString(err.response.data, (parseErr, result) => { + if (parseErr) { + return done(parseErr); + } + + const error = result.Error; + assert.equal(error.Code[0], 'InvalidAccessKeyId', 'Expected InvalidAccessKeyId error code'); + done(); + }); + }); + }); + }); + + + it('should return an error for invalid signature', done => { + let fields = calculateFields(ak, sk); + const laterThanNow = new Date(new Date().getTime() + 60000); + const shortFormattedDate = formatDate(laterThanNow); + + const signingKey = getSignatureKey(sk, shortFormattedDate, 'ap-east-1', 's3'); + const signature = crypto.createHmac('sha256', signingKey).update(fields.find(field => field.name === 'Policy').value).digest('hex'); + + // Modify the signature to be invalid + fields = fields.map(field => { + if (field.name === 'X-Amz-Signature') { + return { name: 'X-Amz-Signature', value: signature }; + } + return field; + }); + + const formData = new FormData(); + + fields.forEach(field => { + formData.append(field.name, field.value); + }); + + formData.append('file', fs.createReadStream(path.join(__dirname, 'test-file.txt'))); + + formData.getLength((err, length) => { + if (err) { + return done(err); + } + + axios.post(url, formData, { + headers: { + ...formData.getHeaders(), + 'Content-Length': length, + }, + }) + .then(response => { + done(new Error('Request should not succeed with an invalid signature')); + }) + .catch(err => { + assert.ok(err.response, 'Error should be returned by axios'); + + // Parse the XML error response + xml2js.parseString(err.response.data, (parseErr, result) => { + if (parseErr) { + return done(parseErr); + } + + const error = result.Error; + assert.equal(error.Code[0], 'SignatureDoesNotMatch', 'Expected SignatureDoesNotMatch error code'); + assert.ok( + error.Message[0].includes('The request signature we calculated does not match the signature you provided'), + 'Expected error message to include signature mismatch details' + ); + done(); + }); + }); + }); + }); + + it('should return an error for invalid credential', done => { + let fields = calculateFields(ak, sk); + const laterThanNow = new Date(new Date().getTime() + 60000); + const shortFormattedDate = formatDate(laterThanNow); + + const credential = `${ak}/${shortFormattedDate}/ap-east-1/s3/aws4_request`; + + // Modify the signature to be invalid + fields = fields.map(field => { + if (field.name === 'X-Amz-Credential') { + return { name: 'X-Amz-Credential', value: credential }; + } + return field; + }); + + const formData = new FormData(); + + fields.forEach(field => { + formData.append(field.name, field.value); + }); + + formData.append('file', fs.createReadStream(path.join(__dirname, 'test-file.txt'))); + + formData.getLength((err, length) => { + if (err) { + return done(err); + } + + axios.post(url, formData, { + headers: { + ...formData.getHeaders(), + 'Content-Length': length, + }, + }) + .then(response => { + done(new Error('Request should not succeed with an invalid credential')); + }) + .catch(err => { + assert.ok(err.response, 'Error should be returned by axios'); + + // Parse the XML error response + xml2js.parseString(err.response.data, (parseErr, result) => { + if (parseErr) { + return done(parseErr); + } + + const error = result.Error; + assert.equal(error.Code[0], 'InvalidArgument', 'Expected InvalidArgument error code'); + done(); + }); + }); + }); + }); + + it('should successfully upload object with valid checksum and verify it', done => { + const filePath = path.join(__dirname, 'test-file.txt'); + const fileContent = 'This is a test file'; + + // Calculate the SHA-256 checksum of the file content + const validChecksum = crypto.createHash('sha256').update(fileContent).digest('base64'); + const fields = calculateFields(ak, sk, [{ 'x-amz-checksum-sha256': validChecksum }]); + const formData = new FormData(); + + fields.forEach(field => { + formData.append(field.name, field.value); + }); + + formData.append('file', fs.createReadStream(filePath)); + + formData.getLength((err, length) => { + if (err) { + return done(err); + } + + axios.post(url, formData, { + headers: { + ...formData.getHeaders(), + 'Content-Length': length, + }, + }) + .then(response => { + assert.equal(response.status, 204); + + // Get the object to verify it was uploaded successfully + s3.getObject({ Bucket: bucketName, Key: filename }, (err, data) => { + if (err) { + return done(err); + } + assert.equal(data.ContentLength, fileContent.length); + done(); + }); + }) + .catch(err => { + done(err); + }); + }); + }); + + it('should return error if posting object with invalid checksum', done => { + const invalidChecksum = 'invalid-checksum'; // Invalid checksum value + const fields = calculateFields(ak, sk, [{ 'x-amz-checksum-sha256': invalidChecksum }]); + const formData = new FormData(); + + fields.forEach(field => { + formData.append(field.name, field.value); + }); + + formData.append('file', fs.createReadStream(path.join(__dirname, 'test-file.txt'))); + + formData.getLength((err, length) => { + if (err) { + return done(err); + } + + axios.post(url, formData, { + headers: { + ...formData.getHeaders(), + 'Content-Length': length, + }, + }) + .then(() => { + done(new Error('Expected error but got success response')); + }) + .catch(err => { + assert.equal(err.response.status, 400); // Assuming 400 Bad Request for invalid checksum + done(); + }); + }); + }); + + + + + + + + + + + + + + + + + + + + + + + + + // Test needs bucket with ACLs enabled + // it('should upload an object with a specified ACL and verify it', done => { + // const aclValue = 'public-read'; // Example ACL value + // const fields = calculateFields(ak, sk, [{ 'acl': aclValue }]); + + // const formData = new FormData(); + + // fields.forEach(field => { + // formData.append(field.name, field.value); + // }); + + // formData.append('file', fs.createReadStream(path.join(__dirname, 'test-file.txt'))); + + // formData.getLength((err, length) => { + // if (err) { + // return done(err); + // } + + // axios.post(url, formData, { + // headers: { + // ...formData.getHeaders(), + // 'Content-Length': length, + // }, + // }) + // .then(response => { + // assert.equal(response.status, 204); + + + // // Get the object's ACL + // s3.getObjectAcl({ Bucket: bucketName, Key: filename }, (err, data) => { + // if (err) { + // return done(err); + // } + + // const grants = data.Grants; + // const publicReadGrant = grants.find(grant => + // grant.Permission === 'READ' && grant.Grantee.URI === 'http://acs.amazonaws.com/groups/global/AllUsers' + // ); + + // assert.ok(publicReadGrant, 'Expected public-read ACL grant not found'); + + // done(); + // }); + // }) + // .catch(err => { + // done(err); + // }); + // }); + // }); + + // it('should be able to post an empty Tag set', done => { + // const fields = calculateFields([{ 'x-amz-tagging': '' }]); + // const formData = new FormData(); + + // fields.forEach(field => { + // formData.append(field.name, field.value); + // }); + + // formData.append('file', fs.createReadStream(path.join(__dirname, 'test-file.txt'))); + + // formData.getLength((err, length) => { + // if (err) { + // return done(err); + // } + + // axios.post(url, formData, { + // headers: { + // ...formData.getHeaders(), + // 'Content-Length': length, + // }, + // }) + // .then(response => { + // assert.equal(response.status, 204); + // done(); + // }) + // .catch(err => { + // done(err); + // }); + // }); + // }); + + // it('should be able to post object with empty tags', done => { + // const fields = calculateFields([{ 'x-amz-tagging': 'Tag1=' }]); + // const formData = new FormData(); + + // fields.forEach(field => { + // formData.append(field.name, field.value); + // }); + + // formData.append('file', fs.createReadStream(path.join(__dirname, 'test-file.txt'))); + + // formData.getLength((err, length) => { + // if (err) { + // return done(err); + // } + + // axios.post(url, formData, { + // headers: { + // ...formData.getHeaders(), + // 'Content-Length': length, + // }, + // }) + // .then(response => { + // assert.equal(response.status, 204); + // done(); + // }) + // .catch(err => { + // done(err); + // }); + // }); + // }); + + // it('should allow posting 50 tags', done => { + // const tags = Array.from({ length: 50 }, (_, i) => `Tag${i + 1}=Value${i + 1}`).join('&'); + // const fields = calculateFields([{ 'x-amz-tagging': tags }]); + // const formData = new FormData(); + + // fields.forEach(field => { + // formData.append(field.name, field.value); + // }); + + // formData.append('file', fs.createReadStream(path.join(__dirname, 'test-file.txt'))); + + // formData.getLength((err, length) => { + // if (err) { + // return done(err); + // } + + // axios.post(url, formData, { + // headers: { + // ...formData.getHeaders(), + // 'Content-Length': length, + // }, + // }) + // .then(response => { + // assert.equal(response.status, 204); + // done(); + // }) + // .catch(err => { + // done(err); + // }); + // }); + // }); + + // it('should return BadRequest if posting more than 50 tags', done => { + // const tags = Array.from({ length: 51 }, (_, i) => `Tag${i + 1}=Value${i + 1}`).join('&'); + // const fields = calculateFields([{ 'x-amz-tagging': tags }]); + // const formData = new FormData(); + + // fields.forEach(field => { + // formData.append(field.name, field.value); + // }); + + // formData.append('file', fs.createReadStream(path.join(__dirname, 'test-file.txt'))); + + // formData.getLength((err, length) => { + // if (err) { + // return done(err); + // } + + // axios.post(url, formData, { + // headers: { + // ...formData.getHeaders(), + // 'Content-Length': length, + // }, + // }) + // .then(() => { + // done(new Error('Expected BadRequest error but got success response')); + // }) + // .catch(err => { + // assert.equal(err.response.status, 400); + // done(); + // }); + // }); + // }); + + // it('should return InvalidArgument if using the same key twice', done => { + // const tags = 'Tag1=Value1&Tag1=Value2'; + // const fields = calculateFields([{ 'x-amz-tagging': tags }]); + // const formData = new FormData(); + + // fields.forEach(field => { + // formData.append(field.name, field.value); + // }); + + // formData.append('file', fs.createReadStream(path.join(__dirname, 'test-file.txt'))); + + // formData.getLength((err, length) => { + // if (err) { + // return done(err); + // } + + // axios.post(url, formData, { + // headers: { + // ...formData.getHeaders(), + // 'Content-Length': length, + // }, + // }) + // .then(() => { + // done(new Error('Expected InvalidArgument error but got success response')); + // }) + // .catch(err => { + // assert.equal(err.response.status, 400); + // done(); + // }); + // }); + // }); + + // it('should return InvalidArgument if using the same key twice and empty tags', done => { + // const tags = 'Tag1=&Tag1=Value2'; + // const fields = calculateFields([{ 'x-amz-tagging': tags }]); + // const formData = new FormData(); + + // fields.forEach(field => { + // formData.append(field.name, field.value); + // }); + + // formData.append('file', fs.createReadStream(path.join(__dirname, 'test-file.txt'))); + + // formData.getLength((err, length) => { + // if (err) { + // return done(err); + // } + + // axios.post(url, formData, { + // headers: { + // ...formData.getHeaders(), + // 'Content-Length': length, + // }, + // }) + // .then(() => { + // done(new Error('Expected InvalidArgument error but got success response')); + // }) + // .catch(err => { + // assert.equal(err.response.status, 400); + // done(); + // }); + // }); + // }); + + // it('should return InvalidArgument if tag with no key', done => { + // const tags = '=Value1'; + // const fields = calculateFields([{ 'x-amz-tagging': tags }]); + // const formData = new FormData(); + + // fields.forEach(field => { + // formData.append(field.name, field.value); + // }); + + // formData.append('file', fs.createReadStream(path.join(__dirname, 'test-file.txt'))); + + // formData.getLength((err, length) => { + // if (err) { + // return done(err); + // } + + // axios.post(url, formData, { + // headers: { + // ...formData.getHeaders(), + // 'Content-Length': length, + // }, + // }) + // .then(() => { + // done(new Error('Expected InvalidArgument error but got success response')); + // }) + // .catch(err => { + // assert.equal(err.response.status, 400); + // done(); + // }); + // }); + // }); + + // it('should return InvalidArgument posting object with bad encoded tags', done => { + // const tags = 'Tag1=Value1%2'; // Bad encoding + // const fields = calculateFields([{ 'x-amz-tagging': tags }]); + // const formData = new FormData(); + + // fields.forEach(field => { + // formData.append(field.name, field.value); + // }); + + // formData.append('file', fs.createReadStream(path.join(__dirname, 'test-file.txt'))); + + // formData.getLength((err, length) => { + // if (err) { + // return done(err); + // } + + // axios.post(url, formData, { + // headers: { + // ...formData.getHeaders(), + // 'Content-Length': length, + // }, + // }) + // .then(() => { + // done(new Error('Expected InvalidArgument error but got success response')); + // }) + // .catch(err => { + // assert.equal(err.response.status, 400); + // done(); + // }); + // }); + // }); + + // it('should return InvalidArgument posting object tag with invalid characters: %', done => { + // const tags = 'Tag1=Value%1'; + // const fields = calculateFields([{ 'x-amz-tagging': tags }]); + // const formData = new FormData(); + + // fields.forEach(field => { + // formData.append(field.name, field.value); + // }); + + // formData.append('file', fs.createReadStream(path.join(__dirname, 'test-file.txt'))); + + // formData.getLength((err, length) => { + // if (err) { + // return done(err); + // } + + // axios.post(url, formData, { + // headers: { + // ...formData.getHeaders(), + // 'Content-Length': length, + // }, + // }) + // .then(() => { + // done(new Error('Expected InvalidArgument error but got success response')); + // }) + // .catch(err => { + // assert.equal(err.response.status, 400); + // done(); + // }); + // }); + // }); + + + + + + + + + + + + + + + + + + + + + + + // it('should handle error when file is too large', done => { + // const fields = calculateFields(); + // const formData = new FormData(); + + // fields.forEach(field => { + // formData.append(field.name, field.value); + // }); + + // formData.append('file', fs.createReadStream(path.join(__dirname, 'large-test-file.txt'))); + + // formData.getLength((err, length) => { + // if (err) { + // return done(err); + // } + + // axios.post(url, formData, { + // headers: { + // ...formData.getHeaders(), + // 'Content-Length': length, + // }, + // }) + // .then(() => { + // done(new Error('Expected error but got success response')); + // }) + // .catch(err => { + // assert.equal(err.response.status, 400); + // done(); + // }); + // }); + // }); + + // it('should return error if posting object with > 2KB user-defined metadata', done => { + // const largeMetadata = 'a'.repeat(2049); // Metadata larger than 2KB + // const fields = calculateFields([{ key: 'x-amz-meta-large', value: largeMetadata }]); + // const formData = new FormData(); + + // fields.forEach(field => { + // formData.append(field.name, field.value); + // }); + + // formData.append('file', fs.createReadStream(path.join(__dirname, 'test-file.txt'))); + + // formData.getLength((err, length) => { + // if (err) { + // return done(err); + // } + + // axios.post(url, formData, { + // headers: { + // ...formData.getHeaders(), + // 'Content-Length': length, + // }, + // }) + // .then(() => { + // done(new Error('Expected error but got success response')); + // }) + // .catch(err => { + // assert.equal(err.response.status, 400); + // done(); + // }); + // }); + // }); + + + // it('should return InvalidRequest error if posting object with object lock retention date and mode when object lock is not enabled on the bucket', done => { + // const fields = calculateFields([ + // { 'x-amz-object-lock-retain-until-date': new Date().toISOString() }, + // { 'x-amz-object-lock-mode': 'GOVERNANCE' } + // ]); + // const formData = new FormData(); + + // fields.forEach(field => { + // formData.append(field.name, field.value); + // }); + + // formData.append('file', fs.createReadStream(path.join(__dirname, 'test-file.txt'))); + + // formData.getLength((err, length) => { + // if (err) { + // return done(err); + // } + + // axios.post(url, formData, { + // headers: { + // ...formData.getHeaders(), + // 'Content-Length': length, + // }, + // }) + // .then(() => { + // done(new Error('Expected InvalidRequest error but got success response')); + // }) + // .catch(err => { + // assert.equal(err.response.status, 400); + // done(); + // }); + // }); + // }); + + // it('should return Not Implemented error for object encryption using customer-provided encryption keys', done => { + // const fields = calculateFields([{ 'x-amz-server-side-encryption-customer-algorithm': 'AES256' }]); + // const formData = new FormData(); + + // fields.forEach(field => { + // formData.append(field.name, field.value); + // }); + + // formData.append('file', fs.createReadStream(path.join(__dirname, 'test-file.txt'))); + + // formData.getLength((err, length) => { + // if (err) { + // return done(err); + // } + + // axios.post(url, formData, { + // headers: { + // ...formData.getHeaders(), + // 'Content-Length': length, + // }, + // }) + // .then(() => { + // done(new Error('Expected Not Implemented error but got success response')); + // }) + // .catch(err => { + // assert.equal(err.response.status, 501); + // done(); + // }); + // }); + // }); + +}); + From a83edc357c2d551424c10852176b65da69cbbd59 Mon Sep 17 00:00:00 2001 From: Will Toozs Date: Fri, 14 Jun 2024 12:21:54 +0200 Subject: [PATCH 10/15] fix other posts --- lib/api/api.js | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/lib/api/api.js b/lib/api/api.js index b69b2e83ec..9e1cf440ed 100644 --- a/lib/api/api.js +++ b/lib/api/api.js @@ -115,7 +115,7 @@ const api = { // no need to check auth on website or cors preflight requests if (apiMethod === 'websiteGet' || apiMethod === 'websiteHead' || - apiMethod === 'corsPreflight') { + apiMethod === 'corsPreflight') { request.actionImplicitDenies = false; return this[apiMethod](request, log, callback); } @@ -161,7 +161,7 @@ const api = { // second item checks s3:GetObject(Version)Tagging action if (!authResults[1].isAllowed) { log.trace('get tagging authorization denial ' + - 'from Vault'); + 'from Vault'); returnTagCount = false; } } else { @@ -211,7 +211,8 @@ const api = { bb.on('field', (fieldname, val) => { request.formData[fieldname] = val; if (request.formData.Policy) { - request.formData.decryptedPolicy = JSON.parse(decryptToken(request.formData.Policy)); + const decrypted = decryptToken(request.formData.Policy); + request.formData.decryptedPolicy = JSON.parse(decrypted); } // TODO - put content type field for file in request @@ -271,9 +272,10 @@ const api = { request.on('end', () => { if (postLength > MAX_POST_LENGTH) { log.error('body length is too long for request type', - { postLength }); + { postLength }); return next(errors.InvalidRequest); } + request.post = Buffer.concat(post, postLength).toString(); return next(null); }); } From 0b3e80208112d8399da2f4172f55e364e436d5a4 Mon Sep 17 00:00:00 2001 From: Will Toozs Date: Mon, 17 Jun 2024 17:38:49 +0200 Subject: [PATCH 11/15] checkpoint: changing the parser --- lib/api/api.js | 194 +++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 154 insertions(+), 40 deletions(-) diff --git a/lib/api/api.js b/lib/api/api.js index 9e1cf440ed..a09e9fe24e 100644 --- a/lib/api/api.js +++ b/lib/api/api.js @@ -71,11 +71,117 @@ const { tagConditionKeyAuth } = require('./apiUtils/authorization/tagConditionKe const checkHttpHeadersSize = require('./apiUtils/object/checkHttpHeadersSize'); const { decryptToken } = require('./apiUtils/object/continueToken'); const busboy = require('busboy'); +const fs = require('fs'); +const path = require('path'); +const os = require('os'); const monitoringMap = policies.actionMaps.actionMonitoringMapS3; auth.setHandler(vault); +function parseMultipartFormData(request, callback) { + let algoOK = false; + let credOK = false; + let dateOK = false; + let sigOK = false; + let policyOK = false; + request.formData = {}; + + const boundary = request.headers['content-type'].split('boundary=')[1]; + const boundaryBuffer = Buffer.from(`--${boundary}`); + const newlineBuffer = Buffer.from('\r\n'); + + let buffer = Buffer.alloc(0); + let currentField = null; + let fileStream = null; + + request.on('data', (chunk) => { + buffer = Buffer.concat([buffer, chunk]); + + let boundaryIndex; + while ((boundaryIndex = buffer.indexOf(boundaryBuffer)) !== -1) { + const part = buffer.slice(0, boundaryIndex); + buffer = buffer.slice(boundaryIndex + boundaryBuffer.length); + + const translatedPart = part.toString(); + const translatedbuffer = buffer.toString(); + if (part.length === 0) continue; // skip empty parts + + if (part.indexOf(newlineBuffer) === 0) { + buffer = buffer.slice(newlineBuffer.length); + continue; // skip initial CRLF + } + + const headersEndIndex = part.indexOf(newlineBuffer); + const headers = part.slice(0, headersEndIndex).toString().split('\r\n'); + const content = part.slice(headersEndIndex + newlineBuffer.length); + + const contentDisposition = headers.find(header => header.startsWith('Content-Disposition')); + if (contentDisposition) { + const nameMatch = contentDisposition.match(/name="([^"]+)"/); + const filenameMatch = contentDisposition.match(/filename="([^"]+)"/); + + if (nameMatch) { + const fieldname = nameMatch[1]; + if (filenameMatch) { + // File field + const filename = filenameMatch[1]; + const encoding = 'binary'; + // 'Content-Disposition: form-data; name="file"; filename="test.txt"' + + //const mimetype = headers.find(header => header.startsWith('Content-Type')).split(': ')[1]; + + fileStream = fs.createWriteStream(path.join(os.tmpdir(), filename)); + + // Write the initial content to the file stream + fileStream.write(content); + + // Pipe the remaining data + //request.pipe(fileStream, { end: false }); + //request.pipe(fileStream); + + if (algoOK && credOK && dateOK && sigOK && policyOK) { + //callback(null, { fieldname, filename, encoding }); + } + + currentField = null; + } else { + // Regular field + currentField = fieldname; + request.formData[fieldname] = content.toString(); + + if (fieldname === 'X-Amz-Algorithm') { + algoOK = true; + } + if (fieldname === 'X-Amz-Credential') { + credOK = true; + } + if (fieldname === 'X-Amz-Date') { + dateOK = true; + } + if (fieldname === 'X-Amz-Signature') { + sigOK = true; + } + if (fieldname === 'Policy') { + const decrypted = decryptToken(request.formData.Policy); + request.formData.decryptedPolicy = JSON.parse(decrypted); + policyOK = true; + } + + currentField = null; + } + } + } + } + }); + + request.on('end', () => { + if (!algoOK || !credOK || !dateOK || !sigOK || !policyOK) { + callback(new Error('InvalidRequest')); + } + }); +} + /* eslint-disable no-param-reassign */ const api = { callApiMethod(apiMethod, request, response, log, callback) { @@ -202,51 +308,59 @@ const api = { if (apiMethod === 'objectPost' && request.headers['content-type'].includes('multipart/form-data')) { writeContinue(request, response); - let algoOK = false; - let credOK = false; - let dateOK = false; - let sigOK = false; - let policyOK = false; - request.formData = {}; - bb.on('field', (fieldname, val) => { - request.formData[fieldname] = val; - if (request.formData.Policy) { - const decrypted = decryptToken(request.formData.Policy); - request.formData.decryptedPolicy = JSON.parse(decrypted); - } + // let algoOK = false; + // let credOK = false; + // let dateOK = false; + // let sigOK = false; + // let policyOK = false; + // request.formData = {}; + // bb.on('field', (fieldname, val) => { + // request.formData[fieldname] = val; + // if (request.formData.Policy) { + // const decrypted = decryptToken(request.formData.Policy); + // request.formData.decryptedPolicy = JSON.parse(decrypted); + // } - // TODO - put content type field for file in request - if (fieldname === 'X-Amz-Algorithm') { - algoOK = true; - } - if (fieldname === 'X-Amz-Credential') { - credOK = true; - } - if (fieldname === 'X-Amz-Date') { - dateOK = true; - } - if (fieldname === 'X-Amz-Signature') { - sigOK = true; - } - if (fieldname === 'Policy') { - policyOK = true; - } - }); + // // TODO - put content type field for file in request + // if (fieldname === 'X-Amz-Algorithm') { + // algoOK = true; + // } + // if (fieldname === 'X-Amz-Credential') { + // credOK = true; + // } + // if (fieldname === 'X-Amz-Date') { + // dateOK = true; + // } + // if (fieldname === 'X-Amz-Signature') { + // sigOK = true; + // } + // if (fieldname === 'Policy') { + // policyOK = true; + // } + // }); - bb.on('file', (fieldname, file, filename, encoding, mimetype) => { - fileEventData = { fieldname, file, filename, encoding, mimetype }; - if (algoOK && credOK && dateOK && sigOK && policyOK) { - return next(null); - } - }); + // bb.on('file', (fieldname, file, filename, encoding, mimetype) => { + // fileEventData = { fieldname, file, filename, encoding, mimetype }; + // if (algoOK && credOK && dateOK && sigOK && policyOK) { + // return next(null); + // } + // }); - bb.on('finish', () => { - // if authorization field is not found, return error - if (!algoOK || !credOK || !dateOK || !sigOK || !policyOK) { - return next(errors.InvalidRequest); + // bb.on('finish', () => { + // // if authorization field is not found, return error + // if (!algoOK || !credOK || !dateOK || !sigOK || !policyOK) { + // return next(errors.InvalidRequest); + // } + // }); + // request.pipe(bb); + + parseMultipartFormData(request, (err, data) => { + if (err) { + return next(err); } + fileEventData = data; + return next(null); }); - request.pipe(bb); } else { // issue 100 Continue to the client writeContinue(request, response); From 15f1472f253b0b8ac977b9c185ed4f3d9960ca15 Mon Sep 17 00:00:00 2001 From: Will Toozs Date: Wed, 19 Jun 2024 16:33:04 +0200 Subject: [PATCH 12/15] checkpoint: parser changed to custom, very slow on large files --- lib/api/api.js | 42 ++++++++++++++++++++++++++---------------- lib/api/objectPost.js | 31 +++++++++++++++++++------------ 2 files changed, 45 insertions(+), 28 deletions(-) diff --git a/lib/api/api.js b/lib/api/api.js index a09e9fe24e..21927db1ab 100644 --- a/lib/api/api.js +++ b/lib/api/api.js @@ -74,6 +74,8 @@ const busboy = require('busboy'); const fs = require('fs'); const path = require('path'); const os = require('os'); +const { PassThrough } = require('stream'); + const monitoringMap = policies.actionMaps.actionMonitoringMapS3; @@ -93,30 +95,37 @@ function parseMultipartFormData(request, callback) { let buffer = Buffer.alloc(0); let currentField = null; - let fileStream = null; + let file = null; + let count = 0; request.on('data', (chunk) => { buffer = Buffer.concat([buffer, chunk]); let boundaryIndex; + console.log('part count:', count++); + while ((boundaryIndex = buffer.indexOf(boundaryBuffer)) !== -1) { - const part = buffer.slice(0, boundaryIndex); + let part = buffer.slice(0, boundaryIndex); buffer = buffer.slice(boundaryIndex + boundaryBuffer.length); - const translatedPart = part.toString(); - const translatedbuffer = buffer.toString(); if (part.length === 0) continue; // skip empty parts + let partToProcess = part; if (part.indexOf(newlineBuffer) === 0) { - buffer = buffer.slice(newlineBuffer.length); - continue; // skip initial CRLF + part = part.slice(newlineBuffer.length); } - const headersEndIndex = part.indexOf(newlineBuffer); - const headers = part.slice(0, headersEndIndex).toString().split('\r\n'); - const content = part.slice(headersEndIndex + newlineBuffer.length); + const headersEndIndex = partToProcess.indexOf(newlineBuffer + newlineBuffer); + const headers = partToProcess.slice(0, headersEndIndex).toString().split('\r\n'); + let content = partToProcess.slice(headersEndIndex + newlineBuffer.length * 2); + if (content.slice(-2).equals(newlineBuffer)) { + content = content.slice(0, -2); + } const contentDisposition = headers.find(header => header.startsWith('Content-Disposition')); + const contentTypeHeader = headers.find(header => header.startsWith('Content-Type')); + const mimetype = contentTypeHeader ? contentTypeHeader.split(': ')[1] : ''; + if (contentDisposition) { const nameMatch = contentDisposition.match(/name="([^"]+)"/); const filenameMatch = contentDisposition.match(/filename="([^"]+)"/); @@ -126,22 +135,22 @@ function parseMultipartFormData(request, callback) { if (filenameMatch) { // File field const filename = filenameMatch[1]; - const encoding = 'binary'; + + // Remove the trailing CRLF from the content + // 'Content-Disposition: form-data; name="file"; filename="test.txt"' //const mimetype = headers.find(header => header.startsWith('Content-Type')).split(': ')[1]; - fileStream = fs.createWriteStream(path.join(os.tmpdir(), filename)); - - // Write the initial content to the file stream - fileStream.write(content); + file = new PassThrough(); + file.write(content); // Pipe the remaining data - //request.pipe(fileStream, { end: false }); + request.pipe(file); //request.pipe(fileStream); if (algoOK && credOK && dateOK && sigOK && policyOK) { - //callback(null, { fieldname, filename, encoding }); + callback(null, { file, fieldname, filename, boundaryBuffer, mimetype }); } currentField = null; @@ -453,6 +462,7 @@ const api = { if (apiMethod === 'objectPost' && fileEventData) { request._response = response; request.file = fileEventData.file; + request.fileEventData = fileEventData; return this[apiMethod](userInfo, request, streamingV4Params, log, callback, authorizationResults); } diff --git a/lib/api/objectPost.js b/lib/api/objectPost.js index 217c8e2ccc..4ad4aa015c 100644 --- a/lib/api/objectPost.js +++ b/lib/api/objectPost.js @@ -64,9 +64,9 @@ function objectPost(authInfo, request, streamingV4Params, log, callback) { const invalidSSEError = errors.InvalidArgument.customizeDescription( 'The encryption method specified is not supported'); const requestType = request.apiMethods || 'objectPost'; - + const valParams = { authInfo, bucketName: request.formData.bucket, objectKey: request.formData.key, requestType, request }; - + const canonicalID = authInfo.getCanonicalID(); // TODO CLDSRV-527 add check for non-printable characters? @@ -103,15 +103,22 @@ function objectPost(authInfo, request, streamingV4Params, log, callback) { monitoring.promMetrics('POST', request.bucketName, 404, 'postObject'); return callback(errors.NoSuchBucket); } - + return async.waterfall([ function countPOSTFileSize(next) { request.file.on('data', (chunk) => { + const boundaryBuffer = Buffer.from(`${request.fileEventData.boundaryBuffer}--`); + const boundaryIndex = chunk.indexOf(boundaryBuffer); + + if (boundaryIndex !== -1) { + // If the boundary is found, slice the chunk to exclude the boundary + chunk = chunk.slice(0, boundaryIndex); + } + parsedContentLength += chunk.length; passThroughStream.write(chunk); - }); - + request.file.on('end', () => { // Here totalBytes will have the total size of the file passThroughStream.end(); @@ -154,8 +161,8 @@ function objectPost(authInfo, request, streamingV4Params, log, callback) { } writeContinue(request, request._response); return createAndStoreObject(request.bucketName, - bucket, request.formData.key, objMD, authInfo, canonicalID, cipherBundle, - request, false, streamingV4Params, overheadField, log, next); + bucket, request.formData.key, objMD, authInfo, canonicalID, cipherBundle, + request, false, streamingV4Params, overheadField, log, next); }, ], (err, storingResult) => { if (err) { @@ -172,7 +179,7 @@ function objectPost(authInfo, request, streamingV4Params, log, callback) { && !Number.isNaN(request.headers['x-amz-meta-size'])) ? Number.parseInt(request.headers['x-amz-meta-size'], 10) : null; const newByteLength = parsedContentLength; - + setExpirationHeaders(responseHeaders, { lifecycleConfig: bucket.getLifecycleConfiguration(), objectParams: { @@ -181,7 +188,7 @@ function objectPost(authInfo, request, streamingV4Params, log, callback) { tags: storingResult.tags, }, }); - + // Utapi expects null or a number for oldByteLength: // * null - new object // * 0 or > 0 - existing object with content-length 0 or > 0 @@ -201,13 +208,13 @@ function objectPost(authInfo, request, streamingV4Params, log, callback) { if (storingResult && storingResult.versionId) { responseHeaders['x-amz-version-id'] = versionIdUtils.encode(storingResult.versionId, - config.versionIdEncodingType); + config.versionIdEncodingType); } } - + // Only pre-existing non-versioned objects get 0 all others use 1 const numberOfObjects = !isVersionedObj && oldByteLength !== null ? 0 : 1; - + // only the bucket owner's metrics should be updated, regardless of // who the requester is pushMetric('postObject', log, { From 7db8da210d2c2ec93b8f3c2b610f5deaa9f705a7 Mon Sep 17 00:00:00 2001 From: Will Toozs Date: Mon, 24 Jun 2024 12:08:01 +0200 Subject: [PATCH 13/15] checkpoint: acl tests --- .../aws-node-sdk/test/object/post.js | 464 +++++++++++++++--- 1 file changed, 402 insertions(+), 62 deletions(-) diff --git a/tests/functional/aws-node-sdk/test/object/post.js b/tests/functional/aws-node-sdk/test/object/post.js index ae8849e52c..a062cdc38e 100644 --- a/tests/functional/aws-node-sdk/test/object/post.js +++ b/tests/functional/aws-node-sdk/test/object/post.js @@ -141,44 +141,77 @@ describe('AWS S3 POST Object with Policy', function () { return done(err); } - // Delete the bucket and its contents - const deleteBucket = () => { - s3.deleteBucket({ Bucket: bucketName }, (err) => { - if (err && err.code !== 'NoSuchBucket') { - return done(err); + // Function to delete a single bucket and its contents + const deleteSingleBucket = (bucket, callback) => { + const deleteBucket = () => { + s3.deleteBucket({ Bucket: bucket }, (err) => { + if (err && err.code !== 'NoSuchBucket') { + return callback(err); + } + callback(); + }); + }; + + s3.listObjects({ Bucket: bucket }, (err, data) => { + if (err && err.code === 'NoSuchBucket') { + return callback(); // Ignore the error if the bucket does not exist + } else if (err) { + return callback(err); } - done(); + + if (data.Contents.length === 0) { + // Bucket is already empty + return deleteBucket(); + } + + // Delete all objects in the bucket, including objects locked with governance + const objects = data.Contents.map(item => ({ Key: item.Key })); + const deleteParams = { + Bucket: bucket, + Delete: { Objects: objects }, + BypassGovernanceRetention: true // Bypass governance mode + }; + s3.deleteObjects(deleteParams, (err) => { + if (err) { + return callback(err); + } + deleteBucket(); + }); }); }; - // List objects in the bucket - s3.listObjects({ Bucket: bucketName }, (err, data) => { - if (err && err.code === 'NoSuchBucket') { - return done(); // Ignore the error if the bucket does not exist - } else if (err) { + // List all buckets + s3.listBuckets((err, data) => { + if (err) { return done(err); } - if (data.Contents.length === 0) { - // Bucket is already empty - return deleteBucket(); - } + // Filter buckets that start with the specified prefix + const bucketsToDelete = data.Buckets.filter(bucket => bucket.Name.startsWith(bucketName)); - // Delete all objects in the bucket - const objects = data.Contents.map(item => ({ Key: item.Key })); - s3.deleteObjects({ - Bucket: bucketName, - Delete: { Objects: objects } - }, (err) => { + // Delete each bucket and its contents + let completed = 0; + const total = bucketsToDelete.length; + const checkDone = (err) => { if (err) { return done(err); } - deleteBucket(); - }); + completed += 1; + if (completed === total) { + done(); + } + }; + + if (total === 0) { + done(); + } else { + bucketsToDelete.forEach(bucket => deleteSingleBucket(bucket.Name, checkDone)); + } }); }); }); + it('should successfully upload an object to S3 using a POST form', done => { const fields = calculateFields(ak, sk); const formData = new FormData(); @@ -1058,16 +1091,362 @@ describe('AWS S3 POST Object with Policy', function () { }); }); + it('should successfully upload object with valid SSE parameters and verify it', done => { + const fields = calculateFields(ak, sk, [{ 'x-amz-server-side-encryption': 'AES256' }]); + const formData = new FormData(); + fields.forEach(field => { + formData.append(field.name, field.value); + }); + formData.append('file', fs.createReadStream(path.join(__dirname, 'test-file.txt'))); + formData.getLength((err, length) => { + if (err) { + return done(err); + } + axios.post(url, formData, { + headers: { + ...formData.getHeaders(), + 'Content-Length': length, + }, + }) + .then(response => { + assert.equal(response.status, 204); + // Get the object's SSE configuration + s3.headObject({ Bucket: bucketName, Key: filename }, (err, data) => { + if (err) { + return done(err); + } + assert.equal(data.ServerSideEncryption, 'AES256'); + done(); + }); + }) + .catch(err => { + done(err); + }); + }); + }); + it('should return error if posting object with invalid SSE parameters', done => { + const fields = calculateFields(ak, sk, [{ 'x-amz-server-side-encryption': 'INVALID' }]); + const formData = new FormData(); + fields.forEach(field => { + formData.append(field.name, field.value); + }); + formData.append('file', fs.createReadStream(path.join(__dirname, 'test-file.txt'))); + + formData.getLength((err, length) => { + if (err) { + return done(err); + } + + axios.post(url, formData, { + headers: { + ...formData.getHeaders(), + 'Content-Length': length, + }, + }) + .then(() => { + done(new Error('Expected error but got success response')); + }) + .catch(err => { + assert.equal(err.response.status, 400); // Assuming 400 Bad Request for invalid SSE parameters + done(); + }); + }); + }); + + + + + + + /** Tests with different bucket setup, failing right now because of policy... for unknown reasons **/ + + + it('should successfully upload object with valid object lock parameters and verify it', done => { + const retentionDate = new Date(); + retentionDate.setDate(retentionDate.getDate() + 1); // Set retention date one day in the future + bucketName = `${bucketName}-object-lock`; + // Create the bucket with Object Lock enabled + const createBucketParams = { + Bucket: bucketName, + ObjectLockEnabledForBucket: true, + }; + + s3.createBucket(createBucketParams, (err) => { + if (err) { + return done(err); + } + + // Enable Object Lock configuration + + + const fields = calculateFields(ak, sk, [ + { 'x-amz-object-lock-mode': 'GOVERNANCE' }, + { 'x-amz-object-lock-retain-until-date': retentionDate.toISOString() } + ]); + + const formData = new FormData(); + + fields.forEach(field => { + formData.append(field.name, field.value); + }); + + formData.append('file', fs.createReadStream(path.join(__dirname, 'test-file.txt'))); + + formData.getLength((err, length) => { + if (err) { + return done(err); + } + + axios.post(url, formData, { + headers: { + ...formData.getHeaders(), + 'Content-Length': length, + }, + }) + .then(response => { + assert.equal(response.status, 204); + + // Get the object's lock configuration + s3.headObject({ Bucket: bucketName, Key: filename }, (err, data) => { + if (err) { + return done(err); + } + + assert.equal(data.ObjectLockMode, 'GOVERNANCE'); + assert.equal(new Date(data.ObjectLockRetainUntilDate).toISOString(), retentionDate.toISOString()); + + done(); + }); + }) + .catch(err => { + console.log(err.response.data) + done(err); + }); + }); + }); + }); + + + it('should successfully upload object with valid object lock parameters and verify it', done => { + const retentionDate = new Date(); + retentionDate.setDate(retentionDate.getDate() + 1); // Set retention date one day in the future + const fields = calculateFields(ak, sk, [ + { 'x-amz-object-lock-mode': 'GOVERNANCE' }, + { 'x-amz-object-lock-retain-until-date': retentionDate.toISOString() }, + ]); + const formData = new FormData(); + + fields.forEach(field => { + formData.append(field.name, field.value); + }); + + formData.append('file', fs.createReadStream(path.join(__dirname, 'test-file.txt'))); + + formData.getLength((err, length) => { + if (err) { + return done(err); + } + + axios.post(url, formData, { + headers: { + ...formData.getHeaders(), + 'Content-Length': length, + }, + }) + .then(response => { + assert.equal(response.status, 204); + + // Get the object's lock configuration + s3.headObject({ Bucket: bucketName, Key: filename }, (err, data) => { + if (err) { + return done(err); + } + + assert.equal(data.ObjectLockMode, 'GOVERNANCE'); + assert.equal(new Date(data.ObjectLockRetainUntilDate).toISOString(), retentionDate.toISOString()); + + done(); + }); + }) + .catch(err => { + console.log(err.response.data) + done(err); + }); + }); + }); + + it('should successfully upload an object with bucket versioning enabled and verify version ID', done => { + bucketName = `${bucketName}-versioning`; + + // Create the bucket + s3.createBucket({ Bucket: bucketName }, (err) => { + if (err) { + return done(err); + } + + // Enable versioning on the bucket + const versioningParams = { + Bucket: bucketName, + VersioningConfiguration: { + Status: 'Enabled', + }, + }; + + s3.putBucketVersioning(versioningParams, (err) => { + if (err) { + return done(err); + } + + const fields = calculateFields(ak, sk, [{ bucket: bucketName }]); + const formData = new FormData(); + + fields.forEach(field => { + formData.append(field.name, field.value); + }); + + formData.append('file', fs.createReadStream(path.join(__dirname, 'test-file.txt'))); + + formData.getLength((err, length) => { + if (err) { + return done(err); + } + + axios.post(url, formData, { + headers: { + ...formData.getHeaders(), + 'Content-Length': length, + }, + }) + .then(response => { + assert.equal(response.status, 204); + + // Verify version ID is present in the response + const versionId = response.headers['x-amz-version-id']; + assert.ok(versionId, 'Version ID should be present in the response headers'); + + // Verify the object versioning + s3.getObject({ Bucket: bucketName, Key: filename, VersionId: versionId }, (err, data) => { + if (err) { + return done(err); + } + + assert.equal(data.VersionId, versionId); + + done(); + }); + }) + .catch(err => { + console.log(err); + done(err); + }); + }); + }); + }); + }); + + it('should successfully upload an object with a specified ACL and verify it', done => { + + // Create the bucket + s3.createBucket({ Bucket: bucketName }, (err) => { + if (err) { + return done(err); + } + + const aclValue = 'public-read'; // Example ACL value + const fields = calculateFields([{ 'acl': aclValue }]); + const formData = new FormData(); + + fields.forEach(field => { + formData.append(field.name, field.value); + }); + + formData.append('file', fs.createReadStream(path.join(__dirname, 'test-file.txt'))); + + formData.getLength((err, length) => { + if (err) { + return done(err); + } + + axios.post(url, formData, { + headers: { + ...formData.getHeaders(), + 'Content-Length': length, + }, + }) + .then(response => { + assert.equal(response.status, 204); + + // Get the object's ACL + s3.getObjectAcl({ Bucket: bucketName, Key: filename }, (err, data) => { + if (err) { + return done(err); + } + + const grants = data.Grants; + const publicReadGrant = grants.find(grant => + grant.Permission === 'READ' && grant.Grantee.URI === 'http://acs.amazonaws.com/groups/global/AllUsers' + ); + + assert.ok(publicReadGrant, 'Expected public-read ACL grant not found'); + + done(); + }); + }) + .catch(err => { + done(err); + }); + }); + }); + }); + + it('should return an error when uploading an object with an invalid ACL', done => { + // Create the bucket + s3.createBucket({ Bucket: bucketName }, (err) => { + if (err) { + return done(err); + } + + const invalidAclValue = 'invalid-acl'; // Example invalid ACL value + const fields = calculateFields([{ 'acl': invalidAclValue }]); + const formData = new FormData(); + + fields.forEach(field => { + formData.append(field.name, field.value); + }); + + formData.append('file', fs.createReadStream(path.join(__dirname, 'test-file.txt'))); + + formData.getLength((err, length) => { + if (err) { + return done(err); + } + + axios.post(url, formData, { + headers: { + ...formData.getHeaders(), + 'Content-Length': length, + }, + }) + .then(() => { + done(new Error('Expected error but got success response')); + }) + .catch(err => { + assert.equal(err.response.status, 400); // Assuming 400 Bad Request for invalid ACL + done(); + }); + }); + }); + }); @@ -1081,55 +1460,16 @@ describe('AWS S3 POST Object with Policy', function () { - // Test needs bucket with ACLs enabled - // it('should upload an object with a specified ACL and verify it', done => { - // const aclValue = 'public-read'; // Example ACL value - // const fields = calculateFields(ak, sk, [{ 'acl': aclValue }]); - // const formData = new FormData(); - // fields.forEach(field => { - // formData.append(field.name, field.value); - // }); - // formData.append('file', fs.createReadStream(path.join(__dirname, 'test-file.txt'))); - // formData.getLength((err, length) => { - // if (err) { - // return done(err); - // } - // axios.post(url, formData, { - // headers: { - // ...formData.getHeaders(), - // 'Content-Length': length, - // }, - // }) - // .then(response => { - // assert.equal(response.status, 204); - // // Get the object's ACL - // s3.getObjectAcl({ Bucket: bucketName, Key: filename }, (err, data) => { - // if (err) { - // return done(err); - // } - // const grants = data.Grants; - // const publicReadGrant = grants.find(grant => - // grant.Permission === 'READ' && grant.Grantee.URI === 'http://acs.amazonaws.com/groups/global/AllUsers' - // ); - // assert.ok(publicReadGrant, 'Expected public-read ACL grant not found'); - // done(); - // }); - // }) - // .catch(err => { - // done(err); - // }); - // }); - // }); // it('should be able to post an empty Tag set', done => { // const fields = calculateFields([{ 'x-amz-tagging': '' }]); From ae17d9a8f149032f33503eb3872f031fecd8d9ce Mon Sep 17 00:00:00 2001 From: Will Toozs Date: Mon, 24 Jun 2024 12:08:43 +0200 Subject: [PATCH 14/15] restablish busboy --- lib/api/api.js | 96 +++++++++++++++++++++++++------------------------- 1 file changed, 48 insertions(+), 48 deletions(-) diff --git a/lib/api/api.js b/lib/api/api.js index 21927db1ab..eeb16f4fb9 100644 --- a/lib/api/api.js +++ b/lib/api/api.js @@ -317,59 +317,59 @@ const api = { if (apiMethod === 'objectPost' && request.headers['content-type'].includes('multipart/form-data')) { writeContinue(request, response); - // let algoOK = false; - // let credOK = false; - // let dateOK = false; - // let sigOK = false; - // let policyOK = false; - // request.formData = {}; - // bb.on('field', (fieldname, val) => { - // request.formData[fieldname] = val; - // if (request.formData.Policy) { - // const decrypted = decryptToken(request.formData.Policy); - // request.formData.decryptedPolicy = JSON.parse(decrypted); - // } - - // // TODO - put content type field for file in request - // if (fieldname === 'X-Amz-Algorithm') { - // algoOK = true; - // } - // if (fieldname === 'X-Amz-Credential') { - // credOK = true; - // } - // if (fieldname === 'X-Amz-Date') { - // dateOK = true; - // } - // if (fieldname === 'X-Amz-Signature') { - // sigOK = true; - // } - // if (fieldname === 'Policy') { - // policyOK = true; - // } - // }); + let algoOK = false; + let credOK = false; + let dateOK = false; + let sigOK = false; + let policyOK = false; + request.formData = {}; + bb.on('field', (fieldname, val) => { + request.formData[fieldname] = val; + if (request.formData.Policy) { + const decrypted = decryptToken(request.formData.Policy); + request.formData.decryptedPolicy = JSON.parse(decrypted); + } - // bb.on('file', (fieldname, file, filename, encoding, mimetype) => { - // fileEventData = { fieldname, file, filename, encoding, mimetype }; - // if (algoOK && credOK && dateOK && sigOK && policyOK) { - // return next(null); - // } - // }); + // TODO - put content type field for file in request + if (fieldname === 'X-Amz-Algorithm') { + algoOK = true; + } + if (fieldname === 'X-Amz-Credential') { + credOK = true; + } + if (fieldname === 'X-Amz-Date') { + dateOK = true; + } + if (fieldname === 'X-Amz-Signature') { + sigOK = true; + } + if (fieldname === 'Policy') { + policyOK = true; + } + }); - // bb.on('finish', () => { - // // if authorization field is not found, return error - // if (!algoOK || !credOK || !dateOK || !sigOK || !policyOK) { - // return next(errors.InvalidRequest); - // } - // }); - // request.pipe(bb); + bb.on('file', (fieldname, file, filename, encoding, mimetype) => { + fileEventData = { fieldname, file, filename, encoding, mimetype }; + if (algoOK && credOK && dateOK && sigOK && policyOK) { + return next(null); + } + }); - parseMultipartFormData(request, (err, data) => { - if (err) { - return next(err); + bb.on('finish', () => { + // if authorization field is not found, return error + if (!algoOK || !credOK || !dateOK || !sigOK || !policyOK) { + return next(errors.InvalidRequest); } - fileEventData = data; - return next(null); }); + request.pipe(bb); + + // parseMultipartFormData(request, (err, data) => { + // if (err) { + // return next(err); + // } + // fileEventData = data; + // return next(null); + // }); } else { // issue 100 Continue to the client writeContinue(request, response); From e86e23a3e71b6da90ff551fb75c9577341acaf75 Mon Sep 17 00:00:00 2001 From: Will Toozs Date: Mon, 24 Jun 2024 12:19:01 +0200 Subject: [PATCH 15/15] quick fixes + unit tests --- lib/api/objectPost.js | 12 +- lib/metadata/metadataUtils.js | 4 +- tests/unit/api/objectPost.js | 894 ++++------------------------------ 3 files changed, 106 insertions(+), 804 deletions(-) diff --git a/lib/api/objectPost.js b/lib/api/objectPost.js index 4ad4aa015c..f33dfe1f60 100644 --- a/lib/api/objectPost.js +++ b/lib/api/objectPost.js @@ -107,13 +107,13 @@ function objectPost(authInfo, request, streamingV4Params, log, callback) { return async.waterfall([ function countPOSTFileSize(next) { request.file.on('data', (chunk) => { - const boundaryBuffer = Buffer.from(`${request.fileEventData.boundaryBuffer}--`); - const boundaryIndex = chunk.indexOf(boundaryBuffer); + // const boundaryBuffer = Buffer.from(`${request.fileEventData.boundaryBuffer}--`); + // const boundaryIndex = chunk.indexOf(boundaryBuffer); - if (boundaryIndex !== -1) { - // If the boundary is found, slice the chunk to exclude the boundary - chunk = chunk.slice(0, boundaryIndex); - } + // if (boundaryIndex !== -1) { + // // If the boundary is found, slice the chunk to exclude the boundary + // chunk = chunk.slice(0, boundaryIndex); + // } parsedContentLength += chunk.length; passThroughStream.write(chunk); diff --git a/lib/metadata/metadataUtils.js b/lib/metadata/metadataUtils.js index 397dd6b8fd..6a39fcf6a7 100644 --- a/lib/metadata/metadataUtils.js +++ b/lib/metadata/metadataUtils.js @@ -210,7 +210,7 @@ function standardMetadataValidateBucketAndObj(params, actionImplicitDenies, log, }, (getResult, next) => { const bucket = getResult.bucket ? - BucketInfo.deSerialize(getResult.bucket) : undefined; + BucketInfo.deSerialize(getResult.bucket) : undefined; if (!bucket) { log.debug('bucketAttrs is undefined', { bucket: bucketName, @@ -225,7 +225,7 @@ function standardMetadataValidateBucketAndObj(params, actionImplicitDenies, log, const objMD = getResult.obj ? JSON.parse(getResult.obj) : undefined; if (!objMD && versionId === 'null') { return getNullVersionFromMaster(bucketName, objectKey, log, - (err, nullVer) => next(err, bucket, nullVer)); + (err, nullVer) => next(err, bucket, nullVer)); } return next(null, bucket, objMD); }, diff --git a/tests/unit/api/objectPost.js b/tests/unit/api/objectPost.js index 81ced2fed7..a2739f3ab0 100644 --- a/tests/unit/api/objectPost.js +++ b/tests/unit/api/objectPost.js @@ -1,817 +1,119 @@ const assert = require('assert'); -const async = require('async'); -const moment = require('moment'); -const { errors, s3middleware } = require('arsenal'); const sinon = require('sinon'); - -const { bucketPut } = require('../../../lib/api/bucketPut'); -const bucketPutObjectLock = require('../../../lib/api/bucketPutObjectLock'); -const bucketPutACL = require('../../../lib/api/bucketPutACL'); -const bucketPutVersioning = require('../../../lib/api/bucketPutVersioning'); -const { parseTagFromQuery } = s3middleware.tagging; -const { cleanup, DummyRequestLogger, makeAuthInfo, versioningTestUtils } - = require('../helpers'); -const { ds } = require('arsenal').storage.data.inMemory.datastore; -const metadata = require('../metadataswitch'); +const async = require('async'); +const { PassThrough } = require('stream'); +const { errors, versioning } = require('arsenal'); const objectPost = require('../../../lib/api/objectPost'); -const { objectLockTestUtils } = require('../helpers'); -const DummyRequest = require('../DummyRequest'); -const mpuUtils = require('../utils/mpuUtils'); -const { lastModifiedHeader } = require('../../../constants'); -const { createPresignedPost } = require('@aws-sdk/s3-presigned-post'); - -const any = sinon.match.any; - -const log = new DummyRequestLogger(); -const canonicalID = 'accessKey1'; -const authInfo = makeAuthInfo(canonicalID); -const namespace = 'default'; -const bucketName = 'bucketname'; -// const postBody = Buffer.from('I am a body', 'utf8'); -// const correctMD5 = 'be747eb4b75517bf6b3cf7c5fbb62f3a'; -// const mockDate = new Date(2050, 10, 12); -const testPutBucketRequest = new DummyRequest({ - bucketName, - namespace, - headers: { host: `${bucketName}.s3.amazonaws.com` }, - url: '/', -}); -// const testPutBucketRequestLock = new DummyRequest({ -// bucketName, -// namespace, -// headers: { -// 'host': `${bucketName}.s3.amazonaws.com`, -// 'x-amz-bucket-object-lock-enabled': 'true', -// }, -// url: '/', -// }); - -// const originalputObjectMD = metadata.putObjectMD; -const objectName = 'objectName'; - -// let testPutObjectRequest; -// const enableVersioningRequest = -// versioningTestUtils.createBucketPutVersioningReq(bucketName, 'Enabled'); -// const suspendVersioningRequest = -// versioningTestUtils.createBucketPutVersioningReq(bucketName, 'Suspended'); - -// function testAuth(bucketOwner, authUser, bucketPutReq, log, cb) { -// bucketPut(bucketOwner, bucketPutReq, log, () => { -// bucketPutACL(bucketOwner, testPutBucketRequest, log, err => { -// assert.strictEqual(err, undefined); -// objectPut(authUser, testPutObjectRequest, undefined, -// log, (err, resHeaders) => { -// assert.strictEqual(err, null); -// assert.strictEqual(resHeaders.ETag, `"${correctMD5}"`); -// cb(); -// }); -// }); -// }); -// } - -// describe('parseTagFromQuery', () => { -// const invalidArgument = { status: 'InvalidArgument', statusCode: 400 }; -// const invalidTag = { status: 'InvalidTag', statusCode: 400 }; -// const allowedChar = '+- =._:/'; -// const tests = [ -// { tagging: 'key1=value1', result: { key1: 'value1' } }, -// { tagging: `key1=${encodeURIComponent(allowedChar)}`, -// result: { key1: allowedChar } }, -// { tagging: 'key1=value1=value2', error: invalidArgument }, -// { tagging: '=value1', error: invalidArgument }, -// { tagging: 'key1%=value1', error: invalidArgument }, -// { tagging: `${'w'.repeat(129)}=value1`, error: invalidTag }, -// { tagging: `key1=${'w'.repeat(257)}`, error: invalidTag }, -// { tagging: `${'w'.repeat(129)}=value1`, error: invalidTag }, -// { tagging: `key1=${'w'.repeat(257)}`, error: invalidTag }, -// ]; -// tests.forEach(test => { -// const behavior = test.error ? 'fail' : 'pass'; -// it(`should ${behavior} if tag set: "${test.tagging}"`, done => { -// const result = parseTagFromQuery(test.tagging); -// if (test.error) { -// assert(result.is[test.error.status]); -// assert.strictEqual(result.code, test.error.statusCode); -// } else { -// assert.deepStrictEqual(result, test.result); -// } -// done(); -// }); -// }); -// }); - -async function generatePresignedPost(options, conditions) { - // Define the conditions for the presigned POST - // const tagging = "Tag NameTag Value"; - // const conditions = [ - // {"bucket": bucketName}, - // { key: key }, // Ensure the key matches - // { "Content-Type": contentType }, // Enforce specific content type - // ]; - - // const fields = { - // "Content-Type": contentType, // Pre-filled field for content type - // "tagging": tagging - // }; - - // const options = { - // Bucket: bucketName, - // Fields: fields, - // Expires: 300, // Time in seconds before the presigned POST expires - // Conditions: conditions, - // Key: key, - // }; - try { - // Generate the presigned POST - // The presigned POST contains the URL and fields to upload the object - // It will generate the Policy, x-amz-signature, x-amz-credential and x-amz-date for you - const presignedPost = await createPresignedPost(s3, options); - return presignedPost; - } catch (error) { - console.error("Error creating presigned post:", error); - throw error; - } -} - -async function uploadFileUsingPresignedPost(presignedPostData, fileContent) { - const formData = new FormData(); - Object.entries(presignedPostData.fields).forEach(([key, value]) => { - formData.append(key, value); - }); - formData.append('file', fileContent, 'filename.txt'); - console.log('formdata: ', formData) - const response = - await axios({ - method: 'post', - url: presignedPostData.url, - data: formData, - headers: { - 'Content-Type': `multipart/form-data; boundary=${formData._boundary}`, - }, +const { + getObjectSSEConfiguration +} = require('../../../lib/api/apiUtils/bucket/bucketEncryption.js'); // Update the path as necessary +const collectCorsHeaders = require('../../../lib/utilities/collectCorsHeaders.js'); // Update the path as necessary +const createAndStoreObject = require('../../../lib/api/apiUtils/object/createAndStoreObject.js'); // Update the path as necessary +const metadataUtils = require('../../../lib/metadata/metadataUtils.js'); // Update the path as necessary +const kms = require('../../../lib/kms/wrapper'); +const { setExpirationHeaders } = require('../../../lib/api/apiUtils/object/expirationHeaders.js'); // Update the path as necessary +const { pushMetric } = require('../../../lib/utapi/utilities.js'); // Update the path as necessary +const { validateHeaders } = require('../../../lib/api/apiUtils/object/objectLockHelpers.js'); // Update the path as necessary +const writeContinue = require('../../../lib/utilities/writeContinue.js'); // Update the path as necessary +const { debug } = require('console'); + +describe('objectPost', () => { + let log, callback, request, authInfo; + + beforeEach(() => { + log = { + trace: sinon.stub(), + error: sinon.stub(), + debug: sinon.stub(), + }; + callback = sinon.stub(); + request = { + headers: {}, + method: 'POST', + formData: { + bucket: 'test-bucket', + key: 'test-key' + }, + file: new PassThrough() + }; + authInfo = { + getCanonicalID: sinon.stub().returns('canonicalID') + }; }); - return response; -} - -describe('objectPost API', () => { - // beforeEach(() => { - // cleanup(); - // sinon.spy(metadata, 'putObjectMD'); - // testPutObjectRequest = new DummyRequest({ - // bucketName, - // namespace, - // objectKey: objectName, - // headers: { host: `${bucketName}.s3.amazonaws.com` }, - // url: '/', - // }, postBody); - // }); - - // afterEach(() => { - // sinon.restore(); - // metadata.putObjectMD = originalputObjectMD; - // }); - -// it('should return an error if the bucket does not exist', done => { -// objectPut(authInfo, testPutObjectRequest, undefined, log, err => { -// assert.deepStrictEqual(err, errors.NoSuchBucket); -// done(); -// }); -// }); - -// it('should return an error if user is not authorized', done => { -// const putAuthInfo = makeAuthInfo('accessKey2'); -// bucketPut(putAuthInfo, testPutBucketRequest, -// log, () => { -// objectPut(authInfo, testPutObjectRequest, -// undefined, log, err => { -// assert.deepStrictEqual(err, errors.AccessDenied); -// done(); -// }); -// }); -// }); - - // NOTE: likely not feasible on POST - // it('should put object if user has FULL_CONTROL grant on bucket', done => { - // const bucketOwner = makeAuthInfo('accessKey2'); - // const authUser = makeAuthInfo('accessKey3'); - // testPutBucketRequest.headers['x-amz-grant-full-control'] = - // `id=${authUser.getCanonicalID()}`; - // testAuth(bucketOwner, authUser, testPutBucketRequest, log, done); - // }); - - // it('should put object if user has WRITE grant on bucket', done => { - // const bucketOwner = makeAuthInfo('accessKey2'); - // const authUser = makeAuthInfo('accessKey3'); - // testPutBucketRequest.headers['x-amz-grant-write'] = - // `id=${authUser.getCanonicalID()}`; - - // testAuth(bucketOwner, authUser, testPutBucketRequest, log, done); - // }); - - // it('should put object in bucket with public-read-write acl', done => { - // const bucketOwner = makeAuthInfo('accessKey2'); - // const authUser = makeAuthInfo('accessKey3'); - // testPutBucketRequest.headers['x-amz-acl'] = 'public-read-write'; + afterEach(() => { + sinon.restore(); + }); - // testAuth(bucketOwner, authUser, testPutBucketRequest, log, done); - // }); + it('should return NoSuchBucket error if bucket does not exist', (done) => { + sinon.stub(metadataUtils, 'standardMetadataValidateBucketAndObj').callsFake((params, actionImplicitDenies, log, callback) => { + callback(errors.NoSuchBucket); + }); - it('should successfully post an object', done => { - const testPutObjectRequest = new DummyRequest({ - bucketName, - namespace, - objectKey: objectName, - headers: {}, - url: `/${bucketName}/${objectName}`, - calculatedHash: 'vnR+tLdVF79rPPfF+7YvOg==', - }, postBody); + objectPost(authInfo, request, null, log, callback); - bucketPut(authInfo, testPutBucketRequest, log, () => { - objectPost(authInfo, testPostObjectRequest, undefined, log, - (err, resHeaders) => { - assert.strictEqual(resHeaders.ETag, `"${correctMD5}"`); - metadata.getObjectMD(bucketName, objectName, - {}, log, (err, md) => { - assert(md); - assert - .strictEqual(md['content-md5'], correctMD5); - done(); - }); - }); + process.nextTick(() => { + assert(callback.calledOnce); + assert(callback.calledWith(errors.NoSuchBucket)); + done(); }); }); - // const mockModes = ['GOVERNANCE', 'COMPLIANCE']; - // mockModes.forEach(mockMode => { - // it(`should put an object with valid date & ${mockMode} mode`, done => { - // const testPutObjectRequest = new DummyRequest({ - // bucketName, - // namespace, - // objectKey: objectName, - // headers: { - // 'x-amz-object-lock-retain-until-date': mockDate, - // 'x-amz-object-lock-mode': mockMode, - // }, - // url: `/${bucketName}/${objectName}`, - // calculatedHash: 'vnR+tLdVF79rPPfF+7YvOg==', - // }, postBody); - // bucketPut(authInfo, testPutBucketRequestLock, log, () => { - // objectPut(authInfo, testPutObjectRequest, undefined, log, - // (err, headers) => { - // assert.ifError(err); - // assert.strictEqual(headers.ETag, `"${correctMD5}"`); - // metadata.getObjectMD(bucketName, objectName, {}, log, - // (err, md) => { - // const mode = md.retentionMode; - // const retainUntilDate = md.retentionDate; - // assert.ifError(err); - // assert(md); - // assert.strictEqual(mode, mockMode); - // assert.strictEqual(retainUntilDate, mockDate); - // done(); - // }); - // }); - // }); - // }); - // }); - - // const formatTime = time => time.slice(0, 20); - - // const testObjectLockConfigs = [ - // { - // testMode: 'COMPLIANCE', - // val: 30, - // type: 'Days', - // }, - // { - // testMode: 'GOVERNANCE', - // val: 5, - // type: 'Years', - // }, - // ]; - // testObjectLockConfigs.forEach(config => { - // const { testMode, type, val } = config; - // it('should put an object with default retention if object does not ' + - // 'have retention configuration but bucket has', done => { - // const testPutObjectRequest = new DummyRequest({ - // bucketName, - // namespace, - // objectKey: objectName, - // headers: {}, - // url: `/${bucketName}/${objectName}`, - // calculatedHash: 'vnR+tLdVF79rPPfF+7YvOg==', - // }, postBody); - - // const testObjLockRequest = { - // bucketName, - // headers: { host: `${bucketName}.s3.amazonaws.com` }, - // post: objectLockTestUtils.generateXml(testMode, val, type), - // }; - - // bucketPut(authInfo, testPutBucketRequestLock, log, () => { - // bucketPutObjectLock(authInfo, testObjLockRequest, log, () => { - // objectPut(authInfo, testPutObjectRequest, undefined, log, - // (err, headers) => { - // assert.ifError(err); - // assert.strictEqual(headers.ETag, `"${correctMD5}"`); - // metadata.getObjectMD(bucketName, objectName, {}, - // log, (err, md) => { - // const mode = md.retentionMode; - // const retainDate = md.retentionDate; - // const date = moment(); - // const days - // = type === 'Days' ? val : val * 365; - // const expectedDate - // = date.add(days, 'days'); - // assert.ifError(err); - // assert.strictEqual(mode, testMode); - // assert.strictEqual(formatTime(retainDate), - // formatTime(expectedDate.toISOString())); - // done(); - // }); - // }); - // }); - // }); - // }); - // }); - - - // it('should successfully put an object with legal hold ON', done => { - // const request = new DummyRequest({ - // bucketName, - // namespace, - // objectKey: objectName, - // headers: { - // 'x-amz-object-lock-legal-hold': 'ON', - // }, - // url: `/${bucketName}/${objectName}`, - // calculatedHash: 'vnR+tLdVF79rPPfF+7YvOg==', - // }, postBody); - - // bucketPut(authInfo, testPutBucketRequestLock, log, () => { - // objectPut(authInfo, request, undefined, log, (err, headers) => { - // assert.ifError(err); - // assert.strictEqual(headers.ETag, `"${correctMD5}"`); - // metadata.getObjectMD(bucketName, objectName, {}, log, - // (err, md) => { - // assert.ifError(err); - // assert.strictEqual(md.legalHold, true); - // done(); - // }); - // }); - // }); - // }); - - // it('should successfully put an object with legal hold OFF', done => { - // const request = new DummyRequest({ - // bucketName, - // namespace, - // objectKey: objectName, - // headers: { - // 'x-amz-object-lock-legal-hold': 'OFF', - // }, - // url: `/${bucketName}/${objectName}`, - // calculatedHash: 'vnR+tLdVF79rPPfF+7YvOg==', - // }, postBody); - - // bucketPut(authInfo, testPutBucketRequestLock, log, () => { - // objectPut(authInfo, request, undefined, log, (err, headers) => { - // assert.ifError(err); - // assert.strictEqual(headers.ETag, `"${correctMD5}"`); - // metadata.getObjectMD(bucketName, objectName, {}, log, - // (err, md) => { - // assert.ifError(err); - // assert(md); - // assert.strictEqual(md.legalHold, false); - // done(); - // }); - // }); - // }); - // }); - - // it('should successfully put an object with user metadata', done => { - // const testPutObjectRequest = new DummyRequest({ - // bucketName, - // namespace, - // objectKey: objectName, - // headers: { - // // Note that Node will collapse common headers into one - // // (e.g. "x-amz-meta-test: hi" and "x-amz-meta-test: - // // there" becomes "x-amz-meta-test: hi, there") - // // Here we are not going through an actual http - // // request so will not collapse properly. - // 'x-amz-meta-test': 'some metadata', - // 'x-amz-meta-test2': 'some more metadata', - // 'x-amz-meta-test3': 'even more metadata', - // }, - // url: `/${bucketName}/${objectName}`, - // calculatedHash: 'vnR+tLdVF79rPPfF+7YvOg==', - // }, postBody); - - // bucketPut(authInfo, testPutBucketRequest, log, () => { - // objectPut(authInfo, testPutObjectRequest, undefined, log, - // (err, resHeaders) => { - // assert.strictEqual(resHeaders.ETag, `"${correctMD5}"`); - // metadata.getObjectMD(bucketName, objectName, {}, log, - // (err, md) => { - // assert(md); - // assert.strictEqual(md['x-amz-meta-test'], - // 'some metadata'); - // assert.strictEqual(md['x-amz-meta-test2'], - // 'some more metadata'); - // assert.strictEqual(md['x-amz-meta-test3'], - // 'even more metadata'); - // done(); - // }); - // }); - // }); - // }); - - // it('If testingMode=true and the last-modified header is given, should set last-modified accordingly', done => { - // const imposedLastModified = '2024-07-19'; - // const testPutObjectRequest = new DummyRequest({ - // bucketName, - // namespace, - // objectKey: objectName, - // headers: { - // [lastModifiedHeader]: imposedLastModified, - // }, - // url: `/${bucketName}/${objectName}`, - // calculatedHash: 'vnR+tLdVF79rPPfF+7YvOg==', - // }, postBody); - - // bucketPut(authInfo, testPutBucketRequest, log, () => { - // const config = require('../../../lib/Config'); - // config.config.testingMode = true; - // objectPut(authInfo, testPutObjectRequest, undefined, log, - // (err, resHeaders) => { - // assert.strictEqual(resHeaders.ETag, `"${correctMD5}"`); - // metadata.getObjectMD(bucketName, objectName, {}, log, - // (err, md) => { - // assert(md); - - // const lastModified = md['last-modified']; - // const lastModifiedDate = lastModified.split('T')[0]; - // // last-modified date should be the one set by the last-modified header - // assert.strictEqual(lastModifiedDate, imposedLastModified); - - // // The header should be removed after being treated. - // assert(md[lastModifiedHeader] === undefined); - - // config.config.testingMode = false; - // done(); - // }); - // }); - // }); - // }); - - // it('should not take into acccount the last-modified header when testingMode=false', done => { - // const imposedLastModified = '2024-07-19'; - - // const testPutObjectRequest = new DummyRequest({ - // bucketName, - // namespace, - // objectKey: objectName, - // headers: { - // 'x-amz-meta-x-scal-last-modified': imposedLastModified, - // }, - // url: `/${bucketName}/${objectName}`, - // calculatedHash: 'vnR+tLdVF79rPPfF+7YvOg==', - // }, postBody); - - // bucketPut(authInfo, testPutBucketRequest, log, () => { - // const config = require('../../../lib/Config'); - // config.config.testingMode = false; - // objectPut(authInfo, testPutObjectRequest, undefined, log, - // (err, resHeaders) => { - // assert.strictEqual(resHeaders.ETag, `"${correctMD5}"`); - // metadata.getObjectMD(bucketName, objectName, {}, log, - // (err, md) => { - // assert(md); - // assert.strictEqual(md['x-amz-meta-x-scal-last-modified'], - // imposedLastModified); - // const lastModified = md['last-modified']; - // const lastModifiedDate = lastModified.split('T')[0]; - // const currentTs = new Date().toJSON(); - // const currentDate = currentTs.split('T')[0]; - // assert.strictEqual(lastModifiedDate, currentDate); - // done(); - // }); - // }); - // }); - // }); - - // it('should put an object with user metadata but no data', done => { - // const postBody = ''; - // const correctMD5 = 'd41d8cd98f00b204e9800998ecf8427e'; - // const testPutObjectRequest = new DummyRequest({ - // bucketName, - // namespace, - // objectKey: objectName, - // headers: { - // 'content-length': '0', - // 'x-amz-meta-test': 'some metadata', - // 'x-amz-meta-test2': 'some more metadata', - // 'x-amz-meta-test3': 'even more metadata', - // }, - // parsedContentLength: 0, - // url: `/${bucketName}/${objectName}`, - // calculatedHash: 'd41d8cd98f00b204e9800998ecf8427e', - // }, postBody); - - // bucketPut(authInfo, testPutBucketRequest, log, () => { - // objectPut(authInfo, testPutObjectRequest, undefined, log, - // (err, resHeaders) => { - // assert.strictEqual(resHeaders.ETag, `"${correctMD5}"`); - // assert.deepStrictEqual(ds, []); - // metadata.getObjectMD(bucketName, objectName, {}, log, - // (err, md) => { - // assert(md); - // assert.strictEqual(md.location, null); - // assert.strictEqual(md['x-amz-meta-test'], - // 'some metadata'); - // assert.strictEqual(md['x-amz-meta-test2'], - // 'some more metadata'); - // assert.strictEqual(md['x-amz-meta-test3'], - // 'even more metadata'); - // done(); - // }); - // }); - // }); - // }); - - // it('should not leave orphans in data when overwriting an object', done => { - // const testPutObjectRequest2 = new DummyRequest({ - // bucketName, - // namespace, - // objectKey: objectName, - // headers: {}, - // url: `/${bucketName}/${objectName}`, - // }, Buffer.from('I am another body', 'utf8')); - - // bucketPut(authInfo, testPutBucketRequest, log, () => { - // objectPut(authInfo, testPutObjectRequest, - // undefined, log, () => { - // objectPut(authInfo, testPutObjectRequest2, undefined, - // log, - // () => { - // // orphan objects don't get deleted - // // until the next tick - // // in memory - // setImmediate(() => { - // // Data store starts at index 1 - // assert.strictEqual(ds[0], undefined); - // assert.strictEqual(ds[1], undefined); - // assert.deepStrictEqual(ds[2].value, - // Buffer.from('I am another body', 'utf8')); - // done(); - // }); - // }); - // }); - // }); - // }); - - // it('should not leave orphans in data when overwriting an multipart upload object', done => { - // bucketPut(authInfo, testPutBucketRequest, log, () => { - // mpuUtils.createMPU(namespace, bucketName, objectName, log, - // (err, testUploadId) => { - // objectPut(authInfo, testPutObjectRequest, undefined, log, err => { - // assert.ifError(err); - // sinon.assert.calledWith(metadata.putObjectMD, - // any, any, any, sinon.match({ oldReplayId: testUploadId }), any, any); - // done(); - // }); - // }); - // }); - // }); - - // it('should not put object with retention configuration if object lock ' + - // 'is not enabled on the bucket', done => { - // const testPutObjectRequest = new DummyRequest({ - // bucketName, - // namespace, - // objectKey: objectName, - // headers: { - // 'x-amz-object-lock-retain-until-date': mockDate, - // 'x-amz-object-lock-mode': 'GOVERNANCE', - // }, - // url: `/${bucketName}/${objectName}`, - // calculatedHash: 'vnR+tLdVF79rPPfF+7YvOg==', - // }, postBody); + it('should return AccessDenied error if user is not authorized', (done) => { + sinon.stub(metadataUtils, 'standardMetadataValidateBucketAndObj').callsFake((params, actionImplicitDenies, log, callback) => { + const err = new Error('AccessDenied'); + err.AccessDenied = true; + callback(err); + }); - // bucketPut(authInfo, testPutBucketRequest, log, () => { - // objectPut(authInfo, testPutObjectRequest, undefined, log, err => { - // assert.deepStrictEqual(err, errors.InvalidRequest - // .customizeDescription( - // 'Bucket is missing ObjectLockConfiguration')); - // done(); - // }); - // }); - // }); - // it('should forward a 400 back to client on metadata 408 response', () => { - // metadata.putObjectMD = - // (bucketName, objName, objVal, params, log, cb) => - // cb({ httpCode: 408 }); + objectPost(authInfo, request, null, log, callback); - // bucketPut(authInfo, testPutBucketRequest, log, () => { - // objectPut(authInfo, testPutObjectRequest, undefined, log, - // err => { - // assert.strictEqual(err.code, 400); - // }); - // }); - // }); + process.nextTick(() => { + assert(callback.calledOnce); + assert(callback.calledWithMatch(sinon.match.has('AccessDenied'))); + done(); + }); + }); - // it('should forward a 502 to the client for 4xx != 408', () => { - // metadata.putObjectMD = - // (bucketName, objName, objVal, params, log, cb) => - // cb({ httpCode: 412 }); + it('should successfully post an object', (done) => { + const bucket = { + getOwner: sinon.stub().returns('ownerID'), + hasDeletedFlag: sinon.stub().returns(false), + getLifecycleConfiguration: sinon.stub().returns(null), + getVersioningConfiguration: sinon.stub().returns({ Status: 'Enabled' }), + getLocationConstraint: sinon.stub().returns('location') + }; - // bucketPut(authInfo, testPutBucketRequest, log, () => { - // objectPut(authInfo, testPutObjectRequest, undefined, log, - // err => { - // assert.strictEqual(err.code, 502); - // }); - // }); - // }); + const objMD = {}; + const responseHeaders = {}; + sinon.stub(metadataUtils, 'standardMetadataValidateBucketAndObj').callsFake((params, actionImplicitDenies, log, callback) => { + callback(null, bucket, objMD); + }); - // it('should pass overheadField to metadata.putObjectMD for a non-versioned request', done => { - // const testPutObjectRequest = new DummyRequest({ - // bucketName, - // namespace, - // objectKey: objectName, - // headers: {}, - // url: `/${bucketName}/${objectName}`, - // contentMD5: correctMD5, - // }, postBody); + sinon.stub(collectCorsHeaders, 'collectCorsHeaders').returns(responseHeaders); + sinon.stub(getObjectSSEConfiguration, 'getObjectSSEConfiguration').callsFake((headers, bucket, log, callback) => { + callback(null, null); + }); + sinon.stub(kms, 'createCipherBundle').callsFake((serverSideEncryptionConfig, log, callback) => { + callback(null, null); + }); + sinon.stub(validateHeaders, 'validateHeaders').returns(null); + sinon.stub(writeContinue, 'writeContinue').returns(null); + sinon.stub(createAndStoreObject, 'createAndStoreObject').callsFake((bucketName, bucket, key, objMD, authInfo, canonicalID, cipherBundle, request, isDeleteMarker, streamingV4Params, overheadField, log, callback) => { + callback(null, { contentMD5: 'md5', lastModified: new Date(), versionId: 'versionId' }); + }); + sinon.stub(setExpirationHeaders, 'setExpirationHeaders').returns(null); + sinon.stub(pushMetric, 'pushMetric').returns(null); - // bucketPut(authInfo, testPutBucketRequest, log, () => { - // objectPut(authInfo, testPutObjectRequest, undefined, log, - // err => { - // assert.ifError(err); - // sinon.assert.calledWith(metadata.putObjectMD.lastCall, - // bucketName, objectName, any, sinon.match({ overheadField: sinon.match.array }), any, any); - // done(); - // }); - // }); - // }); + request.file.end('filecontent'); - // it('should pass overheadField to metadata.putObjectMD for a versioned request', done => { - // const testPutObjectRequest = versioningTestUtils - // .createPutObjectRequest(bucketName, objectName, Buffer.from('I am another body', 'utf8')); - // bucketPut(authInfo, testPutBucketRequest, log, () => { - // bucketPutVersioning(authInfo, enableVersioningRequest, log, () => { - // objectPut(authInfo, testPutObjectRequest, undefined, log, - // err => { - // assert.ifError(err); - // sinon.assert.calledWith(metadata.putObjectMD.lastCall, - // bucketName, objectName, any, sinon.match({ overheadField: sinon.match.array }), any, any); - // done(); - // } - // ); - // }); - // }); - // }); + objectPost(authInfo, request, null, log, callback); - // it('should pass overheadField to metadata.putObjectMD for a version-suspended request', done => { - // const testPutObjectRequest = versioningTestUtils - // .createPutObjectRequest(bucketName, objectName, Buffer.from('I am another body', 'utf8')); - // bucketPut(authInfo, testPutBucketRequest, log, () => { - // bucketPutVersioning(authInfo, suspendVersioningRequest, log, () => { - // objectPut(authInfo, testPutObjectRequest, undefined, log, - // err => { - // assert.ifError(err); - // sinon.assert.calledWith(metadata.putObjectMD.lastCall, - // bucketName, objectName, any, sinon.match({ overheadField: sinon.match.array }), any, any); - // done(); - // } - // ); - // }); - // }); - // }); + process.nextTick(() => { + assert(callback.calledOnce); + assert(callback.calledWith(null, responseHeaders)); + done(); + }); + }); }); - - - - -// describe('objectPut API with versioning', () => { -// beforeEach(() => { -// cleanup(); -// }); - -// const objData = ['foo0', 'foo1', 'foo2'].map(str => -// Buffer.from(str, 'utf8')); -// const testPutObjectRequests = objData.map(data => versioningTestUtils -// .createPutObjectRequest(bucketName, objectName, data)); - -// it('should delete latest version when creating new null version ' + -// 'if latest version is null version', done => { -// async.series([ -// callback => bucketPut(authInfo, testPutBucketRequest, log, -// callback), -// // putting null version by putting obj before versioning configured -// callback => objectPut(authInfo, testPutObjectRequests[0], undefined, -// log, err => { -// versioningTestUtils.assertDataStoreValues(ds, [objData[0]]); -// callback(err); -// }), -// callback => bucketPutVersioning(authInfo, suspendVersioningRequest, -// log, callback), -// // creating new null version by putting obj after ver suspended -// callback => objectPut(authInfo, testPutObjectRequests[1], -// undefined, log, err => { -// // wait until next tick since mem backend executes -// // deletes in the next tick -// setImmediate(() => { -// // old null version should be deleted -// versioningTestUtils.assertDataStoreValues(ds, -// [undefined, objData[1]]); -// callback(err); -// }); -// }), -// // create another null version -// callback => objectPut(authInfo, testPutObjectRequests[2], -// undefined, log, err => { -// setImmediate(() => { -// // old null version should be deleted -// versioningTestUtils.assertDataStoreValues(ds, -// [undefined, undefined, objData[2]]); -// callback(err); -// }); -// }), -// ], done); -// }); - -// describe('when null version is not the latest version', () => { -// const objData = ['foo0', 'foo1', 'foo2'].map(str => -// Buffer.from(str, 'utf8')); -// const testPutObjectRequests = objData.map(data => versioningTestUtils -// .createPutObjectRequest(bucketName, objectName, data)); -// beforeEach(done => { -// async.series([ -// callback => bucketPut(authInfo, testPutBucketRequest, log, -// callback), -// // putting null version: put obj before versioning configured -// callback => objectPut(authInfo, testPutObjectRequests[0], -// undefined, log, callback), -// callback => bucketPutVersioning(authInfo, -// enableVersioningRequest, log, callback), -// // put another version: -// callback => objectPut(authInfo, testPutObjectRequests[1], -// undefined, log, callback), -// callback => bucketPutVersioning(authInfo, -// suspendVersioningRequest, log, callback), -// ], err => { -// if (err) { -// return done(err); -// } -// versioningTestUtils.assertDataStoreValues(ds, -// objData.slice(0, 2)); -// return done(); -// }); -// }); - -// it('should still delete null version when creating new null version', -// done => { -// objectPut(authInfo, testPutObjectRequests[2], undefined, -// log, err => { -// assert.ifError(err, `Unexpected err: ${err}`); -// setImmediate(() => { -// // old null version should be deleted after putting -// // new null version -// versioningTestUtils.assertDataStoreValues(ds, -// [undefined, objData[1], objData[2]]); -// done(err); -// }); -// }); -// }); -// }); - -// it('should return BadDigest error and not leave orphans in data when ' + -// 'contentMD5 and completedHash do not match', done => { -// const testPutObjectRequest = new DummyRequest({ -// bucketName, -// namespace, -// objectKey: objectName, -// headers: {}, -// url: `/${bucketName}/${objectName}`, -// contentMD5: 'vnR+tLdVF79rPPfF+7YvOg==', -// }, Buffer.from('I am another body', 'utf8')); - -// bucketPut(authInfo, testPutBucketRequest, log, () => { -// objectPut(authInfo, testPutObjectRequest, undefined, log, -// err => { -// assert.deepStrictEqual(err, errors.BadDigest); -// // orphan objects don't get deleted -// // until the next tick -// // in memory -// setImmediate(() => { -// // Data store starts at index 1 -// assert.strictEqual(ds[0], undefined); -// assert.strictEqual(ds[1], undefined); -// done(); -// }); -// }); -// }); -// }); -// });