From 90e496d495fa155991a2524a090b5c638407f8ac Mon Sep 17 00:00:00 2001 From: unknown Date: Sun, 4 May 2025 07:38:30 +0700 Subject: [PATCH] intgrasi fungsi edit dan tambah serta upload pada admin --- backend/app.js | 5 + backend/controller/hamaController.js | 41 +- backend/controller/penyakitController.js | 55 +- backend/image_hama/hama-1746265160009.jpg | Bin 0 -> 6187 bytes backend/image_hama/hama-1746270395045.jpg | Bin 0 -> 66943 bytes backend/image_hama/hama-1746270839721.jpg | Bin 0 -> 66943 bytes backend/image_hama/hama-1746297043479.jpg | Bin 0 -> 6187 bytes backend/image_hama/hama-1746300136697.jpg | Bin 0 -> 9811 bytes backend/image_hama/hama-1746308562954.jpg | Bin 0 -> 9811 bytes backend/image_hama/hama-1746308575664.jpg | Bin 0 -> 66943 bytes backend/image_penyakit/hama-1746266358289.jpg | Bin 0 -> 10349 bytes .../image_penyakit/penyakit-1746266733862.jpg | Bin 0 -> 9811 bytes .../image_penyakit/penyakit-1746271780000.jpg | Bin 0 -> 7806 bytes .../image_penyakit/penyakit-1746271815281.jpg | Bin 0 -> 7806 bytes .../image_penyakit/penyakit-1746311138474.jpg | Bin 0 -> 10349 bytes .../image_penyakit/penyakit-1746312599967.jpg | Bin 0 -> 10349 bytes .../image_penyakit/penyakit-1746313148023.jpg | Bin 0 -> 10349 bytes backend/middleware/uploadHamaGambar.js | 47 + backend/middleware/uploadPenyakitGambar.js | 47 + .../20250503100211-add-foto-to-penyakit.js | 14 + backend/models/penyakit.js | 4 + backend/node_modules/.bin/mkdirp | 16 + backend/node_modules/.bin/mkdirp.cmd | 17 + backend/node_modules/.bin/mkdirp.ps1 | 28 + backend/node_modules/append-field/.npmignore | 1 + backend/node_modules/append-field/LICENSE | 21 + backend/node_modules/append-field/README.md | 44 + backend/node_modules/append-field/index.js | 12 + .../append-field/lib/parse-path.js | 53 + .../append-field/lib/set-value.js | 64 + .../node_modules/append-field/package.json | 19 + .../node_modules/append-field/test/forms.js | 19 + backend/node_modules/buffer-from/LICENSE | 21 + backend/node_modules/buffer-from/index.js | 72 + backend/node_modules/buffer-from/package.json | 19 + backend/node_modules/buffer-from/readme.md | 69 + backend/node_modules/busboy/.eslintrc.js | 5 + .../busboy/.github/workflows/ci.yml | 24 + .../busboy/.github/workflows/lint.yml | 23 + backend/node_modules/busboy/LICENSE | 19 + backend/node_modules/busboy/README.md | 191 + .../bench/bench-multipart-fields-100mb-big.js | 149 + .../bench-multipart-fields-100mb-small.js | 143 + .../bench/bench-multipart-files-100mb-big.js | 154 + .../bench-multipart-files-100mb-small.js | 148 + .../bench-urlencoded-fields-100pairs-small.js | 101 + ...ch-urlencoded-fields-900pairs-small-alt.js | 84 + backend/node_modules/busboy/lib/index.js | 57 + .../busboy/lib/types/multipart.js | 653 ++ .../busboy/lib/types/urlencoded.js | 350 + backend/node_modules/busboy/lib/utils.js | 596 ++ backend/node_modules/busboy/package.json | 22 + backend/node_modules/busboy/test/common.js | 109 + .../test/test-types-multipart-charsets.js | 94 + .../test/test-types-multipart-stream-pause.js | 102 + .../busboy/test/test-types-multipart.js | 1053 ++ .../busboy/test/test-types-urlencoded.js | 488 + backend/node_modules/busboy/test/test.js | 20 + backend/node_modules/concat-stream/LICENSE | 24 + backend/node_modules/concat-stream/index.js | 144 + .../node_modules/concat-stream/package.json | 55 + backend/node_modules/concat-stream/readme.md | 102 + backend/node_modules/core-util-is/LICENSE | 19 + backend/node_modules/core-util-is/README.md | 3 + backend/node_modules/core-util-is/lib/util.js | 107 + .../node_modules/core-util-is/package.json | 38 + backend/node_modules/isarray/.npmignore | 1 + backend/node_modules/isarray/.travis.yml | 4 + backend/node_modules/isarray/Makefile | 6 + backend/node_modules/isarray/README.md | 60 + backend/node_modules/isarray/component.json | 19 + backend/node_modules/isarray/index.js | 5 + backend/node_modules/isarray/package.json | 45 + backend/node_modules/isarray/test.js | 20 + backend/node_modules/minimist/.eslintrc | 29 + .../node_modules/minimist/.github/FUNDING.yml | 12 + backend/node_modules/minimist/.nycrc | 14 + backend/node_modules/minimist/CHANGELOG.md | 298 + backend/node_modules/minimist/LICENSE | 18 + backend/node_modules/minimist/README.md | 121 + .../node_modules/minimist/example/parse.js | 4 + backend/node_modules/minimist/index.js | 263 + backend/node_modules/minimist/package.json | 75 + .../node_modules/minimist/test/all_bool.js | 34 + backend/node_modules/minimist/test/bool.js | 177 + backend/node_modules/minimist/test/dash.js | 43 + .../minimist/test/default_bool.js | 37 + backend/node_modules/minimist/test/dotted.js | 24 + .../node_modules/minimist/test/kv_short.js | 32 + backend/node_modules/minimist/test/long.js | 33 + backend/node_modules/minimist/test/num.js | 38 + backend/node_modules/minimist/test/parse.js | 209 + .../minimist/test/parse_modified.js | 11 + backend/node_modules/minimist/test/proto.js | 64 + backend/node_modules/minimist/test/short.js | 69 + .../node_modules/minimist/test/stop_early.js | 17 + backend/node_modules/minimist/test/unknown.js | 104 + .../node_modules/minimist/test/whitespace.js | 10 + backend/node_modules/mkdirp/LICENSE | 21 + backend/node_modules/mkdirp/bin/cmd.js | 33 + backend/node_modules/mkdirp/bin/usage.txt | 12 + backend/node_modules/mkdirp/index.js | 102 + backend/node_modules/mkdirp/package.json | 33 + backend/node_modules/mkdirp/readme.markdown | 100 + backend/node_modules/multer/LICENSE | 17 + backend/node_modules/multer/README.md | 333 + backend/node_modules/multer/index.js | 104 + backend/node_modules/multer/lib/counter.js | 28 + .../node_modules/multer/lib/file-appender.js | 67 + .../multer/lib/make-middleware.js | 175 + .../node_modules/multer/lib/multer-error.js | 24 + .../multer/lib/remove-uploaded-files.js | 28 + backend/node_modules/multer/package.json | 52 + backend/node_modules/multer/storage/disk.js | 66 + backend/node_modules/multer/storage/memory.js | 21 + .../process-nextick-args/index.js | 45 + .../process-nextick-args/license.md | 19 + .../process-nextick-args/package.json | 25 + .../process-nextick-args/readme.md | 18 + .../node_modules/readable-stream/.travis.yml | 34 + .../readable-stream/CONTRIBUTING.md | 38 + .../readable-stream/GOVERNANCE.md | 136 + backend/node_modules/readable-stream/LICENSE | 47 + .../node_modules/readable-stream/README.md | 58 + .../doc/wg-meetings/2015-01-30.md | 60 + .../readable-stream/duplex-browser.js | 1 + .../node_modules/readable-stream/duplex.js | 1 + .../readable-stream/lib/_stream_duplex.js | 131 + .../lib/_stream_passthrough.js | 47 + .../readable-stream/lib/_stream_readable.js | 1019 ++ .../readable-stream/lib/_stream_transform.js | 214 + .../readable-stream/lib/_stream_writable.js | 685 ++ .../lib/internal/streams/BufferList.js | 78 + .../lib/internal/streams/destroy.js | 84 + .../lib/internal/streams/stream-browser.js | 1 + .../lib/internal/streams/stream.js | 1 + .../node_modules/safe-buffer/LICENSE | 21 + .../node_modules/safe-buffer/README.md | 584 ++ .../node_modules/safe-buffer/index.d.ts | 187 + .../node_modules/safe-buffer/index.js | 62 + .../node_modules/safe-buffer/package.json | 37 + .../node_modules/readable-stream/package.json | 52 + .../readable-stream/passthrough.js | 1 + .../readable-stream/readable-browser.js | 7 + .../node_modules/readable-stream/readable.js | 19 + .../node_modules/readable-stream/transform.js | 1 + .../readable-stream/writable-browser.js | 1 + .../node_modules/readable-stream/writable.js | 8 + .../node_modules/streamsearch/.eslintrc.js | 5 + .../streamsearch/.github/workflows/ci.yml | 24 + .../streamsearch/.github/workflows/lint.yml | 23 + backend/node_modules/streamsearch/LICENSE | 19 + backend/node_modules/streamsearch/README.md | 95 + backend/node_modules/streamsearch/lib/sbmh.js | 267 + .../node_modules/streamsearch/package.json | 34 + .../node_modules/streamsearch/test/test.js | 70 + .../node_modules/string_decoder/.travis.yml | 50 + backend/node_modules/string_decoder/LICENSE | 48 + backend/node_modules/string_decoder/README.md | 47 + .../string_decoder/lib/string_decoder.js | 296 + .../node_modules/safe-buffer/LICENSE | 21 + .../node_modules/safe-buffer/README.md | 584 ++ .../node_modules/safe-buffer/index.d.ts | 187 + .../node_modules/safe-buffer/index.js | 62 + .../node_modules/safe-buffer/package.json | 37 + .../node_modules/string_decoder/package.json | 31 + backend/node_modules/typedarray/.travis.yml | 4 + backend/node_modules/typedarray/LICENSE | 35 + .../node_modules/typedarray/example/tarray.js | 4 + backend/node_modules/typedarray/index.js | 630 ++ backend/node_modules/typedarray/package.json | 55 + .../node_modules/typedarray/readme.markdown | 61 + .../typedarray/test/server/undef_globals.js | 19 + .../node_modules/typedarray/test/tarray.js | 10 + .../node_modules/util-deprecate/History.md | 16 + backend/node_modules/util-deprecate/LICENSE | 24 + backend/node_modules/util-deprecate/README.md | 53 + .../node_modules/util-deprecate/browser.js | 67 + backend/node_modules/util-deprecate/node.js | 6 + .../node_modules/util-deprecate/package.json | 27 + backend/node_modules/xtend/.jshintrc | 30 + backend/node_modules/xtend/LICENSE | 20 + backend/node_modules/xtend/README.md | 32 + backend/node_modules/xtend/immutable.js | 19 + backend/node_modules/xtend/mutable.js | 17 + backend/node_modules/xtend/package.json | 55 + backend/node_modules/xtend/test.js | 103 + backend/routes/hamaRoutes.js | 47 +- backend/routes/penyakitRoutes.js | 49 +- frontend/lib/admin/edit_hama_page.dart | 197 +- frontend/lib/admin/edit_penyakit_page.dart | 193 +- frontend/lib/admin/hama_page.dart | 1 + frontend/lib/admin/image_utilities.dart | 140 + .../lib/admin/image_utilities_penyakit.dart | 140 + frontend/lib/admin/penyakit_page.dart | 2 + frontend/lib/admin/tambah_hama_page.dart | 59 + frontend/lib/admin/tambah_penyakit_page.dart | 57 + frontend/lib/api_services/api_services.dart | 502 +- frontend/lib/user/detail_hama_page.dart | 273 +- frontend/lib/user/hama_page.dart | 74 +- .../flutter/generated_plugin_registrant.cc | 4 + .../linux/flutter/generated_plugins.cmake | 1 + .../Flutter/GeneratedPluginRegistrant.swift | 2 + frontend/pubspec.lock | 120 + frontend/pubspec.yaml | 2 + .../flutter/generated_plugin_registrant.cc | 3 + .../windows/flutter/generated_plugins.cmake | 1 + node_modules/.bin/mkdirp | 16 + node_modules/.bin/mkdirp.cmd | 17 + node_modules/.bin/mkdirp.ps1 | 28 + node_modules/.package-lock.json | 225 + node_modules/append-field/.npmignore | 1 + node_modules/append-field/LICENSE | 21 + node_modules/append-field/README.md | 44 + node_modules/append-field/index.js | 12 + node_modules/append-field/lib/parse-path.js | 53 + node_modules/append-field/lib/set-value.js | 64 + node_modules/append-field/package.json | 19 + node_modules/append-field/test/forms.js | 19 + node_modules/buffer-from/LICENSE | 21 + node_modules/buffer-from/index.js | 72 + node_modules/buffer-from/package.json | 19 + node_modules/buffer-from/readme.md | 69 + node_modules/busboy/.eslintrc.js | 5 + node_modules/busboy/.github/workflows/ci.yml | 24 + .../busboy/.github/workflows/lint.yml | 23 + node_modules/busboy/LICENSE | 19 + node_modules/busboy/README.md | 191 + .../bench/bench-multipart-fields-100mb-big.js | 149 + .../bench-multipart-fields-100mb-small.js | 143 + .../bench/bench-multipart-files-100mb-big.js | 154 + .../bench-multipart-files-100mb-small.js | 148 + .../bench-urlencoded-fields-100pairs-small.js | 101 + ...ch-urlencoded-fields-900pairs-small-alt.js | 84 + node_modules/busboy/lib/index.js | 57 + node_modules/busboy/lib/types/multipart.js | 653 ++ node_modules/busboy/lib/types/urlencoded.js | 350 + node_modules/busboy/lib/utils.js | 596 ++ node_modules/busboy/package.json | 22 + node_modules/busboy/test/common.js | 109 + .../test/test-types-multipart-charsets.js | 94 + .../test/test-types-multipart-stream-pause.js | 102 + .../busboy/test/test-types-multipart.js | 1053 ++ .../busboy/test/test-types-urlencoded.js | 488 + node_modules/busboy/test/test.js | 20 + node_modules/concat-stream/LICENSE | 24 + node_modules/concat-stream/index.js | 144 + node_modules/concat-stream/package.json | 55 + node_modules/concat-stream/readme.md | 102 + node_modules/core-util-is/LICENSE | 19 + node_modules/core-util-is/README.md | 3 + node_modules/core-util-is/lib/util.js | 107 + node_modules/core-util-is/package.json | 38 + node_modules/image_picker/.npmignore | 5 + .../image_picker/AngularMediaModal/app/app.js | 1 + .../AngularMediaModal/app/config.js | 77 + .../app/controllers/cameraController.js | 49 + .../app/controllers/fileDestroyController.js | 32 + .../app/controllers/gallery.js | 20 + .../app/controllers/uploadController.js | 43 + .../app/controllers/webServicesController.js | 63 + .../AngularMediaModal/app/enums.js | 13 + .../app/services/backgroundPatterns.js | 51 + .../app/services/facebookPhotos.js | 55 + .../app/services/facebookUsers.js | 53 + .../AngularMediaModal/app/services/flickr.js | 56 + .../AngularMediaModal/app/services/gallery.js | 15 + .../app/services/instagram.js | 53 + .../AngularMediaModal/app/services/pexels.js | 30 + .../app/services/webStorage.js | 32 + .../app/styles/media-modal.css | 68 + .../AngularMediaModal/app/views/camera.html | 82 + .../AngularMediaModal/app/views/gallery.html | 12 + .../app/views/galleryWindow.html | 10 + .../AngularMediaModal/app/views/upload.html | 82 + .../app/views/webService.html | 33 + .../assets/angular/angular-images-resizer.js | 1 + .../assets/fileupload/css/demo-ie8.css | 21 + .../assets/fileupload/css/demo.css | 67 + .../css/jquery.fileupload-noscript.css | 22 + .../css/jquery.fileupload-ui-noscript.css | 17 + .../fileupload/css/jquery.fileupload-ui.css | 57 + .../fileupload/css/jquery.fileupload.css | 37 + .../assets/fileupload/css/style.css | 15 + .../assets/fileupload/img/loading.gif | Bin 0 -> 3897 bytes .../assets/fileupload/img/progressbar.gif | Bin 0 -> 3323 bytes .../js/cors/jquery.postmessage-transport.js | 120 + .../js/cors/jquery.xdr-transport.js | 89 + .../js/jquery.fileupload-angular.js | 425 + .../fileupload/js/jquery.fileupload-audio.js | 112 + .../fileupload/js/jquery.fileupload-image.js | 324 + .../js/jquery.fileupload-jquery-ui.js | 155 + .../js/jquery.fileupload-process.js | 175 + .../fileupload/js/jquery.fileupload-ui.js | 710 ++ .../js/jquery.fileupload-validate.js | 122 + .../fileupload/js/jquery.fileupload-video.js | 112 + .../assets/fileupload/js/jquery.fileupload.js | 1477 +++ .../fileupload/js/jquery.iframe-transport.js | 217 + .../js/vendor/canvas-to-blob.min.js | 2 + .../fileupload/js/vendor/jquery.ui.widget.js | 572 ++ .../js/vendor/load-image.all.min.js | 2 + .../AngularMediaModal/cors/postmessage.html | 75 + .../AngularMediaModal/cors/result.html | 24 + .../image_picker/AngularMediaModal/index.html | 57 + .../AngularMediaModal/testController.js | 14 + node_modules/image_picker/Gruntfile.js | 83 + node_modules/image_picker/bower.json | 16 + .../image_picker/build/image-picker.js | 886 ++ .../image_picker/build/image-picker.min.js | 2 + .../image_picker/build/media-modal.css | 68 + node_modules/image_picker/build/templates.js | 242 + node_modules/image_picker/image-picker.min.js | 2971 ++++++ node_modules/image_picker/index.html | 30 + node_modules/image_picker/package.json | 27 + node_modules/inherits/LICENSE | 16 + node_modules/inherits/README.md | 42 + node_modules/inherits/inherits.js | 9 + node_modules/inherits/inherits_browser.js | 27 + node_modules/inherits/package.json | 29 + node_modules/isarray/.npmignore | 1 + node_modules/isarray/.travis.yml | 4 + node_modules/isarray/Makefile | 6 + node_modules/isarray/README.md | 60 + node_modules/isarray/component.json | 19 + node_modules/isarray/index.js | 5 + node_modules/isarray/package.json | 45 + node_modules/isarray/test.js | 20 + node_modules/media-typer/HISTORY.md | 22 + node_modules/media-typer/LICENSE | 22 + node_modules/media-typer/README.md | 81 + node_modules/media-typer/index.js | 270 + node_modules/media-typer/package.json | 26 + node_modules/mime-db/HISTORY.md | 507 + node_modules/mime-db/LICENSE | 23 + node_modules/mime-db/README.md | 100 + node_modules/mime-db/db.json | 8519 +++++++++++++++++ node_modules/mime-db/index.js | 12 + node_modules/mime-db/package.json | 60 + node_modules/mime-types/HISTORY.md | 397 + node_modules/mime-types/LICENSE | 23 + node_modules/mime-types/README.md | 113 + node_modules/mime-types/index.js | 188 + node_modules/mime-types/package.json | 44 + node_modules/minimist/.eslintrc | 29 + node_modules/minimist/.github/FUNDING.yml | 12 + node_modules/minimist/.nycrc | 14 + node_modules/minimist/CHANGELOG.md | 298 + node_modules/minimist/LICENSE | 18 + node_modules/minimist/README.md | 121 + node_modules/minimist/example/parse.js | 4 + node_modules/minimist/index.js | 263 + node_modules/minimist/package.json | 75 + node_modules/minimist/test/all_bool.js | 34 + node_modules/minimist/test/bool.js | 177 + node_modules/minimist/test/dash.js | 43 + node_modules/minimist/test/default_bool.js | 37 + node_modules/minimist/test/dotted.js | 24 + node_modules/minimist/test/kv_short.js | 32 + node_modules/minimist/test/long.js | 33 + node_modules/minimist/test/num.js | 38 + node_modules/minimist/test/parse.js | 209 + node_modules/minimist/test/parse_modified.js | 11 + node_modules/minimist/test/proto.js | 64 + node_modules/minimist/test/short.js | 69 + node_modules/minimist/test/stop_early.js | 17 + node_modules/minimist/test/unknown.js | 104 + node_modules/minimist/test/whitespace.js | 10 + node_modules/mkdirp/LICENSE | 21 + node_modules/mkdirp/bin/cmd.js | 33 + node_modules/mkdirp/bin/usage.txt | 12 + node_modules/mkdirp/index.js | 102 + node_modules/mkdirp/package.json | 33 + node_modules/mkdirp/readme.markdown | 100 + node_modules/multer/LICENSE | 17 + node_modules/multer/README.md | 333 + node_modules/multer/index.js | 104 + node_modules/multer/lib/counter.js | 28 + node_modules/multer/lib/file-appender.js | 67 + node_modules/multer/lib/make-middleware.js | 175 + node_modules/multer/lib/multer-error.js | 24 + .../multer/lib/remove-uploaded-files.js | 28 + node_modules/multer/package.json | 52 + node_modules/multer/storage/disk.js | 66 + node_modules/multer/storage/memory.js | 21 + node_modules/object-assign/index.js | 90 + node_modules/object-assign/license | 21 + node_modules/object-assign/package.json | 42 + node_modules/object-assign/readme.md | 61 + node_modules/process-nextick-args/index.js | 45 + node_modules/process-nextick-args/license.md | 19 + .../process-nextick-args/package.json | 25 + node_modules/process-nextick-args/readme.md | 18 + node_modules/readable-stream/.travis.yml | 34 + node_modules/readable-stream/CONTRIBUTING.md | 38 + node_modules/readable-stream/GOVERNANCE.md | 136 + node_modules/readable-stream/LICENSE | 47 + node_modules/readable-stream/README.md | 58 + .../doc/wg-meetings/2015-01-30.md | 60 + .../readable-stream/duplex-browser.js | 1 + node_modules/readable-stream/duplex.js | 1 + .../readable-stream/lib/_stream_duplex.js | 131 + .../lib/_stream_passthrough.js | 47 + .../readable-stream/lib/_stream_readable.js | 1019 ++ .../readable-stream/lib/_stream_transform.js | 214 + .../readable-stream/lib/_stream_writable.js | 685 ++ .../lib/internal/streams/BufferList.js | 78 + .../lib/internal/streams/destroy.js | 84 + .../lib/internal/streams/stream-browser.js | 1 + .../lib/internal/streams/stream.js | 1 + node_modules/readable-stream/package.json | 52 + node_modules/readable-stream/passthrough.js | 1 + .../readable-stream/readable-browser.js | 7 + node_modules/readable-stream/readable.js | 19 + node_modules/readable-stream/transform.js | 1 + .../readable-stream/writable-browser.js | 1 + node_modules/readable-stream/writable.js | 8 + node_modules/safe-buffer/LICENSE | 21 + node_modules/safe-buffer/README.md | 584 ++ node_modules/safe-buffer/index.d.ts | 187 + node_modules/safe-buffer/index.js | 62 + node_modules/safe-buffer/package.json | 37 + node_modules/streamsearch/.eslintrc.js | 5 + .../streamsearch/.github/workflows/ci.yml | 24 + .../streamsearch/.github/workflows/lint.yml | 23 + node_modules/streamsearch/LICENSE | 19 + node_modules/streamsearch/README.md | 95 + node_modules/streamsearch/lib/sbmh.js | 267 + node_modules/streamsearch/package.json | 34 + node_modules/streamsearch/test/test.js | 70 + node_modules/string_decoder/.travis.yml | 50 + node_modules/string_decoder/LICENSE | 48 + node_modules/string_decoder/README.md | 47 + .../string_decoder/lib/string_decoder.js | 296 + node_modules/string_decoder/package.json | 31 + node_modules/type-is/HISTORY.md | 259 + node_modules/type-is/LICENSE | 23 + node_modules/type-is/README.md | 170 + node_modules/type-is/index.js | 266 + node_modules/type-is/package.json | 45 + node_modules/typedarray/.travis.yml | 4 + node_modules/typedarray/LICENSE | 35 + node_modules/typedarray/example/tarray.js | 4 + node_modules/typedarray/index.js | 630 ++ node_modules/typedarray/package.json | 55 + node_modules/typedarray/readme.markdown | 61 + .../typedarray/test/server/undef_globals.js | 19 + node_modules/typedarray/test/tarray.js | 10 + node_modules/util-deprecate/History.md | 16 + node_modules/util-deprecate/LICENSE | 24 + node_modules/util-deprecate/README.md | 53 + node_modules/util-deprecate/browser.js | 67 + node_modules/util-deprecate/node.js | 6 + node_modules/util-deprecate/package.json | 27 + node_modules/xtend/.jshintrc | 30 + node_modules/xtend/LICENSE | 20 + node_modules/xtend/README.md | 32 + node_modules/xtend/immutable.js | 19 + node_modules/xtend/mutable.js | 17 + node_modules/xtend/package.json | 55 + node_modules/xtend/test.js | 103 + package-lock.json | 231 + package.json | 6 + 462 files changed, 54400 insertions(+), 185 deletions(-) create mode 100644 backend/image_hama/hama-1746265160009.jpg create mode 100644 backend/image_hama/hama-1746270395045.jpg create mode 100644 backend/image_hama/hama-1746270839721.jpg create mode 100644 backend/image_hama/hama-1746297043479.jpg create mode 100644 backend/image_hama/hama-1746300136697.jpg create mode 100644 backend/image_hama/hama-1746308562954.jpg create mode 100644 backend/image_hama/hama-1746308575664.jpg create mode 100644 backend/image_penyakit/hama-1746266358289.jpg create mode 100644 backend/image_penyakit/penyakit-1746266733862.jpg create mode 100644 backend/image_penyakit/penyakit-1746271780000.jpg create mode 100644 backend/image_penyakit/penyakit-1746271815281.jpg create mode 100644 backend/image_penyakit/penyakit-1746311138474.jpg create mode 100644 backend/image_penyakit/penyakit-1746312599967.jpg create mode 100644 backend/image_penyakit/penyakit-1746313148023.jpg create mode 100644 backend/middleware/uploadHamaGambar.js create mode 100644 backend/middleware/uploadPenyakitGambar.js create mode 100644 backend/migrations/20250503100211-add-foto-to-penyakit.js create mode 100644 backend/node_modules/.bin/mkdirp create mode 100644 backend/node_modules/.bin/mkdirp.cmd create mode 100644 backend/node_modules/.bin/mkdirp.ps1 create mode 100644 backend/node_modules/append-field/.npmignore create mode 100644 backend/node_modules/append-field/LICENSE create mode 100644 backend/node_modules/append-field/README.md create mode 100644 backend/node_modules/append-field/index.js create mode 100644 backend/node_modules/append-field/lib/parse-path.js create mode 100644 backend/node_modules/append-field/lib/set-value.js create mode 100644 backend/node_modules/append-field/package.json create mode 100644 backend/node_modules/append-field/test/forms.js create mode 100644 backend/node_modules/buffer-from/LICENSE create mode 100644 backend/node_modules/buffer-from/index.js create mode 100644 backend/node_modules/buffer-from/package.json create mode 100644 backend/node_modules/buffer-from/readme.md create mode 100644 backend/node_modules/busboy/.eslintrc.js create mode 100644 backend/node_modules/busboy/.github/workflows/ci.yml create mode 100644 backend/node_modules/busboy/.github/workflows/lint.yml create mode 100644 backend/node_modules/busboy/LICENSE create mode 100644 backend/node_modules/busboy/README.md create mode 100644 backend/node_modules/busboy/bench/bench-multipart-fields-100mb-big.js create mode 100644 backend/node_modules/busboy/bench/bench-multipart-fields-100mb-small.js create mode 100644 backend/node_modules/busboy/bench/bench-multipart-files-100mb-big.js create mode 100644 backend/node_modules/busboy/bench/bench-multipart-files-100mb-small.js create mode 100644 backend/node_modules/busboy/bench/bench-urlencoded-fields-100pairs-small.js create mode 100644 backend/node_modules/busboy/bench/bench-urlencoded-fields-900pairs-small-alt.js create mode 100644 backend/node_modules/busboy/lib/index.js create mode 100644 backend/node_modules/busboy/lib/types/multipart.js create mode 100644 backend/node_modules/busboy/lib/types/urlencoded.js create mode 100644 backend/node_modules/busboy/lib/utils.js create mode 100644 backend/node_modules/busboy/package.json create mode 100644 backend/node_modules/busboy/test/common.js create mode 100644 backend/node_modules/busboy/test/test-types-multipart-charsets.js create mode 100644 backend/node_modules/busboy/test/test-types-multipart-stream-pause.js create mode 100644 backend/node_modules/busboy/test/test-types-multipart.js create mode 100644 backend/node_modules/busboy/test/test-types-urlencoded.js create mode 100644 backend/node_modules/busboy/test/test.js create mode 100644 backend/node_modules/concat-stream/LICENSE create mode 100644 backend/node_modules/concat-stream/index.js create mode 100644 backend/node_modules/concat-stream/package.json create mode 100644 backend/node_modules/concat-stream/readme.md create mode 100644 backend/node_modules/core-util-is/LICENSE create mode 100644 backend/node_modules/core-util-is/README.md create mode 100644 backend/node_modules/core-util-is/lib/util.js create mode 100644 backend/node_modules/core-util-is/package.json create mode 100644 backend/node_modules/isarray/.npmignore create mode 100644 backend/node_modules/isarray/.travis.yml create mode 100644 backend/node_modules/isarray/Makefile create mode 100644 backend/node_modules/isarray/README.md create mode 100644 backend/node_modules/isarray/component.json create mode 100644 backend/node_modules/isarray/index.js create mode 100644 backend/node_modules/isarray/package.json create mode 100644 backend/node_modules/isarray/test.js create mode 100644 backend/node_modules/minimist/.eslintrc create mode 100644 backend/node_modules/minimist/.github/FUNDING.yml create mode 100644 backend/node_modules/minimist/.nycrc create mode 100644 backend/node_modules/minimist/CHANGELOG.md create mode 100644 backend/node_modules/minimist/LICENSE create mode 100644 backend/node_modules/minimist/README.md create mode 100644 backend/node_modules/minimist/example/parse.js create mode 100644 backend/node_modules/minimist/index.js create mode 100644 backend/node_modules/minimist/package.json create mode 100644 backend/node_modules/minimist/test/all_bool.js create mode 100644 backend/node_modules/minimist/test/bool.js create mode 100644 backend/node_modules/minimist/test/dash.js create mode 100644 backend/node_modules/minimist/test/default_bool.js create mode 100644 backend/node_modules/minimist/test/dotted.js create mode 100644 backend/node_modules/minimist/test/kv_short.js create mode 100644 backend/node_modules/minimist/test/long.js create mode 100644 backend/node_modules/minimist/test/num.js create mode 100644 backend/node_modules/minimist/test/parse.js create mode 100644 backend/node_modules/minimist/test/parse_modified.js create mode 100644 backend/node_modules/minimist/test/proto.js create mode 100644 backend/node_modules/minimist/test/short.js create mode 100644 backend/node_modules/minimist/test/stop_early.js create mode 100644 backend/node_modules/minimist/test/unknown.js create mode 100644 backend/node_modules/minimist/test/whitespace.js create mode 100644 backend/node_modules/mkdirp/LICENSE create mode 100644 backend/node_modules/mkdirp/bin/cmd.js create mode 100644 backend/node_modules/mkdirp/bin/usage.txt create mode 100644 backend/node_modules/mkdirp/index.js create mode 100644 backend/node_modules/mkdirp/package.json create mode 100644 backend/node_modules/mkdirp/readme.markdown create mode 100644 backend/node_modules/multer/LICENSE create mode 100644 backend/node_modules/multer/README.md create mode 100644 backend/node_modules/multer/index.js create mode 100644 backend/node_modules/multer/lib/counter.js create mode 100644 backend/node_modules/multer/lib/file-appender.js create mode 100644 backend/node_modules/multer/lib/make-middleware.js create mode 100644 backend/node_modules/multer/lib/multer-error.js create mode 100644 backend/node_modules/multer/lib/remove-uploaded-files.js create mode 100644 backend/node_modules/multer/package.json create mode 100644 backend/node_modules/multer/storage/disk.js create mode 100644 backend/node_modules/multer/storage/memory.js create mode 100644 backend/node_modules/process-nextick-args/index.js create mode 100644 backend/node_modules/process-nextick-args/license.md create mode 100644 backend/node_modules/process-nextick-args/package.json create mode 100644 backend/node_modules/process-nextick-args/readme.md create mode 100644 backend/node_modules/readable-stream/.travis.yml create mode 100644 backend/node_modules/readable-stream/CONTRIBUTING.md create mode 100644 backend/node_modules/readable-stream/GOVERNANCE.md create mode 100644 backend/node_modules/readable-stream/LICENSE create mode 100644 backend/node_modules/readable-stream/README.md create mode 100644 backend/node_modules/readable-stream/doc/wg-meetings/2015-01-30.md create mode 100644 backend/node_modules/readable-stream/duplex-browser.js create mode 100644 backend/node_modules/readable-stream/duplex.js create mode 100644 backend/node_modules/readable-stream/lib/_stream_duplex.js create mode 100644 backend/node_modules/readable-stream/lib/_stream_passthrough.js create mode 100644 backend/node_modules/readable-stream/lib/_stream_readable.js create mode 100644 backend/node_modules/readable-stream/lib/_stream_transform.js create mode 100644 backend/node_modules/readable-stream/lib/_stream_writable.js create mode 100644 backend/node_modules/readable-stream/lib/internal/streams/BufferList.js create mode 100644 backend/node_modules/readable-stream/lib/internal/streams/destroy.js create mode 100644 backend/node_modules/readable-stream/lib/internal/streams/stream-browser.js create mode 100644 backend/node_modules/readable-stream/lib/internal/streams/stream.js create mode 100644 backend/node_modules/readable-stream/node_modules/safe-buffer/LICENSE create mode 100644 backend/node_modules/readable-stream/node_modules/safe-buffer/README.md create mode 100644 backend/node_modules/readable-stream/node_modules/safe-buffer/index.d.ts create mode 100644 backend/node_modules/readable-stream/node_modules/safe-buffer/index.js create mode 100644 backend/node_modules/readable-stream/node_modules/safe-buffer/package.json create mode 100644 backend/node_modules/readable-stream/package.json create mode 100644 backend/node_modules/readable-stream/passthrough.js create mode 100644 backend/node_modules/readable-stream/readable-browser.js create mode 100644 backend/node_modules/readable-stream/readable.js create mode 100644 backend/node_modules/readable-stream/transform.js create mode 100644 backend/node_modules/readable-stream/writable-browser.js create mode 100644 backend/node_modules/readable-stream/writable.js create mode 100644 backend/node_modules/streamsearch/.eslintrc.js create mode 100644 backend/node_modules/streamsearch/.github/workflows/ci.yml create mode 100644 backend/node_modules/streamsearch/.github/workflows/lint.yml create mode 100644 backend/node_modules/streamsearch/LICENSE create mode 100644 backend/node_modules/streamsearch/README.md create mode 100644 backend/node_modules/streamsearch/lib/sbmh.js create mode 100644 backend/node_modules/streamsearch/package.json create mode 100644 backend/node_modules/streamsearch/test/test.js create mode 100644 backend/node_modules/string_decoder/.travis.yml create mode 100644 backend/node_modules/string_decoder/LICENSE create mode 100644 backend/node_modules/string_decoder/README.md create mode 100644 backend/node_modules/string_decoder/lib/string_decoder.js create mode 100644 backend/node_modules/string_decoder/node_modules/safe-buffer/LICENSE create mode 100644 backend/node_modules/string_decoder/node_modules/safe-buffer/README.md create mode 100644 backend/node_modules/string_decoder/node_modules/safe-buffer/index.d.ts create mode 100644 backend/node_modules/string_decoder/node_modules/safe-buffer/index.js create mode 100644 backend/node_modules/string_decoder/node_modules/safe-buffer/package.json create mode 100644 backend/node_modules/string_decoder/package.json create mode 100644 backend/node_modules/typedarray/.travis.yml create mode 100644 backend/node_modules/typedarray/LICENSE create mode 100644 backend/node_modules/typedarray/example/tarray.js create mode 100644 backend/node_modules/typedarray/index.js create mode 100644 backend/node_modules/typedarray/package.json create mode 100644 backend/node_modules/typedarray/readme.markdown create mode 100644 backend/node_modules/typedarray/test/server/undef_globals.js create mode 100644 backend/node_modules/typedarray/test/tarray.js create mode 100644 backend/node_modules/util-deprecate/History.md create mode 100644 backend/node_modules/util-deprecate/LICENSE create mode 100644 backend/node_modules/util-deprecate/README.md create mode 100644 backend/node_modules/util-deprecate/browser.js create mode 100644 backend/node_modules/util-deprecate/node.js create mode 100644 backend/node_modules/util-deprecate/package.json create mode 100644 backend/node_modules/xtend/.jshintrc create mode 100644 backend/node_modules/xtend/LICENSE create mode 100644 backend/node_modules/xtend/README.md create mode 100644 backend/node_modules/xtend/immutable.js create mode 100644 backend/node_modules/xtend/mutable.js create mode 100644 backend/node_modules/xtend/package.json create mode 100644 backend/node_modules/xtend/test.js create mode 100644 frontend/lib/admin/image_utilities.dart create mode 100644 frontend/lib/admin/image_utilities_penyakit.dart create mode 100644 node_modules/.bin/mkdirp create mode 100644 node_modules/.bin/mkdirp.cmd create mode 100644 node_modules/.bin/mkdirp.ps1 create mode 100644 node_modules/.package-lock.json create mode 100644 node_modules/append-field/.npmignore create mode 100644 node_modules/append-field/LICENSE create mode 100644 node_modules/append-field/README.md create mode 100644 node_modules/append-field/index.js create mode 100644 node_modules/append-field/lib/parse-path.js create mode 100644 node_modules/append-field/lib/set-value.js create mode 100644 node_modules/append-field/package.json create mode 100644 node_modules/append-field/test/forms.js create mode 100644 node_modules/buffer-from/LICENSE create mode 100644 node_modules/buffer-from/index.js create mode 100644 node_modules/buffer-from/package.json create mode 100644 node_modules/buffer-from/readme.md create mode 100644 node_modules/busboy/.eslintrc.js create mode 100644 node_modules/busboy/.github/workflows/ci.yml create mode 100644 node_modules/busboy/.github/workflows/lint.yml create mode 100644 node_modules/busboy/LICENSE create mode 100644 node_modules/busboy/README.md create mode 100644 node_modules/busboy/bench/bench-multipart-fields-100mb-big.js create mode 100644 node_modules/busboy/bench/bench-multipart-fields-100mb-small.js create mode 100644 node_modules/busboy/bench/bench-multipart-files-100mb-big.js create mode 100644 node_modules/busboy/bench/bench-multipart-files-100mb-small.js create mode 100644 node_modules/busboy/bench/bench-urlencoded-fields-100pairs-small.js create mode 100644 node_modules/busboy/bench/bench-urlencoded-fields-900pairs-small-alt.js create mode 100644 node_modules/busboy/lib/index.js create mode 100644 node_modules/busboy/lib/types/multipart.js create mode 100644 node_modules/busboy/lib/types/urlencoded.js create mode 100644 node_modules/busboy/lib/utils.js create mode 100644 node_modules/busboy/package.json create mode 100644 node_modules/busboy/test/common.js create mode 100644 node_modules/busboy/test/test-types-multipart-charsets.js create mode 100644 node_modules/busboy/test/test-types-multipart-stream-pause.js create mode 100644 node_modules/busboy/test/test-types-multipart.js create mode 100644 node_modules/busboy/test/test-types-urlencoded.js create mode 100644 node_modules/busboy/test/test.js create mode 100644 node_modules/concat-stream/LICENSE create mode 100644 node_modules/concat-stream/index.js create mode 100644 node_modules/concat-stream/package.json create mode 100644 node_modules/concat-stream/readme.md create mode 100644 node_modules/core-util-is/LICENSE create mode 100644 node_modules/core-util-is/README.md create mode 100644 node_modules/core-util-is/lib/util.js create mode 100644 node_modules/core-util-is/package.json create mode 100644 node_modules/image_picker/.npmignore create mode 100644 node_modules/image_picker/AngularMediaModal/app/app.js create mode 100644 node_modules/image_picker/AngularMediaModal/app/config.js create mode 100644 node_modules/image_picker/AngularMediaModal/app/controllers/cameraController.js create mode 100644 node_modules/image_picker/AngularMediaModal/app/controllers/fileDestroyController.js create mode 100644 node_modules/image_picker/AngularMediaModal/app/controllers/gallery.js create mode 100644 node_modules/image_picker/AngularMediaModal/app/controllers/uploadController.js create mode 100644 node_modules/image_picker/AngularMediaModal/app/controllers/webServicesController.js create mode 100644 node_modules/image_picker/AngularMediaModal/app/enums.js create mode 100644 node_modules/image_picker/AngularMediaModal/app/services/backgroundPatterns.js create mode 100644 node_modules/image_picker/AngularMediaModal/app/services/facebookPhotos.js create mode 100644 node_modules/image_picker/AngularMediaModal/app/services/facebookUsers.js create mode 100644 node_modules/image_picker/AngularMediaModal/app/services/flickr.js create mode 100644 node_modules/image_picker/AngularMediaModal/app/services/gallery.js create mode 100644 node_modules/image_picker/AngularMediaModal/app/services/instagram.js create mode 100644 node_modules/image_picker/AngularMediaModal/app/services/pexels.js create mode 100644 node_modules/image_picker/AngularMediaModal/app/services/webStorage.js create mode 100644 node_modules/image_picker/AngularMediaModal/app/styles/media-modal.css create mode 100644 node_modules/image_picker/AngularMediaModal/app/views/camera.html create mode 100644 node_modules/image_picker/AngularMediaModal/app/views/gallery.html create mode 100644 node_modules/image_picker/AngularMediaModal/app/views/galleryWindow.html create mode 100644 node_modules/image_picker/AngularMediaModal/app/views/upload.html create mode 100644 node_modules/image_picker/AngularMediaModal/app/views/webService.html create mode 100644 node_modules/image_picker/AngularMediaModal/assets/angular/angular-images-resizer.js create mode 100644 node_modules/image_picker/AngularMediaModal/assets/fileupload/css/demo-ie8.css create mode 100644 node_modules/image_picker/AngularMediaModal/assets/fileupload/css/demo.css create mode 100644 node_modules/image_picker/AngularMediaModal/assets/fileupload/css/jquery.fileupload-noscript.css create mode 100644 node_modules/image_picker/AngularMediaModal/assets/fileupload/css/jquery.fileupload-ui-noscript.css create mode 100644 node_modules/image_picker/AngularMediaModal/assets/fileupload/css/jquery.fileupload-ui.css create mode 100644 node_modules/image_picker/AngularMediaModal/assets/fileupload/css/jquery.fileupload.css create mode 100644 node_modules/image_picker/AngularMediaModal/assets/fileupload/css/style.css create mode 100644 node_modules/image_picker/AngularMediaModal/assets/fileupload/img/loading.gif create mode 100644 node_modules/image_picker/AngularMediaModal/assets/fileupload/img/progressbar.gif create mode 100644 node_modules/image_picker/AngularMediaModal/assets/fileupload/js/cors/jquery.postmessage-transport.js create mode 100644 node_modules/image_picker/AngularMediaModal/assets/fileupload/js/cors/jquery.xdr-transport.js create mode 100644 node_modules/image_picker/AngularMediaModal/assets/fileupload/js/jquery.fileupload-angular.js create mode 100644 node_modules/image_picker/AngularMediaModal/assets/fileupload/js/jquery.fileupload-audio.js create mode 100644 node_modules/image_picker/AngularMediaModal/assets/fileupload/js/jquery.fileupload-image.js create mode 100644 node_modules/image_picker/AngularMediaModal/assets/fileupload/js/jquery.fileupload-jquery-ui.js create mode 100644 node_modules/image_picker/AngularMediaModal/assets/fileupload/js/jquery.fileupload-process.js create mode 100644 node_modules/image_picker/AngularMediaModal/assets/fileupload/js/jquery.fileupload-ui.js create mode 100644 node_modules/image_picker/AngularMediaModal/assets/fileupload/js/jquery.fileupload-validate.js create mode 100644 node_modules/image_picker/AngularMediaModal/assets/fileupload/js/jquery.fileupload-video.js create mode 100644 node_modules/image_picker/AngularMediaModal/assets/fileupload/js/jquery.fileupload.js create mode 100644 node_modules/image_picker/AngularMediaModal/assets/fileupload/js/jquery.iframe-transport.js create mode 100644 node_modules/image_picker/AngularMediaModal/assets/fileupload/js/vendor/canvas-to-blob.min.js create mode 100644 node_modules/image_picker/AngularMediaModal/assets/fileupload/js/vendor/jquery.ui.widget.js create mode 100644 node_modules/image_picker/AngularMediaModal/assets/fileupload/js/vendor/load-image.all.min.js create mode 100644 node_modules/image_picker/AngularMediaModal/cors/postmessage.html create mode 100644 node_modules/image_picker/AngularMediaModal/cors/result.html create mode 100644 node_modules/image_picker/AngularMediaModal/index.html create mode 100644 node_modules/image_picker/AngularMediaModal/testController.js create mode 100644 node_modules/image_picker/Gruntfile.js create mode 100644 node_modules/image_picker/bower.json create mode 100644 node_modules/image_picker/build/image-picker.js create mode 100644 node_modules/image_picker/build/image-picker.min.js create mode 100644 node_modules/image_picker/build/media-modal.css create mode 100644 node_modules/image_picker/build/templates.js create mode 100644 node_modules/image_picker/image-picker.min.js create mode 100644 node_modules/image_picker/index.html create mode 100644 node_modules/image_picker/package.json create mode 100644 node_modules/inherits/LICENSE create mode 100644 node_modules/inherits/README.md create mode 100644 node_modules/inherits/inherits.js create mode 100644 node_modules/inherits/inherits_browser.js create mode 100644 node_modules/inherits/package.json create mode 100644 node_modules/isarray/.npmignore create mode 100644 node_modules/isarray/.travis.yml create mode 100644 node_modules/isarray/Makefile create mode 100644 node_modules/isarray/README.md create mode 100644 node_modules/isarray/component.json create mode 100644 node_modules/isarray/index.js create mode 100644 node_modules/isarray/package.json create mode 100644 node_modules/isarray/test.js create mode 100644 node_modules/media-typer/HISTORY.md create mode 100644 node_modules/media-typer/LICENSE create mode 100644 node_modules/media-typer/README.md create mode 100644 node_modules/media-typer/index.js create mode 100644 node_modules/media-typer/package.json create mode 100644 node_modules/mime-db/HISTORY.md create mode 100644 node_modules/mime-db/LICENSE create mode 100644 node_modules/mime-db/README.md create mode 100644 node_modules/mime-db/db.json create mode 100644 node_modules/mime-db/index.js create mode 100644 node_modules/mime-db/package.json create mode 100644 node_modules/mime-types/HISTORY.md create mode 100644 node_modules/mime-types/LICENSE create mode 100644 node_modules/mime-types/README.md create mode 100644 node_modules/mime-types/index.js create mode 100644 node_modules/mime-types/package.json create mode 100644 node_modules/minimist/.eslintrc create mode 100644 node_modules/minimist/.github/FUNDING.yml create mode 100644 node_modules/minimist/.nycrc create mode 100644 node_modules/minimist/CHANGELOG.md create mode 100644 node_modules/minimist/LICENSE create mode 100644 node_modules/minimist/README.md create mode 100644 node_modules/minimist/example/parse.js create mode 100644 node_modules/minimist/index.js create mode 100644 node_modules/minimist/package.json create mode 100644 node_modules/minimist/test/all_bool.js create mode 100644 node_modules/minimist/test/bool.js create mode 100644 node_modules/minimist/test/dash.js create mode 100644 node_modules/minimist/test/default_bool.js create mode 100644 node_modules/minimist/test/dotted.js create mode 100644 node_modules/minimist/test/kv_short.js create mode 100644 node_modules/minimist/test/long.js create mode 100644 node_modules/minimist/test/num.js create mode 100644 node_modules/minimist/test/parse.js create mode 100644 node_modules/minimist/test/parse_modified.js create mode 100644 node_modules/minimist/test/proto.js create mode 100644 node_modules/minimist/test/short.js create mode 100644 node_modules/minimist/test/stop_early.js create mode 100644 node_modules/minimist/test/unknown.js create mode 100644 node_modules/minimist/test/whitespace.js create mode 100644 node_modules/mkdirp/LICENSE create mode 100644 node_modules/mkdirp/bin/cmd.js create mode 100644 node_modules/mkdirp/bin/usage.txt create mode 100644 node_modules/mkdirp/index.js create mode 100644 node_modules/mkdirp/package.json create mode 100644 node_modules/mkdirp/readme.markdown create mode 100644 node_modules/multer/LICENSE create mode 100644 node_modules/multer/README.md create mode 100644 node_modules/multer/index.js create mode 100644 node_modules/multer/lib/counter.js create mode 100644 node_modules/multer/lib/file-appender.js create mode 100644 node_modules/multer/lib/make-middleware.js create mode 100644 node_modules/multer/lib/multer-error.js create mode 100644 node_modules/multer/lib/remove-uploaded-files.js create mode 100644 node_modules/multer/package.json create mode 100644 node_modules/multer/storage/disk.js create mode 100644 node_modules/multer/storage/memory.js create mode 100644 node_modules/object-assign/index.js create mode 100644 node_modules/object-assign/license create mode 100644 node_modules/object-assign/package.json create mode 100644 node_modules/object-assign/readme.md create mode 100644 node_modules/process-nextick-args/index.js create mode 100644 node_modules/process-nextick-args/license.md create mode 100644 node_modules/process-nextick-args/package.json create mode 100644 node_modules/process-nextick-args/readme.md create mode 100644 node_modules/readable-stream/.travis.yml create mode 100644 node_modules/readable-stream/CONTRIBUTING.md create mode 100644 node_modules/readable-stream/GOVERNANCE.md create mode 100644 node_modules/readable-stream/LICENSE create mode 100644 node_modules/readable-stream/README.md create mode 100644 node_modules/readable-stream/doc/wg-meetings/2015-01-30.md create mode 100644 node_modules/readable-stream/duplex-browser.js create mode 100644 node_modules/readable-stream/duplex.js create mode 100644 node_modules/readable-stream/lib/_stream_duplex.js create mode 100644 node_modules/readable-stream/lib/_stream_passthrough.js create mode 100644 node_modules/readable-stream/lib/_stream_readable.js create mode 100644 node_modules/readable-stream/lib/_stream_transform.js create mode 100644 node_modules/readable-stream/lib/_stream_writable.js create mode 100644 node_modules/readable-stream/lib/internal/streams/BufferList.js create mode 100644 node_modules/readable-stream/lib/internal/streams/destroy.js create mode 100644 node_modules/readable-stream/lib/internal/streams/stream-browser.js create mode 100644 node_modules/readable-stream/lib/internal/streams/stream.js create mode 100644 node_modules/readable-stream/package.json create mode 100644 node_modules/readable-stream/passthrough.js create mode 100644 node_modules/readable-stream/readable-browser.js create mode 100644 node_modules/readable-stream/readable.js create mode 100644 node_modules/readable-stream/transform.js create mode 100644 node_modules/readable-stream/writable-browser.js create mode 100644 node_modules/readable-stream/writable.js create mode 100644 node_modules/safe-buffer/LICENSE create mode 100644 node_modules/safe-buffer/README.md create mode 100644 node_modules/safe-buffer/index.d.ts create mode 100644 node_modules/safe-buffer/index.js create mode 100644 node_modules/safe-buffer/package.json create mode 100644 node_modules/streamsearch/.eslintrc.js create mode 100644 node_modules/streamsearch/.github/workflows/ci.yml create mode 100644 node_modules/streamsearch/.github/workflows/lint.yml create mode 100644 node_modules/streamsearch/LICENSE create mode 100644 node_modules/streamsearch/README.md create mode 100644 node_modules/streamsearch/lib/sbmh.js create mode 100644 node_modules/streamsearch/package.json create mode 100644 node_modules/streamsearch/test/test.js create mode 100644 node_modules/string_decoder/.travis.yml create mode 100644 node_modules/string_decoder/LICENSE create mode 100644 node_modules/string_decoder/README.md create mode 100644 node_modules/string_decoder/lib/string_decoder.js create mode 100644 node_modules/string_decoder/package.json create mode 100644 node_modules/type-is/HISTORY.md create mode 100644 node_modules/type-is/LICENSE create mode 100644 node_modules/type-is/README.md create mode 100644 node_modules/type-is/index.js create mode 100644 node_modules/type-is/package.json create mode 100644 node_modules/typedarray/.travis.yml create mode 100644 node_modules/typedarray/LICENSE create mode 100644 node_modules/typedarray/example/tarray.js create mode 100644 node_modules/typedarray/index.js create mode 100644 node_modules/typedarray/package.json create mode 100644 node_modules/typedarray/readme.markdown create mode 100644 node_modules/typedarray/test/server/undef_globals.js create mode 100644 node_modules/typedarray/test/tarray.js create mode 100644 node_modules/util-deprecate/History.md create mode 100644 node_modules/util-deprecate/LICENSE create mode 100644 node_modules/util-deprecate/README.md create mode 100644 node_modules/util-deprecate/browser.js create mode 100644 node_modules/util-deprecate/node.js create mode 100644 node_modules/util-deprecate/package.json create mode 100644 node_modules/xtend/.jshintrc create mode 100644 node_modules/xtend/LICENSE create mode 100644 node_modules/xtend/README.md create mode 100644 node_modules/xtend/immutable.js create mode 100644 node_modules/xtend/mutable.js create mode 100644 node_modules/xtend/package.json create mode 100644 node_modules/xtend/test.js create mode 100644 package-lock.json create mode 100644 package.json diff --git a/backend/app.js b/backend/app.js index 3b01061..a2f19c4 100644 --- a/backend/app.js +++ b/backend/app.js @@ -20,6 +20,11 @@ const app = express(); app.use(express.json()); app.use(cors()); +// Serve gambar dari folder image_hama +app.use('/image_hama', express.static(path.join(__dirname, 'image_hama'))); +// Serve gambar dari folder image_penyakit +app.use('/image_penyakit', express.static(path.join(__dirname, 'image_penyakit'))); + // Routes app.use("/api/users", userRoutes); app.use("/api/auth", authRoutes); diff --git a/backend/controller/hamaController.js b/backend/controller/hamaController.js index ddf8161..14e3308 100644 --- a/backend/controller/hamaController.js +++ b/backend/controller/hamaController.js @@ -1,10 +1,12 @@ const {Hama} = require('../models'); +const path = require('path'); +const fs = require('fs'); // 🔹 Fungsi untuk mendapatkan semua data hama exports.getAllHama = async (req, res) => { try { const dataHama = await Hama.findAll({ - attributes: ['id', 'nama' , 'deskripsi' , 'penanganan'] + attributes: ['id', 'nama' , 'deskripsi' , 'penanganan', 'foto'] }); res.status(200).json({ message: 'Data hama berhasil diambil', data: dataHama }); } catch (error) { @@ -17,17 +19,36 @@ exports.getHamaById = async (req, res) => { try { const { id } = req.params; const hama = await Hama.findByPk(id); - if (!hama) { - return res.status(404).json({ message: 'Hama tidak ditemukan' }); + + if (!hama || !hama.foto) { + return res.status(404).json({ message: 'Gambar tidak ditemukan' }); } - res.status(200).json({ message: 'Data hama ditemukan', data: hama }); + + console.log('Nama file gambar dari database:', hama.foto); + + // Naik 1 level dari 'controller' ke 'backend' + const imagePath = path.resolve(__dirname, '..', 'image_hama', hama.foto); + console.log('Path absolut file gambar:', imagePath); + + if (!fs.existsSync(imagePath)) { + return res.status(404).json({ message: 'File gambar tidak ditemukan di path tersebut' }); + } + + const ext = path.extname(hama.foto).toLowerCase(); + let contentType = 'image/jpeg'; + + if (ext === '.png') contentType = 'image/png'; + else if (ext === '.gif') contentType = 'image/gif'; + + res.setHeader('Content-Type', contentType); + res.sendFile(imagePath); } catch (error) { - res.status(500).json({ message: 'Gagal mengambil data hama', error }); + console.error('Error saat mengambil gambar:', error.stack); + res.status(500).json({ message: 'Gagal mengambil gambar', error: error.message }); } }; // Pastikan sudah import 'Hama' model dan multer middleware sebelumnya - exports.createHama = async (req, res) => { try { const { nama, deskripsi, penanganan } = req.body; @@ -74,7 +95,13 @@ exports.updateHama = async (req, res) => { return res.status(404).json({ message: 'Hama tidak ditemukan' }); } - await hama.update({ nama, kategori, deskripsi, penanganan }); + // Ambil nama file jika ada file foto yang diunggah + let foto = hama.foto; // default: tetap gunakan yang lama + if (req.file) { + foto = req.file.filename; + } + + await hama.update({ nama, kategori, deskripsi, penanganan, foto }); res.status(200).json({ message: 'Hama berhasil diperbarui', data: hama }); } catch (error) { diff --git a/backend/controller/penyakitController.js b/backend/controller/penyakitController.js index 207c84f..390ecec 100644 --- a/backend/controller/penyakitController.js +++ b/backend/controller/penyakitController.js @@ -1,10 +1,12 @@ const {Penyakit} = require('../models'); +const path = require('path'); +const fs = require('fs'); // 🔹 Fungsi untuk mendapatkan semua data penyakit exports.getAllPenyakit = async (req, res) => { try { const dataPenyakit = await Penyakit.findAll({ - attributes: ['id', 'nama' , 'deskripsi' , 'penanganan'] + attributes: ['id', 'nama' , 'deskripsi' , 'penanganan' , 'foto'] }); res.status(200).json({ message: 'Data penyakit berhasil diambil', data: dataPenyakit }); } catch (error) { @@ -15,21 +17,41 @@ exports.getAllPenyakit = async (req, res) => { // 🔹 Fungsi untuk mendapatkan detail penyakit berdasarkan ID exports.getPenyakitById = async (req, res) => { try { - const { id } = req.params; - const penyakit = await Penyakit.findByPk(id); - if (!penyakit) { - return res.status(404).json({ message: 'Penyakit tidak ditemukan' }); + const { id } = req.params; + const penyakit = await Penyakit.findByPk(id); + + if (!penyakit || !penyakit.foto) { + return res.status(404).json({ message: 'Gambar tidak ditemukan' }); + } + + console.log('Nama file gambar dari database:', penyakit.foto); + + const imagePath = path.resolve(__dirname, '..', 'image_penyakit', penyakit.foto); + console.log('Path absolut file gambar:', imagePath); + + if (!fs.existsSync(imagePath)) { + return res.status(404).json({ message: 'File gambar tidak ditemukan di path tersebut' }); + } + + const ext = path.extname(penyakit.foto).toLowerCase(); + let contentType = 'image/jpeg'; + + if (ext === '.png') contentType = 'image/png'; + else if (ext === '.gif') contentType = 'image/gif'; + + res.setHeader('Content-Type', contentType); + res.sendFile(imagePath); + } catch (error) { + console.error('Error saat mengambil gambar:', error.stack); + res.status(500).json({ message: 'Gagal mengambil gambar', error: error.message }); } - res.status(200).json({ message: 'Data penyakit ditemukan', data: penyakit }); - } catch (error) { - res.status(500).json({ message: 'Gagal mengambil data penyakit', error }); - } }; // 🔹 Fungsi untuk menambahkan penyakit baru (kode otomatis & kategori default) exports.createPenyakit = async (req, res) => { try { const { nama, deskripsi, penanganan } = req.body; + const file = req.file; // Cek kode terakhir const lastPenyakit = await Penyakit.findOne({ order: [['id', 'DESC']] }); @@ -39,12 +61,19 @@ exports.createPenyakit = async (req, res) => { newKode = `P${lastNumber.toString().padStart(2, '0')}`; } + // Cek kalau ada file yang diupload + let fotoPath = ''; + if (file) { + fotoPath = file.filename; + } + const newPenyakit = await Penyakit.create({ kode: newKode, nama, kategori: 'penyakit', // Default kategori deskripsi, penanganan, + foto: fotoPath, }); res.status(201).json({ message: 'Penyakit berhasil ditambahkan', data: newPenyakit }); @@ -64,7 +93,13 @@ exports.updatePenyakit = async (req, res) => { return res.status(404).json({ message: 'Penyakit tidak ditemukan' }); } - await penyakit.update({ nama, kategori, deskripsi, penanganan }); + // Ambil nama file jika ada file foto yang diunggah + let foto = penyakit.foto; // default: tetap gunakan yang lama + if (req.file) { + foto = req.file.filename; + } + + await penyakit.update({ nama, kategori, deskripsi, penanganan, foto }); res.status(200).json({ message: 'Penyakit berhasil diperbarui', data: penyakit }); } catch (error) { diff --git a/backend/image_hama/hama-1746265160009.jpg b/backend/image_hama/hama-1746265160009.jpg new file mode 100644 index 0000000000000000000000000000000000000000..fa920a58f6ad2fd51ae53f849ed817071f6d5752 GIT binary patch literal 6187 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 AFaQ7m literal 0 HcmV?d00001 diff --git a/backend/image_hama/hama-1746270839721.jpg b/backend/image_hama/hama-1746270839721.jpg new file mode 100644 index 0000000000000000000000000000000000000000..30824d047c8f7a0f3efa3ccaddad6e7dee0dc4f4 GIT binary patch literal 66943 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 AFaQ7m literal 0 HcmV?d00001 diff --git a/backend/image_hama/hama-1746297043479.jpg b/backend/image_hama/hama-1746297043479.jpg new file mode 100644 index 0000000000000000000000000000000000000000..fa920a58f6ad2fd51ae53f849ed817071f6d5752 GIT binary patch literal 6187 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 literal 0 HcmV?d00001 diff --git a/backend/image_hama/hama-1746308562954.jpg b/backend/image_hama/hama-1746308562954.jpg new file mode 100644 index 0000000000000000000000000000000000000000..1edb23cd8a4cd9ea09c61b994cc4999b0fa06755 GIT binary patch literal 9811 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 literal 0 HcmV?d00001 diff --git a/backend/image_hama/hama-1746308575664.jpg b/backend/image_hama/hama-1746308575664.jpg new file mode 100644 index 0000000000000000000000000000000000000000..30824d047c8f7a0f3efa3ccaddad6e7dee0dc4f4 GIT binary patch literal 66943 zcmbTdWmFtb^!JGb2o{1&0t88L7$Df-l91r;?mmMLZb6bD!JWZfXMn%}gZ;3<2iHM@ z6Et`TkYw|Jo;|zg?5o|qUEOcGy3hTbI(@t9dvE<+`n!SiR8>($5eF9+2M71xf%A6- z=N%3{9^U`7fA;8KBX~?e@aPc%2@w(DV=@vlGEx#!Qt~HMPsyK9J|QK2`s^tsH4Oj& zAftFr`;3N`iUvUQKQF<>|M$!z0%8IJVj6N%a+?3o_O}Oz@-bc~UMD^-3l1J7EmB;{5`uO_!2LuL1ef;z}Iwm$QH7z|OGb=j>R#IA4UQt<9 zUGuf68QId>hHCHYA3zTd4Ude@%+Ad(EG{i$H@CKTcK7zb9~_=vTwYz@{Ji~j_di^? zIQah?>)-W%gZ)3aDF1QcJ$i)yi0FT~aPj>Ao%ocG2wn*iQoYq7vhkv35sG+BBcD>- z*h|bRta}Eq^`0hq#wN1Ke*Qmb|BLMZ9a!Z5FS7p&?EmJ%;*jCv{u?}eN*p!0HJWa9{|SwXZP2`4mE6mYF8q-_qaw4c$(HrO^HS9Ag$M-Itd=Co*&t*?r--tADt z|HUEE({^znQDG~%z8H$>W-?z{{mQEmzxAK?!@EsVH$x6G)bLe3*TNrSGjmO|ksk+d zpF8yc&#)USO@SJ)vE0qI@0`CGF=3H!&*X8+XvOdJqc$u`3#@!k|M$qD6F)>9c=|#s;+b;KFXr15AFM~;3n^ret@5oYT zM8_6U^sIV!3f?v#fAi>#v6qReLdouW`sZ)rj55G+X&dLpELy;EdpNOcSjKj@c$+~+ zZK|&02{wJm3K?ZcqUY+L#7_Ze`T~6+1 z1P|9SrXzFbHv}Q6f`2*>g!A=mf)Ux~Ls9yv6NeI63DM%m5M*M&gGt46&qqTEMMjJq z8LYB1;vk4pSMK|{l%V&L3$Yxa>IsOc#Y9%MG80Qf(w4~Wx>AB4w=D9Yo3$}&AV}O< z@gOnSD8ELn9K+@v|0J@+klK$nW$+m1LvAtP@Eb{|tb1i_q8IGq3n@3VPCVBpw!7(; zeIqkRRVW-zbds8o} z&&t_2#KnmkTz-&KVWo&l71qUUyl;z5M*wF}_-x~FDjU#}Kxa*;QbU{>J(uFp#2=Yk zzZO|KljXMZn!$n-?Y<4M4E>i{D^U5K!E+XQ4V1h=?XF6JZc{(mG>`uD!D7>cJ-fuH&|-+aG$kHt4vm9i$58V-B%T>c(i`&L{nuXreF;9b;e$>TDCTLYzPc*a;u z41LK-igUOjR?ag{*==dqCZlDMi9L%y13LDbP@8e{UD~494F<^Xf2+TF?m{Hvl6lv1 zY#(8sk3Pb@+7EY$t&+CTQf*tv%Z!f}ZILPtKuBI{k9ZxJH`9+yuGgM}RePZCG|l1` zS2c8TeJZ6xfu9zx&{19te?Z@c{VO0@i|{elr$9QkR$=f^)}gLgwxZOW==0YQfn?~3 zsbqdNRRK@TN4R|rT820tn~_*E`h86yk9@$;>L#cKjxR$ zxBk5QW((4DerVY2`?RQ`e7gYo4*QrL(rz%W*YtVme-RAEd8?K4JKF0V|+WyKR zi#H*)OEcK&OD5V!n(7;HcL@=Z$MK;_Uc`lrtMfeH3whg;6!??D3~ zzF8hTm#e;^vP`SG^l=$f68SB=5;Say;(r4``U_e^*n9Xeo(t~#)hdTh$eg?ju6w#x zi>?G$vp_pI-yEqRfr6LC>a}aSyPblo*beNlSq<6BBWC!*G&umk`?f?{r&bs_sWI!j zAxgHeP+CQ7JtCtJs8N2EG8`?w*G_s8!q+19f!udGv64?2NY~BLZ9MjL9<8#)%&&`E z6=G7KYED=$n`@M^FD_AEQd}Ypu4bJ(e$a}P0Qmo*O7gPwEoxqz0UTKn-RKu&98?5C zk&|m!u9CW8u97$$u98F+R-;&?Wa*lC6fp3r93JH58LcQ?-G;oMa7Mo5VW zDhkzfGQFYbv`!o2Q6YqaDB(g0p2rB0t=e zordLTtt1CFf)ba|b$&rC9eA|3D%g0a)Fg6;v%Q9mEDiq|u9K@AtyA(h-b0@!A zJj#+al+<76ntC-?LiLt;JdhJG&y2GgS~{kj%i0E5KKPHE|5K4uLY`d(HsmL@Q~HD0 znKX8=mPyZ3X-M_HW78-yOJio%C!mM1cC4EufCG#D^Va(dzQ=~jAmp%1N-c44_z^xW zBadYT8Qi6UML-I?b3k7)#hoOI{uQ*Kwl+KqzN#Q){*%}qaz!I zrekcxpVVT@$N%EcJqvx}FsWlHe0I3ge~vS#^+ql~SSfDUjYr3la>E7pw(5MJfnpC* zpe+vx``lqR^O>h4nU%Q4$h z*i$K<=*v z?9wdQB;TYcJ_m764exs#SYjm4Q$fnLu3|l?qqvFrlh)#Gq#JwF;EEpu1sGoS#*aA* z>11Y5oR_jkk1YH_!C?5#lp5{Zj&7u4PXA+&=Hb?D{kWcP(EO*wN%gZ_=H(R`a<^rc ziTiBt&IN7xEZ#P8fHxM3Z~Yes;8)EknDSjx`Oo6iX&EaAdr!S|I&%y35Hcf8qvHb{ zSfy=~HX^7s%XL$OkauZ`v zqNrbqx>6)zwd30!a;lrDg0U%qOuvL&@N5MLOj4c%(t@5d;4aE=DK`Zxg!8x$f!jEr zC?E64$Pt5~5f}47ZZ#DS=t9dET#sA^lEHjZ6reWjLl6(_lS@z>c0<&k68rLp1#@`G zCB2<_CR{+Dgd%Ky`9Vxx*Y@p>QTfN)q<7?~snr2fRDvIs|7$yo(jvA+xjJlS_NEHp zhGwxnRED#7KP!_nEpGouN(o2TlZ%0q`wCR3X^-`}xj~8FY-S~$Xvs8nq=JT19o0uc zwpm|NJmX}ko06r05J1qY3I&XQb>GKnc9fzHm0z2r+*zJzqkjeKWU+0x=BvX{XE~NXREv$wIIN1?GADSn)sPe%~Dq@@~A>oW%(M|4A**^ z;Zm|0vF#`c?zR4T{+VG5#Qe$L{2-+Vg{@oxtlp;DP`cWpzBsSwKKu0B(?tBnx7(EWP z>-RJl(8Jrf*LmIFM!Ug@1a|iT#g1ZP%9;EeK_DD-YpAy5I^{-^HEO$uceX z(-MlveR4N0DaA#*lF{DQzJ>O8b3VvHAS)VbT7EHRHp?s0{^N?cH=+%_Tiu@L?)m;3Ni^tC`xnQJspC+za}MX!_%|mem)+_+MT~IcFy&Gyr%s( zJK5U!L0CMT3kzdIQ8;cGh2N@f{Ke^8&v@I5^Fi2hx_W%p?Cag)#{h-V2M?I=EdDPT zrjhwQ@X5n$flVt;T&>`WHrvM*AV=gP)fR<#0WKm~Z&r1}M3KNx^gQ~IyZv)PJ*BG= z$Qb(4t8rj3q>dz0#h=sEvGjQwcTdgvu<~Bx$3T^rFnN21pVbROZ$P9&-8K=X3pcr0 zBKrP@czK@B#iir+9}LRaGIwGI15YeEMYG-0F?y=hD=G1dU(B8y;$BWH&hqbYzNEI` z|5Ce|#d%peB>Ad2!S1#dprMD)bZSuC9bDq~Q)i$$y!4{(#fr`=aRAR@i7%1AX%i{$nB{K*ULZbK8ngCQ-_nRs7)U+pG}qkWr7 z;8maz@I&oZ8%=yEpT;*F@I8N~Xh^lI^+MJ_?koN>j#xj$`utuu-8}uGLA(=wXz;UY zszaEt)_jA+1J9{1MN$S&+~f-6zUDun z^5Qc@tPiu>`??KY>->Op1dZ8=I2}D{oOshy*Ci^QXtqns%8l%g zhNEwKS1ciWz+=6o#oAj<0X~|)I8Bz_6wv=DP_;s*U?d>k+k|SA_!@0kU>{Fikb!$n zrfrsNOmc6^b1s`m@lMRBLH}J3(SbP!+*ORyLbP=&W|=k`(0eHRQYrbNA8w<|ah6%; zr1@lxfZ5IrnoxRCj+_T1>F7-gv|0BPqVhy!lqc8dlV36W7zBUe>)9~4C9p^YB$F)U z3s05)U~m}BdSw@`!S+l&{B&~L;=d)zXw{LqB4hm&%Y1RhnQ|X5)3qAqcszf zGP%siE8tPQg2So_jW?fQ~khY$Y1HIq1>rLf$FP`$qNiV+lla|nJk_)b? zI4$L95hv2oSo@)P%Dv=AS#BU+AsyyqnL)bJp^_X>S4ef+@~z+Rc=o9>Bac}_i^wTw zYm@o#8+71)T-fV_Ad#6jsapm=>v?V?&WnH8r|_0JUs|g8R;_~5aw@!!*Hr2_`G;gw zjISx4g#^#-thZ3dGf6IHLYPF)pR~Rb9wtdUbbeOz8+E6DzC?Wxx8EUwiYIIllRHx3 z2cs)LFRzMX5BRRW_eYKr*Zwj5hC)BL8JBxgaHN2pZdo-l{EymKnU#3&j1ZWX6mUPp zQdbbswgDnei?P0zgV zPNK2p))Lj7?3gw!ThiI$oo%CZO4h(j> z)xGrH-POY{(XJ&JUMO~1NZ1y2ov0;@*IxSt%6(#BJNp+WbO!#)Sc7F`*I^j(7w7$K z%ctQQQ>LW+iuR*mLHF?avS+W&SkTWxfl~VIwokCBTfYoNbru>cGV(6K8IgVOyu@+} z<{LerFLlyP78>n$F10$^pB<kg~TaSNg97F4Lc+N=v% zn}y=9lT>p|KR5SU+_rwNMMCcui(DiJj9MsHKaVL(?p2%@WI+%Ir}P4|c@Kk~1^xP{ zv&Yx&24*~4G zl7bQqjYS;G<EQb_nfs-_|SBG&#xRt8b(V>R@VS8w3Dh2nsWkjgG;UXt& z>op&}StJp|B1o}d(C!%iutx-#F*U`d-Ht0QU!t9}kHaP^d=h}9C~8D#Oey}!aIm9N zm_-`2B`fd5H9`b>XfZ(|)#y_q%I(P~pa-)QXtPs!!0s_$<(ivKBIHmzS*EPde$_;o zt{(3;sU$aeAq9st6|x02X9g*;g%7p1nXtfR1RQG9W=|8RPJnHiz ziHS1G=ueZX%d^P5?L7pOS){!<2-H%3naptRwZ_l2Nb0BZ z-bQKO%WUV_wyilbDJ^ta4cvL58X}F-hV0C;hgP}#)O?v>{%;XrnyNLOkwLz65)yAglw;kQeZIgut$*|bpJ9C(KbSz0PPlx0Prb4AA{wZoBfV^wb1qJ8oQ z(b9ZcoPlmPb?WwNt7+C1{q%gu?c z^417+@fQdEBSvvw=fcii=xuL(;Q3+6E_nxY`K-I;QGpCFisC%2Kvu^ zyV2Q{*y_^~0%vJ_0mGCAdE`{|Hkl?shP38sr{3zjN24qs&sU);A_|pN#?|$62#{3O` zudXocOT>OR$@zz6{+;G_IW8%q&>&Y@0sH)w!M9iS?B_qC_8U==r^{x$);)T>gDleMV3MmRS;iFm9RRAi+hpvDBmA8H8>&mi=tKdxyOjT8)F6L92=-z(fA?)G?nKj$1ZoY3W^j~wyWd@huYx_(&yucGTM8Rk(mul4{Att$)@EUzvM#vXTE!ngy%C=Dx8@}sc{CR zgVp3BAXnJ6Hc&46wq${C-kdw-&M?1*055el1Eo71+nEA}z+az`b$Y>aHJUG9uWrMA zKG+$MT4_Zl4xto+s|Fg*zSD{W{&Oj*QJ8Xsv&1YGRTsAnqeFc`rv>RGAC`NyR0CR; zvGs(6Sy_r0RY8FZ zE%Kv-?Jn-2d)ha2F0!;Z5S`6+)D6OvG(U|3?kn1gr*EmGdVNq>{BD!W+?Yy}z)8#5 zZW6FWs>4EUl67MSMf>K>8c7s$CaaX+TeX$yeH@B6^ar60pF2gw^aZP|5B+c!uhb9D zy6?X%ztsQm^x-ISf2%4?P-k>WPbsf(ojKp`Hfi7)mM=bae8WLgAhecl?2Y?S?4M~S z7RX~81y{n2ODmDwjhW`29sD#4=0Pn6tu01_?MpGm`fy=OpBdE{{m_LYOAC`c5SuS0 zSkBdR)ElTM^|gi&lK9o-RTK~nHd||a+qPSq2mpz%A|?{0>I#rHY<$L)=KEb%pP^`< zd$j~%9m%*{UnjO7M}~IkjSTbE^c=>k&TE=)%6zKU!fSPTc4IuGKfQ!SZvDma3#@@Q zI&PO!Ds(2yA*3SgxU07sza@&-KD#-OnrUXGtU2RqGp}oR#}O|V_@zHm&-N%hphn1I z9JBBxZZ8Wf-F>y2XP(~$k$pMC1U`QHOQX%xx$RVAm*7N$(*v9?2AAd$GgVlvaYCDX z*3~V0+NY!%vBZ{bNgx%soAKznxX^)8B?ZG9!|*SFlCGaA!DWfPB|xTXwh}|CF{5@R z+nn8bRdsXOO|XzfCMQcU4q=$7Mnw63piW`AQ*(i9`pHhbL{;9SU5IFpS?-7AnS-11 zZ{-G%Xvbb@!-?$gwP}k0^6-`4OnizH%K2~El^Ki@EL~;;NCyN(N#uXD?RAxdxE2f_ zU1Cv5jNh48GQCwdq-q|J>H0;dz3PiIzUTEcs86kLa2~~Nc8io4JO2pEv%g08_tC?D zMxSP)3n{6G;eT;r1=Q*TT>E4lMnFrAJ|##|ji!<_rsA8zC)gGxpqckDUL%5Z)4eRN z&<6Ua+AHqJz1|={BgnC#I__gPl>_ie+yk!%Mn6-l*_(TW@YV{inrDomLrHbNY$ank zN#=@wS=~Ap?sL|Ph5rsfn2MiAu0g72=t4S!jqIG2*x^GvtGI{Y7xozUfVgP~sqh^` zg0CYlLe_x!4WMF+BHa<$UL7ULlbsnU-i)+lBCP+Ji!GFLNL9I*jwwTC4qT;t6qkc= zj=%);t7^zkC=F@^7`8IsxNYMI#@%t*ZmPSD!Y-L(WErAFJ@t(Ehpt+w9CL&d&$zJ3BEQMu47UUy#wh=dw4qy0i<*l^n`52Y zJ#6%Os#0G8ci-+Vs~1K!s6=m`(J=zYpDz*FD*eU{khN*)RgewuC=uhAft)l5{?wCA zjQgoQ(8^zCTM@1G(h0!Qp!}5rj95G^tp{A|Z=V*#t5~j9V-zt>aNR;f;0SoQ^PI*j z3KhMtyHGCbf!te##!dY9-*^-uDmRJNta$f7)BS4?dPV!+D}RtvEgY})5OeM-3AGW6 z_P?;zlUvK>Y{WdJYcfk?42v3klij0CXf2Ug;U8x0ibW>r)aKU%4rVO%&L3kHLm^8Q zVM$t>GOsmRlp~JR=Zaov(X6qbKdr7e&Um?KlAbbjn0X(qin(V-dK_{I>Wj{`t)^$` zo%Y_FoyyoxZtGHD;pO;D9y^fT4zB!FPY7b>s#Jja^?~KP*oUp)j<7lJ0;Swv^yqVn zq1-}h?61cc{6wCRsHpzlbO%Kqy%KoCur@HWWNJc7K05E`PxT8ywwA!^dc0~J+?lF% z^Di7SxHS!jR?Z>R=m6yO-h7HTWcm>ig1hCe;F@9f^(*T4Z{#xU@!VEyIO8t$n#8yH zG+S1*CxqU@Yt6#@+P;{jQkY_qT|Eq!rqwShd>h9F_4;3TL8q)v#W61LWWBKMtgP=b z&4sKpC7D509DQ@%}lVnGZ~ap6FFoC@Ou%QS^QDuf3&`v|{Mq&Lj!zDHJte!WU-IJwcV?8J%@+_o?b(qLyas;T zfb8pPhi7~3WMgqTWj3cup38l4s{R?t)ZIH&=6h++)qyE_a9<=uaJk))X*~ z>5U^NqyOSKwTZo~`Sg=!+WM=@J!8gy^vBY{mFwm4V+@*F4fT?Ss4eYRE2D>NORzp8IqVLRt%b8oA1b*~7A z!iLBb@|e5F!8Y_tmVEOz)`GVNzF%K>D{9ycA+lrEX4jkbj8wMPALDh`lV{7SPfe*Hvlhwhc;S@5!isRXCOm{nix}hTS7l^6PeWNMZ$yuZ_oO;viBvFYD}C z5!fx?f0+YOZ?63|D$>ZwaESc!3p$Rku54u694!NJDUsZk8(K_+1WLiLhrwShKHCbb z7gz~{%C8#8n?14(tzvz814+*dkj2DH>+@_gyu(U>JzMVTm$IP?yr%1abRq@7apta` zz^M?ulCT>Sylc>YmNsqWkS1i?EB(!EYnmU=)f4&Nk!nzRee@Eh+0IfDi5|g zv)5z&LI8=%IhReYZTw^qB;|?fqRE*TMZ5%oT1m}Hv%V7Ub2fBMmY5)PJuMfu zdmbI4)oPZ{4S2IIoC^F_tF$JZ+PH(Q<%e7M$&$PV?M=(;o}KRR$v|3KyXkG(;@GtC@J{k(e7c@_?DZ+Wwo?~f?5 z$z%v#WUdW}aOIe_c?&=U%zHRlWAP~ z{H$l23$fx9xBImy7`)|DnO4IYPoAm z>1buy=P=lem_#ptEYt}2dpu@~9_>pSLM5?HoXjrVFBkmg)afbyDgJzmT2 zlN(=xnNM_leht?U;&TF`n-_Y_&ANUc4!BOvf;35m${dkywm>XW-TzUfBC}up%G+=; zPq4BXUIi(t4W5cpxaYF%*+Xtd3W$T!T%NNaPi&+eTl8nSSriEfl|;Tf_{j;>a*#Ks z#m{?sy)Q~BF{M^cX@@Q}AYb4NoY27yL3B9Rh+vDHQA3FrwW6(IirnGJKqvEey*g++ z#dn@TGcXl%uaJR-LzAycfLWJ|UI=|>{PP)18R_MLC$EV|rkh|Zwjl=E(F4~Zmajw( zs>=Lf6_qX@LoT>E$<}}(NB*HsK@F^&PbTE-bMS63A# zkntv-v|dnS?O^+-T!Edf?$HEIUua^vtloI$4qBD~O!|A83^p?tWX zd1oKkRLj+5yI{xKvJR2wT4_VL&+I9i0oPls=gccXNN!c|2?@x4>^Xm9)C9S{$`})m zRPLwb{K4|308Jt1>Sxnma~=5n=K0;i)P=Xp135a%Fl0N~!*umtqOoIkQRc0&&5c4- z&!vsVx4FfpmMsc&9`m&QP#wpASS3MKn}sIIQvhHyb)AA+-SXw!il(`Wg~E+xoAk@Z z4D%^RmxaBHVocqi@R;|IDf=<%A|=PnceHpWvN`d2HRaP&V2gEKEo3 zEGJg(&&1Y{_!mblRFTOZ@#u+|+_(DtJXh<{ueFLrt#mcV{!Bc<9Z7Pe1sPN?hY^Oy z@16l%ueO?5#4W+J=_CzLUFB43+%M$S^u|L%>wdYO*ZRoRMn>s7`LAlY-yZ#u*SlPA zTl%no3@eT(%iwA{{C@BVtR@~tjCRt*$qKsE$~~R?=Ssbo26jkGpdeUG2lLA9w~N5P zTs9=OBRU;EHv_o%^L@B{MVIUMa>WgeUA1h!x1lv~>On?I5*`Sz8t7Rpx&^xu(7|0g zgpa*PR}+knbyU+9j zFedRRk-!~o&GB{+YDuDWImEpiw3qi6N0h*;HAw%f@*4)D4zMKyRrN0j!2<_i1PDVZ zy_0xPDTx+2ohsEO9w2UIde$coz*i_Qe$e>w|U{F-ZDo) ziyEQfw)M_;%JL{W1dI`+C~JCFqd4@OJ<*$yrfo-eq$HgY*zkE?{|SMtn!eZ1w{^PA z+bRO5=(%dCUlr4J37fO~GU( z7eZn$eYcRiZn%%*M-1zZ{Z(e;8Xep^BTUatANPn?>0jf8Hw=MO+*%gGW2kj`89afd z%Mk@EEX{mwBj$5oASb4x3oCYX{}jJzO0)h*LZIo2a}P+JOGiV@j9lc{R1BXxKFPF_ z_ibR^_1syYv>wNNMi$&?;ENdrDo+u@@21Zg6nS$$K0$7?rjE&@HU?E=M15|WDvD{s zFCZ*8=ndPknQ9kol&MOjCNZLdo29$i9oGYHXV9%PySmF{J%Vgsw5wcWZ^`@^WJHI+ zf?D>>BaBWwj1OYUZt7!(Ftr6}&VLGLo9W@37Ys8))k4WDZA{k_;GMzUx$CRCF$;?b zuAR#xD^4Y*ZQVuL&vGPEqi&Tt+BB zoAMoaD$usebvQV^_`7vHV`PMxW?Zp;<27D$`xVyZ-MH0ibc# zGkbG()>8LI`Zv!S;@uNk?&nnMurA9lwEuwij0WVD*F{oxE!l4aLp^uOp$i|#t_gC5 zw%vjiE4ORe6#wEV84eYJdIFNRK6QFFdzsOAXp@Ir`_u7}r&G;k^~sX9M(KFS=zrR; zf`6d7&0JBvaD%t8oosdb{$aHUZN$n6`UjDG4t;|A5lvz%9J(aCUsS6(WHJWAQXv5%V?g5)k{4q*+Xu#2*njC0P^+?;a%YCL6qCY0 z-oTp0_PEC;!q}>j>c@7yps_VDhP^*r6Nnj*HT2X}!ae7ioXqu3gu*VyZo9{5(!`#0 zkp~mSmuc4+I~`fFZ>xR%`PS|)j$zPNHx$4RO?5r|!yGUUe$ABceV@`nZw0{S0)JT$ zzM7=17o2ss(&{uRv84?%suek^4{+!I99H(Tp1v-a&U}}RUDm^tDyZOesp{fBE>22s zz4Ipd8B1;o#)NuPYo<7E?%a@|cquIXVCb8I0yv5Kymz8p@Fzn4b@%*Iz=qr0KGWy6 z+wOa#!zH*ZsMUc_|IUX1m3X(hxt%blFlXgUgGfMR?x`8qOsV+Q?N(2_1?G)InT_G|> zZ|W7(_eQK?Tg&=XW2&L?QMXiJJeQ!!ZmnkU^H~ARO+375# zo*R`!>50G(*-o%bH$ivB$9Q%9#OpkFW_!&#%-rK@oBEu7r!Lg0W}116rfytxQlVZK#$3PXJvFH2zJ!lijXuGwkK)v;0S{50plR(>P;Z#**hT zbO&G=@SJGvk*;!zqHuflNL4@4`BnAZDlWYWxNyBvWV%N%1#K-C`zn5W9FXH%Pi)W5 z%u>8nWGV@Ss~Cy1eB}ovdc}#A!gxB_d42@R^JKb4+8{W|zCgd4E zzp(4wV-*v+1NewX|0~w@%I5i3U4?d^m`(e-3f@aOyijn=nP(2+RsUB4K7&>zxXgFB zNpkZq%-1&p)&=ljwbh}YWz&=DEGV1jARk2d{;Lmz_$TH9b7;?;g_=DLm70r;)MHua;_4iUk zroCSbQ)k$9hw#_iHKYavv~Z2Q@{FXb#E(U0+0J&y##0BUBNBy;=<%I=ntoO8p1?F{ z6i0xZ5?3{MO{mzRAfH1`ig(;)lX_%v8^h`@!RZt)(zL zRIStql9(|!k7fNt-JSU1jrQLXC>Ixh988{w~x&mpybzGvkXW9WI zA9-SOD)O9u)+sI2c6F?JJGlM^FTJt*mw3P-f>V%-I@gprt}eUAMD8q7Z;|uL?oB}c zn=_+CdW-c>dgnu)M`muG7$m;p>r=0)qTOc%ey{&jz^oxO;`EqQ8Fetz`Jw-gvSWed zWcU&N9dqA|f|G5Ey*)p0b*$=M6k)?nFT|N6IeZ#sIhb3w-p^i2-eaJl=#646_X_&c zFb(8Cg&eKFc+!sIG*;I9c7MY~&|`Khl5i%=`RuabgQ{Dg0UY z)%n_nX!t%6V9PY&XzRVhTB;{xq0_z3x%B0Z5zGo(SLZcS&tiE#$<46aysj3h7=~QA z6*Cc7dl=$U6fMXY8!PF+8?7j;?1Y7EBr5_FYcz<3XRgWFpYQ{k_Ml0sRV~BpI>}1V z16^myH%p>@T^#L9^e)bOw|xH;}5#$4a;b}VSI;=#D-pVGcI#Edq7)q>5q z3wwSmD4Utp6Tg)^H>1SN2=d>kBi^Cf<6Bu@7KkE%OEn?84*$J0)60gdh zo9bqjo(U#EycNPtfx&uydE>((9Wi_z8LT4j7t(*3d=Glo?bgSI6NUD>X$`Xnhbja< zEA(xgEx7&;GdK&Fazgs`DXs5j=O=tYjYcZaI~E!07$B^?9gDw)rpBGbPySb~2jeVW z{uY>wPy#pJL>(EMy7h1o1{?ek4_Yi&wfw$(J*zArOBY}Z<=Lg+I6=^9`w z&|P|2tsz@nG+cyTu5Gl2#WptQB~8TG|3sUn2ggsnf26N#7Nz%o8$U(UkFZ%c$&)Kr zTR#tK^3zaag1~(cMVQNmo0X@1$UcY3Yi#8TtlkzMf?IVYiueA?x~wzH{a!R8RLSsr z(>z0v2k1pV^-l0*>ssx)Lnm<#Frxc5#e*b!QNmK#yrA|R&n!M>ExewpTZX?VD6?{F zMT%_mOu-vlIB=x^Wavx^x+H}g9zGPVGlAQU$4^iztVfpQt|KdoM8RSMIb}ptVaOcl zw#n`U+iwW$nL^M7_Yq0GK|Qx4^pxONh?4)S;h_0h-<-4-)iU27acS`5gpG6o1$^o$1iq(k4(a&=>Vy)lHiIW?OI}IT&2Bxu2;4q05la;? z-1|zl0n6E=G9&I_^tgHWuwHr}#QXbjC*=GSy1d!F2aOSNTf3AN3l6=ekJ^XPc#p z&P&CVs%0j*dmUH_e;E{=w9J^WM~mY&f zbkU`UratPaVGCzA0G)m#YAzI-CTrV-P6B2bsFZk@XAVU17FOM9#jENO?@dm0!R>J? zqto>`42I-u`?-a=*Y%D`d|VfW60y}TOWB7}eqshO?wM~fP^e1tzGu}3WM_d`7_zk` zPQ0s067{<8jGS^kY8^#!B6N~G?>BLteO+T5lX@iV{%6}qBW|~%aO~gyM(~!#G;A<& zZr$?n5ZeuWQ&-53A7GZU{vb20fEmd{yd&G|P~Hl58UBu{mS!y{7gmf^4;k>Q_KnqD z7Y!q#tCttpF_PN3;yDT`^IO>GBm(TLzgigoNoLS(nuRXlbjluNxI}xlcO&>t?0LQC z`(p+`#X}M-3nd4<4oRkct(|6pD};@?7Qq?VI+yd(!n|atrDV-6YoxANeOag?zeEp0 z^>A)w*q_YEVRM8vpn@VkVjcB^I-w4&zkOiwPsaY5F81`7F(|ECF;xPHc9I#0SyO)C z>C8G#xTH2uv!L{Hve_8v9+HaQkOLHNL0JD)!}MQ3{)w(8oL^j6_ZVwuGX-l7ECcBT zD*_R8{|f!)ssE{AH>)*wi`BPb^KS5WDHA}<72)NN&x!)Uzadk9mUqn3i2(Y4M0}N? zeSdhu@6f&1;Ei?Wv@^uw7H_wEDfbb^`_(_uDJ!*+Cf|)d%g4*qBBqd3XvM=FHVae_oZ^W&vpD{c%uxmJ9a(oh(8#GG zRt%jtM>&Q(;Im&N(*8gnDgSO!KIKl93cih+a}f&^$AEur-_MUb&pN)tKz{t&va6Gy za>NNeT>0^wJlX~un&2&2ALm}i#@=6bMfmlU^yCo2@43=6Qr}-03o|Pm%W{bQ;f}~Y zZSx~qsIqct(hQF^W;UOjR@L8#%Rh}Z$IUZ(bnfeMP9!YM2e0RQVz~PkM{+93q^tFQ z@yRzmo#yc%1i|~G4!D7|fl_DW@k|oBNnt+s5-u*wCZh9+%Q$flCE9HGhgYB6wP@Sd z?>AV-$Tf3fBSe$+kIBrxJqnW^r}Ud)qXiX7Un{H2uNPO1oV>%hX$RE<(JCJ2o)XL` zv?s^Ba<9l_L(%uS(7>OhO^dFw9A!(xc&#U$UXQ{Z=E6W59qJ!^G0c!0q9@<-nJq*6 zj_)WRy#5aWxIjn0Z35?VpT@BDUx{%dkbobhc$}B_Ou#EK9r>vuj# zS2l-Xto%Tl?6RXW{VSEbzrMVE!tKC4vrRg110jt<(MuC>2^Hhl%qK0FT(Gh}FiB(F z)7PG>niCivwK@=Sn(^m}oSx*iA`rb6r%85>dQzcKDJN_v6%mx1)Lg@j!2azxTrc-? zRgtoND%?t^sTJ0eEl6m%U-{=!q|1!$#ZHjLj#yQDgjH+;F;!zuWJb@V@zuF^|9T(6E$6ji-fA0|g*?}tS5|!`7%Lji%W}TDy)suwhuaG8xS3V5fXyu^w%#d8^u{ zqNhCOuq3(O#;y)TYFN0`9|UhWq_UpIW*=#_Nso46S$8Cwf@_~5&4@oYrFT+{rPR(% z*k{CT0@iIIWwV7LVt;^Fn`sfWR~sY*K0JpV$4H}M| zd1Pr!<+j?{$rRF*V7!$*>s0usNxi@td`dBlfla*lXLYJ0O8|F7>x#{_@IiOFW1-UZ%^n$o{gxd1SEYC+;qRw^vqob~akr&-g_pxUV&_v01-!1FqQ0s4 zP2g+IGf#}&TQp~G31MCIV(M(pDa{-=iS3@nw=)u2xU1bok+@@C*WllvIsMyWvF_~WQgVj5 zTqJXHnp$n~L-RI%wX~Y5#8$Z|pqerc2&*&Av6C%|xWSdmcb3|s$qyr%*6?PWZu~R4wiR=QUSVxh$L<*DPbGK-1Fo?;_;} z>f4;&%&C_gJ6)6^{%+xBT1L_Ee#0V=; zabAsg7u%vF4Cb)q(pejoHJpe?D`}>PWl~0YuTAju%GzYnfH_=OD=n9nGDa(3!qb+N z$Jm@#6!kh{KHA&tmt8T=YgXcBc*qQMPVoh&*)GtJOw~wa^KK^jt4%q$ymr@VO<@Z;za<~i2Llb5u#0!=^^5}bGYKRyypJ(X9N(9!k6te z*$1KbRM)guN6`&;zL-Fgpb(K#s4LRH0+maj%fgFQ%^pg{N?Ebih|#4uW18cXuE2@5 z)ptyqzF+`VX;7_c3D^p48yatr4s+I}ZQit^7jc@pl^a?KkogNq_I0URCR&Y{uUh5j zks>v?0P|27#Y`p{!Kasq_Wfw*kQACG&mAd-DSayI0D4mjH)BtlKtT)nN_@@@KkYgD zy($}%<*?sL9vkk~Zl5-+$;(pYGn3b)U5;2t!x5Usb&*E~sy)nz*ezMQ2rhJAW=3O< zl_Fawy_hQw~^U9Ys-?N(rv1?rkOHbDF0#a5q8v*2*l=T;?+%Y?_zMtvi@v^3KuC zWf)qslqA?R!K68+nJ6%|F{5En@bYToj0;vRzTpA&uVV0ec^Y{bz^@+Ctt682CLeGf zYuL1VxTCQOI^b8IQZ6j?=tjn+{m^@J7(CZC3YWZKYVn^JIb-tEVT(5`Ds4!wTEdyz2>7 z?9Rm;{x#3+<;`Xctgq6w1`Zg24T(xe6-#%FvJDZWrtes z{6F!A-rCM6VvvSkdis0~MNf0et0sGG{{Vxm-*MZV`&S{Xd^)zd-L^dE+Nx<^7GS#C zB$p)n*I!}cC@$1K;8*Kj)a4qQql=T9R(VFL@PQmL5afL;h}J$2ZDzzIeiijKzN2Gx zgzkOnwxgok%%y_oHO%6Oa6UfNeiyt^HrS5?(!A?i@P*uRuvm}EYwnxx1f+1Zl14VL z;<$N!8AEz!K300-oM4INJ~y$^?!UBSiICu$`ZK|?{{Uq8X%&VTRC?FLQKjnXDU1YP zYL8dcqP4bY5bds>RTXhs-cj zxyD@vE>FEen~Z}UD(%)4$j3Cc@h;uWD~-Npa7kQaPSB@3R7-K^$NTerPuWM~A zZds2NtPta%lUaizoOi41A+vNVzyhI?Raad4)&Bt9T_6B+imB*anbdfDUssJ)*jClV z>niRXgIooaEpIr-0=ruWMz#gHHO(eWO#N12vlRaT3L#t6Kka6t)Gf{1?TBNu5 zNTgDG8s?GF6Qc2CLP)&P&J=U_S1+eN;0<+}>ksUicXHUSOGr}4D+IMh(J}Q4efN%i z>JwlXXBE($3gYhJimpNStBv71xZ>Q)#yu03_$%~zJ& zCpf7j$ZKzEiLJWvTL#sL;-hOuHAvMfqam%ZI#p3<7?K5UnNa<9WELWE`3j)iujkjXI|8kQ|TciuRzcTuNg zWY(h|?Ob3jRd{^Eo|UHBdMfb1)pv#0g;IN0Od&b!T8T*}Z0#+;sWOCQjMdmYIdyP; zSf8IkTb3UVudgxtxjFW(wS%iIk-T7W(@crc58x`JT^SBGAIiOM)8Nh2z-~tw^%bcH zz}ZR8*3Z(e8FgnZQMa?X;vX^o1Nl^o1g+5<@~?Zh_#F8eQ~6fKpTQu%d4Edu7F905 zsGm2B8;0N`{#9n*NYo_3Q6}H_Yw75I3&gm}kIJ_!J{4PA#uTCwJ*%!6OuH7SpD$T> ze^GEh)_>nMu@8jx2VRf)SJbwD4eZd2#GjRFN8npD1NTY!S6p&=b~IE^lE;H@fyoX> z(wuxLagX(F{{Vc~)~CUiN9FR<@}{2vSs&m^Pbg|jLHiTrli^E``R4xs_svA!6}Zp( zr~d%HYwLFSe`bGyKPrv90cZWh{Hv;_RcKzt#+8qhZ2Tp7*9oy%26^JUyKfEL+F2H6 zIRd?3#2O8zh82Wpd-SeC{{X~rK4A#F@n4_jIDcm*bwWI+p2{YU=!Pd1w`U*`0&3j$ zD{y?kxXnT>w(*=|yvi`S)T&smcQ(**SeH71PNueQ&}|?daa_Kpg?CiSo}{{k4PN}r zi~+7m;qxvz>06Tm>_I$J7f$mqK<2Ycmtq{u#)r#Z1wj-EET1?B>sm~sFWrkaGYugnkQQVGP-m&J5B$~wf#a=J0s zL4P1(w|p-XXRSCW1ZB3L@T~@d#6y%=O(O6!|8Vsvr z=~VnZ;IVZvpLxQzYtU^pNLnml1Jb%@UI`t%w?`Lue;kDFPtK{ycp^;3cAskMwSNXR z%QuuyK9!3nfo`D$kmOf9aW48CbtzoIkHj~&(_~E82eozjKgAo%bO_SOMRIzUoce1h zWG5%RNnvV|FfD;z-5gz`W^lOjyB@)yd{mdrQq1-0nis?&dlK(&&c1ijtYYI50rak- z(*FSL3O31~p{|9AOv#@@r-~O*Rf}-I_pLi&99FV79W&Ov6G!oBf=sjiX1e6@*O_SQ z8=bw4chaeI2{NaSwG=l*0z3Dw74fE~E!E#O)p(;{Gu#qQc@K`YQ!+@ZYr1mx8P7Rr zbDFlT=j7(FWV!R0jMB)&#mVNg(|4LrYVi54&KE0%&6ZB=R&26}q}iI=yqF|M=~yN+ zGjH^*A_VIQkY^OfLZyEMF_pB zDoW(DDr*laOyah*sD!AiQC}wDa4Fg>o?XBOux{f0ECyo zYcX!=Qjw}X+ez>gA_Q`alUf>wgCc8E6&D~@g_fiY3IG_ZV^UU)l;fv*_AvL*^eM>s z-^Y3v_H50C#d-9boEr9@igBwm5$Rq&u>%u*YwIx4(DbRZHP?!T(zFaU3q@X~td65O z(~4n^uC!UC5azMIrc%)qe4|V;8qxAPQ%n`2Iw|Um;}-_0+(LP+ri0p`p4%g(XpE}F z!Vu=F-^22rD|A}m)m!Udkkty4)YVLg%3~Q$wA74`B z)PdrD0VbqNaO79>@;^GUTAJ}0conN1oM3#yog-S=u1^`QqOL0$3#YN_4NRJI5IC#f z+cqiH>N$KzWT5PNe$BF8gdm?L=-nUT)t;Ri<}4BITS=x{T7Uxd_OGSGWNx3tZ5%sClIY(F?IB3R zCI}vt-Dy4mvA0q3ul@5~DqEuRDn&bbSJG0zQHR8hqe{ke_-fJ>`B8r=hAkc#rJ^=aDm{z~k`aF@sVs4)MPJIUo_f_Qk$_09Wru?3ZOT=7a849b zD+lEDs&_N86N(^|u+D3uH0iE|s3Tf77^RW8sK^5%?rISd;g6uLD$|3a&^6YBzNi zE(HH?iO))*Coi7sZ`w~Kb4DF^9UZD6uFEIZTe;b@cq zDf2s_toAc=Dc@+qNY2sQwN$lYqo2-ZE%U34U*?8KeSn>`{okYwVu zyhCcbeVipanvHH?QarQsRev2%a5Vd3gNp2wZoSjCmWJsa+0loDfuReV!zsN3nv@eiEWr-sXGEo4lm7HD5b7P#`U$H~tX#%gSA<|!XM)_#?% zYZgy1lYrG;=I-UC-7RlCHAqUw6sWnoqgLNpy0jS}fIaI*+r`f*3eAID`f9fFCzmN1 zsLh#}?zS1tB?-e;Wh@-o*)ND&BWoJ#yg73v<-3wkPg?Um3f1Sh5zE-urP|7xHHq87 zD_)%n)^(-E5TjehjaaKN4`rSYD{M{ty^ah9g=a{yslSsflSH2k9QSb3t*|Ris|m` zpm_Hn$;N9TEN7BXRAg3&-AIB9956Vodu9;q`IhH)Q;+i3g%-io(%Gqk~pyZFu zWvfy|Vk=Wy+XTS^wkl-8#t zw^}0?A*eFdmFJfp3GG_i91C%Hjp)_Pc~UD6OxJhd`!C$I%s5VSPEc!8MC^0F7Uwa8 z2K!1&rl!gIuBqpwH6d7JCoNHg0^{)F~vW&r=^~g!`yAOIzl5<2}1H?lq@lg(j&HSW|8-*}EFyoUCT(l%W8gn5_w7Ni5GFH@!!1V$6Hh zhtg*wa3^hZ`$o1UQfTLv&PAU&<29?|23hp@BVm#STY}eXki#aT)GS)yC>;e{aT8Bd zrZG5OQ$pH?RT#%LkECk<0A|%=M?3>wfvM>-2zGPx3iGXFMUzj|mP4Eh`dq%hJeAIb ztbHNjM~3S}jzBpzy4qE~qY9(0N4;>r4RuwMK!!4TuI>q28AJJ3>REOk6;`HFVBZgH zB~W~kRCN!AR#QYmLZITh3k#VXkjzKcv=Jm@oy3auD&`3@Sb>GniL8L zwQ{YZ-RTXr$-wrnrmrJUD2GkoTHy8lCM%b0L^<}a3m29~D?~Mjdmde+>M>bNUPU=Q z>vK`pqQ12&8RQIA?O(zOw%gT}PBUJ6s7Tsu#dq!*$6ENR)Fn<@29-=t5yf)SHr}^BKc|&sx`u#TME;%NyGV$F*ppcT>yK+>1`|UF?to{XUg$$HDQX!4oQy z4QY>xR-b3`($5Fj8sznV8%UBgs|%`==xWkC;e)2PIz3-Qg3>i8qwuV|Kn#EnTIHYO zVY`)JF)<|LIIg2c4I-9b#dNwxnwFd1LBJX2yo2M1ibvX@ zf(H3|9luJNhHV~etm==dUo)}Yf@;mIs}um5#ZQ=?nTPZvf>snemjn%dmoupkj>U5Gxy+&%Rk%JvsafKgBfQ*X$8!uK! z_b8%^M@(g_h9T0UWUqc(9k;S6l=Pt!0;e(17P#WCA``nJ*gYuQ?Ne>~QMc(`Rz=wm z?e?ZR)t$8)rYYG4kwSE;GOswTDU&_wl&SMkpzMr2Zalzvt}|Wss=i7Ut@;LV7EHsg*;=! z-vQx{HQt!|iuA1);e~~bx*Or&TBRRNY7eigcYPFm`-X~*5V#J)mx5-y;y#gT2A$oG%@btVow!`aRh>6 z@|x~*4u&*)Iymv08ISRW0Jw;^MXfx{a`P1&_-lm(wW(gEVxggilQ^PJx z8Y*W;pxRG+BC(P1ee2hJHQ|9QYFb0c?_5WP>`W0wmBGNTQPF1+Mz!VEf>%b|g`1BJ z#{_u~n&&l39e{vxeJifJzaCi|v0T;M>SZLJ4R|%(gy4+(OI!{{D(Bf_J#kY;1FsdA zs9j43O7bS|WZ;d9NNtxJokd@k;@u%AyI6YH7jJUc3i+;|wPnv0Vz1 zy`vDU1~Z!Fd^)!$#Az`t<@)D}^=}Zsg44+(lDMu;+R=3QLB}K1SDh6FvFy>t#Wi!) zbPpRYnQW?HS0Hy4i{dYcuQZ<}vW|OK9TuiY#_FqeapwJMxerEo%2v?YlgIZi2oPqo zTUwJ)xgKOY4Kl(ku*lCfa!XKWR>lolf#%i5N1>soTC>O-Zbez~9H^EGs}L{H@mhKu zA(2KZ&Y_@*@OI~#(LnL);-%2wJTu{Asa_eRQh{3{y)HXHv#KHtoMQr}(X_R-jj*^i zr3JRoU5Cy(3VeqrwUu4UFj}_%0GtqgE4T2yk#}F0isyE;zcCn-(!0$jXtYfnU0ok6 zZ9JO7e(5b4{{Rs6drc2UC1z0}L&tjgJ6qKsTf4ZDQhr7|b6tnTZy7efs@y=b7L6YZ zgI;}jHk$M2P|cvzU8TD~N6VV&F74wm6(myX{MJnNYaGnv)vY2=^neFE*KQs*k+n0oEH;jDkxo?JgpNn8D%y!51o6PDcJajx zsmZUm!OF=VtvM?h?1$2#WzK5M)54=Wg=Jmpk;j}hdh)5Eq^el3a4C#bbv1=?;t;(J zRc&#lQgg*{y0&*XyL190wJ^e`(xjzvot< z)F(gwx`&GotsOP{p^vR|b0_Z&I^zCA7|v?9+gg{_!|O-r7Kh;o;*=eo>(ca1Dhp`W zD}!9FoqUYr9<|n3%wz)xiuzTCq-5=Sh=41jwVceLM_h)R8Fn4(sj`u65Hb2y zst{WhWQ_##!RuDxnoxQDD!skh%6fIH$rxdrR*IYGS29J2qzA1^UyeFeL-JCG;-+-w z(43Pcc`62w_M~U1nW|$>YjZwfT1?Xm;8PjVQ%%$CY#Wt?mV?Of4wlb6w==Hh{m&WY9~0S6UnI9G>cdIYbt4mokLbG+K-aX5jk-F5&9%9WyG)6tdHB-swrE6S8$*R6s z&1TIC=>a4RR`rxZD@-1>LMXstS5dbeg07uzNu*L7ZTAfJu4}|@CWWKxfNQL|kXzbh z*A~Ry&vD!UO?=-FRlHfWRnHXho|N*t1RN7yYd?Tuzq|%m^UZq?if*Hh;na@!t|L&? zQq{{2F<%jcf_j}1iOcIA5Qj$7n8cfLT(*&?eX{wLhDCIqB=H%%f#U>`RXil9ZwoLS z`qy)+qS`l6HlWjmx+Vqyt-BIcIH;~rZCqB4muO^+`d5L7mD@3tjO+WhQ;gL}klh`< zYTmbQVvvfUlABmWg1&0I)X$i_5v|4#6;kNOH{Ry0TV5pT^Llrz?NOCJ>HMqBwana# z#@7ci@mFl_mNgMbern0L)Hc2wnu`AKY=&hx6|b^#*r_>4`H?$JSbM&r4 z>&1_5!wh=X{fw`70!VZ7b5r$6?#*0mE8VrhDo+QB!`EOI*6wkR_1ML23^12qyQ_%t zuBM_H0*>9Q+`-fKa=SSyD;eJp08TbG=dXUW?)j#nUwS zVUOiI0-RURVxZ>qIBG=Vb?b(oPzt|yrBS)l-f&b5iqp2Y7gKqJ0&9NS#y7@5&3TIr zH15t9DPG9nzp>sv)adl5WA}eb?rv;j2LVMs%Su^Wg|4L=8YNe$%US7-9uC^Nnq$i_ z!2K(-O#}tb+*X#Ap`c*QqOBujrPRjJw3mfc;8whefrDKOK@1S!01a1;#9Y4uv`E=o zsfz-UBKl^xV$#g)k|_BHTAXPM8!00Ow(Kp6{_h_t{Hm>?T68Pv9vL?`V&JB2`|(~& z@z3Hv*^*5f!dyui!5Obo@$Zf8{4L?ht)d}r2SN`u@~)^Q)%9658P4$`?_NG4Rwbg9 zoNp)Zq~+hKr{3BYP6ukd(FoNs)3B{Lbn$T)Oz~b6s%mD=M#2V3#dR=3ULI+yr1^6F z+SH2MWO&6~WZ9YLn#JoycOqwiYR-ppt0wk7^>f5pDc;fn)YddMvY;VzUY!@HqboDA zy763a#uPR<^{iWu7rUucdc}`Ry1J8+NcvX2pN3r|1;^)KONX72+Zouc{kd^3@lzT} z?mOo-(naCu^Y=w+TX=miK2crnriL{toH5d-mz-zts}N}Jk(`gxy%rA-CO+*$eWM82 z0JYD}%+3clCu7ZSw3a=qK1~&g`@fZXEc!v|imo(bPfS#}u(LQZp|A(#s&d%6lgCQ* z)zHaP^3@xi04P7Z{VNGYg_-9cWngN=5im8=UFjk+eoxYrA*Pqm0)jrNNgRcdpM*wL3+AlH!r| zsodx^?L!1{T@|(0oTDPSi#WD0;IMlw>1%y zxAUWYbKR7pIHqX=w(0Q%*o`YP7!fGd^>guPgPex#Gi>kgyr45@I99+|&&G z)u?$);+FdqlPbzt^TkB4XQ{EQZ=_yIBsg)rJ|#P&OU~#t+ohl z-6@XGHNaKQJ*p)I1a7LPL5uRh;%b09bh#Mhqqg6n;(l5fV|F@x;?k+j6I@WAulG1Lx*NsL>8xD4k#x1npX@`C{R>qrVLkA2~HJzi{G&aFV=QY(> z*!i%j=~-2MQ7TQNR^5Kl9BZCMbT$~XK&PD62AnoN-%8h>Z!c)}uOhQ{IAq<-j}vME zZc6Q}<1l+c^3!}nFPVGRqm4YvV72r0w<7GNHOr=U0HsZ#i?Xks*HyQqn}PUF%=JrTT4B}Z(=J%8*5>MD9EnJ)KJwMQg&u-ufs_-J)}$T zTY9LI!}@$tEwPZG#z3x{!dk2kBCgl;(%lE*pLdV5QNXrW7Y`j+(3-gNBG@y#* zi6@Bs7EORFk&u?`3l3e(XKSv#sh5zyG=g*THDK$8TYPVtp{41K8<*a5P@H1Y<8xzm*9P@%_Lk++}0a1@ylHnF|2m-NF zR7oS?jMd#T`AQwezLK12wR4qIN!*q6;Qs)_Ro_n${{RkaY8m5jFhxqy#^SVbiFP@^ zFhJISRaLH%VvdG@tV$!0!Vo8SjwDO?27YvZaa`j3Nu*uo)HL625Ymo zkRCf#)w7P^b6Gku9g*cz=ms?Ho}PqBzt`a4D&q#s)D zEF<{^a5@~Hy&qcd>|6I@zS2j~M{_|KBUS5Z_Z$OQvQ2_{t2%teq$4%YBdHw(a@F`H z#w#8+ITd~f=~WUWYsW4{Sx4YjY)!>oiM~qQ!>KWB(|3Joy=km*K*wjEw2XMBgUus! zpawap8LC&P7~s$YOH)|-&`v3=awxunOk)~jHUXt`ieq#+u6b%~mCDU8%xOIT0D6Jn z=RaCa*!8S-HcG{hH;%NcFFv(berd}5)jLU8yQEI?b*j?IidTPnuOsk%E1p!8btTk@ zk0n`xX$kg}$P78tf)&{ZhCr!|IMJtW#=+(tj5gL1#%Cu41`7I&&so`RX{KpmZ z=ZZgRD-AbrNk}98s`D*Z_Np?-%N4^9#=QAv(1%l<;u1d4lT)*e2xq{od%qazdSNVO z!wUGef7&Yk@DP_rpRHqBd`Z^4ORq;}ml@yNn)5L^vZ$UMuy4JK;t8WO} zYZdKfaCUU980^_Cl)f`sN%IvL=xS+l`LdQh>fWny<+5{BG;SneN#Y3#$eh<5Sm<+p&VwucMyrN+q-6NYc^a%T?0z8@CRCKejj_c&fY7? zbLD!4j!Nd{>G+6drm*n#rKtfUMo2yD(Dd&OTH63-O!uv4xw5i|q${@`^xi7U(L_t# z)aQIX;pWr!yQx+cy>qSoodRy)aafny%$v^Ez^q+ERUTO^a&cHjQKEw-YsiV!CZhVl5Z3~p)di?4SfzD3szC)dmQy=eGaz!PVGm6w8XT@|;5Cclva#!h`J zzn8##OO?rU% zP=znV0 zy5Gb0tgdk=^7WYnRle+3u3$2beJAd@>6xKJ_~Cy z+iN*sn&?A?k0QLY+Q(?*RT*P=&l#;PTT+TUnH75$PWgj zv+*lhI2*YYq^Zfo(lS1pO-kDQa=?>Hx@t)IPHV(;e;Yc0e9o2F=)VwgaG~EL>s~EN za!lPl4^_3Y^5MgZ$IpEx{`I_f${8W-o$j_0Sb*_pIP-as(`I;k(tXX4P*k%OKAgt`yw9n;}9-=Dv!Bchu&4o}po?lYyGrgHXupUQ?&* z_#khs>wgh&K5F#UYm#~ay9G}*Mp@N!T)Oy&Bn{PQ-eE^Dr#vAWikmb0{v znz=zCe)T*~%11TOwF{l~uBa|}CaihuTqIhdv-yd@H7EQepm#K$;d`G)Xt$@$9QUt5 z({F!tIj<(sB_>hzuR+tLK3L?})X4fMofBOGYQ?S3%mCuBOs75S-Grn`&2z}=TOFpH z+rY`qYbXO1gQiWD6}cPa3bag%w!?K=1db}bsOGFN!L5t~aiqzjiU2Zb$)cK0XaH`s z#=)g06w+}(3A9u)@l%d!3HnxTlD8^KNY6C`xjpJ>YP)g|y<;b1sc1trSUtM-D^ z^i_Dk5;sh#&3xnHzlz$Ih<9w7R0q)40X5Cd%%!H8+b2IUuMVbg1SgQ)iI6V7MVlkW(UOjwssB_B`Aug__x;d4%XV$2- zoJ5jPRl=0gxUUkVr8j$=?!1Vig%E-UE7ZOT=+WQ!w$aRa=da^lVpR6)Uq*h;dIkg+ z?z{uk*DR*udYCKJXR~;tQfmz(?4`eUxx)6Z7rMEBwo1&|Ry?$Ndh++?Ou5~ z`$SO@_q*3bVCIjv!&7MzHe*wajn&I)Nh0demTk-FU4_hJ$Wz=3_3n9D*sg7lQH@6`e()$tdb_*O!D%#az;5mhNE~$Ue1A?6=>A&j!6G z!+sQsJ2>u30#9o2F%Tz|mrKy|8Z!*~*HCQYwS`DQoKxbq5k#2B6>$(;MnU5Oyb90` zQ;pU&L2q)S7!}TH`nLHF(~8#dow&^vmvdYZcjzFv_)4MX9E{P?&l)1|sRa@)4 z#6djbqn}fX5y=^>OYK%U2?51=l;}3rhd(8Z9dh5!W<5n<*+#2&k)jgB_7&5}W1oWH z=Bwz79jA&dvZ6)rpTfSQ44Yi6hZCT%X(6;&e5McUiuCO-Py0TIlNiqOr<_)OiLt%Z z_P>`Bn&~`8;;RiSNsbG4j%Q!tTKa4~1!QFDo^1}1srZLgaU;2nAAm;~uUqh^g`=~D zT1dw5I@3NB-lo3SO)Z+kv=Rvz&2_g|DQzyr0~xQNr9mV#)K!Qs?86>LYM8KfAjWZ8 z4Lc@&+yP)Z*FAr5ov~7BH{gaDsH-a@ zk;j9Xn*R-Q1B}-#pu{b)48Q=r&2<`Sd&!kzQ?XAZdRL1|w61a9$492B z-%P(Q#Z!V$O0VLdh!#E&pX^)FoQ&i(Vf;n$?U#V`X;EZDaM9!r`LB-rPw^iA0L1#6 zT{Fm>p7r!NdbixtH7tESs`$>|U67r?=iZ{WvROt@fDfUsoAe(U&2H^oy*dj4xqPu1Nm2@N>E7YQ$ohb zkBoC!o+P)D=Fx(-D`|A{?YI+J`qWJf4Z$?$C^HGj@P88NlG#q~&yY=WH?lgO4SE-i z3rxfXJ!^x!w06$~SDR8XXAI^J>lBx&4e#{bZE9Bb z`lJR(lmm{{<#wwi2ZC#{@Yc8?j71}7KD5$N*v7^#r3J-|#D+U+&Yi3h04>O_zf;p< z)a@CQ89tTf`lg>gn{dPiz%|ne5YEOePXrQ0Y(cC72hCn*Z>u-mV}tKk?e$j3`Hgo% zW-XqbZ>g&h&TCHI^-ejjH?h>&h~(EpWqwElHQO47PDf9DFp?kvsykcOtF7k)CY|=x zs&x$`BkAoDW%CC-=Di0^okU}v)#W-Qt|b{g>%P;jSVk-CBz-zMLpa^Rs}{3@!yMN? z_L5tzU(%hVmle*=rLo;|$rD)n)ocGjlbVr%#Y9dj>@f+&G@MXd)}!+VX%5VlPC=-p zsiIt=J%v@0buYAa_pIAUo22zC%5zoQrCPaPDlO;k*9qcZiP}Da4Bu&+W2&5TI^wu0 z*MxUvIz-owLCOC7z+v*UqY8}SSFjGq2`m@MvhE=rDT zz_tBy_g}g)Trprt?_WEJ#KG*(GQDKhjPyT<9~|{>6Wqv=l4ET0tzK~@)Yp&ZTss1L z6PmAWw%>u)r#PH}Uq4!>v{pPR$;p~Fa!Y_CP{ZD@8&;eTHFnj#0?R5eIIVlVJnYUZ z#g!zwncCZ&{8p(6`CBHNFx>pr#<%jDr8Df0aQ^@(&2mc1VQ5h)Okr9lOqd*FrA9RA zSdq4|Zck)Vj9C_$Y4)qw;sYg(eQof^PhSss6LA|{bNW}!J`K{OJ}0t`*M@5Pqf?0K zz8Yvp18?-MN{U>{==yxFmDYy?;(Px9?Md8cCbKM)0EXiOih=Gti`J4}BQ+!HFgpI} zvzqsC>O8uiZ$>I8ZQcIBR30lG;}0-#$rSBE;T0K)v@Y}&OLQcd zJ~ThVIHFBb618aE8b? zzpZo@?ui&3@y%mvvPS4Y>s-||GJBXmZ35&!ab0DXjitA=SVk}_&%R-H0qI?qm7_sD zvb&7tyt=L;vD@kXD-&CS>;c7E(|kyiyQ;7}*C#KBZr%^{Mb8{mUK#M-u>{jJtA);M z$f-%oVcDI2t8E^lg`EC1%UyU@ImRQ&6*q`AYn?XP)EqDs%G!9&{@O1-N5o^kYZzf_ zCwmW9re{V1bA{RYs-IkN7&3hb{&=+A|{T7|!>)0QE-^aC2plKR?jna8T9M`W(vQ2Xw zt|yUO=zb(K`LfzHYwMb4g>+lZL&TEDcf2Sgf^%O*Ua*=o8Uc#q?DRoxt4TiGF|U4( zHk@~4@|~`SVF!ow_405T*P4Q2Wt(w4W~s}lNCar-j)N66(|K|7dBuH37a2lO_>VTM zC(WWXW03?{CaGNBEG%$q7JXfyW4Pc|8@uqxFhyXv{r@SJ3Jg7ehJgPe|6x z7AGC+lN$SJXstV8Y7I}t-)?}5XUWH{a8|0jn?i)v=+N;fi&O=3oQmV*(_+wL`$^+( ztzq1F;OTmT!FMLtVsJB1_=m<2YcRE(FS8pdrMu9@QLPyxI3uFFL6iHHJQ^{*kc$};Y* zj?QS+Fb)k|aWads(yHitJ-5U-lmJ``TmJwT+up;1Q+ zHYZT;b%wXRazkdh+uIm+oE~ePw($(I$isGVQ@*gxoyN6QnS`Zsz4fp{aagm+=-2|c z9_l%|nt`n&mnR)+x$a`!%;O>$hH<;nv!{07@{TK5Xtxh3D!(mj>HZfou-=u=%a;+NL$W^$-bIqFbD-ufKR|2a2q2q5cdsF0QQz-nX6|;OiNi7>T_F~TH&jmv!**8PMmoa)@j@* zu0u|2u&%RDKPanCubEj*Q+2akTXb#i5^J?v~y8I*r?LFuO^n&%#w5KP|qoO3aF;s zUPY!u=1A(iXOmo$u8{fBsdp5r10!urax0hdAB{BM58Jy;%e{Zv73Y5vem>iHbsF6W zc42@)uatfy{9wM<=ab2nL{`s3UPXLjS{`+3x}L%DNA{2MJm_rh3W1zp*Ui2p_?@Wu zhT->4%oz8sf6chRl)UcGpIWi@Xye8Z6JIZi#Pc)DsaEWQOHjkiJeK6uHUyjs-j>ab zV}b@LEi|3(GAqM%8)qAx%)4DV<{;v?^v@4GuhO<`w6ujmAR4_D)OO9njMX`47bep* zlWM<(UY%2J2&%E_f%w~sbF)e_T>QM`H*u?H1Gg$FOO~{^x7vw`2Yl9i%<&V0Sz4Xz zG$mIhy{aXLOHoFXk*3#iTY~Zp%6e8tp>)%@Hs@gPS$CJSMG9_KYSPl|F7;5pRlJA& z(OfgCsVkkd@a>+J@P5HBH8>@mce*zQz2C=91(U{{e81ATUxGdqlFv(;dx*-qoQ=Y| zzZ6~uN8D>DRr*-953|~3u)!7Bg zd(9X(usnsq=qgPT$q`8-9M+BaM2z&wtjk%9YENv_l}ixOXH}J>jD$5uLz&v}?m4OD zzDMD)Rc!7RE{RqAtBQDpjO=t;oyyuk8yt~b)7&-g>>AnBE(PQWp*aGoY1(7t{D+h2 zUTthrv7b|xxq?;H6+ytSX7C=4pV`q%a04ZI)~Dh5&DeED{HwLmZ>}`G84#nKdsmHD z5W9}&ORjuD(6mc;ghG7P1aND@yif5fQG1z9jO`R;o-2D)(k?Y?$z))_9(fgsbKx^_ zx;2mHJZBZrhpzx=+F@$3~TIJiK3Ztp6r=pO}HwL{)q*I--;A6FU z=ZkLz&zOW@t#s0tw%EpG-Wu^#mlu&+?7tuS12+BhT*T)NJB1G>7u2E%oyO4>2b_Yq8ZXZKuo|6dLDm z^fbFdBfzS8Ji7-nZ@yx{yNv zEA+03RpwSQt6dz&#V8Wz#E>~}X3gSWF9%ki(o z+kFR4dmCj@FzCzHxg!SUbHc<{PAup?Cj3ZE8%=9Fc{YW}02=wS@v&yNhXh)dzosimnga8HLG-=FzHAVHaqyp=A2v2=9|<-aJ#kbdy9_$jIj1C?8oQ8o7Jer3G4p=4(CNN0-6&H30IhjkZshQ4<-D>$!S^03 zHd;L&UB6rC+nnaS%Ura?j7P0$Hn`ZqCaBy@F1a;}Mu^0MJhlj{E`m1zQcQvZO;$Gs zLz*L_HmA8X$K^Px(A^j%%}S?#6pd(D=ChkuI+@kz-X-#tQ+HlE*FzSK7QCSC3{826 zM}&e2;=2C;33!y+%8KW|dcxLbFLP?+;h&P*mOp-#0q{JkKIiFPt)c36`jiSz+SGen zZ$+<0l&oaY;!${ZTNfv8KMIrVa{3?2y^38H+U#Hj6wmlYAoX9)y4vOykEAWMV5fmr zrLz&OZafPR`D-rnZrI|!>P-4XQHe11vthv%jwDcXn!9axf;Jr2GiOXrw@x`>U0$Ea z6~pOQPDssm8hyzM4P03gXG0OfinSj&taZD5aaxvcSwVgWt#!foCL5EP<$&T&>W{{Rp@&H+7Zk-Cyi=YImSH6Is4 zpu}8BwS{=~u?ZYgnd(>C>^fz+nnnWyy?7VJe~svf@Y~3{fGhHv;(UMcrvCL_-WLUW z3^jP=#ih=X#lWxOt_O*972Zu;kMSZhmU*bk7YD9vN#= zFAH0aUxW0o4z%Sd9%WhGo_}egG=zM$q@Ee*PJL^%Z9dw~enZIV#bsVylu{QM#c|3G zM--c9J$xd!K)hsCeLm?LU^wegYAk1wf%T}g%UzO#?^$~*5Te^VDD6z}<$p?~bjFHu z&yYRqXGwPQfKi&ce`PH3a=cN=eGY|9oV1S|2k#2CZFsIQFlwHsVH8>1aB7W=7ZOeu z-FE_O3bTzBsjMir&3nk&KyWkktVXcAdz{4!NPPgWPw>BiueC%|EF2p3%@g4TuAgAH zZyqKGxUZhXW|5AUJ$g7>o(19m0EaqAWK9^-?IcBoVi@cz%Dh{AWs%WOTJ)b8 zYj5N?LB)8!qp7TygqGsHyt7EVp4A8{YQ`%}Ouk!n9<`%+Dw{~u5s}ubfoS&zM|>Ki zB$7=rNIqaXSEo{{M^j}zQfE@Wl_io1ZO^T4X`U0i)sAi=!0cCzOeU6+1CnS4S#&KwWcBAsI8m=Xf*}>WT zIaFQDPp>s@4-7$RAQ=_Y+-k!F5&^6W{XhapJlBs?5ZTPww|aH7X_BgbqNvX-P}|{A zNcvW0p{%=So<6mcHnAq$16;iFlQVL$sLd0W+=_$gQCeGC10yRdF!ru`=T~8N zy_v>X>38s7tkJ^63hB{1vYu45Whu}a@n019-tSttJKv1< zHPuQolQ5}?XKCXPi!(_p#|&dn9$48_MZ2EfH#j7ljZi8k|tTChj8IjVvnA9s46?JPK=z*s9y zD^MGjH19l`ECcr7=hCB=RU;IpLR5;XV>zW^GC5Omq=*$YDFjk;=~R%57WYOj4NbgF zS+Zo);hCa5R*OI_J*IgApmk`^6=81}j2c!|kYx3(69N%2DmzjvnQDR7VIcv2T9zdP z@9|cd1^Z?kRoOr=5-U0)i}y`TCVXSHCR!u=D@p59q$VNFZ0T0A^-b4jZjLD`f%&5w%Fx1Cun+TEja301f!L?DnB!`PY46;zzweLC$N! z(ZaA`#=8Fi4ETe|bVhC~cs7W!>v3xGM5C!R^RCV5SvJ?OPi(9kENT99exkZnZc$kK z3SSW`@w%x=f9G^HfQbDwnz;xnIW_tene-7aC}wUd_MH(Db6G;@MOQTaL1dR5E19#Z zI~`?%6(YK=I%Bj7C~va>slqu#jx03Li;u+g{v@ppSQcpr*>I6kK% z#RIkv0~O-c#hkLObJzS;@k+|d+Efx^UfkD;>)tfG*PWI*Hv?R#lJi-)5J4>z zMw@j@XZurWdG(Y?aL=YXc_q*A`@G1!<^8w9eeM#l!Io&~@k($StGh`lXtGTpSthwwtI2ERe z6YS1PZ7JP}QAM?r+qf@X5ZeJekdsioqv}o;7}#gpyz2Fo4wz86e@3~1NFD2?Z}f-& zKx!`s{4&41nH_-&eMNh2f$+s`;X^X1$E|o(aq6C@p-&BS#%#VC-0RYUBisq}uS@Wk z!04>D$vgme&MVR^wEHa;e&UQ&8lQ`!iAqPse6~L}Q%=nFDPZhbveC5d4ltnr#Tu80 zA^SI#8(>!(cdtou=jSy{>Kx&YYvAi*r#T+2JUxvYt!_tRirfm5K=Ur8kheQ)ofnE{ zHPU!}w#O+PWLKwzsNckPD5#jeKE6n;-E+<>$4b51mQh}*;%y$|QoU8lDo;wkqId!* zkxQzR?_XO-6UiGOaMqs-YE!34_w9Wp*)1;&1XqDZ@hE(*FQHg9OlgW`#TaFt!G^7%;5ahlO+EDG3Khv zKfPz{?#OJlae7<5QhS0=TCXJh^{5`{R=S}_v4o^ozq}}V)@y2HpS?}gu0s=pSzc~Q z=Dm7U6I!F0$h&iPTy?CCLPl$10p_KeaJ}mnQ`Mu5Sw|+l%oZ1(b~AM>P`12l`^B_g zToI99Mfh&c4-RN>O3St6PPO8{54`-~#o4h45VQ&0*_aEeT;J=jX3l;Qk`(n>83%J_Ki;YlHD0j=tR|lM5b3IInV5 zTw;0Coz8;eT)NaXLLz*Q)!JyW$nd9~&2fGLu`@JtOu5gkdR4u#nS#W-ewFh0%JOe> zH11nERC1~nUQKOjnhQdrMO+~3Q|a1SwYM`&;|eo_T<*W)v~AYb*@&N)HRVEXTban{ zHD4KN`aDez?YBLMHRb*(@zvCH+PQ!UC(#Xaq?yO*!ES}hiBB$gP)4OY6AX_$=Wq>edEW356NqnDLGm1>O< z47T?Qgo?^*dE~eStFVpMNb%*9$E6V1E2(cU81|~Q%0^YagEU-@Ya>$A)WqzV4nk+B4{RQtD|+75XsE zEtG7N9l1YBs}-?F%M*&)(M(awcM4PU--NGK#N^M z<4&|~>bU{X^{+ShmGQGs@h#+$M9iq!8AFQkyZ5@exo4AmvF=W5z{F-g)7>~HU!O*4 z_=m(ghlp=o&CG#IX9U*QhU^s=GPd*TYo5~Wq?Lf%pTfHPJvM0L4YZCc%#}qYdz`YL zW^KyJZ2@#3V0Ns(_(^5owL=<$>S;n4$T&3JI?xsb*PS`pm^n6dnqH+Wgws4d4%obwE9qRuq2qweQY66k zu1e-vZb@d9uzgKK5~OTPKj&X7h{w9Aq(gBxtSd50g2yc1ol(8i{E&8X zD=_OKM#CIeh~YVPDOx7W+yHnrUPflg4f@q+qLS+dAYa+IzG$Mde()@cx%k<)6L@wT{dB;2Fkxy?hw&2Sla?ZEY{ zo2$bbihpp#t?qR42{dw@EcrEzu#daXUcbO$w= zCBc!i{o3q>E16ECMaiznJ!?Mh{Dn9?(=I12rN=>8o@sghZWg^NG;Yk}E^)Kp7AGd6 z7Z_4a65GVgNqkmLwW!Bq3vF!iUqgq&a}_!|TyCYRtXcWMt}?>C(CV zd&N@8F$?tTUH8FH56h`)*7}P@k?UVmgTSh9nVwB|%6%)~?Fv0RLx*yzupNI2#qoZz z9*=7~Zyz$6>m;~Xw9y+Kn@4K-)8g-ocW<|fO!KtYwaB1((v{CI@xO_l`ubSoV#Sz_ zJ5^5!cw%dt*yNOwGn1N&MbPBCy)wuN1+&e1$A&bAd$?eZJfnXQ6~|h&m5iFX)%btI z-d&n3YoDcedUlx(nQSJV)lND!J5INQ!`gTF;rvPBc{O?7-3eINx|;dQ^)5Y$ z{6pf$x|O1cl|(q>xUW3%M!KRlBMSKHD$c2_#iywOvQHJ~`t|<++nnL5=wZ42=5x-i znUjC5OLHklW^jFKvfAh8&MDDY7H!6@Z1RPBapi``JDtqNga$m~qHtHCt1?De$)pzb z(CXb0a-zEy=~j^gjAEqnaoVJYBCZc=#meHj7ukQ=qwJt|tHpcMNEoGZ`HFvL9+eb} zv9Jdfe*&PCeBCMBIf~$)N^oQ8PsgV7sWntc> zVTy=>PT|ZTn2LH#PB}FT3{q3&xN{eg!Rl&YRFAwXEM}K1JDNEInAP4ffk&1Pd{tPV z2r_B@XsotnmPCdghc!GJv5r6rm zk&szW)Yh@LQj$6o@bNe2aI2Ac6%}w;S2h0t3vdqWNBk|}>a|UzTF~6P_-zb`&6VSt z^Pd;^Rc(V{4lAjf;`i>G?)0xe@jt{_bs*p(^XXX1W{I3##kht-Gv1>RmO1TEz09{W zZY$QbAONS$#dFQHYjM$_i9yb3-f;944ghn5ntpwSXUlPJeX@}yuqA76LDYVA40z3P zHd>3^wh7H@i_fvHLBXwIUBR86*`3y-t4A6T8=8|yndc#o3)eNkLwzOGxnq^7yhG!- zJUyljueI@m&37!t#aiO_jz?ePFNrojAFy@v_Q(?*d)Lc;GWeHq;ypo;CESIG>rnX1 z#nNl{Bm&L}sk|BBC)Qe{c{sduDSRyIrYL486UDL;NeQRr)()x3FYlD34a4)yczh-y-HiR#nJ`bFtTM_ z;Ct5{u6W8zoT|C6V-JCC=!H#cb$%i7Ri&YCDo&N>y622V&gC!1*0XMP5pYS9n!(hs zEl~8YtHR+_bZG3F(6W*(K!x9eOd6{!UN&7 zx`B3}$MUaoohu(diB>ufANZ5|FHd<X9|4eITW?6LR;BOXCRu*iCZIeTT-S*Q`pq6ZVKf^dQe=d z#}!AZ3e2iH)H64koK#bzt5o)omg`q@GBFnv;SMTBATbmM6kG;~X~rybP7DPr9F8g6 z2(W2{Vx=OQMk=lZMQSEYny^-YnWErU^8U1We_FHU(dGM5a4U%Ur;(bJrUy!C8KrO_ zVZ}y9YIYp+Q821NMprbOezhnZQs7pJfezkiBQ-ACHr3n+vPTqeH7}EzPbB(QBIYCB zXx?gHC)$rF0g@`rl4(Lt#Saat)FDH)3cij^6o7$+3zotC4Ibb}^_ z^dB)NoZsH06F03MTY6G?N1BAhrZ1dua!oqh$dK}CHie3jRpYji2_W~aW2vHxDeMNWeQ&g`6tPeF(2;F1_jdUnSscL5QG84%)Chx6Qf&Ne`P2Tk`DKl8g=h%8C z*+C=a$E{hsjpn*o*N}UP=QPb=N_IubZaA*08Kcv5$C`2kgP)qbxgByvU1wN>!+KeH zFwQ#;wc{Qn*DgFyb|Z<{D>wjpb5i)z#d2zTw2>($Bk9tm_<5v#qg;ulA13w2eznId zmrl)|qo_5N;-4Mc{5kM7nSoHemi>o0 zuZ_G>@o!!6jaTEqM2cziG?FxN{hn zXQ0h;dS8dG^%NgzE~NHq*q`BSQrd<%IqqvJl_)`e`1)#iNI_kqtRJU}l1n{2OKuKuPs>~f!h?I^{biN+qepDlx03cd4G*~m4}Onv_&HyDCVNN@h;$TC+lAki_7s< z9pa7&R)iVpm!2k?Vt3pYA6lSw$bt1WcLUNZ}z$JhR-z;~RPr zHT07nrKuLy=Hb9peov)(K85hX5GzS0eT{ZDeh#&?QI%H5wQz%vCm(dw@E?bo_UbfLJf66(sI&`r(0n#6OKqZZa#= z{Bx%upU&gwhhTje)*CmeNor@kA=e=@P zdL+In*UF+2Ne6JRqHNMH2l##jOnHo(Ppy3ZDyK2yG*|I=h!aD!hSJ>aAp$nq^LAbWDMOyMr1WMj-Ipa~l;jLkp^;$&owLO!p49aM=ahfFOR$dhOV4U@OvK>R)C+hX)ZLly zQSYOD06)tbvBAXFBHU`ZAm9e zZEBbd*+$BJ6={I+7XZ*x9jR91YU31*HhWaq%G!~Z1GPr)<}Lsr)6BW;NgMp#C@ms5 zgGbjjU|fJXT60Rif}weo4gjdRqC;h!8~ACc0}eh~YfL=_MjL-airSjyc4esCl>EKL zLiZta-jI)ajgS-8y1krG`4~O$1Ep6NM{%i)YBSQS7|!mM7D%-MZ?p&CieHrSDj<#6 z8c`en0CdrIUn4BUz#5l$s8ADTUo{H+#uKb&SlM~tG@0`acDfbZ zb`o3Q;e)P!I_DrBRJ+IBsN%Sn?QFpOpx07qqAAyW$EN4ExQy8NhNO}tT)5M<)BD#fd};rH${T~08qD3A}5SjOQ(1XQNM5=76AQg+B`ks9Scpi-LnOK zg>OT3r`!Jks5l~wMs-FC$sT0za+5sEMEExH1!-epUA~?0($7y(ykO(Fu7P|9=X>V7TDZx{-I^>5j^}H8;+q>)A{l`-&tG`<@G}S$GWypQIgfEY4MqK@ zYajAQbCN9Ma>gs9%eiC;srp8XJg>dU&;(medt!O!C6 zApH5PYtIt1XXmd@o&ryyqQfS74BjSCPtRGGUMEt-@2@$y@dJ{6YR0%K*jVZiJQ0@0LD{05T`$lPTmI9!WWW1-Hx@dq4*}!;X_;}J?qeh5)j=ORinu4a&O`*nIqtJ zto&VSGT?*B?OvxRhV(0PQ*X>Zl}}L6?d+j4+O9aru9!tf;YTxp&S_o-Ylw@!?wP8$ z-V%yCr9w#+xuk1%?-jy^;W9eaXs_PsjI*9p^{rzUd$vP@F#iCtqqDo(y#;68>ylbd z(I5m0?o1Z5+x?@xaJs&p9`V{W;F{S*Zpd*NYpqKxgcTgsi^gfw@TIrW9yAUYtxPRs z=tUA~9MeWklqNfcCc1epQ0d`8=BwLjO3juO?QtQ~wBsf*Qm3tjO=@ypB91H58bo(K zc@+8dqhoInWx;XYpW!GY)b(jgjH>fpW}$Cys!a+Y&y;X$zB6#p>y^1n9}R3~vbl}I z;WLtJtN539CFQKKLGws^3ZKJ*(#qT+;j3d$(34fxR#_Kx9qMb>YA4QO_96IV;fQoC zGA&NvZJEa)iabrMi>8#wcA|2%r>|Z#O4bmZM4bhBXN>ixAVmvuIBN2_xz7$p9=ocx zsUwW=gn^2{iZWZ8wGqy7!98k3h$+o&70RrRDee(k1{pNTti}ynSwd8DO`6s(l;o2# z-UAt=tInbG>qsE>=987gQwU8jmTKsSo|Nyi(zq1`rs0}C$5+4rVwaD4F2K+G9V1%o z8Lb{Y>5?dAr*JcNnl_rbP1>!=`WfW^37(Nrpbysgej|9Coi#7(~rIk>t96!;r6* zL-|&Z{43c10K58EqTJ0CNs(2)-k!B@32ll{vBW*pnt~U+Z!y>IliI0B?(r~F^3}aT zcBu*aRXZLaZQix{&#`M`=VI>l2-vN@)7G_oE#YgeX7~ufAd%X#LYXB1bj^J$@H@ly z@QA0HC2&V^LX45^VX4KvQGdW60kuHXtbGUZJNA#!ygag`2L*HY zTE2JjN9|d0daNy{bsf2{3_6yf;*CIkqE{;2cN(*E;Py|u^ABq47)8}0|`qwdU@ajwJaq=FS>0Ez`{4X|wt@5ABw}deDz1tkq zt9_4t&_8KJn&mCzk(e>{uU66iB1fsl8a5+7_3)>Gp?kez9nH7q$2IiUgB+GnqMR&1UimRy4T z)+N5G>aLT?+hWpWR(tt)*p?^z(g ztg1TkUrJ5zO3zKR+!8rR9jdm8;p=N1D*pf$BXZ{l72Q({2x^hV^~k-C8PI+RYu+fi zj!St0#CqgcSE+u_S2``Pn|2l0bv5lD36M68BQkkx8;td*c=uE6aWr zXU=xoex2dlmitU0lzLX(j=-gE@UB(y;V6gx)pAA z5=4{47!Td!kzQ-B8<_k;7Sa5xZtGo@?}_8ILl(}p%xg@yc27KHd7ZJ=y*g`|YGGb? zIH9ELQh1U{n&URd2Lih-O6_CQV~-h=7$j9KIyV;T?UL1ENzwoefH&5h(Z<<{q{Pw4 z7rSBS0kw<)P`n3r@z_3Juy+)#G9F2a&uisv-08qjxkNM(LrL^9cww)Z$!*_ zZuKxGw+kRDHmz~qIlTtaq6eIwE6}`1W3snw5t{Owdues=5SUmJht|9BQIw_3`VHD9 zw}G^YH1un!N6RAdT~CJmKQ^TIQbWelPfF36);mou-UUFUahmMB0c{HNw&PtUUMi+;)iG1I)%gl^;K9$>>X3?QAvBbS|RBUYwH=k@H3XPmpI@Y!|4NJ{l z6Vkjovb(Y2;-_?Bc!KHRFDY-B_pds-Xzs2Yu*oNy>bzg62aKYgb6iwP!a~P5Cb36~ z7dqbVJbrjH{30E8{^_diOS9|bdGDf(Blof^qD@j%M0663GPV)eW@4|lT_}M+mZ(rV#yxcJH{#vN_gac4SsIgK1vT!Y-CdE zPpy4z@TLSB4ZD+%lR2-CVG=VaL7r>s--TWy)6a*Zws~-X4|FXg&{y>I4!t54~u?cX4@d z=G-U>X|Zc@TFJFrrFdA(OXW!(-AQvtL!yU9u+rcFx^gkduQvFj;p;6%(V1C(?`qc7 zb%(jf%y#G2xgQnkVXWCBY_VKm)!{43RxnXK!@~M4y@sxZq}rSM{x$DfKCfYAZjO_z zgmyLHP+MJGTi(-l-he9&S&dHCS3- zUZ31R(u3(;-k0!Yq$w=%GPhdv-wF5{-$@CSz#gKzp@WQcvF6vzBBf-{1+(xrt9nP7 zXCedMx(g44i*V8T@-#$|+}F~E)&zrUqVeo2p42pFo^>dr8-d_gWjr)dLDIblP>MWSxVbZv z)@;J~ebPHs501-Hcv`|~ts_Q59yzYLEux$Ea`7M1u0d^YYmpB>g?2)eZ=vQ?#Lu{z zWteE49OnbSHAGlL1UC`vP$c%N5#Ka3K6t1u?$w()u6#@^heo`n%MVe4~0=zrLas8&<=8xu(*UHvP=h9)+q+;IcM*4RE40f!| ztsKLWpq^?qHx`65E;H{|P)m0cwlYAaPA9ug4%(G9t4Qu=jaFVr=qo1eUrui)1()8I z=2;=Q^BaPvikYNsQWG)c^si==Y=u!+Wu4oNumj3`yk{EU` zrFrzEV-btB8NHuri*Mv>w@=LQUaYO0zU&vk8jMuUJE%5qTBvZ({Q;u<8 zcYvX#&Kk0@gW|u3Q(IZvOo+s@42rWBj@o=y%YwUr&3kR!P+wYwoBabAWd(T$H1wNsMuCH#72x7pBwWEI59#w#+!{MoHdK%G|fNc|MzvUP$H+BNB4 zB`0~FOmE2NY+?<1oj^@xq7~`7gp^6n;vF8<$s4F~#54)PPg_NvL#C+3>j%aw< z7lBX^-jo=$IhbVBY~q^Ry(x^Wdr)N1=20iUDi({Hu?mn5X(V7N+@y+bFe(;2)bewh zg`;EInCwcUtOLG(50PvN*N2k%DQdscPFk!>(okI6}4q#TANpM^jJiqZc7gm^~3cl52h3)l_; zZfj*NlIcEPe+t?-c7v4QE;R8HwiIN0Rvp5~NdarrE;J^5jl5MB@VsX^U(&jvhH?%= z%zU|3f!SJCR}#cW%4@fO3dQ~3(x8*Wai>MDxM1D&HHsqe_1j!XLkwcOojV(P*CAtK zEM&^@n(3@2Uf=1|j+!dIQO1TW%hrLwt z72hYVT#5xzTKgpRJ0lfhViko}mfajSY%8c2q9UF||wsdKX zEruqV3bOp&YNgA(XEE2CR}FJ1qpOxdIt{I!D=PZQ&9m@o9V-2lk2K&{NN#N>wK!mS zubj*<)~BJ$)z1y_Hj5;)x5{d!kKxG?!xc5^clH+d7Y7{v6>if(kINfZ^skzucu1ua zk+g}ekODDQYg1Cvo+FiBm6qVg!R=pnmsZf#A~xwsM(}!26ymYV&)z-|Y{_dgPpWU5&f&^cCdil-DABZ{{Tk~zbs>Ny)b=+xyoEn~s=pAauMN;l=Z z*J$g4Lyd()jt${x)<523^2RCPS> z#P@9!Ed{5`T&xV}GcVunYq-_)IbL$W6Z92?z8Scl=#D`8SD7kudLHd8V(N2Rj-}NVsx!G9*S=W%FniSn)ExTOoF4+@+)V_OIt?3!Q0JVhe4A~Pd(%YA~;V<&eb)$c@)N}jBtJHw+|bh zleAI9UrF|xlFR<6ADW|kzbx#v$dA&m-C2v*FAgJK>+4O17}Y$Ym~;G9>pyWGeOX^( zD72f4q!LAq=B2WL#Xj48K@ehlV z4LrPat2a-S*XZ;Yvw`AQsA%fi^kB8LZTEP^dGCyM2C+~S8-qq~f18rzES8O>U`$j-t+3<(zZgy$iyyLbI!J zUKb9nC9Hs!HPPuFKl>buxz8T;(Mq4ZhPOVFwY5o4$}0O?d2#XSUTfg*9>;vh3-$M} zPPMy^>J=b^kzVW|nXS%OS<~9%ITgTbwhd`9P%GHywpTgDa^4}(h1A7@_O7VWi0*kC z>J4iqNyT*j9`Q<+3SO#shr49XO!5^bVgacb4V2CltYJmONLS zcz4DoGLf?$c&|gU)Z>^b^&YjgE?Dj>lC_3Z@l@_C2+jp`cEu!K)U(>D&2@7`r6YyD zwK47E6+Nt;QdZ8bW(tf7=kBuzHH@WgjTFhJz5w;9Z8boIbBgCKCt^)PE}aikTym=< z%@sq^t)^LHR`shW^~LGB=nAR3rFD8`!p;HBdUUZGBeE5XVGM$itwIghtJ_sqH3ZXw zHj3?yMI*8^xloMLNX<%Cm_}*1rc>08tidpj1{tUd)p_J8#d)}_H?@U~lyxVLP-m{U zN;{)k;b52dwb$s=^mG-Ts=>pmX_i@k5TU8zUvP=p_#g={mHHt;2%BywclzH?~MH`inz9KIsUX&+~=0& zgt~gD=~YFR=fL8(=Cd+jASSJ8x&z4~nG}JZwfTH0A2sZmo2O`PF&GBAtrNkYX_xnh zU>x&X`c98;ZmY=MTZ%S}C;)p`RB58)PDrC2lp0J}5iEob!n5bQYpF1?lo`iL+?FlN z$gKGz5lO(s5}=;PWI2#p*bHTpeLgKM4bJZ<4{TNY3yy$_(ikTo4r@4Jb-A>sJA0$- z;z*+(C>&IhNOSU>eJP=d;*dwkHSAG@V6`q2snn7+l=5UCRk&dDI=1?QcH}ARQ(DN@ zxWLcWuBD#F{?XWx?NtX0x89_HlyAM7Y}XGu;EZOYgfvZ>5r97Rn?^nYUsxRit%w$ zZ1=Envp5}Q2WV5jU90xP2*)5&K5S{%DEit4c+N3oHf&tLCJiG4^yFqH~(F zySp;VNdD$}S0#HChEPfnGx^tBeHEpt^TgSax~c71SK5?PD8@26SGk0Cu@#)U9R1m7 zz?cGo2i^6i+ug3C7?du?>58*@Ug=Kt+C8eJlo7(=Rjbsjxt50vRJxm*ZMxk4?n#0H z>T4%ZhD|=~81pLb?N)DX{J1>CVi@`wxojO_Q@3$Gyw{0dT%gOaO0&S3{n-HOKo!5o zGzDKhM*P(~nBF`bV>Mh#=^$=N74y}rCPOqYIGlhpR&!Yz~ zpawuey2k?9AJ5S#wss`ejZDU8=I*dIR|zPa=#dL@qazEQIVB! zFe}kM4cJL0fj^uIWq9|mv%z6qIH@zttB8uwZmXlqsmfN}j9s@aSC7H7>4+zvQE9p6 zCSzSZxA5qMsUweC<1ai!`kuDY#&Vv5y~*Mu7})WvR!r|a=&Ur5c-&ORQ=zIwIh^f-4p$*o>T+}YxxeK5-T0=nCaMG>$7 zR5sQp%E%e1jV5PO4%!^j=*)Xm%c5tsbg|jK>}=w+Vbi$D6xQay<=-P(Auf;$T+C5)J}D+{EpQVUWmBjs@&^x%`V-%A4+^yUupoq8K$Dy zT%>oNAQt%V^r}*Lt02{H6KP`C%ktQq^{f+U#AM`F@m59>(VCaK$+=Q9is$a&Gch}< z^GD^LwFsPp>MN!Qi;~%C?Yw7^T@|jH3RZTJIGMtn3heCcB523W&MUGJxVeruR;yJA*Ou6IMzK!s2iayf{ z4cX$pMq8;g>Ha`S2EAk9{{V~LGMmmib*`96+_iJ+7$x(g1XJ4GT?t|N*DK-g6D8bA zGC390MJbyAbT#Njwu@1bjjCyBI3ohM9e=_Bw=$2)y=Gexw{~kj=EO>*6&R%@AaRl4 z7dk|i6NdSKQ(Xp&;zC&$Jvi%KhQHyYnmIV+iu3;f_(xk!nVE8Mb6#y}PA@@nv(>ac zSI$M|x)C<@73A7Jy7r8!cmlg!Kg6&)k1{|isn(ZKsIzuxgvxV+fn2V!2G%@Q*w^ic z2`FNE)>f@D%%zu}GhOkhrMS{(JN9oesyVE%4Oq98%)v_ zR9W*6HN0F~)X}>YqfM<_x=qH?qOs%~1v=LGqmzNaub<2*DQqredPRfzhzGS%*KI(I zoOY_ZKB&wTW36mziY{RME9DdRQ`DQDU#PCxPZdBM*GH>qfIA**pPnBqk&61f9wsu- zEHFjph91?~LerdqTP0&eVcPtLwqW_A2D8@Ot8}ecBij%aqAd6Opw2k$9V-vWTgiO9_4ThFQM*2#mWal`Hq6DHjw=e`;dUk>PTJSL)7ndiha-x?)U2ON zkpUz3ha6X_hgJ%Na$e6k+PtqrOtXp2&<39{`vi!rKK}qbRjU?OzCnugCsUPJ^ zn`H;qwXBg$DVnJ?VTRFKb{55eubstC=1k4;3rMdBi=pdT@kcS8H;yZ#)olWp%M9YE zToqVLfyl1{QRb10(4jrHF1HSqv2i3eI#HF*K2uWZ79#q@NJq?lYnu3zsV$a{Aw~g) zLFT(CQhdnHQ?h51Ge3xYQp$d4(qZ{myQhgQd?%(w3O>TfGhQ9xITFt5=HdFBkZJn1 zv3KGLz(*xlj%)3d?M)-)D^K2MUE-e@KBc{(eCDP2Z)-L7uChuvAah*Km84C#5)6~= zUbo>LDKxD;E_UPIyvmb2`AE$1E$`YTHo)W|=~N?jw|LZaHC9PY&b@NSk)A71z$BU@ z*9NhSt|cR88wQ>n?aviFR)z@nky;j$!v(HRI2D(uYu3>Q6dylICq)Sw#E5%;#yaiDo|5*LbhgMdjJ zaqC^f*sRvhrhE)n0O_^yS1ddh#4_fU za8h&6dhG0VU8ZzMUZ2af4U&D{~VbD;DP1zc{X#Xlk90UGN`|4&)Cf zLyGsG4)~rMsFd%?uZlG7LQOhARk6)_Uxhqq=Eh=DDw|-9s2THNtC}g|H4ob6LJ4@oO<`KkHl;r>@H& zVYt_kiK^SsmnMxJOI=u;m1@Sh@ubiLwf_KG!MM7TXFS$z+)T<%bHby!Jk0ELZy!$g z1UvDcTJ$|L#mR9N9r8%81F?;gMk90Obk(+*~P&90#wvZ)PSzKRxj z)E;wQ1)z9=-ISL#>fRXf0_;Fqx?@$bb2FvabY+qfK%w|cBn*{?Y@ z%3oTg=I#Nl)gQEGmZy{3-A!q7Gr-MyUxhqI2t+N-a=s$bmfV&Dn&s~jmc!LwR8o4Oq6(&ct zI(a8o0|t~zFgTIRrtCQ7jgNXXjX-86?FbgqD+75k#A zNeYwDQyI>-?Z<;kryXl{OC&3kfmxR}S!^diN&v11H0KSsLt1TW`oMlKAxfYWa zp9zuJbUYg8uI&cSiVK=)9&4*!7$0XpIod@)*74|euE2t28RDt{2QYFHpi0WCfdbdYc~R6d%H69{#bmdMi)RuJhqZRWa+5Pa3w=cfRd*o9O)|?*15s0uML`9kMvaNSW6m>I zZD3@#Et8*G!x1x;qV&qCIT;^?T(G*@*u`^VRu?BMYg*pzVgT2JS?Wthz17;X63d<| z4tu1P%wTn^H`l(pp)iSkM!= z;E`U3rTA@Ze71E2zcroUT_jnLGDHk<-n)qjxdB4sw-u!4(H}KA-b|L?M~X!S0MDSV zi&$}{_*FpRhIsrcFAM0dtg<;I4_d3^%davgv|cgTSDQID%$dnpre}G3wP{;i1q@&1 z$8%L6Q3NTP%JEL7Ep4!;X(P33O*1#4V_xxR*{x&?CP?%(=C}SZxSX*L!m3?QEx>YJ zh$q&urW;Nx)Ta|R+bv0~O*@iY^H#0yBqQ(Uu(p!iX0yv7R~pjlRfzOh zm6%s;r|809zbQ4(cz?sU49MPFVArnrTS0Y2-5LCAbzVJ7QMrc)hKY~;U+GzTw};F@ zn`a+N_Cck2aeu1q=~!C!o?h93iyru^_SD{mIB0z5;wv95nBuuRgS3%et>YayMyzpO zS9@+m><3KNcd^SP$}vIbIO3&mGS1~fewBLG(VL>x(`E-OIjD0pxa&U?+*+s^YUuR8 z7Rx!qIj&jKKA07wr0K0JgKmEchbGR5YIQd=z0z*UJZ7;c5;-6tHQQ*~33z}YoR0Oc zZ{W{eb6MFaO^@~h**k&pRiKPuT^ z*|*it0{+LECOCz^Ds&nIvOavKABBAZ4}{`ka}xek=kSa}q(FWZyl{*@u6TH`n$1Sk zZau4^@b0HNGKN+kTJ&kWB^z<&kIJi`3928J0Q@U>;aI+}p_)8E+X9y9R^ZiCA zt6ca(Qssd>)@9d+E+QBV_0=`Y8yxPHnB9L;NxoczT#ejo6lP#XYfke?oe9q4^{jnD z%6}n-)~_0*+Gde~+jtXFQe)j92EA_H_BOX%I42#e=3Or4dj<#QTvun0dFKG?O_ca;ClFSDSL~ zCb)a~+GZyvv~D43B|MYOIdsC~rG9l@y{2-?=4Od2t;-A(T_BzD(A0V^mE~PC73=F; zzh*z(AC-MJTUkn#&gwr7r_iFz4%G2%1!yt~jfM?+{HGaMKm{YN-jyphIHf`6A6m_# z(ko=r;i}lS=cOJ;wPedhL7df?{@~3xTk}o`1yna15De3(0-x5lu_6J3Qg5a=%B0j@ zXV!}WV|^RyznS%;%=*%>76e{zK9ul0s%@>nL?Nmxo1xD&MU$JXbu|-SfzE1!NMEH1 z1IR$23nu;Uh*ajU>Gn;yJc9zAXQd^m5-{t|YRvbFP^uaLx^HdmqrhRhxo;I~a{ZCE z^ya0WCub~rRu_vdBev5r$C~5CJ5pzE4Sx9^X{cSB>#0MWVAm(F+Zj&&a=j~Z=2P~T zX#W6+)(?s9{?75B;4tRCS`xcH@-Rslp{}I1W}pRHX@0fsnNC@>h*7HJNczIftyA2Fvkr*7^N zProM%^{ra5go{aM=CVr>ETgZrY%Izxh^iK_&!#}g{0TV}xo;%bpI=j*?m$0za&cBJ zB6W-rNu|JLyXjkZ)@ra0I#-KT524I7t=}~zouQugH^xAwT`-PdzNWS9%2{c$PTUpm zMJ1``S9Ulbj2h>WrORlI^K->{pMd;1Bj1asUK_cs-xu8<5qOc|Uzu3fr08B6j?YB4 z+L(U)SJhFIr%RW)^EKzGZ%NaCbsB+PWtNB$17ep<(*i{q#~@b1$`#cQdf}PoEnUlc zdkb4$!xiVgB%jXKBst*Md8;tB)1Ai{73MdSHPz{1&2n1o)j%Big9Ej2pAcs#(5N-v zTSdc@*jJYL-u-TFwu7CGitnQhOl2DwtsqEAHJInlJBKwS^Ldak28$STZ;~t5rv!7Q zsMsRQ4A)Jg+ot8sbNohqD_2dw5il90QZ_Z^JqJ#K6Ki@`MQx}mU}m^WJ!EYIbzh48 ziwMr>C_srY?vLlCvi#h}S)bQmr$Yt^MVxN2h7r+cbg$8l!ZZnero1V-=XBDB%- zYE`(bJE!ty+&db|rJlvb9^2uq3J0-;!h@0NUDlJOd1?Zv{Hw=2Iq??S(%LAblLbZ( zHRyUj#Vs*VZ~zK_db(+4<$p@c@dk*GWZ({K8t>xnmXYOV$Kzbav+<72 z;$=y?`_@$9QB5;f4Gu5IItd8)Ut00qLrG+~Z)GTiym9W`7#u{{XfvuO=sU za%{oQ1hiBg1thU39R*tsq*N0XuSeOVre=a0w$geElv`$=Ej!|?kq0swBS%h0=~V7?w3|E+%C%Ae0H=t+tYt%(k7sj| zxbU2FAG=xCo&rfDVYBqFKu`rq5Z>55D~5O*lkDtxuA}hus5@gng>&+FI%p)}7!23Z zR~8YtJXSr{i3<{T{*}z|t^~N)@*O)znhXLBb$V0|iU&2nHi{JRwJoNb<&2Sv&9?wr zGJQ4z$}%fethXmhhS&(PT5!y)0IlPQvM%R6d8W43!q+Q%rOhF1DI&c<+oXW{8ijOd zUpZR&tj7^_Pc6?OZ5Bj)l&cX~vX8r7mwTYZfSt9ACYpiB#e5BKdPI98owKpS6N|&v7 zzP#0`5}BaVh8U{PC|a%djpqV}aJ_2Ou2Lm8Y7a_!&KjwilRc@qFZXl!Ra~T8k=Xsy zii|@frx~KwHzx-*wQXnu=cOi1^|4mdNs)j#tpv3Y@OdJU=P+<7t8?aOHE>rHn?ZAS z(O@yisKknK$2A4hd5kgIg&C1WT}Y^_g*N2u$6iQdguZyMJn`P6=h&Fg^InIpVjVG? z(!6(5)Y9Vd;%pJ`UonfsO3o+L;IQ%LQZZ~`Bsr7Qiow+^n)cuVbA!ffq1A!4hjN?- z^sa4gkIsA7mqluQP3(pxyiv}!E(ee@@O8a*x%ICFncJ1~#v9r!^ zYTsikzzCSbk@s^~Bc4)bww*R3Xl}@r`XThCxeEdnIL%_|?=`%bD$?Zr>a%|+v!lWL(I;<8yR6=#KnGJFq47vOC&O#Briv~Da^WTMfIaH2fFmqnbXQzY#_{p!LQoNb+m7b*9RgsYjp2D3zlOx=r zUe#e6?2Nzd8otDCM*V9UoXbOv@wL-gSxFH*;|m6YIVwP}3)S9BtLfVeL1KDW&>z_!+E)0+aw`Vk;fbDU8)sALUXC7Z zZe=GXc)g9BlPMcX{3~kTNRchR>H1>5n@#vq5Pi?mwyk~|0Wd~RK9%XJlISQ$Q|7zN zSy7M3-<4O4OoxSHewFo1{{RP~yHU0=lkHkMSHMV|67Z?xxTNJLxxXp$-HpVnDguqy z(z-vlc}$?j-90PcH7|lOTh4_e1L!JI@Olv*`2wyzYn{`%4IV+Fw3mB!f-~wXpi4<( zOtTNf*52BZ@s3y%^{L~X zP++ZeDiIy5Yt7;bw~~%UMBXRV5PY+dSnsGPVm8!GdAs;(l^CvtCegCqDb$-Gt{Br7 z#CJ^JG^C2dXD#0p=#ZBKHGax3p+qU_48-;haGTgDK!Zrwk+j5-8+;bu0W(v0?Xz=U*b7e&r4XjZ#OiUbz1NTK@o@ZS|VPDvbKx+u}u(tVT!Y zT9)26v!8&&^RJJg@fF>s7|7@AS8e=zbpanc)hty&=V#dvc#7Ij<6M1e*w-!p0KUJS ze1mK8I_Ypc$H&&RqWGI~M>03(R)$t^ZmXR(pYYNKUTXVU_ zD*mOaM|#CeXBC|@m4U>4NU5b0*n1dS_3XB{;z*Nx1F7va@ zB5gi)#PW0%l#t!u-+y4pzvx|a-o1)zLn#|x9JGy7?X8C1G6CkQT+KG6s46-bq7RIY zIISI4_QuCehfmYrb(Y0@l6CR{X!s#RS=L=Cxc%zCe-HqJMWAtL_(tUX0k zxJL5{1Lh}@UTmRrCoK&PH_M9JW+VfO^6wPqQ1K*DjFl%9*XvrD(yZT{627&|cwK+C zbtSoOb~SX-gM;R=<>Dow>7EMHR>mmf#xY&8-30-CD+66nnL6EgmSaO0PcEOTzkhm1h?ntD|Vj zI(yflMwzmwac%V2gMbl#Dz_$(8cZqzewCx9*coO{I@e)i;my*AW#YYxRIX|4($wcP zEd-ec`1tx)pjvoxFS7y=bo8tCIvi+&1DdPiZxP4gy9Z`)xKq};ey&T2$as<~O)*|c zN+@1=72rP;zBF2Bu*{b7M%eTMuzomx&>}H$ZwyAe&SAz4X&9)h7`3U>{gNpMP%~Ag(xN2%^^A3=P64PN#1ezStrTue zjafW8r;z5LeH5odnq*!ieV`Loqt-;dO%@%D*=(H;3QaZ`kl39CIIe~@t)gjom=VQGgl+|Qy z+*O#Gl5qxbtaSz-722tl+-w+1wkQJtB_q4<24+R zFHu#Uf*!RtxEk#sc=NlO&bWxN;F@)_GmbeGsRo?W4wNMnL58s~KX{6t1g%fvzTcflle=8=v^fLoUABqiNUsXz*CxDPKLTF@3Py2V28rSAPRYma$O62o%b2yyeF3NH z33j7vf_%uZ$Lna-2S6(jeF<&i;p(i_{)z3nC53NLJ$5U0_;1OBZx86`D zzST^Nnv&iH92%q1fiD+)}zeJ7@ zIx9K%T6YRc zLnl%z+1d!8Si*oXF~xTKHkFSWw25GkHh~99+_hMi)m2B&-mz`v3PyTXhMF2nlhg<^KR7<0|kBe|otu9%@9oA~$Rr^oXNNdkcqrXJI0|H^dr&y1tNSATg{{yAnlc zV@VVJy6QX)rHw~cSXgd6S0!a8*~WTTuY48KKH1`=kgiaHU9_Qdc<6dAm!c#V>YI8G zN}04-B$ym<4SEA;X%gpXr&?J@BkkBToL8|*nb8>;8a9n1C@MaF)!OOyuNAmiNZu+2 zSahiOu}}v+YtKA=@s`WO(Mo*948DzCy%;21q^xzGF!9_z6tj1dP^1IizA5%#^#Q>uH5UF4>iN@ zZN!6FOK{+0JX9AlvO21sDOaT&sLiqEM6H(QrHw)kDm1A_N>)Z~i@R+xn2}19JioOGnw`O&yoL3JYZH6&V;@wQdD^{94Z zwHq-=ENVfl%umkMqYeC0efs4kW&?v!rNoMQniK-ZZa&W-=~S;Ym7JGF&0*VWE}czv zS~c=bZ}WAe-zZq-=F{c3Zk$sVV;=_~*K;kDcS-Vrn&-703DJuv;*V&8#z453uqt?~ zO?hxfCnA~TG#LaMVv^io(5m(x!nBOXENK=*L(-*7N0jt6VKiwZ=nX`xuB>GlCP@Go zp#c6G*S7G~vbRI|R+YbnB?Nq@@~mZEJpkuCi6s~vC^|%s&MUgK@VoJpKb303;da~p zto~Jxwrf-eoLK0$K48a6?R0++%<3{Ls^1 zY2aB2{{U7;;a^AD{62};W+7b_kHd%n2g+JKrC#C6&zkJ~09bs{6Zls{r+hy?UKT&n zzKMV(euOH-$WB8Pz($F&^wAH-ZEea{EA_;$=jWBn^;)8UYE0gLpnYlFj)MtMP6 zmfA!S01`x2%}tYh<;V-3wI1X6jg}zC|F}{OVrUt9(8N*zTP9mKp&-B{{V$= z8UFy5U!`})QGrwt1vmRvFKW^>BQ~z|6y~8# z9!=4lmn|f~0&9CuQmPGe);7{Lz$Hy})|RgDs8N&YTSeSrDBNo8rIB+_x6`HF_bYo% z)7eVyTvqW+c4RiXOE4G}qiLXi^Ks(0>}?}e8?Y+OiLm^mBDyKX+@zTmt)zCSI2pw` z?iic_ijfq*2a2?|(FM-#YoSD>nNmA)M&sI_QZwy4K***ZwQ41#F#1zU)6P9*&3#c-b#wKTBPW`HouFRgcKe2Cq@8u|C* zPl$=uW4A4U++w+L5?rUFhJ$ZooAA_u=M2NWZ1|CxEVo9-Iqg{<9)&~zr!7`BTlQ4g z-Mwq%W~A!qeXRv(YG!GgvGmPgJh*j3#o zK`ofPxB@x5t#>OVWi3fHt0;}C`A#?Y0hLd!&f<$H+625?PqJ}mg9Vd3jGFLJ2h8u>5c&+RXFb1bl0NxgH7 zsjqgFN$0WczZZXKyL})_6jG}YI~w@I;xEQq&lBnrG;+uhv(WKehlu=1d*XSS?j6QS z{v%xUcEaC{+rG8lkkrTAJKfG@mCF=~1Amf*Mz5cO#RvIXR_hUAhd@%?QZFBZD7G zi|u3_cA`qbVi~Q9^!+PGODz;VdGw}4$o)-E9z4yf%GWgH?O?I5VW`Ng7pShT!q8vF zGQDe`(lphWsqbC(g0X(q_;ueIkMypV$HIZY`9DhM zRc7}K9#5tCK(im+ewDpx;GN7fAL(ARrT9TXB&X?HF?ebyxhe%{ry(9$Vc;P6{oQU` zd^!goC*~{B;kCB5#z+|Ut8wZUF%DfuJ*!1FE2EpR@C-?8nL(eWXxsQUIFH=|tz3A{ z*42vni?mfI_>W?wk2y11b9oyl8yi-d1W@%0`Bt2E5Cy^7D}}rGk#2yID6C7Li*O;^ z=PrFSOWH2M%@fw6iryZswK>(Tt;hPruLRUSEpT>757M~}SK|f3leNEE6wq@=x%Cv@ zC$=B*)jv9_{xh?KE%6ri=;g};^`$17*ojUy(VtvPW(Q;QP~1li3=nv+C6NrE1^Cl_&fxcqIF-qP}Odw$voS zB2Uh>;5Sy--M{m#V@7YGqMVO^9zKt$kOA&!{{V#k5BcmrI{D*R)~=z2e67U&Yajj+ z4M)-c06K0pW1>Z=$n|({P1ZrAp50V#?^)K;?CDqINp6TyoEqYdHhGB-v_HH7MRq4?(+plz-*RMkT2a#~N!E6`V+8*?7|*vFdP9^|pk#ynNo?WBg>h5rClN6k-$ zdvaVde6aXc4e6QR%)L- zk3O|zi8T0byjQ7xo*};Ek&*3RQ~WgWG#(byp`IA_-8%mBisXI=d^L{V2sMe~8=Pa2 z&3fd{bmlzaI|}w`(&dTaSF3ZK@$7zNN)J*ifzx!qD|6uRYq9ZWqO+CfHHl*)yevDK z;Kb;7wH?`~r+=ixBF9>_;>~q*-3=k#f-)&|JAWybL(;tW;x4Gu&m=0`1Jb;9%q((u z7vJfUzH_|P_K}Ah&(^XbQ*yv}6}M)MXD2+=%WU&~^O_piL^@Zw1!M!-KoY7p80Lp!coear$2F{F$^&-Mv{ajZ zlx_M}p4146j8j!`c&h;^-dC`rp5QSTrC3feQGtU->N$Y78(>nifzqn1yL-|}Hfi$n zYEma_fTOKOZw7kRIb52qOH{bnW{HgQA ztG2dG(gBLi)=|_7(nMsY$E6utrfZ_N(aeqae@fib{5I}K%UtutrUxx+;r+mHYfjt4 zE?X+672Dc)UBLUlrF7a)hZkTDyw{sk5q*m|of|`vHXkl)y3#aR8~|3j%^$)sxgQ{| z!o$N+Tc{;R73EZuVRMSH@WYozDP4`0f%kH?tG3?N4sbD9*FGq>vlonouP{A z+@MXX)}mfP%yhE&v^wlAe>%*$z7u8Dj()X`Yo)! zY+p|2Xf+~f7kB)H&TG=(@B$&pL0kHNhwW^@-59N*NwRrcJYe`%{vJY9)_Qfsaw^Qc z*S*E2*i5+c!L02|!I4~C2_cLCE3sjm^s%)#K4X{g-iaF`lDyYY+9=onU(&DXJ{*$W zlw$(AyImc8vA7PEv@k60j1MuJ!b?1$vUl{Yiw_RP7T=c`_O80;O9}TyR5q^};=M{( zKO?3wA7`dOiPD>QrNJWfrjIo{;+_nR-!jY8 z;nQNZPy&I9u@*_e98$7wImxM_T=88HfucfFgWyt_@!pxTy5g*AiA19n8@5R_!^K3? z0NAQ*tCEsUG@R68aC_6kDeqZ13gJyBH0RnEJF2<3Ur|ux9K?)GG@Mm;yjAWEMB0Ln zN`}JZw-W6jbf~=l0D7$@z_^qEdeU5|gy1%5T+VMo*V+%QA8E%ml=nq?4AkyGwRtm) zDK!#fpsyp$emZ!aAzNrug2M+D=K2l%c6O30AoClMp0wYJ`uWuLxKVi#J`Gj3iEnSM zq(;K183Me@%==6vY>t`r2DQ@K;gs>~UVW-en!Up`L?nljMRh(k@gmx2(pp4{&+S|` zm2B#Zc1bMVS2gD??t0DH8#*Cd*D_Y2#CB6_nsH2C>pY0yHa@>cMMv z*g>ep=H*gY#|k==T`k!W9ZR>?XY(q1#SF1_(O1fLj<=-Oc~@j;=3OR_yFEt!Fwp#<(%y!t$Q5z zP}|!1alnYf_>^GRsfMR>=P`JVbSWp5;rnp}KzVD?RV*Nc0x66p4uWt5&r zYS^)W?vbKbc5n6=40y({c+(6m*6dfaXGpq7``my4wbEpT}zH; zF#tz7^r(n3o`$XqZJ6hFYVEa>k(ySqlbM}wWO5B_+S}{{G^~pkOi+?5U|a#EBYx(VD9+p!qv<_*BwCxaTBO z0tD<0dLGIs*+59Hiw~F_`_dK-o+(Hio|PY&x3H__NYzA*u^GWOSz&@0vk&pkVc>avjKuGWMv8GWyiAUMZ@QT!MPk^9tmK zc4322GM`gX8S~PZ&o4EbVz@}BJ!-qJxuRE))mM3Q*0XFbNV54eFEwK3=X-&g%aNaW z%{)109OkVuWLo>itLyrRveKnF$L`ztRFSw1is3#eYrkOAO76x%6%uDo8GFZpT;DdM zu3rf`K4Hamx*e-pSUip}dlOt8n;Es1m3kR6Ytb(*)vSbZo~kR%s(rp1Hb*;oWiF?v z`HTsR)~(B?J**(cxmxJ7+j#!~u?~OM)k~{|`#+RE@UK2P%!8jOBp9Ok(#sZ|}3b4|KS+sEI7j%%~0QU7Q|k)}`(rFIrs8t9>vmYbHnez3N-LoRiH_X)?N+nbpN260%!o-%O6SIgq%c5BLc?~Qd2w%k8C&TE*7c*R=0oZQYy zIjU^lTUVbQOr@b>-G*D*q1LqlZJ^z;I3a1!f?tmH%KS&vKE+`01A^<)yRgx`kRBL&W}q$ejPs}M-fdaNQVazW@#So1%5uvgpCf>uaKP%%<1AQ-6>3f`i!7^)3eiX~;q z>rnxUV@k-_6~zf!T7hAa0H~TpT8}R0gPLEmNd2p4Ss)Ro8+z4d4I$uF=ya)Fz{lxZ zcAgt{BPse<(rvIpuDKwJmfh7=7C9^v??CZc~ctEPO2*ft0QZW0C{S>@*i0 zR<^A?HRciW*P+AVn26yzRkYILwHYcaf_R5FbW%ABFA4At+SQB0#yn(jD`s6r-qlYy zHJ5ebt9bMC^!KEkq%zt?Zvp^F+$y!sr*Cc!7*_|Xc)9-3CdcDGwa(x8qDeA0*2@nm zf#`SMDULkh#dBKEi?eP*u+4B+dd#vDhRss@RFgg!@m$hThmpMn!4GrXq~uyE!2hq9u~q%_N_Ct-PfUDx*j(_#LT>Ih^EH4k*S>#kQL@ z8%Rw?6#f+=#hN*yj(JF6Ick`kiUbEeD$-kS1B$|POy`uYQxTGSRj3rMD%5sdbg4zA z-qnkl0^1>!7%R)|LuD0CQUw znsX@!98}QQ{hWfs6`>u(YMXo4vqpj22v}@3pdOsn#gAyHqM3baa^Q;e<4GiT!B~Pr zii8}Be8=9RUYypdHhLTBU?+-=y3>g!qhnQOK#Xz?S3T-dO+ZQJmn#lor1hxD8K*mD zqGN+m%Ein!Lz-ndj%l@PLeshL`4nVR4|<7-^)>O=ILjFn*$xF%E^*eDCoFk2n`Ui~ zENVA?X5KoUxSP@lnX_>S}1t>{Mls{1R5CVt(9CgJg~=VG^c7XwJoKi>$0M4AlbTHh#$1JCmExg6 z^Hb&ODZxkCOLz6AowO$Rb|DORoMiD{8}Z{+Fxy6|{FcTC*1m$%<4}a{1mnGYf$^JC zo^KJ|M(heKoD6j4rRsXvSZX`M_A4&dVhL_@U31wO!@gr76#rP7;PSgu&<;sxs$550+DW|I#UAV`<}HtQ7o1USZ+S` z!xd><-fra}t)u}wnRT0>9OA57z_(WNPaY7jI6P8;=QhXrm3jkJE(wn6IM9_sc~R23 z=PQvHVvW|JD@M%Zl^v?(rl!lMhf|MglUao#i0x1~tcwOOaw9);&{irpWL1`juy`)a z>UQxl`tx3)YY~drCUJpWXT!*qZDYXgU0v*r82L^s*P~4zC5xTR>)pubn$5YNiZ7&Q zm}QM;T}s>f*K3BCJTtUri1;@VRRIKvLcx_H2cN|lfWJ35N;O@8WIi;T$Sj!#x#6q{N>|jOWFe}TJK2qpk zDZtuzF+o$XR^`8jBSH5CbjBo6!iuMLAyqiVWa>&{Nf-r-VyYy zDX%7Aa^%;khry>EShhMXSH+S@P8`=cbFRxFKRG$7jK#Xucj;cWEIeXMWYXRjQcH7L z^GsOxs0+88Mou$V?BZ7=BQ@Hfq8!C|mvQ^1u0d)TjybFGLYVuaoD?f@S~;Y)A zXLVD9NT|l+ZZQz!^s0|G?g7nKd7Y0YuIaX^EW>eQ&#hb`%x@ZLmb9Bjo@Dt69>Tq6 z!g>doh)iek6%T=QYeEp1_aA!pJtD&1DA#N%IP|Ylo()S|oe`pp?I%MKqT~TxBuJL= zfsWM_fGdyyA4-h5^{=3#gh?$=S`?CsE8fqO@`G9I?72OwOrSkQ4+>-Miu9pPBSjk& zV7X&jF-?^m)B-in-4x{OT`D&<*p<%ejPc%(lbWw^o@tVG3EFn4Kl9_GuIn7UjP~pv~yjF+**(ZP( AFaQ7m literal 0 HcmV?d00001 diff --git a/backend/image_penyakit/hama-1746266358289.jpg b/backend/image_penyakit/hama-1746266358289.jpg new file mode 100644 index 0000000000000000000000000000000000000000..cb152467a41c08628e0e8498b54f41b9f5afd0ed GIT binary patch literal 10349 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 literal 0 HcmV?d00001 diff --git a/backend/image_penyakit/penyakit-1746266733862.jpg b/backend/image_penyakit/penyakit-1746266733862.jpg new file mode 100644 index 0000000000000000000000000000000000000000..1edb23cd8a4cd9ea09c61b994cc4999b0fa06755 GIT binary patch literal 9811 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 literal 0 HcmV?d00001 diff --git a/backend/image_penyakit/penyakit-1746271780000.jpg b/backend/image_penyakit/penyakit-1746271780000.jpg new file mode 100644 index 0000000000000000000000000000000000000000..80c7ff05a449db8a75a2bf6557c07853e36552ec GIT binary patch literal 7806 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 literal 0 HcmV?d00001 diff --git a/backend/image_penyakit/penyakit-1746312599967.jpg b/backend/image_penyakit/penyakit-1746312599967.jpg new file mode 100644 index 0000000000000000000000000000000000000000..cb152467a41c08628e0e8498b54f41b9f5afd0ed GIT binary patch literal 10349 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 literal 0 HcmV?d00001 diff --git a/backend/image_penyakit/penyakit-1746313148023.jpg b/backend/image_penyakit/penyakit-1746313148023.jpg new file mode 100644 index 0000000000000000000000000000000000000000..cb152467a41c08628e0e8498b54f41b9f5afd0ed GIT binary patch literal 10349 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 literal 0 HcmV?d00001 diff --git a/backend/middleware/uploadHamaGambar.js b/backend/middleware/uploadHamaGambar.js new file mode 100644 index 0000000..537587b --- /dev/null +++ b/backend/middleware/uploadHamaGambar.js @@ -0,0 +1,47 @@ +const multer = require('multer'); +const path = require('path'); +const fs = require('fs'); + +// Path folder penyimpanan gambar +const uploadPath = path.join(__dirname, '../image_hama'); + +// Pastikan folder sudah ada, jika belum maka buat +if (!fs.existsSync(uploadPath)) { + fs.mkdirSync(uploadPath, { recursive: true }); +} + +// Konfigurasi storage untuk multer +const storage = multer.diskStorage({ + destination: function(req, file, cb) { + cb(null, uploadPath); + }, + filename: function(req, file, cb) { + // Format nama file: hama-timestamp.extension + const timestamp = new Date().getTime(); + const ext = path.extname(file.originalname); + cb(null, `hama-${timestamp}${ext}`); + } +}); + +// Filter untuk memastikan hanya file gambar yang diupload +const fileFilter = (req, file, cb) => { + // Izinkan hanya format gambar yang umum + const allowedTypes = ['image/jpeg', 'image/jpg', 'image/png', 'image/gif']; + + if (allowedTypes.includes(file.mimetype)) { + cb(null, true); + } else { + cb(new Error('Format file tidak didukung! Hanya file JPG, JPEG, PNG, dan GIF yang diizinkan.'), false); + } +}; + +// Inisialisasi multer dengan konfigurasi +const uploadHamaGambar = multer({ + storage: storage, + fileFilter: fileFilter, + limits: { + fileSize: 5 * 1024 * 1024 // Batasi ukuran file maksimal 5MB + } +}); + +module.exports = uploadHamaGambar; \ No newline at end of file diff --git a/backend/middleware/uploadPenyakitGambar.js b/backend/middleware/uploadPenyakitGambar.js new file mode 100644 index 0000000..9f47bad --- /dev/null +++ b/backend/middleware/uploadPenyakitGambar.js @@ -0,0 +1,47 @@ +const multer = require('multer'); +const path = require('path'); +const fs = require('fs'); + +// Path folder penyimpanan gambar +const uploadPath = path.join(__dirname, '../image_penyakit'); + +// Pastikan folder sudah ada, jika belum maka buat +if (!fs.existsSync(uploadPath)) { + fs.mkdirSync(uploadPath, { recursive: true }); +} + +// Konfigurasi storage untuk multer +const storage = multer.diskStorage({ + destination: function(req, file, cb) { + cb(null, uploadPath); + }, + filename: function(req, file, cb) { + // Format nama file: hama-timestamp.extension + const timestamp = new Date().getTime(); + const ext = path.extname(file.originalname); + cb(null, `penyakit-${timestamp}${ext}`); + } +}); + +// Filter untuk memastikan hanya file gambar yang diupload +const fileFilter = (req, file, cb) => { + // Izinkan hanya format gambar yang umum + const allowedTypes = ['image/jpeg', 'image/jpg', 'image/png', 'image/gif']; + + if (allowedTypes.includes(file.mimetype)) { + cb(null, true); + } else { + cb(new Error('Format file tidak didukung! Hanya file JPG, JPEG, PNG, dan GIF yang diizinkan.'), false); + } +}; + +// Inisialisasi multer dengan konfigurasi +const uploadHamaGambar = multer({ + storage: storage, + fileFilter: fileFilter, + limits: { + fileSize: 5 * 1024 * 1024 // Batasi ukuran file maksimal 5MB + } +}); + +module.exports = uploadHamaGambar; \ No newline at end of file diff --git a/backend/migrations/20250503100211-add-foto-to-penyakit.js b/backend/migrations/20250503100211-add-foto-to-penyakit.js new file mode 100644 index 0000000..eb188e7 --- /dev/null +++ b/backend/migrations/20250503100211-add-foto-to-penyakit.js @@ -0,0 +1,14 @@ +'use strict'; +/** @type {import('sequelize-cli').Migration} */ +module.exports = { + async up(queryInterface, Sequelize) { + await queryInterface.addColumn('penyakit', 'foto', { + type: Sequelize.STRING, + allowNull: true + }); + }, + + async down(queryInterface, Sequelize) { + await queryInterface.removeColumn('penyakit', 'foto'); + } +}; diff --git a/backend/models/penyakit.js b/backend/models/penyakit.js index 654c731..92c9bbf 100644 --- a/backend/models/penyakit.js +++ b/backend/models/penyakit.js @@ -28,6 +28,10 @@ module.exports =(sequelize) => { type: DataTypes.STRING, allowNull: true, }, + foto: { + type: DataTypes.STRING, + allowNull: false, + } }, { sequelize, diff --git a/backend/node_modules/.bin/mkdirp b/backend/node_modules/.bin/mkdirp new file mode 100644 index 0000000..1ab9c81 --- /dev/null +++ b/backend/node_modules/.bin/mkdirp @@ -0,0 +1,16 @@ +#!/bin/sh +basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')") + +case `uname` in + *CYGWIN*|*MINGW*|*MSYS*) + if command -v cygpath > /dev/null 2>&1; then + basedir=`cygpath -w "$basedir"` + fi + ;; +esac + +if [ -x "$basedir/node" ]; then + exec "$basedir/node" "$basedir/../mkdirp/bin/cmd.js" "$@" +else + exec node "$basedir/../mkdirp/bin/cmd.js" "$@" +fi diff --git a/backend/node_modules/.bin/mkdirp.cmd b/backend/node_modules/.bin/mkdirp.cmd new file mode 100644 index 0000000..a865dd9 --- /dev/null +++ b/backend/node_modules/.bin/mkdirp.cmd @@ -0,0 +1,17 @@ +@ECHO off +GOTO start +:find_dp0 +SET dp0=%~dp0 +EXIT /b +:start +SETLOCAL +CALL :find_dp0 + +IF EXIST "%dp0%\node.exe" ( + SET "_prog=%dp0%\node.exe" +) ELSE ( + SET "_prog=node" + SET PATHEXT=%PATHEXT:;.JS;=;% +) + +endLocal & goto #_undefined_# 2>NUL || title %COMSPEC% & "%_prog%" "%dp0%\..\mkdirp\bin\cmd.js" %* diff --git a/backend/node_modules/.bin/mkdirp.ps1 b/backend/node_modules/.bin/mkdirp.ps1 new file mode 100644 index 0000000..911e854 --- /dev/null +++ b/backend/node_modules/.bin/mkdirp.ps1 @@ -0,0 +1,28 @@ +#!/usr/bin/env pwsh +$basedir=Split-Path $MyInvocation.MyCommand.Definition -Parent + +$exe="" +if ($PSVersionTable.PSVersion -lt "6.0" -or $IsWindows) { + # Fix case when both the Windows and Linux builds of Node + # are installed in the same directory + $exe=".exe" +} +$ret=0 +if (Test-Path "$basedir/node$exe") { + # Support pipeline input + if ($MyInvocation.ExpectingInput) { + $input | & "$basedir/node$exe" "$basedir/../mkdirp/bin/cmd.js" $args + } else { + & "$basedir/node$exe" "$basedir/../mkdirp/bin/cmd.js" $args + } + $ret=$LASTEXITCODE +} else { + # Support pipeline input + if ($MyInvocation.ExpectingInput) { + $input | & "node$exe" "$basedir/../mkdirp/bin/cmd.js" $args + } else { + & "node$exe" "$basedir/../mkdirp/bin/cmd.js" $args + } + $ret=$LASTEXITCODE +} +exit $ret diff --git a/backend/node_modules/append-field/.npmignore b/backend/node_modules/append-field/.npmignore new file mode 100644 index 0000000..c2658d7 --- /dev/null +++ b/backend/node_modules/append-field/.npmignore @@ -0,0 +1 @@ +node_modules/ diff --git a/backend/node_modules/append-field/LICENSE b/backend/node_modules/append-field/LICENSE new file mode 100644 index 0000000..14b1f89 --- /dev/null +++ b/backend/node_modules/append-field/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2015 Linus Unnebäck + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/backend/node_modules/append-field/README.md b/backend/node_modules/append-field/README.md new file mode 100644 index 0000000..62b901b --- /dev/null +++ b/backend/node_modules/append-field/README.md @@ -0,0 +1,44 @@ +# `append-field` + +A [W3C HTML JSON forms spec](http://www.w3.org/TR/html-json-forms/) compliant +field appender (for lack of a better name). Useful for people implementing +`application/x-www-form-urlencoded` and `multipart/form-data` parsers. + +It works best on objects created with `Object.create(null)`. Otherwise it might +conflict with variables from the prototype (e.g. `hasOwnProperty`). + +## Installation + +```sh +npm install --save append-field +``` + +## Usage + +```javascript +var appendField = require('append-field') +var obj = Object.create(null) + +appendField(obj, 'pets[0][species]', 'Dahut') +appendField(obj, 'pets[0][name]', 'Hypatia') +appendField(obj, 'pets[1][species]', 'Felis Stultus') +appendField(obj, 'pets[1][name]', 'Billie') + +console.log(obj) +``` + +```text +{ pets: + [ { species: 'Dahut', name: 'Hypatia' }, + { species: 'Felis Stultus', name: 'Billie' } ] } +``` + +## API + +### `appendField(store, key, value)` + +Adds the field named `key` with the value `value` to the object `store`. + +## License + +MIT diff --git a/backend/node_modules/append-field/index.js b/backend/node_modules/append-field/index.js new file mode 100644 index 0000000..fc5acc8 --- /dev/null +++ b/backend/node_modules/append-field/index.js @@ -0,0 +1,12 @@ +var parsePath = require('./lib/parse-path') +var setValue = require('./lib/set-value') + +function appendField (store, key, value) { + var steps = parsePath(key) + + steps.reduce(function (context, step) { + return setValue(context, step, context[step.key], value) + }, store) +} + +module.exports = appendField diff --git a/backend/node_modules/append-field/lib/parse-path.js b/backend/node_modules/append-field/lib/parse-path.js new file mode 100644 index 0000000..31d6179 --- /dev/null +++ b/backend/node_modules/append-field/lib/parse-path.js @@ -0,0 +1,53 @@ +var reFirstKey = /^[^\[]*/ +var reDigitPath = /^\[(\d+)\]/ +var reNormalPath = /^\[([^\]]+)\]/ + +function parsePath (key) { + function failure () { + return [{ type: 'object', key: key, last: true }] + } + + var firstKey = reFirstKey.exec(key)[0] + if (!firstKey) return failure() + + var len = key.length + var pos = firstKey.length + var tail = { type: 'object', key: firstKey } + var steps = [tail] + + while (pos < len) { + var m + + if (key[pos] === '[' && key[pos + 1] === ']') { + pos += 2 + tail.append = true + if (pos !== len) return failure() + continue + } + + m = reDigitPath.exec(key.substring(pos)) + if (m !== null) { + pos += m[0].length + tail.nextType = 'array' + tail = { type: 'array', key: parseInt(m[1], 10) } + steps.push(tail) + continue + } + + m = reNormalPath.exec(key.substring(pos)) + if (m !== null) { + pos += m[0].length + tail.nextType = 'object' + tail = { type: 'object', key: m[1] } + steps.push(tail) + continue + } + + return failure() + } + + tail.last = true + return steps +} + +module.exports = parsePath diff --git a/backend/node_modules/append-field/lib/set-value.js b/backend/node_modules/append-field/lib/set-value.js new file mode 100644 index 0000000..c15e873 --- /dev/null +++ b/backend/node_modules/append-field/lib/set-value.js @@ -0,0 +1,64 @@ +function valueType (value) { + if (value === undefined) return 'undefined' + if (Array.isArray(value)) return 'array' + if (typeof value === 'object') return 'object' + return 'scalar' +} + +function setLastValue (context, step, currentValue, entryValue) { + switch (valueType(currentValue)) { + case 'undefined': + if (step.append) { + context[step.key] = [entryValue] + } else { + context[step.key] = entryValue + } + break + case 'array': + context[step.key].push(entryValue) + break + case 'object': + return setLastValue(currentValue, { type: 'object', key: '', last: true }, currentValue[''], entryValue) + case 'scalar': + context[step.key] = [context[step.key], entryValue] + break + } + + return context +} + +function setValue (context, step, currentValue, entryValue) { + if (step.last) return setLastValue(context, step, currentValue, entryValue) + + var obj + switch (valueType(currentValue)) { + case 'undefined': + if (step.nextType === 'array') { + context[step.key] = [] + } else { + context[step.key] = Object.create(null) + } + return context[step.key] + case 'object': + return context[step.key] + case 'array': + if (step.nextType === 'array') { + return currentValue + } + + obj = Object.create(null) + context[step.key] = obj + currentValue.forEach(function (item, i) { + if (item !== undefined) obj['' + i] = item + }) + + return obj + case 'scalar': + obj = Object.create(null) + obj[''] = currentValue + context[step.key] = obj + return obj + } +} + +module.exports = setValue diff --git a/backend/node_modules/append-field/package.json b/backend/node_modules/append-field/package.json new file mode 100644 index 0000000..8d6e716 --- /dev/null +++ b/backend/node_modules/append-field/package.json @@ -0,0 +1,19 @@ +{ + "name": "append-field", + "version": "1.0.0", + "license": "MIT", + "author": "Linus Unnebäck ", + "main": "index.js", + "devDependencies": { + "mocha": "^2.2.4", + "standard": "^6.0.5", + "testdata-w3c-json-form": "^0.2.0" + }, + "scripts": { + "test": "standard && mocha" + }, + "repository": { + "type": "git", + "url": "http://github.com/LinusU/node-append-field.git" + } +} diff --git a/backend/node_modules/append-field/test/forms.js b/backend/node_modules/append-field/test/forms.js new file mode 100644 index 0000000..dd6fbc9 --- /dev/null +++ b/backend/node_modules/append-field/test/forms.js @@ -0,0 +1,19 @@ +/* eslint-env mocha */ + +var assert = require('assert') +var appendField = require('../') +var testData = require('testdata-w3c-json-form') + +describe('Append Field', function () { + for (var test of testData) { + it('handles ' + test.name, function () { + var store = Object.create(null) + + for (var field of test.fields) { + appendField(store, field.key, field.value) + } + + assert.deepEqual(store, test.expected) + }) + } +}) diff --git a/backend/node_modules/buffer-from/LICENSE b/backend/node_modules/buffer-from/LICENSE new file mode 100644 index 0000000..e4bf1d6 --- /dev/null +++ b/backend/node_modules/buffer-from/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2016, 2018 Linus Unnebäck + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/backend/node_modules/buffer-from/index.js b/backend/node_modules/buffer-from/index.js new file mode 100644 index 0000000..e1a58b5 --- /dev/null +++ b/backend/node_modules/buffer-from/index.js @@ -0,0 +1,72 @@ +/* eslint-disable node/no-deprecated-api */ + +var toString = Object.prototype.toString + +var isModern = ( + typeof Buffer !== 'undefined' && + typeof Buffer.alloc === 'function' && + typeof Buffer.allocUnsafe === 'function' && + typeof Buffer.from === 'function' +) + +function isArrayBuffer (input) { + return toString.call(input).slice(8, -1) === 'ArrayBuffer' +} + +function fromArrayBuffer (obj, byteOffset, length) { + byteOffset >>>= 0 + + var maxLength = obj.byteLength - byteOffset + + if (maxLength < 0) { + throw new RangeError("'offset' is out of bounds") + } + + if (length === undefined) { + length = maxLength + } else { + length >>>= 0 + + if (length > maxLength) { + throw new RangeError("'length' is out of bounds") + } + } + + return isModern + ? Buffer.from(obj.slice(byteOffset, byteOffset + length)) + : new Buffer(new Uint8Array(obj.slice(byteOffset, byteOffset + length))) +} + +function fromString (string, encoding) { + if (typeof encoding !== 'string' || encoding === '') { + encoding = 'utf8' + } + + if (!Buffer.isEncoding(encoding)) { + throw new TypeError('"encoding" must be a valid string encoding') + } + + return isModern + ? Buffer.from(string, encoding) + : new Buffer(string, encoding) +} + +function bufferFrom (value, encodingOrOffset, length) { + if (typeof value === 'number') { + throw new TypeError('"value" argument must not be a number') + } + + if (isArrayBuffer(value)) { + return fromArrayBuffer(value, encodingOrOffset, length) + } + + if (typeof value === 'string') { + return fromString(value, encodingOrOffset) + } + + return isModern + ? Buffer.from(value) + : new Buffer(value) +} + +module.exports = bufferFrom diff --git a/backend/node_modules/buffer-from/package.json b/backend/node_modules/buffer-from/package.json new file mode 100644 index 0000000..6ac5327 --- /dev/null +++ b/backend/node_modules/buffer-from/package.json @@ -0,0 +1,19 @@ +{ + "name": "buffer-from", + "version": "1.1.2", + "license": "MIT", + "repository": "LinusU/buffer-from", + "files": [ + "index.js" + ], + "scripts": { + "test": "standard && node test" + }, + "devDependencies": { + "standard": "^12.0.1" + }, + "keywords": [ + "buffer", + "buffer from" + ] +} diff --git a/backend/node_modules/buffer-from/readme.md b/backend/node_modules/buffer-from/readme.md new file mode 100644 index 0000000..9880a55 --- /dev/null +++ b/backend/node_modules/buffer-from/readme.md @@ -0,0 +1,69 @@ +# Buffer From + +A [ponyfill](https://ponyfill.com) for `Buffer.from`, uses native implementation if available. + +## Installation + +```sh +npm install --save buffer-from +``` + +## Usage + +```js +const bufferFrom = require('buffer-from') + +console.log(bufferFrom([1, 2, 3, 4])) +//=> + +const arr = new Uint8Array([1, 2, 3, 4]) +console.log(bufferFrom(arr.buffer, 1, 2)) +//=> + +console.log(bufferFrom('test', 'utf8')) +//=> + +const buf = bufferFrom('test') +console.log(bufferFrom(buf)) +//=> +``` + +## API + +### bufferFrom(array) + +- `array` <Array> + +Allocates a new `Buffer` using an `array` of octets. + +### bufferFrom(arrayBuffer[, byteOffset[, length]]) + +- `arrayBuffer` <ArrayBuffer> The `.buffer` property of a TypedArray or ArrayBuffer +- `byteOffset` <Integer> Where to start copying from `arrayBuffer`. **Default:** `0` +- `length` <Integer> How many bytes to copy from `arrayBuffer`. **Default:** `arrayBuffer.length - byteOffset` + +When passed a reference to the `.buffer` property of a TypedArray instance, the +newly created `Buffer` will share the same allocated memory as the TypedArray. + +The optional `byteOffset` and `length` arguments specify a memory range within +the `arrayBuffer` that will be shared by the `Buffer`. + +### bufferFrom(buffer) + +- `buffer` <Buffer> An existing `Buffer` to copy data from + +Copies the passed `buffer` data onto a new `Buffer` instance. + +### bufferFrom(string[, encoding]) + +- `string` <String> A string to encode. +- `encoding` <String> The encoding of `string`. **Default:** `'utf8'` + +Creates a new `Buffer` containing the given JavaScript string `string`. If +provided, the `encoding` parameter identifies the character encoding of +`string`. + +## See also + +- [buffer-alloc](https://github.com/LinusU/buffer-alloc) A ponyfill for `Buffer.alloc` +- [buffer-alloc-unsafe](https://github.com/LinusU/buffer-alloc-unsafe) A ponyfill for `Buffer.allocUnsafe` diff --git a/backend/node_modules/busboy/.eslintrc.js b/backend/node_modules/busboy/.eslintrc.js new file mode 100644 index 0000000..be9311d --- /dev/null +++ b/backend/node_modules/busboy/.eslintrc.js @@ -0,0 +1,5 @@ +'use strict'; + +module.exports = { + extends: '@mscdex/eslint-config', +}; diff --git a/backend/node_modules/busboy/.github/workflows/ci.yml b/backend/node_modules/busboy/.github/workflows/ci.yml new file mode 100644 index 0000000..799bae0 --- /dev/null +++ b/backend/node_modules/busboy/.github/workflows/ci.yml @@ -0,0 +1,24 @@ +name: CI + +on: + pull_request: + push: + branches: [ master ] + +jobs: + tests-linux: + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + node-version: [10.16.0, 10.x, 12.x, 14.x, 16.x] + steps: + - uses: actions/checkout@v2 + - name: Use Node.js ${{ matrix.node-version }} + uses: actions/setup-node@v1 + with: + node-version: ${{ matrix.node-version }} + - name: Install module + run: npm install + - name: Run tests + run: npm test diff --git a/backend/node_modules/busboy/.github/workflows/lint.yml b/backend/node_modules/busboy/.github/workflows/lint.yml new file mode 100644 index 0000000..9f9e1f5 --- /dev/null +++ b/backend/node_modules/busboy/.github/workflows/lint.yml @@ -0,0 +1,23 @@ +name: lint + +on: + pull_request: + push: + branches: [ master ] + +env: + NODE_VERSION: 16.x + +jobs: + lint-js: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - name: Use Node.js ${{ env.NODE_VERSION }} + uses: actions/setup-node@v1 + with: + node-version: ${{ env.NODE_VERSION }} + - name: Install ESLint + ESLint configs/plugins + run: npm install --only=dev + - name: Lint files + run: npm run lint diff --git a/backend/node_modules/busboy/LICENSE b/backend/node_modules/busboy/LICENSE new file mode 100644 index 0000000..290762e --- /dev/null +++ b/backend/node_modules/busboy/LICENSE @@ -0,0 +1,19 @@ +Copyright Brian White. All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to +deal in the Software without restriction, including without limitation the +rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +sell copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +IN THE SOFTWARE. \ No newline at end of file diff --git a/backend/node_modules/busboy/README.md b/backend/node_modules/busboy/README.md new file mode 100644 index 0000000..654af30 --- /dev/null +++ b/backend/node_modules/busboy/README.md @@ -0,0 +1,191 @@ +# Description + +A node.js module for parsing incoming HTML form data. + +Changes (breaking or otherwise) in v1.0.0 can be found [here](https://github.com/mscdex/busboy/issues/266). + +# Requirements + +* [node.js](http://nodejs.org/) -- v10.16.0 or newer + + +# Install + + npm install busboy + + +# Examples + +* Parsing (multipart) with default options: + +```js +const http = require('http'); + +const busboy = require('busboy'); + +http.createServer((req, res) => { + if (req.method === 'POST') { + console.log('POST request'); + const bb = busboy({ headers: req.headers }); + bb.on('file', (name, file, info) => { + const { filename, encoding, mimeType } = info; + console.log( + `File [${name}]: filename: %j, encoding: %j, mimeType: %j`, + filename, + encoding, + mimeType + ); + file.on('data', (data) => { + console.log(`File [${name}] got ${data.length} bytes`); + }).on('close', () => { + console.log(`File [${name}] done`); + }); + }); + bb.on('field', (name, val, info) => { + console.log(`Field [${name}]: value: %j`, val); + }); + bb.on('close', () => { + console.log('Done parsing form!'); + res.writeHead(303, { Connection: 'close', Location: '/' }); + res.end(); + }); + req.pipe(bb); + } else if (req.method === 'GET') { + res.writeHead(200, { Connection: 'close' }); + res.end(` + + + +
+
+
+ +
+ + + `); + } +}).listen(8000, () => { + console.log('Listening for requests'); +}); + +// Example output: +// +// Listening for requests +// < ... form submitted ... > +// POST request +// File [filefield]: filename: "logo.jpg", encoding: "binary", mime: "image/jpeg" +// File [filefield] got 11912 bytes +// Field [textfield]: value: "testing! :-)" +// File [filefield] done +// Done parsing form! +``` + +* Save all incoming files to disk: + +```js +const { randomFillSync } = require('crypto'); +const fs = require('fs'); +const http = require('http'); +const os = require('os'); +const path = require('path'); + +const busboy = require('busboy'); + +const random = (() => { + const buf = Buffer.alloc(16); + return () => randomFillSync(buf).toString('hex'); +})(); + +http.createServer((req, res) => { + if (req.method === 'POST') { + const bb = busboy({ headers: req.headers }); + bb.on('file', (name, file, info) => { + const saveTo = path.join(os.tmpdir(), `busboy-upload-${random()}`); + file.pipe(fs.createWriteStream(saveTo)); + }); + bb.on('close', () => { + res.writeHead(200, { 'Connection': 'close' }); + res.end(`That's all folks!`); + }); + req.pipe(bb); + return; + } + res.writeHead(404); + res.end(); +}).listen(8000, () => { + console.log('Listening for requests'); +}); +``` + + +# API + +## Exports + +`busboy` exports a single function: + +**( _function_ )**(< _object_ >config) - Creates and returns a new _Writable_ form parser stream. + +* Valid `config` properties: + + * **headers** - _object_ - These are the HTTP headers of the incoming request, which are used by individual parsers. + + * **highWaterMark** - _integer_ - highWaterMark to use for the parser stream. **Default:** node's _stream.Writable_ default. + + * **fileHwm** - _integer_ - highWaterMark to use for individual file streams. **Default:** node's _stream.Readable_ default. + + * **defCharset** - _string_ - Default character set to use when one isn't defined. **Default:** `'utf8'`. + + * **defParamCharset** - _string_ - For multipart forms, the default character set to use for values of part header parameters (e.g. filename) that are not extended parameters (that contain an explicit charset). **Default:** `'latin1'`. + + * **preservePath** - _boolean_ - If paths in filenames from file parts in a `'multipart/form-data'` request shall be preserved. **Default:** `false`. + + * **limits** - _object_ - Various limits on incoming data. Valid properties are: + + * **fieldNameSize** - _integer_ - Max field name size (in bytes). **Default:** `100`. + + * **fieldSize** - _integer_ - Max field value size (in bytes). **Default:** `1048576` (1MB). + + * **fields** - _integer_ - Max number of non-file fields. **Default:** `Infinity`. + + * **fileSize** - _integer_ - For multipart forms, the max file size (in bytes). **Default:** `Infinity`. + + * **files** - _integer_ - For multipart forms, the max number of file fields. **Default:** `Infinity`. + + * **parts** - _integer_ - For multipart forms, the max number of parts (fields + files). **Default:** `Infinity`. + + * **headerPairs** - _integer_ - For multipart forms, the max number of header key-value pairs to parse. **Default:** `2000` (same as node's http module). + +This function can throw exceptions if there is something wrong with the values in `config`. For example, if the Content-Type in `headers` is missing entirely, is not a supported type, or is missing the boundary for `'multipart/form-data'` requests. + +## (Special) Parser stream events + +* **file**(< _string_ >name, < _Readable_ >stream, < _object_ >info) - Emitted for each new file found. `name` contains the form field name. `stream` is a _Readable_ stream containing the file's data. No transformations/conversions (e.g. base64 to raw binary) are done on the file's data. `info` contains the following properties: + + * `filename` - _string_ - If supplied, this contains the file's filename. **WARNING:** You should almost _never_ use this value as-is (especially if you are using `preservePath: true` in your `config`) as it could contain malicious input. You are better off generating your own (safe) filenames, or at the very least using a hash of the filename. + + * `encoding` - _string_ - The file's `'Content-Transfer-Encoding'` value. + + * `mimeType` - _string_ - The file's `'Content-Type'` value. + + **Note:** If you listen for this event, you should always consume the `stream` whether you care about its contents or not (you can simply do `stream.resume();` if you want to discard/skip the contents), otherwise the `'finish'`/`'close'` event will never fire on the busboy parser stream. + However, if you aren't accepting files, you can either simply not listen for the `'file'` event at all or set `limits.files` to `0`, and any/all files will be automatically skipped (these skipped files will still count towards any configured `limits.files` and `limits.parts` limits though). + + **Note:** If a configured `limits.fileSize` limit was reached for a file, `stream` will both have a boolean property `truncated` set to `true` (best checked at the end of the stream) and emit a `'limit'` event to notify you when this happens. + +* **field**(< _string_ >name, < _string_ >value, < _object_ >info) - Emitted for each new non-file field found. `name` contains the form field name. `value` contains the string value of the field. `info` contains the following properties: + + * `nameTruncated` - _boolean_ - Whether `name` was truncated or not (due to a configured `limits.fieldNameSize` limit) + + * `valueTruncated` - _boolean_ - Whether `value` was truncated or not (due to a configured `limits.fieldSize` limit) + + * `encoding` - _string_ - The field's `'Content-Transfer-Encoding'` value. + + * `mimeType` - _string_ - The field's `'Content-Type'` value. + +* **partsLimit**() - Emitted when the configured `limits.parts` limit has been reached. No more `'file'` or `'field'` events will be emitted. + +* **filesLimit**() - Emitted when the configured `limits.files` limit has been reached. No more `'file'` events will be emitted. + +* **fieldsLimit**() - Emitted when the configured `limits.fields` limit has been reached. No more `'field'` events will be emitted. diff --git a/backend/node_modules/busboy/bench/bench-multipart-fields-100mb-big.js b/backend/node_modules/busboy/bench/bench-multipart-fields-100mb-big.js new file mode 100644 index 0000000..ef15729 --- /dev/null +++ b/backend/node_modules/busboy/bench/bench-multipart-fields-100mb-big.js @@ -0,0 +1,149 @@ +'use strict'; + +function createMultipartBuffers(boundary, sizes) { + const bufs = []; + for (let i = 0; i < sizes.length; ++i) { + const mb = sizes[i] * 1024 * 1024; + bufs.push(Buffer.from([ + `--${boundary}`, + `content-disposition: form-data; name="field${i + 1}"`, + '', + '0'.repeat(mb), + '', + ].join('\r\n'))); + } + bufs.push(Buffer.from([ + `--${boundary}--`, + '', + ].join('\r\n'))); + return bufs; +} + +const boundary = '-----------------------------168072824752491622650073'; +const buffers = createMultipartBuffers(boundary, [ + 10, + 10, + 10, + 20, + 50, +]); +const calls = { + partBegin: 0, + headerField: 0, + headerValue: 0, + headerEnd: 0, + headersEnd: 0, + partData: 0, + partEnd: 0, + end: 0, +}; + +const moduleName = process.argv[2]; +switch (moduleName) { + case 'busboy': { + const busboy = require('busboy'); + + const parser = busboy({ + limits: { + fieldSizeLimit: Infinity, + }, + headers: { + 'content-type': `multipart/form-data; boundary=${boundary}`, + }, + }); + parser.on('field', (name, val, info) => { + ++calls.partBegin; + ++calls.partData; + ++calls.partEnd; + }).on('close', () => { + ++calls.end; + console.timeEnd(moduleName); + }); + + console.time(moduleName); + for (const buf of buffers) + parser.write(buf); + break; + } + + case 'formidable': { + const { MultipartParser } = require('formidable'); + + const parser = new MultipartParser(); + parser.initWithBoundary(boundary); + parser.on('data', ({ name }) => { + ++calls[name]; + if (name === 'end') + console.timeEnd(moduleName); + }); + + console.time(moduleName); + for (const buf of buffers) + parser.write(buf); + + break; + } + + case 'multiparty': { + const { Readable } = require('stream'); + + const { Form } = require('multiparty'); + + const form = new Form({ + maxFieldsSize: Infinity, + maxFields: Infinity, + maxFilesSize: Infinity, + autoFields: false, + autoFiles: false, + }); + + const req = new Readable({ read: () => {} }); + req.headers = { + 'content-type': `multipart/form-data; boundary=${boundary}`, + }; + + function hijack(name, fn) { + const oldFn = form[name]; + form[name] = function() { + fn(); + return oldFn.apply(this, arguments); + }; + } + + hijack('onParseHeaderField', () => { + ++calls.headerField; + }); + hijack('onParseHeaderValue', () => { + ++calls.headerValue; + }); + hijack('onParsePartBegin', () => { + ++calls.partBegin; + }); + hijack('onParsePartData', () => { + ++calls.partData; + }); + hijack('onParsePartEnd', () => { + ++calls.partEnd; + }); + + form.on('close', () => { + ++calls.end; + console.timeEnd(moduleName); + }).on('part', (p) => p.resume()); + + console.time(moduleName); + form.parse(req); + for (const buf of buffers) + req.push(buf); + req.push(null); + + break; + } + + default: + if (moduleName === undefined) + console.error('Missing parser module name'); + else + console.error(`Invalid parser module name: ${moduleName}`); + process.exit(1); +} diff --git a/backend/node_modules/busboy/bench/bench-multipart-fields-100mb-small.js b/backend/node_modules/busboy/bench/bench-multipart-fields-100mb-small.js new file mode 100644 index 0000000..f32d421 --- /dev/null +++ b/backend/node_modules/busboy/bench/bench-multipart-fields-100mb-small.js @@ -0,0 +1,143 @@ +'use strict'; + +function createMultipartBuffers(boundary, sizes) { + const bufs = []; + for (let i = 0; i < sizes.length; ++i) { + const mb = sizes[i] * 1024 * 1024; + bufs.push(Buffer.from([ + `--${boundary}`, + `content-disposition: form-data; name="field${i + 1}"`, + '', + '0'.repeat(mb), + '', + ].join('\r\n'))); + } + bufs.push(Buffer.from([ + `--${boundary}--`, + '', + ].join('\r\n'))); + return bufs; +} + +const boundary = '-----------------------------168072824752491622650073'; +const buffers = createMultipartBuffers(boundary, (new Array(100)).fill(1)); +const calls = { + partBegin: 0, + headerField: 0, + headerValue: 0, + headerEnd: 0, + headersEnd: 0, + partData: 0, + partEnd: 0, + end: 0, +}; + +const moduleName = process.argv[2]; +switch (moduleName) { + case 'busboy': { + const busboy = require('busboy'); + + const parser = busboy({ + limits: { + fieldSizeLimit: Infinity, + }, + headers: { + 'content-type': `multipart/form-data; boundary=${boundary}`, + }, + }); + parser.on('field', (name, val, info) => { + ++calls.partBegin; + ++calls.partData; + ++calls.partEnd; + }).on('close', () => { + ++calls.end; + console.timeEnd(moduleName); + }); + + console.time(moduleName); + for (const buf of buffers) + parser.write(buf); + break; + } + + case 'formidable': { + const { MultipartParser } = require('formidable'); + + const parser = new MultipartParser(); + parser.initWithBoundary(boundary); + parser.on('data', ({ name }) => { + ++calls[name]; + if (name === 'end') + console.timeEnd(moduleName); + }); + + console.time(moduleName); + for (const buf of buffers) + parser.write(buf); + + break; + } + + case 'multiparty': { + const { Readable } = require('stream'); + + const { Form } = require('multiparty'); + + const form = new Form({ + maxFieldsSize: Infinity, + maxFields: Infinity, + maxFilesSize: Infinity, + autoFields: false, + autoFiles: false, + }); + + const req = new Readable({ read: () => {} }); + req.headers = { + 'content-type': `multipart/form-data; boundary=${boundary}`, + }; + + function hijack(name, fn) { + const oldFn = form[name]; + form[name] = function() { + fn(); + return oldFn.apply(this, arguments); + }; + } + + hijack('onParseHeaderField', () => { + ++calls.headerField; + }); + hijack('onParseHeaderValue', () => { + ++calls.headerValue; + }); + hijack('onParsePartBegin', () => { + ++calls.partBegin; + }); + hijack('onParsePartData', () => { + ++calls.partData; + }); + hijack('onParsePartEnd', () => { + ++calls.partEnd; + }); + + form.on('close', () => { + ++calls.end; + console.timeEnd(moduleName); + }).on('part', (p) => p.resume()); + + console.time(moduleName); + form.parse(req); + for (const buf of buffers) + req.push(buf); + req.push(null); + + break; + } + + default: + if (moduleName === undefined) + console.error('Missing parser module name'); + else + console.error(`Invalid parser module name: ${moduleName}`); + process.exit(1); +} diff --git a/backend/node_modules/busboy/bench/bench-multipart-files-100mb-big.js b/backend/node_modules/busboy/bench/bench-multipart-files-100mb-big.js new file mode 100644 index 0000000..b46bdee --- /dev/null +++ b/backend/node_modules/busboy/bench/bench-multipart-files-100mb-big.js @@ -0,0 +1,154 @@ +'use strict'; + +function createMultipartBuffers(boundary, sizes) { + const bufs = []; + for (let i = 0; i < sizes.length; ++i) { + const mb = sizes[i] * 1024 * 1024; + bufs.push(Buffer.from([ + `--${boundary}`, + `content-disposition: form-data; name="file${i + 1}"; ` + + `filename="random${i + 1}.bin"`, + 'content-type: application/octet-stream', + '', + '0'.repeat(mb), + '', + ].join('\r\n'))); + } + bufs.push(Buffer.from([ + `--${boundary}--`, + '', + ].join('\r\n'))); + return bufs; +} + +const boundary = '-----------------------------168072824752491622650073'; +const buffers = createMultipartBuffers(boundary, [ + 10, + 10, + 10, + 20, + 50, +]); +const calls = { + partBegin: 0, + headerField: 0, + headerValue: 0, + headerEnd: 0, + headersEnd: 0, + partData: 0, + partEnd: 0, + end: 0, +}; + +const moduleName = process.argv[2]; +switch (moduleName) { + case 'busboy': { + const busboy = require('busboy'); + + const parser = busboy({ + limits: { + fieldSizeLimit: Infinity, + }, + headers: { + 'content-type': `multipart/form-data; boundary=${boundary}`, + }, + }); + parser.on('file', (name, stream, info) => { + ++calls.partBegin; + stream.on('data', (chunk) => { + ++calls.partData; + }).on('end', () => { + ++calls.partEnd; + }); + }).on('close', () => { + ++calls.end; + console.timeEnd(moduleName); + }); + + console.time(moduleName); + for (const buf of buffers) + parser.write(buf); + break; + } + + case 'formidable': { + const { MultipartParser } = require('formidable'); + + const parser = new MultipartParser(); + parser.initWithBoundary(boundary); + parser.on('data', ({ name }) => { + ++calls[name]; + if (name === 'end') + console.timeEnd(moduleName); + }); + + console.time(moduleName); + for (const buf of buffers) + parser.write(buf); + + break; + } + + case 'multiparty': { + const { Readable } = require('stream'); + + const { Form } = require('multiparty'); + + const form = new Form({ + maxFieldsSize: Infinity, + maxFields: Infinity, + maxFilesSize: Infinity, + autoFields: false, + autoFiles: false, + }); + + const req = new Readable({ read: () => {} }); + req.headers = { + 'content-type': `multipart/form-data; boundary=${boundary}`, + }; + + function hijack(name, fn) { + const oldFn = form[name]; + form[name] = function() { + fn(); + return oldFn.apply(this, arguments); + }; + } + + hijack('onParseHeaderField', () => { + ++calls.headerField; + }); + hijack('onParseHeaderValue', () => { + ++calls.headerValue; + }); + hijack('onParsePartBegin', () => { + ++calls.partBegin; + }); + hijack('onParsePartData', () => { + ++calls.partData; + }); + hijack('onParsePartEnd', () => { + ++calls.partEnd; + }); + + form.on('close', () => { + ++calls.end; + console.timeEnd(moduleName); + }).on('part', (p) => p.resume()); + + console.time(moduleName); + form.parse(req); + for (const buf of buffers) + req.push(buf); + req.push(null); + + break; + } + + default: + if (moduleName === undefined) + console.error('Missing parser module name'); + else + console.error(`Invalid parser module name: ${moduleName}`); + process.exit(1); +} diff --git a/backend/node_modules/busboy/bench/bench-multipart-files-100mb-small.js b/backend/node_modules/busboy/bench/bench-multipart-files-100mb-small.js new file mode 100644 index 0000000..46b5dff --- /dev/null +++ b/backend/node_modules/busboy/bench/bench-multipart-files-100mb-small.js @@ -0,0 +1,148 @@ +'use strict'; + +function createMultipartBuffers(boundary, sizes) { + const bufs = []; + for (let i = 0; i < sizes.length; ++i) { + const mb = sizes[i] * 1024 * 1024; + bufs.push(Buffer.from([ + `--${boundary}`, + `content-disposition: form-data; name="file${i + 1}"; ` + + `filename="random${i + 1}.bin"`, + 'content-type: application/octet-stream', + '', + '0'.repeat(mb), + '', + ].join('\r\n'))); + } + bufs.push(Buffer.from([ + `--${boundary}--`, + '', + ].join('\r\n'))); + return bufs; +} + +const boundary = '-----------------------------168072824752491622650073'; +const buffers = createMultipartBuffers(boundary, (new Array(100)).fill(1)); +const calls = { + partBegin: 0, + headerField: 0, + headerValue: 0, + headerEnd: 0, + headersEnd: 0, + partData: 0, + partEnd: 0, + end: 0, +}; + +const moduleName = process.argv[2]; +switch (moduleName) { + case 'busboy': { + const busboy = require('busboy'); + + const parser = busboy({ + limits: { + fieldSizeLimit: Infinity, + }, + headers: { + 'content-type': `multipart/form-data; boundary=${boundary}`, + }, + }); + parser.on('file', (name, stream, info) => { + ++calls.partBegin; + stream.on('data', (chunk) => { + ++calls.partData; + }).on('end', () => { + ++calls.partEnd; + }); + }).on('close', () => { + ++calls.end; + console.timeEnd(moduleName); + }); + + console.time(moduleName); + for (const buf of buffers) + parser.write(buf); + break; + } + + case 'formidable': { + const { MultipartParser } = require('formidable'); + + const parser = new MultipartParser(); + parser.initWithBoundary(boundary); + parser.on('data', ({ name }) => { + ++calls[name]; + if (name === 'end') + console.timeEnd(moduleName); + }); + + console.time(moduleName); + for (const buf of buffers) + parser.write(buf); + + break; + } + + case 'multiparty': { + const { Readable } = require('stream'); + + const { Form } = require('multiparty'); + + const form = new Form({ + maxFieldsSize: Infinity, + maxFields: Infinity, + maxFilesSize: Infinity, + autoFields: false, + autoFiles: false, + }); + + const req = new Readable({ read: () => {} }); + req.headers = { + 'content-type': `multipart/form-data; boundary=${boundary}`, + }; + + function hijack(name, fn) { + const oldFn = form[name]; + form[name] = function() { + fn(); + return oldFn.apply(this, arguments); + }; + } + + hijack('onParseHeaderField', () => { + ++calls.headerField; + }); + hijack('onParseHeaderValue', () => { + ++calls.headerValue; + }); + hijack('onParsePartBegin', () => { + ++calls.partBegin; + }); + hijack('onParsePartData', () => { + ++calls.partData; + }); + hijack('onParsePartEnd', () => { + ++calls.partEnd; + }); + + form.on('close', () => { + ++calls.end; + console.timeEnd(moduleName); + }).on('part', (p) => p.resume()); + + console.time(moduleName); + form.parse(req); + for (const buf of buffers) + req.push(buf); + req.push(null); + + break; + } + + default: + if (moduleName === undefined) + console.error('Missing parser module name'); + else + console.error(`Invalid parser module name: ${moduleName}`); + process.exit(1); +} diff --git a/backend/node_modules/busboy/bench/bench-urlencoded-fields-100pairs-small.js b/backend/node_modules/busboy/bench/bench-urlencoded-fields-100pairs-small.js new file mode 100644 index 0000000..5c337df --- /dev/null +++ b/backend/node_modules/busboy/bench/bench-urlencoded-fields-100pairs-small.js @@ -0,0 +1,101 @@ +'use strict'; + +const buffers = [ + Buffer.from( + (new Array(100)).fill('').map((_, i) => `key${i}=value${i}`).join('&') + ), +]; +const calls = { + field: 0, + end: 0, +}; + +let n = 3e3; + +const moduleName = process.argv[2]; +switch (moduleName) { + case 'busboy': { + const busboy = require('busboy'); + + console.time(moduleName); + (function next() { + const parser = busboy({ + limits: { + fieldSizeLimit: Infinity, + }, + headers: { + 'content-type': 'application/x-www-form-urlencoded; charset=utf-8', + }, + }); + parser.on('field', (name, val, info) => { + ++calls.field; + }).on('close', () => { + ++calls.end; + if (--n === 0) + console.timeEnd(moduleName); + else + process.nextTick(next); + }); + + for (const buf of buffers) + parser.write(buf); + parser.end(); + })(); + break; + } + + case 'formidable': { + const QuerystringParser = + require('formidable/src/parsers/Querystring.js'); + + console.time(moduleName); + (function next() { + const parser = new QuerystringParser(); + parser.on('data', (obj) => { + ++calls.field; + }).on('end', () => { + ++calls.end; + if (--n === 0) + console.timeEnd(moduleName); + else + process.nextTick(next); + }); + + for (const buf of buffers) + parser.write(buf); + parser.end(); + })(); + break; + } + + case 'formidable-streaming': { + const QuerystringParser = + require('formidable/src/parsers/StreamingQuerystring.js'); + + console.time(moduleName); + (function next() { + const parser = new QuerystringParser(); + parser.on('data', (obj) => { + ++calls.field; + }).on('end', () => { + ++calls.end; + if (--n === 0) + console.timeEnd(moduleName); + else + process.nextTick(next); + }); + + for (const buf of buffers) + parser.write(buf); + parser.end(); + })(); + break; + } + + default: + if (moduleName === undefined) + console.error('Missing parser module name'); + else + console.error(`Invalid parser module name: ${moduleName}`); + process.exit(1); +} diff --git a/backend/node_modules/busboy/bench/bench-urlencoded-fields-900pairs-small-alt.js b/backend/node_modules/busboy/bench/bench-urlencoded-fields-900pairs-small-alt.js new file mode 100644 index 0000000..1f5645c --- /dev/null +++ b/backend/node_modules/busboy/bench/bench-urlencoded-fields-900pairs-small-alt.js @@ -0,0 +1,84 @@ +'use strict'; + +const buffers = [ + Buffer.from( + (new Array(900)).fill('').map((_, i) => `key${i}=value${i}`).join('&') + ), +]; +const calls = { + field: 0, + end: 0, +}; + +const moduleName = process.argv[2]; +switch (moduleName) { + case 'busboy': { + const busboy = require('busboy'); + + console.time(moduleName); + const parser = busboy({ + limits: { + fieldSizeLimit: Infinity, + }, + headers: { + 'content-type': 'application/x-www-form-urlencoded; charset=utf-8', + }, + }); + parser.on('field', (name, val, info) => { + ++calls.field; + }).on('close', () => { + ++calls.end; + console.timeEnd(moduleName); + }); + + for (const buf of buffers) + parser.write(buf); + parser.end(); + break; + } + + case 'formidable': { + const QuerystringParser = + require('formidable/src/parsers/Querystring.js'); + + console.time(moduleName); + const parser = new QuerystringParser(); + parser.on('data', (obj) => { + ++calls.field; + }).on('end', () => { + ++calls.end; + console.timeEnd(moduleName); + }); + + for (const buf of buffers) + parser.write(buf); + parser.end(); + break; + } + + case 'formidable-streaming': { + const QuerystringParser = + require('formidable/src/parsers/StreamingQuerystring.js'); + + console.time(moduleName); + const parser = new QuerystringParser(); + parser.on('data', (obj) => { + ++calls.field; + }).on('end', () => { + ++calls.end; + console.timeEnd(moduleName); + }); + + for (const buf of buffers) + parser.write(buf); + parser.end(); + break; + } + + default: + if (moduleName === undefined) + console.error('Missing parser module name'); + else + console.error(`Invalid parser module name: ${moduleName}`); + process.exit(1); +} diff --git a/backend/node_modules/busboy/lib/index.js b/backend/node_modules/busboy/lib/index.js new file mode 100644 index 0000000..873272d --- /dev/null +++ b/backend/node_modules/busboy/lib/index.js @@ -0,0 +1,57 @@ +'use strict'; + +const { parseContentType } = require('./utils.js'); + +function getInstance(cfg) { + const headers = cfg.headers; + const conType = parseContentType(headers['content-type']); + if (!conType) + throw new Error('Malformed content type'); + + for (const type of TYPES) { + const matched = type.detect(conType); + if (!matched) + continue; + + const instanceCfg = { + limits: cfg.limits, + headers, + conType, + highWaterMark: undefined, + fileHwm: undefined, + defCharset: undefined, + defParamCharset: undefined, + preservePath: false, + }; + if (cfg.highWaterMark) + instanceCfg.highWaterMark = cfg.highWaterMark; + if (cfg.fileHwm) + instanceCfg.fileHwm = cfg.fileHwm; + instanceCfg.defCharset = cfg.defCharset; + instanceCfg.defParamCharset = cfg.defParamCharset; + instanceCfg.preservePath = cfg.preservePath; + return new type(instanceCfg); + } + + throw new Error(`Unsupported content type: ${headers['content-type']}`); +} + +// Note: types are explicitly listed here for easier bundling +// See: https://github.com/mscdex/busboy/issues/121 +const TYPES = [ + require('./types/multipart'), + require('./types/urlencoded'), +].filter(function(typemod) { return typeof typemod.detect === 'function'; }); + +module.exports = (cfg) => { + if (typeof cfg !== 'object' || cfg === null) + cfg = {}; + + if (typeof cfg.headers !== 'object' + || cfg.headers === null + || typeof cfg.headers['content-type'] !== 'string') { + throw new Error('Missing Content-Type'); + } + + return getInstance(cfg); +}; diff --git a/backend/node_modules/busboy/lib/types/multipart.js b/backend/node_modules/busboy/lib/types/multipart.js new file mode 100644 index 0000000..cc0d7bb --- /dev/null +++ b/backend/node_modules/busboy/lib/types/multipart.js @@ -0,0 +1,653 @@ +'use strict'; + +const { Readable, Writable } = require('stream'); + +const StreamSearch = require('streamsearch'); + +const { + basename, + convertToUTF8, + getDecoder, + parseContentType, + parseDisposition, +} = require('../utils.js'); + +const BUF_CRLF = Buffer.from('\r\n'); +const BUF_CR = Buffer.from('\r'); +const BUF_DASH = Buffer.from('-'); + +function noop() {} + +const MAX_HEADER_PAIRS = 2000; // From node +const MAX_HEADER_SIZE = 16 * 1024; // From node (its default value) + +const HPARSER_NAME = 0; +const HPARSER_PRE_OWS = 1; +const HPARSER_VALUE = 2; +class HeaderParser { + constructor(cb) { + this.header = Object.create(null); + this.pairCount = 0; + this.byteCount = 0; + this.state = HPARSER_NAME; + this.name = ''; + this.value = ''; + this.crlf = 0; + this.cb = cb; + } + + reset() { + this.header = Object.create(null); + this.pairCount = 0; + this.byteCount = 0; + this.state = HPARSER_NAME; + this.name = ''; + this.value = ''; + this.crlf = 0; + } + + push(chunk, pos, end) { + let start = pos; + while (pos < end) { + switch (this.state) { + case HPARSER_NAME: { + let done = false; + for (; pos < end; ++pos) { + if (this.byteCount === MAX_HEADER_SIZE) + return -1; + ++this.byteCount; + const code = chunk[pos]; + if (TOKEN[code] !== 1) { + if (code !== 58/* ':' */) + return -1; + this.name += chunk.latin1Slice(start, pos); + if (this.name.length === 0) + return -1; + ++pos; + done = true; + this.state = HPARSER_PRE_OWS; + break; + } + } + if (!done) { + this.name += chunk.latin1Slice(start, pos); + break; + } + // FALLTHROUGH + } + case HPARSER_PRE_OWS: { + // Skip optional whitespace + let done = false; + for (; pos < end; ++pos) { + if (this.byteCount === MAX_HEADER_SIZE) + return -1; + ++this.byteCount; + const code = chunk[pos]; + if (code !== 32/* ' ' */ && code !== 9/* '\t' */) { + start = pos; + done = true; + this.state = HPARSER_VALUE; + break; + } + } + if (!done) + break; + // FALLTHROUGH + } + case HPARSER_VALUE: + switch (this.crlf) { + case 0: // Nothing yet + for (; pos < end; ++pos) { + if (this.byteCount === MAX_HEADER_SIZE) + return -1; + ++this.byteCount; + const code = chunk[pos]; + if (FIELD_VCHAR[code] !== 1) { + if (code !== 13/* '\r' */) + return -1; + ++this.crlf; + break; + } + } + this.value += chunk.latin1Slice(start, pos++); + break; + case 1: // Received CR + if (this.byteCount === MAX_HEADER_SIZE) + return -1; + ++this.byteCount; + if (chunk[pos++] !== 10/* '\n' */) + return -1; + ++this.crlf; + break; + case 2: { // Received CR LF + if (this.byteCount === MAX_HEADER_SIZE) + return -1; + ++this.byteCount; + const code = chunk[pos]; + if (code === 32/* ' ' */ || code === 9/* '\t' */) { + // Folded value + start = pos; + this.crlf = 0; + } else { + if (++this.pairCount < MAX_HEADER_PAIRS) { + this.name = this.name.toLowerCase(); + if (this.header[this.name] === undefined) + this.header[this.name] = [this.value]; + else + this.header[this.name].push(this.value); + } + if (code === 13/* '\r' */) { + ++this.crlf; + ++pos; + } else { + // Assume start of next header field name + start = pos; + this.crlf = 0; + this.state = HPARSER_NAME; + this.name = ''; + this.value = ''; + } + } + break; + } + case 3: { // Received CR LF CR + if (this.byteCount === MAX_HEADER_SIZE) + return -1; + ++this.byteCount; + if (chunk[pos++] !== 10/* '\n' */) + return -1; + // End of header + const header = this.header; + this.reset(); + this.cb(header); + return pos; + } + } + break; + } + } + + return pos; + } +} + +class FileStream extends Readable { + constructor(opts, owner) { + super(opts); + this.truncated = false; + this._readcb = null; + this.once('end', () => { + // We need to make sure that we call any outstanding _writecb() that is + // associated with this file so that processing of the rest of the form + // can continue. This may not happen if the file stream ends right after + // backpressure kicks in, so we force it here. + this._read(); + if (--owner._fileEndsLeft === 0 && owner._finalcb) { + const cb = owner._finalcb; + owner._finalcb = null; + // Make sure other 'end' event handlers get a chance to be executed + // before busboy's 'finish' event is emitted + process.nextTick(cb); + } + }); + } + _read(n) { + const cb = this._readcb; + if (cb) { + this._readcb = null; + cb(); + } + } +} + +const ignoreData = { + push: (chunk, pos) => {}, + destroy: () => {}, +}; + +function callAndUnsetCb(self, err) { + const cb = self._writecb; + self._writecb = null; + if (err) + self.destroy(err); + else if (cb) + cb(); +} + +function nullDecoder(val, hint) { + return val; +} + +class Multipart extends Writable { + constructor(cfg) { + const streamOpts = { + autoDestroy: true, + emitClose: true, + highWaterMark: (typeof cfg.highWaterMark === 'number' + ? cfg.highWaterMark + : undefined), + }; + super(streamOpts); + + if (!cfg.conType.params || typeof cfg.conType.params.boundary !== 'string') + throw new Error('Multipart: Boundary not found'); + + const boundary = cfg.conType.params.boundary; + const paramDecoder = (typeof cfg.defParamCharset === 'string' + && cfg.defParamCharset + ? getDecoder(cfg.defParamCharset) + : nullDecoder); + const defCharset = (cfg.defCharset || 'utf8'); + const preservePath = cfg.preservePath; + const fileOpts = { + autoDestroy: true, + emitClose: true, + highWaterMark: (typeof cfg.fileHwm === 'number' + ? cfg.fileHwm + : undefined), + }; + + const limits = cfg.limits; + const fieldSizeLimit = (limits && typeof limits.fieldSize === 'number' + ? limits.fieldSize + : 1 * 1024 * 1024); + const fileSizeLimit = (limits && typeof limits.fileSize === 'number' + ? limits.fileSize + : Infinity); + const filesLimit = (limits && typeof limits.files === 'number' + ? limits.files + : Infinity); + const fieldsLimit = (limits && typeof limits.fields === 'number' + ? limits.fields + : Infinity); + const partsLimit = (limits && typeof limits.parts === 'number' + ? limits.parts + : Infinity); + + let parts = -1; // Account for initial boundary + let fields = 0; + let files = 0; + let skipPart = false; + + this._fileEndsLeft = 0; + this._fileStream = undefined; + this._complete = false; + let fileSize = 0; + + let field; + let fieldSize = 0; + let partCharset; + let partEncoding; + let partType; + let partName; + let partTruncated = false; + + let hitFilesLimit = false; + let hitFieldsLimit = false; + + this._hparser = null; + const hparser = new HeaderParser((header) => { + this._hparser = null; + skipPart = false; + + partType = 'text/plain'; + partCharset = defCharset; + partEncoding = '7bit'; + partName = undefined; + partTruncated = false; + + let filename; + if (!header['content-disposition']) { + skipPart = true; + return; + } + + const disp = parseDisposition(header['content-disposition'][0], + paramDecoder); + if (!disp || disp.type !== 'form-data') { + skipPart = true; + return; + } + + if (disp.params) { + if (disp.params.name) + partName = disp.params.name; + + if (disp.params['filename*']) + filename = disp.params['filename*']; + else if (disp.params.filename) + filename = disp.params.filename; + + if (filename !== undefined && !preservePath) + filename = basename(filename); + } + + if (header['content-type']) { + const conType = parseContentType(header['content-type'][0]); + if (conType) { + partType = `${conType.type}/${conType.subtype}`; + if (conType.params && typeof conType.params.charset === 'string') + partCharset = conType.params.charset.toLowerCase(); + } + } + + if (header['content-transfer-encoding']) + partEncoding = header['content-transfer-encoding'][0].toLowerCase(); + + if (partType === 'application/octet-stream' || filename !== undefined) { + // File + + if (files === filesLimit) { + if (!hitFilesLimit) { + hitFilesLimit = true; + this.emit('filesLimit'); + } + skipPart = true; + return; + } + ++files; + + if (this.listenerCount('file') === 0) { + skipPart = true; + return; + } + + fileSize = 0; + this._fileStream = new FileStream(fileOpts, this); + ++this._fileEndsLeft; + this.emit( + 'file', + partName, + this._fileStream, + { filename, + encoding: partEncoding, + mimeType: partType } + ); + } else { + // Non-file + + if (fields === fieldsLimit) { + if (!hitFieldsLimit) { + hitFieldsLimit = true; + this.emit('fieldsLimit'); + } + skipPart = true; + return; + } + ++fields; + + if (this.listenerCount('field') === 0) { + skipPart = true; + return; + } + + field = []; + fieldSize = 0; + } + }); + + let matchPostBoundary = 0; + const ssCb = (isMatch, data, start, end, isDataSafe) => { +retrydata: + while (data) { + if (this._hparser !== null) { + const ret = this._hparser.push(data, start, end); + if (ret === -1) { + this._hparser = null; + hparser.reset(); + this.emit('error', new Error('Malformed part header')); + break; + } + start = ret; + } + + if (start === end) + break; + + if (matchPostBoundary !== 0) { + if (matchPostBoundary === 1) { + switch (data[start]) { + case 45: // '-' + // Try matching '--' after boundary + matchPostBoundary = 2; + ++start; + break; + case 13: // '\r' + // Try matching CR LF before header + matchPostBoundary = 3; + ++start; + break; + default: + matchPostBoundary = 0; + } + if (start === end) + return; + } + + if (matchPostBoundary === 2) { + matchPostBoundary = 0; + if (data[start] === 45/* '-' */) { + // End of multipart data + this._complete = true; + this._bparser = ignoreData; + return; + } + // We saw something other than '-', so put the dash we consumed + // "back" + const writecb = this._writecb; + this._writecb = noop; + ssCb(false, BUF_DASH, 0, 1, false); + this._writecb = writecb; + } else if (matchPostBoundary === 3) { + matchPostBoundary = 0; + if (data[start] === 10/* '\n' */) { + ++start; + if (parts >= partsLimit) + break; + // Prepare the header parser + this._hparser = hparser; + if (start === end) + break; + // Process the remaining data as a header + continue retrydata; + } else { + // We saw something other than LF, so put the CR we consumed + // "back" + const writecb = this._writecb; + this._writecb = noop; + ssCb(false, BUF_CR, 0, 1, false); + this._writecb = writecb; + } + } + } + + if (!skipPart) { + if (this._fileStream) { + let chunk; + const actualLen = Math.min(end - start, fileSizeLimit - fileSize); + if (!isDataSafe) { + chunk = Buffer.allocUnsafe(actualLen); + data.copy(chunk, 0, start, start + actualLen); + } else { + chunk = data.slice(start, start + actualLen); + } + + fileSize += chunk.length; + if (fileSize === fileSizeLimit) { + if (chunk.length > 0) + this._fileStream.push(chunk); + this._fileStream.emit('limit'); + this._fileStream.truncated = true; + skipPart = true; + } else if (!this._fileStream.push(chunk)) { + if (this._writecb) + this._fileStream._readcb = this._writecb; + this._writecb = null; + } + } else if (field !== undefined) { + let chunk; + const actualLen = Math.min( + end - start, + fieldSizeLimit - fieldSize + ); + if (!isDataSafe) { + chunk = Buffer.allocUnsafe(actualLen); + data.copy(chunk, 0, start, start + actualLen); + } else { + chunk = data.slice(start, start + actualLen); + } + + fieldSize += actualLen; + field.push(chunk); + if (fieldSize === fieldSizeLimit) { + skipPart = true; + partTruncated = true; + } + } + } + + break; + } + + if (isMatch) { + matchPostBoundary = 1; + + if (this._fileStream) { + // End the active file stream if the previous part was a file + this._fileStream.push(null); + this._fileStream = null; + } else if (field !== undefined) { + let data; + switch (field.length) { + case 0: + data = ''; + break; + case 1: + data = convertToUTF8(field[0], partCharset, 0); + break; + default: + data = convertToUTF8( + Buffer.concat(field, fieldSize), + partCharset, + 0 + ); + } + field = undefined; + fieldSize = 0; + this.emit( + 'field', + partName, + data, + { nameTruncated: false, + valueTruncated: partTruncated, + encoding: partEncoding, + mimeType: partType } + ); + } + + if (++parts === partsLimit) + this.emit('partsLimit'); + } + }; + this._bparser = new StreamSearch(`\r\n--${boundary}`, ssCb); + + this._writecb = null; + this._finalcb = null; + + // Just in case there is no preamble + this.write(BUF_CRLF); + } + + static detect(conType) { + return (conType.type === 'multipart' && conType.subtype === 'form-data'); + } + + _write(chunk, enc, cb) { + this._writecb = cb; + this._bparser.push(chunk, 0); + if (this._writecb) + callAndUnsetCb(this); + } + + _destroy(err, cb) { + this._hparser = null; + this._bparser = ignoreData; + if (!err) + err = checkEndState(this); + const fileStream = this._fileStream; + if (fileStream) { + this._fileStream = null; + fileStream.destroy(err); + } + cb(err); + } + + _final(cb) { + this._bparser.destroy(); + if (!this._complete) + return cb(new Error('Unexpected end of form')); + if (this._fileEndsLeft) + this._finalcb = finalcb.bind(null, this, cb); + else + finalcb(this, cb); + } +} + +function finalcb(self, cb, err) { + if (err) + return cb(err); + err = checkEndState(self); + cb(err); +} + +function checkEndState(self) { + if (self._hparser) + return new Error('Malformed part header'); + const fileStream = self._fileStream; + if (fileStream) { + self._fileStream = null; + fileStream.destroy(new Error('Unexpected end of file')); + } + if (!self._complete) + return new Error('Unexpected end of form'); +} + +const TOKEN = [ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 1, 0, 1, 1, 1, 1, 1, 0, 0, 1, 1, 0, 1, 1, 0, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, + 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 0, 1, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +]; + +const FIELD_VCHAR = [ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, +]; + +module.exports = Multipart; diff --git a/backend/node_modules/busboy/lib/types/urlencoded.js b/backend/node_modules/busboy/lib/types/urlencoded.js new file mode 100644 index 0000000..5c463a2 --- /dev/null +++ b/backend/node_modules/busboy/lib/types/urlencoded.js @@ -0,0 +1,350 @@ +'use strict'; + +const { Writable } = require('stream'); + +const { getDecoder } = require('../utils.js'); + +class URLEncoded extends Writable { + constructor(cfg) { + const streamOpts = { + autoDestroy: true, + emitClose: true, + highWaterMark: (typeof cfg.highWaterMark === 'number' + ? cfg.highWaterMark + : undefined), + }; + super(streamOpts); + + let charset = (cfg.defCharset || 'utf8'); + if (cfg.conType.params && typeof cfg.conType.params.charset === 'string') + charset = cfg.conType.params.charset; + + this.charset = charset; + + const limits = cfg.limits; + this.fieldSizeLimit = (limits && typeof limits.fieldSize === 'number' + ? limits.fieldSize + : 1 * 1024 * 1024); + this.fieldsLimit = (limits && typeof limits.fields === 'number' + ? limits.fields + : Infinity); + this.fieldNameSizeLimit = ( + limits && typeof limits.fieldNameSize === 'number' + ? limits.fieldNameSize + : 100 + ); + + this._inKey = true; + this._keyTrunc = false; + this._valTrunc = false; + this._bytesKey = 0; + this._bytesVal = 0; + this._fields = 0; + this._key = ''; + this._val = ''; + this._byte = -2; + this._lastPos = 0; + this._encode = 0; + this._decoder = getDecoder(charset); + } + + static detect(conType) { + return (conType.type === 'application' + && conType.subtype === 'x-www-form-urlencoded'); + } + + _write(chunk, enc, cb) { + if (this._fields >= this.fieldsLimit) + return cb(); + + let i = 0; + const len = chunk.length; + this._lastPos = 0; + + // Check if we last ended mid-percent-encoded byte + if (this._byte !== -2) { + i = readPctEnc(this, chunk, i, len); + if (i === -1) + return cb(new Error('Malformed urlencoded form')); + if (i >= len) + return cb(); + if (this._inKey) + ++this._bytesKey; + else + ++this._bytesVal; + } + +main: + while (i < len) { + if (this._inKey) { + // Parsing key + + i = skipKeyBytes(this, chunk, i, len); + + while (i < len) { + switch (chunk[i]) { + case 61: // '=' + if (this._lastPos < i) + this._key += chunk.latin1Slice(this._lastPos, i); + this._lastPos = ++i; + this._key = this._decoder(this._key, this._encode); + this._encode = 0; + this._inKey = false; + continue main; + case 38: // '&' + if (this._lastPos < i) + this._key += chunk.latin1Slice(this._lastPos, i); + this._lastPos = ++i; + this._key = this._decoder(this._key, this._encode); + this._encode = 0; + if (this._bytesKey > 0) { + this.emit( + 'field', + this._key, + '', + { nameTruncated: this._keyTrunc, + valueTruncated: false, + encoding: this.charset, + mimeType: 'text/plain' } + ); + } + this._key = ''; + this._val = ''; + this._keyTrunc = false; + this._valTrunc = false; + this._bytesKey = 0; + this._bytesVal = 0; + if (++this._fields >= this.fieldsLimit) { + this.emit('fieldsLimit'); + return cb(); + } + continue; + case 43: // '+' + if (this._lastPos < i) + this._key += chunk.latin1Slice(this._lastPos, i); + this._key += ' '; + this._lastPos = i + 1; + break; + case 37: // '%' + if (this._encode === 0) + this._encode = 1; + if (this._lastPos < i) + this._key += chunk.latin1Slice(this._lastPos, i); + this._lastPos = i + 1; + this._byte = -1; + i = readPctEnc(this, chunk, i + 1, len); + if (i === -1) + return cb(new Error('Malformed urlencoded form')); + if (i >= len) + return cb(); + ++this._bytesKey; + i = skipKeyBytes(this, chunk, i, len); + continue; + } + ++i; + ++this._bytesKey; + i = skipKeyBytes(this, chunk, i, len); + } + if (this._lastPos < i) + this._key += chunk.latin1Slice(this._lastPos, i); + } else { + // Parsing value + + i = skipValBytes(this, chunk, i, len); + + while (i < len) { + switch (chunk[i]) { + case 38: // '&' + if (this._lastPos < i) + this._val += chunk.latin1Slice(this._lastPos, i); + this._lastPos = ++i; + this._inKey = true; + this._val = this._decoder(this._val, this._encode); + this._encode = 0; + if (this._bytesKey > 0 || this._bytesVal > 0) { + this.emit( + 'field', + this._key, + this._val, + { nameTruncated: this._keyTrunc, + valueTruncated: this._valTrunc, + encoding: this.charset, + mimeType: 'text/plain' } + ); + } + this._key = ''; + this._val = ''; + this._keyTrunc = false; + this._valTrunc = false; + this._bytesKey = 0; + this._bytesVal = 0; + if (++this._fields >= this.fieldsLimit) { + this.emit('fieldsLimit'); + return cb(); + } + continue main; + case 43: // '+' + if (this._lastPos < i) + this._val += chunk.latin1Slice(this._lastPos, i); + this._val += ' '; + this._lastPos = i + 1; + break; + case 37: // '%' + if (this._encode === 0) + this._encode = 1; + if (this._lastPos < i) + this._val += chunk.latin1Slice(this._lastPos, i); + this._lastPos = i + 1; + this._byte = -1; + i = readPctEnc(this, chunk, i + 1, len); + if (i === -1) + return cb(new Error('Malformed urlencoded form')); + if (i >= len) + return cb(); + ++this._bytesVal; + i = skipValBytes(this, chunk, i, len); + continue; + } + ++i; + ++this._bytesVal; + i = skipValBytes(this, chunk, i, len); + } + if (this._lastPos < i) + this._val += chunk.latin1Slice(this._lastPos, i); + } + } + + cb(); + } + + _final(cb) { + if (this._byte !== -2) + return cb(new Error('Malformed urlencoded form')); + if (!this._inKey || this._bytesKey > 0 || this._bytesVal > 0) { + if (this._inKey) + this._key = this._decoder(this._key, this._encode); + else + this._val = this._decoder(this._val, this._encode); + this.emit( + 'field', + this._key, + this._val, + { nameTruncated: this._keyTrunc, + valueTruncated: this._valTrunc, + encoding: this.charset, + mimeType: 'text/plain' } + ); + } + cb(); + } +} + +function readPctEnc(self, chunk, pos, len) { + if (pos >= len) + return len; + + if (self._byte === -1) { + // We saw a '%' but no hex characters yet + const hexUpper = HEX_VALUES[chunk[pos++]]; + if (hexUpper === -1) + return -1; + + if (hexUpper >= 8) + self._encode = 2; // Indicate high bits detected + + if (pos < len) { + // Both hex characters are in this chunk + const hexLower = HEX_VALUES[chunk[pos++]]; + if (hexLower === -1) + return -1; + + if (self._inKey) + self._key += String.fromCharCode((hexUpper << 4) + hexLower); + else + self._val += String.fromCharCode((hexUpper << 4) + hexLower); + + self._byte = -2; + self._lastPos = pos; + } else { + // Only one hex character was available in this chunk + self._byte = hexUpper; + } + } else { + // We saw only one hex character so far + const hexLower = HEX_VALUES[chunk[pos++]]; + if (hexLower === -1) + return -1; + + if (self._inKey) + self._key += String.fromCharCode((self._byte << 4) + hexLower); + else + self._val += String.fromCharCode((self._byte << 4) + hexLower); + + self._byte = -2; + self._lastPos = pos; + } + + return pos; +} + +function skipKeyBytes(self, chunk, pos, len) { + // Skip bytes if we've truncated + if (self._bytesKey > self.fieldNameSizeLimit) { + if (!self._keyTrunc) { + if (self._lastPos < pos) + self._key += chunk.latin1Slice(self._lastPos, pos - 1); + } + self._keyTrunc = true; + for (; pos < len; ++pos) { + const code = chunk[pos]; + if (code === 61/* '=' */ || code === 38/* '&' */) + break; + ++self._bytesKey; + } + self._lastPos = pos; + } + + return pos; +} + +function skipValBytes(self, chunk, pos, len) { + // Skip bytes if we've truncated + if (self._bytesVal > self.fieldSizeLimit) { + if (!self._valTrunc) { + if (self._lastPos < pos) + self._val += chunk.latin1Slice(self._lastPos, pos - 1); + } + self._valTrunc = true; + for (; pos < len; ++pos) { + if (chunk[pos] === 38/* '&' */) + break; + ++self._bytesVal; + } + self._lastPos = pos; + } + + return pos; +} + +/* eslint-disable no-multi-spaces */ +const HEX_VALUES = [ + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, -1, -1, -1, -1, -1, -1, + -1, 10, 11, 12, 13, 14, 15, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, 10, 11, 12, 13, 14, 15, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, +]; +/* eslint-enable no-multi-spaces */ + +module.exports = URLEncoded; diff --git a/backend/node_modules/busboy/lib/utils.js b/backend/node_modules/busboy/lib/utils.js new file mode 100644 index 0000000..8274f6c --- /dev/null +++ b/backend/node_modules/busboy/lib/utils.js @@ -0,0 +1,596 @@ +'use strict'; + +function parseContentType(str) { + if (str.length === 0) + return; + + const params = Object.create(null); + let i = 0; + + // Parse type + for (; i < str.length; ++i) { + const code = str.charCodeAt(i); + if (TOKEN[code] !== 1) { + if (code !== 47/* '/' */ || i === 0) + return; + break; + } + } + // Check for type without subtype + if (i === str.length) + return; + + const type = str.slice(0, i).toLowerCase(); + + // Parse subtype + const subtypeStart = ++i; + for (; i < str.length; ++i) { + const code = str.charCodeAt(i); + if (TOKEN[code] !== 1) { + // Make sure we have a subtype + if (i === subtypeStart) + return; + + if (parseContentTypeParams(str, i, params) === undefined) + return; + break; + } + } + // Make sure we have a subtype + if (i === subtypeStart) + return; + + const subtype = str.slice(subtypeStart, i).toLowerCase(); + + return { type, subtype, params }; +} + +function parseContentTypeParams(str, i, params) { + while (i < str.length) { + // Consume whitespace + for (; i < str.length; ++i) { + const code = str.charCodeAt(i); + if (code !== 32/* ' ' */ && code !== 9/* '\t' */) + break; + } + + // Ended on whitespace + if (i === str.length) + break; + + // Check for malformed parameter + if (str.charCodeAt(i++) !== 59/* ';' */) + return; + + // Consume whitespace + for (; i < str.length; ++i) { + const code = str.charCodeAt(i); + if (code !== 32/* ' ' */ && code !== 9/* '\t' */) + break; + } + + // Ended on whitespace (malformed) + if (i === str.length) + return; + + let name; + const nameStart = i; + // Parse parameter name + for (; i < str.length; ++i) { + const code = str.charCodeAt(i); + if (TOKEN[code] !== 1) { + if (code !== 61/* '=' */) + return; + break; + } + } + + // No value (malformed) + if (i === str.length) + return; + + name = str.slice(nameStart, i); + ++i; // Skip over '=' + + // No value (malformed) + if (i === str.length) + return; + + let value = ''; + let valueStart; + if (str.charCodeAt(i) === 34/* '"' */) { + valueStart = ++i; + let escaping = false; + // Parse quoted value + for (; i < str.length; ++i) { + const code = str.charCodeAt(i); + if (code === 92/* '\\' */) { + if (escaping) { + valueStart = i; + escaping = false; + } else { + value += str.slice(valueStart, i); + escaping = true; + } + continue; + } + if (code === 34/* '"' */) { + if (escaping) { + valueStart = i; + escaping = false; + continue; + } + value += str.slice(valueStart, i); + break; + } + if (escaping) { + valueStart = i - 1; + escaping = false; + } + // Invalid unescaped quoted character (malformed) + if (QDTEXT[code] !== 1) + return; + } + + // No end quote (malformed) + if (i === str.length) + return; + + ++i; // Skip over double quote + } else { + valueStart = i; + // Parse unquoted value + for (; i < str.length; ++i) { + const code = str.charCodeAt(i); + if (TOKEN[code] !== 1) { + // No value (malformed) + if (i === valueStart) + return; + break; + } + } + value = str.slice(valueStart, i); + } + + name = name.toLowerCase(); + if (params[name] === undefined) + params[name] = value; + } + + return params; +} + +function parseDisposition(str, defDecoder) { + if (str.length === 0) + return; + + const params = Object.create(null); + let i = 0; + + for (; i < str.length; ++i) { + const code = str.charCodeAt(i); + if (TOKEN[code] !== 1) { + if (parseDispositionParams(str, i, params, defDecoder) === undefined) + return; + break; + } + } + + const type = str.slice(0, i).toLowerCase(); + + return { type, params }; +} + +function parseDispositionParams(str, i, params, defDecoder) { + while (i < str.length) { + // Consume whitespace + for (; i < str.length; ++i) { + const code = str.charCodeAt(i); + if (code !== 32/* ' ' */ && code !== 9/* '\t' */) + break; + } + + // Ended on whitespace + if (i === str.length) + break; + + // Check for malformed parameter + if (str.charCodeAt(i++) !== 59/* ';' */) + return; + + // Consume whitespace + for (; i < str.length; ++i) { + const code = str.charCodeAt(i); + if (code !== 32/* ' ' */ && code !== 9/* '\t' */) + break; + } + + // Ended on whitespace (malformed) + if (i === str.length) + return; + + let name; + const nameStart = i; + // Parse parameter name + for (; i < str.length; ++i) { + const code = str.charCodeAt(i); + if (TOKEN[code] !== 1) { + if (code === 61/* '=' */) + break; + return; + } + } + + // No value (malformed) + if (i === str.length) + return; + + let value = ''; + let valueStart; + let charset; + //~ let lang; + name = str.slice(nameStart, i); + if (name.charCodeAt(name.length - 1) === 42/* '*' */) { + // Extended value + + const charsetStart = ++i; + // Parse charset name + for (; i < str.length; ++i) { + const code = str.charCodeAt(i); + if (CHARSET[code] !== 1) { + if (code !== 39/* '\'' */) + return; + break; + } + } + + // Incomplete charset (malformed) + if (i === str.length) + return; + + charset = str.slice(charsetStart, i); + ++i; // Skip over the '\'' + + //~ const langStart = ++i; + // Parse language name + for (; i < str.length; ++i) { + const code = str.charCodeAt(i); + if (code === 39/* '\'' */) + break; + } + + // Incomplete language (malformed) + if (i === str.length) + return; + + //~ lang = str.slice(langStart, i); + ++i; // Skip over the '\'' + + // No value (malformed) + if (i === str.length) + return; + + valueStart = i; + + let encode = 0; + // Parse value + for (; i < str.length; ++i) { + const code = str.charCodeAt(i); + if (EXTENDED_VALUE[code] !== 1) { + if (code === 37/* '%' */) { + let hexUpper; + let hexLower; + if (i + 2 < str.length + && (hexUpper = HEX_VALUES[str.charCodeAt(i + 1)]) !== -1 + && (hexLower = HEX_VALUES[str.charCodeAt(i + 2)]) !== -1) { + const byteVal = (hexUpper << 4) + hexLower; + value += str.slice(valueStart, i); + value += String.fromCharCode(byteVal); + i += 2; + valueStart = i + 1; + if (byteVal >= 128) + encode = 2; + else if (encode === 0) + encode = 1; + continue; + } + // '%' disallowed in non-percent encoded contexts (malformed) + return; + } + break; + } + } + + value += str.slice(valueStart, i); + value = convertToUTF8(value, charset, encode); + if (value === undefined) + return; + } else { + // Non-extended value + + ++i; // Skip over '=' + + // No value (malformed) + if (i === str.length) + return; + + if (str.charCodeAt(i) === 34/* '"' */) { + valueStart = ++i; + let escaping = false; + // Parse quoted value + for (; i < str.length; ++i) { + const code = str.charCodeAt(i); + if (code === 92/* '\\' */) { + if (escaping) { + valueStart = i; + escaping = false; + } else { + value += str.slice(valueStart, i); + escaping = true; + } + continue; + } + if (code === 34/* '"' */) { + if (escaping) { + valueStart = i; + escaping = false; + continue; + } + value += str.slice(valueStart, i); + break; + } + if (escaping) { + valueStart = i - 1; + escaping = false; + } + // Invalid unescaped quoted character (malformed) + if (QDTEXT[code] !== 1) + return; + } + + // No end quote (malformed) + if (i === str.length) + return; + + ++i; // Skip over double quote + } else { + valueStart = i; + // Parse unquoted value + for (; i < str.length; ++i) { + const code = str.charCodeAt(i); + if (TOKEN[code] !== 1) { + // No value (malformed) + if (i === valueStart) + return; + break; + } + } + value = str.slice(valueStart, i); + } + + value = defDecoder(value, 2); + if (value === undefined) + return; + } + + name = name.toLowerCase(); + if (params[name] === undefined) + params[name] = value; + } + + return params; +} + +function getDecoder(charset) { + let lc; + while (true) { + switch (charset) { + case 'utf-8': + case 'utf8': + return decoders.utf8; + case 'latin1': + case 'ascii': // TODO: Make these a separate, strict decoder? + case 'us-ascii': + case 'iso-8859-1': + case 'iso8859-1': + case 'iso88591': + case 'iso_8859-1': + case 'windows-1252': + case 'iso_8859-1:1987': + case 'cp1252': + case 'x-cp1252': + return decoders.latin1; + case 'utf16le': + case 'utf-16le': + case 'ucs2': + case 'ucs-2': + return decoders.utf16le; + case 'base64': + return decoders.base64; + default: + if (lc === undefined) { + lc = true; + charset = charset.toLowerCase(); + continue; + } + return decoders.other.bind(charset); + } + } +} + +const decoders = { + utf8: (data, hint) => { + if (data.length === 0) + return ''; + if (typeof data === 'string') { + // If `data` never had any percent-encoded bytes or never had any that + // were outside of the ASCII range, then we can safely just return the + // input since UTF-8 is ASCII compatible + if (hint < 2) + return data; + + data = Buffer.from(data, 'latin1'); + } + return data.utf8Slice(0, data.length); + }, + + latin1: (data, hint) => { + if (data.length === 0) + return ''; + if (typeof data === 'string') + return data; + return data.latin1Slice(0, data.length); + }, + + utf16le: (data, hint) => { + if (data.length === 0) + return ''; + if (typeof data === 'string') + data = Buffer.from(data, 'latin1'); + return data.ucs2Slice(0, data.length); + }, + + base64: (data, hint) => { + if (data.length === 0) + return ''; + if (typeof data === 'string') + data = Buffer.from(data, 'latin1'); + return data.base64Slice(0, data.length); + }, + + other: (data, hint) => { + if (data.length === 0) + return ''; + if (typeof data === 'string') + data = Buffer.from(data, 'latin1'); + try { + const decoder = new TextDecoder(this); + return decoder.decode(data); + } catch {} + }, +}; + +function convertToUTF8(data, charset, hint) { + const decode = getDecoder(charset); + if (decode) + return decode(data, hint); +} + +function basename(path) { + if (typeof path !== 'string') + return ''; + for (let i = path.length - 1; i >= 0; --i) { + switch (path.charCodeAt(i)) { + case 0x2F: // '/' + case 0x5C: // '\' + path = path.slice(i + 1); + return (path === '..' || path === '.' ? '' : path); + } + } + return (path === '..' || path === '.' ? '' : path); +} + +const TOKEN = [ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 1, 0, 1, 1, 1, 1, 1, 0, 0, 1, 1, 0, 1, 1, 0, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, + 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 0, 1, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +]; + +const QDTEXT = [ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, +]; + +const CHARSET = [ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 1, 0, 1, 1, 1, 1, 0, 0, 0, 0, 1, 0, 1, 0, 0, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, + 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +]; + +const EXTENDED_VALUE = [ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 1, 0, 1, 1, 0, 1, 0, 0, 0, 0, 1, 0, 1, 1, 0, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, + 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 0, 1, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +]; + +/* eslint-disable no-multi-spaces */ +const HEX_VALUES = [ + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, -1, -1, -1, -1, -1, -1, + -1, 10, 11, 12, 13, 14, 15, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, 10, 11, 12, 13, 14, 15, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, +]; +/* eslint-enable no-multi-spaces */ + +module.exports = { + basename, + convertToUTF8, + getDecoder, + parseContentType, + parseDisposition, +}; diff --git a/backend/node_modules/busboy/package.json b/backend/node_modules/busboy/package.json new file mode 100644 index 0000000..ac2577f --- /dev/null +++ b/backend/node_modules/busboy/package.json @@ -0,0 +1,22 @@ +{ "name": "busboy", + "version": "1.6.0", + "author": "Brian White ", + "description": "A streaming parser for HTML form data for node.js", + "main": "./lib/index.js", + "dependencies": { + "streamsearch": "^1.1.0" + }, + "devDependencies": { + "@mscdex/eslint-config": "^1.1.0", + "eslint": "^7.32.0" + }, + "scripts": { + "test": "node test/test.js", + "lint": "eslint --cache --report-unused-disable-directives --ext=.js .eslintrc.js lib test bench", + "lint:fix": "npm run lint -- --fix" + }, + "engines": { "node": ">=10.16.0" }, + "keywords": [ "uploads", "forms", "multipart", "form-data" ], + "licenses": [ { "type": "MIT", "url": "http://github.com/mscdex/busboy/raw/master/LICENSE" } ], + "repository": { "type": "git", "url": "http://github.com/mscdex/busboy.git" } +} diff --git a/backend/node_modules/busboy/test/common.js b/backend/node_modules/busboy/test/common.js new file mode 100644 index 0000000..fb82ad8 --- /dev/null +++ b/backend/node_modules/busboy/test/common.js @@ -0,0 +1,109 @@ +'use strict'; + +const assert = require('assert'); +const { inspect } = require('util'); + +const mustCallChecks = []; + +function noop() {} + +function runCallChecks(exitCode) { + if (exitCode !== 0) return; + + const failed = mustCallChecks.filter((context) => { + if ('minimum' in context) { + context.messageSegment = `at least ${context.minimum}`; + return context.actual < context.minimum; + } + context.messageSegment = `exactly ${context.exact}`; + return context.actual !== context.exact; + }); + + failed.forEach((context) => { + console.error('Mismatched %s function calls. Expected %s, actual %d.', + context.name, + context.messageSegment, + context.actual); + console.error(context.stack.split('\n').slice(2).join('\n')); + }); + + if (failed.length) + process.exit(1); +} + +function mustCall(fn, exact) { + return _mustCallInner(fn, exact, 'exact'); +} + +function mustCallAtLeast(fn, minimum) { + return _mustCallInner(fn, minimum, 'minimum'); +} + +function _mustCallInner(fn, criteria = 1, field) { + if (process._exiting) + throw new Error('Cannot use common.mustCall*() in process exit handler'); + + if (typeof fn === 'number') { + criteria = fn; + fn = noop; + } else if (fn === undefined) { + fn = noop; + } + + if (typeof criteria !== 'number') + throw new TypeError(`Invalid ${field} value: ${criteria}`); + + const context = { + [field]: criteria, + actual: 0, + stack: inspect(new Error()), + name: fn.name || '' + }; + + // Add the exit listener only once to avoid listener leak warnings + if (mustCallChecks.length === 0) + process.on('exit', runCallChecks); + + mustCallChecks.push(context); + + function wrapped(...args) { + ++context.actual; + return fn.call(this, ...args); + } + // TODO: remove origFn? + wrapped.origFn = fn; + + return wrapped; +} + +function getCallSite(top) { + const originalStackFormatter = Error.prepareStackTrace; + Error.prepareStackTrace = (err, stack) => + `${stack[0].getFileName()}:${stack[0].getLineNumber()}`; + const err = new Error(); + Error.captureStackTrace(err, top); + // With the V8 Error API, the stack is not formatted until it is accessed + // eslint-disable-next-line no-unused-expressions + err.stack; + Error.prepareStackTrace = originalStackFormatter; + return err.stack; +} + +function mustNotCall(msg) { + const callSite = getCallSite(mustNotCall); + return function mustNotCall(...args) { + args = args.map(inspect).join(', '); + const argsInfo = (args.length > 0 + ? `\ncalled with arguments: ${args}` + : ''); + assert.fail( + `${msg || 'function should not have been called'} at ${callSite}` + + argsInfo); + }; +} + +module.exports = { + mustCall, + mustCallAtLeast, + mustNotCall, +}; diff --git a/backend/node_modules/busboy/test/test-types-multipart-charsets.js b/backend/node_modules/busboy/test/test-types-multipart-charsets.js new file mode 100644 index 0000000..ed9c38a --- /dev/null +++ b/backend/node_modules/busboy/test/test-types-multipart-charsets.js @@ -0,0 +1,94 @@ +'use strict'; + +const assert = require('assert'); +const { inspect } = require('util'); + +const { mustCall } = require(`${__dirname}/common.js`); + +const busboy = require('..'); + +const input = Buffer.from([ + '-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k', + 'Content-Disposition: form-data; ' + + 'name="upload_file_0"; filename="テスト.dat"', + 'Content-Type: application/octet-stream', + '', + 'A'.repeat(1023), + '-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k--' +].join('\r\n')); +const boundary = '---------------------------paZqsnEHRufoShdX6fh0lUhXBP4k'; +const expected = [ + { type: 'file', + name: 'upload_file_0', + data: Buffer.from('A'.repeat(1023)), + info: { + filename: 'テスト.dat', + encoding: '7bit', + mimeType: 'application/octet-stream', + }, + limited: false, + }, +]; +const bb = busboy({ + defParamCharset: 'utf8', + headers: { + 'content-type': `multipart/form-data; boundary=${boundary}`, + } +}); +const results = []; + +bb.on('field', (name, val, info) => { + results.push({ type: 'field', name, val, info }); +}); + +bb.on('file', (name, stream, info) => { + const data = []; + let nb = 0; + const file = { + type: 'file', + name, + data: null, + info, + limited: false, + }; + results.push(file); + stream.on('data', (d) => { + data.push(d); + nb += d.length; + }).on('limit', () => { + file.limited = true; + }).on('close', () => { + file.data = Buffer.concat(data, nb); + assert.strictEqual(stream.truncated, file.limited); + }).once('error', (err) => { + file.err = err.message; + }); +}); + +bb.on('error', (err) => { + results.push({ error: err.message }); +}); + +bb.on('partsLimit', () => { + results.push('partsLimit'); +}); + +bb.on('filesLimit', () => { + results.push('filesLimit'); +}); + +bb.on('fieldsLimit', () => { + results.push('fieldsLimit'); +}); + +bb.on('close', mustCall(() => { + assert.deepStrictEqual( + results, + expected, + 'Results mismatch.\n' + + `Parsed: ${inspect(results)}\n` + + `Expected: ${inspect(expected)}` + ); +})); + +bb.end(input); diff --git a/backend/node_modules/busboy/test/test-types-multipart-stream-pause.js b/backend/node_modules/busboy/test/test-types-multipart-stream-pause.js new file mode 100644 index 0000000..df7268a --- /dev/null +++ b/backend/node_modules/busboy/test/test-types-multipart-stream-pause.js @@ -0,0 +1,102 @@ +'use strict'; + +const assert = require('assert'); +const { randomFillSync } = require('crypto'); +const { inspect } = require('util'); + +const busboy = require('..'); + +const { mustCall } = require('./common.js'); + +const BOUNDARY = 'u2KxIV5yF1y+xUspOQCCZopaVgeV6Jxihv35XQJmuTx8X3sh'; + +function formDataSection(key, value) { + return Buffer.from( + `\r\n--${BOUNDARY}` + + `\r\nContent-Disposition: form-data; name="${key}"` + + `\r\n\r\n${value}` + ); +} + +function formDataFile(key, filename, contentType) { + const buf = Buffer.allocUnsafe(100000); + return Buffer.concat([ + Buffer.from(`\r\n--${BOUNDARY}\r\n`), + Buffer.from(`Content-Disposition: form-data; name="${key}"` + + `; filename="${filename}"\r\n`), + Buffer.from(`Content-Type: ${contentType}\r\n\r\n`), + randomFillSync(buf) + ]); +} + +const reqChunks = [ + Buffer.concat([ + formDataFile('file', 'file.bin', 'application/octet-stream'), + formDataSection('foo', 'foo value'), + ]), + formDataSection('bar', 'bar value'), + Buffer.from(`\r\n--${BOUNDARY}--\r\n`) +]; +const bb = busboy({ + headers: { + 'content-type': `multipart/form-data; boundary=${BOUNDARY}` + } +}); +const expected = [ + { type: 'file', + name: 'file', + info: { + filename: 'file.bin', + encoding: '7bit', + mimeType: 'application/octet-stream', + }, + }, + { type: 'field', + name: 'foo', + val: 'foo value', + info: { + nameTruncated: false, + valueTruncated: false, + encoding: '7bit', + mimeType: 'text/plain', + }, + }, + { type: 'field', + name: 'bar', + val: 'bar value', + info: { + nameTruncated: false, + valueTruncated: false, + encoding: '7bit', + mimeType: 'text/plain', + }, + }, +]; +const results = []; + +bb.on('field', (name, val, info) => { + results.push({ type: 'field', name, val, info }); +}); + +bb.on('file', (name, stream, info) => { + results.push({ type: 'file', name, info }); + // Simulate a pipe where the destination is pausing (perhaps due to waiting + // for file system write to finish) + setTimeout(() => { + stream.resume(); + }, 10); +}); + +bb.on('close', mustCall(() => { + assert.deepStrictEqual( + results, + expected, + 'Results mismatch.\n' + + `Parsed: ${inspect(results)}\n` + + `Expected: ${inspect(expected)}` + ); +})); + +for (const chunk of reqChunks) + bb.write(chunk); +bb.end(); diff --git a/backend/node_modules/busboy/test/test-types-multipart.js b/backend/node_modules/busboy/test/test-types-multipart.js new file mode 100644 index 0000000..9755642 --- /dev/null +++ b/backend/node_modules/busboy/test/test-types-multipart.js @@ -0,0 +1,1053 @@ +'use strict'; + +const assert = require('assert'); +const { inspect } = require('util'); + +const busboy = require('..'); + +const active = new Map(); + +const tests = [ + { source: [ + ['-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k', + 'Content-Disposition: form-data; name="file_name_0"', + '', + 'super alpha file', + '-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k', + 'Content-Disposition: form-data; name="file_name_1"', + '', + 'super beta file', + '-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k', + 'Content-Disposition: form-data; ' + + 'name="upload_file_0"; filename="1k_a.dat"', + 'Content-Type: application/octet-stream', + '', + 'A'.repeat(1023), + '-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k', + 'Content-Disposition: form-data; ' + + 'name="upload_file_1"; filename="1k_b.dat"', + 'Content-Type: application/octet-stream', + '', + 'B'.repeat(1023), + '-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k--' + ].join('\r\n') + ], + boundary: '---------------------------paZqsnEHRufoShdX6fh0lUhXBP4k', + expected: [ + { type: 'field', + name: 'file_name_0', + val: 'super alpha file', + info: { + nameTruncated: false, + valueTruncated: false, + encoding: '7bit', + mimeType: 'text/plain', + }, + }, + { type: 'field', + name: 'file_name_1', + val: 'super beta file', + info: { + nameTruncated: false, + valueTruncated: false, + encoding: '7bit', + mimeType: 'text/plain', + }, + }, + { type: 'file', + name: 'upload_file_0', + data: Buffer.from('A'.repeat(1023)), + info: { + filename: '1k_a.dat', + encoding: '7bit', + mimeType: 'application/octet-stream', + }, + limited: false, + }, + { type: 'file', + name: 'upload_file_1', + data: Buffer.from('B'.repeat(1023)), + info: { + filename: '1k_b.dat', + encoding: '7bit', + mimeType: 'application/octet-stream', + }, + limited: false, + }, + ], + what: 'Fields and files' + }, + { source: [ + ['------WebKitFormBoundaryTB2MiQ36fnSJlrhY', + 'Content-Disposition: form-data; name="cont"', + '', + 'some random content', + '------WebKitFormBoundaryTB2MiQ36fnSJlrhY', + 'Content-Disposition: form-data; name="pass"', + '', + 'some random pass', + '------WebKitFormBoundaryTB2MiQ36fnSJlrhY', + 'Content-Disposition: form-data; name=bit', + '', + '2', + '------WebKitFormBoundaryTB2MiQ36fnSJlrhY--' + ].join('\r\n') + ], + boundary: '----WebKitFormBoundaryTB2MiQ36fnSJlrhY', + expected: [ + { type: 'field', + name: 'cont', + val: 'some random content', + info: { + nameTruncated: false, + valueTruncated: false, + encoding: '7bit', + mimeType: 'text/plain', + }, + }, + { type: 'field', + name: 'pass', + val: 'some random pass', + info: { + nameTruncated: false, + valueTruncated: false, + encoding: '7bit', + mimeType: 'text/plain', + }, + }, + { type: 'field', + name: 'bit', + val: '2', + info: { + nameTruncated: false, + valueTruncated: false, + encoding: '7bit', + mimeType: 'text/plain', + }, + }, + ], + what: 'Fields only' + }, + { source: [ + '' + ], + boundary: '----WebKitFormBoundaryTB2MiQ36fnSJlrhY', + expected: [ + { error: 'Unexpected end of form' }, + ], + what: 'No fields and no files' + }, + { source: [ + ['-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k', + 'Content-Disposition: form-data; name="file_name_0"', + '', + 'super alpha file', + '-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k', + 'Content-Disposition: form-data; ' + + 'name="upload_file_0"; filename="1k_a.dat"', + 'Content-Type: application/octet-stream', + '', + 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', + '-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k--' + ].join('\r\n') + ], + boundary: '---------------------------paZqsnEHRufoShdX6fh0lUhXBP4k', + limits: { + fileSize: 13, + fieldSize: 5 + }, + expected: [ + { type: 'field', + name: 'file_name_0', + val: 'super', + info: { + nameTruncated: false, + valueTruncated: true, + encoding: '7bit', + mimeType: 'text/plain', + }, + }, + { type: 'file', + name: 'upload_file_0', + data: Buffer.from('ABCDEFGHIJKLM'), + info: { + filename: '1k_a.dat', + encoding: '7bit', + mimeType: 'application/octet-stream', + }, + limited: true, + }, + ], + what: 'Fields and files (limits)' + }, + { source: [ + ['-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k', + 'Content-Disposition: form-data; name="file_name_0"', + '', + 'super alpha file', + '-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k', + 'Content-Disposition: form-data; ' + + 'name="upload_file_0"; filename="1k_a.dat"', + 'Content-Type: application/octet-stream', + '', + 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', + '-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k--' + ].join('\r\n') + ], + boundary: '---------------------------paZqsnEHRufoShdX6fh0lUhXBP4k', + limits: { + files: 0 + }, + expected: [ + { type: 'field', + name: 'file_name_0', + val: 'super alpha file', + info: { + nameTruncated: false, + valueTruncated: false, + encoding: '7bit', + mimeType: 'text/plain', + }, + }, + 'filesLimit', + ], + what: 'Fields and files (limits: 0 files)' + }, + { source: [ + ['-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k', + 'Content-Disposition: form-data; name="file_name_0"', + '', + 'super alpha file', + '-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k', + 'Content-Disposition: form-data; name="file_name_1"', + '', + 'super beta file', + '-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k', + 'Content-Disposition: form-data; ' + + 'name="upload_file_0"; filename="1k_a.dat"', + 'Content-Type: application/octet-stream', + '', + 'A'.repeat(1023), + '-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k', + 'Content-Disposition: form-data; ' + + 'name="upload_file_1"; filename="1k_b.dat"', + 'Content-Type: application/octet-stream', + '', + 'B'.repeat(1023), + '-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k--' + ].join('\r\n') + ], + boundary: '---------------------------paZqsnEHRufoShdX6fh0lUhXBP4k', + expected: [ + { type: 'field', + name: 'file_name_0', + val: 'super alpha file', + info: { + nameTruncated: false, + valueTruncated: false, + encoding: '7bit', + mimeType: 'text/plain', + }, + }, + { type: 'field', + name: 'file_name_1', + val: 'super beta file', + info: { + nameTruncated: false, + valueTruncated: false, + encoding: '7bit', + mimeType: 'text/plain', + }, + }, + ], + events: ['field'], + what: 'Fields and (ignored) files' + }, + { source: [ + ['-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k', + 'Content-Disposition: form-data; ' + + 'name="upload_file_0"; filename="/tmp/1k_a.dat"', + 'Content-Type: application/octet-stream', + '', + 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', + '-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k', + 'Content-Disposition: form-data; ' + + 'name="upload_file_1"; filename="C:\\files\\1k_b.dat"', + 'Content-Type: application/octet-stream', + '', + 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', + '-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k', + 'Content-Disposition: form-data; ' + + 'name="upload_file_2"; filename="relative/1k_c.dat"', + 'Content-Type: application/octet-stream', + '', + 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', + '-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k--' + ].join('\r\n') + ], + boundary: '---------------------------paZqsnEHRufoShdX6fh0lUhXBP4k', + expected: [ + { type: 'file', + name: 'upload_file_0', + data: Buffer.from('ABCDEFGHIJKLMNOPQRSTUVWXYZ'), + info: { + filename: '1k_a.dat', + encoding: '7bit', + mimeType: 'application/octet-stream', + }, + limited: false, + }, + { type: 'file', + name: 'upload_file_1', + data: Buffer.from('ABCDEFGHIJKLMNOPQRSTUVWXYZ'), + info: { + filename: '1k_b.dat', + encoding: '7bit', + mimeType: 'application/octet-stream', + }, + limited: false, + }, + { type: 'file', + name: 'upload_file_2', + data: Buffer.from('ABCDEFGHIJKLMNOPQRSTUVWXYZ'), + info: { + filename: '1k_c.dat', + encoding: '7bit', + mimeType: 'application/octet-stream', + }, + limited: false, + }, + ], + what: 'Files with filenames containing paths' + }, + { source: [ + ['-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k', + 'Content-Disposition: form-data; ' + + 'name="upload_file_0"; filename="/absolute/1k_a.dat"', + 'Content-Type: application/octet-stream', + '', + 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', + '-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k', + 'Content-Disposition: form-data; ' + + 'name="upload_file_1"; filename="C:\\absolute\\1k_b.dat"', + 'Content-Type: application/octet-stream', + '', + 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', + '-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k', + 'Content-Disposition: form-data; ' + + 'name="upload_file_2"; filename="relative/1k_c.dat"', + 'Content-Type: application/octet-stream', + '', + 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', + '-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k--' + ].join('\r\n') + ], + boundary: '---------------------------paZqsnEHRufoShdX6fh0lUhXBP4k', + preservePath: true, + expected: [ + { type: 'file', + name: 'upload_file_0', + data: Buffer.from('ABCDEFGHIJKLMNOPQRSTUVWXYZ'), + info: { + filename: '/absolute/1k_a.dat', + encoding: '7bit', + mimeType: 'application/octet-stream', + }, + limited: false, + }, + { type: 'file', + name: 'upload_file_1', + data: Buffer.from('ABCDEFGHIJKLMNOPQRSTUVWXYZ'), + info: { + filename: 'C:\\absolute\\1k_b.dat', + encoding: '7bit', + mimeType: 'application/octet-stream', + }, + limited: false, + }, + { type: 'file', + name: 'upload_file_2', + data: Buffer.from('ABCDEFGHIJKLMNOPQRSTUVWXYZ'), + info: { + filename: 'relative/1k_c.dat', + encoding: '7bit', + mimeType: 'application/octet-stream', + }, + limited: false, + }, + ], + what: 'Paths to be preserved through the preservePath option' + }, + { source: [ + ['------WebKitFormBoundaryTB2MiQ36fnSJlrhY', + 'Content-Disposition: form-data; name="cont"', + 'Content-Type: ', + '', + 'some random content', + '------WebKitFormBoundaryTB2MiQ36fnSJlrhY', + 'Content-Disposition: ', + '', + 'some random pass', + '------WebKitFormBoundaryTB2MiQ36fnSJlrhY--' + ].join('\r\n') + ], + boundary: '----WebKitFormBoundaryTB2MiQ36fnSJlrhY', + expected: [ + { type: 'field', + name: 'cont', + val: 'some random content', + info: { + nameTruncated: false, + valueTruncated: false, + encoding: '7bit', + mimeType: 'text/plain', + }, + }, + ], + what: 'Empty content-type and empty content-disposition' + }, + { source: [ + ['-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k', + 'Content-Disposition: form-data; ' + + 'name="file"; filename*=utf-8\'\'n%C3%A4me.txt', + 'Content-Type: application/octet-stream', + '', + 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', + '-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k--' + ].join('\r\n') + ], + boundary: '---------------------------paZqsnEHRufoShdX6fh0lUhXBP4k', + expected: [ + { type: 'file', + name: 'file', + data: Buffer.from('ABCDEFGHIJKLMNOPQRSTUVWXYZ'), + info: { + filename: 'näme.txt', + encoding: '7bit', + mimeType: 'application/octet-stream', + }, + limited: false, + }, + ], + what: 'Unicode filenames' + }, + { source: [ + ['--asdasdasdasd\r\n', + 'Content-Type: text/plain\r\n', + 'Content-Disposition: form-data; name="foo"\r\n', + '\r\n', + 'asd\r\n', + '--asdasdasdasd--' + ].join(':)') + ], + boundary: 'asdasdasdasd', + expected: [ + { error: 'Malformed part header' }, + { error: 'Unexpected end of form' }, + ], + what: 'Stopped mid-header' + }, + { source: [ + ['------WebKitFormBoundaryTB2MiQ36fnSJlrhY', + 'Content-Disposition: form-data; name="cont"', + 'Content-Type: application/json', + '', + '{}', + '------WebKitFormBoundaryTB2MiQ36fnSJlrhY--', + ].join('\r\n') + ], + boundary: '----WebKitFormBoundaryTB2MiQ36fnSJlrhY', + expected: [ + { type: 'field', + name: 'cont', + val: '{}', + info: { + nameTruncated: false, + valueTruncated: false, + encoding: '7bit', + mimeType: 'application/json', + }, + }, + ], + what: 'content-type for fields' + }, + { source: [ + '------WebKitFormBoundaryTB2MiQ36fnSJlrhY--', + ], + boundary: '----WebKitFormBoundaryTB2MiQ36fnSJlrhY', + expected: [], + what: 'empty form' + }, + { source: [ + ['-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k', + 'Content-Disposition: form-data; ' + + 'name=upload_file_0; filename="1k_a.dat"', + 'Content-Type: application/octet-stream', + 'Content-Transfer-Encoding: binary', + '', + '', + ].join('\r\n') + ], + boundary: '---------------------------paZqsnEHRufoShdX6fh0lUhXBP4k', + expected: [ + { type: 'file', + name: 'upload_file_0', + data: Buffer.alloc(0), + info: { + filename: '1k_a.dat', + encoding: 'binary', + mimeType: 'application/octet-stream', + }, + limited: false, + err: 'Unexpected end of form', + }, + { error: 'Unexpected end of form' }, + ], + what: 'Stopped mid-file #1' + }, + { source: [ + ['-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k', + 'Content-Disposition: form-data; ' + + 'name=upload_file_0; filename="1k_a.dat"', + 'Content-Type: application/octet-stream', + '', + 'a', + ].join('\r\n') + ], + boundary: '---------------------------paZqsnEHRufoShdX6fh0lUhXBP4k', + expected: [ + { type: 'file', + name: 'upload_file_0', + data: Buffer.from('a'), + info: { + filename: '1k_a.dat', + encoding: '7bit', + mimeType: 'application/octet-stream', + }, + limited: false, + err: 'Unexpected end of form', + }, + { error: 'Unexpected end of form' }, + ], + what: 'Stopped mid-file #2' + }, + { source: [ + ['-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k', + 'Content-Disposition: form-data; ' + + 'name="upload_file_0"; filename="notes.txt"', + 'Content-Type: text/plain; charset=utf8', + '', + 'a', + '-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k--', + ].join('\r\n') + ], + boundary: '---------------------------paZqsnEHRufoShdX6fh0lUhXBP4k', + expected: [ + { type: 'file', + name: 'upload_file_0', + data: Buffer.from('a'), + info: { + filename: 'notes.txt', + encoding: '7bit', + mimeType: 'text/plain', + }, + limited: false, + }, + ], + what: 'Text file with charset' + }, + { source: [ + ['-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k', + 'Content-Disposition: form-data; ' + + 'name="upload_file_0"; filename="notes.txt"', + 'Content-Type: ', + ' text/plain; charset=utf8', + '', + 'a', + '-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k--', + ].join('\r\n') + ], + boundary: '---------------------------paZqsnEHRufoShdX6fh0lUhXBP4k', + expected: [ + { type: 'file', + name: 'upload_file_0', + data: Buffer.from('a'), + info: { + filename: 'notes.txt', + encoding: '7bit', + mimeType: 'text/plain', + }, + limited: false, + }, + ], + what: 'Folded header value' + }, + { source: [ + ['-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k', + 'Content-Type: text/plain; charset=utf8', + '', + 'a', + '-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k--', + ].join('\r\n') + ], + boundary: '---------------------------paZqsnEHRufoShdX6fh0lUhXBP4k', + expected: [], + what: 'No Content-Disposition' + }, + { source: [ + ['-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k', + 'Content-Disposition: form-data; name="file_name_0"', + '', + 'a'.repeat(64 * 1024), + '-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k', + 'Content-Disposition: form-data; ' + + 'name="upload_file_0"; filename="notes.txt"', + 'Content-Type: ', + ' text/plain; charset=utf8', + '', + 'bc', + '-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k--', + ].join('\r\n') + ], + boundary: '---------------------------paZqsnEHRufoShdX6fh0lUhXBP4k', + limits: { + fieldSize: Infinity, + }, + expected: [ + { type: 'file', + name: 'upload_file_0', + data: Buffer.from('bc'), + info: { + filename: 'notes.txt', + encoding: '7bit', + mimeType: 'text/plain', + }, + limited: false, + }, + ], + events: [ 'file' ], + what: 'Skip field parts if no listener' + }, + { source: [ + ['-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k', + 'Content-Disposition: form-data; name="file_name_0"', + '', + 'a', + '-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k', + 'Content-Disposition: form-data; ' + + 'name="upload_file_0"; filename="notes.txt"', + 'Content-Type: ', + ' text/plain; charset=utf8', + '', + 'bc', + '-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k--', + ].join('\r\n') + ], + boundary: '---------------------------paZqsnEHRufoShdX6fh0lUhXBP4k', + limits: { + parts: 1, + }, + expected: [ + { type: 'field', + name: 'file_name_0', + val: 'a', + info: { + nameTruncated: false, + valueTruncated: false, + encoding: '7bit', + mimeType: 'text/plain', + }, + }, + 'partsLimit', + ], + what: 'Parts limit' + }, + { source: [ + ['-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k', + 'Content-Disposition: form-data; name="file_name_0"', + '', + 'a', + '-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k', + 'Content-Disposition: form-data; name="file_name_1"', + '', + 'b', + '-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k--', + ].join('\r\n') + ], + boundary: '---------------------------paZqsnEHRufoShdX6fh0lUhXBP4k', + limits: { + fields: 1, + }, + expected: [ + { type: 'field', + name: 'file_name_0', + val: 'a', + info: { + nameTruncated: false, + valueTruncated: false, + encoding: '7bit', + mimeType: 'text/plain', + }, + }, + 'fieldsLimit', + ], + what: 'Fields limit' + }, + { source: [ + ['-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k', + 'Content-Disposition: form-data; ' + + 'name="upload_file_0"; filename="notes.txt"', + 'Content-Type: text/plain; charset=utf8', + '', + 'ab', + '-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k', + 'Content-Disposition: form-data; ' + + 'name="upload_file_1"; filename="notes2.txt"', + 'Content-Type: text/plain; charset=utf8', + '', + 'cd', + '-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k--', + ].join('\r\n') + ], + boundary: '---------------------------paZqsnEHRufoShdX6fh0lUhXBP4k', + limits: { + files: 1, + }, + expected: [ + { type: 'file', + name: 'upload_file_0', + data: Buffer.from('ab'), + info: { + filename: 'notes.txt', + encoding: '7bit', + mimeType: 'text/plain', + }, + limited: false, + }, + 'filesLimit', + ], + what: 'Files limit' + }, + { source: [ + ['-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k', + 'Content-Disposition: form-data; ' + + `name="upload_file_0"; filename="${'a'.repeat(64 * 1024)}.txt"`, + 'Content-Type: text/plain; charset=utf8', + '', + 'ab', + '-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k', + 'Content-Disposition: form-data; ' + + 'name="upload_file_1"; filename="notes2.txt"', + 'Content-Type: text/plain; charset=utf8', + '', + 'cd', + '-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k--', + ].join('\r\n') + ], + boundary: '---------------------------paZqsnEHRufoShdX6fh0lUhXBP4k', + expected: [ + { error: 'Malformed part header' }, + { type: 'file', + name: 'upload_file_1', + data: Buffer.from('cd'), + info: { + filename: 'notes2.txt', + encoding: '7bit', + mimeType: 'text/plain', + }, + limited: false, + }, + ], + what: 'Oversized part header' + }, + { source: [ + ['-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k', + 'Content-Disposition: form-data; ' + + 'name="upload_file_0"; filename="notes.txt"', + 'Content-Type: text/plain; charset=utf8', + '', + 'a'.repeat(31) + '\r', + ].join('\r\n'), + 'b'.repeat(40), + '\r\n-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k--', + ], + boundary: '---------------------------paZqsnEHRufoShdX6fh0lUhXBP4k', + fileHwm: 32, + expected: [ + { type: 'file', + name: 'upload_file_0', + data: Buffer.from('a'.repeat(31) + '\r' + 'b'.repeat(40)), + info: { + filename: 'notes.txt', + encoding: '7bit', + mimeType: 'text/plain', + }, + limited: false, + }, + ], + what: 'Lookbehind data should not stall file streams' + }, + { source: [ + ['-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k', + 'Content-Disposition: form-data; ' + + `name="upload_file_0"; filename="${'a'.repeat(8 * 1024)}.txt"`, + 'Content-Type: text/plain; charset=utf8', + '', + 'ab', + '-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k', + 'Content-Disposition: form-data; ' + + `name="upload_file_1"; filename="${'b'.repeat(8 * 1024)}.txt"`, + 'Content-Type: text/plain; charset=utf8', + '', + 'cd', + '-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k', + 'Content-Disposition: form-data; ' + + `name="upload_file_2"; filename="${'c'.repeat(8 * 1024)}.txt"`, + 'Content-Type: text/plain; charset=utf8', + '', + 'ef', + '-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k--', + ].join('\r\n') + ], + boundary: '---------------------------paZqsnEHRufoShdX6fh0lUhXBP4k', + expected: [ + { type: 'file', + name: 'upload_file_0', + data: Buffer.from('ab'), + info: { + filename: `${'a'.repeat(8 * 1024)}.txt`, + encoding: '7bit', + mimeType: 'text/plain', + }, + limited: false, + }, + { type: 'file', + name: 'upload_file_1', + data: Buffer.from('cd'), + info: { + filename: `${'b'.repeat(8 * 1024)}.txt`, + encoding: '7bit', + mimeType: 'text/plain', + }, + limited: false, + }, + { type: 'file', + name: 'upload_file_2', + data: Buffer.from('ef'), + info: { + filename: `${'c'.repeat(8 * 1024)}.txt`, + encoding: '7bit', + mimeType: 'text/plain', + }, + limited: false, + }, + ], + what: 'Header size limit should be per part' + }, + { source: [ + '\r\n--d1bf46b3-aa33-4061-b28d-6c5ced8b08ee\r\n', + 'Content-Type: application/gzip\r\n' + + 'Content-Encoding: gzip\r\n' + + 'Content-Disposition: form-data; name=batch-1; filename=batch-1' + + '\r\n\r\n', + '\r\n--d1bf46b3-aa33-4061-b28d-6c5ced8b08ee--', + ], + boundary: 'd1bf46b3-aa33-4061-b28d-6c5ced8b08ee', + expected: [ + { type: 'file', + name: 'batch-1', + data: Buffer.alloc(0), + info: { + filename: 'batch-1', + encoding: '7bit', + mimeType: 'application/gzip', + }, + limited: false, + }, + ], + what: 'Empty part' + }, +]; + +for (const test of tests) { + active.set(test, 1); + + const { what, boundary, events, limits, preservePath, fileHwm } = test; + const bb = busboy({ + fileHwm, + limits, + preservePath, + headers: { + 'content-type': `multipart/form-data; boundary=${boundary}`, + } + }); + const results = []; + + if (events === undefined || events.includes('field')) { + bb.on('field', (name, val, info) => { + results.push({ type: 'field', name, val, info }); + }); + } + + if (events === undefined || events.includes('file')) { + bb.on('file', (name, stream, info) => { + const data = []; + let nb = 0; + const file = { + type: 'file', + name, + data: null, + info, + limited: false, + }; + results.push(file); + stream.on('data', (d) => { + data.push(d); + nb += d.length; + }).on('limit', () => { + file.limited = true; + }).on('close', () => { + file.data = Buffer.concat(data, nb); + assert.strictEqual(stream.truncated, file.limited); + }).once('error', (err) => { + file.err = err.message; + }); + }); + } + + bb.on('error', (err) => { + results.push({ error: err.message }); + }); + + bb.on('partsLimit', () => { + results.push('partsLimit'); + }); + + bb.on('filesLimit', () => { + results.push('filesLimit'); + }); + + bb.on('fieldsLimit', () => { + results.push('fieldsLimit'); + }); + + bb.on('close', () => { + active.delete(test); + + assert.deepStrictEqual( + results, + test.expected, + `[${what}] Results mismatch.\n` + + `Parsed: ${inspect(results)}\n` + + `Expected: ${inspect(test.expected)}` + ); + }); + + for (const src of test.source) { + const buf = (typeof src === 'string' ? Buffer.from(src, 'utf8') : src); + bb.write(buf); + } + bb.end(); +} + +// Byte-by-byte versions +for (let test of tests) { + test = { ...test }; + test.what += ' (byte-by-byte)'; + active.set(test, 1); + + const { what, boundary, events, limits, preservePath, fileHwm } = test; + const bb = busboy({ + fileHwm, + limits, + preservePath, + headers: { + 'content-type': `multipart/form-data; boundary=${boundary}`, + } + }); + const results = []; + + if (events === undefined || events.includes('field')) { + bb.on('field', (name, val, info) => { + results.push({ type: 'field', name, val, info }); + }); + } + + if (events === undefined || events.includes('file')) { + bb.on('file', (name, stream, info) => { + const data = []; + let nb = 0; + const file = { + type: 'file', + name, + data: null, + info, + limited: false, + }; + results.push(file); + stream.on('data', (d) => { + data.push(d); + nb += d.length; + }).on('limit', () => { + file.limited = true; + }).on('close', () => { + file.data = Buffer.concat(data, nb); + assert.strictEqual(stream.truncated, file.limited); + }).once('error', (err) => { + file.err = err.message; + }); + }); + } + + bb.on('error', (err) => { + results.push({ error: err.message }); + }); + + bb.on('partsLimit', () => { + results.push('partsLimit'); + }); + + bb.on('filesLimit', () => { + results.push('filesLimit'); + }); + + bb.on('fieldsLimit', () => { + results.push('fieldsLimit'); + }); + + bb.on('close', () => { + active.delete(test); + + assert.deepStrictEqual( + results, + test.expected, + `[${what}] Results mismatch.\n` + + `Parsed: ${inspect(results)}\n` + + `Expected: ${inspect(test.expected)}` + ); + }); + + for (const src of test.source) { + const buf = (typeof src === 'string' ? Buffer.from(src, 'utf8') : src); + for (let i = 0; i < buf.length; ++i) + bb.write(buf.slice(i, i + 1)); + } + bb.end(); +} + +{ + let exception = false; + process.once('uncaughtException', (ex) => { + exception = true; + throw ex; + }); + process.on('exit', () => { + if (exception || active.size === 0) + return; + process.exitCode = 1; + console.error('=========================='); + console.error(`${active.size} test(s) did not finish:`); + console.error('=========================='); + console.error(Array.from(active.keys()).map((v) => v.what).join('\n')); + }); +} diff --git a/backend/node_modules/busboy/test/test-types-urlencoded.js b/backend/node_modules/busboy/test/test-types-urlencoded.js new file mode 100644 index 0000000..c35962b --- /dev/null +++ b/backend/node_modules/busboy/test/test-types-urlencoded.js @@ -0,0 +1,488 @@ +'use strict'; + +const assert = require('assert'); +const { transcode } = require('buffer'); +const { inspect } = require('util'); + +const busboy = require('..'); + +const active = new Map(); + +const tests = [ + { source: ['foo'], + expected: [ + ['foo', + '', + { nameTruncated: false, + valueTruncated: false, + encoding: 'utf-8', + mimeType: 'text/plain' }, + ], + ], + what: 'Unassigned value' + }, + { source: ['foo=bar'], + expected: [ + ['foo', + 'bar', + { nameTruncated: false, + valueTruncated: false, + encoding: 'utf-8', + mimeType: 'text/plain' }, + ], + ], + what: 'Assigned value' + }, + { source: ['foo&bar=baz'], + expected: [ + ['foo', + '', + { nameTruncated: false, + valueTruncated: false, + encoding: 'utf-8', + mimeType: 'text/plain' }, + ], + ['bar', + 'baz', + { nameTruncated: false, + valueTruncated: false, + encoding: 'utf-8', + mimeType: 'text/plain' }, + ], + ], + what: 'Unassigned and assigned value' + }, + { source: ['foo=bar&baz'], + expected: [ + ['foo', + 'bar', + { nameTruncated: false, + valueTruncated: false, + encoding: 'utf-8', + mimeType: 'text/plain' }, + ], + ['baz', + '', + { nameTruncated: false, + valueTruncated: false, + encoding: 'utf-8', + mimeType: 'text/plain' }, + ], + ], + what: 'Assigned and unassigned value' + }, + { source: ['foo=bar&baz=bla'], + expected: [ + ['foo', + 'bar', + { nameTruncated: false, + valueTruncated: false, + encoding: 'utf-8', + mimeType: 'text/plain' }, + ], + ['baz', + 'bla', + { nameTruncated: false, + valueTruncated: false, + encoding: 'utf-8', + mimeType: 'text/plain' }, + ], + ], + what: 'Two assigned values' + }, + { source: ['foo&bar'], + expected: [ + ['foo', + '', + { nameTruncated: false, + valueTruncated: false, + encoding: 'utf-8', + mimeType: 'text/plain' }, + ], + ['bar', + '', + { nameTruncated: false, + valueTruncated: false, + encoding: 'utf-8', + mimeType: 'text/plain' }, + ], + ], + what: 'Two unassigned values' + }, + { source: ['foo&bar&'], + expected: [ + ['foo', + '', + { nameTruncated: false, + valueTruncated: false, + encoding: 'utf-8', + mimeType: 'text/plain' }, + ], + ['bar', + '', + { nameTruncated: false, + valueTruncated: false, + encoding: 'utf-8', + mimeType: 'text/plain' }, + ], + ], + what: 'Two unassigned values and ampersand' + }, + { source: ['foo+1=bar+baz%2Bquux'], + expected: [ + ['foo 1', + 'bar baz+quux', + { nameTruncated: false, + valueTruncated: false, + encoding: 'utf-8', + mimeType: 'text/plain' }, + ], + ], + what: 'Assigned key and value with (plus) space' + }, + { source: ['foo=bar%20baz%21'], + expected: [ + ['foo', + 'bar baz!', + { nameTruncated: false, + valueTruncated: false, + encoding: 'utf-8', + mimeType: 'text/plain' }, + ], + ], + what: 'Assigned value with encoded bytes' + }, + { source: ['foo%20bar=baz%20bla%21'], + expected: [ + ['foo bar', + 'baz bla!', + { nameTruncated: false, + valueTruncated: false, + encoding: 'utf-8', + mimeType: 'text/plain' }, + ], + ], + what: 'Assigned value with encoded bytes #2' + }, + { source: ['foo=bar%20baz%21&num=1000'], + expected: [ + ['foo', + 'bar baz!', + { nameTruncated: false, + valueTruncated: false, + encoding: 'utf-8', + mimeType: 'text/plain' }, + ], + ['num', + '1000', + { nameTruncated: false, + valueTruncated: false, + encoding: 'utf-8', + mimeType: 'text/plain' }, + ], + ], + what: 'Two assigned values, one with encoded bytes' + }, + { source: [ + Array.from(transcode(Buffer.from('foo'), 'utf8', 'utf16le')).map( + (n) => `%${n.toString(16).padStart(2, '0')}` + ).join(''), + '=', + Array.from(transcode(Buffer.from('😀!'), 'utf8', 'utf16le')).map( + (n) => `%${n.toString(16).padStart(2, '0')}` + ).join(''), + ], + expected: [ + ['foo', + '😀!', + { nameTruncated: false, + valueTruncated: false, + encoding: 'UTF-16LE', + mimeType: 'text/plain' }, + ], + ], + charset: 'UTF-16LE', + what: 'Encoded value with multi-byte charset' + }, + { source: [ + 'foo=<', + Array.from(transcode(Buffer.from('©:^þ'), 'utf8', 'latin1')).map( + (n) => `%${n.toString(16).padStart(2, '0')}` + ).join(''), + ], + expected: [ + ['foo', + '<©:^þ', + { nameTruncated: false, + valueTruncated: false, + encoding: 'ISO-8859-1', + mimeType: 'text/plain' }, + ], + ], + charset: 'ISO-8859-1', + what: 'Encoded value with single-byte, ASCII-compatible, non-UTF8 charset' + }, + { source: ['foo=bar&baz=bla'], + expected: [], + what: 'Limits: zero fields', + limits: { fields: 0 } + }, + { source: ['foo=bar&baz=bla'], + expected: [ + ['foo', + 'bar', + { nameTruncated: false, + valueTruncated: false, + encoding: 'utf-8', + mimeType: 'text/plain' }, + ], + ], + what: 'Limits: one field', + limits: { fields: 1 } + }, + { source: ['foo=bar&baz=bla'], + expected: [ + ['foo', + 'bar', + { nameTruncated: false, + valueTruncated: false, + encoding: 'utf-8', + mimeType: 'text/plain' }, + ], + ['baz', + 'bla', + { nameTruncated: false, + valueTruncated: false, + encoding: 'utf-8', + mimeType: 'text/plain' }, + ], + ], + what: 'Limits: field part lengths match limits', + limits: { fieldNameSize: 3, fieldSize: 3 } + }, + { source: ['foo=bar&baz=bla'], + expected: [ + ['fo', + 'bar', + { nameTruncated: true, + valueTruncated: false, + encoding: 'utf-8', + mimeType: 'text/plain' }, + ], + ['ba', + 'bla', + { nameTruncated: true, + valueTruncated: false, + encoding: 'utf-8', + mimeType: 'text/plain' }, + ], + ], + what: 'Limits: truncated field name', + limits: { fieldNameSize: 2 } + }, + { source: ['foo=bar&baz=bla'], + expected: [ + ['foo', + 'ba', + { nameTruncated: false, + valueTruncated: true, + encoding: 'utf-8', + mimeType: 'text/plain' }, + ], + ['baz', + 'bl', + { nameTruncated: false, + valueTruncated: true, + encoding: 'utf-8', + mimeType: 'text/plain' }, + ], + ], + what: 'Limits: truncated field value', + limits: { fieldSize: 2 } + }, + { source: ['foo=bar&baz=bla'], + expected: [ + ['fo', + 'ba', + { nameTruncated: true, + valueTruncated: true, + encoding: 'utf-8', + mimeType: 'text/plain' }, + ], + ['ba', + 'bl', + { nameTruncated: true, + valueTruncated: true, + encoding: 'utf-8', + mimeType: 'text/plain' }, + ], + ], + what: 'Limits: truncated field name and value', + limits: { fieldNameSize: 2, fieldSize: 2 } + }, + { source: ['foo=bar&baz=bla'], + expected: [ + ['fo', + '', + { nameTruncated: true, + valueTruncated: true, + encoding: 'utf-8', + mimeType: 'text/plain' }, + ], + ['ba', + '', + { nameTruncated: true, + valueTruncated: true, + encoding: 'utf-8', + mimeType: 'text/plain' }, + ], + ], + what: 'Limits: truncated field name and zero value limit', + limits: { fieldNameSize: 2, fieldSize: 0 } + }, + { source: ['foo=bar&baz=bla'], + expected: [ + ['', + '', + { nameTruncated: true, + valueTruncated: true, + encoding: 'utf-8', + mimeType: 'text/plain' }, + ], + ['', + '', + { nameTruncated: true, + valueTruncated: true, + encoding: 'utf-8', + mimeType: 'text/plain' }, + ], + ], + what: 'Limits: truncated zero field name and zero value limit', + limits: { fieldNameSize: 0, fieldSize: 0 } + }, + { source: ['&'], + expected: [], + what: 'Ampersand' + }, + { source: ['&&&&&'], + expected: [], + what: 'Many ampersands' + }, + { source: ['='], + expected: [ + ['', + '', + { nameTruncated: false, + valueTruncated: false, + encoding: 'utf-8', + mimeType: 'text/plain' }, + ], + ], + what: 'Assigned value, empty name and value' + }, + { source: [''], + expected: [], + what: 'Nothing' + }, +]; + +for (const test of tests) { + active.set(test, 1); + + const { what } = test; + const charset = test.charset || 'utf-8'; + const bb = busboy({ + limits: test.limits, + headers: { + 'content-type': `application/x-www-form-urlencoded; charset=${charset}`, + }, + }); + const results = []; + + bb.on('field', (key, val, info) => { + results.push([key, val, info]); + }); + + bb.on('file', () => { + throw new Error(`[${what}] Unexpected file`); + }); + + bb.on('close', () => { + active.delete(test); + + assert.deepStrictEqual( + results, + test.expected, + `[${what}] Results mismatch.\n` + + `Parsed: ${inspect(results)}\n` + + `Expected: ${inspect(test.expected)}` + ); + }); + + for (const src of test.source) { + const buf = (typeof src === 'string' ? Buffer.from(src, 'utf8') : src); + bb.write(buf); + } + bb.end(); +} + +// Byte-by-byte versions +for (let test of tests) { + test = { ...test }; + test.what += ' (byte-by-byte)'; + active.set(test, 1); + + const { what } = test; + const charset = test.charset || 'utf-8'; + const bb = busboy({ + limits: test.limits, + headers: { + 'content-type': `application/x-www-form-urlencoded; charset="${charset}"`, + }, + }); + const results = []; + + bb.on('field', (key, val, info) => { + results.push([key, val, info]); + }); + + bb.on('file', () => { + throw new Error(`[${what}] Unexpected file`); + }); + + bb.on('close', () => { + active.delete(test); + + assert.deepStrictEqual( + results, + test.expected, + `[${what}] Results mismatch.\n` + + `Parsed: ${inspect(results)}\n` + + `Expected: ${inspect(test.expected)}` + ); + }); + + for (const src of test.source) { + const buf = (typeof src === 'string' ? Buffer.from(src, 'utf8') : src); + for (let i = 0; i < buf.length; ++i) + bb.write(buf.slice(i, i + 1)); + } + bb.end(); +} + +{ + let exception = false; + process.once('uncaughtException', (ex) => { + exception = true; + throw ex; + }); + process.on('exit', () => { + if (exception || active.size === 0) + return; + process.exitCode = 1; + console.error('=========================='); + console.error(`${active.size} test(s) did not finish:`); + console.error('=========================='); + console.error(Array.from(active.keys()).map((v) => v.what).join('\n')); + }); +} diff --git a/backend/node_modules/busboy/test/test.js b/backend/node_modules/busboy/test/test.js new file mode 100644 index 0000000..d0380f2 --- /dev/null +++ b/backend/node_modules/busboy/test/test.js @@ -0,0 +1,20 @@ +'use strict'; + +const { spawnSync } = require('child_process'); +const { readdirSync } = require('fs'); +const { join } = require('path'); + +const files = readdirSync(__dirname).sort(); +for (const filename of files) { + if (filename.startsWith('test-')) { + const path = join(__dirname, filename); + console.log(`> Running ${filename} ...`); + const result = spawnSync(`${process.argv0} ${path}`, { + shell: true, + stdio: 'inherit', + windowsHide: true + }); + if (result.status !== 0) + process.exitCode = 1; + } +} diff --git a/backend/node_modules/concat-stream/LICENSE b/backend/node_modules/concat-stream/LICENSE new file mode 100644 index 0000000..99c130e --- /dev/null +++ b/backend/node_modules/concat-stream/LICENSE @@ -0,0 +1,24 @@ +The MIT License + +Copyright (c) 2013 Max Ogden + +Permission is hereby granted, free of charge, +to any person obtaining a copy of this software and +associated documentation files (the "Software"), to +deal in the Software without restriction, including +without limitation the rights to use, copy, modify, +merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom +the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice +shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR +ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. \ No newline at end of file diff --git a/backend/node_modules/concat-stream/index.js b/backend/node_modules/concat-stream/index.js new file mode 100644 index 0000000..dd672a7 --- /dev/null +++ b/backend/node_modules/concat-stream/index.js @@ -0,0 +1,144 @@ +var Writable = require('readable-stream').Writable +var inherits = require('inherits') +var bufferFrom = require('buffer-from') + +if (typeof Uint8Array === 'undefined') { + var U8 = require('typedarray').Uint8Array +} else { + var U8 = Uint8Array +} + +function ConcatStream(opts, cb) { + if (!(this instanceof ConcatStream)) return new ConcatStream(opts, cb) + + if (typeof opts === 'function') { + cb = opts + opts = {} + } + if (!opts) opts = {} + + var encoding = opts.encoding + var shouldInferEncoding = false + + if (!encoding) { + shouldInferEncoding = true + } else { + encoding = String(encoding).toLowerCase() + if (encoding === 'u8' || encoding === 'uint8') { + encoding = 'uint8array' + } + } + + Writable.call(this, { objectMode: true }) + + this.encoding = encoding + this.shouldInferEncoding = shouldInferEncoding + + if (cb) this.on('finish', function () { cb(this.getBody()) }) + this.body = [] +} + +module.exports = ConcatStream +inherits(ConcatStream, Writable) + +ConcatStream.prototype._write = function(chunk, enc, next) { + this.body.push(chunk) + next() +} + +ConcatStream.prototype.inferEncoding = function (buff) { + var firstBuffer = buff === undefined ? this.body[0] : buff; + if (Buffer.isBuffer(firstBuffer)) return 'buffer' + if (typeof Uint8Array !== 'undefined' && firstBuffer instanceof Uint8Array) return 'uint8array' + if (Array.isArray(firstBuffer)) return 'array' + if (typeof firstBuffer === 'string') return 'string' + if (Object.prototype.toString.call(firstBuffer) === "[object Object]") return 'object' + return 'buffer' +} + +ConcatStream.prototype.getBody = function () { + if (!this.encoding && this.body.length === 0) return [] + if (this.shouldInferEncoding) this.encoding = this.inferEncoding() + if (this.encoding === 'array') return arrayConcat(this.body) + if (this.encoding === 'string') return stringConcat(this.body) + if (this.encoding === 'buffer') return bufferConcat(this.body) + if (this.encoding === 'uint8array') return u8Concat(this.body) + return this.body +} + +var isArray = Array.isArray || function (arr) { + return Object.prototype.toString.call(arr) == '[object Array]' +} + +function isArrayish (arr) { + return /Array\]$/.test(Object.prototype.toString.call(arr)) +} + +function isBufferish (p) { + return typeof p === 'string' || isArrayish(p) || (p && typeof p.subarray === 'function') +} + +function stringConcat (parts) { + var strings = [] + var needsToString = false + for (var i = 0; i < parts.length; i++) { + var p = parts[i] + if (typeof p === 'string') { + strings.push(p) + } else if (Buffer.isBuffer(p)) { + strings.push(p) + } else if (isBufferish(p)) { + strings.push(bufferFrom(p)) + } else { + strings.push(bufferFrom(String(p))) + } + } + if (Buffer.isBuffer(parts[0])) { + strings = Buffer.concat(strings) + strings = strings.toString('utf8') + } else { + strings = strings.join('') + } + return strings +} + +function bufferConcat (parts) { + var bufs = [] + for (var i = 0; i < parts.length; i++) { + var p = parts[i] + if (Buffer.isBuffer(p)) { + bufs.push(p) + } else if (isBufferish(p)) { + bufs.push(bufferFrom(p)) + } else { + bufs.push(bufferFrom(String(p))) + } + } + return Buffer.concat(bufs) +} + +function arrayConcat (parts) { + var res = [] + for (var i = 0; i < parts.length; i++) { + res.push.apply(res, parts[i]) + } + return res +} + +function u8Concat (parts) { + var len = 0 + for (var i = 0; i < parts.length; i++) { + if (typeof parts[i] === 'string') { + parts[i] = bufferFrom(parts[i]) + } + len += parts[i].length + } + var u8 = new U8(len) + for (var i = 0, offset = 0; i < parts.length; i++) { + var part = parts[i] + for (var j = 0; j < part.length; j++) { + u8[offset++] = part[j] + } + } + return u8 +} diff --git a/backend/node_modules/concat-stream/package.json b/backend/node_modules/concat-stream/package.json new file mode 100644 index 0000000..f709022 --- /dev/null +++ b/backend/node_modules/concat-stream/package.json @@ -0,0 +1,55 @@ +{ + "name": "concat-stream", + "version": "1.6.2", + "description": "writable stream that concatenates strings or binary data and calls a callback with the result", + "tags": [ + "stream", + "simple", + "util", + "utility" + ], + "author": "Max Ogden ", + "repository": { + "type": "git", + "url": "http://github.com/maxogden/concat-stream.git" + }, + "bugs": { + "url": "http://github.com/maxogden/concat-stream/issues" + }, + "engines": [ + "node >= 0.8" + ], + "main": "index.js", + "files": [ + "index.js" + ], + "scripts": { + "test": "tape test/*.js test/server/*.js" + }, + "license": "MIT", + "dependencies": { + "buffer-from": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^2.2.2", + "typedarray": "^0.0.6" + }, + "devDependencies": { + "tape": "^4.6.3" + }, + "testling": { + "files": "test/*.js", + "browsers": [ + "ie/8..latest", + "firefox/17..latest", + "firefox/nightly", + "chrome/22..latest", + "chrome/canary", + "opera/12..latest", + "opera/next", + "safari/5.1..latest", + "ipad/6.0..latest", + "iphone/6.0..latest", + "android-browser/4.2..latest" + ] + } +} diff --git a/backend/node_modules/concat-stream/readme.md b/backend/node_modules/concat-stream/readme.md new file mode 100644 index 0000000..7aa19c4 --- /dev/null +++ b/backend/node_modules/concat-stream/readme.md @@ -0,0 +1,102 @@ +# concat-stream + +Writable stream that concatenates all the data from a stream and calls a callback with the result. Use this when you want to collect all the data from a stream into a single buffer. + +[![Build Status](https://travis-ci.org/maxogden/concat-stream.svg?branch=master)](https://travis-ci.org/maxogden/concat-stream) + +[![NPM](https://nodei.co/npm/concat-stream.png)](https://nodei.co/npm/concat-stream/) + +### description + +Streams emit many buffers. If you want to collect all of the buffers, and when the stream ends concatenate all of the buffers together and receive a single buffer then this is the module for you. + +Only use this if you know you can fit all of the output of your stream into a single Buffer (e.g. in RAM). + +There are also `objectMode` streams that emit things other than Buffers, and you can concatenate these too. See below for details. + +## Related + +`concat-stream` is part of the [mississippi stream utility collection](https://github.com/maxogden/mississippi) which includes more useful stream modules similar to this one. + +### examples + +#### Buffers + +```js +var fs = require('fs') +var concat = require('concat-stream') + +var readStream = fs.createReadStream('cat.png') +var concatStream = concat(gotPicture) + +readStream.on('error', handleError) +readStream.pipe(concatStream) + +function gotPicture(imageBuffer) { + // imageBuffer is all of `cat.png` as a node.js Buffer +} + +function handleError(err) { + // handle your error appropriately here, e.g.: + console.error(err) // print the error to STDERR + process.exit(1) // exit program with non-zero exit code +} + +``` + +#### Arrays + +```js +var write = concat(function(data) {}) +write.write([1,2,3]) +write.write([4,5,6]) +write.end() +// data will be [1,2,3,4,5,6] in the above callback +``` + +#### Uint8Arrays + +```js +var write = concat(function(data) {}) +var a = new Uint8Array(3) +a[0] = 97; a[1] = 98; a[2] = 99 +write.write(a) +write.write('!') +write.end(Buffer.from('!!1')) +``` + +See `test/` for more examples + +# methods + +```js +var concat = require('concat-stream') +``` + +## var writable = concat(opts={}, cb) + +Return a `writable` stream that will fire `cb(data)` with all of the data that +was written to the stream. Data can be written to `writable` as strings, +Buffers, arrays of byte integers, and Uint8Arrays. + +By default `concat-stream` will give you back the same data type as the type of the first buffer written to the stream. Use `opts.encoding` to set what format `data` should be returned as, e.g. if you if you don't want to rely on the built-in type checking or for some other reason. + +* `string` - get a string +* `buffer` - get back a Buffer +* `array` - get an array of byte integers +* `uint8array`, `u8`, `uint8` - get back a Uint8Array +* `object`, get back an array of Objects + +If you don't specify an encoding, and the types can't be inferred (e.g. you write things that aren't in the list above), it will try to convert concat them into a `Buffer`. + +If nothing is written to `writable` then `data` will be an empty array `[]`. + +# error handling + +`concat-stream` does not handle errors for you, so you must handle errors on whatever streams you pipe into `concat-stream`. This is a general rule when programming with node.js streams: always handle errors on each and every stream. Since `concat-stream` is not itself a stream it does not emit errors. + +We recommend using [`end-of-stream`](https://npmjs.org/end-of-stream) or [`pump`](https://npmjs.org/pump) for writing error tolerant stream code. + +# license + +MIT LICENSE diff --git a/backend/node_modules/core-util-is/LICENSE b/backend/node_modules/core-util-is/LICENSE new file mode 100644 index 0000000..d8d7f94 --- /dev/null +++ b/backend/node_modules/core-util-is/LICENSE @@ -0,0 +1,19 @@ +Copyright Node.js contributors. All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to +deal in the Software without restriction, including without limitation the +rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +sell copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +IN THE SOFTWARE. diff --git a/backend/node_modules/core-util-is/README.md b/backend/node_modules/core-util-is/README.md new file mode 100644 index 0000000..5a76b41 --- /dev/null +++ b/backend/node_modules/core-util-is/README.md @@ -0,0 +1,3 @@ +# core-util-is + +The `util.is*` functions introduced in Node v0.12. diff --git a/backend/node_modules/core-util-is/lib/util.js b/backend/node_modules/core-util-is/lib/util.js new file mode 100644 index 0000000..6e5a20d --- /dev/null +++ b/backend/node_modules/core-util-is/lib/util.js @@ -0,0 +1,107 @@ +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +// NOTE: These type checking functions intentionally don't use `instanceof` +// because it is fragile and can be easily faked with `Object.create()`. + +function isArray(arg) { + if (Array.isArray) { + return Array.isArray(arg); + } + return objectToString(arg) === '[object Array]'; +} +exports.isArray = isArray; + +function isBoolean(arg) { + return typeof arg === 'boolean'; +} +exports.isBoolean = isBoolean; + +function isNull(arg) { + return arg === null; +} +exports.isNull = isNull; + +function isNullOrUndefined(arg) { + return arg == null; +} +exports.isNullOrUndefined = isNullOrUndefined; + +function isNumber(arg) { + return typeof arg === 'number'; +} +exports.isNumber = isNumber; + +function isString(arg) { + return typeof arg === 'string'; +} +exports.isString = isString; + +function isSymbol(arg) { + return typeof arg === 'symbol'; +} +exports.isSymbol = isSymbol; + +function isUndefined(arg) { + return arg === void 0; +} +exports.isUndefined = isUndefined; + +function isRegExp(re) { + return objectToString(re) === '[object RegExp]'; +} +exports.isRegExp = isRegExp; + +function isObject(arg) { + return typeof arg === 'object' && arg !== null; +} +exports.isObject = isObject; + +function isDate(d) { + return objectToString(d) === '[object Date]'; +} +exports.isDate = isDate; + +function isError(e) { + return (objectToString(e) === '[object Error]' || e instanceof Error); +} +exports.isError = isError; + +function isFunction(arg) { + return typeof arg === 'function'; +} +exports.isFunction = isFunction; + +function isPrimitive(arg) { + return arg === null || + typeof arg === 'boolean' || + typeof arg === 'number' || + typeof arg === 'string' || + typeof arg === 'symbol' || // ES6 symbol + typeof arg === 'undefined'; +} +exports.isPrimitive = isPrimitive; + +exports.isBuffer = require('buffer').Buffer.isBuffer; + +function objectToString(o) { + return Object.prototype.toString.call(o); +} diff --git a/backend/node_modules/core-util-is/package.json b/backend/node_modules/core-util-is/package.json new file mode 100644 index 0000000..b0c51f5 --- /dev/null +++ b/backend/node_modules/core-util-is/package.json @@ -0,0 +1,38 @@ +{ + "name": "core-util-is", + "version": "1.0.3", + "description": "The `util.is*` functions introduced in Node v0.12.", + "main": "lib/util.js", + "files": [ + "lib" + ], + "repository": { + "type": "git", + "url": "git://github.com/isaacs/core-util-is" + }, + "keywords": [ + "util", + "isBuffer", + "isArray", + "isNumber", + "isString", + "isRegExp", + "isThis", + "isThat", + "polyfill" + ], + "author": "Isaac Z. Schlueter (http://blog.izs.me/)", + "license": "MIT", + "bugs": { + "url": "https://github.com/isaacs/core-util-is/issues" + }, + "scripts": { + "test": "tap test.js", + "preversion": "npm test", + "postversion": "npm publish", + "prepublishOnly": "git push origin --follow-tags" + }, + "devDependencies": { + "tap": "^15.0.9" + } +} diff --git a/backend/node_modules/isarray/.npmignore b/backend/node_modules/isarray/.npmignore new file mode 100644 index 0000000..3c3629e --- /dev/null +++ b/backend/node_modules/isarray/.npmignore @@ -0,0 +1 @@ +node_modules diff --git a/backend/node_modules/isarray/.travis.yml b/backend/node_modules/isarray/.travis.yml new file mode 100644 index 0000000..cc4dba2 --- /dev/null +++ b/backend/node_modules/isarray/.travis.yml @@ -0,0 +1,4 @@ +language: node_js +node_js: + - "0.8" + - "0.10" diff --git a/backend/node_modules/isarray/Makefile b/backend/node_modules/isarray/Makefile new file mode 100644 index 0000000..787d56e --- /dev/null +++ b/backend/node_modules/isarray/Makefile @@ -0,0 +1,6 @@ + +test: + @node_modules/.bin/tape test.js + +.PHONY: test + diff --git a/backend/node_modules/isarray/README.md b/backend/node_modules/isarray/README.md new file mode 100644 index 0000000..16d2c59 --- /dev/null +++ b/backend/node_modules/isarray/README.md @@ -0,0 +1,60 @@ + +# isarray + +`Array#isArray` for older browsers. + +[![build status](https://secure.travis-ci.org/juliangruber/isarray.svg)](http://travis-ci.org/juliangruber/isarray) +[![downloads](https://img.shields.io/npm/dm/isarray.svg)](https://www.npmjs.org/package/isarray) + +[![browser support](https://ci.testling.com/juliangruber/isarray.png) +](https://ci.testling.com/juliangruber/isarray) + +## Usage + +```js +var isArray = require('isarray'); + +console.log(isArray([])); // => true +console.log(isArray({})); // => false +``` + +## Installation + +With [npm](http://npmjs.org) do + +```bash +$ npm install isarray +``` + +Then bundle for the browser with +[browserify](https://github.com/substack/browserify). + +With [component](http://component.io) do + +```bash +$ component install juliangruber/isarray +``` + +## License + +(MIT) + +Copyright (c) 2013 Julian Gruber <julian@juliangruber.com> + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/backend/node_modules/isarray/component.json b/backend/node_modules/isarray/component.json new file mode 100644 index 0000000..9e31b68 --- /dev/null +++ b/backend/node_modules/isarray/component.json @@ -0,0 +1,19 @@ +{ + "name" : "isarray", + "description" : "Array#isArray for older browsers", + "version" : "0.0.1", + "repository" : "juliangruber/isarray", + "homepage": "https://github.com/juliangruber/isarray", + "main" : "index.js", + "scripts" : [ + "index.js" + ], + "dependencies" : {}, + "keywords": ["browser","isarray","array"], + "author": { + "name": "Julian Gruber", + "email": "mail@juliangruber.com", + "url": "http://juliangruber.com" + }, + "license": "MIT" +} diff --git a/backend/node_modules/isarray/index.js b/backend/node_modules/isarray/index.js new file mode 100644 index 0000000..a57f634 --- /dev/null +++ b/backend/node_modules/isarray/index.js @@ -0,0 +1,5 @@ +var toString = {}.toString; + +module.exports = Array.isArray || function (arr) { + return toString.call(arr) == '[object Array]'; +}; diff --git a/backend/node_modules/isarray/package.json b/backend/node_modules/isarray/package.json new file mode 100644 index 0000000..1a4317a --- /dev/null +++ b/backend/node_modules/isarray/package.json @@ -0,0 +1,45 @@ +{ + "name": "isarray", + "description": "Array#isArray for older browsers", + "version": "1.0.0", + "repository": { + "type": "git", + "url": "git://github.com/juliangruber/isarray.git" + }, + "homepage": "https://github.com/juliangruber/isarray", + "main": "index.js", + "dependencies": {}, + "devDependencies": { + "tape": "~2.13.4" + }, + "keywords": [ + "browser", + "isarray", + "array" + ], + "author": { + "name": "Julian Gruber", + "email": "mail@juliangruber.com", + "url": "http://juliangruber.com" + }, + "license": "MIT", + "testling": { + "files": "test.js", + "browsers": [ + "ie/8..latest", + "firefox/17..latest", + "firefox/nightly", + "chrome/22..latest", + "chrome/canary", + "opera/12..latest", + "opera/next", + "safari/5.1..latest", + "ipad/6.0..latest", + "iphone/6.0..latest", + "android-browser/4.2..latest" + ] + }, + "scripts": { + "test": "tape test.js" + } +} diff --git a/backend/node_modules/isarray/test.js b/backend/node_modules/isarray/test.js new file mode 100644 index 0000000..e0c3444 --- /dev/null +++ b/backend/node_modules/isarray/test.js @@ -0,0 +1,20 @@ +var isArray = require('./'); +var test = require('tape'); + +test('is array', function(t){ + t.ok(isArray([])); + t.notOk(isArray({})); + t.notOk(isArray(null)); + t.notOk(isArray(false)); + + var obj = {}; + obj[0] = true; + t.notOk(isArray(obj)); + + var arr = []; + arr.foo = 'bar'; + t.ok(isArray(arr)); + + t.end(); +}); + diff --git a/backend/node_modules/minimist/.eslintrc b/backend/node_modules/minimist/.eslintrc new file mode 100644 index 0000000..bd1a5e0 --- /dev/null +++ b/backend/node_modules/minimist/.eslintrc @@ -0,0 +1,29 @@ +{ + "root": true, + + "extends": "@ljharb/eslint-config/node/0.4", + + "rules": { + "array-element-newline": 0, + "complexity": 0, + "func-style": [2, "declaration"], + "max-lines-per-function": 0, + "max-nested-callbacks": 1, + "max-statements-per-line": 1, + "max-statements": 0, + "multiline-comment-style": 0, + "no-continue": 1, + "no-param-reassign": 1, + "no-restricted-syntax": 1, + "object-curly-newline": 0, + }, + + "overrides": [ + { + "files": "test/**", + "rules": { + "camelcase": 0, + }, + }, + ] +} diff --git a/backend/node_modules/minimist/.github/FUNDING.yml b/backend/node_modules/minimist/.github/FUNDING.yml new file mode 100644 index 0000000..a936622 --- /dev/null +++ b/backend/node_modules/minimist/.github/FUNDING.yml @@ -0,0 +1,12 @@ +# These are supported funding model platforms + +github: [ljharb] +patreon: # Replace with a single Patreon username +open_collective: # Replace with a single Open Collective username +ko_fi: # Replace with a single Ko-fi username +tidelift: npm/minimist +community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry +liberapay: # Replace with a single Liberapay username +issuehunt: # Replace with a single IssueHunt username +otechie: # Replace with a single Otechie username +custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] diff --git a/backend/node_modules/minimist/.nycrc b/backend/node_modules/minimist/.nycrc new file mode 100644 index 0000000..55c3d29 --- /dev/null +++ b/backend/node_modules/minimist/.nycrc @@ -0,0 +1,14 @@ +{ + "all": true, + "check-coverage": false, + "reporter": ["text-summary", "text", "html", "json"], + "lines": 86, + "statements": 85.93, + "functions": 82.43, + "branches": 76.06, + "exclude": [ + "coverage", + "example", + "test" + ] +} diff --git a/backend/node_modules/minimist/CHANGELOG.md b/backend/node_modules/minimist/CHANGELOG.md new file mode 100644 index 0000000..c9a1e15 --- /dev/null +++ b/backend/node_modules/minimist/CHANGELOG.md @@ -0,0 +1,298 @@ +# Changelog + +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## [v1.2.8](https://github.com/minimistjs/minimist/compare/v1.2.7...v1.2.8) - 2023-02-09 + +### Merged + +- [Fix] Fix long option followed by single dash [`#17`](https://github.com/minimistjs/minimist/pull/17) +- [Tests] Remove duplicate test [`#12`](https://github.com/minimistjs/minimist/pull/12) +- [Fix] opt.string works with multiple aliases [`#10`](https://github.com/minimistjs/minimist/pull/10) + +### Fixed + +- [Fix] Fix long option followed by single dash (#17) [`#15`](https://github.com/minimistjs/minimist/issues/15) +- [Tests] Remove duplicate test (#12) [`#8`](https://github.com/minimistjs/minimist/issues/8) +- [Fix] Fix long option followed by single dash [`#15`](https://github.com/minimistjs/minimist/issues/15) +- [Fix] opt.string works with multiple aliases (#10) [`#9`](https://github.com/minimistjs/minimist/issues/9) +- [Fix] Fix handling of short option with non-trivial equals [`#5`](https://github.com/minimistjs/minimist/issues/5) +- [Tests] Remove duplicate test [`#8`](https://github.com/minimistjs/minimist/issues/8) +- [Fix] opt.string works with multiple aliases [`#9`](https://github.com/minimistjs/minimist/issues/9) + +### Commits + +- Merge tag 'v0.2.3' [`a026794`](https://github.com/minimistjs/minimist/commit/a0267947c7870fc5847cf2d437fbe33f392767da) +- [eslint] fix indentation and whitespace [`5368ca4`](https://github.com/minimistjs/minimist/commit/5368ca4147e974138a54cc0dc4cea8f756546b70) +- [eslint] fix indentation and whitespace [`e5f5067`](https://github.com/minimistjs/minimist/commit/e5f5067259ceeaf0b098d14bec910f87e58708c7) +- [eslint] more cleanup [`62fde7d`](https://github.com/minimistjs/minimist/commit/62fde7d935f83417fb046741531a9e2346a36976) +- [eslint] more cleanup [`36ac5d0`](https://github.com/minimistjs/minimist/commit/36ac5d0d95e4947d074e5737d94814034ca335d1) +- [meta] add `auto-changelog` [`73923d2`](https://github.com/minimistjs/minimist/commit/73923d223553fca08b1ba77e3fbc2a492862ae4c) +- [actions] add reusable workflows [`d80727d`](https://github.com/minimistjs/minimist/commit/d80727df77bfa9e631044d7f16368d8f09242c91) +- [eslint] add eslint; rules to enable later are warnings [`48bc06a`](https://github.com/minimistjs/minimist/commit/48bc06a1b41f00e9cdf183db34f7a51ba70e98d4) +- [eslint] fix indentation [`34b0f1c`](https://github.com/minimistjs/minimist/commit/34b0f1ccaa45183c3c4f06a91f9b405180a6f982) +- [readme] rename and add badges [`5df0fe4`](https://github.com/minimistjs/minimist/commit/5df0fe49211bd09a3636f8686a7cb3012c3e98f0) +- [Dev Deps] switch from `covert` to `nyc` [`a48b128`](https://github.com/minimistjs/minimist/commit/a48b128fdb8d427dfb20a15273f83e38d97bef07) +- [Dev Deps] update `covert`, `tape`; remove unnecessary `tap` [`f0fb958`](https://github.com/minimistjs/minimist/commit/f0fb958e9a1fe980cdffc436a211b0bda58f621b) +- [meta] create FUNDING.yml; add `funding` in package.json [`3639e0c`](https://github.com/minimistjs/minimist/commit/3639e0c819359a366387e425ab6eabf4c78d3caa) +- [meta] use `npmignore` to autogenerate an npmignore file [`be2e038`](https://github.com/minimistjs/minimist/commit/be2e038c342d8333b32f0fde67a0026b79c8150e) +- Only apps should have lockfiles [`282b570`](https://github.com/minimistjs/minimist/commit/282b570e7489d01b03f2d6d3dabf79cd3e5f84cf) +- isConstructorOrProto adapted from PR [`ef9153f`](https://github.com/minimistjs/minimist/commit/ef9153fc52b6cea0744b2239921c5dcae4697f11) +- [Dev Deps] update `@ljharb/eslint-config`, `aud` [`098873c`](https://github.com/minimistjs/minimist/commit/098873c213cdb7c92e55ae1ef5aa1af3a8192a79) +- [Dev Deps] update `@ljharb/eslint-config`, `aud` [`3124ed3`](https://github.com/minimistjs/minimist/commit/3124ed3e46306301ebb3c834874ce0241555c2c4) +- [meta] add `safe-publish-latest` [`4b927de`](https://github.com/minimistjs/minimist/commit/4b927de696d561c636b4f43bf49d4597cb36d6d6) +- [Tests] add `aud` in `posttest` [`b32d9bd`](https://github.com/minimistjs/minimist/commit/b32d9bd0ab340f4e9f8c3a97ff2a4424f25fab8c) +- [meta] update repo URLs [`f9fdfc0`](https://github.com/minimistjs/minimist/commit/f9fdfc032c54884d9a9996a390c63cd0719bbe1a) +- [actions] Avoid 0.6 tests due to build failures [`ba92fe6`](https://github.com/minimistjs/minimist/commit/ba92fe6ebbdc0431cca9a2ea8f27beb492f5e4ec) +- [Dev Deps] update `tape` [`950eaa7`](https://github.com/minimistjs/minimist/commit/950eaa74f112e04d23e9c606c67472c46739b473) +- [Dev Deps] add missing `npmignore` dev dep [`3226afa`](https://github.com/minimistjs/minimist/commit/3226afaf09e9d127ca369742437fe6e88f752d6b) +- Merge tag 'v0.2.2' [`980d7ac`](https://github.com/minimistjs/minimist/commit/980d7ac61a0b4bd552711251ac107d506b23e41f) + +## [v1.2.7](https://github.com/minimistjs/minimist/compare/v1.2.6...v1.2.7) - 2022-10-10 + +### Commits + +- [meta] add `auto-changelog` [`0ebf4eb`](https://github.com/minimistjs/minimist/commit/0ebf4ebcd5f7787a5524d31a849ef41316b83c3c) +- [actions] add reusable workflows [`e115b63`](https://github.com/minimistjs/minimist/commit/e115b63fa9d3909f33b00a2db647ff79068388de) +- [eslint] add eslint; rules to enable later are warnings [`f58745b`](https://github.com/minimistjs/minimist/commit/f58745b9bb84348e1be72af7dbba5840c7c13013) +- [Dev Deps] switch from `covert` to `nyc` [`ab03356`](https://github.com/minimistjs/minimist/commit/ab033567b9c8b31117cb026dc7f1e592ce455c65) +- [readme] rename and add badges [`236f4a0`](https://github.com/minimistjs/minimist/commit/236f4a07e4ebe5ee44f1496ec6974991ab293ffd) +- [meta] create FUNDING.yml; add `funding` in package.json [`783a49b`](https://github.com/minimistjs/minimist/commit/783a49bfd47e8335d3098a8cac75662cf71eb32a) +- [meta] use `npmignore` to autogenerate an npmignore file [`f81ece6`](https://github.com/minimistjs/minimist/commit/f81ece6aaec2fa14e69ff4f1e0407a8c4e2635a2) +- Only apps should have lockfiles [`56cad44`](https://github.com/minimistjs/minimist/commit/56cad44c7f879b9bb5ec18fcc349308024a89bfc) +- [Dev Deps] update `covert`, `tape`; remove unnecessary `tap` [`49c5f9f`](https://github.com/minimistjs/minimist/commit/49c5f9fb7e6a92db9eb340cc679de92fb3aacded) +- [Tests] add `aud` in `posttest` [`228ae93`](https://github.com/minimistjs/minimist/commit/228ae938f3cd9db9dfd8bd7458b076a7b2aef280) +- [meta] add `safe-publish-latest` [`01fc23f`](https://github.com/minimistjs/minimist/commit/01fc23f5104f85c75059972e01dd33796ab529ff) +- [meta] update repo URLs [`6b164c7`](https://github.com/minimistjs/minimist/commit/6b164c7d68e0b6bf32f894699effdfb7c63041dd) + +## [v1.2.6](https://github.com/minimistjs/minimist/compare/v1.2.5...v1.2.6) - 2022-03-21 + +### Commits + +- test from prototype pollution PR [`bc8ecee`](https://github.com/minimistjs/minimist/commit/bc8ecee43875261f4f17eb20b1243d3ed15e70eb) +- isConstructorOrProto adapted from PR [`c2b9819`](https://github.com/minimistjs/minimist/commit/c2b981977fa834b223b408cfb860f933c9811e4d) +- security notice for additional prototype pollution issue [`ef88b93`](https://github.com/minimistjs/minimist/commit/ef88b9325f77b5ee643ccfc97e2ebda577e4c4e2) + +## [v1.2.5](https://github.com/minimistjs/minimist/compare/v1.2.4...v1.2.5) - 2020-03-12 + +## [v1.2.4](https://github.com/minimistjs/minimist/compare/v1.2.3...v1.2.4) - 2020-03-11 + +### Commits + +- security notice [`4cf1354`](https://github.com/minimistjs/minimist/commit/4cf1354839cb972e38496d35e12f806eea92c11f) +- additional test for constructor prototype pollution [`1043d21`](https://github.com/minimistjs/minimist/commit/1043d212c3caaf871966e710f52cfdf02f9eea4b) + +## [v1.2.3](https://github.com/minimistjs/minimist/compare/v1.2.2...v1.2.3) - 2020-03-10 + +### Commits + +- more failing proto pollution tests [`13c01a5`](https://github.com/minimistjs/minimist/commit/13c01a5327736903704984b7f65616b8476850cc) +- even more aggressive checks for protocol pollution [`38a4d1c`](https://github.com/minimistjs/minimist/commit/38a4d1caead72ef99e824bb420a2528eec03d9ab) + +## [v1.2.2](https://github.com/minimistjs/minimist/compare/v1.2.1...v1.2.2) - 2020-03-10 + +### Commits + +- failing test for protocol pollution [`0efed03`](https://github.com/minimistjs/minimist/commit/0efed0340ec8433638758f7ca0c77cb20a0bfbab) +- cleanup [`67d3722`](https://github.com/minimistjs/minimist/commit/67d3722413448d00a62963d2d30c34656a92d7e2) +- console.dir -> console.log [`47acf72`](https://github.com/minimistjs/minimist/commit/47acf72c715a630bf9ea013867f47f1dd69dfc54) +- don't assign onto __proto__ [`63e7ed0`](https://github.com/minimistjs/minimist/commit/63e7ed05aa4b1889ec2f3b196426db4500cbda94) + +## [v1.2.1](https://github.com/minimistjs/minimist/compare/v1.2.0...v1.2.1) - 2020-03-10 + +### Merged + +- move the `opts['--']` example back where it belongs [`#63`](https://github.com/minimistjs/minimist/pull/63) + +### Commits + +- add test [`6be5dae`](https://github.com/minimistjs/minimist/commit/6be5dae35a32a987bcf4137fcd6c19c5200ee909) +- fix bad boolean regexp [`ac3fc79`](https://github.com/minimistjs/minimist/commit/ac3fc796e63b95128fdbdf67ea7fad71bd59aa76) + +## [v1.2.0](https://github.com/minimistjs/minimist/compare/v1.1.3...v1.2.0) - 2015-08-24 + +### Commits + +- failing -k=v short test [`63416b8`](https://github.com/minimistjs/minimist/commit/63416b8cd1d0d70e4714564cce465a36e4dd26d7) +- kv short fix [`6bbe145`](https://github.com/minimistjs/minimist/commit/6bbe14529166245e86424f220a2321442fe88dc3) +- failing kv short test [`f72ab7f`](https://github.com/minimistjs/minimist/commit/f72ab7f4572adc52902c9b6873cc969192f01b10) +- fixed kv test [`f5a48c3`](https://github.com/minimistjs/minimist/commit/f5a48c3e50e40ca54f00c8e84de4b4d6e9897fa8) +- enforce space between arg key and value [`86b321a`](https://github.com/minimistjs/minimist/commit/86b321affe648a8e016c095a4f0efa9d9074f502) + +## [v1.1.3](https://github.com/minimistjs/minimist/compare/v1.1.2...v1.1.3) - 2015-08-06 + +### Commits + +- add failing test - boolean alias array [`0fa3c5b`](https://github.com/minimistjs/minimist/commit/0fa3c5b3dd98551ddecf5392831b4c21211743fc) +- fix boolean values with multiple aliases [`9c0a6e7`](https://github.com/minimistjs/minimist/commit/9c0a6e7de25a273b11bbf9a7464f0bd833779795) + +## [v1.1.2](https://github.com/minimistjs/minimist/compare/v1.1.1...v1.1.2) - 2015-07-22 + +### Commits + +- Convert boolean arguments to boolean values [`8f3dc27`](https://github.com/minimistjs/minimist/commit/8f3dc27cf833f1d54671b6d0bcb55c2fe19672a9) +- use non-ancient npm, node 0.12 and iojs [`61ed1d0`](https://github.com/minimistjs/minimist/commit/61ed1d034b9ec7282764ce76f3992b1a0b4906ae) +- an older npm for 0.8 [`25cf778`](https://github.com/minimistjs/minimist/commit/25cf778b1220e7838a526832ad6972f75244054f) + +## [v1.1.1](https://github.com/minimistjs/minimist/compare/v1.1.0...v1.1.1) - 2015-03-10 + +### Commits + +- check that they type of a value is a boolean, not just that it is currently set to a boolean [`6863198`](https://github.com/minimistjs/minimist/commit/6863198e36139830ff1f20ffdceaddd93f2c1db9) +- upgrade tape, fix type issues from old tape version [`806712d`](https://github.com/minimistjs/minimist/commit/806712df91604ed02b8e39aa372b84aea659ee34) +- test for setting a boolean to a null default [`8c444fe`](https://github.com/minimistjs/minimist/commit/8c444fe89384ded7d441c120915ea60620b01dd3) +- if the previous value was a boolean, without an default (or with an alias) don't make an array either [`e5f419a`](https://github.com/minimistjs/minimist/commit/e5f419a3b5b3bc3f9e5ac71b7040621af70ed2dd) + +## [v1.1.0](https://github.com/minimistjs/minimist/compare/v1.0.0...v1.1.0) - 2014-08-10 + +### Commits + +- add support for handling "unknown" options not registered with the parser. [`6f3cc5d`](https://github.com/minimistjs/minimist/commit/6f3cc5d4e84524932a6ef2ce3592acc67cdd4383) +- reformat package.json [`02ed371`](https://github.com/minimistjs/minimist/commit/02ed37115194d3697ff358e8e25e5e66bab1d9f8) +- coverage script [`e5531ba`](https://github.com/minimistjs/minimist/commit/e5531ba0479da3b8138d3d8cac545d84ccb1c8df) +- extra fn to get 100% coverage again [`a6972da`](https://github.com/minimistjs/minimist/commit/a6972da89e56bf77642f8ec05a13b6558db93498) + +## [v1.0.0](https://github.com/minimistjs/minimist/compare/v0.2.3...v1.0.0) - 2014-08-10 + +### Commits + +- added stopEarly option [`471c7e4`](https://github.com/minimistjs/minimist/commit/471c7e4a7e910fc7ad8f9df850a186daf32c64e9) +- fix list [`fef6ae7`](https://github.com/minimistjs/minimist/commit/fef6ae79c38b9dc1c49569abb7cd04eb965eac5e) + +## [v0.2.3](https://github.com/minimistjs/minimist/compare/v0.2.2...v0.2.3) - 2023-02-09 + +### Merged + +- [Fix] Fix long option followed by single dash [`#17`](https://github.com/minimistjs/minimist/pull/17) +- [Tests] Remove duplicate test [`#12`](https://github.com/minimistjs/minimist/pull/12) +- [Fix] opt.string works with multiple aliases [`#10`](https://github.com/minimistjs/minimist/pull/10) + +### Fixed + +- [Fix] Fix long option followed by single dash (#17) [`#15`](https://github.com/minimistjs/minimist/issues/15) +- [Tests] Remove duplicate test (#12) [`#8`](https://github.com/minimistjs/minimist/issues/8) +- [Fix] opt.string works with multiple aliases (#10) [`#9`](https://github.com/minimistjs/minimist/issues/9) + +### Commits + +- [eslint] fix indentation and whitespace [`e5f5067`](https://github.com/minimistjs/minimist/commit/e5f5067259ceeaf0b098d14bec910f87e58708c7) +- [eslint] more cleanup [`36ac5d0`](https://github.com/minimistjs/minimist/commit/36ac5d0d95e4947d074e5737d94814034ca335d1) +- [eslint] fix indentation [`34b0f1c`](https://github.com/minimistjs/minimist/commit/34b0f1ccaa45183c3c4f06a91f9b405180a6f982) +- isConstructorOrProto adapted from PR [`ef9153f`](https://github.com/minimistjs/minimist/commit/ef9153fc52b6cea0744b2239921c5dcae4697f11) +- [Dev Deps] update `@ljharb/eslint-config`, `aud` [`098873c`](https://github.com/minimistjs/minimist/commit/098873c213cdb7c92e55ae1ef5aa1af3a8192a79) +- [Dev Deps] add missing `npmignore` dev dep [`3226afa`](https://github.com/minimistjs/minimist/commit/3226afaf09e9d127ca369742437fe6e88f752d6b) + +## [v0.2.2](https://github.com/minimistjs/minimist/compare/v0.2.1...v0.2.2) - 2022-10-10 + +### Commits + +- [meta] add `auto-changelog` [`73923d2`](https://github.com/minimistjs/minimist/commit/73923d223553fca08b1ba77e3fbc2a492862ae4c) +- [actions] add reusable workflows [`d80727d`](https://github.com/minimistjs/minimist/commit/d80727df77bfa9e631044d7f16368d8f09242c91) +- [eslint] add eslint; rules to enable later are warnings [`48bc06a`](https://github.com/minimistjs/minimist/commit/48bc06a1b41f00e9cdf183db34f7a51ba70e98d4) +- [readme] rename and add badges [`5df0fe4`](https://github.com/minimistjs/minimist/commit/5df0fe49211bd09a3636f8686a7cb3012c3e98f0) +- [Dev Deps] switch from `covert` to `nyc` [`a48b128`](https://github.com/minimistjs/minimist/commit/a48b128fdb8d427dfb20a15273f83e38d97bef07) +- [Dev Deps] update `covert`, `tape`; remove unnecessary `tap` [`f0fb958`](https://github.com/minimistjs/minimist/commit/f0fb958e9a1fe980cdffc436a211b0bda58f621b) +- [meta] create FUNDING.yml; add `funding` in package.json [`3639e0c`](https://github.com/minimistjs/minimist/commit/3639e0c819359a366387e425ab6eabf4c78d3caa) +- [meta] use `npmignore` to autogenerate an npmignore file [`be2e038`](https://github.com/minimistjs/minimist/commit/be2e038c342d8333b32f0fde67a0026b79c8150e) +- Only apps should have lockfiles [`282b570`](https://github.com/minimistjs/minimist/commit/282b570e7489d01b03f2d6d3dabf79cd3e5f84cf) +- [meta] add `safe-publish-latest` [`4b927de`](https://github.com/minimistjs/minimist/commit/4b927de696d561c636b4f43bf49d4597cb36d6d6) +- [Tests] add `aud` in `posttest` [`b32d9bd`](https://github.com/minimistjs/minimist/commit/b32d9bd0ab340f4e9f8c3a97ff2a4424f25fab8c) +- [meta] update repo URLs [`f9fdfc0`](https://github.com/minimistjs/minimist/commit/f9fdfc032c54884d9a9996a390c63cd0719bbe1a) + +## [v0.2.1](https://github.com/minimistjs/minimist/compare/v0.2.0...v0.2.1) - 2020-03-12 + +## [v0.2.0](https://github.com/minimistjs/minimist/compare/v0.1.0...v0.2.0) - 2014-06-19 + +### Commits + +- support all-boolean mode [`450a97f`](https://github.com/minimistjs/minimist/commit/450a97f6e2bc85c7a4a13185c19a818d9a5ebe69) + +## [v0.1.0](https://github.com/minimistjs/minimist/compare/v0.0.10...v0.1.0) - 2014-05-12 + +### Commits + +- Provide a mechanism to segregate -- arguments [`ce4a1e6`](https://github.com/minimistjs/minimist/commit/ce4a1e63a7e8d5ab88d2a3768adefa6af98a445a) +- documented argv['--'] [`14db0e6`](https://github.com/minimistjs/minimist/commit/14db0e6dbc6d2b9e472adaa54dad7004b364634f) +- Adding a test-case for notFlags segregation [`715c1e3`](https://github.com/minimistjs/minimist/commit/715c1e3714be223f998f6c537af6b505f0236c16) + +## [v0.0.10](https://github.com/minimistjs/minimist/compare/v0.0.9...v0.0.10) - 2014-05-11 + +### Commits + +- dedicated boolean test [`46e448f`](https://github.com/minimistjs/minimist/commit/46e448f9f513cfeb2bcc8b688b9b47ba1e515c2b) +- dedicated num test [`9bf2d36`](https://github.com/minimistjs/minimist/commit/9bf2d36f1d3b8795be90b8f7de0a937f098aa394) +- aliased values treated as strings [`1ab743b`](https://github.com/minimistjs/minimist/commit/1ab743bad4484d69f1259bed42f9531de01119de) +- cover the case of already numbers, at 100% coverage [`b2bb044`](https://github.com/minimistjs/minimist/commit/b2bb04436599d77a2ce029e8e555e25b3aa55d13) +- another test for higher coverage [`3662624`](https://github.com/minimistjs/minimist/commit/3662624be976d5489d486a856849c048d13be903) + +## [v0.0.9](https://github.com/minimistjs/minimist/compare/v0.0.8...v0.0.9) - 2014-05-08 + +### Commits + +- Eliminate `longest` fn. [`824f642`](https://github.com/minimistjs/minimist/commit/824f642038d1b02ede68b6261d1d65163390929a) + +## [v0.0.8](https://github.com/minimistjs/minimist/compare/v0.0.7...v0.0.8) - 2014-02-20 + +### Commits + +- return '' if flag is string and empty [`fa63ed4`](https://github.com/minimistjs/minimist/commit/fa63ed4651a4ef4eefddce34188e0d98d745a263) +- handle joined single letters [`66c248f`](https://github.com/minimistjs/minimist/commit/66c248f0241d4d421d193b022e9e365f11178534) + +## [v0.0.7](https://github.com/minimistjs/minimist/compare/v0.0.6...v0.0.7) - 2014-02-08 + +### Commits + +- another swap of .test for .match [`d1da408`](https://github.com/minimistjs/minimist/commit/d1da40819acbe846d89a5c02721211e3c1260dde) + +## [v0.0.6](https://github.com/minimistjs/minimist/compare/v0.0.5...v0.0.6) - 2014-02-08 + +### Commits + +- use .test() instead of .match() to not crash on non-string values in the arguments array [`7e0d1ad`](https://github.com/minimistjs/minimist/commit/7e0d1add8c9e5b9b20a4d3d0f9a94d824c578da1) + +## [v0.0.5](https://github.com/minimistjs/minimist/compare/v0.0.4...v0.0.5) - 2013-09-18 + +### Commits + +- Improve '--' handling. [`b11822c`](https://github.com/minimistjs/minimist/commit/b11822c09cc9d2460f30384d12afc0b953c037a4) + +## [v0.0.4](https://github.com/minimistjs/minimist/compare/v0.0.3...v0.0.4) - 2013-09-17 + +## [v0.0.3](https://github.com/minimistjs/minimist/compare/v0.0.2...v0.0.3) - 2013-09-12 + +### Commits + +- failing test for single dash preceeding a double dash [`b465514`](https://github.com/minimistjs/minimist/commit/b465514b82c9ae28972d714facd951deb2ad762b) +- fix for the dot test [`6a095f1`](https://github.com/minimistjs/minimist/commit/6a095f1d364c8fab2d6753d2291a0649315d297a) + +## [v0.0.2](https://github.com/minimistjs/minimist/compare/v0.0.1...v0.0.2) - 2013-08-28 + +### Commits + +- allow dotted aliases & defaults [`321c33e`](https://github.com/minimistjs/minimist/commit/321c33e755485faaeb44eeb1c05d33b2e0a5a7c4) +- use a better version of ff [`e40f611`](https://github.com/minimistjs/minimist/commit/e40f61114cf7be6f7947f7b3eed345853a67dbbb) + +## [v0.0.1](https://github.com/minimistjs/minimist/compare/v0.0.0...v0.0.1) - 2013-06-25 + +### Commits + +- remove trailing commas [`6ff0fa0`](https://github.com/minimistjs/minimist/commit/6ff0fa055064f15dbe06d50b89d5173a6796e1db) + +## v0.0.0 - 2013-06-25 + +### Commits + +- half of the parse test ported [`3079326`](https://github.com/minimistjs/minimist/commit/307932601325087de6cf94188eb798ffc4f3088a) +- stripped down code and a passing test from optimist [`7cced88`](https://github.com/minimistjs/minimist/commit/7cced88d82e399d1a03ed23eb667f04d3f320d10) +- ported parse tests completely over [`9448754`](https://github.com/minimistjs/minimist/commit/944875452e0820df6830b1408c26a0f7d3e1db04) +- docs, package.json [`a5bf46a`](https://github.com/minimistjs/minimist/commit/a5bf46ac9bb3bd114a9c340276c62c1091e538d5) +- move more short tests into short.js [`503edb5`](https://github.com/minimistjs/minimist/commit/503edb5c41d89c0d40831ee517154fc13b0f18b9) +- default bool test was wrong, not the code [`1b9f5db`](https://github.com/minimistjs/minimist/commit/1b9f5db4741b49962846081b68518de824992097) +- passing long tests ripped out of parse.js [`7972c4a`](https://github.com/minimistjs/minimist/commit/7972c4aff1f4803079e1668006658e2a761a0428) +- badges [`84c0370`](https://github.com/minimistjs/minimist/commit/84c037063664d42878aace715fe6572ce01b6f3b) +- all the tests now ported, some failures [`64239ed`](https://github.com/minimistjs/minimist/commit/64239edfe92c711c4eb0da254fcdfad2a5fdb605) +- failing short test [`f8a5341`](https://github.com/minimistjs/minimist/commit/f8a534112dd1138d2fad722def56a848480c446f) +- fixed the numeric test [`6b034f3`](https://github.com/minimistjs/minimist/commit/6b034f37c79342c60083ed97fd222e16928aac51) diff --git a/backend/node_modules/minimist/LICENSE b/backend/node_modules/minimist/LICENSE new file mode 100644 index 0000000..ee27ba4 --- /dev/null +++ b/backend/node_modules/minimist/LICENSE @@ -0,0 +1,18 @@ +This software is released under the MIT license: + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/backend/node_modules/minimist/README.md b/backend/node_modules/minimist/README.md new file mode 100644 index 0000000..74da323 --- /dev/null +++ b/backend/node_modules/minimist/README.md @@ -0,0 +1,121 @@ +# minimist [![Version Badge][npm-version-svg]][package-url] + +[![github actions][actions-image]][actions-url] +[![coverage][codecov-image]][codecov-url] +[![License][license-image]][license-url] +[![Downloads][downloads-image]][downloads-url] + +[![npm badge][npm-badge-png]][package-url] + +parse argument options + +This module is the guts of optimist's argument parser without all the +fanciful decoration. + +# example + +``` js +var argv = require('minimist')(process.argv.slice(2)); +console.log(argv); +``` + +``` +$ node example/parse.js -a beep -b boop +{ _: [], a: 'beep', b: 'boop' } +``` + +``` +$ node example/parse.js -x 3 -y 4 -n5 -abc --beep=boop foo bar baz +{ + _: ['foo', 'bar', 'baz'], + x: 3, + y: 4, + n: 5, + a: true, + b: true, + c: true, + beep: 'boop' +} +``` + +# security + +Previous versions had a prototype pollution bug that could cause privilege +escalation in some circumstances when handling untrusted user input. + +Please use version 1.2.6 or later: + +* https://security.snyk.io/vuln/SNYK-JS-MINIMIST-2429795 (version <=1.2.5) +* https://snyk.io/vuln/SNYK-JS-MINIMIST-559764 (version <=1.2.3) + +# methods + +``` js +var parseArgs = require('minimist') +``` + +## var argv = parseArgs(args, opts={}) + +Return an argument object `argv` populated with the array arguments from `args`. + +`argv._` contains all the arguments that didn't have an option associated with +them. + +Numeric-looking arguments will be returned as numbers unless `opts.string` or +`opts.boolean` is set for that argument name. + +Any arguments after `'--'` will not be parsed and will end up in `argv._`. + +options can be: + +* `opts.string` - a string or array of strings argument names to always treat as +strings +* `opts.boolean` - a boolean, string or array of strings to always treat as +booleans. if `true` will treat all double hyphenated arguments without equal signs +as boolean (e.g. affects `--foo`, not `-f` or `--foo=bar`) +* `opts.alias` - an object mapping string names to strings or arrays of string +argument names to use as aliases +* `opts.default` - an object mapping string argument names to default values +* `opts.stopEarly` - when true, populate `argv._` with everything after the +first non-option +* `opts['--']` - when true, populate `argv._` with everything before the `--` +and `argv['--']` with everything after the `--`. Here's an example: + + ``` + > require('./')('one two three -- four five --six'.split(' '), { '--': true }) + { + _: ['one', 'two', 'three'], + '--': ['four', 'five', '--six'] + } + ``` + + Note that with `opts['--']` set, parsing for arguments still stops after the + `--`. + +* `opts.unknown` - a function which is invoked with a command line parameter not +defined in the `opts` configuration object. If the function returns `false`, the +unknown option is not added to `argv`. + +# install + +With [npm](https://npmjs.org) do: + +``` +npm install minimist +``` + +# license + +MIT + +[package-url]: https://npmjs.org/package/minimist +[npm-version-svg]: https://versionbadg.es/minimistjs/minimist.svg +[npm-badge-png]: https://nodei.co/npm/minimist.png?downloads=true&stars=true +[license-image]: https://img.shields.io/npm/l/minimist.svg +[license-url]: LICENSE +[downloads-image]: https://img.shields.io/npm/dm/minimist.svg +[downloads-url]: https://npm-stat.com/charts.html?package=minimist +[codecov-image]: https://codecov.io/gh/minimistjs/minimist/branch/main/graphs/badge.svg +[codecov-url]: https://app.codecov.io/gh/minimistjs/minimist/ +[actions-image]: https://img.shields.io/endpoint?url=https://github-actions-badge-u3jn4tfpocch.runkit.sh/minimistjs/minimist +[actions-url]: https://github.com/minimistjs/minimist/actions diff --git a/backend/node_modules/minimist/example/parse.js b/backend/node_modules/minimist/example/parse.js new file mode 100644 index 0000000..9d90ffb --- /dev/null +++ b/backend/node_modules/minimist/example/parse.js @@ -0,0 +1,4 @@ +'use strict'; + +var argv = require('../')(process.argv.slice(2)); +console.log(argv); diff --git a/backend/node_modules/minimist/index.js b/backend/node_modules/minimist/index.js new file mode 100644 index 0000000..f020f39 --- /dev/null +++ b/backend/node_modules/minimist/index.js @@ -0,0 +1,263 @@ +'use strict'; + +function hasKey(obj, keys) { + var o = obj; + keys.slice(0, -1).forEach(function (key) { + o = o[key] || {}; + }); + + var key = keys[keys.length - 1]; + return key in o; +} + +function isNumber(x) { + if (typeof x === 'number') { return true; } + if ((/^0x[0-9a-f]+$/i).test(x)) { return true; } + return (/^[-+]?(?:\d+(?:\.\d*)?|\.\d+)(e[-+]?\d+)?$/).test(x); +} + +function isConstructorOrProto(obj, key) { + return (key === 'constructor' && typeof obj[key] === 'function') || key === '__proto__'; +} + +module.exports = function (args, opts) { + if (!opts) { opts = {}; } + + var flags = { + bools: {}, + strings: {}, + unknownFn: null, + }; + + if (typeof opts.unknown === 'function') { + flags.unknownFn = opts.unknown; + } + + if (typeof opts.boolean === 'boolean' && opts.boolean) { + flags.allBools = true; + } else { + [].concat(opts.boolean).filter(Boolean).forEach(function (key) { + flags.bools[key] = true; + }); + } + + var aliases = {}; + + function aliasIsBoolean(key) { + return aliases[key].some(function (x) { + return flags.bools[x]; + }); + } + + Object.keys(opts.alias || {}).forEach(function (key) { + aliases[key] = [].concat(opts.alias[key]); + aliases[key].forEach(function (x) { + aliases[x] = [key].concat(aliases[key].filter(function (y) { + return x !== y; + })); + }); + }); + + [].concat(opts.string).filter(Boolean).forEach(function (key) { + flags.strings[key] = true; + if (aliases[key]) { + [].concat(aliases[key]).forEach(function (k) { + flags.strings[k] = true; + }); + } + }); + + var defaults = opts.default || {}; + + var argv = { _: [] }; + + function argDefined(key, arg) { + return (flags.allBools && (/^--[^=]+$/).test(arg)) + || flags.strings[key] + || flags.bools[key] + || aliases[key]; + } + + function setKey(obj, keys, value) { + var o = obj; + for (var i = 0; i < keys.length - 1; i++) { + var key = keys[i]; + if (isConstructorOrProto(o, key)) { return; } + if (o[key] === undefined) { o[key] = {}; } + if ( + o[key] === Object.prototype + || o[key] === Number.prototype + || o[key] === String.prototype + ) { + o[key] = {}; + } + if (o[key] === Array.prototype) { o[key] = []; } + o = o[key]; + } + + var lastKey = keys[keys.length - 1]; + if (isConstructorOrProto(o, lastKey)) { return; } + if ( + o === Object.prototype + || o === Number.prototype + || o === String.prototype + ) { + o = {}; + } + if (o === Array.prototype) { o = []; } + if (o[lastKey] === undefined || flags.bools[lastKey] || typeof o[lastKey] === 'boolean') { + o[lastKey] = value; + } else if (Array.isArray(o[lastKey])) { + o[lastKey].push(value); + } else { + o[lastKey] = [o[lastKey], value]; + } + } + + function setArg(key, val, arg) { + if (arg && flags.unknownFn && !argDefined(key, arg)) { + if (flags.unknownFn(arg) === false) { return; } + } + + var value = !flags.strings[key] && isNumber(val) + ? Number(val) + : val; + setKey(argv, key.split('.'), value); + + (aliases[key] || []).forEach(function (x) { + setKey(argv, x.split('.'), value); + }); + } + + Object.keys(flags.bools).forEach(function (key) { + setArg(key, defaults[key] === undefined ? false : defaults[key]); + }); + + var notFlags = []; + + if (args.indexOf('--') !== -1) { + notFlags = args.slice(args.indexOf('--') + 1); + args = args.slice(0, args.indexOf('--')); + } + + for (var i = 0; i < args.length; i++) { + var arg = args[i]; + var key; + var next; + + if ((/^--.+=/).test(arg)) { + // Using [\s\S] instead of . because js doesn't support the + // 'dotall' regex modifier. See: + // http://stackoverflow.com/a/1068308/13216 + var m = arg.match(/^--([^=]+)=([\s\S]*)$/); + key = m[1]; + var value = m[2]; + if (flags.bools[key]) { + value = value !== 'false'; + } + setArg(key, value, arg); + } else if ((/^--no-.+/).test(arg)) { + key = arg.match(/^--no-(.+)/)[1]; + setArg(key, false, arg); + } else if ((/^--.+/).test(arg)) { + key = arg.match(/^--(.+)/)[1]; + next = args[i + 1]; + if ( + next !== undefined + && !(/^(-|--)[^-]/).test(next) + && !flags.bools[key] + && !flags.allBools + && (aliases[key] ? !aliasIsBoolean(key) : true) + ) { + setArg(key, next, arg); + i += 1; + } else if ((/^(true|false)$/).test(next)) { + setArg(key, next === 'true', arg); + i += 1; + } else { + setArg(key, flags.strings[key] ? '' : true, arg); + } + } else if ((/^-[^-]+/).test(arg)) { + var letters = arg.slice(1, -1).split(''); + + var broken = false; + for (var j = 0; j < letters.length; j++) { + next = arg.slice(j + 2); + + if (next === '-') { + setArg(letters[j], next, arg); + continue; + } + + if ((/[A-Za-z]/).test(letters[j]) && next[0] === '=') { + setArg(letters[j], next.slice(1), arg); + broken = true; + break; + } + + if ( + (/[A-Za-z]/).test(letters[j]) + && (/-?\d+(\.\d*)?(e-?\d+)?$/).test(next) + ) { + setArg(letters[j], next, arg); + broken = true; + break; + } + + if (letters[j + 1] && letters[j + 1].match(/\W/)) { + setArg(letters[j], arg.slice(j + 2), arg); + broken = true; + break; + } else { + setArg(letters[j], flags.strings[letters[j]] ? '' : true, arg); + } + } + + key = arg.slice(-1)[0]; + if (!broken && key !== '-') { + if ( + args[i + 1] + && !(/^(-|--)[^-]/).test(args[i + 1]) + && !flags.bools[key] + && (aliases[key] ? !aliasIsBoolean(key) : true) + ) { + setArg(key, args[i + 1], arg); + i += 1; + } else if (args[i + 1] && (/^(true|false)$/).test(args[i + 1])) { + setArg(key, args[i + 1] === 'true', arg); + i += 1; + } else { + setArg(key, flags.strings[key] ? '' : true, arg); + } + } + } else { + if (!flags.unknownFn || flags.unknownFn(arg) !== false) { + argv._.push(flags.strings._ || !isNumber(arg) ? arg : Number(arg)); + } + if (opts.stopEarly) { + argv._.push.apply(argv._, args.slice(i + 1)); + break; + } + } + } + + Object.keys(defaults).forEach(function (k) { + if (!hasKey(argv, k.split('.'))) { + setKey(argv, k.split('.'), defaults[k]); + + (aliases[k] || []).forEach(function (x) { + setKey(argv, x.split('.'), defaults[k]); + }); + } + }); + + if (opts['--']) { + argv['--'] = notFlags.slice(); + } else { + notFlags.forEach(function (k) { + argv._.push(k); + }); + } + + return argv; +}; diff --git a/backend/node_modules/minimist/package.json b/backend/node_modules/minimist/package.json new file mode 100644 index 0000000..c10a334 --- /dev/null +++ b/backend/node_modules/minimist/package.json @@ -0,0 +1,75 @@ +{ + "name": "minimist", + "version": "1.2.8", + "description": "parse argument options", + "main": "index.js", + "devDependencies": { + "@ljharb/eslint-config": "^21.0.1", + "aud": "^2.0.2", + "auto-changelog": "^2.4.0", + "eslint": "=8.8.0", + "in-publish": "^2.0.1", + "npmignore": "^0.3.0", + "nyc": "^10.3.2", + "safe-publish-latest": "^2.0.0", + "tape": "^5.6.3" + }, + "scripts": { + "prepack": "npmignore --auto --commentLines=auto", + "prepublishOnly": "safe-publish-latest", + "prepublish": "not-in-publish || npm run prepublishOnly", + "lint": "eslint --ext=js,mjs .", + "pretest": "npm run lint", + "tests-only": "nyc tape 'test/**/*.js'", + "test": "npm run tests-only", + "posttest": "aud --production", + "version": "auto-changelog && git add CHANGELOG.md", + "postversion": "auto-changelog && git add CHANGELOG.md && git commit --no-edit --amend && git tag -f \"v$(node -e \"console.log(require('./package.json').version)\")\"" + }, + "testling": { + "files": "test/*.js", + "browsers": [ + "ie/6..latest", + "ff/5", + "firefox/latest", + "chrome/10", + "chrome/latest", + "safari/5.1", + "safari/latest", + "opera/12" + ] + }, + "repository": { + "type": "git", + "url": "git://github.com/minimistjs/minimist.git" + }, + "homepage": "https://github.com/minimistjs/minimist", + "keywords": [ + "argv", + "getopt", + "parser", + "optimist" + ], + "author": { + "name": "James Halliday", + "email": "mail@substack.net", + "url": "http://substack.net" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + }, + "license": "MIT", + "auto-changelog": { + "output": "CHANGELOG.md", + "template": "keepachangelog", + "unreleased": false, + "commitLimit": false, + "backfillLimit": false, + "hideCredit": true + }, + "publishConfig": { + "ignore": [ + ".github/workflows" + ] + } +} diff --git a/backend/node_modules/minimist/test/all_bool.js b/backend/node_modules/minimist/test/all_bool.js new file mode 100644 index 0000000..befa0c9 --- /dev/null +++ b/backend/node_modules/minimist/test/all_bool.js @@ -0,0 +1,34 @@ +'use strict'; + +var parse = require('../'); +var test = require('tape'); + +test('flag boolean true (default all --args to boolean)', function (t) { + var argv = parse(['moo', '--honk', 'cow'], { + boolean: true, + }); + + t.deepEqual(argv, { + honk: true, + _: ['moo', 'cow'], + }); + + t.deepEqual(typeof argv.honk, 'boolean'); + t.end(); +}); + +test('flag boolean true only affects double hyphen arguments without equals signs', function (t) { + var argv = parse(['moo', '--honk', 'cow', '-p', '55', '--tacos=good'], { + boolean: true, + }); + + t.deepEqual(argv, { + honk: true, + tacos: 'good', + p: 55, + _: ['moo', 'cow'], + }); + + t.deepEqual(typeof argv.honk, 'boolean'); + t.end(); +}); diff --git a/backend/node_modules/minimist/test/bool.js b/backend/node_modules/minimist/test/bool.js new file mode 100644 index 0000000..e58d47e --- /dev/null +++ b/backend/node_modules/minimist/test/bool.js @@ -0,0 +1,177 @@ +'use strict'; + +var parse = require('../'); +var test = require('tape'); + +test('flag boolean default false', function (t) { + var argv = parse(['moo'], { + boolean: ['t', 'verbose'], + default: { verbose: false, t: false }, + }); + + t.deepEqual(argv, { + verbose: false, + t: false, + _: ['moo'], + }); + + t.deepEqual(typeof argv.verbose, 'boolean'); + t.deepEqual(typeof argv.t, 'boolean'); + t.end(); + +}); + +test('boolean groups', function (t) { + var argv = parse(['-x', '-z', 'one', 'two', 'three'], { + boolean: ['x', 'y', 'z'], + }); + + t.deepEqual(argv, { + x: true, + y: false, + z: true, + _: ['one', 'two', 'three'], + }); + + t.deepEqual(typeof argv.x, 'boolean'); + t.deepEqual(typeof argv.y, 'boolean'); + t.deepEqual(typeof argv.z, 'boolean'); + t.end(); +}); +test('boolean and alias with chainable api', function (t) { + var aliased = ['-h', 'derp']; + var regular = ['--herp', 'derp']; + var aliasedArgv = parse(aliased, { + boolean: 'herp', + alias: { h: 'herp' }, + }); + var propertyArgv = parse(regular, { + boolean: 'herp', + alias: { h: 'herp' }, + }); + var expected = { + herp: true, + h: true, + _: ['derp'], + }; + + t.same(aliasedArgv, expected); + t.same(propertyArgv, expected); + t.end(); +}); + +test('boolean and alias with options hash', function (t) { + var aliased = ['-h', 'derp']; + var regular = ['--herp', 'derp']; + var opts = { + alias: { h: 'herp' }, + boolean: 'herp', + }; + var aliasedArgv = parse(aliased, opts); + var propertyArgv = parse(regular, opts); + var expected = { + herp: true, + h: true, + _: ['derp'], + }; + t.same(aliasedArgv, expected); + t.same(propertyArgv, expected); + t.end(); +}); + +test('boolean and alias array with options hash', function (t) { + var aliased = ['-h', 'derp']; + var regular = ['--herp', 'derp']; + var alt = ['--harp', 'derp']; + var opts = { + alias: { h: ['herp', 'harp'] }, + boolean: 'h', + }; + var aliasedArgv = parse(aliased, opts); + var propertyArgv = parse(regular, opts); + var altPropertyArgv = parse(alt, opts); + var expected = { + harp: true, + herp: true, + h: true, + _: ['derp'], + }; + t.same(aliasedArgv, expected); + t.same(propertyArgv, expected); + t.same(altPropertyArgv, expected); + t.end(); +}); + +test('boolean and alias using explicit true', function (t) { + var aliased = ['-h', 'true']; + var regular = ['--herp', 'true']; + var opts = { + alias: { h: 'herp' }, + boolean: 'h', + }; + var aliasedArgv = parse(aliased, opts); + var propertyArgv = parse(regular, opts); + var expected = { + herp: true, + h: true, + _: [], + }; + + t.same(aliasedArgv, expected); + t.same(propertyArgv, expected); + t.end(); +}); + +// regression, see https://github.com/substack/node-optimist/issues/71 +test('boolean and --x=true', function (t) { + var parsed = parse(['--boool', '--other=true'], { + boolean: 'boool', + }); + + t.same(parsed.boool, true); + t.same(parsed.other, 'true'); + + parsed = parse(['--boool', '--other=false'], { + boolean: 'boool', + }); + + t.same(parsed.boool, true); + t.same(parsed.other, 'false'); + t.end(); +}); + +test('boolean --boool=true', function (t) { + var parsed = parse(['--boool=true'], { + default: { + boool: false, + }, + boolean: ['boool'], + }); + + t.same(parsed.boool, true); + t.end(); +}); + +test('boolean --boool=false', function (t) { + var parsed = parse(['--boool=false'], { + default: { + boool: true, + }, + boolean: ['boool'], + }); + + t.same(parsed.boool, false); + t.end(); +}); + +test('boolean using something similar to true', function (t) { + var opts = { boolean: 'h' }; + var result = parse(['-h', 'true.txt'], opts); + var expected = { + h: true, + _: ['true.txt'], + }; + + t.same(result, expected); + t.end(); +}); diff --git a/backend/node_modules/minimist/test/dash.js b/backend/node_modules/minimist/test/dash.js new file mode 100644 index 0000000..7078817 --- /dev/null +++ b/backend/node_modules/minimist/test/dash.js @@ -0,0 +1,43 @@ +'use strict'; + +var parse = require('../'); +var test = require('tape'); + +test('-', function (t) { + t.plan(6); + t.deepEqual(parse(['-n', '-']), { n: '-', _: [] }); + t.deepEqual(parse(['--nnn', '-']), { nnn: '-', _: [] }); + t.deepEqual(parse(['-']), { _: ['-'] }); + t.deepEqual(parse(['-f-']), { f: '-', _: [] }); + t.deepEqual( + parse(['-b', '-'], { boolean: 'b' }), + { b: true, _: ['-'] } + ); + t.deepEqual( + parse(['-s', '-'], { string: 's' }), + { s: '-', _: [] } + ); +}); + +test('-a -- b', function (t) { + t.plan(2); + t.deepEqual(parse(['-a', '--', 'b']), { a: true, _: ['b'] }); + t.deepEqual(parse(['--a', '--', 'b']), { a: true, _: ['b'] }); +}); + +test('move arguments after the -- into their own `--` array', function (t) { + t.plan(1); + t.deepEqual( + parse(['--name', 'John', 'before', '--', 'after'], { '--': true }), + { name: 'John', _: ['before'], '--': ['after'] } + ); +}); + +test('--- option value', function (t) { + // A multi-dash value is largely an edge case, but check the behaviour is as expected, + // and in particular the same for short option and long option (as made consistent in Jan 2023). + t.plan(2); + t.deepEqual(parse(['-n', '---']), { n: '---', _: [] }); + t.deepEqual(parse(['--nnn', '---']), { nnn: '---', _: [] }); +}); + diff --git a/backend/node_modules/minimist/test/default_bool.js b/backend/node_modules/minimist/test/default_bool.js new file mode 100644 index 0000000..4e9f625 --- /dev/null +++ b/backend/node_modules/minimist/test/default_bool.js @@ -0,0 +1,37 @@ +'use strict'; + +var test = require('tape'); +var parse = require('../'); + +test('boolean default true', function (t) { + var argv = parse([], { + boolean: 'sometrue', + default: { sometrue: true }, + }); + t.equal(argv.sometrue, true); + t.end(); +}); + +test('boolean default false', function (t) { + var argv = parse([], { + boolean: 'somefalse', + default: { somefalse: false }, + }); + t.equal(argv.somefalse, false); + t.end(); +}); + +test('boolean default to null', function (t) { + var argv = parse([], { + boolean: 'maybe', + default: { maybe: null }, + }); + t.equal(argv.maybe, null); + + var argvLong = parse(['--maybe'], { + boolean: 'maybe', + default: { maybe: null }, + }); + t.equal(argvLong.maybe, true); + t.end(); +}); diff --git a/backend/node_modules/minimist/test/dotted.js b/backend/node_modules/minimist/test/dotted.js new file mode 100644 index 0000000..126ff03 --- /dev/null +++ b/backend/node_modules/minimist/test/dotted.js @@ -0,0 +1,24 @@ +'use strict'; + +var parse = require('../'); +var test = require('tape'); + +test('dotted alias', function (t) { + var argv = parse(['--a.b', '22'], { default: { 'a.b': 11 }, alias: { 'a.b': 'aa.bb' } }); + t.equal(argv.a.b, 22); + t.equal(argv.aa.bb, 22); + t.end(); +}); + +test('dotted default', function (t) { + var argv = parse('', { default: { 'a.b': 11 }, alias: { 'a.b': 'aa.bb' } }); + t.equal(argv.a.b, 11); + t.equal(argv.aa.bb, 11); + t.end(); +}); + +test('dotted default with no alias', function (t) { + var argv = parse('', { default: { 'a.b': 11 } }); + t.equal(argv.a.b, 11); + t.end(); +}); diff --git a/backend/node_modules/minimist/test/kv_short.js b/backend/node_modules/minimist/test/kv_short.js new file mode 100644 index 0000000..6d1b53a --- /dev/null +++ b/backend/node_modules/minimist/test/kv_short.js @@ -0,0 +1,32 @@ +'use strict'; + +var parse = require('../'); +var test = require('tape'); + +test('short -k=v', function (t) { + t.plan(1); + + var argv = parse(['-b=123']); + t.deepEqual(argv, { b: 123, _: [] }); +}); + +test('multi short -k=v', function (t) { + t.plan(1); + + var argv = parse(['-a=whatever', '-b=robots']); + t.deepEqual(argv, { a: 'whatever', b: 'robots', _: [] }); +}); + +test('short with embedded equals -k=a=b', function (t) { + t.plan(1); + + var argv = parse(['-k=a=b']); + t.deepEqual(argv, { k: 'a=b', _: [] }); +}); + +test('short with later equals like -ab=c', function (t) { + t.plan(1); + + var argv = parse(['-ab=c']); + t.deepEqual(argv, { a: true, b: 'c', _: [] }); +}); diff --git a/backend/node_modules/minimist/test/long.js b/backend/node_modules/minimist/test/long.js new file mode 100644 index 0000000..9fef51f --- /dev/null +++ b/backend/node_modules/minimist/test/long.js @@ -0,0 +1,33 @@ +'use strict'; + +var test = require('tape'); +var parse = require('../'); + +test('long opts', function (t) { + t.deepEqual( + parse(['--bool']), + { bool: true, _: [] }, + 'long boolean' + ); + t.deepEqual( + parse(['--pow', 'xixxle']), + { pow: 'xixxle', _: [] }, + 'long capture sp' + ); + t.deepEqual( + parse(['--pow=xixxle']), + { pow: 'xixxle', _: [] }, + 'long capture eq' + ); + t.deepEqual( + parse(['--host', 'localhost', '--port', '555']), + { host: 'localhost', port: 555, _: [] }, + 'long captures sp' + ); + t.deepEqual( + parse(['--host=localhost', '--port=555']), + { host: 'localhost', port: 555, _: [] }, + 'long captures eq' + ); + t.end(); +}); diff --git a/backend/node_modules/minimist/test/num.js b/backend/node_modules/minimist/test/num.js new file mode 100644 index 0000000..074393e --- /dev/null +++ b/backend/node_modules/minimist/test/num.js @@ -0,0 +1,38 @@ +'use strict'; + +var parse = require('../'); +var test = require('tape'); + +test('nums', function (t) { + var argv = parse([ + '-x', '1234', + '-y', '5.67', + '-z', '1e7', + '-w', '10f', + '--hex', '0xdeadbeef', + '789', + ]); + t.deepEqual(argv, { + x: 1234, + y: 5.67, + z: 1e7, + w: '10f', + hex: 0xdeadbeef, + _: [789], + }); + t.deepEqual(typeof argv.x, 'number'); + t.deepEqual(typeof argv.y, 'number'); + t.deepEqual(typeof argv.z, 'number'); + t.deepEqual(typeof argv.w, 'string'); + t.deepEqual(typeof argv.hex, 'number'); + t.deepEqual(typeof argv._[0], 'number'); + t.end(); +}); + +test('already a number', function (t) { + var argv = parse(['-x', 1234, 789]); + t.deepEqual(argv, { x: 1234, _: [789] }); + t.deepEqual(typeof argv.x, 'number'); + t.deepEqual(typeof argv._[0], 'number'); + t.end(); +}); diff --git a/backend/node_modules/minimist/test/parse.js b/backend/node_modules/minimist/test/parse.js new file mode 100644 index 0000000..65d9d90 --- /dev/null +++ b/backend/node_modules/minimist/test/parse.js @@ -0,0 +1,209 @@ +'use strict'; + +var parse = require('../'); +var test = require('tape'); + +test('parse args', function (t) { + t.deepEqual( + parse(['--no-moo']), + { moo: false, _: [] }, + 'no' + ); + t.deepEqual( + parse(['-v', 'a', '-v', 'b', '-v', 'c']), + { v: ['a', 'b', 'c'], _: [] }, + 'multi' + ); + t.end(); +}); + +test('comprehensive', function (t) { + t.deepEqual( + parse([ + '--name=meowmers', 'bare', '-cats', 'woo', + '-h', 'awesome', '--multi=quux', + '--key', 'value', + '-b', '--bool', '--no-meep', '--multi=baz', + '--', '--not-a-flag', 'eek', + ]), + { + c: true, + a: true, + t: true, + s: 'woo', + h: 'awesome', + b: true, + bool: true, + key: 'value', + multi: ['quux', 'baz'], + meep: false, + name: 'meowmers', + _: ['bare', '--not-a-flag', 'eek'], + } + ); + t.end(); +}); + +test('flag boolean', function (t) { + var argv = parse(['-t', 'moo'], { boolean: 't' }); + t.deepEqual(argv, { t: true, _: ['moo'] }); + t.deepEqual(typeof argv.t, 'boolean'); + t.end(); +}); + +test('flag boolean value', function (t) { + var argv = parse(['--verbose', 'false', 'moo', '-t', 'true'], { + boolean: ['t', 'verbose'], + default: { verbose: true }, + }); + + t.deepEqual(argv, { + verbose: false, + t: true, + _: ['moo'], + }); + + t.deepEqual(typeof argv.verbose, 'boolean'); + t.deepEqual(typeof argv.t, 'boolean'); + t.end(); +}); + +test('newlines in params', function (t) { + var args = parse(['-s', 'X\nX']); + t.deepEqual(args, { _: [], s: 'X\nX' }); + + // reproduce in bash: + // VALUE="new + // line" + // node program.js --s="$VALUE" + args = parse(['--s=X\nX']); + t.deepEqual(args, { _: [], s: 'X\nX' }); + t.end(); +}); + +test('strings', function (t) { + var s = parse(['-s', '0001234'], { string: 's' }).s; + t.equal(s, '0001234'); + t.equal(typeof s, 'string'); + + var x = parse(['-x', '56'], { string: 'x' }).x; + t.equal(x, '56'); + t.equal(typeof x, 'string'); + t.end(); +}); + +test('stringArgs', function (t) { + var s = parse([' ', ' '], { string: '_' })._; + t.same(s.length, 2); + t.same(typeof s[0], 'string'); + t.same(s[0], ' '); + t.same(typeof s[1], 'string'); + t.same(s[1], ' '); + t.end(); +}); + +test('empty strings', function (t) { + var s = parse(['-s'], { string: 's' }).s; + t.equal(s, ''); + t.equal(typeof s, 'string'); + + var str = parse(['--str'], { string: 'str' }).str; + t.equal(str, ''); + t.equal(typeof str, 'string'); + + var letters = parse(['-art'], { + string: ['a', 't'], + }); + + t.equal(letters.a, ''); + t.equal(letters.r, true); + t.equal(letters.t, ''); + + t.end(); +}); + +test('string and alias', function (t) { + var x = parse(['--str', '000123'], { + string: 's', + alias: { s: 'str' }, + }); + + t.equal(x.str, '000123'); + t.equal(typeof x.str, 'string'); + t.equal(x.s, '000123'); + t.equal(typeof x.s, 'string'); + + var y = parse(['-s', '000123'], { + string: 'str', + alias: { str: 's' }, + }); + + t.equal(y.str, '000123'); + t.equal(typeof y.str, 'string'); + t.equal(y.s, '000123'); + t.equal(typeof y.s, 'string'); + + var z = parse(['-s123'], { + alias: { str: ['s', 'S'] }, + string: ['str'], + }); + + t.deepEqual( + z, + { _: [], s: '123', S: '123', str: '123' }, + 'opt.string works with multiple aliases' + ); + t.end(); +}); + +test('slashBreak', function (t) { + t.same( + parse(['-I/foo/bar/baz']), + { I: '/foo/bar/baz', _: [] } + ); + t.same( + parse(['-xyz/foo/bar/baz']), + { x: true, y: true, z: '/foo/bar/baz', _: [] } + ); + t.end(); +}); + +test('alias', function (t) { + var argv = parse(['-f', '11', '--zoom', '55'], { + alias: { z: 'zoom' }, + }); + t.equal(argv.zoom, 55); + t.equal(argv.z, argv.zoom); + t.equal(argv.f, 11); + t.end(); +}); + +test('multiAlias', function (t) { + var argv = parse(['-f', '11', '--zoom', '55'], { + alias: { z: ['zm', 'zoom'] }, + }); + t.equal(argv.zoom, 55); + t.equal(argv.z, argv.zoom); + t.equal(argv.z, argv.zm); + t.equal(argv.f, 11); + t.end(); +}); + +test('nested dotted objects', function (t) { + var argv = parse([ + '--foo.bar', '3', '--foo.baz', '4', + '--foo.quux.quibble', '5', '--foo.quux.o_O', + '--beep.boop', + ]); + + t.same(argv.foo, { + bar: 3, + baz: 4, + quux: { + quibble: 5, + o_O: true, + }, + }); + t.same(argv.beep, { boop: true }); + t.end(); +}); diff --git a/backend/node_modules/minimist/test/parse_modified.js b/backend/node_modules/minimist/test/parse_modified.js new file mode 100644 index 0000000..32965d1 --- /dev/null +++ b/backend/node_modules/minimist/test/parse_modified.js @@ -0,0 +1,11 @@ +'use strict'; + +var parse = require('../'); +var test = require('tape'); + +test('parse with modifier functions', function (t) { + t.plan(1); + + var argv = parse(['-b', '123'], { boolean: 'b' }); + t.deepEqual(argv, { b: true, _: [123] }); +}); diff --git a/backend/node_modules/minimist/test/proto.js b/backend/node_modules/minimist/test/proto.js new file mode 100644 index 0000000..6e629dd --- /dev/null +++ b/backend/node_modules/minimist/test/proto.js @@ -0,0 +1,64 @@ +'use strict'; + +/* eslint no-proto: 0 */ + +var parse = require('../'); +var test = require('tape'); + +test('proto pollution', function (t) { + var argv = parse(['--__proto__.x', '123']); + t.equal({}.x, undefined); + t.equal(argv.__proto__.x, undefined); + t.equal(argv.x, undefined); + t.end(); +}); + +test('proto pollution (array)', function (t) { + var argv = parse(['--x', '4', '--x', '5', '--x.__proto__.z', '789']); + t.equal({}.z, undefined); + t.deepEqual(argv.x, [4, 5]); + t.equal(argv.x.z, undefined); + t.equal(argv.x.__proto__.z, undefined); + t.end(); +}); + +test('proto pollution (number)', function (t) { + var argv = parse(['--x', '5', '--x.__proto__.z', '100']); + t.equal({}.z, undefined); + t.equal((4).z, undefined); + t.equal(argv.x, 5); + t.equal(argv.x.z, undefined); + t.end(); +}); + +test('proto pollution (string)', function (t) { + var argv = parse(['--x', 'abc', '--x.__proto__.z', 'def']); + t.equal({}.z, undefined); + t.equal('...'.z, undefined); + t.equal(argv.x, 'abc'); + t.equal(argv.x.z, undefined); + t.end(); +}); + +test('proto pollution (constructor)', function (t) { + var argv = parse(['--constructor.prototype.y', '123']); + t.equal({}.y, undefined); + t.equal(argv.y, undefined); + t.end(); +}); + +test('proto pollution (constructor function)', function (t) { + var argv = parse(['--_.concat.constructor.prototype.y', '123']); + function fnToBeTested() {} + t.equal(fnToBeTested.y, undefined); + t.equal(argv.y, undefined); + t.end(); +}); + +// powered by snyk - https://github.com/backstage/backstage/issues/10343 +test('proto pollution (constructor function) snyk', function (t) { + var argv = parse('--_.constructor.constructor.prototype.foo bar'.split(' ')); + t.equal(function () {}.foo, undefined); + t.equal(argv.y, undefined); + t.end(); +}); diff --git a/backend/node_modules/minimist/test/short.js b/backend/node_modules/minimist/test/short.js new file mode 100644 index 0000000..4a7b843 --- /dev/null +++ b/backend/node_modules/minimist/test/short.js @@ -0,0 +1,69 @@ +'use strict'; + +var parse = require('../'); +var test = require('tape'); + +test('numeric short args', function (t) { + t.plan(2); + t.deepEqual(parse(['-n123']), { n: 123, _: [] }); + t.deepEqual( + parse(['-123', '456']), + { 1: true, 2: true, 3: 456, _: [] } + ); +}); + +test('short', function (t) { + t.deepEqual( + parse(['-b']), + { b: true, _: [] }, + 'short boolean' + ); + t.deepEqual( + parse(['foo', 'bar', 'baz']), + { _: ['foo', 'bar', 'baz'] }, + 'bare' + ); + t.deepEqual( + parse(['-cats']), + { c: true, a: true, t: true, s: true, _: [] }, + 'group' + ); + t.deepEqual( + parse(['-cats', 'meow']), + { c: true, a: true, t: true, s: 'meow', _: [] }, + 'short group next' + ); + t.deepEqual( + parse(['-h', 'localhost']), + { h: 'localhost', _: [] }, + 'short capture' + ); + t.deepEqual( + parse(['-h', 'localhost', '-p', '555']), + { h: 'localhost', p: 555, _: [] }, + 'short captures' + ); + t.end(); +}); + +test('mixed short bool and capture', function (t) { + t.same( + parse(['-h', 'localhost', '-fp', '555', 'script.js']), + { + f: true, p: 555, h: 'localhost', + _: ['script.js'], + } + ); + t.end(); +}); + +test('short and long', function (t) { + t.deepEqual( + parse(['-h', 'localhost', '-fp', '555', 'script.js']), + { + f: true, p: 555, h: 'localhost', + _: ['script.js'], + } + ); + t.end(); +}); diff --git a/backend/node_modules/minimist/test/stop_early.js b/backend/node_modules/minimist/test/stop_early.js new file mode 100644 index 0000000..52a6a91 --- /dev/null +++ b/backend/node_modules/minimist/test/stop_early.js @@ -0,0 +1,17 @@ +'use strict'; + +var parse = require('../'); +var test = require('tape'); + +test('stops parsing on the first non-option when stopEarly is set', function (t) { + var argv = parse(['--aaa', 'bbb', 'ccc', '--ddd'], { + stopEarly: true, + }); + + t.deepEqual(argv, { + aaa: 'bbb', + _: ['ccc', '--ddd'], + }); + + t.end(); +}); diff --git a/backend/node_modules/minimist/test/unknown.js b/backend/node_modules/minimist/test/unknown.js new file mode 100644 index 0000000..4f2e0ca --- /dev/null +++ b/backend/node_modules/minimist/test/unknown.js @@ -0,0 +1,104 @@ +'use strict'; + +var parse = require('../'); +var test = require('tape'); + +test('boolean and alias is not unknown', function (t) { + var unknown = []; + function unknownFn(arg) { + unknown.push(arg); + return false; + } + var aliased = ['-h', 'true', '--derp', 'true']; + var regular = ['--herp', 'true', '-d', 'true']; + var opts = { + alias: { h: 'herp' }, + boolean: 'h', + unknown: unknownFn, + }; + parse(aliased, opts); + parse(regular, opts); + + t.same(unknown, ['--derp', '-d']); + t.end(); +}); + +test('flag boolean true any double hyphen argument is not unknown', function (t) { + var unknown = []; + function unknownFn(arg) { + unknown.push(arg); + return false; + } + var argv = parse(['--honk', '--tacos=good', 'cow', '-p', '55'], { + boolean: true, + unknown: unknownFn, + }); + t.same(unknown, ['--tacos=good', 'cow', '-p']); + t.same(argv, { + honk: true, + _: [], + }); + t.end(); +}); + +test('string and alias is not unknown', function (t) { + var unknown = []; + function unknownFn(arg) { + unknown.push(arg); + return false; + } + var aliased = ['-h', 'hello', '--derp', 'goodbye']; + var regular = ['--herp', 'hello', '-d', 'moon']; + var opts = { + alias: { h: 'herp' }, + string: 'h', + unknown: unknownFn, + }; + parse(aliased, opts); + parse(regular, opts); + + t.same(unknown, ['--derp', '-d']); + t.end(); +}); + +test('default and alias is not unknown', function (t) { + var unknown = []; + function unknownFn(arg) { + unknown.push(arg); + return false; + } + var aliased = ['-h', 'hello']; + var regular = ['--herp', 'hello']; + var opts = { + default: { h: 'bar' }, + alias: { h: 'herp' }, + unknown: unknownFn, + }; + parse(aliased, opts); + parse(regular, opts); + + t.same(unknown, []); + t.end(); + unknownFn(); // exercise fn for 100% coverage +}); + +test('value following -- is not unknown', function (t) { + var unknown = []; + function unknownFn(arg) { + unknown.push(arg); + return false; + } + var aliased = ['--bad', '--', 'good', 'arg']; + var opts = { + '--': true, + unknown: unknownFn, + }; + var argv = parse(aliased, opts); + + t.same(unknown, ['--bad']); + t.same(argv, { + '--': ['good', 'arg'], + _: [], + }); + t.end(); +}); diff --git a/backend/node_modules/minimist/test/whitespace.js b/backend/node_modules/minimist/test/whitespace.js new file mode 100644 index 0000000..4fdaf1d --- /dev/null +++ b/backend/node_modules/minimist/test/whitespace.js @@ -0,0 +1,10 @@ +'use strict'; + +var parse = require('../'); +var test = require('tape'); + +test('whitespace should be whitespace', function (t) { + t.plan(1); + var x = parse(['-x', '\t']).x; + t.equal(x, '\t'); +}); diff --git a/backend/node_modules/mkdirp/LICENSE b/backend/node_modules/mkdirp/LICENSE new file mode 100644 index 0000000..432d1ae --- /dev/null +++ b/backend/node_modules/mkdirp/LICENSE @@ -0,0 +1,21 @@ +Copyright 2010 James Halliday (mail@substack.net) + +This project is free software released under the MIT/X11 license: + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/backend/node_modules/mkdirp/bin/cmd.js b/backend/node_modules/mkdirp/bin/cmd.js new file mode 100644 index 0000000..d95de15 --- /dev/null +++ b/backend/node_modules/mkdirp/bin/cmd.js @@ -0,0 +1,33 @@ +#!/usr/bin/env node + +var mkdirp = require('../'); +var minimist = require('minimist'); +var fs = require('fs'); + +var argv = minimist(process.argv.slice(2), { + alias: { m: 'mode', h: 'help' }, + string: [ 'mode' ] +}); +if (argv.help) { + fs.createReadStream(__dirname + '/usage.txt').pipe(process.stdout); + return; +} + +var paths = argv._.slice(); +var mode = argv.mode ? parseInt(argv.mode, 8) : undefined; + +(function next () { + if (paths.length === 0) return; + var p = paths.shift(); + + if (mode === undefined) mkdirp(p, cb) + else mkdirp(p, mode, cb) + + function cb (err) { + if (err) { + console.error(err.message); + process.exit(1); + } + else next(); + } +})(); diff --git a/backend/node_modules/mkdirp/bin/usage.txt b/backend/node_modules/mkdirp/bin/usage.txt new file mode 100644 index 0000000..f952aa2 --- /dev/null +++ b/backend/node_modules/mkdirp/bin/usage.txt @@ -0,0 +1,12 @@ +usage: mkdirp [DIR1,DIR2..] {OPTIONS} + + Create each supplied directory including any necessary parent directories that + don't yet exist. + + If the directory already exists, do nothing. + +OPTIONS are: + + -m, --mode If a directory needs to be created, set the mode as an octal + permission string. + diff --git a/backend/node_modules/mkdirp/index.js b/backend/node_modules/mkdirp/index.js new file mode 100644 index 0000000..0890ac3 --- /dev/null +++ b/backend/node_modules/mkdirp/index.js @@ -0,0 +1,102 @@ +var path = require('path'); +var fs = require('fs'); +var _0777 = parseInt('0777', 8); + +module.exports = mkdirP.mkdirp = mkdirP.mkdirP = mkdirP; + +function mkdirP (p, opts, f, made) { + if (typeof opts === 'function') { + f = opts; + opts = {}; + } + else if (!opts || typeof opts !== 'object') { + opts = { mode: opts }; + } + + var mode = opts.mode; + var xfs = opts.fs || fs; + + if (mode === undefined) { + mode = _0777 + } + if (!made) made = null; + + var cb = f || /* istanbul ignore next */ function () {}; + p = path.resolve(p); + + xfs.mkdir(p, mode, function (er) { + if (!er) { + made = made || p; + return cb(null, made); + } + switch (er.code) { + case 'ENOENT': + /* istanbul ignore if */ + if (path.dirname(p) === p) return cb(er); + mkdirP(path.dirname(p), opts, function (er, made) { + /* istanbul ignore if */ + if (er) cb(er, made); + else mkdirP(p, opts, cb, made); + }); + break; + + // In the case of any other error, just see if there's a dir + // there already. If so, then hooray! If not, then something + // is borked. + default: + xfs.stat(p, function (er2, stat) { + // if the stat fails, then that's super weird. + // let the original error be the failure reason. + if (er2 || !stat.isDirectory()) cb(er, made) + else cb(null, made); + }); + break; + } + }); +} + +mkdirP.sync = function sync (p, opts, made) { + if (!opts || typeof opts !== 'object') { + opts = { mode: opts }; + } + + var mode = opts.mode; + var xfs = opts.fs || fs; + + if (mode === undefined) { + mode = _0777 + } + if (!made) made = null; + + p = path.resolve(p); + + try { + xfs.mkdirSync(p, mode); + made = made || p; + } + catch (err0) { + switch (err0.code) { + case 'ENOENT' : + made = sync(path.dirname(p), opts, made); + sync(p, opts, made); + break; + + // In the case of any other error, just see if there's a dir + // there already. If so, then hooray! If not, then something + // is borked. + default: + var stat; + try { + stat = xfs.statSync(p); + } + catch (err1) /* istanbul ignore next */ { + throw err0; + } + /* istanbul ignore if */ + if (!stat.isDirectory()) throw err0; + break; + } + } + + return made; +}; diff --git a/backend/node_modules/mkdirp/package.json b/backend/node_modules/mkdirp/package.json new file mode 100644 index 0000000..951e58d --- /dev/null +++ b/backend/node_modules/mkdirp/package.json @@ -0,0 +1,33 @@ +{ + "name": "mkdirp", + "description": "Recursively mkdir, like `mkdir -p`", + "version": "0.5.6", + "publishConfig": { + "tag": "legacy" + }, + "author": "James Halliday (http://substack.net)", + "main": "index.js", + "keywords": [ + "mkdir", + "directory" + ], + "repository": { + "type": "git", + "url": "https://github.com/substack/node-mkdirp.git" + }, + "scripts": { + "test": "tap test/*.js" + }, + "dependencies": { + "minimist": "^1.2.6" + }, + "devDependencies": { + "tap": "^16.0.1" + }, + "bin": "bin/cmd.js", + "license": "MIT", + "files": [ + "bin", + "index.js" + ] +} diff --git a/backend/node_modules/mkdirp/readme.markdown b/backend/node_modules/mkdirp/readme.markdown new file mode 100644 index 0000000..fc314bf --- /dev/null +++ b/backend/node_modules/mkdirp/readme.markdown @@ -0,0 +1,100 @@ +# mkdirp + +Like `mkdir -p`, but in node.js! + +[![build status](https://secure.travis-ci.org/substack/node-mkdirp.png)](http://travis-ci.org/substack/node-mkdirp) + +# example + +## pow.js + +```js +var mkdirp = require('mkdirp'); + +mkdirp('/tmp/foo/bar/baz', function (err) { + if (err) console.error(err) + else console.log('pow!') +}); +``` + +Output + +``` +pow! +``` + +And now /tmp/foo/bar/baz exists, huzzah! + +# methods + +```js +var mkdirp = require('mkdirp'); +``` + +## mkdirp(dir, opts, cb) + +Create a new directory and any necessary subdirectories at `dir` with octal +permission string `opts.mode`. If `opts` is a non-object, it will be treated as +the `opts.mode`. + +If `opts.mode` isn't specified, it defaults to `0777`. + +`cb(err, made)` fires with the error or the first directory `made` +that had to be created, if any. + +You can optionally pass in an alternate `fs` implementation by passing in +`opts.fs`. Your implementation should have `opts.fs.mkdir(path, mode, cb)` and +`opts.fs.stat(path, cb)`. + +## mkdirp.sync(dir, opts) + +Synchronously create a new directory and any necessary subdirectories at `dir` +with octal permission string `opts.mode`. If `opts` is a non-object, it will be +treated as the `opts.mode`. + +If `opts.mode` isn't specified, it defaults to `0777`. + +Returns the first directory that had to be created, if any. + +You can optionally pass in an alternate `fs` implementation by passing in +`opts.fs`. Your implementation should have `opts.fs.mkdirSync(path, mode)` and +`opts.fs.statSync(path)`. + +# usage + +This package also ships with a `mkdirp` command. + +``` +usage: mkdirp [DIR1,DIR2..] {OPTIONS} + + Create each supplied directory including any necessary parent directories that + don't yet exist. + + If the directory already exists, do nothing. + +OPTIONS are: + + -m, --mode If a directory needs to be created, set the mode as an octal + permission string. + +``` + +# install + +With [npm](http://npmjs.org) do: + +``` +npm install mkdirp +``` + +to get the library, or + +``` +npm install -g mkdirp +``` + +to get the command. + +# license + +MIT diff --git a/backend/node_modules/multer/LICENSE b/backend/node_modules/multer/LICENSE new file mode 100644 index 0000000..6c011b1 --- /dev/null +++ b/backend/node_modules/multer/LICENSE @@ -0,0 +1,17 @@ +Copyright (c) 2014 Hage Yaapa <[http://www.hacksparrow.com](http://www.hacksparrow.com)> + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/backend/node_modules/multer/README.md b/backend/node_modules/multer/README.md new file mode 100644 index 0000000..7f5d080 --- /dev/null +++ b/backend/node_modules/multer/README.md @@ -0,0 +1,333 @@ +# Multer [![Build Status](https://travis-ci.org/expressjs/multer.svg?branch=master)](https://travis-ci.org/expressjs/multer) [![NPM version](https://badge.fury.io/js/multer.svg)](https://badge.fury.io/js/multer) [![js-standard-style](https://img.shields.io/badge/code%20style-standard-brightgreen.svg?style=flat)](https://github.com/feross/standard) + +Multer is a node.js middleware for handling `multipart/form-data`, which is primarily used for uploading files. It is written +on top of [busboy](https://github.com/mscdex/busboy) for maximum efficiency. + +**NOTE**: Multer will not process any form which is not multipart (`multipart/form-data`). + +## Translations + +This README is also available in other languages: + +- [Español](https://github.com/expressjs/multer/blob/master/doc/README-es.md) (Spanish) +- [简体中文](https://github.com/expressjs/multer/blob/master/doc/README-zh-cn.md) (Chinese) +- [한국어](https://github.com/expressjs/multer/blob/master/doc/README-ko.md) (Korean) +- [Русский язык](https://github.com/expressjs/multer/blob/master/doc/README-ru.md) (Russian) +- [Việt Nam](https://github.com/expressjs/multer/blob/master/doc/README-vi.md) (Vietnam) +- [Português](https://github.com/expressjs/multer/blob/master/doc/README-pt-br.md) (Portuguese Brazil) + +## Installation + +```sh +$ npm install --save multer +``` + +## Usage + +Multer adds a `body` object and a `file` or `files` object to the `request` object. The `body` object contains the values of the text fields of the form, the `file` or `files` object contains the files uploaded via the form. + +Basic usage example: + +Don't forget the `enctype="multipart/form-data"` in your form. + +```html +
+ +
+``` + +```javascript +const express = require('express') +const multer = require('multer') +const upload = multer({ dest: 'uploads/' }) + +const app = express() + +app.post('/profile', upload.single('avatar'), function (req, res, next) { + // req.file is the `avatar` file + // req.body will hold the text fields, if there were any +}) + +app.post('/photos/upload', upload.array('photos', 12), function (req, res, next) { + // req.files is array of `photos` files + // req.body will contain the text fields, if there were any +}) + +const cpUpload = upload.fields([{ name: 'avatar', maxCount: 1 }, { name: 'gallery', maxCount: 8 }]) +app.post('/cool-profile', cpUpload, function (req, res, next) { + // req.files is an object (String -> Array) where fieldname is the key, and the value is array of files + // + // e.g. + // req.files['avatar'][0] -> File + // req.files['gallery'] -> Array + // + // req.body will contain the text fields, if there were any +}) +``` + +In case you need to handle a text-only multipart form, you should use the `.none()` method: + +```javascript +const express = require('express') +const app = express() +const multer = require('multer') +const upload = multer() + +app.post('/profile', upload.none(), function (req, res, next) { + // req.body contains the text fields +}) +``` + +Here's an example on how multer is used an HTML form. Take special note of the `enctype="multipart/form-data"` and `name="uploaded_file"` fields: + +```html +
+
+ + + +
+
+``` + +Then in your javascript file you would add these lines to access both the file and the body. It is important that you use the `name` field value from the form in your upload function. This tells multer which field on the request it should look for the files in. If these fields aren't the same in the HTML form and on your server, your upload will fail: + +```javascript +const multer = require('multer') +const upload = multer({ dest: './public/data/uploads/' }) +app.post('/stats', upload.single('uploaded_file'), function (req, res) { + // req.file is the name of your file in the form above, here 'uploaded_file' + // req.body will hold the text fields, if there were any + console.log(req.file, req.body) +}); +``` + + + +## API + +### File information + +Each file contains the following information: + +Key | Description | Note +--- | --- | --- +`fieldname` | Field name specified in the form | +`originalname` | Name of the file on the user's computer | +`encoding` | Encoding type of the file | +`mimetype` | Mime type of the file | +`size` | Size of the file in bytes | +`destination` | The folder to which the file has been saved | `DiskStorage` +`filename` | The name of the file within the `destination` | `DiskStorage` +`path` | The full path to the uploaded file | `DiskStorage` +`buffer` | A `Buffer` of the entire file | `MemoryStorage` + +### `multer(opts)` + +Multer accepts an options object, the most basic of which is the `dest` +property, which tells Multer where to upload the files. In case you omit the +options object, the files will be kept in memory and never written to disk. + +By default, Multer will rename the files so as to avoid naming conflicts. The +renaming function can be customized according to your needs. + +The following are the options that can be passed to Multer. + +Key | Description +--- | --- +`dest` or `storage` | Where to store the files +`fileFilter` | Function to control which files are accepted +`limits` | Limits of the uploaded data +`preservePath` | Keep the full path of files instead of just the base name + +In an average web app, only `dest` might be required, and configured as shown in +the following example. + +```javascript +const upload = multer({ dest: 'uploads/' }) +``` + +If you want more control over your uploads, you'll want to use the `storage` +option instead of `dest`. Multer ships with storage engines `DiskStorage` +and `MemoryStorage`; More engines are available from third parties. + +#### `.single(fieldname)` + +Accept a single file with the name `fieldname`. The single file will be stored +in `req.file`. + +#### `.array(fieldname[, maxCount])` + +Accept an array of files, all with the name `fieldname`. Optionally error out if +more than `maxCount` files are uploaded. The array of files will be stored in +`req.files`. + +#### `.fields(fields)` + +Accept a mix of files, specified by `fields`. An object with arrays of files +will be stored in `req.files`. + +`fields` should be an array of objects with `name` and optionally a `maxCount`. +Example: + +```javascript +[ + { name: 'avatar', maxCount: 1 }, + { name: 'gallery', maxCount: 8 } +] +``` + +#### `.none()` + +Accept only text fields. If any file upload is made, error with code +"LIMIT\_UNEXPECTED\_FILE" will be issued. + +#### `.any()` + +Accepts all files that comes over the wire. An array of files will be stored in +`req.files`. + +**WARNING:** Make sure that you always handle the files that a user uploads. +Never add multer as a global middleware since a malicious user could upload +files to a route that you didn't anticipate. Only use this function on routes +where you are handling the uploaded files. + +### `storage` + +#### `DiskStorage` + +The disk storage engine gives you full control on storing files to disk. + +```javascript +const storage = multer.diskStorage({ + destination: function (req, file, cb) { + cb(null, '/tmp/my-uploads') + }, + filename: function (req, file, cb) { + const uniqueSuffix = Date.now() + '-' + Math.round(Math.random() * 1E9) + cb(null, file.fieldname + '-' + uniqueSuffix) + } +}) + +const upload = multer({ storage: storage }) +``` + +There are two options available, `destination` and `filename`. They are both +functions that determine where the file should be stored. + +`destination` is used to determine within which folder the uploaded files should +be stored. This can also be given as a `string` (e.g. `'/tmp/uploads'`). If no +`destination` is given, the operating system's default directory for temporary +files is used. + +**Note:** You are responsible for creating the directory when providing +`destination` as a function. When passing a string, multer will make sure that +the directory is created for you. + +`filename` is used to determine what the file should be named inside the folder. +If no `filename` is given, each file will be given a random name that doesn't +include any file extension. + +**Note:** Multer will not append any file extension for you, your function +should return a filename complete with an file extension. + +Each function gets passed both the request (`req`) and some information about +the file (`file`) to aid with the decision. + +Note that `req.body` might not have been fully populated yet. It depends on the +order that the client transmits fields and files to the server. + +For understanding the calling convention used in the callback (needing to pass +null as the first param), refer to +[Node.js error handling](https://www.joyent.com/node-js/production/design/errors) + +#### `MemoryStorage` + +The memory storage engine stores the files in memory as `Buffer` objects. It +doesn't have any options. + +```javascript +const storage = multer.memoryStorage() +const upload = multer({ storage: storage }) +``` + +When using memory storage, the file info will contain a field called +`buffer` that contains the entire file. + +**WARNING**: Uploading very large files, or relatively small files in large +numbers very quickly, can cause your application to run out of memory when +memory storage is used. + +### `limits` + +An object specifying the size limits of the following optional properties. Multer passes this object into busboy directly, and the details of the properties can be found on [busboy's page](https://github.com/mscdex/busboy#busboy-methods). + +The following integer values are available: + +Key | Description | Default +--- | --- | --- +`fieldNameSize` | Max field name size | 100 bytes +`fieldSize` | Max field value size (in bytes) | 1MB +`fields` | Max number of non-file fields | Infinity +`fileSize` | For multipart forms, the max file size (in bytes) | Infinity +`files` | For multipart forms, the max number of file fields | Infinity +`parts` | For multipart forms, the max number of parts (fields + files) | Infinity +`headerPairs` | For multipart forms, the max number of header key=>value pairs to parse | 2000 + +Specifying the limits can help protect your site against denial of service (DoS) attacks. + +### `fileFilter` + +Set this to a function to control which files should be uploaded and which +should be skipped. The function should look like this: + +```javascript +function fileFilter (req, file, cb) { + + // The function should call `cb` with a boolean + // to indicate if the file should be accepted + + // To reject this file pass `false`, like so: + cb(null, false) + + // To accept the file pass `true`, like so: + cb(null, true) + + // You can always pass an error if something goes wrong: + cb(new Error('I don\'t have a clue!')) + +} +``` + +## Error handling + +When encountering an error, Multer will delegate the error to Express. You can +display a nice error page using [the standard express way](http://expressjs.com/guide/error-handling.html). + +If you want to catch errors specifically from Multer, you can call the +middleware function by yourself. Also, if you want to catch only [the Multer errors](https://github.com/expressjs/multer/blob/master/lib/multer-error.js), you can use the `MulterError` class that is attached to the `multer` object itself (e.g. `err instanceof multer.MulterError`). + +```javascript +const multer = require('multer') +const upload = multer().single('avatar') + +app.post('/profile', function (req, res) { + upload(req, res, function (err) { + if (err instanceof multer.MulterError) { + // A Multer error occurred when uploading. + } else if (err) { + // An unknown error occurred when uploading. + } + + // Everything went fine. + }) +}) +``` + +## Custom storage engine + +For information on how to build your own storage engine, see [Multer Storage Engine](https://github.com/expressjs/multer/blob/master/StorageEngine.md). + +## License + +[MIT](LICENSE) diff --git a/backend/node_modules/multer/index.js b/backend/node_modules/multer/index.js new file mode 100644 index 0000000..d5b67eb --- /dev/null +++ b/backend/node_modules/multer/index.js @@ -0,0 +1,104 @@ +var makeMiddleware = require('./lib/make-middleware') + +var diskStorage = require('./storage/disk') +var memoryStorage = require('./storage/memory') +var MulterError = require('./lib/multer-error') + +function allowAll (req, file, cb) { + cb(null, true) +} + +function Multer (options) { + if (options.storage) { + this.storage = options.storage + } else if (options.dest) { + this.storage = diskStorage({ destination: options.dest }) + } else { + this.storage = memoryStorage() + } + + this.limits = options.limits + this.preservePath = options.preservePath + this.fileFilter = options.fileFilter || allowAll +} + +Multer.prototype._makeMiddleware = function (fields, fileStrategy) { + function setup () { + var fileFilter = this.fileFilter + var filesLeft = Object.create(null) + + fields.forEach(function (field) { + if (typeof field.maxCount === 'number') { + filesLeft[field.name] = field.maxCount + } else { + filesLeft[field.name] = Infinity + } + }) + + function wrappedFileFilter (req, file, cb) { + if ((filesLeft[file.fieldname] || 0) <= 0) { + return cb(new MulterError('LIMIT_UNEXPECTED_FILE', file.fieldname)) + } + + filesLeft[file.fieldname] -= 1 + fileFilter(req, file, cb) + } + + return { + limits: this.limits, + preservePath: this.preservePath, + storage: this.storage, + fileFilter: wrappedFileFilter, + fileStrategy: fileStrategy + } + } + + return makeMiddleware(setup.bind(this)) +} + +Multer.prototype.single = function (name) { + return this._makeMiddleware([{ name: name, maxCount: 1 }], 'VALUE') +} + +Multer.prototype.array = function (name, maxCount) { + return this._makeMiddleware([{ name: name, maxCount: maxCount }], 'ARRAY') +} + +Multer.prototype.fields = function (fields) { + return this._makeMiddleware(fields, 'OBJECT') +} + +Multer.prototype.none = function () { + return this._makeMiddleware([], 'NONE') +} + +Multer.prototype.any = function () { + function setup () { + return { + limits: this.limits, + preservePath: this.preservePath, + storage: this.storage, + fileFilter: this.fileFilter, + fileStrategy: 'ARRAY' + } + } + + return makeMiddleware(setup.bind(this)) +} + +function multer (options) { + if (options === undefined) { + return new Multer({}) + } + + if (typeof options === 'object' && options !== null) { + return new Multer(options) + } + + throw new TypeError('Expected object for argument options') +} + +module.exports = multer +module.exports.diskStorage = diskStorage +module.exports.memoryStorage = memoryStorage +module.exports.MulterError = MulterError diff --git a/backend/node_modules/multer/lib/counter.js b/backend/node_modules/multer/lib/counter.js new file mode 100644 index 0000000..29c410c --- /dev/null +++ b/backend/node_modules/multer/lib/counter.js @@ -0,0 +1,28 @@ +var EventEmitter = require('events').EventEmitter + +function Counter () { + EventEmitter.call(this) + this.value = 0 +} + +Counter.prototype = Object.create(EventEmitter.prototype) + +Counter.prototype.increment = function increment () { + this.value++ +} + +Counter.prototype.decrement = function decrement () { + if (--this.value === 0) this.emit('zero') +} + +Counter.prototype.isZero = function isZero () { + return (this.value === 0) +} + +Counter.prototype.onceZero = function onceZero (fn) { + if (this.isZero()) return fn() + + this.once('zero', fn) +} + +module.exports = Counter diff --git a/backend/node_modules/multer/lib/file-appender.js b/backend/node_modules/multer/lib/file-appender.js new file mode 100644 index 0000000..1a2c5e7 --- /dev/null +++ b/backend/node_modules/multer/lib/file-appender.js @@ -0,0 +1,67 @@ +var objectAssign = require('object-assign') + +function arrayRemove (arr, item) { + var idx = arr.indexOf(item) + if (~idx) arr.splice(idx, 1) +} + +function FileAppender (strategy, req) { + this.strategy = strategy + this.req = req + + switch (strategy) { + case 'NONE': break + case 'VALUE': break + case 'ARRAY': req.files = []; break + case 'OBJECT': req.files = Object.create(null); break + default: throw new Error('Unknown file strategy: ' + strategy) + } +} + +FileAppender.prototype.insertPlaceholder = function (file) { + var placeholder = { + fieldname: file.fieldname + } + + switch (this.strategy) { + case 'NONE': break + case 'VALUE': break + case 'ARRAY': this.req.files.push(placeholder); break + case 'OBJECT': + if (this.req.files[file.fieldname]) { + this.req.files[file.fieldname].push(placeholder) + } else { + this.req.files[file.fieldname] = [placeholder] + } + break + } + + return placeholder +} + +FileAppender.prototype.removePlaceholder = function (placeholder) { + switch (this.strategy) { + case 'NONE': break + case 'VALUE': break + case 'ARRAY': arrayRemove(this.req.files, placeholder); break + case 'OBJECT': + if (this.req.files[placeholder.fieldname].length === 1) { + delete this.req.files[placeholder.fieldname] + } else { + arrayRemove(this.req.files[placeholder.fieldname], placeholder) + } + break + } +} + +FileAppender.prototype.replacePlaceholder = function (placeholder, file) { + if (this.strategy === 'VALUE') { + this.req.file = file + return + } + + delete placeholder.fieldname + objectAssign(placeholder, file) +} + +module.exports = FileAppender diff --git a/backend/node_modules/multer/lib/make-middleware.js b/backend/node_modules/multer/lib/make-middleware.js new file mode 100644 index 0000000..cc26414 --- /dev/null +++ b/backend/node_modules/multer/lib/make-middleware.js @@ -0,0 +1,175 @@ +var is = require('type-is') +var Busboy = require('busboy') +var extend = require('xtend') +var appendField = require('append-field') + +var Counter = require('./counter') +var MulterError = require('./multer-error') +var FileAppender = require('./file-appender') +var removeUploadedFiles = require('./remove-uploaded-files') + +function makeMiddleware (setup) { + return function multerMiddleware (req, res, next) { + if (!is(req, ['multipart'])) return next() + + var options = setup() + + var limits = options.limits + var storage = options.storage + var fileFilter = options.fileFilter + var fileStrategy = options.fileStrategy + var preservePath = options.preservePath + + req.body = Object.create(null) + + var busboy + + try { + busboy = Busboy({ headers: req.headers, limits: limits, preservePath: preservePath }) + } catch (err) { + return next(err) + } + + var appender = new FileAppender(fileStrategy, req) + var isDone = false + var readFinished = false + var errorOccured = false + var pendingWrites = new Counter() + var uploadedFiles = [] + + function done (err) { + if (isDone) return + isDone = true + req.unpipe(busboy) + process.nextTick(() => { + busboy.removeAllListeners() + }) + next(err) + } + + function indicateDone () { + if (readFinished && pendingWrites.isZero() && !errorOccured) done() + } + + function abortWithError (uploadError) { + if (errorOccured) return + errorOccured = true + + pendingWrites.onceZero(function () { + function remove (file, cb) { + storage._removeFile(req, file, cb) + } + + removeUploadedFiles(uploadedFiles, remove, function (err, storageErrors) { + if (err) return done(err) + + uploadError.storageErrors = storageErrors + done(uploadError) + }) + }) + } + + function abortWithCode (code, optionalField) { + abortWithError(new MulterError(code, optionalField)) + } + + // handle text field data + busboy.on('field', function (fieldname, value, { nameTruncated, valueTruncated }) { + if (fieldname == null) return abortWithCode('MISSING_FIELD_NAME') + if (nameTruncated) return abortWithCode('LIMIT_FIELD_KEY') + if (valueTruncated) return abortWithCode('LIMIT_FIELD_VALUE', fieldname) + + // Work around bug in Busboy (https://github.com/mscdex/busboy/issues/6) + if (limits && Object.prototype.hasOwnProperty.call(limits, 'fieldNameSize')) { + if (fieldname.length > limits.fieldNameSize) return abortWithCode('LIMIT_FIELD_KEY') + } + + appendField(req.body, fieldname, value) + }) + + // handle files + busboy.on('file', function (fieldname, fileStream, { filename, encoding, mimeType }) { + // don't attach to the files object, if there is no file + if (!filename) return fileStream.resume() + + // Work around bug in Busboy (https://github.com/mscdex/busboy/issues/6) + if (limits && Object.prototype.hasOwnProperty.call(limits, 'fieldNameSize')) { + if (fieldname.length > limits.fieldNameSize) return abortWithCode('LIMIT_FIELD_KEY') + } + + var file = { + fieldname: fieldname, + originalname: filename, + encoding: encoding, + mimetype: mimeType + } + + var placeholder = appender.insertPlaceholder(file) + + fileFilter(req, file, function (err, includeFile) { + if (err) { + appender.removePlaceholder(placeholder) + return abortWithError(err) + } + + if (!includeFile) { + appender.removePlaceholder(placeholder) + return fileStream.resume() + } + + var aborting = false + pendingWrites.increment() + + Object.defineProperty(file, 'stream', { + configurable: true, + enumerable: false, + value: fileStream + }) + + fileStream.on('error', function (err) { + pendingWrites.decrement() + abortWithError(err) + }) + + fileStream.on('limit', function () { + aborting = true + abortWithCode('LIMIT_FILE_SIZE', fieldname) + }) + + storage._handleFile(req, file, function (err, info) { + if (aborting) { + appender.removePlaceholder(placeholder) + uploadedFiles.push(extend(file, info)) + return pendingWrites.decrement() + } + + if (err) { + appender.removePlaceholder(placeholder) + pendingWrites.decrement() + return abortWithError(err) + } + + var fileInfo = extend(file, info) + + appender.replacePlaceholder(placeholder, fileInfo) + uploadedFiles.push(fileInfo) + pendingWrites.decrement() + indicateDone() + }) + }) + }) + + busboy.on('error', function (err) { abortWithError(err) }) + busboy.on('partsLimit', function () { abortWithCode('LIMIT_PART_COUNT') }) + busboy.on('filesLimit', function () { abortWithCode('LIMIT_FILE_COUNT') }) + busboy.on('fieldsLimit', function () { abortWithCode('LIMIT_FIELD_COUNT') }) + busboy.on('close', function () { + readFinished = true + indicateDone() + }) + + req.pipe(busboy) + } +} + +module.exports = makeMiddleware diff --git a/backend/node_modules/multer/lib/multer-error.js b/backend/node_modules/multer/lib/multer-error.js new file mode 100644 index 0000000..d56b00e --- /dev/null +++ b/backend/node_modules/multer/lib/multer-error.js @@ -0,0 +1,24 @@ +var util = require('util') + +var errorMessages = { + LIMIT_PART_COUNT: 'Too many parts', + LIMIT_FILE_SIZE: 'File too large', + LIMIT_FILE_COUNT: 'Too many files', + LIMIT_FIELD_KEY: 'Field name too long', + LIMIT_FIELD_VALUE: 'Field value too long', + LIMIT_FIELD_COUNT: 'Too many fields', + LIMIT_UNEXPECTED_FILE: 'Unexpected field', + MISSING_FIELD_NAME: 'Field name missing' +} + +function MulterError (code, field) { + Error.captureStackTrace(this, this.constructor) + this.name = this.constructor.name + this.message = errorMessages[code] + this.code = code + if (field) this.field = field +} + +util.inherits(MulterError, Error) + +module.exports = MulterError diff --git a/backend/node_modules/multer/lib/remove-uploaded-files.js b/backend/node_modules/multer/lib/remove-uploaded-files.js new file mode 100644 index 0000000..f0b16ea --- /dev/null +++ b/backend/node_modules/multer/lib/remove-uploaded-files.js @@ -0,0 +1,28 @@ +function removeUploadedFiles (uploadedFiles, remove, cb) { + var length = uploadedFiles.length + var errors = [] + + if (length === 0) return cb(null, errors) + + function handleFile (idx) { + var file = uploadedFiles[idx] + + remove(file, function (err) { + if (err) { + err.file = file + err.field = file.fieldname + errors.push(err) + } + + if (idx < length - 1) { + handleFile(idx + 1) + } else { + cb(null, errors) + } + }) + } + + handleFile(0) +} + +module.exports = removeUploadedFiles diff --git a/backend/node_modules/multer/package.json b/backend/node_modules/multer/package.json new file mode 100644 index 0000000..32507d8 --- /dev/null +++ b/backend/node_modules/multer/package.json @@ -0,0 +1,52 @@ +{ + "name": "multer", + "description": "Middleware for handling `multipart/form-data`.", + "version": "1.4.5-lts.2", + "contributors": [ + "Hage Yaapa (http://www.hacksparrow.com)", + "Jaret Pfluger ", + "Linus Unnebäck " + ], + "license": "MIT", + "repository": "expressjs/multer", + "keywords": [ + "form", + "post", + "multipart", + "form-data", + "formdata", + "express", + "middleware" + ], + "dependencies": { + "append-field": "^1.0.0", + "busboy": "^1.0.0", + "concat-stream": "^1.5.2", + "mkdirp": "^0.5.4", + "object-assign": "^4.1.1", + "type-is": "^1.6.4", + "xtend": "^4.0.0" + }, + "devDependencies": { + "deep-equal": "^2.0.3", + "express": "^4.13.1", + "form-data": "^1.0.0-rc1", + "fs-temp": "^1.1.2", + "mocha": "^3.5.3", + "rimraf": "^2.4.1", + "standard": "^14.3.3", + "testdata-w3c-json-form": "^1.0.0" + }, + "engines": { + "node": ">= 6.0.0" + }, + "files": [ + "LICENSE", + "index.js", + "storage/", + "lib/" + ], + "scripts": { + "test": "standard && mocha" + } +} diff --git a/backend/node_modules/multer/storage/disk.js b/backend/node_modules/multer/storage/disk.js new file mode 100644 index 0000000..2f77c9f --- /dev/null +++ b/backend/node_modules/multer/storage/disk.js @@ -0,0 +1,66 @@ +var fs = require('fs') +var os = require('os') +var path = require('path') +var crypto = require('crypto') +var mkdirp = require('mkdirp') + +function getFilename (req, file, cb) { + crypto.randomBytes(16, function (err, raw) { + cb(err, err ? undefined : raw.toString('hex')) + }) +} + +function getDestination (req, file, cb) { + cb(null, os.tmpdir()) +} + +function DiskStorage (opts) { + this.getFilename = (opts.filename || getFilename) + + if (typeof opts.destination === 'string') { + mkdirp.sync(opts.destination) + this.getDestination = function ($0, $1, cb) { cb(null, opts.destination) } + } else { + this.getDestination = (opts.destination || getDestination) + } +} + +DiskStorage.prototype._handleFile = function _handleFile (req, file, cb) { + var that = this + + that.getDestination(req, file, function (err, destination) { + if (err) return cb(err) + + that.getFilename(req, file, function (err, filename) { + if (err) return cb(err) + + var finalPath = path.join(destination, filename) + var outStream = fs.createWriteStream(finalPath) + + file.stream.pipe(outStream) + outStream.on('error', cb) + outStream.on('finish', function () { + cb(null, { + destination: destination, + filename: filename, + path: finalPath, + size: outStream.bytesWritten + }) + }) + }) + }) +} + +DiskStorage.prototype._removeFile = function _removeFile (req, file, cb) { + var path = file.path + + delete file.destination + delete file.filename + delete file.path + + fs.unlink(path, cb) +} + +module.exports = function (opts) { + return new DiskStorage(opts) +} diff --git a/backend/node_modules/multer/storage/memory.js b/backend/node_modules/multer/storage/memory.js new file mode 100644 index 0000000..f953ded --- /dev/null +++ b/backend/node_modules/multer/storage/memory.js @@ -0,0 +1,21 @@ +var concat = require('concat-stream') + +function MemoryStorage (opts) {} + +MemoryStorage.prototype._handleFile = function _handleFile (req, file, cb) { + file.stream.pipe(concat({ encoding: 'buffer' }, function (data) { + cb(null, { + buffer: data, + size: data.length + }) + })) +} + +MemoryStorage.prototype._removeFile = function _removeFile (req, file, cb) { + delete file.buffer + cb(null) +} + +module.exports = function (opts) { + return new MemoryStorage(opts) +} diff --git a/backend/node_modules/process-nextick-args/index.js b/backend/node_modules/process-nextick-args/index.js new file mode 100644 index 0000000..3eecf11 --- /dev/null +++ b/backend/node_modules/process-nextick-args/index.js @@ -0,0 +1,45 @@ +'use strict'; + +if (typeof process === 'undefined' || + !process.version || + process.version.indexOf('v0.') === 0 || + process.version.indexOf('v1.') === 0 && process.version.indexOf('v1.8.') !== 0) { + module.exports = { nextTick: nextTick }; +} else { + module.exports = process +} + +function nextTick(fn, arg1, arg2, arg3) { + if (typeof fn !== 'function') { + throw new TypeError('"callback" argument must be a function'); + } + var len = arguments.length; + var args, i; + switch (len) { + case 0: + case 1: + return process.nextTick(fn); + case 2: + return process.nextTick(function afterTickOne() { + fn.call(null, arg1); + }); + case 3: + return process.nextTick(function afterTickTwo() { + fn.call(null, arg1, arg2); + }); + case 4: + return process.nextTick(function afterTickThree() { + fn.call(null, arg1, arg2, arg3); + }); + default: + args = new Array(len - 1); + i = 0; + while (i < args.length) { + args[i++] = arguments[i]; + } + return process.nextTick(function afterTick() { + fn.apply(null, args); + }); + } +} + diff --git a/backend/node_modules/process-nextick-args/license.md b/backend/node_modules/process-nextick-args/license.md new file mode 100644 index 0000000..c67e353 --- /dev/null +++ b/backend/node_modules/process-nextick-args/license.md @@ -0,0 +1,19 @@ +# Copyright (c) 2015 Calvin Metcalf + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +**THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE.** diff --git a/backend/node_modules/process-nextick-args/package.json b/backend/node_modules/process-nextick-args/package.json new file mode 100644 index 0000000..6070b72 --- /dev/null +++ b/backend/node_modules/process-nextick-args/package.json @@ -0,0 +1,25 @@ +{ + "name": "process-nextick-args", + "version": "2.0.1", + "description": "process.nextTick but always with args", + "main": "index.js", + "files": [ + "index.js" + ], + "scripts": { + "test": "node test.js" + }, + "repository": { + "type": "git", + "url": "https://github.com/calvinmetcalf/process-nextick-args.git" + }, + "author": "", + "license": "MIT", + "bugs": { + "url": "https://github.com/calvinmetcalf/process-nextick-args/issues" + }, + "homepage": "https://github.com/calvinmetcalf/process-nextick-args", + "devDependencies": { + "tap": "~0.2.6" + } +} diff --git a/backend/node_modules/process-nextick-args/readme.md b/backend/node_modules/process-nextick-args/readme.md new file mode 100644 index 0000000..ecb432c --- /dev/null +++ b/backend/node_modules/process-nextick-args/readme.md @@ -0,0 +1,18 @@ +process-nextick-args +===== + +[![Build Status](https://travis-ci.org/calvinmetcalf/process-nextick-args.svg?branch=master)](https://travis-ci.org/calvinmetcalf/process-nextick-args) + +```bash +npm install --save process-nextick-args +``` + +Always be able to pass arguments to process.nextTick, no matter the platform + +```js +var pna = require('process-nextick-args'); + +pna.nextTick(function (a, b, c) { + console.log(a, b, c); +}, 'step', 3, 'profit'); +``` diff --git a/backend/node_modules/readable-stream/.travis.yml b/backend/node_modules/readable-stream/.travis.yml new file mode 100644 index 0000000..f62cdac --- /dev/null +++ b/backend/node_modules/readable-stream/.travis.yml @@ -0,0 +1,34 @@ +sudo: false +language: node_js +before_install: + - (test $NPM_LEGACY && npm install -g npm@2 && npm install -g npm@3) || true +notifications: + email: false +matrix: + fast_finish: true + include: + - node_js: '0.8' + env: NPM_LEGACY=true + - node_js: '0.10' + env: NPM_LEGACY=true + - node_js: '0.11' + env: NPM_LEGACY=true + - node_js: '0.12' + env: NPM_LEGACY=true + - node_js: 1 + env: NPM_LEGACY=true + - node_js: 2 + env: NPM_LEGACY=true + - node_js: 3 + env: NPM_LEGACY=true + - node_js: 4 + - node_js: 5 + - node_js: 6 + - node_js: 7 + - node_js: 8 + - node_js: 9 +script: "npm run test" +env: + global: + - secure: rE2Vvo7vnjabYNULNyLFxOyt98BoJexDqsiOnfiD6kLYYsiQGfr/sbZkPMOFm9qfQG7pjqx+zZWZjGSswhTt+626C0t/njXqug7Yps4c3dFblzGfreQHp7wNX5TFsvrxd6dAowVasMp61sJcRnB2w8cUzoe3RAYUDHyiHktwqMc= + - secure: g9YINaKAdMatsJ28G9jCGbSaguXCyxSTy+pBO6Ch0Cf57ZLOTka3HqDj8p3nV28LUIHZ3ut5WO43CeYKwt4AUtLpBS3a0dndHdY6D83uY6b2qh5hXlrcbeQTq2cvw2y95F7hm4D1kwrgZ7ViqaKggRcEupAL69YbJnxeUDKWEdI= diff --git a/backend/node_modules/readable-stream/CONTRIBUTING.md b/backend/node_modules/readable-stream/CONTRIBUTING.md new file mode 100644 index 0000000..f478d58 --- /dev/null +++ b/backend/node_modules/readable-stream/CONTRIBUTING.md @@ -0,0 +1,38 @@ +# Developer's Certificate of Origin 1.1 + +By making a contribution to this project, I certify that: + +* (a) The contribution was created in whole or in part by me and I + have the right to submit it under the open source license + indicated in the file; or + +* (b) The contribution is based upon previous work that, to the best + of my knowledge, is covered under an appropriate open source + license and I have the right under that license to submit that + work with modifications, whether created in whole or in part + by me, under the same open source license (unless I am + permitted to submit under a different license), as indicated + in the file; or + +* (c) The contribution was provided directly to me by some other + person who certified (a), (b) or (c) and I have not modified + it. + +* (d) I understand and agree that this project and the contribution + are public and that a record of the contribution (including all + personal information I submit with it, including my sign-off) is + maintained indefinitely and may be redistributed consistent with + this project or the open source license(s) involved. + +## Moderation Policy + +The [Node.js Moderation Policy] applies to this WG. + +## Code of Conduct + +The [Node.js Code of Conduct][] applies to this WG. + +[Node.js Code of Conduct]: +https://github.com/nodejs/node/blob/master/CODE_OF_CONDUCT.md +[Node.js Moderation Policy]: +https://github.com/nodejs/TSC/blob/master/Moderation-Policy.md diff --git a/backend/node_modules/readable-stream/GOVERNANCE.md b/backend/node_modules/readable-stream/GOVERNANCE.md new file mode 100644 index 0000000..16ffb93 --- /dev/null +++ b/backend/node_modules/readable-stream/GOVERNANCE.md @@ -0,0 +1,136 @@ +### Streams Working Group + +The Node.js Streams is jointly governed by a Working Group +(WG) +that is responsible for high-level guidance of the project. + +The WG has final authority over this project including: + +* Technical direction +* Project governance and process (including this policy) +* Contribution policy +* GitHub repository hosting +* Conduct guidelines +* Maintaining the list of additional Collaborators + +For the current list of WG members, see the project +[README.md](./README.md#current-project-team-members). + +### Collaborators + +The readable-stream GitHub repository is +maintained by the WG and additional Collaborators who are added by the +WG on an ongoing basis. + +Individuals making significant and valuable contributions are made +Collaborators and given commit-access to the project. These +individuals are identified by the WG and their addition as +Collaborators is discussed during the WG meeting. + +_Note:_ If you make a significant contribution and are not considered +for commit-access log an issue or contact a WG member directly and it +will be brought up in the next WG meeting. + +Modifications of the contents of the readable-stream repository are +made on +a collaborative basis. Anybody with a GitHub account may propose a +modification via pull request and it will be considered by the project +Collaborators. All pull requests must be reviewed and accepted by a +Collaborator with sufficient expertise who is able to take full +responsibility for the change. In the case of pull requests proposed +by an existing Collaborator, an additional Collaborator is required +for sign-off. Consensus should be sought if additional Collaborators +participate and there is disagreement around a particular +modification. See _Consensus Seeking Process_ below for further detail +on the consensus model used for governance. + +Collaborators may opt to elevate significant or controversial +modifications, or modifications that have not found consensus to the +WG for discussion by assigning the ***WG-agenda*** tag to a pull +request or issue. The WG should serve as the final arbiter where +required. + +For the current list of Collaborators, see the project +[README.md](./README.md#members). + +### WG Membership + +WG seats are not time-limited. There is no fixed size of the WG. +However, the expected target is between 6 and 12, to ensure adequate +coverage of important areas of expertise, balanced with the ability to +make decisions efficiently. + +There is no specific set of requirements or qualifications for WG +membership beyond these rules. + +The WG may add additional members to the WG by unanimous consensus. + +A WG member may be removed from the WG by voluntary resignation, or by +unanimous consensus of all other WG members. + +Changes to WG membership should be posted in the agenda, and may be +suggested as any other agenda item (see "WG Meetings" below). + +If an addition or removal is proposed during a meeting, and the full +WG is not in attendance to participate, then the addition or removal +is added to the agenda for the subsequent meeting. This is to ensure +that all members are given the opportunity to participate in all +membership decisions. If a WG member is unable to attend a meeting +where a planned membership decision is being made, then their consent +is assumed. + +No more than 1/3 of the WG members may be affiliated with the same +employer. If removal or resignation of a WG member, or a change of +employment by a WG member, creates a situation where more than 1/3 of +the WG membership shares an employer, then the situation must be +immediately remedied by the resignation or removal of one or more WG +members affiliated with the over-represented employer(s). + +### WG Meetings + +The WG meets occasionally on a Google Hangout On Air. A designated moderator +approved by the WG runs the meeting. Each meeting should be +published to YouTube. + +Items are added to the WG agenda that are considered contentious or +are modifications of governance, contribution policy, WG membership, +or release process. + +The intention of the agenda is not to approve or review all patches; +that should happen continuously on GitHub and be handled by the larger +group of Collaborators. + +Any community member or contributor can ask that something be added to +the next meeting's agenda by logging a GitHub Issue. Any Collaborator, +WG member or the moderator can add the item to the agenda by adding +the ***WG-agenda*** tag to the issue. + +Prior to each WG meeting the moderator will share the Agenda with +members of the WG. WG members can add any items they like to the +agenda at the beginning of each meeting. The moderator and the WG +cannot veto or remove items. + +The WG may invite persons or representatives from certain projects to +participate in a non-voting capacity. + +The moderator is responsible for summarizing the discussion of each +agenda item and sends it as a pull request after the meeting. + +### Consensus Seeking Process + +The WG follows a +[Consensus +Seeking](http://en.wikipedia.org/wiki/Consensus-seeking_decision-making) +decision-making model. + +When an agenda item has appeared to reach a consensus the moderator +will ask "Does anyone object?" as a final call for dissent from the +consensus. + +If an agenda item cannot reach a consensus a WG member can call for +either a closing vote or a vote to table the issue to the next +meeting. The call for a vote must be seconded by a majority of the WG +or else the discussion will continue. Simple majority wins. + +Note that changes to WG membership require a majority consensus. See +"WG Membership" above. diff --git a/backend/node_modules/readable-stream/LICENSE b/backend/node_modules/readable-stream/LICENSE new file mode 100644 index 0000000..2873b3b --- /dev/null +++ b/backend/node_modules/readable-stream/LICENSE @@ -0,0 +1,47 @@ +Node.js is licensed for use as follows: + +""" +Copyright Node.js contributors. All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to +deal in the Software without restriction, including without limitation the +rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +sell copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +IN THE SOFTWARE. +""" + +This license applies to parts of Node.js originating from the +https://github.com/joyent/node repository: + +""" +Copyright Joyent, Inc. and other Node contributors. All rights reserved. +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to +deal in the Software without restriction, including without limitation the +rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +sell copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +IN THE SOFTWARE. +""" diff --git a/backend/node_modules/readable-stream/README.md b/backend/node_modules/readable-stream/README.md new file mode 100644 index 0000000..f1c5a93 --- /dev/null +++ b/backend/node_modules/readable-stream/README.md @@ -0,0 +1,58 @@ +# readable-stream + +***Node-core v8.17.0 streams for userland*** [![Build Status](https://travis-ci.org/nodejs/readable-stream.svg?branch=master)](https://travis-ci.org/nodejs/readable-stream) + + +[![NPM](https://nodei.co/npm/readable-stream.png?downloads=true&downloadRank=true)](https://nodei.co/npm/readable-stream/) +[![NPM](https://nodei.co/npm-dl/readable-stream.png?&months=6&height=3)](https://nodei.co/npm/readable-stream/) + + +[![Sauce Test Status](https://saucelabs.com/browser-matrix/readable-stream.svg)](https://saucelabs.com/u/readable-stream) + +```bash +npm install --save readable-stream +``` + +***Node-core streams for userland*** + +This package is a mirror of the Streams2 and Streams3 implementations in +Node-core. + +Full documentation may be found on the [Node.js website](https://nodejs.org/dist/v8.17.0/docs/api/stream.html). + +If you want to guarantee a stable streams base, regardless of what version of +Node you, or the users of your libraries are using, use **readable-stream** *only* and avoid the *"stream"* module in Node-core, for background see [this blogpost](http://r.va.gg/2014/06/why-i-dont-use-nodes-core-stream-module.html). + +As of version 2.0.0 **readable-stream** uses semantic versioning. + +# Streams Working Group + +`readable-stream` is maintained by the Streams Working Group, which +oversees the development and maintenance of the Streams API within +Node.js. The responsibilities of the Streams Working Group include: + +* Addressing stream issues on the Node.js issue tracker. +* Authoring and editing stream documentation within the Node.js project. +* Reviewing changes to stream subclasses within the Node.js project. +* Redirecting changes to streams from the Node.js project to this + project. +* Assisting in the implementation of stream providers within Node.js. +* Recommending versions of `readable-stream` to be included in Node.js. +* Messaging about the future of streams to give the community advance + notice of changes. + + +## Team Members + +* **Chris Dickinson** ([@chrisdickinson](https://github.com/chrisdickinson)) <christopher.s.dickinson@gmail.com> + - Release GPG key: 9554F04D7259F04124DE6B476D5A82AC7E37093B +* **Calvin Metcalf** ([@calvinmetcalf](https://github.com/calvinmetcalf)) <calvin.metcalf@gmail.com> + - Release GPG key: F3EF5F62A87FC27A22E643F714CE4FF5015AA242 +* **Rod Vagg** ([@rvagg](https://github.com/rvagg)) <rod@vagg.org> + - Release GPG key: DD8F2338BAE7501E3DD5AC78C273792F7D83545D +* **Sam Newman** ([@sonewman](https://github.com/sonewman)) <newmansam@outlook.com> +* **Mathias Buus** ([@mafintosh](https://github.com/mafintosh)) <mathiasbuus@gmail.com> +* **Domenic Denicola** ([@domenic](https://github.com/domenic)) <d@domenic.me> +* **Matteo Collina** ([@mcollina](https://github.com/mcollina)) <matteo.collina@gmail.com> + - Release GPG key: 3ABC01543F22DD2239285CDD818674489FBC127E +* **Irina Shestak** ([@lrlna](https://github.com/lrlna)) <shestak.irina@gmail.com> diff --git a/backend/node_modules/readable-stream/doc/wg-meetings/2015-01-30.md b/backend/node_modules/readable-stream/doc/wg-meetings/2015-01-30.md new file mode 100644 index 0000000..83275f1 --- /dev/null +++ b/backend/node_modules/readable-stream/doc/wg-meetings/2015-01-30.md @@ -0,0 +1,60 @@ +# streams WG Meeting 2015-01-30 + +## Links + +* **Google Hangouts Video**: http://www.youtube.com/watch?v=I9nDOSGfwZg +* **GitHub Issue**: https://github.com/iojs/readable-stream/issues/106 +* **Original Minutes Google Doc**: https://docs.google.com/document/d/17aTgLnjMXIrfjgNaTUnHQO7m3xgzHR2VXBTmi03Qii4/ + +## Agenda + +Extracted from https://github.com/iojs/readable-stream/labels/wg-agenda prior to meeting. + +* adopt a charter [#105](https://github.com/iojs/readable-stream/issues/105) +* release and versioning strategy [#101](https://github.com/iojs/readable-stream/issues/101) +* simpler stream creation [#102](https://github.com/iojs/readable-stream/issues/102) +* proposal: deprecate implicit flowing of streams [#99](https://github.com/iojs/readable-stream/issues/99) + +## Minutes + +### adopt a charter + +* group: +1's all around + +### What versioning scheme should be adopted? +* group: +1’s 3.0.0 +* domenic+group: pulling in patches from other sources where appropriate +* mikeal: version independently, suggesting versions for io.js +* mikeal+domenic: work with TC to notify in advance of changes +simpler stream creation + +### streamline creation of streams +* sam: streamline creation of streams +* domenic: nice simple solution posted + but, we lose the opportunity to change the model + may not be backwards incompatible (double check keys) + + **action item:** domenic will check + +### remove implicit flowing of streams on(‘data’) +* add isFlowing / isPaused +* mikeal: worrying that we’re documenting polyfill methods – confuses users +* domenic: more reflective API is probably good, with warning labels for users +* new section for mad scientists (reflective stream access) +* calvin: name the “third state” +* mikeal: maybe borrow the name from whatwg? +* domenic: we’re missing the “third state” +* consensus: kind of difficult to name the third state +* mikeal: figure out differences in states / compat +* mathias: always flow on data – eliminates third state + * explore what it breaks + +**action items:** +* ask isaac for ability to list packages by what public io.js APIs they use (esp. Stream) +* ask rod/build for infrastructure +* **chris**: explore the “flow on data” approach +* add isPaused/isFlowing +* add new docs section +* move isPaused to that section + + diff --git a/backend/node_modules/readable-stream/duplex-browser.js b/backend/node_modules/readable-stream/duplex-browser.js new file mode 100644 index 0000000..f8b2db8 --- /dev/null +++ b/backend/node_modules/readable-stream/duplex-browser.js @@ -0,0 +1 @@ +module.exports = require('./lib/_stream_duplex.js'); diff --git a/backend/node_modules/readable-stream/duplex.js b/backend/node_modules/readable-stream/duplex.js new file mode 100644 index 0000000..46924cb --- /dev/null +++ b/backend/node_modules/readable-stream/duplex.js @@ -0,0 +1 @@ +module.exports = require('./readable').Duplex diff --git a/backend/node_modules/readable-stream/lib/_stream_duplex.js b/backend/node_modules/readable-stream/lib/_stream_duplex.js new file mode 100644 index 0000000..57003c3 --- /dev/null +++ b/backend/node_modules/readable-stream/lib/_stream_duplex.js @@ -0,0 +1,131 @@ +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +// a duplex stream is just a stream that is both readable and writable. +// Since JS doesn't have multiple prototypal inheritance, this class +// prototypally inherits from Readable, and then parasitically from +// Writable. + +'use strict'; + +/**/ + +var pna = require('process-nextick-args'); +/**/ + +/**/ +var objectKeys = Object.keys || function (obj) { + var keys = []; + for (var key in obj) { + keys.push(key); + }return keys; +}; +/**/ + +module.exports = Duplex; + +/**/ +var util = Object.create(require('core-util-is')); +util.inherits = require('inherits'); +/**/ + +var Readable = require('./_stream_readable'); +var Writable = require('./_stream_writable'); + +util.inherits(Duplex, Readable); + +{ + // avoid scope creep, the keys array can then be collected + var keys = objectKeys(Writable.prototype); + for (var v = 0; v < keys.length; v++) { + var method = keys[v]; + if (!Duplex.prototype[method]) Duplex.prototype[method] = Writable.prototype[method]; + } +} + +function Duplex(options) { + if (!(this instanceof Duplex)) return new Duplex(options); + + Readable.call(this, options); + Writable.call(this, options); + + if (options && options.readable === false) this.readable = false; + + if (options && options.writable === false) this.writable = false; + + this.allowHalfOpen = true; + if (options && options.allowHalfOpen === false) this.allowHalfOpen = false; + + this.once('end', onend); +} + +Object.defineProperty(Duplex.prototype, 'writableHighWaterMark', { + // making it explicit this property is not enumerable + // because otherwise some prototype manipulation in + // userland will fail + enumerable: false, + get: function () { + return this._writableState.highWaterMark; + } +}); + +// the no-half-open enforcer +function onend() { + // if we allow half-open state, or if the writable side ended, + // then we're ok. + if (this.allowHalfOpen || this._writableState.ended) return; + + // no more data can be written. + // But allow more writes to happen in this tick. + pna.nextTick(onEndNT, this); +} + +function onEndNT(self) { + self.end(); +} + +Object.defineProperty(Duplex.prototype, 'destroyed', { + get: function () { + if (this._readableState === undefined || this._writableState === undefined) { + return false; + } + return this._readableState.destroyed && this._writableState.destroyed; + }, + set: function (value) { + // we ignore the value if the stream + // has not been initialized yet + if (this._readableState === undefined || this._writableState === undefined) { + return; + } + + // backward compatibility, the user is explicitly + // managing destroyed + this._readableState.destroyed = value; + this._writableState.destroyed = value; + } +}); + +Duplex.prototype._destroy = function (err, cb) { + this.push(null); + this.end(); + + pna.nextTick(cb, err); +}; \ No newline at end of file diff --git a/backend/node_modules/readable-stream/lib/_stream_passthrough.js b/backend/node_modules/readable-stream/lib/_stream_passthrough.js new file mode 100644 index 0000000..612edb4 --- /dev/null +++ b/backend/node_modules/readable-stream/lib/_stream_passthrough.js @@ -0,0 +1,47 @@ +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +// a passthrough stream. +// basically just the most minimal sort of Transform stream. +// Every written chunk gets output as-is. + +'use strict'; + +module.exports = PassThrough; + +var Transform = require('./_stream_transform'); + +/**/ +var util = Object.create(require('core-util-is')); +util.inherits = require('inherits'); +/**/ + +util.inherits(PassThrough, Transform); + +function PassThrough(options) { + if (!(this instanceof PassThrough)) return new PassThrough(options); + + Transform.call(this, options); +} + +PassThrough.prototype._transform = function (chunk, encoding, cb) { + cb(null, chunk); +}; \ No newline at end of file diff --git a/backend/node_modules/readable-stream/lib/_stream_readable.js b/backend/node_modules/readable-stream/lib/_stream_readable.js new file mode 100644 index 0000000..3af95cb --- /dev/null +++ b/backend/node_modules/readable-stream/lib/_stream_readable.js @@ -0,0 +1,1019 @@ +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +'use strict'; + +/**/ + +var pna = require('process-nextick-args'); +/**/ + +module.exports = Readable; + +/**/ +var isArray = require('isarray'); +/**/ + +/**/ +var Duplex; +/**/ + +Readable.ReadableState = ReadableState; + +/**/ +var EE = require('events').EventEmitter; + +var EElistenerCount = function (emitter, type) { + return emitter.listeners(type).length; +}; +/**/ + +/**/ +var Stream = require('./internal/streams/stream'); +/**/ + +/**/ + +var Buffer = require('safe-buffer').Buffer; +var OurUint8Array = (typeof global !== 'undefined' ? global : typeof window !== 'undefined' ? window : typeof self !== 'undefined' ? self : {}).Uint8Array || function () {}; +function _uint8ArrayToBuffer(chunk) { + return Buffer.from(chunk); +} +function _isUint8Array(obj) { + return Buffer.isBuffer(obj) || obj instanceof OurUint8Array; +} + +/**/ + +/**/ +var util = Object.create(require('core-util-is')); +util.inherits = require('inherits'); +/**/ + +/**/ +var debugUtil = require('util'); +var debug = void 0; +if (debugUtil && debugUtil.debuglog) { + debug = debugUtil.debuglog('stream'); +} else { + debug = function () {}; +} +/**/ + +var BufferList = require('./internal/streams/BufferList'); +var destroyImpl = require('./internal/streams/destroy'); +var StringDecoder; + +util.inherits(Readable, Stream); + +var kProxyEvents = ['error', 'close', 'destroy', 'pause', 'resume']; + +function prependListener(emitter, event, fn) { + // Sadly this is not cacheable as some libraries bundle their own + // event emitter implementation with them. + if (typeof emitter.prependListener === 'function') return emitter.prependListener(event, fn); + + // This is a hack to make sure that our error handler is attached before any + // userland ones. NEVER DO THIS. This is here only because this code needs + // to continue to work with older versions of Node.js that do not include + // the prependListener() method. The goal is to eventually remove this hack. + if (!emitter._events || !emitter._events[event]) emitter.on(event, fn);else if (isArray(emitter._events[event])) emitter._events[event].unshift(fn);else emitter._events[event] = [fn, emitter._events[event]]; +} + +function ReadableState(options, stream) { + Duplex = Duplex || require('./_stream_duplex'); + + options = options || {}; + + // Duplex streams are both readable and writable, but share + // the same options object. + // However, some cases require setting options to different + // values for the readable and the writable sides of the duplex stream. + // These options can be provided separately as readableXXX and writableXXX. + var isDuplex = stream instanceof Duplex; + + // object stream flag. Used to make read(n) ignore n and to + // make all the buffer merging and length checks go away + this.objectMode = !!options.objectMode; + + if (isDuplex) this.objectMode = this.objectMode || !!options.readableObjectMode; + + // the point at which it stops calling _read() to fill the buffer + // Note: 0 is a valid value, means "don't call _read preemptively ever" + var hwm = options.highWaterMark; + var readableHwm = options.readableHighWaterMark; + var defaultHwm = this.objectMode ? 16 : 16 * 1024; + + if (hwm || hwm === 0) this.highWaterMark = hwm;else if (isDuplex && (readableHwm || readableHwm === 0)) this.highWaterMark = readableHwm;else this.highWaterMark = defaultHwm; + + // cast to ints. + this.highWaterMark = Math.floor(this.highWaterMark); + + // A linked list is used to store data chunks instead of an array because the + // linked list can remove elements from the beginning faster than + // array.shift() + this.buffer = new BufferList(); + this.length = 0; + this.pipes = null; + this.pipesCount = 0; + this.flowing = null; + this.ended = false; + this.endEmitted = false; + this.reading = false; + + // a flag to be able to tell if the event 'readable'/'data' is emitted + // immediately, or on a later tick. We set this to true at first, because + // any actions that shouldn't happen until "later" should generally also + // not happen before the first read call. + this.sync = true; + + // whenever we return null, then we set a flag to say + // that we're awaiting a 'readable' event emission. + this.needReadable = false; + this.emittedReadable = false; + this.readableListening = false; + this.resumeScheduled = false; + + // has it been destroyed + this.destroyed = false; + + // Crypto is kind of old and crusty. Historically, its default string + // encoding is 'binary' so we have to make this configurable. + // Everything else in the universe uses 'utf8', though. + this.defaultEncoding = options.defaultEncoding || 'utf8'; + + // the number of writers that are awaiting a drain event in .pipe()s + this.awaitDrain = 0; + + // if true, a maybeReadMore has been scheduled + this.readingMore = false; + + this.decoder = null; + this.encoding = null; + if (options.encoding) { + if (!StringDecoder) StringDecoder = require('string_decoder/').StringDecoder; + this.decoder = new StringDecoder(options.encoding); + this.encoding = options.encoding; + } +} + +function Readable(options) { + Duplex = Duplex || require('./_stream_duplex'); + + if (!(this instanceof Readable)) return new Readable(options); + + this._readableState = new ReadableState(options, this); + + // legacy + this.readable = true; + + if (options) { + if (typeof options.read === 'function') this._read = options.read; + + if (typeof options.destroy === 'function') this._destroy = options.destroy; + } + + Stream.call(this); +} + +Object.defineProperty(Readable.prototype, 'destroyed', { + get: function () { + if (this._readableState === undefined) { + return false; + } + return this._readableState.destroyed; + }, + set: function (value) { + // we ignore the value if the stream + // has not been initialized yet + if (!this._readableState) { + return; + } + + // backward compatibility, the user is explicitly + // managing destroyed + this._readableState.destroyed = value; + } +}); + +Readable.prototype.destroy = destroyImpl.destroy; +Readable.prototype._undestroy = destroyImpl.undestroy; +Readable.prototype._destroy = function (err, cb) { + this.push(null); + cb(err); +}; + +// Manually shove something into the read() buffer. +// This returns true if the highWaterMark has not been hit yet, +// similar to how Writable.write() returns true if you should +// write() some more. +Readable.prototype.push = function (chunk, encoding) { + var state = this._readableState; + var skipChunkCheck; + + if (!state.objectMode) { + if (typeof chunk === 'string') { + encoding = encoding || state.defaultEncoding; + if (encoding !== state.encoding) { + chunk = Buffer.from(chunk, encoding); + encoding = ''; + } + skipChunkCheck = true; + } + } else { + skipChunkCheck = true; + } + + return readableAddChunk(this, chunk, encoding, false, skipChunkCheck); +}; + +// Unshift should *always* be something directly out of read() +Readable.prototype.unshift = function (chunk) { + return readableAddChunk(this, chunk, null, true, false); +}; + +function readableAddChunk(stream, chunk, encoding, addToFront, skipChunkCheck) { + var state = stream._readableState; + if (chunk === null) { + state.reading = false; + onEofChunk(stream, state); + } else { + var er; + if (!skipChunkCheck) er = chunkInvalid(state, chunk); + if (er) { + stream.emit('error', er); + } else if (state.objectMode || chunk && chunk.length > 0) { + if (typeof chunk !== 'string' && !state.objectMode && Object.getPrototypeOf(chunk) !== Buffer.prototype) { + chunk = _uint8ArrayToBuffer(chunk); + } + + if (addToFront) { + if (state.endEmitted) stream.emit('error', new Error('stream.unshift() after end event'));else addChunk(stream, state, chunk, true); + } else if (state.ended) { + stream.emit('error', new Error('stream.push() after EOF')); + } else { + state.reading = false; + if (state.decoder && !encoding) { + chunk = state.decoder.write(chunk); + if (state.objectMode || chunk.length !== 0) addChunk(stream, state, chunk, false);else maybeReadMore(stream, state); + } else { + addChunk(stream, state, chunk, false); + } + } + } else if (!addToFront) { + state.reading = false; + } + } + + return needMoreData(state); +} + +function addChunk(stream, state, chunk, addToFront) { + if (state.flowing && state.length === 0 && !state.sync) { + stream.emit('data', chunk); + stream.read(0); + } else { + // update the buffer info. + state.length += state.objectMode ? 1 : chunk.length; + if (addToFront) state.buffer.unshift(chunk);else state.buffer.push(chunk); + + if (state.needReadable) emitReadable(stream); + } + maybeReadMore(stream, state); +} + +function chunkInvalid(state, chunk) { + var er; + if (!_isUint8Array(chunk) && typeof chunk !== 'string' && chunk !== undefined && !state.objectMode) { + er = new TypeError('Invalid non-string/buffer chunk'); + } + return er; +} + +// if it's past the high water mark, we can push in some more. +// Also, if we have no data yet, we can stand some +// more bytes. This is to work around cases where hwm=0, +// such as the repl. Also, if the push() triggered a +// readable event, and the user called read(largeNumber) such that +// needReadable was set, then we ought to push more, so that another +// 'readable' event will be triggered. +function needMoreData(state) { + return !state.ended && (state.needReadable || state.length < state.highWaterMark || state.length === 0); +} + +Readable.prototype.isPaused = function () { + return this._readableState.flowing === false; +}; + +// backwards compatibility. +Readable.prototype.setEncoding = function (enc) { + if (!StringDecoder) StringDecoder = require('string_decoder/').StringDecoder; + this._readableState.decoder = new StringDecoder(enc); + this._readableState.encoding = enc; + return this; +}; + +// Don't raise the hwm > 8MB +var MAX_HWM = 0x800000; +function computeNewHighWaterMark(n) { + if (n >= MAX_HWM) { + n = MAX_HWM; + } else { + // Get the next highest power of 2 to prevent increasing hwm excessively in + // tiny amounts + n--; + n |= n >>> 1; + n |= n >>> 2; + n |= n >>> 4; + n |= n >>> 8; + n |= n >>> 16; + n++; + } + return n; +} + +// This function is designed to be inlinable, so please take care when making +// changes to the function body. +function howMuchToRead(n, state) { + if (n <= 0 || state.length === 0 && state.ended) return 0; + if (state.objectMode) return 1; + if (n !== n) { + // Only flow one buffer at a time + if (state.flowing && state.length) return state.buffer.head.data.length;else return state.length; + } + // If we're asking for more than the current hwm, then raise the hwm. + if (n > state.highWaterMark) state.highWaterMark = computeNewHighWaterMark(n); + if (n <= state.length) return n; + // Don't have enough + if (!state.ended) { + state.needReadable = true; + return 0; + } + return state.length; +} + +// you can override either this method, or the async _read(n) below. +Readable.prototype.read = function (n) { + debug('read', n); + n = parseInt(n, 10); + var state = this._readableState; + var nOrig = n; + + if (n !== 0) state.emittedReadable = false; + + // if we're doing read(0) to trigger a readable event, but we + // already have a bunch of data in the buffer, then just trigger + // the 'readable' event and move on. + if (n === 0 && state.needReadable && (state.length >= state.highWaterMark || state.ended)) { + debug('read: emitReadable', state.length, state.ended); + if (state.length === 0 && state.ended) endReadable(this);else emitReadable(this); + return null; + } + + n = howMuchToRead(n, state); + + // if we've ended, and we're now clear, then finish it up. + if (n === 0 && state.ended) { + if (state.length === 0) endReadable(this); + return null; + } + + // All the actual chunk generation logic needs to be + // *below* the call to _read. The reason is that in certain + // synthetic stream cases, such as passthrough streams, _read + // may be a completely synchronous operation which may change + // the state of the read buffer, providing enough data when + // before there was *not* enough. + // + // So, the steps are: + // 1. Figure out what the state of things will be after we do + // a read from the buffer. + // + // 2. If that resulting state will trigger a _read, then call _read. + // Note that this may be asynchronous, or synchronous. Yes, it is + // deeply ugly to write APIs this way, but that still doesn't mean + // that the Readable class should behave improperly, as streams are + // designed to be sync/async agnostic. + // Take note if the _read call is sync or async (ie, if the read call + // has returned yet), so that we know whether or not it's safe to emit + // 'readable' etc. + // + // 3. Actually pull the requested chunks out of the buffer and return. + + // if we need a readable event, then we need to do some reading. + var doRead = state.needReadable; + debug('need readable', doRead); + + // if we currently have less than the highWaterMark, then also read some + if (state.length === 0 || state.length - n < state.highWaterMark) { + doRead = true; + debug('length less than watermark', doRead); + } + + // however, if we've ended, then there's no point, and if we're already + // reading, then it's unnecessary. + if (state.ended || state.reading) { + doRead = false; + debug('reading or ended', doRead); + } else if (doRead) { + debug('do read'); + state.reading = true; + state.sync = true; + // if the length is currently zero, then we *need* a readable event. + if (state.length === 0) state.needReadable = true; + // call internal read method + this._read(state.highWaterMark); + state.sync = false; + // If _read pushed data synchronously, then `reading` will be false, + // and we need to re-evaluate how much data we can return to the user. + if (!state.reading) n = howMuchToRead(nOrig, state); + } + + var ret; + if (n > 0) ret = fromList(n, state);else ret = null; + + if (ret === null) { + state.needReadable = true; + n = 0; + } else { + state.length -= n; + } + + if (state.length === 0) { + // If we have nothing in the buffer, then we want to know + // as soon as we *do* get something into the buffer. + if (!state.ended) state.needReadable = true; + + // If we tried to read() past the EOF, then emit end on the next tick. + if (nOrig !== n && state.ended) endReadable(this); + } + + if (ret !== null) this.emit('data', ret); + + return ret; +}; + +function onEofChunk(stream, state) { + if (state.ended) return; + if (state.decoder) { + var chunk = state.decoder.end(); + if (chunk && chunk.length) { + state.buffer.push(chunk); + state.length += state.objectMode ? 1 : chunk.length; + } + } + state.ended = true; + + // emit 'readable' now to make sure it gets picked up. + emitReadable(stream); +} + +// Don't emit readable right away in sync mode, because this can trigger +// another read() call => stack overflow. This way, it might trigger +// a nextTick recursion warning, but that's not so bad. +function emitReadable(stream) { + var state = stream._readableState; + state.needReadable = false; + if (!state.emittedReadable) { + debug('emitReadable', state.flowing); + state.emittedReadable = true; + if (state.sync) pna.nextTick(emitReadable_, stream);else emitReadable_(stream); + } +} + +function emitReadable_(stream) { + debug('emit readable'); + stream.emit('readable'); + flow(stream); +} + +// at this point, the user has presumably seen the 'readable' event, +// and called read() to consume some data. that may have triggered +// in turn another _read(n) call, in which case reading = true if +// it's in progress. +// However, if we're not ended, or reading, and the length < hwm, +// then go ahead and try to read some more preemptively. +function maybeReadMore(stream, state) { + if (!state.readingMore) { + state.readingMore = true; + pna.nextTick(maybeReadMore_, stream, state); + } +} + +function maybeReadMore_(stream, state) { + var len = state.length; + while (!state.reading && !state.flowing && !state.ended && state.length < state.highWaterMark) { + debug('maybeReadMore read 0'); + stream.read(0); + if (len === state.length) + // didn't get any data, stop spinning. + break;else len = state.length; + } + state.readingMore = false; +} + +// abstract method. to be overridden in specific implementation classes. +// call cb(er, data) where data is <= n in length. +// for virtual (non-string, non-buffer) streams, "length" is somewhat +// arbitrary, and perhaps not very meaningful. +Readable.prototype._read = function (n) { + this.emit('error', new Error('_read() is not implemented')); +}; + +Readable.prototype.pipe = function (dest, pipeOpts) { + var src = this; + var state = this._readableState; + + switch (state.pipesCount) { + case 0: + state.pipes = dest; + break; + case 1: + state.pipes = [state.pipes, dest]; + break; + default: + state.pipes.push(dest); + break; + } + state.pipesCount += 1; + debug('pipe count=%d opts=%j', state.pipesCount, pipeOpts); + + var doEnd = (!pipeOpts || pipeOpts.end !== false) && dest !== process.stdout && dest !== process.stderr; + + var endFn = doEnd ? onend : unpipe; + if (state.endEmitted) pna.nextTick(endFn);else src.once('end', endFn); + + dest.on('unpipe', onunpipe); + function onunpipe(readable, unpipeInfo) { + debug('onunpipe'); + if (readable === src) { + if (unpipeInfo && unpipeInfo.hasUnpiped === false) { + unpipeInfo.hasUnpiped = true; + cleanup(); + } + } + } + + function onend() { + debug('onend'); + dest.end(); + } + + // when the dest drains, it reduces the awaitDrain counter + // on the source. This would be more elegant with a .once() + // handler in flow(), but adding and removing repeatedly is + // too slow. + var ondrain = pipeOnDrain(src); + dest.on('drain', ondrain); + + var cleanedUp = false; + function cleanup() { + debug('cleanup'); + // cleanup event handlers once the pipe is broken + dest.removeListener('close', onclose); + dest.removeListener('finish', onfinish); + dest.removeListener('drain', ondrain); + dest.removeListener('error', onerror); + dest.removeListener('unpipe', onunpipe); + src.removeListener('end', onend); + src.removeListener('end', unpipe); + src.removeListener('data', ondata); + + cleanedUp = true; + + // if the reader is waiting for a drain event from this + // specific writer, then it would cause it to never start + // flowing again. + // So, if this is awaiting a drain, then we just call it now. + // If we don't know, then assume that we are waiting for one. + if (state.awaitDrain && (!dest._writableState || dest._writableState.needDrain)) ondrain(); + } + + // If the user pushes more data while we're writing to dest then we'll end up + // in ondata again. However, we only want to increase awaitDrain once because + // dest will only emit one 'drain' event for the multiple writes. + // => Introduce a guard on increasing awaitDrain. + var increasedAwaitDrain = false; + src.on('data', ondata); + function ondata(chunk) { + debug('ondata'); + increasedAwaitDrain = false; + var ret = dest.write(chunk); + if (false === ret && !increasedAwaitDrain) { + // If the user unpiped during `dest.write()`, it is possible + // to get stuck in a permanently paused state if that write + // also returned false. + // => Check whether `dest` is still a piping destination. + if ((state.pipesCount === 1 && state.pipes === dest || state.pipesCount > 1 && indexOf(state.pipes, dest) !== -1) && !cleanedUp) { + debug('false write response, pause', state.awaitDrain); + state.awaitDrain++; + increasedAwaitDrain = true; + } + src.pause(); + } + } + + // if the dest has an error, then stop piping into it. + // however, don't suppress the throwing behavior for this. + function onerror(er) { + debug('onerror', er); + unpipe(); + dest.removeListener('error', onerror); + if (EElistenerCount(dest, 'error') === 0) dest.emit('error', er); + } + + // Make sure our error handler is attached before userland ones. + prependListener(dest, 'error', onerror); + + // Both close and finish should trigger unpipe, but only once. + function onclose() { + dest.removeListener('finish', onfinish); + unpipe(); + } + dest.once('close', onclose); + function onfinish() { + debug('onfinish'); + dest.removeListener('close', onclose); + unpipe(); + } + dest.once('finish', onfinish); + + function unpipe() { + debug('unpipe'); + src.unpipe(dest); + } + + // tell the dest that it's being piped to + dest.emit('pipe', src); + + // start the flow if it hasn't been started already. + if (!state.flowing) { + debug('pipe resume'); + src.resume(); + } + + return dest; +}; + +function pipeOnDrain(src) { + return function () { + var state = src._readableState; + debug('pipeOnDrain', state.awaitDrain); + if (state.awaitDrain) state.awaitDrain--; + if (state.awaitDrain === 0 && EElistenerCount(src, 'data')) { + state.flowing = true; + flow(src); + } + }; +} + +Readable.prototype.unpipe = function (dest) { + var state = this._readableState; + var unpipeInfo = { hasUnpiped: false }; + + // if we're not piping anywhere, then do nothing. + if (state.pipesCount === 0) return this; + + // just one destination. most common case. + if (state.pipesCount === 1) { + // passed in one, but it's not the right one. + if (dest && dest !== state.pipes) return this; + + if (!dest) dest = state.pipes; + + // got a match. + state.pipes = null; + state.pipesCount = 0; + state.flowing = false; + if (dest) dest.emit('unpipe', this, unpipeInfo); + return this; + } + + // slow case. multiple pipe destinations. + + if (!dest) { + // remove all. + var dests = state.pipes; + var len = state.pipesCount; + state.pipes = null; + state.pipesCount = 0; + state.flowing = false; + + for (var i = 0; i < len; i++) { + dests[i].emit('unpipe', this, { hasUnpiped: false }); + }return this; + } + + // try to find the right one. + var index = indexOf(state.pipes, dest); + if (index === -1) return this; + + state.pipes.splice(index, 1); + state.pipesCount -= 1; + if (state.pipesCount === 1) state.pipes = state.pipes[0]; + + dest.emit('unpipe', this, unpipeInfo); + + return this; +}; + +// set up data events if they are asked for +// Ensure readable listeners eventually get something +Readable.prototype.on = function (ev, fn) { + var res = Stream.prototype.on.call(this, ev, fn); + + if (ev === 'data') { + // Start flowing on next tick if stream isn't explicitly paused + if (this._readableState.flowing !== false) this.resume(); + } else if (ev === 'readable') { + var state = this._readableState; + if (!state.endEmitted && !state.readableListening) { + state.readableListening = state.needReadable = true; + state.emittedReadable = false; + if (!state.reading) { + pna.nextTick(nReadingNextTick, this); + } else if (state.length) { + emitReadable(this); + } + } + } + + return res; +}; +Readable.prototype.addListener = Readable.prototype.on; + +function nReadingNextTick(self) { + debug('readable nexttick read 0'); + self.read(0); +} + +// pause() and resume() are remnants of the legacy readable stream API +// If the user uses them, then switch into old mode. +Readable.prototype.resume = function () { + var state = this._readableState; + if (!state.flowing) { + debug('resume'); + state.flowing = true; + resume(this, state); + } + return this; +}; + +function resume(stream, state) { + if (!state.resumeScheduled) { + state.resumeScheduled = true; + pna.nextTick(resume_, stream, state); + } +} + +function resume_(stream, state) { + if (!state.reading) { + debug('resume read 0'); + stream.read(0); + } + + state.resumeScheduled = false; + state.awaitDrain = 0; + stream.emit('resume'); + flow(stream); + if (state.flowing && !state.reading) stream.read(0); +} + +Readable.prototype.pause = function () { + debug('call pause flowing=%j', this._readableState.flowing); + if (false !== this._readableState.flowing) { + debug('pause'); + this._readableState.flowing = false; + this.emit('pause'); + } + return this; +}; + +function flow(stream) { + var state = stream._readableState; + debug('flow', state.flowing); + while (state.flowing && stream.read() !== null) {} +} + +// wrap an old-style stream as the async data source. +// This is *not* part of the readable stream interface. +// It is an ugly unfortunate mess of history. +Readable.prototype.wrap = function (stream) { + var _this = this; + + var state = this._readableState; + var paused = false; + + stream.on('end', function () { + debug('wrapped end'); + if (state.decoder && !state.ended) { + var chunk = state.decoder.end(); + if (chunk && chunk.length) _this.push(chunk); + } + + _this.push(null); + }); + + stream.on('data', function (chunk) { + debug('wrapped data'); + if (state.decoder) chunk = state.decoder.write(chunk); + + // don't skip over falsy values in objectMode + if (state.objectMode && (chunk === null || chunk === undefined)) return;else if (!state.objectMode && (!chunk || !chunk.length)) return; + + var ret = _this.push(chunk); + if (!ret) { + paused = true; + stream.pause(); + } + }); + + // proxy all the other methods. + // important when wrapping filters and duplexes. + for (var i in stream) { + if (this[i] === undefined && typeof stream[i] === 'function') { + this[i] = function (method) { + return function () { + return stream[method].apply(stream, arguments); + }; + }(i); + } + } + + // proxy certain important events. + for (var n = 0; n < kProxyEvents.length; n++) { + stream.on(kProxyEvents[n], this.emit.bind(this, kProxyEvents[n])); + } + + // when we try to consume some more bytes, simply unpause the + // underlying stream. + this._read = function (n) { + debug('wrapped _read', n); + if (paused) { + paused = false; + stream.resume(); + } + }; + + return this; +}; + +Object.defineProperty(Readable.prototype, 'readableHighWaterMark', { + // making it explicit this property is not enumerable + // because otherwise some prototype manipulation in + // userland will fail + enumerable: false, + get: function () { + return this._readableState.highWaterMark; + } +}); + +// exposed for testing purposes only. +Readable._fromList = fromList; + +// Pluck off n bytes from an array of buffers. +// Length is the combined lengths of all the buffers in the list. +// This function is designed to be inlinable, so please take care when making +// changes to the function body. +function fromList(n, state) { + // nothing buffered + if (state.length === 0) return null; + + var ret; + if (state.objectMode) ret = state.buffer.shift();else if (!n || n >= state.length) { + // read it all, truncate the list + if (state.decoder) ret = state.buffer.join('');else if (state.buffer.length === 1) ret = state.buffer.head.data;else ret = state.buffer.concat(state.length); + state.buffer.clear(); + } else { + // read part of list + ret = fromListPartial(n, state.buffer, state.decoder); + } + + return ret; +} + +// Extracts only enough buffered data to satisfy the amount requested. +// This function is designed to be inlinable, so please take care when making +// changes to the function body. +function fromListPartial(n, list, hasStrings) { + var ret; + if (n < list.head.data.length) { + // slice is the same for buffers and strings + ret = list.head.data.slice(0, n); + list.head.data = list.head.data.slice(n); + } else if (n === list.head.data.length) { + // first chunk is a perfect match + ret = list.shift(); + } else { + // result spans more than one buffer + ret = hasStrings ? copyFromBufferString(n, list) : copyFromBuffer(n, list); + } + return ret; +} + +// Copies a specified amount of characters from the list of buffered data +// chunks. +// This function is designed to be inlinable, so please take care when making +// changes to the function body. +function copyFromBufferString(n, list) { + var p = list.head; + var c = 1; + var ret = p.data; + n -= ret.length; + while (p = p.next) { + var str = p.data; + var nb = n > str.length ? str.length : n; + if (nb === str.length) ret += str;else ret += str.slice(0, n); + n -= nb; + if (n === 0) { + if (nb === str.length) { + ++c; + if (p.next) list.head = p.next;else list.head = list.tail = null; + } else { + list.head = p; + p.data = str.slice(nb); + } + break; + } + ++c; + } + list.length -= c; + return ret; +} + +// Copies a specified amount of bytes from the list of buffered data chunks. +// This function is designed to be inlinable, so please take care when making +// changes to the function body. +function copyFromBuffer(n, list) { + var ret = Buffer.allocUnsafe(n); + var p = list.head; + var c = 1; + p.data.copy(ret); + n -= p.data.length; + while (p = p.next) { + var buf = p.data; + var nb = n > buf.length ? buf.length : n; + buf.copy(ret, ret.length - n, 0, nb); + n -= nb; + if (n === 0) { + if (nb === buf.length) { + ++c; + if (p.next) list.head = p.next;else list.head = list.tail = null; + } else { + list.head = p; + p.data = buf.slice(nb); + } + break; + } + ++c; + } + list.length -= c; + return ret; +} + +function endReadable(stream) { + var state = stream._readableState; + + // If we get here before consuming all the bytes, then that is a + // bug in node. Should never happen. + if (state.length > 0) throw new Error('"endReadable()" called on non-empty stream'); + + if (!state.endEmitted) { + state.ended = true; + pna.nextTick(endReadableNT, state, stream); + } +} + +function endReadableNT(state, stream) { + // Check that we didn't get one last unshift. + if (!state.endEmitted && state.length === 0) { + state.endEmitted = true; + stream.readable = false; + stream.emit('end'); + } +} + +function indexOf(xs, x) { + for (var i = 0, l = xs.length; i < l; i++) { + if (xs[i] === x) return i; + } + return -1; +} \ No newline at end of file diff --git a/backend/node_modules/readable-stream/lib/_stream_transform.js b/backend/node_modules/readable-stream/lib/_stream_transform.js new file mode 100644 index 0000000..fcfc105 --- /dev/null +++ b/backend/node_modules/readable-stream/lib/_stream_transform.js @@ -0,0 +1,214 @@ +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +// a transform stream is a readable/writable stream where you do +// something with the data. Sometimes it's called a "filter", +// but that's not a great name for it, since that implies a thing where +// some bits pass through, and others are simply ignored. (That would +// be a valid example of a transform, of course.) +// +// While the output is causally related to the input, it's not a +// necessarily symmetric or synchronous transformation. For example, +// a zlib stream might take multiple plain-text writes(), and then +// emit a single compressed chunk some time in the future. +// +// Here's how this works: +// +// The Transform stream has all the aspects of the readable and writable +// stream classes. When you write(chunk), that calls _write(chunk,cb) +// internally, and returns false if there's a lot of pending writes +// buffered up. When you call read(), that calls _read(n) until +// there's enough pending readable data buffered up. +// +// In a transform stream, the written data is placed in a buffer. When +// _read(n) is called, it transforms the queued up data, calling the +// buffered _write cb's as it consumes chunks. If consuming a single +// written chunk would result in multiple output chunks, then the first +// outputted bit calls the readcb, and subsequent chunks just go into +// the read buffer, and will cause it to emit 'readable' if necessary. +// +// This way, back-pressure is actually determined by the reading side, +// since _read has to be called to start processing a new chunk. However, +// a pathological inflate type of transform can cause excessive buffering +// here. For example, imagine a stream where every byte of input is +// interpreted as an integer from 0-255, and then results in that many +// bytes of output. Writing the 4 bytes {ff,ff,ff,ff} would result in +// 1kb of data being output. In this case, you could write a very small +// amount of input, and end up with a very large amount of output. In +// such a pathological inflating mechanism, there'd be no way to tell +// the system to stop doing the transform. A single 4MB write could +// cause the system to run out of memory. +// +// However, even in such a pathological case, only a single written chunk +// would be consumed, and then the rest would wait (un-transformed) until +// the results of the previous transformed chunk were consumed. + +'use strict'; + +module.exports = Transform; + +var Duplex = require('./_stream_duplex'); + +/**/ +var util = Object.create(require('core-util-is')); +util.inherits = require('inherits'); +/**/ + +util.inherits(Transform, Duplex); + +function afterTransform(er, data) { + var ts = this._transformState; + ts.transforming = false; + + var cb = ts.writecb; + + if (!cb) { + return this.emit('error', new Error('write callback called multiple times')); + } + + ts.writechunk = null; + ts.writecb = null; + + if (data != null) // single equals check for both `null` and `undefined` + this.push(data); + + cb(er); + + var rs = this._readableState; + rs.reading = false; + if (rs.needReadable || rs.length < rs.highWaterMark) { + this._read(rs.highWaterMark); + } +} + +function Transform(options) { + if (!(this instanceof Transform)) return new Transform(options); + + Duplex.call(this, options); + + this._transformState = { + afterTransform: afterTransform.bind(this), + needTransform: false, + transforming: false, + writecb: null, + writechunk: null, + writeencoding: null + }; + + // start out asking for a readable event once data is transformed. + this._readableState.needReadable = true; + + // we have implemented the _read method, and done the other things + // that Readable wants before the first _read call, so unset the + // sync guard flag. + this._readableState.sync = false; + + if (options) { + if (typeof options.transform === 'function') this._transform = options.transform; + + if (typeof options.flush === 'function') this._flush = options.flush; + } + + // When the writable side finishes, then flush out anything remaining. + this.on('prefinish', prefinish); +} + +function prefinish() { + var _this = this; + + if (typeof this._flush === 'function') { + this._flush(function (er, data) { + done(_this, er, data); + }); + } else { + done(this, null, null); + } +} + +Transform.prototype.push = function (chunk, encoding) { + this._transformState.needTransform = false; + return Duplex.prototype.push.call(this, chunk, encoding); +}; + +// This is the part where you do stuff! +// override this function in implementation classes. +// 'chunk' is an input chunk. +// +// Call `push(newChunk)` to pass along transformed output +// to the readable side. You may call 'push' zero or more times. +// +// Call `cb(err)` when you are done with this chunk. If you pass +// an error, then that'll put the hurt on the whole operation. If you +// never call cb(), then you'll never get another chunk. +Transform.prototype._transform = function (chunk, encoding, cb) { + throw new Error('_transform() is not implemented'); +}; + +Transform.prototype._write = function (chunk, encoding, cb) { + var ts = this._transformState; + ts.writecb = cb; + ts.writechunk = chunk; + ts.writeencoding = encoding; + if (!ts.transforming) { + var rs = this._readableState; + if (ts.needTransform || rs.needReadable || rs.length < rs.highWaterMark) this._read(rs.highWaterMark); + } +}; + +// Doesn't matter what the args are here. +// _transform does all the work. +// That we got here means that the readable side wants more data. +Transform.prototype._read = function (n) { + var ts = this._transformState; + + if (ts.writechunk !== null && ts.writecb && !ts.transforming) { + ts.transforming = true; + this._transform(ts.writechunk, ts.writeencoding, ts.afterTransform); + } else { + // mark that we need a transform, so that any data that comes in + // will get processed, now that we've asked for it. + ts.needTransform = true; + } +}; + +Transform.prototype._destroy = function (err, cb) { + var _this2 = this; + + Duplex.prototype._destroy.call(this, err, function (err2) { + cb(err2); + _this2.emit('close'); + }); +}; + +function done(stream, er, data) { + if (er) return stream.emit('error', er); + + if (data != null) // single equals check for both `null` and `undefined` + stream.push(data); + + // if there's nothing in the write buffer, then that means + // that nothing more will ever be provided + if (stream._writableState.length) throw new Error('Calling transform done when ws.length != 0'); + + if (stream._transformState.transforming) throw new Error('Calling transform done when still transforming'); + + return stream.push(null); +} \ No newline at end of file diff --git a/backend/node_modules/readable-stream/lib/_stream_writable.js b/backend/node_modules/readable-stream/lib/_stream_writable.js new file mode 100644 index 0000000..e1e897f --- /dev/null +++ b/backend/node_modules/readable-stream/lib/_stream_writable.js @@ -0,0 +1,685 @@ +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +// A bit simpler than readable streams. +// Implement an async ._write(chunk, encoding, cb), and it'll handle all +// the drain event emission and buffering. + +'use strict'; + +/**/ + +var pna = require('process-nextick-args'); +/**/ + +module.exports = Writable; + +/* */ +function WriteReq(chunk, encoding, cb) { + this.chunk = chunk; + this.encoding = encoding; + this.callback = cb; + this.next = null; +} + +// It seems a linked list but it is not +// there will be only 2 of these for each stream +function CorkedRequest(state) { + var _this = this; + + this.next = null; + this.entry = null; + this.finish = function () { + onCorkedFinish(_this, state); + }; +} +/* */ + +/**/ +var asyncWrite = !process.browser && ['v0.10', 'v0.9.'].indexOf(process.version.slice(0, 5)) > -1 ? setImmediate : pna.nextTick; +/**/ + +/**/ +var Duplex; +/**/ + +Writable.WritableState = WritableState; + +/**/ +var util = Object.create(require('core-util-is')); +util.inherits = require('inherits'); +/**/ + +/**/ +var internalUtil = { + deprecate: require('util-deprecate') +}; +/**/ + +/**/ +var Stream = require('./internal/streams/stream'); +/**/ + +/**/ + +var Buffer = require('safe-buffer').Buffer; +var OurUint8Array = (typeof global !== 'undefined' ? global : typeof window !== 'undefined' ? window : typeof self !== 'undefined' ? self : {}).Uint8Array || function () {}; +function _uint8ArrayToBuffer(chunk) { + return Buffer.from(chunk); +} +function _isUint8Array(obj) { + return Buffer.isBuffer(obj) || obj instanceof OurUint8Array; +} + +/**/ + +var destroyImpl = require('./internal/streams/destroy'); + +util.inherits(Writable, Stream); + +function nop() {} + +function WritableState(options, stream) { + Duplex = Duplex || require('./_stream_duplex'); + + options = options || {}; + + // Duplex streams are both readable and writable, but share + // the same options object. + // However, some cases require setting options to different + // values for the readable and the writable sides of the duplex stream. + // These options can be provided separately as readableXXX and writableXXX. + var isDuplex = stream instanceof Duplex; + + // object stream flag to indicate whether or not this stream + // contains buffers or objects. + this.objectMode = !!options.objectMode; + + if (isDuplex) this.objectMode = this.objectMode || !!options.writableObjectMode; + + // the point at which write() starts returning false + // Note: 0 is a valid value, means that we always return false if + // the entire buffer is not flushed immediately on write() + var hwm = options.highWaterMark; + var writableHwm = options.writableHighWaterMark; + var defaultHwm = this.objectMode ? 16 : 16 * 1024; + + if (hwm || hwm === 0) this.highWaterMark = hwm;else if (isDuplex && (writableHwm || writableHwm === 0)) this.highWaterMark = writableHwm;else this.highWaterMark = defaultHwm; + + // cast to ints. + this.highWaterMark = Math.floor(this.highWaterMark); + + // if _final has been called + this.finalCalled = false; + + // drain event flag. + this.needDrain = false; + // at the start of calling end() + this.ending = false; + // when end() has been called, and returned + this.ended = false; + // when 'finish' is emitted + this.finished = false; + + // has it been destroyed + this.destroyed = false; + + // should we decode strings into buffers before passing to _write? + // this is here so that some node-core streams can optimize string + // handling at a lower level. + var noDecode = options.decodeStrings === false; + this.decodeStrings = !noDecode; + + // Crypto is kind of old and crusty. Historically, its default string + // encoding is 'binary' so we have to make this configurable. + // Everything else in the universe uses 'utf8', though. + this.defaultEncoding = options.defaultEncoding || 'utf8'; + + // not an actual buffer we keep track of, but a measurement + // of how much we're waiting to get pushed to some underlying + // socket or file. + this.length = 0; + + // a flag to see when we're in the middle of a write. + this.writing = false; + + // when true all writes will be buffered until .uncork() call + this.corked = 0; + + // a flag to be able to tell if the onwrite cb is called immediately, + // or on a later tick. We set this to true at first, because any + // actions that shouldn't happen until "later" should generally also + // not happen before the first write call. + this.sync = true; + + // a flag to know if we're processing previously buffered items, which + // may call the _write() callback in the same tick, so that we don't + // end up in an overlapped onwrite situation. + this.bufferProcessing = false; + + // the callback that's passed to _write(chunk,cb) + this.onwrite = function (er) { + onwrite(stream, er); + }; + + // the callback that the user supplies to write(chunk,encoding,cb) + this.writecb = null; + + // the amount that is being written when _write is called. + this.writelen = 0; + + this.bufferedRequest = null; + this.lastBufferedRequest = null; + + // number of pending user-supplied write callbacks + // this must be 0 before 'finish' can be emitted + this.pendingcb = 0; + + // emit prefinish if the only thing we're waiting for is _write cbs + // This is relevant for synchronous Transform streams + this.prefinished = false; + + // True if the error was already emitted and should not be thrown again + this.errorEmitted = false; + + // count buffered requests + this.bufferedRequestCount = 0; + + // allocate the first CorkedRequest, there is always + // one allocated and free to use, and we maintain at most two + this.corkedRequestsFree = new CorkedRequest(this); +} + +WritableState.prototype.getBuffer = function getBuffer() { + var current = this.bufferedRequest; + var out = []; + while (current) { + out.push(current); + current = current.next; + } + return out; +}; + +(function () { + try { + Object.defineProperty(WritableState.prototype, 'buffer', { + get: internalUtil.deprecate(function () { + return this.getBuffer(); + }, '_writableState.buffer is deprecated. Use _writableState.getBuffer ' + 'instead.', 'DEP0003') + }); + } catch (_) {} +})(); + +// Test _writableState for inheritance to account for Duplex streams, +// whose prototype chain only points to Readable. +var realHasInstance; +if (typeof Symbol === 'function' && Symbol.hasInstance && typeof Function.prototype[Symbol.hasInstance] === 'function') { + realHasInstance = Function.prototype[Symbol.hasInstance]; + Object.defineProperty(Writable, Symbol.hasInstance, { + value: function (object) { + if (realHasInstance.call(this, object)) return true; + if (this !== Writable) return false; + + return object && object._writableState instanceof WritableState; + } + }); +} else { + realHasInstance = function (object) { + return object instanceof this; + }; +} + +function Writable(options) { + Duplex = Duplex || require('./_stream_duplex'); + + // Writable ctor is applied to Duplexes, too. + // `realHasInstance` is necessary because using plain `instanceof` + // would return false, as no `_writableState` property is attached. + + // Trying to use the custom `instanceof` for Writable here will also break the + // Node.js LazyTransform implementation, which has a non-trivial getter for + // `_writableState` that would lead to infinite recursion. + if (!realHasInstance.call(Writable, this) && !(this instanceof Duplex)) { + return new Writable(options); + } + + this._writableState = new WritableState(options, this); + + // legacy. + this.writable = true; + + if (options) { + if (typeof options.write === 'function') this._write = options.write; + + if (typeof options.writev === 'function') this._writev = options.writev; + + if (typeof options.destroy === 'function') this._destroy = options.destroy; + + if (typeof options.final === 'function') this._final = options.final; + } + + Stream.call(this); +} + +// Otherwise people can pipe Writable streams, which is just wrong. +Writable.prototype.pipe = function () { + this.emit('error', new Error('Cannot pipe, not readable')); +}; + +function writeAfterEnd(stream, cb) { + var er = new Error('write after end'); + // TODO: defer error events consistently everywhere, not just the cb + stream.emit('error', er); + pna.nextTick(cb, er); +} + +// Checks that a user-supplied chunk is valid, especially for the particular +// mode the stream is in. Currently this means that `null` is never accepted +// and undefined/non-string values are only allowed in object mode. +function validChunk(stream, state, chunk, cb) { + var valid = true; + var er = false; + + if (chunk === null) { + er = new TypeError('May not write null values to stream'); + } else if (typeof chunk !== 'string' && chunk !== undefined && !state.objectMode) { + er = new TypeError('Invalid non-string/buffer chunk'); + } + if (er) { + stream.emit('error', er); + pna.nextTick(cb, er); + valid = false; + } + return valid; +} + +Writable.prototype.write = function (chunk, encoding, cb) { + var state = this._writableState; + var ret = false; + var isBuf = !state.objectMode && _isUint8Array(chunk); + + if (isBuf && !Buffer.isBuffer(chunk)) { + chunk = _uint8ArrayToBuffer(chunk); + } + + if (typeof encoding === 'function') { + cb = encoding; + encoding = null; + } + + if (isBuf) encoding = 'buffer';else if (!encoding) encoding = state.defaultEncoding; + + if (typeof cb !== 'function') cb = nop; + + if (state.ended) writeAfterEnd(this, cb);else if (isBuf || validChunk(this, state, chunk, cb)) { + state.pendingcb++; + ret = writeOrBuffer(this, state, isBuf, chunk, encoding, cb); + } + + return ret; +}; + +Writable.prototype.cork = function () { + var state = this._writableState; + + state.corked++; +}; + +Writable.prototype.uncork = function () { + var state = this._writableState; + + if (state.corked) { + state.corked--; + + if (!state.writing && !state.corked && !state.bufferProcessing && state.bufferedRequest) clearBuffer(this, state); + } +}; + +Writable.prototype.setDefaultEncoding = function setDefaultEncoding(encoding) { + // node::ParseEncoding() requires lower case. + if (typeof encoding === 'string') encoding = encoding.toLowerCase(); + if (!(['hex', 'utf8', 'utf-8', 'ascii', 'binary', 'base64', 'ucs2', 'ucs-2', 'utf16le', 'utf-16le', 'raw'].indexOf((encoding + '').toLowerCase()) > -1)) throw new TypeError('Unknown encoding: ' + encoding); + this._writableState.defaultEncoding = encoding; + return this; +}; + +function decodeChunk(state, chunk, encoding) { + if (!state.objectMode && state.decodeStrings !== false && typeof chunk === 'string') { + chunk = Buffer.from(chunk, encoding); + } + return chunk; +} + +Object.defineProperty(Writable.prototype, 'writableHighWaterMark', { + // making it explicit this property is not enumerable + // because otherwise some prototype manipulation in + // userland will fail + enumerable: false, + get: function () { + return this._writableState.highWaterMark; + } +}); + +// if we're already writing something, then just put this +// in the queue, and wait our turn. Otherwise, call _write +// If we return false, then we need a drain event, so set that flag. +function writeOrBuffer(stream, state, isBuf, chunk, encoding, cb) { + if (!isBuf) { + var newChunk = decodeChunk(state, chunk, encoding); + if (chunk !== newChunk) { + isBuf = true; + encoding = 'buffer'; + chunk = newChunk; + } + } + var len = state.objectMode ? 1 : chunk.length; + + state.length += len; + + var ret = state.length < state.highWaterMark; + // we must ensure that previous needDrain will not be reset to false. + if (!ret) state.needDrain = true; + + if (state.writing || state.corked) { + var last = state.lastBufferedRequest; + state.lastBufferedRequest = { + chunk: chunk, + encoding: encoding, + isBuf: isBuf, + callback: cb, + next: null + }; + if (last) { + last.next = state.lastBufferedRequest; + } else { + state.bufferedRequest = state.lastBufferedRequest; + } + state.bufferedRequestCount += 1; + } else { + doWrite(stream, state, false, len, chunk, encoding, cb); + } + + return ret; +} + +function doWrite(stream, state, writev, len, chunk, encoding, cb) { + state.writelen = len; + state.writecb = cb; + state.writing = true; + state.sync = true; + if (writev) stream._writev(chunk, state.onwrite);else stream._write(chunk, encoding, state.onwrite); + state.sync = false; +} + +function onwriteError(stream, state, sync, er, cb) { + --state.pendingcb; + + if (sync) { + // defer the callback if we are being called synchronously + // to avoid piling up things on the stack + pna.nextTick(cb, er); + // this can emit finish, and it will always happen + // after error + pna.nextTick(finishMaybe, stream, state); + stream._writableState.errorEmitted = true; + stream.emit('error', er); + } else { + // the caller expect this to happen before if + // it is async + cb(er); + stream._writableState.errorEmitted = true; + stream.emit('error', er); + // this can emit finish, but finish must + // always follow error + finishMaybe(stream, state); + } +} + +function onwriteStateUpdate(state) { + state.writing = false; + state.writecb = null; + state.length -= state.writelen; + state.writelen = 0; +} + +function onwrite(stream, er) { + var state = stream._writableState; + var sync = state.sync; + var cb = state.writecb; + + onwriteStateUpdate(state); + + if (er) onwriteError(stream, state, sync, er, cb);else { + // Check if we're actually ready to finish, but don't emit yet + var finished = needFinish(state); + + if (!finished && !state.corked && !state.bufferProcessing && state.bufferedRequest) { + clearBuffer(stream, state); + } + + if (sync) { + /**/ + asyncWrite(afterWrite, stream, state, finished, cb); + /**/ + } else { + afterWrite(stream, state, finished, cb); + } + } +} + +function afterWrite(stream, state, finished, cb) { + if (!finished) onwriteDrain(stream, state); + state.pendingcb--; + cb(); + finishMaybe(stream, state); +} + +// Must force callback to be called on nextTick, so that we don't +// emit 'drain' before the write() consumer gets the 'false' return +// value, and has a chance to attach a 'drain' listener. +function onwriteDrain(stream, state) { + if (state.length === 0 && state.needDrain) { + state.needDrain = false; + stream.emit('drain'); + } +} + +// if there's something in the buffer waiting, then process it +function clearBuffer(stream, state) { + state.bufferProcessing = true; + var entry = state.bufferedRequest; + + if (stream._writev && entry && entry.next) { + // Fast case, write everything using _writev() + var l = state.bufferedRequestCount; + var buffer = new Array(l); + var holder = state.corkedRequestsFree; + holder.entry = entry; + + var count = 0; + var allBuffers = true; + while (entry) { + buffer[count] = entry; + if (!entry.isBuf) allBuffers = false; + entry = entry.next; + count += 1; + } + buffer.allBuffers = allBuffers; + + doWrite(stream, state, true, state.length, buffer, '', holder.finish); + + // doWrite is almost always async, defer these to save a bit of time + // as the hot path ends with doWrite + state.pendingcb++; + state.lastBufferedRequest = null; + if (holder.next) { + state.corkedRequestsFree = holder.next; + holder.next = null; + } else { + state.corkedRequestsFree = new CorkedRequest(state); + } + state.bufferedRequestCount = 0; + } else { + // Slow case, write chunks one-by-one + while (entry) { + var chunk = entry.chunk; + var encoding = entry.encoding; + var cb = entry.callback; + var len = state.objectMode ? 1 : chunk.length; + + doWrite(stream, state, false, len, chunk, encoding, cb); + entry = entry.next; + state.bufferedRequestCount--; + // if we didn't call the onwrite immediately, then + // it means that we need to wait until it does. + // also, that means that the chunk and cb are currently + // being processed, so move the buffer counter past them. + if (state.writing) { + break; + } + } + + if (entry === null) state.lastBufferedRequest = null; + } + + state.bufferedRequest = entry; + state.bufferProcessing = false; +} + +Writable.prototype._write = function (chunk, encoding, cb) { + cb(new Error('_write() is not implemented')); +}; + +Writable.prototype._writev = null; + +Writable.prototype.end = function (chunk, encoding, cb) { + var state = this._writableState; + + if (typeof chunk === 'function') { + cb = chunk; + chunk = null; + encoding = null; + } else if (typeof encoding === 'function') { + cb = encoding; + encoding = null; + } + + if (chunk !== null && chunk !== undefined) this.write(chunk, encoding); + + // .end() fully uncorks + if (state.corked) { + state.corked = 1; + this.uncork(); + } + + // ignore unnecessary end() calls. + if (!state.ending) endWritable(this, state, cb); +}; + +function needFinish(state) { + return state.ending && state.length === 0 && state.bufferedRequest === null && !state.finished && !state.writing; +} +function callFinal(stream, state) { + stream._final(function (err) { + state.pendingcb--; + if (err) { + stream.emit('error', err); + } + state.prefinished = true; + stream.emit('prefinish'); + finishMaybe(stream, state); + }); +} +function prefinish(stream, state) { + if (!state.prefinished && !state.finalCalled) { + if (typeof stream._final === 'function') { + state.pendingcb++; + state.finalCalled = true; + pna.nextTick(callFinal, stream, state); + } else { + state.prefinished = true; + stream.emit('prefinish'); + } + } +} + +function finishMaybe(stream, state) { + var need = needFinish(state); + if (need) { + prefinish(stream, state); + if (state.pendingcb === 0) { + state.finished = true; + stream.emit('finish'); + } + } + return need; +} + +function endWritable(stream, state, cb) { + state.ending = true; + finishMaybe(stream, state); + if (cb) { + if (state.finished) pna.nextTick(cb);else stream.once('finish', cb); + } + state.ended = true; + stream.writable = false; +} + +function onCorkedFinish(corkReq, state, err) { + var entry = corkReq.entry; + corkReq.entry = null; + while (entry) { + var cb = entry.callback; + state.pendingcb--; + cb(err); + entry = entry.next; + } + + // reuse the free corkReq. + state.corkedRequestsFree.next = corkReq; +} + +Object.defineProperty(Writable.prototype, 'destroyed', { + get: function () { + if (this._writableState === undefined) { + return false; + } + return this._writableState.destroyed; + }, + set: function (value) { + // we ignore the value if the stream + // has not been initialized yet + if (!this._writableState) { + return; + } + + // backward compatibility, the user is explicitly + // managing destroyed + this._writableState.destroyed = value; + } +}); + +Writable.prototype.destroy = destroyImpl.destroy; +Writable.prototype._undestroy = destroyImpl.undestroy; +Writable.prototype._destroy = function (err, cb) { + this.end(); + cb(err); +}; \ No newline at end of file diff --git a/backend/node_modules/readable-stream/lib/internal/streams/BufferList.js b/backend/node_modules/readable-stream/lib/internal/streams/BufferList.js new file mode 100644 index 0000000..5e08097 --- /dev/null +++ b/backend/node_modules/readable-stream/lib/internal/streams/BufferList.js @@ -0,0 +1,78 @@ +'use strict'; + +function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } + +var Buffer = require('safe-buffer').Buffer; +var util = require('util'); + +function copyBuffer(src, target, offset) { + src.copy(target, offset); +} + +module.exports = function () { + function BufferList() { + _classCallCheck(this, BufferList); + + this.head = null; + this.tail = null; + this.length = 0; + } + + BufferList.prototype.push = function push(v) { + var entry = { data: v, next: null }; + if (this.length > 0) this.tail.next = entry;else this.head = entry; + this.tail = entry; + ++this.length; + }; + + BufferList.prototype.unshift = function unshift(v) { + var entry = { data: v, next: this.head }; + if (this.length === 0) this.tail = entry; + this.head = entry; + ++this.length; + }; + + BufferList.prototype.shift = function shift() { + if (this.length === 0) return; + var ret = this.head.data; + if (this.length === 1) this.head = this.tail = null;else this.head = this.head.next; + --this.length; + return ret; + }; + + BufferList.prototype.clear = function clear() { + this.head = this.tail = null; + this.length = 0; + }; + + BufferList.prototype.join = function join(s) { + if (this.length === 0) return ''; + var p = this.head; + var ret = '' + p.data; + while (p = p.next) { + ret += s + p.data; + }return ret; + }; + + BufferList.prototype.concat = function concat(n) { + if (this.length === 0) return Buffer.alloc(0); + var ret = Buffer.allocUnsafe(n >>> 0); + var p = this.head; + var i = 0; + while (p) { + copyBuffer(p.data, ret, i); + i += p.data.length; + p = p.next; + } + return ret; + }; + + return BufferList; +}(); + +if (util && util.inspect && util.inspect.custom) { + module.exports.prototype[util.inspect.custom] = function () { + var obj = util.inspect({ length: this.length }); + return this.constructor.name + ' ' + obj; + }; +} \ No newline at end of file diff --git a/backend/node_modules/readable-stream/lib/internal/streams/destroy.js b/backend/node_modules/readable-stream/lib/internal/streams/destroy.js new file mode 100644 index 0000000..85a8214 --- /dev/null +++ b/backend/node_modules/readable-stream/lib/internal/streams/destroy.js @@ -0,0 +1,84 @@ +'use strict'; + +/**/ + +var pna = require('process-nextick-args'); +/**/ + +// undocumented cb() API, needed for core, not for public API +function destroy(err, cb) { + var _this = this; + + var readableDestroyed = this._readableState && this._readableState.destroyed; + var writableDestroyed = this._writableState && this._writableState.destroyed; + + if (readableDestroyed || writableDestroyed) { + if (cb) { + cb(err); + } else if (err) { + if (!this._writableState) { + pna.nextTick(emitErrorNT, this, err); + } else if (!this._writableState.errorEmitted) { + this._writableState.errorEmitted = true; + pna.nextTick(emitErrorNT, this, err); + } + } + + return this; + } + + // we set destroyed to true before firing error callbacks in order + // to make it re-entrance safe in case destroy() is called within callbacks + + if (this._readableState) { + this._readableState.destroyed = true; + } + + // if this is a duplex stream mark the writable part as destroyed as well + if (this._writableState) { + this._writableState.destroyed = true; + } + + this._destroy(err || null, function (err) { + if (!cb && err) { + if (!_this._writableState) { + pna.nextTick(emitErrorNT, _this, err); + } else if (!_this._writableState.errorEmitted) { + _this._writableState.errorEmitted = true; + pna.nextTick(emitErrorNT, _this, err); + } + } else if (cb) { + cb(err); + } + }); + + return this; +} + +function undestroy() { + if (this._readableState) { + this._readableState.destroyed = false; + this._readableState.reading = false; + this._readableState.ended = false; + this._readableState.endEmitted = false; + } + + if (this._writableState) { + this._writableState.destroyed = false; + this._writableState.ended = false; + this._writableState.ending = false; + this._writableState.finalCalled = false; + this._writableState.prefinished = false; + this._writableState.finished = false; + this._writableState.errorEmitted = false; + } +} + +function emitErrorNT(self, err) { + self.emit('error', err); +} + +module.exports = { + destroy: destroy, + undestroy: undestroy +}; \ No newline at end of file diff --git a/backend/node_modules/readable-stream/lib/internal/streams/stream-browser.js b/backend/node_modules/readable-stream/lib/internal/streams/stream-browser.js new file mode 100644 index 0000000..9332a3f --- /dev/null +++ b/backend/node_modules/readable-stream/lib/internal/streams/stream-browser.js @@ -0,0 +1 @@ +module.exports = require('events').EventEmitter; diff --git a/backend/node_modules/readable-stream/lib/internal/streams/stream.js b/backend/node_modules/readable-stream/lib/internal/streams/stream.js new file mode 100644 index 0000000..ce2ad5b --- /dev/null +++ b/backend/node_modules/readable-stream/lib/internal/streams/stream.js @@ -0,0 +1 @@ +module.exports = require('stream'); diff --git a/backend/node_modules/readable-stream/node_modules/safe-buffer/LICENSE b/backend/node_modules/readable-stream/node_modules/safe-buffer/LICENSE new file mode 100644 index 0000000..0c068ce --- /dev/null +++ b/backend/node_modules/readable-stream/node_modules/safe-buffer/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) Feross Aboukhadijeh + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/backend/node_modules/readable-stream/node_modules/safe-buffer/README.md b/backend/node_modules/readable-stream/node_modules/safe-buffer/README.md new file mode 100644 index 0000000..e9a81af --- /dev/null +++ b/backend/node_modules/readable-stream/node_modules/safe-buffer/README.md @@ -0,0 +1,584 @@ +# safe-buffer [![travis][travis-image]][travis-url] [![npm][npm-image]][npm-url] [![downloads][downloads-image]][downloads-url] [![javascript style guide][standard-image]][standard-url] + +[travis-image]: https://img.shields.io/travis/feross/safe-buffer/master.svg +[travis-url]: https://travis-ci.org/feross/safe-buffer +[npm-image]: https://img.shields.io/npm/v/safe-buffer.svg +[npm-url]: https://npmjs.org/package/safe-buffer +[downloads-image]: https://img.shields.io/npm/dm/safe-buffer.svg +[downloads-url]: https://npmjs.org/package/safe-buffer +[standard-image]: https://img.shields.io/badge/code_style-standard-brightgreen.svg +[standard-url]: https://standardjs.com + +#### Safer Node.js Buffer API + +**Use the new Node.js Buffer APIs (`Buffer.from`, `Buffer.alloc`, +`Buffer.allocUnsafe`, `Buffer.allocUnsafeSlow`) in all versions of Node.js.** + +**Uses the built-in implementation when available.** + +## install + +``` +npm install safe-buffer +``` + +## usage + +The goal of this package is to provide a safe replacement for the node.js `Buffer`. + +It's a drop-in replacement for `Buffer`. You can use it by adding one `require` line to +the top of your node.js modules: + +```js +var Buffer = require('safe-buffer').Buffer + +// Existing buffer code will continue to work without issues: + +new Buffer('hey', 'utf8') +new Buffer([1, 2, 3], 'utf8') +new Buffer(obj) +new Buffer(16) // create an uninitialized buffer (potentially unsafe) + +// But you can use these new explicit APIs to make clear what you want: + +Buffer.from('hey', 'utf8') // convert from many types to a Buffer +Buffer.alloc(16) // create a zero-filled buffer (safe) +Buffer.allocUnsafe(16) // create an uninitialized buffer (potentially unsafe) +``` + +## api + +### Class Method: Buffer.from(array) + + +* `array` {Array} + +Allocates a new `Buffer` using an `array` of octets. + +```js +const buf = Buffer.from([0x62,0x75,0x66,0x66,0x65,0x72]); + // creates a new Buffer containing ASCII bytes + // ['b','u','f','f','e','r'] +``` + +A `TypeError` will be thrown if `array` is not an `Array`. + +### Class Method: Buffer.from(arrayBuffer[, byteOffset[, length]]) + + +* `arrayBuffer` {ArrayBuffer} The `.buffer` property of a `TypedArray` or + a `new ArrayBuffer()` +* `byteOffset` {Number} Default: `0` +* `length` {Number} Default: `arrayBuffer.length - byteOffset` + +When passed a reference to the `.buffer` property of a `TypedArray` instance, +the newly created `Buffer` will share the same allocated memory as the +TypedArray. + +```js +const arr = new Uint16Array(2); +arr[0] = 5000; +arr[1] = 4000; + +const buf = Buffer.from(arr.buffer); // shares the memory with arr; + +console.log(buf); + // Prints: + +// changing the TypedArray changes the Buffer also +arr[1] = 6000; + +console.log(buf); + // Prints: +``` + +The optional `byteOffset` and `length` arguments specify a memory range within +the `arrayBuffer` that will be shared by the `Buffer`. + +```js +const ab = new ArrayBuffer(10); +const buf = Buffer.from(ab, 0, 2); +console.log(buf.length); + // Prints: 2 +``` + +A `TypeError` will be thrown if `arrayBuffer` is not an `ArrayBuffer`. + +### Class Method: Buffer.from(buffer) + + +* `buffer` {Buffer} + +Copies the passed `buffer` data onto a new `Buffer` instance. + +```js +const buf1 = Buffer.from('buffer'); +const buf2 = Buffer.from(buf1); + +buf1[0] = 0x61; +console.log(buf1.toString()); + // 'auffer' +console.log(buf2.toString()); + // 'buffer' (copy is not changed) +``` + +A `TypeError` will be thrown if `buffer` is not a `Buffer`. + +### Class Method: Buffer.from(str[, encoding]) + + +* `str` {String} String to encode. +* `encoding` {String} Encoding to use, Default: `'utf8'` + +Creates a new `Buffer` containing the given JavaScript string `str`. If +provided, the `encoding` parameter identifies the character encoding. +If not provided, `encoding` defaults to `'utf8'`. + +```js +const buf1 = Buffer.from('this is a tést'); +console.log(buf1.toString()); + // prints: this is a tést +console.log(buf1.toString('ascii')); + // prints: this is a tC)st + +const buf2 = Buffer.from('7468697320697320612074c3a97374', 'hex'); +console.log(buf2.toString()); + // prints: this is a tést +``` + +A `TypeError` will be thrown if `str` is not a string. + +### Class Method: Buffer.alloc(size[, fill[, encoding]]) + + +* `size` {Number} +* `fill` {Value} Default: `undefined` +* `encoding` {String} Default: `utf8` + +Allocates a new `Buffer` of `size` bytes. If `fill` is `undefined`, the +`Buffer` will be *zero-filled*. + +```js +const buf = Buffer.alloc(5); +console.log(buf); + // +``` + +The `size` must be less than or equal to the value of +`require('buffer').kMaxLength` (on 64-bit architectures, `kMaxLength` is +`(2^31)-1`). Otherwise, a [`RangeError`][] is thrown. A zero-length Buffer will +be created if a `size` less than or equal to 0 is specified. + +If `fill` is specified, the allocated `Buffer` will be initialized by calling +`buf.fill(fill)`. See [`buf.fill()`][] for more information. + +```js +const buf = Buffer.alloc(5, 'a'); +console.log(buf); + // +``` + +If both `fill` and `encoding` are specified, the allocated `Buffer` will be +initialized by calling `buf.fill(fill, encoding)`. For example: + +```js +const buf = Buffer.alloc(11, 'aGVsbG8gd29ybGQ=', 'base64'); +console.log(buf); + // +``` + +Calling `Buffer.alloc(size)` can be significantly slower than the alternative +`Buffer.allocUnsafe(size)` but ensures that the newly created `Buffer` instance +contents will *never contain sensitive data*. + +A `TypeError` will be thrown if `size` is not a number. + +### Class Method: Buffer.allocUnsafe(size) + + +* `size` {Number} + +Allocates a new *non-zero-filled* `Buffer` of `size` bytes. The `size` must +be less than or equal to the value of `require('buffer').kMaxLength` (on 64-bit +architectures, `kMaxLength` is `(2^31)-1`). Otherwise, a [`RangeError`][] is +thrown. A zero-length Buffer will be created if a `size` less than or equal to +0 is specified. + +The underlying memory for `Buffer` instances created in this way is *not +initialized*. The contents of the newly created `Buffer` are unknown and +*may contain sensitive data*. Use [`buf.fill(0)`][] to initialize such +`Buffer` instances to zeroes. + +```js +const buf = Buffer.allocUnsafe(5); +console.log(buf); + // + // (octets will be different, every time) +buf.fill(0); +console.log(buf); + // +``` + +A `TypeError` will be thrown if `size` is not a number. + +Note that the `Buffer` module pre-allocates an internal `Buffer` instance of +size `Buffer.poolSize` that is used as a pool for the fast allocation of new +`Buffer` instances created using `Buffer.allocUnsafe(size)` (and the deprecated +`new Buffer(size)` constructor) only when `size` is less than or equal to +`Buffer.poolSize >> 1` (floor of `Buffer.poolSize` divided by two). The default +value of `Buffer.poolSize` is `8192` but can be modified. + +Use of this pre-allocated internal memory pool is a key difference between +calling `Buffer.alloc(size, fill)` vs. `Buffer.allocUnsafe(size).fill(fill)`. +Specifically, `Buffer.alloc(size, fill)` will *never* use the internal Buffer +pool, while `Buffer.allocUnsafe(size).fill(fill)` *will* use the internal +Buffer pool if `size` is less than or equal to half `Buffer.poolSize`. The +difference is subtle but can be important when an application requires the +additional performance that `Buffer.allocUnsafe(size)` provides. + +### Class Method: Buffer.allocUnsafeSlow(size) + + +* `size` {Number} + +Allocates a new *non-zero-filled* and non-pooled `Buffer` of `size` bytes. The +`size` must be less than or equal to the value of +`require('buffer').kMaxLength` (on 64-bit architectures, `kMaxLength` is +`(2^31)-1`). Otherwise, a [`RangeError`][] is thrown. A zero-length Buffer will +be created if a `size` less than or equal to 0 is specified. + +The underlying memory for `Buffer` instances created in this way is *not +initialized*. The contents of the newly created `Buffer` are unknown and +*may contain sensitive data*. Use [`buf.fill(0)`][] to initialize such +`Buffer` instances to zeroes. + +When using `Buffer.allocUnsafe()` to allocate new `Buffer` instances, +allocations under 4KB are, by default, sliced from a single pre-allocated +`Buffer`. This allows applications to avoid the garbage collection overhead of +creating many individually allocated Buffers. This approach improves both +performance and memory usage by eliminating the need to track and cleanup as +many `Persistent` objects. + +However, in the case where a developer may need to retain a small chunk of +memory from a pool for an indeterminate amount of time, it may be appropriate +to create an un-pooled Buffer instance using `Buffer.allocUnsafeSlow()` then +copy out the relevant bits. + +```js +// need to keep around a few small chunks of memory +const store = []; + +socket.on('readable', () => { + const data = socket.read(); + // allocate for retained data + const sb = Buffer.allocUnsafeSlow(10); + // copy the data into the new allocation + data.copy(sb, 0, 0, 10); + store.push(sb); +}); +``` + +Use of `Buffer.allocUnsafeSlow()` should be used only as a last resort *after* +a developer has observed undue memory retention in their applications. + +A `TypeError` will be thrown if `size` is not a number. + +### All the Rest + +The rest of the `Buffer` API is exactly the same as in node.js. +[See the docs](https://nodejs.org/api/buffer.html). + + +## Related links + +- [Node.js issue: Buffer(number) is unsafe](https://github.com/nodejs/node/issues/4660) +- [Node.js Enhancement Proposal: Buffer.from/Buffer.alloc/Buffer.zalloc/Buffer() soft-deprecate](https://github.com/nodejs/node-eps/pull/4) + +## Why is `Buffer` unsafe? + +Today, the node.js `Buffer` constructor is overloaded to handle many different argument +types like `String`, `Array`, `Object`, `TypedArrayView` (`Uint8Array`, etc.), +`ArrayBuffer`, and also `Number`. + +The API is optimized for convenience: you can throw any type at it, and it will try to do +what you want. + +Because the Buffer constructor is so powerful, you often see code like this: + +```js +// Convert UTF-8 strings to hex +function toHex (str) { + return new Buffer(str).toString('hex') +} +``` + +***But what happens if `toHex` is called with a `Number` argument?*** + +### Remote Memory Disclosure + +If an attacker can make your program call the `Buffer` constructor with a `Number` +argument, then they can make it allocate uninitialized memory from the node.js process. +This could potentially disclose TLS private keys, user data, or database passwords. + +When the `Buffer` constructor is passed a `Number` argument, it returns an +**UNINITIALIZED** block of memory of the specified `size`. When you create a `Buffer` like +this, you **MUST** overwrite the contents before returning it to the user. + +From the [node.js docs](https://nodejs.org/api/buffer.html#buffer_new_buffer_size): + +> `new Buffer(size)` +> +> - `size` Number +> +> The underlying memory for `Buffer` instances created in this way is not initialized. +> **The contents of a newly created `Buffer` are unknown and could contain sensitive +> data.** Use `buf.fill(0)` to initialize a Buffer to zeroes. + +(Emphasis our own.) + +Whenever the programmer intended to create an uninitialized `Buffer` you often see code +like this: + +```js +var buf = new Buffer(16) + +// Immediately overwrite the uninitialized buffer with data from another buffer +for (var i = 0; i < buf.length; i++) { + buf[i] = otherBuf[i] +} +``` + + +### Would this ever be a problem in real code? + +Yes. It's surprisingly common to forget to check the type of your variables in a +dynamically-typed language like JavaScript. + +Usually the consequences of assuming the wrong type is that your program crashes with an +uncaught exception. But the failure mode for forgetting to check the type of arguments to +the `Buffer` constructor is more catastrophic. + +Here's an example of a vulnerable service that takes a JSON payload and converts it to +hex: + +```js +// Take a JSON payload {str: "some string"} and convert it to hex +var server = http.createServer(function (req, res) { + var data = '' + req.setEncoding('utf8') + req.on('data', function (chunk) { + data += chunk + }) + req.on('end', function () { + var body = JSON.parse(data) + res.end(new Buffer(body.str).toString('hex')) + }) +}) + +server.listen(8080) +``` + +In this example, an http client just has to send: + +```json +{ + "str": 1000 +} +``` + +and it will get back 1,000 bytes of uninitialized memory from the server. + +This is a very serious bug. It's similar in severity to the +[the Heartbleed bug](http://heartbleed.com/) that allowed disclosure of OpenSSL process +memory by remote attackers. + + +### Which real-world packages were vulnerable? + +#### [`bittorrent-dht`](https://www.npmjs.com/package/bittorrent-dht) + +[Mathias Buus](https://github.com/mafintosh) and I +([Feross Aboukhadijeh](http://feross.org/)) found this issue in one of our own packages, +[`bittorrent-dht`](https://www.npmjs.com/package/bittorrent-dht). The bug would allow +anyone on the internet to send a series of messages to a user of `bittorrent-dht` and get +them to reveal 20 bytes at a time of uninitialized memory from the node.js process. + +Here's +[the commit](https://github.com/feross/bittorrent-dht/commit/6c7da04025d5633699800a99ec3fbadf70ad35b8) +that fixed it. We released a new fixed version, created a +[Node Security Project disclosure](https://nodesecurity.io/advisories/68), and deprecated all +vulnerable versions on npm so users will get a warning to upgrade to a newer version. + +#### [`ws`](https://www.npmjs.com/package/ws) + +That got us wondering if there were other vulnerable packages. Sure enough, within a short +period of time, we found the same issue in [`ws`](https://www.npmjs.com/package/ws), the +most popular WebSocket implementation in node.js. + +If certain APIs were called with `Number` parameters instead of `String` or `Buffer` as +expected, then uninitialized server memory would be disclosed to the remote peer. + +These were the vulnerable methods: + +```js +socket.send(number) +socket.ping(number) +socket.pong(number) +``` + +Here's a vulnerable socket server with some echo functionality: + +```js +server.on('connection', function (socket) { + socket.on('message', function (message) { + message = JSON.parse(message) + if (message.type === 'echo') { + socket.send(message.data) // send back the user's message + } + }) +}) +``` + +`socket.send(number)` called on the server, will disclose server memory. + +Here's [the release](https://github.com/websockets/ws/releases/tag/1.0.1) where the issue +was fixed, with a more detailed explanation. Props to +[Arnout Kazemier](https://github.com/3rd-Eden) for the quick fix. Here's the +[Node Security Project disclosure](https://nodesecurity.io/advisories/67). + + +### What's the solution? + +It's important that node.js offers a fast way to get memory otherwise performance-critical +applications would needlessly get a lot slower. + +But we need a better way to *signal our intent* as programmers. **When we want +uninitialized memory, we should request it explicitly.** + +Sensitive functionality should not be packed into a developer-friendly API that loosely +accepts many different types. This type of API encourages the lazy practice of passing +variables in without checking the type very carefully. + +#### A new API: `Buffer.allocUnsafe(number)` + +The functionality of creating buffers with uninitialized memory should be part of another +API. We propose `Buffer.allocUnsafe(number)`. This way, it's not part of an API that +frequently gets user input of all sorts of different types passed into it. + +```js +var buf = Buffer.allocUnsafe(16) // careful, uninitialized memory! + +// Immediately overwrite the uninitialized buffer with data from another buffer +for (var i = 0; i < buf.length; i++) { + buf[i] = otherBuf[i] +} +``` + + +### How do we fix node.js core? + +We sent [a PR to node.js core](https://github.com/nodejs/node/pull/4514) (merged as +`semver-major`) which defends against one case: + +```js +var str = 16 +new Buffer(str, 'utf8') +``` + +In this situation, it's implied that the programmer intended the first argument to be a +string, since they passed an encoding as a second argument. Today, node.js will allocate +uninitialized memory in the case of `new Buffer(number, encoding)`, which is probably not +what the programmer intended. + +But this is only a partial solution, since if the programmer does `new Buffer(variable)` +(without an `encoding` parameter) there's no way to know what they intended. If `variable` +is sometimes a number, then uninitialized memory will sometimes be returned. + +### What's the real long-term fix? + +We could deprecate and remove `new Buffer(number)` and use `Buffer.allocUnsafe(number)` when +we need uninitialized memory. But that would break 1000s of packages. + +~~We believe the best solution is to:~~ + +~~1. Change `new Buffer(number)` to return safe, zeroed-out memory~~ + +~~2. Create a new API for creating uninitialized Buffers. We propose: `Buffer.allocUnsafe(number)`~~ + +#### Update + +We now support adding three new APIs: + +- `Buffer.from(value)` - convert from any type to a buffer +- `Buffer.alloc(size)` - create a zero-filled buffer +- `Buffer.allocUnsafe(size)` - create an uninitialized buffer with given size + +This solves the core problem that affected `ws` and `bittorrent-dht` which is +`Buffer(variable)` getting tricked into taking a number argument. + +This way, existing code continues working and the impact on the npm ecosystem will be +minimal. Over time, npm maintainers can migrate performance-critical code to use +`Buffer.allocUnsafe(number)` instead of `new Buffer(number)`. + + +### Conclusion + +We think there's a serious design issue with the `Buffer` API as it exists today. It +promotes insecure software by putting high-risk functionality into a convenient API +with friendly "developer ergonomics". + +This wasn't merely a theoretical exercise because we found the issue in some of the +most popular npm packages. + +Fortunately, there's an easy fix that can be applied today. Use `safe-buffer` in place of +`buffer`. + +```js +var Buffer = require('safe-buffer').Buffer +``` + +Eventually, we hope that node.js core can switch to this new, safer behavior. We believe +the impact on the ecosystem would be minimal since it's not a breaking change. +Well-maintained, popular packages would be updated to use `Buffer.alloc` quickly, while +older, insecure packages would magically become safe from this attack vector. + + +## links + +- [Node.js PR: buffer: throw if both length and enc are passed](https://github.com/nodejs/node/pull/4514) +- [Node Security Project disclosure for `ws`](https://nodesecurity.io/advisories/67) +- [Node Security Project disclosure for`bittorrent-dht`](https://nodesecurity.io/advisories/68) + + +## credit + +The original issues in `bittorrent-dht` +([disclosure](https://nodesecurity.io/advisories/68)) and +`ws` ([disclosure](https://nodesecurity.io/advisories/67)) were discovered by +[Mathias Buus](https://github.com/mafintosh) and +[Feross Aboukhadijeh](http://feross.org/). + +Thanks to [Adam Baldwin](https://github.com/evilpacket) for helping disclose these issues +and for his work running the [Node Security Project](https://nodesecurity.io/). + +Thanks to [John Hiesey](https://github.com/jhiesey) for proofreading this README and +auditing the code. + + +## license + +MIT. Copyright (C) [Feross Aboukhadijeh](http://feross.org) diff --git a/backend/node_modules/readable-stream/node_modules/safe-buffer/index.d.ts b/backend/node_modules/readable-stream/node_modules/safe-buffer/index.d.ts new file mode 100644 index 0000000..e9fed80 --- /dev/null +++ b/backend/node_modules/readable-stream/node_modules/safe-buffer/index.d.ts @@ -0,0 +1,187 @@ +declare module "safe-buffer" { + export class Buffer { + length: number + write(string: string, offset?: number, length?: number, encoding?: string): number; + toString(encoding?: string, start?: number, end?: number): string; + toJSON(): { type: 'Buffer', data: any[] }; + equals(otherBuffer: Buffer): boolean; + compare(otherBuffer: Buffer, targetStart?: number, targetEnd?: number, sourceStart?: number, sourceEnd?: number): number; + copy(targetBuffer: Buffer, targetStart?: number, sourceStart?: number, sourceEnd?: number): number; + slice(start?: number, end?: number): Buffer; + writeUIntLE(value: number, offset: number, byteLength: number, noAssert?: boolean): number; + writeUIntBE(value: number, offset: number, byteLength: number, noAssert?: boolean): number; + writeIntLE(value: number, offset: number, byteLength: number, noAssert?: boolean): number; + writeIntBE(value: number, offset: number, byteLength: number, noAssert?: boolean): number; + readUIntLE(offset: number, byteLength: number, noAssert?: boolean): number; + readUIntBE(offset: number, byteLength: number, noAssert?: boolean): number; + readIntLE(offset: number, byteLength: number, noAssert?: boolean): number; + readIntBE(offset: number, byteLength: number, noAssert?: boolean): number; + readUInt8(offset: number, noAssert?: boolean): number; + readUInt16LE(offset: number, noAssert?: boolean): number; + readUInt16BE(offset: number, noAssert?: boolean): number; + readUInt32LE(offset: number, noAssert?: boolean): number; + readUInt32BE(offset: number, noAssert?: boolean): number; + readInt8(offset: number, noAssert?: boolean): number; + readInt16LE(offset: number, noAssert?: boolean): number; + readInt16BE(offset: number, noAssert?: boolean): number; + readInt32LE(offset: number, noAssert?: boolean): number; + readInt32BE(offset: number, noAssert?: boolean): number; + readFloatLE(offset: number, noAssert?: boolean): number; + readFloatBE(offset: number, noAssert?: boolean): number; + readDoubleLE(offset: number, noAssert?: boolean): number; + readDoubleBE(offset: number, noAssert?: boolean): number; + swap16(): Buffer; + swap32(): Buffer; + swap64(): Buffer; + writeUInt8(value: number, offset: number, noAssert?: boolean): number; + writeUInt16LE(value: number, offset: number, noAssert?: boolean): number; + writeUInt16BE(value: number, offset: number, noAssert?: boolean): number; + writeUInt32LE(value: number, offset: number, noAssert?: boolean): number; + writeUInt32BE(value: number, offset: number, noAssert?: boolean): number; + writeInt8(value: number, offset: number, noAssert?: boolean): number; + writeInt16LE(value: number, offset: number, noAssert?: boolean): number; + writeInt16BE(value: number, offset: number, noAssert?: boolean): number; + writeInt32LE(value: number, offset: number, noAssert?: boolean): number; + writeInt32BE(value: number, offset: number, noAssert?: boolean): number; + writeFloatLE(value: number, offset: number, noAssert?: boolean): number; + writeFloatBE(value: number, offset: number, noAssert?: boolean): number; + writeDoubleLE(value: number, offset: number, noAssert?: boolean): number; + writeDoubleBE(value: number, offset: number, noAssert?: boolean): number; + fill(value: any, offset?: number, end?: number): this; + indexOf(value: string | number | Buffer, byteOffset?: number, encoding?: string): number; + lastIndexOf(value: string | number | Buffer, byteOffset?: number, encoding?: string): number; + includes(value: string | number | Buffer, byteOffset?: number, encoding?: string): boolean; + + /** + * Allocates a new buffer containing the given {str}. + * + * @param str String to store in buffer. + * @param encoding encoding to use, optional. Default is 'utf8' + */ + constructor (str: string, encoding?: string); + /** + * Allocates a new buffer of {size} octets. + * + * @param size count of octets to allocate. + */ + constructor (size: number); + /** + * Allocates a new buffer containing the given {array} of octets. + * + * @param array The octets to store. + */ + constructor (array: Uint8Array); + /** + * Produces a Buffer backed by the same allocated memory as + * the given {ArrayBuffer}. + * + * + * @param arrayBuffer The ArrayBuffer with which to share memory. + */ + constructor (arrayBuffer: ArrayBuffer); + /** + * Allocates a new buffer containing the given {array} of octets. + * + * @param array The octets to store. + */ + constructor (array: any[]); + /** + * Copies the passed {buffer} data onto a new {Buffer} instance. + * + * @param buffer The buffer to copy. + */ + constructor (buffer: Buffer); + prototype: Buffer; + /** + * Allocates a new Buffer using an {array} of octets. + * + * @param array + */ + static from(array: any[]): Buffer; + /** + * When passed a reference to the .buffer property of a TypedArray instance, + * the newly created Buffer will share the same allocated memory as the TypedArray. + * The optional {byteOffset} and {length} arguments specify a memory range + * within the {arrayBuffer} that will be shared by the Buffer. + * + * @param arrayBuffer The .buffer property of a TypedArray or a new ArrayBuffer() + * @param byteOffset + * @param length + */ + static from(arrayBuffer: ArrayBuffer, byteOffset?: number, length?: number): Buffer; + /** + * Copies the passed {buffer} data onto a new Buffer instance. + * + * @param buffer + */ + static from(buffer: Buffer): Buffer; + /** + * Creates a new Buffer containing the given JavaScript string {str}. + * If provided, the {encoding} parameter identifies the character encoding. + * If not provided, {encoding} defaults to 'utf8'. + * + * @param str + */ + static from(str: string, encoding?: string): Buffer; + /** + * Returns true if {obj} is a Buffer + * + * @param obj object to test. + */ + static isBuffer(obj: any): obj is Buffer; + /** + * Returns true if {encoding} is a valid encoding argument. + * Valid string encodings in Node 0.12: 'ascii'|'utf8'|'utf16le'|'ucs2'(alias of 'utf16le')|'base64'|'binary'(deprecated)|'hex' + * + * @param encoding string to test. + */ + static isEncoding(encoding: string): boolean; + /** + * Gives the actual byte length of a string. encoding defaults to 'utf8'. + * This is not the same as String.prototype.length since that returns the number of characters in a string. + * + * @param string string to test. + * @param encoding encoding used to evaluate (defaults to 'utf8') + */ + static byteLength(string: string, encoding?: string): number; + /** + * Returns a buffer which is the result of concatenating all the buffers in the list together. + * + * If the list has no items, or if the totalLength is 0, then it returns a zero-length buffer. + * If the list has exactly one item, then the first item of the list is returned. + * If the list has more than one item, then a new Buffer is created. + * + * @param list An array of Buffer objects to concatenate + * @param totalLength Total length of the buffers when concatenated. + * If totalLength is not provided, it is read from the buffers in the list. However, this adds an additional loop to the function, so it is faster to provide the length explicitly. + */ + static concat(list: Buffer[], totalLength?: number): Buffer; + /** + * The same as buf1.compare(buf2). + */ + static compare(buf1: Buffer, buf2: Buffer): number; + /** + * Allocates a new buffer of {size} octets. + * + * @param size count of octets to allocate. + * @param fill if specified, buffer will be initialized by calling buf.fill(fill). + * If parameter is omitted, buffer will be filled with zeros. + * @param encoding encoding used for call to buf.fill while initalizing + */ + static alloc(size: number, fill?: string | Buffer | number, encoding?: string): Buffer; + /** + * Allocates a new buffer of {size} octets, leaving memory not initialized, so the contents + * of the newly created Buffer are unknown and may contain sensitive data. + * + * @param size count of octets to allocate + */ + static allocUnsafe(size: number): Buffer; + /** + * Allocates a new non-pooled buffer of {size} octets, leaving memory not initialized, so the contents + * of the newly created Buffer are unknown and may contain sensitive data. + * + * @param size count of octets to allocate + */ + static allocUnsafeSlow(size: number): Buffer; + } +} \ No newline at end of file diff --git a/backend/node_modules/readable-stream/node_modules/safe-buffer/index.js b/backend/node_modules/readable-stream/node_modules/safe-buffer/index.js new file mode 100644 index 0000000..22438da --- /dev/null +++ b/backend/node_modules/readable-stream/node_modules/safe-buffer/index.js @@ -0,0 +1,62 @@ +/* eslint-disable node/no-deprecated-api */ +var buffer = require('buffer') +var Buffer = buffer.Buffer + +// alternative to using Object.keys for old browsers +function copyProps (src, dst) { + for (var key in src) { + dst[key] = src[key] + } +} +if (Buffer.from && Buffer.alloc && Buffer.allocUnsafe && Buffer.allocUnsafeSlow) { + module.exports = buffer +} else { + // Copy properties from require('buffer') + copyProps(buffer, exports) + exports.Buffer = SafeBuffer +} + +function SafeBuffer (arg, encodingOrOffset, length) { + return Buffer(arg, encodingOrOffset, length) +} + +// Copy static methods from Buffer +copyProps(Buffer, SafeBuffer) + +SafeBuffer.from = function (arg, encodingOrOffset, length) { + if (typeof arg === 'number') { + throw new TypeError('Argument must not be a number') + } + return Buffer(arg, encodingOrOffset, length) +} + +SafeBuffer.alloc = function (size, fill, encoding) { + if (typeof size !== 'number') { + throw new TypeError('Argument must be a number') + } + var buf = Buffer(size) + if (fill !== undefined) { + if (typeof encoding === 'string') { + buf.fill(fill, encoding) + } else { + buf.fill(fill) + } + } else { + buf.fill(0) + } + return buf +} + +SafeBuffer.allocUnsafe = function (size) { + if (typeof size !== 'number') { + throw new TypeError('Argument must be a number') + } + return Buffer(size) +} + +SafeBuffer.allocUnsafeSlow = function (size) { + if (typeof size !== 'number') { + throw new TypeError('Argument must be a number') + } + return buffer.SlowBuffer(size) +} diff --git a/backend/node_modules/readable-stream/node_modules/safe-buffer/package.json b/backend/node_modules/readable-stream/node_modules/safe-buffer/package.json new file mode 100644 index 0000000..623fbc3 --- /dev/null +++ b/backend/node_modules/readable-stream/node_modules/safe-buffer/package.json @@ -0,0 +1,37 @@ +{ + "name": "safe-buffer", + "description": "Safer Node.js Buffer API", + "version": "5.1.2", + "author": { + "name": "Feross Aboukhadijeh", + "email": "feross@feross.org", + "url": "http://feross.org" + }, + "bugs": { + "url": "https://github.com/feross/safe-buffer/issues" + }, + "devDependencies": { + "standard": "*", + "tape": "^4.0.0" + }, + "homepage": "https://github.com/feross/safe-buffer", + "keywords": [ + "buffer", + "buffer allocate", + "node security", + "safe", + "safe-buffer", + "security", + "uninitialized" + ], + "license": "MIT", + "main": "index.js", + "types": "index.d.ts", + "repository": { + "type": "git", + "url": "git://github.com/feross/safe-buffer.git" + }, + "scripts": { + "test": "standard && tape test/*.js" + } +} diff --git a/backend/node_modules/readable-stream/package.json b/backend/node_modules/readable-stream/package.json new file mode 100644 index 0000000..514c178 --- /dev/null +++ b/backend/node_modules/readable-stream/package.json @@ -0,0 +1,52 @@ +{ + "name": "readable-stream", + "version": "2.3.8", + "description": "Streams3, a user-land copy of the stream library from Node.js", + "main": "readable.js", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + }, + "devDependencies": { + "assert": "^1.4.0", + "babel-polyfill": "^6.9.1", + "buffer": "^4.9.0", + "lolex": "^2.3.2", + "nyc": "^6.4.0", + "tap": "^0.7.0", + "tape": "^4.8.0" + }, + "scripts": { + "test": "tap test/parallel/*.js test/ours/*.js && node test/verify-dependencies.js", + "ci": "tap test/parallel/*.js test/ours/*.js --tap | tee test.tap && node test/verify-dependencies.js", + "cover": "nyc npm test", + "report": "nyc report --reporter=lcov" + }, + "repository": { + "type": "git", + "url": "git://github.com/nodejs/readable-stream" + }, + "keywords": [ + "readable", + "stream", + "pipe" + ], + "browser": { + "util": false, + "./readable.js": "./readable-browser.js", + "./writable.js": "./writable-browser.js", + "./duplex.js": "./duplex-browser.js", + "./lib/internal/streams/stream.js": "./lib/internal/streams/stream-browser.js" + }, + "nyc": { + "include": [ + "lib/**.js" + ] + }, + "license": "MIT" +} diff --git a/backend/node_modules/readable-stream/passthrough.js b/backend/node_modules/readable-stream/passthrough.js new file mode 100644 index 0000000..ffd791d --- /dev/null +++ b/backend/node_modules/readable-stream/passthrough.js @@ -0,0 +1 @@ +module.exports = require('./readable').PassThrough diff --git a/backend/node_modules/readable-stream/readable-browser.js b/backend/node_modules/readable-stream/readable-browser.js new file mode 100644 index 0000000..e503725 --- /dev/null +++ b/backend/node_modules/readable-stream/readable-browser.js @@ -0,0 +1,7 @@ +exports = module.exports = require('./lib/_stream_readable.js'); +exports.Stream = exports; +exports.Readable = exports; +exports.Writable = require('./lib/_stream_writable.js'); +exports.Duplex = require('./lib/_stream_duplex.js'); +exports.Transform = require('./lib/_stream_transform.js'); +exports.PassThrough = require('./lib/_stream_passthrough.js'); diff --git a/backend/node_modules/readable-stream/readable.js b/backend/node_modules/readable-stream/readable.js new file mode 100644 index 0000000..ec89ec5 --- /dev/null +++ b/backend/node_modules/readable-stream/readable.js @@ -0,0 +1,19 @@ +var Stream = require('stream'); +if (process.env.READABLE_STREAM === 'disable' && Stream) { + module.exports = Stream; + exports = module.exports = Stream.Readable; + exports.Readable = Stream.Readable; + exports.Writable = Stream.Writable; + exports.Duplex = Stream.Duplex; + exports.Transform = Stream.Transform; + exports.PassThrough = Stream.PassThrough; + exports.Stream = Stream; +} else { + exports = module.exports = require('./lib/_stream_readable.js'); + exports.Stream = Stream || exports; + exports.Readable = exports; + exports.Writable = require('./lib/_stream_writable.js'); + exports.Duplex = require('./lib/_stream_duplex.js'); + exports.Transform = require('./lib/_stream_transform.js'); + exports.PassThrough = require('./lib/_stream_passthrough.js'); +} diff --git a/backend/node_modules/readable-stream/transform.js b/backend/node_modules/readable-stream/transform.js new file mode 100644 index 0000000..b1baba2 --- /dev/null +++ b/backend/node_modules/readable-stream/transform.js @@ -0,0 +1 @@ +module.exports = require('./readable').Transform diff --git a/backend/node_modules/readable-stream/writable-browser.js b/backend/node_modules/readable-stream/writable-browser.js new file mode 100644 index 0000000..ebdde6a --- /dev/null +++ b/backend/node_modules/readable-stream/writable-browser.js @@ -0,0 +1 @@ +module.exports = require('./lib/_stream_writable.js'); diff --git a/backend/node_modules/readable-stream/writable.js b/backend/node_modules/readable-stream/writable.js new file mode 100644 index 0000000..3211a6f --- /dev/null +++ b/backend/node_modules/readable-stream/writable.js @@ -0,0 +1,8 @@ +var Stream = require("stream") +var Writable = require("./lib/_stream_writable.js") + +if (process.env.READABLE_STREAM === 'disable') { + module.exports = Stream && Stream.Writable || Writable +} else { + module.exports = Writable +} diff --git a/backend/node_modules/streamsearch/.eslintrc.js b/backend/node_modules/streamsearch/.eslintrc.js new file mode 100644 index 0000000..be9311d --- /dev/null +++ b/backend/node_modules/streamsearch/.eslintrc.js @@ -0,0 +1,5 @@ +'use strict'; + +module.exports = { + extends: '@mscdex/eslint-config', +}; diff --git a/backend/node_modules/streamsearch/.github/workflows/ci.yml b/backend/node_modules/streamsearch/.github/workflows/ci.yml new file mode 100644 index 0000000..29d5178 --- /dev/null +++ b/backend/node_modules/streamsearch/.github/workflows/ci.yml @@ -0,0 +1,24 @@ +name: CI + +on: + pull_request: + push: + branches: [ master ] + +jobs: + tests-linux: + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + node-version: [10.x, 12.x, 14.x, 16.x] + steps: + - uses: actions/checkout@v2 + - name: Use Node.js ${{ matrix.node-version }} + uses: actions/setup-node@v1 + with: + node-version: ${{ matrix.node-version }} + - name: Install module + run: npm install + - name: Run tests + run: npm test diff --git a/backend/node_modules/streamsearch/.github/workflows/lint.yml b/backend/node_modules/streamsearch/.github/workflows/lint.yml new file mode 100644 index 0000000..9f9e1f5 --- /dev/null +++ b/backend/node_modules/streamsearch/.github/workflows/lint.yml @@ -0,0 +1,23 @@ +name: lint + +on: + pull_request: + push: + branches: [ master ] + +env: + NODE_VERSION: 16.x + +jobs: + lint-js: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - name: Use Node.js ${{ env.NODE_VERSION }} + uses: actions/setup-node@v1 + with: + node-version: ${{ env.NODE_VERSION }} + - name: Install ESLint + ESLint configs/plugins + run: npm install --only=dev + - name: Lint files + run: npm run lint diff --git a/backend/node_modules/streamsearch/LICENSE b/backend/node_modules/streamsearch/LICENSE new file mode 100644 index 0000000..290762e --- /dev/null +++ b/backend/node_modules/streamsearch/LICENSE @@ -0,0 +1,19 @@ +Copyright Brian White. All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to +deal in the Software without restriction, including without limitation the +rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +sell copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +IN THE SOFTWARE. \ No newline at end of file diff --git a/backend/node_modules/streamsearch/README.md b/backend/node_modules/streamsearch/README.md new file mode 100644 index 0000000..c3934d1 --- /dev/null +++ b/backend/node_modules/streamsearch/README.md @@ -0,0 +1,95 @@ +Description +=========== + +streamsearch is a module for [node.js](http://nodejs.org/) that allows searching a stream using the Boyer-Moore-Horspool algorithm. + +This module is based heavily on the Streaming Boyer-Moore-Horspool C++ implementation by Hongli Lai [here](https://github.com/FooBarWidget/boyer-moore-horspool). + + +Requirements +============ + +* [node.js](http://nodejs.org/) -- v10.0.0 or newer + + +Installation +============ + + npm install streamsearch + +Example +======= + +```js + const { inspect } = require('util'); + + const StreamSearch = require('streamsearch'); + + const needle = Buffer.from('\r\n'); + const ss = new StreamSearch(needle, (isMatch, data, start, end) => { + if (data) + console.log('data: ' + inspect(data.toString('latin1', start, end))); + if (isMatch) + console.log('match!'); + }); + + const chunks = [ + 'foo', + ' bar', + '\r', + '\n', + 'baz, hello\r', + '\n world.', + '\r\n Node.JS rules!!\r\n\r\n', + ]; + for (const chunk of chunks) + ss.push(Buffer.from(chunk)); + + // output: + // + // data: 'foo' + // data: ' bar' + // match! + // data: 'baz, hello' + // match! + // data: ' world.' + // match! + // data: ' Node.JS rules!!' + // match! + // data: '' + // match! +``` + + +API +=== + +Properties +---------- + +* **maxMatches** - < _integer_ > - The maximum number of matches. Defaults to `Infinity`. + +* **matches** - < _integer_ > - The current match count. + + +Functions +--------- + +* **(constructor)**(< _mixed_ >needle, < _function_ >callback) - Creates and returns a new instance for searching for a _Buffer_ or _string_ `needle`. `callback` is called any time there is non-matching data and/or there is a needle match. `callback` will be called with the following arguments: + + 1. `isMatch` - _boolean_ - Indicates whether a match has been found + + 2. `data` - _mixed_ - If set, this contains data that did not match the needle. + + 3. `start` - _integer_ - The index in `data` where the non-matching data begins (inclusive). + + 4. `end` - _integer_ - The index in `data` where the non-matching data ends (exclusive). + + 5. `isSafeData` - _boolean_ - Indicates if it is safe to store a reference to `data` (e.g. as-is or via `data.slice()`) or not, as in some cases `data` may point to a Buffer whose contents change over time. + +* **destroy**() - _(void)_ - Emits any last remaining unmatched data that may still be buffered and then resets internal state. + +* **push**(< _Buffer_ >chunk) - _integer_ - Processes `chunk`, searching for a match. The return value is the last processed index in `chunk` + 1. + +* **reset**() - _(void)_ - Resets internal state. Useful for when you wish to start searching a new/different stream for example. + diff --git a/backend/node_modules/streamsearch/lib/sbmh.js b/backend/node_modules/streamsearch/lib/sbmh.js new file mode 100644 index 0000000..510cae2 --- /dev/null +++ b/backend/node_modules/streamsearch/lib/sbmh.js @@ -0,0 +1,267 @@ +'use strict'; +/* + Based heavily on the Streaming Boyer-Moore-Horspool C++ implementation + by Hongli Lai at: https://github.com/FooBarWidget/boyer-moore-horspool +*/ +function memcmp(buf1, pos1, buf2, pos2, num) { + for (let i = 0; i < num; ++i) { + if (buf1[pos1 + i] !== buf2[pos2 + i]) + return false; + } + return true; +} + +class SBMH { + constructor(needle, cb) { + if (typeof cb !== 'function') + throw new Error('Missing match callback'); + + if (typeof needle === 'string') + needle = Buffer.from(needle); + else if (!Buffer.isBuffer(needle)) + throw new Error(`Expected Buffer for needle, got ${typeof needle}`); + + const needleLen = needle.length; + + this.maxMatches = Infinity; + this.matches = 0; + + this._cb = cb; + this._lookbehindSize = 0; + this._needle = needle; + this._bufPos = 0; + + this._lookbehind = Buffer.allocUnsafe(needleLen); + + // Initialize occurrence table. + this._occ = [ + needleLen, needleLen, needleLen, needleLen, needleLen, needleLen, + needleLen, needleLen, needleLen, needleLen, needleLen, needleLen, + needleLen, needleLen, needleLen, needleLen, needleLen, needleLen, + needleLen, needleLen, needleLen, needleLen, needleLen, needleLen, + needleLen, needleLen, needleLen, needleLen, needleLen, needleLen, + needleLen, needleLen, needleLen, needleLen, needleLen, needleLen, + needleLen, needleLen, needleLen, needleLen, needleLen, needleLen, + needleLen, needleLen, needleLen, needleLen, needleLen, needleLen, + needleLen, needleLen, needleLen, needleLen, needleLen, needleLen, + needleLen, needleLen, needleLen, needleLen, needleLen, needleLen, + needleLen, needleLen, needleLen, needleLen, needleLen, needleLen, + needleLen, needleLen, needleLen, needleLen, needleLen, needleLen, + needleLen, needleLen, needleLen, needleLen, needleLen, needleLen, + needleLen, needleLen, needleLen, needleLen, needleLen, needleLen, + needleLen, needleLen, needleLen, needleLen, needleLen, needleLen, + needleLen, needleLen, needleLen, needleLen, needleLen, needleLen, + needleLen, needleLen, needleLen, needleLen, needleLen, needleLen, + needleLen, needleLen, needleLen, needleLen, needleLen, needleLen, + needleLen, needleLen, needleLen, needleLen, needleLen, needleLen, + needleLen, needleLen, needleLen, needleLen, needleLen, needleLen, + needleLen, needleLen, needleLen, needleLen, needleLen, needleLen, + needleLen, needleLen, needleLen, needleLen, needleLen, needleLen, + needleLen, needleLen, needleLen, needleLen, needleLen, needleLen, + needleLen, needleLen, needleLen, needleLen, needleLen, needleLen, + needleLen, needleLen, needleLen, needleLen, needleLen, needleLen, + needleLen, needleLen, needleLen, needleLen, needleLen, needleLen, + needleLen, needleLen, needleLen, needleLen, needleLen, needleLen, + needleLen, needleLen, needleLen, needleLen, needleLen, needleLen, + needleLen, needleLen, needleLen, needleLen, needleLen, needleLen, + needleLen, needleLen, needleLen, needleLen, needleLen, needleLen, + needleLen, needleLen, needleLen, needleLen, needleLen, needleLen, + needleLen, needleLen, needleLen, needleLen, needleLen, needleLen, + needleLen, needleLen, needleLen, needleLen, needleLen, needleLen, + needleLen, needleLen, needleLen, needleLen, needleLen, needleLen, + needleLen, needleLen, needleLen, needleLen, needleLen, needleLen, + needleLen, needleLen, needleLen, needleLen, needleLen, needleLen, + needleLen, needleLen, needleLen, needleLen, needleLen, needleLen, + needleLen, needleLen, needleLen, needleLen, needleLen, needleLen, + needleLen, needleLen, needleLen, needleLen, needleLen, needleLen, + needleLen, needleLen, needleLen, needleLen, needleLen, needleLen, + needleLen, needleLen, needleLen, needleLen, needleLen, needleLen, + needleLen, needleLen, needleLen, needleLen, needleLen, needleLen, + needleLen, needleLen, needleLen, needleLen + ]; + + // Populate occurrence table with analysis of the needle, ignoring the last + // letter. + if (needleLen > 1) { + for (let i = 0; i < needleLen - 1; ++i) + this._occ[needle[i]] = needleLen - 1 - i; + } + } + + reset() { + this.matches = 0; + this._lookbehindSize = 0; + this._bufPos = 0; + } + + push(chunk, pos) { + let result; + if (!Buffer.isBuffer(chunk)) + chunk = Buffer.from(chunk, 'latin1'); + const chunkLen = chunk.length; + this._bufPos = pos || 0; + while (result !== chunkLen && this.matches < this.maxMatches) + result = feed(this, chunk); + return result; + } + + destroy() { + const lbSize = this._lookbehindSize; + if (lbSize) + this._cb(false, this._lookbehind, 0, lbSize, false); + this.reset(); + } +} + +function feed(self, data) { + const len = data.length; + const needle = self._needle; + const needleLen = needle.length; + + // Positive: points to a position in `data` + // pos == 3 points to data[3] + // Negative: points to a position in the lookbehind buffer + // pos == -2 points to lookbehind[lookbehindSize - 2] + let pos = -self._lookbehindSize; + const lastNeedleCharPos = needleLen - 1; + const lastNeedleChar = needle[lastNeedleCharPos]; + const end = len - needleLen; + const occ = self._occ; + const lookbehind = self._lookbehind; + + if (pos < 0) { + // Lookbehind buffer is not empty. Perform Boyer-Moore-Horspool + // search with character lookup code that considers both the + // lookbehind buffer and the current round's haystack data. + // + // Loop until + // there is a match. + // or until + // we've moved past the position that requires the + // lookbehind buffer. In this case we switch to the + // optimized loop. + // or until + // the character to look at lies outside the haystack. + while (pos < 0 && pos <= end) { + const nextPos = pos + lastNeedleCharPos; + const ch = (nextPos < 0 + ? lookbehind[self._lookbehindSize + nextPos] + : data[nextPos]); + + if (ch === lastNeedleChar + && matchNeedle(self, data, pos, lastNeedleCharPos)) { + self._lookbehindSize = 0; + ++self.matches; + if (pos > -self._lookbehindSize) + self._cb(true, lookbehind, 0, self._lookbehindSize + pos, false); + else + self._cb(true, undefined, 0, 0, true); + + return (self._bufPos = pos + needleLen); + } + + pos += occ[ch]; + } + + // No match. + + // There's too few data for Boyer-Moore-Horspool to run, + // so let's use a different algorithm to skip as much as + // we can. + // Forward pos until + // the trailing part of lookbehind + data + // looks like the beginning of the needle + // or until + // pos == 0 + while (pos < 0 && !matchNeedle(self, data, pos, len - pos)) + ++pos; + + if (pos < 0) { + // Cut off part of the lookbehind buffer that has + // been processed and append the entire haystack + // into it. + const bytesToCutOff = self._lookbehindSize + pos; + + if (bytesToCutOff > 0) { + // The cut off data is guaranteed not to contain the needle. + self._cb(false, lookbehind, 0, bytesToCutOff, false); + } + + self._lookbehindSize -= bytesToCutOff; + lookbehind.copy(lookbehind, 0, bytesToCutOff, self._lookbehindSize); + lookbehind.set(data, self._lookbehindSize); + self._lookbehindSize += len; + + self._bufPos = len; + return len; + } + + // Discard lookbehind buffer. + self._cb(false, lookbehind, 0, self._lookbehindSize, false); + self._lookbehindSize = 0; + } + + pos += self._bufPos; + + const firstNeedleChar = needle[0]; + + // Lookbehind buffer is now empty. Perform Boyer-Moore-Horspool + // search with optimized character lookup code that only considers + // the current round's haystack data. + while (pos <= end) { + const ch = data[pos + lastNeedleCharPos]; + + if (ch === lastNeedleChar + && data[pos] === firstNeedleChar + && memcmp(needle, 0, data, pos, lastNeedleCharPos)) { + ++self.matches; + if (pos > 0) + self._cb(true, data, self._bufPos, pos, true); + else + self._cb(true, undefined, 0, 0, true); + + return (self._bufPos = pos + needleLen); + } + + pos += occ[ch]; + } + + // There was no match. If there's trailing haystack data that we cannot + // match yet using the Boyer-Moore-Horspool algorithm (because the trailing + // data is less than the needle size) then match using a modified + // algorithm that starts matching from the beginning instead of the end. + // Whatever trailing data is left after running this algorithm is added to + // the lookbehind buffer. + while (pos < len) { + if (data[pos] !== firstNeedleChar + || !memcmp(data, pos, needle, 0, len - pos)) { + ++pos; + continue; + } + data.copy(lookbehind, 0, pos, len); + self._lookbehindSize = len - pos; + break; + } + + // Everything until `pos` is guaranteed not to contain needle data. + if (pos > 0) + self._cb(false, data, self._bufPos, pos < len ? pos : len, true); + + self._bufPos = len; + return len; +} + +function matchNeedle(self, data, pos, len) { + const lb = self._lookbehind; + const lbSize = self._lookbehindSize; + const needle = self._needle; + + for (let i = 0; i < len; ++i, ++pos) { + const ch = (pos < 0 ? lb[lbSize + pos] : data[pos]); + if (ch !== needle[i]) + return false; + } + return true; +} + +module.exports = SBMH; diff --git a/backend/node_modules/streamsearch/package.json b/backend/node_modules/streamsearch/package.json new file mode 100644 index 0000000..51df8f9 --- /dev/null +++ b/backend/node_modules/streamsearch/package.json @@ -0,0 +1,34 @@ +{ + "name": "streamsearch", + "version": "1.1.0", + "author": "Brian White ", + "description": "Streaming Boyer-Moore-Horspool searching for node.js", + "main": "./lib/sbmh.js", + "engines": { + "node": ">=10.0.0" + }, + "devDependencies": { + "@mscdex/eslint-config": "^1.1.0", + "eslint": "^7.32.0" + }, + "scripts": { + "test": "node test/test.js", + "lint": "eslint --cache --report-unused-disable-directives --ext=.js .eslintrc.js lib test", + "lint:fix": "npm run lint -- --fix" + }, + "keywords": [ + "stream", + "horspool", + "boyer-moore-horspool", + "boyer-moore", + "search" + ], + "licenses": [{ + "type": "MIT", + "url": "http://github.com/mscdex/streamsearch/raw/master/LICENSE" + }], + "repository": { + "type": "git", + "url": "http://github.com/mscdex/streamsearch.git" + } +} diff --git a/backend/node_modules/streamsearch/test/test.js b/backend/node_modules/streamsearch/test/test.js new file mode 100644 index 0000000..39a04d7 --- /dev/null +++ b/backend/node_modules/streamsearch/test/test.js @@ -0,0 +1,70 @@ +'use strict'; + +const assert = require('assert'); + +const StreamSearch = require('../lib/sbmh.js'); + +[ + { + needle: '\r\n', + chunks: [ + 'foo', + ' bar', + '\r', + '\n', + 'baz, hello\r', + '\n world.', + '\r\n Node.JS rules!!\r\n\r\n', + ], + expect: [ + [false, 'foo'], + [false, ' bar'], + [ true, null], + [false, 'baz, hello'], + [ true, null], + [false, ' world.'], + [ true, null], + [ true, ' Node.JS rules!!'], + [ true, ''], + ], + }, + { + needle: '---foobarbaz', + chunks: [ + '---foobarbaz', + 'asdf', + '\r\n', + '---foobarba', + '---foobar', + 'ba', + '\r\n---foobarbaz--\r\n', + ], + expect: [ + [ true, null], + [false, 'asdf'], + [false, '\r\n'], + [false, '---foobarba'], + [false, '---foobarba'], + [ true, '\r\n'], + [false, '--\r\n'], + ], + }, +].forEach((test, i) => { + console.log(`Running test #${i + 1}`); + const { needle, chunks, expect } = test; + + const results = []; + const ss = new StreamSearch(Buffer.from(needle), + (isMatch, data, start, end) => { + if (data) + data = data.toString('latin1', start, end); + else + data = null; + results.push([isMatch, data]); + }); + + for (const chunk of chunks) + ss.push(Buffer.from(chunk)); + + assert.deepStrictEqual(results, expect); +}); diff --git a/backend/node_modules/string_decoder/.travis.yml b/backend/node_modules/string_decoder/.travis.yml new file mode 100644 index 0000000..3347a72 --- /dev/null +++ b/backend/node_modules/string_decoder/.travis.yml @@ -0,0 +1,50 @@ +sudo: false +language: node_js +before_install: + - npm install -g npm@2 + - test $NPM_LEGACY && npm install -g npm@latest-3 || npm install npm -g +notifications: + email: false +matrix: + fast_finish: true + include: + - node_js: '0.8' + env: + - TASK=test + - NPM_LEGACY=true + - node_js: '0.10' + env: + - TASK=test + - NPM_LEGACY=true + - node_js: '0.11' + env: + - TASK=test + - NPM_LEGACY=true + - node_js: '0.12' + env: + - TASK=test + - NPM_LEGACY=true + - node_js: 1 + env: + - TASK=test + - NPM_LEGACY=true + - node_js: 2 + env: + - TASK=test + - NPM_LEGACY=true + - node_js: 3 + env: + - TASK=test + - NPM_LEGACY=true + - node_js: 4 + env: TASK=test + - node_js: 5 + env: TASK=test + - node_js: 6 + env: TASK=test + - node_js: 7 + env: TASK=test + - node_js: 8 + env: TASK=test + - node_js: 9 + env: TASK=test diff --git a/backend/node_modules/string_decoder/LICENSE b/backend/node_modules/string_decoder/LICENSE new file mode 100644 index 0000000..778edb2 --- /dev/null +++ b/backend/node_modules/string_decoder/LICENSE @@ -0,0 +1,48 @@ +Node.js is licensed for use as follows: + +""" +Copyright Node.js contributors. All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to +deal in the Software without restriction, including without limitation the +rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +sell copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +IN THE SOFTWARE. +""" + +This license applies to parts of Node.js originating from the +https://github.com/joyent/node repository: + +""" +Copyright Joyent, Inc. and other Node contributors. All rights reserved. +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to +deal in the Software without restriction, including without limitation the +rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +sell copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +IN THE SOFTWARE. +""" + diff --git a/backend/node_modules/string_decoder/README.md b/backend/node_modules/string_decoder/README.md new file mode 100644 index 0000000..5fd5831 --- /dev/null +++ b/backend/node_modules/string_decoder/README.md @@ -0,0 +1,47 @@ +# string_decoder + +***Node-core v8.9.4 string_decoder for userland*** + + +[![NPM](https://nodei.co/npm/string_decoder.png?downloads=true&downloadRank=true)](https://nodei.co/npm/string_decoder/) +[![NPM](https://nodei.co/npm-dl/string_decoder.png?&months=6&height=3)](https://nodei.co/npm/string_decoder/) + + +```bash +npm install --save string_decoder +``` + +***Node-core string_decoder for userland*** + +This package is a mirror of the string_decoder implementation in Node-core. + +Full documentation may be found on the [Node.js website](https://nodejs.org/dist/v8.9.4/docs/api/). + +As of version 1.0.0 **string_decoder** uses semantic versioning. + +## Previous versions + +Previous version numbers match the versions found in Node core, e.g. 0.10.24 matches Node 0.10.24, likewise 0.11.10 matches Node 0.11.10. + +## Update + +The *build/* directory contains a build script that will scrape the source from the [nodejs/node](https://github.com/nodejs/node) repo given a specific Node version. + +## Streams Working Group + +`string_decoder` is maintained by the Streams Working Group, which +oversees the development and maintenance of the Streams API within +Node.js. The responsibilities of the Streams Working Group include: + +* Addressing stream issues on the Node.js issue tracker. +* Authoring and editing stream documentation within the Node.js project. +* Reviewing changes to stream subclasses within the Node.js project. +* Redirecting changes to streams from the Node.js project to this + project. +* Assisting in the implementation of stream providers within Node.js. +* Recommending versions of `readable-stream` to be included in Node.js. +* Messaging about the future of streams to give the community advance + notice of changes. + +See [readable-stream](https://github.com/nodejs/readable-stream) for +more details. diff --git a/backend/node_modules/string_decoder/lib/string_decoder.js b/backend/node_modules/string_decoder/lib/string_decoder.js new file mode 100644 index 0000000..2e89e63 --- /dev/null +++ b/backend/node_modules/string_decoder/lib/string_decoder.js @@ -0,0 +1,296 @@ +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +'use strict'; + +/**/ + +var Buffer = require('safe-buffer').Buffer; +/**/ + +var isEncoding = Buffer.isEncoding || function (encoding) { + encoding = '' + encoding; + switch (encoding && encoding.toLowerCase()) { + case 'hex':case 'utf8':case 'utf-8':case 'ascii':case 'binary':case 'base64':case 'ucs2':case 'ucs-2':case 'utf16le':case 'utf-16le':case 'raw': + return true; + default: + return false; + } +}; + +function _normalizeEncoding(enc) { + if (!enc) return 'utf8'; + var retried; + while (true) { + switch (enc) { + case 'utf8': + case 'utf-8': + return 'utf8'; + case 'ucs2': + case 'ucs-2': + case 'utf16le': + case 'utf-16le': + return 'utf16le'; + case 'latin1': + case 'binary': + return 'latin1'; + case 'base64': + case 'ascii': + case 'hex': + return enc; + default: + if (retried) return; // undefined + enc = ('' + enc).toLowerCase(); + retried = true; + } + } +}; + +// Do not cache `Buffer.isEncoding` when checking encoding names as some +// modules monkey-patch it to support additional encodings +function normalizeEncoding(enc) { + var nenc = _normalizeEncoding(enc); + if (typeof nenc !== 'string' && (Buffer.isEncoding === isEncoding || !isEncoding(enc))) throw new Error('Unknown encoding: ' + enc); + return nenc || enc; +} + +// StringDecoder provides an interface for efficiently splitting a series of +// buffers into a series of JS strings without breaking apart multi-byte +// characters. +exports.StringDecoder = StringDecoder; +function StringDecoder(encoding) { + this.encoding = normalizeEncoding(encoding); + var nb; + switch (this.encoding) { + case 'utf16le': + this.text = utf16Text; + this.end = utf16End; + nb = 4; + break; + case 'utf8': + this.fillLast = utf8FillLast; + nb = 4; + break; + case 'base64': + this.text = base64Text; + this.end = base64End; + nb = 3; + break; + default: + this.write = simpleWrite; + this.end = simpleEnd; + return; + } + this.lastNeed = 0; + this.lastTotal = 0; + this.lastChar = Buffer.allocUnsafe(nb); +} + +StringDecoder.prototype.write = function (buf) { + if (buf.length === 0) return ''; + var r; + var i; + if (this.lastNeed) { + r = this.fillLast(buf); + if (r === undefined) return ''; + i = this.lastNeed; + this.lastNeed = 0; + } else { + i = 0; + } + if (i < buf.length) return r ? r + this.text(buf, i) : this.text(buf, i); + return r || ''; +}; + +StringDecoder.prototype.end = utf8End; + +// Returns only complete characters in a Buffer +StringDecoder.prototype.text = utf8Text; + +// Attempts to complete a partial non-UTF-8 character using bytes from a Buffer +StringDecoder.prototype.fillLast = function (buf) { + if (this.lastNeed <= buf.length) { + buf.copy(this.lastChar, this.lastTotal - this.lastNeed, 0, this.lastNeed); + return this.lastChar.toString(this.encoding, 0, this.lastTotal); + } + buf.copy(this.lastChar, this.lastTotal - this.lastNeed, 0, buf.length); + this.lastNeed -= buf.length; +}; + +// Checks the type of a UTF-8 byte, whether it's ASCII, a leading byte, or a +// continuation byte. If an invalid byte is detected, -2 is returned. +function utf8CheckByte(byte) { + if (byte <= 0x7F) return 0;else if (byte >> 5 === 0x06) return 2;else if (byte >> 4 === 0x0E) return 3;else if (byte >> 3 === 0x1E) return 4; + return byte >> 6 === 0x02 ? -1 : -2; +} + +// Checks at most 3 bytes at the end of a Buffer in order to detect an +// incomplete multi-byte UTF-8 character. The total number of bytes (2, 3, or 4) +// needed to complete the UTF-8 character (if applicable) are returned. +function utf8CheckIncomplete(self, buf, i) { + var j = buf.length - 1; + if (j < i) return 0; + var nb = utf8CheckByte(buf[j]); + if (nb >= 0) { + if (nb > 0) self.lastNeed = nb - 1; + return nb; + } + if (--j < i || nb === -2) return 0; + nb = utf8CheckByte(buf[j]); + if (nb >= 0) { + if (nb > 0) self.lastNeed = nb - 2; + return nb; + } + if (--j < i || nb === -2) return 0; + nb = utf8CheckByte(buf[j]); + if (nb >= 0) { + if (nb > 0) { + if (nb === 2) nb = 0;else self.lastNeed = nb - 3; + } + return nb; + } + return 0; +} + +// Validates as many continuation bytes for a multi-byte UTF-8 character as +// needed or are available. If we see a non-continuation byte where we expect +// one, we "replace" the validated continuation bytes we've seen so far with +// a single UTF-8 replacement character ('\ufffd'), to match v8's UTF-8 decoding +// behavior. The continuation byte check is included three times in the case +// where all of the continuation bytes for a character exist in the same buffer. +// It is also done this way as a slight performance increase instead of using a +// loop. +function utf8CheckExtraBytes(self, buf, p) { + if ((buf[0] & 0xC0) !== 0x80) { + self.lastNeed = 0; + return '\ufffd'; + } + if (self.lastNeed > 1 && buf.length > 1) { + if ((buf[1] & 0xC0) !== 0x80) { + self.lastNeed = 1; + return '\ufffd'; + } + if (self.lastNeed > 2 && buf.length > 2) { + if ((buf[2] & 0xC0) !== 0x80) { + self.lastNeed = 2; + return '\ufffd'; + } + } + } +} + +// Attempts to complete a multi-byte UTF-8 character using bytes from a Buffer. +function utf8FillLast(buf) { + var p = this.lastTotal - this.lastNeed; + var r = utf8CheckExtraBytes(this, buf, p); + if (r !== undefined) return r; + if (this.lastNeed <= buf.length) { + buf.copy(this.lastChar, p, 0, this.lastNeed); + return this.lastChar.toString(this.encoding, 0, this.lastTotal); + } + buf.copy(this.lastChar, p, 0, buf.length); + this.lastNeed -= buf.length; +} + +// Returns all complete UTF-8 characters in a Buffer. If the Buffer ended on a +// partial character, the character's bytes are buffered until the required +// number of bytes are available. +function utf8Text(buf, i) { + var total = utf8CheckIncomplete(this, buf, i); + if (!this.lastNeed) return buf.toString('utf8', i); + this.lastTotal = total; + var end = buf.length - (total - this.lastNeed); + buf.copy(this.lastChar, 0, end); + return buf.toString('utf8', i, end); +} + +// For UTF-8, a replacement character is added when ending on a partial +// character. +function utf8End(buf) { + var r = buf && buf.length ? this.write(buf) : ''; + if (this.lastNeed) return r + '\ufffd'; + return r; +} + +// UTF-16LE typically needs two bytes per character, but even if we have an even +// number of bytes available, we need to check if we end on a leading/high +// surrogate. In that case, we need to wait for the next two bytes in order to +// decode the last character properly. +function utf16Text(buf, i) { + if ((buf.length - i) % 2 === 0) { + var r = buf.toString('utf16le', i); + if (r) { + var c = r.charCodeAt(r.length - 1); + if (c >= 0xD800 && c <= 0xDBFF) { + this.lastNeed = 2; + this.lastTotal = 4; + this.lastChar[0] = buf[buf.length - 2]; + this.lastChar[1] = buf[buf.length - 1]; + return r.slice(0, -1); + } + } + return r; + } + this.lastNeed = 1; + this.lastTotal = 2; + this.lastChar[0] = buf[buf.length - 1]; + return buf.toString('utf16le', i, buf.length - 1); +} + +// For UTF-16LE we do not explicitly append special replacement characters if we +// end on a partial character, we simply let v8 handle that. +function utf16End(buf) { + var r = buf && buf.length ? this.write(buf) : ''; + if (this.lastNeed) { + var end = this.lastTotal - this.lastNeed; + return r + this.lastChar.toString('utf16le', 0, end); + } + return r; +} + +function base64Text(buf, i) { + var n = (buf.length - i) % 3; + if (n === 0) return buf.toString('base64', i); + this.lastNeed = 3 - n; + this.lastTotal = 3; + if (n === 1) { + this.lastChar[0] = buf[buf.length - 1]; + } else { + this.lastChar[0] = buf[buf.length - 2]; + this.lastChar[1] = buf[buf.length - 1]; + } + return buf.toString('base64', i, buf.length - n); +} + +function base64End(buf) { + var r = buf && buf.length ? this.write(buf) : ''; + if (this.lastNeed) return r + this.lastChar.toString('base64', 0, 3 - this.lastNeed); + return r; +} + +// Pass bytes on through for single-byte encodings (e.g. ascii, latin1, hex) +function simpleWrite(buf) { + return buf.toString(this.encoding); +} + +function simpleEnd(buf) { + return buf && buf.length ? this.write(buf) : ''; +} \ No newline at end of file diff --git a/backend/node_modules/string_decoder/node_modules/safe-buffer/LICENSE b/backend/node_modules/string_decoder/node_modules/safe-buffer/LICENSE new file mode 100644 index 0000000..0c068ce --- /dev/null +++ b/backend/node_modules/string_decoder/node_modules/safe-buffer/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) Feross Aboukhadijeh + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/backend/node_modules/string_decoder/node_modules/safe-buffer/README.md b/backend/node_modules/string_decoder/node_modules/safe-buffer/README.md new file mode 100644 index 0000000..e9a81af --- /dev/null +++ b/backend/node_modules/string_decoder/node_modules/safe-buffer/README.md @@ -0,0 +1,584 @@ +# safe-buffer [![travis][travis-image]][travis-url] [![npm][npm-image]][npm-url] [![downloads][downloads-image]][downloads-url] [![javascript style guide][standard-image]][standard-url] + +[travis-image]: https://img.shields.io/travis/feross/safe-buffer/master.svg +[travis-url]: https://travis-ci.org/feross/safe-buffer +[npm-image]: https://img.shields.io/npm/v/safe-buffer.svg +[npm-url]: https://npmjs.org/package/safe-buffer +[downloads-image]: https://img.shields.io/npm/dm/safe-buffer.svg +[downloads-url]: https://npmjs.org/package/safe-buffer +[standard-image]: https://img.shields.io/badge/code_style-standard-brightgreen.svg +[standard-url]: https://standardjs.com + +#### Safer Node.js Buffer API + +**Use the new Node.js Buffer APIs (`Buffer.from`, `Buffer.alloc`, +`Buffer.allocUnsafe`, `Buffer.allocUnsafeSlow`) in all versions of Node.js.** + +**Uses the built-in implementation when available.** + +## install + +``` +npm install safe-buffer +``` + +## usage + +The goal of this package is to provide a safe replacement for the node.js `Buffer`. + +It's a drop-in replacement for `Buffer`. You can use it by adding one `require` line to +the top of your node.js modules: + +```js +var Buffer = require('safe-buffer').Buffer + +// Existing buffer code will continue to work without issues: + +new Buffer('hey', 'utf8') +new Buffer([1, 2, 3], 'utf8') +new Buffer(obj) +new Buffer(16) // create an uninitialized buffer (potentially unsafe) + +// But you can use these new explicit APIs to make clear what you want: + +Buffer.from('hey', 'utf8') // convert from many types to a Buffer +Buffer.alloc(16) // create a zero-filled buffer (safe) +Buffer.allocUnsafe(16) // create an uninitialized buffer (potentially unsafe) +``` + +## api + +### Class Method: Buffer.from(array) + + +* `array` {Array} + +Allocates a new `Buffer` using an `array` of octets. + +```js +const buf = Buffer.from([0x62,0x75,0x66,0x66,0x65,0x72]); + // creates a new Buffer containing ASCII bytes + // ['b','u','f','f','e','r'] +``` + +A `TypeError` will be thrown if `array` is not an `Array`. + +### Class Method: Buffer.from(arrayBuffer[, byteOffset[, length]]) + + +* `arrayBuffer` {ArrayBuffer} The `.buffer` property of a `TypedArray` or + a `new ArrayBuffer()` +* `byteOffset` {Number} Default: `0` +* `length` {Number} Default: `arrayBuffer.length - byteOffset` + +When passed a reference to the `.buffer` property of a `TypedArray` instance, +the newly created `Buffer` will share the same allocated memory as the +TypedArray. + +```js +const arr = new Uint16Array(2); +arr[0] = 5000; +arr[1] = 4000; + +const buf = Buffer.from(arr.buffer); // shares the memory with arr; + +console.log(buf); + // Prints: + +// changing the TypedArray changes the Buffer also +arr[1] = 6000; + +console.log(buf); + // Prints: +``` + +The optional `byteOffset` and `length` arguments specify a memory range within +the `arrayBuffer` that will be shared by the `Buffer`. + +```js +const ab = new ArrayBuffer(10); +const buf = Buffer.from(ab, 0, 2); +console.log(buf.length); + // Prints: 2 +``` + +A `TypeError` will be thrown if `arrayBuffer` is not an `ArrayBuffer`. + +### Class Method: Buffer.from(buffer) + + +* `buffer` {Buffer} + +Copies the passed `buffer` data onto a new `Buffer` instance. + +```js +const buf1 = Buffer.from('buffer'); +const buf2 = Buffer.from(buf1); + +buf1[0] = 0x61; +console.log(buf1.toString()); + // 'auffer' +console.log(buf2.toString()); + // 'buffer' (copy is not changed) +``` + +A `TypeError` will be thrown if `buffer` is not a `Buffer`. + +### Class Method: Buffer.from(str[, encoding]) + + +* `str` {String} String to encode. +* `encoding` {String} Encoding to use, Default: `'utf8'` + +Creates a new `Buffer` containing the given JavaScript string `str`. If +provided, the `encoding` parameter identifies the character encoding. +If not provided, `encoding` defaults to `'utf8'`. + +```js +const buf1 = Buffer.from('this is a tést'); +console.log(buf1.toString()); + // prints: this is a tést +console.log(buf1.toString('ascii')); + // prints: this is a tC)st + +const buf2 = Buffer.from('7468697320697320612074c3a97374', 'hex'); +console.log(buf2.toString()); + // prints: this is a tést +``` + +A `TypeError` will be thrown if `str` is not a string. + +### Class Method: Buffer.alloc(size[, fill[, encoding]]) + + +* `size` {Number} +* `fill` {Value} Default: `undefined` +* `encoding` {String} Default: `utf8` + +Allocates a new `Buffer` of `size` bytes. If `fill` is `undefined`, the +`Buffer` will be *zero-filled*. + +```js +const buf = Buffer.alloc(5); +console.log(buf); + // +``` + +The `size` must be less than or equal to the value of +`require('buffer').kMaxLength` (on 64-bit architectures, `kMaxLength` is +`(2^31)-1`). Otherwise, a [`RangeError`][] is thrown. A zero-length Buffer will +be created if a `size` less than or equal to 0 is specified. + +If `fill` is specified, the allocated `Buffer` will be initialized by calling +`buf.fill(fill)`. See [`buf.fill()`][] for more information. + +```js +const buf = Buffer.alloc(5, 'a'); +console.log(buf); + // +``` + +If both `fill` and `encoding` are specified, the allocated `Buffer` will be +initialized by calling `buf.fill(fill, encoding)`. For example: + +```js +const buf = Buffer.alloc(11, 'aGVsbG8gd29ybGQ=', 'base64'); +console.log(buf); + // +``` + +Calling `Buffer.alloc(size)` can be significantly slower than the alternative +`Buffer.allocUnsafe(size)` but ensures that the newly created `Buffer` instance +contents will *never contain sensitive data*. + +A `TypeError` will be thrown if `size` is not a number. + +### Class Method: Buffer.allocUnsafe(size) + + +* `size` {Number} + +Allocates a new *non-zero-filled* `Buffer` of `size` bytes. The `size` must +be less than or equal to the value of `require('buffer').kMaxLength` (on 64-bit +architectures, `kMaxLength` is `(2^31)-1`). Otherwise, a [`RangeError`][] is +thrown. A zero-length Buffer will be created if a `size` less than or equal to +0 is specified. + +The underlying memory for `Buffer` instances created in this way is *not +initialized*. The contents of the newly created `Buffer` are unknown and +*may contain sensitive data*. Use [`buf.fill(0)`][] to initialize such +`Buffer` instances to zeroes. + +```js +const buf = Buffer.allocUnsafe(5); +console.log(buf); + // + // (octets will be different, every time) +buf.fill(0); +console.log(buf); + // +``` + +A `TypeError` will be thrown if `size` is not a number. + +Note that the `Buffer` module pre-allocates an internal `Buffer` instance of +size `Buffer.poolSize` that is used as a pool for the fast allocation of new +`Buffer` instances created using `Buffer.allocUnsafe(size)` (and the deprecated +`new Buffer(size)` constructor) only when `size` is less than or equal to +`Buffer.poolSize >> 1` (floor of `Buffer.poolSize` divided by two). The default +value of `Buffer.poolSize` is `8192` but can be modified. + +Use of this pre-allocated internal memory pool is a key difference between +calling `Buffer.alloc(size, fill)` vs. `Buffer.allocUnsafe(size).fill(fill)`. +Specifically, `Buffer.alloc(size, fill)` will *never* use the internal Buffer +pool, while `Buffer.allocUnsafe(size).fill(fill)` *will* use the internal +Buffer pool if `size` is less than or equal to half `Buffer.poolSize`. The +difference is subtle but can be important when an application requires the +additional performance that `Buffer.allocUnsafe(size)` provides. + +### Class Method: Buffer.allocUnsafeSlow(size) + + +* `size` {Number} + +Allocates a new *non-zero-filled* and non-pooled `Buffer` of `size` bytes. The +`size` must be less than or equal to the value of +`require('buffer').kMaxLength` (on 64-bit architectures, `kMaxLength` is +`(2^31)-1`). Otherwise, a [`RangeError`][] is thrown. A zero-length Buffer will +be created if a `size` less than or equal to 0 is specified. + +The underlying memory for `Buffer` instances created in this way is *not +initialized*. The contents of the newly created `Buffer` are unknown and +*may contain sensitive data*. Use [`buf.fill(0)`][] to initialize such +`Buffer` instances to zeroes. + +When using `Buffer.allocUnsafe()` to allocate new `Buffer` instances, +allocations under 4KB are, by default, sliced from a single pre-allocated +`Buffer`. This allows applications to avoid the garbage collection overhead of +creating many individually allocated Buffers. This approach improves both +performance and memory usage by eliminating the need to track and cleanup as +many `Persistent` objects. + +However, in the case where a developer may need to retain a small chunk of +memory from a pool for an indeterminate amount of time, it may be appropriate +to create an un-pooled Buffer instance using `Buffer.allocUnsafeSlow()` then +copy out the relevant bits. + +```js +// need to keep around a few small chunks of memory +const store = []; + +socket.on('readable', () => { + const data = socket.read(); + // allocate for retained data + const sb = Buffer.allocUnsafeSlow(10); + // copy the data into the new allocation + data.copy(sb, 0, 0, 10); + store.push(sb); +}); +``` + +Use of `Buffer.allocUnsafeSlow()` should be used only as a last resort *after* +a developer has observed undue memory retention in their applications. + +A `TypeError` will be thrown if `size` is not a number. + +### All the Rest + +The rest of the `Buffer` API is exactly the same as in node.js. +[See the docs](https://nodejs.org/api/buffer.html). + + +## Related links + +- [Node.js issue: Buffer(number) is unsafe](https://github.com/nodejs/node/issues/4660) +- [Node.js Enhancement Proposal: Buffer.from/Buffer.alloc/Buffer.zalloc/Buffer() soft-deprecate](https://github.com/nodejs/node-eps/pull/4) + +## Why is `Buffer` unsafe? + +Today, the node.js `Buffer` constructor is overloaded to handle many different argument +types like `String`, `Array`, `Object`, `TypedArrayView` (`Uint8Array`, etc.), +`ArrayBuffer`, and also `Number`. + +The API is optimized for convenience: you can throw any type at it, and it will try to do +what you want. + +Because the Buffer constructor is so powerful, you often see code like this: + +```js +// Convert UTF-8 strings to hex +function toHex (str) { + return new Buffer(str).toString('hex') +} +``` + +***But what happens if `toHex` is called with a `Number` argument?*** + +### Remote Memory Disclosure + +If an attacker can make your program call the `Buffer` constructor with a `Number` +argument, then they can make it allocate uninitialized memory from the node.js process. +This could potentially disclose TLS private keys, user data, or database passwords. + +When the `Buffer` constructor is passed a `Number` argument, it returns an +**UNINITIALIZED** block of memory of the specified `size`. When you create a `Buffer` like +this, you **MUST** overwrite the contents before returning it to the user. + +From the [node.js docs](https://nodejs.org/api/buffer.html#buffer_new_buffer_size): + +> `new Buffer(size)` +> +> - `size` Number +> +> The underlying memory for `Buffer` instances created in this way is not initialized. +> **The contents of a newly created `Buffer` are unknown and could contain sensitive +> data.** Use `buf.fill(0)` to initialize a Buffer to zeroes. + +(Emphasis our own.) + +Whenever the programmer intended to create an uninitialized `Buffer` you often see code +like this: + +```js +var buf = new Buffer(16) + +// Immediately overwrite the uninitialized buffer with data from another buffer +for (var i = 0; i < buf.length; i++) { + buf[i] = otherBuf[i] +} +``` + + +### Would this ever be a problem in real code? + +Yes. It's surprisingly common to forget to check the type of your variables in a +dynamically-typed language like JavaScript. + +Usually the consequences of assuming the wrong type is that your program crashes with an +uncaught exception. But the failure mode for forgetting to check the type of arguments to +the `Buffer` constructor is more catastrophic. + +Here's an example of a vulnerable service that takes a JSON payload and converts it to +hex: + +```js +// Take a JSON payload {str: "some string"} and convert it to hex +var server = http.createServer(function (req, res) { + var data = '' + req.setEncoding('utf8') + req.on('data', function (chunk) { + data += chunk + }) + req.on('end', function () { + var body = JSON.parse(data) + res.end(new Buffer(body.str).toString('hex')) + }) +}) + +server.listen(8080) +``` + +In this example, an http client just has to send: + +```json +{ + "str": 1000 +} +``` + +and it will get back 1,000 bytes of uninitialized memory from the server. + +This is a very serious bug. It's similar in severity to the +[the Heartbleed bug](http://heartbleed.com/) that allowed disclosure of OpenSSL process +memory by remote attackers. + + +### Which real-world packages were vulnerable? + +#### [`bittorrent-dht`](https://www.npmjs.com/package/bittorrent-dht) + +[Mathias Buus](https://github.com/mafintosh) and I +([Feross Aboukhadijeh](http://feross.org/)) found this issue in one of our own packages, +[`bittorrent-dht`](https://www.npmjs.com/package/bittorrent-dht). The bug would allow +anyone on the internet to send a series of messages to a user of `bittorrent-dht` and get +them to reveal 20 bytes at a time of uninitialized memory from the node.js process. + +Here's +[the commit](https://github.com/feross/bittorrent-dht/commit/6c7da04025d5633699800a99ec3fbadf70ad35b8) +that fixed it. We released a new fixed version, created a +[Node Security Project disclosure](https://nodesecurity.io/advisories/68), and deprecated all +vulnerable versions on npm so users will get a warning to upgrade to a newer version. + +#### [`ws`](https://www.npmjs.com/package/ws) + +That got us wondering if there were other vulnerable packages. Sure enough, within a short +period of time, we found the same issue in [`ws`](https://www.npmjs.com/package/ws), the +most popular WebSocket implementation in node.js. + +If certain APIs were called with `Number` parameters instead of `String` or `Buffer` as +expected, then uninitialized server memory would be disclosed to the remote peer. + +These were the vulnerable methods: + +```js +socket.send(number) +socket.ping(number) +socket.pong(number) +``` + +Here's a vulnerable socket server with some echo functionality: + +```js +server.on('connection', function (socket) { + socket.on('message', function (message) { + message = JSON.parse(message) + if (message.type === 'echo') { + socket.send(message.data) // send back the user's message + } + }) +}) +``` + +`socket.send(number)` called on the server, will disclose server memory. + +Here's [the release](https://github.com/websockets/ws/releases/tag/1.0.1) where the issue +was fixed, with a more detailed explanation. Props to +[Arnout Kazemier](https://github.com/3rd-Eden) for the quick fix. Here's the +[Node Security Project disclosure](https://nodesecurity.io/advisories/67). + + +### What's the solution? + +It's important that node.js offers a fast way to get memory otherwise performance-critical +applications would needlessly get a lot slower. + +But we need a better way to *signal our intent* as programmers. **When we want +uninitialized memory, we should request it explicitly.** + +Sensitive functionality should not be packed into a developer-friendly API that loosely +accepts many different types. This type of API encourages the lazy practice of passing +variables in without checking the type very carefully. + +#### A new API: `Buffer.allocUnsafe(number)` + +The functionality of creating buffers with uninitialized memory should be part of another +API. We propose `Buffer.allocUnsafe(number)`. This way, it's not part of an API that +frequently gets user input of all sorts of different types passed into it. + +```js +var buf = Buffer.allocUnsafe(16) // careful, uninitialized memory! + +// Immediately overwrite the uninitialized buffer with data from another buffer +for (var i = 0; i < buf.length; i++) { + buf[i] = otherBuf[i] +} +``` + + +### How do we fix node.js core? + +We sent [a PR to node.js core](https://github.com/nodejs/node/pull/4514) (merged as +`semver-major`) which defends against one case: + +```js +var str = 16 +new Buffer(str, 'utf8') +``` + +In this situation, it's implied that the programmer intended the first argument to be a +string, since they passed an encoding as a second argument. Today, node.js will allocate +uninitialized memory in the case of `new Buffer(number, encoding)`, which is probably not +what the programmer intended. + +But this is only a partial solution, since if the programmer does `new Buffer(variable)` +(without an `encoding` parameter) there's no way to know what they intended. If `variable` +is sometimes a number, then uninitialized memory will sometimes be returned. + +### What's the real long-term fix? + +We could deprecate and remove `new Buffer(number)` and use `Buffer.allocUnsafe(number)` when +we need uninitialized memory. But that would break 1000s of packages. + +~~We believe the best solution is to:~~ + +~~1. Change `new Buffer(number)` to return safe, zeroed-out memory~~ + +~~2. Create a new API for creating uninitialized Buffers. We propose: `Buffer.allocUnsafe(number)`~~ + +#### Update + +We now support adding three new APIs: + +- `Buffer.from(value)` - convert from any type to a buffer +- `Buffer.alloc(size)` - create a zero-filled buffer +- `Buffer.allocUnsafe(size)` - create an uninitialized buffer with given size + +This solves the core problem that affected `ws` and `bittorrent-dht` which is +`Buffer(variable)` getting tricked into taking a number argument. + +This way, existing code continues working and the impact on the npm ecosystem will be +minimal. Over time, npm maintainers can migrate performance-critical code to use +`Buffer.allocUnsafe(number)` instead of `new Buffer(number)`. + + +### Conclusion + +We think there's a serious design issue with the `Buffer` API as it exists today. It +promotes insecure software by putting high-risk functionality into a convenient API +with friendly "developer ergonomics". + +This wasn't merely a theoretical exercise because we found the issue in some of the +most popular npm packages. + +Fortunately, there's an easy fix that can be applied today. Use `safe-buffer` in place of +`buffer`. + +```js +var Buffer = require('safe-buffer').Buffer +``` + +Eventually, we hope that node.js core can switch to this new, safer behavior. We believe +the impact on the ecosystem would be minimal since it's not a breaking change. +Well-maintained, popular packages would be updated to use `Buffer.alloc` quickly, while +older, insecure packages would magically become safe from this attack vector. + + +## links + +- [Node.js PR: buffer: throw if both length and enc are passed](https://github.com/nodejs/node/pull/4514) +- [Node Security Project disclosure for `ws`](https://nodesecurity.io/advisories/67) +- [Node Security Project disclosure for`bittorrent-dht`](https://nodesecurity.io/advisories/68) + + +## credit + +The original issues in `bittorrent-dht` +([disclosure](https://nodesecurity.io/advisories/68)) and +`ws` ([disclosure](https://nodesecurity.io/advisories/67)) were discovered by +[Mathias Buus](https://github.com/mafintosh) and +[Feross Aboukhadijeh](http://feross.org/). + +Thanks to [Adam Baldwin](https://github.com/evilpacket) for helping disclose these issues +and for his work running the [Node Security Project](https://nodesecurity.io/). + +Thanks to [John Hiesey](https://github.com/jhiesey) for proofreading this README and +auditing the code. + + +## license + +MIT. Copyright (C) [Feross Aboukhadijeh](http://feross.org) diff --git a/backend/node_modules/string_decoder/node_modules/safe-buffer/index.d.ts b/backend/node_modules/string_decoder/node_modules/safe-buffer/index.d.ts new file mode 100644 index 0000000..e9fed80 --- /dev/null +++ b/backend/node_modules/string_decoder/node_modules/safe-buffer/index.d.ts @@ -0,0 +1,187 @@ +declare module "safe-buffer" { + export class Buffer { + length: number + write(string: string, offset?: number, length?: number, encoding?: string): number; + toString(encoding?: string, start?: number, end?: number): string; + toJSON(): { type: 'Buffer', data: any[] }; + equals(otherBuffer: Buffer): boolean; + compare(otherBuffer: Buffer, targetStart?: number, targetEnd?: number, sourceStart?: number, sourceEnd?: number): number; + copy(targetBuffer: Buffer, targetStart?: number, sourceStart?: number, sourceEnd?: number): number; + slice(start?: number, end?: number): Buffer; + writeUIntLE(value: number, offset: number, byteLength: number, noAssert?: boolean): number; + writeUIntBE(value: number, offset: number, byteLength: number, noAssert?: boolean): number; + writeIntLE(value: number, offset: number, byteLength: number, noAssert?: boolean): number; + writeIntBE(value: number, offset: number, byteLength: number, noAssert?: boolean): number; + readUIntLE(offset: number, byteLength: number, noAssert?: boolean): number; + readUIntBE(offset: number, byteLength: number, noAssert?: boolean): number; + readIntLE(offset: number, byteLength: number, noAssert?: boolean): number; + readIntBE(offset: number, byteLength: number, noAssert?: boolean): number; + readUInt8(offset: number, noAssert?: boolean): number; + readUInt16LE(offset: number, noAssert?: boolean): number; + readUInt16BE(offset: number, noAssert?: boolean): number; + readUInt32LE(offset: number, noAssert?: boolean): number; + readUInt32BE(offset: number, noAssert?: boolean): number; + readInt8(offset: number, noAssert?: boolean): number; + readInt16LE(offset: number, noAssert?: boolean): number; + readInt16BE(offset: number, noAssert?: boolean): number; + readInt32LE(offset: number, noAssert?: boolean): number; + readInt32BE(offset: number, noAssert?: boolean): number; + readFloatLE(offset: number, noAssert?: boolean): number; + readFloatBE(offset: number, noAssert?: boolean): number; + readDoubleLE(offset: number, noAssert?: boolean): number; + readDoubleBE(offset: number, noAssert?: boolean): number; + swap16(): Buffer; + swap32(): Buffer; + swap64(): Buffer; + writeUInt8(value: number, offset: number, noAssert?: boolean): number; + writeUInt16LE(value: number, offset: number, noAssert?: boolean): number; + writeUInt16BE(value: number, offset: number, noAssert?: boolean): number; + writeUInt32LE(value: number, offset: number, noAssert?: boolean): number; + writeUInt32BE(value: number, offset: number, noAssert?: boolean): number; + writeInt8(value: number, offset: number, noAssert?: boolean): number; + writeInt16LE(value: number, offset: number, noAssert?: boolean): number; + writeInt16BE(value: number, offset: number, noAssert?: boolean): number; + writeInt32LE(value: number, offset: number, noAssert?: boolean): number; + writeInt32BE(value: number, offset: number, noAssert?: boolean): number; + writeFloatLE(value: number, offset: number, noAssert?: boolean): number; + writeFloatBE(value: number, offset: number, noAssert?: boolean): number; + writeDoubleLE(value: number, offset: number, noAssert?: boolean): number; + writeDoubleBE(value: number, offset: number, noAssert?: boolean): number; + fill(value: any, offset?: number, end?: number): this; + indexOf(value: string | number | Buffer, byteOffset?: number, encoding?: string): number; + lastIndexOf(value: string | number | Buffer, byteOffset?: number, encoding?: string): number; + includes(value: string | number | Buffer, byteOffset?: number, encoding?: string): boolean; + + /** + * Allocates a new buffer containing the given {str}. + * + * @param str String to store in buffer. + * @param encoding encoding to use, optional. Default is 'utf8' + */ + constructor (str: string, encoding?: string); + /** + * Allocates a new buffer of {size} octets. + * + * @param size count of octets to allocate. + */ + constructor (size: number); + /** + * Allocates a new buffer containing the given {array} of octets. + * + * @param array The octets to store. + */ + constructor (array: Uint8Array); + /** + * Produces a Buffer backed by the same allocated memory as + * the given {ArrayBuffer}. + * + * + * @param arrayBuffer The ArrayBuffer with which to share memory. + */ + constructor (arrayBuffer: ArrayBuffer); + /** + * Allocates a new buffer containing the given {array} of octets. + * + * @param array The octets to store. + */ + constructor (array: any[]); + /** + * Copies the passed {buffer} data onto a new {Buffer} instance. + * + * @param buffer The buffer to copy. + */ + constructor (buffer: Buffer); + prototype: Buffer; + /** + * Allocates a new Buffer using an {array} of octets. + * + * @param array + */ + static from(array: any[]): Buffer; + /** + * When passed a reference to the .buffer property of a TypedArray instance, + * the newly created Buffer will share the same allocated memory as the TypedArray. + * The optional {byteOffset} and {length} arguments specify a memory range + * within the {arrayBuffer} that will be shared by the Buffer. + * + * @param arrayBuffer The .buffer property of a TypedArray or a new ArrayBuffer() + * @param byteOffset + * @param length + */ + static from(arrayBuffer: ArrayBuffer, byteOffset?: number, length?: number): Buffer; + /** + * Copies the passed {buffer} data onto a new Buffer instance. + * + * @param buffer + */ + static from(buffer: Buffer): Buffer; + /** + * Creates a new Buffer containing the given JavaScript string {str}. + * If provided, the {encoding} parameter identifies the character encoding. + * If not provided, {encoding} defaults to 'utf8'. + * + * @param str + */ + static from(str: string, encoding?: string): Buffer; + /** + * Returns true if {obj} is a Buffer + * + * @param obj object to test. + */ + static isBuffer(obj: any): obj is Buffer; + /** + * Returns true if {encoding} is a valid encoding argument. + * Valid string encodings in Node 0.12: 'ascii'|'utf8'|'utf16le'|'ucs2'(alias of 'utf16le')|'base64'|'binary'(deprecated)|'hex' + * + * @param encoding string to test. + */ + static isEncoding(encoding: string): boolean; + /** + * Gives the actual byte length of a string. encoding defaults to 'utf8'. + * This is not the same as String.prototype.length since that returns the number of characters in a string. + * + * @param string string to test. + * @param encoding encoding used to evaluate (defaults to 'utf8') + */ + static byteLength(string: string, encoding?: string): number; + /** + * Returns a buffer which is the result of concatenating all the buffers in the list together. + * + * If the list has no items, or if the totalLength is 0, then it returns a zero-length buffer. + * If the list has exactly one item, then the first item of the list is returned. + * If the list has more than one item, then a new Buffer is created. + * + * @param list An array of Buffer objects to concatenate + * @param totalLength Total length of the buffers when concatenated. + * If totalLength is not provided, it is read from the buffers in the list. However, this adds an additional loop to the function, so it is faster to provide the length explicitly. + */ + static concat(list: Buffer[], totalLength?: number): Buffer; + /** + * The same as buf1.compare(buf2). + */ + static compare(buf1: Buffer, buf2: Buffer): number; + /** + * Allocates a new buffer of {size} octets. + * + * @param size count of octets to allocate. + * @param fill if specified, buffer will be initialized by calling buf.fill(fill). + * If parameter is omitted, buffer will be filled with zeros. + * @param encoding encoding used for call to buf.fill while initalizing + */ + static alloc(size: number, fill?: string | Buffer | number, encoding?: string): Buffer; + /** + * Allocates a new buffer of {size} octets, leaving memory not initialized, so the contents + * of the newly created Buffer are unknown and may contain sensitive data. + * + * @param size count of octets to allocate + */ + static allocUnsafe(size: number): Buffer; + /** + * Allocates a new non-pooled buffer of {size} octets, leaving memory not initialized, so the contents + * of the newly created Buffer are unknown and may contain sensitive data. + * + * @param size count of octets to allocate + */ + static allocUnsafeSlow(size: number): Buffer; + } +} \ No newline at end of file diff --git a/backend/node_modules/string_decoder/node_modules/safe-buffer/index.js b/backend/node_modules/string_decoder/node_modules/safe-buffer/index.js new file mode 100644 index 0000000..22438da --- /dev/null +++ b/backend/node_modules/string_decoder/node_modules/safe-buffer/index.js @@ -0,0 +1,62 @@ +/* eslint-disable node/no-deprecated-api */ +var buffer = require('buffer') +var Buffer = buffer.Buffer + +// alternative to using Object.keys for old browsers +function copyProps (src, dst) { + for (var key in src) { + dst[key] = src[key] + } +} +if (Buffer.from && Buffer.alloc && Buffer.allocUnsafe && Buffer.allocUnsafeSlow) { + module.exports = buffer +} else { + // Copy properties from require('buffer') + copyProps(buffer, exports) + exports.Buffer = SafeBuffer +} + +function SafeBuffer (arg, encodingOrOffset, length) { + return Buffer(arg, encodingOrOffset, length) +} + +// Copy static methods from Buffer +copyProps(Buffer, SafeBuffer) + +SafeBuffer.from = function (arg, encodingOrOffset, length) { + if (typeof arg === 'number') { + throw new TypeError('Argument must not be a number') + } + return Buffer(arg, encodingOrOffset, length) +} + +SafeBuffer.alloc = function (size, fill, encoding) { + if (typeof size !== 'number') { + throw new TypeError('Argument must be a number') + } + var buf = Buffer(size) + if (fill !== undefined) { + if (typeof encoding === 'string') { + buf.fill(fill, encoding) + } else { + buf.fill(fill) + } + } else { + buf.fill(0) + } + return buf +} + +SafeBuffer.allocUnsafe = function (size) { + if (typeof size !== 'number') { + throw new TypeError('Argument must be a number') + } + return Buffer(size) +} + +SafeBuffer.allocUnsafeSlow = function (size) { + if (typeof size !== 'number') { + throw new TypeError('Argument must be a number') + } + return buffer.SlowBuffer(size) +} diff --git a/backend/node_modules/string_decoder/node_modules/safe-buffer/package.json b/backend/node_modules/string_decoder/node_modules/safe-buffer/package.json new file mode 100644 index 0000000..623fbc3 --- /dev/null +++ b/backend/node_modules/string_decoder/node_modules/safe-buffer/package.json @@ -0,0 +1,37 @@ +{ + "name": "safe-buffer", + "description": "Safer Node.js Buffer API", + "version": "5.1.2", + "author": { + "name": "Feross Aboukhadijeh", + "email": "feross@feross.org", + "url": "http://feross.org" + }, + "bugs": { + "url": "https://github.com/feross/safe-buffer/issues" + }, + "devDependencies": { + "standard": "*", + "tape": "^4.0.0" + }, + "homepage": "https://github.com/feross/safe-buffer", + "keywords": [ + "buffer", + "buffer allocate", + "node security", + "safe", + "safe-buffer", + "security", + "uninitialized" + ], + "license": "MIT", + "main": "index.js", + "types": "index.d.ts", + "repository": { + "type": "git", + "url": "git://github.com/feross/safe-buffer.git" + }, + "scripts": { + "test": "standard && tape test/*.js" + } +} diff --git a/backend/node_modules/string_decoder/package.json b/backend/node_modules/string_decoder/package.json new file mode 100644 index 0000000..518c3eb --- /dev/null +++ b/backend/node_modules/string_decoder/package.json @@ -0,0 +1,31 @@ +{ + "name": "string_decoder", + "version": "1.1.1", + "description": "The string_decoder module from Node core", + "main": "lib/string_decoder.js", + "dependencies": { + "safe-buffer": "~5.1.0" + }, + "devDependencies": { + "babel-polyfill": "^6.23.0", + "core-util-is": "^1.0.2", + "inherits": "^2.0.3", + "tap": "~0.4.8" + }, + "scripts": { + "test": "tap test/parallel/*.js && node test/verify-dependencies", + "ci": "tap test/parallel/*.js test/ours/*.js --tap | tee test.tap && node test/verify-dependencies.js" + }, + "repository": { + "type": "git", + "url": "git://github.com/nodejs/string_decoder.git" + }, + "homepage": "https://github.com/nodejs/string_decoder", + "keywords": [ + "string", + "decoder", + "browser", + "browserify" + ], + "license": "MIT" +} diff --git a/backend/node_modules/typedarray/.travis.yml b/backend/node_modules/typedarray/.travis.yml new file mode 100644 index 0000000..cc4dba2 --- /dev/null +++ b/backend/node_modules/typedarray/.travis.yml @@ -0,0 +1,4 @@ +language: node_js +node_js: + - "0.8" + - "0.10" diff --git a/backend/node_modules/typedarray/LICENSE b/backend/node_modules/typedarray/LICENSE new file mode 100644 index 0000000..11adfae --- /dev/null +++ b/backend/node_modules/typedarray/LICENSE @@ -0,0 +1,35 @@ +/* + Copyright (c) 2010, Linden Research, Inc. + Copyright (c) 2012, Joshua Bell + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. + $/LicenseInfo$ + */ + +// Original can be found at: +// https://bitbucket.org/lindenlab/llsd +// Modifications by Joshua Bell inexorabletash@gmail.com +// https://github.com/inexorabletash/polyfill + +// ES3/ES5 implementation of the Krhonos Typed Array Specification +// Ref: http://www.khronos.org/registry/typedarray/specs/latest/ +// Date: 2011-02-01 +// +// Variations: +// * Allows typed_array.get/set() as alias for subscripts (typed_array[]) diff --git a/backend/node_modules/typedarray/example/tarray.js b/backend/node_modules/typedarray/example/tarray.js new file mode 100644 index 0000000..8423d7c --- /dev/null +++ b/backend/node_modules/typedarray/example/tarray.js @@ -0,0 +1,4 @@ +var Uint8Array = require('../').Uint8Array; +var ua = new Uint8Array(5); +ua[1] = 256 + 55; +console.log(ua[1]); diff --git a/backend/node_modules/typedarray/index.js b/backend/node_modules/typedarray/index.js new file mode 100644 index 0000000..5e54084 --- /dev/null +++ b/backend/node_modules/typedarray/index.js @@ -0,0 +1,630 @@ +var undefined = (void 0); // Paranoia + +// Beyond this value, index getters/setters (i.e. array[0], array[1]) are so slow to +// create, and consume so much memory, that the browser appears frozen. +var MAX_ARRAY_LENGTH = 1e5; + +// Approximations of internal ECMAScript conversion functions +var ECMAScript = (function() { + // Stash a copy in case other scripts modify these + var opts = Object.prototype.toString, + ophop = Object.prototype.hasOwnProperty; + + return { + // Class returns internal [[Class]] property, used to avoid cross-frame instanceof issues: + Class: function(v) { return opts.call(v).replace(/^\[object *|\]$/g, ''); }, + HasProperty: function(o, p) { return p in o; }, + HasOwnProperty: function(o, p) { return ophop.call(o, p); }, + IsCallable: function(o) { return typeof o === 'function'; }, + ToInt32: function(v) { return v >> 0; }, + ToUint32: function(v) { return v >>> 0; } + }; +}()); + +// Snapshot intrinsics +var LN2 = Math.LN2, + abs = Math.abs, + floor = Math.floor, + log = Math.log, + min = Math.min, + pow = Math.pow, + round = Math.round; + +// ES5: lock down object properties +function configureProperties(obj) { + if (getOwnPropNames && defineProp) { + var props = getOwnPropNames(obj), i; + for (i = 0; i < props.length; i += 1) { + defineProp(obj, props[i], { + value: obj[props[i]], + writable: false, + enumerable: false, + configurable: false + }); + } + } +} + +// emulate ES5 getter/setter API using legacy APIs +// http://blogs.msdn.com/b/ie/archive/2010/09/07/transitioning-existing-code-to-the-es5-getter-setter-apis.aspx +// (second clause tests for Object.defineProperty() in IE<9 that only supports extending DOM prototypes, but +// note that IE<9 does not support __defineGetter__ or __defineSetter__ so it just renders the method harmless) +var defineProp +if (Object.defineProperty && (function() { + try { + Object.defineProperty({}, 'x', {}); + return true; + } catch (e) { + return false; + } + })()) { + defineProp = Object.defineProperty; +} else { + defineProp = function(o, p, desc) { + if (!o === Object(o)) throw new TypeError("Object.defineProperty called on non-object"); + if (ECMAScript.HasProperty(desc, 'get') && Object.prototype.__defineGetter__) { Object.prototype.__defineGetter__.call(o, p, desc.get); } + if (ECMAScript.HasProperty(desc, 'set') && Object.prototype.__defineSetter__) { Object.prototype.__defineSetter__.call(o, p, desc.set); } + if (ECMAScript.HasProperty(desc, 'value')) { o[p] = desc.value; } + return o; + }; +} + +var getOwnPropNames = Object.getOwnPropertyNames || function (o) { + if (o !== Object(o)) throw new TypeError("Object.getOwnPropertyNames called on non-object"); + var props = [], p; + for (p in o) { + if (ECMAScript.HasOwnProperty(o, p)) { + props.push(p); + } + } + return props; +}; + +// ES5: Make obj[index] an alias for obj._getter(index)/obj._setter(index, value) +// for index in 0 ... obj.length +function makeArrayAccessors(obj) { + if (!defineProp) { return; } + + if (obj.length > MAX_ARRAY_LENGTH) throw new RangeError("Array too large for polyfill"); + + function makeArrayAccessor(index) { + defineProp(obj, index, { + 'get': function() { return obj._getter(index); }, + 'set': function(v) { obj._setter(index, v); }, + enumerable: true, + configurable: false + }); + } + + var i; + for (i = 0; i < obj.length; i += 1) { + makeArrayAccessor(i); + } +} + +// Internal conversion functions: +// pack() - take a number (interpreted as Type), output a byte array +// unpack() - take a byte array, output a Type-like number + +function as_signed(value, bits) { var s = 32 - bits; return (value << s) >> s; } +function as_unsigned(value, bits) { var s = 32 - bits; return (value << s) >>> s; } + +function packI8(n) { return [n & 0xff]; } +function unpackI8(bytes) { return as_signed(bytes[0], 8); } + +function packU8(n) { return [n & 0xff]; } +function unpackU8(bytes) { return as_unsigned(bytes[0], 8); } + +function packU8Clamped(n) { n = round(Number(n)); return [n < 0 ? 0 : n > 0xff ? 0xff : n & 0xff]; } + +function packI16(n) { return [(n >> 8) & 0xff, n & 0xff]; } +function unpackI16(bytes) { return as_signed(bytes[0] << 8 | bytes[1], 16); } + +function packU16(n) { return [(n >> 8) & 0xff, n & 0xff]; } +function unpackU16(bytes) { return as_unsigned(bytes[0] << 8 | bytes[1], 16); } + +function packI32(n) { return [(n >> 24) & 0xff, (n >> 16) & 0xff, (n >> 8) & 0xff, n & 0xff]; } +function unpackI32(bytes) { return as_signed(bytes[0] << 24 | bytes[1] << 16 | bytes[2] << 8 | bytes[3], 32); } + +function packU32(n) { return [(n >> 24) & 0xff, (n >> 16) & 0xff, (n >> 8) & 0xff, n & 0xff]; } +function unpackU32(bytes) { return as_unsigned(bytes[0] << 24 | bytes[1] << 16 | bytes[2] << 8 | bytes[3], 32); } + +function packIEEE754(v, ebits, fbits) { + + var bias = (1 << (ebits - 1)) - 1, + s, e, f, ln, + i, bits, str, bytes; + + function roundToEven(n) { + var w = floor(n), f = n - w; + if (f < 0.5) + return w; + if (f > 0.5) + return w + 1; + return w % 2 ? w + 1 : w; + } + + // Compute sign, exponent, fraction + if (v !== v) { + // NaN + // http://dev.w3.org/2006/webapi/WebIDL/#es-type-mapping + e = (1 << ebits) - 1; f = pow(2, fbits - 1); s = 0; + } else if (v === Infinity || v === -Infinity) { + e = (1 << ebits) - 1; f = 0; s = (v < 0) ? 1 : 0; + } else if (v === 0) { + e = 0; f = 0; s = (1 / v === -Infinity) ? 1 : 0; + } else { + s = v < 0; + v = abs(v); + + if (v >= pow(2, 1 - bias)) { + e = min(floor(log(v) / LN2), 1023); + f = roundToEven(v / pow(2, e) * pow(2, fbits)); + if (f / pow(2, fbits) >= 2) { + e = e + 1; + f = 1; + } + if (e > bias) { + // Overflow + e = (1 << ebits) - 1; + f = 0; + } else { + // Normalized + e = e + bias; + f = f - pow(2, fbits); + } + } else { + // Denormalized + e = 0; + f = roundToEven(v / pow(2, 1 - bias - fbits)); + } + } + + // Pack sign, exponent, fraction + bits = []; + for (i = fbits; i; i -= 1) { bits.push(f % 2 ? 1 : 0); f = floor(f / 2); } + for (i = ebits; i; i -= 1) { bits.push(e % 2 ? 1 : 0); e = floor(e / 2); } + bits.push(s ? 1 : 0); + bits.reverse(); + str = bits.join(''); + + // Bits to bytes + bytes = []; + while (str.length) { + bytes.push(parseInt(str.substring(0, 8), 2)); + str = str.substring(8); + } + return bytes; +} + +function unpackIEEE754(bytes, ebits, fbits) { + + // Bytes to bits + var bits = [], i, j, b, str, + bias, s, e, f; + + for (i = bytes.length; i; i -= 1) { + b = bytes[i - 1]; + for (j = 8; j; j -= 1) { + bits.push(b % 2 ? 1 : 0); b = b >> 1; + } + } + bits.reverse(); + str = bits.join(''); + + // Unpack sign, exponent, fraction + bias = (1 << (ebits - 1)) - 1; + s = parseInt(str.substring(0, 1), 2) ? -1 : 1; + e = parseInt(str.substring(1, 1 + ebits), 2); + f = parseInt(str.substring(1 + ebits), 2); + + // Produce number + if (e === (1 << ebits) - 1) { + return f !== 0 ? NaN : s * Infinity; + } else if (e > 0) { + // Normalized + return s * pow(2, e - bias) * (1 + f / pow(2, fbits)); + } else if (f !== 0) { + // Denormalized + return s * pow(2, -(bias - 1)) * (f / pow(2, fbits)); + } else { + return s < 0 ? -0 : 0; + } +} + +function unpackF64(b) { return unpackIEEE754(b, 11, 52); } +function packF64(v) { return packIEEE754(v, 11, 52); } +function unpackF32(b) { return unpackIEEE754(b, 8, 23); } +function packF32(v) { return packIEEE754(v, 8, 23); } + + +// +// 3 The ArrayBuffer Type +// + +(function() { + + /** @constructor */ + var ArrayBuffer = function ArrayBuffer(length) { + length = ECMAScript.ToInt32(length); + if (length < 0) throw new RangeError('ArrayBuffer size is not a small enough positive integer'); + + this.byteLength = length; + this._bytes = []; + this._bytes.length = length; + + var i; + for (i = 0; i < this.byteLength; i += 1) { + this._bytes[i] = 0; + } + + configureProperties(this); + }; + + exports.ArrayBuffer = exports.ArrayBuffer || ArrayBuffer; + + // + // 4 The ArrayBufferView Type + // + + // NOTE: this constructor is not exported + /** @constructor */ + var ArrayBufferView = function ArrayBufferView() { + //this.buffer = null; + //this.byteOffset = 0; + //this.byteLength = 0; + }; + + // + // 5 The Typed Array View Types + // + + function makeConstructor(bytesPerElement, pack, unpack) { + // Each TypedArray type requires a distinct constructor instance with + // identical logic, which this produces. + + var ctor; + ctor = function(buffer, byteOffset, length) { + var array, sequence, i, s; + + if (!arguments.length || typeof arguments[0] === 'number') { + // Constructor(unsigned long length) + this.length = ECMAScript.ToInt32(arguments[0]); + if (length < 0) throw new RangeError('ArrayBufferView size is not a small enough positive integer'); + + this.byteLength = this.length * this.BYTES_PER_ELEMENT; + this.buffer = new ArrayBuffer(this.byteLength); + this.byteOffset = 0; + } else if (typeof arguments[0] === 'object' && arguments[0].constructor === ctor) { + // Constructor(TypedArray array) + array = arguments[0]; + + this.length = array.length; + this.byteLength = this.length * this.BYTES_PER_ELEMENT; + this.buffer = new ArrayBuffer(this.byteLength); + this.byteOffset = 0; + + for (i = 0; i < this.length; i += 1) { + this._setter(i, array._getter(i)); + } + } else if (typeof arguments[0] === 'object' && + !(arguments[0] instanceof ArrayBuffer || ECMAScript.Class(arguments[0]) === 'ArrayBuffer')) { + // Constructor(sequence array) + sequence = arguments[0]; + + this.length = ECMAScript.ToUint32(sequence.length); + this.byteLength = this.length * this.BYTES_PER_ELEMENT; + this.buffer = new ArrayBuffer(this.byteLength); + this.byteOffset = 0; + + for (i = 0; i < this.length; i += 1) { + s = sequence[i]; + this._setter(i, Number(s)); + } + } else if (typeof arguments[0] === 'object' && + (arguments[0] instanceof ArrayBuffer || ECMAScript.Class(arguments[0]) === 'ArrayBuffer')) { + // Constructor(ArrayBuffer buffer, + // optional unsigned long byteOffset, optional unsigned long length) + this.buffer = buffer; + + this.byteOffset = ECMAScript.ToUint32(byteOffset); + if (this.byteOffset > this.buffer.byteLength) { + throw new RangeError("byteOffset out of range"); + } + + if (this.byteOffset % this.BYTES_PER_ELEMENT) { + // The given byteOffset must be a multiple of the element + // size of the specific type, otherwise an exception is raised. + throw new RangeError("ArrayBuffer length minus the byteOffset is not a multiple of the element size."); + } + + if (arguments.length < 3) { + this.byteLength = this.buffer.byteLength - this.byteOffset; + + if (this.byteLength % this.BYTES_PER_ELEMENT) { + throw new RangeError("length of buffer minus byteOffset not a multiple of the element size"); + } + this.length = this.byteLength / this.BYTES_PER_ELEMENT; + } else { + this.length = ECMAScript.ToUint32(length); + this.byteLength = this.length * this.BYTES_PER_ELEMENT; + } + + if ((this.byteOffset + this.byteLength) > this.buffer.byteLength) { + throw new RangeError("byteOffset and length reference an area beyond the end of the buffer"); + } + } else { + throw new TypeError("Unexpected argument type(s)"); + } + + this.constructor = ctor; + + configureProperties(this); + makeArrayAccessors(this); + }; + + ctor.prototype = new ArrayBufferView(); + ctor.prototype.BYTES_PER_ELEMENT = bytesPerElement; + ctor.prototype._pack = pack; + ctor.prototype._unpack = unpack; + ctor.BYTES_PER_ELEMENT = bytesPerElement; + + // getter type (unsigned long index); + ctor.prototype._getter = function(index) { + if (arguments.length < 1) throw new SyntaxError("Not enough arguments"); + + index = ECMAScript.ToUint32(index); + if (index >= this.length) { + return undefined; + } + + var bytes = [], i, o; + for (i = 0, o = this.byteOffset + index * this.BYTES_PER_ELEMENT; + i < this.BYTES_PER_ELEMENT; + i += 1, o += 1) { + bytes.push(this.buffer._bytes[o]); + } + return this._unpack(bytes); + }; + + // NONSTANDARD: convenience alias for getter: type get(unsigned long index); + ctor.prototype.get = ctor.prototype._getter; + + // setter void (unsigned long index, type value); + ctor.prototype._setter = function(index, value) { + if (arguments.length < 2) throw new SyntaxError("Not enough arguments"); + + index = ECMAScript.ToUint32(index); + if (index >= this.length) { + return undefined; + } + + var bytes = this._pack(value), i, o; + for (i = 0, o = this.byteOffset + index * this.BYTES_PER_ELEMENT; + i < this.BYTES_PER_ELEMENT; + i += 1, o += 1) { + this.buffer._bytes[o] = bytes[i]; + } + }; + + // void set(TypedArray array, optional unsigned long offset); + // void set(sequence array, optional unsigned long offset); + ctor.prototype.set = function(index, value) { + if (arguments.length < 1) throw new SyntaxError("Not enough arguments"); + var array, sequence, offset, len, + i, s, d, + byteOffset, byteLength, tmp; + + if (typeof arguments[0] === 'object' && arguments[0].constructor === this.constructor) { + // void set(TypedArray array, optional unsigned long offset); + array = arguments[0]; + offset = ECMAScript.ToUint32(arguments[1]); + + if (offset + array.length > this.length) { + throw new RangeError("Offset plus length of array is out of range"); + } + + byteOffset = this.byteOffset + offset * this.BYTES_PER_ELEMENT; + byteLength = array.length * this.BYTES_PER_ELEMENT; + + if (array.buffer === this.buffer) { + tmp = []; + for (i = 0, s = array.byteOffset; i < byteLength; i += 1, s += 1) { + tmp[i] = array.buffer._bytes[s]; + } + for (i = 0, d = byteOffset; i < byteLength; i += 1, d += 1) { + this.buffer._bytes[d] = tmp[i]; + } + } else { + for (i = 0, s = array.byteOffset, d = byteOffset; + i < byteLength; i += 1, s += 1, d += 1) { + this.buffer._bytes[d] = array.buffer._bytes[s]; + } + } + } else if (typeof arguments[0] === 'object' && typeof arguments[0].length !== 'undefined') { + // void set(sequence array, optional unsigned long offset); + sequence = arguments[0]; + len = ECMAScript.ToUint32(sequence.length); + offset = ECMAScript.ToUint32(arguments[1]); + + if (offset + len > this.length) { + throw new RangeError("Offset plus length of array is out of range"); + } + + for (i = 0; i < len; i += 1) { + s = sequence[i]; + this._setter(offset + i, Number(s)); + } + } else { + throw new TypeError("Unexpected argument type(s)"); + } + }; + + // TypedArray subarray(long begin, optional long end); + ctor.prototype.subarray = function(start, end) { + function clamp(v, min, max) { return v < min ? min : v > max ? max : v; } + + start = ECMAScript.ToInt32(start); + end = ECMAScript.ToInt32(end); + + if (arguments.length < 1) { start = 0; } + if (arguments.length < 2) { end = this.length; } + + if (start < 0) { start = this.length + start; } + if (end < 0) { end = this.length + end; } + + start = clamp(start, 0, this.length); + end = clamp(end, 0, this.length); + + var len = end - start; + if (len < 0) { + len = 0; + } + + return new this.constructor( + this.buffer, this.byteOffset + start * this.BYTES_PER_ELEMENT, len); + }; + + return ctor; + } + + var Int8Array = makeConstructor(1, packI8, unpackI8); + var Uint8Array = makeConstructor(1, packU8, unpackU8); + var Uint8ClampedArray = makeConstructor(1, packU8Clamped, unpackU8); + var Int16Array = makeConstructor(2, packI16, unpackI16); + var Uint16Array = makeConstructor(2, packU16, unpackU16); + var Int32Array = makeConstructor(4, packI32, unpackI32); + var Uint32Array = makeConstructor(4, packU32, unpackU32); + var Float32Array = makeConstructor(4, packF32, unpackF32); + var Float64Array = makeConstructor(8, packF64, unpackF64); + + exports.Int8Array = exports.Int8Array || Int8Array; + exports.Uint8Array = exports.Uint8Array || Uint8Array; + exports.Uint8ClampedArray = exports.Uint8ClampedArray || Uint8ClampedArray; + exports.Int16Array = exports.Int16Array || Int16Array; + exports.Uint16Array = exports.Uint16Array || Uint16Array; + exports.Int32Array = exports.Int32Array || Int32Array; + exports.Uint32Array = exports.Uint32Array || Uint32Array; + exports.Float32Array = exports.Float32Array || Float32Array; + exports.Float64Array = exports.Float64Array || Float64Array; +}()); + +// +// 6 The DataView View Type +// + +(function() { + function r(array, index) { + return ECMAScript.IsCallable(array.get) ? array.get(index) : array[index]; + } + + var IS_BIG_ENDIAN = (function() { + var u16array = new(exports.Uint16Array)([0x1234]), + u8array = new(exports.Uint8Array)(u16array.buffer); + return r(u8array, 0) === 0x12; + }()); + + // Constructor(ArrayBuffer buffer, + // optional unsigned long byteOffset, + // optional unsigned long byteLength) + /** @constructor */ + var DataView = function DataView(buffer, byteOffset, byteLength) { + if (arguments.length === 0) { + buffer = new exports.ArrayBuffer(0); + } else if (!(buffer instanceof exports.ArrayBuffer || ECMAScript.Class(buffer) === 'ArrayBuffer')) { + throw new TypeError("TypeError"); + } + + this.buffer = buffer || new exports.ArrayBuffer(0); + + this.byteOffset = ECMAScript.ToUint32(byteOffset); + if (this.byteOffset > this.buffer.byteLength) { + throw new RangeError("byteOffset out of range"); + } + + if (arguments.length < 3) { + this.byteLength = this.buffer.byteLength - this.byteOffset; + } else { + this.byteLength = ECMAScript.ToUint32(byteLength); + } + + if ((this.byteOffset + this.byteLength) > this.buffer.byteLength) { + throw new RangeError("byteOffset and length reference an area beyond the end of the buffer"); + } + + configureProperties(this); + }; + + function makeGetter(arrayType) { + return function(byteOffset, littleEndian) { + + byteOffset = ECMAScript.ToUint32(byteOffset); + + if (byteOffset + arrayType.BYTES_PER_ELEMENT > this.byteLength) { + throw new RangeError("Array index out of range"); + } + byteOffset += this.byteOffset; + + var uint8Array = new exports.Uint8Array(this.buffer, byteOffset, arrayType.BYTES_PER_ELEMENT), + bytes = [], i; + for (i = 0; i < arrayType.BYTES_PER_ELEMENT; i += 1) { + bytes.push(r(uint8Array, i)); + } + + if (Boolean(littleEndian) === Boolean(IS_BIG_ENDIAN)) { + bytes.reverse(); + } + + return r(new arrayType(new exports.Uint8Array(bytes).buffer), 0); + }; + } + + DataView.prototype.getUint8 = makeGetter(exports.Uint8Array); + DataView.prototype.getInt8 = makeGetter(exports.Int8Array); + DataView.prototype.getUint16 = makeGetter(exports.Uint16Array); + DataView.prototype.getInt16 = makeGetter(exports.Int16Array); + DataView.prototype.getUint32 = makeGetter(exports.Uint32Array); + DataView.prototype.getInt32 = makeGetter(exports.Int32Array); + DataView.prototype.getFloat32 = makeGetter(exports.Float32Array); + DataView.prototype.getFloat64 = makeGetter(exports.Float64Array); + + function makeSetter(arrayType) { + return function(byteOffset, value, littleEndian) { + + byteOffset = ECMAScript.ToUint32(byteOffset); + if (byteOffset + arrayType.BYTES_PER_ELEMENT > this.byteLength) { + throw new RangeError("Array index out of range"); + } + + // Get bytes + var typeArray = new arrayType([value]), + byteArray = new exports.Uint8Array(typeArray.buffer), + bytes = [], i, byteView; + + for (i = 0; i < arrayType.BYTES_PER_ELEMENT; i += 1) { + bytes.push(r(byteArray, i)); + } + + // Flip if necessary + if (Boolean(littleEndian) === Boolean(IS_BIG_ENDIAN)) { + bytes.reverse(); + } + + // Write them + byteView = new exports.Uint8Array(this.buffer, byteOffset, arrayType.BYTES_PER_ELEMENT); + byteView.set(bytes); + }; + } + + DataView.prototype.setUint8 = makeSetter(exports.Uint8Array); + DataView.prototype.setInt8 = makeSetter(exports.Int8Array); + DataView.prototype.setUint16 = makeSetter(exports.Uint16Array); + DataView.prototype.setInt16 = makeSetter(exports.Int16Array); + DataView.prototype.setUint32 = makeSetter(exports.Uint32Array); + DataView.prototype.setInt32 = makeSetter(exports.Int32Array); + DataView.prototype.setFloat32 = makeSetter(exports.Float32Array); + DataView.prototype.setFloat64 = makeSetter(exports.Float64Array); + + exports.DataView = exports.DataView || DataView; + +}()); diff --git a/backend/node_modules/typedarray/package.json b/backend/node_modules/typedarray/package.json new file mode 100644 index 0000000..a7854a0 --- /dev/null +++ b/backend/node_modules/typedarray/package.json @@ -0,0 +1,55 @@ +{ + "name": "typedarray", + "version": "0.0.6", + "description": "TypedArray polyfill for old browsers", + "main": "index.js", + "devDependencies": { + "tape": "~2.3.2" + }, + "scripts": { + "test": "tape test/*.js test/server/*.js" + }, + "repository": { + "type": "git", + "url": "git://github.com/substack/typedarray.git" + }, + "homepage": "https://github.com/substack/typedarray", + "keywords": [ + "ArrayBuffer", + "DataView", + "Float32Array", + "Float64Array", + "Int8Array", + "Int16Array", + "Int32Array", + "Uint8Array", + "Uint8ClampedArray", + "Uint16Array", + "Uint32Array", + "typed", + "array", + "polyfill" + ], + "author": { + "name": "James Halliday", + "email": "mail@substack.net", + "url": "http://substack.net" + }, + "license": "MIT", + "testling": { + "files": "test/*.js", + "browsers": [ + "ie/6..latest", + "firefox/16..latest", + "firefox/nightly", + "chrome/22..latest", + "chrome/canary", + "opera/12..latest", + "opera/next", + "safari/5.1..latest", + "ipad/6.0..latest", + "iphone/6.0..latest", + "android-browser/4.2..latest" + ] + } +} diff --git a/backend/node_modules/typedarray/readme.markdown b/backend/node_modules/typedarray/readme.markdown new file mode 100644 index 0000000..d18f6f7 --- /dev/null +++ b/backend/node_modules/typedarray/readme.markdown @@ -0,0 +1,61 @@ +# typedarray + +TypedArray polyfill ripped from [this +module](https://raw.github.com/inexorabletash/polyfill). + +[![build status](https://secure.travis-ci.org/substack/typedarray.png)](http://travis-ci.org/substack/typedarray) + +[![testling badge](https://ci.testling.com/substack/typedarray.png)](https://ci.testling.com/substack/typedarray) + +# example + +``` js +var Uint8Array = require('typedarray').Uint8Array; +var ua = new Uint8Array(5); +ua[1] = 256 + 55; +console.log(ua[1]); +``` + +output: + +``` +55 +``` + +# methods + +``` js +var TA = require('typedarray') +``` + +The `TA` object has the following constructors: + +* TA.ArrayBuffer +* TA.DataView +* TA.Float32Array +* TA.Float64Array +* TA.Int8Array +* TA.Int16Array +* TA.Int32Array +* TA.Uint8Array +* TA.Uint8ClampedArray +* TA.Uint16Array +* TA.Uint32Array + +# install + +With [npm](https://npmjs.org) do: + +``` +npm install typedarray +``` + +To use this module in the browser, compile with +[browserify](http://browserify.org) +or download a UMD build from browserify CDN: + +http://wzrd.in/standalone/typedarray@latest + +# license + +MIT diff --git a/backend/node_modules/typedarray/test/server/undef_globals.js b/backend/node_modules/typedarray/test/server/undef_globals.js new file mode 100644 index 0000000..425950f --- /dev/null +++ b/backend/node_modules/typedarray/test/server/undef_globals.js @@ -0,0 +1,19 @@ +var test = require('tape'); +var vm = require('vm'); +var fs = require('fs'); +var src = fs.readFileSync(__dirname + '/../../index.js', 'utf8'); + +test('u8a without globals', function (t) { + var c = { + module: { exports: {} }, + }; + c.exports = c.module.exports; + vm.runInNewContext(src, c); + var TA = c.module.exports; + var ua = new(TA.Uint8Array)(5); + + t.equal(ua.length, 5); + ua[1] = 256 + 55; + t.equal(ua[1], 55); + t.end(); +}); diff --git a/backend/node_modules/typedarray/test/tarray.js b/backend/node_modules/typedarray/test/tarray.js new file mode 100644 index 0000000..df596a3 --- /dev/null +++ b/backend/node_modules/typedarray/test/tarray.js @@ -0,0 +1,10 @@ +var TA = require('../'); +var test = require('tape'); + +test('tiny u8a test', function (t) { + var ua = new(TA.Uint8Array)(5); + t.equal(ua.length, 5); + ua[1] = 256 + 55; + t.equal(ua[1], 55); + t.end(); +}); diff --git a/backend/node_modules/util-deprecate/History.md b/backend/node_modules/util-deprecate/History.md new file mode 100644 index 0000000..acc8675 --- /dev/null +++ b/backend/node_modules/util-deprecate/History.md @@ -0,0 +1,16 @@ + +1.0.2 / 2015-10-07 +================== + + * use try/catch when checking `localStorage` (#3, @kumavis) + +1.0.1 / 2014-11-25 +================== + + * browser: use `console.warn()` for deprecation calls + * browser: more jsdocs + +1.0.0 / 2014-04-30 +================== + + * initial commit diff --git a/backend/node_modules/util-deprecate/LICENSE b/backend/node_modules/util-deprecate/LICENSE new file mode 100644 index 0000000..6a60e8c --- /dev/null +++ b/backend/node_modules/util-deprecate/LICENSE @@ -0,0 +1,24 @@ +(The MIT License) + +Copyright (c) 2014 Nathan Rajlich + +Permission is hereby granted, free of charge, to any person +obtaining a copy of this software and associated documentation +files (the "Software"), to deal in the Software without +restriction, including without limitation the rights to use, +copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the +Software is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +OTHER DEALINGS IN THE SOFTWARE. diff --git a/backend/node_modules/util-deprecate/README.md b/backend/node_modules/util-deprecate/README.md new file mode 100644 index 0000000..75622fa --- /dev/null +++ b/backend/node_modules/util-deprecate/README.md @@ -0,0 +1,53 @@ +util-deprecate +============== +### The Node.js `util.deprecate()` function with browser support + +In Node.js, this module simply re-exports the `util.deprecate()` function. + +In the web browser (i.e. via browserify), a browser-specific implementation +of the `util.deprecate()` function is used. + + +## API + +A `deprecate()` function is the only thing exposed by this module. + +``` javascript +// setup: +exports.foo = deprecate(foo, 'foo() is deprecated, use bar() instead'); + + +// users see: +foo(); +// foo() is deprecated, use bar() instead +foo(); +foo(); +``` + + +## License + +(The MIT License) + +Copyright (c) 2014 Nathan Rajlich + +Permission is hereby granted, free of charge, to any person +obtaining a copy of this software and associated documentation +files (the "Software"), to deal in the Software without +restriction, including without limitation the rights to use, +copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the +Software is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +OTHER DEALINGS IN THE SOFTWARE. diff --git a/backend/node_modules/util-deprecate/browser.js b/backend/node_modules/util-deprecate/browser.js new file mode 100644 index 0000000..549ae2f --- /dev/null +++ b/backend/node_modules/util-deprecate/browser.js @@ -0,0 +1,67 @@ + +/** + * Module exports. + */ + +module.exports = deprecate; + +/** + * Mark that a method should not be used. + * Returns a modified function which warns once by default. + * + * If `localStorage.noDeprecation = true` is set, then it is a no-op. + * + * If `localStorage.throwDeprecation = true` is set, then deprecated functions + * will throw an Error when invoked. + * + * If `localStorage.traceDeprecation = true` is set, then deprecated functions + * will invoke `console.trace()` instead of `console.error()`. + * + * @param {Function} fn - the function to deprecate + * @param {String} msg - the string to print to the console when `fn` is invoked + * @returns {Function} a new "deprecated" version of `fn` + * @api public + */ + +function deprecate (fn, msg) { + if (config('noDeprecation')) { + return fn; + } + + var warned = false; + function deprecated() { + if (!warned) { + if (config('throwDeprecation')) { + throw new Error(msg); + } else if (config('traceDeprecation')) { + console.trace(msg); + } else { + console.warn(msg); + } + warned = true; + } + return fn.apply(this, arguments); + } + + return deprecated; +} + +/** + * Checks `localStorage` for boolean values for the given `name`. + * + * @param {String} name + * @returns {Boolean} + * @api private + */ + +function config (name) { + // accessing global.localStorage can trigger a DOMException in sandboxed iframes + try { + if (!global.localStorage) return false; + } catch (_) { + return false; + } + var val = global.localStorage[name]; + if (null == val) return false; + return String(val).toLowerCase() === 'true'; +} diff --git a/backend/node_modules/util-deprecate/node.js b/backend/node_modules/util-deprecate/node.js new file mode 100644 index 0000000..5e6fcff --- /dev/null +++ b/backend/node_modules/util-deprecate/node.js @@ -0,0 +1,6 @@ + +/** + * For Node.js, simply re-export the core `util.deprecate` function. + */ + +module.exports = require('util').deprecate; diff --git a/backend/node_modules/util-deprecate/package.json b/backend/node_modules/util-deprecate/package.json new file mode 100644 index 0000000..2e79f89 --- /dev/null +++ b/backend/node_modules/util-deprecate/package.json @@ -0,0 +1,27 @@ +{ + "name": "util-deprecate", + "version": "1.0.2", + "description": "The Node.js `util.deprecate()` function with browser support", + "main": "node.js", + "browser": "browser.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "repository": { + "type": "git", + "url": "git://github.com/TooTallNate/util-deprecate.git" + }, + "keywords": [ + "util", + "deprecate", + "browserify", + "browser", + "node" + ], + "author": "Nathan Rajlich (http://n8.io/)", + "license": "MIT", + "bugs": { + "url": "https://github.com/TooTallNate/util-deprecate/issues" + }, + "homepage": "https://github.com/TooTallNate/util-deprecate" +} diff --git a/backend/node_modules/xtend/.jshintrc b/backend/node_modules/xtend/.jshintrc new file mode 100644 index 0000000..77887b5 --- /dev/null +++ b/backend/node_modules/xtend/.jshintrc @@ -0,0 +1,30 @@ +{ + "maxdepth": 4, + "maxstatements": 200, + "maxcomplexity": 12, + "maxlen": 80, + "maxparams": 5, + + "curly": true, + "eqeqeq": true, + "immed": true, + "latedef": false, + "noarg": true, + "noempty": true, + "nonew": true, + "undef": true, + "unused": "vars", + "trailing": true, + + "quotmark": true, + "expr": true, + "asi": true, + + "browser": false, + "esnext": true, + "devel": false, + "node": false, + "nonstandard": false, + + "predef": ["require", "module", "__dirname", "__filename"] +} diff --git a/backend/node_modules/xtend/LICENSE b/backend/node_modules/xtend/LICENSE new file mode 100644 index 0000000..0099f4f --- /dev/null +++ b/backend/node_modules/xtend/LICENSE @@ -0,0 +1,20 @@ +The MIT License (MIT) +Copyright (c) 2012-2014 Raynos. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/backend/node_modules/xtend/README.md b/backend/node_modules/xtend/README.md new file mode 100644 index 0000000..4a2703c --- /dev/null +++ b/backend/node_modules/xtend/README.md @@ -0,0 +1,32 @@ +# xtend + +[![browser support][3]][4] + +[![locked](http://badges.github.io/stability-badges/dist/locked.svg)](http://github.com/badges/stability-badges) + +Extend like a boss + +xtend is a basic utility library which allows you to extend an object by appending all of the properties from each object in a list. When there are identical properties, the right-most property takes precedence. + +## Examples + +```js +var extend = require("xtend") + +// extend returns a new object. Does not mutate arguments +var combination = extend({ + a: "a", + b: "c" +}, { + b: "b" +}) +// { a: "a", b: "b" } +``` + +## Stability status: Locked + +## MIT Licensed + + + [3]: http://ci.testling.com/Raynos/xtend.png + [4]: http://ci.testling.com/Raynos/xtend diff --git a/backend/node_modules/xtend/immutable.js b/backend/node_modules/xtend/immutable.js new file mode 100644 index 0000000..94889c9 --- /dev/null +++ b/backend/node_modules/xtend/immutable.js @@ -0,0 +1,19 @@ +module.exports = extend + +var hasOwnProperty = Object.prototype.hasOwnProperty; + +function extend() { + var target = {} + + for (var i = 0; i < arguments.length; i++) { + var source = arguments[i] + + for (var key in source) { + if (hasOwnProperty.call(source, key)) { + target[key] = source[key] + } + } + } + + return target +} diff --git a/backend/node_modules/xtend/mutable.js b/backend/node_modules/xtend/mutable.js new file mode 100644 index 0000000..72debed --- /dev/null +++ b/backend/node_modules/xtend/mutable.js @@ -0,0 +1,17 @@ +module.exports = extend + +var hasOwnProperty = Object.prototype.hasOwnProperty; + +function extend(target) { + for (var i = 1; i < arguments.length; i++) { + var source = arguments[i] + + for (var key in source) { + if (hasOwnProperty.call(source, key)) { + target[key] = source[key] + } + } + } + + return target +} diff --git a/backend/node_modules/xtend/package.json b/backend/node_modules/xtend/package.json new file mode 100644 index 0000000..f7a39d1 --- /dev/null +++ b/backend/node_modules/xtend/package.json @@ -0,0 +1,55 @@ +{ + "name": "xtend", + "version": "4.0.2", + "description": "extend like a boss", + "keywords": [ + "extend", + "merge", + "options", + "opts", + "object", + "array" + ], + "author": "Raynos ", + "repository": "git://github.com/Raynos/xtend.git", + "main": "immutable", + "scripts": { + "test": "node test" + }, + "dependencies": {}, + "devDependencies": { + "tape": "~1.1.0" + }, + "homepage": "https://github.com/Raynos/xtend", + "contributors": [ + { + "name": "Jake Verbaten" + }, + { + "name": "Matt Esch" + } + ], + "bugs": { + "url": "https://github.com/Raynos/xtend/issues", + "email": "raynos2@gmail.com" + }, + "license": "MIT", + "testling": { + "files": "test.js", + "browsers": [ + "ie/7..latest", + "firefox/16..latest", + "firefox/nightly", + "chrome/22..latest", + "chrome/canary", + "opera/12..latest", + "opera/next", + "safari/5.1..latest", + "ipad/6.0..latest", + "iphone/6.0..latest" + ] + }, + "engines": { + "node": ">=0.4" + } +} diff --git a/backend/node_modules/xtend/test.js b/backend/node_modules/xtend/test.js new file mode 100644 index 0000000..b895b42 --- /dev/null +++ b/backend/node_modules/xtend/test.js @@ -0,0 +1,103 @@ +var test = require("tape") +var extend = require("./") +var mutableExtend = require("./mutable") + +test("merge", function(assert) { + var a = { a: "foo" } + var b = { b: "bar" } + + assert.deepEqual(extend(a, b), { a: "foo", b: "bar" }) + assert.end() +}) + +test("replace", function(assert) { + var a = { a: "foo" } + var b = { a: "bar" } + + assert.deepEqual(extend(a, b), { a: "bar" }) + assert.end() +}) + +test("undefined", function(assert) { + var a = { a: undefined } + var b = { b: "foo" } + + assert.deepEqual(extend(a, b), { a: undefined, b: "foo" }) + assert.deepEqual(extend(b, a), { a: undefined, b: "foo" }) + assert.end() +}) + +test("handle 0", function(assert) { + var a = { a: "default" } + var b = { a: 0 } + + assert.deepEqual(extend(a, b), { a: 0 }) + assert.deepEqual(extend(b, a), { a: "default" }) + assert.end() +}) + +test("is immutable", function (assert) { + var record = {} + + extend(record, { foo: "bar" }) + assert.equal(record.foo, undefined) + assert.end() +}) + +test("null as argument", function (assert) { + var a = { foo: "bar" } + var b = null + var c = void 0 + + assert.deepEqual(extend(b, a, c), { foo: "bar" }) + assert.end() +}) + +test("mutable", function (assert) { + var a = { foo: "bar" } + + mutableExtend(a, { bar: "baz" }) + + assert.equal(a.bar, "baz") + assert.end() +}) + +test("null prototype", function(assert) { + var a = { a: "foo" } + var b = Object.create(null) + b.b = "bar"; + + assert.deepEqual(extend(a, b), { a: "foo", b: "bar" }) + assert.end() +}) + +test("null prototype mutable", function (assert) { + var a = { foo: "bar" } + var b = Object.create(null) + b.bar = "baz"; + + mutableExtend(a, b) + + assert.equal(a.bar, "baz") + assert.end() +}) + +test("prototype pollution", function (assert) { + var a = {} + var maliciousPayload = '{"__proto__":{"oops":"It works!"}}' + + assert.strictEqual(a.oops, undefined) + extend({}, maliciousPayload) + assert.strictEqual(a.oops, undefined) + assert.end() +}) + +test("prototype pollution mutable", function (assert) { + var a = {} + var maliciousPayload = '{"__proto__":{"oops":"It works!"}}' + + assert.strictEqual(a.oops, undefined) + mutableExtend({}, maliciousPayload) + assert.strictEqual(a.oops, undefined) + assert.end() +}) diff --git a/backend/routes/hamaRoutes.js b/backend/routes/hamaRoutes.js index c2349fb..e09e941 100644 --- a/backend/routes/hamaRoutes.js +++ b/backend/routes/hamaRoutes.js @@ -1,6 +1,8 @@ const express = require('express'); const router = express.Router(); const hamaController = require('../controller/hamaController'); +const uploadHamaGambar = require('../middleware/uploadHamaGambar'); +const multer = require('multer'); /** * @swagger @@ -38,32 +40,43 @@ router.get('/', hamaController.getAllHama); * 404: * description: Hama tidak ditemukan */ -router.get('/:id', hamaController.getHamaById); +router.get('/:id/image', hamaController.getHamaById); /** * @swagger * /api/hama: * post: - * summary: Tambahkan data hama baru + * summary: Tambahkan data hama baru dengan foto * tags: [Hama] * requestBody: * required: true * content: - * application/json: + * multipart/form-data: * schema: * type: object * properties: * nama: * type: string + * description: Nama hama * deskripsi: * type: string + * description: Deskripsi tentang hama * penanganan: * type: string + * description: Cara penanganan hama + * foto: + * type: string + * format: binary + * description: Foto hama (JPG, JPEG, PNG, GIF) * responses: * 201: * description: Hama berhasil ditambahkan + * 400: + * description: Format file tidak valid atau data tidak lengkap + * 500: + * description: Server error */ -router.post('/', hamaController.createHama); +router.post('/', uploadHamaGambar.single('foto'), hamaController.createHama); /** * @swagger @@ -79,23 +92,24 @@ router.post('/', hamaController.createHama); * requestBody: * required: true * content: - * application/json: + * multipart/form-data: * schema: * type: object * properties: * nama: * type: string - * kategori: - * type: string * deskripsi: * type: string * penanganan: * type: string + * foto: + * type: string + * format: binary * responses: * 200: * description: Hama berhasil diperbarui */ -router.put('/:id', hamaController.updateHama); +router.put('/:id', uploadHamaGambar.single('foto'), hamaController.updateHama); /** * @swagger @@ -114,4 +128,21 @@ router.put('/:id', hamaController.updateHama); */ router.delete('/:id', hamaController.deleteHama); +// Error handler untuk multer - letakkan SETELAH semua definisi rute +router.use((err, req, res, next) => { + if (err instanceof multer.MulterError) { + return res.status(400).json({ + success: false, + message: 'Error saat upload file', + error: err.message + }); + } else if (err) { + return res.status(400).json({ + success: false, + message: err.message + }); + } + next(); + }); + module.exports = router; diff --git a/backend/routes/penyakitRoutes.js b/backend/routes/penyakitRoutes.js index 96dab3a..3859dd7 100644 --- a/backend/routes/penyakitRoutes.js +++ b/backend/routes/penyakitRoutes.js @@ -1,6 +1,9 @@ const express = require('express'); const router = express.Router(); const penyakitController = require('../controller/penyakitController'); +const uploadPenyakitGambar = require('../middleware/uploadPenyakitGambar'); +const multer = require('multer'); + /** * @swagger @@ -38,32 +41,43 @@ router.get('/', penyakitController.getAllPenyakit); * 404: * description: Penyakit tidak ditemukan */ -router.get('/:id', penyakitController.getPenyakitById); +router.get('/:id/image', penyakitController.getPenyakitById); /** * @swagger * /api/penyakit: * post: - * summary: Tambahkan data penyakit baru + * summary: Tambahkan data penyakit baru dengan foto * tags: [Penyakit] * requestBody: * required: true * content: - * application/json: + * multipart/form-data: * schema: * type: object * properties: * nama: * type: string + * description: Nama penyakit * deskripsi: * type: string + * description: Deskripsi tentang penyakit * penanganan: * type: string + * description: Cara penanganan penyakit + * foto: + * type: string + * format: binary + * description: Foto penyakit (JPG, JPEG, PNG, GIF) * responses: * 201: * description: Hama berhasil ditambahkan + * 400: + * description: Format file tidak valid atau data tidak lengkap + * 500: + * description: Server error */ -router.post('/', penyakitController.createPenyakit); +router.post('/', uploadPenyakitGambar.single('foto'), penyakitController.createPenyakit); /** * @swagger @@ -79,23 +93,24 @@ router.post('/', penyakitController.createPenyakit); * requestBody: * required: true * content: - * application/json: + * multipart/form-data: * schema: * type: object * properties: * nama: * type: string - * kategori: - * type: string * deskripsi: * type: string * penanganan: * type: string + * foto: + * type: string + * format: binary * responses: * 200: * description: penyakit berhasil diperbarui */ -router.put('/:id', penyakitController.updatePenyakit); +router.put('/:id', uploadPenyakitGambar.single('foto'), penyakitController.updatePenyakit); /** * @swagger @@ -114,4 +129,22 @@ router.put('/:id', penyakitController.updatePenyakit); */ router.delete('/:id', penyakitController.deletePenyakit); +// Error handler untuk multer - letakkan SETELAH semua definisi rute +router.use((err, req, res, next) => { + if (err instanceof multer.MulterError) { + return res.status(400).json({ + success: false, + message: 'Error saat upload file', + error: err.message + }); + } else if (err) { + return res.status(400).json({ + success: false, + message: err.message + }); + } + next(); + }); + + module.exports = router; diff --git a/frontend/lib/admin/edit_hama_page.dart b/frontend/lib/admin/edit_hama_page.dart index 9e51a5e..9391e23 100644 --- a/frontend/lib/admin/edit_hama_page.dart +++ b/frontend/lib/admin/edit_hama_page.dart @@ -1,11 +1,17 @@ import 'package:flutter/material.dart'; -import 'package:frontend/api_services/api_services.dart'; // Pastikan ini di-import ya +import 'package:frontend/api_services/api_services.dart'; +import 'image_utilities.dart'; // Import file baru +import 'dart:io'; +import 'dart:typed_data'; +import 'package:image_picker/image_picker.dart'; +import 'package:flutter/foundation.dart' show kIsWeb; class EditHamaPage extends StatefulWidget { final int idHama; final String namaAwal; final String deskripsiAwal; final String penangananAwal; + final String gambarUrl; final VoidCallback onHamaUpdated; const EditHamaPage({ @@ -14,6 +20,7 @@ class EditHamaPage extends StatefulWidget { required this.namaAwal, required this.deskripsiAwal, required this.penangananAwal, + required this.gambarUrl, required this.onHamaUpdated, }) : super(key: key); @@ -26,6 +33,14 @@ class _EditHamaPageState extends State { final TextEditingController _deskripsiController = TextEditingController(); final TextEditingController _penangananController = TextEditingController(); final ApiService apiService = ApiService(); + final ImagePicker _picker = ImagePicker(); + + XFile? _pickedFile; + Uint8List? _webImage; + bool _isLoading = false; + String? _errorMessage; + bool _isImageLoading = false; + Uint8List? _currentImageBytes; @override void initState() { @@ -33,6 +48,40 @@ class _EditHamaPageState extends State { _namaController.text = widget.namaAwal; _deskripsiController.text = widget.deskripsiAwal; _penangananController.text = widget.penangananAwal; + + // Load existing image + _loadExistingImage(); + } + + Future _loadExistingImage() async { + if (widget.gambarUrl.isEmpty) return; + + setState(() { + _isImageLoading = true; + }); + + try { + // Coba mengambil gambar langsung dari API + final bytes = await apiService.getHamaImageBytes(widget.idHama); + + if (bytes != null) { + setState(() { + _currentImageBytes = bytes; + _isImageLoading = false; + }); + } else { + setState(() { + _isImageLoading = false; + _errorMessage = "Gagal memuat gambar dari server"; + }); + } + } catch (e) { + print("Error loading image: $e"); + setState(() { + _isImageLoading = false; + _errorMessage = "Error: $e"; + }); + } } @override @@ -45,20 +94,133 @@ class _EditHamaPageState extends State { Future _updateHama() async { try { + setState(() { + _isLoading = true; + _errorMessage = null; + }); + await apiService.updateHama( widget.idHama, _namaController.text, _deskripsiController.text, _penangananController.text, + _pickedFile, ); + + setState(() { + _isLoading = false; + }); + widget.onHamaUpdated(); Navigator.pop(context); ScaffoldMessenger.of(context).showSnackBar( - SnackBar(content: Text('Data hama berhasil diperbarui')), + SnackBar(content: Text('Data hama berhasil diperbarui')) ); } catch (e) { + setState(() { + _isLoading = false; + _errorMessage = 'Gagal memperbarui data: $e'; + }); + ScaffoldMessenger.of(context).showSnackBar( - SnackBar(content: Text('Gagal memperbarui data: $e')), + SnackBar(content: Text('Gagal memperbarui data: $e')) + ); + } + } + + Future _pickImage() async { + try { + final pickedFile = await _picker.pickImage(source: ImageSource.gallery); + if (pickedFile != null) { + _pickedFile = pickedFile; + print('Gambar dipilih: ${pickedFile.path}'); + + // Baca file sebagai bytes untuk ditampilkan di UI + final bytes = await pickedFile.readAsBytes(); + + setState(() { + _webImage = bytes; + // Hapus referensi ke gambar lama + _currentImageBytes = null; + }); + } else { + print('Tidak ada gambar dipilih'); + } + } catch (e) { + print('Error saat memilih gambar: $e'); + ScaffoldMessenger.of(context).showSnackBar( + SnackBar(content: Text('Gagal memilih gambar: $e')) + ); + } + } + + Widget _buildImagePreview() { + if (_isImageLoading) { + return Container( + height: 150, + color: Colors.grey[200], + child: const Center(child: CircularProgressIndicator()), + ); + } + if (_webImage != null) { + // Tampilkan gambar yang baru dipilih + return Column( + children: [ + Image.memory( + _webImage!, + height: 150, + fit: BoxFit.cover, + errorBuilder: (context, error, stackTrace) { + print('Error displaying selected image: $error'); + return Text('Gagal memuat gambar yang dipilih'); + }, + ), + SizedBox(height: 8), + Text('Gambar baru dipilih', style: TextStyle(fontStyle: FontStyle.italic)), + ], + ); + } else if (_currentImageBytes != null) { + // Tampilkan gambar yang diambil dari server + return Column( + children: [ + Image.memory( + _currentImageBytes!, + height: 150, + fit: BoxFit.cover, + errorBuilder: (context, error, stackTrace) { + print('Error displaying current image: $error'); + return Text('Gagal memuat gambar saat ini'); + }, + ), + SizedBox(height: 8), + Text('Gambar saat ini', style: TextStyle(fontStyle: FontStyle.italic)), + ], + ); + } else if (widget.idHama > 0) { + // Coba tampilkan gambar dari ID menggunakan komponen terpisah + return Column( + children: [ + ImageUtilities.buildHamaImage(widget.idHama, height: 150), + SizedBox(height: 8), + Text('Gambar saat ini', style: TextStyle(fontStyle: FontStyle.italic)), + ], + ); + } else { + // Tampilkan placeholder + return Container( + height: 150, + width: double.infinity, + color: Colors.grey[300], + child: const Center( + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Icon(Icons.image_not_supported, size: 40, color: Colors.grey), + SizedBox(height: 8), + Text('Tidak ada gambar tersedia'), + ], + ), + ), ); } } @@ -66,9 +228,7 @@ class _EditHamaPageState extends State { @override Widget build(BuildContext context) { return Scaffold( - appBar: AppBar( - title: Text('Edit Data Hama'), - ), + appBar: AppBar(title: Text('Edit Data Hama')), body: SingleChildScrollView( child: Padding( padding: const EdgeInsets.all(16.0), @@ -97,6 +257,31 @@ class _EditHamaPageState extends State { maxLines: 3, ), SizedBox(height: 20), + Text( + 'Foto Hama', + style: TextStyle( + fontWeight: FontWeight.bold, + fontSize: 16, + ), + ), + SizedBox(height: 8), + _buildImagePreview(), + SizedBox(height: 12), + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + ElevatedButton.icon( + onPressed: _pickImage, + icon: Icon(Icons.photo_library), + label: Text('Pilih Gambar'), + style: ElevatedButton.styleFrom( + backgroundColor: Colors.blue, + foregroundColor: Colors.white, + ), + ), + ], + ), + ElevatedButton( onPressed: _updateHama, style: ElevatedButton.styleFrom( diff --git a/frontend/lib/admin/edit_penyakit_page.dart b/frontend/lib/admin/edit_penyakit_page.dart index cddfc6f..f605fdf 100644 --- a/frontend/lib/admin/edit_penyakit_page.dart +++ b/frontend/lib/admin/edit_penyakit_page.dart @@ -1,11 +1,18 @@ import 'package:flutter/material.dart'; -import 'package:frontend/api_services/api_services.dart'; // Pastikan ini di-import ya +import 'package:frontend/api_services/api_services.dart'; +import 'dart:io'; +import 'dart:typed_data'; +import 'package:image_picker/image_picker.dart'; +import 'package:flutter/foundation.dart' show kIsWeb; +import 'image_utilities_penyakit.dart'; + class EditPenyakitPage extends StatefulWidget { final int idPenyakit; final String namaAwal; final String deskripsiAwal; final String penangananAwal; + final String gambarUrl; final VoidCallback onPenyakitUpdated; const EditPenyakitPage({ @@ -14,6 +21,7 @@ class EditPenyakitPage extends StatefulWidget { required this.namaAwal, required this.deskripsiAwal, required this.penangananAwal, + required this.gambarUrl, required this.onPenyakitUpdated, }) : super(key: key); @@ -26,6 +34,14 @@ class _EditPenyakitPageState extends State { final TextEditingController _deskripsiController = TextEditingController(); final TextEditingController _penangananController = TextEditingController(); final ApiService apiService = ApiService(); + final ImagePicker _picker = ImagePicker(); + + XFile? _pickedFile; + Uint8List? _webImage; + bool _isLoading = false; + String? _errorMessage; + bool _isImageLoading = false; + Uint8List? _currentImageBytes; @override void initState() { @@ -33,6 +49,38 @@ class _EditPenyakitPageState extends State { _namaController.text = widget.namaAwal; _deskripsiController.text = widget.deskripsiAwal; _penangananController.text = widget.penangananAwal; + _loadExistingImage(); + } + + Future _loadExistingImage() async { + if (widget.gambarUrl.isEmpty) return; + + setState(() { + _isImageLoading = true; + }); + + try { + // Coba mengambil gambar langsung dari API + final bytes = await apiService.getPenyakitImageBytes(widget.idPenyakit); + + if (bytes != null) { + setState(() { + _currentImageBytes = bytes; + _isImageLoading = false; + }); + } else { + setState(() { + _isImageLoading = false; + _errorMessage = "Gagal memuat gambar dari server"; + }); + } + } catch (e) { + print("Error loading image: $e"); + setState(() { + _isImageLoading = false; + _errorMessage = "Error: $e"; + }); + } } @override @@ -45,24 +93,139 @@ class _EditPenyakitPageState extends State { Future _updatePenyakit() async { try { + setState(() { + _isLoading = true; + _errorMessage = null; + }); + await apiService.updatePenyakit( widget.idPenyakit, _namaController.text, _deskripsiController.text, _penangananController.text, + _pickedFile, ); + + setState(() { + _isLoading = false; + }); + widget.onPenyakitUpdated(); Navigator.pop(context); ScaffoldMessenger.of(context).showSnackBar( - SnackBar(content: Text('Data penyakit berhasil diperbarui')), + SnackBar(content: Text('Data penyakit berhasil diperbarui')) ); } catch (e) { + setState(() { + _isLoading = false; + _errorMessage = 'Gagal memperbarui data: $e'; + }); + ScaffoldMessenger.of(context).showSnackBar( - SnackBar(content: Text('Gagal memperbarui data: $e')), + SnackBar(content: Text('Gagal memperbarui data: $e')) ); } } + Future _pickImage() async { + try { + final pickedFile = await _picker.pickImage(source: ImageSource.gallery); + if (pickedFile != null) { + _pickedFile = pickedFile; + print('Gambar dipilih: ${pickedFile.path}'); + + // Baca file sebagai bytes untuk ditampilkan di UI + final bytes = await pickedFile.readAsBytes(); + + setState(() { + _webImage = bytes; + // Hapus referensi ke gambar lama + _currentImageBytes = null; + }); + } else { + print('Tidak ada gambar dipilih'); + } + } catch (e) { + print('Error saat memilih gambar: $e'); + ScaffoldMessenger.of(context).showSnackBar( + SnackBar(content: Text('Gagal memilih gambar: $e')) + ); + } + } + + Widget _buildImagePreview() { + if (_isImageLoading) { + return Container( + height: 150, + color: Colors.grey[200], + child: const Center(child: CircularProgressIndicator()), + ); + } + if (_webImage != null) { + // Tampilkan gambar yang baru dipilih + return Column( + children: [ + Image.memory( + _webImage!, + height: 150, + fit: BoxFit.cover, + errorBuilder: (context, error, stackTrace) { + print('Error displaying selected image: $error'); + return Text('Gagal memuat gambar yang dipilih'); + }, + ), + SizedBox(height: 8), + Text('Gambar baru dipilih', style: TextStyle(fontStyle: FontStyle.italic)), + ], + ); + } else if (_currentImageBytes != null) { + // Tampilkan gambar yang diambil dari server + return Column( + children: [ + Image.memory( + _currentImageBytes!, + height: 150, + fit: BoxFit.cover, + errorBuilder: (context, error, stackTrace) { + print('Error displaying current image: $error'); + return Text('Gagal memuat gambar saat ini'); + }, + ), + SizedBox(height: 8), + Text('Gambar saat ini', style: TextStyle(fontStyle: FontStyle.italic)), + ], + ); + } else if (widget.idPenyakit > 0) { + // Coba tampilkan gambar dari ID menggunakan komponen terpisah + return Column( + children: [ + ImageUtilitiesPenyakit.buildPenyakitImage(widget.idPenyakit, height: 150), + SizedBox(height: 8), + Text('Gambar saat ini', style: TextStyle(fontStyle: FontStyle.italic)), + ], + ); + } else { + // Tampilkan placeholder + return Container( + height: 150, + width: double.infinity, + color: Colors.grey[300], + child: const Center( + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Icon(Icons.image_not_supported, size: 40, color: Colors.grey), + SizedBox(height: 8), + Text('Tidak ada gambar tersedia'), + ], + ), + ), + ); + } + } + + + @override Widget build(BuildContext context) { return Scaffold( @@ -97,6 +260,30 @@ class _EditPenyakitPageState extends State { maxLines: 3, ), SizedBox(height: 20), + Text( + 'Foto Penyakit', + style: TextStyle( + fontWeight: FontWeight.bold, + fontSize: 16, + ), + ), + SizedBox(height: 8), + _buildImagePreview(), + SizedBox(height: 12), + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + ElevatedButton.icon( + onPressed: _pickImage, + icon: Icon(Icons.photo_library), + label: Text('Pilih Gambar'), + style: ElevatedButton.styleFrom( + backgroundColor: Colors.blue, + foregroundColor: Colors.white, + ), + ), + ], + ), ElevatedButton( onPressed: _updatePenyakit, style: ElevatedButton.styleFrom( diff --git a/frontend/lib/admin/hama_page.dart b/frontend/lib/admin/hama_page.dart index c980dc2..8b5dec9 100644 --- a/frontend/lib/admin/hama_page.dart +++ b/frontend/lib/admin/hama_page.dart @@ -295,6 +295,7 @@ class _HamaPageState extends State { hama['deskripsi'] ?? '', penangananAwal: hama['penanganan'] ?? '', + gambarUrl: hama['foto'] ?? '', onHamaUpdated: _fetchHama, // fungsi untuk refresh list setelah update ), diff --git a/frontend/lib/admin/image_utilities.dart b/frontend/lib/admin/image_utilities.dart new file mode 100644 index 0000000..8de4056 --- /dev/null +++ b/frontend/lib/admin/image_utilities.dart @@ -0,0 +1,140 @@ +import 'package:flutter/material.dart'; +import 'package:frontend/api_services/api_services.dart'; // Sesuaikan dengan path import Anda + +class ImageUtilities { + static Widget buildHamaImage(int id, {double? width, double? height}) { + final ApiService apiService = ApiService(); + + return FutureBuilder( + future: apiService.isHamaImageAvailable(id), + builder: (context, snapshot) { + if (snapshot.connectionState == ConnectionState.waiting) { + return Container( + width: width, + height: height, + color: Colors.grey[200], + child: const Center(child: CircularProgressIndicator()), + ); + } + + final bool imageAvailable = snapshot.data ?? false; + if (!imageAvailable) { + return Container( + width: width, + height: height ?? 150, + color: Colors.grey[300], + child: const Center( + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Icon(Icons.image_not_supported, size: 40, color: Colors.grey), + SizedBox(height: 8), + Text('Gambar tidak tersedia'), + ], + ), + ), + ); + } + + return Image.network( + apiService.getHamaImageUrl(id), + width: width, + height: height, + fit: BoxFit.cover, + errorBuilder: (context, error, stackTrace) { + print('Error loading image: $error'); + return Container( + width: width, + height: height ?? 150, + color: Colors.grey[300], + child: Center( + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Icon(Icons.error_outline, size: 40, color: Colors.red), + SizedBox(height: 8), + Text('Tidak dapat memuat gambar'), + SizedBox(height: 4), + Text( + error.toString(), + style: TextStyle(fontSize: 10, color: Colors.red[700]), + maxLines: 2, + overflow: TextOverflow.ellipsis, + ), + ], + ), + ), + ); + }, + loadingBuilder: (context, child, loadingProgress) { + if (loadingProgress == null) return child; + return Container( + width: width, + height: height ?? 150, + color: Colors.grey[200], + child: Center( + child: CircularProgressIndicator( + value: loadingProgress.expectedTotalBytes != null + ? loadingProgress.cumulativeBytesLoaded / + loadingProgress.expectedTotalBytes! + : null, + ), + ), + ); + }, + ); + }, + ); + } + + // Widget untuk menampilkan gambar secara langsung dari URL (tanpa pengecekan ketersediaan) + static Widget buildImageFromUrl(String imageUrl, {double? width, double? height}) { + return Image.network( + imageUrl, + width: width, + height: height ?? 150, + fit: BoxFit.cover, + errorBuilder: (context, error, stackTrace) { + print('Error loading image from URL: $error'); + return Container( + width: width, + height: height ?? 150, + color: Colors.grey[300], + child: Center( + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Icon(Icons.error_outline, size: 40, color: Colors.red), + SizedBox(height: 8), + Text('Gagal memuat gambar'), + SizedBox(height: 4), + Text( + error.toString(), + style: TextStyle(fontSize: 10, color: Colors.red[700]), + maxLines: 2, + overflow: TextOverflow.ellipsis, + ), + ], + ), + ), + ); + }, + loadingBuilder: (context, child, loadingProgress) { + if (loadingProgress == null) return child; + return Container( + width: width, + height: height ?? 150, + color: Colors.grey[200], + child: Center( + child: CircularProgressIndicator( + value: loadingProgress.expectedTotalBytes != null + ? loadingProgress.cumulativeBytesLoaded / + loadingProgress.expectedTotalBytes! + : null, + ), + ), + ); + }, + ); + } +} \ No newline at end of file diff --git a/frontend/lib/admin/image_utilities_penyakit.dart b/frontend/lib/admin/image_utilities_penyakit.dart new file mode 100644 index 0000000..d7f7fa5 --- /dev/null +++ b/frontend/lib/admin/image_utilities_penyakit.dart @@ -0,0 +1,140 @@ +import 'package:flutter/material.dart'; +import 'package:frontend/api_services/api_services.dart'; // Sesuaikan dengan path import Anda + +class ImageUtilitiesPenyakit { + static Widget buildPenyakitImage(int id, {double? width, double? height}) { + final ApiService apiService = ApiService(); + + return FutureBuilder( + future: apiService.isPenyakitImageAvailable(id), + builder: (context, snapshot) { + if (snapshot.connectionState == ConnectionState.waiting) { + return Container( + width: width, + height: height, + color: Colors.grey[200], + child: const Center(child: CircularProgressIndicator()), + ); + } + + final bool imageAvailable = snapshot.data ?? false; + if (!imageAvailable) { + return Container( + width: width, + height: height ?? 150, + color: Colors.grey[300], + child: const Center( + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Icon(Icons.image_not_supported, size: 40, color: Colors.grey), + SizedBox(height: 8), + Text('Gambar tidak tersedia'), + ], + ), + ), + ); + } + + return Image.network( + apiService.getPenyakitImageUrl(id), + width: width, + height: height, + fit: BoxFit.cover, + errorBuilder: (context, error, stackTrace) { + print('Error loading image: $error'); + return Container( + width: width, + height: height ?? 150, + color: Colors.grey[300], + child: Center( + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Icon(Icons.error_outline, size: 40, color: Colors.red), + SizedBox(height: 8), + Text('Tidak dapat memuat gambar'), + SizedBox(height: 4), + Text( + error.toString(), + style: TextStyle(fontSize: 10, color: Colors.red[700]), + maxLines: 2, + overflow: TextOverflow.ellipsis, + ), + ], + ), + ), + ); + }, + loadingBuilder: (context, child, loadingProgress) { + if (loadingProgress == null) return child; + return Container( + width: width, + height: height ?? 150, + color: Colors.grey[200], + child: Center( + child: CircularProgressIndicator( + value: loadingProgress.expectedTotalBytes != null + ? loadingProgress.cumulativeBytesLoaded / + loadingProgress.expectedTotalBytes! + : null, + ), + ), + ); + }, + ); + }, + ); + } + + // Widget untuk menampilkan gambar secara langsung dari URL (tanpa pengecekan ketersediaan) + static Widget buildImageFromUrl(String imageUrl, {double? width, double? height}) { + return Image.network( + imageUrl, + width: width, + height: height ?? 150, + fit: BoxFit.cover, + errorBuilder: (context, error, stackTrace) { + print('Error loading image from URL: $error'); + return Container( + width: width, + height: height ?? 150, + color: Colors.grey[300], + child: Center( + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Icon(Icons.error_outline, size: 40, color: Colors.red), + SizedBox(height: 8), + Text('Gagal memuat gambar'), + SizedBox(height: 4), + Text( + error.toString(), + style: TextStyle(fontSize: 10, color: Colors.red[700]), + maxLines: 2, + overflow: TextOverflow.ellipsis, + ), + ], + ), + ), + ); + }, + loadingBuilder: (context, child, loadingProgress) { + if (loadingProgress == null) return child; + return Container( + width: width, + height: height ?? 150, + color: Colors.grey[200], + child: Center( + child: CircularProgressIndicator( + value: loadingProgress.expectedTotalBytes != null + ? loadingProgress.cumulativeBytesLoaded / + loadingProgress.expectedTotalBytes! + : null, + ), + ), + ); + }, + ); + } +} \ No newline at end of file diff --git a/frontend/lib/admin/penyakit_page.dart b/frontend/lib/admin/penyakit_page.dart index f5a21f7..66923b3 100644 --- a/frontend/lib/admin/penyakit_page.dart +++ b/frontend/lib/admin/penyakit_page.dart @@ -174,6 +174,8 @@ class _PenyakitPageState extends State { penyakit['deskripsi'] ?? '', penangananAwal: penyakit['penanganan'] ?? '', + gambarUrl: + penyakit['foto'] ?? '', onPenyakitUpdated: _fetchPenyakit, // fungsi untuk refresh list setelah update ), diff --git a/frontend/lib/admin/tambah_hama_page.dart b/frontend/lib/admin/tambah_hama_page.dart index d10e247..7566ad7 100644 --- a/frontend/lib/admin/tambah_hama_page.dart +++ b/frontend/lib/admin/tambah_hama_page.dart @@ -1,5 +1,10 @@ import 'package:flutter/material.dart'; import 'package:frontend/api_services/api_services.dart'; +import 'package:image_picker/image_picker.dart'; +import 'dart:io'; // untuk File +import 'package:image_picker/image_picker.dart'; // untuk ImagePicker & ImageSource +import 'package:flutter/foundation.dart' show kIsWeb, Uint8List; + class TambahHamaPage extends StatefulWidget { final VoidCallback onHamaAdded; @@ -15,6 +20,12 @@ class _TambahHamaPageState extends State { final TextEditingController deskripsiController = TextEditingController(); final TextEditingController penangananController = TextEditingController(); final ApiService apiService = ApiService(); + final ImagePicker _picker = ImagePicker(); + File? _imageFile; + String? _gambarUrl; + // Untuk web + Uint8List? _webImage; + XFile? _pickedFile; @override void dispose() { @@ -33,6 +44,7 @@ class _TambahHamaPageState extends State { namaController.text, deskripsiController.text, penangananController.text, + _pickedFile, ); widget.onHamaAdded(); Navigator.pop(context); @@ -62,6 +74,29 @@ class _TambahHamaPageState extends State { ); } + Future _pickImage() async { + final pickedFile = await _picker.pickImage(source: ImageSource.gallery); + if (pickedFile != null) { + _pickedFile = pickedFile; + print('Gambar dipilih: ${pickedFile.path}'); + + // Baca file sebagai bytes untuk ditampilkan di web + final bytes = await pickedFile.readAsBytes(); + + setState(() { + // Simpan data gambar sebagai bytes untuk ditampilkan + _webImage = bytes; + + // Jika bukan di web, buat File object (untuk Android/iOS) + if (!kIsWeb) { + _imageFile = File(pickedFile.path); + } + }); + } else { + print('Tidak ada gambar dipilih'); + } + } + @override Widget build(BuildContext context) { return Scaffold( @@ -101,6 +136,30 @@ class _TambahHamaPageState extends State { maxLines: 3, ), SizedBox(height: 15), + Text('Foto'), + (_webImage != null) + ? Image.memory( + _webImage!, + height: 150, + errorBuilder: (context, error, stackTrace) { + print('Error displaying image: $error'); + return Text('Gagal memuat gambar'); + }, + ) + : (_gambarUrl != null && _gambarUrl!.isNotEmpty) + ? Image.network( + _gambarUrl!, + height: 150, + errorBuilder: (context, error, stackTrace) { + print('Error loading network image: $error'); + return Text('Gagal memuat gambar dari server'); + }, + ) + : Text('Tidak ada gambar tersedia'), + TextButton( + onPressed: _pickImage, + child: Text('Pilih Gambar'), + ), ], ), ), diff --git a/frontend/lib/admin/tambah_penyakit_page.dart b/frontend/lib/admin/tambah_penyakit_page.dart index d6ee55c..d908fe4 100644 --- a/frontend/lib/admin/tambah_penyakit_page.dart +++ b/frontend/lib/admin/tambah_penyakit_page.dart @@ -1,5 +1,9 @@ import 'package:flutter/material.dart'; import 'package:frontend/api_services/api_services.dart'; +import 'package:image_picker/image_picker.dart'; +import 'dart:io'; // untuk File +import 'package:image_picker/image_picker.dart'; // untuk ImagePicker & ImageSource +import 'package:flutter/foundation.dart' show kIsWeb, Uint8List; class TambahPenyakitPage extends StatefulWidget { final VoidCallback onPenyakitAdded; @@ -15,6 +19,12 @@ class _TambahPenyakitPageState extends State { final TextEditingController deskripsiController = TextEditingController(); final TextEditingController penangananController = TextEditingController(); final ApiService apiService = ApiService(); + final ImagePicker _picker = ImagePicker(); + File? _imageFile; + String? _gambarUrl; + // Untuk web + Uint8List? _webImage; + XFile? _pickedFile; @override void dispose() { @@ -33,6 +43,7 @@ class _TambahPenyakitPageState extends State { namaController.text, deskripsiController.text, penangananController.text, + _pickedFile, ); widget.onPenyakitAdded(); Navigator.pop(context); @@ -62,6 +73,29 @@ class _TambahPenyakitPageState extends State { ); } + Future _pickImage() async { + final pickedFile = await _picker.pickImage(source: ImageSource.gallery); + if (pickedFile != null) { + _pickedFile = pickedFile; + print('Gambar dipilih: ${pickedFile.path}'); + + // Baca file sebagai bytes untuk ditampilkan di web + final bytes = await pickedFile.readAsBytes(); + + setState(() { + // Simpan data gambar sebagai bytes untuk ditampilkan + _webImage = bytes; + + // Jika bukan di web, buat File object (untuk Android/iOS) + if (!kIsWeb) { + _imageFile = File(pickedFile.path); + } + }); + } else { + print('Tidak ada gambar dipilih'); + } + } + @override Widget build(BuildContext context) { return Scaffold( @@ -101,6 +135,29 @@ class _TambahPenyakitPageState extends State { maxLines: 3, ), SizedBox(height: 15), + (_webImage != null) + ? Image.memory( + _webImage!, + height: 150, + errorBuilder: (context, error, stackTrace) { + print('Error displaying image: $error'); + return Text('Gagal memuat gambar'); + }, + ) + : (_gambarUrl != null && _gambarUrl!.isNotEmpty) + ? Image.network( + _gambarUrl!, + height: 150, + errorBuilder: (context, error, stackTrace) { + print('Error loading network image: $error'); + return Text('Gagal memuat gambar dari server'); + }, + ) + : Text('Tidak ada gambar tersedia'), + TextButton( + onPressed: _pickImage, + child: Text('Pilih Gambar'), + ), ], ), ), diff --git a/frontend/lib/api_services/api_services.dart b/frontend/lib/api_services/api_services.dart index 241dab1..477b4bb 100644 --- a/frontend/lib/api_services/api_services.dart +++ b/frontend/lib/api_services/api_services.dart @@ -1,6 +1,10 @@ import 'dart:convert'; +import 'dart:io'; +import 'dart:typed_data'; import 'package:http/http.dart' as http; import 'package:shared_preferences/shared_preferences.dart'; +import 'package:image_picker/image_picker.dart'; +import 'package:http_parser/http_parser.dart'; class ApiService { static const String baseUrl = 'http://localhost:5000/api/auth'; @@ -9,6 +13,7 @@ class ApiService { static const String penyakitUrl = 'http://localhost:5000/api/penyakit'; static const String rulesPenyakitUrl ='http://localhost:5000/api/rules_penyakit'; static const String rulesHamaUrl = 'http://localhost:5000/api/rules_hama'; + static const Duration timeout = Duration(seconds: 15); // Fungsi Login (dengan perbaikan) static Future> loginUser( @@ -118,9 +123,10 @@ class ApiService { } // Ambil semua hama + // Get all hama Future>> getHama() async { try { - final response = await http.get(Uri.parse(ApiService.hamaUrl)); + final response = await http.get(Uri.parse(hamaUrl)).timeout(timeout); if (response.statusCode == 200) { final responseData = jsonDecode(response.body); @@ -137,39 +143,183 @@ class ApiService { throw Exception("Format respons API tidak sesuai"); } } else { - throw Exception("Gagal mengambil data hama"); + print('Error response: ${response.statusCode} - ${response.body}'); + throw Exception("Gagal mengambil data hama (Status: ${response.statusCode})"); } } catch (e) { print("Error getHama: $e"); - throw Exception("Gagal mengambil data hama"); + throw Exception("Gagal mengambil data hama: $e"); } } + Future> getHamaById(int id) async { + try { + final response = await http.get(Uri.parse('$hamaUrl/$id')); + print('Fetching hama with ID $id from $hamaUrl/$id'); + + if (response.statusCode == 200) { + final responseData = jsonDecode(response.body); + print('Response data: $responseData'); + + // Periksa format respons + if (responseData is Map && + responseData.containsKey("data")) { + final data = responseData["data"]; + return Map.from(data); + } else if (responseData is Map) { + // Jika langsung mengembalikan objek tanpa wrapper "data" + return responseData; + } else { + throw Exception("Format respons API tidak sesuai"); + } + } else { + print('Error response: ${response.statusCode} - ${response.body}'); + throw Exception( + "Gagal mengambil data hama dengan ID $id (Status: ${response.statusCode})", + ); + } + } catch (e) { + print("Error getHamaById: $e"); + throw Exception("Gagal mengambil data hama dengan ID $id: $e"); + } + } + + // Fungsi untuk mendapatkan URL gambar hama + String getHamaImageUrl(int id) { + return '$hamaUrl/$id/image'; + } + + // Fungsi untuk mengecek apakah gambar tersedia + Future isHamaImageAvailable(int id) async { + try { + final url = Uri.parse(getHamaImageUrl(id)); + print('Checking image availability: $url'); + final response = await http.head(url); + print('Image availability status: ${response.statusCode}'); + return response.statusCode == 200; + } catch (e) { + print("Error checking image availability: $e"); + return false; + } + } + + // Fungsi untuk mengambil gambar hama sebagai bytes + Future getHamaImageBytes(int id) async { + try { + final url = Uri.parse(getHamaImageUrl(id)); + print('Fetching image bytes from: $url'); + final response = await http.get(url); + + if (response.statusCode == 200) { + return response.bodyBytes; + } else { + print('Failed to get image bytes: ${response.statusCode}'); + print( + 'Response body: ${response.body}', + ); // Tambahkan ini untuk melihat pesan error + return null; + } + } catch (e) { + print('Error getting image bytes: $e'); + return null; + } + } + + Future getHamaImageBytesByFilename(String filename) async { + try { + final url = Uri.parse('http://localhost:5000/image_hama/$filename'); + print('Fetching image from: $url'); + final response = await http.get(url); + + if (response.statusCode == 200) { + return response.bodyBytes; + } else { + print('Failed to fetch image. Status: ${response.statusCode}'); + print('Response body: ${response.body}'); + return null; + } + } catch (e) { + print('Error fetching image by filename: $e'); + return null; + } +} + + // Tambah hama baru (kode otomatis) Future> createHama( String nama, String deskripsi, String penanganan, + XFile? pickedFile, ) async { try { - final response = await http.post( - Uri.parse(hamaUrl), - headers: {"Content-Type": "application/json"}, - body: jsonEncode({ - "nama": nama, - "deskripsi": deskripsi, - "penanganan": penanganan, - }), - ); + var uri = Uri.parse(hamaUrl); + var request = http.MultipartRequest('POST', uri); + + request.fields['nama'] = nama; + request.fields['deskripsi'] = deskripsi; + request.fields['penanganan'] = penanganan; + + print('Mengirim request ke: $uri'); + print('Dengan fields: ${request.fields}'); + + if (pickedFile != null) { + String mimeType = 'image/jpeg'; + String fileName = pickedFile.name; + + if (fileName.isEmpty) { + fileName = pickedFile.path.split('/').last; + } + + if (fileName.toLowerCase().endsWith('.png')) { + mimeType = 'image/png'; + } else if (fileName.toLowerCase().endsWith('.jpg') || + fileName.toLowerCase().endsWith('.jpeg')) { + mimeType = 'image/jpeg'; + } + + final bytes = await pickedFile.readAsBytes(); + + request.files.add( + http.MultipartFile.fromBytes( + 'foto', // Sesuaikan dengan field name yang diterima backend + bytes, + filename: fileName, + contentType: MediaType.parse(mimeType), + ), + ); + + print('Menambahkan file: $fileName (${bytes.length} bytes)'); + } else { + print('Tidak ada file yang dilampirkan'); + } + + var streamedResponse = await request.send(); + var response = await http.Response.fromStream(streamedResponse); + + print('Status response: ${response.statusCode}'); + print('Body response: ${response.body}'); if (response.statusCode == 201) { return jsonDecode(response.body); } else { - throw Exception('Gagal menambahkan hama'); + String errorMessage = + 'Gagal menambahkan hama (kode: ${response.statusCode})'; + try { + var errorBody = jsonDecode(response.body); + if (errorBody is Map && errorBody.containsKey('message')) { + errorMessage = errorBody['message']; + } + } catch (e) { + if (response.body.isNotEmpty) { + errorMessage = response.body; + } + } + throw Exception(errorMessage); } } catch (e) { - print('Error createHama: $e'); - throw Exception('Gagal menambahkan hama'); + print('Error dalam createHama: $e'); + throw Exception('Gagal menambahkan hama: $e'); } } @@ -179,26 +329,85 @@ class ApiService { String nama, String deskripsi, String penanganan, + XFile? pickedFile, ) async { try { - final response = await http.put( - Uri.parse('$hamaUrl/$id'), - headers: {"Content-Type": "application/json"}, - body: jsonEncode({ - "nama": nama, - "deskripsi": deskripsi, - "penanganan": penanganan, - }), - ); + var uri = Uri.parse('$hamaUrl/$id'); + var request = http.MultipartRequest('PUT', uri); + + // Tambahkan fields untuk data teks + request.fields['nama'] = nama; + request.fields['deskripsi'] = deskripsi; + request.fields['penanganan'] = penanganan; + + // Log untuk debugging + print('Mengirim request ke: $uri'); + print('Dengan fields: ${request.fields}'); + + if (pickedFile != null) { + // Dapatkan tipe MIME berdasarkan ekstensi file + String mimeType = 'image/jpeg'; // Default + String fileName = pickedFile.name; + + if (fileName.isEmpty) { + fileName = pickedFile.path.split('/').last; + } + + if (fileName.toLowerCase().endsWith('.png')) { + mimeType = 'image/png'; + } else if (fileName.toLowerCase().endsWith('.jpg') || + fileName.toLowerCase().endsWith('.jpeg')) { + mimeType = 'image/jpeg'; + } + + // Baca file sebagai bytes + final bytes = await pickedFile.readAsBytes(); + + // Tambahkan file ke request dengan tipe yang tepat + request.files.add( + http.MultipartFile.fromBytes( + 'foto', // Nama field ini harus sama dengan yang diharapkan backend + bytes, + filename: fileName, + contentType: MediaType.parse(mimeType), + ), + ); + + print('Menambahkan file: $fileName (${bytes.length} bytes)'); + } else { + print('Tidak ada file yang dilampirkan'); + } + + // Kirim request + var streamedResponse = await request.send(); + var response = await http.Response.fromStream(streamedResponse); + + // Debug response + print('Status response: ${response.statusCode}'); + print('Body response: ${response.body}'); if (response.statusCode == 200) { return jsonDecode(response.body); } else { - throw Exception('Gagal mengupdate hama'); + // Coba ambil pesan error dari response body + String errorMessage = + 'Gagal mengupdate hama (kode: ${response.statusCode})'; + try { + var errorBody = jsonDecode(response.body); + if (errorBody is Map && errorBody.containsKey('message')) { + errorMessage = errorBody['message']; + } + } catch (e) { + // Jika gagal parse JSON, gunakan response body langsung + if (response.body.isNotEmpty) { + errorMessage = response.body; + } + } + throw Exception(errorMessage); } } catch (e) { - print('Error updateHama: $e'); - throw Exception('Gagal mengupdate hama'); + print('Error dalam updateHama: $e'); + throw Exception('Gagal mengupdate hama: $e'); } } @@ -243,60 +452,241 @@ class ApiService { } } + Future> getPenyakitById(int id) async { + try { + final response = await http.get(Uri.parse('$penyakitUrl/$id')); + print('Fetching penyakit with ID $id from $penyakitUrl/$id'); + + if (response.statusCode == 200) { + final responseData = jsonDecode(response.body); + print('Response data: $responseData'); + + // Periksa format respons + if (responseData is Map && + responseData.containsKey("data")) { + final data = responseData["data"]; + return Map.from(data); + } else if (responseData is Map) { + // Jika langsung mengembalikan objek tanpa wrapper "data" + return responseData; + } else { + throw Exception("Format respons API tidak sesuai"); + } + } else { + print('Error response: ${response.statusCode} - ${response.body}'); + throw Exception( + "Gagal mengambil data penyakit dengan ID $id (Status: ${response.statusCode})", + ); + } + } catch (e) { + print("Error getPenyakitById: $e"); + throw Exception("Gagal mengambil data penyakit dengan ID $id: $e"); + } + } + + // Fungsi untuk mendapatkan URL gambar penyakit + String getPenyakitImageUrl(int id) { + return '$penyakitUrl/$id/image'; + } + + // Fungsi untuk mengecek apakah gambar tersedia + Future isPenyakitImageAvailable(int id) async { + try { + final url = Uri.parse(getHamaImageUrl(id)); + print('Checking image availability: $url'); + final response = await http.head(url); + print('Image availability status: ${response.statusCode}'); + return response.statusCode == 200; + } catch (e) { + print("Error checking image availability: $e"); + return false; + } + } + + Future getPenyakitImageBytes(int id) async { + try { + final url = Uri.parse(getPenyakitImageUrl(id)); + print('Fetching image bytes from: $url'); + final response = await http.get(url); + + if (response.statusCode == 200) { + return response.bodyBytes; + } else { + print('Failed to get image bytes: ${response.statusCode}'); + print( + 'Response body: ${response.body}', + ); // Tambahkan ini untuk melihat pesan error + return null; + } + } catch (e) { + print('Error getting image bytes: $e'); + return null; + } + } + // Tambah penyakit baru (kode otomatis) Future> createPenyakit( String nama, String deskripsi, String penanganan, + XFile? pickedFile, ) async { try { - final response = await http.post( - Uri.parse(penyakitUrl), - headers: {"Content-Type": "application/json"}, - body: jsonEncode({ - "nama": nama, - "deskripsi": deskripsi, - "penanganan": penanganan, - }), - ); + var uri = Uri.parse(penyakitUrl); + var request = http.MultipartRequest('POST', uri); + + request.fields['nama'] = nama; + request.fields['deskripsi'] = deskripsi; + request.fields['penanganan'] = penanganan; + + print('Mengirim request ke: $uri'); + print('Dengan fields: ${request.fields}'); + + if (pickedFile != null) { + String mimeType = 'image/jpeg'; + String fileName = pickedFile.name; + + if (fileName.isEmpty) { + fileName = pickedFile.path.split('/').last; + } + + if (fileName.toLowerCase().endsWith('.png')) { + mimeType = 'image/png'; + } else if (fileName.toLowerCase().endsWith('.jpg') || + fileName.toLowerCase().endsWith('.jpeg')) { + mimeType = 'image/jpeg'; + } + + final bytes = await pickedFile.readAsBytes(); + + request.files.add( + http.MultipartFile.fromBytes( + 'foto', // Sesuaikan dengan field name yang diterima backend + bytes, + filename: fileName, + contentType: MediaType.parse(mimeType), + ), + ); + + print('Menambahkan file: $fileName (${bytes.length} bytes)'); + } else { + print('Tidak ada file yang dilampirkan'); + } + + var streamedResponse = await request.send(); + var response = await http.Response.fromStream(streamedResponse); + + print('Status response: ${response.statusCode}'); + print('Body response: ${response.body}'); if (response.statusCode == 201) { return jsonDecode(response.body); } else { - throw Exception('Gagal menambahkan penyakit'); + String errorMessage = + 'Gagal menambahkan penyakit (kode: ${response.statusCode})'; + try { + var errorBody = jsonDecode(response.body); + if (errorBody is Map && errorBody.containsKey('message')) { + errorMessage = errorBody['message']; + } + } catch (e) { + if (response.body.isNotEmpty) { + errorMessage = response.body; + } + } + throw Exception(errorMessage); } } catch (e) { - print('Error createPenyakit: $e'); - throw Exception('Gagal menambahkan penyakit'); + print('Error dalam createPenyakit: $e'); + throw Exception('Gagal menambahkan penyakit: $e'); } } // Update penyakit berdasarkan ID Future> updatePenyakit( - int id, + int id, String nama, String deskripsi, String penanganan, + XFile? pickedFile, ) async { try { - final response = await http.put( - Uri.parse('$penyakitUrl/$id'), - headers: {"Content-Type": "application/json"}, - body: jsonEncode({ - "nama": nama, - "deskripsi": deskripsi, - "penanganan": penanganan, - }), - ); + var uri = Uri.parse('$penyakitUrl/$id'); + var request = http.MultipartRequest('PUT', uri); + + // Tambahkan fields untuk data teks + request.fields['nama'] = nama; + request.fields['deskripsi'] = deskripsi; + request.fields['penanganan'] = penanganan; + + // Log untuk debugging + print('Mengirim request ke: $uri'); + print('Dengan fields: ${request.fields}'); + + if (pickedFile != null) { + // Dapatkan tipe MIME berdasarkan ekstensi file + String mimeType = 'image/jpeg'; // Default + String fileName = pickedFile.name; + + if (fileName.isEmpty) { + fileName = pickedFile.path.split('/').last; + } + + if (fileName.toLowerCase().endsWith('.png')) { + mimeType = 'image/png'; + } else if (fileName.toLowerCase().endsWith('.jpg') || + fileName.toLowerCase().endsWith('.jpeg')) { + mimeType = 'image/jpeg'; + } + + // Baca file sebagai bytes + final bytes = await pickedFile.readAsBytes(); + + // Tambahkan file ke request dengan tipe yang tepat + request.files.add( + http.MultipartFile.fromBytes( + 'foto', // Nama field ini harus sama dengan yang diharapkan backend + bytes, + filename: fileName, + contentType: MediaType.parse(mimeType), + ), + ); + + print('Menambahkan file: $fileName (${bytes.length} bytes)'); + } else { + print('Tidak ada file yang dilampirkan'); + } + + // Kirim request + var streamedResponse = await request.send(); + var response = await http.Response.fromStream(streamedResponse); + + // Debug response + print('Status response: ${response.statusCode}'); + print('Body response: ${response.body}'); if (response.statusCode == 200) { return jsonDecode(response.body); } else { - throw Exception('Gagal mengupdate penyakit'); + // Coba ambil pesan error dari response body + String errorMessage = + 'Gagal mengupdate hama (kode: ${response.statusCode})'; + try { + var errorBody = jsonDecode(response.body); + if (errorBody is Map && errorBody.containsKey('message')) { + errorMessage = errorBody['message']; + } + } catch (e) { + // Jika gagal parse JSON, gunakan response body langsung + if (response.body.isNotEmpty) { + errorMessage = response.body; + } + } + throw Exception(errorMessage); } } catch (e) { - print('Error updatePenyakit: $e'); - throw Exception('Gagal mengupdate penyakit'); + print('Error dalam updateHama: $e'); + throw Exception('Gagal mengupdate hama: $e'); } } @@ -418,9 +808,7 @@ class ApiService { // Delete Rule penyakit static Future deleteRulePenyakit(int id) async { - final response = await http.delete( - Uri.parse('$rulesPenyakitUrl/$id'), - ); + final response = await http.delete(Uri.parse('$rulesPenyakitUrl/$id')); return response; } @@ -498,9 +886,7 @@ class ApiService { // Delete Rule hama static Future deleteRuleHama(int id) async { - final response = await http.delete( - Uri.parse('$rulesHamaUrl/$id'), - ); + final response = await http.delete(Uri.parse('$rulesHamaUrl/$id')); return response; } } diff --git a/frontend/lib/user/detail_hama_page.dart b/frontend/lib/user/detail_hama_page.dart index a05370a..3b6690e 100644 --- a/frontend/lib/user/detail_hama_page.dart +++ b/frontend/lib/user/detail_hama_page.dart @@ -1,9 +1,105 @@ import 'package:flutter/material.dart'; +import 'package:frontend/api_services/api_services.dart'; +import 'dart:typed_data'; -class DetailHamaPage extends StatelessWidget { +class DetailHamaPage extends StatefulWidget { final Map detailHama; + final int? hamaId; - const DetailHamaPage({required this.detailHama}); + const DetailHamaPage({Key? key, required this.detailHama, this.hamaId}) + : super(key: key); + + @override + _DetailHamaPageState createState() => _DetailHamaPageState(); +} + +class _DetailHamaPageState extends State { + late Future> _detailHamaFuture; + late Map _currentDetailHama; + + @override + void initState() { + super.initState(); + _currentDetailHama = widget.detailHama; + + // Jika hamaId tersedia, fetch data terbaru dari API + if (widget.hamaId != null) { + _detailHamaFuture = _fetchDetailHama(widget.hamaId!); + } else { + // Jika tidak ada ID, gunakan data yang sudah diberikan + _detailHamaFuture = Future.value(widget.detailHama); + } + } + + Future> _fetchDetailHama(int id) async { + try { + final detailData = await ApiService().getHamaById(id); + setState(() { + _currentDetailHama = detailData; + }); + return detailData; + } catch (e) { + print('Error fetching detail hama: $e'); + // Jika gagal fetch, gunakan data yang sudah ada + return widget.detailHama; + } + } + + // Fungsi untuk memvalidasi URL gambar + bool _isValidImageUrl(String? url) { + if (url == null || url.isEmpty) return false; + + // Periksa apakah URL berakhir dengan ekstensi gambar yang umum + final validExtensions = ['.jpg', '.jpeg', '.png', '.gif', '.webp']; + return validExtensions.any((ext) => url.toLowerCase().endsWith(ext)); + } + + // Widget untuk menampilkan gambar dengan penanganan error yang lebih baik + Widget _buildImageWidget(String? filename) { + if (filename == null || filename.isEmpty) { + return Text("Tidak ada gambar tersedia"); + } + + return FutureBuilder( + future: ApiService().getHamaImageBytesByFilename(filename), + builder: (context, snapshot) { + if (snapshot.connectionState == ConnectionState.waiting) { + return CircularProgressIndicator(); + } else if (snapshot.hasError || snapshot.data == null) { + return Text("Gagal memuat gambar"); + } else { + return Image.memory(snapshot.data!, fit: BoxFit.cover); + } + }, + ); +} + + + // Widget untuk placeholder gambar + Widget _buildPlaceholderImage(String message, IconData icon) { + return Container( + height: 200, + width: double.infinity, + decoration: BoxDecoration( + color: Colors.grey[200], + borderRadius: BorderRadius.circular(12), + ), + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Icon(icon, size: 64, color: Colors.grey[600]), + SizedBox(height: 8), + Text( + message, + style: TextStyle( + color: Colors.grey[600], + fontWeight: FontWeight.bold, + ), + ), + ], + ), + ); + } @override Widget build(BuildContext context) { @@ -11,96 +107,123 @@ class DetailHamaPage extends StatelessWidget { backgroundColor: Color(0xFF9DC08D), appBar: AppBar( backgroundColor: Color(0xFF9DC08D), - title: Text( - "Detail Hama", - style: TextStyle(color: Colors.white), - ), + title: Text("Detail Hama", style: TextStyle(color: Colors.white)), leading: IconButton( icon: Icon(Icons.arrow_back, color: Colors.white), onPressed: () => Navigator.of(context).pop(), ), ), - body: SingleChildScrollView( - child: Padding( - padding: const EdgeInsets.all(16.0), - child: Column( - children: [ - if (detailHama["gambar"] != null && detailHama["gambar"]!.isNotEmpty) - ClipRRect( + body: FutureBuilder>( + future: _detailHamaFuture, + builder: (context, snapshot) { + if (snapshot.connectionState == ConnectionState.waiting) { + return Center( + child: CircularProgressIndicator(color: Colors.white), + ); + } + + if (snapshot.hasError) { + print('Error: ${snapshot.error}'); + // Tampilkan data yang sudah ada jika terjadi error + return _buildDetailContent(_currentDetailHama); + } + print("Snapshot data runtimeType: ${snapshot.data.runtimeType}"); + print("Snapshot data content: ${snapshot.data}"); + + // Jika berhasil fetch data baru, tampilkan data tersebut + final detailData = snapshot.data ?? _currentDetailHama; + return _buildDetailContent(detailData); + }, + ), + ); + } + + Widget _buildDetailContent(Map detailData) { + return SingleChildScrollView( + child: Padding( + padding: const EdgeInsets.all(16.0), + child: Column( + children: [ + // Tampilkan foto dari database dengan penanganan error yang lebih baik + _buildImageWidget(detailData["foto"]), + SizedBox(height: 16), + + // Card Nama Hama + SizedBox( + width: double.infinity, + child: Card( + elevation: 6, + shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(12), - child: Image.asset( - detailHama["gambar"]!, - height: 200, - width: 200, // Biar gambar full lebar - fit: BoxFit.cover, - ), ), - SizedBox(height: 16), - - // Card Nama Hama - SizedBox( - width: double.infinity, // Bikin card full lebar - child: Card( - elevation: 6, - shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)), - child: Padding( - padding: const EdgeInsets.all(16.0), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - "Nama Hama:", - style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold), + child: Padding( + padding: const EdgeInsets.all(16.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + "Nama Hama:", + style: TextStyle( + fontSize: 18, + fontWeight: FontWeight.bold, ), - SizedBox(height: 8), - Text( - detailHama["nama"] ?? "Nama hama tidak tersedia", - style: TextStyle(fontSize: 16), - ), - ], - ), + ), + SizedBox(height: 8), + Text( + detailData["nama"] ?? "Nama hama tidak tersedia", + style: TextStyle(fontSize: 16), + ), + ], ), ), ), - SizedBox(height: 16), + ), + SizedBox(height: 16), - // Card Deskripsi + Penanganan - SizedBox( - width: double.infinity, // Bikin card full lebar - child: Card( - elevation: 6, - shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)), - child: Padding( - padding: const EdgeInsets.all(16.0), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - "Deskripsi:", - style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold), + // Card Deskripsi + Penanganan + SizedBox( + width: double.infinity, + child: Card( + elevation: 6, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(12), + ), + child: Padding( + padding: const EdgeInsets.all(16.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + "Deskripsi:", + style: TextStyle( + fontSize: 18, + fontWeight: FontWeight.bold, ), - SizedBox(height: 8), - Text( - detailHama["deskripsi"] ?? "Deskripsi tidak tersedia", - style: TextStyle(fontSize: 16), + ), + SizedBox(height: 8), + Text( + detailData["deskripsi"] ?? "Deskripsi tidak tersedia", + style: TextStyle(fontSize: 16), + ), + SizedBox(height: 16), + Text( + "Penanganan:", + style: TextStyle( + fontSize: 18, + fontWeight: FontWeight.bold, ), - SizedBox(height: 16), - Text( - "Penanganan:", - style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold), - ), - SizedBox(height: 8), - Text( - detailHama["penanganan"] ?? "Penanganan tidak tersedia", - style: TextStyle(fontSize: 16), - ), - ], - ), + ), + SizedBox(height: 8), + Text( + detailData["penanganan"] ?? "Penanganan tidak tersedia", + style: TextStyle(fontSize: 16), + ), + ], ), ), ), - ], - ), + ), + ], ), ), ); diff --git a/frontend/lib/user/hama_page.dart b/frontend/lib/user/hama_page.dart index dc80897..e3106b6 100644 --- a/frontend/lib/user/hama_page.dart +++ b/frontend/lib/user/hama_page.dart @@ -13,7 +13,21 @@ class _HamaPageState extends State { @override void initState() { super.initState(); - _hamaListFuture = ApiService().getHama(); + _hamaListFuture = _fetchHamaData(); + } + + // Fungsi untuk mengambil dan memproses data hama + Future>> _fetchHamaData() async { + try { + // Mengambil data dari API + final hamaList = await ApiService().getHama(); + + // Data sudah lengkap dengan URL foto dari backend + return hamaList; + } catch (e) { + print('Error fetching hama data: $e'); + throw e; + } } @override @@ -54,24 +68,64 @@ class _HamaPageState extends State { return ListView.builder( itemCount: hamaList.length, itemBuilder: (context, index) { - final diagnosa = hamaList[index]; + final hama = hamaList[index]; return Card( elevation: 4, margin: const EdgeInsets.symmetric(vertical: 8), - child: ListTile( - title: Text( - diagnosa["nama"] ?? "Tidak ada data", - style: TextStyle(fontWeight: FontWeight.bold), - ), - subtitle: Text(diagnosa["deskripsi"] ?? "Deskripsi tidak tersedia"), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(10.0), + ), + child: InkWell( onTap: () { + // Navigasi ke halaman detail dengan ID dan data awal Navigator.push( context, MaterialPageRoute( - builder: (context) => DetailHamaPage(detailHama: diagnosa), + builder: (context) => DetailHamaPage( + detailHama: hama, + hamaId: hama["id"], // Kirim ID untuk fetch data detil + ), ), ); }, + child: Padding( + padding: const EdgeInsets.all(12.0), + child: Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + // Informasi hama + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + hama["nama"] ?? "Tidak ada data", + style: TextStyle( + fontWeight: FontWeight.bold, + fontSize: 16, + ), + ), + SizedBox(height: 4), + Text( + hama["deskripsi"] ?? "Deskripsi tidak tersedia", + maxLines: 2, + overflow: TextOverflow.ellipsis, + style: TextStyle( + color: Colors.black87, + ), + ), + ], + ), + ), + // Icon panah ke kanan + Icon( + Icons.arrow_forward_ios, + size: 16, + color: Colors.grey, + ), + ], + ), + ), ), ); }, @@ -82,4 +136,4 @@ class _HamaPageState extends State { ), ); } -} +} \ No newline at end of file diff --git a/frontend/linux/flutter/generated_plugin_registrant.cc b/frontend/linux/flutter/generated_plugin_registrant.cc index e71a16d..64a0ece 100644 --- a/frontend/linux/flutter/generated_plugin_registrant.cc +++ b/frontend/linux/flutter/generated_plugin_registrant.cc @@ -6,6 +6,10 @@ #include "generated_plugin_registrant.h" +#include void fl_register_plugins(FlPluginRegistry* registry) { + g_autoptr(FlPluginRegistrar) file_selector_linux_registrar = + fl_plugin_registry_get_registrar_for_plugin(registry, "FileSelectorPlugin"); + file_selector_plugin_register_with_registrar(file_selector_linux_registrar); } diff --git a/frontend/linux/flutter/generated_plugins.cmake b/frontend/linux/flutter/generated_plugins.cmake index 2e1de87..2db3c22 100644 --- a/frontend/linux/flutter/generated_plugins.cmake +++ b/frontend/linux/flutter/generated_plugins.cmake @@ -3,6 +3,7 @@ # list(APPEND FLUTTER_PLUGIN_LIST + file_selector_linux ) list(APPEND FLUTTER_FFI_PLUGIN_LIST diff --git a/frontend/macos/Flutter/GeneratedPluginRegistrant.swift b/frontend/macos/Flutter/GeneratedPluginRegistrant.swift index 724bb2a..ab1fdba 100644 --- a/frontend/macos/Flutter/GeneratedPluginRegistrant.swift +++ b/frontend/macos/Flutter/GeneratedPluginRegistrant.swift @@ -5,8 +5,10 @@ import FlutterMacOS import Foundation +import file_selector_macos import shared_preferences_foundation func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { + FileSelectorPlugin.register(with: registry.registrar(forPlugin: "FileSelectorPlugin")) SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin")) } diff --git a/frontend/pubspec.lock b/frontend/pubspec.lock index f6ab4cf..8967569 100644 --- a/frontend/pubspec.lock +++ b/frontend/pubspec.lock @@ -41,6 +41,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.19.1" + cross_file: + dependency: transitive + description: + name: cross_file + sha256: "7caf6a750a0c04effbb52a676dce9a4a592e10ad35c34d6d2d0e4811160d5670" + url: "https://pub.dev" + source: hosted + version: "0.3.4+2" cupertino_icons: dependency: "direct main" description: @@ -73,6 +81,38 @@ packages: url: "https://pub.dev" source: hosted version: "7.0.1" + file_selector_linux: + dependency: transitive + description: + name: file_selector_linux + sha256: "54cbbd957e1156d29548c7d9b9ec0c0ebb6de0a90452198683a7d23aed617a33" + url: "https://pub.dev" + source: hosted + version: "0.9.3+2" + file_selector_macos: + dependency: transitive + description: + name: file_selector_macos + sha256: "271ab9986df0c135d45c3cdb6bd0faa5db6f4976d3e4b437cf7d0f258d941bfc" + url: "https://pub.dev" + source: hosted + version: "0.9.4+2" + file_selector_platform_interface: + dependency: transitive + description: + name: file_selector_platform_interface + sha256: a3994c26f10378a039faa11de174d7b78eb8f79e4dd0af2a451410c1a5c3f66b + url: "https://pub.dev" + source: hosted + version: "2.6.2" + file_selector_windows: + dependency: transitive + description: + name: file_selector_windows + sha256: "320fcfb6f33caa90f0b58380489fc5ac05d99ee94b61aa96ec2bff0ba81d3c2b" + url: "https://pub.dev" + source: hosted + version: "0.9.3+4" flutter: dependency: "direct main" description: flutter @@ -86,6 +126,14 @@ packages: url: "https://pub.dev" source: hosted version: "5.0.0" + flutter_plugin_android_lifecycle: + dependency: transitive + description: + name: flutter_plugin_android_lifecycle + sha256: f948e346c12f8d5480d2825e03de228d0eb8c3a737e4cdaa122267b89c022b5e + url: "https://pub.dev" + source: hosted + version: "2.0.28" flutter_test: dependency: "direct dev" description: flutter @@ -112,6 +160,70 @@ packages: url: "https://pub.dev" source: hosted version: "4.1.2" + image_picker: + dependency: "direct main" + description: + name: image_picker + sha256: "021834d9c0c3de46bf0fe40341fa07168407f694d9b2bb18d532dc1261867f7a" + url: "https://pub.dev" + source: hosted + version: "1.1.2" + image_picker_android: + dependency: transitive + description: + name: image_picker_android + sha256: "317a5d961cec5b34e777b9252393f2afbd23084aa6e60fcf601dcf6341b9ebeb" + url: "https://pub.dev" + source: hosted + version: "0.8.12+23" + image_picker_for_web: + dependency: transitive + description: + name: image_picker_for_web + sha256: "717eb042ab08c40767684327be06a5d8dbb341fe791d514e4b92c7bbe1b7bb83" + url: "https://pub.dev" + source: hosted + version: "3.0.6" + image_picker_ios: + dependency: transitive + description: + name: image_picker_ios + sha256: "05da758e67bc7839e886b3959848aa6b44ff123ab4b28f67891008afe8ef9100" + url: "https://pub.dev" + source: hosted + version: "0.8.12+2" + image_picker_linux: + dependency: transitive + description: + name: image_picker_linux + sha256: "34a65f6740df08bbbeb0a1abd8e6d32107941fd4868f67a507b25601651022c9" + url: "https://pub.dev" + source: hosted + version: "0.2.1+2" + image_picker_macos: + dependency: transitive + description: + name: image_picker_macos + sha256: "1b90ebbd9dcf98fb6c1d01427e49a55bd96b5d67b8c67cf955d60a5de74207c1" + url: "https://pub.dev" + source: hosted + version: "0.2.1+2" + image_picker_platform_interface: + dependency: transitive + description: + name: image_picker_platform_interface + sha256: "886d57f0be73c4b140004e78b9f28a8914a09e50c2d816bdd0520051a71236a0" + url: "https://pub.dev" + source: hosted + version: "2.10.1" + image_picker_windows: + dependency: transitive + description: + name: image_picker_windows + sha256: "6ad07afc4eb1bc25f3a01084d28520496c4a3bb0cb13685435838167c9dcedeb" + url: "https://pub.dev" + source: hosted + version: "0.2.1+1" leak_tracker: dependency: transitive description: @@ -168,6 +280,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.16.0" + mime: + dependency: transitive + description: + name: mime + sha256: "41a20518f0cb1256669420fdba0cd90d21561e560ac240f26ef8322e45bb7ed6" + url: "https://pub.dev" + source: hosted + version: "2.0.0" path: dependency: transitive description: diff --git a/frontend/pubspec.yaml b/frontend/pubspec.yaml index 3ba5131..f0ad098 100644 --- a/frontend/pubspec.yaml +++ b/frontend/pubspec.yaml @@ -30,12 +30,14 @@ environment: dependencies: flutter: sdk: flutter + # The following adds the Cupertino Icons font to your application. # Use with the CupertinoIcons class for iOS style icons. cupertino_icons: ^1.0.8 http: ^1.3.0 shared_preferences: ^2.5.2 + image_picker: ^1.1.2 dev_dependencies: flutter_test: diff --git a/frontend/windows/flutter/generated_plugin_registrant.cc b/frontend/windows/flutter/generated_plugin_registrant.cc index 8b6d468..77ab7a0 100644 --- a/frontend/windows/flutter/generated_plugin_registrant.cc +++ b/frontend/windows/flutter/generated_plugin_registrant.cc @@ -6,6 +6,9 @@ #include "generated_plugin_registrant.h" +#include void RegisterPlugins(flutter::PluginRegistry* registry) { + FileSelectorWindowsRegisterWithRegistrar( + registry->GetRegistrarForPlugin("FileSelectorWindows")); } diff --git a/frontend/windows/flutter/generated_plugins.cmake b/frontend/windows/flutter/generated_plugins.cmake index b93c4c3..a423a02 100644 --- a/frontend/windows/flutter/generated_plugins.cmake +++ b/frontend/windows/flutter/generated_plugins.cmake @@ -3,6 +3,7 @@ # list(APPEND FLUTTER_PLUGIN_LIST + file_selector_windows ) list(APPEND FLUTTER_FFI_PLUGIN_LIST diff --git a/node_modules/.bin/mkdirp b/node_modules/.bin/mkdirp new file mode 100644 index 0000000..1ab9c81 --- /dev/null +++ b/node_modules/.bin/mkdirp @@ -0,0 +1,16 @@ +#!/bin/sh +basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')") + +case `uname` in + *CYGWIN*|*MINGW*|*MSYS*) + if command -v cygpath > /dev/null 2>&1; then + basedir=`cygpath -w "$basedir"` + fi + ;; +esac + +if [ -x "$basedir/node" ]; then + exec "$basedir/node" "$basedir/../mkdirp/bin/cmd.js" "$@" +else + exec node "$basedir/../mkdirp/bin/cmd.js" "$@" +fi diff --git a/node_modules/.bin/mkdirp.cmd b/node_modules/.bin/mkdirp.cmd new file mode 100644 index 0000000..a865dd9 --- /dev/null +++ b/node_modules/.bin/mkdirp.cmd @@ -0,0 +1,17 @@ +@ECHO off +GOTO start +:find_dp0 +SET dp0=%~dp0 +EXIT /b +:start +SETLOCAL +CALL :find_dp0 + +IF EXIST "%dp0%\node.exe" ( + SET "_prog=%dp0%\node.exe" +) ELSE ( + SET "_prog=node" + SET PATHEXT=%PATHEXT:;.JS;=;% +) + +endLocal & goto #_undefined_# 2>NUL || title %COMSPEC% & "%_prog%" "%dp0%\..\mkdirp\bin\cmd.js" %* diff --git a/node_modules/.bin/mkdirp.ps1 b/node_modules/.bin/mkdirp.ps1 new file mode 100644 index 0000000..911e854 --- /dev/null +++ b/node_modules/.bin/mkdirp.ps1 @@ -0,0 +1,28 @@ +#!/usr/bin/env pwsh +$basedir=Split-Path $MyInvocation.MyCommand.Definition -Parent + +$exe="" +if ($PSVersionTable.PSVersion -lt "6.0" -or $IsWindows) { + # Fix case when both the Windows and Linux builds of Node + # are installed in the same directory + $exe=".exe" +} +$ret=0 +if (Test-Path "$basedir/node$exe") { + # Support pipeline input + if ($MyInvocation.ExpectingInput) { + $input | & "$basedir/node$exe" "$basedir/../mkdirp/bin/cmd.js" $args + } else { + & "$basedir/node$exe" "$basedir/../mkdirp/bin/cmd.js" $args + } + $ret=$LASTEXITCODE +} else { + # Support pipeline input + if ($MyInvocation.ExpectingInput) { + $input | & "node$exe" "$basedir/../mkdirp/bin/cmd.js" $args + } else { + & "node$exe" "$basedir/../mkdirp/bin/cmd.js" $args + } + $ret=$LASTEXITCODE +} +exit $ret diff --git a/node_modules/.package-lock.json b/node_modules/.package-lock.json new file mode 100644 index 0000000..d6c82c2 --- /dev/null +++ b/node_modules/.package-lock.json @@ -0,0 +1,225 @@ +{ + "name": "SistemPakar", + "lockfileVersion": 3, + "requires": true, + "packages": { + "node_modules/append-field": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/append-field/-/append-field-1.0.0.tgz", + "integrity": "sha512-klpgFSWLW1ZEs8svjfb7g4qWY0YS5imI82dTg+QahUvJ8YqAY0P10Uk8tTyh9ZGuYEZEMaeJYCF5BFuX552hsw==", + "license": "MIT" + }, + "node_modules/buffer-from": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", + "license": "MIT" + }, + "node_modules/busboy": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/busboy/-/busboy-1.6.0.tgz", + "integrity": "sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==", + "dependencies": { + "streamsearch": "^1.1.0" + }, + "engines": { + "node": ">=10.16.0" + } + }, + "node_modules/concat-stream": { + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz", + "integrity": "sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==", + "engines": [ + "node >= 0.8" + ], + "license": "MIT", + "dependencies": { + "buffer-from": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^2.2.2", + "typedarray": "^0.0.6" + } + }, + "node_modules/core-util-is": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", + "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", + "license": "MIT" + }, + "node_modules/image_picker": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/image_picker/-/image_picker-1.0.0.tgz", + "integrity": "sha512-kMx5lZ1TpgjnUPUJz/wCF8tpcYfZz4cSkuIoWraeaBR8Rb2MTziMqXKlDIS0FCWTdSLrtB3xjw/YXTj97mqOLw==", + "license": "ISC" + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "license": "ISC" + }, + "node_modules/isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", + "license": "MIT" + }, + "node_modules/media-typer": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/mkdirp": { + "version": "0.5.6", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", + "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", + "license": "MIT", + "dependencies": { + "minimist": "^1.2.6" + }, + "bin": { + "mkdirp": "bin/cmd.js" + } + }, + "node_modules/multer": { + "version": "1.4.5-lts.2", + "resolved": "https://registry.npmjs.org/multer/-/multer-1.4.5-lts.2.tgz", + "integrity": "sha512-VzGiVigcG9zUAoCNU+xShztrlr1auZOlurXynNvO9GiWD1/mTBbUljOKY+qMeazBqXgRnjzeEgJI/wyjJUHg9A==", + "license": "MIT", + "dependencies": { + "append-field": "^1.0.0", + "busboy": "^1.0.0", + "concat-stream": "^1.5.2", + "mkdirp": "^0.5.4", + "object-assign": "^4.1.1", + "type-is": "^1.6.4", + "xtend": "^4.0.0" + }, + "engines": { + "node": ">= 6.0.0" + } + }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/process-nextick-args": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", + "license": "MIT" + }, + "node_modules/readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "license": "MIT", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "license": "MIT" + }, + "node_modules/streamsearch": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-1.1.0.tgz", + "integrity": "sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==", + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/type-is": { + "version": "1.6.18", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", + "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", + "license": "MIT", + "dependencies": { + "media-typer": "0.3.0", + "mime-types": "~2.1.24" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/typedarray": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", + "integrity": "sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==", + "license": "MIT" + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "license": "MIT" + }, + "node_modules/xtend": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", + "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", + "license": "MIT", + "engines": { + "node": ">=0.4" + } + } + } +} diff --git a/node_modules/append-field/.npmignore b/node_modules/append-field/.npmignore new file mode 100644 index 0000000..c2658d7 --- /dev/null +++ b/node_modules/append-field/.npmignore @@ -0,0 +1 @@ +node_modules/ diff --git a/node_modules/append-field/LICENSE b/node_modules/append-field/LICENSE new file mode 100644 index 0000000..14b1f89 --- /dev/null +++ b/node_modules/append-field/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2015 Linus Unnebäck + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/node_modules/append-field/README.md b/node_modules/append-field/README.md new file mode 100644 index 0000000..62b901b --- /dev/null +++ b/node_modules/append-field/README.md @@ -0,0 +1,44 @@ +# `append-field` + +A [W3C HTML JSON forms spec](http://www.w3.org/TR/html-json-forms/) compliant +field appender (for lack of a better name). Useful for people implementing +`application/x-www-form-urlencoded` and `multipart/form-data` parsers. + +It works best on objects created with `Object.create(null)`. Otherwise it might +conflict with variables from the prototype (e.g. `hasOwnProperty`). + +## Installation + +```sh +npm install --save append-field +``` + +## Usage + +```javascript +var appendField = require('append-field') +var obj = Object.create(null) + +appendField(obj, 'pets[0][species]', 'Dahut') +appendField(obj, 'pets[0][name]', 'Hypatia') +appendField(obj, 'pets[1][species]', 'Felis Stultus') +appendField(obj, 'pets[1][name]', 'Billie') + +console.log(obj) +``` + +```text +{ pets: + [ { species: 'Dahut', name: 'Hypatia' }, + { species: 'Felis Stultus', name: 'Billie' } ] } +``` + +## API + +### `appendField(store, key, value)` + +Adds the field named `key` with the value `value` to the object `store`. + +## License + +MIT diff --git a/node_modules/append-field/index.js b/node_modules/append-field/index.js new file mode 100644 index 0000000..fc5acc8 --- /dev/null +++ b/node_modules/append-field/index.js @@ -0,0 +1,12 @@ +var parsePath = require('./lib/parse-path') +var setValue = require('./lib/set-value') + +function appendField (store, key, value) { + var steps = parsePath(key) + + steps.reduce(function (context, step) { + return setValue(context, step, context[step.key], value) + }, store) +} + +module.exports = appendField diff --git a/node_modules/append-field/lib/parse-path.js b/node_modules/append-field/lib/parse-path.js new file mode 100644 index 0000000..31d6179 --- /dev/null +++ b/node_modules/append-field/lib/parse-path.js @@ -0,0 +1,53 @@ +var reFirstKey = /^[^\[]*/ +var reDigitPath = /^\[(\d+)\]/ +var reNormalPath = /^\[([^\]]+)\]/ + +function parsePath (key) { + function failure () { + return [{ type: 'object', key: key, last: true }] + } + + var firstKey = reFirstKey.exec(key)[0] + if (!firstKey) return failure() + + var len = key.length + var pos = firstKey.length + var tail = { type: 'object', key: firstKey } + var steps = [tail] + + while (pos < len) { + var m + + if (key[pos] === '[' && key[pos + 1] === ']') { + pos += 2 + tail.append = true + if (pos !== len) return failure() + continue + } + + m = reDigitPath.exec(key.substring(pos)) + if (m !== null) { + pos += m[0].length + tail.nextType = 'array' + tail = { type: 'array', key: parseInt(m[1], 10) } + steps.push(tail) + continue + } + + m = reNormalPath.exec(key.substring(pos)) + if (m !== null) { + pos += m[0].length + tail.nextType = 'object' + tail = { type: 'object', key: m[1] } + steps.push(tail) + continue + } + + return failure() + } + + tail.last = true + return steps +} + +module.exports = parsePath diff --git a/node_modules/append-field/lib/set-value.js b/node_modules/append-field/lib/set-value.js new file mode 100644 index 0000000..c15e873 --- /dev/null +++ b/node_modules/append-field/lib/set-value.js @@ -0,0 +1,64 @@ +function valueType (value) { + if (value === undefined) return 'undefined' + if (Array.isArray(value)) return 'array' + if (typeof value === 'object') return 'object' + return 'scalar' +} + +function setLastValue (context, step, currentValue, entryValue) { + switch (valueType(currentValue)) { + case 'undefined': + if (step.append) { + context[step.key] = [entryValue] + } else { + context[step.key] = entryValue + } + break + case 'array': + context[step.key].push(entryValue) + break + case 'object': + return setLastValue(currentValue, { type: 'object', key: '', last: true }, currentValue[''], entryValue) + case 'scalar': + context[step.key] = [context[step.key], entryValue] + break + } + + return context +} + +function setValue (context, step, currentValue, entryValue) { + if (step.last) return setLastValue(context, step, currentValue, entryValue) + + var obj + switch (valueType(currentValue)) { + case 'undefined': + if (step.nextType === 'array') { + context[step.key] = [] + } else { + context[step.key] = Object.create(null) + } + return context[step.key] + case 'object': + return context[step.key] + case 'array': + if (step.nextType === 'array') { + return currentValue + } + + obj = Object.create(null) + context[step.key] = obj + currentValue.forEach(function (item, i) { + if (item !== undefined) obj['' + i] = item + }) + + return obj + case 'scalar': + obj = Object.create(null) + obj[''] = currentValue + context[step.key] = obj + return obj + } +} + +module.exports = setValue diff --git a/node_modules/append-field/package.json b/node_modules/append-field/package.json new file mode 100644 index 0000000..8d6e716 --- /dev/null +++ b/node_modules/append-field/package.json @@ -0,0 +1,19 @@ +{ + "name": "append-field", + "version": "1.0.0", + "license": "MIT", + "author": "Linus Unnebäck ", + "main": "index.js", + "devDependencies": { + "mocha": "^2.2.4", + "standard": "^6.0.5", + "testdata-w3c-json-form": "^0.2.0" + }, + "scripts": { + "test": "standard && mocha" + }, + "repository": { + "type": "git", + "url": "http://github.com/LinusU/node-append-field.git" + } +} diff --git a/node_modules/append-field/test/forms.js b/node_modules/append-field/test/forms.js new file mode 100644 index 0000000..dd6fbc9 --- /dev/null +++ b/node_modules/append-field/test/forms.js @@ -0,0 +1,19 @@ +/* eslint-env mocha */ + +var assert = require('assert') +var appendField = require('../') +var testData = require('testdata-w3c-json-form') + +describe('Append Field', function () { + for (var test of testData) { + it('handles ' + test.name, function () { + var store = Object.create(null) + + for (var field of test.fields) { + appendField(store, field.key, field.value) + } + + assert.deepEqual(store, test.expected) + }) + } +}) diff --git a/node_modules/buffer-from/LICENSE b/node_modules/buffer-from/LICENSE new file mode 100644 index 0000000..e4bf1d6 --- /dev/null +++ b/node_modules/buffer-from/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2016, 2018 Linus Unnebäck + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/node_modules/buffer-from/index.js b/node_modules/buffer-from/index.js new file mode 100644 index 0000000..e1a58b5 --- /dev/null +++ b/node_modules/buffer-from/index.js @@ -0,0 +1,72 @@ +/* eslint-disable node/no-deprecated-api */ + +var toString = Object.prototype.toString + +var isModern = ( + typeof Buffer !== 'undefined' && + typeof Buffer.alloc === 'function' && + typeof Buffer.allocUnsafe === 'function' && + typeof Buffer.from === 'function' +) + +function isArrayBuffer (input) { + return toString.call(input).slice(8, -1) === 'ArrayBuffer' +} + +function fromArrayBuffer (obj, byteOffset, length) { + byteOffset >>>= 0 + + var maxLength = obj.byteLength - byteOffset + + if (maxLength < 0) { + throw new RangeError("'offset' is out of bounds") + } + + if (length === undefined) { + length = maxLength + } else { + length >>>= 0 + + if (length > maxLength) { + throw new RangeError("'length' is out of bounds") + } + } + + return isModern + ? Buffer.from(obj.slice(byteOffset, byteOffset + length)) + : new Buffer(new Uint8Array(obj.slice(byteOffset, byteOffset + length))) +} + +function fromString (string, encoding) { + if (typeof encoding !== 'string' || encoding === '') { + encoding = 'utf8' + } + + if (!Buffer.isEncoding(encoding)) { + throw new TypeError('"encoding" must be a valid string encoding') + } + + return isModern + ? Buffer.from(string, encoding) + : new Buffer(string, encoding) +} + +function bufferFrom (value, encodingOrOffset, length) { + if (typeof value === 'number') { + throw new TypeError('"value" argument must not be a number') + } + + if (isArrayBuffer(value)) { + return fromArrayBuffer(value, encodingOrOffset, length) + } + + if (typeof value === 'string') { + return fromString(value, encodingOrOffset) + } + + return isModern + ? Buffer.from(value) + : new Buffer(value) +} + +module.exports = bufferFrom diff --git a/node_modules/buffer-from/package.json b/node_modules/buffer-from/package.json new file mode 100644 index 0000000..6ac5327 --- /dev/null +++ b/node_modules/buffer-from/package.json @@ -0,0 +1,19 @@ +{ + "name": "buffer-from", + "version": "1.1.2", + "license": "MIT", + "repository": "LinusU/buffer-from", + "files": [ + "index.js" + ], + "scripts": { + "test": "standard && node test" + }, + "devDependencies": { + "standard": "^12.0.1" + }, + "keywords": [ + "buffer", + "buffer from" + ] +} diff --git a/node_modules/buffer-from/readme.md b/node_modules/buffer-from/readme.md new file mode 100644 index 0000000..9880a55 --- /dev/null +++ b/node_modules/buffer-from/readme.md @@ -0,0 +1,69 @@ +# Buffer From + +A [ponyfill](https://ponyfill.com) for `Buffer.from`, uses native implementation if available. + +## Installation + +```sh +npm install --save buffer-from +``` + +## Usage + +```js +const bufferFrom = require('buffer-from') + +console.log(bufferFrom([1, 2, 3, 4])) +//=> + +const arr = new Uint8Array([1, 2, 3, 4]) +console.log(bufferFrom(arr.buffer, 1, 2)) +//=> + +console.log(bufferFrom('test', 'utf8')) +//=> + +const buf = bufferFrom('test') +console.log(bufferFrom(buf)) +//=> +``` + +## API + +### bufferFrom(array) + +- `array` <Array> + +Allocates a new `Buffer` using an `array` of octets. + +### bufferFrom(arrayBuffer[, byteOffset[, length]]) + +- `arrayBuffer` <ArrayBuffer> The `.buffer` property of a TypedArray or ArrayBuffer +- `byteOffset` <Integer> Where to start copying from `arrayBuffer`. **Default:** `0` +- `length` <Integer> How many bytes to copy from `arrayBuffer`. **Default:** `arrayBuffer.length - byteOffset` + +When passed a reference to the `.buffer` property of a TypedArray instance, the +newly created `Buffer` will share the same allocated memory as the TypedArray. + +The optional `byteOffset` and `length` arguments specify a memory range within +the `arrayBuffer` that will be shared by the `Buffer`. + +### bufferFrom(buffer) + +- `buffer` <Buffer> An existing `Buffer` to copy data from + +Copies the passed `buffer` data onto a new `Buffer` instance. + +### bufferFrom(string[, encoding]) + +- `string` <String> A string to encode. +- `encoding` <String> The encoding of `string`. **Default:** `'utf8'` + +Creates a new `Buffer` containing the given JavaScript string `string`. If +provided, the `encoding` parameter identifies the character encoding of +`string`. + +## See also + +- [buffer-alloc](https://github.com/LinusU/buffer-alloc) A ponyfill for `Buffer.alloc` +- [buffer-alloc-unsafe](https://github.com/LinusU/buffer-alloc-unsafe) A ponyfill for `Buffer.allocUnsafe` diff --git a/node_modules/busboy/.eslintrc.js b/node_modules/busboy/.eslintrc.js new file mode 100644 index 0000000..be9311d --- /dev/null +++ b/node_modules/busboy/.eslintrc.js @@ -0,0 +1,5 @@ +'use strict'; + +module.exports = { + extends: '@mscdex/eslint-config', +}; diff --git a/node_modules/busboy/.github/workflows/ci.yml b/node_modules/busboy/.github/workflows/ci.yml new file mode 100644 index 0000000..799bae0 --- /dev/null +++ b/node_modules/busboy/.github/workflows/ci.yml @@ -0,0 +1,24 @@ +name: CI + +on: + pull_request: + push: + branches: [ master ] + +jobs: + tests-linux: + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + node-version: [10.16.0, 10.x, 12.x, 14.x, 16.x] + steps: + - uses: actions/checkout@v2 + - name: Use Node.js ${{ matrix.node-version }} + uses: actions/setup-node@v1 + with: + node-version: ${{ matrix.node-version }} + - name: Install module + run: npm install + - name: Run tests + run: npm test diff --git a/node_modules/busboy/.github/workflows/lint.yml b/node_modules/busboy/.github/workflows/lint.yml new file mode 100644 index 0000000..9f9e1f5 --- /dev/null +++ b/node_modules/busboy/.github/workflows/lint.yml @@ -0,0 +1,23 @@ +name: lint + +on: + pull_request: + push: + branches: [ master ] + +env: + NODE_VERSION: 16.x + +jobs: + lint-js: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - name: Use Node.js ${{ env.NODE_VERSION }} + uses: actions/setup-node@v1 + with: + node-version: ${{ env.NODE_VERSION }} + - name: Install ESLint + ESLint configs/plugins + run: npm install --only=dev + - name: Lint files + run: npm run lint diff --git a/node_modules/busboy/LICENSE b/node_modules/busboy/LICENSE new file mode 100644 index 0000000..290762e --- /dev/null +++ b/node_modules/busboy/LICENSE @@ -0,0 +1,19 @@ +Copyright Brian White. All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to +deal in the Software without restriction, including without limitation the +rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +sell copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +IN THE SOFTWARE. \ No newline at end of file diff --git a/node_modules/busboy/README.md b/node_modules/busboy/README.md new file mode 100644 index 0000000..654af30 --- /dev/null +++ b/node_modules/busboy/README.md @@ -0,0 +1,191 @@ +# Description + +A node.js module for parsing incoming HTML form data. + +Changes (breaking or otherwise) in v1.0.0 can be found [here](https://github.com/mscdex/busboy/issues/266). + +# Requirements + +* [node.js](http://nodejs.org/) -- v10.16.0 or newer + + +# Install + + npm install busboy + + +# Examples + +* Parsing (multipart) with default options: + +```js +const http = require('http'); + +const busboy = require('busboy'); + +http.createServer((req, res) => { + if (req.method === 'POST') { + console.log('POST request'); + const bb = busboy({ headers: req.headers }); + bb.on('file', (name, file, info) => { + const { filename, encoding, mimeType } = info; + console.log( + `File [${name}]: filename: %j, encoding: %j, mimeType: %j`, + filename, + encoding, + mimeType + ); + file.on('data', (data) => { + console.log(`File [${name}] got ${data.length} bytes`); + }).on('close', () => { + console.log(`File [${name}] done`); + }); + }); + bb.on('field', (name, val, info) => { + console.log(`Field [${name}]: value: %j`, val); + }); + bb.on('close', () => { + console.log('Done parsing form!'); + res.writeHead(303, { Connection: 'close', Location: '/' }); + res.end(); + }); + req.pipe(bb); + } else if (req.method === 'GET') { + res.writeHead(200, { Connection: 'close' }); + res.end(` + + + +
+
+
+ +
+ + + `); + } +}).listen(8000, () => { + console.log('Listening for requests'); +}); + +// Example output: +// +// Listening for requests +// < ... form submitted ... > +// POST request +// File [filefield]: filename: "logo.jpg", encoding: "binary", mime: "image/jpeg" +// File [filefield] got 11912 bytes +// Field [textfield]: value: "testing! :-)" +// File [filefield] done +// Done parsing form! +``` + +* Save all incoming files to disk: + +```js +const { randomFillSync } = require('crypto'); +const fs = require('fs'); +const http = require('http'); +const os = require('os'); +const path = require('path'); + +const busboy = require('busboy'); + +const random = (() => { + const buf = Buffer.alloc(16); + return () => randomFillSync(buf).toString('hex'); +})(); + +http.createServer((req, res) => { + if (req.method === 'POST') { + const bb = busboy({ headers: req.headers }); + bb.on('file', (name, file, info) => { + const saveTo = path.join(os.tmpdir(), `busboy-upload-${random()}`); + file.pipe(fs.createWriteStream(saveTo)); + }); + bb.on('close', () => { + res.writeHead(200, { 'Connection': 'close' }); + res.end(`That's all folks!`); + }); + req.pipe(bb); + return; + } + res.writeHead(404); + res.end(); +}).listen(8000, () => { + console.log('Listening for requests'); +}); +``` + + +# API + +## Exports + +`busboy` exports a single function: + +**( _function_ )**(< _object_ >config) - Creates and returns a new _Writable_ form parser stream. + +* Valid `config` properties: + + * **headers** - _object_ - These are the HTTP headers of the incoming request, which are used by individual parsers. + + * **highWaterMark** - _integer_ - highWaterMark to use for the parser stream. **Default:** node's _stream.Writable_ default. + + * **fileHwm** - _integer_ - highWaterMark to use for individual file streams. **Default:** node's _stream.Readable_ default. + + * **defCharset** - _string_ - Default character set to use when one isn't defined. **Default:** `'utf8'`. + + * **defParamCharset** - _string_ - For multipart forms, the default character set to use for values of part header parameters (e.g. filename) that are not extended parameters (that contain an explicit charset). **Default:** `'latin1'`. + + * **preservePath** - _boolean_ - If paths in filenames from file parts in a `'multipart/form-data'` request shall be preserved. **Default:** `false`. + + * **limits** - _object_ - Various limits on incoming data. Valid properties are: + + * **fieldNameSize** - _integer_ - Max field name size (in bytes). **Default:** `100`. + + * **fieldSize** - _integer_ - Max field value size (in bytes). **Default:** `1048576` (1MB). + + * **fields** - _integer_ - Max number of non-file fields. **Default:** `Infinity`. + + * **fileSize** - _integer_ - For multipart forms, the max file size (in bytes). **Default:** `Infinity`. + + * **files** - _integer_ - For multipart forms, the max number of file fields. **Default:** `Infinity`. + + * **parts** - _integer_ - For multipart forms, the max number of parts (fields + files). **Default:** `Infinity`. + + * **headerPairs** - _integer_ - For multipart forms, the max number of header key-value pairs to parse. **Default:** `2000` (same as node's http module). + +This function can throw exceptions if there is something wrong with the values in `config`. For example, if the Content-Type in `headers` is missing entirely, is not a supported type, or is missing the boundary for `'multipart/form-data'` requests. + +## (Special) Parser stream events + +* **file**(< _string_ >name, < _Readable_ >stream, < _object_ >info) - Emitted for each new file found. `name` contains the form field name. `stream` is a _Readable_ stream containing the file's data. No transformations/conversions (e.g. base64 to raw binary) are done on the file's data. `info` contains the following properties: + + * `filename` - _string_ - If supplied, this contains the file's filename. **WARNING:** You should almost _never_ use this value as-is (especially if you are using `preservePath: true` in your `config`) as it could contain malicious input. You are better off generating your own (safe) filenames, or at the very least using a hash of the filename. + + * `encoding` - _string_ - The file's `'Content-Transfer-Encoding'` value. + + * `mimeType` - _string_ - The file's `'Content-Type'` value. + + **Note:** If you listen for this event, you should always consume the `stream` whether you care about its contents or not (you can simply do `stream.resume();` if you want to discard/skip the contents), otherwise the `'finish'`/`'close'` event will never fire on the busboy parser stream. + However, if you aren't accepting files, you can either simply not listen for the `'file'` event at all or set `limits.files` to `0`, and any/all files will be automatically skipped (these skipped files will still count towards any configured `limits.files` and `limits.parts` limits though). + + **Note:** If a configured `limits.fileSize` limit was reached for a file, `stream` will both have a boolean property `truncated` set to `true` (best checked at the end of the stream) and emit a `'limit'` event to notify you when this happens. + +* **field**(< _string_ >name, < _string_ >value, < _object_ >info) - Emitted for each new non-file field found. `name` contains the form field name. `value` contains the string value of the field. `info` contains the following properties: + + * `nameTruncated` - _boolean_ - Whether `name` was truncated or not (due to a configured `limits.fieldNameSize` limit) + + * `valueTruncated` - _boolean_ - Whether `value` was truncated or not (due to a configured `limits.fieldSize` limit) + + * `encoding` - _string_ - The field's `'Content-Transfer-Encoding'` value. + + * `mimeType` - _string_ - The field's `'Content-Type'` value. + +* **partsLimit**() - Emitted when the configured `limits.parts` limit has been reached. No more `'file'` or `'field'` events will be emitted. + +* **filesLimit**() - Emitted when the configured `limits.files` limit has been reached. No more `'file'` events will be emitted. + +* **fieldsLimit**() - Emitted when the configured `limits.fields` limit has been reached. No more `'field'` events will be emitted. diff --git a/node_modules/busboy/bench/bench-multipart-fields-100mb-big.js b/node_modules/busboy/bench/bench-multipart-fields-100mb-big.js new file mode 100644 index 0000000..ef15729 --- /dev/null +++ b/node_modules/busboy/bench/bench-multipart-fields-100mb-big.js @@ -0,0 +1,149 @@ +'use strict'; + +function createMultipartBuffers(boundary, sizes) { + const bufs = []; + for (let i = 0; i < sizes.length; ++i) { + const mb = sizes[i] * 1024 * 1024; + bufs.push(Buffer.from([ + `--${boundary}`, + `content-disposition: form-data; name="field${i + 1}"`, + '', + '0'.repeat(mb), + '', + ].join('\r\n'))); + } + bufs.push(Buffer.from([ + `--${boundary}--`, + '', + ].join('\r\n'))); + return bufs; +} + +const boundary = '-----------------------------168072824752491622650073'; +const buffers = createMultipartBuffers(boundary, [ + 10, + 10, + 10, + 20, + 50, +]); +const calls = { + partBegin: 0, + headerField: 0, + headerValue: 0, + headerEnd: 0, + headersEnd: 0, + partData: 0, + partEnd: 0, + end: 0, +}; + +const moduleName = process.argv[2]; +switch (moduleName) { + case 'busboy': { + const busboy = require('busboy'); + + const parser = busboy({ + limits: { + fieldSizeLimit: Infinity, + }, + headers: { + 'content-type': `multipart/form-data; boundary=${boundary}`, + }, + }); + parser.on('field', (name, val, info) => { + ++calls.partBegin; + ++calls.partData; + ++calls.partEnd; + }).on('close', () => { + ++calls.end; + console.timeEnd(moduleName); + }); + + console.time(moduleName); + for (const buf of buffers) + parser.write(buf); + break; + } + + case 'formidable': { + const { MultipartParser } = require('formidable'); + + const parser = new MultipartParser(); + parser.initWithBoundary(boundary); + parser.on('data', ({ name }) => { + ++calls[name]; + if (name === 'end') + console.timeEnd(moduleName); + }); + + console.time(moduleName); + for (const buf of buffers) + parser.write(buf); + + break; + } + + case 'multiparty': { + const { Readable } = require('stream'); + + const { Form } = require('multiparty'); + + const form = new Form({ + maxFieldsSize: Infinity, + maxFields: Infinity, + maxFilesSize: Infinity, + autoFields: false, + autoFiles: false, + }); + + const req = new Readable({ read: () => {} }); + req.headers = { + 'content-type': `multipart/form-data; boundary=${boundary}`, + }; + + function hijack(name, fn) { + const oldFn = form[name]; + form[name] = function() { + fn(); + return oldFn.apply(this, arguments); + }; + } + + hijack('onParseHeaderField', () => { + ++calls.headerField; + }); + hijack('onParseHeaderValue', () => { + ++calls.headerValue; + }); + hijack('onParsePartBegin', () => { + ++calls.partBegin; + }); + hijack('onParsePartData', () => { + ++calls.partData; + }); + hijack('onParsePartEnd', () => { + ++calls.partEnd; + }); + + form.on('close', () => { + ++calls.end; + console.timeEnd(moduleName); + }).on('part', (p) => p.resume()); + + console.time(moduleName); + form.parse(req); + for (const buf of buffers) + req.push(buf); + req.push(null); + + break; + } + + default: + if (moduleName === undefined) + console.error('Missing parser module name'); + else + console.error(`Invalid parser module name: ${moduleName}`); + process.exit(1); +} diff --git a/node_modules/busboy/bench/bench-multipart-fields-100mb-small.js b/node_modules/busboy/bench/bench-multipart-fields-100mb-small.js new file mode 100644 index 0000000..f32d421 --- /dev/null +++ b/node_modules/busboy/bench/bench-multipart-fields-100mb-small.js @@ -0,0 +1,143 @@ +'use strict'; + +function createMultipartBuffers(boundary, sizes) { + const bufs = []; + for (let i = 0; i < sizes.length; ++i) { + const mb = sizes[i] * 1024 * 1024; + bufs.push(Buffer.from([ + `--${boundary}`, + `content-disposition: form-data; name="field${i + 1}"`, + '', + '0'.repeat(mb), + '', + ].join('\r\n'))); + } + bufs.push(Buffer.from([ + `--${boundary}--`, + '', + ].join('\r\n'))); + return bufs; +} + +const boundary = '-----------------------------168072824752491622650073'; +const buffers = createMultipartBuffers(boundary, (new Array(100)).fill(1)); +const calls = { + partBegin: 0, + headerField: 0, + headerValue: 0, + headerEnd: 0, + headersEnd: 0, + partData: 0, + partEnd: 0, + end: 0, +}; + +const moduleName = process.argv[2]; +switch (moduleName) { + case 'busboy': { + const busboy = require('busboy'); + + const parser = busboy({ + limits: { + fieldSizeLimit: Infinity, + }, + headers: { + 'content-type': `multipart/form-data; boundary=${boundary}`, + }, + }); + parser.on('field', (name, val, info) => { + ++calls.partBegin; + ++calls.partData; + ++calls.partEnd; + }).on('close', () => { + ++calls.end; + console.timeEnd(moduleName); + }); + + console.time(moduleName); + for (const buf of buffers) + parser.write(buf); + break; + } + + case 'formidable': { + const { MultipartParser } = require('formidable'); + + const parser = new MultipartParser(); + parser.initWithBoundary(boundary); + parser.on('data', ({ name }) => { + ++calls[name]; + if (name === 'end') + console.timeEnd(moduleName); + }); + + console.time(moduleName); + for (const buf of buffers) + parser.write(buf); + + break; + } + + case 'multiparty': { + const { Readable } = require('stream'); + + const { Form } = require('multiparty'); + + const form = new Form({ + maxFieldsSize: Infinity, + maxFields: Infinity, + maxFilesSize: Infinity, + autoFields: false, + autoFiles: false, + }); + + const req = new Readable({ read: () => {} }); + req.headers = { + 'content-type': `multipart/form-data; boundary=${boundary}`, + }; + + function hijack(name, fn) { + const oldFn = form[name]; + form[name] = function() { + fn(); + return oldFn.apply(this, arguments); + }; + } + + hijack('onParseHeaderField', () => { + ++calls.headerField; + }); + hijack('onParseHeaderValue', () => { + ++calls.headerValue; + }); + hijack('onParsePartBegin', () => { + ++calls.partBegin; + }); + hijack('onParsePartData', () => { + ++calls.partData; + }); + hijack('onParsePartEnd', () => { + ++calls.partEnd; + }); + + form.on('close', () => { + ++calls.end; + console.timeEnd(moduleName); + }).on('part', (p) => p.resume()); + + console.time(moduleName); + form.parse(req); + for (const buf of buffers) + req.push(buf); + req.push(null); + + break; + } + + default: + if (moduleName === undefined) + console.error('Missing parser module name'); + else + console.error(`Invalid parser module name: ${moduleName}`); + process.exit(1); +} diff --git a/node_modules/busboy/bench/bench-multipart-files-100mb-big.js b/node_modules/busboy/bench/bench-multipart-files-100mb-big.js new file mode 100644 index 0000000..b46bdee --- /dev/null +++ b/node_modules/busboy/bench/bench-multipart-files-100mb-big.js @@ -0,0 +1,154 @@ +'use strict'; + +function createMultipartBuffers(boundary, sizes) { + const bufs = []; + for (let i = 0; i < sizes.length; ++i) { + const mb = sizes[i] * 1024 * 1024; + bufs.push(Buffer.from([ + `--${boundary}`, + `content-disposition: form-data; name="file${i + 1}"; ` + + `filename="random${i + 1}.bin"`, + 'content-type: application/octet-stream', + '', + '0'.repeat(mb), + '', + ].join('\r\n'))); + } + bufs.push(Buffer.from([ + `--${boundary}--`, + '', + ].join('\r\n'))); + return bufs; +} + +const boundary = '-----------------------------168072824752491622650073'; +const buffers = createMultipartBuffers(boundary, [ + 10, + 10, + 10, + 20, + 50, +]); +const calls = { + partBegin: 0, + headerField: 0, + headerValue: 0, + headerEnd: 0, + headersEnd: 0, + partData: 0, + partEnd: 0, + end: 0, +}; + +const moduleName = process.argv[2]; +switch (moduleName) { + case 'busboy': { + const busboy = require('busboy'); + + const parser = busboy({ + limits: { + fieldSizeLimit: Infinity, + }, + headers: { + 'content-type': `multipart/form-data; boundary=${boundary}`, + }, + }); + parser.on('file', (name, stream, info) => { + ++calls.partBegin; + stream.on('data', (chunk) => { + ++calls.partData; + }).on('end', () => { + ++calls.partEnd; + }); + }).on('close', () => { + ++calls.end; + console.timeEnd(moduleName); + }); + + console.time(moduleName); + for (const buf of buffers) + parser.write(buf); + break; + } + + case 'formidable': { + const { MultipartParser } = require('formidable'); + + const parser = new MultipartParser(); + parser.initWithBoundary(boundary); + parser.on('data', ({ name }) => { + ++calls[name]; + if (name === 'end') + console.timeEnd(moduleName); + }); + + console.time(moduleName); + for (const buf of buffers) + parser.write(buf); + + break; + } + + case 'multiparty': { + const { Readable } = require('stream'); + + const { Form } = require('multiparty'); + + const form = new Form({ + maxFieldsSize: Infinity, + maxFields: Infinity, + maxFilesSize: Infinity, + autoFields: false, + autoFiles: false, + }); + + const req = new Readable({ read: () => {} }); + req.headers = { + 'content-type': `multipart/form-data; boundary=${boundary}`, + }; + + function hijack(name, fn) { + const oldFn = form[name]; + form[name] = function() { + fn(); + return oldFn.apply(this, arguments); + }; + } + + hijack('onParseHeaderField', () => { + ++calls.headerField; + }); + hijack('onParseHeaderValue', () => { + ++calls.headerValue; + }); + hijack('onParsePartBegin', () => { + ++calls.partBegin; + }); + hijack('onParsePartData', () => { + ++calls.partData; + }); + hijack('onParsePartEnd', () => { + ++calls.partEnd; + }); + + form.on('close', () => { + ++calls.end; + console.timeEnd(moduleName); + }).on('part', (p) => p.resume()); + + console.time(moduleName); + form.parse(req); + for (const buf of buffers) + req.push(buf); + req.push(null); + + break; + } + + default: + if (moduleName === undefined) + console.error('Missing parser module name'); + else + console.error(`Invalid parser module name: ${moduleName}`); + process.exit(1); +} diff --git a/node_modules/busboy/bench/bench-multipart-files-100mb-small.js b/node_modules/busboy/bench/bench-multipart-files-100mb-small.js new file mode 100644 index 0000000..46b5dff --- /dev/null +++ b/node_modules/busboy/bench/bench-multipart-files-100mb-small.js @@ -0,0 +1,148 @@ +'use strict'; + +function createMultipartBuffers(boundary, sizes) { + const bufs = []; + for (let i = 0; i < sizes.length; ++i) { + const mb = sizes[i] * 1024 * 1024; + bufs.push(Buffer.from([ + `--${boundary}`, + `content-disposition: form-data; name="file${i + 1}"; ` + + `filename="random${i + 1}.bin"`, + 'content-type: application/octet-stream', + '', + '0'.repeat(mb), + '', + ].join('\r\n'))); + } + bufs.push(Buffer.from([ + `--${boundary}--`, + '', + ].join('\r\n'))); + return bufs; +} + +const boundary = '-----------------------------168072824752491622650073'; +const buffers = createMultipartBuffers(boundary, (new Array(100)).fill(1)); +const calls = { + partBegin: 0, + headerField: 0, + headerValue: 0, + headerEnd: 0, + headersEnd: 0, + partData: 0, + partEnd: 0, + end: 0, +}; + +const moduleName = process.argv[2]; +switch (moduleName) { + case 'busboy': { + const busboy = require('busboy'); + + const parser = busboy({ + limits: { + fieldSizeLimit: Infinity, + }, + headers: { + 'content-type': `multipart/form-data; boundary=${boundary}`, + }, + }); + parser.on('file', (name, stream, info) => { + ++calls.partBegin; + stream.on('data', (chunk) => { + ++calls.partData; + }).on('end', () => { + ++calls.partEnd; + }); + }).on('close', () => { + ++calls.end; + console.timeEnd(moduleName); + }); + + console.time(moduleName); + for (const buf of buffers) + parser.write(buf); + break; + } + + case 'formidable': { + const { MultipartParser } = require('formidable'); + + const parser = new MultipartParser(); + parser.initWithBoundary(boundary); + parser.on('data', ({ name }) => { + ++calls[name]; + if (name === 'end') + console.timeEnd(moduleName); + }); + + console.time(moduleName); + for (const buf of buffers) + parser.write(buf); + + break; + } + + case 'multiparty': { + const { Readable } = require('stream'); + + const { Form } = require('multiparty'); + + const form = new Form({ + maxFieldsSize: Infinity, + maxFields: Infinity, + maxFilesSize: Infinity, + autoFields: false, + autoFiles: false, + }); + + const req = new Readable({ read: () => {} }); + req.headers = { + 'content-type': `multipart/form-data; boundary=${boundary}`, + }; + + function hijack(name, fn) { + const oldFn = form[name]; + form[name] = function() { + fn(); + return oldFn.apply(this, arguments); + }; + } + + hijack('onParseHeaderField', () => { + ++calls.headerField; + }); + hijack('onParseHeaderValue', () => { + ++calls.headerValue; + }); + hijack('onParsePartBegin', () => { + ++calls.partBegin; + }); + hijack('onParsePartData', () => { + ++calls.partData; + }); + hijack('onParsePartEnd', () => { + ++calls.partEnd; + }); + + form.on('close', () => { + ++calls.end; + console.timeEnd(moduleName); + }).on('part', (p) => p.resume()); + + console.time(moduleName); + form.parse(req); + for (const buf of buffers) + req.push(buf); + req.push(null); + + break; + } + + default: + if (moduleName === undefined) + console.error('Missing parser module name'); + else + console.error(`Invalid parser module name: ${moduleName}`); + process.exit(1); +} diff --git a/node_modules/busboy/bench/bench-urlencoded-fields-100pairs-small.js b/node_modules/busboy/bench/bench-urlencoded-fields-100pairs-small.js new file mode 100644 index 0000000..5c337df --- /dev/null +++ b/node_modules/busboy/bench/bench-urlencoded-fields-100pairs-small.js @@ -0,0 +1,101 @@ +'use strict'; + +const buffers = [ + Buffer.from( + (new Array(100)).fill('').map((_, i) => `key${i}=value${i}`).join('&') + ), +]; +const calls = { + field: 0, + end: 0, +}; + +let n = 3e3; + +const moduleName = process.argv[2]; +switch (moduleName) { + case 'busboy': { + const busboy = require('busboy'); + + console.time(moduleName); + (function next() { + const parser = busboy({ + limits: { + fieldSizeLimit: Infinity, + }, + headers: { + 'content-type': 'application/x-www-form-urlencoded; charset=utf-8', + }, + }); + parser.on('field', (name, val, info) => { + ++calls.field; + }).on('close', () => { + ++calls.end; + if (--n === 0) + console.timeEnd(moduleName); + else + process.nextTick(next); + }); + + for (const buf of buffers) + parser.write(buf); + parser.end(); + })(); + break; + } + + case 'formidable': { + const QuerystringParser = + require('formidable/src/parsers/Querystring.js'); + + console.time(moduleName); + (function next() { + const parser = new QuerystringParser(); + parser.on('data', (obj) => { + ++calls.field; + }).on('end', () => { + ++calls.end; + if (--n === 0) + console.timeEnd(moduleName); + else + process.nextTick(next); + }); + + for (const buf of buffers) + parser.write(buf); + parser.end(); + })(); + break; + } + + case 'formidable-streaming': { + const QuerystringParser = + require('formidable/src/parsers/StreamingQuerystring.js'); + + console.time(moduleName); + (function next() { + const parser = new QuerystringParser(); + parser.on('data', (obj) => { + ++calls.field; + }).on('end', () => { + ++calls.end; + if (--n === 0) + console.timeEnd(moduleName); + else + process.nextTick(next); + }); + + for (const buf of buffers) + parser.write(buf); + parser.end(); + })(); + break; + } + + default: + if (moduleName === undefined) + console.error('Missing parser module name'); + else + console.error(`Invalid parser module name: ${moduleName}`); + process.exit(1); +} diff --git a/node_modules/busboy/bench/bench-urlencoded-fields-900pairs-small-alt.js b/node_modules/busboy/bench/bench-urlencoded-fields-900pairs-small-alt.js new file mode 100644 index 0000000..1f5645c --- /dev/null +++ b/node_modules/busboy/bench/bench-urlencoded-fields-900pairs-small-alt.js @@ -0,0 +1,84 @@ +'use strict'; + +const buffers = [ + Buffer.from( + (new Array(900)).fill('').map((_, i) => `key${i}=value${i}`).join('&') + ), +]; +const calls = { + field: 0, + end: 0, +}; + +const moduleName = process.argv[2]; +switch (moduleName) { + case 'busboy': { + const busboy = require('busboy'); + + console.time(moduleName); + const parser = busboy({ + limits: { + fieldSizeLimit: Infinity, + }, + headers: { + 'content-type': 'application/x-www-form-urlencoded; charset=utf-8', + }, + }); + parser.on('field', (name, val, info) => { + ++calls.field; + }).on('close', () => { + ++calls.end; + console.timeEnd(moduleName); + }); + + for (const buf of buffers) + parser.write(buf); + parser.end(); + break; + } + + case 'formidable': { + const QuerystringParser = + require('formidable/src/parsers/Querystring.js'); + + console.time(moduleName); + const parser = new QuerystringParser(); + parser.on('data', (obj) => { + ++calls.field; + }).on('end', () => { + ++calls.end; + console.timeEnd(moduleName); + }); + + for (const buf of buffers) + parser.write(buf); + parser.end(); + break; + } + + case 'formidable-streaming': { + const QuerystringParser = + require('formidable/src/parsers/StreamingQuerystring.js'); + + console.time(moduleName); + const parser = new QuerystringParser(); + parser.on('data', (obj) => { + ++calls.field; + }).on('end', () => { + ++calls.end; + console.timeEnd(moduleName); + }); + + for (const buf of buffers) + parser.write(buf); + parser.end(); + break; + } + + default: + if (moduleName === undefined) + console.error('Missing parser module name'); + else + console.error(`Invalid parser module name: ${moduleName}`); + process.exit(1); +} diff --git a/node_modules/busboy/lib/index.js b/node_modules/busboy/lib/index.js new file mode 100644 index 0000000..873272d --- /dev/null +++ b/node_modules/busboy/lib/index.js @@ -0,0 +1,57 @@ +'use strict'; + +const { parseContentType } = require('./utils.js'); + +function getInstance(cfg) { + const headers = cfg.headers; + const conType = parseContentType(headers['content-type']); + if (!conType) + throw new Error('Malformed content type'); + + for (const type of TYPES) { + const matched = type.detect(conType); + if (!matched) + continue; + + const instanceCfg = { + limits: cfg.limits, + headers, + conType, + highWaterMark: undefined, + fileHwm: undefined, + defCharset: undefined, + defParamCharset: undefined, + preservePath: false, + }; + if (cfg.highWaterMark) + instanceCfg.highWaterMark = cfg.highWaterMark; + if (cfg.fileHwm) + instanceCfg.fileHwm = cfg.fileHwm; + instanceCfg.defCharset = cfg.defCharset; + instanceCfg.defParamCharset = cfg.defParamCharset; + instanceCfg.preservePath = cfg.preservePath; + return new type(instanceCfg); + } + + throw new Error(`Unsupported content type: ${headers['content-type']}`); +} + +// Note: types are explicitly listed here for easier bundling +// See: https://github.com/mscdex/busboy/issues/121 +const TYPES = [ + require('./types/multipart'), + require('./types/urlencoded'), +].filter(function(typemod) { return typeof typemod.detect === 'function'; }); + +module.exports = (cfg) => { + if (typeof cfg !== 'object' || cfg === null) + cfg = {}; + + if (typeof cfg.headers !== 'object' + || cfg.headers === null + || typeof cfg.headers['content-type'] !== 'string') { + throw new Error('Missing Content-Type'); + } + + return getInstance(cfg); +}; diff --git a/node_modules/busboy/lib/types/multipart.js b/node_modules/busboy/lib/types/multipart.js new file mode 100644 index 0000000..cc0d7bb --- /dev/null +++ b/node_modules/busboy/lib/types/multipart.js @@ -0,0 +1,653 @@ +'use strict'; + +const { Readable, Writable } = require('stream'); + +const StreamSearch = require('streamsearch'); + +const { + basename, + convertToUTF8, + getDecoder, + parseContentType, + parseDisposition, +} = require('../utils.js'); + +const BUF_CRLF = Buffer.from('\r\n'); +const BUF_CR = Buffer.from('\r'); +const BUF_DASH = Buffer.from('-'); + +function noop() {} + +const MAX_HEADER_PAIRS = 2000; // From node +const MAX_HEADER_SIZE = 16 * 1024; // From node (its default value) + +const HPARSER_NAME = 0; +const HPARSER_PRE_OWS = 1; +const HPARSER_VALUE = 2; +class HeaderParser { + constructor(cb) { + this.header = Object.create(null); + this.pairCount = 0; + this.byteCount = 0; + this.state = HPARSER_NAME; + this.name = ''; + this.value = ''; + this.crlf = 0; + this.cb = cb; + } + + reset() { + this.header = Object.create(null); + this.pairCount = 0; + this.byteCount = 0; + this.state = HPARSER_NAME; + this.name = ''; + this.value = ''; + this.crlf = 0; + } + + push(chunk, pos, end) { + let start = pos; + while (pos < end) { + switch (this.state) { + case HPARSER_NAME: { + let done = false; + for (; pos < end; ++pos) { + if (this.byteCount === MAX_HEADER_SIZE) + return -1; + ++this.byteCount; + const code = chunk[pos]; + if (TOKEN[code] !== 1) { + if (code !== 58/* ':' */) + return -1; + this.name += chunk.latin1Slice(start, pos); + if (this.name.length === 0) + return -1; + ++pos; + done = true; + this.state = HPARSER_PRE_OWS; + break; + } + } + if (!done) { + this.name += chunk.latin1Slice(start, pos); + break; + } + // FALLTHROUGH + } + case HPARSER_PRE_OWS: { + // Skip optional whitespace + let done = false; + for (; pos < end; ++pos) { + if (this.byteCount === MAX_HEADER_SIZE) + return -1; + ++this.byteCount; + const code = chunk[pos]; + if (code !== 32/* ' ' */ && code !== 9/* '\t' */) { + start = pos; + done = true; + this.state = HPARSER_VALUE; + break; + } + } + if (!done) + break; + // FALLTHROUGH + } + case HPARSER_VALUE: + switch (this.crlf) { + case 0: // Nothing yet + for (; pos < end; ++pos) { + if (this.byteCount === MAX_HEADER_SIZE) + return -1; + ++this.byteCount; + const code = chunk[pos]; + if (FIELD_VCHAR[code] !== 1) { + if (code !== 13/* '\r' */) + return -1; + ++this.crlf; + break; + } + } + this.value += chunk.latin1Slice(start, pos++); + break; + case 1: // Received CR + if (this.byteCount === MAX_HEADER_SIZE) + return -1; + ++this.byteCount; + if (chunk[pos++] !== 10/* '\n' */) + return -1; + ++this.crlf; + break; + case 2: { // Received CR LF + if (this.byteCount === MAX_HEADER_SIZE) + return -1; + ++this.byteCount; + const code = chunk[pos]; + if (code === 32/* ' ' */ || code === 9/* '\t' */) { + // Folded value + start = pos; + this.crlf = 0; + } else { + if (++this.pairCount < MAX_HEADER_PAIRS) { + this.name = this.name.toLowerCase(); + if (this.header[this.name] === undefined) + this.header[this.name] = [this.value]; + else + this.header[this.name].push(this.value); + } + if (code === 13/* '\r' */) { + ++this.crlf; + ++pos; + } else { + // Assume start of next header field name + start = pos; + this.crlf = 0; + this.state = HPARSER_NAME; + this.name = ''; + this.value = ''; + } + } + break; + } + case 3: { // Received CR LF CR + if (this.byteCount === MAX_HEADER_SIZE) + return -1; + ++this.byteCount; + if (chunk[pos++] !== 10/* '\n' */) + return -1; + // End of header + const header = this.header; + this.reset(); + this.cb(header); + return pos; + } + } + break; + } + } + + return pos; + } +} + +class FileStream extends Readable { + constructor(opts, owner) { + super(opts); + this.truncated = false; + this._readcb = null; + this.once('end', () => { + // We need to make sure that we call any outstanding _writecb() that is + // associated with this file so that processing of the rest of the form + // can continue. This may not happen if the file stream ends right after + // backpressure kicks in, so we force it here. + this._read(); + if (--owner._fileEndsLeft === 0 && owner._finalcb) { + const cb = owner._finalcb; + owner._finalcb = null; + // Make sure other 'end' event handlers get a chance to be executed + // before busboy's 'finish' event is emitted + process.nextTick(cb); + } + }); + } + _read(n) { + const cb = this._readcb; + if (cb) { + this._readcb = null; + cb(); + } + } +} + +const ignoreData = { + push: (chunk, pos) => {}, + destroy: () => {}, +}; + +function callAndUnsetCb(self, err) { + const cb = self._writecb; + self._writecb = null; + if (err) + self.destroy(err); + else if (cb) + cb(); +} + +function nullDecoder(val, hint) { + return val; +} + +class Multipart extends Writable { + constructor(cfg) { + const streamOpts = { + autoDestroy: true, + emitClose: true, + highWaterMark: (typeof cfg.highWaterMark === 'number' + ? cfg.highWaterMark + : undefined), + }; + super(streamOpts); + + if (!cfg.conType.params || typeof cfg.conType.params.boundary !== 'string') + throw new Error('Multipart: Boundary not found'); + + const boundary = cfg.conType.params.boundary; + const paramDecoder = (typeof cfg.defParamCharset === 'string' + && cfg.defParamCharset + ? getDecoder(cfg.defParamCharset) + : nullDecoder); + const defCharset = (cfg.defCharset || 'utf8'); + const preservePath = cfg.preservePath; + const fileOpts = { + autoDestroy: true, + emitClose: true, + highWaterMark: (typeof cfg.fileHwm === 'number' + ? cfg.fileHwm + : undefined), + }; + + const limits = cfg.limits; + const fieldSizeLimit = (limits && typeof limits.fieldSize === 'number' + ? limits.fieldSize + : 1 * 1024 * 1024); + const fileSizeLimit = (limits && typeof limits.fileSize === 'number' + ? limits.fileSize + : Infinity); + const filesLimit = (limits && typeof limits.files === 'number' + ? limits.files + : Infinity); + const fieldsLimit = (limits && typeof limits.fields === 'number' + ? limits.fields + : Infinity); + const partsLimit = (limits && typeof limits.parts === 'number' + ? limits.parts + : Infinity); + + let parts = -1; // Account for initial boundary + let fields = 0; + let files = 0; + let skipPart = false; + + this._fileEndsLeft = 0; + this._fileStream = undefined; + this._complete = false; + let fileSize = 0; + + let field; + let fieldSize = 0; + let partCharset; + let partEncoding; + let partType; + let partName; + let partTruncated = false; + + let hitFilesLimit = false; + let hitFieldsLimit = false; + + this._hparser = null; + const hparser = new HeaderParser((header) => { + this._hparser = null; + skipPart = false; + + partType = 'text/plain'; + partCharset = defCharset; + partEncoding = '7bit'; + partName = undefined; + partTruncated = false; + + let filename; + if (!header['content-disposition']) { + skipPart = true; + return; + } + + const disp = parseDisposition(header['content-disposition'][0], + paramDecoder); + if (!disp || disp.type !== 'form-data') { + skipPart = true; + return; + } + + if (disp.params) { + if (disp.params.name) + partName = disp.params.name; + + if (disp.params['filename*']) + filename = disp.params['filename*']; + else if (disp.params.filename) + filename = disp.params.filename; + + if (filename !== undefined && !preservePath) + filename = basename(filename); + } + + if (header['content-type']) { + const conType = parseContentType(header['content-type'][0]); + if (conType) { + partType = `${conType.type}/${conType.subtype}`; + if (conType.params && typeof conType.params.charset === 'string') + partCharset = conType.params.charset.toLowerCase(); + } + } + + if (header['content-transfer-encoding']) + partEncoding = header['content-transfer-encoding'][0].toLowerCase(); + + if (partType === 'application/octet-stream' || filename !== undefined) { + // File + + if (files === filesLimit) { + if (!hitFilesLimit) { + hitFilesLimit = true; + this.emit('filesLimit'); + } + skipPart = true; + return; + } + ++files; + + if (this.listenerCount('file') === 0) { + skipPart = true; + return; + } + + fileSize = 0; + this._fileStream = new FileStream(fileOpts, this); + ++this._fileEndsLeft; + this.emit( + 'file', + partName, + this._fileStream, + { filename, + encoding: partEncoding, + mimeType: partType } + ); + } else { + // Non-file + + if (fields === fieldsLimit) { + if (!hitFieldsLimit) { + hitFieldsLimit = true; + this.emit('fieldsLimit'); + } + skipPart = true; + return; + } + ++fields; + + if (this.listenerCount('field') === 0) { + skipPart = true; + return; + } + + field = []; + fieldSize = 0; + } + }); + + let matchPostBoundary = 0; + const ssCb = (isMatch, data, start, end, isDataSafe) => { +retrydata: + while (data) { + if (this._hparser !== null) { + const ret = this._hparser.push(data, start, end); + if (ret === -1) { + this._hparser = null; + hparser.reset(); + this.emit('error', new Error('Malformed part header')); + break; + } + start = ret; + } + + if (start === end) + break; + + if (matchPostBoundary !== 0) { + if (matchPostBoundary === 1) { + switch (data[start]) { + case 45: // '-' + // Try matching '--' after boundary + matchPostBoundary = 2; + ++start; + break; + case 13: // '\r' + // Try matching CR LF before header + matchPostBoundary = 3; + ++start; + break; + default: + matchPostBoundary = 0; + } + if (start === end) + return; + } + + if (matchPostBoundary === 2) { + matchPostBoundary = 0; + if (data[start] === 45/* '-' */) { + // End of multipart data + this._complete = true; + this._bparser = ignoreData; + return; + } + // We saw something other than '-', so put the dash we consumed + // "back" + const writecb = this._writecb; + this._writecb = noop; + ssCb(false, BUF_DASH, 0, 1, false); + this._writecb = writecb; + } else if (matchPostBoundary === 3) { + matchPostBoundary = 0; + if (data[start] === 10/* '\n' */) { + ++start; + if (parts >= partsLimit) + break; + // Prepare the header parser + this._hparser = hparser; + if (start === end) + break; + // Process the remaining data as a header + continue retrydata; + } else { + // We saw something other than LF, so put the CR we consumed + // "back" + const writecb = this._writecb; + this._writecb = noop; + ssCb(false, BUF_CR, 0, 1, false); + this._writecb = writecb; + } + } + } + + if (!skipPart) { + if (this._fileStream) { + let chunk; + const actualLen = Math.min(end - start, fileSizeLimit - fileSize); + if (!isDataSafe) { + chunk = Buffer.allocUnsafe(actualLen); + data.copy(chunk, 0, start, start + actualLen); + } else { + chunk = data.slice(start, start + actualLen); + } + + fileSize += chunk.length; + if (fileSize === fileSizeLimit) { + if (chunk.length > 0) + this._fileStream.push(chunk); + this._fileStream.emit('limit'); + this._fileStream.truncated = true; + skipPart = true; + } else if (!this._fileStream.push(chunk)) { + if (this._writecb) + this._fileStream._readcb = this._writecb; + this._writecb = null; + } + } else if (field !== undefined) { + let chunk; + const actualLen = Math.min( + end - start, + fieldSizeLimit - fieldSize + ); + if (!isDataSafe) { + chunk = Buffer.allocUnsafe(actualLen); + data.copy(chunk, 0, start, start + actualLen); + } else { + chunk = data.slice(start, start + actualLen); + } + + fieldSize += actualLen; + field.push(chunk); + if (fieldSize === fieldSizeLimit) { + skipPart = true; + partTruncated = true; + } + } + } + + break; + } + + if (isMatch) { + matchPostBoundary = 1; + + if (this._fileStream) { + // End the active file stream if the previous part was a file + this._fileStream.push(null); + this._fileStream = null; + } else if (field !== undefined) { + let data; + switch (field.length) { + case 0: + data = ''; + break; + case 1: + data = convertToUTF8(field[0], partCharset, 0); + break; + default: + data = convertToUTF8( + Buffer.concat(field, fieldSize), + partCharset, + 0 + ); + } + field = undefined; + fieldSize = 0; + this.emit( + 'field', + partName, + data, + { nameTruncated: false, + valueTruncated: partTruncated, + encoding: partEncoding, + mimeType: partType } + ); + } + + if (++parts === partsLimit) + this.emit('partsLimit'); + } + }; + this._bparser = new StreamSearch(`\r\n--${boundary}`, ssCb); + + this._writecb = null; + this._finalcb = null; + + // Just in case there is no preamble + this.write(BUF_CRLF); + } + + static detect(conType) { + return (conType.type === 'multipart' && conType.subtype === 'form-data'); + } + + _write(chunk, enc, cb) { + this._writecb = cb; + this._bparser.push(chunk, 0); + if (this._writecb) + callAndUnsetCb(this); + } + + _destroy(err, cb) { + this._hparser = null; + this._bparser = ignoreData; + if (!err) + err = checkEndState(this); + const fileStream = this._fileStream; + if (fileStream) { + this._fileStream = null; + fileStream.destroy(err); + } + cb(err); + } + + _final(cb) { + this._bparser.destroy(); + if (!this._complete) + return cb(new Error('Unexpected end of form')); + if (this._fileEndsLeft) + this._finalcb = finalcb.bind(null, this, cb); + else + finalcb(this, cb); + } +} + +function finalcb(self, cb, err) { + if (err) + return cb(err); + err = checkEndState(self); + cb(err); +} + +function checkEndState(self) { + if (self._hparser) + return new Error('Malformed part header'); + const fileStream = self._fileStream; + if (fileStream) { + self._fileStream = null; + fileStream.destroy(new Error('Unexpected end of file')); + } + if (!self._complete) + return new Error('Unexpected end of form'); +} + +const TOKEN = [ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 1, 0, 1, 1, 1, 1, 1, 0, 0, 1, 1, 0, 1, 1, 0, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, + 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 0, 1, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +]; + +const FIELD_VCHAR = [ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, +]; + +module.exports = Multipart; diff --git a/node_modules/busboy/lib/types/urlencoded.js b/node_modules/busboy/lib/types/urlencoded.js new file mode 100644 index 0000000..5c463a2 --- /dev/null +++ b/node_modules/busboy/lib/types/urlencoded.js @@ -0,0 +1,350 @@ +'use strict'; + +const { Writable } = require('stream'); + +const { getDecoder } = require('../utils.js'); + +class URLEncoded extends Writable { + constructor(cfg) { + const streamOpts = { + autoDestroy: true, + emitClose: true, + highWaterMark: (typeof cfg.highWaterMark === 'number' + ? cfg.highWaterMark + : undefined), + }; + super(streamOpts); + + let charset = (cfg.defCharset || 'utf8'); + if (cfg.conType.params && typeof cfg.conType.params.charset === 'string') + charset = cfg.conType.params.charset; + + this.charset = charset; + + const limits = cfg.limits; + this.fieldSizeLimit = (limits && typeof limits.fieldSize === 'number' + ? limits.fieldSize + : 1 * 1024 * 1024); + this.fieldsLimit = (limits && typeof limits.fields === 'number' + ? limits.fields + : Infinity); + this.fieldNameSizeLimit = ( + limits && typeof limits.fieldNameSize === 'number' + ? limits.fieldNameSize + : 100 + ); + + this._inKey = true; + this._keyTrunc = false; + this._valTrunc = false; + this._bytesKey = 0; + this._bytesVal = 0; + this._fields = 0; + this._key = ''; + this._val = ''; + this._byte = -2; + this._lastPos = 0; + this._encode = 0; + this._decoder = getDecoder(charset); + } + + static detect(conType) { + return (conType.type === 'application' + && conType.subtype === 'x-www-form-urlencoded'); + } + + _write(chunk, enc, cb) { + if (this._fields >= this.fieldsLimit) + return cb(); + + let i = 0; + const len = chunk.length; + this._lastPos = 0; + + // Check if we last ended mid-percent-encoded byte + if (this._byte !== -2) { + i = readPctEnc(this, chunk, i, len); + if (i === -1) + return cb(new Error('Malformed urlencoded form')); + if (i >= len) + return cb(); + if (this._inKey) + ++this._bytesKey; + else + ++this._bytesVal; + } + +main: + while (i < len) { + if (this._inKey) { + // Parsing key + + i = skipKeyBytes(this, chunk, i, len); + + while (i < len) { + switch (chunk[i]) { + case 61: // '=' + if (this._lastPos < i) + this._key += chunk.latin1Slice(this._lastPos, i); + this._lastPos = ++i; + this._key = this._decoder(this._key, this._encode); + this._encode = 0; + this._inKey = false; + continue main; + case 38: // '&' + if (this._lastPos < i) + this._key += chunk.latin1Slice(this._lastPos, i); + this._lastPos = ++i; + this._key = this._decoder(this._key, this._encode); + this._encode = 0; + if (this._bytesKey > 0) { + this.emit( + 'field', + this._key, + '', + { nameTruncated: this._keyTrunc, + valueTruncated: false, + encoding: this.charset, + mimeType: 'text/plain' } + ); + } + this._key = ''; + this._val = ''; + this._keyTrunc = false; + this._valTrunc = false; + this._bytesKey = 0; + this._bytesVal = 0; + if (++this._fields >= this.fieldsLimit) { + this.emit('fieldsLimit'); + return cb(); + } + continue; + case 43: // '+' + if (this._lastPos < i) + this._key += chunk.latin1Slice(this._lastPos, i); + this._key += ' '; + this._lastPos = i + 1; + break; + case 37: // '%' + if (this._encode === 0) + this._encode = 1; + if (this._lastPos < i) + this._key += chunk.latin1Slice(this._lastPos, i); + this._lastPos = i + 1; + this._byte = -1; + i = readPctEnc(this, chunk, i + 1, len); + if (i === -1) + return cb(new Error('Malformed urlencoded form')); + if (i >= len) + return cb(); + ++this._bytesKey; + i = skipKeyBytes(this, chunk, i, len); + continue; + } + ++i; + ++this._bytesKey; + i = skipKeyBytes(this, chunk, i, len); + } + if (this._lastPos < i) + this._key += chunk.latin1Slice(this._lastPos, i); + } else { + // Parsing value + + i = skipValBytes(this, chunk, i, len); + + while (i < len) { + switch (chunk[i]) { + case 38: // '&' + if (this._lastPos < i) + this._val += chunk.latin1Slice(this._lastPos, i); + this._lastPos = ++i; + this._inKey = true; + this._val = this._decoder(this._val, this._encode); + this._encode = 0; + if (this._bytesKey > 0 || this._bytesVal > 0) { + this.emit( + 'field', + this._key, + this._val, + { nameTruncated: this._keyTrunc, + valueTruncated: this._valTrunc, + encoding: this.charset, + mimeType: 'text/plain' } + ); + } + this._key = ''; + this._val = ''; + this._keyTrunc = false; + this._valTrunc = false; + this._bytesKey = 0; + this._bytesVal = 0; + if (++this._fields >= this.fieldsLimit) { + this.emit('fieldsLimit'); + return cb(); + } + continue main; + case 43: // '+' + if (this._lastPos < i) + this._val += chunk.latin1Slice(this._lastPos, i); + this._val += ' '; + this._lastPos = i + 1; + break; + case 37: // '%' + if (this._encode === 0) + this._encode = 1; + if (this._lastPos < i) + this._val += chunk.latin1Slice(this._lastPos, i); + this._lastPos = i + 1; + this._byte = -1; + i = readPctEnc(this, chunk, i + 1, len); + if (i === -1) + return cb(new Error('Malformed urlencoded form')); + if (i >= len) + return cb(); + ++this._bytesVal; + i = skipValBytes(this, chunk, i, len); + continue; + } + ++i; + ++this._bytesVal; + i = skipValBytes(this, chunk, i, len); + } + if (this._lastPos < i) + this._val += chunk.latin1Slice(this._lastPos, i); + } + } + + cb(); + } + + _final(cb) { + if (this._byte !== -2) + return cb(new Error('Malformed urlencoded form')); + if (!this._inKey || this._bytesKey > 0 || this._bytesVal > 0) { + if (this._inKey) + this._key = this._decoder(this._key, this._encode); + else + this._val = this._decoder(this._val, this._encode); + this.emit( + 'field', + this._key, + this._val, + { nameTruncated: this._keyTrunc, + valueTruncated: this._valTrunc, + encoding: this.charset, + mimeType: 'text/plain' } + ); + } + cb(); + } +} + +function readPctEnc(self, chunk, pos, len) { + if (pos >= len) + return len; + + if (self._byte === -1) { + // We saw a '%' but no hex characters yet + const hexUpper = HEX_VALUES[chunk[pos++]]; + if (hexUpper === -1) + return -1; + + if (hexUpper >= 8) + self._encode = 2; // Indicate high bits detected + + if (pos < len) { + // Both hex characters are in this chunk + const hexLower = HEX_VALUES[chunk[pos++]]; + if (hexLower === -1) + return -1; + + if (self._inKey) + self._key += String.fromCharCode((hexUpper << 4) + hexLower); + else + self._val += String.fromCharCode((hexUpper << 4) + hexLower); + + self._byte = -2; + self._lastPos = pos; + } else { + // Only one hex character was available in this chunk + self._byte = hexUpper; + } + } else { + // We saw only one hex character so far + const hexLower = HEX_VALUES[chunk[pos++]]; + if (hexLower === -1) + return -1; + + if (self._inKey) + self._key += String.fromCharCode((self._byte << 4) + hexLower); + else + self._val += String.fromCharCode((self._byte << 4) + hexLower); + + self._byte = -2; + self._lastPos = pos; + } + + return pos; +} + +function skipKeyBytes(self, chunk, pos, len) { + // Skip bytes if we've truncated + if (self._bytesKey > self.fieldNameSizeLimit) { + if (!self._keyTrunc) { + if (self._lastPos < pos) + self._key += chunk.latin1Slice(self._lastPos, pos - 1); + } + self._keyTrunc = true; + for (; pos < len; ++pos) { + const code = chunk[pos]; + if (code === 61/* '=' */ || code === 38/* '&' */) + break; + ++self._bytesKey; + } + self._lastPos = pos; + } + + return pos; +} + +function skipValBytes(self, chunk, pos, len) { + // Skip bytes if we've truncated + if (self._bytesVal > self.fieldSizeLimit) { + if (!self._valTrunc) { + if (self._lastPos < pos) + self._val += chunk.latin1Slice(self._lastPos, pos - 1); + } + self._valTrunc = true; + for (; pos < len; ++pos) { + if (chunk[pos] === 38/* '&' */) + break; + ++self._bytesVal; + } + self._lastPos = pos; + } + + return pos; +} + +/* eslint-disable no-multi-spaces */ +const HEX_VALUES = [ + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, -1, -1, -1, -1, -1, -1, + -1, 10, 11, 12, 13, 14, 15, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, 10, 11, 12, 13, 14, 15, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, +]; +/* eslint-enable no-multi-spaces */ + +module.exports = URLEncoded; diff --git a/node_modules/busboy/lib/utils.js b/node_modules/busboy/lib/utils.js new file mode 100644 index 0000000..8274f6c --- /dev/null +++ b/node_modules/busboy/lib/utils.js @@ -0,0 +1,596 @@ +'use strict'; + +function parseContentType(str) { + if (str.length === 0) + return; + + const params = Object.create(null); + let i = 0; + + // Parse type + for (; i < str.length; ++i) { + const code = str.charCodeAt(i); + if (TOKEN[code] !== 1) { + if (code !== 47/* '/' */ || i === 0) + return; + break; + } + } + // Check for type without subtype + if (i === str.length) + return; + + const type = str.slice(0, i).toLowerCase(); + + // Parse subtype + const subtypeStart = ++i; + for (; i < str.length; ++i) { + const code = str.charCodeAt(i); + if (TOKEN[code] !== 1) { + // Make sure we have a subtype + if (i === subtypeStart) + return; + + if (parseContentTypeParams(str, i, params) === undefined) + return; + break; + } + } + // Make sure we have a subtype + if (i === subtypeStart) + return; + + const subtype = str.slice(subtypeStart, i).toLowerCase(); + + return { type, subtype, params }; +} + +function parseContentTypeParams(str, i, params) { + while (i < str.length) { + // Consume whitespace + for (; i < str.length; ++i) { + const code = str.charCodeAt(i); + if (code !== 32/* ' ' */ && code !== 9/* '\t' */) + break; + } + + // Ended on whitespace + if (i === str.length) + break; + + // Check for malformed parameter + if (str.charCodeAt(i++) !== 59/* ';' */) + return; + + // Consume whitespace + for (; i < str.length; ++i) { + const code = str.charCodeAt(i); + if (code !== 32/* ' ' */ && code !== 9/* '\t' */) + break; + } + + // Ended on whitespace (malformed) + if (i === str.length) + return; + + let name; + const nameStart = i; + // Parse parameter name + for (; i < str.length; ++i) { + const code = str.charCodeAt(i); + if (TOKEN[code] !== 1) { + if (code !== 61/* '=' */) + return; + break; + } + } + + // No value (malformed) + if (i === str.length) + return; + + name = str.slice(nameStart, i); + ++i; // Skip over '=' + + // No value (malformed) + if (i === str.length) + return; + + let value = ''; + let valueStart; + if (str.charCodeAt(i) === 34/* '"' */) { + valueStart = ++i; + let escaping = false; + // Parse quoted value + for (; i < str.length; ++i) { + const code = str.charCodeAt(i); + if (code === 92/* '\\' */) { + if (escaping) { + valueStart = i; + escaping = false; + } else { + value += str.slice(valueStart, i); + escaping = true; + } + continue; + } + if (code === 34/* '"' */) { + if (escaping) { + valueStart = i; + escaping = false; + continue; + } + value += str.slice(valueStart, i); + break; + } + if (escaping) { + valueStart = i - 1; + escaping = false; + } + // Invalid unescaped quoted character (malformed) + if (QDTEXT[code] !== 1) + return; + } + + // No end quote (malformed) + if (i === str.length) + return; + + ++i; // Skip over double quote + } else { + valueStart = i; + // Parse unquoted value + for (; i < str.length; ++i) { + const code = str.charCodeAt(i); + if (TOKEN[code] !== 1) { + // No value (malformed) + if (i === valueStart) + return; + break; + } + } + value = str.slice(valueStart, i); + } + + name = name.toLowerCase(); + if (params[name] === undefined) + params[name] = value; + } + + return params; +} + +function parseDisposition(str, defDecoder) { + if (str.length === 0) + return; + + const params = Object.create(null); + let i = 0; + + for (; i < str.length; ++i) { + const code = str.charCodeAt(i); + if (TOKEN[code] !== 1) { + if (parseDispositionParams(str, i, params, defDecoder) === undefined) + return; + break; + } + } + + const type = str.slice(0, i).toLowerCase(); + + return { type, params }; +} + +function parseDispositionParams(str, i, params, defDecoder) { + while (i < str.length) { + // Consume whitespace + for (; i < str.length; ++i) { + const code = str.charCodeAt(i); + if (code !== 32/* ' ' */ && code !== 9/* '\t' */) + break; + } + + // Ended on whitespace + if (i === str.length) + break; + + // Check for malformed parameter + if (str.charCodeAt(i++) !== 59/* ';' */) + return; + + // Consume whitespace + for (; i < str.length; ++i) { + const code = str.charCodeAt(i); + if (code !== 32/* ' ' */ && code !== 9/* '\t' */) + break; + } + + // Ended on whitespace (malformed) + if (i === str.length) + return; + + let name; + const nameStart = i; + // Parse parameter name + for (; i < str.length; ++i) { + const code = str.charCodeAt(i); + if (TOKEN[code] !== 1) { + if (code === 61/* '=' */) + break; + return; + } + } + + // No value (malformed) + if (i === str.length) + return; + + let value = ''; + let valueStart; + let charset; + //~ let lang; + name = str.slice(nameStart, i); + if (name.charCodeAt(name.length - 1) === 42/* '*' */) { + // Extended value + + const charsetStart = ++i; + // Parse charset name + for (; i < str.length; ++i) { + const code = str.charCodeAt(i); + if (CHARSET[code] !== 1) { + if (code !== 39/* '\'' */) + return; + break; + } + } + + // Incomplete charset (malformed) + if (i === str.length) + return; + + charset = str.slice(charsetStart, i); + ++i; // Skip over the '\'' + + //~ const langStart = ++i; + // Parse language name + for (; i < str.length; ++i) { + const code = str.charCodeAt(i); + if (code === 39/* '\'' */) + break; + } + + // Incomplete language (malformed) + if (i === str.length) + return; + + //~ lang = str.slice(langStart, i); + ++i; // Skip over the '\'' + + // No value (malformed) + if (i === str.length) + return; + + valueStart = i; + + let encode = 0; + // Parse value + for (; i < str.length; ++i) { + const code = str.charCodeAt(i); + if (EXTENDED_VALUE[code] !== 1) { + if (code === 37/* '%' */) { + let hexUpper; + let hexLower; + if (i + 2 < str.length + && (hexUpper = HEX_VALUES[str.charCodeAt(i + 1)]) !== -1 + && (hexLower = HEX_VALUES[str.charCodeAt(i + 2)]) !== -1) { + const byteVal = (hexUpper << 4) + hexLower; + value += str.slice(valueStart, i); + value += String.fromCharCode(byteVal); + i += 2; + valueStart = i + 1; + if (byteVal >= 128) + encode = 2; + else if (encode === 0) + encode = 1; + continue; + } + // '%' disallowed in non-percent encoded contexts (malformed) + return; + } + break; + } + } + + value += str.slice(valueStart, i); + value = convertToUTF8(value, charset, encode); + if (value === undefined) + return; + } else { + // Non-extended value + + ++i; // Skip over '=' + + // No value (malformed) + if (i === str.length) + return; + + if (str.charCodeAt(i) === 34/* '"' */) { + valueStart = ++i; + let escaping = false; + // Parse quoted value + for (; i < str.length; ++i) { + const code = str.charCodeAt(i); + if (code === 92/* '\\' */) { + if (escaping) { + valueStart = i; + escaping = false; + } else { + value += str.slice(valueStart, i); + escaping = true; + } + continue; + } + if (code === 34/* '"' */) { + if (escaping) { + valueStart = i; + escaping = false; + continue; + } + value += str.slice(valueStart, i); + break; + } + if (escaping) { + valueStart = i - 1; + escaping = false; + } + // Invalid unescaped quoted character (malformed) + if (QDTEXT[code] !== 1) + return; + } + + // No end quote (malformed) + if (i === str.length) + return; + + ++i; // Skip over double quote + } else { + valueStart = i; + // Parse unquoted value + for (; i < str.length; ++i) { + const code = str.charCodeAt(i); + if (TOKEN[code] !== 1) { + // No value (malformed) + if (i === valueStart) + return; + break; + } + } + value = str.slice(valueStart, i); + } + + value = defDecoder(value, 2); + if (value === undefined) + return; + } + + name = name.toLowerCase(); + if (params[name] === undefined) + params[name] = value; + } + + return params; +} + +function getDecoder(charset) { + let lc; + while (true) { + switch (charset) { + case 'utf-8': + case 'utf8': + return decoders.utf8; + case 'latin1': + case 'ascii': // TODO: Make these a separate, strict decoder? + case 'us-ascii': + case 'iso-8859-1': + case 'iso8859-1': + case 'iso88591': + case 'iso_8859-1': + case 'windows-1252': + case 'iso_8859-1:1987': + case 'cp1252': + case 'x-cp1252': + return decoders.latin1; + case 'utf16le': + case 'utf-16le': + case 'ucs2': + case 'ucs-2': + return decoders.utf16le; + case 'base64': + return decoders.base64; + default: + if (lc === undefined) { + lc = true; + charset = charset.toLowerCase(); + continue; + } + return decoders.other.bind(charset); + } + } +} + +const decoders = { + utf8: (data, hint) => { + if (data.length === 0) + return ''; + if (typeof data === 'string') { + // If `data` never had any percent-encoded bytes or never had any that + // were outside of the ASCII range, then we can safely just return the + // input since UTF-8 is ASCII compatible + if (hint < 2) + return data; + + data = Buffer.from(data, 'latin1'); + } + return data.utf8Slice(0, data.length); + }, + + latin1: (data, hint) => { + if (data.length === 0) + return ''; + if (typeof data === 'string') + return data; + return data.latin1Slice(0, data.length); + }, + + utf16le: (data, hint) => { + if (data.length === 0) + return ''; + if (typeof data === 'string') + data = Buffer.from(data, 'latin1'); + return data.ucs2Slice(0, data.length); + }, + + base64: (data, hint) => { + if (data.length === 0) + return ''; + if (typeof data === 'string') + data = Buffer.from(data, 'latin1'); + return data.base64Slice(0, data.length); + }, + + other: (data, hint) => { + if (data.length === 0) + return ''; + if (typeof data === 'string') + data = Buffer.from(data, 'latin1'); + try { + const decoder = new TextDecoder(this); + return decoder.decode(data); + } catch {} + }, +}; + +function convertToUTF8(data, charset, hint) { + const decode = getDecoder(charset); + if (decode) + return decode(data, hint); +} + +function basename(path) { + if (typeof path !== 'string') + return ''; + for (let i = path.length - 1; i >= 0; --i) { + switch (path.charCodeAt(i)) { + case 0x2F: // '/' + case 0x5C: // '\' + path = path.slice(i + 1); + return (path === '..' || path === '.' ? '' : path); + } + } + return (path === '..' || path === '.' ? '' : path); +} + +const TOKEN = [ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 1, 0, 1, 1, 1, 1, 1, 0, 0, 1, 1, 0, 1, 1, 0, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, + 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 0, 1, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +]; + +const QDTEXT = [ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, +]; + +const CHARSET = [ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 1, 0, 1, 1, 1, 1, 0, 0, 0, 0, 1, 0, 1, 0, 0, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, + 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +]; + +const EXTENDED_VALUE = [ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 1, 0, 1, 1, 0, 1, 0, 0, 0, 0, 1, 0, 1, 1, 0, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, + 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 0, 1, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +]; + +/* eslint-disable no-multi-spaces */ +const HEX_VALUES = [ + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, -1, -1, -1, -1, -1, -1, + -1, 10, 11, 12, 13, 14, 15, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, 10, 11, 12, 13, 14, 15, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, +]; +/* eslint-enable no-multi-spaces */ + +module.exports = { + basename, + convertToUTF8, + getDecoder, + parseContentType, + parseDisposition, +}; diff --git a/node_modules/busboy/package.json b/node_modules/busboy/package.json new file mode 100644 index 0000000..ac2577f --- /dev/null +++ b/node_modules/busboy/package.json @@ -0,0 +1,22 @@ +{ "name": "busboy", + "version": "1.6.0", + "author": "Brian White ", + "description": "A streaming parser for HTML form data for node.js", + "main": "./lib/index.js", + "dependencies": { + "streamsearch": "^1.1.0" + }, + "devDependencies": { + "@mscdex/eslint-config": "^1.1.0", + "eslint": "^7.32.0" + }, + "scripts": { + "test": "node test/test.js", + "lint": "eslint --cache --report-unused-disable-directives --ext=.js .eslintrc.js lib test bench", + "lint:fix": "npm run lint -- --fix" + }, + "engines": { "node": ">=10.16.0" }, + "keywords": [ "uploads", "forms", "multipart", "form-data" ], + "licenses": [ { "type": "MIT", "url": "http://github.com/mscdex/busboy/raw/master/LICENSE" } ], + "repository": { "type": "git", "url": "http://github.com/mscdex/busboy.git" } +} diff --git a/node_modules/busboy/test/common.js b/node_modules/busboy/test/common.js new file mode 100644 index 0000000..fb82ad8 --- /dev/null +++ b/node_modules/busboy/test/common.js @@ -0,0 +1,109 @@ +'use strict'; + +const assert = require('assert'); +const { inspect } = require('util'); + +const mustCallChecks = []; + +function noop() {} + +function runCallChecks(exitCode) { + if (exitCode !== 0) return; + + const failed = mustCallChecks.filter((context) => { + if ('minimum' in context) { + context.messageSegment = `at least ${context.minimum}`; + return context.actual < context.minimum; + } + context.messageSegment = `exactly ${context.exact}`; + return context.actual !== context.exact; + }); + + failed.forEach((context) => { + console.error('Mismatched %s function calls. Expected %s, actual %d.', + context.name, + context.messageSegment, + context.actual); + console.error(context.stack.split('\n').slice(2).join('\n')); + }); + + if (failed.length) + process.exit(1); +} + +function mustCall(fn, exact) { + return _mustCallInner(fn, exact, 'exact'); +} + +function mustCallAtLeast(fn, minimum) { + return _mustCallInner(fn, minimum, 'minimum'); +} + +function _mustCallInner(fn, criteria = 1, field) { + if (process._exiting) + throw new Error('Cannot use common.mustCall*() in process exit handler'); + + if (typeof fn === 'number') { + criteria = fn; + fn = noop; + } else if (fn === undefined) { + fn = noop; + } + + if (typeof criteria !== 'number') + throw new TypeError(`Invalid ${field} value: ${criteria}`); + + const context = { + [field]: criteria, + actual: 0, + stack: inspect(new Error()), + name: fn.name || '' + }; + + // Add the exit listener only once to avoid listener leak warnings + if (mustCallChecks.length === 0) + process.on('exit', runCallChecks); + + mustCallChecks.push(context); + + function wrapped(...args) { + ++context.actual; + return fn.call(this, ...args); + } + // TODO: remove origFn? + wrapped.origFn = fn; + + return wrapped; +} + +function getCallSite(top) { + const originalStackFormatter = Error.prepareStackTrace; + Error.prepareStackTrace = (err, stack) => + `${stack[0].getFileName()}:${stack[0].getLineNumber()}`; + const err = new Error(); + Error.captureStackTrace(err, top); + // With the V8 Error API, the stack is not formatted until it is accessed + // eslint-disable-next-line no-unused-expressions + err.stack; + Error.prepareStackTrace = originalStackFormatter; + return err.stack; +} + +function mustNotCall(msg) { + const callSite = getCallSite(mustNotCall); + return function mustNotCall(...args) { + args = args.map(inspect).join(', '); + const argsInfo = (args.length > 0 + ? `\ncalled with arguments: ${args}` + : ''); + assert.fail( + `${msg || 'function should not have been called'} at ${callSite}` + + argsInfo); + }; +} + +module.exports = { + mustCall, + mustCallAtLeast, + mustNotCall, +}; diff --git a/node_modules/busboy/test/test-types-multipart-charsets.js b/node_modules/busboy/test/test-types-multipart-charsets.js new file mode 100644 index 0000000..ed9c38a --- /dev/null +++ b/node_modules/busboy/test/test-types-multipart-charsets.js @@ -0,0 +1,94 @@ +'use strict'; + +const assert = require('assert'); +const { inspect } = require('util'); + +const { mustCall } = require(`${__dirname}/common.js`); + +const busboy = require('..'); + +const input = Buffer.from([ + '-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k', + 'Content-Disposition: form-data; ' + + 'name="upload_file_0"; filename="テスト.dat"', + 'Content-Type: application/octet-stream', + '', + 'A'.repeat(1023), + '-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k--' +].join('\r\n')); +const boundary = '---------------------------paZqsnEHRufoShdX6fh0lUhXBP4k'; +const expected = [ + { type: 'file', + name: 'upload_file_0', + data: Buffer.from('A'.repeat(1023)), + info: { + filename: 'テスト.dat', + encoding: '7bit', + mimeType: 'application/octet-stream', + }, + limited: false, + }, +]; +const bb = busboy({ + defParamCharset: 'utf8', + headers: { + 'content-type': `multipart/form-data; boundary=${boundary}`, + } +}); +const results = []; + +bb.on('field', (name, val, info) => { + results.push({ type: 'field', name, val, info }); +}); + +bb.on('file', (name, stream, info) => { + const data = []; + let nb = 0; + const file = { + type: 'file', + name, + data: null, + info, + limited: false, + }; + results.push(file); + stream.on('data', (d) => { + data.push(d); + nb += d.length; + }).on('limit', () => { + file.limited = true; + }).on('close', () => { + file.data = Buffer.concat(data, nb); + assert.strictEqual(stream.truncated, file.limited); + }).once('error', (err) => { + file.err = err.message; + }); +}); + +bb.on('error', (err) => { + results.push({ error: err.message }); +}); + +bb.on('partsLimit', () => { + results.push('partsLimit'); +}); + +bb.on('filesLimit', () => { + results.push('filesLimit'); +}); + +bb.on('fieldsLimit', () => { + results.push('fieldsLimit'); +}); + +bb.on('close', mustCall(() => { + assert.deepStrictEqual( + results, + expected, + 'Results mismatch.\n' + + `Parsed: ${inspect(results)}\n` + + `Expected: ${inspect(expected)}` + ); +})); + +bb.end(input); diff --git a/node_modules/busboy/test/test-types-multipart-stream-pause.js b/node_modules/busboy/test/test-types-multipart-stream-pause.js new file mode 100644 index 0000000..df7268a --- /dev/null +++ b/node_modules/busboy/test/test-types-multipart-stream-pause.js @@ -0,0 +1,102 @@ +'use strict'; + +const assert = require('assert'); +const { randomFillSync } = require('crypto'); +const { inspect } = require('util'); + +const busboy = require('..'); + +const { mustCall } = require('./common.js'); + +const BOUNDARY = 'u2KxIV5yF1y+xUspOQCCZopaVgeV6Jxihv35XQJmuTx8X3sh'; + +function formDataSection(key, value) { + return Buffer.from( + `\r\n--${BOUNDARY}` + + `\r\nContent-Disposition: form-data; name="${key}"` + + `\r\n\r\n${value}` + ); +} + +function formDataFile(key, filename, contentType) { + const buf = Buffer.allocUnsafe(100000); + return Buffer.concat([ + Buffer.from(`\r\n--${BOUNDARY}\r\n`), + Buffer.from(`Content-Disposition: form-data; name="${key}"` + + `; filename="${filename}"\r\n`), + Buffer.from(`Content-Type: ${contentType}\r\n\r\n`), + randomFillSync(buf) + ]); +} + +const reqChunks = [ + Buffer.concat([ + formDataFile('file', 'file.bin', 'application/octet-stream'), + formDataSection('foo', 'foo value'), + ]), + formDataSection('bar', 'bar value'), + Buffer.from(`\r\n--${BOUNDARY}--\r\n`) +]; +const bb = busboy({ + headers: { + 'content-type': `multipart/form-data; boundary=${BOUNDARY}` + } +}); +const expected = [ + { type: 'file', + name: 'file', + info: { + filename: 'file.bin', + encoding: '7bit', + mimeType: 'application/octet-stream', + }, + }, + { type: 'field', + name: 'foo', + val: 'foo value', + info: { + nameTruncated: false, + valueTruncated: false, + encoding: '7bit', + mimeType: 'text/plain', + }, + }, + { type: 'field', + name: 'bar', + val: 'bar value', + info: { + nameTruncated: false, + valueTruncated: false, + encoding: '7bit', + mimeType: 'text/plain', + }, + }, +]; +const results = []; + +bb.on('field', (name, val, info) => { + results.push({ type: 'field', name, val, info }); +}); + +bb.on('file', (name, stream, info) => { + results.push({ type: 'file', name, info }); + // Simulate a pipe where the destination is pausing (perhaps due to waiting + // for file system write to finish) + setTimeout(() => { + stream.resume(); + }, 10); +}); + +bb.on('close', mustCall(() => { + assert.deepStrictEqual( + results, + expected, + 'Results mismatch.\n' + + `Parsed: ${inspect(results)}\n` + + `Expected: ${inspect(expected)}` + ); +})); + +for (const chunk of reqChunks) + bb.write(chunk); +bb.end(); diff --git a/node_modules/busboy/test/test-types-multipart.js b/node_modules/busboy/test/test-types-multipart.js new file mode 100644 index 0000000..9755642 --- /dev/null +++ b/node_modules/busboy/test/test-types-multipart.js @@ -0,0 +1,1053 @@ +'use strict'; + +const assert = require('assert'); +const { inspect } = require('util'); + +const busboy = require('..'); + +const active = new Map(); + +const tests = [ + { source: [ + ['-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k', + 'Content-Disposition: form-data; name="file_name_0"', + '', + 'super alpha file', + '-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k', + 'Content-Disposition: form-data; name="file_name_1"', + '', + 'super beta file', + '-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k', + 'Content-Disposition: form-data; ' + + 'name="upload_file_0"; filename="1k_a.dat"', + 'Content-Type: application/octet-stream', + '', + 'A'.repeat(1023), + '-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k', + 'Content-Disposition: form-data; ' + + 'name="upload_file_1"; filename="1k_b.dat"', + 'Content-Type: application/octet-stream', + '', + 'B'.repeat(1023), + '-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k--' + ].join('\r\n') + ], + boundary: '---------------------------paZqsnEHRufoShdX6fh0lUhXBP4k', + expected: [ + { type: 'field', + name: 'file_name_0', + val: 'super alpha file', + info: { + nameTruncated: false, + valueTruncated: false, + encoding: '7bit', + mimeType: 'text/plain', + }, + }, + { type: 'field', + name: 'file_name_1', + val: 'super beta file', + info: { + nameTruncated: false, + valueTruncated: false, + encoding: '7bit', + mimeType: 'text/plain', + }, + }, + { type: 'file', + name: 'upload_file_0', + data: Buffer.from('A'.repeat(1023)), + info: { + filename: '1k_a.dat', + encoding: '7bit', + mimeType: 'application/octet-stream', + }, + limited: false, + }, + { type: 'file', + name: 'upload_file_1', + data: Buffer.from('B'.repeat(1023)), + info: { + filename: '1k_b.dat', + encoding: '7bit', + mimeType: 'application/octet-stream', + }, + limited: false, + }, + ], + what: 'Fields and files' + }, + { source: [ + ['------WebKitFormBoundaryTB2MiQ36fnSJlrhY', + 'Content-Disposition: form-data; name="cont"', + '', + 'some random content', + '------WebKitFormBoundaryTB2MiQ36fnSJlrhY', + 'Content-Disposition: form-data; name="pass"', + '', + 'some random pass', + '------WebKitFormBoundaryTB2MiQ36fnSJlrhY', + 'Content-Disposition: form-data; name=bit', + '', + '2', + '------WebKitFormBoundaryTB2MiQ36fnSJlrhY--' + ].join('\r\n') + ], + boundary: '----WebKitFormBoundaryTB2MiQ36fnSJlrhY', + expected: [ + { type: 'field', + name: 'cont', + val: 'some random content', + info: { + nameTruncated: false, + valueTruncated: false, + encoding: '7bit', + mimeType: 'text/plain', + }, + }, + { type: 'field', + name: 'pass', + val: 'some random pass', + info: { + nameTruncated: false, + valueTruncated: false, + encoding: '7bit', + mimeType: 'text/plain', + }, + }, + { type: 'field', + name: 'bit', + val: '2', + info: { + nameTruncated: false, + valueTruncated: false, + encoding: '7bit', + mimeType: 'text/plain', + }, + }, + ], + what: 'Fields only' + }, + { source: [ + '' + ], + boundary: '----WebKitFormBoundaryTB2MiQ36fnSJlrhY', + expected: [ + { error: 'Unexpected end of form' }, + ], + what: 'No fields and no files' + }, + { source: [ + ['-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k', + 'Content-Disposition: form-data; name="file_name_0"', + '', + 'super alpha file', + '-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k', + 'Content-Disposition: form-data; ' + + 'name="upload_file_0"; filename="1k_a.dat"', + 'Content-Type: application/octet-stream', + '', + 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', + '-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k--' + ].join('\r\n') + ], + boundary: '---------------------------paZqsnEHRufoShdX6fh0lUhXBP4k', + limits: { + fileSize: 13, + fieldSize: 5 + }, + expected: [ + { type: 'field', + name: 'file_name_0', + val: 'super', + info: { + nameTruncated: false, + valueTruncated: true, + encoding: '7bit', + mimeType: 'text/plain', + }, + }, + { type: 'file', + name: 'upload_file_0', + data: Buffer.from('ABCDEFGHIJKLM'), + info: { + filename: '1k_a.dat', + encoding: '7bit', + mimeType: 'application/octet-stream', + }, + limited: true, + }, + ], + what: 'Fields and files (limits)' + }, + { source: [ + ['-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k', + 'Content-Disposition: form-data; name="file_name_0"', + '', + 'super alpha file', + '-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k', + 'Content-Disposition: form-data; ' + + 'name="upload_file_0"; filename="1k_a.dat"', + 'Content-Type: application/octet-stream', + '', + 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', + '-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k--' + ].join('\r\n') + ], + boundary: '---------------------------paZqsnEHRufoShdX6fh0lUhXBP4k', + limits: { + files: 0 + }, + expected: [ + { type: 'field', + name: 'file_name_0', + val: 'super alpha file', + info: { + nameTruncated: false, + valueTruncated: false, + encoding: '7bit', + mimeType: 'text/plain', + }, + }, + 'filesLimit', + ], + what: 'Fields and files (limits: 0 files)' + }, + { source: [ + ['-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k', + 'Content-Disposition: form-data; name="file_name_0"', + '', + 'super alpha file', + '-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k', + 'Content-Disposition: form-data; name="file_name_1"', + '', + 'super beta file', + '-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k', + 'Content-Disposition: form-data; ' + + 'name="upload_file_0"; filename="1k_a.dat"', + 'Content-Type: application/octet-stream', + '', + 'A'.repeat(1023), + '-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k', + 'Content-Disposition: form-data; ' + + 'name="upload_file_1"; filename="1k_b.dat"', + 'Content-Type: application/octet-stream', + '', + 'B'.repeat(1023), + '-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k--' + ].join('\r\n') + ], + boundary: '---------------------------paZqsnEHRufoShdX6fh0lUhXBP4k', + expected: [ + { type: 'field', + name: 'file_name_0', + val: 'super alpha file', + info: { + nameTruncated: false, + valueTruncated: false, + encoding: '7bit', + mimeType: 'text/plain', + }, + }, + { type: 'field', + name: 'file_name_1', + val: 'super beta file', + info: { + nameTruncated: false, + valueTruncated: false, + encoding: '7bit', + mimeType: 'text/plain', + }, + }, + ], + events: ['field'], + what: 'Fields and (ignored) files' + }, + { source: [ + ['-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k', + 'Content-Disposition: form-data; ' + + 'name="upload_file_0"; filename="/tmp/1k_a.dat"', + 'Content-Type: application/octet-stream', + '', + 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', + '-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k', + 'Content-Disposition: form-data; ' + + 'name="upload_file_1"; filename="C:\\files\\1k_b.dat"', + 'Content-Type: application/octet-stream', + '', + 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', + '-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k', + 'Content-Disposition: form-data; ' + + 'name="upload_file_2"; filename="relative/1k_c.dat"', + 'Content-Type: application/octet-stream', + '', + 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', + '-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k--' + ].join('\r\n') + ], + boundary: '---------------------------paZqsnEHRufoShdX6fh0lUhXBP4k', + expected: [ + { type: 'file', + name: 'upload_file_0', + data: Buffer.from('ABCDEFGHIJKLMNOPQRSTUVWXYZ'), + info: { + filename: '1k_a.dat', + encoding: '7bit', + mimeType: 'application/octet-stream', + }, + limited: false, + }, + { type: 'file', + name: 'upload_file_1', + data: Buffer.from('ABCDEFGHIJKLMNOPQRSTUVWXYZ'), + info: { + filename: '1k_b.dat', + encoding: '7bit', + mimeType: 'application/octet-stream', + }, + limited: false, + }, + { type: 'file', + name: 'upload_file_2', + data: Buffer.from('ABCDEFGHIJKLMNOPQRSTUVWXYZ'), + info: { + filename: '1k_c.dat', + encoding: '7bit', + mimeType: 'application/octet-stream', + }, + limited: false, + }, + ], + what: 'Files with filenames containing paths' + }, + { source: [ + ['-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k', + 'Content-Disposition: form-data; ' + + 'name="upload_file_0"; filename="/absolute/1k_a.dat"', + 'Content-Type: application/octet-stream', + '', + 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', + '-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k', + 'Content-Disposition: form-data; ' + + 'name="upload_file_1"; filename="C:\\absolute\\1k_b.dat"', + 'Content-Type: application/octet-stream', + '', + 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', + '-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k', + 'Content-Disposition: form-data; ' + + 'name="upload_file_2"; filename="relative/1k_c.dat"', + 'Content-Type: application/octet-stream', + '', + 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', + '-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k--' + ].join('\r\n') + ], + boundary: '---------------------------paZqsnEHRufoShdX6fh0lUhXBP4k', + preservePath: true, + expected: [ + { type: 'file', + name: 'upload_file_0', + data: Buffer.from('ABCDEFGHIJKLMNOPQRSTUVWXYZ'), + info: { + filename: '/absolute/1k_a.dat', + encoding: '7bit', + mimeType: 'application/octet-stream', + }, + limited: false, + }, + { type: 'file', + name: 'upload_file_1', + data: Buffer.from('ABCDEFGHIJKLMNOPQRSTUVWXYZ'), + info: { + filename: 'C:\\absolute\\1k_b.dat', + encoding: '7bit', + mimeType: 'application/octet-stream', + }, + limited: false, + }, + { type: 'file', + name: 'upload_file_2', + data: Buffer.from('ABCDEFGHIJKLMNOPQRSTUVWXYZ'), + info: { + filename: 'relative/1k_c.dat', + encoding: '7bit', + mimeType: 'application/octet-stream', + }, + limited: false, + }, + ], + what: 'Paths to be preserved through the preservePath option' + }, + { source: [ + ['------WebKitFormBoundaryTB2MiQ36fnSJlrhY', + 'Content-Disposition: form-data; name="cont"', + 'Content-Type: ', + '', + 'some random content', + '------WebKitFormBoundaryTB2MiQ36fnSJlrhY', + 'Content-Disposition: ', + '', + 'some random pass', + '------WebKitFormBoundaryTB2MiQ36fnSJlrhY--' + ].join('\r\n') + ], + boundary: '----WebKitFormBoundaryTB2MiQ36fnSJlrhY', + expected: [ + { type: 'field', + name: 'cont', + val: 'some random content', + info: { + nameTruncated: false, + valueTruncated: false, + encoding: '7bit', + mimeType: 'text/plain', + }, + }, + ], + what: 'Empty content-type and empty content-disposition' + }, + { source: [ + ['-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k', + 'Content-Disposition: form-data; ' + + 'name="file"; filename*=utf-8\'\'n%C3%A4me.txt', + 'Content-Type: application/octet-stream', + '', + 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', + '-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k--' + ].join('\r\n') + ], + boundary: '---------------------------paZqsnEHRufoShdX6fh0lUhXBP4k', + expected: [ + { type: 'file', + name: 'file', + data: Buffer.from('ABCDEFGHIJKLMNOPQRSTUVWXYZ'), + info: { + filename: 'näme.txt', + encoding: '7bit', + mimeType: 'application/octet-stream', + }, + limited: false, + }, + ], + what: 'Unicode filenames' + }, + { source: [ + ['--asdasdasdasd\r\n', + 'Content-Type: text/plain\r\n', + 'Content-Disposition: form-data; name="foo"\r\n', + '\r\n', + 'asd\r\n', + '--asdasdasdasd--' + ].join(':)') + ], + boundary: 'asdasdasdasd', + expected: [ + { error: 'Malformed part header' }, + { error: 'Unexpected end of form' }, + ], + what: 'Stopped mid-header' + }, + { source: [ + ['------WebKitFormBoundaryTB2MiQ36fnSJlrhY', + 'Content-Disposition: form-data; name="cont"', + 'Content-Type: application/json', + '', + '{}', + '------WebKitFormBoundaryTB2MiQ36fnSJlrhY--', + ].join('\r\n') + ], + boundary: '----WebKitFormBoundaryTB2MiQ36fnSJlrhY', + expected: [ + { type: 'field', + name: 'cont', + val: '{}', + info: { + nameTruncated: false, + valueTruncated: false, + encoding: '7bit', + mimeType: 'application/json', + }, + }, + ], + what: 'content-type for fields' + }, + { source: [ + '------WebKitFormBoundaryTB2MiQ36fnSJlrhY--', + ], + boundary: '----WebKitFormBoundaryTB2MiQ36fnSJlrhY', + expected: [], + what: 'empty form' + }, + { source: [ + ['-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k', + 'Content-Disposition: form-data; ' + + 'name=upload_file_0; filename="1k_a.dat"', + 'Content-Type: application/octet-stream', + 'Content-Transfer-Encoding: binary', + '', + '', + ].join('\r\n') + ], + boundary: '---------------------------paZqsnEHRufoShdX6fh0lUhXBP4k', + expected: [ + { type: 'file', + name: 'upload_file_0', + data: Buffer.alloc(0), + info: { + filename: '1k_a.dat', + encoding: 'binary', + mimeType: 'application/octet-stream', + }, + limited: false, + err: 'Unexpected end of form', + }, + { error: 'Unexpected end of form' }, + ], + what: 'Stopped mid-file #1' + }, + { source: [ + ['-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k', + 'Content-Disposition: form-data; ' + + 'name=upload_file_0; filename="1k_a.dat"', + 'Content-Type: application/octet-stream', + '', + 'a', + ].join('\r\n') + ], + boundary: '---------------------------paZqsnEHRufoShdX6fh0lUhXBP4k', + expected: [ + { type: 'file', + name: 'upload_file_0', + data: Buffer.from('a'), + info: { + filename: '1k_a.dat', + encoding: '7bit', + mimeType: 'application/octet-stream', + }, + limited: false, + err: 'Unexpected end of form', + }, + { error: 'Unexpected end of form' }, + ], + what: 'Stopped mid-file #2' + }, + { source: [ + ['-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k', + 'Content-Disposition: form-data; ' + + 'name="upload_file_0"; filename="notes.txt"', + 'Content-Type: text/plain; charset=utf8', + '', + 'a', + '-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k--', + ].join('\r\n') + ], + boundary: '---------------------------paZqsnEHRufoShdX6fh0lUhXBP4k', + expected: [ + { type: 'file', + name: 'upload_file_0', + data: Buffer.from('a'), + info: { + filename: 'notes.txt', + encoding: '7bit', + mimeType: 'text/plain', + }, + limited: false, + }, + ], + what: 'Text file with charset' + }, + { source: [ + ['-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k', + 'Content-Disposition: form-data; ' + + 'name="upload_file_0"; filename="notes.txt"', + 'Content-Type: ', + ' text/plain; charset=utf8', + '', + 'a', + '-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k--', + ].join('\r\n') + ], + boundary: '---------------------------paZqsnEHRufoShdX6fh0lUhXBP4k', + expected: [ + { type: 'file', + name: 'upload_file_0', + data: Buffer.from('a'), + info: { + filename: 'notes.txt', + encoding: '7bit', + mimeType: 'text/plain', + }, + limited: false, + }, + ], + what: 'Folded header value' + }, + { source: [ + ['-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k', + 'Content-Type: text/plain; charset=utf8', + '', + 'a', + '-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k--', + ].join('\r\n') + ], + boundary: '---------------------------paZqsnEHRufoShdX6fh0lUhXBP4k', + expected: [], + what: 'No Content-Disposition' + }, + { source: [ + ['-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k', + 'Content-Disposition: form-data; name="file_name_0"', + '', + 'a'.repeat(64 * 1024), + '-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k', + 'Content-Disposition: form-data; ' + + 'name="upload_file_0"; filename="notes.txt"', + 'Content-Type: ', + ' text/plain; charset=utf8', + '', + 'bc', + '-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k--', + ].join('\r\n') + ], + boundary: '---------------------------paZqsnEHRufoShdX6fh0lUhXBP4k', + limits: { + fieldSize: Infinity, + }, + expected: [ + { type: 'file', + name: 'upload_file_0', + data: Buffer.from('bc'), + info: { + filename: 'notes.txt', + encoding: '7bit', + mimeType: 'text/plain', + }, + limited: false, + }, + ], + events: [ 'file' ], + what: 'Skip field parts if no listener' + }, + { source: [ + ['-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k', + 'Content-Disposition: form-data; name="file_name_0"', + '', + 'a', + '-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k', + 'Content-Disposition: form-data; ' + + 'name="upload_file_0"; filename="notes.txt"', + 'Content-Type: ', + ' text/plain; charset=utf8', + '', + 'bc', + '-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k--', + ].join('\r\n') + ], + boundary: '---------------------------paZqsnEHRufoShdX6fh0lUhXBP4k', + limits: { + parts: 1, + }, + expected: [ + { type: 'field', + name: 'file_name_0', + val: 'a', + info: { + nameTruncated: false, + valueTruncated: false, + encoding: '7bit', + mimeType: 'text/plain', + }, + }, + 'partsLimit', + ], + what: 'Parts limit' + }, + { source: [ + ['-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k', + 'Content-Disposition: form-data; name="file_name_0"', + '', + 'a', + '-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k', + 'Content-Disposition: form-data; name="file_name_1"', + '', + 'b', + '-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k--', + ].join('\r\n') + ], + boundary: '---------------------------paZqsnEHRufoShdX6fh0lUhXBP4k', + limits: { + fields: 1, + }, + expected: [ + { type: 'field', + name: 'file_name_0', + val: 'a', + info: { + nameTruncated: false, + valueTruncated: false, + encoding: '7bit', + mimeType: 'text/plain', + }, + }, + 'fieldsLimit', + ], + what: 'Fields limit' + }, + { source: [ + ['-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k', + 'Content-Disposition: form-data; ' + + 'name="upload_file_0"; filename="notes.txt"', + 'Content-Type: text/plain; charset=utf8', + '', + 'ab', + '-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k', + 'Content-Disposition: form-data; ' + + 'name="upload_file_1"; filename="notes2.txt"', + 'Content-Type: text/plain; charset=utf8', + '', + 'cd', + '-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k--', + ].join('\r\n') + ], + boundary: '---------------------------paZqsnEHRufoShdX6fh0lUhXBP4k', + limits: { + files: 1, + }, + expected: [ + { type: 'file', + name: 'upload_file_0', + data: Buffer.from('ab'), + info: { + filename: 'notes.txt', + encoding: '7bit', + mimeType: 'text/plain', + }, + limited: false, + }, + 'filesLimit', + ], + what: 'Files limit' + }, + { source: [ + ['-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k', + 'Content-Disposition: form-data; ' + + `name="upload_file_0"; filename="${'a'.repeat(64 * 1024)}.txt"`, + 'Content-Type: text/plain; charset=utf8', + '', + 'ab', + '-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k', + 'Content-Disposition: form-data; ' + + 'name="upload_file_1"; filename="notes2.txt"', + 'Content-Type: text/plain; charset=utf8', + '', + 'cd', + '-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k--', + ].join('\r\n') + ], + boundary: '---------------------------paZqsnEHRufoShdX6fh0lUhXBP4k', + expected: [ + { error: 'Malformed part header' }, + { type: 'file', + name: 'upload_file_1', + data: Buffer.from('cd'), + info: { + filename: 'notes2.txt', + encoding: '7bit', + mimeType: 'text/plain', + }, + limited: false, + }, + ], + what: 'Oversized part header' + }, + { source: [ + ['-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k', + 'Content-Disposition: form-data; ' + + 'name="upload_file_0"; filename="notes.txt"', + 'Content-Type: text/plain; charset=utf8', + '', + 'a'.repeat(31) + '\r', + ].join('\r\n'), + 'b'.repeat(40), + '\r\n-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k--', + ], + boundary: '---------------------------paZqsnEHRufoShdX6fh0lUhXBP4k', + fileHwm: 32, + expected: [ + { type: 'file', + name: 'upload_file_0', + data: Buffer.from('a'.repeat(31) + '\r' + 'b'.repeat(40)), + info: { + filename: 'notes.txt', + encoding: '7bit', + mimeType: 'text/plain', + }, + limited: false, + }, + ], + what: 'Lookbehind data should not stall file streams' + }, + { source: [ + ['-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k', + 'Content-Disposition: form-data; ' + + `name="upload_file_0"; filename="${'a'.repeat(8 * 1024)}.txt"`, + 'Content-Type: text/plain; charset=utf8', + '', + 'ab', + '-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k', + 'Content-Disposition: form-data; ' + + `name="upload_file_1"; filename="${'b'.repeat(8 * 1024)}.txt"`, + 'Content-Type: text/plain; charset=utf8', + '', + 'cd', + '-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k', + 'Content-Disposition: form-data; ' + + `name="upload_file_2"; filename="${'c'.repeat(8 * 1024)}.txt"`, + 'Content-Type: text/plain; charset=utf8', + '', + 'ef', + '-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k--', + ].join('\r\n') + ], + boundary: '---------------------------paZqsnEHRufoShdX6fh0lUhXBP4k', + expected: [ + { type: 'file', + name: 'upload_file_0', + data: Buffer.from('ab'), + info: { + filename: `${'a'.repeat(8 * 1024)}.txt`, + encoding: '7bit', + mimeType: 'text/plain', + }, + limited: false, + }, + { type: 'file', + name: 'upload_file_1', + data: Buffer.from('cd'), + info: { + filename: `${'b'.repeat(8 * 1024)}.txt`, + encoding: '7bit', + mimeType: 'text/plain', + }, + limited: false, + }, + { type: 'file', + name: 'upload_file_2', + data: Buffer.from('ef'), + info: { + filename: `${'c'.repeat(8 * 1024)}.txt`, + encoding: '7bit', + mimeType: 'text/plain', + }, + limited: false, + }, + ], + what: 'Header size limit should be per part' + }, + { source: [ + '\r\n--d1bf46b3-aa33-4061-b28d-6c5ced8b08ee\r\n', + 'Content-Type: application/gzip\r\n' + + 'Content-Encoding: gzip\r\n' + + 'Content-Disposition: form-data; name=batch-1; filename=batch-1' + + '\r\n\r\n', + '\r\n--d1bf46b3-aa33-4061-b28d-6c5ced8b08ee--', + ], + boundary: 'd1bf46b3-aa33-4061-b28d-6c5ced8b08ee', + expected: [ + { type: 'file', + name: 'batch-1', + data: Buffer.alloc(0), + info: { + filename: 'batch-1', + encoding: '7bit', + mimeType: 'application/gzip', + }, + limited: false, + }, + ], + what: 'Empty part' + }, +]; + +for (const test of tests) { + active.set(test, 1); + + const { what, boundary, events, limits, preservePath, fileHwm } = test; + const bb = busboy({ + fileHwm, + limits, + preservePath, + headers: { + 'content-type': `multipart/form-data; boundary=${boundary}`, + } + }); + const results = []; + + if (events === undefined || events.includes('field')) { + bb.on('field', (name, val, info) => { + results.push({ type: 'field', name, val, info }); + }); + } + + if (events === undefined || events.includes('file')) { + bb.on('file', (name, stream, info) => { + const data = []; + let nb = 0; + const file = { + type: 'file', + name, + data: null, + info, + limited: false, + }; + results.push(file); + stream.on('data', (d) => { + data.push(d); + nb += d.length; + }).on('limit', () => { + file.limited = true; + }).on('close', () => { + file.data = Buffer.concat(data, nb); + assert.strictEqual(stream.truncated, file.limited); + }).once('error', (err) => { + file.err = err.message; + }); + }); + } + + bb.on('error', (err) => { + results.push({ error: err.message }); + }); + + bb.on('partsLimit', () => { + results.push('partsLimit'); + }); + + bb.on('filesLimit', () => { + results.push('filesLimit'); + }); + + bb.on('fieldsLimit', () => { + results.push('fieldsLimit'); + }); + + bb.on('close', () => { + active.delete(test); + + assert.deepStrictEqual( + results, + test.expected, + `[${what}] Results mismatch.\n` + + `Parsed: ${inspect(results)}\n` + + `Expected: ${inspect(test.expected)}` + ); + }); + + for (const src of test.source) { + const buf = (typeof src === 'string' ? Buffer.from(src, 'utf8') : src); + bb.write(buf); + } + bb.end(); +} + +// Byte-by-byte versions +for (let test of tests) { + test = { ...test }; + test.what += ' (byte-by-byte)'; + active.set(test, 1); + + const { what, boundary, events, limits, preservePath, fileHwm } = test; + const bb = busboy({ + fileHwm, + limits, + preservePath, + headers: { + 'content-type': `multipart/form-data; boundary=${boundary}`, + } + }); + const results = []; + + if (events === undefined || events.includes('field')) { + bb.on('field', (name, val, info) => { + results.push({ type: 'field', name, val, info }); + }); + } + + if (events === undefined || events.includes('file')) { + bb.on('file', (name, stream, info) => { + const data = []; + let nb = 0; + const file = { + type: 'file', + name, + data: null, + info, + limited: false, + }; + results.push(file); + stream.on('data', (d) => { + data.push(d); + nb += d.length; + }).on('limit', () => { + file.limited = true; + }).on('close', () => { + file.data = Buffer.concat(data, nb); + assert.strictEqual(stream.truncated, file.limited); + }).once('error', (err) => { + file.err = err.message; + }); + }); + } + + bb.on('error', (err) => { + results.push({ error: err.message }); + }); + + bb.on('partsLimit', () => { + results.push('partsLimit'); + }); + + bb.on('filesLimit', () => { + results.push('filesLimit'); + }); + + bb.on('fieldsLimit', () => { + results.push('fieldsLimit'); + }); + + bb.on('close', () => { + active.delete(test); + + assert.deepStrictEqual( + results, + test.expected, + `[${what}] Results mismatch.\n` + + `Parsed: ${inspect(results)}\n` + + `Expected: ${inspect(test.expected)}` + ); + }); + + for (const src of test.source) { + const buf = (typeof src === 'string' ? Buffer.from(src, 'utf8') : src); + for (let i = 0; i < buf.length; ++i) + bb.write(buf.slice(i, i + 1)); + } + bb.end(); +} + +{ + let exception = false; + process.once('uncaughtException', (ex) => { + exception = true; + throw ex; + }); + process.on('exit', () => { + if (exception || active.size === 0) + return; + process.exitCode = 1; + console.error('=========================='); + console.error(`${active.size} test(s) did not finish:`); + console.error('=========================='); + console.error(Array.from(active.keys()).map((v) => v.what).join('\n')); + }); +} diff --git a/node_modules/busboy/test/test-types-urlencoded.js b/node_modules/busboy/test/test-types-urlencoded.js new file mode 100644 index 0000000..c35962b --- /dev/null +++ b/node_modules/busboy/test/test-types-urlencoded.js @@ -0,0 +1,488 @@ +'use strict'; + +const assert = require('assert'); +const { transcode } = require('buffer'); +const { inspect } = require('util'); + +const busboy = require('..'); + +const active = new Map(); + +const tests = [ + { source: ['foo'], + expected: [ + ['foo', + '', + { nameTruncated: false, + valueTruncated: false, + encoding: 'utf-8', + mimeType: 'text/plain' }, + ], + ], + what: 'Unassigned value' + }, + { source: ['foo=bar'], + expected: [ + ['foo', + 'bar', + { nameTruncated: false, + valueTruncated: false, + encoding: 'utf-8', + mimeType: 'text/plain' }, + ], + ], + what: 'Assigned value' + }, + { source: ['foo&bar=baz'], + expected: [ + ['foo', + '', + { nameTruncated: false, + valueTruncated: false, + encoding: 'utf-8', + mimeType: 'text/plain' }, + ], + ['bar', + 'baz', + { nameTruncated: false, + valueTruncated: false, + encoding: 'utf-8', + mimeType: 'text/plain' }, + ], + ], + what: 'Unassigned and assigned value' + }, + { source: ['foo=bar&baz'], + expected: [ + ['foo', + 'bar', + { nameTruncated: false, + valueTruncated: false, + encoding: 'utf-8', + mimeType: 'text/plain' }, + ], + ['baz', + '', + { nameTruncated: false, + valueTruncated: false, + encoding: 'utf-8', + mimeType: 'text/plain' }, + ], + ], + what: 'Assigned and unassigned value' + }, + { source: ['foo=bar&baz=bla'], + expected: [ + ['foo', + 'bar', + { nameTruncated: false, + valueTruncated: false, + encoding: 'utf-8', + mimeType: 'text/plain' }, + ], + ['baz', + 'bla', + { nameTruncated: false, + valueTruncated: false, + encoding: 'utf-8', + mimeType: 'text/plain' }, + ], + ], + what: 'Two assigned values' + }, + { source: ['foo&bar'], + expected: [ + ['foo', + '', + { nameTruncated: false, + valueTruncated: false, + encoding: 'utf-8', + mimeType: 'text/plain' }, + ], + ['bar', + '', + { nameTruncated: false, + valueTruncated: false, + encoding: 'utf-8', + mimeType: 'text/plain' }, + ], + ], + what: 'Two unassigned values' + }, + { source: ['foo&bar&'], + expected: [ + ['foo', + '', + { nameTruncated: false, + valueTruncated: false, + encoding: 'utf-8', + mimeType: 'text/plain' }, + ], + ['bar', + '', + { nameTruncated: false, + valueTruncated: false, + encoding: 'utf-8', + mimeType: 'text/plain' }, + ], + ], + what: 'Two unassigned values and ampersand' + }, + { source: ['foo+1=bar+baz%2Bquux'], + expected: [ + ['foo 1', + 'bar baz+quux', + { nameTruncated: false, + valueTruncated: false, + encoding: 'utf-8', + mimeType: 'text/plain' }, + ], + ], + what: 'Assigned key and value with (plus) space' + }, + { source: ['foo=bar%20baz%21'], + expected: [ + ['foo', + 'bar baz!', + { nameTruncated: false, + valueTruncated: false, + encoding: 'utf-8', + mimeType: 'text/plain' }, + ], + ], + what: 'Assigned value with encoded bytes' + }, + { source: ['foo%20bar=baz%20bla%21'], + expected: [ + ['foo bar', + 'baz bla!', + { nameTruncated: false, + valueTruncated: false, + encoding: 'utf-8', + mimeType: 'text/plain' }, + ], + ], + what: 'Assigned value with encoded bytes #2' + }, + { source: ['foo=bar%20baz%21&num=1000'], + expected: [ + ['foo', + 'bar baz!', + { nameTruncated: false, + valueTruncated: false, + encoding: 'utf-8', + mimeType: 'text/plain' }, + ], + ['num', + '1000', + { nameTruncated: false, + valueTruncated: false, + encoding: 'utf-8', + mimeType: 'text/plain' }, + ], + ], + what: 'Two assigned values, one with encoded bytes' + }, + { source: [ + Array.from(transcode(Buffer.from('foo'), 'utf8', 'utf16le')).map( + (n) => `%${n.toString(16).padStart(2, '0')}` + ).join(''), + '=', + Array.from(transcode(Buffer.from('😀!'), 'utf8', 'utf16le')).map( + (n) => `%${n.toString(16).padStart(2, '0')}` + ).join(''), + ], + expected: [ + ['foo', + '😀!', + { nameTruncated: false, + valueTruncated: false, + encoding: 'UTF-16LE', + mimeType: 'text/plain' }, + ], + ], + charset: 'UTF-16LE', + what: 'Encoded value with multi-byte charset' + }, + { source: [ + 'foo=<', + Array.from(transcode(Buffer.from('©:^þ'), 'utf8', 'latin1')).map( + (n) => `%${n.toString(16).padStart(2, '0')}` + ).join(''), + ], + expected: [ + ['foo', + '<©:^þ', + { nameTruncated: false, + valueTruncated: false, + encoding: 'ISO-8859-1', + mimeType: 'text/plain' }, + ], + ], + charset: 'ISO-8859-1', + what: 'Encoded value with single-byte, ASCII-compatible, non-UTF8 charset' + }, + { source: ['foo=bar&baz=bla'], + expected: [], + what: 'Limits: zero fields', + limits: { fields: 0 } + }, + { source: ['foo=bar&baz=bla'], + expected: [ + ['foo', + 'bar', + { nameTruncated: false, + valueTruncated: false, + encoding: 'utf-8', + mimeType: 'text/plain' }, + ], + ], + what: 'Limits: one field', + limits: { fields: 1 } + }, + { source: ['foo=bar&baz=bla'], + expected: [ + ['foo', + 'bar', + { nameTruncated: false, + valueTruncated: false, + encoding: 'utf-8', + mimeType: 'text/plain' }, + ], + ['baz', + 'bla', + { nameTruncated: false, + valueTruncated: false, + encoding: 'utf-8', + mimeType: 'text/plain' }, + ], + ], + what: 'Limits: field part lengths match limits', + limits: { fieldNameSize: 3, fieldSize: 3 } + }, + { source: ['foo=bar&baz=bla'], + expected: [ + ['fo', + 'bar', + { nameTruncated: true, + valueTruncated: false, + encoding: 'utf-8', + mimeType: 'text/plain' }, + ], + ['ba', + 'bla', + { nameTruncated: true, + valueTruncated: false, + encoding: 'utf-8', + mimeType: 'text/plain' }, + ], + ], + what: 'Limits: truncated field name', + limits: { fieldNameSize: 2 } + }, + { source: ['foo=bar&baz=bla'], + expected: [ + ['foo', + 'ba', + { nameTruncated: false, + valueTruncated: true, + encoding: 'utf-8', + mimeType: 'text/plain' }, + ], + ['baz', + 'bl', + { nameTruncated: false, + valueTruncated: true, + encoding: 'utf-8', + mimeType: 'text/plain' }, + ], + ], + what: 'Limits: truncated field value', + limits: { fieldSize: 2 } + }, + { source: ['foo=bar&baz=bla'], + expected: [ + ['fo', + 'ba', + { nameTruncated: true, + valueTruncated: true, + encoding: 'utf-8', + mimeType: 'text/plain' }, + ], + ['ba', + 'bl', + { nameTruncated: true, + valueTruncated: true, + encoding: 'utf-8', + mimeType: 'text/plain' }, + ], + ], + what: 'Limits: truncated field name and value', + limits: { fieldNameSize: 2, fieldSize: 2 } + }, + { source: ['foo=bar&baz=bla'], + expected: [ + ['fo', + '', + { nameTruncated: true, + valueTruncated: true, + encoding: 'utf-8', + mimeType: 'text/plain' }, + ], + ['ba', + '', + { nameTruncated: true, + valueTruncated: true, + encoding: 'utf-8', + mimeType: 'text/plain' }, + ], + ], + what: 'Limits: truncated field name and zero value limit', + limits: { fieldNameSize: 2, fieldSize: 0 } + }, + { source: ['foo=bar&baz=bla'], + expected: [ + ['', + '', + { nameTruncated: true, + valueTruncated: true, + encoding: 'utf-8', + mimeType: 'text/plain' }, + ], + ['', + '', + { nameTruncated: true, + valueTruncated: true, + encoding: 'utf-8', + mimeType: 'text/plain' }, + ], + ], + what: 'Limits: truncated zero field name and zero value limit', + limits: { fieldNameSize: 0, fieldSize: 0 } + }, + { source: ['&'], + expected: [], + what: 'Ampersand' + }, + { source: ['&&&&&'], + expected: [], + what: 'Many ampersands' + }, + { source: ['='], + expected: [ + ['', + '', + { nameTruncated: false, + valueTruncated: false, + encoding: 'utf-8', + mimeType: 'text/plain' }, + ], + ], + what: 'Assigned value, empty name and value' + }, + { source: [''], + expected: [], + what: 'Nothing' + }, +]; + +for (const test of tests) { + active.set(test, 1); + + const { what } = test; + const charset = test.charset || 'utf-8'; + const bb = busboy({ + limits: test.limits, + headers: { + 'content-type': `application/x-www-form-urlencoded; charset=${charset}`, + }, + }); + const results = []; + + bb.on('field', (key, val, info) => { + results.push([key, val, info]); + }); + + bb.on('file', () => { + throw new Error(`[${what}] Unexpected file`); + }); + + bb.on('close', () => { + active.delete(test); + + assert.deepStrictEqual( + results, + test.expected, + `[${what}] Results mismatch.\n` + + `Parsed: ${inspect(results)}\n` + + `Expected: ${inspect(test.expected)}` + ); + }); + + for (const src of test.source) { + const buf = (typeof src === 'string' ? Buffer.from(src, 'utf8') : src); + bb.write(buf); + } + bb.end(); +} + +// Byte-by-byte versions +for (let test of tests) { + test = { ...test }; + test.what += ' (byte-by-byte)'; + active.set(test, 1); + + const { what } = test; + const charset = test.charset || 'utf-8'; + const bb = busboy({ + limits: test.limits, + headers: { + 'content-type': `application/x-www-form-urlencoded; charset="${charset}"`, + }, + }); + const results = []; + + bb.on('field', (key, val, info) => { + results.push([key, val, info]); + }); + + bb.on('file', () => { + throw new Error(`[${what}] Unexpected file`); + }); + + bb.on('close', () => { + active.delete(test); + + assert.deepStrictEqual( + results, + test.expected, + `[${what}] Results mismatch.\n` + + `Parsed: ${inspect(results)}\n` + + `Expected: ${inspect(test.expected)}` + ); + }); + + for (const src of test.source) { + const buf = (typeof src === 'string' ? Buffer.from(src, 'utf8') : src); + for (let i = 0; i < buf.length; ++i) + bb.write(buf.slice(i, i + 1)); + } + bb.end(); +} + +{ + let exception = false; + process.once('uncaughtException', (ex) => { + exception = true; + throw ex; + }); + process.on('exit', () => { + if (exception || active.size === 0) + return; + process.exitCode = 1; + console.error('=========================='); + console.error(`${active.size} test(s) did not finish:`); + console.error('=========================='); + console.error(Array.from(active.keys()).map((v) => v.what).join('\n')); + }); +} diff --git a/node_modules/busboy/test/test.js b/node_modules/busboy/test/test.js new file mode 100644 index 0000000..d0380f2 --- /dev/null +++ b/node_modules/busboy/test/test.js @@ -0,0 +1,20 @@ +'use strict'; + +const { spawnSync } = require('child_process'); +const { readdirSync } = require('fs'); +const { join } = require('path'); + +const files = readdirSync(__dirname).sort(); +for (const filename of files) { + if (filename.startsWith('test-')) { + const path = join(__dirname, filename); + console.log(`> Running ${filename} ...`); + const result = spawnSync(`${process.argv0} ${path}`, { + shell: true, + stdio: 'inherit', + windowsHide: true + }); + if (result.status !== 0) + process.exitCode = 1; + } +} diff --git a/node_modules/concat-stream/LICENSE b/node_modules/concat-stream/LICENSE new file mode 100644 index 0000000..99c130e --- /dev/null +++ b/node_modules/concat-stream/LICENSE @@ -0,0 +1,24 @@ +The MIT License + +Copyright (c) 2013 Max Ogden + +Permission is hereby granted, free of charge, +to any person obtaining a copy of this software and +associated documentation files (the "Software"), to +deal in the Software without restriction, including +without limitation the rights to use, copy, modify, +merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom +the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice +shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR +ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. \ No newline at end of file diff --git a/node_modules/concat-stream/index.js b/node_modules/concat-stream/index.js new file mode 100644 index 0000000..dd672a7 --- /dev/null +++ b/node_modules/concat-stream/index.js @@ -0,0 +1,144 @@ +var Writable = require('readable-stream').Writable +var inherits = require('inherits') +var bufferFrom = require('buffer-from') + +if (typeof Uint8Array === 'undefined') { + var U8 = require('typedarray').Uint8Array +} else { + var U8 = Uint8Array +} + +function ConcatStream(opts, cb) { + if (!(this instanceof ConcatStream)) return new ConcatStream(opts, cb) + + if (typeof opts === 'function') { + cb = opts + opts = {} + } + if (!opts) opts = {} + + var encoding = opts.encoding + var shouldInferEncoding = false + + if (!encoding) { + shouldInferEncoding = true + } else { + encoding = String(encoding).toLowerCase() + if (encoding === 'u8' || encoding === 'uint8') { + encoding = 'uint8array' + } + } + + Writable.call(this, { objectMode: true }) + + this.encoding = encoding + this.shouldInferEncoding = shouldInferEncoding + + if (cb) this.on('finish', function () { cb(this.getBody()) }) + this.body = [] +} + +module.exports = ConcatStream +inherits(ConcatStream, Writable) + +ConcatStream.prototype._write = function(chunk, enc, next) { + this.body.push(chunk) + next() +} + +ConcatStream.prototype.inferEncoding = function (buff) { + var firstBuffer = buff === undefined ? this.body[0] : buff; + if (Buffer.isBuffer(firstBuffer)) return 'buffer' + if (typeof Uint8Array !== 'undefined' && firstBuffer instanceof Uint8Array) return 'uint8array' + if (Array.isArray(firstBuffer)) return 'array' + if (typeof firstBuffer === 'string') return 'string' + if (Object.prototype.toString.call(firstBuffer) === "[object Object]") return 'object' + return 'buffer' +} + +ConcatStream.prototype.getBody = function () { + if (!this.encoding && this.body.length === 0) return [] + if (this.shouldInferEncoding) this.encoding = this.inferEncoding() + if (this.encoding === 'array') return arrayConcat(this.body) + if (this.encoding === 'string') return stringConcat(this.body) + if (this.encoding === 'buffer') return bufferConcat(this.body) + if (this.encoding === 'uint8array') return u8Concat(this.body) + return this.body +} + +var isArray = Array.isArray || function (arr) { + return Object.prototype.toString.call(arr) == '[object Array]' +} + +function isArrayish (arr) { + return /Array\]$/.test(Object.prototype.toString.call(arr)) +} + +function isBufferish (p) { + return typeof p === 'string' || isArrayish(p) || (p && typeof p.subarray === 'function') +} + +function stringConcat (parts) { + var strings = [] + var needsToString = false + for (var i = 0; i < parts.length; i++) { + var p = parts[i] + if (typeof p === 'string') { + strings.push(p) + } else if (Buffer.isBuffer(p)) { + strings.push(p) + } else if (isBufferish(p)) { + strings.push(bufferFrom(p)) + } else { + strings.push(bufferFrom(String(p))) + } + } + if (Buffer.isBuffer(parts[0])) { + strings = Buffer.concat(strings) + strings = strings.toString('utf8') + } else { + strings = strings.join('') + } + return strings +} + +function bufferConcat (parts) { + var bufs = [] + for (var i = 0; i < parts.length; i++) { + var p = parts[i] + if (Buffer.isBuffer(p)) { + bufs.push(p) + } else if (isBufferish(p)) { + bufs.push(bufferFrom(p)) + } else { + bufs.push(bufferFrom(String(p))) + } + } + return Buffer.concat(bufs) +} + +function arrayConcat (parts) { + var res = [] + for (var i = 0; i < parts.length; i++) { + res.push.apply(res, parts[i]) + } + return res +} + +function u8Concat (parts) { + var len = 0 + for (var i = 0; i < parts.length; i++) { + if (typeof parts[i] === 'string') { + parts[i] = bufferFrom(parts[i]) + } + len += parts[i].length + } + var u8 = new U8(len) + for (var i = 0, offset = 0; i < parts.length; i++) { + var part = parts[i] + for (var j = 0; j < part.length; j++) { + u8[offset++] = part[j] + } + } + return u8 +} diff --git a/node_modules/concat-stream/package.json b/node_modules/concat-stream/package.json new file mode 100644 index 0000000..f709022 --- /dev/null +++ b/node_modules/concat-stream/package.json @@ -0,0 +1,55 @@ +{ + "name": "concat-stream", + "version": "1.6.2", + "description": "writable stream that concatenates strings or binary data and calls a callback with the result", + "tags": [ + "stream", + "simple", + "util", + "utility" + ], + "author": "Max Ogden ", + "repository": { + "type": "git", + "url": "http://github.com/maxogden/concat-stream.git" + }, + "bugs": { + "url": "http://github.com/maxogden/concat-stream/issues" + }, + "engines": [ + "node >= 0.8" + ], + "main": "index.js", + "files": [ + "index.js" + ], + "scripts": { + "test": "tape test/*.js test/server/*.js" + }, + "license": "MIT", + "dependencies": { + "buffer-from": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^2.2.2", + "typedarray": "^0.0.6" + }, + "devDependencies": { + "tape": "^4.6.3" + }, + "testling": { + "files": "test/*.js", + "browsers": [ + "ie/8..latest", + "firefox/17..latest", + "firefox/nightly", + "chrome/22..latest", + "chrome/canary", + "opera/12..latest", + "opera/next", + "safari/5.1..latest", + "ipad/6.0..latest", + "iphone/6.0..latest", + "android-browser/4.2..latest" + ] + } +} diff --git a/node_modules/concat-stream/readme.md b/node_modules/concat-stream/readme.md new file mode 100644 index 0000000..7aa19c4 --- /dev/null +++ b/node_modules/concat-stream/readme.md @@ -0,0 +1,102 @@ +# concat-stream + +Writable stream that concatenates all the data from a stream and calls a callback with the result. Use this when you want to collect all the data from a stream into a single buffer. + +[![Build Status](https://travis-ci.org/maxogden/concat-stream.svg?branch=master)](https://travis-ci.org/maxogden/concat-stream) + +[![NPM](https://nodei.co/npm/concat-stream.png)](https://nodei.co/npm/concat-stream/) + +### description + +Streams emit many buffers. If you want to collect all of the buffers, and when the stream ends concatenate all of the buffers together and receive a single buffer then this is the module for you. + +Only use this if you know you can fit all of the output of your stream into a single Buffer (e.g. in RAM). + +There are also `objectMode` streams that emit things other than Buffers, and you can concatenate these too. See below for details. + +## Related + +`concat-stream` is part of the [mississippi stream utility collection](https://github.com/maxogden/mississippi) which includes more useful stream modules similar to this one. + +### examples + +#### Buffers + +```js +var fs = require('fs') +var concat = require('concat-stream') + +var readStream = fs.createReadStream('cat.png') +var concatStream = concat(gotPicture) + +readStream.on('error', handleError) +readStream.pipe(concatStream) + +function gotPicture(imageBuffer) { + // imageBuffer is all of `cat.png` as a node.js Buffer +} + +function handleError(err) { + // handle your error appropriately here, e.g.: + console.error(err) // print the error to STDERR + process.exit(1) // exit program with non-zero exit code +} + +``` + +#### Arrays + +```js +var write = concat(function(data) {}) +write.write([1,2,3]) +write.write([4,5,6]) +write.end() +// data will be [1,2,3,4,5,6] in the above callback +``` + +#### Uint8Arrays + +```js +var write = concat(function(data) {}) +var a = new Uint8Array(3) +a[0] = 97; a[1] = 98; a[2] = 99 +write.write(a) +write.write('!') +write.end(Buffer.from('!!1')) +``` + +See `test/` for more examples + +# methods + +```js +var concat = require('concat-stream') +``` + +## var writable = concat(opts={}, cb) + +Return a `writable` stream that will fire `cb(data)` with all of the data that +was written to the stream. Data can be written to `writable` as strings, +Buffers, arrays of byte integers, and Uint8Arrays. + +By default `concat-stream` will give you back the same data type as the type of the first buffer written to the stream. Use `opts.encoding` to set what format `data` should be returned as, e.g. if you if you don't want to rely on the built-in type checking or for some other reason. + +* `string` - get a string +* `buffer` - get back a Buffer +* `array` - get an array of byte integers +* `uint8array`, `u8`, `uint8` - get back a Uint8Array +* `object`, get back an array of Objects + +If you don't specify an encoding, and the types can't be inferred (e.g. you write things that aren't in the list above), it will try to convert concat them into a `Buffer`. + +If nothing is written to `writable` then `data` will be an empty array `[]`. + +# error handling + +`concat-stream` does not handle errors for you, so you must handle errors on whatever streams you pipe into `concat-stream`. This is a general rule when programming with node.js streams: always handle errors on each and every stream. Since `concat-stream` is not itself a stream it does not emit errors. + +We recommend using [`end-of-stream`](https://npmjs.org/end-of-stream) or [`pump`](https://npmjs.org/pump) for writing error tolerant stream code. + +# license + +MIT LICENSE diff --git a/node_modules/core-util-is/LICENSE b/node_modules/core-util-is/LICENSE new file mode 100644 index 0000000..d8d7f94 --- /dev/null +++ b/node_modules/core-util-is/LICENSE @@ -0,0 +1,19 @@ +Copyright Node.js contributors. All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to +deal in the Software without restriction, including without limitation the +rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +sell copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +IN THE SOFTWARE. diff --git a/node_modules/core-util-is/README.md b/node_modules/core-util-is/README.md new file mode 100644 index 0000000..5a76b41 --- /dev/null +++ b/node_modules/core-util-is/README.md @@ -0,0 +1,3 @@ +# core-util-is + +The `util.is*` functions introduced in Node v0.12. diff --git a/node_modules/core-util-is/lib/util.js b/node_modules/core-util-is/lib/util.js new file mode 100644 index 0000000..6e5a20d --- /dev/null +++ b/node_modules/core-util-is/lib/util.js @@ -0,0 +1,107 @@ +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +// NOTE: These type checking functions intentionally don't use `instanceof` +// because it is fragile and can be easily faked with `Object.create()`. + +function isArray(arg) { + if (Array.isArray) { + return Array.isArray(arg); + } + return objectToString(arg) === '[object Array]'; +} +exports.isArray = isArray; + +function isBoolean(arg) { + return typeof arg === 'boolean'; +} +exports.isBoolean = isBoolean; + +function isNull(arg) { + return arg === null; +} +exports.isNull = isNull; + +function isNullOrUndefined(arg) { + return arg == null; +} +exports.isNullOrUndefined = isNullOrUndefined; + +function isNumber(arg) { + return typeof arg === 'number'; +} +exports.isNumber = isNumber; + +function isString(arg) { + return typeof arg === 'string'; +} +exports.isString = isString; + +function isSymbol(arg) { + return typeof arg === 'symbol'; +} +exports.isSymbol = isSymbol; + +function isUndefined(arg) { + return arg === void 0; +} +exports.isUndefined = isUndefined; + +function isRegExp(re) { + return objectToString(re) === '[object RegExp]'; +} +exports.isRegExp = isRegExp; + +function isObject(arg) { + return typeof arg === 'object' && arg !== null; +} +exports.isObject = isObject; + +function isDate(d) { + return objectToString(d) === '[object Date]'; +} +exports.isDate = isDate; + +function isError(e) { + return (objectToString(e) === '[object Error]' || e instanceof Error); +} +exports.isError = isError; + +function isFunction(arg) { + return typeof arg === 'function'; +} +exports.isFunction = isFunction; + +function isPrimitive(arg) { + return arg === null || + typeof arg === 'boolean' || + typeof arg === 'number' || + typeof arg === 'string' || + typeof arg === 'symbol' || // ES6 symbol + typeof arg === 'undefined'; +} +exports.isPrimitive = isPrimitive; + +exports.isBuffer = require('buffer').Buffer.isBuffer; + +function objectToString(o) { + return Object.prototype.toString.call(o); +} diff --git a/node_modules/core-util-is/package.json b/node_modules/core-util-is/package.json new file mode 100644 index 0000000..b0c51f5 --- /dev/null +++ b/node_modules/core-util-is/package.json @@ -0,0 +1,38 @@ +{ + "name": "core-util-is", + "version": "1.0.3", + "description": "The `util.is*` functions introduced in Node v0.12.", + "main": "lib/util.js", + "files": [ + "lib" + ], + "repository": { + "type": "git", + "url": "git://github.com/isaacs/core-util-is" + }, + "keywords": [ + "util", + "isBuffer", + "isArray", + "isNumber", + "isString", + "isRegExp", + "isThis", + "isThat", + "polyfill" + ], + "author": "Isaac Z. Schlueter (http://blog.izs.me/)", + "license": "MIT", + "bugs": { + "url": "https://github.com/isaacs/core-util-is/issues" + }, + "scripts": { + "test": "tap test.js", + "preversion": "npm test", + "postversion": "npm publish", + "prepublishOnly": "git push origin --follow-tags" + }, + "devDependencies": { + "tap": "^15.0.9" + } +} diff --git a/node_modules/image_picker/.npmignore b/node_modules/image_picker/.npmignore new file mode 100644 index 0000000..dd151be --- /dev/null +++ b/node_modules/image_picker/.npmignore @@ -0,0 +1,5 @@ +.DS_Store +.idea +node_modules +bower_components +bower_components diff --git a/node_modules/image_picker/AngularMediaModal/app/app.js b/node_modules/image_picker/AngularMediaModal/app/app.js new file mode 100644 index 0000000..2a5cd30 --- /dev/null +++ b/node_modules/image_picker/AngularMediaModal/app/app.js @@ -0,0 +1 @@ +angular.module("mediaModal", ["ui.bootstrap", "angularSpinner", "flow", "images-resizer", "mediaModalTemplates", "blueimp.fileupload"]); \ No newline at end of file diff --git a/node_modules/image_picker/AngularMediaModal/app/config.js b/node_modules/image_picker/AngularMediaModal/app/config.js new file mode 100644 index 0000000..452bd08 --- /dev/null +++ b/node_modules/image_picker/AngularMediaModal/app/config.js @@ -0,0 +1,77 @@ +angular.module("mediaModal") + .config([ + '$httpProvider', 'fileUploadProvider', '$sceDelegateProvider', + function ($httpProvider, fileUploadProvider, $sceDelegateProvider) { + + $sceDelegateProvider.resourceUrlWhitelist([ + 'self', + 'http://business.otonomic.com/**' + ]); + + delete $httpProvider.defaults.headers.common['X-Requested-With']; + fileUploadProvider.defaults.redirect = window.location.href.replace( + /\/[^\/]*$/, + '/cors/result.html?%s' + ); + // Uploader settings: + angular.extend(fileUploadProvider.defaults, { + autoUpload: true, + forceIframeTransport: true, + // Enable image resizing, egrunt devxcept for Android and Opera, + // which actually support image resizing, but fail to + // send Blob objects via XHR requests: + disableImageResize: /Android(?!.*Chrome)|Opera/ + .test(window.navigator.userAgent), + /*maxFileSize: 9990000,*/ + acceptFileTypes: /(\.|\/)(gif|jpe?g|png)$/i + }); + } + ]) + .constant("flickrApiKey", "b5245e9b9cbaeecee26ea278bfa20253") + .constant("upload_url", "http://business.otonomic.com/api/v1/?cmd=upload") + .constant("instagramApiKey", "44171713.10d405b.25e232f920f94ac9907d8c0ea34ce1de") + //.constant("facebookApiKey", "CAACEdEose0cBAJuIrBZCXQZBpMRGixqcQun3gJAwUL2cfGZB0iuvLu5lpDJy3txqnhAbdbFneQHpZACHiWQsymYxZCtFbH3GuB87NpQdZB8DvtUGIpmZCF3yip71ZBqqajhgw8uPcuaerccTBYZC1gXtGZAV11gvcrYOodJNnT87aJQ1ZBR9T4AX7NHJZCQfmJIIOd6k6oR55ubMZBy7R6ZBAYylzC") + .constant("facebookApiKey", "CAANkofzWtSoBAG0FcIPXZAFFUJjZBdZAiejDxnPXXapvIP1UZA9ZBKiryHlB3GZCjrY1P3rgnKO70AWYQ1bLFp03MxZAuYTaimqsETawQxE9PRhCuLKHZCxDd9wrzJQrqXMsscI80UzPapvPoQKa9WeE7NHIN8H30lb48AhXqZAbVbX7w33nuEhI8ZCPc56VnTS2IZD") + .constant("pexelsApiKey", "563492ad6f9170000100000183fb18af4b5f4b5c6a67f7e6433578ba") + + .factory("availableResources", function(enums){ + return [{ + title: "Camera", + icon: "fa fa-camera", + viewPath: "camera.html" + },{ + title: "Upload", + icon: "fa fa-cloud-upload", + viewPath: "upload.html" + },{ + title: "Flickr", + icon: "fa fa-flickr", + viewPath: "webService.html", + webServiceType: enums.webServiceTypes.flickr + },{ + title: "Instagram", + icon: "fa fa-instagram", + viewPath: "webService.html", + webServiceType: enums.webServiceTypes.instagram + },{ + title: "Background Patterns", + icon: "fa fa-star", + viewPath: "webService.html", + webServiceType: enums.webServiceTypes.backgroundPatterns + },{ + title: "Facebook Photos", + icon: "fa fa-facebook", + viewPath: "webService.html", + webServiceType: enums.webServiceTypes.facebookPhotos + },{ + title: "Facebook Users", + icon: "fa fa-facebook-official", + viewPath: "webService.html", + webServiceType: enums.webServiceTypes.facebookUsers + },{ + title: "Pexels", + icon: "fa fa-picture-o", + viewPath: "webService.html", + webServiceType: enums.webServiceTypes.pexels + }]; + }); \ No newline at end of file diff --git a/node_modules/image_picker/AngularMediaModal/app/controllers/cameraController.js b/node_modules/image_picker/AngularMediaModal/app/controllers/cameraController.js new file mode 100644 index 0000000..6b2da10 --- /dev/null +++ b/node_modules/image_picker/AngularMediaModal/app/controllers/cameraController.js @@ -0,0 +1,49 @@ +/** + * Created by lobanovana on 23.03.2016. + */ +angular.module("mediaModal") +.controller("cameraController", ['$scope', '$http', 'upload_url', function ($scope, $http, upload_url) { + $scope.upload_url = upload_url; + $scope.options = { + url: upload_url + }; + $scope.loadingFiles = true; + $http.get(upload_url) + .then( + function (response) { + $scope.loadingFiles = false; + $scope.filequeue = response.data.files || []; + //console.log($scope.queue); + }, + function () { + $scope.loadingFiles = false; + } + ); + + $scope.selectImage = function(image){ + + //console.log(image); + + image.selected = !image.selected; + image.thumbnail = image.url; + if ($scope.selectedImages){ + if (image.selected){ + $scope.selectedImages.push(image); + return; + } + var imageIndex = $scope.selectedImages.indexOf(image); + if (imageIndex != -1){ + $scope.selectedImages.splice(imageIndex, 1); + } + } + }; + + $scope.$on('fileuploaddone', function(e, data){ + // Your code here + $.each(data.result.files, function (index, file) { + $scope.selectImage(file); + }); + }); + + } +]); \ No newline at end of file diff --git a/node_modules/image_picker/AngularMediaModal/app/controllers/fileDestroyController.js b/node_modules/image_picker/AngularMediaModal/app/controllers/fileDestroyController.js new file mode 100644 index 0000000..3e375b8 --- /dev/null +++ b/node_modules/image_picker/AngularMediaModal/app/controllers/fileDestroyController.js @@ -0,0 +1,32 @@ +angular.module("mediaModal") + .controller('FileDestroyController', [ + '$scope', '$http', 'upload_url', + function ($scope, $http, upload_url) { + var file = $scope.file, + state; + if (file.url) { + file.$state = function () { + return state; + }; + file.$destroy = function () { + state = 'pending'; + return $http({ + url: file.deleteUrl, + method: file.deleteType + }).then( + function () { + state = 'resolved'; + $scope.clear(file); + }, + function () { + state = 'rejected'; + } + ); + }; + } else if (!file.$cancel && !file._index) { + file.$cancel = function () { + $scope.clear(file); + }; + } + } +]); \ No newline at end of file diff --git a/node_modules/image_picker/AngularMediaModal/app/controllers/gallery.js b/node_modules/image_picker/AngularMediaModal/app/controllers/gallery.js new file mode 100644 index 0000000..5267301 --- /dev/null +++ b/node_modules/image_picker/AngularMediaModal/app/controllers/gallery.js @@ -0,0 +1,20 @@ +(function(){ + angular.module("mediaModal").controller("GalleryController", function($scope, availableResources, $uibModalInstance) { + + $scope.availableResources=availableResources; + $scope.appState = {}; + $scope.selectedImages = []; + + $scope.tabSelected = function(resource){ + $scope.$broadcast("currentResourceChanged", resource.webServiceType) + }; + + $scope.cancel = function(){ + $uibModalInstance.dismiss("cancel"); + }; + + $scope.selectImages = function(){ + $uibModalInstance.close($scope.selectedImages); + } + }); +}()); \ No newline at end of file diff --git a/node_modules/image_picker/AngularMediaModal/app/controllers/uploadController.js b/node_modules/image_picker/AngularMediaModal/app/controllers/uploadController.js new file mode 100644 index 0000000..2811e0d --- /dev/null +++ b/node_modules/image_picker/AngularMediaModal/app/controllers/uploadController.js @@ -0,0 +1,43 @@ +angular.module("mediaModal") +.controller("uploadController", ['$scope', '$http', 'upload_url', function ($scope, $http, upload_url) { + $scope.upload_url = upload_url; + $scope.options = { + url: upload_url + }; + $scope.loadingFiles = true; + $http.get(upload_url) + .then( + function (response) { + $scope.loadingFiles = false; + $scope.filequeue = response.data.files || []; + }, + function () { + $scope.loadingFiles = false; + } + ); + + $scope.selectImage = function(image){ + + //console.log(image); + + image.selected = !image.selected; + image.thumbnail = image.url; + if ($scope.selectedImages){ + if (image.selected){ + $scope.selectedImages.push(image); + return; + } + var imageIndex = $scope.selectedImages.indexOf(image); + if (imageIndex != -1){ + $scope.selectedImages.splice(imageIndex, 1); + } + } + }; + $scope.$on('fileuploaddone', function(e, data){ + // Your code here + $.each(data.result.files, function (index, file) { + $scope.selectImage(file); + }); + }); +} +]); \ No newline at end of file diff --git a/node_modules/image_picker/AngularMediaModal/app/controllers/webServicesController.js b/node_modules/image_picker/AngularMediaModal/app/controllers/webServicesController.js new file mode 100644 index 0000000..bc983d4 --- /dev/null +++ b/node_modules/image_picker/AngularMediaModal/app/controllers/webServicesController.js @@ -0,0 +1,63 @@ +angular.module("mediaModal") +.controller("webServicesController", function($scope, webStorage, enums){ + $scope.currentPage = 0; + $scope.itemsOnPage = 25; + $scope.search = function(){ + $scope.appState.loading = true; + webStorage.search($scope.webServiceType, $scope.imageFilter, $scope.currentPage, $scope.itemsOnPage) + .then( + function(result){ + $scope.fullImagesCount = result.count; + $scope.images = result.images; + } + ) + .finally( + function(){ + $scope.appState.loading = false; + } + ); + }; + + $scope.loadMore = function(){ + $scope.appState.loading = true; + webStorage.loadMore($scope.webServiceType) + .then(function(result){ + if (!$scope.images){ + $scope.images = []; + } + $scope.images = $scope.images.concat(result.images); + }) + .finally(function(){ + $scope.appState.loading = false; + }); + }; + + $scope.setWebServiceType = function(webServiceType){ + $scope.webServiceType = webServiceType; + }; + + $scope.selectImage = function(image){ + image.selected = !image.selected; + if ($scope.selectedImages){ + if (image.selected){ + $scope.selectedImages.push(image); + return; + } + var imageIndex = $scope.selectedImages.indexOf(image); + if (imageIndex != -1){ + $scope.selectedImages.splice(imageIndex, 1); + } + } + }; + + $scope.showSearcher = function(){ + return $scope.webServiceType !== enums.webServiceTypes.backgroundPatterns; + }; + + $scope.$on("currentResourceChanged", function(event, resourceType){ + if ($scope.webServiceType === enums.webServiceTypes.backgroundPatterns && resourceType === enums.webServiceTypes.backgroundPatterns){ + $scope.search(); + } + }) + } +); \ No newline at end of file diff --git a/node_modules/image_picker/AngularMediaModal/app/enums.js b/node_modules/image_picker/AngularMediaModal/app/enums.js new file mode 100644 index 0000000..072ceb9 --- /dev/null +++ b/node_modules/image_picker/AngularMediaModal/app/enums.js @@ -0,0 +1,13 @@ +angular.module("mediaModal") + .factory("enums", function(){ + return { + webServiceTypes: { + flickr: 1, + instagram: 2, + backgroundPatterns: 3, + facebookUsers: 4, + pexels: 5 + } + } + } +); \ No newline at end of file diff --git a/node_modules/image_picker/AngularMediaModal/app/services/backgroundPatterns.js b/node_modules/image_picker/AngularMediaModal/app/services/backgroundPatterns.js new file mode 100644 index 0000000..53c55b7 --- /dev/null +++ b/node_modules/image_picker/AngularMediaModal/app/services/backgroundPatterns.js @@ -0,0 +1,51 @@ +angular.module("mediaModal") + .factory("backgroundPatterns", function($http, $q){ + var _currentPage = 0; + var _count = 20; + + function search(searchString, page, count){ + return $http.jsonp("http://www.colourlovers.com/api/patterns/top?format=json&jsonCallback=JSON_CALLBACK&numResults=" + _count + "&resultOffset=" + _currentPage) + .then(function(response){ + _currentPage++; + return { + count: 0, + images: response.data.map(function(image){ + return { + thumbnail: image.imageUrl, + full: image.imageUrl, + title: image.title + }; + }) + }; + }, + function(response){ + return $q.reject(response.data); + } + ); + } + function loadMore(){ + return $http.jsonp("http://www.colourlovers.com/api/patterns/top?format=json&jsonCallback=JSON_CALLBACK&numResults=" + _count + "&resultOffset=" + _currentPage) + .then(function(response){ + _currentPage++; + return { + count: 0, + images: response.data.map(function(image){ + return { + thumbnail: image.imageUrl, + full: image.imageUrl, + title: image.title + }; + }) + }; + }, + function(response){ + return $q.reject(response.data); + } + ); + } + return { + search: search, + loadMore: loadMore + } + } +); \ No newline at end of file diff --git a/node_modules/image_picker/AngularMediaModal/app/services/facebookPhotos.js b/node_modules/image_picker/AngularMediaModal/app/services/facebookPhotos.js new file mode 100644 index 0000000..075d4c4 --- /dev/null +++ b/node_modules/image_picker/AngularMediaModal/app/services/facebookPhotos.js @@ -0,0 +1,55 @@ +angular.module("mediaModal") + .factory("facebookPhotos", function($http, facebookApiKey, $q){ + var _nextUrl; + function search(searchString, page, count){ + return $http.get("https://graph.facebook.com/v2.5/" + searchString + "/photos?access_token=" + facebookApiKey) + .then(function(response){ + _nextUrl = response.data.paging.next; + return { + count: 0, + images: response.data.data.map(function(user){ + return { + thumbnail: "http://graph.facebook.com/" + user.id + "/picture?type=normal", + full: "http://graph.facebook.com/" + user.id + "/picture?type=large", + title: user.name + }; + }) + }; + }, + function(response){ + return $q.reject(response.data); + } + ); + } + + function loadMore(){ + + if (!_nextUrl){ + return $q.reject("no more images"); + } + + return $http.get(_nextUrl) + .then(function(response){ + _nextUrl = response.data.paging.next; + return { + count: 0, + images: response.data.data.map(function(user){ + return { + thumbnail: "http://graph.facebook.com/" + user.id + "/picture?type=normal", + full: "http://graph.facebook.com/" + user.id + "/picture?type=large", + title: user.name + }; + }) + }; + }, + function(response){ + return $q.reject(response.data); + } + ); + } + return { + search: search, + loadMore: loadMore + } + } +); \ No newline at end of file diff --git a/node_modules/image_picker/AngularMediaModal/app/services/facebookUsers.js b/node_modules/image_picker/AngularMediaModal/app/services/facebookUsers.js new file mode 100644 index 0000000..c00d848 --- /dev/null +++ b/node_modules/image_picker/AngularMediaModal/app/services/facebookUsers.js @@ -0,0 +1,53 @@ +angular.module("mediaModal") + .factory("facebookUsers", function($http, facebookApiKey, $q){ + var _nextUrl; + function search(searchString, page, count){ + return $http.get("https://graph.facebook.com/v2.2/search?q=" + searchString + "&type=user&access_token=" + facebookApiKey) + .then(function(response){ + _nextUrl = response.data.paging.next; + return { + count: 0, + images: response.data.data.map(function(user){ + return { + thumbnail: "http://graph.facebook.com/" + user.id + "/picture?type=normal", + full: "http://graph.facebook.com/" + user.id + "/picture?type=large", + title: user.name + }; + }) + }; + }, + function(response){ + return $q.reject(response.data); + } + ); + } + + function loadMore(){ + if (!_nextUrl){ + return $q.reject("no more images"); + } + return $http.get(_nextUrl) + .then(function(response){ + _nextUrl = response.data.paging.next; + return { + count: 0, + images: response.data.data.map(function(user){ + return { + thumbnail: "http://graph.facebook.com/" + user.id + "/picture?type=normal", + full: "http://graph.facebook.com/" + user.id + "/picture?type=large", + title: user.name + }; + }) + }; + }, + function(response){ + return $q.reject(response.data); + } + ); + } + return { + search: search, + loadMore: loadMore + } + } +); \ No newline at end of file diff --git a/node_modules/image_picker/AngularMediaModal/app/services/flickr.js b/node_modules/image_picker/AngularMediaModal/app/services/flickr.js new file mode 100644 index 0000000..e7442d4 --- /dev/null +++ b/node_modules/image_picker/AngularMediaModal/app/services/flickr.js @@ -0,0 +1,56 @@ +angular.module("mediaModal") +.factory("flickr", function($http, flickrApiKey){ + var _lastPage = 1; + var _count = 50; + var _searchString = ""; + function search(searchString, page, count){ + _lastPage = 1; + _count = count; + _searchString = searchString; + return $http.get("https://api.flickr.com/services/rest/?api_key=" + flickrApiKey + + "&format=json&nojsoncallback=1&method=flickr.photos.search&format=json&page=" + page + "&per_page=" + + count +"&text=" + searchString) + .then(function(response){ + + return { + count: +response.data.photos.total, + images: response.data.photos.photo.map(function(photo){ + return { + thumbnail: "https://farm" + photo.farm + ".staticflickr.com/" + photo.server + "/" + photo.id + "_" + photo.secret + "_t.jpg", + full: "https://farm" + photo.farm + ".staticflickr.com/" + photo.server + "/" + photo.id + "_" + photo.secret + ".jpg" + }; + }) + }; + }, + function(response){ + return $q.reject(response.data); + } + ); + } + function loadMore(){ + _lastPage++; + return $http.get("https://api.flickr.com/services/rest/?api_key=" + flickrApiKey + + "&format=json&nojsoncallback=1&method=flickr.photos.search&format=json&page=" + _lastPage + "&per_page=" + + _count +"&text=" + _searchString) + .then(function(response){ + return { + count: +response.data.photos.total, + images: response.data.photos.photo.map(function(photo){ + return { + thumbnail: "https://farm" + photo.farm + ".staticflickr.com/" + photo.server + "/" + photo.id + "_" + photo.secret + "_t.jpg", + full: "https://farm" + photo.farm + ".staticflickr.com/" + photo.server + "/" + photo.id + "_" + photo.secret + ".jpg" + }; + }) + }; + }, + function(response){ + return $q.reject(response.data); + } + ); + } + return { + search: search, + loadMore: loadMore + } + } +); \ No newline at end of file diff --git a/node_modules/image_picker/AngularMediaModal/app/services/gallery.js b/node_modules/image_picker/AngularMediaModal/app/services/gallery.js new file mode 100644 index 0000000..eec67f0 --- /dev/null +++ b/node_modules/image_picker/AngularMediaModal/app/services/gallery.js @@ -0,0 +1,15 @@ +angular.module("mediaModal") + .factory("gallery", function($q, $uibModal){ + function open(animate){ + return $uibModal.open({ + animation: animate, + templateUrl: 'galleryWindow.html', + controller: 'GalleryController', + size: "lg" + }).result; + } + return { + open: open + } + } +); \ No newline at end of file diff --git a/node_modules/image_picker/AngularMediaModal/app/services/instagram.js b/node_modules/image_picker/AngularMediaModal/app/services/instagram.js new file mode 100644 index 0000000..ff76021 --- /dev/null +++ b/node_modules/image_picker/AngularMediaModal/app/services/instagram.js @@ -0,0 +1,53 @@ +angular.module("mediaModal") +.factory("instagram", function($http, instagramApiKey, $q){ + var _nextUrl; + function search(searchString, page, count){ + return $http.jsonp("https://api.instagram.com/v1/tags/" + searchString +"/media/recent?access_token=" + instagramApiKey + "&callback=JSON_CALLBACK&count=20") + .then(function(response){ + _nextUrl = response.data.pagination.next_url; + return { + count: 0, + images: response.data.data.map(function(image){ + return { + thumbnail: image.images.thumbnail.url, + full: image.images.standard_resolution.url, + title: image.caption.text.substr(0, 50) + }; + }) + }; + }, + function(response){ + return $q.reject(response.data); + } + ); + } + + function loadMore(){ + if (!_nextUrl){ + return $q.reject("no more images"); + } + return $http.jsonp(_nextUrl + "&callback=JSON_CALLBACK") + .then(function(response){ + _nextUrl = response.data.pagination.next_url; + return { + count: 0, + images: response.data.data.map(function(image){ + return { + thumbnail: image.images.thumbnail.url, + full: image.images.standard_resolution.url, + title: image.caption.text.substr(0, 50) + }; + }) + }; + }, + function(response){ + return $q.reject(response.data); + } + ); + } + return { + search: search, + loadMore: loadMore + } + } +); \ No newline at end of file diff --git a/node_modules/image_picker/AngularMediaModal/app/services/pexels.js b/node_modules/image_picker/AngularMediaModal/app/services/pexels.js new file mode 100644 index 0000000..4701293 --- /dev/null +++ b/node_modules/image_picker/AngularMediaModal/app/services/pexels.js @@ -0,0 +1,30 @@ +angular.module("mediaModal") +.factory("pexels", function($http, pexelsApiKey, $q){ + function search(searchString, page, count){ + return $http.get("http://api.pexels.com/v1/search?query=" + searchString + "&per_page=" + count + "&page=" + (page + 1), { + headers: { + "Authorization": pexelsApiKey + } + }) + .then(function(response){ + return { + count: response.data.total_results, + images: response.data.photos.map(function(photo){ + return { + thumbnail: photo.src.square, + full: photo.src.large, + title: photo.photographer + }; + }) + }; + }, + function(response){ + return $q.reject(response.data); + } + ); + } + return { + search: search + } + } +); \ No newline at end of file diff --git a/node_modules/image_picker/AngularMediaModal/app/services/webStorage.js b/node_modules/image_picker/AngularMediaModal/app/services/webStorage.js new file mode 100644 index 0000000..fbeebc4 --- /dev/null +++ b/node_modules/image_picker/AngularMediaModal/app/services/webStorage.js @@ -0,0 +1,32 @@ +angular.module("mediaModal") +.factory("webStorage", function(enums, flickr, instagram, backgroundPatterns, facebookUsers, pexels, facebookPhotos){ + function getService(serviceType){ + switch (serviceType){ + case enums.webServiceTypes.flickr: + return flickr; + case enums.webServiceTypes.instagram: + return instagram; + case enums.webServiceTypes.backgroundPatterns: + return backgroundPatterns; + case enums.webServiceTypes.facebookUsers: + return facebookUsers; + case enums.webServiceTypes.pexels: + return pexels; + case enums.webServiceTypes.facebookPhotos: + return facebookPhotos; + } + } + function search(storageType, searchString, page, count){ + return getService(storageType).search(searchString, page, count); + } + + function loadMore(storageType){ + return getService(storageType).loadMore(); + } + + return{ + search: search, + loadMore: loadMore + } + } +); \ No newline at end of file diff --git a/node_modules/image_picker/AngularMediaModal/app/styles/media-modal.css b/node_modules/image_picker/AngularMediaModal/app/styles/media-modal.css new file mode 100644 index 0000000..fd36ba0 --- /dev/null +++ b/node_modules/image_picker/AngularMediaModal/app/styles/media-modal.css @@ -0,0 +1,68 @@ +#footer { + position: fixed; + bottom: 0; + width: 100%; + height: 60px; + background-color: #f5f5f5; + line-height: 60px; + z-index: 1000; +} +#otonomic-media-modal { + +} +#otonomic-media-modal .media-wrapper { + position: relative; + margin-top: 20px; + border: 3px solid transparent; + overflow: hidden; +} +#otonomic-media-modal .image-wrapper { + height:200px; + position: relative; + + -webkit-box-shadow: inset 0 0 15px rgba( 0, 0, 0, 0.1 ), + inset 0 0 0 1px rgba( 0, 0, 0, 0.05 ); + box-shadow: inset 0 0 15px rgba( 0, 0, 0, 0.1 ), + inset 0 0 0 1px rgba( 0, 0, 0, 0.05 ); + background: #eee; + cursor: pointer; +} +#otonomic-media-modal .image-wrapper .centered { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + -webkit-transform: translate( 50%, 50% ); + -ms-transform: translate(50%,50%); + transform: translate( 50%, 50% ); +} +#otonomic-media-modal .image-wrapper img{ + position: absolute; + top: 0; + left: 0; + + -webkit-transform: translate( -50%, -50% ); + -ms-transform: translate(-50%,-50%); + transform: translate( -50%, -50% ); +} +#otonomic-media-modal .media-wrapper .image-title { + position: absolute; + bottom: 0; + left: 0; + right: 0; + color: #fff; + background: rgba(0,0,0,0.5); + padding: 5px; +} +#otonomic-media-modal .media-wrapper.selected { + border-color:#1e8cbe; +} +#otonomic-media-modal #tab-footer { + padding: 10px 0; +} + +.media-resource{ + max-height: 500px; + overflow-y: auto; +} \ No newline at end of file diff --git a/node_modules/image_picker/AngularMediaModal/app/views/camera.html b/node_modules/image_picker/AngularMediaModal/app/views/camera.html new file mode 100644 index 0000000..8ab8af3 --- /dev/null +++ b/node_modules/image_picker/AngularMediaModal/app/views/camera.html @@ -0,0 +1,82 @@ +
+

Upload Media

+
+
+ +
+
+ + + + Add files... + + + + + + +
+ +
+ +
+ +
 
+
+
+
+
+
+
+
+
+
+ +
+
+
{{file.name}}
+
+
+
+
+
+ +
+
+ +
+
+
+
+
+
+ +
+
+
{{file.name}}
+
+
+
+
+
+ +
+
+ +
+ +
+
+
+
+
\ No newline at end of file diff --git a/node_modules/image_picker/AngularMediaModal/app/views/gallery.html b/node_modules/image_picker/AngularMediaModal/app/views/gallery.html new file mode 100644 index 0000000..b671daa --- /dev/null +++ b/node_modules/image_picker/AngularMediaModal/app/views/gallery.html @@ -0,0 +1,12 @@ +
+ + + + + {{resource.title}} + +
+
+
+
+ \ No newline at end of file diff --git a/node_modules/image_picker/AngularMediaModal/app/views/galleryWindow.html b/node_modules/image_picker/AngularMediaModal/app/views/galleryWindow.html new file mode 100644 index 0000000..a46c170 --- /dev/null +++ b/node_modules/image_picker/AngularMediaModal/app/views/galleryWindow.html @@ -0,0 +1,10 @@ + + + \ No newline at end of file diff --git a/node_modules/image_picker/AngularMediaModal/app/views/upload.html b/node_modules/image_picker/AngularMediaModal/app/views/upload.html new file mode 100644 index 0000000..b841b7b --- /dev/null +++ b/node_modules/image_picker/AngularMediaModal/app/views/upload.html @@ -0,0 +1,82 @@ +
+

Upload Media

+
+
+ +
+
+ + + + Add files... + + + + + + +
+ +
+ +
+ +
 
+
+
+
+
+
+
+
+
+
+ +
+
+
{{file.name}}
+
+
+
+
+
+ +
+
+ +
+
+
+
+
+
+ +
+
+
{{file.name}}
+
+
+
+
+
+ +
+
+ +
+ +
+
+
+
+
\ No newline at end of file diff --git a/node_modules/image_picker/AngularMediaModal/app/views/webService.html b/node_modules/image_picker/AngularMediaModal/app/views/webService.html new file mode 100644 index 0000000..0422a1a --- /dev/null +++ b/node_modules/image_picker/AngularMediaModal/app/views/webService.html @@ -0,0 +1,33 @@ +
+

{{resource.title}}

+
+
+ + + + +
+
+ +
+
+
+
+
+
+ +
+
+
{{image.title}}
+
+
+
+
+ +
+ +
+ +
\ No newline at end of file diff --git a/node_modules/image_picker/AngularMediaModal/assets/angular/angular-images-resizer.js b/node_modules/image_picker/AngularMediaModal/assets/angular/angular-images-resizer.js new file mode 100644 index 0000000..1ef4c2a --- /dev/null +++ b/node_modules/image_picker/AngularMediaModal/assets/angular/angular-images-resizer.js @@ -0,0 +1 @@ +"use strict";angular.module("images-resizer",[]),angular.module("images-resizer").service("resizeService",["$q","$document","$window",function(e,r,t){var i,a=this,n=!(!r[0].createElement("canvas").getContext||!r[0].createElement("canvas").getContext("2d"));this.createImage=function(r,i){var a=e.defer(),n=new t.Image;return i&&(n.crossOrigin=i),n.onload=function(){a.resolve(n)},n.onabort=function(){a.reject("image creation was aborted")},n.onerror=function(e){a.reject(e)},n.src=r,a.promise},this.resizeCanvas=function(e,t,i){if(!t||!i)return e;var a=r[0].createElement("canvas");a.width=t,a.height=i;var n=a.getContext("2d");return n.drawImage(e,0,0,a.width,a.height),a},this.resizeImage=function(r,t){if(!n)return e.reject("Canvas is not supported on your browser");if(!t||!r)return e.reject("Missing argument when calling resizeImage function");var i=e.defer();return t={height:t.height?t.height:t.width?null:t.size?null:1024,width:t.width?t.width:t.height?null:t.size?null:1024,size:t.size?t.size:500,sizeScale:t.sizeScale?t.sizeScale:"ko",step:t.step?t.step:3,outputFormat:t.outputFormat?t.outputFormat:"image/jpeg",crossOrigin:t.crossOrigin?t.crossOrigin:null},a.createImage(r,t.crossOrigin).then(function(e){if(t.height||t.width)i.resolve(a.resizeImageWidthHeight(e,t.width,t.height,t.step,t.outputFormat));else if(t.size){if(angular.isString(t.sizeScale))switch(t.sizeScale.toLowerCase()){case"ko":t.size*=1024;break;case"mo":t.size*=1048576;break;case"go":t.size*=1073741824}i.resolve(a.resizeImageBySize(e,t.size,t.outputFormat))}else i.reject("Missing option to resize the image")})["catch"](i.reject),i.promise},this.resizeImageWidthHeight=function(e,t,a,n,s){if(!e)return null;s||(s="image/jpeg"),i=r[0].createElement("canvas"),t||a?!t&&a?t=a/e.height*e.width:t&&!a&&(a=t/e.width*e.height):(t=e.width,a=e.height);var o=e.width!==t&&n?(e.width-t)/n:0,c=e.height!==a&&n?(e.height-a)/n:0;i.width=e.width,i.height=e.height,i.getContext("2d").drawImage(e,0,0,i.width,i.height);for(var h=1;n>h;h++){var g=e.width-o*h,l=e.height-c*h;i=this.resizeCanvas(i,g,l)}return i=this.resizeCanvas(i,t,a),i.toDataURL(s)},this.resizeImageBySize=function(e,t,n){if(!e)return null;n||(n="image/jpeg"),i=r[0].createElement("canvas"),i.width=e.width,i.height=e.height,i.getContext("2d").drawImage(e,0,0,i.width,i.height);for(var s=i.toDataURL(n),o=s,c=a.calulateImageSize(s,n),h=1>=c/(2*t)?.9:c/(2.3*t);c>t;){var g=r[0].createElement("canvas");g.width=i.width/h,g.height=i.height/h,g.getContext("2d").drawImage(i,0,0,g.width,g.height),s=i.toDataURL(n);var l=a.calulateImageSize(s,n);.5>l/t?h=1>=c/(2*t)?.9:c/(2.3*t):(i=g,o=s,c=a.calulateImageSize(s,n)),i=g}return o},this.calulateImageSize=function(e,r){switch(r){case"image/jpeg":r="image/jpg";break;default:r="image/jpg"}return Math.round(3*(e.length-("data:"+r+";base64,").length)/4)}}]),angular.module("images-resizer").service("readLocalPicService",["$q","$window",function(e,r){function t(e){var r=null;switch(e.target.error.code){case FileError.NOT_FOUND_ERR:r="NOT_FOUND_ERR";break;case FileError.SECURITY_ERR:r="SECURITY_ERR";break;case FileError.ABORT_ERR:r="ABORT_ERR";break;case FileError.NOT_READABLE_ERR:r="NOT_READABLE_ERR";break;case FileError.ENCODING_ERR:r="ENCODING_ERR";break;case FileError.NO_MODIFICATION_ALLOWED_ERR:r="NO_MODIFICATION_ALLOWED_ERR";break;case FileError.INVALID_STATE_ERR:r="INVALID_STATE_ERR";break;case FileError.SYNTAX_ERR:r="SYNTAX_ERR";break;case FileError.INVALID_MODIFICATION_ERR:r="INVALID_MODIFICATION_ERR";break;case FileError.QUOTA_EXCEEDED_ERR:r="QUOTA_EXCEEDED_ERR";break;case FileError.TYPE_MISMATCH_ERR:r="TYPE_MISMATCH_ERR";break;case FileError.PATH_EXISTS_ERR:r="PATH_EXISTS_ERR";break;default:r="Unknown Error: "+e.target.error.code}return r}this.readFileInput=function(i){var a=e.defer();if(i.files&&i.files[0]){r.File&&r.FileReader&&r.FileList&&r.Blob||a.reject("Your browser do not support reading file");var n=new r.FileReader;n.onload=function(e){a.resolve(e.target.result)},n.onabort=function(e){a.reject("Fail to convert file in base64img, aborded: "+t(e))},n.onerror=function(e){a.reject("Fail to convert file in base64img, error: "+t(e))},n.readAsDataURL(i.files[0])}else a.reject("No file selected");return a.promise}}]); \ No newline at end of file diff --git a/node_modules/image_picker/AngularMediaModal/assets/fileupload/css/demo-ie8.css b/node_modules/image_picker/AngularMediaModal/assets/fileupload/css/demo-ie8.css new file mode 100644 index 0000000..861852a --- /dev/null +++ b/node_modules/image_picker/AngularMediaModal/assets/fileupload/css/demo-ie8.css @@ -0,0 +1,21 @@ +@charset "UTF-8"; +/* + * jQuery File Upload Demo CSS Fixes for IE<9 + * https://github.com/blueimp/jQuery-File-Upload + * + * Copyright 2013, Sebastian Tschan + * https://blueimp.net + * + * Licensed under the MIT license: + * http://www.opensource.org/licenses/MIT + */ + +.navigation { + list-style: none; + padding: 0; + margin: 1em 0; +} +.navigation li { + display: inline; + margin-right: 10px; +} diff --git a/node_modules/image_picker/AngularMediaModal/assets/fileupload/css/demo.css b/node_modules/image_picker/AngularMediaModal/assets/fileupload/css/demo.css new file mode 100644 index 0000000..83af7ad --- /dev/null +++ b/node_modules/image_picker/AngularMediaModal/assets/fileupload/css/demo.css @@ -0,0 +1,67 @@ +@charset "UTF-8"; +/* + * jQuery File Upload Demo CSS + * https://github.com/blueimp/jQuery-File-Upload + * + * Copyright 2013, Sebastian Tschan + * https://blueimp.net + * + * Licensed under the MIT license: + * http://www.opensource.org/licenses/MIT + */ + +body { + max-width: 750px; + margin: 0 auto; + padding: 1em; + font-family: "Lucida Grande", "Lucida Sans Unicode", Arial, sans-serif; + font-size: 1em; + line-height: 1.4em; + background: #222; + color: #fff; + -webkit-text-size-adjust: 100%; + -ms-text-size-adjust: 100%; +} +a { + color: orange; + text-decoration: none; +} +img { + border: 0; + vertical-align: middle; +} +h1 { + line-height: 1em; +} +blockquote { + padding: 0 0 0 15px; + margin: 0 0 20px; + border-left: 5px solid #eee; +} +table { + width: 100%; + margin: 10px 0; +} + +.fileupload-progress { + margin: 10px 0; +} +.fileupload-progress .progress-extended { + margin-top: 5px; +} +.error { + color: red; +} + +@media (min-width: 481px) { + .navigation { + list-style: none; + padding: 0; + } + .navigation li { + display: inline-block; + } + .navigation li:not(:first-child):before { + content: "| "; + } +} diff --git a/node_modules/image_picker/AngularMediaModal/assets/fileupload/css/jquery.fileupload-noscript.css b/node_modules/image_picker/AngularMediaModal/assets/fileupload/css/jquery.fileupload-noscript.css new file mode 100644 index 0000000..d0dda3a --- /dev/null +++ b/node_modules/image_picker/AngularMediaModal/assets/fileupload/css/jquery.fileupload-noscript.css @@ -0,0 +1,22 @@ +@charset "UTF-8"; +/* + * jQuery File Upload Plugin NoScript CSS + * https://github.com/blueimp/jQuery-File-Upload + * + * Copyright 2013, Sebastian Tschan + * https://blueimp.net + * + * Licensed under the MIT license: + * http://www.opensource.org/licenses/MIT + */ + +.fileinput-button input { + position: static; + opacity: 1; + filter: none; + font-size: inherit; + direction: inherit; +} +.fileinput-button span { + display: none; +} diff --git a/node_modules/image_picker/AngularMediaModal/assets/fileupload/css/jquery.fileupload-ui-noscript.css b/node_modules/image_picker/AngularMediaModal/assets/fileupload/css/jquery.fileupload-ui-noscript.css new file mode 100644 index 0000000..6ad2ad6 --- /dev/null +++ b/node_modules/image_picker/AngularMediaModal/assets/fileupload/css/jquery.fileupload-ui-noscript.css @@ -0,0 +1,17 @@ +@charset "UTF-8"; +/* + * jQuery File Upload UI Plugin NoScript CSS + * https://github.com/blueimp/jQuery-File-Upload + * + * Copyright 2012, Sebastian Tschan + * https://blueimp.net + * + * Licensed under the MIT license: + * http://www.opensource.org/licenses/MIT + */ + +.fileinput-button i, +.fileupload-buttonbar .delete, +.fileupload-buttonbar .toggle { + display: none; +} diff --git a/node_modules/image_picker/AngularMediaModal/assets/fileupload/css/jquery.fileupload-ui.css b/node_modules/image_picker/AngularMediaModal/assets/fileupload/css/jquery.fileupload-ui.css new file mode 100644 index 0000000..95cca15 --- /dev/null +++ b/node_modules/image_picker/AngularMediaModal/assets/fileupload/css/jquery.fileupload-ui.css @@ -0,0 +1,57 @@ +@charset "UTF-8"; +/* + * jQuery File Upload UI Plugin CSS + * https://github.com/blueimp/jQuery-File-Upload + * + * Copyright 2010, Sebastian Tschan + * https://blueimp.net + * + * Licensed under the MIT license: + * http://www.opensource.org/licenses/MIT + */ + +.fileupload-buttonbar .btn, +.fileupload-buttonbar .toggle { + margin-bottom: 5px; +} +.progress-animated .progress-bar, +.progress-animated .bar { + background: url("../img/progressbar.gif") !important; + filter: none; +} +.fileupload-process { + float: right; + display: none; +} +.fileupload-processing .fileupload-process, +.files .processing .preview { + display: block; + width: 32px; + height: 32px; + background: url("../img/loading.gif") center no-repeat; + background-size: contain; +} +.files audio, +.files video { + max-width: 300px; +} + +@media (max-width: 767px) { + .fileupload-buttonbar .toggle, + .files .toggle, + .files .btn span { + display: none; + } + .files .name { + width: 80px; + word-wrap: break-word; + } + .files audio, + .files video { + max-width: 80px; + } + .files img, + .files canvas { + max-width: 100%; + } +} diff --git a/node_modules/image_picker/AngularMediaModal/assets/fileupload/css/jquery.fileupload.css b/node_modules/image_picker/AngularMediaModal/assets/fileupload/css/jquery.fileupload.css new file mode 100644 index 0000000..f714c4d --- /dev/null +++ b/node_modules/image_picker/AngularMediaModal/assets/fileupload/css/jquery.fileupload.css @@ -0,0 +1,37 @@ +@charset "UTF-8"; +/* + * jQuery File Upload Plugin CSS + * https://github.com/blueimp/jQuery-File-Upload + * + * Copyright 2013, Sebastian Tschan + * https://blueimp.net + * + * Licensed under the MIT license: + * http://www.opensource.org/licenses/MIT + */ + +.fileinput-button { + position: relative; + overflow: hidden; + display: inline-block; +} +.fileinput-button input { + position: absolute; + top: 0; + right: 0; + margin: 0; + opacity: 0; + -ms-filter: 'alpha(opacity=0)'; + font-size: 200px !important; + direction: ltr; + cursor: pointer; +} + +/* Fixes for IE < 8 */ +@media screen\9 { + .fileinput-button input { + filter: alpha(opacity=0); + font-size: 100%; + height: 100%; + } +} diff --git a/node_modules/image_picker/AngularMediaModal/assets/fileupload/css/style.css b/node_modules/image_picker/AngularMediaModal/assets/fileupload/css/style.css new file mode 100644 index 0000000..c33c03d --- /dev/null +++ b/node_modules/image_picker/AngularMediaModal/assets/fileupload/css/style.css @@ -0,0 +1,15 @@ +@charset "UTF-8"; +/* + * jQuery File Upload Plugin CSS Example + * https://github.com/blueimp/jQuery-File-Upload + * + * Copyright 2013, Sebastian Tschan + * https://blueimp.net + * + * Licensed under the MIT license: + * http://www.opensource.org/licenses/MIT + */ + +body { + padding-top: 60px; +} diff --git a/node_modules/image_picker/AngularMediaModal/assets/fileupload/img/loading.gif b/node_modules/image_picker/AngularMediaModal/assets/fileupload/img/loading.gif new file mode 100644 index 0000000000000000000000000000000000000000..90f28cbdbb390b095e0d619cbe8d91208798e58f GIT binary patch literal 3897 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 D^>?LD literal 0 HcmV?d00001 diff --git a/node_modules/image_picker/AngularMediaModal/assets/fileupload/img/progressbar.gif b/node_modules/image_picker/AngularMediaModal/assets/fileupload/img/progressbar.gif new file mode 100644 index 0000000000000000000000000000000000000000..fbcce6bc9abfcc7893e65ef20b3e77ee16ec37b1 GIT binary patch literal 3323 zcmcK7eNYp38VB%=tbvu|O{=v;j+Z$Bg`>zI2IVD3Ovp-%sA$2LilR;|zMv9BB_t%- zylmcIh`dQLinJh9P{0Ja6I28#3PquysCf3GB1bBEXgfV_qnx8-n07k#kNmNdnVrvm zpZz`0v#~Ih7`{0em<2RA0wa|vr`!U1uI~v zF&O+zyfdOBoHdq{xOf7$)mN>lwcHgLVd-HT}JxYe94Gv(X_H__aHV~b7Ud4Pvk@t?84W?#& z+#WmNc5cUNoapyda#-P`2+n z-sGAbb$_$TW887}6BiJKaV$E-69{23pNeG2=_;8-CM9c*OiHOp|F9Igl_JQHqf)Y3 zhR7s(mQBh5ER~A&6^|p|OQqmd+(8 zZCb1qD*K}>!|B|YVuCsS=V0!N=?-)po(^$x*kptYX9N)B^~VJ^d7-@(M1WK;9&Liw~wO?NN(E}be} z`e0?=)*y}@Xkgx6S$ZS(8d|ifzK_`x?=N$)pWk;s0Ve?`qDX{%L@X0)6guz8Adh+qh(mq90aE=iAZp(dGccGHgmlkOke19p-dfci z@1Ni#x_B;&@l8icy*4roEXN?9&vh-w;n!Fp3;~Dn4#aOVi22tbo-KY@B1*TAgMsta zMh~68EXdvgJe$<9yOgn~E8aiQNo1$l`FUCI52J)nHW8+@|McSTe{t30@NpO^cv#waY+68ew4oT={#AGoA?Je^k$f`&u}bW zOjoRA_$utq5rspZ;5HXZvqE1#D) z5*M}9jPoq=-V*<cQuuK%yr=$XZl2k``40P4ol60hjb({{!c5Rtn3Urk4?uRRFNQuFGPhZnTBMO zu{W&|u4t53w>M}v(wtDAV!oHP(QY&~_X0qupII*-&GksTPf#SZ#4eaOCrG~hUblo4 zya(R){3rfyxiH2%l{iN;xS(G)OWOx8>UV9u+Vm_iN98oT20F}U!8utl4(lm=E)S6) zN~xM^Q?U;_!Ku4seH{bquy+4OfwlV{0C?tW&XteqJmP<|V)6eK9ey(~1Sbd1rgF1{ zd@&*xkriY}MS><}4DI}o3>d2jm74@fNpg-Vf(zPY?8nj=O8o$gp_gzr3~{MEs({T( z5+EcMIErR!`wwLZHp{c_?{mEhe%dlbNH8E^nm+5|owEXTR;S2+hYWR^j zWB!qbQWumI=vg;ZQvO$8W9QeMX~YdqNb^K%y~T6@tCTOlSM-j*$OOSSZX6cRfzPb* zP-Q}8v|6Q&$hAFfGA(f5>_XzuqhFP@h(0x@%#tsExSB}v(zy6^qc>xW*XCVMBPKf` zF>gsuD~+eKuRP{tWLo#OoJ2?fR@_0f5Olf}}Khc$ZhB$$3I7S9&n z^G{G0pQnju2Md}?zmbr').prop('href', options.postMessage)[0], + target = loc.protocol + '//' + loc.host, + xhrUpload = options.xhr().upload; + return { + send: function (_, completeCallback) { + counter += 1; + var message = { + id: 'postmessage-transport-' + counter + }, + eventName = 'message.' + message.id; + iframe = $( + '' + ).bind('load', function () { + $.each(names, function (i, name) { + message[name] = options[name]; + }); + message.dataType = message.dataType.replace('postmessage ', ''); + $(window).bind(eventName, function (e) { + e = e.originalEvent; + var data = e.data, + ev; + if (e.origin === target && data.id === message.id) { + if (data.type === 'progress') { + ev = document.createEvent('Event'); + ev.initEvent(data.type, false, true); + $.extend(ev, data); + xhrUpload.dispatchEvent(ev); + } else { + completeCallback( + data.status, + data.statusText, + {postmessage: data.result}, + data.headers + ); + iframe.remove(); + $(window).unbind(eventName); + } + } + }); + iframe[0].contentWindow.postMessage( + message, + target + ); + }).appendTo(document.body); + }, + abort: function () { + if (iframe) { + iframe.remove(); + } + } + }; + } + }); + +})); diff --git a/node_modules/image_picker/AngularMediaModal/assets/fileupload/js/cors/jquery.xdr-transport.js b/node_modules/image_picker/AngularMediaModal/assets/fileupload/js/cors/jquery.xdr-transport.js new file mode 100644 index 0000000..5b9c6ca --- /dev/null +++ b/node_modules/image_picker/AngularMediaModal/assets/fileupload/js/cors/jquery.xdr-transport.js @@ -0,0 +1,89 @@ +/* + * jQuery XDomainRequest Transport Plugin + * https://github.com/blueimp/jQuery-File-Upload + * + * Copyright 2011, Sebastian Tschan + * https://blueimp.net + * + * Licensed under the MIT license: + * http://www.opensource.org/licenses/MIT + * + * Based on Julian Aubourg's ajaxHooks xdr.js: + * https://github.com/jaubourg/ajaxHooks/ + */ + +/* global define, require, window, XDomainRequest */ + +(function (factory) { + 'use strict'; + if (typeof define === 'function' && define.amd) { + // Register as an anonymous AMD module: + define(['jquery'], factory); + } else if (typeof exports === 'object') { + // Node/CommonJS: + factory(require('jquery')); + } else { + // Browser globals: + factory(window.jQuery); + } +}(function ($) { + 'use strict'; + if (window.XDomainRequest && !$.support.cors) { + $.ajaxTransport(function (s) { + if (s.crossDomain && s.async) { + if (s.timeout) { + s.xdrTimeout = s.timeout; + delete s.timeout; + } + var xdr; + return { + send: function (headers, completeCallback) { + var addParamChar = /\?/.test(s.url) ? '&' : '?'; + function callback(status, statusText, responses, responseHeaders) { + xdr.onload = xdr.onerror = xdr.ontimeout = $.noop; + xdr = null; + completeCallback(status, statusText, responses, responseHeaders); + } + xdr = new XDomainRequest(); + // XDomainRequest only supports GET and POST: + if (s.type === 'DELETE') { + s.url = s.url + addParamChar + '_method=DELETE'; + s.type = 'POST'; + } else if (s.type === 'PUT') { + s.url = s.url + addParamChar + '_method=PUT'; + s.type = 'POST'; + } else if (s.type === 'PATCH') { + s.url = s.url + addParamChar + '_method=PATCH'; + s.type = 'POST'; + } + xdr.open(s.type, s.url); + xdr.onload = function () { + callback( + 200, + 'OK', + {text: xdr.responseText}, + 'Content-Type: ' + xdr.contentType + ); + }; + xdr.onerror = function () { + callback(404, 'Not Found'); + }; + if (s.xdrTimeout) { + xdr.ontimeout = function () { + callback(0, 'timeout'); + }; + xdr.timeout = s.xdrTimeout; + } + xdr.send((s.hasContent && s.data) || null); + }, + abort: function () { + if (xdr) { + xdr.onerror = $.noop(); + xdr.abort(); + } + } + }; + } + }); + } +})); diff --git a/node_modules/image_picker/AngularMediaModal/assets/fileupload/js/jquery.fileupload-angular.js b/node_modules/image_picker/AngularMediaModal/assets/fileupload/js/jquery.fileupload-angular.js new file mode 100644 index 0000000..f7ba07b --- /dev/null +++ b/node_modules/image_picker/AngularMediaModal/assets/fileupload/js/jquery.fileupload-angular.js @@ -0,0 +1,425 @@ +/* + * jQuery File Upload AngularJS Plugin + * https://github.com/blueimp/jQuery-File-Upload + * + * Copyright 2013, Sebastian Tschan + * https://blueimp.net + * + * Licensed under the MIT license: + * http://www.opensource.org/licenses/MIT + */ + +/* jshint nomen:false */ +/* global define, angular */ + +(function (factory) { + 'use strict'; + if (typeof define === 'function' && define.amd) { + // Register as an anonymous AMD module: + define([ + 'jquery', + 'angular', + './jquery.fileupload-image', + './jquery.fileupload-audio', + './jquery.fileupload-video', + './jquery.fileupload-validate' + ], factory); + } else { + factory(); + } +}(function () { + 'use strict'; + + angular.module('blueimp.fileupload', []) + + // The fileUpload service provides configuration options + // for the fileUpload directive and default handlers for + // File Upload events: + .provider('fileUpload', function () { + var scopeEvalAsync = function (expression) { + var scope = angular.element(this) + .fileupload('option', 'scope'); + // Schedule a new $digest cycle if not already inside of one + // and evaluate the given expression: + scope.$evalAsync(expression); + }, + addFileMethods = function (scope, data) { + var files = data.files, + file = files[0]; + angular.forEach(files, function (file, index) { + file._index = index; + file.$state = function () { + return data.state(); + }; + file.$processing = function () { + return data.processing(); + }; + file.$progress = function () { + return data.progress(); + }; + file.$response = function () { + return data.response(); + }; + }); + file.$submit = function () { + if (!file.error) { + return data.submit(); + } + }; + file.$cancel = function () { + return data.abort(); + }; + }, + $config; + $config = this.defaults = { + handleResponse: function (e, data) { + var files = data.result && data.result.files; + if (files) { + data.scope.replace(data.files, files); + } else if (data.errorThrown || + data.textStatus === 'error') { + data.files[0].error = data.errorThrown || + data.textStatus; + } + }, + add: function (e, data) { + if (e.isDefaultPrevented()) { + return false; + } + var scope = data.scope, + filesCopy = []; + angular.forEach(data.files, function (file) { + filesCopy.push(file); + }); + scope.$parent.$applyAsync(function () { + addFileMethods(scope, data); + var method = scope.option('prependFiles') ? + 'unshift' : 'push'; + Array.prototype[method].apply(scope.queue, data.files); + }); + data.process(function () { + return scope.process(data); + }).always(function () { + scope.$parent.$applyAsync(function () { + addFileMethods(scope, data); + scope.replace(filesCopy, data.files); + }); + }).then(function () { + if ((scope.option('autoUpload') || + data.autoUpload) && + data.autoUpload !== false) { + data.submit(); + } + }); + }, + done: function (e, data) { + if (e.isDefaultPrevented()) { + return false; + } + var that = this; + data.scope.$apply(function () { + data.handleResponse.call(that, e, data); + }); + }, + fail: function (e, data) { + if (e.isDefaultPrevented()) { + return false; + } + var that = this, + scope = data.scope; + if (data.errorThrown === 'abort') { + scope.clear(data.files); + return; + } + scope.$apply(function () { + data.handleResponse.call(that, e, data); + }); + }, + stop: scopeEvalAsync, + processstart: scopeEvalAsync, + processstop: scopeEvalAsync, + getNumberOfFiles: function () { + var scope = this.scope; + return scope.queue.length - scope.processing(); + }, + dataType: 'json', + autoUpload: false + }; + this.$get = [ + function () { + return { + defaults: $config + }; + } + ]; + }) + + // Format byte numbers to readable presentations: + .provider('formatFileSizeFilter', function () { + var $config = { + // Byte units following the IEC format + // http://en.wikipedia.org/wiki/Kilobyte + units: [ + {size: 1000000000, suffix: ' GB'}, + {size: 1000000, suffix: ' MB'}, + {size: 1000, suffix: ' KB'} + ] + }; + this.defaults = $config; + this.$get = function () { + return function (bytes) { + if (!angular.isNumber(bytes)) { + return ''; + } + var unit = true, + i = 0, + prefix, + suffix; + while (unit) { + unit = $config.units[i]; + prefix = unit.prefix || ''; + suffix = unit.suffix || ''; + if (i === $config.units.length - 1 || bytes >= unit.size) { + return prefix + (bytes / unit.size).toFixed(2) + suffix; + } + i += 1; + } + }; + }; + }) + + // The FileUploadController initializes the fileupload widget and + // provides scope methods to control the File Upload functionality: + .controller('FileUploadController', [ + '$scope', '$element', '$attrs', '$window', 'fileUpload', + function ($scope, $element, $attrs, $window, fileUpload) { + var uploadMethods = { + progress: function () { + return $element.fileupload('progress'); + }, + active: function () { + return $element.fileupload('active'); + }, + option: function (option, data) { + if (arguments.length === 1) { + return $element.fileupload('option', option); + } + $element.fileupload('option', option, data); + }, + add: function (data) { + return $element.fileupload('add', data); + }, + send: function (data) { + return $element.fileupload('send', data); + }, + process: function (data) { + return $element.fileupload('process', data); + }, + processing: function (data) { + return $element.fileupload('processing', data); + } + }; + $scope.disabled = !$window.jQuery.support.fileInput; + $scope.queue = $scope.queue || []; + $scope.clear = function (files) { + var queue = this.queue, + i = queue.length, + file = files, + length = 1; + if (angular.isArray(files)) { + file = files[0]; + length = files.length; + } + while (i) { + i -= 1; + if (queue[i] === file) { + return queue.splice(i, length); + } + } + }; + $scope.replace = function (oldFiles, newFiles) { + var queue = this.queue, + file = oldFiles[0], + i, + j; + for (i = 0; i < queue.length; i += 1) { + if (queue[i] === file) { + for (j = 0; j < newFiles.length; j += 1) { + queue[i + j] = newFiles[j]; + } + return; + } + } + }; + $scope.applyOnQueue = function (method) { + var list = this.queue.slice(0), + i, + file; + for (i = 0; i < list.length; i += 1) { + file = list[i]; + if (file[method]) { + file[method](); + } + } + }; + $scope.submit = function () { + this.applyOnQueue('$submit'); + }; + $scope.cancel = function () { + this.applyOnQueue('$cancel'); + }; + // Add upload methods to the scope: + angular.extend($scope, uploadMethods); + // The fileupload widget will initialize with + // the options provided via "data-"-parameters, + // as well as those given via options object: + $element.fileupload(angular.extend( + {scope: $scope}, + fileUpload.defaults + )).on('fileuploadadd', function (e, data) { + data.scope = $scope; + }).on('fileuploadfail', function (e, data) { + if (data.errorThrown === 'abort') { + return; + } + if (data.dataType && + data.dataType.indexOf('json') === data.dataType.length - 4) { + try { + data.result = angular.fromJson(data.jqXHR.responseText); + } catch (ignore) {} + } + }).on([ + 'fileuploadadd', + 'fileuploadsubmit', + 'fileuploadsend', + 'fileuploaddone', + 'fileuploadfail', + 'fileuploadalways', + 'fileuploadprogress', + 'fileuploadprogressall', + 'fileuploadstart', + 'fileuploadstop', + 'fileuploadchange', + 'fileuploadpaste', + 'fileuploaddrop', + 'fileuploaddragover', + 'fileuploadchunksend', + 'fileuploadchunkdone', + 'fileuploadchunkfail', + 'fileuploadchunkalways', + 'fileuploadprocessstart', + 'fileuploadprocess', + 'fileuploadprocessdone', + 'fileuploadprocessfail', + 'fileuploadprocessalways', + 'fileuploadprocessstop' + ].join(' '), function (e, data) { + $scope.$parent.$applyAsync(function () { + if ($scope.$emit(e.type, data).defaultPrevented) { + e.preventDefault(); + } + }); + }).on('remove', function () { + // Remove upload methods from the scope, + // when the widget is removed: + var method; + for (method in uploadMethods) { + if (uploadMethods.hasOwnProperty(method)) { + delete $scope[method]; + } + } + }); + // Observe option changes: + $scope.$watch( + $attrs.fileUpload, + function (newOptions) { + if (newOptions) { + $element.fileupload('option', newOptions); + } + } + ); + } + ]) + + // Provide File Upload progress feedback: + .controller('FileUploadProgressController', [ + '$scope', '$attrs', '$parse', + function ($scope, $attrs, $parse) { + var fn = $parse($attrs.fileUploadProgress), + update = function () { + var progress = fn($scope); + if (!progress || !progress.total) { + return; + } + $scope.num = Math.floor( + progress.loaded / progress.total * 100 + ); + }; + update(); + $scope.$watch( + $attrs.fileUploadProgress + '.loaded', + function (newValue, oldValue) { + if (newValue !== oldValue) { + update(); + } + } + ); + } + ]) + + // Display File Upload previews: + .controller('FileUploadPreviewController', [ + '$scope', '$element', '$attrs', + function ($scope, $element, $attrs) { + $scope.$watch( + $attrs.fileUploadPreview + '.preview', + function (preview) { + $element.empty(); + if (preview) { + $element.append(preview); + } + } + ); + } + ]) + + .directive('fileUpload', function () { + return { + controller: 'FileUploadController', + scope: true + }; + }) + + .directive('fileUploadProgress', function () { + return { + controller: 'FileUploadProgressController', + scope: true + }; + }) + + .directive('fileUploadPreview', function () { + return { + controller: 'FileUploadPreviewController' + }; + }) + + // Enhance the HTML5 download attribute to + // allow drag&drop of files to the desktop: + .directive('download', function () { + return function (scope, elm) { + elm.on('dragstart', function (e) { + try { + e.originalEvent.dataTransfer.setData( + 'DownloadURL', + [ + 'application/octet-stream', + elm.prop('download'), + elm.prop('href') + ].join(':') + ); + } catch (ignore) {} + }); + }; + }); + +})); diff --git a/node_modules/image_picker/AngularMediaModal/assets/fileupload/js/jquery.fileupload-audio.js b/node_modules/image_picker/AngularMediaModal/assets/fileupload/js/jquery.fileupload-audio.js new file mode 100644 index 0000000..1a746f9 --- /dev/null +++ b/node_modules/image_picker/AngularMediaModal/assets/fileupload/js/jquery.fileupload-audio.js @@ -0,0 +1,112 @@ +/* + * jQuery File Upload Audio Preview Plugin + * https://github.com/blueimp/jQuery-File-Upload + * + * Copyright 2013, Sebastian Tschan + * https://blueimp.net + * + * Licensed under the MIT license: + * http://www.opensource.org/licenses/MIT + */ + +/* jshint nomen:false */ +/* global define, require, window, document */ + +(function (factory) { + 'use strict'; + if (typeof define === 'function' && define.amd) { + // Register as an anonymous AMD module: + define([ + 'jquery', + 'load-image', + './jquery.fileupload-process' + ], factory); + } else if (typeof exports === 'object') { + // Node/CommonJS: + factory( + require('jquery'), + require('load-image') + ); + } else { + // Browser globals: + factory( + window.jQuery, + window.loadImage + ); + } +}(function ($, loadImage) { + 'use strict'; + + // Prepend to the default processQueue: + $.blueimp.fileupload.prototype.options.processQueue.unshift( + { + action: 'loadAudio', + // Use the action as prefix for the "@" options: + prefix: true, + fileTypes: '@', + maxFileSize: '@', + disabled: '@disableAudioPreview' + }, + { + action: 'setAudio', + name: '@audioPreviewName', + disabled: '@disableAudioPreview' + } + ); + + // The File Upload Audio Preview plugin extends the fileupload widget + // with audio preview functionality: + $.widget('blueimp.fileupload', $.blueimp.fileupload, { + + options: { + // The regular expression for the types of audio files to load, + // matched against the file type: + loadAudioFileTypes: /^audio\/.*$/ + }, + + _audioElement: document.createElement('audio'), + + processActions: { + + // Loads the audio file given via data.files and data.index + // as audio element if the browser supports playing it. + // Accepts the options fileTypes (regular expression) + // and maxFileSize (integer) to limit the files to load: + loadAudio: function (data, options) { + if (options.disabled) { + return data; + } + var file = data.files[data.index], + url, + audio; + if (this._audioElement.canPlayType && + this._audioElement.canPlayType(file.type) && + ($.type(options.maxFileSize) !== 'number' || + file.size <= options.maxFileSize) && + (!options.fileTypes || + options.fileTypes.test(file.type))) { + url = loadImage.createObjectURL(file); + if (url) { + audio = this._audioElement.cloneNode(false); + audio.src = url; + audio.controls = true; + data.audio = audio; + return data; + } + } + return data; + }, + + // Sets the audio element as a property of the file object: + setAudio: function (data, options) { + if (data.audio && !options.disabled) { + data.files[data.index][options.name || 'preview'] = data.audio; + } + return data; + } + + } + + }); + +})); diff --git a/node_modules/image_picker/AngularMediaModal/assets/fileupload/js/jquery.fileupload-image.js b/node_modules/image_picker/AngularMediaModal/assets/fileupload/js/jquery.fileupload-image.js new file mode 100644 index 0000000..ffb02c2 --- /dev/null +++ b/node_modules/image_picker/AngularMediaModal/assets/fileupload/js/jquery.fileupload-image.js @@ -0,0 +1,324 @@ +/* + * jQuery File Upload Image Preview & Resize Plugin + * https://github.com/blueimp/jQuery-File-Upload + * + * Copyright 2013, Sebastian Tschan + * https://blueimp.net + * + * Licensed under the MIT license: + * http://www.opensource.org/licenses/MIT + */ + +/* jshint nomen:false */ +/* global define, require, window, Blob */ + +(function (factory) { + 'use strict'; + if (typeof define === 'function' && define.amd) { + // Register as an anonymous AMD module: + define([ + 'jquery', + 'load-image', + 'load-image-meta', + 'load-image-exif', + 'canvas-to-blob', + './jquery.fileupload-process' + ], factory); + } else if (typeof exports === 'object') { + // Node/CommonJS: + factory( + require('jquery'), + require('blueimp-load-image/js/load-image'), + require('blueimp-load-image/js/load-image-meta'), + require('blueimp-load-image/js/load-image-exif'), + require('blueimp-canvas-to-blob'), + require('./jquery.fileupload-process') + ); + } else { + // Browser globals: + factory( + window.jQuery, + window.loadImage + ); + } +}(function ($, loadImage) { + 'use strict'; + + // Prepend to the default processQueue: + $.blueimp.fileupload.prototype.options.processQueue.unshift( + { + action: 'loadImageMetaData', + disableImageHead: '@', + disableExif: '@', + disableExifThumbnail: '@', + disableExifSub: '@', + disableExifGps: '@', + disabled: '@disableImageMetaDataLoad' + }, + { + action: 'loadImage', + // Use the action as prefix for the "@" options: + prefix: true, + fileTypes: '@', + maxFileSize: '@', + noRevoke: '@', + disabled: '@disableImageLoad' + }, + { + action: 'resizeImage', + // Use "image" as prefix for the "@" options: + prefix: 'image', + maxWidth: '@', + maxHeight: '@', + minWidth: '@', + minHeight: '@', + crop: '@', + orientation: '@', + forceResize: '@', + disabled: '@disableImageResize' + }, + { + action: 'saveImage', + quality: '@imageQuality', + type: '@imageType', + disabled: '@disableImageResize' + }, + { + action: 'saveImageMetaData', + disabled: '@disableImageMetaDataSave' + }, + { + action: 'resizeImage', + // Use "preview" as prefix for the "@" options: + prefix: 'preview', + maxWidth: '@', + maxHeight: '@', + minWidth: '@', + minHeight: '@', + crop: '@', + orientation: '@', + thumbnail: '@', + canvas: '@', + disabled: '@disableImagePreview' + }, + { + action: 'setImage', + name: '@imagePreviewName', + disabled: '@disableImagePreview' + }, + { + action: 'deleteImageReferences', + disabled: '@disableImageReferencesDeletion' + } + ); + + // The File Upload Resize plugin extends the fileupload widget + // with image resize functionality: + $.widget('blueimp.fileupload', $.blueimp.fileupload, { + + options: { + // The regular expression for the types of images to load: + // matched against the file type: + loadImageFileTypes: /^image\/(gif|jpeg|png|svg\+xml)$/, + // The maximum file size of images to load: + loadImageMaxFileSize: 10000000, // 10MB + // The maximum width of resized images: + imageMaxWidth: 1920, + // The maximum height of resized images: + imageMaxHeight: 1080, + // Defines the image orientation (1-8) or takes the orientation + // value from Exif data if set to true: + imageOrientation: false, + // Define if resized images should be cropped or only scaled: + imageCrop: false, + // Disable the resize image functionality by default: + disableImageResize: true, + // The maximum width of the preview images: + previewMaxWidth: 80, + // The maximum height of the preview images: + previewMaxHeight: 80, + // Defines the preview orientation (1-8) or takes the orientation + // value from Exif data if set to true: + previewOrientation: true, + // Create the preview using the Exif data thumbnail: + previewThumbnail: true, + // Define if preview images should be cropped or only scaled: + previewCrop: false, + // Define if preview images should be resized as canvas elements: + previewCanvas: true + }, + + processActions: { + + // Loads the image given via data.files and data.index + // as img element, if the browser supports the File API. + // Accepts the options fileTypes (regular expression) + // and maxFileSize (integer) to limit the files to load: + loadImage: function (data, options) { + if (options.disabled) { + return data; + } + var that = this, + file = data.files[data.index], + dfd = $.Deferred(); + if (($.type(options.maxFileSize) === 'number' && + file.size > options.maxFileSize) || + (options.fileTypes && + !options.fileTypes.test(file.type)) || + !loadImage( + file, + function (img) { + if (img.src) { + data.img = img; + } + dfd.resolveWith(that, [data]); + }, + options + )) { + return data; + } + return dfd.promise(); + }, + + // Resizes the image given as data.canvas or data.img + // and updates data.canvas or data.img with the resized image. + // Also stores the resized image as preview property. + // Accepts the options maxWidth, maxHeight, minWidth, + // minHeight, canvas and crop: + resizeImage: function (data, options) { + if (options.disabled || !(data.canvas || data.img)) { + return data; + } + options = $.extend({canvas: true}, options); + var that = this, + dfd = $.Deferred(), + img = (options.canvas && data.canvas) || data.img, + resolve = function (newImg) { + if (newImg && (newImg.width !== img.width || + newImg.height !== img.height || + options.forceResize)) { + data[newImg.getContext ? 'canvas' : 'img'] = newImg; + } + data.preview = newImg; + dfd.resolveWith(that, [data]); + }, + thumbnail; + if (data.exif) { + if (options.orientation === true) { + options.orientation = data.exif.get('Orientation'); + } + if (options.thumbnail) { + thumbnail = data.exif.get('Thumbnail'); + if (thumbnail) { + loadImage(thumbnail, resolve, options); + return dfd.promise(); + } + } + // Prevent orienting the same image twice: + if (data.orientation) { + delete options.orientation; + } else { + data.orientation = options.orientation; + } + } + if (img) { + resolve(loadImage.scale(img, options)); + return dfd.promise(); + } + return data; + }, + + // Saves the processed image given as data.canvas + // inplace at data.index of data.files: + saveImage: function (data, options) { + if (!data.canvas || options.disabled) { + return data; + } + var that = this, + file = data.files[data.index], + dfd = $.Deferred(); + if (data.canvas.toBlob) { + data.canvas.toBlob( + function (blob) { + if (!blob.name) { + if (file.type === blob.type) { + blob.name = file.name; + } else if (file.name) { + blob.name = file.name.replace( + /\.\w+$/, + '.' + blob.type.substr(6) + ); + } + } + // Don't restore invalid meta data: + if (file.type !== blob.type) { + delete data.imageHead; + } + // Store the created blob at the position + // of the original file in the files list: + data.files[data.index] = blob; + dfd.resolveWith(that, [data]); + }, + options.type || file.type, + options.quality + ); + } else { + return data; + } + return dfd.promise(); + }, + + loadImageMetaData: function (data, options) { + if (options.disabled) { + return data; + } + var that = this, + dfd = $.Deferred(); + loadImage.parseMetaData(data.files[data.index], function (result) { + $.extend(data, result); + dfd.resolveWith(that, [data]); + }, options); + return dfd.promise(); + }, + + saveImageMetaData: function (data, options) { + if (!(data.imageHead && data.canvas && + data.canvas.toBlob && !options.disabled)) { + return data; + } + var file = data.files[data.index], + blob = new Blob([ + data.imageHead, + // Resized images always have a head size of 20 bytes, + // including the JPEG marker and a minimal JFIF header: + this._blobSlice.call(file, 20) + ], {type: file.type}); + blob.name = file.name; + data.files[data.index] = blob; + return data; + }, + + // Sets the resized version of the image as a property of the + // file object, must be called after "saveImage": + setImage: function (data, options) { + if (data.preview && !options.disabled) { + data.files[data.index][options.name || 'preview'] = data.preview; + } + return data; + }, + + deleteImageReferences: function (data, options) { + if (!options.disabled) { + delete data.img; + delete data.canvas; + delete data.preview; + delete data.imageHead; + } + return data; + } + + } + + }); + +})); diff --git a/node_modules/image_picker/AngularMediaModal/assets/fileupload/js/jquery.fileupload-jquery-ui.js b/node_modules/image_picker/AngularMediaModal/assets/fileupload/js/jquery.fileupload-jquery-ui.js new file mode 100644 index 0000000..4f239fa --- /dev/null +++ b/node_modules/image_picker/AngularMediaModal/assets/fileupload/js/jquery.fileupload-jquery-ui.js @@ -0,0 +1,155 @@ +/* + * jQuery File Upload jQuery UI Plugin + * https://github.com/blueimp/jQuery-File-Upload + * + * Copyright 2013, Sebastian Tschan + * https://blueimp.net + * + * Licensed under the MIT license: + * http://www.opensource.org/licenses/MIT + */ + +/* jshint nomen:false */ +/* global define, require, window */ + +(function (factory) { + 'use strict'; + if (typeof define === 'function' && define.amd) { + // Register as an anonymous AMD module: + define(['jquery', './jquery.fileupload-ui'], factory); + } else if (typeof exports === 'object') { + // Node/CommonJS: + factory(require('jquery')); + } else { + // Browser globals: + factory(window.jQuery); + } +}(function ($) { + 'use strict'; + + $.widget('blueimp.fileupload', $.blueimp.fileupload, { + + options: { + processdone: function (e, data) { + data.context.find('.start').button('enable'); + }, + progress: function (e, data) { + if (data.context) { + data.context.find('.progress').progressbar( + 'option', + 'value', + parseInt(data.loaded / data.total * 100, 10) + ); + } + }, + progressall: function (e, data) { + var $this = $(this); + $this.find('.fileupload-progress') + .find('.progress').progressbar( + 'option', + 'value', + parseInt(data.loaded / data.total * 100, 10) + ).end() + .find('.progress-extended').each(function () { + $(this).html( + ($this.data('blueimp-fileupload') || + $this.data('fileupload')) + ._renderExtendedProgress(data) + ); + }); + } + }, + + _renderUpload: function (func, files) { + var node = this._super(func, files), + showIconText = $(window).width() > 480; + node.find('.progress').empty().progressbar(); + node.find('.start').button({ + icons: {primary: 'ui-icon-circle-arrow-e'}, + text: showIconText + }); + node.find('.cancel').button({ + icons: {primary: 'ui-icon-cancel'}, + text: showIconText + }); + if (node.hasClass('fade')) { + node.hide(); + } + return node; + }, + + _renderDownload: function (func, files) { + var node = this._super(func, files), + showIconText = $(window).width() > 480; + node.find('.delete').button({ + icons: {primary: 'ui-icon-trash'}, + text: showIconText + }); + if (node.hasClass('fade')) { + node.hide(); + } + return node; + }, + + _startHandler: function (e) { + $(e.currentTarget).button('disable'); + this._super(e); + }, + + _transition: function (node) { + var deferred = $.Deferred(); + if (node.hasClass('fade')) { + node.fadeToggle( + this.options.transitionDuration, + this.options.transitionEasing, + function () { + deferred.resolveWith(node); + } + ); + } else { + deferred.resolveWith(node); + } + return deferred; + }, + + _create: function () { + this._super(); + this.element + .find('.fileupload-buttonbar') + .find('.fileinput-button').each(function () { + var input = $(this).find('input:file').detach(); + $(this) + .button({icons: {primary: 'ui-icon-plusthick'}}) + .append(input); + }) + .end().find('.start') + .button({icons: {primary: 'ui-icon-circle-arrow-e'}}) + .end().find('.cancel') + .button({icons: {primary: 'ui-icon-cancel'}}) + .end().find('.delete') + .button({icons: {primary: 'ui-icon-trash'}}) + .end().find('.progress').progressbar(); + }, + + _destroy: function () { + this.element + .find('.fileupload-buttonbar') + .find('.fileinput-button').each(function () { + var input = $(this).find('input:file').detach(); + $(this) + .button('destroy') + .append(input); + }) + .end().find('.start') + .button('destroy') + .end().find('.cancel') + .button('destroy') + .end().find('.delete') + .button('destroy') + .end().find('.progress').progressbar('destroy'); + this._super(); + } + + }); + +})); diff --git a/node_modules/image_picker/AngularMediaModal/assets/fileupload/js/jquery.fileupload-process.js b/node_modules/image_picker/AngularMediaModal/assets/fileupload/js/jquery.fileupload-process.js new file mode 100644 index 0000000..ce914df --- /dev/null +++ b/node_modules/image_picker/AngularMediaModal/assets/fileupload/js/jquery.fileupload-process.js @@ -0,0 +1,175 @@ +/* + * jQuery File Upload Processing Plugin + * https://github.com/blueimp/jQuery-File-Upload + * + * Copyright 2012, Sebastian Tschan + * https://blueimp.net + * + * Licensed under the MIT license: + * http://www.opensource.org/licenses/MIT + */ + +/* jshint nomen:false */ +/* global define, require, window */ + +(function (factory) { + 'use strict'; + if (typeof define === 'function' && define.amd) { + // Register as an anonymous AMD module: + define([ + 'jquery', + './jquery.fileupload' + ], factory); + } else if (typeof exports === 'object') { + // Node/CommonJS: + factory(require('jquery')); + } else { + // Browser globals: + factory( + window.jQuery + ); + } +}(function ($) { + 'use strict'; + + var originalAdd = $.blueimp.fileupload.prototype.options.add; + + // The File Upload Processing plugin extends the fileupload widget + // with file processing functionality: + $.widget('blueimp.fileupload', $.blueimp.fileupload, { + + options: { + // The list of processing actions: + processQueue: [ + /* + { + action: 'log', + type: 'debug' + } + */ + ], + add: function (e, data) { + var $this = $(this); + data.process(function () { + return $this.fileupload('process', data); + }); + originalAdd.call(this, e, data); + } + }, + + processActions: { + /* + log: function (data, options) { + console[options.type]( + 'Processing "' + data.files[data.index].name + '"' + ); + } + */ + }, + + _processFile: function (data, originalData) { + var that = this, + dfd = $.Deferred().resolveWith(that, [data]), + chain = dfd.promise(); + this._trigger('process', null, data); + $.each(data.processQueue, function (i, settings) { + var func = function (data) { + if (originalData.errorThrown) { + return $.Deferred() + .rejectWith(that, [originalData]).promise(); + } + return that.processActions[settings.action].call( + that, + data, + settings + ); + }; + chain = chain.pipe(func, settings.always && func); + }); + chain + .done(function () { + that._trigger('processdone', null, data); + that._trigger('processalways', null, data); + }) + .fail(function () { + that._trigger('processfail', null, data); + that._trigger('processalways', null, data); + }); + return chain; + }, + + // Replaces the settings of each processQueue item that + // are strings starting with an "@", using the remaining + // substring as key for the option map, + // e.g. "@autoUpload" is replaced with options.autoUpload: + _transformProcessQueue: function (options) { + var processQueue = []; + $.each(options.processQueue, function () { + var settings = {}, + action = this.action, + prefix = this.prefix === true ? action : this.prefix; + $.each(this, function (key, value) { + if ($.type(value) === 'string' && + value.charAt(0) === '@') { + settings[key] = options[ + value.slice(1) || (prefix ? prefix + + key.charAt(0).toUpperCase() + key.slice(1) : key) + ]; + } else { + settings[key] = value; + } + + }); + processQueue.push(settings); + }); + options.processQueue = processQueue; + }, + + // Returns the number of files currently in the processsing queue: + processing: function () { + return this._processing; + }, + + // Processes the files given as files property of the data parameter, + // returns a Promise object that allows to bind callbacks: + process: function (data) { + var that = this, + options = $.extend({}, this.options, data); + if (options.processQueue && options.processQueue.length) { + this._transformProcessQueue(options); + if (this._processing === 0) { + this._trigger('processstart'); + } + $.each(data.files, function (index) { + var opts = index ? $.extend({}, options) : options, + func = function () { + if (data.errorThrown) { + return $.Deferred() + .rejectWith(that, [data]).promise(); + } + return that._processFile(opts, data); + }; + opts.index = index; + that._processing += 1; + that._processingQueue = that._processingQueue.pipe(func, func) + .always(function () { + that._processing -= 1; + if (that._processing === 0) { + that._trigger('processstop'); + } + }); + }); + } + return this._processingQueue; + }, + + _create: function () { + this._super(); + this._processing = 0; + this._processingQueue = $.Deferred().resolveWith(this) + .promise(); + } + + }); + +})); diff --git a/node_modules/image_picker/AngularMediaModal/assets/fileupload/js/jquery.fileupload-ui.js b/node_modules/image_picker/AngularMediaModal/assets/fileupload/js/jquery.fileupload-ui.js new file mode 100644 index 0000000..8154218 --- /dev/null +++ b/node_modules/image_picker/AngularMediaModal/assets/fileupload/js/jquery.fileupload-ui.js @@ -0,0 +1,710 @@ +/* + * jQuery File Upload User Interface Plugin + * https://github.com/blueimp/jQuery-File-Upload + * + * Copyright 2010, Sebastian Tschan + * https://blueimp.net + * + * Licensed under the MIT license: + * http://www.opensource.org/licenses/MIT + */ + +/* jshint nomen:false */ +/* global define, require, window */ + +(function (factory) { + 'use strict'; + if (typeof define === 'function' && define.amd) { + // Register as an anonymous AMD module: + define([ + 'jquery', + 'tmpl', + './jquery.fileupload-image', + './jquery.fileupload-audio', + './jquery.fileupload-video', + './jquery.fileupload-validate' + ], factory); + } else if (typeof exports === 'object') { + // Node/CommonJS: + factory( + require('jquery'), + require('tmpl') + ); + } else { + // Browser globals: + factory( + window.jQuery, + window.tmpl + ); + } +}(function ($, tmpl) { + 'use strict'; + + $.blueimp.fileupload.prototype._specialOptions.push( + 'filesContainer', + 'uploadTemplateId', + 'downloadTemplateId' + ); + + // The UI version extends the file upload widget + // and adds complete user interface interaction: + $.widget('blueimp.fileupload', $.blueimp.fileupload, { + + options: { + // By default, files added to the widget are uploaded as soon + // as the user clicks on the start buttons. To enable automatic + // uploads, set the following option to true: + autoUpload: false, + // The ID of the upload template: + uploadTemplateId: 'template-upload', + // The ID of the download template: + downloadTemplateId: 'template-download', + // The container for the list of files. If undefined, it is set to + // an element with class "files" inside of the widget element: + filesContainer: undefined, + // By default, files are appended to the files container. + // Set the following option to true, to prepend files instead: + prependFiles: false, + // The expected data type of the upload response, sets the dataType + // option of the $.ajax upload requests: + dataType: 'json', + + // Error and info messages: + messages: { + unknownError: 'Unknown error' + }, + + // Function returning the current number of files, + // used by the maxNumberOfFiles validation: + getNumberOfFiles: function () { + return this.filesContainer.children() + .not('.processing').length; + }, + + // Callback to retrieve the list of files from the server response: + getFilesFromResponse: function (data) { + if (data.result && $.isArray(data.result.files)) { + return data.result.files; + } + return []; + }, + + // The add callback is invoked as soon as files are added to the fileupload + // widget (via file input selection, drag & drop or add API call). + // See the basic file upload widget for more information: + add: function (e, data) { + if (e.isDefaultPrevented()) { + return false; + } + var $this = $(this), + that = $this.data('blueimp-fileupload') || + $this.data('fileupload'), + options = that.options; + data.context = that._renderUpload(data.files) + .data('data', data) + .addClass('processing'); + options.filesContainer[ + options.prependFiles ? 'prepend' : 'append' + ](data.context); + that._forceReflow(data.context); + that._transition(data.context); + data.process(function () { + return $this.fileupload('process', data); + }).always(function () { + data.context.each(function (index) { + $(this).find('.size').text( + that._formatFileSize(data.files[index].size) + ); + }).removeClass('processing'); + that._renderPreviews(data); + }).done(function () { + data.context.find('.start').prop('disabled', false); + if ((that._trigger('added', e, data) !== false) && + (options.autoUpload || data.autoUpload) && + data.autoUpload !== false) { + data.submit(); + } + }).fail(function () { + if (data.files.error) { + data.context.each(function (index) { + var error = data.files[index].error; + if (error) { + $(this).find('.error').text(error); + } + }); + } + }); + }, + // Callback for the start of each file upload request: + send: function (e, data) { + if (e.isDefaultPrevented()) { + return false; + } + var that = $(this).data('blueimp-fileupload') || + $(this).data('fileupload'); + if (data.context && data.dataType && + data.dataType.substr(0, 6) === 'iframe') { + // Iframe Transport does not support progress events. + // In lack of an indeterminate progress bar, we set + // the progress to 100%, showing the full animated bar: + data.context + .find('.progress').addClass( + !$.support.transition && 'progress-animated' + ) + .attr('aria-valuenow', 100) + .children().first().css( + 'width', + '100%' + ); + } + return that._trigger('sent', e, data); + }, + // Callback for successful uploads: + done: function (e, data) { + if (e.isDefaultPrevented()) { + return false; + } + var that = $(this).data('blueimp-fileupload') || + $(this).data('fileupload'), + getFilesFromResponse = data.getFilesFromResponse || + that.options.getFilesFromResponse, + files = getFilesFromResponse(data), + template, + deferred; + if (data.context) { + data.context.each(function (index) { + var file = files[index] || + {error: 'Empty file upload result'}; + deferred = that._addFinishedDeferreds(); + that._transition($(this)).done( + function () { + var node = $(this); + template = that._renderDownload([file]) + .replaceAll(node); + that._forceReflow(template); + that._transition(template).done( + function () { + data.context = $(this); + that._trigger('completed', e, data); + that._trigger('finished', e, data); + deferred.resolve(); + } + ); + } + ); + }); + } else { + template = that._renderDownload(files)[ + that.options.prependFiles ? 'prependTo' : 'appendTo' + ](that.options.filesContainer); + that._forceReflow(template); + deferred = that._addFinishedDeferreds(); + that._transition(template).done( + function () { + data.context = $(this); + that._trigger('completed', e, data); + that._trigger('finished', e, data); + deferred.resolve(); + } + ); + } + }, + // Callback for failed (abort or error) uploads: + fail: function (e, data) { + if (e.isDefaultPrevented()) { + return false; + } + var that = $(this).data('blueimp-fileupload') || + $(this).data('fileupload'), + template, + deferred; + if (data.context) { + data.context.each(function (index) { + if (data.errorThrown !== 'abort') { + var file = data.files[index]; + file.error = file.error || data.errorThrown || + data.i18n('unknownError'); + deferred = that._addFinishedDeferreds(); + that._transition($(this)).done( + function () { + var node = $(this); + template = that._renderDownload([file]) + .replaceAll(node); + that._forceReflow(template); + that._transition(template).done( + function () { + data.context = $(this); + that._trigger('failed', e, data); + that._trigger('finished', e, data); + deferred.resolve(); + } + ); + } + ); + } else { + deferred = that._addFinishedDeferreds(); + that._transition($(this)).done( + function () { + $(this).remove(); + that._trigger('failed', e, data); + that._trigger('finished', e, data); + deferred.resolve(); + } + ); + } + }); + } else if (data.errorThrown !== 'abort') { + data.context = that._renderUpload(data.files)[ + that.options.prependFiles ? 'prependTo' : 'appendTo' + ](that.options.filesContainer) + .data('data', data); + that._forceReflow(data.context); + deferred = that._addFinishedDeferreds(); + that._transition(data.context).done( + function () { + data.context = $(this); + that._trigger('failed', e, data); + that._trigger('finished', e, data); + deferred.resolve(); + } + ); + } else { + that._trigger('failed', e, data); + that._trigger('finished', e, data); + that._addFinishedDeferreds().resolve(); + } + }, + // Callback for upload progress events: + progress: function (e, data) { + if (e.isDefaultPrevented()) { + return false; + } + var progress = Math.floor(data.loaded / data.total * 100); + if (data.context) { + data.context.each(function () { + $(this).find('.progress') + .attr('aria-valuenow', progress) + .children().first().css( + 'width', + progress + '%' + ); + }); + } + }, + // Callback for global upload progress events: + progressall: function (e, data) { + if (e.isDefaultPrevented()) { + return false; + } + var $this = $(this), + progress = Math.floor(data.loaded / data.total * 100), + globalProgressNode = $this.find('.fileupload-progress'), + extendedProgressNode = globalProgressNode + .find('.progress-extended'); + if (extendedProgressNode.length) { + extendedProgressNode.html( + ($this.data('blueimp-fileupload') || $this.data('fileupload')) + ._renderExtendedProgress(data) + ); + } + globalProgressNode + .find('.progress') + .attr('aria-valuenow', progress) + .children().first().css( + 'width', + progress + '%' + ); + }, + // Callback for uploads start, equivalent to the global ajaxStart event: + start: function (e) { + if (e.isDefaultPrevented()) { + return false; + } + var that = $(this).data('blueimp-fileupload') || + $(this).data('fileupload'); + that._resetFinishedDeferreds(); + that._transition($(this).find('.fileupload-progress')).done( + function () { + that._trigger('started', e); + } + ); + }, + // Callback for uploads stop, equivalent to the global ajaxStop event: + stop: function (e) { + if (e.isDefaultPrevented()) { + return false; + } + var that = $(this).data('blueimp-fileupload') || + $(this).data('fileupload'), + deferred = that._addFinishedDeferreds(); + $.when.apply($, that._getFinishedDeferreds()) + .done(function () { + that._trigger('stopped', e); + }); + that._transition($(this).find('.fileupload-progress')).done( + function () { + $(this).find('.progress') + .attr('aria-valuenow', '0') + .children().first().css('width', '0%'); + $(this).find('.progress-extended').html(' '); + deferred.resolve(); + } + ); + }, + processstart: function (e) { + if (e.isDefaultPrevented()) { + return false; + } + $(this).addClass('fileupload-processing'); + }, + processstop: function (e) { + if (e.isDefaultPrevented()) { + return false; + } + $(this).removeClass('fileupload-processing'); + }, + // Callback for file deletion: + destroy: function (e, data) { + if (e.isDefaultPrevented()) { + return false; + } + var that = $(this).data('blueimp-fileupload') || + $(this).data('fileupload'), + removeNode = function () { + that._transition(data.context).done( + function () { + $(this).remove(); + that._trigger('destroyed', e, data); + } + ); + }; + if (data.url) { + data.dataType = data.dataType || that.options.dataType; + $.ajax(data).done(removeNode).fail(function () { + that._trigger('destroyfailed', e, data); + }); + } else { + removeNode(); + } + } + }, + + _resetFinishedDeferreds: function () { + this._finishedUploads = []; + }, + + _addFinishedDeferreds: function (deferred) { + if (!deferred) { + deferred = $.Deferred(); + } + this._finishedUploads.push(deferred); + return deferred; + }, + + _getFinishedDeferreds: function () { + return this._finishedUploads; + }, + + // Link handler, that allows to download files + // by drag & drop of the links to the desktop: + _enableDragToDesktop: function () { + var link = $(this), + url = link.prop('href'), + name = link.prop('download'), + type = 'application/octet-stream'; + link.bind('dragstart', function (e) { + try { + e.originalEvent.dataTransfer.setData( + 'DownloadURL', + [type, name, url].join(':') + ); + } catch (ignore) {} + }); + }, + + _formatFileSize: function (bytes) { + if (typeof bytes !== 'number') { + return ''; + } + if (bytes >= 1000000000) { + return (bytes / 1000000000).toFixed(2) + ' GB'; + } + if (bytes >= 1000000) { + return (bytes / 1000000).toFixed(2) + ' MB'; + } + return (bytes / 1000).toFixed(2) + ' KB'; + }, + + _formatBitrate: function (bits) { + if (typeof bits !== 'number') { + return ''; + } + if (bits >= 1000000000) { + return (bits / 1000000000).toFixed(2) + ' Gbit/s'; + } + if (bits >= 1000000) { + return (bits / 1000000).toFixed(2) + ' Mbit/s'; + } + if (bits >= 1000) { + return (bits / 1000).toFixed(2) + ' kbit/s'; + } + return bits.toFixed(2) + ' bit/s'; + }, + + _formatTime: function (seconds) { + var date = new Date(seconds * 1000), + days = Math.floor(seconds / 86400); + days = days ? days + 'd ' : ''; + return days + + ('0' + date.getUTCHours()).slice(-2) + ':' + + ('0' + date.getUTCMinutes()).slice(-2) + ':' + + ('0' + date.getUTCSeconds()).slice(-2); + }, + + _formatPercentage: function (floatValue) { + return (floatValue * 100).toFixed(2) + ' %'; + }, + + _renderExtendedProgress: function (data) { + return this._formatBitrate(data.bitrate) + ' | ' + + this._formatTime( + (data.total - data.loaded) * 8 / data.bitrate + ) + ' | ' + + this._formatPercentage( + data.loaded / data.total + ) + ' | ' + + this._formatFileSize(data.loaded) + ' / ' + + this._formatFileSize(data.total); + }, + + _renderTemplate: function (func, files) { + if (!func) { + return $(); + } + var result = func({ + files: files, + formatFileSize: this._formatFileSize, + options: this.options + }); + if (result instanceof $) { + return result; + } + return $(this.options.templatesContainer).html(result).children(); + }, + + _renderPreviews: function (data) { + data.context.find('.preview').each(function (index, elm) { + $(elm).append(data.files[index].preview); + }); + }, + + _renderUpload: function (files) { + return this._renderTemplate( + this.options.uploadTemplate, + files + ); + }, + + _renderDownload: function (files) { + return this._renderTemplate( + this.options.downloadTemplate, + files + ).find('a[download]').each(this._enableDragToDesktop).end(); + }, + + _startHandler: function (e) { + e.preventDefault(); + var button = $(e.currentTarget), + template = button.closest('.template-upload'), + data = template.data('data'); + button.prop('disabled', true); + if (data && data.submit) { + data.submit(); + } + }, + + _cancelHandler: function (e) { + e.preventDefault(); + var template = $(e.currentTarget) + .closest('.template-upload,.template-download'), + data = template.data('data') || {}; + data.context = data.context || template; + if (data.abort) { + data.abort(); + } else { + data.errorThrown = 'abort'; + this._trigger('fail', e, data); + } + }, + + _deleteHandler: function (e) { + e.preventDefault(); + var button = $(e.currentTarget); + this._trigger('destroy', e, $.extend({ + context: button.closest('.template-download'), + type: 'DELETE' + }, button.data())); + }, + + _forceReflow: function (node) { + return $.support.transition && node.length && + node[0].offsetWidth; + }, + + _transition: function (node) { + var dfd = $.Deferred(); + if ($.support.transition && node.hasClass('fade') && node.is(':visible')) { + node.bind( + $.support.transition.end, + function (e) { + // Make sure we don't respond to other transitions events + // in the container element, e.g. from button elements: + if (e.target === node[0]) { + node.unbind($.support.transition.end); + dfd.resolveWith(node); + } + } + ).toggleClass('in'); + } else { + node.toggleClass('in'); + dfd.resolveWith(node); + } + return dfd; + }, + + _initButtonBarEventHandlers: function () { + var fileUploadButtonBar = this.element.find('.fileupload-buttonbar'), + filesList = this.options.filesContainer; + this._on(fileUploadButtonBar.find('.start'), { + click: function (e) { + e.preventDefault(); + filesList.find('.start').click(); + } + }); + this._on(fileUploadButtonBar.find('.cancel'), { + click: function (e) { + e.preventDefault(); + filesList.find('.cancel').click(); + } + }); + this._on(fileUploadButtonBar.find('.delete'), { + click: function (e) { + e.preventDefault(); + filesList.find('.toggle:checked') + .closest('.template-download') + .find('.delete').click(); + fileUploadButtonBar.find('.toggle') + .prop('checked', false); + } + }); + this._on(fileUploadButtonBar.find('.toggle'), { + change: function (e) { + filesList.find('.toggle').prop( + 'checked', + $(e.currentTarget).is(':checked') + ); + } + }); + }, + + _destroyButtonBarEventHandlers: function () { + this._off( + this.element.find('.fileupload-buttonbar') + .find('.start, .cancel, .delete'), + 'click' + ); + this._off( + this.element.find('.fileupload-buttonbar .toggle'), + 'change.' + ); + }, + + _initEventHandlers: function () { + this._super(); + this._on(this.options.filesContainer, { + 'click .start': this._startHandler, + 'click .cancel': this._cancelHandler, + 'click .delete': this._deleteHandler + }); + this._initButtonBarEventHandlers(); + }, + + _destroyEventHandlers: function () { + this._destroyButtonBarEventHandlers(); + this._off(this.options.filesContainer, 'click'); + this._super(); + }, + + _enableFileInputButton: function () { + this.element.find('.fileinput-button input') + .prop('disabled', false) + .parent().removeClass('disabled'); + }, + + _disableFileInputButton: function () { + this.element.find('.fileinput-button input') + .prop('disabled', true) + .parent().addClass('disabled'); + }, + + _initTemplates: function () { + var options = this.options; + options.templatesContainer = this.document[0].createElement( + options.filesContainer.prop('nodeName') + ); + if (tmpl) { + if (options.uploadTemplateId) { + options.uploadTemplate = tmpl(options.uploadTemplateId); + } + if (options.downloadTemplateId) { + options.downloadTemplate = tmpl(options.downloadTemplateId); + } + } + }, + + _initFilesContainer: function () { + var options = this.options; + if (options.filesContainer === undefined) { + options.filesContainer = this.element.find('.files'); + } else if (!(options.filesContainer instanceof $)) { + options.filesContainer = $(options.filesContainer); + } + }, + + _initSpecialOptions: function () { + this._super(); + this._initFilesContainer(); + this._initTemplates(); + }, + + _create: function () { + this._super(); + this._resetFinishedDeferreds(); + if (!$.support.fileInput) { + this._disableFileInputButton(); + } + }, + + enable: function () { + var wasDisabled = false; + if (this.options.disabled) { + wasDisabled = true; + } + this._super(); + if (wasDisabled) { + this.element.find('input, button').prop('disabled', false); + this._enableFileInputButton(); + } + }, + + disable: function () { + if (!this.options.disabled) { + this.element.find('input, button').prop('disabled', true); + this._disableFileInputButton(); + } + this._super(); + } + + }); + +})); diff --git a/node_modules/image_picker/AngularMediaModal/assets/fileupload/js/jquery.fileupload-validate.js b/node_modules/image_picker/AngularMediaModal/assets/fileupload/js/jquery.fileupload-validate.js new file mode 100644 index 0000000..d6f754c --- /dev/null +++ b/node_modules/image_picker/AngularMediaModal/assets/fileupload/js/jquery.fileupload-validate.js @@ -0,0 +1,122 @@ +/* + * jQuery File Upload Validation Plugin + * https://github.com/blueimp/jQuery-File-Upload + * + * Copyright 2013, Sebastian Tschan + * https://blueimp.net + * + * Licensed under the MIT license: + * http://www.opensource.org/licenses/MIT + */ + +/* global define, require, window */ + +(function (factory) { + 'use strict'; + if (typeof define === 'function' && define.amd) { + // Register as an anonymous AMD module: + define([ + 'jquery', + './jquery.fileupload-process' + ], factory); + } else if (typeof exports === 'object') { + // Node/CommonJS: + factory(require('jquery')); + } else { + // Browser globals: + factory( + window.jQuery + ); + } +}(function ($) { + 'use strict'; + + // Append to the default processQueue: + $.blueimp.fileupload.prototype.options.processQueue.push( + { + action: 'validate', + // Always trigger this action, + // even if the previous action was rejected: + always: true, + // Options taken from the global options map: + acceptFileTypes: '@', + maxFileSize: '@', + minFileSize: '@', + maxNumberOfFiles: '@', + disabled: '@disableValidation' + } + ); + + // The File Upload Validation plugin extends the fileupload widget + // with file validation functionality: + $.widget('blueimp.fileupload', $.blueimp.fileupload, { + + options: { + /* + // The regular expression for allowed file types, matches + // against either file type or file name: + acceptFileTypes: /(\.|\/)(gif|jpe?g|png)$/i, + // The maximum allowed file size in bytes: + maxFileSize: 10000000, // 10 MB + // The minimum allowed file size in bytes: + minFileSize: undefined, // No minimal file size + // The limit of files to be uploaded: + maxNumberOfFiles: 10, + */ + + // Function returning the current number of files, + // has to be overriden for maxNumberOfFiles validation: + getNumberOfFiles: $.noop, + + // Error and info messages: + messages: { + maxNumberOfFiles: 'Maximum number of files exceeded', + acceptFileTypes: 'File type not allowed', + maxFileSize: 'File is too large', + minFileSize: 'File is too small' + } + }, + + processActions: { + + validate: function (data, options) { + if (options.disabled) { + return data; + } + var dfd = $.Deferred(), + settings = this.options, + file = data.files[data.index], + fileSize; + if (options.minFileSize || options.maxFileSize) { + fileSize = file.size; + } + if ($.type(options.maxNumberOfFiles) === 'number' && + (settings.getNumberOfFiles() || 0) + data.files.length > + options.maxNumberOfFiles) { + file.error = settings.i18n('maxNumberOfFiles'); + } else if (options.acceptFileTypes && + !(options.acceptFileTypes.test(file.type) || + options.acceptFileTypes.test(file.name))) { + file.error = settings.i18n('acceptFileTypes'); + } else if (fileSize > options.maxFileSize) { + file.error = settings.i18n('maxFileSize'); + } else if ($.type(fileSize) === 'number' && + fileSize < options.minFileSize) { + file.error = settings.i18n('minFileSize'); + } else { + delete file.error; + } + if (file.error || data.files.error) { + data.files.error = true; + dfd.rejectWith(this, [data]); + } else { + dfd.resolveWith(this, [data]); + } + return dfd.promise(); + } + + } + + }); + +})); diff --git a/node_modules/image_picker/AngularMediaModal/assets/fileupload/js/jquery.fileupload-video.js b/node_modules/image_picker/AngularMediaModal/assets/fileupload/js/jquery.fileupload-video.js new file mode 100644 index 0000000..8067ca1 --- /dev/null +++ b/node_modules/image_picker/AngularMediaModal/assets/fileupload/js/jquery.fileupload-video.js @@ -0,0 +1,112 @@ +/* + * jQuery File Upload Video Preview Plugin + * https://github.com/blueimp/jQuery-File-Upload + * + * Copyright 2013, Sebastian Tschan + * https://blueimp.net + * + * Licensed under the MIT license: + * http://www.opensource.org/licenses/MIT + */ + +/* jshint nomen:false */ +/* global define, require, window, document */ + +(function (factory) { + 'use strict'; + if (typeof define === 'function' && define.amd) { + // Register as an anonymous AMD module: + define([ + 'jquery', + 'load-image', + './jquery.fileupload-process' + ], factory); + } else if (typeof exports === 'object') { + // Node/CommonJS: + factory( + require('jquery'), + require('load-image') + ); + } else { + // Browser globals: + factory( + window.jQuery, + window.loadImage + ); + } +}(function ($, loadImage) { + 'use strict'; + + // Prepend to the default processQueue: + $.blueimp.fileupload.prototype.options.processQueue.unshift( + { + action: 'loadVideo', + // Use the action as prefix for the "@" options: + prefix: true, + fileTypes: '@', + maxFileSize: '@', + disabled: '@disableVideoPreview' + }, + { + action: 'setVideo', + name: '@videoPreviewName', + disabled: '@disableVideoPreview' + } + ); + + // The File Upload Video Preview plugin extends the fileupload widget + // with video preview functionality: + $.widget('blueimp.fileupload', $.blueimp.fileupload, { + + options: { + // The regular expression for the types of video files to load, + // matched against the file type: + loadVideoFileTypes: /^video\/.*$/ + }, + + _videoElement: document.createElement('video'), + + processActions: { + + // Loads the video file given via data.files and data.index + // as video element if the browser supports playing it. + // Accepts the options fileTypes (regular expression) + // and maxFileSize (integer) to limit the files to load: + loadVideo: function (data, options) { + if (options.disabled) { + return data; + } + var file = data.files[data.index], + url, + video; + if (this._videoElement.canPlayType && + this._videoElement.canPlayType(file.type) && + ($.type(options.maxFileSize) !== 'number' || + file.size <= options.maxFileSize) && + (!options.fileTypes || + options.fileTypes.test(file.type))) { + url = loadImage.createObjectURL(file); + if (url) { + video = this._videoElement.cloneNode(false); + video.src = url; + video.controls = true; + data.video = video; + return data; + } + } + return data; + }, + + // Sets the video element as a property of the file object: + setVideo: function (data, options) { + if (data.video && !options.disabled) { + data.files[data.index][options.name || 'preview'] = data.video; + } + return data; + } + + } + + }); + +})); diff --git a/node_modules/image_picker/AngularMediaModal/assets/fileupload/js/jquery.fileupload.js b/node_modules/image_picker/AngularMediaModal/assets/fileupload/js/jquery.fileupload.js new file mode 100644 index 0000000..91b7254 --- /dev/null +++ b/node_modules/image_picker/AngularMediaModal/assets/fileupload/js/jquery.fileupload.js @@ -0,0 +1,1477 @@ +/* + * jQuery File Upload Plugin + * https://github.com/blueimp/jQuery-File-Upload + * + * Copyright 2010, Sebastian Tschan + * https://blueimp.net + * + * Licensed under the MIT license: + * http://www.opensource.org/licenses/MIT + */ + +/* jshint nomen:false */ +/* global define, require, window, document, location, Blob, FormData */ + +(function (factory) { + 'use strict'; + if (typeof define === 'function' && define.amd) { + // Register as an anonymous AMD module: + define([ + 'jquery', + 'jquery.ui.widget' + ], factory); + } else if (typeof exports === 'object') { + // Node/CommonJS: + factory( + require('jquery'), + require('./vendor/jquery.ui.widget') + ); + } else { + // Browser globals: + factory(window.jQuery); + } +}(function ($) { + 'use strict'; + + // Detect file input support, based on + // http://viljamis.com/blog/2012/file-upload-support-on-mobile/ + $.support.fileInput = !(new RegExp( + // Handle devices which give false positives for the feature detection: + '(Android (1\\.[0156]|2\\.[01]))' + + '|(Windows Phone (OS 7|8\\.0))|(XBLWP)|(ZuneWP)|(WPDesktop)' + + '|(w(eb)?OSBrowser)|(webOS)' + + '|(Kindle/(1\\.0|2\\.[05]|3\\.0))' + ).test(window.navigator.userAgent) || + // Feature detection for all other devices: + $('').prop('disabled')); + + // The FileReader API is not actually used, but works as feature detection, + // as some Safari versions (5?) support XHR file uploads via the FormData API, + // but not non-multipart XHR file uploads. + // window.XMLHttpRequestUpload is not available on IE10, so we check for + // window.ProgressEvent instead to detect XHR2 file upload capability: + $.support.xhrFileUpload = !!(window.ProgressEvent && window.FileReader); + $.support.xhrFormDataFileUpload = !!window.FormData; + + // Detect support for Blob slicing (required for chunked uploads): + $.support.blobSlice = window.Blob && (Blob.prototype.slice || + Blob.prototype.webkitSlice || Blob.prototype.mozSlice); + + // Helper function to create drag handlers for dragover/dragenter/dragleave: + function getDragHandler(type) { + var isDragOver = type === 'dragover'; + return function (e) { + e.dataTransfer = e.originalEvent && e.originalEvent.dataTransfer; + var dataTransfer = e.dataTransfer; + if (dataTransfer && $.inArray('Files', dataTransfer.types) !== -1 && + this._trigger( + type, + $.Event(type, {delegatedEvent: e}) + ) !== false) { + e.preventDefault(); + if (isDragOver) { + dataTransfer.dropEffect = 'copy'; + } + } + }; + } + + // The fileupload widget listens for change events on file input fields defined + // via fileInput setting and paste or drop events of the given dropZone. + // In addition to the default jQuery Widget methods, the fileupload widget + // exposes the "add" and "send" methods, to add or directly send files using + // the fileupload API. + // By default, files added via file input selection, paste, drag & drop or + // "add" method are uploaded immediately, but it is possible to override + // the "add" callback option to queue file uploads. + $.widget('blueimp.fileupload', { + + options: { + // The drop target element(s), by the default the complete document. + // Set to null to disable drag & drop support: + dropZone: $(document), + // The paste target element(s), by the default undefined. + // Set to a DOM node or jQuery object to enable file pasting: + pasteZone: undefined, + // The file input field(s), that are listened to for change events. + // If undefined, it is set to the file input fields inside + // of the widget element on plugin initialization. + // Set to null to disable the change listener. + fileInput: undefined, + // By default, the file input field is replaced with a clone after + // each input field change event. This is required for iframe transport + // queues and allows change events to be fired for the same file + // selection, but can be disabled by setting the following option to false: + replaceFileInput: true, + // The parameter name for the file form data (the request argument name). + // If undefined or empty, the name property of the file input field is + // used, or "files[]" if the file input name property is also empty, + // can be a string or an array of strings: + paramName: undefined, + // By default, each file of a selection is uploaded using an individual + // request for XHR type uploads. Set to false to upload file + // selections in one request each: + singleFileUploads: true, + // To limit the number of files uploaded with one XHR request, + // set the following option to an integer greater than 0: + limitMultiFileUploads: undefined, + // The following option limits the number of files uploaded with one + // XHR request to keep the request size under or equal to the defined + // limit in bytes: + limitMultiFileUploadSize: undefined, + // Multipart file uploads add a number of bytes to each uploaded file, + // therefore the following option adds an overhead for each file used + // in the limitMultiFileUploadSize configuration: + limitMultiFileUploadSizeOverhead: 512, + // Set the following option to true to issue all file upload requests + // in a sequential order: + sequentialUploads: false, + // To limit the number of concurrent uploads, + // set the following option to an integer greater than 0: + limitConcurrentUploads: undefined, + // Set the following option to true to force iframe transport uploads: + forceIframeTransport: false, + // Set the following option to the location of a redirect url on the + // origin server, for cross-domain iframe transport uploads: + redirect: undefined, + // The parameter name for the redirect url, sent as part of the form + // data and set to 'redirect' if this option is empty: + redirectParamName: undefined, + // Set the following option to the location of a postMessage window, + // to enable postMessage transport uploads: + postMessage: undefined, + // By default, XHR file uploads are sent as multipart/form-data. + // The iframe transport is always using multipart/form-data. + // Set to false to enable non-multipart XHR uploads: + multipart: true, + // To upload large files in smaller chunks, set the following option + // to a preferred maximum chunk size. If set to 0, null or undefined, + // or the browser does not support the required Blob API, files will + // be uploaded as a whole. + maxChunkSize: undefined, + // When a non-multipart upload or a chunked multipart upload has been + // aborted, this option can be used to resume the upload by setting + // it to the size of the already uploaded bytes. This option is most + // useful when modifying the options object inside of the "add" or + // "send" callbacks, as the options are cloned for each file upload. + uploadedBytes: undefined, + // By default, failed (abort or error) file uploads are removed from the + // global progress calculation. Set the following option to false to + // prevent recalculating the global progress data: + recalculateProgress: true, + // Interval in milliseconds to calculate and trigger progress events: + progressInterval: 100, + // Interval in milliseconds to calculate progress bitrate: + bitrateInterval: 500, + // By default, uploads are started automatically when adding files: + autoUpload: true, + + // Error and info messages: + messages: { + uploadedBytes: 'Uploaded bytes exceed file size' + }, + + // Translation function, gets the message key to be translated + // and an object with context specific data as arguments: + i18n: function (message, context) { + message = this.messages[message] || message.toString(); + if (context) { + $.each(context, function (key, value) { + message = message.replace('{' + key + '}', value); + }); + } + return message; + }, + + // Additional form data to be sent along with the file uploads can be set + // using this option, which accepts an array of objects with name and + // value properties, a function returning such an array, a FormData + // object (for XHR file uploads), or a simple object. + // The form of the first fileInput is given as parameter to the function: + formData: function (form) { + return form.serializeArray(); + }, + + // The add callback is invoked as soon as files are added to the fileupload + // widget (via file input selection, drag & drop, paste or add API call). + // If the singleFileUploads option is enabled, this callback will be + // called once for each file in the selection for XHR file uploads, else + // once for each file selection. + // + // The upload starts when the submit method is invoked on the data parameter. + // The data object contains a files property holding the added files + // and allows you to override plugin options as well as define ajax settings. + // + // Listeners for this callback can also be bound the following way: + // .bind('fileuploadadd', func); + // + // data.submit() returns a Promise object and allows to attach additional + // handlers using jQuery's Deferred callbacks: + // data.submit().done(func).fail(func).always(func); + add: function (e, data) { + if (e.isDefaultPrevented()) { + return false; + } + if (data.autoUpload || (data.autoUpload !== false && + $(this).fileupload('option', 'autoUpload'))) { + data.process().done(function () { + data.submit(); + }); + } + }, + + // Other callbacks: + + // Callback for the submit event of each file upload: + // submit: function (e, data) {}, // .bind('fileuploadsubmit', func); + + // Callback for the start of each file upload request: + // send: function (e, data) {}, // .bind('fileuploadsend', func); + + // Callback for successful uploads: + // done: function (e, data) {}, // .bind('fileuploaddone', func); + + // Callback for failed (abort or error) uploads: + // fail: function (e, data) {}, // .bind('fileuploadfail', func); + + // Callback for completed (success, abort or error) requests: + // always: function (e, data) {}, // .bind('fileuploadalways', func); + + // Callback for upload progress events: + // progress: function (e, data) {}, // .bind('fileuploadprogress', func); + + // Callback for global upload progress events: + // progressall: function (e, data) {}, // .bind('fileuploadprogressall', func); + + // Callback for uploads start, equivalent to the global ajaxStart event: + // start: function (e) {}, // .bind('fileuploadstart', func); + + // Callback for uploads stop, equivalent to the global ajaxStop event: + // stop: function (e) {}, // .bind('fileuploadstop', func); + + // Callback for change events of the fileInput(s): + // change: function (e, data) {}, // .bind('fileuploadchange', func); + + // Callback for paste events to the pasteZone(s): + // paste: function (e, data) {}, // .bind('fileuploadpaste', func); + + // Callback for drop events of the dropZone(s): + // drop: function (e, data) {}, // .bind('fileuploaddrop', func); + + // Callback for dragover events of the dropZone(s): + // dragover: function (e) {}, // .bind('fileuploaddragover', func); + + // Callback for the start of each chunk upload request: + // chunksend: function (e, data) {}, // .bind('fileuploadchunksend', func); + + // Callback for successful chunk uploads: + // chunkdone: function (e, data) {}, // .bind('fileuploadchunkdone', func); + + // Callback for failed (abort or error) chunk uploads: + // chunkfail: function (e, data) {}, // .bind('fileuploadchunkfail', func); + + // Callback for completed (success, abort or error) chunk upload requests: + // chunkalways: function (e, data) {}, // .bind('fileuploadchunkalways', func); + + // The plugin options are used as settings object for the ajax calls. + // The following are jQuery ajax settings required for the file uploads: + processData: false, + contentType: false, + cache: false, + timeout: 0 + }, + + // A list of options that require reinitializing event listeners and/or + // special initialization code: + _specialOptions: [ + 'fileInput', + 'dropZone', + 'pasteZone', + 'multipart', + 'forceIframeTransport' + ], + + _blobSlice: $.support.blobSlice && function () { + var slice = this.slice || this.webkitSlice || this.mozSlice; + return slice.apply(this, arguments); + }, + + _BitrateTimer: function () { + this.timestamp = ((Date.now) ? Date.now() : (new Date()).getTime()); + this.loaded = 0; + this.bitrate = 0; + this.getBitrate = function (now, loaded, interval) { + var timeDiff = now - this.timestamp; + if (!this.bitrate || !interval || timeDiff > interval) { + this.bitrate = (loaded - this.loaded) * (1000 / timeDiff) * 8; + this.loaded = loaded; + this.timestamp = now; + } + return this.bitrate; + }; + }, + + _isXHRUpload: function (options) { + return !options.forceIframeTransport && + ((!options.multipart && $.support.xhrFileUpload) || + $.support.xhrFormDataFileUpload); + }, + + _getFormData: function (options) { + var formData; + if ($.type(options.formData) === 'function') { + return options.formData(options.form); + } + if ($.isArray(options.formData)) { + return options.formData; + } + if ($.type(options.formData) === 'object') { + formData = []; + $.each(options.formData, function (name, value) { + formData.push({name: name, value: value}); + }); + return formData; + } + return []; + }, + + _getTotal: function (files) { + var total = 0; + $.each(files, function (index, file) { + total += file.size || 1; + }); + return total; + }, + + _initProgressObject: function (obj) { + var progress = { + loaded: 0, + total: 0, + bitrate: 0 + }; + if (obj._progress) { + $.extend(obj._progress, progress); + } else { + obj._progress = progress; + } + }, + + _initResponseObject: function (obj) { + var prop; + if (obj._response) { + for (prop in obj._response) { + if (obj._response.hasOwnProperty(prop)) { + delete obj._response[prop]; + } + } + } else { + obj._response = {}; + } + }, + + _onProgress: function (e, data) { + if (e.lengthComputable) { + var now = ((Date.now) ? Date.now() : (new Date()).getTime()), + loaded; + if (data._time && data.progressInterval && + (now - data._time < data.progressInterval) && + e.loaded !== e.total) { + return; + } + data._time = now; + loaded = Math.floor( + e.loaded / e.total * (data.chunkSize || data._progress.total) + ) + (data.uploadedBytes || 0); + // Add the difference from the previously loaded state + // to the global loaded counter: + this._progress.loaded += (loaded - data._progress.loaded); + this._progress.bitrate = this._bitrateTimer.getBitrate( + now, + this._progress.loaded, + data.bitrateInterval + ); + data._progress.loaded = data.loaded = loaded; + data._progress.bitrate = data.bitrate = data._bitrateTimer.getBitrate( + now, + loaded, + data.bitrateInterval + ); + // Trigger a custom progress event with a total data property set + // to the file size(s) of the current upload and a loaded data + // property calculated accordingly: + this._trigger( + 'progress', + $.Event('progress', {delegatedEvent: e}), + data + ); + // Trigger a global progress event for all current file uploads, + // including ajax calls queued for sequential file uploads: + this._trigger( + 'progressall', + $.Event('progressall', {delegatedEvent: e}), + this._progress + ); + } + }, + + _initProgressListener: function (options) { + var that = this, + xhr = options.xhr ? options.xhr() : $.ajaxSettings.xhr(); + // Accesss to the native XHR object is required to add event listeners + // for the upload progress event: + if (xhr.upload) { + $(xhr.upload).bind('progress', function (e) { + var oe = e.originalEvent; + // Make sure the progress event properties get copied over: + e.lengthComputable = oe.lengthComputable; + e.loaded = oe.loaded; + e.total = oe.total; + that._onProgress(e, options); + }); + options.xhr = function () { + return xhr; + }; + } + }, + + _isInstanceOf: function (type, obj) { + // Cross-frame instanceof check + return Object.prototype.toString.call(obj) === '[object ' + type + ']'; + }, + + _initXHRData: function (options) { + var that = this, + formData, + file = options.files[0], + // Ignore non-multipart setting if not supported: + multipart = options.multipart || !$.support.xhrFileUpload, + paramName = $.type(options.paramName) === 'array' ? + options.paramName[0] : options.paramName; + options.headers = $.extend({}, options.headers); + if (options.contentRange) { + options.headers['Content-Range'] = options.contentRange; + } + if (!multipart || options.blob || !this._isInstanceOf('File', file)) { + options.headers['Content-Disposition'] = 'attachment; filename="' + + encodeURI(file.name) + '"'; + } + if (!multipart) { + options.contentType = file.type || 'application/octet-stream'; + options.data = options.blob || file; + } else if ($.support.xhrFormDataFileUpload) { + if (options.postMessage) { + // window.postMessage does not allow sending FormData + // objects, so we just add the File/Blob objects to + // the formData array and let the postMessage window + // create the FormData object out of this array: + formData = this._getFormData(options); + if (options.blob) { + formData.push({ + name: paramName, + value: options.blob + }); + } else { + $.each(options.files, function (index, file) { + formData.push({ + name: ($.type(options.paramName) === 'array' && + options.paramName[index]) || paramName, + value: file + }); + }); + } + } else { + if (that._isInstanceOf('FormData', options.formData)) { + formData = options.formData; + } else { + formData = new FormData(); + $.each(this._getFormData(options), function (index, field) { + formData.append(field.name, field.value); + }); + } + if (options.blob) { + formData.append(paramName, options.blob, file.name); + } else { + $.each(options.files, function (index, file) { + // This check allows the tests to run with + // dummy objects: + if (that._isInstanceOf('File', file) || + that._isInstanceOf('Blob', file)) { + formData.append( + ($.type(options.paramName) === 'array' && + options.paramName[index]) || paramName, + file, + file.uploadName || file.name + ); + } + }); + } + } + options.data = formData; + } + // Blob reference is not needed anymore, free memory: + options.blob = null; + }, + + _initIframeSettings: function (options) { + var targetHost = $('').prop('href', options.url).prop('host'); + // Setting the dataType to iframe enables the iframe transport: + options.dataType = 'iframe ' + (options.dataType || ''); + // The iframe transport accepts a serialized array as form data: + options.formData = this._getFormData(options); + // Add redirect url to form data on cross-domain uploads: + if (options.redirect && targetHost && targetHost !== location.host) { + options.formData.push({ + name: options.redirectParamName || 'redirect', + value: options.redirect + }); + } + }, + + _initDataSettings: function (options) { + if (this._isXHRUpload(options)) { + if (!this._chunkedUpload(options, true)) { + if (!options.data) { + this._initXHRData(options); + } + this._initProgressListener(options); + } + if (options.postMessage) { + // Setting the dataType to postmessage enables the + // postMessage transport: + options.dataType = 'postmessage ' + (options.dataType || ''); + } + } else { + this._initIframeSettings(options); + } + }, + + _getParamName: function (options) { + var fileInput = $(options.fileInput), + paramName = options.paramName; + if (!paramName) { + paramName = []; + fileInput.each(function () { + var input = $(this), + name = input.prop('name') || 'files[]', + i = (input.prop('files') || [1]).length; + while (i) { + paramName.push(name); + i -= 1; + } + }); + if (!paramName.length) { + paramName = [fileInput.prop('name') || 'files[]']; + } + } else if (!$.isArray(paramName)) { + paramName = [paramName]; + } + return paramName; + }, + + _initFormSettings: function (options) { + // Retrieve missing options from the input field and the + // associated form, if available: + if (!options.form || !options.form.length) { + options.form = $(options.fileInput.prop('form')); + // If the given file input doesn't have an associated form, + // use the default widget file input's form: + if (!options.form.length) { + options.form = $(this.options.fileInput.prop('form')); + } + } + options.paramName = this._getParamName(options); + if (!options.url) { + options.url = options.form.prop('action') || location.href; + } + // The HTTP request method must be "POST" or "PUT": + options.type = (options.type || + ($.type(options.form.prop('method')) === 'string' && + options.form.prop('method')) || '' + ).toUpperCase(); + if (options.type !== 'POST' && options.type !== 'PUT' && + options.type !== 'PATCH') { + options.type = 'POST'; + } + if (!options.formAcceptCharset) { + options.formAcceptCharset = options.form.attr('accept-charset'); + } + }, + + _getAJAXSettings: function (data) { + var options = $.extend({}, this.options, data); + this._initFormSettings(options); + this._initDataSettings(options); + return options; + }, + + // jQuery 1.6 doesn't provide .state(), + // while jQuery 1.8+ removed .isRejected() and .isResolved(): + _getDeferredState: function (deferred) { + if (deferred.state) { + return deferred.state(); + } + if (deferred.isResolved()) { + return 'resolved'; + } + if (deferred.isRejected()) { + return 'rejected'; + } + return 'pending'; + }, + + // Maps jqXHR callbacks to the equivalent + // methods of the given Promise object: + _enhancePromise: function (promise) { + promise.success = promise.done; + promise.error = promise.fail; + promise.complete = promise.always; + return promise; + }, + + // Creates and returns a Promise object enhanced with + // the jqXHR methods abort, success, error and complete: + _getXHRPromise: function (resolveOrReject, context, args) { + var dfd = $.Deferred(), + promise = dfd.promise(); + context = context || this.options.context || promise; + if (resolveOrReject === true) { + dfd.resolveWith(context, args); + } else if (resolveOrReject === false) { + dfd.rejectWith(context, args); + } + promise.abort = dfd.promise; + return this._enhancePromise(promise); + }, + + // Adds convenience methods to the data callback argument: + _addConvenienceMethods: function (e, data) { + var that = this, + getPromise = function (args) { + return $.Deferred().resolveWith(that, args).promise(); + }; + data.process = function (resolveFunc, rejectFunc) { + if (resolveFunc || rejectFunc) { + data._processQueue = this._processQueue = + (this._processQueue || getPromise([this])).pipe( + function () { + if (data.errorThrown) { + return $.Deferred() + .rejectWith(that, [data]).promise(); + } + return getPromise(arguments); + } + ).pipe(resolveFunc, rejectFunc); + } + return this._processQueue || getPromise([this]); + }; + data.submit = function () { + if (this.state() !== 'pending') { + data.jqXHR = this.jqXHR = + (that._trigger( + 'submit', + $.Event('submit', {delegatedEvent: e}), + this + ) !== false) && that._onSend(e, this); + } + return this.jqXHR || that._getXHRPromise(); + }; + data.abort = function () { + if (this.jqXHR) { + return this.jqXHR.abort(); + } + this.errorThrown = 'abort'; + that._trigger('fail', null, this); + return that._getXHRPromise(false); + }; + data.state = function () { + if (this.jqXHR) { + return that._getDeferredState(this.jqXHR); + } + if (this._processQueue) { + return that._getDeferredState(this._processQueue); + } + }; + data.processing = function () { + return !this.jqXHR && this._processQueue && that + ._getDeferredState(this._processQueue) === 'pending'; + }; + data.progress = function () { + return this._progress; + }; + data.response = function () { + return this._response; + }; + }, + + // Parses the Range header from the server response + // and returns the uploaded bytes: + _getUploadedBytes: function (jqXHR) { + var range = jqXHR.getResponseHeader('Range'), + parts = range && range.split('-'), + upperBytesPos = parts && parts.length > 1 && + parseInt(parts[1], 10); + return upperBytesPos && upperBytesPos + 1; + }, + + // Uploads a file in multiple, sequential requests + // by splitting the file up in multiple blob chunks. + // If the second parameter is true, only tests if the file + // should be uploaded in chunks, but does not invoke any + // upload requests: + _chunkedUpload: function (options, testOnly) { + options.uploadedBytes = options.uploadedBytes || 0; + var that = this, + file = options.files[0], + fs = file.size, + ub = options.uploadedBytes, + mcs = options.maxChunkSize || fs, + slice = this._blobSlice, + dfd = $.Deferred(), + promise = dfd.promise(), + jqXHR, + upload; + if (!(this._isXHRUpload(options) && slice && (ub || mcs < fs)) || + options.data) { + return false; + } + if (testOnly) { + return true; + } + if (ub >= fs) { + file.error = options.i18n('uploadedBytes'); + return this._getXHRPromise( + false, + options.context, + [null, 'error', file.error] + ); + } + // The chunk upload method: + upload = function () { + // Clone the options object for each chunk upload: + var o = $.extend({}, options), + currentLoaded = o._progress.loaded; + o.blob = slice.call( + file, + ub, + ub + mcs, + file.type + ); + // Store the current chunk size, as the blob itself + // will be dereferenced after data processing: + o.chunkSize = o.blob.size; + // Expose the chunk bytes position range: + o.contentRange = 'bytes ' + ub + '-' + + (ub + o.chunkSize - 1) + '/' + fs; + // Process the upload data (the blob and potential form data): + that._initXHRData(o); + // Add progress listeners for this chunk upload: + that._initProgressListener(o); + jqXHR = ((that._trigger('chunksend', null, o) !== false && $.ajax(o)) || + that._getXHRPromise(false, o.context)) + .done(function (result, textStatus, jqXHR) { + ub = that._getUploadedBytes(jqXHR) || + (ub + o.chunkSize); + // Create a progress event if no final progress event + // with loaded equaling total has been triggered + // for this chunk: + if (currentLoaded + o.chunkSize - o._progress.loaded) { + that._onProgress($.Event('progress', { + lengthComputable: true, + loaded: ub - o.uploadedBytes, + total: ub - o.uploadedBytes + }), o); + } + options.uploadedBytes = o.uploadedBytes = ub; + o.result = result; + o.textStatus = textStatus; + o.jqXHR = jqXHR; + that._trigger('chunkdone', null, o); + that._trigger('chunkalways', null, o); + if (ub < fs) { + // File upload not yet complete, + // continue with the next chunk: + upload(); + } else { + dfd.resolveWith( + o.context, + [result, textStatus, jqXHR] + ); + } + }) + .fail(function (jqXHR, textStatus, errorThrown) { + o.jqXHR = jqXHR; + o.textStatus = textStatus; + o.errorThrown = errorThrown; + that._trigger('chunkfail', null, o); + that._trigger('chunkalways', null, o); + dfd.rejectWith( + o.context, + [jqXHR, textStatus, errorThrown] + ); + }); + }; + this._enhancePromise(promise); + promise.abort = function () { + return jqXHR.abort(); + }; + upload(); + return promise; + }, + + _beforeSend: function (e, data) { + if (this._active === 0) { + // the start callback is triggered when an upload starts + // and no other uploads are currently running, + // equivalent to the global ajaxStart event: + this._trigger('start'); + // Set timer for global bitrate progress calculation: + this._bitrateTimer = new this._BitrateTimer(); + // Reset the global progress values: + this._progress.loaded = this._progress.total = 0; + this._progress.bitrate = 0; + } + // Make sure the container objects for the .response() and + // .progress() methods on the data object are available + // and reset to their initial state: + this._initResponseObject(data); + this._initProgressObject(data); + data._progress.loaded = data.loaded = data.uploadedBytes || 0; + data._progress.total = data.total = this._getTotal(data.files) || 1; + data._progress.bitrate = data.bitrate = 0; + this._active += 1; + // Initialize the global progress values: + this._progress.loaded += data.loaded; + this._progress.total += data.total; + }, + + _onDone: function (result, textStatus, jqXHR, options) { + var total = options._progress.total, + response = options._response; + if (options._progress.loaded < total) { + // Create a progress event if no final progress event + // with loaded equaling total has been triggered: + this._onProgress($.Event('progress', { + lengthComputable: true, + loaded: total, + total: total + }), options); + } + response.result = options.result = result; + response.textStatus = options.textStatus = textStatus; + response.jqXHR = options.jqXHR = jqXHR; + this._trigger('done', null, options); + }, + + _onFail: function (jqXHR, textStatus, errorThrown, options) { + var response = options._response; + if (options.recalculateProgress) { + // Remove the failed (error or abort) file upload from + // the global progress calculation: + this._progress.loaded -= options._progress.loaded; + this._progress.total -= options._progress.total; + } + response.jqXHR = options.jqXHR = jqXHR; + response.textStatus = options.textStatus = textStatus; + response.errorThrown = options.errorThrown = errorThrown; + this._trigger('fail', null, options); + }, + + _onAlways: function (jqXHRorResult, textStatus, jqXHRorError, options) { + // jqXHRorResult, textStatus and jqXHRorError are added to the + // options object via done and fail callbacks + this._trigger('always', null, options); + }, + + _onSend: function (e, data) { + if (!data.submit) { + this._addConvenienceMethods(e, data); + } + var that = this, + jqXHR, + aborted, + slot, + pipe, + options = that._getAJAXSettings(data), + send = function () { + that._sending += 1; + // Set timer for bitrate progress calculation: + options._bitrateTimer = new that._BitrateTimer(); + jqXHR = jqXHR || ( + ((aborted || that._trigger( + 'send', + $.Event('send', {delegatedEvent: e}), + options + ) === false) && + that._getXHRPromise(false, options.context, aborted)) || + that._chunkedUpload(options) || $.ajax(options) + ).done(function (result, textStatus, jqXHR) { + that._onDone(result, textStatus, jqXHR, options); + }).fail(function (jqXHR, textStatus, errorThrown) { + that._onFail(jqXHR, textStatus, errorThrown, options); + }).always(function (jqXHRorResult, textStatus, jqXHRorError) { + that._onAlways( + jqXHRorResult, + textStatus, + jqXHRorError, + options + ); + that._sending -= 1; + that._active -= 1; + if (options.limitConcurrentUploads && + options.limitConcurrentUploads > that._sending) { + // Start the next queued upload, + // that has not been aborted: + var nextSlot = that._slots.shift(); + while (nextSlot) { + if (that._getDeferredState(nextSlot) === 'pending') { + nextSlot.resolve(); + break; + } + nextSlot = that._slots.shift(); + } + } + if (that._active === 0) { + // The stop callback is triggered when all uploads have + // been completed, equivalent to the global ajaxStop event: + that._trigger('stop'); + } + }); + return jqXHR; + }; + this._beforeSend(e, options); + if (this.options.sequentialUploads || + (this.options.limitConcurrentUploads && + this.options.limitConcurrentUploads <= this._sending)) { + if (this.options.limitConcurrentUploads > 1) { + slot = $.Deferred(); + this._slots.push(slot); + pipe = slot.pipe(send); + } else { + this._sequence = this._sequence.pipe(send, send); + pipe = this._sequence; + } + // Return the piped Promise object, enhanced with an abort method, + // which is delegated to the jqXHR object of the current upload, + // and jqXHR callbacks mapped to the equivalent Promise methods: + pipe.abort = function () { + aborted = [undefined, 'abort', 'abort']; + if (!jqXHR) { + if (slot) { + slot.rejectWith(options.context, aborted); + } + return send(); + } + return jqXHR.abort(); + }; + return this._enhancePromise(pipe); + } + return send(); + }, + + _onAdd: function (e, data) { + var that = this, + result = true, + options = $.extend({}, this.options, data), + files = data.files, + filesLength = files.length, + limit = options.limitMultiFileUploads, + limitSize = options.limitMultiFileUploadSize, + overhead = options.limitMultiFileUploadSizeOverhead, + batchSize = 0, + paramName = this._getParamName(options), + paramNameSet, + paramNameSlice, + fileSet, + i, + j = 0; + if (!filesLength) { + return false; + } + if (limitSize && files[0].size === undefined) { + limitSize = undefined; + } + if (!(options.singleFileUploads || limit || limitSize) || + !this._isXHRUpload(options)) { + fileSet = [files]; + paramNameSet = [paramName]; + } else if (!(options.singleFileUploads || limitSize) && limit) { + fileSet = []; + paramNameSet = []; + for (i = 0; i < filesLength; i += limit) { + fileSet.push(files.slice(i, i + limit)); + paramNameSlice = paramName.slice(i, i + limit); + if (!paramNameSlice.length) { + paramNameSlice = paramName; + } + paramNameSet.push(paramNameSlice); + } + } else if (!options.singleFileUploads && limitSize) { + fileSet = []; + paramNameSet = []; + for (i = 0; i < filesLength; i = i + 1) { + batchSize += files[i].size + overhead; + if (i + 1 === filesLength || + ((batchSize + files[i + 1].size + overhead) > limitSize) || + (limit && i + 1 - j >= limit)) { + fileSet.push(files.slice(j, i + 1)); + paramNameSlice = paramName.slice(j, i + 1); + if (!paramNameSlice.length) { + paramNameSlice = paramName; + } + paramNameSet.push(paramNameSlice); + j = i + 1; + batchSize = 0; + } + } + } else { + paramNameSet = paramName; + } + data.originalFiles = files; + $.each(fileSet || files, function (index, element) { + var newData = $.extend({}, data); + newData.files = fileSet ? element : [element]; + newData.paramName = paramNameSet[index]; + that._initResponseObject(newData); + that._initProgressObject(newData); + that._addConvenienceMethods(e, newData); + result = that._trigger( + 'add', + $.Event('add', {delegatedEvent: e}), + newData + ); + return result; + }); + return result; + }, + + _replaceFileInput: function (data) { + var input = data.fileInput, + inputClone = input.clone(true), + restoreFocus = input.is(document.activeElement); + // Add a reference for the new cloned file input to the data argument: + data.fileInputClone = inputClone; + $('
').append(inputClone)[0].reset(); + // Detaching allows to insert the fileInput on another form + // without loosing the file input value: + input.after(inputClone).detach(); + // If the fileInput had focus before it was detached, + // restore focus to the inputClone. + if (restoreFocus) { + inputClone.focus(); + } + // Avoid memory leaks with the detached file input: + $.cleanData(input.unbind('remove')); + // Replace the original file input element in the fileInput + // elements set with the clone, which has been copied including + // event handlers: + this.options.fileInput = this.options.fileInput.map(function (i, el) { + if (el === input[0]) { + return inputClone[0]; + } + return el; + }); + // If the widget has been initialized on the file input itself, + // override this.element with the file input clone: + if (input[0] === this.element[0]) { + this.element = inputClone; + } + }, + + _handleFileTreeEntry: function (entry, path) { + var that = this, + dfd = $.Deferred(), + errorHandler = function (e) { + if (e && !e.entry) { + e.entry = entry; + } + // Since $.when returns immediately if one + // Deferred is rejected, we use resolve instead. + // This allows valid files and invalid items + // to be returned together in one set: + dfd.resolve([e]); + }, + successHandler = function (entries) { + that._handleFileTreeEntries( + entries, + path + entry.name + '/' + ).done(function (files) { + dfd.resolve(files); + }).fail(errorHandler); + }, + readEntries = function () { + dirReader.readEntries(function (results) { + if (!results.length) { + successHandler(entries); + } else { + entries = entries.concat(results); + readEntries(); + } + }, errorHandler); + }, + dirReader, entries = []; + path = path || ''; + if (entry.isFile) { + if (entry._file) { + // Workaround for Chrome bug #149735 + entry._file.relativePath = path; + dfd.resolve(entry._file); + } else { + entry.file(function (file) { + file.relativePath = path; + dfd.resolve(file); + }, errorHandler); + } + } else if (entry.isDirectory) { + dirReader = entry.createReader(); + readEntries(); + } else { + // Return an empy list for file system items + // other than files or directories: + dfd.resolve([]); + } + return dfd.promise(); + }, + + _handleFileTreeEntries: function (entries, path) { + var that = this; + return $.when.apply( + $, + $.map(entries, function (entry) { + return that._handleFileTreeEntry(entry, path); + }) + ).pipe(function () { + return Array.prototype.concat.apply( + [], + arguments + ); + }); + }, + + _getDroppedFiles: function (dataTransfer) { + dataTransfer = dataTransfer || {}; + var items = dataTransfer.items; + if (items && items.length && (items[0].webkitGetAsEntry || + items[0].getAsEntry)) { + return this._handleFileTreeEntries( + $.map(items, function (item) { + var entry; + if (item.webkitGetAsEntry) { + entry = item.webkitGetAsEntry(); + if (entry) { + // Workaround for Chrome bug #149735: + entry._file = item.getAsFile(); + } + return entry; + } + return item.getAsEntry(); + }) + ); + } + return $.Deferred().resolve( + $.makeArray(dataTransfer.files) + ).promise(); + }, + + _getSingleFileInputFiles: function (fileInput) { + fileInput = $(fileInput); + var entries = fileInput.prop('webkitEntries') || + fileInput.prop('entries'), + files, + value; + if (entries && entries.length) { + return this._handleFileTreeEntries(entries); + } + files = $.makeArray(fileInput.prop('files')); + if (!files.length) { + value = fileInput.prop('value'); + if (!value) { + return $.Deferred().resolve([]).promise(); + } + // If the files property is not available, the browser does not + // support the File API and we add a pseudo File object with + // the input value as name with path information removed: + files = [{name: value.replace(/^.*\\/, '')}]; + } else if (files[0].name === undefined && files[0].fileName) { + // File normalization for Safari 4 and Firefox 3: + $.each(files, function (index, file) { + file.name = file.fileName; + file.size = file.fileSize; + }); + } + return $.Deferred().resolve(files).promise(); + }, + + _getFileInputFiles: function (fileInput) { + if (!(fileInput instanceof $) || fileInput.length === 1) { + return this._getSingleFileInputFiles(fileInput); + } + return $.when.apply( + $, + $.map(fileInput, this._getSingleFileInputFiles) + ).pipe(function () { + return Array.prototype.concat.apply( + [], + arguments + ); + }); + }, + + _onChange: function (e) { + var that = this, + data = { + fileInput: $(e.target), + form: $(e.target.form) + }; + this._getFileInputFiles(data.fileInput).always(function (files) { + data.files = files; + if (that.options.replaceFileInput) { + that._replaceFileInput(data); + } + if (that._trigger( + 'change', + $.Event('change', {delegatedEvent: e}), + data + ) !== false) { + that._onAdd(e, data); + } + }); + }, + + _onPaste: function (e) { + var items = e.originalEvent && e.originalEvent.clipboardData && + e.originalEvent.clipboardData.items, + data = {files: []}; + if (items && items.length) { + $.each(items, function (index, item) { + var file = item.getAsFile && item.getAsFile(); + if (file) { + data.files.push(file); + } + }); + if (this._trigger( + 'paste', + $.Event('paste', {delegatedEvent: e}), + data + ) !== false) { + this._onAdd(e, data); + } + } + }, + + _onDrop: function (e) { + e.dataTransfer = e.originalEvent && e.originalEvent.dataTransfer; + var that = this, + dataTransfer = e.dataTransfer, + data = {}; + if (dataTransfer && dataTransfer.files && dataTransfer.files.length) { + e.preventDefault(); + this._getDroppedFiles(dataTransfer).always(function (files) { + data.files = files; + if (that._trigger( + 'drop', + $.Event('drop', {delegatedEvent: e}), + data + ) !== false) { + that._onAdd(e, data); + } + }); + } + }, + + _onDragOver: getDragHandler('dragover'), + + _onDragEnter: getDragHandler('dragenter'), + + _onDragLeave: getDragHandler('dragleave'), + + _initEventHandlers: function () { + if (this._isXHRUpload(this.options)) { + this._on(this.options.dropZone, { + dragover: this._onDragOver, + drop: this._onDrop, + // event.preventDefault() on dragenter is required for IE10+: + dragenter: this._onDragEnter, + // dragleave is not required, but added for completeness: + dragleave: this._onDragLeave + }); + this._on(this.options.pasteZone, { + paste: this._onPaste + }); + } + if ($.support.fileInput) { + this._on(this.options.fileInput, { + change: this._onChange + }); + } + }, + + _destroyEventHandlers: function () { + this._off(this.options.dropZone, 'dragenter dragleave dragover drop'); + this._off(this.options.pasteZone, 'paste'); + this._off(this.options.fileInput, 'change'); + }, + + _setOption: function (key, value) { + var reinit = $.inArray(key, this._specialOptions) !== -1; + if (reinit) { + this._destroyEventHandlers(); + } + this._super(key, value); + if (reinit) { + this._initSpecialOptions(); + this._initEventHandlers(); + } + }, + + _initSpecialOptions: function () { + var options = this.options; + if (options.fileInput === undefined) { + options.fileInput = this.element.is('input[type="file"]') ? + this.element : this.element.find('input[type="file"]'); + } else if (!(options.fileInput instanceof $)) { + options.fileInput = $(options.fileInput); + } + if (!(options.dropZone instanceof $)) { + options.dropZone = $(options.dropZone); + } + if (!(options.pasteZone instanceof $)) { + options.pasteZone = $(options.pasteZone); + } + }, + + _getRegExp: function (str) { + var parts = str.split('/'), + modifiers = parts.pop(); + parts.shift(); + return new RegExp(parts.join('/'), modifiers); + }, + + _isRegExpOption: function (key, value) { + return key !== 'url' && $.type(value) === 'string' && + /^\/.*\/[igm]{0,3}$/.test(value); + }, + + _initDataAttributes: function () { + var that = this, + options = this.options, + data = this.element.data(); + // Initialize options set via HTML5 data-attributes: + $.each( + this.element[0].attributes, + function (index, attr) { + var key = attr.name.toLowerCase(), + value; + if (/^data-/.test(key)) { + // Convert hyphen-ated key to camelCase: + key = key.slice(5).replace(/-[a-z]/g, function (str) { + return str.charAt(1).toUpperCase(); + }); + value = data[key]; + if (that._isRegExpOption(key, value)) { + value = that._getRegExp(value); + } + options[key] = value; + } + } + ); + }, + + _create: function () { + this._initDataAttributes(); + this._initSpecialOptions(); + this._slots = []; + this._sequence = this._getXHRPromise(true); + this._sending = this._active = 0; + this._initProgressObject(this); + this._initEventHandlers(); + }, + + // This method is exposed to the widget API and allows to query + // the number of active uploads: + active: function () { + return this._active; + }, + + // This method is exposed to the widget API and allows to query + // the widget upload progress. + // It returns an object with loaded, total and bitrate properties + // for the running uploads: + progress: function () { + return this._progress; + }, + + // This method is exposed to the widget API and allows adding files + // using the fileupload API. The data parameter accepts an object which + // must have a files property and can contain additional options: + // .fileupload('add', {files: filesList}); + add: function (data) { + var that = this; + if (!data || this.options.disabled) { + return; + } + if (data.fileInput && !data.files) { + this._getFileInputFiles(data.fileInput).always(function (files) { + data.files = files; + that._onAdd(null, data); + }); + } else { + data.files = $.makeArray(data.files); + this._onAdd(null, data); + } + }, + + // This method is exposed to the widget API and allows sending files + // using the fileupload API. The data parameter accepts an object which + // must have a files or fileInput property and can contain additional options: + // .fileupload('send', {files: filesList}); + // The method returns a Promise object for the file upload call. + send: function (data) { + if (data && !this.options.disabled) { + if (data.fileInput && !data.files) { + var that = this, + dfd = $.Deferred(), + promise = dfd.promise(), + jqXHR, + aborted; + promise.abort = function () { + aborted = true; + if (jqXHR) { + return jqXHR.abort(); + } + dfd.reject(null, 'abort', 'abort'); + return promise; + }; + this._getFileInputFiles(data.fileInput).always( + function (files) { + if (aborted) { + return; + } + if (!files.length) { + dfd.reject(); + return; + } + data.files = files; + jqXHR = that._onSend(null, data); + jqXHR.then( + function (result, textStatus, jqXHR) { + dfd.resolve(result, textStatus, jqXHR); + }, + function (jqXHR, textStatus, errorThrown) { + dfd.reject(jqXHR, textStatus, errorThrown); + } + ); + } + ); + return this._enhancePromise(promise); + } + data.files = $.makeArray(data.files); + if (data.files.length) { + return this._onSend(null, data); + } + } + return this._getXHRPromise(false, data && data.context); + } + + }); + +})); diff --git a/node_modules/image_picker/AngularMediaModal/assets/fileupload/js/jquery.iframe-transport.js b/node_modules/image_picker/AngularMediaModal/assets/fileupload/js/jquery.iframe-transport.js new file mode 100644 index 0000000..a7d34e0 --- /dev/null +++ b/node_modules/image_picker/AngularMediaModal/assets/fileupload/js/jquery.iframe-transport.js @@ -0,0 +1,217 @@ +/* + * jQuery Iframe Transport Plugin + * https://github.com/blueimp/jQuery-File-Upload + * + * Copyright 2011, Sebastian Tschan + * https://blueimp.net + * + * Licensed under the MIT license: + * http://www.opensource.org/licenses/MIT + */ + +/* global define, require, window, document */ + +(function (factory) { + 'use strict'; + if (typeof define === 'function' && define.amd) { + // Register as an anonymous AMD module: + define(['jquery'], factory); + } else if (typeof exports === 'object') { + // Node/CommonJS: + factory(require('jquery')); + } else { + // Browser globals: + factory(window.jQuery); + } +}(function ($) { + 'use strict'; + + // Helper variable to create unique names for the transport iframes: + var counter = 0; + + // The iframe transport accepts four additional options: + // options.fileInput: a jQuery collection of file input fields + // options.paramName: the parameter name for the file form data, + // overrides the name property of the file input field(s), + // can be a string or an array of strings. + // options.formData: an array of objects with name and value properties, + // equivalent to the return data of .serializeArray(), e.g.: + // [{name: 'a', value: 1}, {name: 'b', value: 2}] + // options.initialIframeSrc: the URL of the initial iframe src, + // by default set to "javascript:false;" + $.ajaxTransport('iframe', function (options) { + if (options.async) { + // javascript:false as initial iframe src + // prevents warning popups on HTTPS in IE6: + /*jshint scripturl: true */ + var initialIframeSrc = options.initialIframeSrc || 'javascript:false;', + /*jshint scripturl: false */ + form, + iframe, + addParamChar; + return { + send: function (_, completeCallback) { + form = $('
'); + form.attr('accept-charset', options.formAcceptCharset); + addParamChar = /\?/.test(options.url) ? '&' : '?'; + // XDomainRequest only supports GET and POST: + if (options.type === 'DELETE') { + options.url = options.url + addParamChar + '_method=DELETE'; + options.type = 'POST'; + } else if (options.type === 'PUT') { + options.url = options.url + addParamChar + '_method=PUT'; + options.type = 'POST'; + } else if (options.type === 'PATCH') { + options.url = options.url + addParamChar + '_method=PATCH'; + options.type = 'POST'; + } + // IE versions below IE8 cannot set the name property of + // elements that have already been added to the DOM, + // so we set the name along with the iframe HTML markup: + counter += 1; + iframe = $( + '' + ).bind('load', function () { + var fileInputClones, + paramNames = $.isArray(options.paramName) ? + options.paramName : [options.paramName]; + iframe + .unbind('load') + .bind('load', function () { + var response; + // Wrap in a try/catch block to catch exceptions thrown + // when trying to access cross-domain iframe contents: + try { + response = iframe.contents(); + // Google Chrome and Firefox do not throw an + // exception when calling iframe.contents() on + // cross-domain requests, so we unify the response: + if (!response.length || !response[0].firstChild) { + throw new Error(); + } + } catch (e) { + response = undefined; + } + // The complete callback returns the + // iframe content document as response object: + completeCallback( + 200, + 'success', + {'iframe': response} + ); + // Fix for IE endless progress bar activity bug + // (happens on form submits to iframe targets): + $('') + .appendTo(form); + window.setTimeout(function () { + // Removing the form in a setTimeout call + // allows Chrome's developer tools to display + // the response result + form.remove(); + }, 0); + }); + form + .prop('target', iframe.prop('name')) + .prop('action', options.url) + .prop('method', options.type); + if (options.formData) { + $.each(options.formData, function (index, field) { + $('') + .prop('name', field.name) + .val(field.value) + .appendTo(form); + }); + } + if (options.fileInput && options.fileInput.length && + options.type === 'POST') { + fileInputClones = options.fileInput.clone(); + // Insert a clone for each file input field: + options.fileInput.after(function (index) { + return fileInputClones[index]; + }); + if (options.paramName) { + options.fileInput.each(function (index) { + $(this).prop( + 'name', + paramNames[index] || options.paramName + ); + }); + } + // Appending the file input fields to the hidden form + // removes them from their original location: + form + .append(options.fileInput) + .prop('enctype', 'multipart/form-data') + // enctype must be set as encoding for IE: + .prop('encoding', 'multipart/form-data'); + // Remove the HTML5 form attribute from the input(s): + options.fileInput.removeAttr('form'); + } + form.submit(); + // Insert the file input fields at their original location + // by replacing the clones with the originals: + if (fileInputClones && fileInputClones.length) { + options.fileInput.each(function (index, input) { + var clone = $(fileInputClones[index]); + // Restore the original name and form properties: + $(input) + .prop('name', clone.prop('name')) + .attr('form', clone.attr('form')); + clone.replaceWith(input); + }); + } + }); + form.append(iframe).appendTo(document.body); + }, + abort: function () { + if (iframe) { + // javascript:false as iframe src aborts the request + // and prevents warning popups on HTTPS in IE6. + // concat is used to avoid the "Script URL" JSLint error: + iframe + .unbind('load') + .prop('src', initialIframeSrc); + } + if (form) { + form.remove(); + } + } + }; + } + }); + + // The iframe transport returns the iframe content document as response. + // The following adds converters from iframe to text, json, html, xml + // and script. + // Please note that the Content-Type for JSON responses has to be text/plain + // or text/html, if the browser doesn't include application/json in the + // Accept header, else IE will show a download dialog. + // The Content-Type for XML responses on the other hand has to be always + // application/xml or text/xml, so IE properly parses the XML response. + // See also + // https://github.com/blueimp/jQuery-File-Upload/wiki/Setup#content-type-negotiation + $.ajaxSetup({ + converters: { + 'iframe text': function (iframe) { + return iframe && $(iframe[0].body).text(); + }, + 'iframe json': function (iframe) { + return iframe && $.parseJSON($(iframe[0].body).text()); + }, + 'iframe html': function (iframe) { + return iframe && $(iframe[0].body).html(); + }, + 'iframe xml': function (iframe) { + var xmlDoc = iframe && iframe[0]; + return xmlDoc && $.isXMLDoc(xmlDoc) ? xmlDoc : + $.parseXML((xmlDoc.XMLDocument && xmlDoc.XMLDocument.xml) || + $(xmlDoc.body).html()); + }, + 'iframe script': function (iframe) { + return iframe && $.globalEval($(iframe[0].body).text()); + } + } + }); + +})); diff --git a/node_modules/image_picker/AngularMediaModal/assets/fileupload/js/vendor/canvas-to-blob.min.js b/node_modules/image_picker/AngularMediaModal/assets/fileupload/js/vendor/canvas-to-blob.min.js new file mode 100644 index 0000000..1c82bba --- /dev/null +++ b/node_modules/image_picker/AngularMediaModal/assets/fileupload/js/vendor/canvas-to-blob.min.js @@ -0,0 +1,2 @@ +!function(t){"use strict";var e=t.HTMLCanvasElement&&t.HTMLCanvasElement.prototype,o=t.Blob&&function(){try{return Boolean(new Blob)}catch(t){return!1}}(),n=o&&t.Uint8Array&&function(){try{return 100===new Blob([new Uint8Array(100)]).size}catch(t){return!1}}(),r=t.BlobBuilder||t.WebKitBlobBuilder||t.MozBlobBuilder||t.MSBlobBuilder,a=/^data:((.*?)(;charset=.*?)?)(;base64)?,/,i=(o||r)&&t.atob&&t.ArrayBuffer&&t.Uint8Array&&function(t){var e,i,l,u,b,c,d,B,f;if(e=t.match(a),!e)throw new Error("invalid data URI");for(i=e[2]?e[1]:"text/plain"+(e[3]||";charset=US-ASCII"),l=!!e[4],u=t.slice(e[0].length),b=l?atob(u):decodeURIComponent(u),c=new ArrayBuffer(b.length),d=new Uint8Array(c),B=0;B", + options: { + disabled: false, + + // callbacks + create: null + }, + _createWidget: function( options, element ) { + element = $( element || this.defaultElement || this )[ 0 ]; + this.element = $( element ); + this.uuid = widget_uuid++; + this.eventNamespace = "." + this.widgetName + this.uuid; + + this.bindings = $(); + this.hoverable = $(); + this.focusable = $(); + + if ( element !== this ) { + $.data( element, this.widgetFullName, this ); + this._on( true, this.element, { + remove: function( event ) { + if ( event.target === element ) { + this.destroy(); + } + } + }); + this.document = $( element.style ? + // element within the document + element.ownerDocument : + // element is window or document + element.document || element ); + this.window = $( this.document[0].defaultView || this.document[0].parentWindow ); + } + + this.options = $.widget.extend( {}, + this.options, + this._getCreateOptions(), + options ); + + this._create(); + this._trigger( "create", null, this._getCreateEventData() ); + this._init(); + }, + _getCreateOptions: $.noop, + _getCreateEventData: $.noop, + _create: $.noop, + _init: $.noop, + + destroy: function() { + this._destroy(); + // we can probably remove the unbind calls in 2.0 + // all event bindings should go through this._on() + this.element + .unbind( this.eventNamespace ) + .removeData( this.widgetFullName ) + // support: jquery <1.6.3 + // http://bugs.jquery.com/ticket/9413 + .removeData( $.camelCase( this.widgetFullName ) ); + this.widget() + .unbind( this.eventNamespace ) + .removeAttr( "aria-disabled" ) + .removeClass( + this.widgetFullName + "-disabled " + + "ui-state-disabled" ); + + // clean up events and states + this.bindings.unbind( this.eventNamespace ); + this.hoverable.removeClass( "ui-state-hover" ); + this.focusable.removeClass( "ui-state-focus" ); + }, + _destroy: $.noop, + + widget: function() { + return this.element; + }, + + option: function( key, value ) { + var options = key, + parts, + curOption, + i; + + if ( arguments.length === 0 ) { + // don't return a reference to the internal hash + return $.widget.extend( {}, this.options ); + } + + if ( typeof key === "string" ) { + // handle nested keys, e.g., "foo.bar" => { foo: { bar: ___ } } + options = {}; + parts = key.split( "." ); + key = parts.shift(); + if ( parts.length ) { + curOption = options[ key ] = $.widget.extend( {}, this.options[ key ] ); + for ( i = 0; i < parts.length - 1; i++ ) { + curOption[ parts[ i ] ] = curOption[ parts[ i ] ] || {}; + curOption = curOption[ parts[ i ] ]; + } + key = parts.pop(); + if ( arguments.length === 1 ) { + return curOption[ key ] === undefined ? null : curOption[ key ]; + } + curOption[ key ] = value; + } else { + if ( arguments.length === 1 ) { + return this.options[ key ] === undefined ? null : this.options[ key ]; + } + options[ key ] = value; + } + } + + this._setOptions( options ); + + return this; + }, + _setOptions: function( options ) { + var key; + + for ( key in options ) { + this._setOption( key, options[ key ] ); + } + + return this; + }, + _setOption: function( key, value ) { + this.options[ key ] = value; + + if ( key === "disabled" ) { + this.widget() + .toggleClass( this.widgetFullName + "-disabled", !!value ); + + // If the widget is becoming disabled, then nothing is interactive + if ( value ) { + this.hoverable.removeClass( "ui-state-hover" ); + this.focusable.removeClass( "ui-state-focus" ); + } + } + + return this; + }, + + enable: function() { + return this._setOptions({ disabled: false }); + }, + disable: function() { + return this._setOptions({ disabled: true }); + }, + + _on: function( suppressDisabledCheck, element, handlers ) { + var delegateElement, + instance = this; + + // no suppressDisabledCheck flag, shuffle arguments + if ( typeof suppressDisabledCheck !== "boolean" ) { + handlers = element; + element = suppressDisabledCheck; + suppressDisabledCheck = false; + } + + // no element argument, shuffle and use this.element + if ( !handlers ) { + handlers = element; + element = this.element; + delegateElement = this.widget(); + } else { + element = delegateElement = $( element ); + this.bindings = this.bindings.add( element ); + } + + $.each( handlers, function( event, handler ) { + function handlerProxy() { + // allow widgets to customize the disabled handling + // - disabled as an array instead of boolean + // - disabled class as method for disabling individual parts + if ( !suppressDisabledCheck && + ( instance.options.disabled === true || + $( this ).hasClass( "ui-state-disabled" ) ) ) { + return; + } + return ( typeof handler === "string" ? instance[ handler ] : handler ) + .apply( instance, arguments ); + } + + // copy the guid so direct unbinding works + if ( typeof handler !== "string" ) { + handlerProxy.guid = handler.guid = + handler.guid || handlerProxy.guid || $.guid++; + } + + var match = event.match( /^([\w:-]*)\s*(.*)$/ ), + eventName = match[1] + instance.eventNamespace, + selector = match[2]; + if ( selector ) { + delegateElement.delegate( selector, eventName, handlerProxy ); + } else { + element.bind( eventName, handlerProxy ); + } + }); + }, + + _off: function( element, eventName ) { + eventName = (eventName || "").split( " " ).join( this.eventNamespace + " " ) + + this.eventNamespace; + element.unbind( eventName ).undelegate( eventName ); + + // Clear the stack to avoid memory leaks (#10056) + this.bindings = $( this.bindings.not( element ).get() ); + this.focusable = $( this.focusable.not( element ).get() ); + this.hoverable = $( this.hoverable.not( element ).get() ); + }, + + _delay: function( handler, delay ) { + function handlerProxy() { + return ( typeof handler === "string" ? instance[ handler ] : handler ) + .apply( instance, arguments ); + } + var instance = this; + return setTimeout( handlerProxy, delay || 0 ); + }, + + _hoverable: function( element ) { + this.hoverable = this.hoverable.add( element ); + this._on( element, { + mouseenter: function( event ) { + $( event.currentTarget ).addClass( "ui-state-hover" ); + }, + mouseleave: function( event ) { + $( event.currentTarget ).removeClass( "ui-state-hover" ); + } + }); + }, + + _focusable: function( element ) { + this.focusable = this.focusable.add( element ); + this._on( element, { + focusin: function( event ) { + $( event.currentTarget ).addClass( "ui-state-focus" ); + }, + focusout: function( event ) { + $( event.currentTarget ).removeClass( "ui-state-focus" ); + } + }); + }, + + _trigger: function( type, event, data ) { + var prop, orig, + callback = this.options[ type ]; + + data = data || {}; + event = $.Event( event ); + event.type = ( type === this.widgetEventPrefix ? + type : + this.widgetEventPrefix + type ).toLowerCase(); + // the original event may come from any element + // so we need to reset the target on the new event + event.target = this.element[ 0 ]; + + // copy original event properties over to the new event + orig = event.originalEvent; + if ( orig ) { + for ( prop in orig ) { + if ( !( prop in event ) ) { + event[ prop ] = orig[ prop ]; + } + } + } + + this.element.trigger( event, data ); + return !( $.isFunction( callback ) && + callback.apply( this.element[0], [ event ].concat( data ) ) === false || + event.isDefaultPrevented() ); + } +}; + +$.each( { show: "fadeIn", hide: "fadeOut" }, function( method, defaultEffect ) { + $.Widget.prototype[ "_" + method ] = function( element, options, callback ) { + if ( typeof options === "string" ) { + options = { effect: options }; + } + var hasOptions, + effectName = !options ? + method : + options === true || typeof options === "number" ? + defaultEffect : + options.effect || defaultEffect; + options = options || {}; + if ( typeof options === "number" ) { + options = { duration: options }; + } + hasOptions = !$.isEmptyObject( options ); + options.complete = callback; + if ( options.delay ) { + element.delay( options.delay ); + } + if ( hasOptions && $.effects && $.effects.effect[ effectName ] ) { + element[ method ]( options ); + } else if ( effectName !== method && element[ effectName ] ) { + element[ effectName ]( options.duration, options.easing, callback ); + } else { + element.queue(function( next ) { + $( this )[ method ](); + if ( callback ) { + callback.call( element[ 0 ] ); + } + next(); + }); + } + }; +}); + +var widget = $.widget; + + + +})); diff --git a/node_modules/image_picker/AngularMediaModal/assets/fileupload/js/vendor/load-image.all.min.js b/node_modules/image_picker/AngularMediaModal/assets/fileupload/js/vendor/load-image.all.min.js new file mode 100644 index 0000000..2ec3313 --- /dev/null +++ b/node_modules/image_picker/AngularMediaModal/assets/fileupload/js/vendor/load-image.all.min.js @@ -0,0 +1,2 @@ +!function(e){"use strict";var t=function(e,i,a){var o,r,n=document.createElement("img");if(n.onerror=i,n.onload=function(){!r||a&&a.noRevoke||t.revokeObjectURL(r),i&&i(t.scale(n,a))},t.isInstanceOf("Blob",e)||t.isInstanceOf("File",e))o=r=t.createObjectURL(e),n._type=e.type;else{if("string"!=typeof e)return!1;o=e,a&&a.crossOrigin&&(n.crossOrigin=a.crossOrigin)}return o?(n.src=o,n):t.readFile(e,function(e){var t=e.target;t&&t.result?n.src=t.result:i&&i(e)})},i=window.createObjectURL&&window||window.URL&&URL.revokeObjectURL&&URL||window.webkitURL&&webkitURL;t.isInstanceOf=function(e,t){return Object.prototype.toString.call(t)==="[object "+e+"]"},t.transformCoordinates=function(){},t.getTransformedOptions=function(e,t){var i,a,o,r,n=t.aspectRatio;if(!n)return t;i={};for(a in t)t.hasOwnProperty(a)&&(i[a]=t[a]);return i.crop=!0,o=e.naturalWidth||e.width,r=e.naturalHeight||e.height,o/r>n?(i.maxWidth=r*n,i.maxHeight=r):(i.maxWidth=o,i.maxHeight=o/n),i},t.renderImageToCanvas=function(e,t,i,a,o,r,n,s,l,d){return e.getContext("2d").drawImage(t,i,a,o,r,n,s,l,d),e},t.hasCanvasOption=function(e){return e.canvas||e.crop||!!e.aspectRatio},t.scale=function(e,i){function a(){var e=Math.max((s||y)/y,(l||v)/v);e>1&&(y*=e,v*=e)}function o(){var e=Math.min((r||y)/y,(n||v)/v);1>e&&(y*=e,v*=e)}i=i||{};var r,n,s,l,d,u,c,g,f,h,m,p=document.createElement("canvas"),S=e.getContext||t.hasCanvasOption(i)&&p.getContext,b=e.naturalWidth||e.width,x=e.naturalHeight||e.height,y=b,v=x;if(S&&(i=t.getTransformedOptions(e,i),c=i.left||0,g=i.top||0,i.sourceWidth?(d=i.sourceWidth,void 0!==i.right&&void 0===i.left&&(c=b-d-i.right)):d=b-c-(i.right||0),i.sourceHeight?(u=i.sourceHeight,void 0!==i.bottom&&void 0===i.top&&(g=x-u-i.bottom)):u=x-g-(i.bottom||0),y=d,v=u),r=i.maxWidth,n=i.maxHeight,s=i.minWidth,l=i.minHeight,S&&r&&n&&i.crop?(y=r,v=n,m=d/u-r/n,0>m?(u=n*d/r,void 0===i.top&&void 0===i.bottom&&(g=(x-u)/2)):m>0&&(d=r*u/n,void 0===i.left&&void 0===i.right&&(c=(b-d)/2))):((i.contain||i.cover)&&(s=r=r||s,l=n=n||l),i.cover?(o(),a()):(a(),o())),S){if(f=i.pixelRatio,f>1&&(p.style.width=y+"px",p.style.height=v+"px",y*=f,v*=f,p.getContext("2d").scale(f,f)),h=i.downsamplingRatio,h>0&&1>h&&d>y&&u>v)for(;d*h>y;)p.width=d*h,p.height=u*h,t.renderImageToCanvas(p,e,c,g,d,u,0,0,p.width,p.height),d=p.width,u=p.height,e=document.createElement("canvas"),e.width=d,e.height=u,t.renderImageToCanvas(e,p,0,0,d,u,0,0,d,u);return p.width=y,p.height=v,t.transformCoordinates(p,i),t.renderImageToCanvas(p,e,c,g,d,u,0,0,y,v)}return e.width=y,e.height=v,e},t.createObjectURL=function(e){return i?i.createObjectURL(e):!1},t.revokeObjectURL=function(e){return i?i.revokeObjectURL(e):!1},t.readFile=function(e,t,i){if(window.FileReader){var a=new FileReader;if(a.onload=a.onerror=t,i=i||"readAsDataURL",a[i])return a[i](e),a}return!1},"function"==typeof define&&define.amd?define(function(){return t}):"object"==typeof module&&module.exports?module.exports=t:e.loadImage=t}(window),function(e){"use strict";"function"==typeof define&&define.amd?define(["./load-image"],e):e("object"==typeof module&&module.exports?require("./load-image"):window.loadImage)}(function(e){"use strict";var t=e.hasCanvasOption,i=e.transformCoordinates,a=e.getTransformedOptions;e.hasCanvasOption=function(i){return!!i.orientation||t.call(e,i)},e.transformCoordinates=function(t,a){i.call(e,t,a);var o=t.getContext("2d"),r=t.width,n=t.height,s=t.style.width,l=t.style.height,d=a.orientation;if(d&&!(d>8))switch(d>4&&(t.width=n,t.height=r,t.style.width=l,t.style.height=s),d){case 2:o.translate(r,0),o.scale(-1,1);break;case 3:o.translate(r,n),o.rotate(Math.PI);break;case 4:o.translate(0,n),o.scale(1,-1);break;case 5:o.rotate(.5*Math.PI),o.scale(1,-1);break;case 6:o.rotate(.5*Math.PI),o.translate(0,-n);break;case 7:o.rotate(.5*Math.PI),o.translate(r,-n),o.scale(-1,1);break;case 8:o.rotate(-.5*Math.PI),o.translate(-r,0)}},e.getTransformedOptions=function(t,i){var o,r,n=a.call(e,t,i),s=n.orientation;if(!s||s>8||1===s)return n;o={};for(r in n)n.hasOwnProperty(r)&&(o[r]=n[r]);switch(n.orientation){case 2:o.left=n.right,o.right=n.left;break;case 3:o.left=n.right,o.top=n.bottom,o.right=n.left,o.bottom=n.top;break;case 4:o.top=n.bottom,o.bottom=n.top;break;case 5:o.left=n.top,o.top=n.left,o.right=n.bottom,o.bottom=n.right;break;case 6:o.left=n.top,o.top=n.right,o.right=n.bottom,o.bottom=n.left;break;case 7:o.left=n.bottom,o.top=n.right,o.right=n.top,o.bottom=n.left;break;case 8:o.left=n.bottom,o.top=n.left,o.right=n.top,o.bottom=n.right}return n.orientation>4&&(o.maxWidth=n.maxHeight,o.maxHeight=n.maxWidth,o.minWidth=n.minHeight,o.minHeight=n.minWidth,o.sourceWidth=n.sourceHeight,o.sourceHeight=n.sourceWidth),o}}),function(e){"use strict";"function"==typeof define&&define.amd?define(["./load-image"],e):e("object"==typeof module&&module.exports?require("./load-image"):window.loadImage)}(function(e){"use strict";var t=window.Blob&&(Blob.prototype.slice||Blob.prototype.webkitSlice||Blob.prototype.mozSlice);e.blobSlice=t&&function(){var e=this.slice||this.webkitSlice||this.mozSlice;return e.apply(this,arguments)},e.metaDataParsers={jpeg:{65505:[]}},e.parseMetaData=function(t,i,a){a=a||{};var o=this,r=a.maxMetaDataSize||262144,n={},s=!(window.DataView&&t&&t.size>=12&&"image/jpeg"===t.type&&e.blobSlice);(s||!e.readFile(e.blobSlice.call(t,0,r),function(t){if(t.target.error)return console.log(t.target.error),void i(n);var r,s,l,d,u=t.target.result,c=new DataView(u),g=2,f=c.byteLength-4,h=g;if(65496===c.getUint16(0)){for(;f>g&&(r=c.getUint16(g),r>=65504&&65519>=r||65534===r);){if(s=c.getUint16(g+2)+2,g+s>c.byteLength){console.log("Invalid meta data: Invalid segment size.");break}if(l=e.metaDataParsers.jpeg[r])for(d=0;d6&&(u.slice?n.imageHead=u.slice(0,h):n.imageHead=new Uint8Array(u).subarray(0,h))}else console.log("Invalid JPEG file: Missing JPEG marker.");i(n)},"readAsArrayBuffer"))&&i(n)}}),function(e){"use strict";"function"==typeof define&&define.amd?define(["./load-image","./load-image-meta"],e):"object"==typeof module&&module.exports?e(require("./load-image"),require("./load-image-meta")):e(window.loadImage)}(function(e){"use strict";e.ExifMap=function(){return this},e.ExifMap.prototype.map={Orientation:274},e.ExifMap.prototype.get=function(e){return this[e]||this[this.map[e]]},e.getExifThumbnail=function(e,t,i){var a,o,r;if(!i||t+i>e.byteLength)return void console.log("Invalid Exif data: Invalid thumbnail data.");for(a=[],o=0;i>o;o+=1)r=e.getUint8(t+o),a.push((16>r?"0":"")+r.toString(16));return"data:image/jpeg,%"+a.join("%")},e.exifTagTypes={1:{getValue:function(e,t){return e.getUint8(t)},size:1},2:{getValue:function(e,t){return String.fromCharCode(e.getUint8(t))},size:1,ascii:!0},3:{getValue:function(e,t,i){return e.getUint16(t,i)},size:2},4:{getValue:function(e,t,i){return e.getUint32(t,i)},size:4},5:{getValue:function(e,t,i){return e.getUint32(t,i)/e.getUint32(t+4,i)},size:8},9:{getValue:function(e,t,i){return e.getInt32(t,i)},size:4},10:{getValue:function(e,t,i){return e.getInt32(t,i)/e.getInt32(t+4,i)},size:8}},e.exifTagTypes[7]=e.exifTagTypes[1],e.getExifValue=function(t,i,a,o,r,n){var s,l,d,u,c,g,f=e.exifTagTypes[o];if(!f)return void console.log("Invalid Exif data: Invalid tag type.");if(s=f.size*r,l=s>4?i+t.getUint32(a+8,n):a+8,l+s>t.byteLength)return void console.log("Invalid Exif data: Invalid data offset.");if(1===r)return f.getValue(t,l,n);for(d=[],u=0;r>u;u+=1)d[u]=f.getValue(t,l+u*f.size,n);if(f.ascii){for(c="",u=0;ue.byteLength)return void console.log("Invalid Exif data: Invalid directory offset.");if(r=e.getUint16(i,a),n=i+2+12*r,n+4>e.byteLength)return void console.log("Invalid Exif data: Invalid directory size.");for(s=0;r>s;s+=1)this.parseExifTag(e,t,i+2+12*s,a,o);return e.getUint32(n,a)},e.parseExifData=function(t,i,a,o,r){if(!r.disableExif){var n,s,l,d=i+10;if(1165519206===t.getUint32(i+4)){if(d+8>t.byteLength)return void console.log("Invalid Exif data: Invalid segment size.");if(0!==t.getUint16(i+8))return void console.log("Invalid Exif data: Missing byte alignment offset.");switch(t.getUint16(d)){case 18761:n=!0;break;case 19789:n=!1;break;default:return void console.log("Invalid Exif data: Invalid byte alignment marker.")}if(42!==t.getUint16(d+2,n))return void console.log("Invalid Exif data: Missing TIFF marker.");s=t.getUint32(d+4,n),o.exif=new e.ExifMap,s=e.parseExifTags(t,d,d+s,n,o),s&&!r.disableExifThumbnail&&(l={exif:{}},s=e.parseExifTags(t,d,d+s,n,l),l.exif[513]&&(o.exif.Thumbnail=e.getExifThumbnail(t,d+l.exif[513],l.exif[514]))),o.exif[34665]&&!r.disableExifSub&&e.parseExifTags(t,d,d+o.exif[34665],n,o),o.exif[34853]&&!r.disableExifGps&&e.parseExifTags(t,d,d+o.exif[34853],n,o)}}},e.metaDataParsers.jpeg[65505].push(e.parseExifData)}),function(e){"use strict";"function"==typeof define&&define.amd?define(["./load-image","./load-image-exif"],e):"object"==typeof module&&module.exports?e(require("./load-image"),require("./load-image-exif")):e(window.loadImage)}(function(e){"use strict";e.ExifMap.prototype.tags={256:"ImageWidth",257:"ImageHeight",34665:"ExifIFDPointer",34853:"GPSInfoIFDPointer",40965:"InteroperabilityIFDPointer",258:"BitsPerSample",259:"Compression",262:"PhotometricInterpretation",274:"Orientation",277:"SamplesPerPixel",284:"PlanarConfiguration",530:"YCbCrSubSampling",531:"YCbCrPositioning",282:"XResolution",283:"YResolution",296:"ResolutionUnit",273:"StripOffsets",278:"RowsPerStrip",279:"StripByteCounts",513:"JPEGInterchangeFormat",514:"JPEGInterchangeFormatLength",301:"TransferFunction",318:"WhitePoint",319:"PrimaryChromaticities",529:"YCbCrCoefficients",532:"ReferenceBlackWhite",306:"DateTime",270:"ImageDescription",271:"Make",272:"Model",305:"Software",315:"Artist",33432:"Copyright",36864:"ExifVersion",40960:"FlashpixVersion",40961:"ColorSpace",40962:"PixelXDimension",40963:"PixelYDimension",42240:"Gamma",37121:"ComponentsConfiguration",37122:"CompressedBitsPerPixel",37500:"MakerNote",37510:"UserComment",40964:"RelatedSoundFile",36867:"DateTimeOriginal",36868:"DateTimeDigitized",37520:"SubSecTime",37521:"SubSecTimeOriginal",37522:"SubSecTimeDigitized",33434:"ExposureTime",33437:"FNumber",34850:"ExposureProgram",34852:"SpectralSensitivity",34855:"PhotographicSensitivity",34856:"OECF",34864:"SensitivityType",34865:"StandardOutputSensitivity",34866:"RecommendedExposureIndex",34867:"ISOSpeed",34868:"ISOSpeedLatitudeyyy",34869:"ISOSpeedLatitudezzz",37377:"ShutterSpeedValue",37378:"ApertureValue",37379:"BrightnessValue",37380:"ExposureBias",37381:"MaxApertureValue",37382:"SubjectDistance",37383:"MeteringMode",37384:"LightSource",37385:"Flash",37396:"SubjectArea",37386:"FocalLength",41483:"FlashEnergy",41484:"SpatialFrequencyResponse",41486:"FocalPlaneXResolution",41487:"FocalPlaneYResolution",41488:"FocalPlaneResolutionUnit",41492:"SubjectLocation",41493:"ExposureIndex",41495:"SensingMethod",41728:"FileSource",41729:"SceneType",41730:"CFAPattern",41985:"CustomRendered",41986:"ExposureMode",41987:"WhiteBalance",41988:"DigitalZoomRatio",41989:"FocalLengthIn35mmFilm",41990:"SceneCaptureType",41991:"GainControl",41992:"Contrast",41993:"Saturation",41994:"Sharpness",41995:"DeviceSettingDescription",41996:"SubjectDistanceRange",42016:"ImageUniqueID",42032:"CameraOwnerName",42033:"BodySerialNumber",42034:"LensSpecification",42035:"LensMake",42036:"LensModel",42037:"LensSerialNumber",0:"GPSVersionID",1:"GPSLatitudeRef",2:"GPSLatitude",3:"GPSLongitudeRef",4:"GPSLongitude",5:"GPSAltitudeRef",6:"GPSAltitude",7:"GPSTimeStamp",8:"GPSSatellites",9:"GPSStatus",10:"GPSMeasureMode",11:"GPSDOP",12:"GPSSpeedRef",13:"GPSSpeed",14:"GPSTrackRef",15:"GPSTrack",16:"GPSImgDirectionRef",17:"GPSImgDirection",18:"GPSMapDatum",19:"GPSDestLatitudeRef",20:"GPSDestLatitude",21:"GPSDestLongitudeRef",22:"GPSDestLongitude",23:"GPSDestBearingRef",24:"GPSDestBearing",25:"GPSDestDistanceRef",26:"GPSDestDistance",27:"GPSProcessingMethod",28:"GPSAreaInformation",29:"GPSDateStamp",30:"GPSDifferential",31:"GPSHPositioningError"},e.ExifMap.prototype.stringValues={ExposureProgram:{0:"Undefined",1:"Manual",2:"Normal program",3:"Aperture priority",4:"Shutter priority",5:"Creative program",6:"Action program",7:"Portrait mode",8:"Landscape mode"},MeteringMode:{0:"Unknown",1:"Average",2:"CenterWeightedAverage",3:"Spot",4:"MultiSpot",5:"Pattern",6:"Partial",255:"Other"},LightSource:{0:"Unknown",1:"Daylight",2:"Fluorescent",3:"Tungsten (incandescent light)",4:"Flash",9:"Fine weather",10:"Cloudy weather",11:"Shade",12:"Daylight fluorescent (D 5700 - 7100K)",13:"Day white fluorescent (N 4600 - 5400K)",14:"Cool white fluorescent (W 3900 - 4500K)",15:"White fluorescent (WW 3200 - 3700K)",17:"Standard light A",18:"Standard light B",19:"Standard light C",20:"D55",21:"D65",22:"D75",23:"D50",24:"ISO studio tungsten",255:"Other"},Flash:{0:"Flash did not fire",1:"Flash fired",5:"Strobe return light not detected",7:"Strobe return light detected",9:"Flash fired, compulsory flash mode",13:"Flash fired, compulsory flash mode, return light not detected",15:"Flash fired, compulsory flash mode, return light detected",16:"Flash did not fire, compulsory flash mode",24:"Flash did not fire, auto mode",25:"Flash fired, auto mode",29:"Flash fired, auto mode, return light not detected",31:"Flash fired, auto mode, return light detected",32:"No flash function",65:"Flash fired, red-eye reduction mode",69:"Flash fired, red-eye reduction mode, return light not detected",71:"Flash fired, red-eye reduction mode, return light detected",73:"Flash fired, compulsory flash mode, red-eye reduction mode",77:"Flash fired, compulsory flash mode, red-eye reduction mode, return light not detected",79:"Flash fired, compulsory flash mode, red-eye reduction mode, return light detected",89:"Flash fired, auto mode, red-eye reduction mode",93:"Flash fired, auto mode, return light not detected, red-eye reduction mode",95:"Flash fired, auto mode, return light detected, red-eye reduction mode"},SensingMethod:{1:"Undefined",2:"One-chip color area sensor",3:"Two-chip color area sensor",4:"Three-chip color area sensor",5:"Color sequential area sensor",7:"Trilinear sensor",8:"Color sequential linear sensor"},SceneCaptureType:{0:"Standard",1:"Landscape",2:"Portrait",3:"Night scene"},SceneType:{1:"Directly photographed"},CustomRendered:{0:"Normal process",1:"Custom process"},WhiteBalance:{0:"Auto white balance",1:"Manual white balance"},GainControl:{0:"None",1:"Low gain up",2:"High gain up",3:"Low gain down",4:"High gain down"},Contrast:{0:"Normal",1:"Soft",2:"Hard"},Saturation:{0:"Normal",1:"Low saturation",2:"High saturation"},Sharpness:{0:"Normal",1:"Soft",2:"Hard"},SubjectDistanceRange:{0:"Unknown",1:"Macro",2:"Close view",3:"Distant view"},FileSource:{3:"DSC"},ComponentsConfiguration:{0:"",1:"Y",2:"Cb",3:"Cr",4:"R",5:"G",6:"B"},Orientation:{1:"top-left",2:"top-right",3:"bottom-right",4:"bottom-left",5:"left-top",6:"right-top",7:"right-bottom",8:"left-bottom"}},e.ExifMap.prototype.getText=function(e){var t=this.get(e);switch(e){case"LightSource":case"Flash":case"MeteringMode":case"ExposureProgram":case"SensingMethod":case"SceneCaptureType":case"SceneType":case"CustomRendered":case"WhiteBalance":case"GainControl":case"Contrast":case"Saturation":case"Sharpness":case"SubjectDistanceRange":case"FileSource":case"Orientation":return this.stringValues[e][t];case"ExifVersion":case"FlashpixVersion":return String.fromCharCode(t[0],t[1],t[2],t[3]);case"ComponentsConfiguration":return this.stringValues[e][t[0]]+this.stringValues[e][t[1]]+this.stringValues[e][t[2]]+this.stringValues[e][t[3]];case"GPSVersionID":return t[0]+"."+t[1]+"."+t[2]+"."+t[3]}return String(t)},function(e){var t,i=e.tags,a=e.map;for(t in i)i.hasOwnProperty(t)&&(a[i[t]]=t)}(e.ExifMap.prototype),e.ExifMap.prototype.getAll=function(){var e,t,i={};for(e in this)this.hasOwnProperty(e)&&(t=this.tags[e],t&&(i[t]=this.getText(t)));return i}}); +//# sourceMappingURL=load-image.all.min.js.map \ No newline at end of file diff --git a/node_modules/image_picker/AngularMediaModal/cors/postmessage.html b/node_modules/image_picker/AngularMediaModal/cors/postmessage.html new file mode 100644 index 0000000..fab9d60 --- /dev/null +++ b/node_modules/image_picker/AngularMediaModal/cors/postmessage.html @@ -0,0 +1,75 @@ + + + + + +jQuery File Upload Plugin postMessage API + + + + + + diff --git a/node_modules/image_picker/AngularMediaModal/cors/result.html b/node_modules/image_picker/AngularMediaModal/cors/result.html new file mode 100644 index 0000000..c5f30f9 --- /dev/null +++ b/node_modules/image_picker/AngularMediaModal/cors/result.html @@ -0,0 +1,24 @@ + + + + + +jQuery Iframe Transport Plugin Redirect Page + + + + + diff --git a/node_modules/image_picker/AngularMediaModal/index.html b/node_modules/image_picker/AngularMediaModal/index.html new file mode 100644 index 0000000..8c10214 --- /dev/null +++ b/node_modules/image_picker/AngularMediaModal/index.html @@ -0,0 +1,57 @@ + + + + + + + File Picker + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + \ No newline at end of file diff --git a/node_modules/image_picker/AngularMediaModal/testController.js b/node_modules/image_picker/AngularMediaModal/testController.js new file mode 100644 index 0000000..f8d48b7 --- /dev/null +++ b/node_modules/image_picker/AngularMediaModal/testController.js @@ -0,0 +1,14 @@ +angular.module("testModule", ["mediaModal"]) +.controller("testController", function($scope, gallery){ + $scope.openGallery = function(){ + gallery.open(true) + .then(function(images){ + $scope.images = images; + }, + function(){ + + } + ) + } + } +); \ No newline at end of file diff --git a/node_modules/image_picker/Gruntfile.js b/node_modules/image_picker/Gruntfile.js new file mode 100644 index 0000000..0bc57d5 --- /dev/null +++ b/node_modules/image_picker/Gruntfile.js @@ -0,0 +1,83 @@ +module.exports = function(grunt) { + + grunt.initConfig({ + pkg: grunt.file.readJSON('package.json'), + + uglify: { + options: { + banner: '/*! <%= pkg.name %> <%= grunt.template.today("yyyy-mm-dd") %> */\n' + }, + app: { + src: ["build/image-picker.js"], + dest: "build/image-picker.min.js" + } + }, + ngAnnotate: { + app: { + src: ["AngularMediaModal/app/app.js", "AngularMediaModal/app/**/*.js"], + dest: "build/image-picker.js" + } + }, + ngtemplates: { + app: { + options: { + module: "mediaModalTemplates", + standalone: true + }, + cwd: "AngularMediaModal/app/views/", + src: ["**/*.html"], + dest: "build/templates.js" + } + }, + concat: { + app: { + src: ["build/templates.js", "build/image-picker.js"], + dest: "build/image-picker.js" + }, + dist: { + src: [ + 'AngularMediaModal/assets/fileupload/js/s/vendor/jquery.ui.widget.js', + 'AngularMediaModal/assets/fileupload/js/s/vendor/canvas-to-blob.min.js', + 'AngularMediaModal/assets/fileupload/js/s/vendor/load-image.all.min.js', + 'AngularMediaModal/assets/fileupload/js/jquery.iframe-transport.js', + 'AngularMediaModal/assets/fileupload/js/jquery.fileupload.js', + 'AngularMediaModal/assets/fileupload/js/jquery.fileupload-process.js', + 'AngularMediaModal/assets/fileupload/js/jquery.fileupload-image.js', + 'AngularMediaModal/assets/fileupload/js/jquery.fileupload-audio.js', + 'AngularMediaModal/assets/fileupload/js/jquery.fileupload-video.js', + 'AngularMediaModal/assets/fileupload/js/jquery.fileupload-validate.js', + 'AngularMediaModal/assets/fileupload/js/jquery.fileupload-angular.js', + 'build/image-picker.min.js', + + ], + dest: 'image-picker.min.js' + } + }, + copy: { + css: { + src: ["AngularMediaModal/app/styles/media-modal.css"], + dest: "build/media-modal.css" + } + }, + watch: { + js: { + files: ["AngularMediaModal/app/**/*.js"], + tasks: ["ngAnnotate", "concat"] + }, + templates: { + files: ["AngularMediaModal/app/views/**/*.html"], + tasks: ["ngtemplates", "ngAnnotate", "concat"] + }, + css: { + files: ["AngularMediaModal/app/styles/**/*.css"], + tasks: ["copy:css"] + } + } + }); + + require('load-grunt-tasks')(grunt); + + grunt.registerTask('default', ["ngtemplates", "ngAnnotate", "concat", "copy", "uglify"]); + grunt.registerTask('dev', ["ngtemplates", "ngAnnotate", "concat", "copy", "watch"]); + +}; \ No newline at end of file diff --git a/node_modules/image_picker/bower.json b/node_modules/image_picker/bower.json new file mode 100644 index 0000000..97c9ec4 --- /dev/null +++ b/node_modules/image_picker/bower.json @@ -0,0 +1,16 @@ +{ + "name": "image_picker", + "version": "0.0.0", + "authors": [ + "l-o-b " + ], + "license": "MIT", + "dependencies": { + "ng-flow": "~2", + "angular-bootstrap": "~1.2.5", + "angular-spinner": "~0.8.1", + "bootstrap": "~3.3.6", + "jquery": "~2.2.2", + "fontawesome": "~4.5.0" + } +} diff --git a/node_modules/image_picker/build/image-picker.js b/node_modules/image_picker/build/image-picker.js new file mode 100644 index 0000000..a085c36 --- /dev/null +++ b/node_modules/image_picker/build/image-picker.js @@ -0,0 +1,886 @@ +angular.module('mediaModalTemplates', []).run(['$templateCache', function($templateCache) { + 'use strict'; + + $templateCache.put('camera.html', + "
\n" + + "

Upload Media

\n" + + "
\n" + + "
\n" + + " \n" + + "
\n" + + "
\n" + + " \n" + + " \n" + + " \n" + + " Add files...\n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + "
\n" + + " \n" + + "
\n" + + " \n" + + "
\n" + + " \n" + + "
 
\n" + + "
\n" + + "
\n" + + "
\n" + + "
\n" + + "
\n" + + "
\n" + + "
\n" + + "
\n" + + "
\n" + + " \n" + + "
\n" + + "
\n" + + "
{{file.name}}
\n" + + "
\n" + + "
\n" + + "
\n" + + "
\n" + + "
\n" + + " \n" + + "
\n" + + "
\n" + + "\n" + + "
\n" + + "
\n" + + "
\n" + + "
\n" + + "
\n" + + "
\n" + + " \n" + + "
\n" + + "
\n" + + "
{{file.name}}
\n" + + "
\n" + + "
\n" + + "
\n" + + "
\n" + + "
\n" + + " \n" + + "
\n" + + "
\n" + + "\n" + + "
\n" + + "\n" + + "
\n" + + "
\n" + + "
\n" + + "
\n" + + "
" + ); + + + $templateCache.put('gallery.html', + "
\n" + + " \n" + + " \n" + + " \n" + + " \n" + + " {{resource.title}}\n" + + " \n" + + "
\n" + + "
\n" + + "
\n" + + "
\n" + + "" + ); + + + $templateCache.put('galleryWindow.html', + "
\n" + + "

Add Image

\n" + + "
\n" + + "
\n" + + "
\n" + + "
\n" + + "
\n" + + " \n" + + " \n" + + "
" + ); + + + $templateCache.put('upload.html', + "
\n" + + "

Upload Media

\n" + + "
\n" + + "
\n" + + " \n" + + "
\n" + + "
\n" + + " \n" + + " \n" + + " \n" + + " Add files...\n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + "
\n" + + " \n" + + "
\n" + + " \n" + + "
\n" + + " \n" + + "
 
\n" + + "
\n" + + "
\n" + + "
\n" + + "
\n" + + "
\n" + + "
\n" + + "
\n" + + "
\n" + + "
\n" + + " \n" + + "
\n" + + "
\n" + + "
{{file.name}}
\n" + + "
\n" + + "
\n" + + "
\n" + + "
\n" + + "
\n" + + " \n" + + "
\n" + + "
\n" + + "\n" + + "
\n" + + "
\n" + + "
\n" + + "
\n" + + "
\n" + + "
\n" + + " \n" + + "
\n" + + "
\n" + + "
{{file.name}}
\n" + + "
\n" + + "
\n" + + "
\n" + + "
\n" + + "
\n" + + " \n" + + "
\n" + + "
\n" + + "\n" + + "
\n" + + "\n" + + "
\n" + + "
\n" + + "
\n" + + "
\n" + + "
" + ); + + + $templateCache.put('webService.html', + "
\n" + + "

{{resource.title}}

\n" + + "
\n" + + "
\n" + + " \n" + + " \n" + + " \n" + + " \n" + + "
\n" + + "
\n" + + "\n" + + "
\n" + + "
\n" + + "
\n" + + "
\n" + + "
\n" + + "
\n" + + " \n" + + "
\n" + + "
\n" + + "
{{image.title}}
\n" + + "
\n" + + "
\n" + + "
\n" + + "
\n" + + "\n" + + "
\n" + + " \n" + + "
\n" + + "\n" + + "
" + ); + +}]); + +angular.module("mediaModal", ["ui.bootstrap", "angularSpinner", "flow", "images-resizer", "mediaModalTemplates", "blueimp.fileupload"]); +angular.module("mediaModal") + .config([ + '$httpProvider', 'fileUploadProvider', '$sceDelegateProvider', + function ($httpProvider, fileUploadProvider, $sceDelegateProvider) { + + $sceDelegateProvider.resourceUrlWhitelist([ + 'self', + 'http://business.otonomic.com/**' + ]); + + delete $httpProvider.defaults.headers.common['X-Requested-With']; + fileUploadProvider.defaults.redirect = window.location.href.replace( + /\/[^\/]*$/, + '/cors/result.html?%s' + ); + // Uploader settings: + angular.extend(fileUploadProvider.defaults, { + autoUpload: true, + forceIframeTransport: true, + // Enable image resizing, egrunt devxcept for Android and Opera, + // which actually support image resizing, but fail to + // send Blob objects via XHR requests: + disableImageResize: /Android(?!.*Chrome)|Opera/ + .test(window.navigator.userAgent), + /*maxFileSize: 9990000,*/ + acceptFileTypes: /(\.|\/)(gif|jpe?g|png)$/i + }); + } + ]) + .constant("flickrApiKey", "b5245e9b9cbaeecee26ea278bfa20253") + .constant("upload_url", "http://business.otonomic.com/api/v1/?cmd=upload") + .constant("instagramApiKey", "44171713.10d405b.25e232f920f94ac9907d8c0ea34ce1de") + //.constant("facebookApiKey", "CAACEdEose0cBAJuIrBZCXQZBpMRGixqcQun3gJAwUL2cfGZB0iuvLu5lpDJy3txqnhAbdbFneQHpZACHiWQsymYxZCtFbH3GuB87NpQdZB8DvtUGIpmZCF3yip71ZBqqajhgw8uPcuaerccTBYZC1gXtGZAV11gvcrYOodJNnT87aJQ1ZBR9T4AX7NHJZCQfmJIIOd6k6oR55ubMZBy7R6ZBAYylzC") + .constant("facebookApiKey", "CAANkofzWtSoBAG0FcIPXZAFFUJjZBdZAiejDxnPXXapvIP1UZA9ZBKiryHlB3GZCjrY1P3rgnKO70AWYQ1bLFp03MxZAuYTaimqsETawQxE9PRhCuLKHZCxDd9wrzJQrqXMsscI80UzPapvPoQKa9WeE7NHIN8H30lb48AhXqZAbVbX7w33nuEhI8ZCPc56VnTS2IZD") + .constant("pexelsApiKey", "563492ad6f9170000100000183fb18af4b5f4b5c6a67f7e6433578ba") + + .factory("availableResources", ["enums", function(enums){ + return [{ + title: "Camera", + icon: "fa fa-camera", + viewPath: "camera.html" + },{ + title: "Upload", + icon: "fa fa-cloud-upload", + viewPath: "upload.html" + },{ + title: "Flickr", + icon: "fa fa-flickr", + viewPath: "webService.html", + webServiceType: enums.webServiceTypes.flickr + },{ + title: "Instagram", + icon: "fa fa-instagram", + viewPath: "webService.html", + webServiceType: enums.webServiceTypes.instagram + },{ + title: "Background Patterns", + icon: "fa fa-star", + viewPath: "webService.html", + webServiceType: enums.webServiceTypes.backgroundPatterns + },{ + title: "Facebook Photos", + icon: "fa fa-facebook", + viewPath: "webService.html", + webServiceType: enums.webServiceTypes.facebookPhotos + },{ + title: "Facebook Users", + icon: "fa fa-facebook-official", + viewPath: "webService.html", + webServiceType: enums.webServiceTypes.facebookUsers + },{ + title: "Pexels", + icon: "fa fa-picture-o", + viewPath: "webService.html", + webServiceType: enums.webServiceTypes.pexels + }]; + }]); +/** + * Created by lobanovana on 23.03.2016. + */ +angular.module("mediaModal") +.controller("cameraController", ['$scope', '$http', 'upload_url', function ($scope, $http, upload_url) { + $scope.upload_url = upload_url; + $scope.options = { + url: upload_url + }; + $scope.loadingFiles = true; + $http.get(upload_url) + .then( + function (response) { + $scope.loadingFiles = false; + $scope.filequeue = response.data.files || []; + //console.log($scope.queue); + }, + function () { + $scope.loadingFiles = false; + } + ); + + $scope.selectImage = function(image){ + + //console.log(image); + + image.selected = !image.selected; + image.thumbnail = image.url; + if ($scope.selectedImages){ + if (image.selected){ + $scope.selectedImages.push(image); + return; + } + var imageIndex = $scope.selectedImages.indexOf(image); + if (imageIndex != -1){ + $scope.selectedImages.splice(imageIndex, 1); + } + } + }; + + $scope.$on('fileuploaddone', function(e, data){ + // Your code here + $.each(data.result.files, function (index, file) { + $scope.selectImage(file); + }); + }); + + } +]); +angular.module("mediaModal") + .controller('FileDestroyController', [ + '$scope', '$http', 'upload_url', + function ($scope, $http, upload_url) { + var file = $scope.file, + state; + if (file.url) { + file.$state = function () { + return state; + }; + file.$destroy = function () { + state = 'pending'; + return $http({ + url: file.deleteUrl, + method: file.deleteType + }).then( + function () { + state = 'resolved'; + $scope.clear(file); + }, + function () { + state = 'rejected'; + } + ); + }; + } else if (!file.$cancel && !file._index) { + file.$cancel = function () { + $scope.clear(file); + }; + } + } +]); +(function(){ + angular.module("mediaModal").controller("GalleryController", ["$scope", "availableResources", "$uibModalInstance", function($scope, availableResources, $uibModalInstance) { + + $scope.availableResources=availableResources; + $scope.appState = {}; + $scope.selectedImages = []; + + $scope.tabSelected = function(resource){ + $scope.$broadcast("currentResourceChanged", resource.webServiceType) + }; + + $scope.cancel = function(){ + $uibModalInstance.dismiss("cancel"); + }; + + $scope.selectImages = function(){ + $uibModalInstance.close($scope.selectedImages); + } + }]); +}()); +angular.module("mediaModal") +.controller("uploadController", ['$scope', '$http', 'upload_url', function ($scope, $http, upload_url) { + $scope.upload_url = upload_url; + $scope.options = { + url: upload_url + }; + $scope.loadingFiles = true; + $http.get(upload_url) + .then( + function (response) { + $scope.loadingFiles = false; + $scope.filequeue = response.data.files || []; + }, + function () { + $scope.loadingFiles = false; + } + ); + + $scope.selectImage = function(image){ + + //console.log(image); + + image.selected = !image.selected; + image.thumbnail = image.url; + if ($scope.selectedImages){ + if (image.selected){ + $scope.selectedImages.push(image); + return; + } + var imageIndex = $scope.selectedImages.indexOf(image); + if (imageIndex != -1){ + $scope.selectedImages.splice(imageIndex, 1); + } + } + }; + $scope.$on('fileuploaddone', function(e, data){ + // Your code here + $.each(data.result.files, function (index, file) { + $scope.selectImage(file); + }); + }); +} +]); +angular.module("mediaModal") +.controller("webServicesController", ["$scope", "webStorage", "enums", function($scope, webStorage, enums){ + $scope.currentPage = 0; + $scope.itemsOnPage = 25; + $scope.search = function(){ + $scope.appState.loading = true; + webStorage.search($scope.webServiceType, $scope.imageFilter, $scope.currentPage, $scope.itemsOnPage) + .then( + function(result){ + $scope.fullImagesCount = result.count; + $scope.images = result.images; + } + ) + .finally( + function(){ + $scope.appState.loading = false; + } + ); + }; + + $scope.loadMore = function(){ + $scope.appState.loading = true; + webStorage.loadMore($scope.webServiceType) + .then(function(result){ + if (!$scope.images){ + $scope.images = []; + } + $scope.images = $scope.images.concat(result.images); + }) + .finally(function(){ + $scope.appState.loading = false; + }); + }; + + $scope.setWebServiceType = function(webServiceType){ + $scope.webServiceType = webServiceType; + }; + + $scope.selectImage = function(image){ + image.selected = !image.selected; + if ($scope.selectedImages){ + if (image.selected){ + $scope.selectedImages.push(image); + return; + } + var imageIndex = $scope.selectedImages.indexOf(image); + if (imageIndex != -1){ + $scope.selectedImages.splice(imageIndex, 1); + } + } + }; + + $scope.showSearcher = function(){ + return $scope.webServiceType !== enums.webServiceTypes.backgroundPatterns; + }; + + $scope.$on("currentResourceChanged", function(event, resourceType){ + if ($scope.webServiceType === enums.webServiceTypes.backgroundPatterns && resourceType === enums.webServiceTypes.backgroundPatterns){ + $scope.search(); + } + }) + }] +); +angular.module("mediaModal") + .factory("enums", function(){ + return { + webServiceTypes: { + flickr: 1, + instagram: 2, + backgroundPatterns: 3, + facebookUsers: 4, + pexels: 5 + } + } + } +); +angular.module("mediaModal") + .factory("backgroundPatterns", ["$http", "$q", function($http, $q){ + var _currentPage = 0; + var _count = 20; + + function search(searchString, page, count){ + return $http.jsonp("http://www.colourlovers.com/api/patterns/top?format=json&jsonCallback=JSON_CALLBACK&numResults=" + _count + "&resultOffset=" + _currentPage) + .then(function(response){ + _currentPage++; + return { + count: 0, + images: response.data.map(function(image){ + return { + thumbnail: image.imageUrl, + full: image.imageUrl, + title: image.title + }; + }) + }; + }, + function(response){ + return $q.reject(response.data); + } + ); + } + function loadMore(){ + return $http.jsonp("http://www.colourlovers.com/api/patterns/top?format=json&jsonCallback=JSON_CALLBACK&numResults=" + _count + "&resultOffset=" + _currentPage) + .then(function(response){ + _currentPage++; + return { + count: 0, + images: response.data.map(function(image){ + return { + thumbnail: image.imageUrl, + full: image.imageUrl, + title: image.title + }; + }) + }; + }, + function(response){ + return $q.reject(response.data); + } + ); + } + return { + search: search, + loadMore: loadMore + } + }] +); +angular.module("mediaModal") + .factory("facebookPhotos", ["$http", "facebookApiKey", "$q", function($http, facebookApiKey, $q){ + var _nextUrl; + function search(searchString, page, count){ + return $http.get("https://graph.facebook.com/v2.5/" + searchString + "/photos?access_token=" + facebookApiKey) + .then(function(response){ + _nextUrl = response.data.paging.next; + return { + count: 0, + images: response.data.data.map(function(user){ + return { + thumbnail: "http://graph.facebook.com/" + user.id + "/picture?type=normal", + full: "http://graph.facebook.com/" + user.id + "/picture?type=large", + title: user.name + }; + }) + }; + }, + function(response){ + return $q.reject(response.data); + } + ); + } + + function loadMore(){ + + if (!_nextUrl){ + return $q.reject("no more images"); + } + + return $http.get(_nextUrl) + .then(function(response){ + _nextUrl = response.data.paging.next; + return { + count: 0, + images: response.data.data.map(function(user){ + return { + thumbnail: "http://graph.facebook.com/" + user.id + "/picture?type=normal", + full: "http://graph.facebook.com/" + user.id + "/picture?type=large", + title: user.name + }; + }) + }; + }, + function(response){ + return $q.reject(response.data); + } + ); + } + return { + search: search, + loadMore: loadMore + } + }] +); +angular.module("mediaModal") + .factory("facebookUsers", ["$http", "facebookApiKey", "$q", function($http, facebookApiKey, $q){ + var _nextUrl; + function search(searchString, page, count){ + return $http.get("https://graph.facebook.com/v2.2/search?q=" + searchString + "&type=user&access_token=" + facebookApiKey) + .then(function(response){ + _nextUrl = response.data.paging.next; + return { + count: 0, + images: response.data.data.map(function(user){ + return { + thumbnail: "http://graph.facebook.com/" + user.id + "/picture?type=normal", + full: "http://graph.facebook.com/" + user.id + "/picture?type=large", + title: user.name + }; + }) + }; + }, + function(response){ + return $q.reject(response.data); + } + ); + } + + function loadMore(){ + if (!_nextUrl){ + return $q.reject("no more images"); + } + return $http.get(_nextUrl) + .then(function(response){ + _nextUrl = response.data.paging.next; + return { + count: 0, + images: response.data.data.map(function(user){ + return { + thumbnail: "http://graph.facebook.com/" + user.id + "/picture?type=normal", + full: "http://graph.facebook.com/" + user.id + "/picture?type=large", + title: user.name + }; + }) + }; + }, + function(response){ + return $q.reject(response.data); + } + ); + } + return { + search: search, + loadMore: loadMore + } + }] +); +angular.module("mediaModal") +.factory("flickr", ["$http", "flickrApiKey", function($http, flickrApiKey){ + var _lastPage = 1; + var _count = 50; + var _searchString = ""; + function search(searchString, page, count){ + _lastPage = 1; + _count = count; + _searchString = searchString; + return $http.get("https://api.flickr.com/services/rest/?api_key=" + flickrApiKey + + "&format=json&nojsoncallback=1&method=flickr.photos.search&format=json&page=" + page + "&per_page=" + + count +"&text=" + searchString) + .then(function(response){ + + return { + count: +response.data.photos.total, + images: response.data.photos.photo.map(function(photo){ + return { + thumbnail: "https://farm" + photo.farm + ".staticflickr.com/" + photo.server + "/" + photo.id + "_" + photo.secret + "_t.jpg", + full: "https://farm" + photo.farm + ".staticflickr.com/" + photo.server + "/" + photo.id + "_" + photo.secret + ".jpg" + }; + }) + }; + }, + function(response){ + return $q.reject(response.data); + } + ); + } + function loadMore(){ + _lastPage++; + return $http.get("https://api.flickr.com/services/rest/?api_key=" + flickrApiKey + + "&format=json&nojsoncallback=1&method=flickr.photos.search&format=json&page=" + _lastPage + "&per_page=" + + _count +"&text=" + _searchString) + .then(function(response){ + return { + count: +response.data.photos.total, + images: response.data.photos.photo.map(function(photo){ + return { + thumbnail: "https://farm" + photo.farm + ".staticflickr.com/" + photo.server + "/" + photo.id + "_" + photo.secret + "_t.jpg", + full: "https://farm" + photo.farm + ".staticflickr.com/" + photo.server + "/" + photo.id + "_" + photo.secret + ".jpg" + }; + }) + }; + }, + function(response){ + return $q.reject(response.data); + } + ); + } + return { + search: search, + loadMore: loadMore + } + }] +); +angular.module("mediaModal") + .factory("gallery", ["$q", "$uibModal", function($q, $uibModal){ + function open(animate){ + return $uibModal.open({ + animation: animate, + templateUrl: 'galleryWindow.html', + controller: 'GalleryController', + size: "lg" + }).result; + } + return { + open: open + } + }] +); +angular.module("mediaModal") +.factory("instagram", ["$http", "instagramApiKey", "$q", function($http, instagramApiKey, $q){ + var _nextUrl; + function search(searchString, page, count){ + return $http.jsonp("https://api.instagram.com/v1/tags/" + searchString +"/media/recent?access_token=" + instagramApiKey + "&callback=JSON_CALLBACK&count=20") + .then(function(response){ + _nextUrl = response.data.pagination.next_url; + return { + count: 0, + images: response.data.data.map(function(image){ + return { + thumbnail: image.images.thumbnail.url, + full: image.images.standard_resolution.url, + title: image.caption.text.substr(0, 50) + }; + }) + }; + }, + function(response){ + return $q.reject(response.data); + } + ); + } + + function loadMore(){ + if (!_nextUrl){ + return $q.reject("no more images"); + } + return $http.jsonp(_nextUrl + "&callback=JSON_CALLBACK") + .then(function(response){ + _nextUrl = response.data.pagination.next_url; + return { + count: 0, + images: response.data.data.map(function(image){ + return { + thumbnail: image.images.thumbnail.url, + full: image.images.standard_resolution.url, + title: image.caption.text.substr(0, 50) + }; + }) + }; + }, + function(response){ + return $q.reject(response.data); + } + ); + } + return { + search: search, + loadMore: loadMore + } + }] +); +angular.module("mediaModal") +.factory("pexels", ["$http", "pexelsApiKey", "$q", function($http, pexelsApiKey, $q){ + function search(searchString, page, count){ + return $http.get("http://api.pexels.com/v1/search?query=" + searchString + "&per_page=" + count + "&page=" + (page + 1), { + headers: { + "Authorization": pexelsApiKey + } + }) + .then(function(response){ + return { + count: response.data.total_results, + images: response.data.photos.map(function(photo){ + return { + thumbnail: photo.src.square, + full: photo.src.large, + title: photo.photographer + }; + }) + }; + }, + function(response){ + return $q.reject(response.data); + } + ); + } + return { + search: search + } + }] +); +angular.module("mediaModal") +.factory("webStorage", ["enums", "flickr", "instagram", "backgroundPatterns", "facebookUsers", "pexels", "facebookPhotos", function(enums, flickr, instagram, backgroundPatterns, facebookUsers, pexels, facebookPhotos){ + function getService(serviceType){ + switch (serviceType){ + case enums.webServiceTypes.flickr: + return flickr; + case enums.webServiceTypes.instagram: + return instagram; + case enums.webServiceTypes.backgroundPatterns: + return backgroundPatterns; + case enums.webServiceTypes.facebookUsers: + return facebookUsers; + case enums.webServiceTypes.pexels: + return pexels; + case enums.webServiceTypes.facebookPhotos: + return facebookPhotos; + } + } + function search(storageType, searchString, page, count){ + return getService(storageType).search(searchString, page, count); + } + + function loadMore(storageType){ + return getService(storageType).loadMore(); + } + + return{ + search: search, + loadMore: loadMore + } + }] +); \ No newline at end of file diff --git a/node_modules/image_picker/build/image-picker.min.js b/node_modules/image_picker/build/image-picker.min.js new file mode 100644 index 0000000..b6a33b6 --- /dev/null +++ b/node_modules/image_picker/build/image-picker.min.js @@ -0,0 +1,2 @@ +/*! image_picker 2016-04-18 */ +angular.module("mediaModalTemplates",[]).run(["$templateCache",function(a){"use strict";a.put("camera.html",'
\n

Upload Media

\n
\n
\n \n
\n
\n \n \n \n Add files...\n \n \n \n \n \n \n
\n \n
\n \n
\n \n
 
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n \n
\n
\n
{{file.name}}
\n
\n
\n
\n
\n
\n \n
\n
\n\n
\n
\n
\n
\n
\n
\n \n
\n
\n
{{file.name}}
\n
\n
\n
\n
\n
\n \n
\n
\n\n
\n\n
\n
\n
\n
\n
'),a.put("gallery.html",'
\n \n \n \n \n {{resource.title}}\n \n
\n
\n
\n
\n'),a.put("galleryWindow.html",'\n\n'),a.put("upload.html",'
\n

Upload Media

\n
\n
\n \n
\n
\n \n \n \n Add files...\n \n \n \n \n \n \n
\n \n
\n \n
\n \n
 
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n \n
\n
\n
{{file.name}}
\n
\n
\n
\n
\n
\n \n
\n
\n\n
\n
\n
\n
\n
\n
\n \n
\n
\n
{{file.name}}
\n
\n
\n
\n
\n
\n \n
\n
\n\n
\n\n
\n
\n
\n
\n
'),a.put("webService.html",'
\n

{{resource.title}}

\n
\n
\n \n \n \n \n
\n
\n\n
\n
\n
\n
\n
\n
\n \n
\n
\n
{{image.title}}
\n
\n
\n
\n
\n\n
\n \n
\n\n
')}]),angular.module("mediaModal",["ui.bootstrap","angularSpinner","flow","images-resizer","mediaModalTemplates","blueimp.fileupload"]),angular.module("mediaModal").config(["$httpProvider","fileUploadProvider","$sceDelegateProvider",function(a,b,c){c.resourceUrlWhitelist(["self","http://business.otonomic.com/**"]),delete a.defaults.headers.common["X-Requested-With"],b.defaults.redirect=window.location.href.replace(/\/[^\/]*$/,"/cors/result.html?%s"),angular.extend(b.defaults,{autoUpload:!0,forceIframeTransport:!0,disableImageResize:/Android(?!.*Chrome)|Opera/.test(window.navigator.userAgent),acceptFileTypes:/(\.|\/)(gif|jpe?g|png)$/i})}]).constant("flickrApiKey","b5245e9b9cbaeecee26ea278bfa20253").constant("upload_url","http://business.otonomic.com/api/v1/?cmd=upload").constant("instagramApiKey","44171713.10d405b.25e232f920f94ac9907d8c0ea34ce1de").constant("facebookApiKey","CAANkofzWtSoBAG0FcIPXZAFFUJjZBdZAiejDxnPXXapvIP1UZA9ZBKiryHlB3GZCjrY1P3rgnKO70AWYQ1bLFp03MxZAuYTaimqsETawQxE9PRhCuLKHZCxDd9wrzJQrqXMsscI80UzPapvPoQKa9WeE7NHIN8H30lb48AhXqZAbVbX7w33nuEhI8ZCPc56VnTS2IZD").constant("pexelsApiKey","563492ad6f9170000100000183fb18af4b5f4b5c6a67f7e6433578ba").factory("availableResources",["enums",function(a){return[{title:"Camera",icon:"fa fa-camera",viewPath:"camera.html"},{title:"Upload",icon:"fa fa-cloud-upload",viewPath:"upload.html"},{title:"Flickr",icon:"fa fa-flickr",viewPath:"webService.html",webServiceType:a.webServiceTypes.flickr},{title:"Instagram",icon:"fa fa-instagram",viewPath:"webService.html",webServiceType:a.webServiceTypes.instagram},{title:"Background Patterns",icon:"fa fa-star",viewPath:"webService.html",webServiceType:a.webServiceTypes.backgroundPatterns},{title:"Facebook Photos",icon:"fa fa-facebook",viewPath:"webService.html",webServiceType:a.webServiceTypes.facebookPhotos},{title:"Facebook Users",icon:"fa fa-facebook-official",viewPath:"webService.html",webServiceType:a.webServiceTypes.facebookUsers},{title:"Pexels",icon:"fa fa-picture-o",viewPath:"webService.html",webServiceType:a.webServiceTypes.pexels}]}]),angular.module("mediaModal").controller("cameraController",["$scope","$http","upload_url",function(a,b,c){a.upload_url=c,a.options={url:c},a.loadingFiles=!0,b.get(c).then(function(b){a.loadingFiles=!1,a.filequeue=b.data.files||[]},function(){a.loadingFiles=!1}),a.selectImage=function(b){if(b.selected=!b.selected,b.thumbnail=b.url,a.selectedImages){if(b.selected)return void a.selectedImages.push(b);var c=a.selectedImages.indexOf(b);-1!=c&&a.selectedImages.splice(c,1)}},a.$on("fileuploaddone",function(b,c){$.each(c.result.files,function(b,c){a.selectImage(c)})})}]),angular.module("mediaModal").controller("FileDestroyController",["$scope","$http","upload_url",function(a,b,c){var d,e=a.file;e.url?(e.$state=function(){return d},e.$destroy=function(){return d="pending",b({url:e.deleteUrl,method:e.deleteType}).then(function(){d="resolved",a.clear(e)},function(){d="rejected"})}):e.$cancel||e._index||(e.$cancel=function(){a.clear(e)})}]),function(){angular.module("mediaModal").controller("GalleryController",["$scope","availableResources","$uibModalInstance",function(a,b,c){a.availableResources=b,a.appState={},a.selectedImages=[],a.tabSelected=function(b){a.$broadcast("currentResourceChanged",b.webServiceType)},a.cancel=function(){c.dismiss("cancel")},a.selectImages=function(){c.close(a.selectedImages)}}])}(),angular.module("mediaModal").controller("uploadController",["$scope","$http","upload_url",function(a,b,c){a.upload_url=c,a.options={url:c},a.loadingFiles=!0,b.get(c).then(function(b){a.loadingFiles=!1,a.filequeue=b.data.files||[]},function(){a.loadingFiles=!1}),a.selectImage=function(b){if(b.selected=!b.selected,b.thumbnail=b.url,a.selectedImages){if(b.selected)return void a.selectedImages.push(b);var c=a.selectedImages.indexOf(b);-1!=c&&a.selectedImages.splice(c,1)}},a.$on("fileuploaddone",function(b,c){$.each(c.result.files,function(b,c){a.selectImage(c)})})}]),angular.module("mediaModal").controller("webServicesController",["$scope","webStorage","enums",function(a,b,c){a.currentPage=0,a.itemsOnPage=25,a.search=function(){a.appState.loading=!0,b.search(a.webServiceType,a.imageFilter,a.currentPage,a.itemsOnPage).then(function(b){a.fullImagesCount=b.count,a.images=b.images})["finally"](function(){a.appState.loading=!1})},a.loadMore=function(){a.appState.loading=!0,b.loadMore(a.webServiceType).then(function(b){a.images||(a.images=[]),a.images=a.images.concat(b.images)})["finally"](function(){a.appState.loading=!1})},a.setWebServiceType=function(b){a.webServiceType=b},a.selectImage=function(b){if(b.selected=!b.selected,a.selectedImages){if(b.selected)return void a.selectedImages.push(b);var c=a.selectedImages.indexOf(b);-1!=c&&a.selectedImages.splice(c,1)}},a.showSearcher=function(){return a.webServiceType!==c.webServiceTypes.backgroundPatterns},a.$on("currentResourceChanged",function(b,d){a.webServiceType===c.webServiceTypes.backgroundPatterns&&d===c.webServiceTypes.backgroundPatterns&&a.search()})}]),angular.module("mediaModal").factory("enums",function(){return{webServiceTypes:{flickr:1,instagram:2,backgroundPatterns:3,facebookUsers:4,pexels:5}}}),angular.module("mediaModal").factory("backgroundPatterns",["$http","$q",function(a,b){function c(c,d,g){return a.jsonp("http://www.colourlovers.com/api/patterns/top?format=json&jsonCallback=JSON_CALLBACK&numResults="+f+"&resultOffset="+e).then(function(a){return e++,{count:0,images:a.data.map(function(a){return{thumbnail:a.imageUrl,full:a.imageUrl,title:a.title}})}},function(a){return b.reject(a.data)})}function d(){return a.jsonp("http://www.colourlovers.com/api/patterns/top?format=json&jsonCallback=JSON_CALLBACK&numResults="+f+"&resultOffset="+e).then(function(a){return e++,{count:0,images:a.data.map(function(a){return{thumbnail:a.imageUrl,full:a.imageUrl,title:a.title}})}},function(a){return b.reject(a.data)})}var e=0,f=20;return{search:c,loadMore:d}}]),angular.module("mediaModal").factory("facebookPhotos",["$http","facebookApiKey","$q",function(a,b,c){function d(d,e,g){return a.get("https://graph.facebook.com/v2.5/"+d+"/photos?access_token="+b).then(function(a){return f=a.data.paging.next,{count:0,images:a.data.data.map(function(a){return{thumbnail:"http://graph.facebook.com/"+a.id+"/picture?type=normal",full:"http://graph.facebook.com/"+a.id+"/picture?type=large",title:a.name}})}},function(a){return c.reject(a.data)})}function e(){return f?a.get(f).then(function(a){return f=a.data.paging.next,{count:0,images:a.data.data.map(function(a){return{thumbnail:"http://graph.facebook.com/"+a.id+"/picture?type=normal",full:"http://graph.facebook.com/"+a.id+"/picture?type=large",title:a.name}})}},function(a){return c.reject(a.data)}):c.reject("no more images")}var f;return{search:d,loadMore:e}}]),angular.module("mediaModal").factory("facebookUsers",["$http","facebookApiKey","$q",function(a,b,c){function d(d,e,g){return a.get("https://graph.facebook.com/v2.2/search?q="+d+"&type=user&access_token="+b).then(function(a){return f=a.data.paging.next,{count:0,images:a.data.data.map(function(a){return{thumbnail:"http://graph.facebook.com/"+a.id+"/picture?type=normal",full:"http://graph.facebook.com/"+a.id+"/picture?type=large",title:a.name}})}},function(a){return c.reject(a.data)})}function e(){return f?a.get(f).then(function(a){return f=a.data.paging.next,{count:0,images:a.data.data.map(function(a){return{thumbnail:"http://graph.facebook.com/"+a.id+"/picture?type=normal",full:"http://graph.facebook.com/"+a.id+"/picture?type=large",title:a.name}})}},function(a){return c.reject(a.data)}):c.reject("no more images")}var f;return{search:d,loadMore:e}}]),angular.module("mediaModal").factory("flickr",["$http","flickrApiKey",function(a,b){function c(c,d,h){return e=1,f=h,g=c,a.get("https://api.flickr.com/services/rest/?api_key="+b+"&format=json&nojsoncallback=1&method=flickr.photos.search&format=json&page="+d+"&per_page="+h+"&text="+c).then(function(a){return{count:+a.data.photos.total,images:a.data.photos.photo.map(function(a){return{thumbnail:"https://farm"+a.farm+".staticflickr.com/"+a.server+"/"+a.id+"_"+a.secret+"_t.jpg",full:"https://farm"+a.farm+".staticflickr.com/"+a.server+"/"+a.id+"_"+a.secret+".jpg"}})}},function(a){return $q.reject(a.data)})}function d(){return e++,a.get("https://api.flickr.com/services/rest/?api_key="+b+"&format=json&nojsoncallback=1&method=flickr.photos.search&format=json&page="+e+"&per_page="+f+"&text="+g).then(function(a){return{count:+a.data.photos.total,images:a.data.photos.photo.map(function(a){return{thumbnail:"https://farm"+a.farm+".staticflickr.com/"+a.server+"/"+a.id+"_"+a.secret+"_t.jpg",full:"https://farm"+a.farm+".staticflickr.com/"+a.server+"/"+a.id+"_"+a.secret+".jpg"}})}},function(a){return $q.reject(a.data)})}var e=1,f=50,g="";return{search:c,loadMore:d}}]),angular.module("mediaModal").factory("gallery",["$q","$uibModal",function(a,b){function c(a){return b.open({animation:a,templateUrl:"galleryWindow.html",controller:"GalleryController",size:"lg"}).result}return{open:c}}]),angular.module("mediaModal").factory("instagram",["$http","instagramApiKey","$q",function(a,b,c){function d(d,e,g){return a.jsonp("https://api.instagram.com/v1/tags/"+d+"/media/recent?access_token="+b+"&callback=JSON_CALLBACK&count=20").then(function(a){return f=a.data.pagination.next_url,{count:0,images:a.data.data.map(function(a){return{thumbnail:a.images.thumbnail.url,full:a.images.standard_resolution.url,title:a.caption.text.substr(0,50)}})}},function(a){return c.reject(a.data)})}function e(){return f?a.jsonp(f+"&callback=JSON_CALLBACK").then(function(a){return f=a.data.pagination.next_url,{count:0,images:a.data.data.map(function(a){return{thumbnail:a.images.thumbnail.url,full:a.images.standard_resolution.url,title:a.caption.text.substr(0,50)}})}},function(a){return c.reject(a.data)}):c.reject("no more images")}var f;return{search:d,loadMore:e}}]),angular.module("mediaModal").factory("pexels",["$http","pexelsApiKey","$q",function(a,b,c){function d(d,e,f){return a.get("http://api.pexels.com/v1/search?query="+d+"&per_page="+f+"&page="+(e+1),{headers:{Authorization:b}}).then(function(a){return{count:a.data.total_results,images:a.data.photos.map(function(a){return{thumbnail:a.src.square,full:a.src.large,title:a.photographer}})}},function(a){return c.reject(a.data)})}return{search:d}}]),angular.module("mediaModal").factory("webStorage",["enums","flickr","instagram","backgroundPatterns","facebookUsers","pexels","facebookPhotos",function(a,b,c,d,e,f,g){function h(h){switch(h){case a.webServiceTypes.flickr:return b;case a.webServiceTypes.instagram:return c;case a.webServiceTypes.backgroundPatterns:return d;case a.webServiceTypes.facebookUsers:return e;case a.webServiceTypes.pexels:return f;case a.webServiceTypes.facebookPhotos:return g}}function i(a,b,c,d){return h(a).search(b,c,d)}function j(a){return h(a).loadMore()}return{search:i,loadMore:j}}]); \ No newline at end of file diff --git a/node_modules/image_picker/build/media-modal.css b/node_modules/image_picker/build/media-modal.css new file mode 100644 index 0000000..fd36ba0 --- /dev/null +++ b/node_modules/image_picker/build/media-modal.css @@ -0,0 +1,68 @@ +#footer { + position: fixed; + bottom: 0; + width: 100%; + height: 60px; + background-color: #f5f5f5; + line-height: 60px; + z-index: 1000; +} +#otonomic-media-modal { + +} +#otonomic-media-modal .media-wrapper { + position: relative; + margin-top: 20px; + border: 3px solid transparent; + overflow: hidden; +} +#otonomic-media-modal .image-wrapper { + height:200px; + position: relative; + + -webkit-box-shadow: inset 0 0 15px rgba( 0, 0, 0, 0.1 ), + inset 0 0 0 1px rgba( 0, 0, 0, 0.05 ); + box-shadow: inset 0 0 15px rgba( 0, 0, 0, 0.1 ), + inset 0 0 0 1px rgba( 0, 0, 0, 0.05 ); + background: #eee; + cursor: pointer; +} +#otonomic-media-modal .image-wrapper .centered { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + -webkit-transform: translate( 50%, 50% ); + -ms-transform: translate(50%,50%); + transform: translate( 50%, 50% ); +} +#otonomic-media-modal .image-wrapper img{ + position: absolute; + top: 0; + left: 0; + + -webkit-transform: translate( -50%, -50% ); + -ms-transform: translate(-50%,-50%); + transform: translate( -50%, -50% ); +} +#otonomic-media-modal .media-wrapper .image-title { + position: absolute; + bottom: 0; + left: 0; + right: 0; + color: #fff; + background: rgba(0,0,0,0.5); + padding: 5px; +} +#otonomic-media-modal .media-wrapper.selected { + border-color:#1e8cbe; +} +#otonomic-media-modal #tab-footer { + padding: 10px 0; +} + +.media-resource{ + max-height: 500px; + overflow-y: auto; +} \ No newline at end of file diff --git a/node_modules/image_picker/build/templates.js b/node_modules/image_picker/build/templates.js new file mode 100644 index 0000000..1f1524d --- /dev/null +++ b/node_modules/image_picker/build/templates.js @@ -0,0 +1,242 @@ +angular.module('mediaModalTemplates', []).run(['$templateCache', function($templateCache) { + 'use strict'; + + $templateCache.put('camera.html', + "
\n" + + "

Upload Media

\n" + + "
\n" + + "
\n" + + " \n" + + "
\n" + + "
\n" + + " \n" + + " \n" + + " \n" + + " Add files...\n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + "
\n" + + " \n" + + "
\n" + + " \n" + + "
\n" + + " \n" + + "
 
\n" + + "
\n" + + "
\n" + + "
\n" + + "
\n" + + "
\n" + + "
\n" + + "
\n" + + "
\n" + + "
\n" + + " \n" + + "
\n" + + "
\n" + + "
{{file.name}}
\n" + + "
\n" + + "
\n" + + "
\n" + + "
\n" + + "
\n" + + " \n" + + "
\n" + + "
\n" + + "\n" + + "
\n" + + "
\n" + + "
\n" + + "
\n" + + "
\n" + + "
\n" + + " \n" + + "
\n" + + "
\n" + + "
{{file.name}}
\n" + + "
\n" + + "
\n" + + "
\n" + + "
\n" + + "
\n" + + " \n" + + "
\n" + + "
\n" + + "\n" + + "
\n" + + "\n" + + "
\n" + + "
\n" + + "
\n" + + "
\n" + + "
" + ); + + + $templateCache.put('gallery.html', + "
\n" + + " \n" + + " \n" + + " \n" + + " \n" + + " {{resource.title}}\n" + + " \n" + + "
\n" + + "
\n" + + "
\n" + + "
\n" + + "" + ); + + + $templateCache.put('galleryWindow.html', + "
\n" + + "

Add Image

\n" + + "
\n" + + "
\n" + + "
\n" + + "
\n" + + "
\n" + + " \n" + + " \n" + + "
" + ); + + + $templateCache.put('upload.html', + "
\n" + + "

Upload Media

\n" + + "
\n" + + "
\n" + + " \n" + + "
\n" + + "
\n" + + " \n" + + " \n" + + " \n" + + " Add files...\n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + "
\n" + + " \n" + + "
\n" + + " \n" + + "
\n" + + " \n" + + "
 
\n" + + "
\n" + + "
\n" + + "
\n" + + "
\n" + + "
\n" + + "
\n" + + "
\n" + + "
\n" + + "
\n" + + " \n" + + "
\n" + + "
\n" + + "
{{file.name}}
\n" + + "
\n" + + "
\n" + + "
\n" + + "
\n" + + "
\n" + + " \n" + + "
\n" + + "
\n" + + "\n" + + "
\n" + + "
\n" + + "
\n" + + "
\n" + + "
\n" + + "
\n" + + " \n" + + "
\n" + + "
\n" + + "
{{file.name}}
\n" + + "
\n" + + "
\n" + + "
\n" + + "
\n" + + "
\n" + + " \n" + + "
\n" + + "
\n" + + "\n" + + "
\n" + + "\n" + + "
\n" + + "
\n" + + "
\n" + + "
\n" + + "
" + ); + + + $templateCache.put('webService.html', + "
\n" + + "

{{resource.title}}

\n" + + "
\n" + + "
\n" + + " \n" + + " \n" + + " \n" + + " \n" + + "
\n" + + "
\n" + + "\n" + + "
\n" + + "
\n" + + "
\n" + + "
\n" + + "
\n" + + "
\n" + + " \n" + + "
\n" + + "
\n" + + "
{{image.title}}
\n" + + "
\n" + + "
\n" + + "
\n" + + "
\n" + + "\n" + + "
\n" + + " \n" + + "
\n" + + "\n" + + "
" + ); + +}]); diff --git a/node_modules/image_picker/image-picker.min.js b/node_modules/image_picker/image-picker.min.js new file mode 100644 index 0000000..b5eeffa --- /dev/null +++ b/node_modules/image_picker/image-picker.min.js @@ -0,0 +1,2971 @@ +/* + * jQuery Iframe Transport Plugin + * https://github.com/blueimp/jQuery-File-Upload + * + * Copyright 2011, Sebastian Tschan + * https://blueimp.net + * + * Licensed under the MIT license: + * http://www.opensource.org/licenses/MIT + */ + +/* global define, require, window, document */ + +(function (factory) { + 'use strict'; + if (typeof define === 'function' && define.amd) { + // Register as an anonymous AMD module: + define(['jquery'], factory); + } else if (typeof exports === 'object') { + // Node/CommonJS: + factory(require('jquery')); + } else { + // Browser globals: + factory(window.jQuery); + } +}(function ($) { + 'use strict'; + + // Helper variable to create unique names for the transport iframes: + var counter = 0; + + // The iframe transport accepts four additional options: + // options.fileInput: a jQuery collection of file input fields + // options.paramName: the parameter name for the file form data, + // overrides the name property of the file input field(s), + // can be a string or an array of strings. + // options.formData: an array of objects with name and value properties, + // equivalent to the return data of .serializeArray(), e.g.: + // [{name: 'a', value: 1}, {name: 'b', value: 2}] + // options.initialIframeSrc: the URL of the initial iframe src, + // by default set to "javascript:false;" + $.ajaxTransport('iframe', function (options) { + if (options.async) { + // javascript:false as initial iframe src + // prevents warning popups on HTTPS in IE6: + /*jshint scripturl: true */ + var initialIframeSrc = options.initialIframeSrc || 'javascript:false;', + /*jshint scripturl: false */ + form, + iframe, + addParamChar; + return { + send: function (_, completeCallback) { + form = $('
'); + form.attr('accept-charset', options.formAcceptCharset); + addParamChar = /\?/.test(options.url) ? '&' : '?'; + // XDomainRequest only supports GET and POST: + if (options.type === 'DELETE') { + options.url = options.url + addParamChar + '_method=DELETE'; + options.type = 'POST'; + } else if (options.type === 'PUT') { + options.url = options.url + addParamChar + '_method=PUT'; + options.type = 'POST'; + } else if (options.type === 'PATCH') { + options.url = options.url + addParamChar + '_method=PATCH'; + options.type = 'POST'; + } + // IE versions below IE8 cannot set the name property of + // elements that have already been added to the DOM, + // so we set the name along with the iframe HTML markup: + counter += 1; + iframe = $( + '' + ).bind('load', function () { + var fileInputClones, + paramNames = $.isArray(options.paramName) ? + options.paramName : [options.paramName]; + iframe + .unbind('load') + .bind('load', function () { + var response; + // Wrap in a try/catch block to catch exceptions thrown + // when trying to access cross-domain iframe contents: + try { + response = iframe.contents(); + // Google Chrome and Firefox do not throw an + // exception when calling iframe.contents() on + // cross-domain requests, so we unify the response: + if (!response.length || !response[0].firstChild) { + throw new Error(); + } + } catch (e) { + response = undefined; + } + // The complete callback returns the + // iframe content document as response object: + completeCallback( + 200, + 'success', + {'iframe': response} + ); + // Fix for IE endless progress bar activity bug + // (happens on form submits to iframe targets): + $('') + .appendTo(form); + window.setTimeout(function () { + // Removing the form in a setTimeout call + // allows Chrome's developer tools to display + // the response result + form.remove(); + }, 0); + }); + form + .prop('target', iframe.prop('name')) + .prop('action', options.url) + .prop('method', options.type); + if (options.formData) { + $.each(options.formData, function (index, field) { + $('') + .prop('name', field.name) + .val(field.value) + .appendTo(form); + }); + } + if (options.fileInput && options.fileInput.length && + options.type === 'POST') { + fileInputClones = options.fileInput.clone(); + // Insert a clone for each file input field: + options.fileInput.after(function (index) { + return fileInputClones[index]; + }); + if (options.paramName) { + options.fileInput.each(function (index) { + $(this).prop( + 'name', + paramNames[index] || options.paramName + ); + }); + } + // Appending the file input fields to the hidden form + // removes them from their original location: + form + .append(options.fileInput) + .prop('enctype', 'multipart/form-data') + // enctype must be set as encoding for IE: + .prop('encoding', 'multipart/form-data'); + // Remove the HTML5 form attribute from the input(s): + options.fileInput.removeAttr('form'); + } + form.submit(); + // Insert the file input fields at their original location + // by replacing the clones with the originals: + if (fileInputClones && fileInputClones.length) { + options.fileInput.each(function (index, input) { + var clone = $(fileInputClones[index]); + // Restore the original name and form properties: + $(input) + .prop('name', clone.prop('name')) + .attr('form', clone.attr('form')); + clone.replaceWith(input); + }); + } + }); + form.append(iframe).appendTo(document.body); + }, + abort: function () { + if (iframe) { + // javascript:false as iframe src aborts the request + // and prevents warning popups on HTTPS in IE6. + // concat is used to avoid the "Script URL" JSLint error: + iframe + .unbind('load') + .prop('src', initialIframeSrc); + } + if (form) { + form.remove(); + } + } + }; + } + }); + + // The iframe transport returns the iframe content document as response. + // The following adds converters from iframe to text, json, html, xml + // and script. + // Please note that the Content-Type for JSON responses has to be text/plain + // or text/html, if the browser doesn't include application/json in the + // Accept header, else IE will show a download dialog. + // The Content-Type for XML responses on the other hand has to be always + // application/xml or text/xml, so IE properly parses the XML response. + // See also + // https://github.com/blueimp/jQuery-File-Upload/wiki/Setup#content-type-negotiation + $.ajaxSetup({ + converters: { + 'iframe text': function (iframe) { + return iframe && $(iframe[0].body).text(); + }, + 'iframe json': function (iframe) { + return iframe && $.parseJSON($(iframe[0].body).text()); + }, + 'iframe html': function (iframe) { + return iframe && $(iframe[0].body).html(); + }, + 'iframe xml': function (iframe) { + var xmlDoc = iframe && iframe[0]; + return xmlDoc && $.isXMLDoc(xmlDoc) ? xmlDoc : + $.parseXML((xmlDoc.XMLDocument && xmlDoc.XMLDocument.xml) || + $(xmlDoc.body).html()); + }, + 'iframe script': function (iframe) { + return iframe && $.globalEval($(iframe[0].body).text()); + } + } + }); + +})); + +/* + * jQuery File Upload Plugin + * https://github.com/blueimp/jQuery-File-Upload + * + * Copyright 2010, Sebastian Tschan + * https://blueimp.net + * + * Licensed under the MIT license: + * http://www.opensource.org/licenses/MIT + */ + +/* jshint nomen:false */ +/* global define, require, window, document, location, Blob, FormData */ + +(function (factory) { + 'use strict'; + if (typeof define === 'function' && define.amd) { + // Register as an anonymous AMD module: + define([ + 'jquery', + 'jquery.ui.widget' + ], factory); + } else if (typeof exports === 'object') { + // Node/CommonJS: + factory( + require('jquery'), + require('./vendor/jquery.ui.widget') + ); + } else { + // Browser globals: + factory(window.jQuery); + } +}(function ($) { + 'use strict'; + + // Detect file input support, based on + // http://viljamis.com/blog/2012/file-upload-support-on-mobile/ + $.support.fileInput = !(new RegExp( + // Handle devices which give false positives for the feature detection: + '(Android (1\\.[0156]|2\\.[01]))' + + '|(Windows Phone (OS 7|8\\.0))|(XBLWP)|(ZuneWP)|(WPDesktop)' + + '|(w(eb)?OSBrowser)|(webOS)' + + '|(Kindle/(1\\.0|2\\.[05]|3\\.0))' + ).test(window.navigator.userAgent) || + // Feature detection for all other devices: + $('').prop('disabled')); + + // The FileReader API is not actually used, but works as feature detection, + // as some Safari versions (5?) support XHR file uploads via the FormData API, + // but not non-multipart XHR file uploads. + // window.XMLHttpRequestUpload is not available on IE10, so we check for + // window.ProgressEvent instead to detect XHR2 file upload capability: + $.support.xhrFileUpload = !!(window.ProgressEvent && window.FileReader); + $.support.xhrFormDataFileUpload = !!window.FormData; + + // Detect support for Blob slicing (required for chunked uploads): + $.support.blobSlice = window.Blob && (Blob.prototype.slice || + Blob.prototype.webkitSlice || Blob.prototype.mozSlice); + + // Helper function to create drag handlers for dragover/dragenter/dragleave: + function getDragHandler(type) { + var isDragOver = type === 'dragover'; + return function (e) { + e.dataTransfer = e.originalEvent && e.originalEvent.dataTransfer; + var dataTransfer = e.dataTransfer; + if (dataTransfer && $.inArray('Files', dataTransfer.types) !== -1 && + this._trigger( + type, + $.Event(type, {delegatedEvent: e}) + ) !== false) { + e.preventDefault(); + if (isDragOver) { + dataTransfer.dropEffect = 'copy'; + } + } + }; + } + + // The fileupload widget listens for change events on file input fields defined + // via fileInput setting and paste or drop events of the given dropZone. + // In addition to the default jQuery Widget methods, the fileupload widget + // exposes the "add" and "send" methods, to add or directly send files using + // the fileupload API. + // By default, files added via file input selection, paste, drag & drop or + // "add" method are uploaded immediately, but it is possible to override + // the "add" callback option to queue file uploads. + $.widget('blueimp.fileupload', { + + options: { + // The drop target element(s), by the default the complete document. + // Set to null to disable drag & drop support: + dropZone: $(document), + // The paste target element(s), by the default undefined. + // Set to a DOM node or jQuery object to enable file pasting: + pasteZone: undefined, + // The file input field(s), that are listened to for change events. + // If undefined, it is set to the file input fields inside + // of the widget element on plugin initialization. + // Set to null to disable the change listener. + fileInput: undefined, + // By default, the file input field is replaced with a clone after + // each input field change event. This is required for iframe transport + // queues and allows change events to be fired for the same file + // selection, but can be disabled by setting the following option to false: + replaceFileInput: true, + // The parameter name for the file form data (the request argument name). + // If undefined or empty, the name property of the file input field is + // used, or "files[]" if the file input name property is also empty, + // can be a string or an array of strings: + paramName: undefined, + // By default, each file of a selection is uploaded using an individual + // request for XHR type uploads. Set to false to upload file + // selections in one request each: + singleFileUploads: true, + // To limit the number of files uploaded with one XHR request, + // set the following option to an integer greater than 0: + limitMultiFileUploads: undefined, + // The following option limits the number of files uploaded with one + // XHR request to keep the request size under or equal to the defined + // limit in bytes: + limitMultiFileUploadSize: undefined, + // Multipart file uploads add a number of bytes to each uploaded file, + // therefore the following option adds an overhead for each file used + // in the limitMultiFileUploadSize configuration: + limitMultiFileUploadSizeOverhead: 512, + // Set the following option to true to issue all file upload requests + // in a sequential order: + sequentialUploads: false, + // To limit the number of concurrent uploads, + // set the following option to an integer greater than 0: + limitConcurrentUploads: undefined, + // Set the following option to true to force iframe transport uploads: + forceIframeTransport: false, + // Set the following option to the location of a redirect url on the + // origin server, for cross-domain iframe transport uploads: + redirect: undefined, + // The parameter name for the redirect url, sent as part of the form + // data and set to 'redirect' if this option is empty: + redirectParamName: undefined, + // Set the following option to the location of a postMessage window, + // to enable postMessage transport uploads: + postMessage: undefined, + // By default, XHR file uploads are sent as multipart/form-data. + // The iframe transport is always using multipart/form-data. + // Set to false to enable non-multipart XHR uploads: + multipart: true, + // To upload large files in smaller chunks, set the following option + // to a preferred maximum chunk size. If set to 0, null or undefined, + // or the browser does not support the required Blob API, files will + // be uploaded as a whole. + maxChunkSize: undefined, + // When a non-multipart upload or a chunked multipart upload has been + // aborted, this option can be used to resume the upload by setting + // it to the size of the already uploaded bytes. This option is most + // useful when modifying the options object inside of the "add" or + // "send" callbacks, as the options are cloned for each file upload. + uploadedBytes: undefined, + // By default, failed (abort or error) file uploads are removed from the + // global progress calculation. Set the following option to false to + // prevent recalculating the global progress data: + recalculateProgress: true, + // Interval in milliseconds to calculate and trigger progress events: + progressInterval: 100, + // Interval in milliseconds to calculate progress bitrate: + bitrateInterval: 500, + // By default, uploads are started automatically when adding files: + autoUpload: true, + + // Error and info messages: + messages: { + uploadedBytes: 'Uploaded bytes exceed file size' + }, + + // Translation function, gets the message key to be translated + // and an object with context specific data as arguments: + i18n: function (message, context) { + message = this.messages[message] || message.toString(); + if (context) { + $.each(context, function (key, value) { + message = message.replace('{' + key + '}', value); + }); + } + return message; + }, + + // Additional form data to be sent along with the file uploads can be set + // using this option, which accepts an array of objects with name and + // value properties, a function returning such an array, a FormData + // object (for XHR file uploads), or a simple object. + // The form of the first fileInput is given as parameter to the function: + formData: function (form) { + return form.serializeArray(); + }, + + // The add callback is invoked as soon as files are added to the fileupload + // widget (via file input selection, drag & drop, paste or add API call). + // If the singleFileUploads option is enabled, this callback will be + // called once for each file in the selection for XHR file uploads, else + // once for each file selection. + // + // The upload starts when the submit method is invoked on the data parameter. + // The data object contains a files property holding the added files + // and allows you to override plugin options as well as define ajax settings. + // + // Listeners for this callback can also be bound the following way: + // .bind('fileuploadadd', func); + // + // data.submit() returns a Promise object and allows to attach additional + // handlers using jQuery's Deferred callbacks: + // data.submit().done(func).fail(func).always(func); + add: function (e, data) { + if (e.isDefaultPrevented()) { + return false; + } + if (data.autoUpload || (data.autoUpload !== false && + $(this).fileupload('option', 'autoUpload'))) { + data.process().done(function () { + data.submit(); + }); + } + }, + + // Other callbacks: + + // Callback for the submit event of each file upload: + // submit: function (e, data) {}, // .bind('fileuploadsubmit', func); + + // Callback for the start of each file upload request: + // send: function (e, data) {}, // .bind('fileuploadsend', func); + + // Callback for successful uploads: + // done: function (e, data) {}, // .bind('fileuploaddone', func); + + // Callback for failed (abort or error) uploads: + // fail: function (e, data) {}, // .bind('fileuploadfail', func); + + // Callback for completed (success, abort or error) requests: + // always: function (e, data) {}, // .bind('fileuploadalways', func); + + // Callback for upload progress events: + // progress: function (e, data) {}, // .bind('fileuploadprogress', func); + + // Callback for global upload progress events: + // progressall: function (e, data) {}, // .bind('fileuploadprogressall', func); + + // Callback for uploads start, equivalent to the global ajaxStart event: + // start: function (e) {}, // .bind('fileuploadstart', func); + + // Callback for uploads stop, equivalent to the global ajaxStop event: + // stop: function (e) {}, // .bind('fileuploadstop', func); + + // Callback for change events of the fileInput(s): + // change: function (e, data) {}, // .bind('fileuploadchange', func); + + // Callback for paste events to the pasteZone(s): + // paste: function (e, data) {}, // .bind('fileuploadpaste', func); + + // Callback for drop events of the dropZone(s): + // drop: function (e, data) {}, // .bind('fileuploaddrop', func); + + // Callback for dragover events of the dropZone(s): + // dragover: function (e) {}, // .bind('fileuploaddragover', func); + + // Callback for the start of each chunk upload request: + // chunksend: function (e, data) {}, // .bind('fileuploadchunksend', func); + + // Callback for successful chunk uploads: + // chunkdone: function (e, data) {}, // .bind('fileuploadchunkdone', func); + + // Callback for failed (abort or error) chunk uploads: + // chunkfail: function (e, data) {}, // .bind('fileuploadchunkfail', func); + + // Callback for completed (success, abort or error) chunk upload requests: + // chunkalways: function (e, data) {}, // .bind('fileuploadchunkalways', func); + + // The plugin options are used as settings object for the ajax calls. + // The following are jQuery ajax settings required for the file uploads: + processData: false, + contentType: false, + cache: false, + timeout: 0 + }, + + // A list of options that require reinitializing event listeners and/or + // special initialization code: + _specialOptions: [ + 'fileInput', + 'dropZone', + 'pasteZone', + 'multipart', + 'forceIframeTransport' + ], + + _blobSlice: $.support.blobSlice && function () { + var slice = this.slice || this.webkitSlice || this.mozSlice; + return slice.apply(this, arguments); + }, + + _BitrateTimer: function () { + this.timestamp = ((Date.now) ? Date.now() : (new Date()).getTime()); + this.loaded = 0; + this.bitrate = 0; + this.getBitrate = function (now, loaded, interval) { + var timeDiff = now - this.timestamp; + if (!this.bitrate || !interval || timeDiff > interval) { + this.bitrate = (loaded - this.loaded) * (1000 / timeDiff) * 8; + this.loaded = loaded; + this.timestamp = now; + } + return this.bitrate; + }; + }, + + _isXHRUpload: function (options) { + return !options.forceIframeTransport && + ((!options.multipart && $.support.xhrFileUpload) || + $.support.xhrFormDataFileUpload); + }, + + _getFormData: function (options) { + var formData; + if ($.type(options.formData) === 'function') { + return options.formData(options.form); + } + if ($.isArray(options.formData)) { + return options.formData; + } + if ($.type(options.formData) === 'object') { + formData = []; + $.each(options.formData, function (name, value) { + formData.push({name: name, value: value}); + }); + return formData; + } + return []; + }, + + _getTotal: function (files) { + var total = 0; + $.each(files, function (index, file) { + total += file.size || 1; + }); + return total; + }, + + _initProgressObject: function (obj) { + var progress = { + loaded: 0, + total: 0, + bitrate: 0 + }; + if (obj._progress) { + $.extend(obj._progress, progress); + } else { + obj._progress = progress; + } + }, + + _initResponseObject: function (obj) { + var prop; + if (obj._response) { + for (prop in obj._response) { + if (obj._response.hasOwnProperty(prop)) { + delete obj._response[prop]; + } + } + } else { + obj._response = {}; + } + }, + + _onProgress: function (e, data) { + if (e.lengthComputable) { + var now = ((Date.now) ? Date.now() : (new Date()).getTime()), + loaded; + if (data._time && data.progressInterval && + (now - data._time < data.progressInterval) && + e.loaded !== e.total) { + return; + } + data._time = now; + loaded = Math.floor( + e.loaded / e.total * (data.chunkSize || data._progress.total) + ) + (data.uploadedBytes || 0); + // Add the difference from the previously loaded state + // to the global loaded counter: + this._progress.loaded += (loaded - data._progress.loaded); + this._progress.bitrate = this._bitrateTimer.getBitrate( + now, + this._progress.loaded, + data.bitrateInterval + ); + data._progress.loaded = data.loaded = loaded; + data._progress.bitrate = data.bitrate = data._bitrateTimer.getBitrate( + now, + loaded, + data.bitrateInterval + ); + // Trigger a custom progress event with a total data property set + // to the file size(s) of the current upload and a loaded data + // property calculated accordingly: + this._trigger( + 'progress', + $.Event('progress', {delegatedEvent: e}), + data + ); + // Trigger a global progress event for all current file uploads, + // including ajax calls queued for sequential file uploads: + this._trigger( + 'progressall', + $.Event('progressall', {delegatedEvent: e}), + this._progress + ); + } + }, + + _initProgressListener: function (options) { + var that = this, + xhr = options.xhr ? options.xhr() : $.ajaxSettings.xhr(); + // Accesss to the native XHR object is required to add event listeners + // for the upload progress event: + if (xhr.upload) { + $(xhr.upload).bind('progress', function (e) { + var oe = e.originalEvent; + // Make sure the progress event properties get copied over: + e.lengthComputable = oe.lengthComputable; + e.loaded = oe.loaded; + e.total = oe.total; + that._onProgress(e, options); + }); + options.xhr = function () { + return xhr; + }; + } + }, + + _isInstanceOf: function (type, obj) { + // Cross-frame instanceof check + return Object.prototype.toString.call(obj) === '[object ' + type + ']'; + }, + + _initXHRData: function (options) { + var that = this, + formData, + file = options.files[0], + // Ignore non-multipart setting if not supported: + multipart = options.multipart || !$.support.xhrFileUpload, + paramName = $.type(options.paramName) === 'array' ? + options.paramName[0] : options.paramName; + options.headers = $.extend({}, options.headers); + if (options.contentRange) { + options.headers['Content-Range'] = options.contentRange; + } + if (!multipart || options.blob || !this._isInstanceOf('File', file)) { + options.headers['Content-Disposition'] = 'attachment; filename="' + + encodeURI(file.name) + '"'; + } + if (!multipart) { + options.contentType = file.type || 'application/octet-stream'; + options.data = options.blob || file; + } else if ($.support.xhrFormDataFileUpload) { + if (options.postMessage) { + // window.postMessage does not allow sending FormData + // objects, so we just add the File/Blob objects to + // the formData array and let the postMessage window + // create the FormData object out of this array: + formData = this._getFormData(options); + if (options.blob) { + formData.push({ + name: paramName, + value: options.blob + }); + } else { + $.each(options.files, function (index, file) { + formData.push({ + name: ($.type(options.paramName) === 'array' && + options.paramName[index]) || paramName, + value: file + }); + }); + } + } else { + if (that._isInstanceOf('FormData', options.formData)) { + formData = options.formData; + } else { + formData = new FormData(); + $.each(this._getFormData(options), function (index, field) { + formData.append(field.name, field.value); + }); + } + if (options.blob) { + formData.append(paramName, options.blob, file.name); + } else { + $.each(options.files, function (index, file) { + // This check allows the tests to run with + // dummy objects: + if (that._isInstanceOf('File', file) || + that._isInstanceOf('Blob', file)) { + formData.append( + ($.type(options.paramName) === 'array' && + options.paramName[index]) || paramName, + file, + file.uploadName || file.name + ); + } + }); + } + } + options.data = formData; + } + // Blob reference is not needed anymore, free memory: + options.blob = null; + }, + + _initIframeSettings: function (options) { + var targetHost = $('').prop('href', options.url).prop('host'); + // Setting the dataType to iframe enables the iframe transport: + options.dataType = 'iframe ' + (options.dataType || ''); + // The iframe transport accepts a serialized array as form data: + options.formData = this._getFormData(options); + // Add redirect url to form data on cross-domain uploads: + if (options.redirect && targetHost && targetHost !== location.host) { + options.formData.push({ + name: options.redirectParamName || 'redirect', + value: options.redirect + }); + } + }, + + _initDataSettings: function (options) { + if (this._isXHRUpload(options)) { + if (!this._chunkedUpload(options, true)) { + if (!options.data) { + this._initXHRData(options); + } + this._initProgressListener(options); + } + if (options.postMessage) { + // Setting the dataType to postmessage enables the + // postMessage transport: + options.dataType = 'postmessage ' + (options.dataType || ''); + } + } else { + this._initIframeSettings(options); + } + }, + + _getParamName: function (options) { + var fileInput = $(options.fileInput), + paramName = options.paramName; + if (!paramName) { + paramName = []; + fileInput.each(function () { + var input = $(this), + name = input.prop('name') || 'files[]', + i = (input.prop('files') || [1]).length; + while (i) { + paramName.push(name); + i -= 1; + } + }); + if (!paramName.length) { + paramName = [fileInput.prop('name') || 'files[]']; + } + } else if (!$.isArray(paramName)) { + paramName = [paramName]; + } + return paramName; + }, + + _initFormSettings: function (options) { + // Retrieve missing options from the input field and the + // associated form, if available: + if (!options.form || !options.form.length) { + options.form = $(options.fileInput.prop('form')); + // If the given file input doesn't have an associated form, + // use the default widget file input's form: + if (!options.form.length) { + options.form = $(this.options.fileInput.prop('form')); + } + } + options.paramName = this._getParamName(options); + if (!options.url) { + options.url = options.form.prop('action') || location.href; + } + // The HTTP request method must be "POST" or "PUT": + options.type = (options.type || + ($.type(options.form.prop('method')) === 'string' && + options.form.prop('method')) || '' + ).toUpperCase(); + if (options.type !== 'POST' && options.type !== 'PUT' && + options.type !== 'PATCH') { + options.type = 'POST'; + } + if (!options.formAcceptCharset) { + options.formAcceptCharset = options.form.attr('accept-charset'); + } + }, + + _getAJAXSettings: function (data) { + var options = $.extend({}, this.options, data); + this._initFormSettings(options); + this._initDataSettings(options); + return options; + }, + + // jQuery 1.6 doesn't provide .state(), + // while jQuery 1.8+ removed .isRejected() and .isResolved(): + _getDeferredState: function (deferred) { + if (deferred.state) { + return deferred.state(); + } + if (deferred.isResolved()) { + return 'resolved'; + } + if (deferred.isRejected()) { + return 'rejected'; + } + return 'pending'; + }, + + // Maps jqXHR callbacks to the equivalent + // methods of the given Promise object: + _enhancePromise: function (promise) { + promise.success = promise.done; + promise.error = promise.fail; + promise.complete = promise.always; + return promise; + }, + + // Creates and returns a Promise object enhanced with + // the jqXHR methods abort, success, error and complete: + _getXHRPromise: function (resolveOrReject, context, args) { + var dfd = $.Deferred(), + promise = dfd.promise(); + context = context || this.options.context || promise; + if (resolveOrReject === true) { + dfd.resolveWith(context, args); + } else if (resolveOrReject === false) { + dfd.rejectWith(context, args); + } + promise.abort = dfd.promise; + return this._enhancePromise(promise); + }, + + // Adds convenience methods to the data callback argument: + _addConvenienceMethods: function (e, data) { + var that = this, + getPromise = function (args) { + return $.Deferred().resolveWith(that, args).promise(); + }; + data.process = function (resolveFunc, rejectFunc) { + if (resolveFunc || rejectFunc) { + data._processQueue = this._processQueue = + (this._processQueue || getPromise([this])).pipe( + function () { + if (data.errorThrown) { + return $.Deferred() + .rejectWith(that, [data]).promise(); + } + return getPromise(arguments); + } + ).pipe(resolveFunc, rejectFunc); + } + return this._processQueue || getPromise([this]); + }; + data.submit = function () { + if (this.state() !== 'pending') { + data.jqXHR = this.jqXHR = + (that._trigger( + 'submit', + $.Event('submit', {delegatedEvent: e}), + this + ) !== false) && that._onSend(e, this); + } + return this.jqXHR || that._getXHRPromise(); + }; + data.abort = function () { + if (this.jqXHR) { + return this.jqXHR.abort(); + } + this.errorThrown = 'abort'; + that._trigger('fail', null, this); + return that._getXHRPromise(false); + }; + data.state = function () { + if (this.jqXHR) { + return that._getDeferredState(this.jqXHR); + } + if (this._processQueue) { + return that._getDeferredState(this._processQueue); + } + }; + data.processing = function () { + return !this.jqXHR && this._processQueue && that + ._getDeferredState(this._processQueue) === 'pending'; + }; + data.progress = function () { + return this._progress; + }; + data.response = function () { + return this._response; + }; + }, + + // Parses the Range header from the server response + // and returns the uploaded bytes: + _getUploadedBytes: function (jqXHR) { + var range = jqXHR.getResponseHeader('Range'), + parts = range && range.split('-'), + upperBytesPos = parts && parts.length > 1 && + parseInt(parts[1], 10); + return upperBytesPos && upperBytesPos + 1; + }, + + // Uploads a file in multiple, sequential requests + // by splitting the file up in multiple blob chunks. + // If the second parameter is true, only tests if the file + // should be uploaded in chunks, but does not invoke any + // upload requests: + _chunkedUpload: function (options, testOnly) { + options.uploadedBytes = options.uploadedBytes || 0; + var that = this, + file = options.files[0], + fs = file.size, + ub = options.uploadedBytes, + mcs = options.maxChunkSize || fs, + slice = this._blobSlice, + dfd = $.Deferred(), + promise = dfd.promise(), + jqXHR, + upload; + if (!(this._isXHRUpload(options) && slice && (ub || mcs < fs)) || + options.data) { + return false; + } + if (testOnly) { + return true; + } + if (ub >= fs) { + file.error = options.i18n('uploadedBytes'); + return this._getXHRPromise( + false, + options.context, + [null, 'error', file.error] + ); + } + // The chunk upload method: + upload = function () { + // Clone the options object for each chunk upload: + var o = $.extend({}, options), + currentLoaded = o._progress.loaded; + o.blob = slice.call( + file, + ub, + ub + mcs, + file.type + ); + // Store the current chunk size, as the blob itself + // will be dereferenced after data processing: + o.chunkSize = o.blob.size; + // Expose the chunk bytes position range: + o.contentRange = 'bytes ' + ub + '-' + + (ub + o.chunkSize - 1) + '/' + fs; + // Process the upload data (the blob and potential form data): + that._initXHRData(o); + // Add progress listeners for this chunk upload: + that._initProgressListener(o); + jqXHR = ((that._trigger('chunksend', null, o) !== false && $.ajax(o)) || + that._getXHRPromise(false, o.context)) + .done(function (result, textStatus, jqXHR) { + ub = that._getUploadedBytes(jqXHR) || + (ub + o.chunkSize); + // Create a progress event if no final progress event + // with loaded equaling total has been triggered + // for this chunk: + if (currentLoaded + o.chunkSize - o._progress.loaded) { + that._onProgress($.Event('progress', { + lengthComputable: true, + loaded: ub - o.uploadedBytes, + total: ub - o.uploadedBytes + }), o); + } + options.uploadedBytes = o.uploadedBytes = ub; + o.result = result; + o.textStatus = textStatus; + o.jqXHR = jqXHR; + that._trigger('chunkdone', null, o); + that._trigger('chunkalways', null, o); + if (ub < fs) { + // File upload not yet complete, + // continue with the next chunk: + upload(); + } else { + dfd.resolveWith( + o.context, + [result, textStatus, jqXHR] + ); + } + }) + .fail(function (jqXHR, textStatus, errorThrown) { + o.jqXHR = jqXHR; + o.textStatus = textStatus; + o.errorThrown = errorThrown; + that._trigger('chunkfail', null, o); + that._trigger('chunkalways', null, o); + dfd.rejectWith( + o.context, + [jqXHR, textStatus, errorThrown] + ); + }); + }; + this._enhancePromise(promise); + promise.abort = function () { + return jqXHR.abort(); + }; + upload(); + return promise; + }, + + _beforeSend: function (e, data) { + if (this._active === 0) { + // the start callback is triggered when an upload starts + // and no other uploads are currently running, + // equivalent to the global ajaxStart event: + this._trigger('start'); + // Set timer for global bitrate progress calculation: + this._bitrateTimer = new this._BitrateTimer(); + // Reset the global progress values: + this._progress.loaded = this._progress.total = 0; + this._progress.bitrate = 0; + } + // Make sure the container objects for the .response() and + // .progress() methods on the data object are available + // and reset to their initial state: + this._initResponseObject(data); + this._initProgressObject(data); + data._progress.loaded = data.loaded = data.uploadedBytes || 0; + data._progress.total = data.total = this._getTotal(data.files) || 1; + data._progress.bitrate = data.bitrate = 0; + this._active += 1; + // Initialize the global progress values: + this._progress.loaded += data.loaded; + this._progress.total += data.total; + }, + + _onDone: function (result, textStatus, jqXHR, options) { + var total = options._progress.total, + response = options._response; + if (options._progress.loaded < total) { + // Create a progress event if no final progress event + // with loaded equaling total has been triggered: + this._onProgress($.Event('progress', { + lengthComputable: true, + loaded: total, + total: total + }), options); + } + response.result = options.result = result; + response.textStatus = options.textStatus = textStatus; + response.jqXHR = options.jqXHR = jqXHR; + this._trigger('done', null, options); + }, + + _onFail: function (jqXHR, textStatus, errorThrown, options) { + var response = options._response; + if (options.recalculateProgress) { + // Remove the failed (error or abort) file upload from + // the global progress calculation: + this._progress.loaded -= options._progress.loaded; + this._progress.total -= options._progress.total; + } + response.jqXHR = options.jqXHR = jqXHR; + response.textStatus = options.textStatus = textStatus; + response.errorThrown = options.errorThrown = errorThrown; + this._trigger('fail', null, options); + }, + + _onAlways: function (jqXHRorResult, textStatus, jqXHRorError, options) { + // jqXHRorResult, textStatus and jqXHRorError are added to the + // options object via done and fail callbacks + this._trigger('always', null, options); + }, + + _onSend: function (e, data) { + if (!data.submit) { + this._addConvenienceMethods(e, data); + } + var that = this, + jqXHR, + aborted, + slot, + pipe, + options = that._getAJAXSettings(data), + send = function () { + that._sending += 1; + // Set timer for bitrate progress calculation: + options._bitrateTimer = new that._BitrateTimer(); + jqXHR = jqXHR || ( + ((aborted || that._trigger( + 'send', + $.Event('send', {delegatedEvent: e}), + options + ) === false) && + that._getXHRPromise(false, options.context, aborted)) || + that._chunkedUpload(options) || $.ajax(options) + ).done(function (result, textStatus, jqXHR) { + that._onDone(result, textStatus, jqXHR, options); + }).fail(function (jqXHR, textStatus, errorThrown) { + that._onFail(jqXHR, textStatus, errorThrown, options); + }).always(function (jqXHRorResult, textStatus, jqXHRorError) { + that._onAlways( + jqXHRorResult, + textStatus, + jqXHRorError, + options + ); + that._sending -= 1; + that._active -= 1; + if (options.limitConcurrentUploads && + options.limitConcurrentUploads > that._sending) { + // Start the next queued upload, + // that has not been aborted: + var nextSlot = that._slots.shift(); + while (nextSlot) { + if (that._getDeferredState(nextSlot) === 'pending') { + nextSlot.resolve(); + break; + } + nextSlot = that._slots.shift(); + } + } + if (that._active === 0) { + // The stop callback is triggered when all uploads have + // been completed, equivalent to the global ajaxStop event: + that._trigger('stop'); + } + }); + return jqXHR; + }; + this._beforeSend(e, options); + if (this.options.sequentialUploads || + (this.options.limitConcurrentUploads && + this.options.limitConcurrentUploads <= this._sending)) { + if (this.options.limitConcurrentUploads > 1) { + slot = $.Deferred(); + this._slots.push(slot); + pipe = slot.pipe(send); + } else { + this._sequence = this._sequence.pipe(send, send); + pipe = this._sequence; + } + // Return the piped Promise object, enhanced with an abort method, + // which is delegated to the jqXHR object of the current upload, + // and jqXHR callbacks mapped to the equivalent Promise methods: + pipe.abort = function () { + aborted = [undefined, 'abort', 'abort']; + if (!jqXHR) { + if (slot) { + slot.rejectWith(options.context, aborted); + } + return send(); + } + return jqXHR.abort(); + }; + return this._enhancePromise(pipe); + } + return send(); + }, + + _onAdd: function (e, data) { + var that = this, + result = true, + options = $.extend({}, this.options, data), + files = data.files, + filesLength = files.length, + limit = options.limitMultiFileUploads, + limitSize = options.limitMultiFileUploadSize, + overhead = options.limitMultiFileUploadSizeOverhead, + batchSize = 0, + paramName = this._getParamName(options), + paramNameSet, + paramNameSlice, + fileSet, + i, + j = 0; + if (!filesLength) { + return false; + } + if (limitSize && files[0].size === undefined) { + limitSize = undefined; + } + if (!(options.singleFileUploads || limit || limitSize) || + !this._isXHRUpload(options)) { + fileSet = [files]; + paramNameSet = [paramName]; + } else if (!(options.singleFileUploads || limitSize) && limit) { + fileSet = []; + paramNameSet = []; + for (i = 0; i < filesLength; i += limit) { + fileSet.push(files.slice(i, i + limit)); + paramNameSlice = paramName.slice(i, i + limit); + if (!paramNameSlice.length) { + paramNameSlice = paramName; + } + paramNameSet.push(paramNameSlice); + } + } else if (!options.singleFileUploads && limitSize) { + fileSet = []; + paramNameSet = []; + for (i = 0; i < filesLength; i = i + 1) { + batchSize += files[i].size + overhead; + if (i + 1 === filesLength || + ((batchSize + files[i + 1].size + overhead) > limitSize) || + (limit && i + 1 - j >= limit)) { + fileSet.push(files.slice(j, i + 1)); + paramNameSlice = paramName.slice(j, i + 1); + if (!paramNameSlice.length) { + paramNameSlice = paramName; + } + paramNameSet.push(paramNameSlice); + j = i + 1; + batchSize = 0; + } + } + } else { + paramNameSet = paramName; + } + data.originalFiles = files; + $.each(fileSet || files, function (index, element) { + var newData = $.extend({}, data); + newData.files = fileSet ? element : [element]; + newData.paramName = paramNameSet[index]; + that._initResponseObject(newData); + that._initProgressObject(newData); + that._addConvenienceMethods(e, newData); + result = that._trigger( + 'add', + $.Event('add', {delegatedEvent: e}), + newData + ); + return result; + }); + return result; + }, + + _replaceFileInput: function (data) { + var input = data.fileInput, + inputClone = input.clone(true), + restoreFocus = input.is(document.activeElement); + // Add a reference for the new cloned file input to the data argument: + data.fileInputClone = inputClone; + $('
').append(inputClone)[0].reset(); + // Detaching allows to insert the fileInput on another form + // without loosing the file input value: + input.after(inputClone).detach(); + // If the fileInput had focus before it was detached, + // restore focus to the inputClone. + if (restoreFocus) { + inputClone.focus(); + } + // Avoid memory leaks with the detached file input: + $.cleanData(input.unbind('remove')); + // Replace the original file input element in the fileInput + // elements set with the clone, which has been copied including + // event handlers: + this.options.fileInput = this.options.fileInput.map(function (i, el) { + if (el === input[0]) { + return inputClone[0]; + } + return el; + }); + // If the widget has been initialized on the file input itself, + // override this.element with the file input clone: + if (input[0] === this.element[0]) { + this.element = inputClone; + } + }, + + _handleFileTreeEntry: function (entry, path) { + var that = this, + dfd = $.Deferred(), + errorHandler = function (e) { + if (e && !e.entry) { + e.entry = entry; + } + // Since $.when returns immediately if one + // Deferred is rejected, we use resolve instead. + // This allows valid files and invalid items + // to be returned together in one set: + dfd.resolve([e]); + }, + successHandler = function (entries) { + that._handleFileTreeEntries( + entries, + path + entry.name + '/' + ).done(function (files) { + dfd.resolve(files); + }).fail(errorHandler); + }, + readEntries = function () { + dirReader.readEntries(function (results) { + if (!results.length) { + successHandler(entries); + } else { + entries = entries.concat(results); + readEntries(); + } + }, errorHandler); + }, + dirReader, entries = []; + path = path || ''; + if (entry.isFile) { + if (entry._file) { + // Workaround for Chrome bug #149735 + entry._file.relativePath = path; + dfd.resolve(entry._file); + } else { + entry.file(function (file) { + file.relativePath = path; + dfd.resolve(file); + }, errorHandler); + } + } else if (entry.isDirectory) { + dirReader = entry.createReader(); + readEntries(); + } else { + // Return an empy list for file system items + // other than files or directories: + dfd.resolve([]); + } + return dfd.promise(); + }, + + _handleFileTreeEntries: function (entries, path) { + var that = this; + return $.when.apply( + $, + $.map(entries, function (entry) { + return that._handleFileTreeEntry(entry, path); + }) + ).pipe(function () { + return Array.prototype.concat.apply( + [], + arguments + ); + }); + }, + + _getDroppedFiles: function (dataTransfer) { + dataTransfer = dataTransfer || {}; + var items = dataTransfer.items; + if (items && items.length && (items[0].webkitGetAsEntry || + items[0].getAsEntry)) { + return this._handleFileTreeEntries( + $.map(items, function (item) { + var entry; + if (item.webkitGetAsEntry) { + entry = item.webkitGetAsEntry(); + if (entry) { + // Workaround for Chrome bug #149735: + entry._file = item.getAsFile(); + } + return entry; + } + return item.getAsEntry(); + }) + ); + } + return $.Deferred().resolve( + $.makeArray(dataTransfer.files) + ).promise(); + }, + + _getSingleFileInputFiles: function (fileInput) { + fileInput = $(fileInput); + var entries = fileInput.prop('webkitEntries') || + fileInput.prop('entries'), + files, + value; + if (entries && entries.length) { + return this._handleFileTreeEntries(entries); + } + files = $.makeArray(fileInput.prop('files')); + if (!files.length) { + value = fileInput.prop('value'); + if (!value) { + return $.Deferred().resolve([]).promise(); + } + // If the files property is not available, the browser does not + // support the File API and we add a pseudo File object with + // the input value as name with path information removed: + files = [{name: value.replace(/^.*\\/, '')}]; + } else if (files[0].name === undefined && files[0].fileName) { + // File normalization for Safari 4 and Firefox 3: + $.each(files, function (index, file) { + file.name = file.fileName; + file.size = file.fileSize; + }); + } + return $.Deferred().resolve(files).promise(); + }, + + _getFileInputFiles: function (fileInput) { + if (!(fileInput instanceof $) || fileInput.length === 1) { + return this._getSingleFileInputFiles(fileInput); + } + return $.when.apply( + $, + $.map(fileInput, this._getSingleFileInputFiles) + ).pipe(function () { + return Array.prototype.concat.apply( + [], + arguments + ); + }); + }, + + _onChange: function (e) { + var that = this, + data = { + fileInput: $(e.target), + form: $(e.target.form) + }; + this._getFileInputFiles(data.fileInput).always(function (files) { + data.files = files; + if (that.options.replaceFileInput) { + that._replaceFileInput(data); + } + if (that._trigger( + 'change', + $.Event('change', {delegatedEvent: e}), + data + ) !== false) { + that._onAdd(e, data); + } + }); + }, + + _onPaste: function (e) { + var items = e.originalEvent && e.originalEvent.clipboardData && + e.originalEvent.clipboardData.items, + data = {files: []}; + if (items && items.length) { + $.each(items, function (index, item) { + var file = item.getAsFile && item.getAsFile(); + if (file) { + data.files.push(file); + } + }); + if (this._trigger( + 'paste', + $.Event('paste', {delegatedEvent: e}), + data + ) !== false) { + this._onAdd(e, data); + } + } + }, + + _onDrop: function (e) { + e.dataTransfer = e.originalEvent && e.originalEvent.dataTransfer; + var that = this, + dataTransfer = e.dataTransfer, + data = {}; + if (dataTransfer && dataTransfer.files && dataTransfer.files.length) { + e.preventDefault(); + this._getDroppedFiles(dataTransfer).always(function (files) { + data.files = files; + if (that._trigger( + 'drop', + $.Event('drop', {delegatedEvent: e}), + data + ) !== false) { + that._onAdd(e, data); + } + }); + } + }, + + _onDragOver: getDragHandler('dragover'), + + _onDragEnter: getDragHandler('dragenter'), + + _onDragLeave: getDragHandler('dragleave'), + + _initEventHandlers: function () { + if (this._isXHRUpload(this.options)) { + this._on(this.options.dropZone, { + dragover: this._onDragOver, + drop: this._onDrop, + // event.preventDefault() on dragenter is required for IE10+: + dragenter: this._onDragEnter, + // dragleave is not required, but added for completeness: + dragleave: this._onDragLeave + }); + this._on(this.options.pasteZone, { + paste: this._onPaste + }); + } + if ($.support.fileInput) { + this._on(this.options.fileInput, { + change: this._onChange + }); + } + }, + + _destroyEventHandlers: function () { + this._off(this.options.dropZone, 'dragenter dragleave dragover drop'); + this._off(this.options.pasteZone, 'paste'); + this._off(this.options.fileInput, 'change'); + }, + + _setOption: function (key, value) { + var reinit = $.inArray(key, this._specialOptions) !== -1; + if (reinit) { + this._destroyEventHandlers(); + } + this._super(key, value); + if (reinit) { + this._initSpecialOptions(); + this._initEventHandlers(); + } + }, + + _initSpecialOptions: function () { + var options = this.options; + if (options.fileInput === undefined) { + options.fileInput = this.element.is('input[type="file"]') ? + this.element : this.element.find('input[type="file"]'); + } else if (!(options.fileInput instanceof $)) { + options.fileInput = $(options.fileInput); + } + if (!(options.dropZone instanceof $)) { + options.dropZone = $(options.dropZone); + } + if (!(options.pasteZone instanceof $)) { + options.pasteZone = $(options.pasteZone); + } + }, + + _getRegExp: function (str) { + var parts = str.split('/'), + modifiers = parts.pop(); + parts.shift(); + return new RegExp(parts.join('/'), modifiers); + }, + + _isRegExpOption: function (key, value) { + return key !== 'url' && $.type(value) === 'string' && + /^\/.*\/[igm]{0,3}$/.test(value); + }, + + _initDataAttributes: function () { + var that = this, + options = this.options, + data = this.element.data(); + // Initialize options set via HTML5 data-attributes: + $.each( + this.element[0].attributes, + function (index, attr) { + var key = attr.name.toLowerCase(), + value; + if (/^data-/.test(key)) { + // Convert hyphen-ated key to camelCase: + key = key.slice(5).replace(/-[a-z]/g, function (str) { + return str.charAt(1).toUpperCase(); + }); + value = data[key]; + if (that._isRegExpOption(key, value)) { + value = that._getRegExp(value); + } + options[key] = value; + } + } + ); + }, + + _create: function () { + this._initDataAttributes(); + this._initSpecialOptions(); + this._slots = []; + this._sequence = this._getXHRPromise(true); + this._sending = this._active = 0; + this._initProgressObject(this); + this._initEventHandlers(); + }, + + // This method is exposed to the widget API and allows to query + // the number of active uploads: + active: function () { + return this._active; + }, + + // This method is exposed to the widget API and allows to query + // the widget upload progress. + // It returns an object with loaded, total and bitrate properties + // for the running uploads: + progress: function () { + return this._progress; + }, + + // This method is exposed to the widget API and allows adding files + // using the fileupload API. The data parameter accepts an object which + // must have a files property and can contain additional options: + // .fileupload('add', {files: filesList}); + add: function (data) { + var that = this; + if (!data || this.options.disabled) { + return; + } + if (data.fileInput && !data.files) { + this._getFileInputFiles(data.fileInput).always(function (files) { + data.files = files; + that._onAdd(null, data); + }); + } else { + data.files = $.makeArray(data.files); + this._onAdd(null, data); + } + }, + + // This method is exposed to the widget API and allows sending files + // using the fileupload API. The data parameter accepts an object which + // must have a files or fileInput property and can contain additional options: + // .fileupload('send', {files: filesList}); + // The method returns a Promise object for the file upload call. + send: function (data) { + if (data && !this.options.disabled) { + if (data.fileInput && !data.files) { + var that = this, + dfd = $.Deferred(), + promise = dfd.promise(), + jqXHR, + aborted; + promise.abort = function () { + aborted = true; + if (jqXHR) { + return jqXHR.abort(); + } + dfd.reject(null, 'abort', 'abort'); + return promise; + }; + this._getFileInputFiles(data.fileInput).always( + function (files) { + if (aborted) { + return; + } + if (!files.length) { + dfd.reject(); + return; + } + data.files = files; + jqXHR = that._onSend(null, data); + jqXHR.then( + function (result, textStatus, jqXHR) { + dfd.resolve(result, textStatus, jqXHR); + }, + function (jqXHR, textStatus, errorThrown) { + dfd.reject(jqXHR, textStatus, errorThrown); + } + ); + } + ); + return this._enhancePromise(promise); + } + data.files = $.makeArray(data.files); + if (data.files.length) { + return this._onSend(null, data); + } + } + return this._getXHRPromise(false, data && data.context); + } + + }); + +})); + +/* + * jQuery File Upload Processing Plugin + * https://github.com/blueimp/jQuery-File-Upload + * + * Copyright 2012, Sebastian Tschan + * https://blueimp.net + * + * Licensed under the MIT license: + * http://www.opensource.org/licenses/MIT + */ + +/* jshint nomen:false */ +/* global define, require, window */ + +(function (factory) { + 'use strict'; + if (typeof define === 'function' && define.amd) { + // Register as an anonymous AMD module: + define([ + 'jquery', + './jquery.fileupload' + ], factory); + } else if (typeof exports === 'object') { + // Node/CommonJS: + factory(require('jquery')); + } else { + // Browser globals: + factory( + window.jQuery + ); + } +}(function ($) { + 'use strict'; + + var originalAdd = $.blueimp.fileupload.prototype.options.add; + + // The File Upload Processing plugin extends the fileupload widget + // with file processing functionality: + $.widget('blueimp.fileupload', $.blueimp.fileupload, { + + options: { + // The list of processing actions: + processQueue: [ + /* + { + action: 'log', + type: 'debug' + } + */ + ], + add: function (e, data) { + var $this = $(this); + data.process(function () { + return $this.fileupload('process', data); + }); + originalAdd.call(this, e, data); + } + }, + + processActions: { + /* + log: function (data, options) { + console[options.type]( + 'Processing "' + data.files[data.index].name + '"' + ); + } + */ + }, + + _processFile: function (data, originalData) { + var that = this, + dfd = $.Deferred().resolveWith(that, [data]), + chain = dfd.promise(); + this._trigger('process', null, data); + $.each(data.processQueue, function (i, settings) { + var func = function (data) { + if (originalData.errorThrown) { + return $.Deferred() + .rejectWith(that, [originalData]).promise(); + } + return that.processActions[settings.action].call( + that, + data, + settings + ); + }; + chain = chain.pipe(func, settings.always && func); + }); + chain + .done(function () { + that._trigger('processdone', null, data); + that._trigger('processalways', null, data); + }) + .fail(function () { + that._trigger('processfail', null, data); + that._trigger('processalways', null, data); + }); + return chain; + }, + + // Replaces the settings of each processQueue item that + // are strings starting with an "@", using the remaining + // substring as key for the option map, + // e.g. "@autoUpload" is replaced with options.autoUpload: + _transformProcessQueue: function (options) { + var processQueue = []; + $.each(options.processQueue, function () { + var settings = {}, + action = this.action, + prefix = this.prefix === true ? action : this.prefix; + $.each(this, function (key, value) { + if ($.type(value) === 'string' && + value.charAt(0) === '@') { + settings[key] = options[ + value.slice(1) || (prefix ? prefix + + key.charAt(0).toUpperCase() + key.slice(1) : key) + ]; + } else { + settings[key] = value; + } + + }); + processQueue.push(settings); + }); + options.processQueue = processQueue; + }, + + // Returns the number of files currently in the processsing queue: + processing: function () { + return this._processing; + }, + + // Processes the files given as files property of the data parameter, + // returns a Promise object that allows to bind callbacks: + process: function (data) { + var that = this, + options = $.extend({}, this.options, data); + if (options.processQueue && options.processQueue.length) { + this._transformProcessQueue(options); + if (this._processing === 0) { + this._trigger('processstart'); + } + $.each(data.files, function (index) { + var opts = index ? $.extend({}, options) : options, + func = function () { + if (data.errorThrown) { + return $.Deferred() + .rejectWith(that, [data]).promise(); + } + return that._processFile(opts, data); + }; + opts.index = index; + that._processing += 1; + that._processingQueue = that._processingQueue.pipe(func, func) + .always(function () { + that._processing -= 1; + if (that._processing === 0) { + that._trigger('processstop'); + } + }); + }); + } + return this._processingQueue; + }, + + _create: function () { + this._super(); + this._processing = 0; + this._processingQueue = $.Deferred().resolveWith(this) + .promise(); + } + + }); + +})); + +/* + * jQuery File Upload Image Preview & Resize Plugin + * https://github.com/blueimp/jQuery-File-Upload + * + * Copyright 2013, Sebastian Tschan + * https://blueimp.net + * + * Licensed under the MIT license: + * http://www.opensource.org/licenses/MIT + */ + +/* jshint nomen:false */ +/* global define, require, window, Blob */ + +(function (factory) { + 'use strict'; + if (typeof define === 'function' && define.amd) { + // Register as an anonymous AMD module: + define([ + 'jquery', + 'load-image', + 'load-image-meta', + 'load-image-exif', + 'canvas-to-blob', + './jquery.fileupload-process' + ], factory); + } else if (typeof exports === 'object') { + // Node/CommonJS: + factory( + require('jquery'), + require('blueimp-load-image/js/load-image'), + require('blueimp-load-image/js/load-image-meta'), + require('blueimp-load-image/js/load-image-exif'), + require('blueimp-canvas-to-blob'), + require('./jquery.fileupload-process') + ); + } else { + // Browser globals: + factory( + window.jQuery, + window.loadImage + ); + } +}(function ($, loadImage) { + 'use strict'; + + // Prepend to the default processQueue: + $.blueimp.fileupload.prototype.options.processQueue.unshift( + { + action: 'loadImageMetaData', + disableImageHead: '@', + disableExif: '@', + disableExifThumbnail: '@', + disableExifSub: '@', + disableExifGps: '@', + disabled: '@disableImageMetaDataLoad' + }, + { + action: 'loadImage', + // Use the action as prefix for the "@" options: + prefix: true, + fileTypes: '@', + maxFileSize: '@', + noRevoke: '@', + disabled: '@disableImageLoad' + }, + { + action: 'resizeImage', + // Use "image" as prefix for the "@" options: + prefix: 'image', + maxWidth: '@', + maxHeight: '@', + minWidth: '@', + minHeight: '@', + crop: '@', + orientation: '@', + forceResize: '@', + disabled: '@disableImageResize' + }, + { + action: 'saveImage', + quality: '@imageQuality', + type: '@imageType', + disabled: '@disableImageResize' + }, + { + action: 'saveImageMetaData', + disabled: '@disableImageMetaDataSave' + }, + { + action: 'resizeImage', + // Use "preview" as prefix for the "@" options: + prefix: 'preview', + maxWidth: '@', + maxHeight: '@', + minWidth: '@', + minHeight: '@', + crop: '@', + orientation: '@', + thumbnail: '@', + canvas: '@', + disabled: '@disableImagePreview' + }, + { + action: 'setImage', + name: '@imagePreviewName', + disabled: '@disableImagePreview' + }, + { + action: 'deleteImageReferences', + disabled: '@disableImageReferencesDeletion' + } + ); + + // The File Upload Resize plugin extends the fileupload widget + // with image resize functionality: + $.widget('blueimp.fileupload', $.blueimp.fileupload, { + + options: { + // The regular expression for the types of images to load: + // matched against the file type: + loadImageFileTypes: /^image\/(gif|jpeg|png|svg\+xml)$/, + // The maximum file size of images to load: + loadImageMaxFileSize: 10000000, // 10MB + // The maximum width of resized images: + imageMaxWidth: 1920, + // The maximum height of resized images: + imageMaxHeight: 1080, + // Defines the image orientation (1-8) or takes the orientation + // value from Exif data if set to true: + imageOrientation: false, + // Define if resized images should be cropped or only scaled: + imageCrop: false, + // Disable the resize image functionality by default: + disableImageResize: true, + // The maximum width of the preview images: + previewMaxWidth: 80, + // The maximum height of the preview images: + previewMaxHeight: 80, + // Defines the preview orientation (1-8) or takes the orientation + // value from Exif data if set to true: + previewOrientation: true, + // Create the preview using the Exif data thumbnail: + previewThumbnail: true, + // Define if preview images should be cropped or only scaled: + previewCrop: false, + // Define if preview images should be resized as canvas elements: + previewCanvas: true + }, + + processActions: { + + // Loads the image given via data.files and data.index + // as img element, if the browser supports the File API. + // Accepts the options fileTypes (regular expression) + // and maxFileSize (integer) to limit the files to load: + loadImage: function (data, options) { + if (options.disabled) { + return data; + } + var that = this, + file = data.files[data.index], + dfd = $.Deferred(); + if (($.type(options.maxFileSize) === 'number' && + file.size > options.maxFileSize) || + (options.fileTypes && + !options.fileTypes.test(file.type)) || + !loadImage( + file, + function (img) { + if (img.src) { + data.img = img; + } + dfd.resolveWith(that, [data]); + }, + options + )) { + return data; + } + return dfd.promise(); + }, + + // Resizes the image given as data.canvas or data.img + // and updates data.canvas or data.img with the resized image. + // Also stores the resized image as preview property. + // Accepts the options maxWidth, maxHeight, minWidth, + // minHeight, canvas and crop: + resizeImage: function (data, options) { + if (options.disabled || !(data.canvas || data.img)) { + return data; + } + options = $.extend({canvas: true}, options); + var that = this, + dfd = $.Deferred(), + img = (options.canvas && data.canvas) || data.img, + resolve = function (newImg) { + if (newImg && (newImg.width !== img.width || + newImg.height !== img.height || + options.forceResize)) { + data[newImg.getContext ? 'canvas' : 'img'] = newImg; + } + data.preview = newImg; + dfd.resolveWith(that, [data]); + }, + thumbnail; + if (data.exif) { + if (options.orientation === true) { + options.orientation = data.exif.get('Orientation'); + } + if (options.thumbnail) { + thumbnail = data.exif.get('Thumbnail'); + if (thumbnail) { + loadImage(thumbnail, resolve, options); + return dfd.promise(); + } + } + // Prevent orienting the same image twice: + if (data.orientation) { + delete options.orientation; + } else { + data.orientation = options.orientation; + } + } + if (img) { + resolve(loadImage.scale(img, options)); + return dfd.promise(); + } + return data; + }, + + // Saves the processed image given as data.canvas + // inplace at data.index of data.files: + saveImage: function (data, options) { + if (!data.canvas || options.disabled) { + return data; + } + var that = this, + file = data.files[data.index], + dfd = $.Deferred(); + if (data.canvas.toBlob) { + data.canvas.toBlob( + function (blob) { + if (!blob.name) { + if (file.type === blob.type) { + blob.name = file.name; + } else if (file.name) { + blob.name = file.name.replace( + /\.\w+$/, + '.' + blob.type.substr(6) + ); + } + } + // Don't restore invalid meta data: + if (file.type !== blob.type) { + delete data.imageHead; + } + // Store the created blob at the position + // of the original file in the files list: + data.files[data.index] = blob; + dfd.resolveWith(that, [data]); + }, + options.type || file.type, + options.quality + ); + } else { + return data; + } + return dfd.promise(); + }, + + loadImageMetaData: function (data, options) { + if (options.disabled) { + return data; + } + var that = this, + dfd = $.Deferred(); + loadImage.parseMetaData(data.files[data.index], function (result) { + $.extend(data, result); + dfd.resolveWith(that, [data]); + }, options); + return dfd.promise(); + }, + + saveImageMetaData: function (data, options) { + if (!(data.imageHead && data.canvas && + data.canvas.toBlob && !options.disabled)) { + return data; + } + var file = data.files[data.index], + blob = new Blob([ + data.imageHead, + // Resized images always have a head size of 20 bytes, + // including the JPEG marker and a minimal JFIF header: + this._blobSlice.call(file, 20) + ], {type: file.type}); + blob.name = file.name; + data.files[data.index] = blob; + return data; + }, + + // Sets the resized version of the image as a property of the + // file object, must be called after "saveImage": + setImage: function (data, options) { + if (data.preview && !options.disabled) { + data.files[data.index][options.name || 'preview'] = data.preview; + } + return data; + }, + + deleteImageReferences: function (data, options) { + if (!options.disabled) { + delete data.img; + delete data.canvas; + delete data.preview; + delete data.imageHead; + } + return data; + } + + } + + }); + +})); + +/* + * jQuery File Upload Audio Preview Plugin + * https://github.com/blueimp/jQuery-File-Upload + * + * Copyright 2013, Sebastian Tschan + * https://blueimp.net + * + * Licensed under the MIT license: + * http://www.opensource.org/licenses/MIT + */ + +/* jshint nomen:false */ +/* global define, require, window, document */ + +(function (factory) { + 'use strict'; + if (typeof define === 'function' && define.amd) { + // Register as an anonymous AMD module: + define([ + 'jquery', + 'load-image', + './jquery.fileupload-process' + ], factory); + } else if (typeof exports === 'object') { + // Node/CommonJS: + factory( + require('jquery'), + require('load-image') + ); + } else { + // Browser globals: + factory( + window.jQuery, + window.loadImage + ); + } +}(function ($, loadImage) { + 'use strict'; + + // Prepend to the default processQueue: + $.blueimp.fileupload.prototype.options.processQueue.unshift( + { + action: 'loadAudio', + // Use the action as prefix for the "@" options: + prefix: true, + fileTypes: '@', + maxFileSize: '@', + disabled: '@disableAudioPreview' + }, + { + action: 'setAudio', + name: '@audioPreviewName', + disabled: '@disableAudioPreview' + } + ); + + // The File Upload Audio Preview plugin extends the fileupload widget + // with audio preview functionality: + $.widget('blueimp.fileupload', $.blueimp.fileupload, { + + options: { + // The regular expression for the types of audio files to load, + // matched against the file type: + loadAudioFileTypes: /^audio\/.*$/ + }, + + _audioElement: document.createElement('audio'), + + processActions: { + + // Loads the audio file given via data.files and data.index + // as audio element if the browser supports playing it. + // Accepts the options fileTypes (regular expression) + // and maxFileSize (integer) to limit the files to load: + loadAudio: function (data, options) { + if (options.disabled) { + return data; + } + var file = data.files[data.index], + url, + audio; + if (this._audioElement.canPlayType && + this._audioElement.canPlayType(file.type) && + ($.type(options.maxFileSize) !== 'number' || + file.size <= options.maxFileSize) && + (!options.fileTypes || + options.fileTypes.test(file.type))) { + url = loadImage.createObjectURL(file); + if (url) { + audio = this._audioElement.cloneNode(false); + audio.src = url; + audio.controls = true; + data.audio = audio; + return data; + } + } + return data; + }, + + // Sets the audio element as a property of the file object: + setAudio: function (data, options) { + if (data.audio && !options.disabled) { + data.files[data.index][options.name || 'preview'] = data.audio; + } + return data; + } + + } + + }); + +})); + +/* + * jQuery File Upload Video Preview Plugin + * https://github.com/blueimp/jQuery-File-Upload + * + * Copyright 2013, Sebastian Tschan + * https://blueimp.net + * + * Licensed under the MIT license: + * http://www.opensource.org/licenses/MIT + */ + +/* jshint nomen:false */ +/* global define, require, window, document */ + +(function (factory) { + 'use strict'; + if (typeof define === 'function' && define.amd) { + // Register as an anonymous AMD module: + define([ + 'jquery', + 'load-image', + './jquery.fileupload-process' + ], factory); + } else if (typeof exports === 'object') { + // Node/CommonJS: + factory( + require('jquery'), + require('load-image') + ); + } else { + // Browser globals: + factory( + window.jQuery, + window.loadImage + ); + } +}(function ($, loadImage) { + 'use strict'; + + // Prepend to the default processQueue: + $.blueimp.fileupload.prototype.options.processQueue.unshift( + { + action: 'loadVideo', + // Use the action as prefix for the "@" options: + prefix: true, + fileTypes: '@', + maxFileSize: '@', + disabled: '@disableVideoPreview' + }, + { + action: 'setVideo', + name: '@videoPreviewName', + disabled: '@disableVideoPreview' + } + ); + + // The File Upload Video Preview plugin extends the fileupload widget + // with video preview functionality: + $.widget('blueimp.fileupload', $.blueimp.fileupload, { + + options: { + // The regular expression for the types of video files to load, + // matched against the file type: + loadVideoFileTypes: /^video\/.*$/ + }, + + _videoElement: document.createElement('video'), + + processActions: { + + // Loads the video file given via data.files and data.index + // as video element if the browser supports playing it. + // Accepts the options fileTypes (regular expression) + // and maxFileSize (integer) to limit the files to load: + loadVideo: function (data, options) { + if (options.disabled) { + return data; + } + var file = data.files[data.index], + url, + video; + if (this._videoElement.canPlayType && + this._videoElement.canPlayType(file.type) && + ($.type(options.maxFileSize) !== 'number' || + file.size <= options.maxFileSize) && + (!options.fileTypes || + options.fileTypes.test(file.type))) { + url = loadImage.createObjectURL(file); + if (url) { + video = this._videoElement.cloneNode(false); + video.src = url; + video.controls = true; + data.video = video; + return data; + } + } + return data; + }, + + // Sets the video element as a property of the file object: + setVideo: function (data, options) { + if (data.video && !options.disabled) { + data.files[data.index][options.name || 'preview'] = data.video; + } + return data; + } + + } + + }); + +})); + +/* + * jQuery File Upload Validation Plugin + * https://github.com/blueimp/jQuery-File-Upload + * + * Copyright 2013, Sebastian Tschan + * https://blueimp.net + * + * Licensed under the MIT license: + * http://www.opensource.org/licenses/MIT + */ + +/* global define, require, window */ + +(function (factory) { + 'use strict'; + if (typeof define === 'function' && define.amd) { + // Register as an anonymous AMD module: + define([ + 'jquery', + './jquery.fileupload-process' + ], factory); + } else if (typeof exports === 'object') { + // Node/CommonJS: + factory(require('jquery')); + } else { + // Browser globals: + factory( + window.jQuery + ); + } +}(function ($) { + 'use strict'; + + // Append to the default processQueue: + $.blueimp.fileupload.prototype.options.processQueue.push( + { + action: 'validate', + // Always trigger this action, + // even if the previous action was rejected: + always: true, + // Options taken from the global options map: + acceptFileTypes: '@', + maxFileSize: '@', + minFileSize: '@', + maxNumberOfFiles: '@', + disabled: '@disableValidation' + } + ); + + // The File Upload Validation plugin extends the fileupload widget + // with file validation functionality: + $.widget('blueimp.fileupload', $.blueimp.fileupload, { + + options: { + /* + // The regular expression for allowed file types, matches + // against either file type or file name: + acceptFileTypes: /(\.|\/)(gif|jpe?g|png)$/i, + // The maximum allowed file size in bytes: + maxFileSize: 10000000, // 10 MB + // The minimum allowed file size in bytes: + minFileSize: undefined, // No minimal file size + // The limit of files to be uploaded: + maxNumberOfFiles: 10, + */ + + // Function returning the current number of files, + // has to be overriden for maxNumberOfFiles validation: + getNumberOfFiles: $.noop, + + // Error and info messages: + messages: { + maxNumberOfFiles: 'Maximum number of files exceeded', + acceptFileTypes: 'File type not allowed', + maxFileSize: 'File is too large', + minFileSize: 'File is too small' + } + }, + + processActions: { + + validate: function (data, options) { + if (options.disabled) { + return data; + } + var dfd = $.Deferred(), + settings = this.options, + file = data.files[data.index], + fileSize; + if (options.minFileSize || options.maxFileSize) { + fileSize = file.size; + } + if ($.type(options.maxNumberOfFiles) === 'number' && + (settings.getNumberOfFiles() || 0) + data.files.length > + options.maxNumberOfFiles) { + file.error = settings.i18n('maxNumberOfFiles'); + } else if (options.acceptFileTypes && + !(options.acceptFileTypes.test(file.type) || + options.acceptFileTypes.test(file.name))) { + file.error = settings.i18n('acceptFileTypes'); + } else if (fileSize > options.maxFileSize) { + file.error = settings.i18n('maxFileSize'); + } else if ($.type(fileSize) === 'number' && + fileSize < options.minFileSize) { + file.error = settings.i18n('minFileSize'); + } else { + delete file.error; + } + if (file.error || data.files.error) { + data.files.error = true; + dfd.rejectWith(this, [data]); + } else { + dfd.resolveWith(this, [data]); + } + return dfd.promise(); + } + + } + + }); + +})); + +/* + * jQuery File Upload AngularJS Plugin + * https://github.com/blueimp/jQuery-File-Upload + * + * Copyright 2013, Sebastian Tschan + * https://blueimp.net + * + * Licensed under the MIT license: + * http://www.opensource.org/licenses/MIT + */ + +/* jshint nomen:false */ +/* global define, angular */ + +(function (factory) { + 'use strict'; + if (typeof define === 'function' && define.amd) { + // Register as an anonymous AMD module: + define([ + 'jquery', + 'angular', + './jquery.fileupload-image', + './jquery.fileupload-audio', + './jquery.fileupload-video', + './jquery.fileupload-validate' + ], factory); + } else { + factory(); + } +}(function () { + 'use strict'; + + angular.module('blueimp.fileupload', []) + + // The fileUpload service provides configuration options + // for the fileUpload directive and default handlers for + // File Upload events: + .provider('fileUpload', function () { + var scopeEvalAsync = function (expression) { + var scope = angular.element(this) + .fileupload('option', 'scope'); + // Schedule a new $digest cycle if not already inside of one + // and evaluate the given expression: + scope.$evalAsync(expression); + }, + addFileMethods = function (scope, data) { + var files = data.files, + file = files[0]; + angular.forEach(files, function (file, index) { + file._index = index; + file.$state = function () { + return data.state(); + }; + file.$processing = function () { + return data.processing(); + }; + file.$progress = function () { + return data.progress(); + }; + file.$response = function () { + return data.response(); + }; + }); + file.$submit = function () { + if (!file.error) { + return data.submit(); + } + }; + file.$cancel = function () { + return data.abort(); + }; + }, + $config; + $config = this.defaults = { + handleResponse: function (e, data) { + var files = data.result && data.result.files; + if (files) { + data.scope.replace(data.files, files); + } else if (data.errorThrown || + data.textStatus === 'error') { + data.files[0].error = data.errorThrown || + data.textStatus; + } + }, + add: function (e, data) { + if (e.isDefaultPrevented()) { + return false; + } + var scope = data.scope, + filesCopy = []; + angular.forEach(data.files, function (file) { + filesCopy.push(file); + }); + scope.$parent.$applyAsync(function () { + addFileMethods(scope, data); + var method = scope.option('prependFiles') ? + 'unshift' : 'push'; + Array.prototype[method].apply(scope.queue, data.files); + }); + data.process(function () { + return scope.process(data); + }).always(function () { + scope.$parent.$applyAsync(function () { + addFileMethods(scope, data); + scope.replace(filesCopy, data.files); + }); + }).then(function () { + if ((scope.option('autoUpload') || + data.autoUpload) && + data.autoUpload !== false) { + data.submit(); + } + }); + }, + done: function (e, data) { + if (e.isDefaultPrevented()) { + return false; + } + var that = this; + data.scope.$apply(function () { + data.handleResponse.call(that, e, data); + }); + }, + fail: function (e, data) { + if (e.isDefaultPrevented()) { + return false; + } + var that = this, + scope = data.scope; + if (data.errorThrown === 'abort') { + scope.clear(data.files); + return; + } + scope.$apply(function () { + data.handleResponse.call(that, e, data); + }); + }, + stop: scopeEvalAsync, + processstart: scopeEvalAsync, + processstop: scopeEvalAsync, + getNumberOfFiles: function () { + var scope = this.scope; + return scope.queue.length - scope.processing(); + }, + dataType: 'json', + autoUpload: false + }; + this.$get = [ + function () { + return { + defaults: $config + }; + } + ]; + }) + + // Format byte numbers to readable presentations: + .provider('formatFileSizeFilter', function () { + var $config = { + // Byte units following the IEC format + // http://en.wikipedia.org/wiki/Kilobyte + units: [ + {size: 1000000000, suffix: ' GB'}, + {size: 1000000, suffix: ' MB'}, + {size: 1000, suffix: ' KB'} + ] + }; + this.defaults = $config; + this.$get = function () { + return function (bytes) { + if (!angular.isNumber(bytes)) { + return ''; + } + var unit = true, + i = 0, + prefix, + suffix; + while (unit) { + unit = $config.units[i]; + prefix = unit.prefix || ''; + suffix = unit.suffix || ''; + if (i === $config.units.length - 1 || bytes >= unit.size) { + return prefix + (bytes / unit.size).toFixed(2) + suffix; + } + i += 1; + } + }; + }; + }) + + // The FileUploadController initializes the fileupload widget and + // provides scope methods to control the File Upload functionality: + .controller('FileUploadController', [ + '$scope', '$element', '$attrs', '$window', 'fileUpload', + function ($scope, $element, $attrs, $window, fileUpload) { + var uploadMethods = { + progress: function () { + return $element.fileupload('progress'); + }, + active: function () { + return $element.fileupload('active'); + }, + option: function (option, data) { + if (arguments.length === 1) { + return $element.fileupload('option', option); + } + $element.fileupload('option', option, data); + }, + add: function (data) { + return $element.fileupload('add', data); + }, + send: function (data) { + return $element.fileupload('send', data); + }, + process: function (data) { + return $element.fileupload('process', data); + }, + processing: function (data) { + return $element.fileupload('processing', data); + } + }; + $scope.disabled = !$window.jQuery.support.fileInput; + $scope.queue = $scope.queue || []; + $scope.clear = function (files) { + var queue = this.queue, + i = queue.length, + file = files, + length = 1; + if (angular.isArray(files)) { + file = files[0]; + length = files.length; + } + while (i) { + i -= 1; + if (queue[i] === file) { + return queue.splice(i, length); + } + } + }; + $scope.replace = function (oldFiles, newFiles) { + var queue = this.queue, + file = oldFiles[0], + i, + j; + for (i = 0; i < queue.length; i += 1) { + if (queue[i] === file) { + for (j = 0; j < newFiles.length; j += 1) { + queue[i + j] = newFiles[j]; + } + return; + } + } + }; + $scope.applyOnQueue = function (method) { + var list = this.queue.slice(0), + i, + file; + for (i = 0; i < list.length; i += 1) { + file = list[i]; + if (file[method]) { + file[method](); + } + } + }; + $scope.submit = function () { + this.applyOnQueue('$submit'); + }; + $scope.cancel = function () { + this.applyOnQueue('$cancel'); + }; + // Add upload methods to the scope: + angular.extend($scope, uploadMethods); + // The fileupload widget will initialize with + // the options provided via "data-"-parameters, + // as well as those given via options object: + $element.fileupload(angular.extend( + {scope: $scope}, + fileUpload.defaults + )).on('fileuploadadd', function (e, data) { + data.scope = $scope; + }).on('fileuploadfail', function (e, data) { + if (data.errorThrown === 'abort') { + return; + } + if (data.dataType && + data.dataType.indexOf('json') === data.dataType.length - 4) { + try { + data.result = angular.fromJson(data.jqXHR.responseText); + } catch (ignore) {} + } + }).on([ + 'fileuploadadd', + 'fileuploadsubmit', + 'fileuploadsend', + 'fileuploaddone', + 'fileuploadfail', + 'fileuploadalways', + 'fileuploadprogress', + 'fileuploadprogressall', + 'fileuploadstart', + 'fileuploadstop', + 'fileuploadchange', + 'fileuploadpaste', + 'fileuploaddrop', + 'fileuploaddragover', + 'fileuploadchunksend', + 'fileuploadchunkdone', + 'fileuploadchunkfail', + 'fileuploadchunkalways', + 'fileuploadprocessstart', + 'fileuploadprocess', + 'fileuploadprocessdone', + 'fileuploadprocessfail', + 'fileuploadprocessalways', + 'fileuploadprocessstop' + ].join(' '), function (e, data) { + $scope.$parent.$applyAsync(function () { + if ($scope.$emit(e.type, data).defaultPrevented) { + e.preventDefault(); + } + }); + }).on('remove', function () { + // Remove upload methods from the scope, + // when the widget is removed: + var method; + for (method in uploadMethods) { + if (uploadMethods.hasOwnProperty(method)) { + delete $scope[method]; + } + } + }); + // Observe option changes: + $scope.$watch( + $attrs.fileUpload, + function (newOptions) { + if (newOptions) { + $element.fileupload('option', newOptions); + } + } + ); + } + ]) + + // Provide File Upload progress feedback: + .controller('FileUploadProgressController', [ + '$scope', '$attrs', '$parse', + function ($scope, $attrs, $parse) { + var fn = $parse($attrs.fileUploadProgress), + update = function () { + var progress = fn($scope); + if (!progress || !progress.total) { + return; + } + $scope.num = Math.floor( + progress.loaded / progress.total * 100 + ); + }; + update(); + $scope.$watch( + $attrs.fileUploadProgress + '.loaded', + function (newValue, oldValue) { + if (newValue !== oldValue) { + update(); + } + } + ); + } + ]) + + // Display File Upload previews: + .controller('FileUploadPreviewController', [ + '$scope', '$element', '$attrs', + function ($scope, $element, $attrs) { + $scope.$watch( + $attrs.fileUploadPreview + '.preview', + function (preview) { + $element.empty(); + if (preview) { + $element.append(preview); + } + } + ); + } + ]) + + .directive('fileUpload', function () { + return { + controller: 'FileUploadController', + scope: true + }; + }) + + .directive('fileUploadProgress', function () { + return { + controller: 'FileUploadProgressController', + scope: true + }; + }) + + .directive('fileUploadPreview', function () { + return { + controller: 'FileUploadPreviewController' + }; + }) + + // Enhance the HTML5 download attribute to + // allow drag&drop of files to the desktop: + .directive('download', function () { + return function (scope, elm) { + elm.on('dragstart', function (e) { + try { + e.originalEvent.dataTransfer.setData( + 'DownloadURL', + [ + 'application/octet-stream', + elm.prop('download'), + elm.prop('href') + ].join(':') + ); + } catch (ignore) {} + }); + }; + }); + +})); diff --git a/node_modules/image_picker/index.html b/node_modules/image_picker/index.html new file mode 100644 index 0000000..737a9e2 --- /dev/null +++ b/node_modules/image_picker/index.html @@ -0,0 +1,30 @@ + + + + + + + File Picker + + + + + + + + + + + +
+
+
+ +
+
+
+ + \ No newline at end of file diff --git a/node_modules/image_picker/package.json b/node_modules/image_picker/package.json new file mode 100644 index 0000000..663a4d8 --- /dev/null +++ b/node_modules/image_picker/package.json @@ -0,0 +1,27 @@ +{ + "name": "image_picker", + "version": "1.0.0", + "description": "angular service for picking images", + "main": "index.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "repository": { + "type": "git", + "url": "git+https://l-o-b@bitbucket.org/otonomic/image_picker.git" + }, + "author": "", + "license": "ISC", + "homepage": "https://bitbucket.org/otonomic/image_picker#readme", + "devDependencies": { + "bower": "^1.7.7", + "grunt": "^0.4.5", + "grunt-angular-templates": "^1.0.3", + "grunt-contrib-concat": "^1.0.0", + "grunt-contrib-copy": "^1.0.0", + "grunt-contrib-uglify": "^1.0.1", + "grunt-contrib-watch": "^1.0.0", + "grunt-ng-annotate": "^2.0.1", + "load-grunt-tasks": "^3.4.1" + } +} diff --git a/node_modules/inherits/LICENSE b/node_modules/inherits/LICENSE new file mode 100644 index 0000000..dea3013 --- /dev/null +++ b/node_modules/inherits/LICENSE @@ -0,0 +1,16 @@ +The ISC License + +Copyright (c) Isaac Z. Schlueter + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH +REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND +FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, +INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM +LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR +OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR +PERFORMANCE OF THIS SOFTWARE. + diff --git a/node_modules/inherits/README.md b/node_modules/inherits/README.md new file mode 100644 index 0000000..b1c5665 --- /dev/null +++ b/node_modules/inherits/README.md @@ -0,0 +1,42 @@ +Browser-friendly inheritance fully compatible with standard node.js +[inherits](http://nodejs.org/api/util.html#util_util_inherits_constructor_superconstructor). + +This package exports standard `inherits` from node.js `util` module in +node environment, but also provides alternative browser-friendly +implementation through [browser +field](https://gist.github.com/shtylman/4339901). Alternative +implementation is a literal copy of standard one located in standalone +module to avoid requiring of `util`. It also has a shim for old +browsers with no `Object.create` support. + +While keeping you sure you are using standard `inherits` +implementation in node.js environment, it allows bundlers such as +[browserify](https://github.com/substack/node-browserify) to not +include full `util` package to your client code if all you need is +just `inherits` function. It worth, because browser shim for `util` +package is large and `inherits` is often the single function you need +from it. + +It's recommended to use this package instead of +`require('util').inherits` for any code that has chances to be used +not only in node.js but in browser too. + +## usage + +```js +var inherits = require('inherits'); +// then use exactly as the standard one +``` + +## note on version ~1.0 + +Version ~1.0 had completely different motivation and is not compatible +neither with 2.0 nor with standard node.js `inherits`. + +If you are using version ~1.0 and planning to switch to ~2.0, be +careful: + +* new version uses `super_` instead of `super` for referencing + superclass +* new version overwrites current prototype while old one preserves any + existing fields on it diff --git a/node_modules/inherits/inherits.js b/node_modules/inherits/inherits.js new file mode 100644 index 0000000..f71f2d9 --- /dev/null +++ b/node_modules/inherits/inherits.js @@ -0,0 +1,9 @@ +try { + var util = require('util'); + /* istanbul ignore next */ + if (typeof util.inherits !== 'function') throw ''; + module.exports = util.inherits; +} catch (e) { + /* istanbul ignore next */ + module.exports = require('./inherits_browser.js'); +} diff --git a/node_modules/inherits/inherits_browser.js b/node_modules/inherits/inherits_browser.js new file mode 100644 index 0000000..86bbb3d --- /dev/null +++ b/node_modules/inherits/inherits_browser.js @@ -0,0 +1,27 @@ +if (typeof Object.create === 'function') { + // implementation from standard node.js 'util' module + module.exports = function inherits(ctor, superCtor) { + if (superCtor) { + ctor.super_ = superCtor + ctor.prototype = Object.create(superCtor.prototype, { + constructor: { + value: ctor, + enumerable: false, + writable: true, + configurable: true + } + }) + } + }; +} else { + // old school shim for old browsers + module.exports = function inherits(ctor, superCtor) { + if (superCtor) { + ctor.super_ = superCtor + var TempCtor = function () {} + TempCtor.prototype = superCtor.prototype + ctor.prototype = new TempCtor() + ctor.prototype.constructor = ctor + } + } +} diff --git a/node_modules/inherits/package.json b/node_modules/inherits/package.json new file mode 100644 index 0000000..37b4366 --- /dev/null +++ b/node_modules/inherits/package.json @@ -0,0 +1,29 @@ +{ + "name": "inherits", + "description": "Browser-friendly inheritance fully compatible with standard node.js inherits()", + "version": "2.0.4", + "keywords": [ + "inheritance", + "class", + "klass", + "oop", + "object-oriented", + "inherits", + "browser", + "browserify" + ], + "main": "./inherits.js", + "browser": "./inherits_browser.js", + "repository": "git://github.com/isaacs/inherits", + "license": "ISC", + "scripts": { + "test": "tap" + }, + "devDependencies": { + "tap": "^14.2.4" + }, + "files": [ + "inherits.js", + "inherits_browser.js" + ] +} diff --git a/node_modules/isarray/.npmignore b/node_modules/isarray/.npmignore new file mode 100644 index 0000000..3c3629e --- /dev/null +++ b/node_modules/isarray/.npmignore @@ -0,0 +1 @@ +node_modules diff --git a/node_modules/isarray/.travis.yml b/node_modules/isarray/.travis.yml new file mode 100644 index 0000000..cc4dba2 --- /dev/null +++ b/node_modules/isarray/.travis.yml @@ -0,0 +1,4 @@ +language: node_js +node_js: + - "0.8" + - "0.10" diff --git a/node_modules/isarray/Makefile b/node_modules/isarray/Makefile new file mode 100644 index 0000000..787d56e --- /dev/null +++ b/node_modules/isarray/Makefile @@ -0,0 +1,6 @@ + +test: + @node_modules/.bin/tape test.js + +.PHONY: test + diff --git a/node_modules/isarray/README.md b/node_modules/isarray/README.md new file mode 100644 index 0000000..16d2c59 --- /dev/null +++ b/node_modules/isarray/README.md @@ -0,0 +1,60 @@ + +# isarray + +`Array#isArray` for older browsers. + +[![build status](https://secure.travis-ci.org/juliangruber/isarray.svg)](http://travis-ci.org/juliangruber/isarray) +[![downloads](https://img.shields.io/npm/dm/isarray.svg)](https://www.npmjs.org/package/isarray) + +[![browser support](https://ci.testling.com/juliangruber/isarray.png) +](https://ci.testling.com/juliangruber/isarray) + +## Usage + +```js +var isArray = require('isarray'); + +console.log(isArray([])); // => true +console.log(isArray({})); // => false +``` + +## Installation + +With [npm](http://npmjs.org) do + +```bash +$ npm install isarray +``` + +Then bundle for the browser with +[browserify](https://github.com/substack/browserify). + +With [component](http://component.io) do + +```bash +$ component install juliangruber/isarray +``` + +## License + +(MIT) + +Copyright (c) 2013 Julian Gruber <julian@juliangruber.com> + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/node_modules/isarray/component.json b/node_modules/isarray/component.json new file mode 100644 index 0000000..9e31b68 --- /dev/null +++ b/node_modules/isarray/component.json @@ -0,0 +1,19 @@ +{ + "name" : "isarray", + "description" : "Array#isArray for older browsers", + "version" : "0.0.1", + "repository" : "juliangruber/isarray", + "homepage": "https://github.com/juliangruber/isarray", + "main" : "index.js", + "scripts" : [ + "index.js" + ], + "dependencies" : {}, + "keywords": ["browser","isarray","array"], + "author": { + "name": "Julian Gruber", + "email": "mail@juliangruber.com", + "url": "http://juliangruber.com" + }, + "license": "MIT" +} diff --git a/node_modules/isarray/index.js b/node_modules/isarray/index.js new file mode 100644 index 0000000..a57f634 --- /dev/null +++ b/node_modules/isarray/index.js @@ -0,0 +1,5 @@ +var toString = {}.toString; + +module.exports = Array.isArray || function (arr) { + return toString.call(arr) == '[object Array]'; +}; diff --git a/node_modules/isarray/package.json b/node_modules/isarray/package.json new file mode 100644 index 0000000..1a4317a --- /dev/null +++ b/node_modules/isarray/package.json @@ -0,0 +1,45 @@ +{ + "name": "isarray", + "description": "Array#isArray for older browsers", + "version": "1.0.0", + "repository": { + "type": "git", + "url": "git://github.com/juliangruber/isarray.git" + }, + "homepage": "https://github.com/juliangruber/isarray", + "main": "index.js", + "dependencies": {}, + "devDependencies": { + "tape": "~2.13.4" + }, + "keywords": [ + "browser", + "isarray", + "array" + ], + "author": { + "name": "Julian Gruber", + "email": "mail@juliangruber.com", + "url": "http://juliangruber.com" + }, + "license": "MIT", + "testling": { + "files": "test.js", + "browsers": [ + "ie/8..latest", + "firefox/17..latest", + "firefox/nightly", + "chrome/22..latest", + "chrome/canary", + "opera/12..latest", + "opera/next", + "safari/5.1..latest", + "ipad/6.0..latest", + "iphone/6.0..latest", + "android-browser/4.2..latest" + ] + }, + "scripts": { + "test": "tape test.js" + } +} diff --git a/node_modules/isarray/test.js b/node_modules/isarray/test.js new file mode 100644 index 0000000..e0c3444 --- /dev/null +++ b/node_modules/isarray/test.js @@ -0,0 +1,20 @@ +var isArray = require('./'); +var test = require('tape'); + +test('is array', function(t){ + t.ok(isArray([])); + t.notOk(isArray({})); + t.notOk(isArray(null)); + t.notOk(isArray(false)); + + var obj = {}; + obj[0] = true; + t.notOk(isArray(obj)); + + var arr = []; + arr.foo = 'bar'; + t.ok(isArray(arr)); + + t.end(); +}); + diff --git a/node_modules/media-typer/HISTORY.md b/node_modules/media-typer/HISTORY.md new file mode 100644 index 0000000..62c2003 --- /dev/null +++ b/node_modules/media-typer/HISTORY.md @@ -0,0 +1,22 @@ +0.3.0 / 2014-09-07 +================== + + * Support Node.js 0.6 + * Throw error when parameter format invalid on parse + +0.2.0 / 2014-06-18 +================== + + * Add `typer.format()` to format media types + +0.1.0 / 2014-06-17 +================== + + * Accept `req` as argument to `parse` + * Accept `res` as argument to `parse` + * Parse media type with extra LWS between type and first parameter + +0.0.0 / 2014-06-13 +================== + + * Initial implementation diff --git a/node_modules/media-typer/LICENSE b/node_modules/media-typer/LICENSE new file mode 100644 index 0000000..b7dce6c --- /dev/null +++ b/node_modules/media-typer/LICENSE @@ -0,0 +1,22 @@ +(The MIT License) + +Copyright (c) 2014 Douglas Christopher Wilson + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +'Software'), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/node_modules/media-typer/README.md b/node_modules/media-typer/README.md new file mode 100644 index 0000000..d8df623 --- /dev/null +++ b/node_modules/media-typer/README.md @@ -0,0 +1,81 @@ +# media-typer + +[![NPM Version][npm-image]][npm-url] +[![NPM Downloads][downloads-image]][downloads-url] +[![Node.js Version][node-version-image]][node-version-url] +[![Build Status][travis-image]][travis-url] +[![Test Coverage][coveralls-image]][coveralls-url] + +Simple RFC 6838 media type parser + +## Installation + +```sh +$ npm install media-typer +``` + +## API + +```js +var typer = require('media-typer') +``` + +### typer.parse(string) + +```js +var obj = typer.parse('image/svg+xml; charset=utf-8') +``` + +Parse a media type string. This will return an object with the following +properties (examples are shown for the string `'image/svg+xml; charset=utf-8'`): + + - `type`: The type of the media type (always lower case). Example: `'image'` + + - `subtype`: The subtype of the media type (always lower case). Example: `'svg'` + + - `suffix`: The suffix of the media type (always lower case). Example: `'xml'` + + - `parameters`: An object of the parameters in the media type (name of parameter always lower case). Example: `{charset: 'utf-8'}` + +### typer.parse(req) + +```js +var obj = typer.parse(req) +``` + +Parse the `content-type` header from the given `req`. Short-cut for +`typer.parse(req.headers['content-type'])`. + +### typer.parse(res) + +```js +var obj = typer.parse(res) +``` + +Parse the `content-type` header set on the given `res`. Short-cut for +`typer.parse(res.getHeader('content-type'))`. + +### typer.format(obj) + +```js +var obj = typer.format({type: 'image', subtype: 'svg', suffix: 'xml'}) +``` + +Format an object into a media type string. This will return a string of the +mime type for the given object. For the properties of the object, see the +documentation for `typer.parse(string)`. + +## License + +[MIT](LICENSE) + +[npm-image]: https://img.shields.io/npm/v/media-typer.svg?style=flat +[npm-url]: https://npmjs.org/package/media-typer +[node-version-image]: https://img.shields.io/badge/node.js-%3E%3D_0.6-brightgreen.svg?style=flat +[node-version-url]: http://nodejs.org/download/ +[travis-image]: https://img.shields.io/travis/jshttp/media-typer.svg?style=flat +[travis-url]: https://travis-ci.org/jshttp/media-typer +[coveralls-image]: https://img.shields.io/coveralls/jshttp/media-typer.svg?style=flat +[coveralls-url]: https://coveralls.io/r/jshttp/media-typer +[downloads-image]: https://img.shields.io/npm/dm/media-typer.svg?style=flat +[downloads-url]: https://npmjs.org/package/media-typer diff --git a/node_modules/media-typer/index.js b/node_modules/media-typer/index.js new file mode 100644 index 0000000..07f7295 --- /dev/null +++ b/node_modules/media-typer/index.js @@ -0,0 +1,270 @@ +/*! + * media-typer + * Copyright(c) 2014 Douglas Christopher Wilson + * MIT Licensed + */ + +/** + * RegExp to match *( ";" parameter ) in RFC 2616 sec 3.7 + * + * parameter = token "=" ( token | quoted-string ) + * token = 1* + * separators = "(" | ")" | "<" | ">" | "@" + * | "," | ";" | ":" | "\" | <"> + * | "/" | "[" | "]" | "?" | "=" + * | "{" | "}" | SP | HT + * quoted-string = ( <"> *(qdtext | quoted-pair ) <"> ) + * qdtext = > + * quoted-pair = "\" CHAR + * CHAR = + * TEXT = + * LWS = [CRLF] 1*( SP | HT ) + * CRLF = CR LF + * CR = + * LF = + * SP = + * SHT = + * CTL = + * OCTET = + */ +var paramRegExp = /; *([!#$%&'\*\+\-\.0-9A-Z\^_`a-z\|~]+) *= *("(?:[ !\u0023-\u005b\u005d-\u007e\u0080-\u00ff]|\\[\u0020-\u007e])*"|[!#$%&'\*\+\-\.0-9A-Z\^_`a-z\|~]+) */g; +var textRegExp = /^[\u0020-\u007e\u0080-\u00ff]+$/ +var tokenRegExp = /^[!#$%&'\*\+\-\.0-9A-Z\^_`a-z\|~]+$/ + +/** + * RegExp to match quoted-pair in RFC 2616 + * + * quoted-pair = "\" CHAR + * CHAR = + */ +var qescRegExp = /\\([\u0000-\u007f])/g; + +/** + * RegExp to match chars that must be quoted-pair in RFC 2616 + */ +var quoteRegExp = /([\\"])/g; + +/** + * RegExp to match type in RFC 6838 + * + * type-name = restricted-name + * subtype-name = restricted-name + * restricted-name = restricted-name-first *126restricted-name-chars + * restricted-name-first = ALPHA / DIGIT + * restricted-name-chars = ALPHA / DIGIT / "!" / "#" / + * "$" / "&" / "-" / "^" / "_" + * restricted-name-chars =/ "." ; Characters before first dot always + * ; specify a facet name + * restricted-name-chars =/ "+" ; Characters after last plus always + * ; specify a structured syntax suffix + * ALPHA = %x41-5A / %x61-7A ; A-Z / a-z + * DIGIT = %x30-39 ; 0-9 + */ +var subtypeNameRegExp = /^[A-Za-z0-9][A-Za-z0-9!#$&^_.-]{0,126}$/ +var typeNameRegExp = /^[A-Za-z0-9][A-Za-z0-9!#$&^_-]{0,126}$/ +var typeRegExp = /^ *([A-Za-z0-9][A-Za-z0-9!#$&^_-]{0,126})\/([A-Za-z0-9][A-Za-z0-9!#$&^_.+-]{0,126}) *$/; + +/** + * Module exports. + */ + +exports.format = format +exports.parse = parse + +/** + * Format object to media type. + * + * @param {object} obj + * @return {string} + * @api public + */ + +function format(obj) { + if (!obj || typeof obj !== 'object') { + throw new TypeError('argument obj is required') + } + + var parameters = obj.parameters + var subtype = obj.subtype + var suffix = obj.suffix + var type = obj.type + + if (!type || !typeNameRegExp.test(type)) { + throw new TypeError('invalid type') + } + + if (!subtype || !subtypeNameRegExp.test(subtype)) { + throw new TypeError('invalid subtype') + } + + // format as type/subtype + var string = type + '/' + subtype + + // append +suffix + if (suffix) { + if (!typeNameRegExp.test(suffix)) { + throw new TypeError('invalid suffix') + } + + string += '+' + suffix + } + + // append parameters + if (parameters && typeof parameters === 'object') { + var param + var params = Object.keys(parameters).sort() + + for (var i = 0; i < params.length; i++) { + param = params[i] + + if (!tokenRegExp.test(param)) { + throw new TypeError('invalid parameter name') + } + + string += '; ' + param + '=' + qstring(parameters[param]) + } + } + + return string +} + +/** + * Parse media type to object. + * + * @param {string|object} string + * @return {Object} + * @api public + */ + +function parse(string) { + if (!string) { + throw new TypeError('argument string is required') + } + + // support req/res-like objects as argument + if (typeof string === 'object') { + string = getcontenttype(string) + } + + if (typeof string !== 'string') { + throw new TypeError('argument string is required to be a string') + } + + var index = string.indexOf(';') + var type = index !== -1 + ? string.substr(0, index) + : string + + var key + var match + var obj = splitType(type) + var params = {} + var value + + paramRegExp.lastIndex = index + + while (match = paramRegExp.exec(string)) { + if (match.index !== index) { + throw new TypeError('invalid parameter format') + } + + index += match[0].length + key = match[1].toLowerCase() + value = match[2] + + if (value[0] === '"') { + // remove quotes and escapes + value = value + .substr(1, value.length - 2) + .replace(qescRegExp, '$1') + } + + params[key] = value + } + + if (index !== -1 && index !== string.length) { + throw new TypeError('invalid parameter format') + } + + obj.parameters = params + + return obj +} + +/** + * Get content-type from req/res objects. + * + * @param {object} + * @return {Object} + * @api private + */ + +function getcontenttype(obj) { + if (typeof obj.getHeader === 'function') { + // res-like + return obj.getHeader('content-type') + } + + if (typeof obj.headers === 'object') { + // req-like + return obj.headers && obj.headers['content-type'] + } +} + +/** + * Quote a string if necessary. + * + * @param {string} val + * @return {string} + * @api private + */ + +function qstring(val) { + var str = String(val) + + // no need to quote tokens + if (tokenRegExp.test(str)) { + return str + } + + if (str.length > 0 && !textRegExp.test(str)) { + throw new TypeError('invalid parameter value') + } + + return '"' + str.replace(quoteRegExp, '\\$1') + '"' +} + +/** + * Simply "type/subtype+siffx" into parts. + * + * @param {string} string + * @return {Object} + * @api private + */ + +function splitType(string) { + var match = typeRegExp.exec(string.toLowerCase()) + + if (!match) { + throw new TypeError('invalid media type') + } + + var type = match[1] + var subtype = match[2] + var suffix + + // suffix after last + + var index = subtype.lastIndexOf('+') + if (index !== -1) { + suffix = subtype.substr(index + 1) + subtype = subtype.substr(0, index) + } + + var obj = { + type: type, + subtype: subtype, + suffix: suffix + } + + return obj +} diff --git a/node_modules/media-typer/package.json b/node_modules/media-typer/package.json new file mode 100644 index 0000000..8cf3ebc --- /dev/null +++ b/node_modules/media-typer/package.json @@ -0,0 +1,26 @@ +{ + "name": "media-typer", + "description": "Simple RFC 6838 media type parser and formatter", + "version": "0.3.0", + "author": "Douglas Christopher Wilson ", + "license": "MIT", + "repository": "jshttp/media-typer", + "devDependencies": { + "istanbul": "0.3.2", + "mocha": "~1.21.4", + "should": "~4.0.4" + }, + "files": [ + "LICENSE", + "HISTORY.md", + "index.js" + ], + "engines": { + "node": ">= 0.6" + }, + "scripts": { + "test": "mocha --reporter spec --check-leaks --bail test/", + "test-cov": "istanbul cover node_modules/mocha/bin/_mocha -- --reporter dot --check-leaks test/", + "test-travis": "istanbul cover node_modules/mocha/bin/_mocha --report lcovonly -- --reporter spec --check-leaks test/" + } +} diff --git a/node_modules/mime-db/HISTORY.md b/node_modules/mime-db/HISTORY.md new file mode 100644 index 0000000..7436f64 --- /dev/null +++ b/node_modules/mime-db/HISTORY.md @@ -0,0 +1,507 @@ +1.52.0 / 2022-02-21 +=================== + + * Add extensions from IANA for more `image/*` types + * Add extension `.asc` to `application/pgp-keys` + * Add extensions to various XML types + * Add new upstream MIME types + +1.51.0 / 2021-11-08 +=================== + + * Add new upstream MIME types + * Mark `image/vnd.microsoft.icon` as compressible + * Mark `image/vnd.ms-dds` as compressible + +1.50.0 / 2021-09-15 +=================== + + * Add deprecated iWorks mime types and extensions + * Add new upstream MIME types + +1.49.0 / 2021-07-26 +=================== + + * Add extension `.trig` to `application/trig` + * Add new upstream MIME types + +1.48.0 / 2021-05-30 +=================== + + * Add extension `.mvt` to `application/vnd.mapbox-vector-tile` + * Add new upstream MIME types + * Mark `text/yaml` as compressible + +1.47.0 / 2021-04-01 +=================== + + * Add new upstream MIME types + * Remove ambigious extensions from IANA for `application/*+xml` types + * Update primary extension to `.es` for `application/ecmascript` + +1.46.0 / 2021-02-13 +=================== + + * Add extension `.amr` to `audio/amr` + * Add extension `.m4s` to `video/iso.segment` + * Add extension `.opus` to `audio/ogg` + * Add new upstream MIME types + +1.45.0 / 2020-09-22 +=================== + + * Add `application/ubjson` with extension `.ubj` + * Add `image/avif` with extension `.avif` + * Add `image/ktx2` with extension `.ktx2` + * Add extension `.dbf` to `application/vnd.dbf` + * Add extension `.rar` to `application/vnd.rar` + * Add extension `.td` to `application/urc-targetdesc+xml` + * Add new upstream MIME types + * Fix extension of `application/vnd.apple.keynote` to be `.key` + +1.44.0 / 2020-04-22 +=================== + + * Add charsets from IANA + * Add extension `.cjs` to `application/node` + * Add new upstream MIME types + +1.43.0 / 2020-01-05 +=================== + + * Add `application/x-keepass2` with extension `.kdbx` + * Add extension `.mxmf` to `audio/mobile-xmf` + * Add extensions from IANA for `application/*+xml` types + * Add new upstream MIME types + +1.42.0 / 2019-09-25 +=================== + + * Add `image/vnd.ms-dds` with extension `.dds` + * Add new upstream MIME types + * Remove compressible from `multipart/mixed` + +1.41.0 / 2019-08-30 +=================== + + * Add new upstream MIME types + * Add `application/toml` with extension `.toml` + * Mark `font/ttf` as compressible + +1.40.0 / 2019-04-20 +=================== + + * Add extensions from IANA for `model/*` types + * Add `text/mdx` with extension `.mdx` + +1.39.0 / 2019-04-04 +=================== + + * Add extensions `.siv` and `.sieve` to `application/sieve` + * Add new upstream MIME types + +1.38.0 / 2019-02-04 +=================== + + * Add extension `.nq` to `application/n-quads` + * Add extension `.nt` to `application/n-triples` + * Add new upstream MIME types + * Mark `text/less` as compressible + +1.37.0 / 2018-10-19 +=================== + + * Add extensions to HEIC image types + * Add new upstream MIME types + +1.36.0 / 2018-08-20 +=================== + + * Add Apple file extensions from IANA + * Add extensions from IANA for `image/*` types + * Add new upstream MIME types + +1.35.0 / 2018-07-15 +=================== + + * Add extension `.owl` to `application/rdf+xml` + * Add new upstream MIME types + - Removes extension `.woff` from `application/font-woff` + +1.34.0 / 2018-06-03 +=================== + + * Add extension `.csl` to `application/vnd.citationstyles.style+xml` + * Add extension `.es` to `application/ecmascript` + * Add new upstream MIME types + * Add `UTF-8` as default charset for `text/turtle` + * Mark all XML-derived types as compressible + +1.33.0 / 2018-02-15 +=================== + + * Add extensions from IANA for `message/*` types + * Add new upstream MIME types + * Fix some incorrect OOXML types + * Remove `application/font-woff2` + +1.32.0 / 2017-11-29 +=================== + + * Add new upstream MIME types + * Update `text/hjson` to registered `application/hjson` + * Add `text/shex` with extension `.shex` + +1.31.0 / 2017-10-25 +=================== + + * Add `application/raml+yaml` with extension `.raml` + * Add `application/wasm` with extension `.wasm` + * Add new `font` type from IANA + * Add new upstream font extensions + * Add new upstream MIME types + * Add extensions for JPEG-2000 images + +1.30.0 / 2017-08-27 +=================== + + * Add `application/vnd.ms-outlook` + * Add `application/x-arj` + * Add extension `.mjs` to `application/javascript` + * Add glTF types and extensions + * Add new upstream MIME types + * Add `text/x-org` + * Add VirtualBox MIME types + * Fix `source` records for `video/*` types that are IANA + * Update `font/opentype` to registered `font/otf` + +1.29.0 / 2017-07-10 +=================== + + * Add `application/fido.trusted-apps+json` + * Add extension `.wadl` to `application/vnd.sun.wadl+xml` + * Add new upstream MIME types + * Add `UTF-8` as default charset for `text/css` + +1.28.0 / 2017-05-14 +=================== + + * Add new upstream MIME types + * Add extension `.gz` to `application/gzip` + * Update extensions `.md` and `.markdown` to be `text/markdown` + +1.27.0 / 2017-03-16 +=================== + + * Add new upstream MIME types + * Add `image/apng` with extension `.apng` + +1.26.0 / 2017-01-14 +=================== + + * Add new upstream MIME types + * Add extension `.geojson` to `application/geo+json` + +1.25.0 / 2016-11-11 +=================== + + * Add new upstream MIME types + +1.24.0 / 2016-09-18 +=================== + + * Add `audio/mp3` + * Add new upstream MIME types + +1.23.0 / 2016-05-01 +=================== + + * Add new upstream MIME types + * Add extension `.3gpp` to `audio/3gpp` + +1.22.0 / 2016-02-15 +=================== + + * Add `text/slim` + * Add extension `.rng` to `application/xml` + * Add new upstream MIME types + * Fix extension of `application/dash+xml` to be `.mpd` + * Update primary extension to `.m4a` for `audio/mp4` + +1.21.0 / 2016-01-06 +=================== + + * Add Google document types + * Add new upstream MIME types + +1.20.0 / 2015-11-10 +=================== + + * Add `text/x-suse-ymp` + * Add new upstream MIME types + +1.19.0 / 2015-09-17 +=================== + + * Add `application/vnd.apple.pkpass` + * Add new upstream MIME types + +1.18.0 / 2015-09-03 +=================== + + * Add new upstream MIME types + +1.17.0 / 2015-08-13 +=================== + + * Add `application/x-msdos-program` + * Add `audio/g711-0` + * Add `image/vnd.mozilla.apng` + * Add extension `.exe` to `application/x-msdos-program` + +1.16.0 / 2015-07-29 +=================== + + * Add `application/vnd.uri-map` + +1.15.0 / 2015-07-13 +=================== + + * Add `application/x-httpd-php` + +1.14.0 / 2015-06-25 +=================== + + * Add `application/scim+json` + * Add `application/vnd.3gpp.ussd+xml` + * Add `application/vnd.biopax.rdf+xml` + * Add `text/x-processing` + +1.13.0 / 2015-06-07 +=================== + + * Add nginx as a source + * Add `application/x-cocoa` + * Add `application/x-java-archive-diff` + * Add `application/x-makeself` + * Add `application/x-perl` + * Add `application/x-pilot` + * Add `application/x-redhat-package-manager` + * Add `application/x-sea` + * Add `audio/x-m4a` + * Add `audio/x-realaudio` + * Add `image/x-jng` + * Add `text/mathml` + +1.12.0 / 2015-06-05 +=================== + + * Add `application/bdoc` + * Add `application/vnd.hyperdrive+json` + * Add `application/x-bdoc` + * Add extension `.rtf` to `text/rtf` + +1.11.0 / 2015-05-31 +=================== + + * Add `audio/wav` + * Add `audio/wave` + * Add extension `.litcoffee` to `text/coffeescript` + * Add extension `.sfd-hdstx` to `application/vnd.hydrostatix.sof-data` + * Add extension `.n-gage` to `application/vnd.nokia.n-gage.symbian.install` + +1.10.0 / 2015-05-19 +=================== + + * Add `application/vnd.balsamiq.bmpr` + * Add `application/vnd.microsoft.portable-executable` + * Add `application/x-ns-proxy-autoconfig` + +1.9.1 / 2015-04-19 +================== + + * Remove `.json` extension from `application/manifest+json` + - This is causing bugs downstream + +1.9.0 / 2015-04-19 +================== + + * Add `application/manifest+json` + * Add `application/vnd.micro+json` + * Add `image/vnd.zbrush.pcx` + * Add `image/x-ms-bmp` + +1.8.0 / 2015-03-13 +================== + + * Add `application/vnd.citationstyles.style+xml` + * Add `application/vnd.fastcopy-disk-image` + * Add `application/vnd.gov.sk.xmldatacontainer+xml` + * Add extension `.jsonld` to `application/ld+json` + +1.7.0 / 2015-02-08 +================== + + * Add `application/vnd.gerber` + * Add `application/vnd.msa-disk-image` + +1.6.1 / 2015-02-05 +================== + + * Community extensions ownership transferred from `node-mime` + +1.6.0 / 2015-01-29 +================== + + * Add `application/jose` + * Add `application/jose+json` + * Add `application/json-seq` + * Add `application/jwk+json` + * Add `application/jwk-set+json` + * Add `application/jwt` + * Add `application/rdap+json` + * Add `application/vnd.gov.sk.e-form+xml` + * Add `application/vnd.ims.imsccv1p3` + +1.5.0 / 2014-12-30 +================== + + * Add `application/vnd.oracle.resource+json` + * Fix various invalid MIME type entries + - `application/mbox+xml` + - `application/oscp-response` + - `application/vwg-multiplexed` + - `audio/g721` + +1.4.0 / 2014-12-21 +================== + + * Add `application/vnd.ims.imsccv1p2` + * Fix various invalid MIME type entries + - `application/vnd-acucobol` + - `application/vnd-curl` + - `application/vnd-dart` + - `application/vnd-dxr` + - `application/vnd-fdf` + - `application/vnd-mif` + - `application/vnd-sema` + - `application/vnd-wap-wmlc` + - `application/vnd.adobe.flash-movie` + - `application/vnd.dece-zip` + - `application/vnd.dvb_service` + - `application/vnd.micrografx-igx` + - `application/vnd.sealed-doc` + - `application/vnd.sealed-eml` + - `application/vnd.sealed-mht` + - `application/vnd.sealed-ppt` + - `application/vnd.sealed-tiff` + - `application/vnd.sealed-xls` + - `application/vnd.sealedmedia.softseal-html` + - `application/vnd.sealedmedia.softseal-pdf` + - `application/vnd.wap-slc` + - `application/vnd.wap-wbxml` + - `audio/vnd.sealedmedia.softseal-mpeg` + - `image/vnd-djvu` + - `image/vnd-svf` + - `image/vnd-wap-wbmp` + - `image/vnd.sealed-png` + - `image/vnd.sealedmedia.softseal-gif` + - `image/vnd.sealedmedia.softseal-jpg` + - `model/vnd-dwf` + - `model/vnd.parasolid.transmit-binary` + - `model/vnd.parasolid.transmit-text` + - `text/vnd-a` + - `text/vnd-curl` + - `text/vnd.wap-wml` + * Remove example template MIME types + - `application/example` + - `audio/example` + - `image/example` + - `message/example` + - `model/example` + - `multipart/example` + - `text/example` + - `video/example` + +1.3.1 / 2014-12-16 +================== + + * Fix missing extensions + - `application/json5` + - `text/hjson` + +1.3.0 / 2014-12-07 +================== + + * Add `application/a2l` + * Add `application/aml` + * Add `application/atfx` + * Add `application/atxml` + * Add `application/cdfx+xml` + * Add `application/dii` + * Add `application/json5` + * Add `application/lxf` + * Add `application/mf4` + * Add `application/vnd.apache.thrift.compact` + * Add `application/vnd.apache.thrift.json` + * Add `application/vnd.coffeescript` + * Add `application/vnd.enphase.envoy` + * Add `application/vnd.ims.imsccv1p1` + * Add `text/csv-schema` + * Add `text/hjson` + * Add `text/markdown` + * Add `text/yaml` + +1.2.0 / 2014-11-09 +================== + + * Add `application/cea` + * Add `application/dit` + * Add `application/vnd.gov.sk.e-form+zip` + * Add `application/vnd.tmd.mediaflex.api+xml` + * Type `application/epub+zip` is now IANA-registered + +1.1.2 / 2014-10-23 +================== + + * Rebuild database for `application/x-www-form-urlencoded` change + +1.1.1 / 2014-10-20 +================== + + * Mark `application/x-www-form-urlencoded` as compressible. + +1.1.0 / 2014-09-28 +================== + + * Add `application/font-woff2` + +1.0.3 / 2014-09-25 +================== + + * Fix engine requirement in package + +1.0.2 / 2014-09-25 +================== + + * Add `application/coap-group+json` + * Add `application/dcd` + * Add `application/vnd.apache.thrift.binary` + * Add `image/vnd.tencent.tap` + * Mark all JSON-derived types as compressible + * Update `text/vtt` data + +1.0.1 / 2014-08-30 +================== + + * Fix extension ordering + +1.0.0 / 2014-08-30 +================== + + * Add `application/atf` + * Add `application/merge-patch+json` + * Add `multipart/x-mixed-replace` + * Add `source: 'apache'` metadata + * Add `source: 'iana'` metadata + * Remove badly-assumed charset data diff --git a/node_modules/mime-db/LICENSE b/node_modules/mime-db/LICENSE new file mode 100644 index 0000000..0751cb1 --- /dev/null +++ b/node_modules/mime-db/LICENSE @@ -0,0 +1,23 @@ +(The MIT License) + +Copyright (c) 2014 Jonathan Ong +Copyright (c) 2015-2022 Douglas Christopher Wilson + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +'Software'), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/node_modules/mime-db/README.md b/node_modules/mime-db/README.md new file mode 100644 index 0000000..5a8fcfe --- /dev/null +++ b/node_modules/mime-db/README.md @@ -0,0 +1,100 @@ +# mime-db + +[![NPM Version][npm-version-image]][npm-url] +[![NPM Downloads][npm-downloads-image]][npm-url] +[![Node.js Version][node-image]][node-url] +[![Build Status][ci-image]][ci-url] +[![Coverage Status][coveralls-image]][coveralls-url] + +This is a large database of mime types and information about them. +It consists of a single, public JSON file and does not include any logic, +allowing it to remain as un-opinionated as possible with an API. +It aggregates data from the following sources: + +- http://www.iana.org/assignments/media-types/media-types.xhtml +- http://svn.apache.org/repos/asf/httpd/httpd/trunk/docs/conf/mime.types +- http://hg.nginx.org/nginx/raw-file/default/conf/mime.types + +## Installation + +```bash +npm install mime-db +``` + +### Database Download + +If you're crazy enough to use this in the browser, you can just grab the +JSON file using [jsDelivr](https://www.jsdelivr.com/). It is recommended to +replace `master` with [a release tag](https://github.com/jshttp/mime-db/tags) +as the JSON format may change in the future. + +``` +https://cdn.jsdelivr.net/gh/jshttp/mime-db@master/db.json +``` + +## Usage + +```js +var db = require('mime-db') + +// grab data on .js files +var data = db['application/javascript'] +``` + +## Data Structure + +The JSON file is a map lookup for lowercased mime types. +Each mime type has the following properties: + +- `.source` - where the mime type is defined. + If not set, it's probably a custom media type. + - `apache` - [Apache common media types](http://svn.apache.org/repos/asf/httpd/httpd/trunk/docs/conf/mime.types) + - `iana` - [IANA-defined media types](http://www.iana.org/assignments/media-types/media-types.xhtml) + - `nginx` - [nginx media types](http://hg.nginx.org/nginx/raw-file/default/conf/mime.types) +- `.extensions[]` - known extensions associated with this mime type. +- `.compressible` - whether a file of this type can be gzipped. +- `.charset` - the default charset associated with this type, if any. + +If unknown, every property could be `undefined`. + +## Contributing + +To edit the database, only make PRs against `src/custom-types.json` or +`src/custom-suffix.json`. + +The `src/custom-types.json` file is a JSON object with the MIME type as the +keys and the values being an object with the following keys: + +- `compressible` - leave out if you don't know, otherwise `true`/`false` to + indicate whether the data represented by the type is typically compressible. +- `extensions` - include an array of file extensions that are associated with + the type. +- `notes` - human-readable notes about the type, typically what the type is. +- `sources` - include an array of URLs of where the MIME type and the associated + extensions are sourced from. This needs to be a [primary source](https://en.wikipedia.org/wiki/Primary_source); + links to type aggregating sites and Wikipedia are _not acceptable_. + +To update the build, run `npm run build`. + +### Adding Custom Media Types + +The best way to get new media types included in this library is to register +them with the IANA. The community registration procedure is outlined in +[RFC 6838 section 5](http://tools.ietf.org/html/rfc6838#section-5). Types +registered with the IANA are automatically pulled into this library. + +If that is not possible / feasible, they can be added directly here as a +"custom" type. To do this, it is required to have a primary source that +definitively lists the media type. If an extension is going to be listed as +associateed with this media type, the source must definitively link the +media type and extension as well. + +[ci-image]: https://badgen.net/github/checks/jshttp/mime-db/master?label=ci +[ci-url]: https://github.com/jshttp/mime-db/actions?query=workflow%3Aci +[coveralls-image]: https://badgen.net/coveralls/c/github/jshttp/mime-db/master +[coveralls-url]: https://coveralls.io/r/jshttp/mime-db?branch=master +[node-image]: https://badgen.net/npm/node/mime-db +[node-url]: https://nodejs.org/en/download +[npm-downloads-image]: https://badgen.net/npm/dm/mime-db +[npm-url]: https://npmjs.org/package/mime-db +[npm-version-image]: https://badgen.net/npm/v/mime-db diff --git a/node_modules/mime-db/db.json b/node_modules/mime-db/db.json new file mode 100644 index 0000000..eb9c42c --- /dev/null +++ b/node_modules/mime-db/db.json @@ -0,0 +1,8519 @@ +{ + "application/1d-interleaved-parityfec": { + "source": "iana" + }, + "application/3gpdash-qoe-report+xml": { + "source": "iana", + "charset": "UTF-8", + "compressible": true + }, + "application/3gpp-ims+xml": { + "source": "iana", + "compressible": true + }, + "application/3gpphal+json": { + "source": "iana", + "compressible": true + }, + "application/3gpphalforms+json": { + "source": "iana", + "compressible": true + }, + "application/a2l": { + "source": "iana" + }, + "application/ace+cbor": { + "source": "iana" + }, + "application/activemessage": { + "source": "iana" + }, + "application/activity+json": { + "source": "iana", + "compressible": true + }, + "application/alto-costmap+json": { + "source": "iana", + "compressible": true + }, + "application/alto-costmapfilter+json": { + "source": "iana", + "compressible": true + }, + "application/alto-directory+json": { + "source": "iana", + "compressible": true + }, + "application/alto-endpointcost+json": { + "source": "iana", + "compressible": true + }, + "application/alto-endpointcostparams+json": { + "source": "iana", + "compressible": true + }, + "application/alto-endpointprop+json": { + "source": "iana", + "compressible": true + }, + "application/alto-endpointpropparams+json": { + "source": "iana", + "compressible": true + }, + "application/alto-error+json": { + "source": "iana", + "compressible": true + }, + "application/alto-networkmap+json": { + "source": "iana", + "compressible": true + }, + "application/alto-networkmapfilter+json": { + "source": "iana", + "compressible": true + }, + "application/alto-updatestreamcontrol+json": { + "source": "iana", + "compressible": true + }, + "application/alto-updatestreamparams+json": { + "source": "iana", + "compressible": true + }, + "application/aml": { + "source": "iana" + }, + "application/andrew-inset": { + "source": "iana", + "extensions": ["ez"] + }, + "application/applefile": { + "source": "iana" + }, + "application/applixware": { + "source": "apache", + "extensions": ["aw"] + }, + "application/at+jwt": { + "source": "iana" + }, + "application/atf": { + "source": "iana" + }, + "application/atfx": { + "source": "iana" + }, + "application/atom+xml": { + "source": "iana", + "compressible": true, + "extensions": ["atom"] + }, + "application/atomcat+xml": { + "source": "iana", + "compressible": true, + "extensions": ["atomcat"] + }, + "application/atomdeleted+xml": { + "source": "iana", + "compressible": true, + "extensions": ["atomdeleted"] + }, + "application/atomicmail": { + "source": "iana" + }, + "application/atomsvc+xml": { + "source": "iana", + "compressible": true, + "extensions": ["atomsvc"] + }, + "application/atsc-dwd+xml": { + "source": "iana", + "compressible": true, + "extensions": ["dwd"] + }, + "application/atsc-dynamic-event-message": { + "source": "iana" + }, + "application/atsc-held+xml": { + "source": "iana", + "compressible": true, + "extensions": ["held"] + }, + "application/atsc-rdt+json": { + "source": "iana", + "compressible": true + }, + "application/atsc-rsat+xml": { + "source": "iana", + "compressible": true, + "extensions": ["rsat"] + }, + "application/atxml": { + "source": "iana" + }, + "application/auth-policy+xml": { + "source": "iana", + "compressible": true + }, + "application/bacnet-xdd+zip": { + "source": "iana", + "compressible": false + }, + "application/batch-smtp": { + "source": "iana" + }, + "application/bdoc": { + "compressible": false, + "extensions": ["bdoc"] + }, + "application/beep+xml": { + "source": "iana", + "charset": "UTF-8", + "compressible": true + }, + "application/calendar+json": { + "source": "iana", + "compressible": true + }, + "application/calendar+xml": { + "source": "iana", + "compressible": true, + "extensions": ["xcs"] + }, + "application/call-completion": { + "source": "iana" + }, + "application/cals-1840": { + "source": "iana" + }, + "application/captive+json": { + "source": "iana", + "compressible": true + }, + "application/cbor": { + "source": "iana" + }, + "application/cbor-seq": { + "source": "iana" + }, + "application/cccex": { + "source": "iana" + }, + "application/ccmp+xml": { + "source": "iana", + "compressible": true + }, + "application/ccxml+xml": { + "source": "iana", + "compressible": true, + "extensions": ["ccxml"] + }, + "application/cdfx+xml": { + "source": "iana", + "compressible": true, + "extensions": ["cdfx"] + }, + "application/cdmi-capability": { + "source": "iana", + "extensions": ["cdmia"] + }, + "application/cdmi-container": { + "source": "iana", + "extensions": ["cdmic"] + }, + "application/cdmi-domain": { + "source": "iana", + "extensions": ["cdmid"] + }, + "application/cdmi-object": { + "source": "iana", + "extensions": ["cdmio"] + }, + "application/cdmi-queue": { + "source": "iana", + "extensions": ["cdmiq"] + }, + "application/cdni": { + "source": "iana" + }, + "application/cea": { + "source": "iana" + }, + "application/cea-2018+xml": { + "source": "iana", + "compressible": true + }, + "application/cellml+xml": { + "source": "iana", + "compressible": true + }, + "application/cfw": { + "source": "iana" + }, + "application/city+json": { + "source": "iana", + "compressible": true + }, + "application/clr": { + "source": "iana" + }, + "application/clue+xml": { + "source": "iana", + "compressible": true + }, + "application/clue_info+xml": { + "source": "iana", + "compressible": true + }, + "application/cms": { + "source": "iana" + }, + "application/cnrp+xml": { + "source": "iana", + "compressible": true + }, + "application/coap-group+json": { + "source": "iana", + "compressible": true + }, + "application/coap-payload": { + "source": "iana" + }, + "application/commonground": { + "source": "iana" + }, + "application/conference-info+xml": { + "source": "iana", + "compressible": true + }, + "application/cose": { + "source": "iana" + }, + "application/cose-key": { + "source": "iana" + }, + "application/cose-key-set": { + "source": "iana" + }, + "application/cpl+xml": { + "source": "iana", + "compressible": true, + "extensions": ["cpl"] + }, + "application/csrattrs": { + "source": "iana" + }, + "application/csta+xml": { + "source": "iana", + "compressible": true + }, + "application/cstadata+xml": { + "source": "iana", + "compressible": true + }, + "application/csvm+json": { + "source": "iana", + "compressible": true + }, + "application/cu-seeme": { + "source": "apache", + "extensions": ["cu"] + }, + "application/cwt": { + "source": "iana" + }, + "application/cybercash": { + "source": "iana" + }, + "application/dart": { + "compressible": true + }, + "application/dash+xml": { + "source": "iana", + "compressible": true, + "extensions": ["mpd"] + }, + "application/dash-patch+xml": { + "source": "iana", + "compressible": true, + "extensions": ["mpp"] + }, + "application/dashdelta": { + "source": "iana" + }, + "application/davmount+xml": { + "source": "iana", + "compressible": true, + "extensions": ["davmount"] + }, + "application/dca-rft": { + "source": "iana" + }, + "application/dcd": { + "source": "iana" + }, + "application/dec-dx": { + "source": "iana" + }, + "application/dialog-info+xml": { + "source": "iana", + "compressible": true + }, + "application/dicom": { + "source": "iana" + }, + "application/dicom+json": { + "source": "iana", + "compressible": true + }, + "application/dicom+xml": { + "source": "iana", + "compressible": true + }, + "application/dii": { + "source": "iana" + }, + "application/dit": { + "source": "iana" + }, + "application/dns": { + "source": "iana" + }, + "application/dns+json": { + "source": "iana", + "compressible": true + }, + "application/dns-message": { + "source": "iana" + }, + "application/docbook+xml": { + "source": "apache", + "compressible": true, + "extensions": ["dbk"] + }, + "application/dots+cbor": { + "source": "iana" + }, + "application/dskpp+xml": { + "source": "iana", + "compressible": true + }, + "application/dssc+der": { + "source": "iana", + "extensions": ["dssc"] + }, + "application/dssc+xml": { + "source": "iana", + "compressible": true, + "extensions": ["xdssc"] + }, + "application/dvcs": { + "source": "iana" + }, + "application/ecmascript": { + "source": "iana", + "compressible": true, + "extensions": ["es","ecma"] + }, + "application/edi-consent": { + "source": "iana" + }, + "application/edi-x12": { + "source": "iana", + "compressible": false + }, + "application/edifact": { + "source": "iana", + "compressible": false + }, + "application/efi": { + "source": "iana" + }, + "application/elm+json": { + "source": "iana", + "charset": "UTF-8", + "compressible": true + }, + "application/elm+xml": { + "source": "iana", + "compressible": true + }, + "application/emergencycalldata.cap+xml": { + "source": "iana", + "charset": "UTF-8", + "compressible": true + }, + "application/emergencycalldata.comment+xml": { + "source": "iana", + "compressible": true + }, + "application/emergencycalldata.control+xml": { + "source": "iana", + "compressible": true + }, + "application/emergencycalldata.deviceinfo+xml": { + "source": "iana", + "compressible": true + }, + "application/emergencycalldata.ecall.msd": { + "source": "iana" + }, + "application/emergencycalldata.providerinfo+xml": { + "source": "iana", + "compressible": true + }, + "application/emergencycalldata.serviceinfo+xml": { + "source": "iana", + "compressible": true + }, + "application/emergencycalldata.subscriberinfo+xml": { + "source": "iana", + "compressible": true + }, + "application/emergencycalldata.veds+xml": { + "source": "iana", + "compressible": true + }, + "application/emma+xml": { + "source": "iana", + "compressible": true, + "extensions": ["emma"] + }, + "application/emotionml+xml": { + "source": "iana", + "compressible": true, + "extensions": ["emotionml"] + }, + "application/encaprtp": { + "source": "iana" + }, + "application/epp+xml": { + "source": "iana", + "compressible": true + }, + "application/epub+zip": { + "source": "iana", + "compressible": false, + "extensions": ["epub"] + }, + "application/eshop": { + "source": "iana" + }, + "application/exi": { + "source": "iana", + "extensions": ["exi"] + }, + "application/expect-ct-report+json": { + "source": "iana", + "compressible": true + }, + "application/express": { + "source": "iana", + "extensions": ["exp"] + }, + "application/fastinfoset": { + "source": "iana" + }, + "application/fastsoap": { + "source": "iana" + }, + "application/fdt+xml": { + "source": "iana", + "compressible": true, + "extensions": ["fdt"] + }, + "application/fhir+json": { + "source": "iana", + "charset": "UTF-8", + "compressible": true + }, + "application/fhir+xml": { + "source": "iana", + "charset": "UTF-8", + "compressible": true + }, + "application/fido.trusted-apps+json": { + "compressible": true + }, + "application/fits": { + "source": "iana" + }, + "application/flexfec": { + "source": "iana" + }, + "application/font-sfnt": { + "source": "iana" + }, + "application/font-tdpfr": { + "source": "iana", + "extensions": ["pfr"] + }, + "application/font-woff": { + "source": "iana", + "compressible": false + }, + "application/framework-attributes+xml": { + "source": "iana", + "compressible": true + }, + "application/geo+json": { + "source": "iana", + "compressible": true, + "extensions": ["geojson"] + }, + "application/geo+json-seq": { + "source": "iana" + }, + "application/geopackage+sqlite3": { + "source": "iana" + }, + "application/geoxacml+xml": { + "source": "iana", + "compressible": true + }, + "application/gltf-buffer": { + "source": "iana" + }, + "application/gml+xml": { + "source": "iana", + "compressible": true, + "extensions": ["gml"] + }, + "application/gpx+xml": { + "source": "apache", + "compressible": true, + "extensions": ["gpx"] + }, + "application/gxf": { + "source": "apache", + "extensions": ["gxf"] + }, + "application/gzip": { + "source": "iana", + "compressible": false, + "extensions": ["gz"] + }, + "application/h224": { + "source": "iana" + }, + "application/held+xml": { + "source": "iana", + "compressible": true + }, + "application/hjson": { + "extensions": ["hjson"] + }, + "application/http": { + "source": "iana" + }, + "application/hyperstudio": { + "source": "iana", + "extensions": ["stk"] + }, + "application/ibe-key-request+xml": { + "source": "iana", + "compressible": true + }, + "application/ibe-pkg-reply+xml": { + "source": "iana", + "compressible": true + }, + "application/ibe-pp-data": { + "source": "iana" + }, + "application/iges": { + "source": "iana" + }, + "application/im-iscomposing+xml": { + "source": "iana", + "charset": "UTF-8", + "compressible": true + }, + "application/index": { + "source": "iana" + }, + "application/index.cmd": { + "source": "iana" + }, + "application/index.obj": { + "source": "iana" + }, + "application/index.response": { + "source": "iana" + }, + "application/index.vnd": { + "source": "iana" + }, + "application/inkml+xml": { + "source": "iana", + "compressible": true, + "extensions": ["ink","inkml"] + }, + "application/iotp": { + "source": "iana" + }, + "application/ipfix": { + "source": "iana", + "extensions": ["ipfix"] + }, + "application/ipp": { + "source": "iana" + }, + "application/isup": { + "source": "iana" + }, + "application/its+xml": { + "source": "iana", + "compressible": true, + "extensions": ["its"] + }, + "application/java-archive": { + "source": "apache", + "compressible": false, + "extensions": ["jar","war","ear"] + }, + "application/java-serialized-object": { + "source": "apache", + "compressible": false, + "extensions": ["ser"] + }, + "application/java-vm": { + "source": "apache", + "compressible": false, + "extensions": ["class"] + }, + "application/javascript": { + "source": "iana", + "charset": "UTF-8", + "compressible": true, + "extensions": ["js","mjs"] + }, + "application/jf2feed+json": { + "source": "iana", + "compressible": true + }, + "application/jose": { + "source": "iana" + }, + "application/jose+json": { + "source": "iana", + "compressible": true + }, + "application/jrd+json": { + "source": "iana", + "compressible": true + }, + "application/jscalendar+json": { + "source": "iana", + "compressible": true + }, + "application/json": { + "source": "iana", + "charset": "UTF-8", + "compressible": true, + "extensions": ["json","map"] + }, + "application/json-patch+json": { + "source": "iana", + "compressible": true + }, + "application/json-seq": { + "source": "iana" + }, + "application/json5": { + "extensions": ["json5"] + }, + "application/jsonml+json": { + "source": "apache", + "compressible": true, + "extensions": ["jsonml"] + }, + "application/jwk+json": { + "source": "iana", + "compressible": true + }, + "application/jwk-set+json": { + "source": "iana", + "compressible": true + }, + "application/jwt": { + "source": "iana" + }, + "application/kpml-request+xml": { + "source": "iana", + "compressible": true + }, + "application/kpml-response+xml": { + "source": "iana", + "compressible": true + }, + "application/ld+json": { + "source": "iana", + "compressible": true, + "extensions": ["jsonld"] + }, + "application/lgr+xml": { + "source": "iana", + "compressible": true, + "extensions": ["lgr"] + }, + "application/link-format": { + "source": "iana" + }, + "application/load-control+xml": { + "source": "iana", + "compressible": true + }, + "application/lost+xml": { + "source": "iana", + "compressible": true, + "extensions": ["lostxml"] + }, + "application/lostsync+xml": { + "source": "iana", + "compressible": true + }, + "application/lpf+zip": { + "source": "iana", + "compressible": false + }, + "application/lxf": { + "source": "iana" + }, + "application/mac-binhex40": { + "source": "iana", + "extensions": ["hqx"] + }, + "application/mac-compactpro": { + "source": "apache", + "extensions": ["cpt"] + }, + "application/macwriteii": { + "source": "iana" + }, + "application/mads+xml": { + "source": "iana", + "compressible": true, + "extensions": ["mads"] + }, + "application/manifest+json": { + "source": "iana", + "charset": "UTF-8", + "compressible": true, + "extensions": ["webmanifest"] + }, + "application/marc": { + "source": "iana", + "extensions": ["mrc"] + }, + "application/marcxml+xml": { + "source": "iana", + "compressible": true, + "extensions": ["mrcx"] + }, + "application/mathematica": { + "source": "iana", + "extensions": ["ma","nb","mb"] + }, + "application/mathml+xml": { + "source": "iana", + "compressible": true, + "extensions": ["mathml"] + }, + "application/mathml-content+xml": { + "source": "iana", + "compressible": true + }, + "application/mathml-presentation+xml": { + "source": "iana", + "compressible": true + }, + "application/mbms-associated-procedure-description+xml": { + "source": "iana", + "compressible": true + }, + "application/mbms-deregister+xml": { + "source": "iana", + "compressible": true + }, + "application/mbms-envelope+xml": { + "source": "iana", + "compressible": true + }, + "application/mbms-msk+xml": { + "source": "iana", + "compressible": true + }, + "application/mbms-msk-response+xml": { + "source": "iana", + "compressible": true + }, + "application/mbms-protection-description+xml": { + "source": "iana", + "compressible": true + }, + "application/mbms-reception-report+xml": { + "source": "iana", + "compressible": true + }, + "application/mbms-register+xml": { + "source": "iana", + "compressible": true + }, + "application/mbms-register-response+xml": { + "source": "iana", + "compressible": true + }, + "application/mbms-schedule+xml": { + "source": "iana", + "compressible": true + }, + "application/mbms-user-service-description+xml": { + "source": "iana", + "compressible": true + }, + "application/mbox": { + "source": "iana", + "extensions": ["mbox"] + }, + "application/media-policy-dataset+xml": { + "source": "iana", + "compressible": true, + "extensions": ["mpf"] + }, + "application/media_control+xml": { + "source": "iana", + "compressible": true + }, + "application/mediaservercontrol+xml": { + "source": "iana", + "compressible": true, + "extensions": ["mscml"] + }, + "application/merge-patch+json": { + "source": "iana", + "compressible": true + }, + "application/metalink+xml": { + "source": "apache", + "compressible": true, + "extensions": ["metalink"] + }, + "application/metalink4+xml": { + "source": "iana", + "compressible": true, + "extensions": ["meta4"] + }, + "application/mets+xml": { + "source": "iana", + "compressible": true, + "extensions": ["mets"] + }, + "application/mf4": { + "source": "iana" + }, + "application/mikey": { + "source": "iana" + }, + "application/mipc": { + "source": "iana" + }, + "application/missing-blocks+cbor-seq": { + "source": "iana" + }, + "application/mmt-aei+xml": { + "source": "iana", + "compressible": true, + "extensions": ["maei"] + }, + "application/mmt-usd+xml": { + "source": "iana", + "compressible": true, + "extensions": ["musd"] + }, + "application/mods+xml": { + "source": "iana", + "compressible": true, + "extensions": ["mods"] + }, + "application/moss-keys": { + "source": "iana" + }, + "application/moss-signature": { + "source": "iana" + }, + "application/mosskey-data": { + "source": "iana" + }, + "application/mosskey-request": { + "source": "iana" + }, + "application/mp21": { + "source": "iana", + "extensions": ["m21","mp21"] + }, + "application/mp4": { + "source": "iana", + "extensions": ["mp4s","m4p"] + }, + "application/mpeg4-generic": { + "source": "iana" + }, + "application/mpeg4-iod": { + "source": "iana" + }, + "application/mpeg4-iod-xmt": { + "source": "iana" + }, + "application/mrb-consumer+xml": { + "source": "iana", + "compressible": true + }, + "application/mrb-publish+xml": { + "source": "iana", + "compressible": true + }, + "application/msc-ivr+xml": { + "source": "iana", + "charset": "UTF-8", + "compressible": true + }, + "application/msc-mixer+xml": { + "source": "iana", + "charset": "UTF-8", + "compressible": true + }, + "application/msword": { + "source": "iana", + "compressible": false, + "extensions": ["doc","dot"] + }, + "application/mud+json": { + "source": "iana", + "compressible": true + }, + "application/multipart-core": { + "source": "iana" + }, + "application/mxf": { + "source": "iana", + "extensions": ["mxf"] + }, + "application/n-quads": { + "source": "iana", + "extensions": ["nq"] + }, + "application/n-triples": { + "source": "iana", + "extensions": ["nt"] + }, + "application/nasdata": { + "source": "iana" + }, + "application/news-checkgroups": { + "source": "iana", + "charset": "US-ASCII" + }, + "application/news-groupinfo": { + "source": "iana", + "charset": "US-ASCII" + }, + "application/news-transmission": { + "source": "iana" + }, + "application/nlsml+xml": { + "source": "iana", + "compressible": true + }, + "application/node": { + "source": "iana", + "extensions": ["cjs"] + }, + "application/nss": { + "source": "iana" + }, + "application/oauth-authz-req+jwt": { + "source": "iana" + }, + "application/oblivious-dns-message": { + "source": "iana" + }, + "application/ocsp-request": { + "source": "iana" + }, + "application/ocsp-response": { + "source": "iana" + }, + "application/octet-stream": { + "source": "iana", + "compressible": false, + "extensions": ["bin","dms","lrf","mar","so","dist","distz","pkg","bpk","dump","elc","deploy","exe","dll","deb","dmg","iso","img","msi","msp","msm","buffer"] + }, + "application/oda": { + "source": "iana", + "extensions": ["oda"] + }, + "application/odm+xml": { + "source": "iana", + "compressible": true + }, + "application/odx": { + "source": "iana" + }, + "application/oebps-package+xml": { + "source": "iana", + "compressible": true, + "extensions": ["opf"] + }, + "application/ogg": { + "source": "iana", + "compressible": false, + "extensions": ["ogx"] + }, + "application/omdoc+xml": { + "source": "apache", + "compressible": true, + "extensions": ["omdoc"] + }, + "application/onenote": { + "source": "apache", + "extensions": ["onetoc","onetoc2","onetmp","onepkg"] + }, + "application/opc-nodeset+xml": { + "source": "iana", + "compressible": true + }, + "application/oscore": { + "source": "iana" + }, + "application/oxps": { + "source": "iana", + "extensions": ["oxps"] + }, + "application/p21": { + "source": "iana" + }, + "application/p21+zip": { + "source": "iana", + "compressible": false + }, + "application/p2p-overlay+xml": { + "source": "iana", + "compressible": true, + "extensions": ["relo"] + }, + "application/parityfec": { + "source": "iana" + }, + "application/passport": { + "source": "iana" + }, + "application/patch-ops-error+xml": { + "source": "iana", + "compressible": true, + "extensions": ["xer"] + }, + "application/pdf": { + "source": "iana", + "compressible": false, + "extensions": ["pdf"] + }, + "application/pdx": { + "source": "iana" + }, + "application/pem-certificate-chain": { + "source": "iana" + }, + "application/pgp-encrypted": { + "source": "iana", + "compressible": false, + "extensions": ["pgp"] + }, + "application/pgp-keys": { + "source": "iana", + "extensions": ["asc"] + }, + "application/pgp-signature": { + "source": "iana", + "extensions": ["asc","sig"] + }, + "application/pics-rules": { + "source": "apache", + "extensions": ["prf"] + }, + "application/pidf+xml": { + "source": "iana", + "charset": "UTF-8", + "compressible": true + }, + "application/pidf-diff+xml": { + "source": "iana", + "charset": "UTF-8", + "compressible": true + }, + "application/pkcs10": { + "source": "iana", + "extensions": ["p10"] + }, + "application/pkcs12": { + "source": "iana" + }, + "application/pkcs7-mime": { + "source": "iana", + "extensions": ["p7m","p7c"] + }, + "application/pkcs7-signature": { + "source": "iana", + "extensions": ["p7s"] + }, + "application/pkcs8": { + "source": "iana", + "extensions": ["p8"] + }, + "application/pkcs8-encrypted": { + "source": "iana" + }, + "application/pkix-attr-cert": { + "source": "iana", + "extensions": ["ac"] + }, + "application/pkix-cert": { + "source": "iana", + "extensions": ["cer"] + }, + "application/pkix-crl": { + "source": "iana", + "extensions": ["crl"] + }, + "application/pkix-pkipath": { + "source": "iana", + "extensions": ["pkipath"] + }, + "application/pkixcmp": { + "source": "iana", + "extensions": ["pki"] + }, + "application/pls+xml": { + "source": "iana", + "compressible": true, + "extensions": ["pls"] + }, + "application/poc-settings+xml": { + "source": "iana", + "charset": "UTF-8", + "compressible": true + }, + "application/postscript": { + "source": "iana", + "compressible": true, + "extensions": ["ai","eps","ps"] + }, + "application/ppsp-tracker+json": { + "source": "iana", + "compressible": true + }, + "application/problem+json": { + "source": "iana", + "compressible": true + }, + "application/problem+xml": { + "source": "iana", + "compressible": true + }, + "application/provenance+xml": { + "source": "iana", + "compressible": true, + "extensions": ["provx"] + }, + "application/prs.alvestrand.titrax-sheet": { + "source": "iana" + }, + "application/prs.cww": { + "source": "iana", + "extensions": ["cww"] + }, + "application/prs.cyn": { + "source": "iana", + "charset": "7-BIT" + }, + "application/prs.hpub+zip": { + "source": "iana", + "compressible": false + }, + "application/prs.nprend": { + "source": "iana" + }, + "application/prs.plucker": { + "source": "iana" + }, + "application/prs.rdf-xml-crypt": { + "source": "iana" + }, + "application/prs.xsf+xml": { + "source": "iana", + "compressible": true + }, + "application/pskc+xml": { + "source": "iana", + "compressible": true, + "extensions": ["pskcxml"] + }, + "application/pvd+json": { + "source": "iana", + "compressible": true + }, + "application/qsig": { + "source": "iana" + }, + "application/raml+yaml": { + "compressible": true, + "extensions": ["raml"] + }, + "application/raptorfec": { + "source": "iana" + }, + "application/rdap+json": { + "source": "iana", + "compressible": true + }, + "application/rdf+xml": { + "source": "iana", + "compressible": true, + "extensions": ["rdf","owl"] + }, + "application/reginfo+xml": { + "source": "iana", + "compressible": true, + "extensions": ["rif"] + }, + "application/relax-ng-compact-syntax": { + "source": "iana", + "extensions": ["rnc"] + }, + "application/remote-printing": { + "source": "iana" + }, + "application/reputon+json": { + "source": "iana", + "compressible": true + }, + "application/resource-lists+xml": { + "source": "iana", + "compressible": true, + "extensions": ["rl"] + }, + "application/resource-lists-diff+xml": { + "source": "iana", + "compressible": true, + "extensions": ["rld"] + }, + "application/rfc+xml": { + "source": "iana", + "compressible": true + }, + "application/riscos": { + "source": "iana" + }, + "application/rlmi+xml": { + "source": "iana", + "compressible": true + }, + "application/rls-services+xml": { + "source": "iana", + "compressible": true, + "extensions": ["rs"] + }, + "application/route-apd+xml": { + "source": "iana", + "compressible": true, + "extensions": ["rapd"] + }, + "application/route-s-tsid+xml": { + "source": "iana", + "compressible": true, + "extensions": ["sls"] + }, + "application/route-usd+xml": { + "source": "iana", + "compressible": true, + "extensions": ["rusd"] + }, + "application/rpki-ghostbusters": { + "source": "iana", + "extensions": ["gbr"] + }, + "application/rpki-manifest": { + "source": "iana", + "extensions": ["mft"] + }, + "application/rpki-publication": { + "source": "iana" + }, + "application/rpki-roa": { + "source": "iana", + "extensions": ["roa"] + }, + "application/rpki-updown": { + "source": "iana" + }, + "application/rsd+xml": { + "source": "apache", + "compressible": true, + "extensions": ["rsd"] + }, + "application/rss+xml": { + "source": "apache", + "compressible": true, + "extensions": ["rss"] + }, + "application/rtf": { + "source": "iana", + "compressible": true, + "extensions": ["rtf"] + }, + "application/rtploopback": { + "source": "iana" + }, + "application/rtx": { + "source": "iana" + }, + "application/samlassertion+xml": { + "source": "iana", + "compressible": true + }, + "application/samlmetadata+xml": { + "source": "iana", + "compressible": true + }, + "application/sarif+json": { + "source": "iana", + "compressible": true + }, + "application/sarif-external-properties+json": { + "source": "iana", + "compressible": true + }, + "application/sbe": { + "source": "iana" + }, + "application/sbml+xml": { + "source": "iana", + "compressible": true, + "extensions": ["sbml"] + }, + "application/scaip+xml": { + "source": "iana", + "compressible": true + }, + "application/scim+json": { + "source": "iana", + "compressible": true + }, + "application/scvp-cv-request": { + "source": "iana", + "extensions": ["scq"] + }, + "application/scvp-cv-response": { + "source": "iana", + "extensions": ["scs"] + }, + "application/scvp-vp-request": { + "source": "iana", + "extensions": ["spq"] + }, + "application/scvp-vp-response": { + "source": "iana", + "extensions": ["spp"] + }, + "application/sdp": { + "source": "iana", + "extensions": ["sdp"] + }, + "application/secevent+jwt": { + "source": "iana" + }, + "application/senml+cbor": { + "source": "iana" + }, + "application/senml+json": { + "source": "iana", + "compressible": true + }, + "application/senml+xml": { + "source": "iana", + "compressible": true, + "extensions": ["senmlx"] + }, + "application/senml-etch+cbor": { + "source": "iana" + }, + "application/senml-etch+json": { + "source": "iana", + "compressible": true + }, + "application/senml-exi": { + "source": "iana" + }, + "application/sensml+cbor": { + "source": "iana" + }, + "application/sensml+json": { + "source": "iana", + "compressible": true + }, + "application/sensml+xml": { + "source": "iana", + "compressible": true, + "extensions": ["sensmlx"] + }, + "application/sensml-exi": { + "source": "iana" + }, + "application/sep+xml": { + "source": "iana", + "compressible": true + }, + "application/sep-exi": { + "source": "iana" + }, + "application/session-info": { + "source": "iana" + }, + "application/set-payment": { + "source": "iana" + }, + "application/set-payment-initiation": { + "source": "iana", + "extensions": ["setpay"] + }, + "application/set-registration": { + "source": "iana" + }, + "application/set-registration-initiation": { + "source": "iana", + "extensions": ["setreg"] + }, + "application/sgml": { + "source": "iana" + }, + "application/sgml-open-catalog": { + "source": "iana" + }, + "application/shf+xml": { + "source": "iana", + "compressible": true, + "extensions": ["shf"] + }, + "application/sieve": { + "source": "iana", + "extensions": ["siv","sieve"] + }, + "application/simple-filter+xml": { + "source": "iana", + "compressible": true + }, + "application/simple-message-summary": { + "source": "iana" + }, + "application/simplesymbolcontainer": { + "source": "iana" + }, + "application/sipc": { + "source": "iana" + }, + "application/slate": { + "source": "iana" + }, + "application/smil": { + "source": "iana" + }, + "application/smil+xml": { + "source": "iana", + "compressible": true, + "extensions": ["smi","smil"] + }, + "application/smpte336m": { + "source": "iana" + }, + "application/soap+fastinfoset": { + "source": "iana" + }, + "application/soap+xml": { + "source": "iana", + "compressible": true + }, + "application/sparql-query": { + "source": "iana", + "extensions": ["rq"] + }, + "application/sparql-results+xml": { + "source": "iana", + "compressible": true, + "extensions": ["srx"] + }, + "application/spdx+json": { + "source": "iana", + "compressible": true + }, + "application/spirits-event+xml": { + "source": "iana", + "compressible": true + }, + "application/sql": { + "source": "iana" + }, + "application/srgs": { + "source": "iana", + "extensions": ["gram"] + }, + "application/srgs+xml": { + "source": "iana", + "compressible": true, + "extensions": ["grxml"] + }, + "application/sru+xml": { + "source": "iana", + "compressible": true, + "extensions": ["sru"] + }, + "application/ssdl+xml": { + "source": "apache", + "compressible": true, + "extensions": ["ssdl"] + }, + "application/ssml+xml": { + "source": "iana", + "compressible": true, + "extensions": ["ssml"] + }, + "application/stix+json": { + "source": "iana", + "compressible": true + }, + "application/swid+xml": { + "source": "iana", + "compressible": true, + "extensions": ["swidtag"] + }, + "application/tamp-apex-update": { + "source": "iana" + }, + "application/tamp-apex-update-confirm": { + "source": "iana" + }, + "application/tamp-community-update": { + "source": "iana" + }, + "application/tamp-community-update-confirm": { + "source": "iana" + }, + "application/tamp-error": { + "source": "iana" + }, + "application/tamp-sequence-adjust": { + "source": "iana" + }, + "application/tamp-sequence-adjust-confirm": { + "source": "iana" + }, + "application/tamp-status-query": { + "source": "iana" + }, + "application/tamp-status-response": { + "source": "iana" + }, + "application/tamp-update": { + "source": "iana" + }, + "application/tamp-update-confirm": { + "source": "iana" + }, + "application/tar": { + "compressible": true + }, + "application/taxii+json": { + "source": "iana", + "compressible": true + }, + "application/td+json": { + "source": "iana", + "compressible": true + }, + "application/tei+xml": { + "source": "iana", + "compressible": true, + "extensions": ["tei","teicorpus"] + }, + "application/tetra_isi": { + "source": "iana" + }, + "application/thraud+xml": { + "source": "iana", + "compressible": true, + "extensions": ["tfi"] + }, + "application/timestamp-query": { + "source": "iana" + }, + "application/timestamp-reply": { + "source": "iana" + }, + "application/timestamped-data": { + "source": "iana", + "extensions": ["tsd"] + }, + "application/tlsrpt+gzip": { + "source": "iana" + }, + "application/tlsrpt+json": { + "source": "iana", + "compressible": true + }, + "application/tnauthlist": { + "source": "iana" + }, + "application/token-introspection+jwt": { + "source": "iana" + }, + "application/toml": { + "compressible": true, + "extensions": ["toml"] + }, + "application/trickle-ice-sdpfrag": { + "source": "iana" + }, + "application/trig": { + "source": "iana", + "extensions": ["trig"] + }, + "application/ttml+xml": { + "source": "iana", + "compressible": true, + "extensions": ["ttml"] + }, + "application/tve-trigger": { + "source": "iana" + }, + "application/tzif": { + "source": "iana" + }, + "application/tzif-leap": { + "source": "iana" + }, + "application/ubjson": { + "compressible": false, + "extensions": ["ubj"] + }, + "application/ulpfec": { + "source": "iana" + }, + "application/urc-grpsheet+xml": { + "source": "iana", + "compressible": true + }, + "application/urc-ressheet+xml": { + "source": "iana", + "compressible": true, + "extensions": ["rsheet"] + }, + "application/urc-targetdesc+xml": { + "source": "iana", + "compressible": true, + "extensions": ["td"] + }, + "application/urc-uisocketdesc+xml": { + "source": "iana", + "compressible": true + }, + "application/vcard+json": { + "source": "iana", + "compressible": true + }, + "application/vcard+xml": { + "source": "iana", + "compressible": true + }, + "application/vemmi": { + "source": "iana" + }, + "application/vividence.scriptfile": { + "source": "apache" + }, + "application/vnd.1000minds.decision-model+xml": { + "source": "iana", + "compressible": true, + "extensions": ["1km"] + }, + "application/vnd.3gpp-prose+xml": { + "source": "iana", + "compressible": true + }, + "application/vnd.3gpp-prose-pc3ch+xml": { + "source": "iana", + "compressible": true + }, + "application/vnd.3gpp-v2x-local-service-information": { + "source": "iana" + }, + "application/vnd.3gpp.5gnas": { + "source": "iana" + }, + "application/vnd.3gpp.access-transfer-events+xml": { + "source": "iana", + "compressible": true + }, + "application/vnd.3gpp.bsf+xml": { + "source": "iana", + "compressible": true + }, + "application/vnd.3gpp.gmop+xml": { + "source": "iana", + "compressible": true + }, + "application/vnd.3gpp.gtpc": { + "source": "iana" + }, + "application/vnd.3gpp.interworking-data": { + "source": "iana" + }, + "application/vnd.3gpp.lpp": { + "source": "iana" + }, + "application/vnd.3gpp.mc-signalling-ear": { + "source": "iana" + }, + "application/vnd.3gpp.mcdata-affiliation-command+xml": { + "source": "iana", + "compressible": true + }, + "application/vnd.3gpp.mcdata-info+xml": { + "source": "iana", + "compressible": true + }, + "application/vnd.3gpp.mcdata-payload": { + "source": "iana" + }, + "application/vnd.3gpp.mcdata-service-config+xml": { + "source": "iana", + "compressible": true + }, + "application/vnd.3gpp.mcdata-signalling": { + "source": "iana" + }, + "application/vnd.3gpp.mcdata-ue-config+xml": { + "source": "iana", + "compressible": true + }, + "application/vnd.3gpp.mcdata-user-profile+xml": { + "source": "iana", + "compressible": true + }, + "application/vnd.3gpp.mcptt-affiliation-command+xml": { + "source": "iana", + "compressible": true + }, + "application/vnd.3gpp.mcptt-floor-request+xml": { + "source": "iana", + "compressible": true + }, + "application/vnd.3gpp.mcptt-info+xml": { + "source": "iana", + "compressible": true + }, + "application/vnd.3gpp.mcptt-location-info+xml": { + "source": "iana", + "compressible": true + }, + "application/vnd.3gpp.mcptt-mbms-usage-info+xml": { + "source": "iana", + "compressible": true + }, + "application/vnd.3gpp.mcptt-service-config+xml": { + "source": "iana", + "compressible": true + }, + "application/vnd.3gpp.mcptt-signed+xml": { + "source": "iana", + "compressible": true + }, + "application/vnd.3gpp.mcptt-ue-config+xml": { + "source": "iana", + "compressible": true + }, + "application/vnd.3gpp.mcptt-ue-init-config+xml": { + "source": "iana", + "compressible": true + }, + "application/vnd.3gpp.mcptt-user-profile+xml": { + "source": "iana", + "compressible": true + }, + "application/vnd.3gpp.mcvideo-affiliation-command+xml": { + "source": "iana", + "compressible": true + }, + "application/vnd.3gpp.mcvideo-affiliation-info+xml": { + "source": "iana", + "compressible": true + }, + "application/vnd.3gpp.mcvideo-info+xml": { + "source": "iana", + "compressible": true + }, + "application/vnd.3gpp.mcvideo-location-info+xml": { + "source": "iana", + "compressible": true + }, + "application/vnd.3gpp.mcvideo-mbms-usage-info+xml": { + "source": "iana", + "compressible": true + }, + "application/vnd.3gpp.mcvideo-service-config+xml": { + "source": "iana", + "compressible": true + }, + "application/vnd.3gpp.mcvideo-transmission-request+xml": { + "source": "iana", + "compressible": true + }, + "application/vnd.3gpp.mcvideo-ue-config+xml": { + "source": "iana", + "compressible": true + }, + "application/vnd.3gpp.mcvideo-user-profile+xml": { + "source": "iana", + "compressible": true + }, + "application/vnd.3gpp.mid-call+xml": { + "source": "iana", + "compressible": true + }, + "application/vnd.3gpp.ngap": { + "source": "iana" + }, + "application/vnd.3gpp.pfcp": { + "source": "iana" + }, + "application/vnd.3gpp.pic-bw-large": { + "source": "iana", + "extensions": ["plb"] + }, + "application/vnd.3gpp.pic-bw-small": { + "source": "iana", + "extensions": ["psb"] + }, + "application/vnd.3gpp.pic-bw-var": { + "source": "iana", + "extensions": ["pvb"] + }, + "application/vnd.3gpp.s1ap": { + "source": "iana" + }, + "application/vnd.3gpp.sms": { + "source": "iana" + }, + "application/vnd.3gpp.sms+xml": { + "source": "iana", + "compressible": true + }, + "application/vnd.3gpp.srvcc-ext+xml": { + "source": "iana", + "compressible": true + }, + "application/vnd.3gpp.srvcc-info+xml": { + "source": "iana", + "compressible": true + }, + "application/vnd.3gpp.state-and-event-info+xml": { + "source": "iana", + "compressible": true + }, + "application/vnd.3gpp.ussd+xml": { + "source": "iana", + "compressible": true + }, + "application/vnd.3gpp2.bcmcsinfo+xml": { + "source": "iana", + "compressible": true + }, + "application/vnd.3gpp2.sms": { + "source": "iana" + }, + "application/vnd.3gpp2.tcap": { + "source": "iana", + "extensions": ["tcap"] + }, + "application/vnd.3lightssoftware.imagescal": { + "source": "iana" + }, + "application/vnd.3m.post-it-notes": { + "source": "iana", + "extensions": ["pwn"] + }, + "application/vnd.accpac.simply.aso": { + "source": "iana", + "extensions": ["aso"] + }, + "application/vnd.accpac.simply.imp": { + "source": "iana", + "extensions": ["imp"] + }, + "application/vnd.acucobol": { + "source": "iana", + "extensions": ["acu"] + }, + "application/vnd.acucorp": { + "source": "iana", + "extensions": ["atc","acutc"] + }, + "application/vnd.adobe.air-application-installer-package+zip": { + "source": "apache", + "compressible": false, + "extensions": ["air"] + }, + "application/vnd.adobe.flash.movie": { + "source": "iana" + }, + "application/vnd.adobe.formscentral.fcdt": { + "source": "iana", + "extensions": ["fcdt"] + }, + "application/vnd.adobe.fxp": { + "source": "iana", + "extensions": ["fxp","fxpl"] + }, + "application/vnd.adobe.partial-upload": { + "source": "iana" + }, + "application/vnd.adobe.xdp+xml": { + "source": "iana", + "compressible": true, + "extensions": ["xdp"] + }, + "application/vnd.adobe.xfdf": { + "source": "iana", + "extensions": ["xfdf"] + }, + "application/vnd.aether.imp": { + "source": "iana" + }, + "application/vnd.afpc.afplinedata": { + "source": "iana" + }, + "application/vnd.afpc.afplinedata-pagedef": { + "source": "iana" + }, + "application/vnd.afpc.cmoca-cmresource": { + "source": "iana" + }, + "application/vnd.afpc.foca-charset": { + "source": "iana" + }, + "application/vnd.afpc.foca-codedfont": { + "source": "iana" + }, + "application/vnd.afpc.foca-codepage": { + "source": "iana" + }, + "application/vnd.afpc.modca": { + "source": "iana" + }, + "application/vnd.afpc.modca-cmtable": { + "source": "iana" + }, + "application/vnd.afpc.modca-formdef": { + "source": "iana" + }, + "application/vnd.afpc.modca-mediummap": { + "source": "iana" + }, + "application/vnd.afpc.modca-objectcontainer": { + "source": "iana" + }, + "application/vnd.afpc.modca-overlay": { + "source": "iana" + }, + "application/vnd.afpc.modca-pagesegment": { + "source": "iana" + }, + "application/vnd.age": { + "source": "iana", + "extensions": ["age"] + }, + "application/vnd.ah-barcode": { + "source": "iana" + }, + "application/vnd.ahead.space": { + "source": "iana", + "extensions": ["ahead"] + }, + "application/vnd.airzip.filesecure.azf": { + "source": "iana", + "extensions": ["azf"] + }, + "application/vnd.airzip.filesecure.azs": { + "source": "iana", + "extensions": ["azs"] + }, + "application/vnd.amadeus+json": { + "source": "iana", + "compressible": true + }, + "application/vnd.amazon.ebook": { + "source": "apache", + "extensions": ["azw"] + }, + "application/vnd.amazon.mobi8-ebook": { + "source": "iana" + }, + "application/vnd.americandynamics.acc": { + "source": "iana", + "extensions": ["acc"] + }, + "application/vnd.amiga.ami": { + "source": "iana", + "extensions": ["ami"] + }, + "application/vnd.amundsen.maze+xml": { + "source": "iana", + "compressible": true + }, + "application/vnd.android.ota": { + "source": "iana" + }, + "application/vnd.android.package-archive": { + "source": "apache", + "compressible": false, + "extensions": ["apk"] + }, + "application/vnd.anki": { + "source": "iana" + }, + "application/vnd.anser-web-certificate-issue-initiation": { + "source": "iana", + "extensions": ["cii"] + }, + "application/vnd.anser-web-funds-transfer-initiation": { + "source": "apache", + "extensions": ["fti"] + }, + "application/vnd.antix.game-component": { + "source": "iana", + "extensions": ["atx"] + }, + "application/vnd.apache.arrow.file": { + "source": "iana" + }, + "application/vnd.apache.arrow.stream": { + "source": "iana" + }, + "application/vnd.apache.thrift.binary": { + "source": "iana" + }, + "application/vnd.apache.thrift.compact": { + "source": "iana" + }, + "application/vnd.apache.thrift.json": { + "source": "iana" + }, + "application/vnd.api+json": { + "source": "iana", + "compressible": true + }, + "application/vnd.aplextor.warrp+json": { + "source": "iana", + "compressible": true + }, + "application/vnd.apothekende.reservation+json": { + "source": "iana", + "compressible": true + }, + "application/vnd.apple.installer+xml": { + "source": "iana", + "compressible": true, + "extensions": ["mpkg"] + }, + "application/vnd.apple.keynote": { + "source": "iana", + "extensions": ["key"] + }, + "application/vnd.apple.mpegurl": { + "source": "iana", + "extensions": ["m3u8"] + }, + "application/vnd.apple.numbers": { + "source": "iana", + "extensions": ["numbers"] + }, + "application/vnd.apple.pages": { + "source": "iana", + "extensions": ["pages"] + }, + "application/vnd.apple.pkpass": { + "compressible": false, + "extensions": ["pkpass"] + }, + "application/vnd.arastra.swi": { + "source": "iana" + }, + "application/vnd.aristanetworks.swi": { + "source": "iana", + "extensions": ["swi"] + }, + "application/vnd.artisan+json": { + "source": "iana", + "compressible": true + }, + "application/vnd.artsquare": { + "source": "iana" + }, + "application/vnd.astraea-software.iota": { + "source": "iana", + "extensions": ["iota"] + }, + "application/vnd.audiograph": { + "source": "iana", + "extensions": ["aep"] + }, + "application/vnd.autopackage": { + "source": "iana" + }, + "application/vnd.avalon+json": { + "source": "iana", + "compressible": true + }, + "application/vnd.avistar+xml": { + "source": "iana", + "compressible": true + }, + "application/vnd.balsamiq.bmml+xml": { + "source": "iana", + "compressible": true, + "extensions": ["bmml"] + }, + "application/vnd.balsamiq.bmpr": { + "source": "iana" + }, + "application/vnd.banana-accounting": { + "source": "iana" + }, + "application/vnd.bbf.usp.error": { + "source": "iana" + }, + "application/vnd.bbf.usp.msg": { + "source": "iana" + }, + "application/vnd.bbf.usp.msg+json": { + "source": "iana", + "compressible": true + }, + "application/vnd.bekitzur-stech+json": { + "source": "iana", + "compressible": true + }, + "application/vnd.bint.med-content": { + "source": "iana" + }, + "application/vnd.biopax.rdf+xml": { + "source": "iana", + "compressible": true + }, + "application/vnd.blink-idb-value-wrapper": { + "source": "iana" + }, + "application/vnd.blueice.multipass": { + "source": "iana", + "extensions": ["mpm"] + }, + "application/vnd.bluetooth.ep.oob": { + "source": "iana" + }, + "application/vnd.bluetooth.le.oob": { + "source": "iana" + }, + "application/vnd.bmi": { + "source": "iana", + "extensions": ["bmi"] + }, + "application/vnd.bpf": { + "source": "iana" + }, + "application/vnd.bpf3": { + "source": "iana" + }, + "application/vnd.businessobjects": { + "source": "iana", + "extensions": ["rep"] + }, + "application/vnd.byu.uapi+json": { + "source": "iana", + "compressible": true + }, + "application/vnd.cab-jscript": { + "source": "iana" + }, + "application/vnd.canon-cpdl": { + "source": "iana" + }, + "application/vnd.canon-lips": { + "source": "iana" + }, + "application/vnd.capasystems-pg+json": { + "source": "iana", + "compressible": true + }, + "application/vnd.cendio.thinlinc.clientconf": { + "source": "iana" + }, + "application/vnd.century-systems.tcp_stream": { + "source": "iana" + }, + "application/vnd.chemdraw+xml": { + "source": "iana", + "compressible": true, + "extensions": ["cdxml"] + }, + "application/vnd.chess-pgn": { + "source": "iana" + }, + "application/vnd.chipnuts.karaoke-mmd": { + "source": "iana", + "extensions": ["mmd"] + }, + "application/vnd.ciedi": { + "source": "iana" + }, + "application/vnd.cinderella": { + "source": "iana", + "extensions": ["cdy"] + }, + "application/vnd.cirpack.isdn-ext": { + "source": "iana" + }, + "application/vnd.citationstyles.style+xml": { + "source": "iana", + "compressible": true, + "extensions": ["csl"] + }, + "application/vnd.claymore": { + "source": "iana", + "extensions": ["cla"] + }, + "application/vnd.cloanto.rp9": { + "source": "iana", + "extensions": ["rp9"] + }, + "application/vnd.clonk.c4group": { + "source": "iana", + "extensions": ["c4g","c4d","c4f","c4p","c4u"] + }, + "application/vnd.cluetrust.cartomobile-config": { + "source": "iana", + "extensions": ["c11amc"] + }, + "application/vnd.cluetrust.cartomobile-config-pkg": { + "source": "iana", + "extensions": ["c11amz"] + }, + "application/vnd.coffeescript": { + "source": "iana" + }, + "application/vnd.collabio.xodocuments.document": { + "source": "iana" + }, + "application/vnd.collabio.xodocuments.document-template": { + "source": "iana" + }, + "application/vnd.collabio.xodocuments.presentation": { + "source": "iana" + }, + "application/vnd.collabio.xodocuments.presentation-template": { + "source": "iana" + }, + "application/vnd.collabio.xodocuments.spreadsheet": { + "source": "iana" + }, + "application/vnd.collabio.xodocuments.spreadsheet-template": { + "source": "iana" + }, + "application/vnd.collection+json": { + "source": "iana", + "compressible": true + }, + "application/vnd.collection.doc+json": { + "source": "iana", + "compressible": true + }, + "application/vnd.collection.next+json": { + "source": "iana", + "compressible": true + }, + "application/vnd.comicbook+zip": { + "source": "iana", + "compressible": false + }, + "application/vnd.comicbook-rar": { + "source": "iana" + }, + "application/vnd.commerce-battelle": { + "source": "iana" + }, + "application/vnd.commonspace": { + "source": "iana", + "extensions": ["csp"] + }, + "application/vnd.contact.cmsg": { + "source": "iana", + "extensions": ["cdbcmsg"] + }, + "application/vnd.coreos.ignition+json": { + "source": "iana", + "compressible": true + }, + "application/vnd.cosmocaller": { + "source": "iana", + "extensions": ["cmc"] + }, + "application/vnd.crick.clicker": { + "source": "iana", + "extensions": ["clkx"] + }, + "application/vnd.crick.clicker.keyboard": { + "source": "iana", + "extensions": ["clkk"] + }, + "application/vnd.crick.clicker.palette": { + "source": "iana", + "extensions": ["clkp"] + }, + "application/vnd.crick.clicker.template": { + "source": "iana", + "extensions": ["clkt"] + }, + "application/vnd.crick.clicker.wordbank": { + "source": "iana", + "extensions": ["clkw"] + }, + "application/vnd.criticaltools.wbs+xml": { + "source": "iana", + "compressible": true, + "extensions": ["wbs"] + }, + "application/vnd.cryptii.pipe+json": { + "source": "iana", + "compressible": true + }, + "application/vnd.crypto-shade-file": { + "source": "iana" + }, + "application/vnd.cryptomator.encrypted": { + "source": "iana" + }, + "application/vnd.cryptomator.vault": { + "source": "iana" + }, + "application/vnd.ctc-posml": { + "source": "iana", + "extensions": ["pml"] + }, + "application/vnd.ctct.ws+xml": { + "source": "iana", + "compressible": true + }, + "application/vnd.cups-pdf": { + "source": "iana" + }, + "application/vnd.cups-postscript": { + "source": "iana" + }, + "application/vnd.cups-ppd": { + "source": "iana", + "extensions": ["ppd"] + }, + "application/vnd.cups-raster": { + "source": "iana" + }, + "application/vnd.cups-raw": { + "source": "iana" + }, + "application/vnd.curl": { + "source": "iana" + }, + "application/vnd.curl.car": { + "source": "apache", + "extensions": ["car"] + }, + "application/vnd.curl.pcurl": { + "source": "apache", + "extensions": ["pcurl"] + }, + "application/vnd.cyan.dean.root+xml": { + "source": "iana", + "compressible": true + }, + "application/vnd.cybank": { + "source": "iana" + }, + "application/vnd.cyclonedx+json": { + "source": "iana", + "compressible": true + }, + "application/vnd.cyclonedx+xml": { + "source": "iana", + "compressible": true + }, + "application/vnd.d2l.coursepackage1p0+zip": { + "source": "iana", + "compressible": false + }, + "application/vnd.d3m-dataset": { + "source": "iana" + }, + "application/vnd.d3m-problem": { + "source": "iana" + }, + "application/vnd.dart": { + "source": "iana", + "compressible": true, + "extensions": ["dart"] + }, + "application/vnd.data-vision.rdz": { + "source": "iana", + "extensions": ["rdz"] + }, + "application/vnd.datapackage+json": { + "source": "iana", + "compressible": true + }, + "application/vnd.dataresource+json": { + "source": "iana", + "compressible": true + }, + "application/vnd.dbf": { + "source": "iana", + "extensions": ["dbf"] + }, + "application/vnd.debian.binary-package": { + "source": "iana" + }, + "application/vnd.dece.data": { + "source": "iana", + "extensions": ["uvf","uvvf","uvd","uvvd"] + }, + "application/vnd.dece.ttml+xml": { + "source": "iana", + "compressible": true, + "extensions": ["uvt","uvvt"] + }, + "application/vnd.dece.unspecified": { + "source": "iana", + "extensions": ["uvx","uvvx"] + }, + "application/vnd.dece.zip": { + "source": "iana", + "extensions": ["uvz","uvvz"] + }, + "application/vnd.denovo.fcselayout-link": { + "source": "iana", + "extensions": ["fe_launch"] + }, + "application/vnd.desmume.movie": { + "source": "iana" + }, + "application/vnd.dir-bi.plate-dl-nosuffix": { + "source": "iana" + }, + "application/vnd.dm.delegation+xml": { + "source": "iana", + "compressible": true + }, + "application/vnd.dna": { + "source": "iana", + "extensions": ["dna"] + }, + "application/vnd.document+json": { + "source": "iana", + "compressible": true + }, + "application/vnd.dolby.mlp": { + "source": "apache", + "extensions": ["mlp"] + }, + "application/vnd.dolby.mobile.1": { + "source": "iana" + }, + "application/vnd.dolby.mobile.2": { + "source": "iana" + }, + "application/vnd.doremir.scorecloud-binary-document": { + "source": "iana" + }, + "application/vnd.dpgraph": { + "source": "iana", + "extensions": ["dpg"] + }, + "application/vnd.dreamfactory": { + "source": "iana", + "extensions": ["dfac"] + }, + "application/vnd.drive+json": { + "source": "iana", + "compressible": true + }, + "application/vnd.ds-keypoint": { + "source": "apache", + "extensions": ["kpxx"] + }, + "application/vnd.dtg.local": { + "source": "iana" + }, + "application/vnd.dtg.local.flash": { + "source": "iana" + }, + "application/vnd.dtg.local.html": { + "source": "iana" + }, + "application/vnd.dvb.ait": { + "source": "iana", + "extensions": ["ait"] + }, + "application/vnd.dvb.dvbisl+xml": { + "source": "iana", + "compressible": true + }, + "application/vnd.dvb.dvbj": { + "source": "iana" + }, + "application/vnd.dvb.esgcontainer": { + "source": "iana" + }, + "application/vnd.dvb.ipdcdftnotifaccess": { + "source": "iana" + }, + "application/vnd.dvb.ipdcesgaccess": { + "source": "iana" + }, + "application/vnd.dvb.ipdcesgaccess2": { + "source": "iana" + }, + "application/vnd.dvb.ipdcesgpdd": { + "source": "iana" + }, + "application/vnd.dvb.ipdcroaming": { + "source": "iana" + }, + "application/vnd.dvb.iptv.alfec-base": { + "source": "iana" + }, + "application/vnd.dvb.iptv.alfec-enhancement": { + "source": "iana" + }, + "application/vnd.dvb.notif-aggregate-root+xml": { + "source": "iana", + "compressible": true + }, + "application/vnd.dvb.notif-container+xml": { + "source": "iana", + "compressible": true + }, + "application/vnd.dvb.notif-generic+xml": { + "source": "iana", + "compressible": true + }, + "application/vnd.dvb.notif-ia-msglist+xml": { + "source": "iana", + "compressible": true + }, + "application/vnd.dvb.notif-ia-registration-request+xml": { + "source": "iana", + "compressible": true + }, + "application/vnd.dvb.notif-ia-registration-response+xml": { + "source": "iana", + "compressible": true + }, + "application/vnd.dvb.notif-init+xml": { + "source": "iana", + "compressible": true + }, + "application/vnd.dvb.pfr": { + "source": "iana" + }, + "application/vnd.dvb.service": { + "source": "iana", + "extensions": ["svc"] + }, + "application/vnd.dxr": { + "source": "iana" + }, + "application/vnd.dynageo": { + "source": "iana", + "extensions": ["geo"] + }, + "application/vnd.dzr": { + "source": "iana" + }, + "application/vnd.easykaraoke.cdgdownload": { + "source": "iana" + }, + "application/vnd.ecdis-update": { + "source": "iana" + }, + "application/vnd.ecip.rlp": { + "source": "iana" + }, + "application/vnd.eclipse.ditto+json": { + "source": "iana", + "compressible": true + }, + "application/vnd.ecowin.chart": { + "source": "iana", + "extensions": ["mag"] + }, + "application/vnd.ecowin.filerequest": { + "source": "iana" + }, + "application/vnd.ecowin.fileupdate": { + "source": "iana" + }, + "application/vnd.ecowin.series": { + "source": "iana" + }, + "application/vnd.ecowin.seriesrequest": { + "source": "iana" + }, + "application/vnd.ecowin.seriesupdate": { + "source": "iana" + }, + "application/vnd.efi.img": { + "source": "iana" + }, + "application/vnd.efi.iso": { + "source": "iana" + }, + "application/vnd.emclient.accessrequest+xml": { + "source": "iana", + "compressible": true + }, + "application/vnd.enliven": { + "source": "iana", + "extensions": ["nml"] + }, + "application/vnd.enphase.envoy": { + "source": "iana" + }, + "application/vnd.eprints.data+xml": { + "source": "iana", + "compressible": true + }, + "application/vnd.epson.esf": { + "source": "iana", + "extensions": ["esf"] + }, + "application/vnd.epson.msf": { + "source": "iana", + "extensions": ["msf"] + }, + "application/vnd.epson.quickanime": { + "source": "iana", + "extensions": ["qam"] + }, + "application/vnd.epson.salt": { + "source": "iana", + "extensions": ["slt"] + }, + "application/vnd.epson.ssf": { + "source": "iana", + "extensions": ["ssf"] + }, + "application/vnd.ericsson.quickcall": { + "source": "iana" + }, + "application/vnd.espass-espass+zip": { + "source": "iana", + "compressible": false + }, + "application/vnd.eszigno3+xml": { + "source": "iana", + "compressible": true, + "extensions": ["es3","et3"] + }, + "application/vnd.etsi.aoc+xml": { + "source": "iana", + "compressible": true + }, + "application/vnd.etsi.asic-e+zip": { + "source": "iana", + "compressible": false + }, + "application/vnd.etsi.asic-s+zip": { + "source": "iana", + "compressible": false + }, + "application/vnd.etsi.cug+xml": { + "source": "iana", + "compressible": true + }, + "application/vnd.etsi.iptvcommand+xml": { + "source": "iana", + "compressible": true + }, + "application/vnd.etsi.iptvdiscovery+xml": { + "source": "iana", + "compressible": true + }, + "application/vnd.etsi.iptvprofile+xml": { + "source": "iana", + "compressible": true + }, + "application/vnd.etsi.iptvsad-bc+xml": { + "source": "iana", + "compressible": true + }, + "application/vnd.etsi.iptvsad-cod+xml": { + "source": "iana", + "compressible": true + }, + "application/vnd.etsi.iptvsad-npvr+xml": { + "source": "iana", + "compressible": true + }, + "application/vnd.etsi.iptvservice+xml": { + "source": "iana", + "compressible": true + }, + "application/vnd.etsi.iptvsync+xml": { + "source": "iana", + "compressible": true + }, + "application/vnd.etsi.iptvueprofile+xml": { + "source": "iana", + "compressible": true + }, + "application/vnd.etsi.mcid+xml": { + "source": "iana", + "compressible": true + }, + "application/vnd.etsi.mheg5": { + "source": "iana" + }, + "application/vnd.etsi.overload-control-policy-dataset+xml": { + "source": "iana", + "compressible": true + }, + "application/vnd.etsi.pstn+xml": { + "source": "iana", + "compressible": true + }, + "application/vnd.etsi.sci+xml": { + "source": "iana", + "compressible": true + }, + "application/vnd.etsi.simservs+xml": { + "source": "iana", + "compressible": true + }, + "application/vnd.etsi.timestamp-token": { + "source": "iana" + }, + "application/vnd.etsi.tsl+xml": { + "source": "iana", + "compressible": true + }, + "application/vnd.etsi.tsl.der": { + "source": "iana" + }, + "application/vnd.eu.kasparian.car+json": { + "source": "iana", + "compressible": true + }, + "application/vnd.eudora.data": { + "source": "iana" + }, + "application/vnd.evolv.ecig.profile": { + "source": "iana" + }, + "application/vnd.evolv.ecig.settings": { + "source": "iana" + }, + "application/vnd.evolv.ecig.theme": { + "source": "iana" + }, + "application/vnd.exstream-empower+zip": { + "source": "iana", + "compressible": false + }, + "application/vnd.exstream-package": { + "source": "iana" + }, + "application/vnd.ezpix-album": { + "source": "iana", + "extensions": ["ez2"] + }, + "application/vnd.ezpix-package": { + "source": "iana", + "extensions": ["ez3"] + }, + "application/vnd.f-secure.mobile": { + "source": "iana" + }, + "application/vnd.familysearch.gedcom+zip": { + "source": "iana", + "compressible": false + }, + "application/vnd.fastcopy-disk-image": { + "source": "iana" + }, + "application/vnd.fdf": { + "source": "iana", + "extensions": ["fdf"] + }, + "application/vnd.fdsn.mseed": { + "source": "iana", + "extensions": ["mseed"] + }, + "application/vnd.fdsn.seed": { + "source": "iana", + "extensions": ["seed","dataless"] + }, + "application/vnd.ffsns": { + "source": "iana" + }, + "application/vnd.ficlab.flb+zip": { + "source": "iana", + "compressible": false + }, + "application/vnd.filmit.zfc": { + "source": "iana" + }, + "application/vnd.fints": { + "source": "iana" + }, + "application/vnd.firemonkeys.cloudcell": { + "source": "iana" + }, + "application/vnd.flographit": { + "source": "iana", + "extensions": ["gph"] + }, + "application/vnd.fluxtime.clip": { + "source": "iana", + "extensions": ["ftc"] + }, + "application/vnd.font-fontforge-sfd": { + "source": "iana" + }, + "application/vnd.framemaker": { + "source": "iana", + "extensions": ["fm","frame","maker","book"] + }, + "application/vnd.frogans.fnc": { + "source": "iana", + "extensions": ["fnc"] + }, + "application/vnd.frogans.ltf": { + "source": "iana", + "extensions": ["ltf"] + }, + "application/vnd.fsc.weblaunch": { + "source": "iana", + "extensions": ["fsc"] + }, + "application/vnd.fujifilm.fb.docuworks": { + "source": "iana" + }, + "application/vnd.fujifilm.fb.docuworks.binder": { + "source": "iana" + }, + "application/vnd.fujifilm.fb.docuworks.container": { + "source": "iana" + }, + "application/vnd.fujifilm.fb.jfi+xml": { + "source": "iana", + "compressible": true + }, + "application/vnd.fujitsu.oasys": { + "source": "iana", + "extensions": ["oas"] + }, + "application/vnd.fujitsu.oasys2": { + "source": "iana", + "extensions": ["oa2"] + }, + "application/vnd.fujitsu.oasys3": { + "source": "iana", + "extensions": ["oa3"] + }, + "application/vnd.fujitsu.oasysgp": { + "source": "iana", + "extensions": ["fg5"] + }, + "application/vnd.fujitsu.oasysprs": { + "source": "iana", + "extensions": ["bh2"] + }, + "application/vnd.fujixerox.art-ex": { + "source": "iana" + }, + "application/vnd.fujixerox.art4": { + "source": "iana" + }, + "application/vnd.fujixerox.ddd": { + "source": "iana", + "extensions": ["ddd"] + }, + "application/vnd.fujixerox.docuworks": { + "source": "iana", + "extensions": ["xdw"] + }, + "application/vnd.fujixerox.docuworks.binder": { + "source": "iana", + "extensions": ["xbd"] + }, + "application/vnd.fujixerox.docuworks.container": { + "source": "iana" + }, + "application/vnd.fujixerox.hbpl": { + "source": "iana" + }, + "application/vnd.fut-misnet": { + "source": "iana" + }, + "application/vnd.futoin+cbor": { + "source": "iana" + }, + "application/vnd.futoin+json": { + "source": "iana", + "compressible": true + }, + "application/vnd.fuzzysheet": { + "source": "iana", + "extensions": ["fzs"] + }, + "application/vnd.genomatix.tuxedo": { + "source": "iana", + "extensions": ["txd"] + }, + "application/vnd.gentics.grd+json": { + "source": "iana", + "compressible": true + }, + "application/vnd.geo+json": { + "source": "iana", + "compressible": true + }, + "application/vnd.geocube+xml": { + "source": "iana", + "compressible": true + }, + "application/vnd.geogebra.file": { + "source": "iana", + "extensions": ["ggb"] + }, + "application/vnd.geogebra.slides": { + "source": "iana" + }, + "application/vnd.geogebra.tool": { + "source": "iana", + "extensions": ["ggt"] + }, + "application/vnd.geometry-explorer": { + "source": "iana", + "extensions": ["gex","gre"] + }, + "application/vnd.geonext": { + "source": "iana", + "extensions": ["gxt"] + }, + "application/vnd.geoplan": { + "source": "iana", + "extensions": ["g2w"] + }, + "application/vnd.geospace": { + "source": "iana", + "extensions": ["g3w"] + }, + "application/vnd.gerber": { + "source": "iana" + }, + "application/vnd.globalplatform.card-content-mgt": { + "source": "iana" + }, + "application/vnd.globalplatform.card-content-mgt-response": { + "source": "iana" + }, + "application/vnd.gmx": { + "source": "iana", + "extensions": ["gmx"] + }, + "application/vnd.google-apps.document": { + "compressible": false, + "extensions": ["gdoc"] + }, + "application/vnd.google-apps.presentation": { + "compressible": false, + "extensions": ["gslides"] + }, + "application/vnd.google-apps.spreadsheet": { + "compressible": false, + "extensions": ["gsheet"] + }, + "application/vnd.google-earth.kml+xml": { + "source": "iana", + "compressible": true, + "extensions": ["kml"] + }, + "application/vnd.google-earth.kmz": { + "source": "iana", + "compressible": false, + "extensions": ["kmz"] + }, + "application/vnd.gov.sk.e-form+xml": { + "source": "iana", + "compressible": true + }, + "application/vnd.gov.sk.e-form+zip": { + "source": "iana", + "compressible": false + }, + "application/vnd.gov.sk.xmldatacontainer+xml": { + "source": "iana", + "compressible": true + }, + "application/vnd.grafeq": { + "source": "iana", + "extensions": ["gqf","gqs"] + }, + "application/vnd.gridmp": { + "source": "iana" + }, + "application/vnd.groove-account": { + "source": "iana", + "extensions": ["gac"] + }, + "application/vnd.groove-help": { + "source": "iana", + "extensions": ["ghf"] + }, + "application/vnd.groove-identity-message": { + "source": "iana", + "extensions": ["gim"] + }, + "application/vnd.groove-injector": { + "source": "iana", + "extensions": ["grv"] + }, + "application/vnd.groove-tool-message": { + "source": "iana", + "extensions": ["gtm"] + }, + "application/vnd.groove-tool-template": { + "source": "iana", + "extensions": ["tpl"] + }, + "application/vnd.groove-vcard": { + "source": "iana", + "extensions": ["vcg"] + }, + "application/vnd.hal+json": { + "source": "iana", + "compressible": true + }, + "application/vnd.hal+xml": { + "source": "iana", + "compressible": true, + "extensions": ["hal"] + }, + "application/vnd.handheld-entertainment+xml": { + "source": "iana", + "compressible": true, + "extensions": ["zmm"] + }, + "application/vnd.hbci": { + "source": "iana", + "extensions": ["hbci"] + }, + "application/vnd.hc+json": { + "source": "iana", + "compressible": true + }, + "application/vnd.hcl-bireports": { + "source": "iana" + }, + "application/vnd.hdt": { + "source": "iana" + }, + "application/vnd.heroku+json": { + "source": "iana", + "compressible": true + }, + "application/vnd.hhe.lesson-player": { + "source": "iana", + "extensions": ["les"] + }, + "application/vnd.hl7cda+xml": { + "source": "iana", + "charset": "UTF-8", + "compressible": true + }, + "application/vnd.hl7v2+xml": { + "source": "iana", + "charset": "UTF-8", + "compressible": true + }, + "application/vnd.hp-hpgl": { + "source": "iana", + "extensions": ["hpgl"] + }, + "application/vnd.hp-hpid": { + "source": "iana", + "extensions": ["hpid"] + }, + "application/vnd.hp-hps": { + "source": "iana", + "extensions": ["hps"] + }, + "application/vnd.hp-jlyt": { + "source": "iana", + "extensions": ["jlt"] + }, + "application/vnd.hp-pcl": { + "source": "iana", + "extensions": ["pcl"] + }, + "application/vnd.hp-pclxl": { + "source": "iana", + "extensions": ["pclxl"] + }, + "application/vnd.httphone": { + "source": "iana" + }, + "application/vnd.hydrostatix.sof-data": { + "source": "iana", + "extensions": ["sfd-hdstx"] + }, + "application/vnd.hyper+json": { + "source": "iana", + "compressible": true + }, + "application/vnd.hyper-item+json": { + "source": "iana", + "compressible": true + }, + "application/vnd.hyperdrive+json": { + "source": "iana", + "compressible": true + }, + "application/vnd.hzn-3d-crossword": { + "source": "iana" + }, + "application/vnd.ibm.afplinedata": { + "source": "iana" + }, + "application/vnd.ibm.electronic-media": { + "source": "iana" + }, + "application/vnd.ibm.minipay": { + "source": "iana", + "extensions": ["mpy"] + }, + "application/vnd.ibm.modcap": { + "source": "iana", + "extensions": ["afp","listafp","list3820"] + }, + "application/vnd.ibm.rights-management": { + "source": "iana", + "extensions": ["irm"] + }, + "application/vnd.ibm.secure-container": { + "source": "iana", + "extensions": ["sc"] + }, + "application/vnd.iccprofile": { + "source": "iana", + "extensions": ["icc","icm"] + }, + "application/vnd.ieee.1905": { + "source": "iana" + }, + "application/vnd.igloader": { + "source": "iana", + "extensions": ["igl"] + }, + "application/vnd.imagemeter.folder+zip": { + "source": "iana", + "compressible": false + }, + "application/vnd.imagemeter.image+zip": { + "source": "iana", + "compressible": false + }, + "application/vnd.immervision-ivp": { + "source": "iana", + "extensions": ["ivp"] + }, + "application/vnd.immervision-ivu": { + "source": "iana", + "extensions": ["ivu"] + }, + "application/vnd.ims.imsccv1p1": { + "source": "iana" + }, + "application/vnd.ims.imsccv1p2": { + "source": "iana" + }, + "application/vnd.ims.imsccv1p3": { + "source": "iana" + }, + "application/vnd.ims.lis.v2.result+json": { + "source": "iana", + "compressible": true + }, + "application/vnd.ims.lti.v2.toolconsumerprofile+json": { + "source": "iana", + "compressible": true + }, + "application/vnd.ims.lti.v2.toolproxy+json": { + "source": "iana", + "compressible": true + }, + "application/vnd.ims.lti.v2.toolproxy.id+json": { + "source": "iana", + "compressible": true + }, + "application/vnd.ims.lti.v2.toolsettings+json": { + "source": "iana", + "compressible": true + }, + "application/vnd.ims.lti.v2.toolsettings.simple+json": { + "source": "iana", + "compressible": true + }, + "application/vnd.informedcontrol.rms+xml": { + "source": "iana", + "compressible": true + }, + "application/vnd.informix-visionary": { + "source": "iana" + }, + "application/vnd.infotech.project": { + "source": "iana" + }, + "application/vnd.infotech.project+xml": { + "source": "iana", + "compressible": true + }, + "application/vnd.innopath.wamp.notification": { + "source": "iana" + }, + "application/vnd.insors.igm": { + "source": "iana", + "extensions": ["igm"] + }, + "application/vnd.intercon.formnet": { + "source": "iana", + "extensions": ["xpw","xpx"] + }, + "application/vnd.intergeo": { + "source": "iana", + "extensions": ["i2g"] + }, + "application/vnd.intertrust.digibox": { + "source": "iana" + }, + "application/vnd.intertrust.nncp": { + "source": "iana" + }, + "application/vnd.intu.qbo": { + "source": "iana", + "extensions": ["qbo"] + }, + "application/vnd.intu.qfx": { + "source": "iana", + "extensions": ["qfx"] + }, + "application/vnd.iptc.g2.catalogitem+xml": { + "source": "iana", + "compressible": true + }, + "application/vnd.iptc.g2.conceptitem+xml": { + "source": "iana", + "compressible": true + }, + "application/vnd.iptc.g2.knowledgeitem+xml": { + "source": "iana", + "compressible": true + }, + "application/vnd.iptc.g2.newsitem+xml": { + "source": "iana", + "compressible": true + }, + "application/vnd.iptc.g2.newsmessage+xml": { + "source": "iana", + "compressible": true + }, + "application/vnd.iptc.g2.packageitem+xml": { + "source": "iana", + "compressible": true + }, + "application/vnd.iptc.g2.planningitem+xml": { + "source": "iana", + "compressible": true + }, + "application/vnd.ipunplugged.rcprofile": { + "source": "iana", + "extensions": ["rcprofile"] + }, + "application/vnd.irepository.package+xml": { + "source": "iana", + "compressible": true, + "extensions": ["irp"] + }, + "application/vnd.is-xpr": { + "source": "iana", + "extensions": ["xpr"] + }, + "application/vnd.isac.fcs": { + "source": "iana", + "extensions": ["fcs"] + }, + "application/vnd.iso11783-10+zip": { + "source": "iana", + "compressible": false + }, + "application/vnd.jam": { + "source": "iana", + "extensions": ["jam"] + }, + "application/vnd.japannet-directory-service": { + "source": "iana" + }, + "application/vnd.japannet-jpnstore-wakeup": { + "source": "iana" + }, + "application/vnd.japannet-payment-wakeup": { + "source": "iana" + }, + "application/vnd.japannet-registration": { + "source": "iana" + }, + "application/vnd.japannet-registration-wakeup": { + "source": "iana" + }, + "application/vnd.japannet-setstore-wakeup": { + "source": "iana" + }, + "application/vnd.japannet-verification": { + "source": "iana" + }, + "application/vnd.japannet-verification-wakeup": { + "source": "iana" + }, + "application/vnd.jcp.javame.midlet-rms": { + "source": "iana", + "extensions": ["rms"] + }, + "application/vnd.jisp": { + "source": "iana", + "extensions": ["jisp"] + }, + "application/vnd.joost.joda-archive": { + "source": "iana", + "extensions": ["joda"] + }, + "application/vnd.jsk.isdn-ngn": { + "source": "iana" + }, + "application/vnd.kahootz": { + "source": "iana", + "extensions": ["ktz","ktr"] + }, + "application/vnd.kde.karbon": { + "source": "iana", + "extensions": ["karbon"] + }, + "application/vnd.kde.kchart": { + "source": "iana", + "extensions": ["chrt"] + }, + "application/vnd.kde.kformula": { + "source": "iana", + "extensions": ["kfo"] + }, + "application/vnd.kde.kivio": { + "source": "iana", + "extensions": ["flw"] + }, + "application/vnd.kde.kontour": { + "source": "iana", + "extensions": ["kon"] + }, + "application/vnd.kde.kpresenter": { + "source": "iana", + "extensions": ["kpr","kpt"] + }, + "application/vnd.kde.kspread": { + "source": "iana", + "extensions": ["ksp"] + }, + "application/vnd.kde.kword": { + "source": "iana", + "extensions": ["kwd","kwt"] + }, + "application/vnd.kenameaapp": { + "source": "iana", + "extensions": ["htke"] + }, + "application/vnd.kidspiration": { + "source": "iana", + "extensions": ["kia"] + }, + "application/vnd.kinar": { + "source": "iana", + "extensions": ["kne","knp"] + }, + "application/vnd.koan": { + "source": "iana", + "extensions": ["skp","skd","skt","skm"] + }, + "application/vnd.kodak-descriptor": { + "source": "iana", + "extensions": ["sse"] + }, + "application/vnd.las": { + "source": "iana" + }, + "application/vnd.las.las+json": { + "source": "iana", + "compressible": true + }, + "application/vnd.las.las+xml": { + "source": "iana", + "compressible": true, + "extensions": ["lasxml"] + }, + "application/vnd.laszip": { + "source": "iana" + }, + "application/vnd.leap+json": { + "source": "iana", + "compressible": true + }, + "application/vnd.liberty-request+xml": { + "source": "iana", + "compressible": true + }, + "application/vnd.llamagraphics.life-balance.desktop": { + "source": "iana", + "extensions": ["lbd"] + }, + "application/vnd.llamagraphics.life-balance.exchange+xml": { + "source": "iana", + "compressible": true, + "extensions": ["lbe"] + }, + "application/vnd.logipipe.circuit+zip": { + "source": "iana", + "compressible": false + }, + "application/vnd.loom": { + "source": "iana" + }, + "application/vnd.lotus-1-2-3": { + "source": "iana", + "extensions": ["123"] + }, + "application/vnd.lotus-approach": { + "source": "iana", + "extensions": ["apr"] + }, + "application/vnd.lotus-freelance": { + "source": "iana", + "extensions": ["pre"] + }, + "application/vnd.lotus-notes": { + "source": "iana", + "extensions": ["nsf"] + }, + "application/vnd.lotus-organizer": { + "source": "iana", + "extensions": ["org"] + }, + "application/vnd.lotus-screencam": { + "source": "iana", + "extensions": ["scm"] + }, + "application/vnd.lotus-wordpro": { + "source": "iana", + "extensions": ["lwp"] + }, + "application/vnd.macports.portpkg": { + "source": "iana", + "extensions": ["portpkg"] + }, + "application/vnd.mapbox-vector-tile": { + "source": "iana", + "extensions": ["mvt"] + }, + "application/vnd.marlin.drm.actiontoken+xml": { + "source": "iana", + "compressible": true + }, + "application/vnd.marlin.drm.conftoken+xml": { + "source": "iana", + "compressible": true + }, + "application/vnd.marlin.drm.license+xml": { + "source": "iana", + "compressible": true + }, + "application/vnd.marlin.drm.mdcf": { + "source": "iana" + }, + "application/vnd.mason+json": { + "source": "iana", + "compressible": true + }, + "application/vnd.maxar.archive.3tz+zip": { + "source": "iana", + "compressible": false + }, + "application/vnd.maxmind.maxmind-db": { + "source": "iana" + }, + "application/vnd.mcd": { + "source": "iana", + "extensions": ["mcd"] + }, + "application/vnd.medcalcdata": { + "source": "iana", + "extensions": ["mc1"] + }, + "application/vnd.mediastation.cdkey": { + "source": "iana", + "extensions": ["cdkey"] + }, + "application/vnd.meridian-slingshot": { + "source": "iana" + }, + "application/vnd.mfer": { + "source": "iana", + "extensions": ["mwf"] + }, + "application/vnd.mfmp": { + "source": "iana", + "extensions": ["mfm"] + }, + "application/vnd.micro+json": { + "source": "iana", + "compressible": true + }, + "application/vnd.micrografx.flo": { + "source": "iana", + "extensions": ["flo"] + }, + "application/vnd.micrografx.igx": { + "source": "iana", + "extensions": ["igx"] + }, + "application/vnd.microsoft.portable-executable": { + "source": "iana" + }, + "application/vnd.microsoft.windows.thumbnail-cache": { + "source": "iana" + }, + "application/vnd.miele+json": { + "source": "iana", + "compressible": true + }, + "application/vnd.mif": { + "source": "iana", + "extensions": ["mif"] + }, + "application/vnd.minisoft-hp3000-save": { + "source": "iana" + }, + "application/vnd.mitsubishi.misty-guard.trustweb": { + "source": "iana" + }, + "application/vnd.mobius.daf": { + "source": "iana", + "extensions": ["daf"] + }, + "application/vnd.mobius.dis": { + "source": "iana", + "extensions": ["dis"] + }, + "application/vnd.mobius.mbk": { + "source": "iana", + "extensions": ["mbk"] + }, + "application/vnd.mobius.mqy": { + "source": "iana", + "extensions": ["mqy"] + }, + "application/vnd.mobius.msl": { + "source": "iana", + "extensions": ["msl"] + }, + "application/vnd.mobius.plc": { + "source": "iana", + "extensions": ["plc"] + }, + "application/vnd.mobius.txf": { + "source": "iana", + "extensions": ["txf"] + }, + "application/vnd.mophun.application": { + "source": "iana", + "extensions": ["mpn"] + }, + "application/vnd.mophun.certificate": { + "source": "iana", + "extensions": ["mpc"] + }, + "application/vnd.motorola.flexsuite": { + "source": "iana" + }, + "application/vnd.motorola.flexsuite.adsi": { + "source": "iana" + }, + "application/vnd.motorola.flexsuite.fis": { + "source": "iana" + }, + "application/vnd.motorola.flexsuite.gotap": { + "source": "iana" + }, + "application/vnd.motorola.flexsuite.kmr": { + "source": "iana" + }, + "application/vnd.motorola.flexsuite.ttc": { + "source": "iana" + }, + "application/vnd.motorola.flexsuite.wem": { + "source": "iana" + }, + "application/vnd.motorola.iprm": { + "source": "iana" + }, + "application/vnd.mozilla.xul+xml": { + "source": "iana", + "compressible": true, + "extensions": ["xul"] + }, + "application/vnd.ms-3mfdocument": { + "source": "iana" + }, + "application/vnd.ms-artgalry": { + "source": "iana", + "extensions": ["cil"] + }, + "application/vnd.ms-asf": { + "source": "iana" + }, + "application/vnd.ms-cab-compressed": { + "source": "iana", + "extensions": ["cab"] + }, + "application/vnd.ms-color.iccprofile": { + "source": "apache" + }, + "application/vnd.ms-excel": { + "source": "iana", + "compressible": false, + "extensions": ["xls","xlm","xla","xlc","xlt","xlw"] + }, + "application/vnd.ms-excel.addin.macroenabled.12": { + "source": "iana", + "extensions": ["xlam"] + }, + "application/vnd.ms-excel.sheet.binary.macroenabled.12": { + "source": "iana", + "extensions": ["xlsb"] + }, + "application/vnd.ms-excel.sheet.macroenabled.12": { + "source": "iana", + "extensions": ["xlsm"] + }, + "application/vnd.ms-excel.template.macroenabled.12": { + "source": "iana", + "extensions": ["xltm"] + }, + "application/vnd.ms-fontobject": { + "source": "iana", + "compressible": true, + "extensions": ["eot"] + }, + "application/vnd.ms-htmlhelp": { + "source": "iana", + "extensions": ["chm"] + }, + "application/vnd.ms-ims": { + "source": "iana", + "extensions": ["ims"] + }, + "application/vnd.ms-lrm": { + "source": "iana", + "extensions": ["lrm"] + }, + "application/vnd.ms-office.activex+xml": { + "source": "iana", + "compressible": true + }, + "application/vnd.ms-officetheme": { + "source": "iana", + "extensions": ["thmx"] + }, + "application/vnd.ms-opentype": { + "source": "apache", + "compressible": true + }, + "application/vnd.ms-outlook": { + "compressible": false, + "extensions": ["msg"] + }, + "application/vnd.ms-package.obfuscated-opentype": { + "source": "apache" + }, + "application/vnd.ms-pki.seccat": { + "source": "apache", + "extensions": ["cat"] + }, + "application/vnd.ms-pki.stl": { + "source": "apache", + "extensions": ["stl"] + }, + "application/vnd.ms-playready.initiator+xml": { + "source": "iana", + "compressible": true + }, + "application/vnd.ms-powerpoint": { + "source": "iana", + "compressible": false, + "extensions": ["ppt","pps","pot"] + }, + "application/vnd.ms-powerpoint.addin.macroenabled.12": { + "source": "iana", + "extensions": ["ppam"] + }, + "application/vnd.ms-powerpoint.presentation.macroenabled.12": { + "source": "iana", + "extensions": ["pptm"] + }, + "application/vnd.ms-powerpoint.slide.macroenabled.12": { + "source": "iana", + "extensions": ["sldm"] + }, + "application/vnd.ms-powerpoint.slideshow.macroenabled.12": { + "source": "iana", + "extensions": ["ppsm"] + }, + "application/vnd.ms-powerpoint.template.macroenabled.12": { + "source": "iana", + "extensions": ["potm"] + }, + "application/vnd.ms-printdevicecapabilities+xml": { + "source": "iana", + "compressible": true + }, + "application/vnd.ms-printing.printticket+xml": { + "source": "apache", + "compressible": true + }, + "application/vnd.ms-printschematicket+xml": { + "source": "iana", + "compressible": true + }, + "application/vnd.ms-project": { + "source": "iana", + "extensions": ["mpp","mpt"] + }, + "application/vnd.ms-tnef": { + "source": "iana" + }, + "application/vnd.ms-windows.devicepairing": { + "source": "iana" + }, + "application/vnd.ms-windows.nwprinting.oob": { + "source": "iana" + }, + "application/vnd.ms-windows.printerpairing": { + "source": "iana" + }, + "application/vnd.ms-windows.wsd.oob": { + "source": "iana" + }, + "application/vnd.ms-wmdrm.lic-chlg-req": { + "source": "iana" + }, + "application/vnd.ms-wmdrm.lic-resp": { + "source": "iana" + }, + "application/vnd.ms-wmdrm.meter-chlg-req": { + "source": "iana" + }, + "application/vnd.ms-wmdrm.meter-resp": { + "source": "iana" + }, + "application/vnd.ms-word.document.macroenabled.12": { + "source": "iana", + "extensions": ["docm"] + }, + "application/vnd.ms-word.template.macroenabled.12": { + "source": "iana", + "extensions": ["dotm"] + }, + "application/vnd.ms-works": { + "source": "iana", + "extensions": ["wps","wks","wcm","wdb"] + }, + "application/vnd.ms-wpl": { + "source": "iana", + "extensions": ["wpl"] + }, + "application/vnd.ms-xpsdocument": { + "source": "iana", + "compressible": false, + "extensions": ["xps"] + }, + "application/vnd.msa-disk-image": { + "source": "iana" + }, + "application/vnd.mseq": { + "source": "iana", + "extensions": ["mseq"] + }, + "application/vnd.msign": { + "source": "iana" + }, + "application/vnd.multiad.creator": { + "source": "iana" + }, + "application/vnd.multiad.creator.cif": { + "source": "iana" + }, + "application/vnd.music-niff": { + "source": "iana" + }, + "application/vnd.musician": { + "source": "iana", + "extensions": ["mus"] + }, + "application/vnd.muvee.style": { + "source": "iana", + "extensions": ["msty"] + }, + "application/vnd.mynfc": { + "source": "iana", + "extensions": ["taglet"] + }, + "application/vnd.nacamar.ybrid+json": { + "source": "iana", + "compressible": true + }, + "application/vnd.ncd.control": { + "source": "iana" + }, + "application/vnd.ncd.reference": { + "source": "iana" + }, + "application/vnd.nearst.inv+json": { + "source": "iana", + "compressible": true + }, + "application/vnd.nebumind.line": { + "source": "iana" + }, + "application/vnd.nervana": { + "source": "iana" + }, + "application/vnd.netfpx": { + "source": "iana" + }, + "application/vnd.neurolanguage.nlu": { + "source": "iana", + "extensions": ["nlu"] + }, + "application/vnd.nimn": { + "source": "iana" + }, + "application/vnd.nintendo.nitro.rom": { + "source": "iana" + }, + "application/vnd.nintendo.snes.rom": { + "source": "iana" + }, + "application/vnd.nitf": { + "source": "iana", + "extensions": ["ntf","nitf"] + }, + "application/vnd.noblenet-directory": { + "source": "iana", + "extensions": ["nnd"] + }, + "application/vnd.noblenet-sealer": { + "source": "iana", + "extensions": ["nns"] + }, + "application/vnd.noblenet-web": { + "source": "iana", + "extensions": ["nnw"] + }, + "application/vnd.nokia.catalogs": { + "source": "iana" + }, + "application/vnd.nokia.conml+wbxml": { + "source": "iana" + }, + "application/vnd.nokia.conml+xml": { + "source": "iana", + "compressible": true + }, + "application/vnd.nokia.iptv.config+xml": { + "source": "iana", + "compressible": true + }, + "application/vnd.nokia.isds-radio-presets": { + "source": "iana" + }, + "application/vnd.nokia.landmark+wbxml": { + "source": "iana" + }, + "application/vnd.nokia.landmark+xml": { + "source": "iana", + "compressible": true + }, + "application/vnd.nokia.landmarkcollection+xml": { + "source": "iana", + "compressible": true + }, + "application/vnd.nokia.n-gage.ac+xml": { + "source": "iana", + "compressible": true, + "extensions": ["ac"] + }, + "application/vnd.nokia.n-gage.data": { + "source": "iana", + "extensions": ["ngdat"] + }, + "application/vnd.nokia.n-gage.symbian.install": { + "source": "iana", + "extensions": ["n-gage"] + }, + "application/vnd.nokia.ncd": { + "source": "iana" + }, + "application/vnd.nokia.pcd+wbxml": { + "source": "iana" + }, + "application/vnd.nokia.pcd+xml": { + "source": "iana", + "compressible": true + }, + "application/vnd.nokia.radio-preset": { + "source": "iana", + "extensions": ["rpst"] + }, + "application/vnd.nokia.radio-presets": { + "source": "iana", + "extensions": ["rpss"] + }, + "application/vnd.novadigm.edm": { + "source": "iana", + "extensions": ["edm"] + }, + "application/vnd.novadigm.edx": { + "source": "iana", + "extensions": ["edx"] + }, + "application/vnd.novadigm.ext": { + "source": "iana", + "extensions": ["ext"] + }, + "application/vnd.ntt-local.content-share": { + "source": "iana" + }, + "application/vnd.ntt-local.file-transfer": { + "source": "iana" + }, + "application/vnd.ntt-local.ogw_remote-access": { + "source": "iana" + }, + "application/vnd.ntt-local.sip-ta_remote": { + "source": "iana" + }, + "application/vnd.ntt-local.sip-ta_tcp_stream": { + "source": "iana" + }, + "application/vnd.oasis.opendocument.chart": { + "source": "iana", + "extensions": ["odc"] + }, + "application/vnd.oasis.opendocument.chart-template": { + "source": "iana", + "extensions": ["otc"] + }, + "application/vnd.oasis.opendocument.database": { + "source": "iana", + "extensions": ["odb"] + }, + "application/vnd.oasis.opendocument.formula": { + "source": "iana", + "extensions": ["odf"] + }, + "application/vnd.oasis.opendocument.formula-template": { + "source": "iana", + "extensions": ["odft"] + }, + "application/vnd.oasis.opendocument.graphics": { + "source": "iana", + "compressible": false, + "extensions": ["odg"] + }, + "application/vnd.oasis.opendocument.graphics-template": { + "source": "iana", + "extensions": ["otg"] + }, + "application/vnd.oasis.opendocument.image": { + "source": "iana", + "extensions": ["odi"] + }, + "application/vnd.oasis.opendocument.image-template": { + "source": "iana", + "extensions": ["oti"] + }, + "application/vnd.oasis.opendocument.presentation": { + "source": "iana", + "compressible": false, + "extensions": ["odp"] + }, + "application/vnd.oasis.opendocument.presentation-template": { + "source": "iana", + "extensions": ["otp"] + }, + "application/vnd.oasis.opendocument.spreadsheet": { + "source": "iana", + "compressible": false, + "extensions": ["ods"] + }, + "application/vnd.oasis.opendocument.spreadsheet-template": { + "source": "iana", + "extensions": ["ots"] + }, + "application/vnd.oasis.opendocument.text": { + "source": "iana", + "compressible": false, + "extensions": ["odt"] + }, + "application/vnd.oasis.opendocument.text-master": { + "source": "iana", + "extensions": ["odm"] + }, + "application/vnd.oasis.opendocument.text-template": { + "source": "iana", + "extensions": ["ott"] + }, + "application/vnd.oasis.opendocument.text-web": { + "source": "iana", + "extensions": ["oth"] + }, + "application/vnd.obn": { + "source": "iana" + }, + "application/vnd.ocf+cbor": { + "source": "iana" + }, + "application/vnd.oci.image.manifest.v1+json": { + "source": "iana", + "compressible": true + }, + "application/vnd.oftn.l10n+json": { + "source": "iana", + "compressible": true + }, + "application/vnd.oipf.contentaccessdownload+xml": { + "source": "iana", + "compressible": true + }, + "application/vnd.oipf.contentaccessstreaming+xml": { + "source": "iana", + "compressible": true + }, + "application/vnd.oipf.cspg-hexbinary": { + "source": "iana" + }, + "application/vnd.oipf.dae.svg+xml": { + "source": "iana", + "compressible": true + }, + "application/vnd.oipf.dae.xhtml+xml": { + "source": "iana", + "compressible": true + }, + "application/vnd.oipf.mippvcontrolmessage+xml": { + "source": "iana", + "compressible": true + }, + "application/vnd.oipf.pae.gem": { + "source": "iana" + }, + "application/vnd.oipf.spdiscovery+xml": { + "source": "iana", + "compressible": true + }, + "application/vnd.oipf.spdlist+xml": { + "source": "iana", + "compressible": true + }, + "application/vnd.oipf.ueprofile+xml": { + "source": "iana", + "compressible": true + }, + "application/vnd.oipf.userprofile+xml": { + "source": "iana", + "compressible": true + }, + "application/vnd.olpc-sugar": { + "source": "iana", + "extensions": ["xo"] + }, + "application/vnd.oma-scws-config": { + "source": "iana" + }, + "application/vnd.oma-scws-http-request": { + "source": "iana" + }, + "application/vnd.oma-scws-http-response": { + "source": "iana" + }, + "application/vnd.oma.bcast.associated-procedure-parameter+xml": { + "source": "iana", + "compressible": true + }, + "application/vnd.oma.bcast.drm-trigger+xml": { + "source": "iana", + "compressible": true + }, + "application/vnd.oma.bcast.imd+xml": { + "source": "iana", + "compressible": true + }, + "application/vnd.oma.bcast.ltkm": { + "source": "iana" + }, + "application/vnd.oma.bcast.notification+xml": { + "source": "iana", + "compressible": true + }, + "application/vnd.oma.bcast.provisioningtrigger": { + "source": "iana" + }, + "application/vnd.oma.bcast.sgboot": { + "source": "iana" + }, + "application/vnd.oma.bcast.sgdd+xml": { + "source": "iana", + "compressible": true + }, + "application/vnd.oma.bcast.sgdu": { + "source": "iana" + }, + "application/vnd.oma.bcast.simple-symbol-container": { + "source": "iana" + }, + "application/vnd.oma.bcast.smartcard-trigger+xml": { + "source": "iana", + "compressible": true + }, + "application/vnd.oma.bcast.sprov+xml": { + "source": "iana", + "compressible": true + }, + "application/vnd.oma.bcast.stkm": { + "source": "iana" + }, + "application/vnd.oma.cab-address-book+xml": { + "source": "iana", + "compressible": true + }, + "application/vnd.oma.cab-feature-handler+xml": { + "source": "iana", + "compressible": true + }, + "application/vnd.oma.cab-pcc+xml": { + "source": "iana", + "compressible": true + }, + "application/vnd.oma.cab-subs-invite+xml": { + "source": "iana", + "compressible": true + }, + "application/vnd.oma.cab-user-prefs+xml": { + "source": "iana", + "compressible": true + }, + "application/vnd.oma.dcd": { + "source": "iana" + }, + "application/vnd.oma.dcdc": { + "source": "iana" + }, + "application/vnd.oma.dd2+xml": { + "source": "iana", + "compressible": true, + "extensions": ["dd2"] + }, + "application/vnd.oma.drm.risd+xml": { + "source": "iana", + "compressible": true + }, + "application/vnd.oma.group-usage-list+xml": { + "source": "iana", + "compressible": true + }, + "application/vnd.oma.lwm2m+cbor": { + "source": "iana" + }, + "application/vnd.oma.lwm2m+json": { + "source": "iana", + "compressible": true + }, + "application/vnd.oma.lwm2m+tlv": { + "source": "iana" + }, + "application/vnd.oma.pal+xml": { + "source": "iana", + "compressible": true + }, + "application/vnd.oma.poc.detailed-progress-report+xml": { + "source": "iana", + "compressible": true + }, + "application/vnd.oma.poc.final-report+xml": { + "source": "iana", + "compressible": true + }, + "application/vnd.oma.poc.groups+xml": { + "source": "iana", + "compressible": true + }, + "application/vnd.oma.poc.invocation-descriptor+xml": { + "source": "iana", + "compressible": true + }, + "application/vnd.oma.poc.optimized-progress-report+xml": { + "source": "iana", + "compressible": true + }, + "application/vnd.oma.push": { + "source": "iana" + }, + "application/vnd.oma.scidm.messages+xml": { + "source": "iana", + "compressible": true + }, + "application/vnd.oma.xcap-directory+xml": { + "source": "iana", + "compressible": true + }, + "application/vnd.omads-email+xml": { + "source": "iana", + "charset": "UTF-8", + "compressible": true + }, + "application/vnd.omads-file+xml": { + "source": "iana", + "charset": "UTF-8", + "compressible": true + }, + "application/vnd.omads-folder+xml": { + "source": "iana", + "charset": "UTF-8", + "compressible": true + }, + "application/vnd.omaloc-supl-init": { + "source": "iana" + }, + "application/vnd.onepager": { + "source": "iana" + }, + "application/vnd.onepagertamp": { + "source": "iana" + }, + "application/vnd.onepagertamx": { + "source": "iana" + }, + "application/vnd.onepagertat": { + "source": "iana" + }, + "application/vnd.onepagertatp": { + "source": "iana" + }, + "application/vnd.onepagertatx": { + "source": "iana" + }, + "application/vnd.openblox.game+xml": { + "source": "iana", + "compressible": true, + "extensions": ["obgx"] + }, + "application/vnd.openblox.game-binary": { + "source": "iana" + }, + "application/vnd.openeye.oeb": { + "source": "iana" + }, + "application/vnd.openofficeorg.extension": { + "source": "apache", + "extensions": ["oxt"] + }, + "application/vnd.openstreetmap.data+xml": { + "source": "iana", + "compressible": true, + "extensions": ["osm"] + }, + "application/vnd.opentimestamps.ots": { + "source": "iana" + }, + "application/vnd.openxmlformats-officedocument.custom-properties+xml": { + "source": "iana", + "compressible": true + }, + "application/vnd.openxmlformats-officedocument.customxmlproperties+xml": { + "source": "iana", + "compressible": true + }, + "application/vnd.openxmlformats-officedocument.drawing+xml": { + "source": "iana", + "compressible": true + }, + "application/vnd.openxmlformats-officedocument.drawingml.chart+xml": { + "source": "iana", + "compressible": true + }, + "application/vnd.openxmlformats-officedocument.drawingml.chartshapes+xml": { + "source": "iana", + "compressible": true + }, + "application/vnd.openxmlformats-officedocument.drawingml.diagramcolors+xml": { + "source": "iana", + "compressible": true + }, + "application/vnd.openxmlformats-officedocument.drawingml.diagramdata+xml": { + "source": "iana", + "compressible": true + }, + "application/vnd.openxmlformats-officedocument.drawingml.diagramlayout+xml": { + "source": "iana", + "compressible": true + }, + "application/vnd.openxmlformats-officedocument.drawingml.diagramstyle+xml": { + "source": "iana", + "compressible": true + }, + "application/vnd.openxmlformats-officedocument.extended-properties+xml": { + "source": "iana", + "compressible": true + }, + "application/vnd.openxmlformats-officedocument.presentationml.commentauthors+xml": { + "source": "iana", + "compressible": true + }, + "application/vnd.openxmlformats-officedocument.presentationml.comments+xml": { + "source": "iana", + "compressible": true + }, + "application/vnd.openxmlformats-officedocument.presentationml.handoutmaster+xml": { + "source": "iana", + "compressible": true + }, + "application/vnd.openxmlformats-officedocument.presentationml.notesmaster+xml": { + "source": "iana", + "compressible": true + }, + "application/vnd.openxmlformats-officedocument.presentationml.notesslide+xml": { + "source": "iana", + "compressible": true + }, + "application/vnd.openxmlformats-officedocument.presentationml.presentation": { + "source": "iana", + "compressible": false, + "extensions": ["pptx"] + }, + "application/vnd.openxmlformats-officedocument.presentationml.presentation.main+xml": { + "source": "iana", + "compressible": true + }, + "application/vnd.openxmlformats-officedocument.presentationml.presprops+xml": { + "source": "iana", + "compressible": true + }, + "application/vnd.openxmlformats-officedocument.presentationml.slide": { + "source": "iana", + "extensions": ["sldx"] + }, + "application/vnd.openxmlformats-officedocument.presentationml.slide+xml": { + "source": "iana", + "compressible": true + }, + "application/vnd.openxmlformats-officedocument.presentationml.slidelayout+xml": { + "source": "iana", + "compressible": true + }, + "application/vnd.openxmlformats-officedocument.presentationml.slidemaster+xml": { + "source": "iana", + "compressible": true + }, + "application/vnd.openxmlformats-officedocument.presentationml.slideshow": { + "source": "iana", + "extensions": ["ppsx"] + }, + "application/vnd.openxmlformats-officedocument.presentationml.slideshow.main+xml": { + "source": "iana", + "compressible": true + }, + "application/vnd.openxmlformats-officedocument.presentationml.slideupdateinfo+xml": { + "source": "iana", + "compressible": true + }, + "application/vnd.openxmlformats-officedocument.presentationml.tablestyles+xml": { + "source": "iana", + "compressible": true + }, + "application/vnd.openxmlformats-officedocument.presentationml.tags+xml": { + "source": "iana", + "compressible": true + }, + "application/vnd.openxmlformats-officedocument.presentationml.template": { + "source": "iana", + "extensions": ["potx"] + }, + "application/vnd.openxmlformats-officedocument.presentationml.template.main+xml": { + "source": "iana", + "compressible": true + }, + "application/vnd.openxmlformats-officedocument.presentationml.viewprops+xml": { + "source": "iana", + "compressible": true + }, + "application/vnd.openxmlformats-officedocument.spreadsheetml.calcchain+xml": { + "source": "iana", + "compressible": true + }, + "application/vnd.openxmlformats-officedocument.spreadsheetml.chartsheet+xml": { + "source": "iana", + "compressible": true + }, + "application/vnd.openxmlformats-officedocument.spreadsheetml.comments+xml": { + "source": "iana", + "compressible": true + }, + "application/vnd.openxmlformats-officedocument.spreadsheetml.connections+xml": { + "source": "iana", + "compressible": true + }, + "application/vnd.openxmlformats-officedocument.spreadsheetml.dialogsheet+xml": { + "source": "iana", + "compressible": true + }, + "application/vnd.openxmlformats-officedocument.spreadsheetml.externallink+xml": { + "source": "iana", + "compressible": true + }, + "application/vnd.openxmlformats-officedocument.spreadsheetml.pivotcachedefinition+xml": { + "source": "iana", + "compressible": true + }, + "application/vnd.openxmlformats-officedocument.spreadsheetml.pivotcacherecords+xml": { + "source": "iana", + "compressible": true + }, + "application/vnd.openxmlformats-officedocument.spreadsheetml.pivottable+xml": { + "source": "iana", + "compressible": true + }, + "application/vnd.openxmlformats-officedocument.spreadsheetml.querytable+xml": { + "source": "iana", + "compressible": true + }, + "application/vnd.openxmlformats-officedocument.spreadsheetml.revisionheaders+xml": { + "source": "iana", + "compressible": true + }, + "application/vnd.openxmlformats-officedocument.spreadsheetml.revisionlog+xml": { + "source": "iana", + "compressible": true + }, + "application/vnd.openxmlformats-officedocument.spreadsheetml.sharedstrings+xml": { + "source": "iana", + "compressible": true + }, + "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet": { + "source": "iana", + "compressible": false, + "extensions": ["xlsx"] + }, + "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet.main+xml": { + "source": "iana", + "compressible": true + }, + "application/vnd.openxmlformats-officedocument.spreadsheetml.sheetmetadata+xml": { + "source": "iana", + "compressible": true + }, + "application/vnd.openxmlformats-officedocument.spreadsheetml.styles+xml": { + "source": "iana", + "compressible": true + }, + "application/vnd.openxmlformats-officedocument.spreadsheetml.table+xml": { + "source": "iana", + "compressible": true + }, + "application/vnd.openxmlformats-officedocument.spreadsheetml.tablesinglecells+xml": { + "source": "iana", + "compressible": true + }, + "application/vnd.openxmlformats-officedocument.spreadsheetml.template": { + "source": "iana", + "extensions": ["xltx"] + }, + "application/vnd.openxmlformats-officedocument.spreadsheetml.template.main+xml": { + "source": "iana", + "compressible": true + }, + "application/vnd.openxmlformats-officedocument.spreadsheetml.usernames+xml": { + "source": "iana", + "compressible": true + }, + "application/vnd.openxmlformats-officedocument.spreadsheetml.volatiledependencies+xml": { + "source": "iana", + "compressible": true + }, + "application/vnd.openxmlformats-officedocument.spreadsheetml.worksheet+xml": { + "source": "iana", + "compressible": true + }, + "application/vnd.openxmlformats-officedocument.theme+xml": { + "source": "iana", + "compressible": true + }, + "application/vnd.openxmlformats-officedocument.themeoverride+xml": { + "source": "iana", + "compressible": true + }, + "application/vnd.openxmlformats-officedocument.vmldrawing": { + "source": "iana" + }, + "application/vnd.openxmlformats-officedocument.wordprocessingml.comments+xml": { + "source": "iana", + "compressible": true + }, + "application/vnd.openxmlformats-officedocument.wordprocessingml.document": { + "source": "iana", + "compressible": false, + "extensions": ["docx"] + }, + "application/vnd.openxmlformats-officedocument.wordprocessingml.document.glossary+xml": { + "source": "iana", + "compressible": true + }, + "application/vnd.openxmlformats-officedocument.wordprocessingml.document.main+xml": { + "source": "iana", + "compressible": true + }, + "application/vnd.openxmlformats-officedocument.wordprocessingml.endnotes+xml": { + "source": "iana", + "compressible": true + }, + "application/vnd.openxmlformats-officedocument.wordprocessingml.fonttable+xml": { + "source": "iana", + "compressible": true + }, + "application/vnd.openxmlformats-officedocument.wordprocessingml.footer+xml": { + "source": "iana", + "compressible": true + }, + "application/vnd.openxmlformats-officedocument.wordprocessingml.footnotes+xml": { + "source": "iana", + "compressible": true + }, + "application/vnd.openxmlformats-officedocument.wordprocessingml.numbering+xml": { + "source": "iana", + "compressible": true + }, + "application/vnd.openxmlformats-officedocument.wordprocessingml.settings+xml": { + "source": "iana", + "compressible": true + }, + "application/vnd.openxmlformats-officedocument.wordprocessingml.styles+xml": { + "source": "iana", + "compressible": true + }, + "application/vnd.openxmlformats-officedocument.wordprocessingml.template": { + "source": "iana", + "extensions": ["dotx"] + }, + "application/vnd.openxmlformats-officedocument.wordprocessingml.template.main+xml": { + "source": "iana", + "compressible": true + }, + "application/vnd.openxmlformats-officedocument.wordprocessingml.websettings+xml": { + "source": "iana", + "compressible": true + }, + "application/vnd.openxmlformats-package.core-properties+xml": { + "source": "iana", + "compressible": true + }, + "application/vnd.openxmlformats-package.digital-signature-xmlsignature+xml": { + "source": "iana", + "compressible": true + }, + "application/vnd.openxmlformats-package.relationships+xml": { + "source": "iana", + "compressible": true + }, + "application/vnd.oracle.resource+json": { + "source": "iana", + "compressible": true + }, + "application/vnd.orange.indata": { + "source": "iana" + }, + "application/vnd.osa.netdeploy": { + "source": "iana" + }, + "application/vnd.osgeo.mapguide.package": { + "source": "iana", + "extensions": ["mgp"] + }, + "application/vnd.osgi.bundle": { + "source": "iana" + }, + "application/vnd.osgi.dp": { + "source": "iana", + "extensions": ["dp"] + }, + "application/vnd.osgi.subsystem": { + "source": "iana", + "extensions": ["esa"] + }, + "application/vnd.otps.ct-kip+xml": { + "source": "iana", + "compressible": true + }, + "application/vnd.oxli.countgraph": { + "source": "iana" + }, + "application/vnd.pagerduty+json": { + "source": "iana", + "compressible": true + }, + "application/vnd.palm": { + "source": "iana", + "extensions": ["pdb","pqa","oprc"] + }, + "application/vnd.panoply": { + "source": "iana" + }, + "application/vnd.paos.xml": { + "source": "iana" + }, + "application/vnd.patentdive": { + "source": "iana" + }, + "application/vnd.patientecommsdoc": { + "source": "iana" + }, + "application/vnd.pawaafile": { + "source": "iana", + "extensions": ["paw"] + }, + "application/vnd.pcos": { + "source": "iana" + }, + "application/vnd.pg.format": { + "source": "iana", + "extensions": ["str"] + }, + "application/vnd.pg.osasli": { + "source": "iana", + "extensions": ["ei6"] + }, + "application/vnd.piaccess.application-licence": { + "source": "iana" + }, + "application/vnd.picsel": { + "source": "iana", + "extensions": ["efif"] + }, + "application/vnd.pmi.widget": { + "source": "iana", + "extensions": ["wg"] + }, + "application/vnd.poc.group-advertisement+xml": { + "source": "iana", + "compressible": true + }, + "application/vnd.pocketlearn": { + "source": "iana", + "extensions": ["plf"] + }, + "application/vnd.powerbuilder6": { + "source": "iana", + "extensions": ["pbd"] + }, + "application/vnd.powerbuilder6-s": { + "source": "iana" + }, + "application/vnd.powerbuilder7": { + "source": "iana" + }, + "application/vnd.powerbuilder7-s": { + "source": "iana" + }, + "application/vnd.powerbuilder75": { + "source": "iana" + }, + "application/vnd.powerbuilder75-s": { + "source": "iana" + }, + "application/vnd.preminet": { + "source": "iana" + }, + "application/vnd.previewsystems.box": { + "source": "iana", + "extensions": ["box"] + }, + "application/vnd.proteus.magazine": { + "source": "iana", + "extensions": ["mgz"] + }, + "application/vnd.psfs": { + "source": "iana" + }, + "application/vnd.publishare-delta-tree": { + "source": "iana", + "extensions": ["qps"] + }, + "application/vnd.pvi.ptid1": { + "source": "iana", + "extensions": ["ptid"] + }, + "application/vnd.pwg-multiplexed": { + "source": "iana" + }, + "application/vnd.pwg-xhtml-print+xml": { + "source": "iana", + "compressible": true + }, + "application/vnd.qualcomm.brew-app-res": { + "source": "iana" + }, + "application/vnd.quarantainenet": { + "source": "iana" + }, + "application/vnd.quark.quarkxpress": { + "source": "iana", + "extensions": ["qxd","qxt","qwd","qwt","qxl","qxb"] + }, + "application/vnd.quobject-quoxdocument": { + "source": "iana" + }, + "application/vnd.radisys.moml+xml": { + "source": "iana", + "compressible": true + }, + "application/vnd.radisys.msml+xml": { + "source": "iana", + "compressible": true + }, + "application/vnd.radisys.msml-audit+xml": { + "source": "iana", + "compressible": true + }, + "application/vnd.radisys.msml-audit-conf+xml": { + "source": "iana", + "compressible": true + }, + "application/vnd.radisys.msml-audit-conn+xml": { + "source": "iana", + "compressible": true + }, + "application/vnd.radisys.msml-audit-dialog+xml": { + "source": "iana", + "compressible": true + }, + "application/vnd.radisys.msml-audit-stream+xml": { + "source": "iana", + "compressible": true + }, + "application/vnd.radisys.msml-conf+xml": { + "source": "iana", + "compressible": true + }, + "application/vnd.radisys.msml-dialog+xml": { + "source": "iana", + "compressible": true + }, + "application/vnd.radisys.msml-dialog-base+xml": { + "source": "iana", + "compressible": true + }, + "application/vnd.radisys.msml-dialog-fax-detect+xml": { + "source": "iana", + "compressible": true + }, + "application/vnd.radisys.msml-dialog-fax-sendrecv+xml": { + "source": "iana", + "compressible": true + }, + "application/vnd.radisys.msml-dialog-group+xml": { + "source": "iana", + "compressible": true + }, + "application/vnd.radisys.msml-dialog-speech+xml": { + "source": "iana", + "compressible": true + }, + "application/vnd.radisys.msml-dialog-transform+xml": { + "source": "iana", + "compressible": true + }, + "application/vnd.rainstor.data": { + "source": "iana" + }, + "application/vnd.rapid": { + "source": "iana" + }, + "application/vnd.rar": { + "source": "iana", + "extensions": ["rar"] + }, + "application/vnd.realvnc.bed": { + "source": "iana", + "extensions": ["bed"] + }, + "application/vnd.recordare.musicxml": { + "source": "iana", + "extensions": ["mxl"] + }, + "application/vnd.recordare.musicxml+xml": { + "source": "iana", + "compressible": true, + "extensions": ["musicxml"] + }, + "application/vnd.renlearn.rlprint": { + "source": "iana" + }, + "application/vnd.resilient.logic": { + "source": "iana" + }, + "application/vnd.restful+json": { + "source": "iana", + "compressible": true + }, + "application/vnd.rig.cryptonote": { + "source": "iana", + "extensions": ["cryptonote"] + }, + "application/vnd.rim.cod": { + "source": "apache", + "extensions": ["cod"] + }, + "application/vnd.rn-realmedia": { + "source": "apache", + "extensions": ["rm"] + }, + "application/vnd.rn-realmedia-vbr": { + "source": "apache", + "extensions": ["rmvb"] + }, + "application/vnd.route66.link66+xml": { + "source": "iana", + "compressible": true, + "extensions": ["link66"] + }, + "application/vnd.rs-274x": { + "source": "iana" + }, + "application/vnd.ruckus.download": { + "source": "iana" + }, + "application/vnd.s3sms": { + "source": "iana" + }, + "application/vnd.sailingtracker.track": { + "source": "iana", + "extensions": ["st"] + }, + "application/vnd.sar": { + "source": "iana" + }, + "application/vnd.sbm.cid": { + "source": "iana" + }, + "application/vnd.sbm.mid2": { + "source": "iana" + }, + "application/vnd.scribus": { + "source": "iana" + }, + "application/vnd.sealed.3df": { + "source": "iana" + }, + "application/vnd.sealed.csf": { + "source": "iana" + }, + "application/vnd.sealed.doc": { + "source": "iana" + }, + "application/vnd.sealed.eml": { + "source": "iana" + }, + "application/vnd.sealed.mht": { + "source": "iana" + }, + "application/vnd.sealed.net": { + "source": "iana" + }, + "application/vnd.sealed.ppt": { + "source": "iana" + }, + "application/vnd.sealed.tiff": { + "source": "iana" + }, + "application/vnd.sealed.xls": { + "source": "iana" + }, + "application/vnd.sealedmedia.softseal.html": { + "source": "iana" + }, + "application/vnd.sealedmedia.softseal.pdf": { + "source": "iana" + }, + "application/vnd.seemail": { + "source": "iana", + "extensions": ["see"] + }, + "application/vnd.seis+json": { + "source": "iana", + "compressible": true + }, + "application/vnd.sema": { + "source": "iana", + "extensions": ["sema"] + }, + "application/vnd.semd": { + "source": "iana", + "extensions": ["semd"] + }, + "application/vnd.semf": { + "source": "iana", + "extensions": ["semf"] + }, + "application/vnd.shade-save-file": { + "source": "iana" + }, + "application/vnd.shana.informed.formdata": { + "source": "iana", + "extensions": ["ifm"] + }, + "application/vnd.shana.informed.formtemplate": { + "source": "iana", + "extensions": ["itp"] + }, + "application/vnd.shana.informed.interchange": { + "source": "iana", + "extensions": ["iif"] + }, + "application/vnd.shana.informed.package": { + "source": "iana", + "extensions": ["ipk"] + }, + "application/vnd.shootproof+json": { + "source": "iana", + "compressible": true + }, + "application/vnd.shopkick+json": { + "source": "iana", + "compressible": true + }, + "application/vnd.shp": { + "source": "iana" + }, + "application/vnd.shx": { + "source": "iana" + }, + "application/vnd.sigrok.session": { + "source": "iana" + }, + "application/vnd.simtech-mindmapper": { + "source": "iana", + "extensions": ["twd","twds"] + }, + "application/vnd.siren+json": { + "source": "iana", + "compressible": true + }, + "application/vnd.smaf": { + "source": "iana", + "extensions": ["mmf"] + }, + "application/vnd.smart.notebook": { + "source": "iana" + }, + "application/vnd.smart.teacher": { + "source": "iana", + "extensions": ["teacher"] + }, + "application/vnd.snesdev-page-table": { + "source": "iana" + }, + "application/vnd.software602.filler.form+xml": { + "source": "iana", + "compressible": true, + "extensions": ["fo"] + }, + "application/vnd.software602.filler.form-xml-zip": { + "source": "iana" + }, + "application/vnd.solent.sdkm+xml": { + "source": "iana", + "compressible": true, + "extensions": ["sdkm","sdkd"] + }, + "application/vnd.spotfire.dxp": { + "source": "iana", + "extensions": ["dxp"] + }, + "application/vnd.spotfire.sfs": { + "source": "iana", + "extensions": ["sfs"] + }, + "application/vnd.sqlite3": { + "source": "iana" + }, + "application/vnd.sss-cod": { + "source": "iana" + }, + "application/vnd.sss-dtf": { + "source": "iana" + }, + "application/vnd.sss-ntf": { + "source": "iana" + }, + "application/vnd.stardivision.calc": { + "source": "apache", + "extensions": ["sdc"] + }, + "application/vnd.stardivision.draw": { + "source": "apache", + "extensions": ["sda"] + }, + "application/vnd.stardivision.impress": { + "source": "apache", + "extensions": ["sdd"] + }, + "application/vnd.stardivision.math": { + "source": "apache", + "extensions": ["smf"] + }, + "application/vnd.stardivision.writer": { + "source": "apache", + "extensions": ["sdw","vor"] + }, + "application/vnd.stardivision.writer-global": { + "source": "apache", + "extensions": ["sgl"] + }, + "application/vnd.stepmania.package": { + "source": "iana", + "extensions": ["smzip"] + }, + "application/vnd.stepmania.stepchart": { + "source": "iana", + "extensions": ["sm"] + }, + "application/vnd.street-stream": { + "source": "iana" + }, + "application/vnd.sun.wadl+xml": { + "source": "iana", + "compressible": true, + "extensions": ["wadl"] + }, + "application/vnd.sun.xml.calc": { + "source": "apache", + "extensions": ["sxc"] + }, + "application/vnd.sun.xml.calc.template": { + "source": "apache", + "extensions": ["stc"] + }, + "application/vnd.sun.xml.draw": { + "source": "apache", + "extensions": ["sxd"] + }, + "application/vnd.sun.xml.draw.template": { + "source": "apache", + "extensions": ["std"] + }, + "application/vnd.sun.xml.impress": { + "source": "apache", + "extensions": ["sxi"] + }, + "application/vnd.sun.xml.impress.template": { + "source": "apache", + "extensions": ["sti"] + }, + "application/vnd.sun.xml.math": { + "source": "apache", + "extensions": ["sxm"] + }, + "application/vnd.sun.xml.writer": { + "source": "apache", + "extensions": ["sxw"] + }, + "application/vnd.sun.xml.writer.global": { + "source": "apache", + "extensions": ["sxg"] + }, + "application/vnd.sun.xml.writer.template": { + "source": "apache", + "extensions": ["stw"] + }, + "application/vnd.sus-calendar": { + "source": "iana", + "extensions": ["sus","susp"] + }, + "application/vnd.svd": { + "source": "iana", + "extensions": ["svd"] + }, + "application/vnd.swiftview-ics": { + "source": "iana" + }, + "application/vnd.sycle+xml": { + "source": "iana", + "compressible": true + }, + "application/vnd.syft+json": { + "source": "iana", + "compressible": true + }, + "application/vnd.symbian.install": { + "source": "apache", + "extensions": ["sis","sisx"] + }, + "application/vnd.syncml+xml": { + "source": "iana", + "charset": "UTF-8", + "compressible": true, + "extensions": ["xsm"] + }, + "application/vnd.syncml.dm+wbxml": { + "source": "iana", + "charset": "UTF-8", + "extensions": ["bdm"] + }, + "application/vnd.syncml.dm+xml": { + "source": "iana", + "charset": "UTF-8", + "compressible": true, + "extensions": ["xdm"] + }, + "application/vnd.syncml.dm.notification": { + "source": "iana" + }, + "application/vnd.syncml.dmddf+wbxml": { + "source": "iana" + }, + "application/vnd.syncml.dmddf+xml": { + "source": "iana", + "charset": "UTF-8", + "compressible": true, + "extensions": ["ddf"] + }, + "application/vnd.syncml.dmtnds+wbxml": { + "source": "iana" + }, + "application/vnd.syncml.dmtnds+xml": { + "source": "iana", + "charset": "UTF-8", + "compressible": true + }, + "application/vnd.syncml.ds.notification": { + "source": "iana" + }, + "application/vnd.tableschema+json": { + "source": "iana", + "compressible": true + }, + "application/vnd.tao.intent-module-archive": { + "source": "iana", + "extensions": ["tao"] + }, + "application/vnd.tcpdump.pcap": { + "source": "iana", + "extensions": ["pcap","cap","dmp"] + }, + "application/vnd.think-cell.ppttc+json": { + "source": "iana", + "compressible": true + }, + "application/vnd.tmd.mediaflex.api+xml": { + "source": "iana", + "compressible": true + }, + "application/vnd.tml": { + "source": "iana" + }, + "application/vnd.tmobile-livetv": { + "source": "iana", + "extensions": ["tmo"] + }, + "application/vnd.tri.onesource": { + "source": "iana" + }, + "application/vnd.trid.tpt": { + "source": "iana", + "extensions": ["tpt"] + }, + "application/vnd.triscape.mxs": { + "source": "iana", + "extensions": ["mxs"] + }, + "application/vnd.trueapp": { + "source": "iana", + "extensions": ["tra"] + }, + "application/vnd.truedoc": { + "source": "iana" + }, + "application/vnd.ubisoft.webplayer": { + "source": "iana" + }, + "application/vnd.ufdl": { + "source": "iana", + "extensions": ["ufd","ufdl"] + }, + "application/vnd.uiq.theme": { + "source": "iana", + "extensions": ["utz"] + }, + "application/vnd.umajin": { + "source": "iana", + "extensions": ["umj"] + }, + "application/vnd.unity": { + "source": "iana", + "extensions": ["unityweb"] + }, + "application/vnd.uoml+xml": { + "source": "iana", + "compressible": true, + "extensions": ["uoml"] + }, + "application/vnd.uplanet.alert": { + "source": "iana" + }, + "application/vnd.uplanet.alert-wbxml": { + "source": "iana" + }, + "application/vnd.uplanet.bearer-choice": { + "source": "iana" + }, + "application/vnd.uplanet.bearer-choice-wbxml": { + "source": "iana" + }, + "application/vnd.uplanet.cacheop": { + "source": "iana" + }, + "application/vnd.uplanet.cacheop-wbxml": { + "source": "iana" + }, + "application/vnd.uplanet.channel": { + "source": "iana" + }, + "application/vnd.uplanet.channel-wbxml": { + "source": "iana" + }, + "application/vnd.uplanet.list": { + "source": "iana" + }, + "application/vnd.uplanet.list-wbxml": { + "source": "iana" + }, + "application/vnd.uplanet.listcmd": { + "source": "iana" + }, + "application/vnd.uplanet.listcmd-wbxml": { + "source": "iana" + }, + "application/vnd.uplanet.signal": { + "source": "iana" + }, + "application/vnd.uri-map": { + "source": "iana" + }, + "application/vnd.valve.source.material": { + "source": "iana" + }, + "application/vnd.vcx": { + "source": "iana", + "extensions": ["vcx"] + }, + "application/vnd.vd-study": { + "source": "iana" + }, + "application/vnd.vectorworks": { + "source": "iana" + }, + "application/vnd.vel+json": { + "source": "iana", + "compressible": true + }, + "application/vnd.verimatrix.vcas": { + "source": "iana" + }, + "application/vnd.veritone.aion+json": { + "source": "iana", + "compressible": true + }, + "application/vnd.veryant.thin": { + "source": "iana" + }, + "application/vnd.ves.encrypted": { + "source": "iana" + }, + "application/vnd.vidsoft.vidconference": { + "source": "iana" + }, + "application/vnd.visio": { + "source": "iana", + "extensions": ["vsd","vst","vss","vsw"] + }, + "application/vnd.visionary": { + "source": "iana", + "extensions": ["vis"] + }, + "application/vnd.vividence.scriptfile": { + "source": "iana" + }, + "application/vnd.vsf": { + "source": "iana", + "extensions": ["vsf"] + }, + "application/vnd.wap.sic": { + "source": "iana" + }, + "application/vnd.wap.slc": { + "source": "iana" + }, + "application/vnd.wap.wbxml": { + "source": "iana", + "charset": "UTF-8", + "extensions": ["wbxml"] + }, + "application/vnd.wap.wmlc": { + "source": "iana", + "extensions": ["wmlc"] + }, + "application/vnd.wap.wmlscriptc": { + "source": "iana", + "extensions": ["wmlsc"] + }, + "application/vnd.webturbo": { + "source": "iana", + "extensions": ["wtb"] + }, + "application/vnd.wfa.dpp": { + "source": "iana" + }, + "application/vnd.wfa.p2p": { + "source": "iana" + }, + "application/vnd.wfa.wsc": { + "source": "iana" + }, + "application/vnd.windows.devicepairing": { + "source": "iana" + }, + "application/vnd.wmc": { + "source": "iana" + }, + "application/vnd.wmf.bootstrap": { + "source": "iana" + }, + "application/vnd.wolfram.mathematica": { + "source": "iana" + }, + "application/vnd.wolfram.mathematica.package": { + "source": "iana" + }, + "application/vnd.wolfram.player": { + "source": "iana", + "extensions": ["nbp"] + }, + "application/vnd.wordperfect": { + "source": "iana", + "extensions": ["wpd"] + }, + "application/vnd.wqd": { + "source": "iana", + "extensions": ["wqd"] + }, + "application/vnd.wrq-hp3000-labelled": { + "source": "iana" + }, + "application/vnd.wt.stf": { + "source": "iana", + "extensions": ["stf"] + }, + "application/vnd.wv.csp+wbxml": { + "source": "iana" + }, + "application/vnd.wv.csp+xml": { + "source": "iana", + "compressible": true + }, + "application/vnd.wv.ssp+xml": { + "source": "iana", + "compressible": true + }, + "application/vnd.xacml+json": { + "source": "iana", + "compressible": true + }, + "application/vnd.xara": { + "source": "iana", + "extensions": ["xar"] + }, + "application/vnd.xfdl": { + "source": "iana", + "extensions": ["xfdl"] + }, + "application/vnd.xfdl.webform": { + "source": "iana" + }, + "application/vnd.xmi+xml": { + "source": "iana", + "compressible": true + }, + "application/vnd.xmpie.cpkg": { + "source": "iana" + }, + "application/vnd.xmpie.dpkg": { + "source": "iana" + }, + "application/vnd.xmpie.plan": { + "source": "iana" + }, + "application/vnd.xmpie.ppkg": { + "source": "iana" + }, + "application/vnd.xmpie.xlim": { + "source": "iana" + }, + "application/vnd.yamaha.hv-dic": { + "source": "iana", + "extensions": ["hvd"] + }, + "application/vnd.yamaha.hv-script": { + "source": "iana", + "extensions": ["hvs"] + }, + "application/vnd.yamaha.hv-voice": { + "source": "iana", + "extensions": ["hvp"] + }, + "application/vnd.yamaha.openscoreformat": { + "source": "iana", + "extensions": ["osf"] + }, + "application/vnd.yamaha.openscoreformat.osfpvg+xml": { + "source": "iana", + "compressible": true, + "extensions": ["osfpvg"] + }, + "application/vnd.yamaha.remote-setup": { + "source": "iana" + }, + "application/vnd.yamaha.smaf-audio": { + "source": "iana", + "extensions": ["saf"] + }, + "application/vnd.yamaha.smaf-phrase": { + "source": "iana", + "extensions": ["spf"] + }, + "application/vnd.yamaha.through-ngn": { + "source": "iana" + }, + "application/vnd.yamaha.tunnel-udpencap": { + "source": "iana" + }, + "application/vnd.yaoweme": { + "source": "iana" + }, + "application/vnd.yellowriver-custom-menu": { + "source": "iana", + "extensions": ["cmp"] + }, + "application/vnd.youtube.yt": { + "source": "iana" + }, + "application/vnd.zul": { + "source": "iana", + "extensions": ["zir","zirz"] + }, + "application/vnd.zzazz.deck+xml": { + "source": "iana", + "compressible": true, + "extensions": ["zaz"] + }, + "application/voicexml+xml": { + "source": "iana", + "compressible": true, + "extensions": ["vxml"] + }, + "application/voucher-cms+json": { + "source": "iana", + "compressible": true + }, + "application/vq-rtcpxr": { + "source": "iana" + }, + "application/wasm": { + "source": "iana", + "compressible": true, + "extensions": ["wasm"] + }, + "application/watcherinfo+xml": { + "source": "iana", + "compressible": true, + "extensions": ["wif"] + }, + "application/webpush-options+json": { + "source": "iana", + "compressible": true + }, + "application/whoispp-query": { + "source": "iana" + }, + "application/whoispp-response": { + "source": "iana" + }, + "application/widget": { + "source": "iana", + "extensions": ["wgt"] + }, + "application/winhlp": { + "source": "apache", + "extensions": ["hlp"] + }, + "application/wita": { + "source": "iana" + }, + "application/wordperfect5.1": { + "source": "iana" + }, + "application/wsdl+xml": { + "source": "iana", + "compressible": true, + "extensions": ["wsdl"] + }, + "application/wspolicy+xml": { + "source": "iana", + "compressible": true, + "extensions": ["wspolicy"] + }, + "application/x-7z-compressed": { + "source": "apache", + "compressible": false, + "extensions": ["7z"] + }, + "application/x-abiword": { + "source": "apache", + "extensions": ["abw"] + }, + "application/x-ace-compressed": { + "source": "apache", + "extensions": ["ace"] + }, + "application/x-amf": { + "source": "apache" + }, + "application/x-apple-diskimage": { + "source": "apache", + "extensions": ["dmg"] + }, + "application/x-arj": { + "compressible": false, + "extensions": ["arj"] + }, + "application/x-authorware-bin": { + "source": "apache", + "extensions": ["aab","x32","u32","vox"] + }, + "application/x-authorware-map": { + "source": "apache", + "extensions": ["aam"] + }, + "application/x-authorware-seg": { + "source": "apache", + "extensions": ["aas"] + }, + "application/x-bcpio": { + "source": "apache", + "extensions": ["bcpio"] + }, + "application/x-bdoc": { + "compressible": false, + "extensions": ["bdoc"] + }, + "application/x-bittorrent": { + "source": "apache", + "extensions": ["torrent"] + }, + "application/x-blorb": { + "source": "apache", + "extensions": ["blb","blorb"] + }, + "application/x-bzip": { + "source": "apache", + "compressible": false, + "extensions": ["bz"] + }, + "application/x-bzip2": { + "source": "apache", + "compressible": false, + "extensions": ["bz2","boz"] + }, + "application/x-cbr": { + "source": "apache", + "extensions": ["cbr","cba","cbt","cbz","cb7"] + }, + "application/x-cdlink": { + "source": "apache", + "extensions": ["vcd"] + }, + "application/x-cfs-compressed": { + "source": "apache", + "extensions": ["cfs"] + }, + "application/x-chat": { + "source": "apache", + "extensions": ["chat"] + }, + "application/x-chess-pgn": { + "source": "apache", + "extensions": ["pgn"] + }, + "application/x-chrome-extension": { + "extensions": ["crx"] + }, + "application/x-cocoa": { + "source": "nginx", + "extensions": ["cco"] + }, + "application/x-compress": { + "source": "apache" + }, + "application/x-conference": { + "source": "apache", + "extensions": ["nsc"] + }, + "application/x-cpio": { + "source": "apache", + "extensions": ["cpio"] + }, + "application/x-csh": { + "source": "apache", + "extensions": ["csh"] + }, + "application/x-deb": { + "compressible": false + }, + "application/x-debian-package": { + "source": "apache", + "extensions": ["deb","udeb"] + }, + "application/x-dgc-compressed": { + "source": "apache", + "extensions": ["dgc"] + }, + "application/x-director": { + "source": "apache", + "extensions": ["dir","dcr","dxr","cst","cct","cxt","w3d","fgd","swa"] + }, + "application/x-doom": { + "source": "apache", + "extensions": ["wad"] + }, + "application/x-dtbncx+xml": { + "source": "apache", + "compressible": true, + "extensions": ["ncx"] + }, + "application/x-dtbook+xml": { + "source": "apache", + "compressible": true, + "extensions": ["dtb"] + }, + "application/x-dtbresource+xml": { + "source": "apache", + "compressible": true, + "extensions": ["res"] + }, + "application/x-dvi": { + "source": "apache", + "compressible": false, + "extensions": ["dvi"] + }, + "application/x-envoy": { + "source": "apache", + "extensions": ["evy"] + }, + "application/x-eva": { + "source": "apache", + "extensions": ["eva"] + }, + "application/x-font-bdf": { + "source": "apache", + "extensions": ["bdf"] + }, + "application/x-font-dos": { + "source": "apache" + }, + "application/x-font-framemaker": { + "source": "apache" + }, + "application/x-font-ghostscript": { + "source": "apache", + "extensions": ["gsf"] + }, + "application/x-font-libgrx": { + "source": "apache" + }, + "application/x-font-linux-psf": { + "source": "apache", + "extensions": ["psf"] + }, + "application/x-font-pcf": { + "source": "apache", + "extensions": ["pcf"] + }, + "application/x-font-snf": { + "source": "apache", + "extensions": ["snf"] + }, + "application/x-font-speedo": { + "source": "apache" + }, + "application/x-font-sunos-news": { + "source": "apache" + }, + "application/x-font-type1": { + "source": "apache", + "extensions": ["pfa","pfb","pfm","afm"] + }, + "application/x-font-vfont": { + "source": "apache" + }, + "application/x-freearc": { + "source": "apache", + "extensions": ["arc"] + }, + "application/x-futuresplash": { + "source": "apache", + "extensions": ["spl"] + }, + "application/x-gca-compressed": { + "source": "apache", + "extensions": ["gca"] + }, + "application/x-glulx": { + "source": "apache", + "extensions": ["ulx"] + }, + "application/x-gnumeric": { + "source": "apache", + "extensions": ["gnumeric"] + }, + "application/x-gramps-xml": { + "source": "apache", + "extensions": ["gramps"] + }, + "application/x-gtar": { + "source": "apache", + "extensions": ["gtar"] + }, + "application/x-gzip": { + "source": "apache" + }, + "application/x-hdf": { + "source": "apache", + "extensions": ["hdf"] + }, + "application/x-httpd-php": { + "compressible": true, + "extensions": ["php"] + }, + "application/x-install-instructions": { + "source": "apache", + "extensions": ["install"] + }, + "application/x-iso9660-image": { + "source": "apache", + "extensions": ["iso"] + }, + "application/x-iwork-keynote-sffkey": { + "extensions": ["key"] + }, + "application/x-iwork-numbers-sffnumbers": { + "extensions": ["numbers"] + }, + "application/x-iwork-pages-sffpages": { + "extensions": ["pages"] + }, + "application/x-java-archive-diff": { + "source": "nginx", + "extensions": ["jardiff"] + }, + "application/x-java-jnlp-file": { + "source": "apache", + "compressible": false, + "extensions": ["jnlp"] + }, + "application/x-javascript": { + "compressible": true + }, + "application/x-keepass2": { + "extensions": ["kdbx"] + }, + "application/x-latex": { + "source": "apache", + "compressible": false, + "extensions": ["latex"] + }, + "application/x-lua-bytecode": { + "extensions": ["luac"] + }, + "application/x-lzh-compressed": { + "source": "apache", + "extensions": ["lzh","lha"] + }, + "application/x-makeself": { + "source": "nginx", + "extensions": ["run"] + }, + "application/x-mie": { + "source": "apache", + "extensions": ["mie"] + }, + "application/x-mobipocket-ebook": { + "source": "apache", + "extensions": ["prc","mobi"] + }, + "application/x-mpegurl": { + "compressible": false + }, + "application/x-ms-application": { + "source": "apache", + "extensions": ["application"] + }, + "application/x-ms-shortcut": { + "source": "apache", + "extensions": ["lnk"] + }, + "application/x-ms-wmd": { + "source": "apache", + "extensions": ["wmd"] + }, + "application/x-ms-wmz": { + "source": "apache", + "extensions": ["wmz"] + }, + "application/x-ms-xbap": { + "source": "apache", + "extensions": ["xbap"] + }, + "application/x-msaccess": { + "source": "apache", + "extensions": ["mdb"] + }, + "application/x-msbinder": { + "source": "apache", + "extensions": ["obd"] + }, + "application/x-mscardfile": { + "source": "apache", + "extensions": ["crd"] + }, + "application/x-msclip": { + "source": "apache", + "extensions": ["clp"] + }, + "application/x-msdos-program": { + "extensions": ["exe"] + }, + "application/x-msdownload": { + "source": "apache", + "extensions": ["exe","dll","com","bat","msi"] + }, + "application/x-msmediaview": { + "source": "apache", + "extensions": ["mvb","m13","m14"] + }, + "application/x-msmetafile": { + "source": "apache", + "extensions": ["wmf","wmz","emf","emz"] + }, + "application/x-msmoney": { + "source": "apache", + "extensions": ["mny"] + }, + "application/x-mspublisher": { + "source": "apache", + "extensions": ["pub"] + }, + "application/x-msschedule": { + "source": "apache", + "extensions": ["scd"] + }, + "application/x-msterminal": { + "source": "apache", + "extensions": ["trm"] + }, + "application/x-mswrite": { + "source": "apache", + "extensions": ["wri"] + }, + "application/x-netcdf": { + "source": "apache", + "extensions": ["nc","cdf"] + }, + "application/x-ns-proxy-autoconfig": { + "compressible": true, + "extensions": ["pac"] + }, + "application/x-nzb": { + "source": "apache", + "extensions": ["nzb"] + }, + "application/x-perl": { + "source": "nginx", + "extensions": ["pl","pm"] + }, + "application/x-pilot": { + "source": "nginx", + "extensions": ["prc","pdb"] + }, + "application/x-pkcs12": { + "source": "apache", + "compressible": false, + "extensions": ["p12","pfx"] + }, + "application/x-pkcs7-certificates": { + "source": "apache", + "extensions": ["p7b","spc"] + }, + "application/x-pkcs7-certreqresp": { + "source": "apache", + "extensions": ["p7r"] + }, + "application/x-pki-message": { + "source": "iana" + }, + "application/x-rar-compressed": { + "source": "apache", + "compressible": false, + "extensions": ["rar"] + }, + "application/x-redhat-package-manager": { + "source": "nginx", + "extensions": ["rpm"] + }, + "application/x-research-info-systems": { + "source": "apache", + "extensions": ["ris"] + }, + "application/x-sea": { + "source": "nginx", + "extensions": ["sea"] + }, + "application/x-sh": { + "source": "apache", + "compressible": true, + "extensions": ["sh"] + }, + "application/x-shar": { + "source": "apache", + "extensions": ["shar"] + }, + "application/x-shockwave-flash": { + "source": "apache", + "compressible": false, + "extensions": ["swf"] + }, + "application/x-silverlight-app": { + "source": "apache", + "extensions": ["xap"] + }, + "application/x-sql": { + "source": "apache", + "extensions": ["sql"] + }, + "application/x-stuffit": { + "source": "apache", + "compressible": false, + "extensions": ["sit"] + }, + "application/x-stuffitx": { + "source": "apache", + "extensions": ["sitx"] + }, + "application/x-subrip": { + "source": "apache", + "extensions": ["srt"] + }, + "application/x-sv4cpio": { + "source": "apache", + "extensions": ["sv4cpio"] + }, + "application/x-sv4crc": { + "source": "apache", + "extensions": ["sv4crc"] + }, + "application/x-t3vm-image": { + "source": "apache", + "extensions": ["t3"] + }, + "application/x-tads": { + "source": "apache", + "extensions": ["gam"] + }, + "application/x-tar": { + "source": "apache", + "compressible": true, + "extensions": ["tar"] + }, + "application/x-tcl": { + "source": "apache", + "extensions": ["tcl","tk"] + }, + "application/x-tex": { + "source": "apache", + "extensions": ["tex"] + }, + "application/x-tex-tfm": { + "source": "apache", + "extensions": ["tfm"] + }, + "application/x-texinfo": { + "source": "apache", + "extensions": ["texinfo","texi"] + }, + "application/x-tgif": { + "source": "apache", + "extensions": ["obj"] + }, + "application/x-ustar": { + "source": "apache", + "extensions": ["ustar"] + }, + "application/x-virtualbox-hdd": { + "compressible": true, + "extensions": ["hdd"] + }, + "application/x-virtualbox-ova": { + "compressible": true, + "extensions": ["ova"] + }, + "application/x-virtualbox-ovf": { + "compressible": true, + "extensions": ["ovf"] + }, + "application/x-virtualbox-vbox": { + "compressible": true, + "extensions": ["vbox"] + }, + "application/x-virtualbox-vbox-extpack": { + "compressible": false, + "extensions": ["vbox-extpack"] + }, + "application/x-virtualbox-vdi": { + "compressible": true, + "extensions": ["vdi"] + }, + "application/x-virtualbox-vhd": { + "compressible": true, + "extensions": ["vhd"] + }, + "application/x-virtualbox-vmdk": { + "compressible": true, + "extensions": ["vmdk"] + }, + "application/x-wais-source": { + "source": "apache", + "extensions": ["src"] + }, + "application/x-web-app-manifest+json": { + "compressible": true, + "extensions": ["webapp"] + }, + "application/x-www-form-urlencoded": { + "source": "iana", + "compressible": true + }, + "application/x-x509-ca-cert": { + "source": "iana", + "extensions": ["der","crt","pem"] + }, + "application/x-x509-ca-ra-cert": { + "source": "iana" + }, + "application/x-x509-next-ca-cert": { + "source": "iana" + }, + "application/x-xfig": { + "source": "apache", + "extensions": ["fig"] + }, + "application/x-xliff+xml": { + "source": "apache", + "compressible": true, + "extensions": ["xlf"] + }, + "application/x-xpinstall": { + "source": "apache", + "compressible": false, + "extensions": ["xpi"] + }, + "application/x-xz": { + "source": "apache", + "extensions": ["xz"] + }, + "application/x-zmachine": { + "source": "apache", + "extensions": ["z1","z2","z3","z4","z5","z6","z7","z8"] + }, + "application/x400-bp": { + "source": "iana" + }, + "application/xacml+xml": { + "source": "iana", + "compressible": true + }, + "application/xaml+xml": { + "source": "apache", + "compressible": true, + "extensions": ["xaml"] + }, + "application/xcap-att+xml": { + "source": "iana", + "compressible": true, + "extensions": ["xav"] + }, + "application/xcap-caps+xml": { + "source": "iana", + "compressible": true, + "extensions": ["xca"] + }, + "application/xcap-diff+xml": { + "source": "iana", + "compressible": true, + "extensions": ["xdf"] + }, + "application/xcap-el+xml": { + "source": "iana", + "compressible": true, + "extensions": ["xel"] + }, + "application/xcap-error+xml": { + "source": "iana", + "compressible": true + }, + "application/xcap-ns+xml": { + "source": "iana", + "compressible": true, + "extensions": ["xns"] + }, + "application/xcon-conference-info+xml": { + "source": "iana", + "compressible": true + }, + "application/xcon-conference-info-diff+xml": { + "source": "iana", + "compressible": true + }, + "application/xenc+xml": { + "source": "iana", + "compressible": true, + "extensions": ["xenc"] + }, + "application/xhtml+xml": { + "source": "iana", + "compressible": true, + "extensions": ["xhtml","xht"] + }, + "application/xhtml-voice+xml": { + "source": "apache", + "compressible": true + }, + "application/xliff+xml": { + "source": "iana", + "compressible": true, + "extensions": ["xlf"] + }, + "application/xml": { + "source": "iana", + "compressible": true, + "extensions": ["xml","xsl","xsd","rng"] + }, + "application/xml-dtd": { + "source": "iana", + "compressible": true, + "extensions": ["dtd"] + }, + "application/xml-external-parsed-entity": { + "source": "iana" + }, + "application/xml-patch+xml": { + "source": "iana", + "compressible": true + }, + "application/xmpp+xml": { + "source": "iana", + "compressible": true + }, + "application/xop+xml": { + "source": "iana", + "compressible": true, + "extensions": ["xop"] + }, + "application/xproc+xml": { + "source": "apache", + "compressible": true, + "extensions": ["xpl"] + }, + "application/xslt+xml": { + "source": "iana", + "compressible": true, + "extensions": ["xsl","xslt"] + }, + "application/xspf+xml": { + "source": "apache", + "compressible": true, + "extensions": ["xspf"] + }, + "application/xv+xml": { + "source": "iana", + "compressible": true, + "extensions": ["mxml","xhvml","xvml","xvm"] + }, + "application/yang": { + "source": "iana", + "extensions": ["yang"] + }, + "application/yang-data+json": { + "source": "iana", + "compressible": true + }, + "application/yang-data+xml": { + "source": "iana", + "compressible": true + }, + "application/yang-patch+json": { + "source": "iana", + "compressible": true + }, + "application/yang-patch+xml": { + "source": "iana", + "compressible": true + }, + "application/yin+xml": { + "source": "iana", + "compressible": true, + "extensions": ["yin"] + }, + "application/zip": { + "source": "iana", + "compressible": false, + "extensions": ["zip"] + }, + "application/zlib": { + "source": "iana" + }, + "application/zstd": { + "source": "iana" + }, + "audio/1d-interleaved-parityfec": { + "source": "iana" + }, + "audio/32kadpcm": { + "source": "iana" + }, + "audio/3gpp": { + "source": "iana", + "compressible": false, + "extensions": ["3gpp"] + }, + "audio/3gpp2": { + "source": "iana" + }, + "audio/aac": { + "source": "iana" + }, + "audio/ac3": { + "source": "iana" + }, + "audio/adpcm": { + "source": "apache", + "extensions": ["adp"] + }, + "audio/amr": { + "source": "iana", + "extensions": ["amr"] + }, + "audio/amr-wb": { + "source": "iana" + }, + "audio/amr-wb+": { + "source": "iana" + }, + "audio/aptx": { + "source": "iana" + }, + "audio/asc": { + "source": "iana" + }, + "audio/atrac-advanced-lossless": { + "source": "iana" + }, + "audio/atrac-x": { + "source": "iana" + }, + "audio/atrac3": { + "source": "iana" + }, + "audio/basic": { + "source": "iana", + "compressible": false, + "extensions": ["au","snd"] + }, + "audio/bv16": { + "source": "iana" + }, + "audio/bv32": { + "source": "iana" + }, + "audio/clearmode": { + "source": "iana" + }, + "audio/cn": { + "source": "iana" + }, + "audio/dat12": { + "source": "iana" + }, + "audio/dls": { + "source": "iana" + }, + "audio/dsr-es201108": { + "source": "iana" + }, + "audio/dsr-es202050": { + "source": "iana" + }, + "audio/dsr-es202211": { + "source": "iana" + }, + "audio/dsr-es202212": { + "source": "iana" + }, + "audio/dv": { + "source": "iana" + }, + "audio/dvi4": { + "source": "iana" + }, + "audio/eac3": { + "source": "iana" + }, + "audio/encaprtp": { + "source": "iana" + }, + "audio/evrc": { + "source": "iana" + }, + "audio/evrc-qcp": { + "source": "iana" + }, + "audio/evrc0": { + "source": "iana" + }, + "audio/evrc1": { + "source": "iana" + }, + "audio/evrcb": { + "source": "iana" + }, + "audio/evrcb0": { + "source": "iana" + }, + "audio/evrcb1": { + "source": "iana" + }, + "audio/evrcnw": { + "source": "iana" + }, + "audio/evrcnw0": { + "source": "iana" + }, + "audio/evrcnw1": { + "source": "iana" + }, + "audio/evrcwb": { + "source": "iana" + }, + "audio/evrcwb0": { + "source": "iana" + }, + "audio/evrcwb1": { + "source": "iana" + }, + "audio/evs": { + "source": "iana" + }, + "audio/flexfec": { + "source": "iana" + }, + "audio/fwdred": { + "source": "iana" + }, + "audio/g711-0": { + "source": "iana" + }, + "audio/g719": { + "source": "iana" + }, + "audio/g722": { + "source": "iana" + }, + "audio/g7221": { + "source": "iana" + }, + "audio/g723": { + "source": "iana" + }, + "audio/g726-16": { + "source": "iana" + }, + "audio/g726-24": { + "source": "iana" + }, + "audio/g726-32": { + "source": "iana" + }, + "audio/g726-40": { + "source": "iana" + }, + "audio/g728": { + "source": "iana" + }, + "audio/g729": { + "source": "iana" + }, + "audio/g7291": { + "source": "iana" + }, + "audio/g729d": { + "source": "iana" + }, + "audio/g729e": { + "source": "iana" + }, + "audio/gsm": { + "source": "iana" + }, + "audio/gsm-efr": { + "source": "iana" + }, + "audio/gsm-hr-08": { + "source": "iana" + }, + "audio/ilbc": { + "source": "iana" + }, + "audio/ip-mr_v2.5": { + "source": "iana" + }, + "audio/isac": { + "source": "apache" + }, + "audio/l16": { + "source": "iana" + }, + "audio/l20": { + "source": "iana" + }, + "audio/l24": { + "source": "iana", + "compressible": false + }, + "audio/l8": { + "source": "iana" + }, + "audio/lpc": { + "source": "iana" + }, + "audio/melp": { + "source": "iana" + }, + "audio/melp1200": { + "source": "iana" + }, + "audio/melp2400": { + "source": "iana" + }, + "audio/melp600": { + "source": "iana" + }, + "audio/mhas": { + "source": "iana" + }, + "audio/midi": { + "source": "apache", + "extensions": ["mid","midi","kar","rmi"] + }, + "audio/mobile-xmf": { + "source": "iana", + "extensions": ["mxmf"] + }, + "audio/mp3": { + "compressible": false, + "extensions": ["mp3"] + }, + "audio/mp4": { + "source": "iana", + "compressible": false, + "extensions": ["m4a","mp4a"] + }, + "audio/mp4a-latm": { + "source": "iana" + }, + "audio/mpa": { + "source": "iana" + }, + "audio/mpa-robust": { + "source": "iana" + }, + "audio/mpeg": { + "source": "iana", + "compressible": false, + "extensions": ["mpga","mp2","mp2a","mp3","m2a","m3a"] + }, + "audio/mpeg4-generic": { + "source": "iana" + }, + "audio/musepack": { + "source": "apache" + }, + "audio/ogg": { + "source": "iana", + "compressible": false, + "extensions": ["oga","ogg","spx","opus"] + }, + "audio/opus": { + "source": "iana" + }, + "audio/parityfec": { + "source": "iana" + }, + "audio/pcma": { + "source": "iana" + }, + "audio/pcma-wb": { + "source": "iana" + }, + "audio/pcmu": { + "source": "iana" + }, + "audio/pcmu-wb": { + "source": "iana" + }, + "audio/prs.sid": { + "source": "iana" + }, + "audio/qcelp": { + "source": "iana" + }, + "audio/raptorfec": { + "source": "iana" + }, + "audio/red": { + "source": "iana" + }, + "audio/rtp-enc-aescm128": { + "source": "iana" + }, + "audio/rtp-midi": { + "source": "iana" + }, + "audio/rtploopback": { + "source": "iana" + }, + "audio/rtx": { + "source": "iana" + }, + "audio/s3m": { + "source": "apache", + "extensions": ["s3m"] + }, + "audio/scip": { + "source": "iana" + }, + "audio/silk": { + "source": "apache", + "extensions": ["sil"] + }, + "audio/smv": { + "source": "iana" + }, + "audio/smv-qcp": { + "source": "iana" + }, + "audio/smv0": { + "source": "iana" + }, + "audio/sofa": { + "source": "iana" + }, + "audio/sp-midi": { + "source": "iana" + }, + "audio/speex": { + "source": "iana" + }, + "audio/t140c": { + "source": "iana" + }, + "audio/t38": { + "source": "iana" + }, + "audio/telephone-event": { + "source": "iana" + }, + "audio/tetra_acelp": { + "source": "iana" + }, + "audio/tetra_acelp_bb": { + "source": "iana" + }, + "audio/tone": { + "source": "iana" + }, + "audio/tsvcis": { + "source": "iana" + }, + "audio/uemclip": { + "source": "iana" + }, + "audio/ulpfec": { + "source": "iana" + }, + "audio/usac": { + "source": "iana" + }, + "audio/vdvi": { + "source": "iana" + }, + "audio/vmr-wb": { + "source": "iana" + }, + "audio/vnd.3gpp.iufp": { + "source": "iana" + }, + "audio/vnd.4sb": { + "source": "iana" + }, + "audio/vnd.audiokoz": { + "source": "iana" + }, + "audio/vnd.celp": { + "source": "iana" + }, + "audio/vnd.cisco.nse": { + "source": "iana" + }, + "audio/vnd.cmles.radio-events": { + "source": "iana" + }, + "audio/vnd.cns.anp1": { + "source": "iana" + }, + "audio/vnd.cns.inf1": { + "source": "iana" + }, + "audio/vnd.dece.audio": { + "source": "iana", + "extensions": ["uva","uvva"] + }, + "audio/vnd.digital-winds": { + "source": "iana", + "extensions": ["eol"] + }, + "audio/vnd.dlna.adts": { + "source": "iana" + }, + "audio/vnd.dolby.heaac.1": { + "source": "iana" + }, + "audio/vnd.dolby.heaac.2": { + "source": "iana" + }, + "audio/vnd.dolby.mlp": { + "source": "iana" + }, + "audio/vnd.dolby.mps": { + "source": "iana" + }, + "audio/vnd.dolby.pl2": { + "source": "iana" + }, + "audio/vnd.dolby.pl2x": { + "source": "iana" + }, + "audio/vnd.dolby.pl2z": { + "source": "iana" + }, + "audio/vnd.dolby.pulse.1": { + "source": "iana" + }, + "audio/vnd.dra": { + "source": "iana", + "extensions": ["dra"] + }, + "audio/vnd.dts": { + "source": "iana", + "extensions": ["dts"] + }, + "audio/vnd.dts.hd": { + "source": "iana", + "extensions": ["dtshd"] + }, + "audio/vnd.dts.uhd": { + "source": "iana" + }, + "audio/vnd.dvb.file": { + "source": "iana" + }, + "audio/vnd.everad.plj": { + "source": "iana" + }, + "audio/vnd.hns.audio": { + "source": "iana" + }, + "audio/vnd.lucent.voice": { + "source": "iana", + "extensions": ["lvp"] + }, + "audio/vnd.ms-playready.media.pya": { + "source": "iana", + "extensions": ["pya"] + }, + "audio/vnd.nokia.mobile-xmf": { + "source": "iana" + }, + "audio/vnd.nortel.vbk": { + "source": "iana" + }, + "audio/vnd.nuera.ecelp4800": { + "source": "iana", + "extensions": ["ecelp4800"] + }, + "audio/vnd.nuera.ecelp7470": { + "source": "iana", + "extensions": ["ecelp7470"] + }, + "audio/vnd.nuera.ecelp9600": { + "source": "iana", + "extensions": ["ecelp9600"] + }, + "audio/vnd.octel.sbc": { + "source": "iana" + }, + "audio/vnd.presonus.multitrack": { + "source": "iana" + }, + "audio/vnd.qcelp": { + "source": "iana" + }, + "audio/vnd.rhetorex.32kadpcm": { + "source": "iana" + }, + "audio/vnd.rip": { + "source": "iana", + "extensions": ["rip"] + }, + "audio/vnd.rn-realaudio": { + "compressible": false + }, + "audio/vnd.sealedmedia.softseal.mpeg": { + "source": "iana" + }, + "audio/vnd.vmx.cvsd": { + "source": "iana" + }, + "audio/vnd.wave": { + "compressible": false + }, + "audio/vorbis": { + "source": "iana", + "compressible": false + }, + "audio/vorbis-config": { + "source": "iana" + }, + "audio/wav": { + "compressible": false, + "extensions": ["wav"] + }, + "audio/wave": { + "compressible": false, + "extensions": ["wav"] + }, + "audio/webm": { + "source": "apache", + "compressible": false, + "extensions": ["weba"] + }, + "audio/x-aac": { + "source": "apache", + "compressible": false, + "extensions": ["aac"] + }, + "audio/x-aiff": { + "source": "apache", + "extensions": ["aif","aiff","aifc"] + }, + "audio/x-caf": { + "source": "apache", + "compressible": false, + "extensions": ["caf"] + }, + "audio/x-flac": { + "source": "apache", + "extensions": ["flac"] + }, + "audio/x-m4a": { + "source": "nginx", + "extensions": ["m4a"] + }, + "audio/x-matroska": { + "source": "apache", + "extensions": ["mka"] + }, + "audio/x-mpegurl": { + "source": "apache", + "extensions": ["m3u"] + }, + "audio/x-ms-wax": { + "source": "apache", + "extensions": ["wax"] + }, + "audio/x-ms-wma": { + "source": "apache", + "extensions": ["wma"] + }, + "audio/x-pn-realaudio": { + "source": "apache", + "extensions": ["ram","ra"] + }, + "audio/x-pn-realaudio-plugin": { + "source": "apache", + "extensions": ["rmp"] + }, + "audio/x-realaudio": { + "source": "nginx", + "extensions": ["ra"] + }, + "audio/x-tta": { + "source": "apache" + }, + "audio/x-wav": { + "source": "apache", + "extensions": ["wav"] + }, + "audio/xm": { + "source": "apache", + "extensions": ["xm"] + }, + "chemical/x-cdx": { + "source": "apache", + "extensions": ["cdx"] + }, + "chemical/x-cif": { + "source": "apache", + "extensions": ["cif"] + }, + "chemical/x-cmdf": { + "source": "apache", + "extensions": ["cmdf"] + }, + "chemical/x-cml": { + "source": "apache", + "extensions": ["cml"] + }, + "chemical/x-csml": { + "source": "apache", + "extensions": ["csml"] + }, + "chemical/x-pdb": { + "source": "apache" + }, + "chemical/x-xyz": { + "source": "apache", + "extensions": ["xyz"] + }, + "font/collection": { + "source": "iana", + "extensions": ["ttc"] + }, + "font/otf": { + "source": "iana", + "compressible": true, + "extensions": ["otf"] + }, + "font/sfnt": { + "source": "iana" + }, + "font/ttf": { + "source": "iana", + "compressible": true, + "extensions": ["ttf"] + }, + "font/woff": { + "source": "iana", + "extensions": ["woff"] + }, + "font/woff2": { + "source": "iana", + "extensions": ["woff2"] + }, + "image/aces": { + "source": "iana", + "extensions": ["exr"] + }, + "image/apng": { + "compressible": false, + "extensions": ["apng"] + }, + "image/avci": { + "source": "iana", + "extensions": ["avci"] + }, + "image/avcs": { + "source": "iana", + "extensions": ["avcs"] + }, + "image/avif": { + "source": "iana", + "compressible": false, + "extensions": ["avif"] + }, + "image/bmp": { + "source": "iana", + "compressible": true, + "extensions": ["bmp"] + }, + "image/cgm": { + "source": "iana", + "extensions": ["cgm"] + }, + "image/dicom-rle": { + "source": "iana", + "extensions": ["drle"] + }, + "image/emf": { + "source": "iana", + "extensions": ["emf"] + }, + "image/fits": { + "source": "iana", + "extensions": ["fits"] + }, + "image/g3fax": { + "source": "iana", + "extensions": ["g3"] + }, + "image/gif": { + "source": "iana", + "compressible": false, + "extensions": ["gif"] + }, + "image/heic": { + "source": "iana", + "extensions": ["heic"] + }, + "image/heic-sequence": { + "source": "iana", + "extensions": ["heics"] + }, + "image/heif": { + "source": "iana", + "extensions": ["heif"] + }, + "image/heif-sequence": { + "source": "iana", + "extensions": ["heifs"] + }, + "image/hej2k": { + "source": "iana", + "extensions": ["hej2"] + }, + "image/hsj2": { + "source": "iana", + "extensions": ["hsj2"] + }, + "image/ief": { + "source": "iana", + "extensions": ["ief"] + }, + "image/jls": { + "source": "iana", + "extensions": ["jls"] + }, + "image/jp2": { + "source": "iana", + "compressible": false, + "extensions": ["jp2","jpg2"] + }, + "image/jpeg": { + "source": "iana", + "compressible": false, + "extensions": ["jpeg","jpg","jpe"] + }, + "image/jph": { + "source": "iana", + "extensions": ["jph"] + }, + "image/jphc": { + "source": "iana", + "extensions": ["jhc"] + }, + "image/jpm": { + "source": "iana", + "compressible": false, + "extensions": ["jpm"] + }, + "image/jpx": { + "source": "iana", + "compressible": false, + "extensions": ["jpx","jpf"] + }, + "image/jxr": { + "source": "iana", + "extensions": ["jxr"] + }, + "image/jxra": { + "source": "iana", + "extensions": ["jxra"] + }, + "image/jxrs": { + "source": "iana", + "extensions": ["jxrs"] + }, + "image/jxs": { + "source": "iana", + "extensions": ["jxs"] + }, + "image/jxsc": { + "source": "iana", + "extensions": ["jxsc"] + }, + "image/jxsi": { + "source": "iana", + "extensions": ["jxsi"] + }, + "image/jxss": { + "source": "iana", + "extensions": ["jxss"] + }, + "image/ktx": { + "source": "iana", + "extensions": ["ktx"] + }, + "image/ktx2": { + "source": "iana", + "extensions": ["ktx2"] + }, + "image/naplps": { + "source": "iana" + }, + "image/pjpeg": { + "compressible": false + }, + "image/png": { + "source": "iana", + "compressible": false, + "extensions": ["png"] + }, + "image/prs.btif": { + "source": "iana", + "extensions": ["btif"] + }, + "image/prs.pti": { + "source": "iana", + "extensions": ["pti"] + }, + "image/pwg-raster": { + "source": "iana" + }, + "image/sgi": { + "source": "apache", + "extensions": ["sgi"] + }, + "image/svg+xml": { + "source": "iana", + "compressible": true, + "extensions": ["svg","svgz"] + }, + "image/t38": { + "source": "iana", + "extensions": ["t38"] + }, + "image/tiff": { + "source": "iana", + "compressible": false, + "extensions": ["tif","tiff"] + }, + "image/tiff-fx": { + "source": "iana", + "extensions": ["tfx"] + }, + "image/vnd.adobe.photoshop": { + "source": "iana", + "compressible": true, + "extensions": ["psd"] + }, + "image/vnd.airzip.accelerator.azv": { + "source": "iana", + "extensions": ["azv"] + }, + "image/vnd.cns.inf2": { + "source": "iana" + }, + "image/vnd.dece.graphic": { + "source": "iana", + "extensions": ["uvi","uvvi","uvg","uvvg"] + }, + "image/vnd.djvu": { + "source": "iana", + "extensions": ["djvu","djv"] + }, + "image/vnd.dvb.subtitle": { + "source": "iana", + "extensions": ["sub"] + }, + "image/vnd.dwg": { + "source": "iana", + "extensions": ["dwg"] + }, + "image/vnd.dxf": { + "source": "iana", + "extensions": ["dxf"] + }, + "image/vnd.fastbidsheet": { + "source": "iana", + "extensions": ["fbs"] + }, + "image/vnd.fpx": { + "source": "iana", + "extensions": ["fpx"] + }, + "image/vnd.fst": { + "source": "iana", + "extensions": ["fst"] + }, + "image/vnd.fujixerox.edmics-mmr": { + "source": "iana", + "extensions": ["mmr"] + }, + "image/vnd.fujixerox.edmics-rlc": { + "source": "iana", + "extensions": ["rlc"] + }, + "image/vnd.globalgraphics.pgb": { + "source": "iana" + }, + "image/vnd.microsoft.icon": { + "source": "iana", + "compressible": true, + "extensions": ["ico"] + }, + "image/vnd.mix": { + "source": "iana" + }, + "image/vnd.mozilla.apng": { + "source": "iana" + }, + "image/vnd.ms-dds": { + "compressible": true, + "extensions": ["dds"] + }, + "image/vnd.ms-modi": { + "source": "iana", + "extensions": ["mdi"] + }, + "image/vnd.ms-photo": { + "source": "apache", + "extensions": ["wdp"] + }, + "image/vnd.net-fpx": { + "source": "iana", + "extensions": ["npx"] + }, + "image/vnd.pco.b16": { + "source": "iana", + "extensions": ["b16"] + }, + "image/vnd.radiance": { + "source": "iana" + }, + "image/vnd.sealed.png": { + "source": "iana" + }, + "image/vnd.sealedmedia.softseal.gif": { + "source": "iana" + }, + "image/vnd.sealedmedia.softseal.jpg": { + "source": "iana" + }, + "image/vnd.svf": { + "source": "iana" + }, + "image/vnd.tencent.tap": { + "source": "iana", + "extensions": ["tap"] + }, + "image/vnd.valve.source.texture": { + "source": "iana", + "extensions": ["vtf"] + }, + "image/vnd.wap.wbmp": { + "source": "iana", + "extensions": ["wbmp"] + }, + "image/vnd.xiff": { + "source": "iana", + "extensions": ["xif"] + }, + "image/vnd.zbrush.pcx": { + "source": "iana", + "extensions": ["pcx"] + }, + "image/webp": { + "source": "apache", + "extensions": ["webp"] + }, + "image/wmf": { + "source": "iana", + "extensions": ["wmf"] + }, + "image/x-3ds": { + "source": "apache", + "extensions": ["3ds"] + }, + "image/x-cmu-raster": { + "source": "apache", + "extensions": ["ras"] + }, + "image/x-cmx": { + "source": "apache", + "extensions": ["cmx"] + }, + "image/x-freehand": { + "source": "apache", + "extensions": ["fh","fhc","fh4","fh5","fh7"] + }, + "image/x-icon": { + "source": "apache", + "compressible": true, + "extensions": ["ico"] + }, + "image/x-jng": { + "source": "nginx", + "extensions": ["jng"] + }, + "image/x-mrsid-image": { + "source": "apache", + "extensions": ["sid"] + }, + "image/x-ms-bmp": { + "source": "nginx", + "compressible": true, + "extensions": ["bmp"] + }, + "image/x-pcx": { + "source": "apache", + "extensions": ["pcx"] + }, + "image/x-pict": { + "source": "apache", + "extensions": ["pic","pct"] + }, + "image/x-portable-anymap": { + "source": "apache", + "extensions": ["pnm"] + }, + "image/x-portable-bitmap": { + "source": "apache", + "extensions": ["pbm"] + }, + "image/x-portable-graymap": { + "source": "apache", + "extensions": ["pgm"] + }, + "image/x-portable-pixmap": { + "source": "apache", + "extensions": ["ppm"] + }, + "image/x-rgb": { + "source": "apache", + "extensions": ["rgb"] + }, + "image/x-tga": { + "source": "apache", + "extensions": ["tga"] + }, + "image/x-xbitmap": { + "source": "apache", + "extensions": ["xbm"] + }, + "image/x-xcf": { + "compressible": false + }, + "image/x-xpixmap": { + "source": "apache", + "extensions": ["xpm"] + }, + "image/x-xwindowdump": { + "source": "apache", + "extensions": ["xwd"] + }, + "message/cpim": { + "source": "iana" + }, + "message/delivery-status": { + "source": "iana" + }, + "message/disposition-notification": { + "source": "iana", + "extensions": [ + "disposition-notification" + ] + }, + "message/external-body": { + "source": "iana" + }, + "message/feedback-report": { + "source": "iana" + }, + "message/global": { + "source": "iana", + "extensions": ["u8msg"] + }, + "message/global-delivery-status": { + "source": "iana", + "extensions": ["u8dsn"] + }, + "message/global-disposition-notification": { + "source": "iana", + "extensions": ["u8mdn"] + }, + "message/global-headers": { + "source": "iana", + "extensions": ["u8hdr"] + }, + "message/http": { + "source": "iana", + "compressible": false + }, + "message/imdn+xml": { + "source": "iana", + "compressible": true + }, + "message/news": { + "source": "iana" + }, + "message/partial": { + "source": "iana", + "compressible": false + }, + "message/rfc822": { + "source": "iana", + "compressible": true, + "extensions": ["eml","mime"] + }, + "message/s-http": { + "source": "iana" + }, + "message/sip": { + "source": "iana" + }, + "message/sipfrag": { + "source": "iana" + }, + "message/tracking-status": { + "source": "iana" + }, + "message/vnd.si.simp": { + "source": "iana" + }, + "message/vnd.wfa.wsc": { + "source": "iana", + "extensions": ["wsc"] + }, + "model/3mf": { + "source": "iana", + "extensions": ["3mf"] + }, + "model/e57": { + "source": "iana" + }, + "model/gltf+json": { + "source": "iana", + "compressible": true, + "extensions": ["gltf"] + }, + "model/gltf-binary": { + "source": "iana", + "compressible": true, + "extensions": ["glb"] + }, + "model/iges": { + "source": "iana", + "compressible": false, + "extensions": ["igs","iges"] + }, + "model/mesh": { + "source": "iana", + "compressible": false, + "extensions": ["msh","mesh","silo"] + }, + "model/mtl": { + "source": "iana", + "extensions": ["mtl"] + }, + "model/obj": { + "source": "iana", + "extensions": ["obj"] + }, + "model/step": { + "source": "iana" + }, + "model/step+xml": { + "source": "iana", + "compressible": true, + "extensions": ["stpx"] + }, + "model/step+zip": { + "source": "iana", + "compressible": false, + "extensions": ["stpz"] + }, + "model/step-xml+zip": { + "source": "iana", + "compressible": false, + "extensions": ["stpxz"] + }, + "model/stl": { + "source": "iana", + "extensions": ["stl"] + }, + "model/vnd.collada+xml": { + "source": "iana", + "compressible": true, + "extensions": ["dae"] + }, + "model/vnd.dwf": { + "source": "iana", + "extensions": ["dwf"] + }, + "model/vnd.flatland.3dml": { + "source": "iana" + }, + "model/vnd.gdl": { + "source": "iana", + "extensions": ["gdl"] + }, + "model/vnd.gs-gdl": { + "source": "apache" + }, + "model/vnd.gs.gdl": { + "source": "iana" + }, + "model/vnd.gtw": { + "source": "iana", + "extensions": ["gtw"] + }, + "model/vnd.moml+xml": { + "source": "iana", + "compressible": true + }, + "model/vnd.mts": { + "source": "iana", + "extensions": ["mts"] + }, + "model/vnd.opengex": { + "source": "iana", + "extensions": ["ogex"] + }, + "model/vnd.parasolid.transmit.binary": { + "source": "iana", + "extensions": ["x_b"] + }, + "model/vnd.parasolid.transmit.text": { + "source": "iana", + "extensions": ["x_t"] + }, + "model/vnd.pytha.pyox": { + "source": "iana" + }, + "model/vnd.rosette.annotated-data-model": { + "source": "iana" + }, + "model/vnd.sap.vds": { + "source": "iana", + "extensions": ["vds"] + }, + "model/vnd.usdz+zip": { + "source": "iana", + "compressible": false, + "extensions": ["usdz"] + }, + "model/vnd.valve.source.compiled-map": { + "source": "iana", + "extensions": ["bsp"] + }, + "model/vnd.vtu": { + "source": "iana", + "extensions": ["vtu"] + }, + "model/vrml": { + "source": "iana", + "compressible": false, + "extensions": ["wrl","vrml"] + }, + "model/x3d+binary": { + "source": "apache", + "compressible": false, + "extensions": ["x3db","x3dbz"] + }, + "model/x3d+fastinfoset": { + "source": "iana", + "extensions": ["x3db"] + }, + "model/x3d+vrml": { + "source": "apache", + "compressible": false, + "extensions": ["x3dv","x3dvz"] + }, + "model/x3d+xml": { + "source": "iana", + "compressible": true, + "extensions": ["x3d","x3dz"] + }, + "model/x3d-vrml": { + "source": "iana", + "extensions": ["x3dv"] + }, + "multipart/alternative": { + "source": "iana", + "compressible": false + }, + "multipart/appledouble": { + "source": "iana" + }, + "multipart/byteranges": { + "source": "iana" + }, + "multipart/digest": { + "source": "iana" + }, + "multipart/encrypted": { + "source": "iana", + "compressible": false + }, + "multipart/form-data": { + "source": "iana", + "compressible": false + }, + "multipart/header-set": { + "source": "iana" + }, + "multipart/mixed": { + "source": "iana" + }, + "multipart/multilingual": { + "source": "iana" + }, + "multipart/parallel": { + "source": "iana" + }, + "multipart/related": { + "source": "iana", + "compressible": false + }, + "multipart/report": { + "source": "iana" + }, + "multipart/signed": { + "source": "iana", + "compressible": false + }, + "multipart/vnd.bint.med-plus": { + "source": "iana" + }, + "multipart/voice-message": { + "source": "iana" + }, + "multipart/x-mixed-replace": { + "source": "iana" + }, + "text/1d-interleaved-parityfec": { + "source": "iana" + }, + "text/cache-manifest": { + "source": "iana", + "compressible": true, + "extensions": ["appcache","manifest"] + }, + "text/calendar": { + "source": "iana", + "extensions": ["ics","ifb"] + }, + "text/calender": { + "compressible": true + }, + "text/cmd": { + "compressible": true + }, + "text/coffeescript": { + "extensions": ["coffee","litcoffee"] + }, + "text/cql": { + "source": "iana" + }, + "text/cql-expression": { + "source": "iana" + }, + "text/cql-identifier": { + "source": "iana" + }, + "text/css": { + "source": "iana", + "charset": "UTF-8", + "compressible": true, + "extensions": ["css"] + }, + "text/csv": { + "source": "iana", + "compressible": true, + "extensions": ["csv"] + }, + "text/csv-schema": { + "source": "iana" + }, + "text/directory": { + "source": "iana" + }, + "text/dns": { + "source": "iana" + }, + "text/ecmascript": { + "source": "iana" + }, + "text/encaprtp": { + "source": "iana" + }, + "text/enriched": { + "source": "iana" + }, + "text/fhirpath": { + "source": "iana" + }, + "text/flexfec": { + "source": "iana" + }, + "text/fwdred": { + "source": "iana" + }, + "text/gff3": { + "source": "iana" + }, + "text/grammar-ref-list": { + "source": "iana" + }, + "text/html": { + "source": "iana", + "compressible": true, + "extensions": ["html","htm","shtml"] + }, + "text/jade": { + "extensions": ["jade"] + }, + "text/javascript": { + "source": "iana", + "compressible": true + }, + "text/jcr-cnd": { + "source": "iana" + }, + "text/jsx": { + "compressible": true, + "extensions": ["jsx"] + }, + "text/less": { + "compressible": true, + "extensions": ["less"] + }, + "text/markdown": { + "source": "iana", + "compressible": true, + "extensions": ["markdown","md"] + }, + "text/mathml": { + "source": "nginx", + "extensions": ["mml"] + }, + "text/mdx": { + "compressible": true, + "extensions": ["mdx"] + }, + "text/mizar": { + "source": "iana" + }, + "text/n3": { + "source": "iana", + "charset": "UTF-8", + "compressible": true, + "extensions": ["n3"] + }, + "text/parameters": { + "source": "iana", + "charset": "UTF-8" + }, + "text/parityfec": { + "source": "iana" + }, + "text/plain": { + "source": "iana", + "compressible": true, + "extensions": ["txt","text","conf","def","list","log","in","ini"] + }, + "text/provenance-notation": { + "source": "iana", + "charset": "UTF-8" + }, + "text/prs.fallenstein.rst": { + "source": "iana" + }, + "text/prs.lines.tag": { + "source": "iana", + "extensions": ["dsc"] + }, + "text/prs.prop.logic": { + "source": "iana" + }, + "text/raptorfec": { + "source": "iana" + }, + "text/red": { + "source": "iana" + }, + "text/rfc822-headers": { + "source": "iana" + }, + "text/richtext": { + "source": "iana", + "compressible": true, + "extensions": ["rtx"] + }, + "text/rtf": { + "source": "iana", + "compressible": true, + "extensions": ["rtf"] + }, + "text/rtp-enc-aescm128": { + "source": "iana" + }, + "text/rtploopback": { + "source": "iana" + }, + "text/rtx": { + "source": "iana" + }, + "text/sgml": { + "source": "iana", + "extensions": ["sgml","sgm"] + }, + "text/shaclc": { + "source": "iana" + }, + "text/shex": { + "source": "iana", + "extensions": ["shex"] + }, + "text/slim": { + "extensions": ["slim","slm"] + }, + "text/spdx": { + "source": "iana", + "extensions": ["spdx"] + }, + "text/strings": { + "source": "iana" + }, + "text/stylus": { + "extensions": ["stylus","styl"] + }, + "text/t140": { + "source": "iana" + }, + "text/tab-separated-values": { + "source": "iana", + "compressible": true, + "extensions": ["tsv"] + }, + "text/troff": { + "source": "iana", + "extensions": ["t","tr","roff","man","me","ms"] + }, + "text/turtle": { + "source": "iana", + "charset": "UTF-8", + "extensions": ["ttl"] + }, + "text/ulpfec": { + "source": "iana" + }, + "text/uri-list": { + "source": "iana", + "compressible": true, + "extensions": ["uri","uris","urls"] + }, + "text/vcard": { + "source": "iana", + "compressible": true, + "extensions": ["vcard"] + }, + "text/vnd.a": { + "source": "iana" + }, + "text/vnd.abc": { + "source": "iana" + }, + "text/vnd.ascii-art": { + "source": "iana" + }, + "text/vnd.curl": { + "source": "iana", + "extensions": ["curl"] + }, + "text/vnd.curl.dcurl": { + "source": "apache", + "extensions": ["dcurl"] + }, + "text/vnd.curl.mcurl": { + "source": "apache", + "extensions": ["mcurl"] + }, + "text/vnd.curl.scurl": { + "source": "apache", + "extensions": ["scurl"] + }, + "text/vnd.debian.copyright": { + "source": "iana", + "charset": "UTF-8" + }, + "text/vnd.dmclientscript": { + "source": "iana" + }, + "text/vnd.dvb.subtitle": { + "source": "iana", + "extensions": ["sub"] + }, + "text/vnd.esmertec.theme-descriptor": { + "source": "iana", + "charset": "UTF-8" + }, + "text/vnd.familysearch.gedcom": { + "source": "iana", + "extensions": ["ged"] + }, + "text/vnd.ficlab.flt": { + "source": "iana" + }, + "text/vnd.fly": { + "source": "iana", + "extensions": ["fly"] + }, + "text/vnd.fmi.flexstor": { + "source": "iana", + "extensions": ["flx"] + }, + "text/vnd.gml": { + "source": "iana" + }, + "text/vnd.graphviz": { + "source": "iana", + "extensions": ["gv"] + }, + "text/vnd.hans": { + "source": "iana" + }, + "text/vnd.hgl": { + "source": "iana" + }, + "text/vnd.in3d.3dml": { + "source": "iana", + "extensions": ["3dml"] + }, + "text/vnd.in3d.spot": { + "source": "iana", + "extensions": ["spot"] + }, + "text/vnd.iptc.newsml": { + "source": "iana" + }, + "text/vnd.iptc.nitf": { + "source": "iana" + }, + "text/vnd.latex-z": { + "source": "iana" + }, + "text/vnd.motorola.reflex": { + "source": "iana" + }, + "text/vnd.ms-mediapackage": { + "source": "iana" + }, + "text/vnd.net2phone.commcenter.command": { + "source": "iana" + }, + "text/vnd.radisys.msml-basic-layout": { + "source": "iana" + }, + "text/vnd.senx.warpscript": { + "source": "iana" + }, + "text/vnd.si.uricatalogue": { + "source": "iana" + }, + "text/vnd.sosi": { + "source": "iana" + }, + "text/vnd.sun.j2me.app-descriptor": { + "source": "iana", + "charset": "UTF-8", + "extensions": ["jad"] + }, + "text/vnd.trolltech.linguist": { + "source": "iana", + "charset": "UTF-8" + }, + "text/vnd.wap.si": { + "source": "iana" + }, + "text/vnd.wap.sl": { + "source": "iana" + }, + "text/vnd.wap.wml": { + "source": "iana", + "extensions": ["wml"] + }, + "text/vnd.wap.wmlscript": { + "source": "iana", + "extensions": ["wmls"] + }, + "text/vtt": { + "source": "iana", + "charset": "UTF-8", + "compressible": true, + "extensions": ["vtt"] + }, + "text/x-asm": { + "source": "apache", + "extensions": ["s","asm"] + }, + "text/x-c": { + "source": "apache", + "extensions": ["c","cc","cxx","cpp","h","hh","dic"] + }, + "text/x-component": { + "source": "nginx", + "extensions": ["htc"] + }, + "text/x-fortran": { + "source": "apache", + "extensions": ["f","for","f77","f90"] + }, + "text/x-gwt-rpc": { + "compressible": true + }, + "text/x-handlebars-template": { + "extensions": ["hbs"] + }, + "text/x-java-source": { + "source": "apache", + "extensions": ["java"] + }, + "text/x-jquery-tmpl": { + "compressible": true + }, + "text/x-lua": { + "extensions": ["lua"] + }, + "text/x-markdown": { + "compressible": true, + "extensions": ["mkd"] + }, + "text/x-nfo": { + "source": "apache", + "extensions": ["nfo"] + }, + "text/x-opml": { + "source": "apache", + "extensions": ["opml"] + }, + "text/x-org": { + "compressible": true, + "extensions": ["org"] + }, + "text/x-pascal": { + "source": "apache", + "extensions": ["p","pas"] + }, + "text/x-processing": { + "compressible": true, + "extensions": ["pde"] + }, + "text/x-sass": { + "extensions": ["sass"] + }, + "text/x-scss": { + "extensions": ["scss"] + }, + "text/x-setext": { + "source": "apache", + "extensions": ["etx"] + }, + "text/x-sfv": { + "source": "apache", + "extensions": ["sfv"] + }, + "text/x-suse-ymp": { + "compressible": true, + "extensions": ["ymp"] + }, + "text/x-uuencode": { + "source": "apache", + "extensions": ["uu"] + }, + "text/x-vcalendar": { + "source": "apache", + "extensions": ["vcs"] + }, + "text/x-vcard": { + "source": "apache", + "extensions": ["vcf"] + }, + "text/xml": { + "source": "iana", + "compressible": true, + "extensions": ["xml"] + }, + "text/xml-external-parsed-entity": { + "source": "iana" + }, + "text/yaml": { + "compressible": true, + "extensions": ["yaml","yml"] + }, + "video/1d-interleaved-parityfec": { + "source": "iana" + }, + "video/3gpp": { + "source": "iana", + "extensions": ["3gp","3gpp"] + }, + "video/3gpp-tt": { + "source": "iana" + }, + "video/3gpp2": { + "source": "iana", + "extensions": ["3g2"] + }, + "video/av1": { + "source": "iana" + }, + "video/bmpeg": { + "source": "iana" + }, + "video/bt656": { + "source": "iana" + }, + "video/celb": { + "source": "iana" + }, + "video/dv": { + "source": "iana" + }, + "video/encaprtp": { + "source": "iana" + }, + "video/ffv1": { + "source": "iana" + }, + "video/flexfec": { + "source": "iana" + }, + "video/h261": { + "source": "iana", + "extensions": ["h261"] + }, + "video/h263": { + "source": "iana", + "extensions": ["h263"] + }, + "video/h263-1998": { + "source": "iana" + }, + "video/h263-2000": { + "source": "iana" + }, + "video/h264": { + "source": "iana", + "extensions": ["h264"] + }, + "video/h264-rcdo": { + "source": "iana" + }, + "video/h264-svc": { + "source": "iana" + }, + "video/h265": { + "source": "iana" + }, + "video/iso.segment": { + "source": "iana", + "extensions": ["m4s"] + }, + "video/jpeg": { + "source": "iana", + "extensions": ["jpgv"] + }, + "video/jpeg2000": { + "source": "iana" + }, + "video/jpm": { + "source": "apache", + "extensions": ["jpm","jpgm"] + }, + "video/jxsv": { + "source": "iana" + }, + "video/mj2": { + "source": "iana", + "extensions": ["mj2","mjp2"] + }, + "video/mp1s": { + "source": "iana" + }, + "video/mp2p": { + "source": "iana" + }, + "video/mp2t": { + "source": "iana", + "extensions": ["ts"] + }, + "video/mp4": { + "source": "iana", + "compressible": false, + "extensions": ["mp4","mp4v","mpg4"] + }, + "video/mp4v-es": { + "source": "iana" + }, + "video/mpeg": { + "source": "iana", + "compressible": false, + "extensions": ["mpeg","mpg","mpe","m1v","m2v"] + }, + "video/mpeg4-generic": { + "source": "iana" + }, + "video/mpv": { + "source": "iana" + }, + "video/nv": { + "source": "iana" + }, + "video/ogg": { + "source": "iana", + "compressible": false, + "extensions": ["ogv"] + }, + "video/parityfec": { + "source": "iana" + }, + "video/pointer": { + "source": "iana" + }, + "video/quicktime": { + "source": "iana", + "compressible": false, + "extensions": ["qt","mov"] + }, + "video/raptorfec": { + "source": "iana" + }, + "video/raw": { + "source": "iana" + }, + "video/rtp-enc-aescm128": { + "source": "iana" + }, + "video/rtploopback": { + "source": "iana" + }, + "video/rtx": { + "source": "iana" + }, + "video/scip": { + "source": "iana" + }, + "video/smpte291": { + "source": "iana" + }, + "video/smpte292m": { + "source": "iana" + }, + "video/ulpfec": { + "source": "iana" + }, + "video/vc1": { + "source": "iana" + }, + "video/vc2": { + "source": "iana" + }, + "video/vnd.cctv": { + "source": "iana" + }, + "video/vnd.dece.hd": { + "source": "iana", + "extensions": ["uvh","uvvh"] + }, + "video/vnd.dece.mobile": { + "source": "iana", + "extensions": ["uvm","uvvm"] + }, + "video/vnd.dece.mp4": { + "source": "iana" + }, + "video/vnd.dece.pd": { + "source": "iana", + "extensions": ["uvp","uvvp"] + }, + "video/vnd.dece.sd": { + "source": "iana", + "extensions": ["uvs","uvvs"] + }, + "video/vnd.dece.video": { + "source": "iana", + "extensions": ["uvv","uvvv"] + }, + "video/vnd.directv.mpeg": { + "source": "iana" + }, + "video/vnd.directv.mpeg-tts": { + "source": "iana" + }, + "video/vnd.dlna.mpeg-tts": { + "source": "iana" + }, + "video/vnd.dvb.file": { + "source": "iana", + "extensions": ["dvb"] + }, + "video/vnd.fvt": { + "source": "iana", + "extensions": ["fvt"] + }, + "video/vnd.hns.video": { + "source": "iana" + }, + "video/vnd.iptvforum.1dparityfec-1010": { + "source": "iana" + }, + "video/vnd.iptvforum.1dparityfec-2005": { + "source": "iana" + }, + "video/vnd.iptvforum.2dparityfec-1010": { + "source": "iana" + }, + "video/vnd.iptvforum.2dparityfec-2005": { + "source": "iana" + }, + "video/vnd.iptvforum.ttsavc": { + "source": "iana" + }, + "video/vnd.iptvforum.ttsmpeg2": { + "source": "iana" + }, + "video/vnd.motorola.video": { + "source": "iana" + }, + "video/vnd.motorola.videop": { + "source": "iana" + }, + "video/vnd.mpegurl": { + "source": "iana", + "extensions": ["mxu","m4u"] + }, + "video/vnd.ms-playready.media.pyv": { + "source": "iana", + "extensions": ["pyv"] + }, + "video/vnd.nokia.interleaved-multimedia": { + "source": "iana" + }, + "video/vnd.nokia.mp4vr": { + "source": "iana" + }, + "video/vnd.nokia.videovoip": { + "source": "iana" + }, + "video/vnd.objectvideo": { + "source": "iana" + }, + "video/vnd.radgamettools.bink": { + "source": "iana" + }, + "video/vnd.radgamettools.smacker": { + "source": "iana" + }, + "video/vnd.sealed.mpeg1": { + "source": "iana" + }, + "video/vnd.sealed.mpeg4": { + "source": "iana" + }, + "video/vnd.sealed.swf": { + "source": "iana" + }, + "video/vnd.sealedmedia.softseal.mov": { + "source": "iana" + }, + "video/vnd.uvvu.mp4": { + "source": "iana", + "extensions": ["uvu","uvvu"] + }, + "video/vnd.vivo": { + "source": "iana", + "extensions": ["viv"] + }, + "video/vnd.youtube.yt": { + "source": "iana" + }, + "video/vp8": { + "source": "iana" + }, + "video/vp9": { + "source": "iana" + }, + "video/webm": { + "source": "apache", + "compressible": false, + "extensions": ["webm"] + }, + "video/x-f4v": { + "source": "apache", + "extensions": ["f4v"] + }, + "video/x-fli": { + "source": "apache", + "extensions": ["fli"] + }, + "video/x-flv": { + "source": "apache", + "compressible": false, + "extensions": ["flv"] + }, + "video/x-m4v": { + "source": "apache", + "extensions": ["m4v"] + }, + "video/x-matroska": { + "source": "apache", + "compressible": false, + "extensions": ["mkv","mk3d","mks"] + }, + "video/x-mng": { + "source": "apache", + "extensions": ["mng"] + }, + "video/x-ms-asf": { + "source": "apache", + "extensions": ["asf","asx"] + }, + "video/x-ms-vob": { + "source": "apache", + "extensions": ["vob"] + }, + "video/x-ms-wm": { + "source": "apache", + "extensions": ["wm"] + }, + "video/x-ms-wmv": { + "source": "apache", + "compressible": false, + "extensions": ["wmv"] + }, + "video/x-ms-wmx": { + "source": "apache", + "extensions": ["wmx"] + }, + "video/x-ms-wvx": { + "source": "apache", + "extensions": ["wvx"] + }, + "video/x-msvideo": { + "source": "apache", + "extensions": ["avi"] + }, + "video/x-sgi-movie": { + "source": "apache", + "extensions": ["movie"] + }, + "video/x-smv": { + "source": "apache", + "extensions": ["smv"] + }, + "x-conference/x-cooltalk": { + "source": "apache", + "extensions": ["ice"] + }, + "x-shader/x-fragment": { + "compressible": true + }, + "x-shader/x-vertex": { + "compressible": true + } +} diff --git a/node_modules/mime-db/index.js b/node_modules/mime-db/index.js new file mode 100644 index 0000000..ec2be30 --- /dev/null +++ b/node_modules/mime-db/index.js @@ -0,0 +1,12 @@ +/*! + * mime-db + * Copyright(c) 2014 Jonathan Ong + * Copyright(c) 2015-2022 Douglas Christopher Wilson + * MIT Licensed + */ + +/** + * Module exports. + */ + +module.exports = require('./db.json') diff --git a/node_modules/mime-db/package.json b/node_modules/mime-db/package.json new file mode 100644 index 0000000..32c14b8 --- /dev/null +++ b/node_modules/mime-db/package.json @@ -0,0 +1,60 @@ +{ + "name": "mime-db", + "description": "Media Type Database", + "version": "1.52.0", + "contributors": [ + "Douglas Christopher Wilson ", + "Jonathan Ong (http://jongleberry.com)", + "Robert Kieffer (http://github.com/broofa)" + ], + "license": "MIT", + "keywords": [ + "mime", + "db", + "type", + "types", + "database", + "charset", + "charsets" + ], + "repository": "jshttp/mime-db", + "devDependencies": { + "bluebird": "3.7.2", + "co": "4.6.0", + "cogent": "1.0.1", + "csv-parse": "4.16.3", + "eslint": "7.32.0", + "eslint-config-standard": "15.0.1", + "eslint-plugin-import": "2.25.4", + "eslint-plugin-markdown": "2.2.1", + "eslint-plugin-node": "11.1.0", + "eslint-plugin-promise": "5.1.1", + "eslint-plugin-standard": "4.1.0", + "gnode": "0.1.2", + "media-typer": "1.1.0", + "mocha": "9.2.1", + "nyc": "15.1.0", + "raw-body": "2.5.0", + "stream-to-array": "2.3.0" + }, + "files": [ + "HISTORY.md", + "LICENSE", + "README.md", + "db.json", + "index.js" + ], + "engines": { + "node": ">= 0.6" + }, + "scripts": { + "build": "node scripts/build", + "fetch": "node scripts/fetch-apache && gnode scripts/fetch-iana && node scripts/fetch-nginx", + "lint": "eslint .", + "test": "mocha --reporter spec --bail --check-leaks test/", + "test-ci": "nyc --reporter=lcov --reporter=text npm test", + "test-cov": "nyc --reporter=html --reporter=text npm test", + "update": "npm run fetch && npm run build", + "version": "node scripts/version-history.js && git add HISTORY.md" + } +} diff --git a/node_modules/mime-types/HISTORY.md b/node_modules/mime-types/HISTORY.md new file mode 100644 index 0000000..c5043b7 --- /dev/null +++ b/node_modules/mime-types/HISTORY.md @@ -0,0 +1,397 @@ +2.1.35 / 2022-03-12 +=================== + + * deps: mime-db@1.52.0 + - Add extensions from IANA for more `image/*` types + - Add extension `.asc` to `application/pgp-keys` + - Add extensions to various XML types + - Add new upstream MIME types + +2.1.34 / 2021-11-08 +=================== + + * deps: mime-db@1.51.0 + - Add new upstream MIME types + +2.1.33 / 2021-10-01 +=================== + + * deps: mime-db@1.50.0 + - Add deprecated iWorks mime types and extensions + - Add new upstream MIME types + +2.1.32 / 2021-07-27 +=================== + + * deps: mime-db@1.49.0 + - Add extension `.trig` to `application/trig` + - Add new upstream MIME types + +2.1.31 / 2021-06-01 +=================== + + * deps: mime-db@1.48.0 + - Add extension `.mvt` to `application/vnd.mapbox-vector-tile` + - Add new upstream MIME types + +2.1.30 / 2021-04-02 +=================== + + * deps: mime-db@1.47.0 + - Add extension `.amr` to `audio/amr` + - Remove ambigious extensions from IANA for `application/*+xml` types + - Update primary extension to `.es` for `application/ecmascript` + +2.1.29 / 2021-02-17 +=================== + + * deps: mime-db@1.46.0 + - Add extension `.amr` to `audio/amr` + - Add extension `.m4s` to `video/iso.segment` + - Add extension `.opus` to `audio/ogg` + - Add new upstream MIME types + +2.1.28 / 2021-01-01 +=================== + + * deps: mime-db@1.45.0 + - Add `application/ubjson` with extension `.ubj` + - Add `image/avif` with extension `.avif` + - Add `image/ktx2` with extension `.ktx2` + - Add extension `.dbf` to `application/vnd.dbf` + - Add extension `.rar` to `application/vnd.rar` + - Add extension `.td` to `application/urc-targetdesc+xml` + - Add new upstream MIME types + - Fix extension of `application/vnd.apple.keynote` to be `.key` + +2.1.27 / 2020-04-23 +=================== + + * deps: mime-db@1.44.0 + - Add charsets from IANA + - Add extension `.cjs` to `application/node` + - Add new upstream MIME types + +2.1.26 / 2020-01-05 +=================== + + * deps: mime-db@1.43.0 + - Add `application/x-keepass2` with extension `.kdbx` + - Add extension `.mxmf` to `audio/mobile-xmf` + - Add extensions from IANA for `application/*+xml` types + - Add new upstream MIME types + +2.1.25 / 2019-11-12 +=================== + + * deps: mime-db@1.42.0 + - Add new upstream MIME types + - Add `application/toml` with extension `.toml` + - Add `image/vnd.ms-dds` with extension `.dds` + +2.1.24 / 2019-04-20 +=================== + + * deps: mime-db@1.40.0 + - Add extensions from IANA for `model/*` types + - Add `text/mdx` with extension `.mdx` + +2.1.23 / 2019-04-17 +=================== + + * deps: mime-db@~1.39.0 + - Add extensions `.siv` and `.sieve` to `application/sieve` + - Add new upstream MIME types + +2.1.22 / 2019-02-14 +=================== + + * deps: mime-db@~1.38.0 + - Add extension `.nq` to `application/n-quads` + - Add extension `.nt` to `application/n-triples` + - Add new upstream MIME types + +2.1.21 / 2018-10-19 +=================== + + * deps: mime-db@~1.37.0 + - Add extensions to HEIC image types + - Add new upstream MIME types + +2.1.20 / 2018-08-26 +=================== + + * deps: mime-db@~1.36.0 + - Add Apple file extensions from IANA + - Add extensions from IANA for `image/*` types + - Add new upstream MIME types + +2.1.19 / 2018-07-17 +=================== + + * deps: mime-db@~1.35.0 + - Add extension `.csl` to `application/vnd.citationstyles.style+xml` + - Add extension `.es` to `application/ecmascript` + - Add extension `.owl` to `application/rdf+xml` + - Add new upstream MIME types + - Add UTF-8 as default charset for `text/turtle` + +2.1.18 / 2018-02-16 +=================== + + * deps: mime-db@~1.33.0 + - Add `application/raml+yaml` with extension `.raml` + - Add `application/wasm` with extension `.wasm` + - Add `text/shex` with extension `.shex` + - Add extensions for JPEG-2000 images + - Add extensions from IANA for `message/*` types + - Add new upstream MIME types + - Update font MIME types + - Update `text/hjson` to registered `application/hjson` + +2.1.17 / 2017-09-01 +=================== + + * deps: mime-db@~1.30.0 + - Add `application/vnd.ms-outlook` + - Add `application/x-arj` + - Add extension `.mjs` to `application/javascript` + - Add glTF types and extensions + - Add new upstream MIME types + - Add `text/x-org` + - Add VirtualBox MIME types + - Fix `source` records for `video/*` types that are IANA + - Update `font/opentype` to registered `font/otf` + +2.1.16 / 2017-07-24 +=================== + + * deps: mime-db@~1.29.0 + - Add `application/fido.trusted-apps+json` + - Add extension `.wadl` to `application/vnd.sun.wadl+xml` + - Add extension `.gz` to `application/gzip` + - Add new upstream MIME types + - Update extensions `.md` and `.markdown` to be `text/markdown` + +2.1.15 / 2017-03-23 +=================== + + * deps: mime-db@~1.27.0 + - Add new mime types + - Add `image/apng` + +2.1.14 / 2017-01-14 +=================== + + * deps: mime-db@~1.26.0 + - Add new mime types + +2.1.13 / 2016-11-18 +=================== + + * deps: mime-db@~1.25.0 + - Add new mime types + +2.1.12 / 2016-09-18 +=================== + + * deps: mime-db@~1.24.0 + - Add new mime types + - Add `audio/mp3` + +2.1.11 / 2016-05-01 +=================== + + * deps: mime-db@~1.23.0 + - Add new mime types + +2.1.10 / 2016-02-15 +=================== + + * deps: mime-db@~1.22.0 + - Add new mime types + - Fix extension of `application/dash+xml` + - Update primary extension for `audio/mp4` + +2.1.9 / 2016-01-06 +================== + + * deps: mime-db@~1.21.0 + - Add new mime types + +2.1.8 / 2015-11-30 +================== + + * deps: mime-db@~1.20.0 + - Add new mime types + +2.1.7 / 2015-09-20 +================== + + * deps: mime-db@~1.19.0 + - Add new mime types + +2.1.6 / 2015-09-03 +================== + + * deps: mime-db@~1.18.0 + - Add new mime types + +2.1.5 / 2015-08-20 +================== + + * deps: mime-db@~1.17.0 + - Add new mime types + +2.1.4 / 2015-07-30 +================== + + * deps: mime-db@~1.16.0 + - Add new mime types + +2.1.3 / 2015-07-13 +================== + + * deps: mime-db@~1.15.0 + - Add new mime types + +2.1.2 / 2015-06-25 +================== + + * deps: mime-db@~1.14.0 + - Add new mime types + +2.1.1 / 2015-06-08 +================== + + * perf: fix deopt during mapping + +2.1.0 / 2015-06-07 +================== + + * Fix incorrectly treating extension-less file name as extension + - i.e. `'path/to/json'` will no longer return `application/json` + * Fix `.charset(type)` to accept parameters + * Fix `.charset(type)` to match case-insensitive + * Improve generation of extension to MIME mapping + * Refactor internals for readability and no argument reassignment + * Prefer `application/*` MIME types from the same source + * Prefer any type over `application/octet-stream` + * deps: mime-db@~1.13.0 + - Add nginx as a source + - Add new mime types + +2.0.14 / 2015-06-06 +=================== + + * deps: mime-db@~1.12.0 + - Add new mime types + +2.0.13 / 2015-05-31 +=================== + + * deps: mime-db@~1.11.0 + - Add new mime types + +2.0.12 / 2015-05-19 +=================== + + * deps: mime-db@~1.10.0 + - Add new mime types + +2.0.11 / 2015-05-05 +=================== + + * deps: mime-db@~1.9.1 + - Add new mime types + +2.0.10 / 2015-03-13 +=================== + + * deps: mime-db@~1.8.0 + - Add new mime types + +2.0.9 / 2015-02-09 +================== + + * deps: mime-db@~1.7.0 + - Add new mime types + - Community extensions ownership transferred from `node-mime` + +2.0.8 / 2015-01-29 +================== + + * deps: mime-db@~1.6.0 + - Add new mime types + +2.0.7 / 2014-12-30 +================== + + * deps: mime-db@~1.5.0 + - Add new mime types + - Fix various invalid MIME type entries + +2.0.6 / 2014-12-30 +================== + + * deps: mime-db@~1.4.0 + - Add new mime types + - Fix various invalid MIME type entries + - Remove example template MIME types + +2.0.5 / 2014-12-29 +================== + + * deps: mime-db@~1.3.1 + - Fix missing extensions + +2.0.4 / 2014-12-10 +================== + + * deps: mime-db@~1.3.0 + - Add new mime types + +2.0.3 / 2014-11-09 +================== + + * deps: mime-db@~1.2.0 + - Add new mime types + +2.0.2 / 2014-09-28 +================== + + * deps: mime-db@~1.1.0 + - Add new mime types + - Update charsets + +2.0.1 / 2014-09-07 +================== + + * Support Node.js 0.6 + +2.0.0 / 2014-09-02 +================== + + * Use `mime-db` + * Remove `.define()` + +1.0.2 / 2014-08-04 +================== + + * Set charset=utf-8 for `text/javascript` + +1.0.1 / 2014-06-24 +================== + + * Add `text/jsx` type + +1.0.0 / 2014-05-12 +================== + + * Return `false` for unknown types + * Set charset=utf-8 for `application/json` + +0.1.0 / 2014-05-02 +================== + + * Initial release diff --git a/node_modules/mime-types/LICENSE b/node_modules/mime-types/LICENSE new file mode 100644 index 0000000..0616607 --- /dev/null +++ b/node_modules/mime-types/LICENSE @@ -0,0 +1,23 @@ +(The MIT License) + +Copyright (c) 2014 Jonathan Ong +Copyright (c) 2015 Douglas Christopher Wilson + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +'Software'), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/node_modules/mime-types/README.md b/node_modules/mime-types/README.md new file mode 100644 index 0000000..48d2fb4 --- /dev/null +++ b/node_modules/mime-types/README.md @@ -0,0 +1,113 @@ +# mime-types + +[![NPM Version][npm-version-image]][npm-url] +[![NPM Downloads][npm-downloads-image]][npm-url] +[![Node.js Version][node-version-image]][node-version-url] +[![Build Status][ci-image]][ci-url] +[![Test Coverage][coveralls-image]][coveralls-url] + +The ultimate javascript content-type utility. + +Similar to [the `mime@1.x` module](https://www.npmjs.com/package/mime), except: + +- __No fallbacks.__ Instead of naively returning the first available type, + `mime-types` simply returns `false`, so do + `var type = mime.lookup('unrecognized') || 'application/octet-stream'`. +- No `new Mime()` business, so you could do `var lookup = require('mime-types').lookup`. +- No `.define()` functionality +- Bug fixes for `.lookup(path)` + +Otherwise, the API is compatible with `mime` 1.x. + +## Install + +This is a [Node.js](https://nodejs.org/en/) module available through the +[npm registry](https://www.npmjs.com/). Installation is done using the +[`npm install` command](https://docs.npmjs.com/getting-started/installing-npm-packages-locally): + +```sh +$ npm install mime-types +``` + +## Adding Types + +All mime types are based on [mime-db](https://www.npmjs.com/package/mime-db), +so open a PR there if you'd like to add mime types. + +## API + +```js +var mime = require('mime-types') +``` + +All functions return `false` if input is invalid or not found. + +### mime.lookup(path) + +Lookup the content-type associated with a file. + +```js +mime.lookup('json') // 'application/json' +mime.lookup('.md') // 'text/markdown' +mime.lookup('file.html') // 'text/html' +mime.lookup('folder/file.js') // 'application/javascript' +mime.lookup('folder/.htaccess') // false + +mime.lookup('cats') // false +``` + +### mime.contentType(type) + +Create a full content-type header given a content-type or extension. +When given an extension, `mime.lookup` is used to get the matching +content-type, otherwise the given content-type is used. Then if the +content-type does not already have a `charset` parameter, `mime.charset` +is used to get the default charset and add to the returned content-type. + +```js +mime.contentType('markdown') // 'text/x-markdown; charset=utf-8' +mime.contentType('file.json') // 'application/json; charset=utf-8' +mime.contentType('text/html') // 'text/html; charset=utf-8' +mime.contentType('text/html; charset=iso-8859-1') // 'text/html; charset=iso-8859-1' + +// from a full path +mime.contentType(path.extname('/path/to/file.json')) // 'application/json; charset=utf-8' +``` + +### mime.extension(type) + +Get the default extension for a content-type. + +```js +mime.extension('application/octet-stream') // 'bin' +``` + +### mime.charset(type) + +Lookup the implied default charset of a content-type. + +```js +mime.charset('text/markdown') // 'UTF-8' +``` + +### var type = mime.types[extension] + +A map of content-types by extension. + +### [extensions...] = mime.extensions[type] + +A map of extensions by content-type. + +## License + +[MIT](LICENSE) + +[ci-image]: https://badgen.net/github/checks/jshttp/mime-types/master?label=ci +[ci-url]: https://github.com/jshttp/mime-types/actions/workflows/ci.yml +[coveralls-image]: https://badgen.net/coveralls/c/github/jshttp/mime-types/master +[coveralls-url]: https://coveralls.io/r/jshttp/mime-types?branch=master +[node-version-image]: https://badgen.net/npm/node/mime-types +[node-version-url]: https://nodejs.org/en/download +[npm-downloads-image]: https://badgen.net/npm/dm/mime-types +[npm-url]: https://npmjs.org/package/mime-types +[npm-version-image]: https://badgen.net/npm/v/mime-types diff --git a/node_modules/mime-types/index.js b/node_modules/mime-types/index.js new file mode 100644 index 0000000..b9f34d5 --- /dev/null +++ b/node_modules/mime-types/index.js @@ -0,0 +1,188 @@ +/*! + * mime-types + * Copyright(c) 2014 Jonathan Ong + * Copyright(c) 2015 Douglas Christopher Wilson + * MIT Licensed + */ + +'use strict' + +/** + * Module dependencies. + * @private + */ + +var db = require('mime-db') +var extname = require('path').extname + +/** + * Module variables. + * @private + */ + +var EXTRACT_TYPE_REGEXP = /^\s*([^;\s]*)(?:;|\s|$)/ +var TEXT_TYPE_REGEXP = /^text\//i + +/** + * Module exports. + * @public + */ + +exports.charset = charset +exports.charsets = { lookup: charset } +exports.contentType = contentType +exports.extension = extension +exports.extensions = Object.create(null) +exports.lookup = lookup +exports.types = Object.create(null) + +// Populate the extensions/types maps +populateMaps(exports.extensions, exports.types) + +/** + * Get the default charset for a MIME type. + * + * @param {string} type + * @return {boolean|string} + */ + +function charset (type) { + if (!type || typeof type !== 'string') { + return false + } + + // TODO: use media-typer + var match = EXTRACT_TYPE_REGEXP.exec(type) + var mime = match && db[match[1].toLowerCase()] + + if (mime && mime.charset) { + return mime.charset + } + + // default text/* to utf-8 + if (match && TEXT_TYPE_REGEXP.test(match[1])) { + return 'UTF-8' + } + + return false +} + +/** + * Create a full Content-Type header given a MIME type or extension. + * + * @param {string} str + * @return {boolean|string} + */ + +function contentType (str) { + // TODO: should this even be in this module? + if (!str || typeof str !== 'string') { + return false + } + + var mime = str.indexOf('/') === -1 + ? exports.lookup(str) + : str + + if (!mime) { + return false + } + + // TODO: use content-type or other module + if (mime.indexOf('charset') === -1) { + var charset = exports.charset(mime) + if (charset) mime += '; charset=' + charset.toLowerCase() + } + + return mime +} + +/** + * Get the default extension for a MIME type. + * + * @param {string} type + * @return {boolean|string} + */ + +function extension (type) { + if (!type || typeof type !== 'string') { + return false + } + + // TODO: use media-typer + var match = EXTRACT_TYPE_REGEXP.exec(type) + + // get extensions + var exts = match && exports.extensions[match[1].toLowerCase()] + + if (!exts || !exts.length) { + return false + } + + return exts[0] +} + +/** + * Lookup the MIME type for a file path/extension. + * + * @param {string} path + * @return {boolean|string} + */ + +function lookup (path) { + if (!path || typeof path !== 'string') { + return false + } + + // get the extension ("ext" or ".ext" or full path) + var extension = extname('x.' + path) + .toLowerCase() + .substr(1) + + if (!extension) { + return false + } + + return exports.types[extension] || false +} + +/** + * Populate the extensions and types maps. + * @private + */ + +function populateMaps (extensions, types) { + // source preference (least -> most) + var preference = ['nginx', 'apache', undefined, 'iana'] + + Object.keys(db).forEach(function forEachMimeType (type) { + var mime = db[type] + var exts = mime.extensions + + if (!exts || !exts.length) { + return + } + + // mime -> extensions + extensions[type] = exts + + // extension -> mime + for (var i = 0; i < exts.length; i++) { + var extension = exts[i] + + if (types[extension]) { + var from = preference.indexOf(db[types[extension]].source) + var to = preference.indexOf(mime.source) + + if (types[extension] !== 'application/octet-stream' && + (from > to || (from === to && types[extension].substr(0, 12) === 'application/'))) { + // skip the remapping + continue + } + } + + // set the extension -> mime + types[extension] = type + } + }) +} diff --git a/node_modules/mime-types/package.json b/node_modules/mime-types/package.json new file mode 100644 index 0000000..bbef696 --- /dev/null +++ b/node_modules/mime-types/package.json @@ -0,0 +1,44 @@ +{ + "name": "mime-types", + "description": "The ultimate javascript content-type utility.", + "version": "2.1.35", + "contributors": [ + "Douglas Christopher Wilson ", + "Jeremiah Senkpiel (https://searchbeam.jit.su)", + "Jonathan Ong (http://jongleberry.com)" + ], + "license": "MIT", + "keywords": [ + "mime", + "types" + ], + "repository": "jshttp/mime-types", + "dependencies": { + "mime-db": "1.52.0" + }, + "devDependencies": { + "eslint": "7.32.0", + "eslint-config-standard": "14.1.1", + "eslint-plugin-import": "2.25.4", + "eslint-plugin-markdown": "2.2.1", + "eslint-plugin-node": "11.1.0", + "eslint-plugin-promise": "5.2.0", + "eslint-plugin-standard": "4.1.0", + "mocha": "9.2.2", + "nyc": "15.1.0" + }, + "files": [ + "HISTORY.md", + "LICENSE", + "index.js" + ], + "engines": { + "node": ">= 0.6" + }, + "scripts": { + "lint": "eslint .", + "test": "mocha --reporter spec test/test.js", + "test-ci": "nyc --reporter=lcov --reporter=text npm test", + "test-cov": "nyc --reporter=html --reporter=text npm test" + } +} diff --git a/node_modules/minimist/.eslintrc b/node_modules/minimist/.eslintrc new file mode 100644 index 0000000..bd1a5e0 --- /dev/null +++ b/node_modules/minimist/.eslintrc @@ -0,0 +1,29 @@ +{ + "root": true, + + "extends": "@ljharb/eslint-config/node/0.4", + + "rules": { + "array-element-newline": 0, + "complexity": 0, + "func-style": [2, "declaration"], + "max-lines-per-function": 0, + "max-nested-callbacks": 1, + "max-statements-per-line": 1, + "max-statements": 0, + "multiline-comment-style": 0, + "no-continue": 1, + "no-param-reassign": 1, + "no-restricted-syntax": 1, + "object-curly-newline": 0, + }, + + "overrides": [ + { + "files": "test/**", + "rules": { + "camelcase": 0, + }, + }, + ] +} diff --git a/node_modules/minimist/.github/FUNDING.yml b/node_modules/minimist/.github/FUNDING.yml new file mode 100644 index 0000000..a936622 --- /dev/null +++ b/node_modules/minimist/.github/FUNDING.yml @@ -0,0 +1,12 @@ +# These are supported funding model platforms + +github: [ljharb] +patreon: # Replace with a single Patreon username +open_collective: # Replace with a single Open Collective username +ko_fi: # Replace with a single Ko-fi username +tidelift: npm/minimist +community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry +liberapay: # Replace with a single Liberapay username +issuehunt: # Replace with a single IssueHunt username +otechie: # Replace with a single Otechie username +custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] diff --git a/node_modules/minimist/.nycrc b/node_modules/minimist/.nycrc new file mode 100644 index 0000000..55c3d29 --- /dev/null +++ b/node_modules/minimist/.nycrc @@ -0,0 +1,14 @@ +{ + "all": true, + "check-coverage": false, + "reporter": ["text-summary", "text", "html", "json"], + "lines": 86, + "statements": 85.93, + "functions": 82.43, + "branches": 76.06, + "exclude": [ + "coverage", + "example", + "test" + ] +} diff --git a/node_modules/minimist/CHANGELOG.md b/node_modules/minimist/CHANGELOG.md new file mode 100644 index 0000000..c9a1e15 --- /dev/null +++ b/node_modules/minimist/CHANGELOG.md @@ -0,0 +1,298 @@ +# Changelog + +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## [v1.2.8](https://github.com/minimistjs/minimist/compare/v1.2.7...v1.2.8) - 2023-02-09 + +### Merged + +- [Fix] Fix long option followed by single dash [`#17`](https://github.com/minimistjs/minimist/pull/17) +- [Tests] Remove duplicate test [`#12`](https://github.com/minimistjs/minimist/pull/12) +- [Fix] opt.string works with multiple aliases [`#10`](https://github.com/minimistjs/minimist/pull/10) + +### Fixed + +- [Fix] Fix long option followed by single dash (#17) [`#15`](https://github.com/minimistjs/minimist/issues/15) +- [Tests] Remove duplicate test (#12) [`#8`](https://github.com/minimistjs/minimist/issues/8) +- [Fix] Fix long option followed by single dash [`#15`](https://github.com/minimistjs/minimist/issues/15) +- [Fix] opt.string works with multiple aliases (#10) [`#9`](https://github.com/minimistjs/minimist/issues/9) +- [Fix] Fix handling of short option with non-trivial equals [`#5`](https://github.com/minimistjs/minimist/issues/5) +- [Tests] Remove duplicate test [`#8`](https://github.com/minimistjs/minimist/issues/8) +- [Fix] opt.string works with multiple aliases [`#9`](https://github.com/minimistjs/minimist/issues/9) + +### Commits + +- Merge tag 'v0.2.3' [`a026794`](https://github.com/minimistjs/minimist/commit/a0267947c7870fc5847cf2d437fbe33f392767da) +- [eslint] fix indentation and whitespace [`5368ca4`](https://github.com/minimistjs/minimist/commit/5368ca4147e974138a54cc0dc4cea8f756546b70) +- [eslint] fix indentation and whitespace [`e5f5067`](https://github.com/minimistjs/minimist/commit/e5f5067259ceeaf0b098d14bec910f87e58708c7) +- [eslint] more cleanup [`62fde7d`](https://github.com/minimistjs/minimist/commit/62fde7d935f83417fb046741531a9e2346a36976) +- [eslint] more cleanup [`36ac5d0`](https://github.com/minimistjs/minimist/commit/36ac5d0d95e4947d074e5737d94814034ca335d1) +- [meta] add `auto-changelog` [`73923d2`](https://github.com/minimistjs/minimist/commit/73923d223553fca08b1ba77e3fbc2a492862ae4c) +- [actions] add reusable workflows [`d80727d`](https://github.com/minimistjs/minimist/commit/d80727df77bfa9e631044d7f16368d8f09242c91) +- [eslint] add eslint; rules to enable later are warnings [`48bc06a`](https://github.com/minimistjs/minimist/commit/48bc06a1b41f00e9cdf183db34f7a51ba70e98d4) +- [eslint] fix indentation [`34b0f1c`](https://github.com/minimistjs/minimist/commit/34b0f1ccaa45183c3c4f06a91f9b405180a6f982) +- [readme] rename and add badges [`5df0fe4`](https://github.com/minimistjs/minimist/commit/5df0fe49211bd09a3636f8686a7cb3012c3e98f0) +- [Dev Deps] switch from `covert` to `nyc` [`a48b128`](https://github.com/minimistjs/minimist/commit/a48b128fdb8d427dfb20a15273f83e38d97bef07) +- [Dev Deps] update `covert`, `tape`; remove unnecessary `tap` [`f0fb958`](https://github.com/minimistjs/minimist/commit/f0fb958e9a1fe980cdffc436a211b0bda58f621b) +- [meta] create FUNDING.yml; add `funding` in package.json [`3639e0c`](https://github.com/minimistjs/minimist/commit/3639e0c819359a366387e425ab6eabf4c78d3caa) +- [meta] use `npmignore` to autogenerate an npmignore file [`be2e038`](https://github.com/minimistjs/minimist/commit/be2e038c342d8333b32f0fde67a0026b79c8150e) +- Only apps should have lockfiles [`282b570`](https://github.com/minimistjs/minimist/commit/282b570e7489d01b03f2d6d3dabf79cd3e5f84cf) +- isConstructorOrProto adapted from PR [`ef9153f`](https://github.com/minimistjs/minimist/commit/ef9153fc52b6cea0744b2239921c5dcae4697f11) +- [Dev Deps] update `@ljharb/eslint-config`, `aud` [`098873c`](https://github.com/minimistjs/minimist/commit/098873c213cdb7c92e55ae1ef5aa1af3a8192a79) +- [Dev Deps] update `@ljharb/eslint-config`, `aud` [`3124ed3`](https://github.com/minimistjs/minimist/commit/3124ed3e46306301ebb3c834874ce0241555c2c4) +- [meta] add `safe-publish-latest` [`4b927de`](https://github.com/minimistjs/minimist/commit/4b927de696d561c636b4f43bf49d4597cb36d6d6) +- [Tests] add `aud` in `posttest` [`b32d9bd`](https://github.com/minimistjs/minimist/commit/b32d9bd0ab340f4e9f8c3a97ff2a4424f25fab8c) +- [meta] update repo URLs [`f9fdfc0`](https://github.com/minimistjs/minimist/commit/f9fdfc032c54884d9a9996a390c63cd0719bbe1a) +- [actions] Avoid 0.6 tests due to build failures [`ba92fe6`](https://github.com/minimistjs/minimist/commit/ba92fe6ebbdc0431cca9a2ea8f27beb492f5e4ec) +- [Dev Deps] update `tape` [`950eaa7`](https://github.com/minimistjs/minimist/commit/950eaa74f112e04d23e9c606c67472c46739b473) +- [Dev Deps] add missing `npmignore` dev dep [`3226afa`](https://github.com/minimistjs/minimist/commit/3226afaf09e9d127ca369742437fe6e88f752d6b) +- Merge tag 'v0.2.2' [`980d7ac`](https://github.com/minimistjs/minimist/commit/980d7ac61a0b4bd552711251ac107d506b23e41f) + +## [v1.2.7](https://github.com/minimistjs/minimist/compare/v1.2.6...v1.2.7) - 2022-10-10 + +### Commits + +- [meta] add `auto-changelog` [`0ebf4eb`](https://github.com/minimistjs/minimist/commit/0ebf4ebcd5f7787a5524d31a849ef41316b83c3c) +- [actions] add reusable workflows [`e115b63`](https://github.com/minimistjs/minimist/commit/e115b63fa9d3909f33b00a2db647ff79068388de) +- [eslint] add eslint; rules to enable later are warnings [`f58745b`](https://github.com/minimistjs/minimist/commit/f58745b9bb84348e1be72af7dbba5840c7c13013) +- [Dev Deps] switch from `covert` to `nyc` [`ab03356`](https://github.com/minimistjs/minimist/commit/ab033567b9c8b31117cb026dc7f1e592ce455c65) +- [readme] rename and add badges [`236f4a0`](https://github.com/minimistjs/minimist/commit/236f4a07e4ebe5ee44f1496ec6974991ab293ffd) +- [meta] create FUNDING.yml; add `funding` in package.json [`783a49b`](https://github.com/minimistjs/minimist/commit/783a49bfd47e8335d3098a8cac75662cf71eb32a) +- [meta] use `npmignore` to autogenerate an npmignore file [`f81ece6`](https://github.com/minimistjs/minimist/commit/f81ece6aaec2fa14e69ff4f1e0407a8c4e2635a2) +- Only apps should have lockfiles [`56cad44`](https://github.com/minimistjs/minimist/commit/56cad44c7f879b9bb5ec18fcc349308024a89bfc) +- [Dev Deps] update `covert`, `tape`; remove unnecessary `tap` [`49c5f9f`](https://github.com/minimistjs/minimist/commit/49c5f9fb7e6a92db9eb340cc679de92fb3aacded) +- [Tests] add `aud` in `posttest` [`228ae93`](https://github.com/minimistjs/minimist/commit/228ae938f3cd9db9dfd8bd7458b076a7b2aef280) +- [meta] add `safe-publish-latest` [`01fc23f`](https://github.com/minimistjs/minimist/commit/01fc23f5104f85c75059972e01dd33796ab529ff) +- [meta] update repo URLs [`6b164c7`](https://github.com/minimistjs/minimist/commit/6b164c7d68e0b6bf32f894699effdfb7c63041dd) + +## [v1.2.6](https://github.com/minimistjs/minimist/compare/v1.2.5...v1.2.6) - 2022-03-21 + +### Commits + +- test from prototype pollution PR [`bc8ecee`](https://github.com/minimistjs/minimist/commit/bc8ecee43875261f4f17eb20b1243d3ed15e70eb) +- isConstructorOrProto adapted from PR [`c2b9819`](https://github.com/minimistjs/minimist/commit/c2b981977fa834b223b408cfb860f933c9811e4d) +- security notice for additional prototype pollution issue [`ef88b93`](https://github.com/minimistjs/minimist/commit/ef88b9325f77b5ee643ccfc97e2ebda577e4c4e2) + +## [v1.2.5](https://github.com/minimistjs/minimist/compare/v1.2.4...v1.2.5) - 2020-03-12 + +## [v1.2.4](https://github.com/minimistjs/minimist/compare/v1.2.3...v1.2.4) - 2020-03-11 + +### Commits + +- security notice [`4cf1354`](https://github.com/minimistjs/minimist/commit/4cf1354839cb972e38496d35e12f806eea92c11f) +- additional test for constructor prototype pollution [`1043d21`](https://github.com/minimistjs/minimist/commit/1043d212c3caaf871966e710f52cfdf02f9eea4b) + +## [v1.2.3](https://github.com/minimistjs/minimist/compare/v1.2.2...v1.2.3) - 2020-03-10 + +### Commits + +- more failing proto pollution tests [`13c01a5`](https://github.com/minimistjs/minimist/commit/13c01a5327736903704984b7f65616b8476850cc) +- even more aggressive checks for protocol pollution [`38a4d1c`](https://github.com/minimistjs/minimist/commit/38a4d1caead72ef99e824bb420a2528eec03d9ab) + +## [v1.2.2](https://github.com/minimistjs/minimist/compare/v1.2.1...v1.2.2) - 2020-03-10 + +### Commits + +- failing test for protocol pollution [`0efed03`](https://github.com/minimistjs/minimist/commit/0efed0340ec8433638758f7ca0c77cb20a0bfbab) +- cleanup [`67d3722`](https://github.com/minimistjs/minimist/commit/67d3722413448d00a62963d2d30c34656a92d7e2) +- console.dir -> console.log [`47acf72`](https://github.com/minimistjs/minimist/commit/47acf72c715a630bf9ea013867f47f1dd69dfc54) +- don't assign onto __proto__ [`63e7ed0`](https://github.com/minimistjs/minimist/commit/63e7ed05aa4b1889ec2f3b196426db4500cbda94) + +## [v1.2.1](https://github.com/minimistjs/minimist/compare/v1.2.0...v1.2.1) - 2020-03-10 + +### Merged + +- move the `opts['--']` example back where it belongs [`#63`](https://github.com/minimistjs/minimist/pull/63) + +### Commits + +- add test [`6be5dae`](https://github.com/minimistjs/minimist/commit/6be5dae35a32a987bcf4137fcd6c19c5200ee909) +- fix bad boolean regexp [`ac3fc79`](https://github.com/minimistjs/minimist/commit/ac3fc796e63b95128fdbdf67ea7fad71bd59aa76) + +## [v1.2.0](https://github.com/minimistjs/minimist/compare/v1.1.3...v1.2.0) - 2015-08-24 + +### Commits + +- failing -k=v short test [`63416b8`](https://github.com/minimistjs/minimist/commit/63416b8cd1d0d70e4714564cce465a36e4dd26d7) +- kv short fix [`6bbe145`](https://github.com/minimistjs/minimist/commit/6bbe14529166245e86424f220a2321442fe88dc3) +- failing kv short test [`f72ab7f`](https://github.com/minimistjs/minimist/commit/f72ab7f4572adc52902c9b6873cc969192f01b10) +- fixed kv test [`f5a48c3`](https://github.com/minimistjs/minimist/commit/f5a48c3e50e40ca54f00c8e84de4b4d6e9897fa8) +- enforce space between arg key and value [`86b321a`](https://github.com/minimistjs/minimist/commit/86b321affe648a8e016c095a4f0efa9d9074f502) + +## [v1.1.3](https://github.com/minimistjs/minimist/compare/v1.1.2...v1.1.3) - 2015-08-06 + +### Commits + +- add failing test - boolean alias array [`0fa3c5b`](https://github.com/minimistjs/minimist/commit/0fa3c5b3dd98551ddecf5392831b4c21211743fc) +- fix boolean values with multiple aliases [`9c0a6e7`](https://github.com/minimistjs/minimist/commit/9c0a6e7de25a273b11bbf9a7464f0bd833779795) + +## [v1.1.2](https://github.com/minimistjs/minimist/compare/v1.1.1...v1.1.2) - 2015-07-22 + +### Commits + +- Convert boolean arguments to boolean values [`8f3dc27`](https://github.com/minimistjs/minimist/commit/8f3dc27cf833f1d54671b6d0bcb55c2fe19672a9) +- use non-ancient npm, node 0.12 and iojs [`61ed1d0`](https://github.com/minimistjs/minimist/commit/61ed1d034b9ec7282764ce76f3992b1a0b4906ae) +- an older npm for 0.8 [`25cf778`](https://github.com/minimistjs/minimist/commit/25cf778b1220e7838a526832ad6972f75244054f) + +## [v1.1.1](https://github.com/minimistjs/minimist/compare/v1.1.0...v1.1.1) - 2015-03-10 + +### Commits + +- check that they type of a value is a boolean, not just that it is currently set to a boolean [`6863198`](https://github.com/minimistjs/minimist/commit/6863198e36139830ff1f20ffdceaddd93f2c1db9) +- upgrade tape, fix type issues from old tape version [`806712d`](https://github.com/minimistjs/minimist/commit/806712df91604ed02b8e39aa372b84aea659ee34) +- test for setting a boolean to a null default [`8c444fe`](https://github.com/minimistjs/minimist/commit/8c444fe89384ded7d441c120915ea60620b01dd3) +- if the previous value was a boolean, without an default (or with an alias) don't make an array either [`e5f419a`](https://github.com/minimistjs/minimist/commit/e5f419a3b5b3bc3f9e5ac71b7040621af70ed2dd) + +## [v1.1.0](https://github.com/minimistjs/minimist/compare/v1.0.0...v1.1.0) - 2014-08-10 + +### Commits + +- add support for handling "unknown" options not registered with the parser. [`6f3cc5d`](https://github.com/minimistjs/minimist/commit/6f3cc5d4e84524932a6ef2ce3592acc67cdd4383) +- reformat package.json [`02ed371`](https://github.com/minimistjs/minimist/commit/02ed37115194d3697ff358e8e25e5e66bab1d9f8) +- coverage script [`e5531ba`](https://github.com/minimistjs/minimist/commit/e5531ba0479da3b8138d3d8cac545d84ccb1c8df) +- extra fn to get 100% coverage again [`a6972da`](https://github.com/minimistjs/minimist/commit/a6972da89e56bf77642f8ec05a13b6558db93498) + +## [v1.0.0](https://github.com/minimistjs/minimist/compare/v0.2.3...v1.0.0) - 2014-08-10 + +### Commits + +- added stopEarly option [`471c7e4`](https://github.com/minimistjs/minimist/commit/471c7e4a7e910fc7ad8f9df850a186daf32c64e9) +- fix list [`fef6ae7`](https://github.com/minimistjs/minimist/commit/fef6ae79c38b9dc1c49569abb7cd04eb965eac5e) + +## [v0.2.3](https://github.com/minimistjs/minimist/compare/v0.2.2...v0.2.3) - 2023-02-09 + +### Merged + +- [Fix] Fix long option followed by single dash [`#17`](https://github.com/minimistjs/minimist/pull/17) +- [Tests] Remove duplicate test [`#12`](https://github.com/minimistjs/minimist/pull/12) +- [Fix] opt.string works with multiple aliases [`#10`](https://github.com/minimistjs/minimist/pull/10) + +### Fixed + +- [Fix] Fix long option followed by single dash (#17) [`#15`](https://github.com/minimistjs/minimist/issues/15) +- [Tests] Remove duplicate test (#12) [`#8`](https://github.com/minimistjs/minimist/issues/8) +- [Fix] opt.string works with multiple aliases (#10) [`#9`](https://github.com/minimistjs/minimist/issues/9) + +### Commits + +- [eslint] fix indentation and whitespace [`e5f5067`](https://github.com/minimistjs/minimist/commit/e5f5067259ceeaf0b098d14bec910f87e58708c7) +- [eslint] more cleanup [`36ac5d0`](https://github.com/minimistjs/minimist/commit/36ac5d0d95e4947d074e5737d94814034ca335d1) +- [eslint] fix indentation [`34b0f1c`](https://github.com/minimistjs/minimist/commit/34b0f1ccaa45183c3c4f06a91f9b405180a6f982) +- isConstructorOrProto adapted from PR [`ef9153f`](https://github.com/minimistjs/minimist/commit/ef9153fc52b6cea0744b2239921c5dcae4697f11) +- [Dev Deps] update `@ljharb/eslint-config`, `aud` [`098873c`](https://github.com/minimistjs/minimist/commit/098873c213cdb7c92e55ae1ef5aa1af3a8192a79) +- [Dev Deps] add missing `npmignore` dev dep [`3226afa`](https://github.com/minimistjs/minimist/commit/3226afaf09e9d127ca369742437fe6e88f752d6b) + +## [v0.2.2](https://github.com/minimistjs/minimist/compare/v0.2.1...v0.2.2) - 2022-10-10 + +### Commits + +- [meta] add `auto-changelog` [`73923d2`](https://github.com/minimistjs/minimist/commit/73923d223553fca08b1ba77e3fbc2a492862ae4c) +- [actions] add reusable workflows [`d80727d`](https://github.com/minimistjs/minimist/commit/d80727df77bfa9e631044d7f16368d8f09242c91) +- [eslint] add eslint; rules to enable later are warnings [`48bc06a`](https://github.com/minimistjs/minimist/commit/48bc06a1b41f00e9cdf183db34f7a51ba70e98d4) +- [readme] rename and add badges [`5df0fe4`](https://github.com/minimistjs/minimist/commit/5df0fe49211bd09a3636f8686a7cb3012c3e98f0) +- [Dev Deps] switch from `covert` to `nyc` [`a48b128`](https://github.com/minimistjs/minimist/commit/a48b128fdb8d427dfb20a15273f83e38d97bef07) +- [Dev Deps] update `covert`, `tape`; remove unnecessary `tap` [`f0fb958`](https://github.com/minimistjs/minimist/commit/f0fb958e9a1fe980cdffc436a211b0bda58f621b) +- [meta] create FUNDING.yml; add `funding` in package.json [`3639e0c`](https://github.com/minimistjs/minimist/commit/3639e0c819359a366387e425ab6eabf4c78d3caa) +- [meta] use `npmignore` to autogenerate an npmignore file [`be2e038`](https://github.com/minimistjs/minimist/commit/be2e038c342d8333b32f0fde67a0026b79c8150e) +- Only apps should have lockfiles [`282b570`](https://github.com/minimistjs/minimist/commit/282b570e7489d01b03f2d6d3dabf79cd3e5f84cf) +- [meta] add `safe-publish-latest` [`4b927de`](https://github.com/minimistjs/minimist/commit/4b927de696d561c636b4f43bf49d4597cb36d6d6) +- [Tests] add `aud` in `posttest` [`b32d9bd`](https://github.com/minimistjs/minimist/commit/b32d9bd0ab340f4e9f8c3a97ff2a4424f25fab8c) +- [meta] update repo URLs [`f9fdfc0`](https://github.com/minimistjs/minimist/commit/f9fdfc032c54884d9a9996a390c63cd0719bbe1a) + +## [v0.2.1](https://github.com/minimistjs/minimist/compare/v0.2.0...v0.2.1) - 2020-03-12 + +## [v0.2.0](https://github.com/minimistjs/minimist/compare/v0.1.0...v0.2.0) - 2014-06-19 + +### Commits + +- support all-boolean mode [`450a97f`](https://github.com/minimistjs/minimist/commit/450a97f6e2bc85c7a4a13185c19a818d9a5ebe69) + +## [v0.1.0](https://github.com/minimistjs/minimist/compare/v0.0.10...v0.1.0) - 2014-05-12 + +### Commits + +- Provide a mechanism to segregate -- arguments [`ce4a1e6`](https://github.com/minimistjs/minimist/commit/ce4a1e63a7e8d5ab88d2a3768adefa6af98a445a) +- documented argv['--'] [`14db0e6`](https://github.com/minimistjs/minimist/commit/14db0e6dbc6d2b9e472adaa54dad7004b364634f) +- Adding a test-case for notFlags segregation [`715c1e3`](https://github.com/minimistjs/minimist/commit/715c1e3714be223f998f6c537af6b505f0236c16) + +## [v0.0.10](https://github.com/minimistjs/minimist/compare/v0.0.9...v0.0.10) - 2014-05-11 + +### Commits + +- dedicated boolean test [`46e448f`](https://github.com/minimistjs/minimist/commit/46e448f9f513cfeb2bcc8b688b9b47ba1e515c2b) +- dedicated num test [`9bf2d36`](https://github.com/minimistjs/minimist/commit/9bf2d36f1d3b8795be90b8f7de0a937f098aa394) +- aliased values treated as strings [`1ab743b`](https://github.com/minimistjs/minimist/commit/1ab743bad4484d69f1259bed42f9531de01119de) +- cover the case of already numbers, at 100% coverage [`b2bb044`](https://github.com/minimistjs/minimist/commit/b2bb04436599d77a2ce029e8e555e25b3aa55d13) +- another test for higher coverage [`3662624`](https://github.com/minimistjs/minimist/commit/3662624be976d5489d486a856849c048d13be903) + +## [v0.0.9](https://github.com/minimistjs/minimist/compare/v0.0.8...v0.0.9) - 2014-05-08 + +### Commits + +- Eliminate `longest` fn. [`824f642`](https://github.com/minimistjs/minimist/commit/824f642038d1b02ede68b6261d1d65163390929a) + +## [v0.0.8](https://github.com/minimistjs/minimist/compare/v0.0.7...v0.0.8) - 2014-02-20 + +### Commits + +- return '' if flag is string and empty [`fa63ed4`](https://github.com/minimistjs/minimist/commit/fa63ed4651a4ef4eefddce34188e0d98d745a263) +- handle joined single letters [`66c248f`](https://github.com/minimistjs/minimist/commit/66c248f0241d4d421d193b022e9e365f11178534) + +## [v0.0.7](https://github.com/minimistjs/minimist/compare/v0.0.6...v0.0.7) - 2014-02-08 + +### Commits + +- another swap of .test for .match [`d1da408`](https://github.com/minimistjs/minimist/commit/d1da40819acbe846d89a5c02721211e3c1260dde) + +## [v0.0.6](https://github.com/minimistjs/minimist/compare/v0.0.5...v0.0.6) - 2014-02-08 + +### Commits + +- use .test() instead of .match() to not crash on non-string values in the arguments array [`7e0d1ad`](https://github.com/minimistjs/minimist/commit/7e0d1add8c9e5b9b20a4d3d0f9a94d824c578da1) + +## [v0.0.5](https://github.com/minimistjs/minimist/compare/v0.0.4...v0.0.5) - 2013-09-18 + +### Commits + +- Improve '--' handling. [`b11822c`](https://github.com/minimistjs/minimist/commit/b11822c09cc9d2460f30384d12afc0b953c037a4) + +## [v0.0.4](https://github.com/minimistjs/minimist/compare/v0.0.3...v0.0.4) - 2013-09-17 + +## [v0.0.3](https://github.com/minimistjs/minimist/compare/v0.0.2...v0.0.3) - 2013-09-12 + +### Commits + +- failing test for single dash preceeding a double dash [`b465514`](https://github.com/minimistjs/minimist/commit/b465514b82c9ae28972d714facd951deb2ad762b) +- fix for the dot test [`6a095f1`](https://github.com/minimistjs/minimist/commit/6a095f1d364c8fab2d6753d2291a0649315d297a) + +## [v0.0.2](https://github.com/minimistjs/minimist/compare/v0.0.1...v0.0.2) - 2013-08-28 + +### Commits + +- allow dotted aliases & defaults [`321c33e`](https://github.com/minimistjs/minimist/commit/321c33e755485faaeb44eeb1c05d33b2e0a5a7c4) +- use a better version of ff [`e40f611`](https://github.com/minimistjs/minimist/commit/e40f61114cf7be6f7947f7b3eed345853a67dbbb) + +## [v0.0.1](https://github.com/minimistjs/minimist/compare/v0.0.0...v0.0.1) - 2013-06-25 + +### Commits + +- remove trailing commas [`6ff0fa0`](https://github.com/minimistjs/minimist/commit/6ff0fa055064f15dbe06d50b89d5173a6796e1db) + +## v0.0.0 - 2013-06-25 + +### Commits + +- half of the parse test ported [`3079326`](https://github.com/minimistjs/minimist/commit/307932601325087de6cf94188eb798ffc4f3088a) +- stripped down code and a passing test from optimist [`7cced88`](https://github.com/minimistjs/minimist/commit/7cced88d82e399d1a03ed23eb667f04d3f320d10) +- ported parse tests completely over [`9448754`](https://github.com/minimistjs/minimist/commit/944875452e0820df6830b1408c26a0f7d3e1db04) +- docs, package.json [`a5bf46a`](https://github.com/minimistjs/minimist/commit/a5bf46ac9bb3bd114a9c340276c62c1091e538d5) +- move more short tests into short.js [`503edb5`](https://github.com/minimistjs/minimist/commit/503edb5c41d89c0d40831ee517154fc13b0f18b9) +- default bool test was wrong, not the code [`1b9f5db`](https://github.com/minimistjs/minimist/commit/1b9f5db4741b49962846081b68518de824992097) +- passing long tests ripped out of parse.js [`7972c4a`](https://github.com/minimistjs/minimist/commit/7972c4aff1f4803079e1668006658e2a761a0428) +- badges [`84c0370`](https://github.com/minimistjs/minimist/commit/84c037063664d42878aace715fe6572ce01b6f3b) +- all the tests now ported, some failures [`64239ed`](https://github.com/minimistjs/minimist/commit/64239edfe92c711c4eb0da254fcdfad2a5fdb605) +- failing short test [`f8a5341`](https://github.com/minimistjs/minimist/commit/f8a534112dd1138d2fad722def56a848480c446f) +- fixed the numeric test [`6b034f3`](https://github.com/minimistjs/minimist/commit/6b034f37c79342c60083ed97fd222e16928aac51) diff --git a/node_modules/minimist/LICENSE b/node_modules/minimist/LICENSE new file mode 100644 index 0000000..ee27ba4 --- /dev/null +++ b/node_modules/minimist/LICENSE @@ -0,0 +1,18 @@ +This software is released under the MIT license: + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/node_modules/minimist/README.md b/node_modules/minimist/README.md new file mode 100644 index 0000000..74da323 --- /dev/null +++ b/node_modules/minimist/README.md @@ -0,0 +1,121 @@ +# minimist [![Version Badge][npm-version-svg]][package-url] + +[![github actions][actions-image]][actions-url] +[![coverage][codecov-image]][codecov-url] +[![License][license-image]][license-url] +[![Downloads][downloads-image]][downloads-url] + +[![npm badge][npm-badge-png]][package-url] + +parse argument options + +This module is the guts of optimist's argument parser without all the +fanciful decoration. + +# example + +``` js +var argv = require('minimist')(process.argv.slice(2)); +console.log(argv); +``` + +``` +$ node example/parse.js -a beep -b boop +{ _: [], a: 'beep', b: 'boop' } +``` + +``` +$ node example/parse.js -x 3 -y 4 -n5 -abc --beep=boop foo bar baz +{ + _: ['foo', 'bar', 'baz'], + x: 3, + y: 4, + n: 5, + a: true, + b: true, + c: true, + beep: 'boop' +} +``` + +# security + +Previous versions had a prototype pollution bug that could cause privilege +escalation in some circumstances when handling untrusted user input. + +Please use version 1.2.6 or later: + +* https://security.snyk.io/vuln/SNYK-JS-MINIMIST-2429795 (version <=1.2.5) +* https://snyk.io/vuln/SNYK-JS-MINIMIST-559764 (version <=1.2.3) + +# methods + +``` js +var parseArgs = require('minimist') +``` + +## var argv = parseArgs(args, opts={}) + +Return an argument object `argv` populated with the array arguments from `args`. + +`argv._` contains all the arguments that didn't have an option associated with +them. + +Numeric-looking arguments will be returned as numbers unless `opts.string` or +`opts.boolean` is set for that argument name. + +Any arguments after `'--'` will not be parsed and will end up in `argv._`. + +options can be: + +* `opts.string` - a string or array of strings argument names to always treat as +strings +* `opts.boolean` - a boolean, string or array of strings to always treat as +booleans. if `true` will treat all double hyphenated arguments without equal signs +as boolean (e.g. affects `--foo`, not `-f` or `--foo=bar`) +* `opts.alias` - an object mapping string names to strings or arrays of string +argument names to use as aliases +* `opts.default` - an object mapping string argument names to default values +* `opts.stopEarly` - when true, populate `argv._` with everything after the +first non-option +* `opts['--']` - when true, populate `argv._` with everything before the `--` +and `argv['--']` with everything after the `--`. Here's an example: + + ``` + > require('./')('one two three -- four five --six'.split(' '), { '--': true }) + { + _: ['one', 'two', 'three'], + '--': ['four', 'five', '--six'] + } + ``` + + Note that with `opts['--']` set, parsing for arguments still stops after the + `--`. + +* `opts.unknown` - a function which is invoked with a command line parameter not +defined in the `opts` configuration object. If the function returns `false`, the +unknown option is not added to `argv`. + +# install + +With [npm](https://npmjs.org) do: + +``` +npm install minimist +``` + +# license + +MIT + +[package-url]: https://npmjs.org/package/minimist +[npm-version-svg]: https://versionbadg.es/minimistjs/minimist.svg +[npm-badge-png]: https://nodei.co/npm/minimist.png?downloads=true&stars=true +[license-image]: https://img.shields.io/npm/l/minimist.svg +[license-url]: LICENSE +[downloads-image]: https://img.shields.io/npm/dm/minimist.svg +[downloads-url]: https://npm-stat.com/charts.html?package=minimist +[codecov-image]: https://codecov.io/gh/minimistjs/minimist/branch/main/graphs/badge.svg +[codecov-url]: https://app.codecov.io/gh/minimistjs/minimist/ +[actions-image]: https://img.shields.io/endpoint?url=https://github-actions-badge-u3jn4tfpocch.runkit.sh/minimistjs/minimist +[actions-url]: https://github.com/minimistjs/minimist/actions diff --git a/node_modules/minimist/example/parse.js b/node_modules/minimist/example/parse.js new file mode 100644 index 0000000..9d90ffb --- /dev/null +++ b/node_modules/minimist/example/parse.js @@ -0,0 +1,4 @@ +'use strict'; + +var argv = require('../')(process.argv.slice(2)); +console.log(argv); diff --git a/node_modules/minimist/index.js b/node_modules/minimist/index.js new file mode 100644 index 0000000..f020f39 --- /dev/null +++ b/node_modules/minimist/index.js @@ -0,0 +1,263 @@ +'use strict'; + +function hasKey(obj, keys) { + var o = obj; + keys.slice(0, -1).forEach(function (key) { + o = o[key] || {}; + }); + + var key = keys[keys.length - 1]; + return key in o; +} + +function isNumber(x) { + if (typeof x === 'number') { return true; } + if ((/^0x[0-9a-f]+$/i).test(x)) { return true; } + return (/^[-+]?(?:\d+(?:\.\d*)?|\.\d+)(e[-+]?\d+)?$/).test(x); +} + +function isConstructorOrProto(obj, key) { + return (key === 'constructor' && typeof obj[key] === 'function') || key === '__proto__'; +} + +module.exports = function (args, opts) { + if (!opts) { opts = {}; } + + var flags = { + bools: {}, + strings: {}, + unknownFn: null, + }; + + if (typeof opts.unknown === 'function') { + flags.unknownFn = opts.unknown; + } + + if (typeof opts.boolean === 'boolean' && opts.boolean) { + flags.allBools = true; + } else { + [].concat(opts.boolean).filter(Boolean).forEach(function (key) { + flags.bools[key] = true; + }); + } + + var aliases = {}; + + function aliasIsBoolean(key) { + return aliases[key].some(function (x) { + return flags.bools[x]; + }); + } + + Object.keys(opts.alias || {}).forEach(function (key) { + aliases[key] = [].concat(opts.alias[key]); + aliases[key].forEach(function (x) { + aliases[x] = [key].concat(aliases[key].filter(function (y) { + return x !== y; + })); + }); + }); + + [].concat(opts.string).filter(Boolean).forEach(function (key) { + flags.strings[key] = true; + if (aliases[key]) { + [].concat(aliases[key]).forEach(function (k) { + flags.strings[k] = true; + }); + } + }); + + var defaults = opts.default || {}; + + var argv = { _: [] }; + + function argDefined(key, arg) { + return (flags.allBools && (/^--[^=]+$/).test(arg)) + || flags.strings[key] + || flags.bools[key] + || aliases[key]; + } + + function setKey(obj, keys, value) { + var o = obj; + for (var i = 0; i < keys.length - 1; i++) { + var key = keys[i]; + if (isConstructorOrProto(o, key)) { return; } + if (o[key] === undefined) { o[key] = {}; } + if ( + o[key] === Object.prototype + || o[key] === Number.prototype + || o[key] === String.prototype + ) { + o[key] = {}; + } + if (o[key] === Array.prototype) { o[key] = []; } + o = o[key]; + } + + var lastKey = keys[keys.length - 1]; + if (isConstructorOrProto(o, lastKey)) { return; } + if ( + o === Object.prototype + || o === Number.prototype + || o === String.prototype + ) { + o = {}; + } + if (o === Array.prototype) { o = []; } + if (o[lastKey] === undefined || flags.bools[lastKey] || typeof o[lastKey] === 'boolean') { + o[lastKey] = value; + } else if (Array.isArray(o[lastKey])) { + o[lastKey].push(value); + } else { + o[lastKey] = [o[lastKey], value]; + } + } + + function setArg(key, val, arg) { + if (arg && flags.unknownFn && !argDefined(key, arg)) { + if (flags.unknownFn(arg) === false) { return; } + } + + var value = !flags.strings[key] && isNumber(val) + ? Number(val) + : val; + setKey(argv, key.split('.'), value); + + (aliases[key] || []).forEach(function (x) { + setKey(argv, x.split('.'), value); + }); + } + + Object.keys(flags.bools).forEach(function (key) { + setArg(key, defaults[key] === undefined ? false : defaults[key]); + }); + + var notFlags = []; + + if (args.indexOf('--') !== -1) { + notFlags = args.slice(args.indexOf('--') + 1); + args = args.slice(0, args.indexOf('--')); + } + + for (var i = 0; i < args.length; i++) { + var arg = args[i]; + var key; + var next; + + if ((/^--.+=/).test(arg)) { + // Using [\s\S] instead of . because js doesn't support the + // 'dotall' regex modifier. See: + // http://stackoverflow.com/a/1068308/13216 + var m = arg.match(/^--([^=]+)=([\s\S]*)$/); + key = m[1]; + var value = m[2]; + if (flags.bools[key]) { + value = value !== 'false'; + } + setArg(key, value, arg); + } else if ((/^--no-.+/).test(arg)) { + key = arg.match(/^--no-(.+)/)[1]; + setArg(key, false, arg); + } else if ((/^--.+/).test(arg)) { + key = arg.match(/^--(.+)/)[1]; + next = args[i + 1]; + if ( + next !== undefined + && !(/^(-|--)[^-]/).test(next) + && !flags.bools[key] + && !flags.allBools + && (aliases[key] ? !aliasIsBoolean(key) : true) + ) { + setArg(key, next, arg); + i += 1; + } else if ((/^(true|false)$/).test(next)) { + setArg(key, next === 'true', arg); + i += 1; + } else { + setArg(key, flags.strings[key] ? '' : true, arg); + } + } else if ((/^-[^-]+/).test(arg)) { + var letters = arg.slice(1, -1).split(''); + + var broken = false; + for (var j = 0; j < letters.length; j++) { + next = arg.slice(j + 2); + + if (next === '-') { + setArg(letters[j], next, arg); + continue; + } + + if ((/[A-Za-z]/).test(letters[j]) && next[0] === '=') { + setArg(letters[j], next.slice(1), arg); + broken = true; + break; + } + + if ( + (/[A-Za-z]/).test(letters[j]) + && (/-?\d+(\.\d*)?(e-?\d+)?$/).test(next) + ) { + setArg(letters[j], next, arg); + broken = true; + break; + } + + if (letters[j + 1] && letters[j + 1].match(/\W/)) { + setArg(letters[j], arg.slice(j + 2), arg); + broken = true; + break; + } else { + setArg(letters[j], flags.strings[letters[j]] ? '' : true, arg); + } + } + + key = arg.slice(-1)[0]; + if (!broken && key !== '-') { + if ( + args[i + 1] + && !(/^(-|--)[^-]/).test(args[i + 1]) + && !flags.bools[key] + && (aliases[key] ? !aliasIsBoolean(key) : true) + ) { + setArg(key, args[i + 1], arg); + i += 1; + } else if (args[i + 1] && (/^(true|false)$/).test(args[i + 1])) { + setArg(key, args[i + 1] === 'true', arg); + i += 1; + } else { + setArg(key, flags.strings[key] ? '' : true, arg); + } + } + } else { + if (!flags.unknownFn || flags.unknownFn(arg) !== false) { + argv._.push(flags.strings._ || !isNumber(arg) ? arg : Number(arg)); + } + if (opts.stopEarly) { + argv._.push.apply(argv._, args.slice(i + 1)); + break; + } + } + } + + Object.keys(defaults).forEach(function (k) { + if (!hasKey(argv, k.split('.'))) { + setKey(argv, k.split('.'), defaults[k]); + + (aliases[k] || []).forEach(function (x) { + setKey(argv, x.split('.'), defaults[k]); + }); + } + }); + + if (opts['--']) { + argv['--'] = notFlags.slice(); + } else { + notFlags.forEach(function (k) { + argv._.push(k); + }); + } + + return argv; +}; diff --git a/node_modules/minimist/package.json b/node_modules/minimist/package.json new file mode 100644 index 0000000..c10a334 --- /dev/null +++ b/node_modules/minimist/package.json @@ -0,0 +1,75 @@ +{ + "name": "minimist", + "version": "1.2.8", + "description": "parse argument options", + "main": "index.js", + "devDependencies": { + "@ljharb/eslint-config": "^21.0.1", + "aud": "^2.0.2", + "auto-changelog": "^2.4.0", + "eslint": "=8.8.0", + "in-publish": "^2.0.1", + "npmignore": "^0.3.0", + "nyc": "^10.3.2", + "safe-publish-latest": "^2.0.0", + "tape": "^5.6.3" + }, + "scripts": { + "prepack": "npmignore --auto --commentLines=auto", + "prepublishOnly": "safe-publish-latest", + "prepublish": "not-in-publish || npm run prepublishOnly", + "lint": "eslint --ext=js,mjs .", + "pretest": "npm run lint", + "tests-only": "nyc tape 'test/**/*.js'", + "test": "npm run tests-only", + "posttest": "aud --production", + "version": "auto-changelog && git add CHANGELOG.md", + "postversion": "auto-changelog && git add CHANGELOG.md && git commit --no-edit --amend && git tag -f \"v$(node -e \"console.log(require('./package.json').version)\")\"" + }, + "testling": { + "files": "test/*.js", + "browsers": [ + "ie/6..latest", + "ff/5", + "firefox/latest", + "chrome/10", + "chrome/latest", + "safari/5.1", + "safari/latest", + "opera/12" + ] + }, + "repository": { + "type": "git", + "url": "git://github.com/minimistjs/minimist.git" + }, + "homepage": "https://github.com/minimistjs/minimist", + "keywords": [ + "argv", + "getopt", + "parser", + "optimist" + ], + "author": { + "name": "James Halliday", + "email": "mail@substack.net", + "url": "http://substack.net" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + }, + "license": "MIT", + "auto-changelog": { + "output": "CHANGELOG.md", + "template": "keepachangelog", + "unreleased": false, + "commitLimit": false, + "backfillLimit": false, + "hideCredit": true + }, + "publishConfig": { + "ignore": [ + ".github/workflows" + ] + } +} diff --git a/node_modules/minimist/test/all_bool.js b/node_modules/minimist/test/all_bool.js new file mode 100644 index 0000000..befa0c9 --- /dev/null +++ b/node_modules/minimist/test/all_bool.js @@ -0,0 +1,34 @@ +'use strict'; + +var parse = require('../'); +var test = require('tape'); + +test('flag boolean true (default all --args to boolean)', function (t) { + var argv = parse(['moo', '--honk', 'cow'], { + boolean: true, + }); + + t.deepEqual(argv, { + honk: true, + _: ['moo', 'cow'], + }); + + t.deepEqual(typeof argv.honk, 'boolean'); + t.end(); +}); + +test('flag boolean true only affects double hyphen arguments without equals signs', function (t) { + var argv = parse(['moo', '--honk', 'cow', '-p', '55', '--tacos=good'], { + boolean: true, + }); + + t.deepEqual(argv, { + honk: true, + tacos: 'good', + p: 55, + _: ['moo', 'cow'], + }); + + t.deepEqual(typeof argv.honk, 'boolean'); + t.end(); +}); diff --git a/node_modules/minimist/test/bool.js b/node_modules/minimist/test/bool.js new file mode 100644 index 0000000..e58d47e --- /dev/null +++ b/node_modules/minimist/test/bool.js @@ -0,0 +1,177 @@ +'use strict'; + +var parse = require('../'); +var test = require('tape'); + +test('flag boolean default false', function (t) { + var argv = parse(['moo'], { + boolean: ['t', 'verbose'], + default: { verbose: false, t: false }, + }); + + t.deepEqual(argv, { + verbose: false, + t: false, + _: ['moo'], + }); + + t.deepEqual(typeof argv.verbose, 'boolean'); + t.deepEqual(typeof argv.t, 'boolean'); + t.end(); + +}); + +test('boolean groups', function (t) { + var argv = parse(['-x', '-z', 'one', 'two', 'three'], { + boolean: ['x', 'y', 'z'], + }); + + t.deepEqual(argv, { + x: true, + y: false, + z: true, + _: ['one', 'two', 'three'], + }); + + t.deepEqual(typeof argv.x, 'boolean'); + t.deepEqual(typeof argv.y, 'boolean'); + t.deepEqual(typeof argv.z, 'boolean'); + t.end(); +}); +test('boolean and alias with chainable api', function (t) { + var aliased = ['-h', 'derp']; + var regular = ['--herp', 'derp']; + var aliasedArgv = parse(aliased, { + boolean: 'herp', + alias: { h: 'herp' }, + }); + var propertyArgv = parse(regular, { + boolean: 'herp', + alias: { h: 'herp' }, + }); + var expected = { + herp: true, + h: true, + _: ['derp'], + }; + + t.same(aliasedArgv, expected); + t.same(propertyArgv, expected); + t.end(); +}); + +test('boolean and alias with options hash', function (t) { + var aliased = ['-h', 'derp']; + var regular = ['--herp', 'derp']; + var opts = { + alias: { h: 'herp' }, + boolean: 'herp', + }; + var aliasedArgv = parse(aliased, opts); + var propertyArgv = parse(regular, opts); + var expected = { + herp: true, + h: true, + _: ['derp'], + }; + t.same(aliasedArgv, expected); + t.same(propertyArgv, expected); + t.end(); +}); + +test('boolean and alias array with options hash', function (t) { + var aliased = ['-h', 'derp']; + var regular = ['--herp', 'derp']; + var alt = ['--harp', 'derp']; + var opts = { + alias: { h: ['herp', 'harp'] }, + boolean: 'h', + }; + var aliasedArgv = parse(aliased, opts); + var propertyArgv = parse(regular, opts); + var altPropertyArgv = parse(alt, opts); + var expected = { + harp: true, + herp: true, + h: true, + _: ['derp'], + }; + t.same(aliasedArgv, expected); + t.same(propertyArgv, expected); + t.same(altPropertyArgv, expected); + t.end(); +}); + +test('boolean and alias using explicit true', function (t) { + var aliased = ['-h', 'true']; + var regular = ['--herp', 'true']; + var opts = { + alias: { h: 'herp' }, + boolean: 'h', + }; + var aliasedArgv = parse(aliased, opts); + var propertyArgv = parse(regular, opts); + var expected = { + herp: true, + h: true, + _: [], + }; + + t.same(aliasedArgv, expected); + t.same(propertyArgv, expected); + t.end(); +}); + +// regression, see https://github.com/substack/node-optimist/issues/71 +test('boolean and --x=true', function (t) { + var parsed = parse(['--boool', '--other=true'], { + boolean: 'boool', + }); + + t.same(parsed.boool, true); + t.same(parsed.other, 'true'); + + parsed = parse(['--boool', '--other=false'], { + boolean: 'boool', + }); + + t.same(parsed.boool, true); + t.same(parsed.other, 'false'); + t.end(); +}); + +test('boolean --boool=true', function (t) { + var parsed = parse(['--boool=true'], { + default: { + boool: false, + }, + boolean: ['boool'], + }); + + t.same(parsed.boool, true); + t.end(); +}); + +test('boolean --boool=false', function (t) { + var parsed = parse(['--boool=false'], { + default: { + boool: true, + }, + boolean: ['boool'], + }); + + t.same(parsed.boool, false); + t.end(); +}); + +test('boolean using something similar to true', function (t) { + var opts = { boolean: 'h' }; + var result = parse(['-h', 'true.txt'], opts); + var expected = { + h: true, + _: ['true.txt'], + }; + + t.same(result, expected); + t.end(); +}); diff --git a/node_modules/minimist/test/dash.js b/node_modules/minimist/test/dash.js new file mode 100644 index 0000000..7078817 --- /dev/null +++ b/node_modules/minimist/test/dash.js @@ -0,0 +1,43 @@ +'use strict'; + +var parse = require('../'); +var test = require('tape'); + +test('-', function (t) { + t.plan(6); + t.deepEqual(parse(['-n', '-']), { n: '-', _: [] }); + t.deepEqual(parse(['--nnn', '-']), { nnn: '-', _: [] }); + t.deepEqual(parse(['-']), { _: ['-'] }); + t.deepEqual(parse(['-f-']), { f: '-', _: [] }); + t.deepEqual( + parse(['-b', '-'], { boolean: 'b' }), + { b: true, _: ['-'] } + ); + t.deepEqual( + parse(['-s', '-'], { string: 's' }), + { s: '-', _: [] } + ); +}); + +test('-a -- b', function (t) { + t.plan(2); + t.deepEqual(parse(['-a', '--', 'b']), { a: true, _: ['b'] }); + t.deepEqual(parse(['--a', '--', 'b']), { a: true, _: ['b'] }); +}); + +test('move arguments after the -- into their own `--` array', function (t) { + t.plan(1); + t.deepEqual( + parse(['--name', 'John', 'before', '--', 'after'], { '--': true }), + { name: 'John', _: ['before'], '--': ['after'] } + ); +}); + +test('--- option value', function (t) { + // A multi-dash value is largely an edge case, but check the behaviour is as expected, + // and in particular the same for short option and long option (as made consistent in Jan 2023). + t.plan(2); + t.deepEqual(parse(['-n', '---']), { n: '---', _: [] }); + t.deepEqual(parse(['--nnn', '---']), { nnn: '---', _: [] }); +}); + diff --git a/node_modules/minimist/test/default_bool.js b/node_modules/minimist/test/default_bool.js new file mode 100644 index 0000000..4e9f625 --- /dev/null +++ b/node_modules/minimist/test/default_bool.js @@ -0,0 +1,37 @@ +'use strict'; + +var test = require('tape'); +var parse = require('../'); + +test('boolean default true', function (t) { + var argv = parse([], { + boolean: 'sometrue', + default: { sometrue: true }, + }); + t.equal(argv.sometrue, true); + t.end(); +}); + +test('boolean default false', function (t) { + var argv = parse([], { + boolean: 'somefalse', + default: { somefalse: false }, + }); + t.equal(argv.somefalse, false); + t.end(); +}); + +test('boolean default to null', function (t) { + var argv = parse([], { + boolean: 'maybe', + default: { maybe: null }, + }); + t.equal(argv.maybe, null); + + var argvLong = parse(['--maybe'], { + boolean: 'maybe', + default: { maybe: null }, + }); + t.equal(argvLong.maybe, true); + t.end(); +}); diff --git a/node_modules/minimist/test/dotted.js b/node_modules/minimist/test/dotted.js new file mode 100644 index 0000000..126ff03 --- /dev/null +++ b/node_modules/minimist/test/dotted.js @@ -0,0 +1,24 @@ +'use strict'; + +var parse = require('../'); +var test = require('tape'); + +test('dotted alias', function (t) { + var argv = parse(['--a.b', '22'], { default: { 'a.b': 11 }, alias: { 'a.b': 'aa.bb' } }); + t.equal(argv.a.b, 22); + t.equal(argv.aa.bb, 22); + t.end(); +}); + +test('dotted default', function (t) { + var argv = parse('', { default: { 'a.b': 11 }, alias: { 'a.b': 'aa.bb' } }); + t.equal(argv.a.b, 11); + t.equal(argv.aa.bb, 11); + t.end(); +}); + +test('dotted default with no alias', function (t) { + var argv = parse('', { default: { 'a.b': 11 } }); + t.equal(argv.a.b, 11); + t.end(); +}); diff --git a/node_modules/minimist/test/kv_short.js b/node_modules/minimist/test/kv_short.js new file mode 100644 index 0000000..6d1b53a --- /dev/null +++ b/node_modules/minimist/test/kv_short.js @@ -0,0 +1,32 @@ +'use strict'; + +var parse = require('../'); +var test = require('tape'); + +test('short -k=v', function (t) { + t.plan(1); + + var argv = parse(['-b=123']); + t.deepEqual(argv, { b: 123, _: [] }); +}); + +test('multi short -k=v', function (t) { + t.plan(1); + + var argv = parse(['-a=whatever', '-b=robots']); + t.deepEqual(argv, { a: 'whatever', b: 'robots', _: [] }); +}); + +test('short with embedded equals -k=a=b', function (t) { + t.plan(1); + + var argv = parse(['-k=a=b']); + t.deepEqual(argv, { k: 'a=b', _: [] }); +}); + +test('short with later equals like -ab=c', function (t) { + t.plan(1); + + var argv = parse(['-ab=c']); + t.deepEqual(argv, { a: true, b: 'c', _: [] }); +}); diff --git a/node_modules/minimist/test/long.js b/node_modules/minimist/test/long.js new file mode 100644 index 0000000..9fef51f --- /dev/null +++ b/node_modules/minimist/test/long.js @@ -0,0 +1,33 @@ +'use strict'; + +var test = require('tape'); +var parse = require('../'); + +test('long opts', function (t) { + t.deepEqual( + parse(['--bool']), + { bool: true, _: [] }, + 'long boolean' + ); + t.deepEqual( + parse(['--pow', 'xixxle']), + { pow: 'xixxle', _: [] }, + 'long capture sp' + ); + t.deepEqual( + parse(['--pow=xixxle']), + { pow: 'xixxle', _: [] }, + 'long capture eq' + ); + t.deepEqual( + parse(['--host', 'localhost', '--port', '555']), + { host: 'localhost', port: 555, _: [] }, + 'long captures sp' + ); + t.deepEqual( + parse(['--host=localhost', '--port=555']), + { host: 'localhost', port: 555, _: [] }, + 'long captures eq' + ); + t.end(); +}); diff --git a/node_modules/minimist/test/num.js b/node_modules/minimist/test/num.js new file mode 100644 index 0000000..074393e --- /dev/null +++ b/node_modules/minimist/test/num.js @@ -0,0 +1,38 @@ +'use strict'; + +var parse = require('../'); +var test = require('tape'); + +test('nums', function (t) { + var argv = parse([ + '-x', '1234', + '-y', '5.67', + '-z', '1e7', + '-w', '10f', + '--hex', '0xdeadbeef', + '789', + ]); + t.deepEqual(argv, { + x: 1234, + y: 5.67, + z: 1e7, + w: '10f', + hex: 0xdeadbeef, + _: [789], + }); + t.deepEqual(typeof argv.x, 'number'); + t.deepEqual(typeof argv.y, 'number'); + t.deepEqual(typeof argv.z, 'number'); + t.deepEqual(typeof argv.w, 'string'); + t.deepEqual(typeof argv.hex, 'number'); + t.deepEqual(typeof argv._[0], 'number'); + t.end(); +}); + +test('already a number', function (t) { + var argv = parse(['-x', 1234, 789]); + t.deepEqual(argv, { x: 1234, _: [789] }); + t.deepEqual(typeof argv.x, 'number'); + t.deepEqual(typeof argv._[0], 'number'); + t.end(); +}); diff --git a/node_modules/minimist/test/parse.js b/node_modules/minimist/test/parse.js new file mode 100644 index 0000000..65d9d90 --- /dev/null +++ b/node_modules/minimist/test/parse.js @@ -0,0 +1,209 @@ +'use strict'; + +var parse = require('../'); +var test = require('tape'); + +test('parse args', function (t) { + t.deepEqual( + parse(['--no-moo']), + { moo: false, _: [] }, + 'no' + ); + t.deepEqual( + parse(['-v', 'a', '-v', 'b', '-v', 'c']), + { v: ['a', 'b', 'c'], _: [] }, + 'multi' + ); + t.end(); +}); + +test('comprehensive', function (t) { + t.deepEqual( + parse([ + '--name=meowmers', 'bare', '-cats', 'woo', + '-h', 'awesome', '--multi=quux', + '--key', 'value', + '-b', '--bool', '--no-meep', '--multi=baz', + '--', '--not-a-flag', 'eek', + ]), + { + c: true, + a: true, + t: true, + s: 'woo', + h: 'awesome', + b: true, + bool: true, + key: 'value', + multi: ['quux', 'baz'], + meep: false, + name: 'meowmers', + _: ['bare', '--not-a-flag', 'eek'], + } + ); + t.end(); +}); + +test('flag boolean', function (t) { + var argv = parse(['-t', 'moo'], { boolean: 't' }); + t.deepEqual(argv, { t: true, _: ['moo'] }); + t.deepEqual(typeof argv.t, 'boolean'); + t.end(); +}); + +test('flag boolean value', function (t) { + var argv = parse(['--verbose', 'false', 'moo', '-t', 'true'], { + boolean: ['t', 'verbose'], + default: { verbose: true }, + }); + + t.deepEqual(argv, { + verbose: false, + t: true, + _: ['moo'], + }); + + t.deepEqual(typeof argv.verbose, 'boolean'); + t.deepEqual(typeof argv.t, 'boolean'); + t.end(); +}); + +test('newlines in params', function (t) { + var args = parse(['-s', 'X\nX']); + t.deepEqual(args, { _: [], s: 'X\nX' }); + + // reproduce in bash: + // VALUE="new + // line" + // node program.js --s="$VALUE" + args = parse(['--s=X\nX']); + t.deepEqual(args, { _: [], s: 'X\nX' }); + t.end(); +}); + +test('strings', function (t) { + var s = parse(['-s', '0001234'], { string: 's' }).s; + t.equal(s, '0001234'); + t.equal(typeof s, 'string'); + + var x = parse(['-x', '56'], { string: 'x' }).x; + t.equal(x, '56'); + t.equal(typeof x, 'string'); + t.end(); +}); + +test('stringArgs', function (t) { + var s = parse([' ', ' '], { string: '_' })._; + t.same(s.length, 2); + t.same(typeof s[0], 'string'); + t.same(s[0], ' '); + t.same(typeof s[1], 'string'); + t.same(s[1], ' '); + t.end(); +}); + +test('empty strings', function (t) { + var s = parse(['-s'], { string: 's' }).s; + t.equal(s, ''); + t.equal(typeof s, 'string'); + + var str = parse(['--str'], { string: 'str' }).str; + t.equal(str, ''); + t.equal(typeof str, 'string'); + + var letters = parse(['-art'], { + string: ['a', 't'], + }); + + t.equal(letters.a, ''); + t.equal(letters.r, true); + t.equal(letters.t, ''); + + t.end(); +}); + +test('string and alias', function (t) { + var x = parse(['--str', '000123'], { + string: 's', + alias: { s: 'str' }, + }); + + t.equal(x.str, '000123'); + t.equal(typeof x.str, 'string'); + t.equal(x.s, '000123'); + t.equal(typeof x.s, 'string'); + + var y = parse(['-s', '000123'], { + string: 'str', + alias: { str: 's' }, + }); + + t.equal(y.str, '000123'); + t.equal(typeof y.str, 'string'); + t.equal(y.s, '000123'); + t.equal(typeof y.s, 'string'); + + var z = parse(['-s123'], { + alias: { str: ['s', 'S'] }, + string: ['str'], + }); + + t.deepEqual( + z, + { _: [], s: '123', S: '123', str: '123' }, + 'opt.string works with multiple aliases' + ); + t.end(); +}); + +test('slashBreak', function (t) { + t.same( + parse(['-I/foo/bar/baz']), + { I: '/foo/bar/baz', _: [] } + ); + t.same( + parse(['-xyz/foo/bar/baz']), + { x: true, y: true, z: '/foo/bar/baz', _: [] } + ); + t.end(); +}); + +test('alias', function (t) { + var argv = parse(['-f', '11', '--zoom', '55'], { + alias: { z: 'zoom' }, + }); + t.equal(argv.zoom, 55); + t.equal(argv.z, argv.zoom); + t.equal(argv.f, 11); + t.end(); +}); + +test('multiAlias', function (t) { + var argv = parse(['-f', '11', '--zoom', '55'], { + alias: { z: ['zm', 'zoom'] }, + }); + t.equal(argv.zoom, 55); + t.equal(argv.z, argv.zoom); + t.equal(argv.z, argv.zm); + t.equal(argv.f, 11); + t.end(); +}); + +test('nested dotted objects', function (t) { + var argv = parse([ + '--foo.bar', '3', '--foo.baz', '4', + '--foo.quux.quibble', '5', '--foo.quux.o_O', + '--beep.boop', + ]); + + t.same(argv.foo, { + bar: 3, + baz: 4, + quux: { + quibble: 5, + o_O: true, + }, + }); + t.same(argv.beep, { boop: true }); + t.end(); +}); diff --git a/node_modules/minimist/test/parse_modified.js b/node_modules/minimist/test/parse_modified.js new file mode 100644 index 0000000..32965d1 --- /dev/null +++ b/node_modules/minimist/test/parse_modified.js @@ -0,0 +1,11 @@ +'use strict'; + +var parse = require('../'); +var test = require('tape'); + +test('parse with modifier functions', function (t) { + t.plan(1); + + var argv = parse(['-b', '123'], { boolean: 'b' }); + t.deepEqual(argv, { b: true, _: [123] }); +}); diff --git a/node_modules/minimist/test/proto.js b/node_modules/minimist/test/proto.js new file mode 100644 index 0000000..6e629dd --- /dev/null +++ b/node_modules/minimist/test/proto.js @@ -0,0 +1,64 @@ +'use strict'; + +/* eslint no-proto: 0 */ + +var parse = require('../'); +var test = require('tape'); + +test('proto pollution', function (t) { + var argv = parse(['--__proto__.x', '123']); + t.equal({}.x, undefined); + t.equal(argv.__proto__.x, undefined); + t.equal(argv.x, undefined); + t.end(); +}); + +test('proto pollution (array)', function (t) { + var argv = parse(['--x', '4', '--x', '5', '--x.__proto__.z', '789']); + t.equal({}.z, undefined); + t.deepEqual(argv.x, [4, 5]); + t.equal(argv.x.z, undefined); + t.equal(argv.x.__proto__.z, undefined); + t.end(); +}); + +test('proto pollution (number)', function (t) { + var argv = parse(['--x', '5', '--x.__proto__.z', '100']); + t.equal({}.z, undefined); + t.equal((4).z, undefined); + t.equal(argv.x, 5); + t.equal(argv.x.z, undefined); + t.end(); +}); + +test('proto pollution (string)', function (t) { + var argv = parse(['--x', 'abc', '--x.__proto__.z', 'def']); + t.equal({}.z, undefined); + t.equal('...'.z, undefined); + t.equal(argv.x, 'abc'); + t.equal(argv.x.z, undefined); + t.end(); +}); + +test('proto pollution (constructor)', function (t) { + var argv = parse(['--constructor.prototype.y', '123']); + t.equal({}.y, undefined); + t.equal(argv.y, undefined); + t.end(); +}); + +test('proto pollution (constructor function)', function (t) { + var argv = parse(['--_.concat.constructor.prototype.y', '123']); + function fnToBeTested() {} + t.equal(fnToBeTested.y, undefined); + t.equal(argv.y, undefined); + t.end(); +}); + +// powered by snyk - https://github.com/backstage/backstage/issues/10343 +test('proto pollution (constructor function) snyk', function (t) { + var argv = parse('--_.constructor.constructor.prototype.foo bar'.split(' ')); + t.equal(function () {}.foo, undefined); + t.equal(argv.y, undefined); + t.end(); +}); diff --git a/node_modules/minimist/test/short.js b/node_modules/minimist/test/short.js new file mode 100644 index 0000000..4a7b843 --- /dev/null +++ b/node_modules/minimist/test/short.js @@ -0,0 +1,69 @@ +'use strict'; + +var parse = require('../'); +var test = require('tape'); + +test('numeric short args', function (t) { + t.plan(2); + t.deepEqual(parse(['-n123']), { n: 123, _: [] }); + t.deepEqual( + parse(['-123', '456']), + { 1: true, 2: true, 3: 456, _: [] } + ); +}); + +test('short', function (t) { + t.deepEqual( + parse(['-b']), + { b: true, _: [] }, + 'short boolean' + ); + t.deepEqual( + parse(['foo', 'bar', 'baz']), + { _: ['foo', 'bar', 'baz'] }, + 'bare' + ); + t.deepEqual( + parse(['-cats']), + { c: true, a: true, t: true, s: true, _: [] }, + 'group' + ); + t.deepEqual( + parse(['-cats', 'meow']), + { c: true, a: true, t: true, s: 'meow', _: [] }, + 'short group next' + ); + t.deepEqual( + parse(['-h', 'localhost']), + { h: 'localhost', _: [] }, + 'short capture' + ); + t.deepEqual( + parse(['-h', 'localhost', '-p', '555']), + { h: 'localhost', p: 555, _: [] }, + 'short captures' + ); + t.end(); +}); + +test('mixed short bool and capture', function (t) { + t.same( + parse(['-h', 'localhost', '-fp', '555', 'script.js']), + { + f: true, p: 555, h: 'localhost', + _: ['script.js'], + } + ); + t.end(); +}); + +test('short and long', function (t) { + t.deepEqual( + parse(['-h', 'localhost', '-fp', '555', 'script.js']), + { + f: true, p: 555, h: 'localhost', + _: ['script.js'], + } + ); + t.end(); +}); diff --git a/node_modules/minimist/test/stop_early.js b/node_modules/minimist/test/stop_early.js new file mode 100644 index 0000000..52a6a91 --- /dev/null +++ b/node_modules/minimist/test/stop_early.js @@ -0,0 +1,17 @@ +'use strict'; + +var parse = require('../'); +var test = require('tape'); + +test('stops parsing on the first non-option when stopEarly is set', function (t) { + var argv = parse(['--aaa', 'bbb', 'ccc', '--ddd'], { + stopEarly: true, + }); + + t.deepEqual(argv, { + aaa: 'bbb', + _: ['ccc', '--ddd'], + }); + + t.end(); +}); diff --git a/node_modules/minimist/test/unknown.js b/node_modules/minimist/test/unknown.js new file mode 100644 index 0000000..4f2e0ca --- /dev/null +++ b/node_modules/minimist/test/unknown.js @@ -0,0 +1,104 @@ +'use strict'; + +var parse = require('../'); +var test = require('tape'); + +test('boolean and alias is not unknown', function (t) { + var unknown = []; + function unknownFn(arg) { + unknown.push(arg); + return false; + } + var aliased = ['-h', 'true', '--derp', 'true']; + var regular = ['--herp', 'true', '-d', 'true']; + var opts = { + alias: { h: 'herp' }, + boolean: 'h', + unknown: unknownFn, + }; + parse(aliased, opts); + parse(regular, opts); + + t.same(unknown, ['--derp', '-d']); + t.end(); +}); + +test('flag boolean true any double hyphen argument is not unknown', function (t) { + var unknown = []; + function unknownFn(arg) { + unknown.push(arg); + return false; + } + var argv = parse(['--honk', '--tacos=good', 'cow', '-p', '55'], { + boolean: true, + unknown: unknownFn, + }); + t.same(unknown, ['--tacos=good', 'cow', '-p']); + t.same(argv, { + honk: true, + _: [], + }); + t.end(); +}); + +test('string and alias is not unknown', function (t) { + var unknown = []; + function unknownFn(arg) { + unknown.push(arg); + return false; + } + var aliased = ['-h', 'hello', '--derp', 'goodbye']; + var regular = ['--herp', 'hello', '-d', 'moon']; + var opts = { + alias: { h: 'herp' }, + string: 'h', + unknown: unknownFn, + }; + parse(aliased, opts); + parse(regular, opts); + + t.same(unknown, ['--derp', '-d']); + t.end(); +}); + +test('default and alias is not unknown', function (t) { + var unknown = []; + function unknownFn(arg) { + unknown.push(arg); + return false; + } + var aliased = ['-h', 'hello']; + var regular = ['--herp', 'hello']; + var opts = { + default: { h: 'bar' }, + alias: { h: 'herp' }, + unknown: unknownFn, + }; + parse(aliased, opts); + parse(regular, opts); + + t.same(unknown, []); + t.end(); + unknownFn(); // exercise fn for 100% coverage +}); + +test('value following -- is not unknown', function (t) { + var unknown = []; + function unknownFn(arg) { + unknown.push(arg); + return false; + } + var aliased = ['--bad', '--', 'good', 'arg']; + var opts = { + '--': true, + unknown: unknownFn, + }; + var argv = parse(aliased, opts); + + t.same(unknown, ['--bad']); + t.same(argv, { + '--': ['good', 'arg'], + _: [], + }); + t.end(); +}); diff --git a/node_modules/minimist/test/whitespace.js b/node_modules/minimist/test/whitespace.js new file mode 100644 index 0000000..4fdaf1d --- /dev/null +++ b/node_modules/minimist/test/whitespace.js @@ -0,0 +1,10 @@ +'use strict'; + +var parse = require('../'); +var test = require('tape'); + +test('whitespace should be whitespace', function (t) { + t.plan(1); + var x = parse(['-x', '\t']).x; + t.equal(x, '\t'); +}); diff --git a/node_modules/mkdirp/LICENSE b/node_modules/mkdirp/LICENSE new file mode 100644 index 0000000..432d1ae --- /dev/null +++ b/node_modules/mkdirp/LICENSE @@ -0,0 +1,21 @@ +Copyright 2010 James Halliday (mail@substack.net) + +This project is free software released under the MIT/X11 license: + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/node_modules/mkdirp/bin/cmd.js b/node_modules/mkdirp/bin/cmd.js new file mode 100644 index 0000000..d95de15 --- /dev/null +++ b/node_modules/mkdirp/bin/cmd.js @@ -0,0 +1,33 @@ +#!/usr/bin/env node + +var mkdirp = require('../'); +var minimist = require('minimist'); +var fs = require('fs'); + +var argv = minimist(process.argv.slice(2), { + alias: { m: 'mode', h: 'help' }, + string: [ 'mode' ] +}); +if (argv.help) { + fs.createReadStream(__dirname + '/usage.txt').pipe(process.stdout); + return; +} + +var paths = argv._.slice(); +var mode = argv.mode ? parseInt(argv.mode, 8) : undefined; + +(function next () { + if (paths.length === 0) return; + var p = paths.shift(); + + if (mode === undefined) mkdirp(p, cb) + else mkdirp(p, mode, cb) + + function cb (err) { + if (err) { + console.error(err.message); + process.exit(1); + } + else next(); + } +})(); diff --git a/node_modules/mkdirp/bin/usage.txt b/node_modules/mkdirp/bin/usage.txt new file mode 100644 index 0000000..f952aa2 --- /dev/null +++ b/node_modules/mkdirp/bin/usage.txt @@ -0,0 +1,12 @@ +usage: mkdirp [DIR1,DIR2..] {OPTIONS} + + Create each supplied directory including any necessary parent directories that + don't yet exist. + + If the directory already exists, do nothing. + +OPTIONS are: + + -m, --mode If a directory needs to be created, set the mode as an octal + permission string. + diff --git a/node_modules/mkdirp/index.js b/node_modules/mkdirp/index.js new file mode 100644 index 0000000..0890ac3 --- /dev/null +++ b/node_modules/mkdirp/index.js @@ -0,0 +1,102 @@ +var path = require('path'); +var fs = require('fs'); +var _0777 = parseInt('0777', 8); + +module.exports = mkdirP.mkdirp = mkdirP.mkdirP = mkdirP; + +function mkdirP (p, opts, f, made) { + if (typeof opts === 'function') { + f = opts; + opts = {}; + } + else if (!opts || typeof opts !== 'object') { + opts = { mode: opts }; + } + + var mode = opts.mode; + var xfs = opts.fs || fs; + + if (mode === undefined) { + mode = _0777 + } + if (!made) made = null; + + var cb = f || /* istanbul ignore next */ function () {}; + p = path.resolve(p); + + xfs.mkdir(p, mode, function (er) { + if (!er) { + made = made || p; + return cb(null, made); + } + switch (er.code) { + case 'ENOENT': + /* istanbul ignore if */ + if (path.dirname(p) === p) return cb(er); + mkdirP(path.dirname(p), opts, function (er, made) { + /* istanbul ignore if */ + if (er) cb(er, made); + else mkdirP(p, opts, cb, made); + }); + break; + + // In the case of any other error, just see if there's a dir + // there already. If so, then hooray! If not, then something + // is borked. + default: + xfs.stat(p, function (er2, stat) { + // if the stat fails, then that's super weird. + // let the original error be the failure reason. + if (er2 || !stat.isDirectory()) cb(er, made) + else cb(null, made); + }); + break; + } + }); +} + +mkdirP.sync = function sync (p, opts, made) { + if (!opts || typeof opts !== 'object') { + opts = { mode: opts }; + } + + var mode = opts.mode; + var xfs = opts.fs || fs; + + if (mode === undefined) { + mode = _0777 + } + if (!made) made = null; + + p = path.resolve(p); + + try { + xfs.mkdirSync(p, mode); + made = made || p; + } + catch (err0) { + switch (err0.code) { + case 'ENOENT' : + made = sync(path.dirname(p), opts, made); + sync(p, opts, made); + break; + + // In the case of any other error, just see if there's a dir + // there already. If so, then hooray! If not, then something + // is borked. + default: + var stat; + try { + stat = xfs.statSync(p); + } + catch (err1) /* istanbul ignore next */ { + throw err0; + } + /* istanbul ignore if */ + if (!stat.isDirectory()) throw err0; + break; + } + } + + return made; +}; diff --git a/node_modules/mkdirp/package.json b/node_modules/mkdirp/package.json new file mode 100644 index 0000000..951e58d --- /dev/null +++ b/node_modules/mkdirp/package.json @@ -0,0 +1,33 @@ +{ + "name": "mkdirp", + "description": "Recursively mkdir, like `mkdir -p`", + "version": "0.5.6", + "publishConfig": { + "tag": "legacy" + }, + "author": "James Halliday (http://substack.net)", + "main": "index.js", + "keywords": [ + "mkdir", + "directory" + ], + "repository": { + "type": "git", + "url": "https://github.com/substack/node-mkdirp.git" + }, + "scripts": { + "test": "tap test/*.js" + }, + "dependencies": { + "minimist": "^1.2.6" + }, + "devDependencies": { + "tap": "^16.0.1" + }, + "bin": "bin/cmd.js", + "license": "MIT", + "files": [ + "bin", + "index.js" + ] +} diff --git a/node_modules/mkdirp/readme.markdown b/node_modules/mkdirp/readme.markdown new file mode 100644 index 0000000..fc314bf --- /dev/null +++ b/node_modules/mkdirp/readme.markdown @@ -0,0 +1,100 @@ +# mkdirp + +Like `mkdir -p`, but in node.js! + +[![build status](https://secure.travis-ci.org/substack/node-mkdirp.png)](http://travis-ci.org/substack/node-mkdirp) + +# example + +## pow.js + +```js +var mkdirp = require('mkdirp'); + +mkdirp('/tmp/foo/bar/baz', function (err) { + if (err) console.error(err) + else console.log('pow!') +}); +``` + +Output + +``` +pow! +``` + +And now /tmp/foo/bar/baz exists, huzzah! + +# methods + +```js +var mkdirp = require('mkdirp'); +``` + +## mkdirp(dir, opts, cb) + +Create a new directory and any necessary subdirectories at `dir` with octal +permission string `opts.mode`. If `opts` is a non-object, it will be treated as +the `opts.mode`. + +If `opts.mode` isn't specified, it defaults to `0777`. + +`cb(err, made)` fires with the error or the first directory `made` +that had to be created, if any. + +You can optionally pass in an alternate `fs` implementation by passing in +`opts.fs`. Your implementation should have `opts.fs.mkdir(path, mode, cb)` and +`opts.fs.stat(path, cb)`. + +## mkdirp.sync(dir, opts) + +Synchronously create a new directory and any necessary subdirectories at `dir` +with octal permission string `opts.mode`. If `opts` is a non-object, it will be +treated as the `opts.mode`. + +If `opts.mode` isn't specified, it defaults to `0777`. + +Returns the first directory that had to be created, if any. + +You can optionally pass in an alternate `fs` implementation by passing in +`opts.fs`. Your implementation should have `opts.fs.mkdirSync(path, mode)` and +`opts.fs.statSync(path)`. + +# usage + +This package also ships with a `mkdirp` command. + +``` +usage: mkdirp [DIR1,DIR2..] {OPTIONS} + + Create each supplied directory including any necessary parent directories that + don't yet exist. + + If the directory already exists, do nothing. + +OPTIONS are: + + -m, --mode If a directory needs to be created, set the mode as an octal + permission string. + +``` + +# install + +With [npm](http://npmjs.org) do: + +``` +npm install mkdirp +``` + +to get the library, or + +``` +npm install -g mkdirp +``` + +to get the command. + +# license + +MIT diff --git a/node_modules/multer/LICENSE b/node_modules/multer/LICENSE new file mode 100644 index 0000000..6c011b1 --- /dev/null +++ b/node_modules/multer/LICENSE @@ -0,0 +1,17 @@ +Copyright (c) 2014 Hage Yaapa <[http://www.hacksparrow.com](http://www.hacksparrow.com)> + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/node_modules/multer/README.md b/node_modules/multer/README.md new file mode 100644 index 0000000..7f5d080 --- /dev/null +++ b/node_modules/multer/README.md @@ -0,0 +1,333 @@ +# Multer [![Build Status](https://travis-ci.org/expressjs/multer.svg?branch=master)](https://travis-ci.org/expressjs/multer) [![NPM version](https://badge.fury.io/js/multer.svg)](https://badge.fury.io/js/multer) [![js-standard-style](https://img.shields.io/badge/code%20style-standard-brightgreen.svg?style=flat)](https://github.com/feross/standard) + +Multer is a node.js middleware for handling `multipart/form-data`, which is primarily used for uploading files. It is written +on top of [busboy](https://github.com/mscdex/busboy) for maximum efficiency. + +**NOTE**: Multer will not process any form which is not multipart (`multipart/form-data`). + +## Translations + +This README is also available in other languages: + +- [Español](https://github.com/expressjs/multer/blob/master/doc/README-es.md) (Spanish) +- [简体中文](https://github.com/expressjs/multer/blob/master/doc/README-zh-cn.md) (Chinese) +- [한국어](https://github.com/expressjs/multer/blob/master/doc/README-ko.md) (Korean) +- [Русский язык](https://github.com/expressjs/multer/blob/master/doc/README-ru.md) (Russian) +- [Việt Nam](https://github.com/expressjs/multer/blob/master/doc/README-vi.md) (Vietnam) +- [Português](https://github.com/expressjs/multer/blob/master/doc/README-pt-br.md) (Portuguese Brazil) + +## Installation + +```sh +$ npm install --save multer +``` + +## Usage + +Multer adds a `body` object and a `file` or `files` object to the `request` object. The `body` object contains the values of the text fields of the form, the `file` or `files` object contains the files uploaded via the form. + +Basic usage example: + +Don't forget the `enctype="multipart/form-data"` in your form. + +```html +
+ +
+``` + +```javascript +const express = require('express') +const multer = require('multer') +const upload = multer({ dest: 'uploads/' }) + +const app = express() + +app.post('/profile', upload.single('avatar'), function (req, res, next) { + // req.file is the `avatar` file + // req.body will hold the text fields, if there were any +}) + +app.post('/photos/upload', upload.array('photos', 12), function (req, res, next) { + // req.files is array of `photos` files + // req.body will contain the text fields, if there were any +}) + +const cpUpload = upload.fields([{ name: 'avatar', maxCount: 1 }, { name: 'gallery', maxCount: 8 }]) +app.post('/cool-profile', cpUpload, function (req, res, next) { + // req.files is an object (String -> Array) where fieldname is the key, and the value is array of files + // + // e.g. + // req.files['avatar'][0] -> File + // req.files['gallery'] -> Array + // + // req.body will contain the text fields, if there were any +}) +``` + +In case you need to handle a text-only multipart form, you should use the `.none()` method: + +```javascript +const express = require('express') +const app = express() +const multer = require('multer') +const upload = multer() + +app.post('/profile', upload.none(), function (req, res, next) { + // req.body contains the text fields +}) +``` + +Here's an example on how multer is used an HTML form. Take special note of the `enctype="multipart/form-data"` and `name="uploaded_file"` fields: + +```html +
+
+ + + +
+
+``` + +Then in your javascript file you would add these lines to access both the file and the body. It is important that you use the `name` field value from the form in your upload function. This tells multer which field on the request it should look for the files in. If these fields aren't the same in the HTML form and on your server, your upload will fail: + +```javascript +const multer = require('multer') +const upload = multer({ dest: './public/data/uploads/' }) +app.post('/stats', upload.single('uploaded_file'), function (req, res) { + // req.file is the name of your file in the form above, here 'uploaded_file' + // req.body will hold the text fields, if there were any + console.log(req.file, req.body) +}); +``` + + + +## API + +### File information + +Each file contains the following information: + +Key | Description | Note +--- | --- | --- +`fieldname` | Field name specified in the form | +`originalname` | Name of the file on the user's computer | +`encoding` | Encoding type of the file | +`mimetype` | Mime type of the file | +`size` | Size of the file in bytes | +`destination` | The folder to which the file has been saved | `DiskStorage` +`filename` | The name of the file within the `destination` | `DiskStorage` +`path` | The full path to the uploaded file | `DiskStorage` +`buffer` | A `Buffer` of the entire file | `MemoryStorage` + +### `multer(opts)` + +Multer accepts an options object, the most basic of which is the `dest` +property, which tells Multer where to upload the files. In case you omit the +options object, the files will be kept in memory and never written to disk. + +By default, Multer will rename the files so as to avoid naming conflicts. The +renaming function can be customized according to your needs. + +The following are the options that can be passed to Multer. + +Key | Description +--- | --- +`dest` or `storage` | Where to store the files +`fileFilter` | Function to control which files are accepted +`limits` | Limits of the uploaded data +`preservePath` | Keep the full path of files instead of just the base name + +In an average web app, only `dest` might be required, and configured as shown in +the following example. + +```javascript +const upload = multer({ dest: 'uploads/' }) +``` + +If you want more control over your uploads, you'll want to use the `storage` +option instead of `dest`. Multer ships with storage engines `DiskStorage` +and `MemoryStorage`; More engines are available from third parties. + +#### `.single(fieldname)` + +Accept a single file with the name `fieldname`. The single file will be stored +in `req.file`. + +#### `.array(fieldname[, maxCount])` + +Accept an array of files, all with the name `fieldname`. Optionally error out if +more than `maxCount` files are uploaded. The array of files will be stored in +`req.files`. + +#### `.fields(fields)` + +Accept a mix of files, specified by `fields`. An object with arrays of files +will be stored in `req.files`. + +`fields` should be an array of objects with `name` and optionally a `maxCount`. +Example: + +```javascript +[ + { name: 'avatar', maxCount: 1 }, + { name: 'gallery', maxCount: 8 } +] +``` + +#### `.none()` + +Accept only text fields. If any file upload is made, error with code +"LIMIT\_UNEXPECTED\_FILE" will be issued. + +#### `.any()` + +Accepts all files that comes over the wire. An array of files will be stored in +`req.files`. + +**WARNING:** Make sure that you always handle the files that a user uploads. +Never add multer as a global middleware since a malicious user could upload +files to a route that you didn't anticipate. Only use this function on routes +where you are handling the uploaded files. + +### `storage` + +#### `DiskStorage` + +The disk storage engine gives you full control on storing files to disk. + +```javascript +const storage = multer.diskStorage({ + destination: function (req, file, cb) { + cb(null, '/tmp/my-uploads') + }, + filename: function (req, file, cb) { + const uniqueSuffix = Date.now() + '-' + Math.round(Math.random() * 1E9) + cb(null, file.fieldname + '-' + uniqueSuffix) + } +}) + +const upload = multer({ storage: storage }) +``` + +There are two options available, `destination` and `filename`. They are both +functions that determine where the file should be stored. + +`destination` is used to determine within which folder the uploaded files should +be stored. This can also be given as a `string` (e.g. `'/tmp/uploads'`). If no +`destination` is given, the operating system's default directory for temporary +files is used. + +**Note:** You are responsible for creating the directory when providing +`destination` as a function. When passing a string, multer will make sure that +the directory is created for you. + +`filename` is used to determine what the file should be named inside the folder. +If no `filename` is given, each file will be given a random name that doesn't +include any file extension. + +**Note:** Multer will not append any file extension for you, your function +should return a filename complete with an file extension. + +Each function gets passed both the request (`req`) and some information about +the file (`file`) to aid with the decision. + +Note that `req.body` might not have been fully populated yet. It depends on the +order that the client transmits fields and files to the server. + +For understanding the calling convention used in the callback (needing to pass +null as the first param), refer to +[Node.js error handling](https://www.joyent.com/node-js/production/design/errors) + +#### `MemoryStorage` + +The memory storage engine stores the files in memory as `Buffer` objects. It +doesn't have any options. + +```javascript +const storage = multer.memoryStorage() +const upload = multer({ storage: storage }) +``` + +When using memory storage, the file info will contain a field called +`buffer` that contains the entire file. + +**WARNING**: Uploading very large files, or relatively small files in large +numbers very quickly, can cause your application to run out of memory when +memory storage is used. + +### `limits` + +An object specifying the size limits of the following optional properties. Multer passes this object into busboy directly, and the details of the properties can be found on [busboy's page](https://github.com/mscdex/busboy#busboy-methods). + +The following integer values are available: + +Key | Description | Default +--- | --- | --- +`fieldNameSize` | Max field name size | 100 bytes +`fieldSize` | Max field value size (in bytes) | 1MB +`fields` | Max number of non-file fields | Infinity +`fileSize` | For multipart forms, the max file size (in bytes) | Infinity +`files` | For multipart forms, the max number of file fields | Infinity +`parts` | For multipart forms, the max number of parts (fields + files) | Infinity +`headerPairs` | For multipart forms, the max number of header key=>value pairs to parse | 2000 + +Specifying the limits can help protect your site against denial of service (DoS) attacks. + +### `fileFilter` + +Set this to a function to control which files should be uploaded and which +should be skipped. The function should look like this: + +```javascript +function fileFilter (req, file, cb) { + + // The function should call `cb` with a boolean + // to indicate if the file should be accepted + + // To reject this file pass `false`, like so: + cb(null, false) + + // To accept the file pass `true`, like so: + cb(null, true) + + // You can always pass an error if something goes wrong: + cb(new Error('I don\'t have a clue!')) + +} +``` + +## Error handling + +When encountering an error, Multer will delegate the error to Express. You can +display a nice error page using [the standard express way](http://expressjs.com/guide/error-handling.html). + +If you want to catch errors specifically from Multer, you can call the +middleware function by yourself. Also, if you want to catch only [the Multer errors](https://github.com/expressjs/multer/blob/master/lib/multer-error.js), you can use the `MulterError` class that is attached to the `multer` object itself (e.g. `err instanceof multer.MulterError`). + +```javascript +const multer = require('multer') +const upload = multer().single('avatar') + +app.post('/profile', function (req, res) { + upload(req, res, function (err) { + if (err instanceof multer.MulterError) { + // A Multer error occurred when uploading. + } else if (err) { + // An unknown error occurred when uploading. + } + + // Everything went fine. + }) +}) +``` + +## Custom storage engine + +For information on how to build your own storage engine, see [Multer Storage Engine](https://github.com/expressjs/multer/blob/master/StorageEngine.md). + +## License + +[MIT](LICENSE) diff --git a/node_modules/multer/index.js b/node_modules/multer/index.js new file mode 100644 index 0000000..d5b67eb --- /dev/null +++ b/node_modules/multer/index.js @@ -0,0 +1,104 @@ +var makeMiddleware = require('./lib/make-middleware') + +var diskStorage = require('./storage/disk') +var memoryStorage = require('./storage/memory') +var MulterError = require('./lib/multer-error') + +function allowAll (req, file, cb) { + cb(null, true) +} + +function Multer (options) { + if (options.storage) { + this.storage = options.storage + } else if (options.dest) { + this.storage = diskStorage({ destination: options.dest }) + } else { + this.storage = memoryStorage() + } + + this.limits = options.limits + this.preservePath = options.preservePath + this.fileFilter = options.fileFilter || allowAll +} + +Multer.prototype._makeMiddleware = function (fields, fileStrategy) { + function setup () { + var fileFilter = this.fileFilter + var filesLeft = Object.create(null) + + fields.forEach(function (field) { + if (typeof field.maxCount === 'number') { + filesLeft[field.name] = field.maxCount + } else { + filesLeft[field.name] = Infinity + } + }) + + function wrappedFileFilter (req, file, cb) { + if ((filesLeft[file.fieldname] || 0) <= 0) { + return cb(new MulterError('LIMIT_UNEXPECTED_FILE', file.fieldname)) + } + + filesLeft[file.fieldname] -= 1 + fileFilter(req, file, cb) + } + + return { + limits: this.limits, + preservePath: this.preservePath, + storage: this.storage, + fileFilter: wrappedFileFilter, + fileStrategy: fileStrategy + } + } + + return makeMiddleware(setup.bind(this)) +} + +Multer.prototype.single = function (name) { + return this._makeMiddleware([{ name: name, maxCount: 1 }], 'VALUE') +} + +Multer.prototype.array = function (name, maxCount) { + return this._makeMiddleware([{ name: name, maxCount: maxCount }], 'ARRAY') +} + +Multer.prototype.fields = function (fields) { + return this._makeMiddleware(fields, 'OBJECT') +} + +Multer.prototype.none = function () { + return this._makeMiddleware([], 'NONE') +} + +Multer.prototype.any = function () { + function setup () { + return { + limits: this.limits, + preservePath: this.preservePath, + storage: this.storage, + fileFilter: this.fileFilter, + fileStrategy: 'ARRAY' + } + } + + return makeMiddleware(setup.bind(this)) +} + +function multer (options) { + if (options === undefined) { + return new Multer({}) + } + + if (typeof options === 'object' && options !== null) { + return new Multer(options) + } + + throw new TypeError('Expected object for argument options') +} + +module.exports = multer +module.exports.diskStorage = diskStorage +module.exports.memoryStorage = memoryStorage +module.exports.MulterError = MulterError diff --git a/node_modules/multer/lib/counter.js b/node_modules/multer/lib/counter.js new file mode 100644 index 0000000..29c410c --- /dev/null +++ b/node_modules/multer/lib/counter.js @@ -0,0 +1,28 @@ +var EventEmitter = require('events').EventEmitter + +function Counter () { + EventEmitter.call(this) + this.value = 0 +} + +Counter.prototype = Object.create(EventEmitter.prototype) + +Counter.prototype.increment = function increment () { + this.value++ +} + +Counter.prototype.decrement = function decrement () { + if (--this.value === 0) this.emit('zero') +} + +Counter.prototype.isZero = function isZero () { + return (this.value === 0) +} + +Counter.prototype.onceZero = function onceZero (fn) { + if (this.isZero()) return fn() + + this.once('zero', fn) +} + +module.exports = Counter diff --git a/node_modules/multer/lib/file-appender.js b/node_modules/multer/lib/file-appender.js new file mode 100644 index 0000000..1a2c5e7 --- /dev/null +++ b/node_modules/multer/lib/file-appender.js @@ -0,0 +1,67 @@ +var objectAssign = require('object-assign') + +function arrayRemove (arr, item) { + var idx = arr.indexOf(item) + if (~idx) arr.splice(idx, 1) +} + +function FileAppender (strategy, req) { + this.strategy = strategy + this.req = req + + switch (strategy) { + case 'NONE': break + case 'VALUE': break + case 'ARRAY': req.files = []; break + case 'OBJECT': req.files = Object.create(null); break + default: throw new Error('Unknown file strategy: ' + strategy) + } +} + +FileAppender.prototype.insertPlaceholder = function (file) { + var placeholder = { + fieldname: file.fieldname + } + + switch (this.strategy) { + case 'NONE': break + case 'VALUE': break + case 'ARRAY': this.req.files.push(placeholder); break + case 'OBJECT': + if (this.req.files[file.fieldname]) { + this.req.files[file.fieldname].push(placeholder) + } else { + this.req.files[file.fieldname] = [placeholder] + } + break + } + + return placeholder +} + +FileAppender.prototype.removePlaceholder = function (placeholder) { + switch (this.strategy) { + case 'NONE': break + case 'VALUE': break + case 'ARRAY': arrayRemove(this.req.files, placeholder); break + case 'OBJECT': + if (this.req.files[placeholder.fieldname].length === 1) { + delete this.req.files[placeholder.fieldname] + } else { + arrayRemove(this.req.files[placeholder.fieldname], placeholder) + } + break + } +} + +FileAppender.prototype.replacePlaceholder = function (placeholder, file) { + if (this.strategy === 'VALUE') { + this.req.file = file + return + } + + delete placeholder.fieldname + objectAssign(placeholder, file) +} + +module.exports = FileAppender diff --git a/node_modules/multer/lib/make-middleware.js b/node_modules/multer/lib/make-middleware.js new file mode 100644 index 0000000..cc26414 --- /dev/null +++ b/node_modules/multer/lib/make-middleware.js @@ -0,0 +1,175 @@ +var is = require('type-is') +var Busboy = require('busboy') +var extend = require('xtend') +var appendField = require('append-field') + +var Counter = require('./counter') +var MulterError = require('./multer-error') +var FileAppender = require('./file-appender') +var removeUploadedFiles = require('./remove-uploaded-files') + +function makeMiddleware (setup) { + return function multerMiddleware (req, res, next) { + if (!is(req, ['multipart'])) return next() + + var options = setup() + + var limits = options.limits + var storage = options.storage + var fileFilter = options.fileFilter + var fileStrategy = options.fileStrategy + var preservePath = options.preservePath + + req.body = Object.create(null) + + var busboy + + try { + busboy = Busboy({ headers: req.headers, limits: limits, preservePath: preservePath }) + } catch (err) { + return next(err) + } + + var appender = new FileAppender(fileStrategy, req) + var isDone = false + var readFinished = false + var errorOccured = false + var pendingWrites = new Counter() + var uploadedFiles = [] + + function done (err) { + if (isDone) return + isDone = true + req.unpipe(busboy) + process.nextTick(() => { + busboy.removeAllListeners() + }) + next(err) + } + + function indicateDone () { + if (readFinished && pendingWrites.isZero() && !errorOccured) done() + } + + function abortWithError (uploadError) { + if (errorOccured) return + errorOccured = true + + pendingWrites.onceZero(function () { + function remove (file, cb) { + storage._removeFile(req, file, cb) + } + + removeUploadedFiles(uploadedFiles, remove, function (err, storageErrors) { + if (err) return done(err) + + uploadError.storageErrors = storageErrors + done(uploadError) + }) + }) + } + + function abortWithCode (code, optionalField) { + abortWithError(new MulterError(code, optionalField)) + } + + // handle text field data + busboy.on('field', function (fieldname, value, { nameTruncated, valueTruncated }) { + if (fieldname == null) return abortWithCode('MISSING_FIELD_NAME') + if (nameTruncated) return abortWithCode('LIMIT_FIELD_KEY') + if (valueTruncated) return abortWithCode('LIMIT_FIELD_VALUE', fieldname) + + // Work around bug in Busboy (https://github.com/mscdex/busboy/issues/6) + if (limits && Object.prototype.hasOwnProperty.call(limits, 'fieldNameSize')) { + if (fieldname.length > limits.fieldNameSize) return abortWithCode('LIMIT_FIELD_KEY') + } + + appendField(req.body, fieldname, value) + }) + + // handle files + busboy.on('file', function (fieldname, fileStream, { filename, encoding, mimeType }) { + // don't attach to the files object, if there is no file + if (!filename) return fileStream.resume() + + // Work around bug in Busboy (https://github.com/mscdex/busboy/issues/6) + if (limits && Object.prototype.hasOwnProperty.call(limits, 'fieldNameSize')) { + if (fieldname.length > limits.fieldNameSize) return abortWithCode('LIMIT_FIELD_KEY') + } + + var file = { + fieldname: fieldname, + originalname: filename, + encoding: encoding, + mimetype: mimeType + } + + var placeholder = appender.insertPlaceholder(file) + + fileFilter(req, file, function (err, includeFile) { + if (err) { + appender.removePlaceholder(placeholder) + return abortWithError(err) + } + + if (!includeFile) { + appender.removePlaceholder(placeholder) + return fileStream.resume() + } + + var aborting = false + pendingWrites.increment() + + Object.defineProperty(file, 'stream', { + configurable: true, + enumerable: false, + value: fileStream + }) + + fileStream.on('error', function (err) { + pendingWrites.decrement() + abortWithError(err) + }) + + fileStream.on('limit', function () { + aborting = true + abortWithCode('LIMIT_FILE_SIZE', fieldname) + }) + + storage._handleFile(req, file, function (err, info) { + if (aborting) { + appender.removePlaceholder(placeholder) + uploadedFiles.push(extend(file, info)) + return pendingWrites.decrement() + } + + if (err) { + appender.removePlaceholder(placeholder) + pendingWrites.decrement() + return abortWithError(err) + } + + var fileInfo = extend(file, info) + + appender.replacePlaceholder(placeholder, fileInfo) + uploadedFiles.push(fileInfo) + pendingWrites.decrement() + indicateDone() + }) + }) + }) + + busboy.on('error', function (err) { abortWithError(err) }) + busboy.on('partsLimit', function () { abortWithCode('LIMIT_PART_COUNT') }) + busboy.on('filesLimit', function () { abortWithCode('LIMIT_FILE_COUNT') }) + busboy.on('fieldsLimit', function () { abortWithCode('LIMIT_FIELD_COUNT') }) + busboy.on('close', function () { + readFinished = true + indicateDone() + }) + + req.pipe(busboy) + } +} + +module.exports = makeMiddleware diff --git a/node_modules/multer/lib/multer-error.js b/node_modules/multer/lib/multer-error.js new file mode 100644 index 0000000..d56b00e --- /dev/null +++ b/node_modules/multer/lib/multer-error.js @@ -0,0 +1,24 @@ +var util = require('util') + +var errorMessages = { + LIMIT_PART_COUNT: 'Too many parts', + LIMIT_FILE_SIZE: 'File too large', + LIMIT_FILE_COUNT: 'Too many files', + LIMIT_FIELD_KEY: 'Field name too long', + LIMIT_FIELD_VALUE: 'Field value too long', + LIMIT_FIELD_COUNT: 'Too many fields', + LIMIT_UNEXPECTED_FILE: 'Unexpected field', + MISSING_FIELD_NAME: 'Field name missing' +} + +function MulterError (code, field) { + Error.captureStackTrace(this, this.constructor) + this.name = this.constructor.name + this.message = errorMessages[code] + this.code = code + if (field) this.field = field +} + +util.inherits(MulterError, Error) + +module.exports = MulterError diff --git a/node_modules/multer/lib/remove-uploaded-files.js b/node_modules/multer/lib/remove-uploaded-files.js new file mode 100644 index 0000000..f0b16ea --- /dev/null +++ b/node_modules/multer/lib/remove-uploaded-files.js @@ -0,0 +1,28 @@ +function removeUploadedFiles (uploadedFiles, remove, cb) { + var length = uploadedFiles.length + var errors = [] + + if (length === 0) return cb(null, errors) + + function handleFile (idx) { + var file = uploadedFiles[idx] + + remove(file, function (err) { + if (err) { + err.file = file + err.field = file.fieldname + errors.push(err) + } + + if (idx < length - 1) { + handleFile(idx + 1) + } else { + cb(null, errors) + } + }) + } + + handleFile(0) +} + +module.exports = removeUploadedFiles diff --git a/node_modules/multer/package.json b/node_modules/multer/package.json new file mode 100644 index 0000000..32507d8 --- /dev/null +++ b/node_modules/multer/package.json @@ -0,0 +1,52 @@ +{ + "name": "multer", + "description": "Middleware for handling `multipart/form-data`.", + "version": "1.4.5-lts.2", + "contributors": [ + "Hage Yaapa (http://www.hacksparrow.com)", + "Jaret Pfluger ", + "Linus Unnebäck " + ], + "license": "MIT", + "repository": "expressjs/multer", + "keywords": [ + "form", + "post", + "multipart", + "form-data", + "formdata", + "express", + "middleware" + ], + "dependencies": { + "append-field": "^1.0.0", + "busboy": "^1.0.0", + "concat-stream": "^1.5.2", + "mkdirp": "^0.5.4", + "object-assign": "^4.1.1", + "type-is": "^1.6.4", + "xtend": "^4.0.0" + }, + "devDependencies": { + "deep-equal": "^2.0.3", + "express": "^4.13.1", + "form-data": "^1.0.0-rc1", + "fs-temp": "^1.1.2", + "mocha": "^3.5.3", + "rimraf": "^2.4.1", + "standard": "^14.3.3", + "testdata-w3c-json-form": "^1.0.0" + }, + "engines": { + "node": ">= 6.0.0" + }, + "files": [ + "LICENSE", + "index.js", + "storage/", + "lib/" + ], + "scripts": { + "test": "standard && mocha" + } +} diff --git a/node_modules/multer/storage/disk.js b/node_modules/multer/storage/disk.js new file mode 100644 index 0000000..2f77c9f --- /dev/null +++ b/node_modules/multer/storage/disk.js @@ -0,0 +1,66 @@ +var fs = require('fs') +var os = require('os') +var path = require('path') +var crypto = require('crypto') +var mkdirp = require('mkdirp') + +function getFilename (req, file, cb) { + crypto.randomBytes(16, function (err, raw) { + cb(err, err ? undefined : raw.toString('hex')) + }) +} + +function getDestination (req, file, cb) { + cb(null, os.tmpdir()) +} + +function DiskStorage (opts) { + this.getFilename = (opts.filename || getFilename) + + if (typeof opts.destination === 'string') { + mkdirp.sync(opts.destination) + this.getDestination = function ($0, $1, cb) { cb(null, opts.destination) } + } else { + this.getDestination = (opts.destination || getDestination) + } +} + +DiskStorage.prototype._handleFile = function _handleFile (req, file, cb) { + var that = this + + that.getDestination(req, file, function (err, destination) { + if (err) return cb(err) + + that.getFilename(req, file, function (err, filename) { + if (err) return cb(err) + + var finalPath = path.join(destination, filename) + var outStream = fs.createWriteStream(finalPath) + + file.stream.pipe(outStream) + outStream.on('error', cb) + outStream.on('finish', function () { + cb(null, { + destination: destination, + filename: filename, + path: finalPath, + size: outStream.bytesWritten + }) + }) + }) + }) +} + +DiskStorage.prototype._removeFile = function _removeFile (req, file, cb) { + var path = file.path + + delete file.destination + delete file.filename + delete file.path + + fs.unlink(path, cb) +} + +module.exports = function (opts) { + return new DiskStorage(opts) +} diff --git a/node_modules/multer/storage/memory.js b/node_modules/multer/storage/memory.js new file mode 100644 index 0000000..f953ded --- /dev/null +++ b/node_modules/multer/storage/memory.js @@ -0,0 +1,21 @@ +var concat = require('concat-stream') + +function MemoryStorage (opts) {} + +MemoryStorage.prototype._handleFile = function _handleFile (req, file, cb) { + file.stream.pipe(concat({ encoding: 'buffer' }, function (data) { + cb(null, { + buffer: data, + size: data.length + }) + })) +} + +MemoryStorage.prototype._removeFile = function _removeFile (req, file, cb) { + delete file.buffer + cb(null) +} + +module.exports = function (opts) { + return new MemoryStorage(opts) +} diff --git a/node_modules/object-assign/index.js b/node_modules/object-assign/index.js new file mode 100644 index 0000000..0930cf8 --- /dev/null +++ b/node_modules/object-assign/index.js @@ -0,0 +1,90 @@ +/* +object-assign +(c) Sindre Sorhus +@license MIT +*/ + +'use strict'; +/* eslint-disable no-unused-vars */ +var getOwnPropertySymbols = Object.getOwnPropertySymbols; +var hasOwnProperty = Object.prototype.hasOwnProperty; +var propIsEnumerable = Object.prototype.propertyIsEnumerable; + +function toObject(val) { + if (val === null || val === undefined) { + throw new TypeError('Object.assign cannot be called with null or undefined'); + } + + return Object(val); +} + +function shouldUseNative() { + try { + if (!Object.assign) { + return false; + } + + // Detect buggy property enumeration order in older V8 versions. + + // https://bugs.chromium.org/p/v8/issues/detail?id=4118 + var test1 = new String('abc'); // eslint-disable-line no-new-wrappers + test1[5] = 'de'; + if (Object.getOwnPropertyNames(test1)[0] === '5') { + return false; + } + + // https://bugs.chromium.org/p/v8/issues/detail?id=3056 + var test2 = {}; + for (var i = 0; i < 10; i++) { + test2['_' + String.fromCharCode(i)] = i; + } + var order2 = Object.getOwnPropertyNames(test2).map(function (n) { + return test2[n]; + }); + if (order2.join('') !== '0123456789') { + return false; + } + + // https://bugs.chromium.org/p/v8/issues/detail?id=3056 + var test3 = {}; + 'abcdefghijklmnopqrst'.split('').forEach(function (letter) { + test3[letter] = letter; + }); + if (Object.keys(Object.assign({}, test3)).join('') !== + 'abcdefghijklmnopqrst') { + return false; + } + + return true; + } catch (err) { + // We don't expect any of the above to throw, but better to be safe. + return false; + } +} + +module.exports = shouldUseNative() ? Object.assign : function (target, source) { + var from; + var to = toObject(target); + var symbols; + + for (var s = 1; s < arguments.length; s++) { + from = Object(arguments[s]); + + for (var key in from) { + if (hasOwnProperty.call(from, key)) { + to[key] = from[key]; + } + } + + if (getOwnPropertySymbols) { + symbols = getOwnPropertySymbols(from); + for (var i = 0; i < symbols.length; i++) { + if (propIsEnumerable.call(from, symbols[i])) { + to[symbols[i]] = from[symbols[i]]; + } + } + } + } + + return to; +}; diff --git a/node_modules/object-assign/license b/node_modules/object-assign/license new file mode 100644 index 0000000..654d0bf --- /dev/null +++ b/node_modules/object-assign/license @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) Sindre Sorhus (sindresorhus.com) + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/node_modules/object-assign/package.json b/node_modules/object-assign/package.json new file mode 100644 index 0000000..503eb1e --- /dev/null +++ b/node_modules/object-assign/package.json @@ -0,0 +1,42 @@ +{ + "name": "object-assign", + "version": "4.1.1", + "description": "ES2015 `Object.assign()` ponyfill", + "license": "MIT", + "repository": "sindresorhus/object-assign", + "author": { + "name": "Sindre Sorhus", + "email": "sindresorhus@gmail.com", + "url": "sindresorhus.com" + }, + "engines": { + "node": ">=0.10.0" + }, + "scripts": { + "test": "xo && ava", + "bench": "matcha bench.js" + }, + "files": [ + "index.js" + ], + "keywords": [ + "object", + "assign", + "extend", + "properties", + "es2015", + "ecmascript", + "harmony", + "ponyfill", + "prollyfill", + "polyfill", + "shim", + "browser" + ], + "devDependencies": { + "ava": "^0.16.0", + "lodash": "^4.16.4", + "matcha": "^0.7.0", + "xo": "^0.16.0" + } +} diff --git a/node_modules/object-assign/readme.md b/node_modules/object-assign/readme.md new file mode 100644 index 0000000..1be09d3 --- /dev/null +++ b/node_modules/object-assign/readme.md @@ -0,0 +1,61 @@ +# object-assign [![Build Status](https://travis-ci.org/sindresorhus/object-assign.svg?branch=master)](https://travis-ci.org/sindresorhus/object-assign) + +> ES2015 [`Object.assign()`](http://www.2ality.com/2014/01/object-assign.html) [ponyfill](https://ponyfill.com) + + +## Use the built-in + +Node.js 4 and up, as well as every evergreen browser (Chrome, Edge, Firefox, Opera, Safari), +support `Object.assign()` :tada:. If you target only those environments, then by all +means, use `Object.assign()` instead of this package. + + +## Install + +``` +$ npm install --save object-assign +``` + + +## Usage + +```js +const objectAssign = require('object-assign'); + +objectAssign({foo: 0}, {bar: 1}); +//=> {foo: 0, bar: 1} + +// multiple sources +objectAssign({foo: 0}, {bar: 1}, {baz: 2}); +//=> {foo: 0, bar: 1, baz: 2} + +// overwrites equal keys +objectAssign({foo: 0}, {foo: 1}, {foo: 2}); +//=> {foo: 2} + +// ignores null and undefined sources +objectAssign({foo: 0}, null, {bar: 1}, undefined); +//=> {foo: 0, bar: 1} +``` + + +## API + +### objectAssign(target, [source, ...]) + +Assigns enumerable own properties of `source` objects to the `target` object and returns the `target` object. Additional `source` objects will overwrite previous ones. + + +## Resources + +- [ES2015 spec - Object.assign](https://people.mozilla.org/~jorendorff/es6-draft.html#sec-object.assign) + + +## Related + +- [deep-assign](https://github.com/sindresorhus/deep-assign) - Recursive `Object.assign()` + + +## License + +MIT © [Sindre Sorhus](https://sindresorhus.com) diff --git a/node_modules/process-nextick-args/index.js b/node_modules/process-nextick-args/index.js new file mode 100644 index 0000000..3eecf11 --- /dev/null +++ b/node_modules/process-nextick-args/index.js @@ -0,0 +1,45 @@ +'use strict'; + +if (typeof process === 'undefined' || + !process.version || + process.version.indexOf('v0.') === 0 || + process.version.indexOf('v1.') === 0 && process.version.indexOf('v1.8.') !== 0) { + module.exports = { nextTick: nextTick }; +} else { + module.exports = process +} + +function nextTick(fn, arg1, arg2, arg3) { + if (typeof fn !== 'function') { + throw new TypeError('"callback" argument must be a function'); + } + var len = arguments.length; + var args, i; + switch (len) { + case 0: + case 1: + return process.nextTick(fn); + case 2: + return process.nextTick(function afterTickOne() { + fn.call(null, arg1); + }); + case 3: + return process.nextTick(function afterTickTwo() { + fn.call(null, arg1, arg2); + }); + case 4: + return process.nextTick(function afterTickThree() { + fn.call(null, arg1, arg2, arg3); + }); + default: + args = new Array(len - 1); + i = 0; + while (i < args.length) { + args[i++] = arguments[i]; + } + return process.nextTick(function afterTick() { + fn.apply(null, args); + }); + } +} + diff --git a/node_modules/process-nextick-args/license.md b/node_modules/process-nextick-args/license.md new file mode 100644 index 0000000..c67e353 --- /dev/null +++ b/node_modules/process-nextick-args/license.md @@ -0,0 +1,19 @@ +# Copyright (c) 2015 Calvin Metcalf + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +**THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE.** diff --git a/node_modules/process-nextick-args/package.json b/node_modules/process-nextick-args/package.json new file mode 100644 index 0000000..6070b72 --- /dev/null +++ b/node_modules/process-nextick-args/package.json @@ -0,0 +1,25 @@ +{ + "name": "process-nextick-args", + "version": "2.0.1", + "description": "process.nextTick but always with args", + "main": "index.js", + "files": [ + "index.js" + ], + "scripts": { + "test": "node test.js" + }, + "repository": { + "type": "git", + "url": "https://github.com/calvinmetcalf/process-nextick-args.git" + }, + "author": "", + "license": "MIT", + "bugs": { + "url": "https://github.com/calvinmetcalf/process-nextick-args/issues" + }, + "homepage": "https://github.com/calvinmetcalf/process-nextick-args", + "devDependencies": { + "tap": "~0.2.6" + } +} diff --git a/node_modules/process-nextick-args/readme.md b/node_modules/process-nextick-args/readme.md new file mode 100644 index 0000000..ecb432c --- /dev/null +++ b/node_modules/process-nextick-args/readme.md @@ -0,0 +1,18 @@ +process-nextick-args +===== + +[![Build Status](https://travis-ci.org/calvinmetcalf/process-nextick-args.svg?branch=master)](https://travis-ci.org/calvinmetcalf/process-nextick-args) + +```bash +npm install --save process-nextick-args +``` + +Always be able to pass arguments to process.nextTick, no matter the platform + +```js +var pna = require('process-nextick-args'); + +pna.nextTick(function (a, b, c) { + console.log(a, b, c); +}, 'step', 3, 'profit'); +``` diff --git a/node_modules/readable-stream/.travis.yml b/node_modules/readable-stream/.travis.yml new file mode 100644 index 0000000..f62cdac --- /dev/null +++ b/node_modules/readable-stream/.travis.yml @@ -0,0 +1,34 @@ +sudo: false +language: node_js +before_install: + - (test $NPM_LEGACY && npm install -g npm@2 && npm install -g npm@3) || true +notifications: + email: false +matrix: + fast_finish: true + include: + - node_js: '0.8' + env: NPM_LEGACY=true + - node_js: '0.10' + env: NPM_LEGACY=true + - node_js: '0.11' + env: NPM_LEGACY=true + - node_js: '0.12' + env: NPM_LEGACY=true + - node_js: 1 + env: NPM_LEGACY=true + - node_js: 2 + env: NPM_LEGACY=true + - node_js: 3 + env: NPM_LEGACY=true + - node_js: 4 + - node_js: 5 + - node_js: 6 + - node_js: 7 + - node_js: 8 + - node_js: 9 +script: "npm run test" +env: + global: + - secure: rE2Vvo7vnjabYNULNyLFxOyt98BoJexDqsiOnfiD6kLYYsiQGfr/sbZkPMOFm9qfQG7pjqx+zZWZjGSswhTt+626C0t/njXqug7Yps4c3dFblzGfreQHp7wNX5TFsvrxd6dAowVasMp61sJcRnB2w8cUzoe3RAYUDHyiHktwqMc= + - secure: g9YINaKAdMatsJ28G9jCGbSaguXCyxSTy+pBO6Ch0Cf57ZLOTka3HqDj8p3nV28LUIHZ3ut5WO43CeYKwt4AUtLpBS3a0dndHdY6D83uY6b2qh5hXlrcbeQTq2cvw2y95F7hm4D1kwrgZ7ViqaKggRcEupAL69YbJnxeUDKWEdI= diff --git a/node_modules/readable-stream/CONTRIBUTING.md b/node_modules/readable-stream/CONTRIBUTING.md new file mode 100644 index 0000000..f478d58 --- /dev/null +++ b/node_modules/readable-stream/CONTRIBUTING.md @@ -0,0 +1,38 @@ +# Developer's Certificate of Origin 1.1 + +By making a contribution to this project, I certify that: + +* (a) The contribution was created in whole or in part by me and I + have the right to submit it under the open source license + indicated in the file; or + +* (b) The contribution is based upon previous work that, to the best + of my knowledge, is covered under an appropriate open source + license and I have the right under that license to submit that + work with modifications, whether created in whole or in part + by me, under the same open source license (unless I am + permitted to submit under a different license), as indicated + in the file; or + +* (c) The contribution was provided directly to me by some other + person who certified (a), (b) or (c) and I have not modified + it. + +* (d) I understand and agree that this project and the contribution + are public and that a record of the contribution (including all + personal information I submit with it, including my sign-off) is + maintained indefinitely and may be redistributed consistent with + this project or the open source license(s) involved. + +## Moderation Policy + +The [Node.js Moderation Policy] applies to this WG. + +## Code of Conduct + +The [Node.js Code of Conduct][] applies to this WG. + +[Node.js Code of Conduct]: +https://github.com/nodejs/node/blob/master/CODE_OF_CONDUCT.md +[Node.js Moderation Policy]: +https://github.com/nodejs/TSC/blob/master/Moderation-Policy.md diff --git a/node_modules/readable-stream/GOVERNANCE.md b/node_modules/readable-stream/GOVERNANCE.md new file mode 100644 index 0000000..16ffb93 --- /dev/null +++ b/node_modules/readable-stream/GOVERNANCE.md @@ -0,0 +1,136 @@ +### Streams Working Group + +The Node.js Streams is jointly governed by a Working Group +(WG) +that is responsible for high-level guidance of the project. + +The WG has final authority over this project including: + +* Technical direction +* Project governance and process (including this policy) +* Contribution policy +* GitHub repository hosting +* Conduct guidelines +* Maintaining the list of additional Collaborators + +For the current list of WG members, see the project +[README.md](./README.md#current-project-team-members). + +### Collaborators + +The readable-stream GitHub repository is +maintained by the WG and additional Collaborators who are added by the +WG on an ongoing basis. + +Individuals making significant and valuable contributions are made +Collaborators and given commit-access to the project. These +individuals are identified by the WG and their addition as +Collaborators is discussed during the WG meeting. + +_Note:_ If you make a significant contribution and are not considered +for commit-access log an issue or contact a WG member directly and it +will be brought up in the next WG meeting. + +Modifications of the contents of the readable-stream repository are +made on +a collaborative basis. Anybody with a GitHub account may propose a +modification via pull request and it will be considered by the project +Collaborators. All pull requests must be reviewed and accepted by a +Collaborator with sufficient expertise who is able to take full +responsibility for the change. In the case of pull requests proposed +by an existing Collaborator, an additional Collaborator is required +for sign-off. Consensus should be sought if additional Collaborators +participate and there is disagreement around a particular +modification. See _Consensus Seeking Process_ below for further detail +on the consensus model used for governance. + +Collaborators may opt to elevate significant or controversial +modifications, or modifications that have not found consensus to the +WG for discussion by assigning the ***WG-agenda*** tag to a pull +request or issue. The WG should serve as the final arbiter where +required. + +For the current list of Collaborators, see the project +[README.md](./README.md#members). + +### WG Membership + +WG seats are not time-limited. There is no fixed size of the WG. +However, the expected target is between 6 and 12, to ensure adequate +coverage of important areas of expertise, balanced with the ability to +make decisions efficiently. + +There is no specific set of requirements or qualifications for WG +membership beyond these rules. + +The WG may add additional members to the WG by unanimous consensus. + +A WG member may be removed from the WG by voluntary resignation, or by +unanimous consensus of all other WG members. + +Changes to WG membership should be posted in the agenda, and may be +suggested as any other agenda item (see "WG Meetings" below). + +If an addition or removal is proposed during a meeting, and the full +WG is not in attendance to participate, then the addition or removal +is added to the agenda for the subsequent meeting. This is to ensure +that all members are given the opportunity to participate in all +membership decisions. If a WG member is unable to attend a meeting +where a planned membership decision is being made, then their consent +is assumed. + +No more than 1/3 of the WG members may be affiliated with the same +employer. If removal or resignation of a WG member, or a change of +employment by a WG member, creates a situation where more than 1/3 of +the WG membership shares an employer, then the situation must be +immediately remedied by the resignation or removal of one or more WG +members affiliated with the over-represented employer(s). + +### WG Meetings + +The WG meets occasionally on a Google Hangout On Air. A designated moderator +approved by the WG runs the meeting. Each meeting should be +published to YouTube. + +Items are added to the WG agenda that are considered contentious or +are modifications of governance, contribution policy, WG membership, +or release process. + +The intention of the agenda is not to approve or review all patches; +that should happen continuously on GitHub and be handled by the larger +group of Collaborators. + +Any community member or contributor can ask that something be added to +the next meeting's agenda by logging a GitHub Issue. Any Collaborator, +WG member or the moderator can add the item to the agenda by adding +the ***WG-agenda*** tag to the issue. + +Prior to each WG meeting the moderator will share the Agenda with +members of the WG. WG members can add any items they like to the +agenda at the beginning of each meeting. The moderator and the WG +cannot veto or remove items. + +The WG may invite persons or representatives from certain projects to +participate in a non-voting capacity. + +The moderator is responsible for summarizing the discussion of each +agenda item and sends it as a pull request after the meeting. + +### Consensus Seeking Process + +The WG follows a +[Consensus +Seeking](http://en.wikipedia.org/wiki/Consensus-seeking_decision-making) +decision-making model. + +When an agenda item has appeared to reach a consensus the moderator +will ask "Does anyone object?" as a final call for dissent from the +consensus. + +If an agenda item cannot reach a consensus a WG member can call for +either a closing vote or a vote to table the issue to the next +meeting. The call for a vote must be seconded by a majority of the WG +or else the discussion will continue. Simple majority wins. + +Note that changes to WG membership require a majority consensus. See +"WG Membership" above. diff --git a/node_modules/readable-stream/LICENSE b/node_modules/readable-stream/LICENSE new file mode 100644 index 0000000..2873b3b --- /dev/null +++ b/node_modules/readable-stream/LICENSE @@ -0,0 +1,47 @@ +Node.js is licensed for use as follows: + +""" +Copyright Node.js contributors. All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to +deal in the Software without restriction, including without limitation the +rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +sell copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +IN THE SOFTWARE. +""" + +This license applies to parts of Node.js originating from the +https://github.com/joyent/node repository: + +""" +Copyright Joyent, Inc. and other Node contributors. All rights reserved. +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to +deal in the Software without restriction, including without limitation the +rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +sell copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +IN THE SOFTWARE. +""" diff --git a/node_modules/readable-stream/README.md b/node_modules/readable-stream/README.md new file mode 100644 index 0000000..f1c5a93 --- /dev/null +++ b/node_modules/readable-stream/README.md @@ -0,0 +1,58 @@ +# readable-stream + +***Node-core v8.17.0 streams for userland*** [![Build Status](https://travis-ci.org/nodejs/readable-stream.svg?branch=master)](https://travis-ci.org/nodejs/readable-stream) + + +[![NPM](https://nodei.co/npm/readable-stream.png?downloads=true&downloadRank=true)](https://nodei.co/npm/readable-stream/) +[![NPM](https://nodei.co/npm-dl/readable-stream.png?&months=6&height=3)](https://nodei.co/npm/readable-stream/) + + +[![Sauce Test Status](https://saucelabs.com/browser-matrix/readable-stream.svg)](https://saucelabs.com/u/readable-stream) + +```bash +npm install --save readable-stream +``` + +***Node-core streams for userland*** + +This package is a mirror of the Streams2 and Streams3 implementations in +Node-core. + +Full documentation may be found on the [Node.js website](https://nodejs.org/dist/v8.17.0/docs/api/stream.html). + +If you want to guarantee a stable streams base, regardless of what version of +Node you, or the users of your libraries are using, use **readable-stream** *only* and avoid the *"stream"* module in Node-core, for background see [this blogpost](http://r.va.gg/2014/06/why-i-dont-use-nodes-core-stream-module.html). + +As of version 2.0.0 **readable-stream** uses semantic versioning. + +# Streams Working Group + +`readable-stream` is maintained by the Streams Working Group, which +oversees the development and maintenance of the Streams API within +Node.js. The responsibilities of the Streams Working Group include: + +* Addressing stream issues on the Node.js issue tracker. +* Authoring and editing stream documentation within the Node.js project. +* Reviewing changes to stream subclasses within the Node.js project. +* Redirecting changes to streams from the Node.js project to this + project. +* Assisting in the implementation of stream providers within Node.js. +* Recommending versions of `readable-stream` to be included in Node.js. +* Messaging about the future of streams to give the community advance + notice of changes. + + +## Team Members + +* **Chris Dickinson** ([@chrisdickinson](https://github.com/chrisdickinson)) <christopher.s.dickinson@gmail.com> + - Release GPG key: 9554F04D7259F04124DE6B476D5A82AC7E37093B +* **Calvin Metcalf** ([@calvinmetcalf](https://github.com/calvinmetcalf)) <calvin.metcalf@gmail.com> + - Release GPG key: F3EF5F62A87FC27A22E643F714CE4FF5015AA242 +* **Rod Vagg** ([@rvagg](https://github.com/rvagg)) <rod@vagg.org> + - Release GPG key: DD8F2338BAE7501E3DD5AC78C273792F7D83545D +* **Sam Newman** ([@sonewman](https://github.com/sonewman)) <newmansam@outlook.com> +* **Mathias Buus** ([@mafintosh](https://github.com/mafintosh)) <mathiasbuus@gmail.com> +* **Domenic Denicola** ([@domenic](https://github.com/domenic)) <d@domenic.me> +* **Matteo Collina** ([@mcollina](https://github.com/mcollina)) <matteo.collina@gmail.com> + - Release GPG key: 3ABC01543F22DD2239285CDD818674489FBC127E +* **Irina Shestak** ([@lrlna](https://github.com/lrlna)) <shestak.irina@gmail.com> diff --git a/node_modules/readable-stream/doc/wg-meetings/2015-01-30.md b/node_modules/readable-stream/doc/wg-meetings/2015-01-30.md new file mode 100644 index 0000000..83275f1 --- /dev/null +++ b/node_modules/readable-stream/doc/wg-meetings/2015-01-30.md @@ -0,0 +1,60 @@ +# streams WG Meeting 2015-01-30 + +## Links + +* **Google Hangouts Video**: http://www.youtube.com/watch?v=I9nDOSGfwZg +* **GitHub Issue**: https://github.com/iojs/readable-stream/issues/106 +* **Original Minutes Google Doc**: https://docs.google.com/document/d/17aTgLnjMXIrfjgNaTUnHQO7m3xgzHR2VXBTmi03Qii4/ + +## Agenda + +Extracted from https://github.com/iojs/readable-stream/labels/wg-agenda prior to meeting. + +* adopt a charter [#105](https://github.com/iojs/readable-stream/issues/105) +* release and versioning strategy [#101](https://github.com/iojs/readable-stream/issues/101) +* simpler stream creation [#102](https://github.com/iojs/readable-stream/issues/102) +* proposal: deprecate implicit flowing of streams [#99](https://github.com/iojs/readable-stream/issues/99) + +## Minutes + +### adopt a charter + +* group: +1's all around + +### What versioning scheme should be adopted? +* group: +1’s 3.0.0 +* domenic+group: pulling in patches from other sources where appropriate +* mikeal: version independently, suggesting versions for io.js +* mikeal+domenic: work with TC to notify in advance of changes +simpler stream creation + +### streamline creation of streams +* sam: streamline creation of streams +* domenic: nice simple solution posted + but, we lose the opportunity to change the model + may not be backwards incompatible (double check keys) + + **action item:** domenic will check + +### remove implicit flowing of streams on(‘data’) +* add isFlowing / isPaused +* mikeal: worrying that we’re documenting polyfill methods – confuses users +* domenic: more reflective API is probably good, with warning labels for users +* new section for mad scientists (reflective stream access) +* calvin: name the “third state” +* mikeal: maybe borrow the name from whatwg? +* domenic: we’re missing the “third state” +* consensus: kind of difficult to name the third state +* mikeal: figure out differences in states / compat +* mathias: always flow on data – eliminates third state + * explore what it breaks + +**action items:** +* ask isaac for ability to list packages by what public io.js APIs they use (esp. Stream) +* ask rod/build for infrastructure +* **chris**: explore the “flow on data” approach +* add isPaused/isFlowing +* add new docs section +* move isPaused to that section + + diff --git a/node_modules/readable-stream/duplex-browser.js b/node_modules/readable-stream/duplex-browser.js new file mode 100644 index 0000000..f8b2db8 --- /dev/null +++ b/node_modules/readable-stream/duplex-browser.js @@ -0,0 +1 @@ +module.exports = require('./lib/_stream_duplex.js'); diff --git a/node_modules/readable-stream/duplex.js b/node_modules/readable-stream/duplex.js new file mode 100644 index 0000000..46924cb --- /dev/null +++ b/node_modules/readable-stream/duplex.js @@ -0,0 +1 @@ +module.exports = require('./readable').Duplex diff --git a/node_modules/readable-stream/lib/_stream_duplex.js b/node_modules/readable-stream/lib/_stream_duplex.js new file mode 100644 index 0000000..57003c3 --- /dev/null +++ b/node_modules/readable-stream/lib/_stream_duplex.js @@ -0,0 +1,131 @@ +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +// a duplex stream is just a stream that is both readable and writable. +// Since JS doesn't have multiple prototypal inheritance, this class +// prototypally inherits from Readable, and then parasitically from +// Writable. + +'use strict'; + +/**/ + +var pna = require('process-nextick-args'); +/**/ + +/**/ +var objectKeys = Object.keys || function (obj) { + var keys = []; + for (var key in obj) { + keys.push(key); + }return keys; +}; +/**/ + +module.exports = Duplex; + +/**/ +var util = Object.create(require('core-util-is')); +util.inherits = require('inherits'); +/**/ + +var Readable = require('./_stream_readable'); +var Writable = require('./_stream_writable'); + +util.inherits(Duplex, Readable); + +{ + // avoid scope creep, the keys array can then be collected + var keys = objectKeys(Writable.prototype); + for (var v = 0; v < keys.length; v++) { + var method = keys[v]; + if (!Duplex.prototype[method]) Duplex.prototype[method] = Writable.prototype[method]; + } +} + +function Duplex(options) { + if (!(this instanceof Duplex)) return new Duplex(options); + + Readable.call(this, options); + Writable.call(this, options); + + if (options && options.readable === false) this.readable = false; + + if (options && options.writable === false) this.writable = false; + + this.allowHalfOpen = true; + if (options && options.allowHalfOpen === false) this.allowHalfOpen = false; + + this.once('end', onend); +} + +Object.defineProperty(Duplex.prototype, 'writableHighWaterMark', { + // making it explicit this property is not enumerable + // because otherwise some prototype manipulation in + // userland will fail + enumerable: false, + get: function () { + return this._writableState.highWaterMark; + } +}); + +// the no-half-open enforcer +function onend() { + // if we allow half-open state, or if the writable side ended, + // then we're ok. + if (this.allowHalfOpen || this._writableState.ended) return; + + // no more data can be written. + // But allow more writes to happen in this tick. + pna.nextTick(onEndNT, this); +} + +function onEndNT(self) { + self.end(); +} + +Object.defineProperty(Duplex.prototype, 'destroyed', { + get: function () { + if (this._readableState === undefined || this._writableState === undefined) { + return false; + } + return this._readableState.destroyed && this._writableState.destroyed; + }, + set: function (value) { + // we ignore the value if the stream + // has not been initialized yet + if (this._readableState === undefined || this._writableState === undefined) { + return; + } + + // backward compatibility, the user is explicitly + // managing destroyed + this._readableState.destroyed = value; + this._writableState.destroyed = value; + } +}); + +Duplex.prototype._destroy = function (err, cb) { + this.push(null); + this.end(); + + pna.nextTick(cb, err); +}; \ No newline at end of file diff --git a/node_modules/readable-stream/lib/_stream_passthrough.js b/node_modules/readable-stream/lib/_stream_passthrough.js new file mode 100644 index 0000000..612edb4 --- /dev/null +++ b/node_modules/readable-stream/lib/_stream_passthrough.js @@ -0,0 +1,47 @@ +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +// a passthrough stream. +// basically just the most minimal sort of Transform stream. +// Every written chunk gets output as-is. + +'use strict'; + +module.exports = PassThrough; + +var Transform = require('./_stream_transform'); + +/**/ +var util = Object.create(require('core-util-is')); +util.inherits = require('inherits'); +/**/ + +util.inherits(PassThrough, Transform); + +function PassThrough(options) { + if (!(this instanceof PassThrough)) return new PassThrough(options); + + Transform.call(this, options); +} + +PassThrough.prototype._transform = function (chunk, encoding, cb) { + cb(null, chunk); +}; \ No newline at end of file diff --git a/node_modules/readable-stream/lib/_stream_readable.js b/node_modules/readable-stream/lib/_stream_readable.js new file mode 100644 index 0000000..3af95cb --- /dev/null +++ b/node_modules/readable-stream/lib/_stream_readable.js @@ -0,0 +1,1019 @@ +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +'use strict'; + +/**/ + +var pna = require('process-nextick-args'); +/**/ + +module.exports = Readable; + +/**/ +var isArray = require('isarray'); +/**/ + +/**/ +var Duplex; +/**/ + +Readable.ReadableState = ReadableState; + +/**/ +var EE = require('events').EventEmitter; + +var EElistenerCount = function (emitter, type) { + return emitter.listeners(type).length; +}; +/**/ + +/**/ +var Stream = require('./internal/streams/stream'); +/**/ + +/**/ + +var Buffer = require('safe-buffer').Buffer; +var OurUint8Array = (typeof global !== 'undefined' ? global : typeof window !== 'undefined' ? window : typeof self !== 'undefined' ? self : {}).Uint8Array || function () {}; +function _uint8ArrayToBuffer(chunk) { + return Buffer.from(chunk); +} +function _isUint8Array(obj) { + return Buffer.isBuffer(obj) || obj instanceof OurUint8Array; +} + +/**/ + +/**/ +var util = Object.create(require('core-util-is')); +util.inherits = require('inherits'); +/**/ + +/**/ +var debugUtil = require('util'); +var debug = void 0; +if (debugUtil && debugUtil.debuglog) { + debug = debugUtil.debuglog('stream'); +} else { + debug = function () {}; +} +/**/ + +var BufferList = require('./internal/streams/BufferList'); +var destroyImpl = require('./internal/streams/destroy'); +var StringDecoder; + +util.inherits(Readable, Stream); + +var kProxyEvents = ['error', 'close', 'destroy', 'pause', 'resume']; + +function prependListener(emitter, event, fn) { + // Sadly this is not cacheable as some libraries bundle their own + // event emitter implementation with them. + if (typeof emitter.prependListener === 'function') return emitter.prependListener(event, fn); + + // This is a hack to make sure that our error handler is attached before any + // userland ones. NEVER DO THIS. This is here only because this code needs + // to continue to work with older versions of Node.js that do not include + // the prependListener() method. The goal is to eventually remove this hack. + if (!emitter._events || !emitter._events[event]) emitter.on(event, fn);else if (isArray(emitter._events[event])) emitter._events[event].unshift(fn);else emitter._events[event] = [fn, emitter._events[event]]; +} + +function ReadableState(options, stream) { + Duplex = Duplex || require('./_stream_duplex'); + + options = options || {}; + + // Duplex streams are both readable and writable, but share + // the same options object. + // However, some cases require setting options to different + // values for the readable and the writable sides of the duplex stream. + // These options can be provided separately as readableXXX and writableXXX. + var isDuplex = stream instanceof Duplex; + + // object stream flag. Used to make read(n) ignore n and to + // make all the buffer merging and length checks go away + this.objectMode = !!options.objectMode; + + if (isDuplex) this.objectMode = this.objectMode || !!options.readableObjectMode; + + // the point at which it stops calling _read() to fill the buffer + // Note: 0 is a valid value, means "don't call _read preemptively ever" + var hwm = options.highWaterMark; + var readableHwm = options.readableHighWaterMark; + var defaultHwm = this.objectMode ? 16 : 16 * 1024; + + if (hwm || hwm === 0) this.highWaterMark = hwm;else if (isDuplex && (readableHwm || readableHwm === 0)) this.highWaterMark = readableHwm;else this.highWaterMark = defaultHwm; + + // cast to ints. + this.highWaterMark = Math.floor(this.highWaterMark); + + // A linked list is used to store data chunks instead of an array because the + // linked list can remove elements from the beginning faster than + // array.shift() + this.buffer = new BufferList(); + this.length = 0; + this.pipes = null; + this.pipesCount = 0; + this.flowing = null; + this.ended = false; + this.endEmitted = false; + this.reading = false; + + // a flag to be able to tell if the event 'readable'/'data' is emitted + // immediately, or on a later tick. We set this to true at first, because + // any actions that shouldn't happen until "later" should generally also + // not happen before the first read call. + this.sync = true; + + // whenever we return null, then we set a flag to say + // that we're awaiting a 'readable' event emission. + this.needReadable = false; + this.emittedReadable = false; + this.readableListening = false; + this.resumeScheduled = false; + + // has it been destroyed + this.destroyed = false; + + // Crypto is kind of old and crusty. Historically, its default string + // encoding is 'binary' so we have to make this configurable. + // Everything else in the universe uses 'utf8', though. + this.defaultEncoding = options.defaultEncoding || 'utf8'; + + // the number of writers that are awaiting a drain event in .pipe()s + this.awaitDrain = 0; + + // if true, a maybeReadMore has been scheduled + this.readingMore = false; + + this.decoder = null; + this.encoding = null; + if (options.encoding) { + if (!StringDecoder) StringDecoder = require('string_decoder/').StringDecoder; + this.decoder = new StringDecoder(options.encoding); + this.encoding = options.encoding; + } +} + +function Readable(options) { + Duplex = Duplex || require('./_stream_duplex'); + + if (!(this instanceof Readable)) return new Readable(options); + + this._readableState = new ReadableState(options, this); + + // legacy + this.readable = true; + + if (options) { + if (typeof options.read === 'function') this._read = options.read; + + if (typeof options.destroy === 'function') this._destroy = options.destroy; + } + + Stream.call(this); +} + +Object.defineProperty(Readable.prototype, 'destroyed', { + get: function () { + if (this._readableState === undefined) { + return false; + } + return this._readableState.destroyed; + }, + set: function (value) { + // we ignore the value if the stream + // has not been initialized yet + if (!this._readableState) { + return; + } + + // backward compatibility, the user is explicitly + // managing destroyed + this._readableState.destroyed = value; + } +}); + +Readable.prototype.destroy = destroyImpl.destroy; +Readable.prototype._undestroy = destroyImpl.undestroy; +Readable.prototype._destroy = function (err, cb) { + this.push(null); + cb(err); +}; + +// Manually shove something into the read() buffer. +// This returns true if the highWaterMark has not been hit yet, +// similar to how Writable.write() returns true if you should +// write() some more. +Readable.prototype.push = function (chunk, encoding) { + var state = this._readableState; + var skipChunkCheck; + + if (!state.objectMode) { + if (typeof chunk === 'string') { + encoding = encoding || state.defaultEncoding; + if (encoding !== state.encoding) { + chunk = Buffer.from(chunk, encoding); + encoding = ''; + } + skipChunkCheck = true; + } + } else { + skipChunkCheck = true; + } + + return readableAddChunk(this, chunk, encoding, false, skipChunkCheck); +}; + +// Unshift should *always* be something directly out of read() +Readable.prototype.unshift = function (chunk) { + return readableAddChunk(this, chunk, null, true, false); +}; + +function readableAddChunk(stream, chunk, encoding, addToFront, skipChunkCheck) { + var state = stream._readableState; + if (chunk === null) { + state.reading = false; + onEofChunk(stream, state); + } else { + var er; + if (!skipChunkCheck) er = chunkInvalid(state, chunk); + if (er) { + stream.emit('error', er); + } else if (state.objectMode || chunk && chunk.length > 0) { + if (typeof chunk !== 'string' && !state.objectMode && Object.getPrototypeOf(chunk) !== Buffer.prototype) { + chunk = _uint8ArrayToBuffer(chunk); + } + + if (addToFront) { + if (state.endEmitted) stream.emit('error', new Error('stream.unshift() after end event'));else addChunk(stream, state, chunk, true); + } else if (state.ended) { + stream.emit('error', new Error('stream.push() after EOF')); + } else { + state.reading = false; + if (state.decoder && !encoding) { + chunk = state.decoder.write(chunk); + if (state.objectMode || chunk.length !== 0) addChunk(stream, state, chunk, false);else maybeReadMore(stream, state); + } else { + addChunk(stream, state, chunk, false); + } + } + } else if (!addToFront) { + state.reading = false; + } + } + + return needMoreData(state); +} + +function addChunk(stream, state, chunk, addToFront) { + if (state.flowing && state.length === 0 && !state.sync) { + stream.emit('data', chunk); + stream.read(0); + } else { + // update the buffer info. + state.length += state.objectMode ? 1 : chunk.length; + if (addToFront) state.buffer.unshift(chunk);else state.buffer.push(chunk); + + if (state.needReadable) emitReadable(stream); + } + maybeReadMore(stream, state); +} + +function chunkInvalid(state, chunk) { + var er; + if (!_isUint8Array(chunk) && typeof chunk !== 'string' && chunk !== undefined && !state.objectMode) { + er = new TypeError('Invalid non-string/buffer chunk'); + } + return er; +} + +// if it's past the high water mark, we can push in some more. +// Also, if we have no data yet, we can stand some +// more bytes. This is to work around cases where hwm=0, +// such as the repl. Also, if the push() triggered a +// readable event, and the user called read(largeNumber) such that +// needReadable was set, then we ought to push more, so that another +// 'readable' event will be triggered. +function needMoreData(state) { + return !state.ended && (state.needReadable || state.length < state.highWaterMark || state.length === 0); +} + +Readable.prototype.isPaused = function () { + return this._readableState.flowing === false; +}; + +// backwards compatibility. +Readable.prototype.setEncoding = function (enc) { + if (!StringDecoder) StringDecoder = require('string_decoder/').StringDecoder; + this._readableState.decoder = new StringDecoder(enc); + this._readableState.encoding = enc; + return this; +}; + +// Don't raise the hwm > 8MB +var MAX_HWM = 0x800000; +function computeNewHighWaterMark(n) { + if (n >= MAX_HWM) { + n = MAX_HWM; + } else { + // Get the next highest power of 2 to prevent increasing hwm excessively in + // tiny amounts + n--; + n |= n >>> 1; + n |= n >>> 2; + n |= n >>> 4; + n |= n >>> 8; + n |= n >>> 16; + n++; + } + return n; +} + +// This function is designed to be inlinable, so please take care when making +// changes to the function body. +function howMuchToRead(n, state) { + if (n <= 0 || state.length === 0 && state.ended) return 0; + if (state.objectMode) return 1; + if (n !== n) { + // Only flow one buffer at a time + if (state.flowing && state.length) return state.buffer.head.data.length;else return state.length; + } + // If we're asking for more than the current hwm, then raise the hwm. + if (n > state.highWaterMark) state.highWaterMark = computeNewHighWaterMark(n); + if (n <= state.length) return n; + // Don't have enough + if (!state.ended) { + state.needReadable = true; + return 0; + } + return state.length; +} + +// you can override either this method, or the async _read(n) below. +Readable.prototype.read = function (n) { + debug('read', n); + n = parseInt(n, 10); + var state = this._readableState; + var nOrig = n; + + if (n !== 0) state.emittedReadable = false; + + // if we're doing read(0) to trigger a readable event, but we + // already have a bunch of data in the buffer, then just trigger + // the 'readable' event and move on. + if (n === 0 && state.needReadable && (state.length >= state.highWaterMark || state.ended)) { + debug('read: emitReadable', state.length, state.ended); + if (state.length === 0 && state.ended) endReadable(this);else emitReadable(this); + return null; + } + + n = howMuchToRead(n, state); + + // if we've ended, and we're now clear, then finish it up. + if (n === 0 && state.ended) { + if (state.length === 0) endReadable(this); + return null; + } + + // All the actual chunk generation logic needs to be + // *below* the call to _read. The reason is that in certain + // synthetic stream cases, such as passthrough streams, _read + // may be a completely synchronous operation which may change + // the state of the read buffer, providing enough data when + // before there was *not* enough. + // + // So, the steps are: + // 1. Figure out what the state of things will be after we do + // a read from the buffer. + // + // 2. If that resulting state will trigger a _read, then call _read. + // Note that this may be asynchronous, or synchronous. Yes, it is + // deeply ugly to write APIs this way, but that still doesn't mean + // that the Readable class should behave improperly, as streams are + // designed to be sync/async agnostic. + // Take note if the _read call is sync or async (ie, if the read call + // has returned yet), so that we know whether or not it's safe to emit + // 'readable' etc. + // + // 3. Actually pull the requested chunks out of the buffer and return. + + // if we need a readable event, then we need to do some reading. + var doRead = state.needReadable; + debug('need readable', doRead); + + // if we currently have less than the highWaterMark, then also read some + if (state.length === 0 || state.length - n < state.highWaterMark) { + doRead = true; + debug('length less than watermark', doRead); + } + + // however, if we've ended, then there's no point, and if we're already + // reading, then it's unnecessary. + if (state.ended || state.reading) { + doRead = false; + debug('reading or ended', doRead); + } else if (doRead) { + debug('do read'); + state.reading = true; + state.sync = true; + // if the length is currently zero, then we *need* a readable event. + if (state.length === 0) state.needReadable = true; + // call internal read method + this._read(state.highWaterMark); + state.sync = false; + // If _read pushed data synchronously, then `reading` will be false, + // and we need to re-evaluate how much data we can return to the user. + if (!state.reading) n = howMuchToRead(nOrig, state); + } + + var ret; + if (n > 0) ret = fromList(n, state);else ret = null; + + if (ret === null) { + state.needReadable = true; + n = 0; + } else { + state.length -= n; + } + + if (state.length === 0) { + // If we have nothing in the buffer, then we want to know + // as soon as we *do* get something into the buffer. + if (!state.ended) state.needReadable = true; + + // If we tried to read() past the EOF, then emit end on the next tick. + if (nOrig !== n && state.ended) endReadable(this); + } + + if (ret !== null) this.emit('data', ret); + + return ret; +}; + +function onEofChunk(stream, state) { + if (state.ended) return; + if (state.decoder) { + var chunk = state.decoder.end(); + if (chunk && chunk.length) { + state.buffer.push(chunk); + state.length += state.objectMode ? 1 : chunk.length; + } + } + state.ended = true; + + // emit 'readable' now to make sure it gets picked up. + emitReadable(stream); +} + +// Don't emit readable right away in sync mode, because this can trigger +// another read() call => stack overflow. This way, it might trigger +// a nextTick recursion warning, but that's not so bad. +function emitReadable(stream) { + var state = stream._readableState; + state.needReadable = false; + if (!state.emittedReadable) { + debug('emitReadable', state.flowing); + state.emittedReadable = true; + if (state.sync) pna.nextTick(emitReadable_, stream);else emitReadable_(stream); + } +} + +function emitReadable_(stream) { + debug('emit readable'); + stream.emit('readable'); + flow(stream); +} + +// at this point, the user has presumably seen the 'readable' event, +// and called read() to consume some data. that may have triggered +// in turn another _read(n) call, in which case reading = true if +// it's in progress. +// However, if we're not ended, or reading, and the length < hwm, +// then go ahead and try to read some more preemptively. +function maybeReadMore(stream, state) { + if (!state.readingMore) { + state.readingMore = true; + pna.nextTick(maybeReadMore_, stream, state); + } +} + +function maybeReadMore_(stream, state) { + var len = state.length; + while (!state.reading && !state.flowing && !state.ended && state.length < state.highWaterMark) { + debug('maybeReadMore read 0'); + stream.read(0); + if (len === state.length) + // didn't get any data, stop spinning. + break;else len = state.length; + } + state.readingMore = false; +} + +// abstract method. to be overridden in specific implementation classes. +// call cb(er, data) where data is <= n in length. +// for virtual (non-string, non-buffer) streams, "length" is somewhat +// arbitrary, and perhaps not very meaningful. +Readable.prototype._read = function (n) { + this.emit('error', new Error('_read() is not implemented')); +}; + +Readable.prototype.pipe = function (dest, pipeOpts) { + var src = this; + var state = this._readableState; + + switch (state.pipesCount) { + case 0: + state.pipes = dest; + break; + case 1: + state.pipes = [state.pipes, dest]; + break; + default: + state.pipes.push(dest); + break; + } + state.pipesCount += 1; + debug('pipe count=%d opts=%j', state.pipesCount, pipeOpts); + + var doEnd = (!pipeOpts || pipeOpts.end !== false) && dest !== process.stdout && dest !== process.stderr; + + var endFn = doEnd ? onend : unpipe; + if (state.endEmitted) pna.nextTick(endFn);else src.once('end', endFn); + + dest.on('unpipe', onunpipe); + function onunpipe(readable, unpipeInfo) { + debug('onunpipe'); + if (readable === src) { + if (unpipeInfo && unpipeInfo.hasUnpiped === false) { + unpipeInfo.hasUnpiped = true; + cleanup(); + } + } + } + + function onend() { + debug('onend'); + dest.end(); + } + + // when the dest drains, it reduces the awaitDrain counter + // on the source. This would be more elegant with a .once() + // handler in flow(), but adding and removing repeatedly is + // too slow. + var ondrain = pipeOnDrain(src); + dest.on('drain', ondrain); + + var cleanedUp = false; + function cleanup() { + debug('cleanup'); + // cleanup event handlers once the pipe is broken + dest.removeListener('close', onclose); + dest.removeListener('finish', onfinish); + dest.removeListener('drain', ondrain); + dest.removeListener('error', onerror); + dest.removeListener('unpipe', onunpipe); + src.removeListener('end', onend); + src.removeListener('end', unpipe); + src.removeListener('data', ondata); + + cleanedUp = true; + + // if the reader is waiting for a drain event from this + // specific writer, then it would cause it to never start + // flowing again. + // So, if this is awaiting a drain, then we just call it now. + // If we don't know, then assume that we are waiting for one. + if (state.awaitDrain && (!dest._writableState || dest._writableState.needDrain)) ondrain(); + } + + // If the user pushes more data while we're writing to dest then we'll end up + // in ondata again. However, we only want to increase awaitDrain once because + // dest will only emit one 'drain' event for the multiple writes. + // => Introduce a guard on increasing awaitDrain. + var increasedAwaitDrain = false; + src.on('data', ondata); + function ondata(chunk) { + debug('ondata'); + increasedAwaitDrain = false; + var ret = dest.write(chunk); + if (false === ret && !increasedAwaitDrain) { + // If the user unpiped during `dest.write()`, it is possible + // to get stuck in a permanently paused state if that write + // also returned false. + // => Check whether `dest` is still a piping destination. + if ((state.pipesCount === 1 && state.pipes === dest || state.pipesCount > 1 && indexOf(state.pipes, dest) !== -1) && !cleanedUp) { + debug('false write response, pause', state.awaitDrain); + state.awaitDrain++; + increasedAwaitDrain = true; + } + src.pause(); + } + } + + // if the dest has an error, then stop piping into it. + // however, don't suppress the throwing behavior for this. + function onerror(er) { + debug('onerror', er); + unpipe(); + dest.removeListener('error', onerror); + if (EElistenerCount(dest, 'error') === 0) dest.emit('error', er); + } + + // Make sure our error handler is attached before userland ones. + prependListener(dest, 'error', onerror); + + // Both close and finish should trigger unpipe, but only once. + function onclose() { + dest.removeListener('finish', onfinish); + unpipe(); + } + dest.once('close', onclose); + function onfinish() { + debug('onfinish'); + dest.removeListener('close', onclose); + unpipe(); + } + dest.once('finish', onfinish); + + function unpipe() { + debug('unpipe'); + src.unpipe(dest); + } + + // tell the dest that it's being piped to + dest.emit('pipe', src); + + // start the flow if it hasn't been started already. + if (!state.flowing) { + debug('pipe resume'); + src.resume(); + } + + return dest; +}; + +function pipeOnDrain(src) { + return function () { + var state = src._readableState; + debug('pipeOnDrain', state.awaitDrain); + if (state.awaitDrain) state.awaitDrain--; + if (state.awaitDrain === 0 && EElistenerCount(src, 'data')) { + state.flowing = true; + flow(src); + } + }; +} + +Readable.prototype.unpipe = function (dest) { + var state = this._readableState; + var unpipeInfo = { hasUnpiped: false }; + + // if we're not piping anywhere, then do nothing. + if (state.pipesCount === 0) return this; + + // just one destination. most common case. + if (state.pipesCount === 1) { + // passed in one, but it's not the right one. + if (dest && dest !== state.pipes) return this; + + if (!dest) dest = state.pipes; + + // got a match. + state.pipes = null; + state.pipesCount = 0; + state.flowing = false; + if (dest) dest.emit('unpipe', this, unpipeInfo); + return this; + } + + // slow case. multiple pipe destinations. + + if (!dest) { + // remove all. + var dests = state.pipes; + var len = state.pipesCount; + state.pipes = null; + state.pipesCount = 0; + state.flowing = false; + + for (var i = 0; i < len; i++) { + dests[i].emit('unpipe', this, { hasUnpiped: false }); + }return this; + } + + // try to find the right one. + var index = indexOf(state.pipes, dest); + if (index === -1) return this; + + state.pipes.splice(index, 1); + state.pipesCount -= 1; + if (state.pipesCount === 1) state.pipes = state.pipes[0]; + + dest.emit('unpipe', this, unpipeInfo); + + return this; +}; + +// set up data events if they are asked for +// Ensure readable listeners eventually get something +Readable.prototype.on = function (ev, fn) { + var res = Stream.prototype.on.call(this, ev, fn); + + if (ev === 'data') { + // Start flowing on next tick if stream isn't explicitly paused + if (this._readableState.flowing !== false) this.resume(); + } else if (ev === 'readable') { + var state = this._readableState; + if (!state.endEmitted && !state.readableListening) { + state.readableListening = state.needReadable = true; + state.emittedReadable = false; + if (!state.reading) { + pna.nextTick(nReadingNextTick, this); + } else if (state.length) { + emitReadable(this); + } + } + } + + return res; +}; +Readable.prototype.addListener = Readable.prototype.on; + +function nReadingNextTick(self) { + debug('readable nexttick read 0'); + self.read(0); +} + +// pause() and resume() are remnants of the legacy readable stream API +// If the user uses them, then switch into old mode. +Readable.prototype.resume = function () { + var state = this._readableState; + if (!state.flowing) { + debug('resume'); + state.flowing = true; + resume(this, state); + } + return this; +}; + +function resume(stream, state) { + if (!state.resumeScheduled) { + state.resumeScheduled = true; + pna.nextTick(resume_, stream, state); + } +} + +function resume_(stream, state) { + if (!state.reading) { + debug('resume read 0'); + stream.read(0); + } + + state.resumeScheduled = false; + state.awaitDrain = 0; + stream.emit('resume'); + flow(stream); + if (state.flowing && !state.reading) stream.read(0); +} + +Readable.prototype.pause = function () { + debug('call pause flowing=%j', this._readableState.flowing); + if (false !== this._readableState.flowing) { + debug('pause'); + this._readableState.flowing = false; + this.emit('pause'); + } + return this; +}; + +function flow(stream) { + var state = stream._readableState; + debug('flow', state.flowing); + while (state.flowing && stream.read() !== null) {} +} + +// wrap an old-style stream as the async data source. +// This is *not* part of the readable stream interface. +// It is an ugly unfortunate mess of history. +Readable.prototype.wrap = function (stream) { + var _this = this; + + var state = this._readableState; + var paused = false; + + stream.on('end', function () { + debug('wrapped end'); + if (state.decoder && !state.ended) { + var chunk = state.decoder.end(); + if (chunk && chunk.length) _this.push(chunk); + } + + _this.push(null); + }); + + stream.on('data', function (chunk) { + debug('wrapped data'); + if (state.decoder) chunk = state.decoder.write(chunk); + + // don't skip over falsy values in objectMode + if (state.objectMode && (chunk === null || chunk === undefined)) return;else if (!state.objectMode && (!chunk || !chunk.length)) return; + + var ret = _this.push(chunk); + if (!ret) { + paused = true; + stream.pause(); + } + }); + + // proxy all the other methods. + // important when wrapping filters and duplexes. + for (var i in stream) { + if (this[i] === undefined && typeof stream[i] === 'function') { + this[i] = function (method) { + return function () { + return stream[method].apply(stream, arguments); + }; + }(i); + } + } + + // proxy certain important events. + for (var n = 0; n < kProxyEvents.length; n++) { + stream.on(kProxyEvents[n], this.emit.bind(this, kProxyEvents[n])); + } + + // when we try to consume some more bytes, simply unpause the + // underlying stream. + this._read = function (n) { + debug('wrapped _read', n); + if (paused) { + paused = false; + stream.resume(); + } + }; + + return this; +}; + +Object.defineProperty(Readable.prototype, 'readableHighWaterMark', { + // making it explicit this property is not enumerable + // because otherwise some prototype manipulation in + // userland will fail + enumerable: false, + get: function () { + return this._readableState.highWaterMark; + } +}); + +// exposed for testing purposes only. +Readable._fromList = fromList; + +// Pluck off n bytes from an array of buffers. +// Length is the combined lengths of all the buffers in the list. +// This function is designed to be inlinable, so please take care when making +// changes to the function body. +function fromList(n, state) { + // nothing buffered + if (state.length === 0) return null; + + var ret; + if (state.objectMode) ret = state.buffer.shift();else if (!n || n >= state.length) { + // read it all, truncate the list + if (state.decoder) ret = state.buffer.join('');else if (state.buffer.length === 1) ret = state.buffer.head.data;else ret = state.buffer.concat(state.length); + state.buffer.clear(); + } else { + // read part of list + ret = fromListPartial(n, state.buffer, state.decoder); + } + + return ret; +} + +// Extracts only enough buffered data to satisfy the amount requested. +// This function is designed to be inlinable, so please take care when making +// changes to the function body. +function fromListPartial(n, list, hasStrings) { + var ret; + if (n < list.head.data.length) { + // slice is the same for buffers and strings + ret = list.head.data.slice(0, n); + list.head.data = list.head.data.slice(n); + } else if (n === list.head.data.length) { + // first chunk is a perfect match + ret = list.shift(); + } else { + // result spans more than one buffer + ret = hasStrings ? copyFromBufferString(n, list) : copyFromBuffer(n, list); + } + return ret; +} + +// Copies a specified amount of characters from the list of buffered data +// chunks. +// This function is designed to be inlinable, so please take care when making +// changes to the function body. +function copyFromBufferString(n, list) { + var p = list.head; + var c = 1; + var ret = p.data; + n -= ret.length; + while (p = p.next) { + var str = p.data; + var nb = n > str.length ? str.length : n; + if (nb === str.length) ret += str;else ret += str.slice(0, n); + n -= nb; + if (n === 0) { + if (nb === str.length) { + ++c; + if (p.next) list.head = p.next;else list.head = list.tail = null; + } else { + list.head = p; + p.data = str.slice(nb); + } + break; + } + ++c; + } + list.length -= c; + return ret; +} + +// Copies a specified amount of bytes from the list of buffered data chunks. +// This function is designed to be inlinable, so please take care when making +// changes to the function body. +function copyFromBuffer(n, list) { + var ret = Buffer.allocUnsafe(n); + var p = list.head; + var c = 1; + p.data.copy(ret); + n -= p.data.length; + while (p = p.next) { + var buf = p.data; + var nb = n > buf.length ? buf.length : n; + buf.copy(ret, ret.length - n, 0, nb); + n -= nb; + if (n === 0) { + if (nb === buf.length) { + ++c; + if (p.next) list.head = p.next;else list.head = list.tail = null; + } else { + list.head = p; + p.data = buf.slice(nb); + } + break; + } + ++c; + } + list.length -= c; + return ret; +} + +function endReadable(stream) { + var state = stream._readableState; + + // If we get here before consuming all the bytes, then that is a + // bug in node. Should never happen. + if (state.length > 0) throw new Error('"endReadable()" called on non-empty stream'); + + if (!state.endEmitted) { + state.ended = true; + pna.nextTick(endReadableNT, state, stream); + } +} + +function endReadableNT(state, stream) { + // Check that we didn't get one last unshift. + if (!state.endEmitted && state.length === 0) { + state.endEmitted = true; + stream.readable = false; + stream.emit('end'); + } +} + +function indexOf(xs, x) { + for (var i = 0, l = xs.length; i < l; i++) { + if (xs[i] === x) return i; + } + return -1; +} \ No newline at end of file diff --git a/node_modules/readable-stream/lib/_stream_transform.js b/node_modules/readable-stream/lib/_stream_transform.js new file mode 100644 index 0000000..fcfc105 --- /dev/null +++ b/node_modules/readable-stream/lib/_stream_transform.js @@ -0,0 +1,214 @@ +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +// a transform stream is a readable/writable stream where you do +// something with the data. Sometimes it's called a "filter", +// but that's not a great name for it, since that implies a thing where +// some bits pass through, and others are simply ignored. (That would +// be a valid example of a transform, of course.) +// +// While the output is causally related to the input, it's not a +// necessarily symmetric or synchronous transformation. For example, +// a zlib stream might take multiple plain-text writes(), and then +// emit a single compressed chunk some time in the future. +// +// Here's how this works: +// +// The Transform stream has all the aspects of the readable and writable +// stream classes. When you write(chunk), that calls _write(chunk,cb) +// internally, and returns false if there's a lot of pending writes +// buffered up. When you call read(), that calls _read(n) until +// there's enough pending readable data buffered up. +// +// In a transform stream, the written data is placed in a buffer. When +// _read(n) is called, it transforms the queued up data, calling the +// buffered _write cb's as it consumes chunks. If consuming a single +// written chunk would result in multiple output chunks, then the first +// outputted bit calls the readcb, and subsequent chunks just go into +// the read buffer, and will cause it to emit 'readable' if necessary. +// +// This way, back-pressure is actually determined by the reading side, +// since _read has to be called to start processing a new chunk. However, +// a pathological inflate type of transform can cause excessive buffering +// here. For example, imagine a stream where every byte of input is +// interpreted as an integer from 0-255, and then results in that many +// bytes of output. Writing the 4 bytes {ff,ff,ff,ff} would result in +// 1kb of data being output. In this case, you could write a very small +// amount of input, and end up with a very large amount of output. In +// such a pathological inflating mechanism, there'd be no way to tell +// the system to stop doing the transform. A single 4MB write could +// cause the system to run out of memory. +// +// However, even in such a pathological case, only a single written chunk +// would be consumed, and then the rest would wait (un-transformed) until +// the results of the previous transformed chunk were consumed. + +'use strict'; + +module.exports = Transform; + +var Duplex = require('./_stream_duplex'); + +/**/ +var util = Object.create(require('core-util-is')); +util.inherits = require('inherits'); +/**/ + +util.inherits(Transform, Duplex); + +function afterTransform(er, data) { + var ts = this._transformState; + ts.transforming = false; + + var cb = ts.writecb; + + if (!cb) { + return this.emit('error', new Error('write callback called multiple times')); + } + + ts.writechunk = null; + ts.writecb = null; + + if (data != null) // single equals check for both `null` and `undefined` + this.push(data); + + cb(er); + + var rs = this._readableState; + rs.reading = false; + if (rs.needReadable || rs.length < rs.highWaterMark) { + this._read(rs.highWaterMark); + } +} + +function Transform(options) { + if (!(this instanceof Transform)) return new Transform(options); + + Duplex.call(this, options); + + this._transformState = { + afterTransform: afterTransform.bind(this), + needTransform: false, + transforming: false, + writecb: null, + writechunk: null, + writeencoding: null + }; + + // start out asking for a readable event once data is transformed. + this._readableState.needReadable = true; + + // we have implemented the _read method, and done the other things + // that Readable wants before the first _read call, so unset the + // sync guard flag. + this._readableState.sync = false; + + if (options) { + if (typeof options.transform === 'function') this._transform = options.transform; + + if (typeof options.flush === 'function') this._flush = options.flush; + } + + // When the writable side finishes, then flush out anything remaining. + this.on('prefinish', prefinish); +} + +function prefinish() { + var _this = this; + + if (typeof this._flush === 'function') { + this._flush(function (er, data) { + done(_this, er, data); + }); + } else { + done(this, null, null); + } +} + +Transform.prototype.push = function (chunk, encoding) { + this._transformState.needTransform = false; + return Duplex.prototype.push.call(this, chunk, encoding); +}; + +// This is the part where you do stuff! +// override this function in implementation classes. +// 'chunk' is an input chunk. +// +// Call `push(newChunk)` to pass along transformed output +// to the readable side. You may call 'push' zero or more times. +// +// Call `cb(err)` when you are done with this chunk. If you pass +// an error, then that'll put the hurt on the whole operation. If you +// never call cb(), then you'll never get another chunk. +Transform.prototype._transform = function (chunk, encoding, cb) { + throw new Error('_transform() is not implemented'); +}; + +Transform.prototype._write = function (chunk, encoding, cb) { + var ts = this._transformState; + ts.writecb = cb; + ts.writechunk = chunk; + ts.writeencoding = encoding; + if (!ts.transforming) { + var rs = this._readableState; + if (ts.needTransform || rs.needReadable || rs.length < rs.highWaterMark) this._read(rs.highWaterMark); + } +}; + +// Doesn't matter what the args are here. +// _transform does all the work. +// That we got here means that the readable side wants more data. +Transform.prototype._read = function (n) { + var ts = this._transformState; + + if (ts.writechunk !== null && ts.writecb && !ts.transforming) { + ts.transforming = true; + this._transform(ts.writechunk, ts.writeencoding, ts.afterTransform); + } else { + // mark that we need a transform, so that any data that comes in + // will get processed, now that we've asked for it. + ts.needTransform = true; + } +}; + +Transform.prototype._destroy = function (err, cb) { + var _this2 = this; + + Duplex.prototype._destroy.call(this, err, function (err2) { + cb(err2); + _this2.emit('close'); + }); +}; + +function done(stream, er, data) { + if (er) return stream.emit('error', er); + + if (data != null) // single equals check for both `null` and `undefined` + stream.push(data); + + // if there's nothing in the write buffer, then that means + // that nothing more will ever be provided + if (stream._writableState.length) throw new Error('Calling transform done when ws.length != 0'); + + if (stream._transformState.transforming) throw new Error('Calling transform done when still transforming'); + + return stream.push(null); +} \ No newline at end of file diff --git a/node_modules/readable-stream/lib/_stream_writable.js b/node_modules/readable-stream/lib/_stream_writable.js new file mode 100644 index 0000000..e1e897f --- /dev/null +++ b/node_modules/readable-stream/lib/_stream_writable.js @@ -0,0 +1,685 @@ +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +// A bit simpler than readable streams. +// Implement an async ._write(chunk, encoding, cb), and it'll handle all +// the drain event emission and buffering. + +'use strict'; + +/**/ + +var pna = require('process-nextick-args'); +/**/ + +module.exports = Writable; + +/* */ +function WriteReq(chunk, encoding, cb) { + this.chunk = chunk; + this.encoding = encoding; + this.callback = cb; + this.next = null; +} + +// It seems a linked list but it is not +// there will be only 2 of these for each stream +function CorkedRequest(state) { + var _this = this; + + this.next = null; + this.entry = null; + this.finish = function () { + onCorkedFinish(_this, state); + }; +} +/* */ + +/**/ +var asyncWrite = !process.browser && ['v0.10', 'v0.9.'].indexOf(process.version.slice(0, 5)) > -1 ? setImmediate : pna.nextTick; +/**/ + +/**/ +var Duplex; +/**/ + +Writable.WritableState = WritableState; + +/**/ +var util = Object.create(require('core-util-is')); +util.inherits = require('inherits'); +/**/ + +/**/ +var internalUtil = { + deprecate: require('util-deprecate') +}; +/**/ + +/**/ +var Stream = require('./internal/streams/stream'); +/**/ + +/**/ + +var Buffer = require('safe-buffer').Buffer; +var OurUint8Array = (typeof global !== 'undefined' ? global : typeof window !== 'undefined' ? window : typeof self !== 'undefined' ? self : {}).Uint8Array || function () {}; +function _uint8ArrayToBuffer(chunk) { + return Buffer.from(chunk); +} +function _isUint8Array(obj) { + return Buffer.isBuffer(obj) || obj instanceof OurUint8Array; +} + +/**/ + +var destroyImpl = require('./internal/streams/destroy'); + +util.inherits(Writable, Stream); + +function nop() {} + +function WritableState(options, stream) { + Duplex = Duplex || require('./_stream_duplex'); + + options = options || {}; + + // Duplex streams are both readable and writable, but share + // the same options object. + // However, some cases require setting options to different + // values for the readable and the writable sides of the duplex stream. + // These options can be provided separately as readableXXX and writableXXX. + var isDuplex = stream instanceof Duplex; + + // object stream flag to indicate whether or not this stream + // contains buffers or objects. + this.objectMode = !!options.objectMode; + + if (isDuplex) this.objectMode = this.objectMode || !!options.writableObjectMode; + + // the point at which write() starts returning false + // Note: 0 is a valid value, means that we always return false if + // the entire buffer is not flushed immediately on write() + var hwm = options.highWaterMark; + var writableHwm = options.writableHighWaterMark; + var defaultHwm = this.objectMode ? 16 : 16 * 1024; + + if (hwm || hwm === 0) this.highWaterMark = hwm;else if (isDuplex && (writableHwm || writableHwm === 0)) this.highWaterMark = writableHwm;else this.highWaterMark = defaultHwm; + + // cast to ints. + this.highWaterMark = Math.floor(this.highWaterMark); + + // if _final has been called + this.finalCalled = false; + + // drain event flag. + this.needDrain = false; + // at the start of calling end() + this.ending = false; + // when end() has been called, and returned + this.ended = false; + // when 'finish' is emitted + this.finished = false; + + // has it been destroyed + this.destroyed = false; + + // should we decode strings into buffers before passing to _write? + // this is here so that some node-core streams can optimize string + // handling at a lower level. + var noDecode = options.decodeStrings === false; + this.decodeStrings = !noDecode; + + // Crypto is kind of old and crusty. Historically, its default string + // encoding is 'binary' so we have to make this configurable. + // Everything else in the universe uses 'utf8', though. + this.defaultEncoding = options.defaultEncoding || 'utf8'; + + // not an actual buffer we keep track of, but a measurement + // of how much we're waiting to get pushed to some underlying + // socket or file. + this.length = 0; + + // a flag to see when we're in the middle of a write. + this.writing = false; + + // when true all writes will be buffered until .uncork() call + this.corked = 0; + + // a flag to be able to tell if the onwrite cb is called immediately, + // or on a later tick. We set this to true at first, because any + // actions that shouldn't happen until "later" should generally also + // not happen before the first write call. + this.sync = true; + + // a flag to know if we're processing previously buffered items, which + // may call the _write() callback in the same tick, so that we don't + // end up in an overlapped onwrite situation. + this.bufferProcessing = false; + + // the callback that's passed to _write(chunk,cb) + this.onwrite = function (er) { + onwrite(stream, er); + }; + + // the callback that the user supplies to write(chunk,encoding,cb) + this.writecb = null; + + // the amount that is being written when _write is called. + this.writelen = 0; + + this.bufferedRequest = null; + this.lastBufferedRequest = null; + + // number of pending user-supplied write callbacks + // this must be 0 before 'finish' can be emitted + this.pendingcb = 0; + + // emit prefinish if the only thing we're waiting for is _write cbs + // This is relevant for synchronous Transform streams + this.prefinished = false; + + // True if the error was already emitted and should not be thrown again + this.errorEmitted = false; + + // count buffered requests + this.bufferedRequestCount = 0; + + // allocate the first CorkedRequest, there is always + // one allocated and free to use, and we maintain at most two + this.corkedRequestsFree = new CorkedRequest(this); +} + +WritableState.prototype.getBuffer = function getBuffer() { + var current = this.bufferedRequest; + var out = []; + while (current) { + out.push(current); + current = current.next; + } + return out; +}; + +(function () { + try { + Object.defineProperty(WritableState.prototype, 'buffer', { + get: internalUtil.deprecate(function () { + return this.getBuffer(); + }, '_writableState.buffer is deprecated. Use _writableState.getBuffer ' + 'instead.', 'DEP0003') + }); + } catch (_) {} +})(); + +// Test _writableState for inheritance to account for Duplex streams, +// whose prototype chain only points to Readable. +var realHasInstance; +if (typeof Symbol === 'function' && Symbol.hasInstance && typeof Function.prototype[Symbol.hasInstance] === 'function') { + realHasInstance = Function.prototype[Symbol.hasInstance]; + Object.defineProperty(Writable, Symbol.hasInstance, { + value: function (object) { + if (realHasInstance.call(this, object)) return true; + if (this !== Writable) return false; + + return object && object._writableState instanceof WritableState; + } + }); +} else { + realHasInstance = function (object) { + return object instanceof this; + }; +} + +function Writable(options) { + Duplex = Duplex || require('./_stream_duplex'); + + // Writable ctor is applied to Duplexes, too. + // `realHasInstance` is necessary because using plain `instanceof` + // would return false, as no `_writableState` property is attached. + + // Trying to use the custom `instanceof` for Writable here will also break the + // Node.js LazyTransform implementation, which has a non-trivial getter for + // `_writableState` that would lead to infinite recursion. + if (!realHasInstance.call(Writable, this) && !(this instanceof Duplex)) { + return new Writable(options); + } + + this._writableState = new WritableState(options, this); + + // legacy. + this.writable = true; + + if (options) { + if (typeof options.write === 'function') this._write = options.write; + + if (typeof options.writev === 'function') this._writev = options.writev; + + if (typeof options.destroy === 'function') this._destroy = options.destroy; + + if (typeof options.final === 'function') this._final = options.final; + } + + Stream.call(this); +} + +// Otherwise people can pipe Writable streams, which is just wrong. +Writable.prototype.pipe = function () { + this.emit('error', new Error('Cannot pipe, not readable')); +}; + +function writeAfterEnd(stream, cb) { + var er = new Error('write after end'); + // TODO: defer error events consistently everywhere, not just the cb + stream.emit('error', er); + pna.nextTick(cb, er); +} + +// Checks that a user-supplied chunk is valid, especially for the particular +// mode the stream is in. Currently this means that `null` is never accepted +// and undefined/non-string values are only allowed in object mode. +function validChunk(stream, state, chunk, cb) { + var valid = true; + var er = false; + + if (chunk === null) { + er = new TypeError('May not write null values to stream'); + } else if (typeof chunk !== 'string' && chunk !== undefined && !state.objectMode) { + er = new TypeError('Invalid non-string/buffer chunk'); + } + if (er) { + stream.emit('error', er); + pna.nextTick(cb, er); + valid = false; + } + return valid; +} + +Writable.prototype.write = function (chunk, encoding, cb) { + var state = this._writableState; + var ret = false; + var isBuf = !state.objectMode && _isUint8Array(chunk); + + if (isBuf && !Buffer.isBuffer(chunk)) { + chunk = _uint8ArrayToBuffer(chunk); + } + + if (typeof encoding === 'function') { + cb = encoding; + encoding = null; + } + + if (isBuf) encoding = 'buffer';else if (!encoding) encoding = state.defaultEncoding; + + if (typeof cb !== 'function') cb = nop; + + if (state.ended) writeAfterEnd(this, cb);else if (isBuf || validChunk(this, state, chunk, cb)) { + state.pendingcb++; + ret = writeOrBuffer(this, state, isBuf, chunk, encoding, cb); + } + + return ret; +}; + +Writable.prototype.cork = function () { + var state = this._writableState; + + state.corked++; +}; + +Writable.prototype.uncork = function () { + var state = this._writableState; + + if (state.corked) { + state.corked--; + + if (!state.writing && !state.corked && !state.bufferProcessing && state.bufferedRequest) clearBuffer(this, state); + } +}; + +Writable.prototype.setDefaultEncoding = function setDefaultEncoding(encoding) { + // node::ParseEncoding() requires lower case. + if (typeof encoding === 'string') encoding = encoding.toLowerCase(); + if (!(['hex', 'utf8', 'utf-8', 'ascii', 'binary', 'base64', 'ucs2', 'ucs-2', 'utf16le', 'utf-16le', 'raw'].indexOf((encoding + '').toLowerCase()) > -1)) throw new TypeError('Unknown encoding: ' + encoding); + this._writableState.defaultEncoding = encoding; + return this; +}; + +function decodeChunk(state, chunk, encoding) { + if (!state.objectMode && state.decodeStrings !== false && typeof chunk === 'string') { + chunk = Buffer.from(chunk, encoding); + } + return chunk; +} + +Object.defineProperty(Writable.prototype, 'writableHighWaterMark', { + // making it explicit this property is not enumerable + // because otherwise some prototype manipulation in + // userland will fail + enumerable: false, + get: function () { + return this._writableState.highWaterMark; + } +}); + +// if we're already writing something, then just put this +// in the queue, and wait our turn. Otherwise, call _write +// If we return false, then we need a drain event, so set that flag. +function writeOrBuffer(stream, state, isBuf, chunk, encoding, cb) { + if (!isBuf) { + var newChunk = decodeChunk(state, chunk, encoding); + if (chunk !== newChunk) { + isBuf = true; + encoding = 'buffer'; + chunk = newChunk; + } + } + var len = state.objectMode ? 1 : chunk.length; + + state.length += len; + + var ret = state.length < state.highWaterMark; + // we must ensure that previous needDrain will not be reset to false. + if (!ret) state.needDrain = true; + + if (state.writing || state.corked) { + var last = state.lastBufferedRequest; + state.lastBufferedRequest = { + chunk: chunk, + encoding: encoding, + isBuf: isBuf, + callback: cb, + next: null + }; + if (last) { + last.next = state.lastBufferedRequest; + } else { + state.bufferedRequest = state.lastBufferedRequest; + } + state.bufferedRequestCount += 1; + } else { + doWrite(stream, state, false, len, chunk, encoding, cb); + } + + return ret; +} + +function doWrite(stream, state, writev, len, chunk, encoding, cb) { + state.writelen = len; + state.writecb = cb; + state.writing = true; + state.sync = true; + if (writev) stream._writev(chunk, state.onwrite);else stream._write(chunk, encoding, state.onwrite); + state.sync = false; +} + +function onwriteError(stream, state, sync, er, cb) { + --state.pendingcb; + + if (sync) { + // defer the callback if we are being called synchronously + // to avoid piling up things on the stack + pna.nextTick(cb, er); + // this can emit finish, and it will always happen + // after error + pna.nextTick(finishMaybe, stream, state); + stream._writableState.errorEmitted = true; + stream.emit('error', er); + } else { + // the caller expect this to happen before if + // it is async + cb(er); + stream._writableState.errorEmitted = true; + stream.emit('error', er); + // this can emit finish, but finish must + // always follow error + finishMaybe(stream, state); + } +} + +function onwriteStateUpdate(state) { + state.writing = false; + state.writecb = null; + state.length -= state.writelen; + state.writelen = 0; +} + +function onwrite(stream, er) { + var state = stream._writableState; + var sync = state.sync; + var cb = state.writecb; + + onwriteStateUpdate(state); + + if (er) onwriteError(stream, state, sync, er, cb);else { + // Check if we're actually ready to finish, but don't emit yet + var finished = needFinish(state); + + if (!finished && !state.corked && !state.bufferProcessing && state.bufferedRequest) { + clearBuffer(stream, state); + } + + if (sync) { + /**/ + asyncWrite(afterWrite, stream, state, finished, cb); + /**/ + } else { + afterWrite(stream, state, finished, cb); + } + } +} + +function afterWrite(stream, state, finished, cb) { + if (!finished) onwriteDrain(stream, state); + state.pendingcb--; + cb(); + finishMaybe(stream, state); +} + +// Must force callback to be called on nextTick, so that we don't +// emit 'drain' before the write() consumer gets the 'false' return +// value, and has a chance to attach a 'drain' listener. +function onwriteDrain(stream, state) { + if (state.length === 0 && state.needDrain) { + state.needDrain = false; + stream.emit('drain'); + } +} + +// if there's something in the buffer waiting, then process it +function clearBuffer(stream, state) { + state.bufferProcessing = true; + var entry = state.bufferedRequest; + + if (stream._writev && entry && entry.next) { + // Fast case, write everything using _writev() + var l = state.bufferedRequestCount; + var buffer = new Array(l); + var holder = state.corkedRequestsFree; + holder.entry = entry; + + var count = 0; + var allBuffers = true; + while (entry) { + buffer[count] = entry; + if (!entry.isBuf) allBuffers = false; + entry = entry.next; + count += 1; + } + buffer.allBuffers = allBuffers; + + doWrite(stream, state, true, state.length, buffer, '', holder.finish); + + // doWrite is almost always async, defer these to save a bit of time + // as the hot path ends with doWrite + state.pendingcb++; + state.lastBufferedRequest = null; + if (holder.next) { + state.corkedRequestsFree = holder.next; + holder.next = null; + } else { + state.corkedRequestsFree = new CorkedRequest(state); + } + state.bufferedRequestCount = 0; + } else { + // Slow case, write chunks one-by-one + while (entry) { + var chunk = entry.chunk; + var encoding = entry.encoding; + var cb = entry.callback; + var len = state.objectMode ? 1 : chunk.length; + + doWrite(stream, state, false, len, chunk, encoding, cb); + entry = entry.next; + state.bufferedRequestCount--; + // if we didn't call the onwrite immediately, then + // it means that we need to wait until it does. + // also, that means that the chunk and cb are currently + // being processed, so move the buffer counter past them. + if (state.writing) { + break; + } + } + + if (entry === null) state.lastBufferedRequest = null; + } + + state.bufferedRequest = entry; + state.bufferProcessing = false; +} + +Writable.prototype._write = function (chunk, encoding, cb) { + cb(new Error('_write() is not implemented')); +}; + +Writable.prototype._writev = null; + +Writable.prototype.end = function (chunk, encoding, cb) { + var state = this._writableState; + + if (typeof chunk === 'function') { + cb = chunk; + chunk = null; + encoding = null; + } else if (typeof encoding === 'function') { + cb = encoding; + encoding = null; + } + + if (chunk !== null && chunk !== undefined) this.write(chunk, encoding); + + // .end() fully uncorks + if (state.corked) { + state.corked = 1; + this.uncork(); + } + + // ignore unnecessary end() calls. + if (!state.ending) endWritable(this, state, cb); +}; + +function needFinish(state) { + return state.ending && state.length === 0 && state.bufferedRequest === null && !state.finished && !state.writing; +} +function callFinal(stream, state) { + stream._final(function (err) { + state.pendingcb--; + if (err) { + stream.emit('error', err); + } + state.prefinished = true; + stream.emit('prefinish'); + finishMaybe(stream, state); + }); +} +function prefinish(stream, state) { + if (!state.prefinished && !state.finalCalled) { + if (typeof stream._final === 'function') { + state.pendingcb++; + state.finalCalled = true; + pna.nextTick(callFinal, stream, state); + } else { + state.prefinished = true; + stream.emit('prefinish'); + } + } +} + +function finishMaybe(stream, state) { + var need = needFinish(state); + if (need) { + prefinish(stream, state); + if (state.pendingcb === 0) { + state.finished = true; + stream.emit('finish'); + } + } + return need; +} + +function endWritable(stream, state, cb) { + state.ending = true; + finishMaybe(stream, state); + if (cb) { + if (state.finished) pna.nextTick(cb);else stream.once('finish', cb); + } + state.ended = true; + stream.writable = false; +} + +function onCorkedFinish(corkReq, state, err) { + var entry = corkReq.entry; + corkReq.entry = null; + while (entry) { + var cb = entry.callback; + state.pendingcb--; + cb(err); + entry = entry.next; + } + + // reuse the free corkReq. + state.corkedRequestsFree.next = corkReq; +} + +Object.defineProperty(Writable.prototype, 'destroyed', { + get: function () { + if (this._writableState === undefined) { + return false; + } + return this._writableState.destroyed; + }, + set: function (value) { + // we ignore the value if the stream + // has not been initialized yet + if (!this._writableState) { + return; + } + + // backward compatibility, the user is explicitly + // managing destroyed + this._writableState.destroyed = value; + } +}); + +Writable.prototype.destroy = destroyImpl.destroy; +Writable.prototype._undestroy = destroyImpl.undestroy; +Writable.prototype._destroy = function (err, cb) { + this.end(); + cb(err); +}; \ No newline at end of file diff --git a/node_modules/readable-stream/lib/internal/streams/BufferList.js b/node_modules/readable-stream/lib/internal/streams/BufferList.js new file mode 100644 index 0000000..5e08097 --- /dev/null +++ b/node_modules/readable-stream/lib/internal/streams/BufferList.js @@ -0,0 +1,78 @@ +'use strict'; + +function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } + +var Buffer = require('safe-buffer').Buffer; +var util = require('util'); + +function copyBuffer(src, target, offset) { + src.copy(target, offset); +} + +module.exports = function () { + function BufferList() { + _classCallCheck(this, BufferList); + + this.head = null; + this.tail = null; + this.length = 0; + } + + BufferList.prototype.push = function push(v) { + var entry = { data: v, next: null }; + if (this.length > 0) this.tail.next = entry;else this.head = entry; + this.tail = entry; + ++this.length; + }; + + BufferList.prototype.unshift = function unshift(v) { + var entry = { data: v, next: this.head }; + if (this.length === 0) this.tail = entry; + this.head = entry; + ++this.length; + }; + + BufferList.prototype.shift = function shift() { + if (this.length === 0) return; + var ret = this.head.data; + if (this.length === 1) this.head = this.tail = null;else this.head = this.head.next; + --this.length; + return ret; + }; + + BufferList.prototype.clear = function clear() { + this.head = this.tail = null; + this.length = 0; + }; + + BufferList.prototype.join = function join(s) { + if (this.length === 0) return ''; + var p = this.head; + var ret = '' + p.data; + while (p = p.next) { + ret += s + p.data; + }return ret; + }; + + BufferList.prototype.concat = function concat(n) { + if (this.length === 0) return Buffer.alloc(0); + var ret = Buffer.allocUnsafe(n >>> 0); + var p = this.head; + var i = 0; + while (p) { + copyBuffer(p.data, ret, i); + i += p.data.length; + p = p.next; + } + return ret; + }; + + return BufferList; +}(); + +if (util && util.inspect && util.inspect.custom) { + module.exports.prototype[util.inspect.custom] = function () { + var obj = util.inspect({ length: this.length }); + return this.constructor.name + ' ' + obj; + }; +} \ No newline at end of file diff --git a/node_modules/readable-stream/lib/internal/streams/destroy.js b/node_modules/readable-stream/lib/internal/streams/destroy.js new file mode 100644 index 0000000..85a8214 --- /dev/null +++ b/node_modules/readable-stream/lib/internal/streams/destroy.js @@ -0,0 +1,84 @@ +'use strict'; + +/**/ + +var pna = require('process-nextick-args'); +/**/ + +// undocumented cb() API, needed for core, not for public API +function destroy(err, cb) { + var _this = this; + + var readableDestroyed = this._readableState && this._readableState.destroyed; + var writableDestroyed = this._writableState && this._writableState.destroyed; + + if (readableDestroyed || writableDestroyed) { + if (cb) { + cb(err); + } else if (err) { + if (!this._writableState) { + pna.nextTick(emitErrorNT, this, err); + } else if (!this._writableState.errorEmitted) { + this._writableState.errorEmitted = true; + pna.nextTick(emitErrorNT, this, err); + } + } + + return this; + } + + // we set destroyed to true before firing error callbacks in order + // to make it re-entrance safe in case destroy() is called within callbacks + + if (this._readableState) { + this._readableState.destroyed = true; + } + + // if this is a duplex stream mark the writable part as destroyed as well + if (this._writableState) { + this._writableState.destroyed = true; + } + + this._destroy(err || null, function (err) { + if (!cb && err) { + if (!_this._writableState) { + pna.nextTick(emitErrorNT, _this, err); + } else if (!_this._writableState.errorEmitted) { + _this._writableState.errorEmitted = true; + pna.nextTick(emitErrorNT, _this, err); + } + } else if (cb) { + cb(err); + } + }); + + return this; +} + +function undestroy() { + if (this._readableState) { + this._readableState.destroyed = false; + this._readableState.reading = false; + this._readableState.ended = false; + this._readableState.endEmitted = false; + } + + if (this._writableState) { + this._writableState.destroyed = false; + this._writableState.ended = false; + this._writableState.ending = false; + this._writableState.finalCalled = false; + this._writableState.prefinished = false; + this._writableState.finished = false; + this._writableState.errorEmitted = false; + } +} + +function emitErrorNT(self, err) { + self.emit('error', err); +} + +module.exports = { + destroy: destroy, + undestroy: undestroy +}; \ No newline at end of file diff --git a/node_modules/readable-stream/lib/internal/streams/stream-browser.js b/node_modules/readable-stream/lib/internal/streams/stream-browser.js new file mode 100644 index 0000000..9332a3f --- /dev/null +++ b/node_modules/readable-stream/lib/internal/streams/stream-browser.js @@ -0,0 +1 @@ +module.exports = require('events').EventEmitter; diff --git a/node_modules/readable-stream/lib/internal/streams/stream.js b/node_modules/readable-stream/lib/internal/streams/stream.js new file mode 100644 index 0000000..ce2ad5b --- /dev/null +++ b/node_modules/readable-stream/lib/internal/streams/stream.js @@ -0,0 +1 @@ +module.exports = require('stream'); diff --git a/node_modules/readable-stream/package.json b/node_modules/readable-stream/package.json new file mode 100644 index 0000000..514c178 --- /dev/null +++ b/node_modules/readable-stream/package.json @@ -0,0 +1,52 @@ +{ + "name": "readable-stream", + "version": "2.3.8", + "description": "Streams3, a user-land copy of the stream library from Node.js", + "main": "readable.js", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + }, + "devDependencies": { + "assert": "^1.4.0", + "babel-polyfill": "^6.9.1", + "buffer": "^4.9.0", + "lolex": "^2.3.2", + "nyc": "^6.4.0", + "tap": "^0.7.0", + "tape": "^4.8.0" + }, + "scripts": { + "test": "tap test/parallel/*.js test/ours/*.js && node test/verify-dependencies.js", + "ci": "tap test/parallel/*.js test/ours/*.js --tap | tee test.tap && node test/verify-dependencies.js", + "cover": "nyc npm test", + "report": "nyc report --reporter=lcov" + }, + "repository": { + "type": "git", + "url": "git://github.com/nodejs/readable-stream" + }, + "keywords": [ + "readable", + "stream", + "pipe" + ], + "browser": { + "util": false, + "./readable.js": "./readable-browser.js", + "./writable.js": "./writable-browser.js", + "./duplex.js": "./duplex-browser.js", + "./lib/internal/streams/stream.js": "./lib/internal/streams/stream-browser.js" + }, + "nyc": { + "include": [ + "lib/**.js" + ] + }, + "license": "MIT" +} diff --git a/node_modules/readable-stream/passthrough.js b/node_modules/readable-stream/passthrough.js new file mode 100644 index 0000000..ffd791d --- /dev/null +++ b/node_modules/readable-stream/passthrough.js @@ -0,0 +1 @@ +module.exports = require('./readable').PassThrough diff --git a/node_modules/readable-stream/readable-browser.js b/node_modules/readable-stream/readable-browser.js new file mode 100644 index 0000000..e503725 --- /dev/null +++ b/node_modules/readable-stream/readable-browser.js @@ -0,0 +1,7 @@ +exports = module.exports = require('./lib/_stream_readable.js'); +exports.Stream = exports; +exports.Readable = exports; +exports.Writable = require('./lib/_stream_writable.js'); +exports.Duplex = require('./lib/_stream_duplex.js'); +exports.Transform = require('./lib/_stream_transform.js'); +exports.PassThrough = require('./lib/_stream_passthrough.js'); diff --git a/node_modules/readable-stream/readable.js b/node_modules/readable-stream/readable.js new file mode 100644 index 0000000..ec89ec5 --- /dev/null +++ b/node_modules/readable-stream/readable.js @@ -0,0 +1,19 @@ +var Stream = require('stream'); +if (process.env.READABLE_STREAM === 'disable' && Stream) { + module.exports = Stream; + exports = module.exports = Stream.Readable; + exports.Readable = Stream.Readable; + exports.Writable = Stream.Writable; + exports.Duplex = Stream.Duplex; + exports.Transform = Stream.Transform; + exports.PassThrough = Stream.PassThrough; + exports.Stream = Stream; +} else { + exports = module.exports = require('./lib/_stream_readable.js'); + exports.Stream = Stream || exports; + exports.Readable = exports; + exports.Writable = require('./lib/_stream_writable.js'); + exports.Duplex = require('./lib/_stream_duplex.js'); + exports.Transform = require('./lib/_stream_transform.js'); + exports.PassThrough = require('./lib/_stream_passthrough.js'); +} diff --git a/node_modules/readable-stream/transform.js b/node_modules/readable-stream/transform.js new file mode 100644 index 0000000..b1baba2 --- /dev/null +++ b/node_modules/readable-stream/transform.js @@ -0,0 +1 @@ +module.exports = require('./readable').Transform diff --git a/node_modules/readable-stream/writable-browser.js b/node_modules/readable-stream/writable-browser.js new file mode 100644 index 0000000..ebdde6a --- /dev/null +++ b/node_modules/readable-stream/writable-browser.js @@ -0,0 +1 @@ +module.exports = require('./lib/_stream_writable.js'); diff --git a/node_modules/readable-stream/writable.js b/node_modules/readable-stream/writable.js new file mode 100644 index 0000000..3211a6f --- /dev/null +++ b/node_modules/readable-stream/writable.js @@ -0,0 +1,8 @@ +var Stream = require("stream") +var Writable = require("./lib/_stream_writable.js") + +if (process.env.READABLE_STREAM === 'disable') { + module.exports = Stream && Stream.Writable || Writable +} else { + module.exports = Writable +} diff --git a/node_modules/safe-buffer/LICENSE b/node_modules/safe-buffer/LICENSE new file mode 100644 index 0000000..0c068ce --- /dev/null +++ b/node_modules/safe-buffer/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) Feross Aboukhadijeh + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/node_modules/safe-buffer/README.md b/node_modules/safe-buffer/README.md new file mode 100644 index 0000000..e9a81af --- /dev/null +++ b/node_modules/safe-buffer/README.md @@ -0,0 +1,584 @@ +# safe-buffer [![travis][travis-image]][travis-url] [![npm][npm-image]][npm-url] [![downloads][downloads-image]][downloads-url] [![javascript style guide][standard-image]][standard-url] + +[travis-image]: https://img.shields.io/travis/feross/safe-buffer/master.svg +[travis-url]: https://travis-ci.org/feross/safe-buffer +[npm-image]: https://img.shields.io/npm/v/safe-buffer.svg +[npm-url]: https://npmjs.org/package/safe-buffer +[downloads-image]: https://img.shields.io/npm/dm/safe-buffer.svg +[downloads-url]: https://npmjs.org/package/safe-buffer +[standard-image]: https://img.shields.io/badge/code_style-standard-brightgreen.svg +[standard-url]: https://standardjs.com + +#### Safer Node.js Buffer API + +**Use the new Node.js Buffer APIs (`Buffer.from`, `Buffer.alloc`, +`Buffer.allocUnsafe`, `Buffer.allocUnsafeSlow`) in all versions of Node.js.** + +**Uses the built-in implementation when available.** + +## install + +``` +npm install safe-buffer +``` + +## usage + +The goal of this package is to provide a safe replacement for the node.js `Buffer`. + +It's a drop-in replacement for `Buffer`. You can use it by adding one `require` line to +the top of your node.js modules: + +```js +var Buffer = require('safe-buffer').Buffer + +// Existing buffer code will continue to work without issues: + +new Buffer('hey', 'utf8') +new Buffer([1, 2, 3], 'utf8') +new Buffer(obj) +new Buffer(16) // create an uninitialized buffer (potentially unsafe) + +// But you can use these new explicit APIs to make clear what you want: + +Buffer.from('hey', 'utf8') // convert from many types to a Buffer +Buffer.alloc(16) // create a zero-filled buffer (safe) +Buffer.allocUnsafe(16) // create an uninitialized buffer (potentially unsafe) +``` + +## api + +### Class Method: Buffer.from(array) + + +* `array` {Array} + +Allocates a new `Buffer` using an `array` of octets. + +```js +const buf = Buffer.from([0x62,0x75,0x66,0x66,0x65,0x72]); + // creates a new Buffer containing ASCII bytes + // ['b','u','f','f','e','r'] +``` + +A `TypeError` will be thrown if `array` is not an `Array`. + +### Class Method: Buffer.from(arrayBuffer[, byteOffset[, length]]) + + +* `arrayBuffer` {ArrayBuffer} The `.buffer` property of a `TypedArray` or + a `new ArrayBuffer()` +* `byteOffset` {Number} Default: `0` +* `length` {Number} Default: `arrayBuffer.length - byteOffset` + +When passed a reference to the `.buffer` property of a `TypedArray` instance, +the newly created `Buffer` will share the same allocated memory as the +TypedArray. + +```js +const arr = new Uint16Array(2); +arr[0] = 5000; +arr[1] = 4000; + +const buf = Buffer.from(arr.buffer); // shares the memory with arr; + +console.log(buf); + // Prints: + +// changing the TypedArray changes the Buffer also +arr[1] = 6000; + +console.log(buf); + // Prints: +``` + +The optional `byteOffset` and `length` arguments specify a memory range within +the `arrayBuffer` that will be shared by the `Buffer`. + +```js +const ab = new ArrayBuffer(10); +const buf = Buffer.from(ab, 0, 2); +console.log(buf.length); + // Prints: 2 +``` + +A `TypeError` will be thrown if `arrayBuffer` is not an `ArrayBuffer`. + +### Class Method: Buffer.from(buffer) + + +* `buffer` {Buffer} + +Copies the passed `buffer` data onto a new `Buffer` instance. + +```js +const buf1 = Buffer.from('buffer'); +const buf2 = Buffer.from(buf1); + +buf1[0] = 0x61; +console.log(buf1.toString()); + // 'auffer' +console.log(buf2.toString()); + // 'buffer' (copy is not changed) +``` + +A `TypeError` will be thrown if `buffer` is not a `Buffer`. + +### Class Method: Buffer.from(str[, encoding]) + + +* `str` {String} String to encode. +* `encoding` {String} Encoding to use, Default: `'utf8'` + +Creates a new `Buffer` containing the given JavaScript string `str`. If +provided, the `encoding` parameter identifies the character encoding. +If not provided, `encoding` defaults to `'utf8'`. + +```js +const buf1 = Buffer.from('this is a tést'); +console.log(buf1.toString()); + // prints: this is a tést +console.log(buf1.toString('ascii')); + // prints: this is a tC)st + +const buf2 = Buffer.from('7468697320697320612074c3a97374', 'hex'); +console.log(buf2.toString()); + // prints: this is a tést +``` + +A `TypeError` will be thrown if `str` is not a string. + +### Class Method: Buffer.alloc(size[, fill[, encoding]]) + + +* `size` {Number} +* `fill` {Value} Default: `undefined` +* `encoding` {String} Default: `utf8` + +Allocates a new `Buffer` of `size` bytes. If `fill` is `undefined`, the +`Buffer` will be *zero-filled*. + +```js +const buf = Buffer.alloc(5); +console.log(buf); + // +``` + +The `size` must be less than or equal to the value of +`require('buffer').kMaxLength` (on 64-bit architectures, `kMaxLength` is +`(2^31)-1`). Otherwise, a [`RangeError`][] is thrown. A zero-length Buffer will +be created if a `size` less than or equal to 0 is specified. + +If `fill` is specified, the allocated `Buffer` will be initialized by calling +`buf.fill(fill)`. See [`buf.fill()`][] for more information. + +```js +const buf = Buffer.alloc(5, 'a'); +console.log(buf); + // +``` + +If both `fill` and `encoding` are specified, the allocated `Buffer` will be +initialized by calling `buf.fill(fill, encoding)`. For example: + +```js +const buf = Buffer.alloc(11, 'aGVsbG8gd29ybGQ=', 'base64'); +console.log(buf); + // +``` + +Calling `Buffer.alloc(size)` can be significantly slower than the alternative +`Buffer.allocUnsafe(size)` but ensures that the newly created `Buffer` instance +contents will *never contain sensitive data*. + +A `TypeError` will be thrown if `size` is not a number. + +### Class Method: Buffer.allocUnsafe(size) + + +* `size` {Number} + +Allocates a new *non-zero-filled* `Buffer` of `size` bytes. The `size` must +be less than or equal to the value of `require('buffer').kMaxLength` (on 64-bit +architectures, `kMaxLength` is `(2^31)-1`). Otherwise, a [`RangeError`][] is +thrown. A zero-length Buffer will be created if a `size` less than or equal to +0 is specified. + +The underlying memory for `Buffer` instances created in this way is *not +initialized*. The contents of the newly created `Buffer` are unknown and +*may contain sensitive data*. Use [`buf.fill(0)`][] to initialize such +`Buffer` instances to zeroes. + +```js +const buf = Buffer.allocUnsafe(5); +console.log(buf); + // + // (octets will be different, every time) +buf.fill(0); +console.log(buf); + // +``` + +A `TypeError` will be thrown if `size` is not a number. + +Note that the `Buffer` module pre-allocates an internal `Buffer` instance of +size `Buffer.poolSize` that is used as a pool for the fast allocation of new +`Buffer` instances created using `Buffer.allocUnsafe(size)` (and the deprecated +`new Buffer(size)` constructor) only when `size` is less than or equal to +`Buffer.poolSize >> 1` (floor of `Buffer.poolSize` divided by two). The default +value of `Buffer.poolSize` is `8192` but can be modified. + +Use of this pre-allocated internal memory pool is a key difference between +calling `Buffer.alloc(size, fill)` vs. `Buffer.allocUnsafe(size).fill(fill)`. +Specifically, `Buffer.alloc(size, fill)` will *never* use the internal Buffer +pool, while `Buffer.allocUnsafe(size).fill(fill)` *will* use the internal +Buffer pool if `size` is less than or equal to half `Buffer.poolSize`. The +difference is subtle but can be important when an application requires the +additional performance that `Buffer.allocUnsafe(size)` provides. + +### Class Method: Buffer.allocUnsafeSlow(size) + + +* `size` {Number} + +Allocates a new *non-zero-filled* and non-pooled `Buffer` of `size` bytes. The +`size` must be less than or equal to the value of +`require('buffer').kMaxLength` (on 64-bit architectures, `kMaxLength` is +`(2^31)-1`). Otherwise, a [`RangeError`][] is thrown. A zero-length Buffer will +be created if a `size` less than or equal to 0 is specified. + +The underlying memory for `Buffer` instances created in this way is *not +initialized*. The contents of the newly created `Buffer` are unknown and +*may contain sensitive data*. Use [`buf.fill(0)`][] to initialize such +`Buffer` instances to zeroes. + +When using `Buffer.allocUnsafe()` to allocate new `Buffer` instances, +allocations under 4KB are, by default, sliced from a single pre-allocated +`Buffer`. This allows applications to avoid the garbage collection overhead of +creating many individually allocated Buffers. This approach improves both +performance and memory usage by eliminating the need to track and cleanup as +many `Persistent` objects. + +However, in the case where a developer may need to retain a small chunk of +memory from a pool for an indeterminate amount of time, it may be appropriate +to create an un-pooled Buffer instance using `Buffer.allocUnsafeSlow()` then +copy out the relevant bits. + +```js +// need to keep around a few small chunks of memory +const store = []; + +socket.on('readable', () => { + const data = socket.read(); + // allocate for retained data + const sb = Buffer.allocUnsafeSlow(10); + // copy the data into the new allocation + data.copy(sb, 0, 0, 10); + store.push(sb); +}); +``` + +Use of `Buffer.allocUnsafeSlow()` should be used only as a last resort *after* +a developer has observed undue memory retention in their applications. + +A `TypeError` will be thrown if `size` is not a number. + +### All the Rest + +The rest of the `Buffer` API is exactly the same as in node.js. +[See the docs](https://nodejs.org/api/buffer.html). + + +## Related links + +- [Node.js issue: Buffer(number) is unsafe](https://github.com/nodejs/node/issues/4660) +- [Node.js Enhancement Proposal: Buffer.from/Buffer.alloc/Buffer.zalloc/Buffer() soft-deprecate](https://github.com/nodejs/node-eps/pull/4) + +## Why is `Buffer` unsafe? + +Today, the node.js `Buffer` constructor is overloaded to handle many different argument +types like `String`, `Array`, `Object`, `TypedArrayView` (`Uint8Array`, etc.), +`ArrayBuffer`, and also `Number`. + +The API is optimized for convenience: you can throw any type at it, and it will try to do +what you want. + +Because the Buffer constructor is so powerful, you often see code like this: + +```js +// Convert UTF-8 strings to hex +function toHex (str) { + return new Buffer(str).toString('hex') +} +``` + +***But what happens if `toHex` is called with a `Number` argument?*** + +### Remote Memory Disclosure + +If an attacker can make your program call the `Buffer` constructor with a `Number` +argument, then they can make it allocate uninitialized memory from the node.js process. +This could potentially disclose TLS private keys, user data, or database passwords. + +When the `Buffer` constructor is passed a `Number` argument, it returns an +**UNINITIALIZED** block of memory of the specified `size`. When you create a `Buffer` like +this, you **MUST** overwrite the contents before returning it to the user. + +From the [node.js docs](https://nodejs.org/api/buffer.html#buffer_new_buffer_size): + +> `new Buffer(size)` +> +> - `size` Number +> +> The underlying memory for `Buffer` instances created in this way is not initialized. +> **The contents of a newly created `Buffer` are unknown and could contain sensitive +> data.** Use `buf.fill(0)` to initialize a Buffer to zeroes. + +(Emphasis our own.) + +Whenever the programmer intended to create an uninitialized `Buffer` you often see code +like this: + +```js +var buf = new Buffer(16) + +// Immediately overwrite the uninitialized buffer with data from another buffer +for (var i = 0; i < buf.length; i++) { + buf[i] = otherBuf[i] +} +``` + + +### Would this ever be a problem in real code? + +Yes. It's surprisingly common to forget to check the type of your variables in a +dynamically-typed language like JavaScript. + +Usually the consequences of assuming the wrong type is that your program crashes with an +uncaught exception. But the failure mode for forgetting to check the type of arguments to +the `Buffer` constructor is more catastrophic. + +Here's an example of a vulnerable service that takes a JSON payload and converts it to +hex: + +```js +// Take a JSON payload {str: "some string"} and convert it to hex +var server = http.createServer(function (req, res) { + var data = '' + req.setEncoding('utf8') + req.on('data', function (chunk) { + data += chunk + }) + req.on('end', function () { + var body = JSON.parse(data) + res.end(new Buffer(body.str).toString('hex')) + }) +}) + +server.listen(8080) +``` + +In this example, an http client just has to send: + +```json +{ + "str": 1000 +} +``` + +and it will get back 1,000 bytes of uninitialized memory from the server. + +This is a very serious bug. It's similar in severity to the +[the Heartbleed bug](http://heartbleed.com/) that allowed disclosure of OpenSSL process +memory by remote attackers. + + +### Which real-world packages were vulnerable? + +#### [`bittorrent-dht`](https://www.npmjs.com/package/bittorrent-dht) + +[Mathias Buus](https://github.com/mafintosh) and I +([Feross Aboukhadijeh](http://feross.org/)) found this issue in one of our own packages, +[`bittorrent-dht`](https://www.npmjs.com/package/bittorrent-dht). The bug would allow +anyone on the internet to send a series of messages to a user of `bittorrent-dht` and get +them to reveal 20 bytes at a time of uninitialized memory from the node.js process. + +Here's +[the commit](https://github.com/feross/bittorrent-dht/commit/6c7da04025d5633699800a99ec3fbadf70ad35b8) +that fixed it. We released a new fixed version, created a +[Node Security Project disclosure](https://nodesecurity.io/advisories/68), and deprecated all +vulnerable versions on npm so users will get a warning to upgrade to a newer version. + +#### [`ws`](https://www.npmjs.com/package/ws) + +That got us wondering if there were other vulnerable packages. Sure enough, within a short +period of time, we found the same issue in [`ws`](https://www.npmjs.com/package/ws), the +most popular WebSocket implementation in node.js. + +If certain APIs were called with `Number` parameters instead of `String` or `Buffer` as +expected, then uninitialized server memory would be disclosed to the remote peer. + +These were the vulnerable methods: + +```js +socket.send(number) +socket.ping(number) +socket.pong(number) +``` + +Here's a vulnerable socket server with some echo functionality: + +```js +server.on('connection', function (socket) { + socket.on('message', function (message) { + message = JSON.parse(message) + if (message.type === 'echo') { + socket.send(message.data) // send back the user's message + } + }) +}) +``` + +`socket.send(number)` called on the server, will disclose server memory. + +Here's [the release](https://github.com/websockets/ws/releases/tag/1.0.1) where the issue +was fixed, with a more detailed explanation. Props to +[Arnout Kazemier](https://github.com/3rd-Eden) for the quick fix. Here's the +[Node Security Project disclosure](https://nodesecurity.io/advisories/67). + + +### What's the solution? + +It's important that node.js offers a fast way to get memory otherwise performance-critical +applications would needlessly get a lot slower. + +But we need a better way to *signal our intent* as programmers. **When we want +uninitialized memory, we should request it explicitly.** + +Sensitive functionality should not be packed into a developer-friendly API that loosely +accepts many different types. This type of API encourages the lazy practice of passing +variables in without checking the type very carefully. + +#### A new API: `Buffer.allocUnsafe(number)` + +The functionality of creating buffers with uninitialized memory should be part of another +API. We propose `Buffer.allocUnsafe(number)`. This way, it's not part of an API that +frequently gets user input of all sorts of different types passed into it. + +```js +var buf = Buffer.allocUnsafe(16) // careful, uninitialized memory! + +// Immediately overwrite the uninitialized buffer with data from another buffer +for (var i = 0; i < buf.length; i++) { + buf[i] = otherBuf[i] +} +``` + + +### How do we fix node.js core? + +We sent [a PR to node.js core](https://github.com/nodejs/node/pull/4514) (merged as +`semver-major`) which defends against one case: + +```js +var str = 16 +new Buffer(str, 'utf8') +``` + +In this situation, it's implied that the programmer intended the first argument to be a +string, since they passed an encoding as a second argument. Today, node.js will allocate +uninitialized memory in the case of `new Buffer(number, encoding)`, which is probably not +what the programmer intended. + +But this is only a partial solution, since if the programmer does `new Buffer(variable)` +(without an `encoding` parameter) there's no way to know what they intended. If `variable` +is sometimes a number, then uninitialized memory will sometimes be returned. + +### What's the real long-term fix? + +We could deprecate and remove `new Buffer(number)` and use `Buffer.allocUnsafe(number)` when +we need uninitialized memory. But that would break 1000s of packages. + +~~We believe the best solution is to:~~ + +~~1. Change `new Buffer(number)` to return safe, zeroed-out memory~~ + +~~2. Create a new API for creating uninitialized Buffers. We propose: `Buffer.allocUnsafe(number)`~~ + +#### Update + +We now support adding three new APIs: + +- `Buffer.from(value)` - convert from any type to a buffer +- `Buffer.alloc(size)` - create a zero-filled buffer +- `Buffer.allocUnsafe(size)` - create an uninitialized buffer with given size + +This solves the core problem that affected `ws` and `bittorrent-dht` which is +`Buffer(variable)` getting tricked into taking a number argument. + +This way, existing code continues working and the impact on the npm ecosystem will be +minimal. Over time, npm maintainers can migrate performance-critical code to use +`Buffer.allocUnsafe(number)` instead of `new Buffer(number)`. + + +### Conclusion + +We think there's a serious design issue with the `Buffer` API as it exists today. It +promotes insecure software by putting high-risk functionality into a convenient API +with friendly "developer ergonomics". + +This wasn't merely a theoretical exercise because we found the issue in some of the +most popular npm packages. + +Fortunately, there's an easy fix that can be applied today. Use `safe-buffer` in place of +`buffer`. + +```js +var Buffer = require('safe-buffer').Buffer +``` + +Eventually, we hope that node.js core can switch to this new, safer behavior. We believe +the impact on the ecosystem would be minimal since it's not a breaking change. +Well-maintained, popular packages would be updated to use `Buffer.alloc` quickly, while +older, insecure packages would magically become safe from this attack vector. + + +## links + +- [Node.js PR: buffer: throw if both length and enc are passed](https://github.com/nodejs/node/pull/4514) +- [Node Security Project disclosure for `ws`](https://nodesecurity.io/advisories/67) +- [Node Security Project disclosure for`bittorrent-dht`](https://nodesecurity.io/advisories/68) + + +## credit + +The original issues in `bittorrent-dht` +([disclosure](https://nodesecurity.io/advisories/68)) and +`ws` ([disclosure](https://nodesecurity.io/advisories/67)) were discovered by +[Mathias Buus](https://github.com/mafintosh) and +[Feross Aboukhadijeh](http://feross.org/). + +Thanks to [Adam Baldwin](https://github.com/evilpacket) for helping disclose these issues +and for his work running the [Node Security Project](https://nodesecurity.io/). + +Thanks to [John Hiesey](https://github.com/jhiesey) for proofreading this README and +auditing the code. + + +## license + +MIT. Copyright (C) [Feross Aboukhadijeh](http://feross.org) diff --git a/node_modules/safe-buffer/index.d.ts b/node_modules/safe-buffer/index.d.ts new file mode 100644 index 0000000..e9fed80 --- /dev/null +++ b/node_modules/safe-buffer/index.d.ts @@ -0,0 +1,187 @@ +declare module "safe-buffer" { + export class Buffer { + length: number + write(string: string, offset?: number, length?: number, encoding?: string): number; + toString(encoding?: string, start?: number, end?: number): string; + toJSON(): { type: 'Buffer', data: any[] }; + equals(otherBuffer: Buffer): boolean; + compare(otherBuffer: Buffer, targetStart?: number, targetEnd?: number, sourceStart?: number, sourceEnd?: number): number; + copy(targetBuffer: Buffer, targetStart?: number, sourceStart?: number, sourceEnd?: number): number; + slice(start?: number, end?: number): Buffer; + writeUIntLE(value: number, offset: number, byteLength: number, noAssert?: boolean): number; + writeUIntBE(value: number, offset: number, byteLength: number, noAssert?: boolean): number; + writeIntLE(value: number, offset: number, byteLength: number, noAssert?: boolean): number; + writeIntBE(value: number, offset: number, byteLength: number, noAssert?: boolean): number; + readUIntLE(offset: number, byteLength: number, noAssert?: boolean): number; + readUIntBE(offset: number, byteLength: number, noAssert?: boolean): number; + readIntLE(offset: number, byteLength: number, noAssert?: boolean): number; + readIntBE(offset: number, byteLength: number, noAssert?: boolean): number; + readUInt8(offset: number, noAssert?: boolean): number; + readUInt16LE(offset: number, noAssert?: boolean): number; + readUInt16BE(offset: number, noAssert?: boolean): number; + readUInt32LE(offset: number, noAssert?: boolean): number; + readUInt32BE(offset: number, noAssert?: boolean): number; + readInt8(offset: number, noAssert?: boolean): number; + readInt16LE(offset: number, noAssert?: boolean): number; + readInt16BE(offset: number, noAssert?: boolean): number; + readInt32LE(offset: number, noAssert?: boolean): number; + readInt32BE(offset: number, noAssert?: boolean): number; + readFloatLE(offset: number, noAssert?: boolean): number; + readFloatBE(offset: number, noAssert?: boolean): number; + readDoubleLE(offset: number, noAssert?: boolean): number; + readDoubleBE(offset: number, noAssert?: boolean): number; + swap16(): Buffer; + swap32(): Buffer; + swap64(): Buffer; + writeUInt8(value: number, offset: number, noAssert?: boolean): number; + writeUInt16LE(value: number, offset: number, noAssert?: boolean): number; + writeUInt16BE(value: number, offset: number, noAssert?: boolean): number; + writeUInt32LE(value: number, offset: number, noAssert?: boolean): number; + writeUInt32BE(value: number, offset: number, noAssert?: boolean): number; + writeInt8(value: number, offset: number, noAssert?: boolean): number; + writeInt16LE(value: number, offset: number, noAssert?: boolean): number; + writeInt16BE(value: number, offset: number, noAssert?: boolean): number; + writeInt32LE(value: number, offset: number, noAssert?: boolean): number; + writeInt32BE(value: number, offset: number, noAssert?: boolean): number; + writeFloatLE(value: number, offset: number, noAssert?: boolean): number; + writeFloatBE(value: number, offset: number, noAssert?: boolean): number; + writeDoubleLE(value: number, offset: number, noAssert?: boolean): number; + writeDoubleBE(value: number, offset: number, noAssert?: boolean): number; + fill(value: any, offset?: number, end?: number): this; + indexOf(value: string | number | Buffer, byteOffset?: number, encoding?: string): number; + lastIndexOf(value: string | number | Buffer, byteOffset?: number, encoding?: string): number; + includes(value: string | number | Buffer, byteOffset?: number, encoding?: string): boolean; + + /** + * Allocates a new buffer containing the given {str}. + * + * @param str String to store in buffer. + * @param encoding encoding to use, optional. Default is 'utf8' + */ + constructor (str: string, encoding?: string); + /** + * Allocates a new buffer of {size} octets. + * + * @param size count of octets to allocate. + */ + constructor (size: number); + /** + * Allocates a new buffer containing the given {array} of octets. + * + * @param array The octets to store. + */ + constructor (array: Uint8Array); + /** + * Produces a Buffer backed by the same allocated memory as + * the given {ArrayBuffer}. + * + * + * @param arrayBuffer The ArrayBuffer with which to share memory. + */ + constructor (arrayBuffer: ArrayBuffer); + /** + * Allocates a new buffer containing the given {array} of octets. + * + * @param array The octets to store. + */ + constructor (array: any[]); + /** + * Copies the passed {buffer} data onto a new {Buffer} instance. + * + * @param buffer The buffer to copy. + */ + constructor (buffer: Buffer); + prototype: Buffer; + /** + * Allocates a new Buffer using an {array} of octets. + * + * @param array + */ + static from(array: any[]): Buffer; + /** + * When passed a reference to the .buffer property of a TypedArray instance, + * the newly created Buffer will share the same allocated memory as the TypedArray. + * The optional {byteOffset} and {length} arguments specify a memory range + * within the {arrayBuffer} that will be shared by the Buffer. + * + * @param arrayBuffer The .buffer property of a TypedArray or a new ArrayBuffer() + * @param byteOffset + * @param length + */ + static from(arrayBuffer: ArrayBuffer, byteOffset?: number, length?: number): Buffer; + /** + * Copies the passed {buffer} data onto a new Buffer instance. + * + * @param buffer + */ + static from(buffer: Buffer): Buffer; + /** + * Creates a new Buffer containing the given JavaScript string {str}. + * If provided, the {encoding} parameter identifies the character encoding. + * If not provided, {encoding} defaults to 'utf8'. + * + * @param str + */ + static from(str: string, encoding?: string): Buffer; + /** + * Returns true if {obj} is a Buffer + * + * @param obj object to test. + */ + static isBuffer(obj: any): obj is Buffer; + /** + * Returns true if {encoding} is a valid encoding argument. + * Valid string encodings in Node 0.12: 'ascii'|'utf8'|'utf16le'|'ucs2'(alias of 'utf16le')|'base64'|'binary'(deprecated)|'hex' + * + * @param encoding string to test. + */ + static isEncoding(encoding: string): boolean; + /** + * Gives the actual byte length of a string. encoding defaults to 'utf8'. + * This is not the same as String.prototype.length since that returns the number of characters in a string. + * + * @param string string to test. + * @param encoding encoding used to evaluate (defaults to 'utf8') + */ + static byteLength(string: string, encoding?: string): number; + /** + * Returns a buffer which is the result of concatenating all the buffers in the list together. + * + * If the list has no items, or if the totalLength is 0, then it returns a zero-length buffer. + * If the list has exactly one item, then the first item of the list is returned. + * If the list has more than one item, then a new Buffer is created. + * + * @param list An array of Buffer objects to concatenate + * @param totalLength Total length of the buffers when concatenated. + * If totalLength is not provided, it is read from the buffers in the list. However, this adds an additional loop to the function, so it is faster to provide the length explicitly. + */ + static concat(list: Buffer[], totalLength?: number): Buffer; + /** + * The same as buf1.compare(buf2). + */ + static compare(buf1: Buffer, buf2: Buffer): number; + /** + * Allocates a new buffer of {size} octets. + * + * @param size count of octets to allocate. + * @param fill if specified, buffer will be initialized by calling buf.fill(fill). + * If parameter is omitted, buffer will be filled with zeros. + * @param encoding encoding used for call to buf.fill while initalizing + */ + static alloc(size: number, fill?: string | Buffer | number, encoding?: string): Buffer; + /** + * Allocates a new buffer of {size} octets, leaving memory not initialized, so the contents + * of the newly created Buffer are unknown and may contain sensitive data. + * + * @param size count of octets to allocate + */ + static allocUnsafe(size: number): Buffer; + /** + * Allocates a new non-pooled buffer of {size} octets, leaving memory not initialized, so the contents + * of the newly created Buffer are unknown and may contain sensitive data. + * + * @param size count of octets to allocate + */ + static allocUnsafeSlow(size: number): Buffer; + } +} \ No newline at end of file diff --git a/node_modules/safe-buffer/index.js b/node_modules/safe-buffer/index.js new file mode 100644 index 0000000..22438da --- /dev/null +++ b/node_modules/safe-buffer/index.js @@ -0,0 +1,62 @@ +/* eslint-disable node/no-deprecated-api */ +var buffer = require('buffer') +var Buffer = buffer.Buffer + +// alternative to using Object.keys for old browsers +function copyProps (src, dst) { + for (var key in src) { + dst[key] = src[key] + } +} +if (Buffer.from && Buffer.alloc && Buffer.allocUnsafe && Buffer.allocUnsafeSlow) { + module.exports = buffer +} else { + // Copy properties from require('buffer') + copyProps(buffer, exports) + exports.Buffer = SafeBuffer +} + +function SafeBuffer (arg, encodingOrOffset, length) { + return Buffer(arg, encodingOrOffset, length) +} + +// Copy static methods from Buffer +copyProps(Buffer, SafeBuffer) + +SafeBuffer.from = function (arg, encodingOrOffset, length) { + if (typeof arg === 'number') { + throw new TypeError('Argument must not be a number') + } + return Buffer(arg, encodingOrOffset, length) +} + +SafeBuffer.alloc = function (size, fill, encoding) { + if (typeof size !== 'number') { + throw new TypeError('Argument must be a number') + } + var buf = Buffer(size) + if (fill !== undefined) { + if (typeof encoding === 'string') { + buf.fill(fill, encoding) + } else { + buf.fill(fill) + } + } else { + buf.fill(0) + } + return buf +} + +SafeBuffer.allocUnsafe = function (size) { + if (typeof size !== 'number') { + throw new TypeError('Argument must be a number') + } + return Buffer(size) +} + +SafeBuffer.allocUnsafeSlow = function (size) { + if (typeof size !== 'number') { + throw new TypeError('Argument must be a number') + } + return buffer.SlowBuffer(size) +} diff --git a/node_modules/safe-buffer/package.json b/node_modules/safe-buffer/package.json new file mode 100644 index 0000000..623fbc3 --- /dev/null +++ b/node_modules/safe-buffer/package.json @@ -0,0 +1,37 @@ +{ + "name": "safe-buffer", + "description": "Safer Node.js Buffer API", + "version": "5.1.2", + "author": { + "name": "Feross Aboukhadijeh", + "email": "feross@feross.org", + "url": "http://feross.org" + }, + "bugs": { + "url": "https://github.com/feross/safe-buffer/issues" + }, + "devDependencies": { + "standard": "*", + "tape": "^4.0.0" + }, + "homepage": "https://github.com/feross/safe-buffer", + "keywords": [ + "buffer", + "buffer allocate", + "node security", + "safe", + "safe-buffer", + "security", + "uninitialized" + ], + "license": "MIT", + "main": "index.js", + "types": "index.d.ts", + "repository": { + "type": "git", + "url": "git://github.com/feross/safe-buffer.git" + }, + "scripts": { + "test": "standard && tape test/*.js" + } +} diff --git a/node_modules/streamsearch/.eslintrc.js b/node_modules/streamsearch/.eslintrc.js new file mode 100644 index 0000000..be9311d --- /dev/null +++ b/node_modules/streamsearch/.eslintrc.js @@ -0,0 +1,5 @@ +'use strict'; + +module.exports = { + extends: '@mscdex/eslint-config', +}; diff --git a/node_modules/streamsearch/.github/workflows/ci.yml b/node_modules/streamsearch/.github/workflows/ci.yml new file mode 100644 index 0000000..29d5178 --- /dev/null +++ b/node_modules/streamsearch/.github/workflows/ci.yml @@ -0,0 +1,24 @@ +name: CI + +on: + pull_request: + push: + branches: [ master ] + +jobs: + tests-linux: + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + node-version: [10.x, 12.x, 14.x, 16.x] + steps: + - uses: actions/checkout@v2 + - name: Use Node.js ${{ matrix.node-version }} + uses: actions/setup-node@v1 + with: + node-version: ${{ matrix.node-version }} + - name: Install module + run: npm install + - name: Run tests + run: npm test diff --git a/node_modules/streamsearch/.github/workflows/lint.yml b/node_modules/streamsearch/.github/workflows/lint.yml new file mode 100644 index 0000000..9f9e1f5 --- /dev/null +++ b/node_modules/streamsearch/.github/workflows/lint.yml @@ -0,0 +1,23 @@ +name: lint + +on: + pull_request: + push: + branches: [ master ] + +env: + NODE_VERSION: 16.x + +jobs: + lint-js: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - name: Use Node.js ${{ env.NODE_VERSION }} + uses: actions/setup-node@v1 + with: + node-version: ${{ env.NODE_VERSION }} + - name: Install ESLint + ESLint configs/plugins + run: npm install --only=dev + - name: Lint files + run: npm run lint diff --git a/node_modules/streamsearch/LICENSE b/node_modules/streamsearch/LICENSE new file mode 100644 index 0000000..290762e --- /dev/null +++ b/node_modules/streamsearch/LICENSE @@ -0,0 +1,19 @@ +Copyright Brian White. All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to +deal in the Software without restriction, including without limitation the +rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +sell copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +IN THE SOFTWARE. \ No newline at end of file diff --git a/node_modules/streamsearch/README.md b/node_modules/streamsearch/README.md new file mode 100644 index 0000000..c3934d1 --- /dev/null +++ b/node_modules/streamsearch/README.md @@ -0,0 +1,95 @@ +Description +=========== + +streamsearch is a module for [node.js](http://nodejs.org/) that allows searching a stream using the Boyer-Moore-Horspool algorithm. + +This module is based heavily on the Streaming Boyer-Moore-Horspool C++ implementation by Hongli Lai [here](https://github.com/FooBarWidget/boyer-moore-horspool). + + +Requirements +============ + +* [node.js](http://nodejs.org/) -- v10.0.0 or newer + + +Installation +============ + + npm install streamsearch + +Example +======= + +```js + const { inspect } = require('util'); + + const StreamSearch = require('streamsearch'); + + const needle = Buffer.from('\r\n'); + const ss = new StreamSearch(needle, (isMatch, data, start, end) => { + if (data) + console.log('data: ' + inspect(data.toString('latin1', start, end))); + if (isMatch) + console.log('match!'); + }); + + const chunks = [ + 'foo', + ' bar', + '\r', + '\n', + 'baz, hello\r', + '\n world.', + '\r\n Node.JS rules!!\r\n\r\n', + ]; + for (const chunk of chunks) + ss.push(Buffer.from(chunk)); + + // output: + // + // data: 'foo' + // data: ' bar' + // match! + // data: 'baz, hello' + // match! + // data: ' world.' + // match! + // data: ' Node.JS rules!!' + // match! + // data: '' + // match! +``` + + +API +=== + +Properties +---------- + +* **maxMatches** - < _integer_ > - The maximum number of matches. Defaults to `Infinity`. + +* **matches** - < _integer_ > - The current match count. + + +Functions +--------- + +* **(constructor)**(< _mixed_ >needle, < _function_ >callback) - Creates and returns a new instance for searching for a _Buffer_ or _string_ `needle`. `callback` is called any time there is non-matching data and/or there is a needle match. `callback` will be called with the following arguments: + + 1. `isMatch` - _boolean_ - Indicates whether a match has been found + + 2. `data` - _mixed_ - If set, this contains data that did not match the needle. + + 3. `start` - _integer_ - The index in `data` where the non-matching data begins (inclusive). + + 4. `end` - _integer_ - The index in `data` where the non-matching data ends (exclusive). + + 5. `isSafeData` - _boolean_ - Indicates if it is safe to store a reference to `data` (e.g. as-is or via `data.slice()`) or not, as in some cases `data` may point to a Buffer whose contents change over time. + +* **destroy**() - _(void)_ - Emits any last remaining unmatched data that may still be buffered and then resets internal state. + +* **push**(< _Buffer_ >chunk) - _integer_ - Processes `chunk`, searching for a match. The return value is the last processed index in `chunk` + 1. + +* **reset**() - _(void)_ - Resets internal state. Useful for when you wish to start searching a new/different stream for example. + diff --git a/node_modules/streamsearch/lib/sbmh.js b/node_modules/streamsearch/lib/sbmh.js new file mode 100644 index 0000000..510cae2 --- /dev/null +++ b/node_modules/streamsearch/lib/sbmh.js @@ -0,0 +1,267 @@ +'use strict'; +/* + Based heavily on the Streaming Boyer-Moore-Horspool C++ implementation + by Hongli Lai at: https://github.com/FooBarWidget/boyer-moore-horspool +*/ +function memcmp(buf1, pos1, buf2, pos2, num) { + for (let i = 0; i < num; ++i) { + if (buf1[pos1 + i] !== buf2[pos2 + i]) + return false; + } + return true; +} + +class SBMH { + constructor(needle, cb) { + if (typeof cb !== 'function') + throw new Error('Missing match callback'); + + if (typeof needle === 'string') + needle = Buffer.from(needle); + else if (!Buffer.isBuffer(needle)) + throw new Error(`Expected Buffer for needle, got ${typeof needle}`); + + const needleLen = needle.length; + + this.maxMatches = Infinity; + this.matches = 0; + + this._cb = cb; + this._lookbehindSize = 0; + this._needle = needle; + this._bufPos = 0; + + this._lookbehind = Buffer.allocUnsafe(needleLen); + + // Initialize occurrence table. + this._occ = [ + needleLen, needleLen, needleLen, needleLen, needleLen, needleLen, + needleLen, needleLen, needleLen, needleLen, needleLen, needleLen, + needleLen, needleLen, needleLen, needleLen, needleLen, needleLen, + needleLen, needleLen, needleLen, needleLen, needleLen, needleLen, + needleLen, needleLen, needleLen, needleLen, needleLen, needleLen, + needleLen, needleLen, needleLen, needleLen, needleLen, needleLen, + needleLen, needleLen, needleLen, needleLen, needleLen, needleLen, + needleLen, needleLen, needleLen, needleLen, needleLen, needleLen, + needleLen, needleLen, needleLen, needleLen, needleLen, needleLen, + needleLen, needleLen, needleLen, needleLen, needleLen, needleLen, + needleLen, needleLen, needleLen, needleLen, needleLen, needleLen, + needleLen, needleLen, needleLen, needleLen, needleLen, needleLen, + needleLen, needleLen, needleLen, needleLen, needleLen, needleLen, + needleLen, needleLen, needleLen, needleLen, needleLen, needleLen, + needleLen, needleLen, needleLen, needleLen, needleLen, needleLen, + needleLen, needleLen, needleLen, needleLen, needleLen, needleLen, + needleLen, needleLen, needleLen, needleLen, needleLen, needleLen, + needleLen, needleLen, needleLen, needleLen, needleLen, needleLen, + needleLen, needleLen, needleLen, needleLen, needleLen, needleLen, + needleLen, needleLen, needleLen, needleLen, needleLen, needleLen, + needleLen, needleLen, needleLen, needleLen, needleLen, needleLen, + needleLen, needleLen, needleLen, needleLen, needleLen, needleLen, + needleLen, needleLen, needleLen, needleLen, needleLen, needleLen, + needleLen, needleLen, needleLen, needleLen, needleLen, needleLen, + needleLen, needleLen, needleLen, needleLen, needleLen, needleLen, + needleLen, needleLen, needleLen, needleLen, needleLen, needleLen, + needleLen, needleLen, needleLen, needleLen, needleLen, needleLen, + needleLen, needleLen, needleLen, needleLen, needleLen, needleLen, + needleLen, needleLen, needleLen, needleLen, needleLen, needleLen, + needleLen, needleLen, needleLen, needleLen, needleLen, needleLen, + needleLen, needleLen, needleLen, needleLen, needleLen, needleLen, + needleLen, needleLen, needleLen, needleLen, needleLen, needleLen, + needleLen, needleLen, needleLen, needleLen, needleLen, needleLen, + needleLen, needleLen, needleLen, needleLen, needleLen, needleLen, + needleLen, needleLen, needleLen, needleLen, needleLen, needleLen, + needleLen, needleLen, needleLen, needleLen, needleLen, needleLen, + needleLen, needleLen, needleLen, needleLen, needleLen, needleLen, + needleLen, needleLen, needleLen, needleLen, needleLen, needleLen, + needleLen, needleLen, needleLen, needleLen, needleLen, needleLen, + needleLen, needleLen, needleLen, needleLen, needleLen, needleLen, + needleLen, needleLen, needleLen, needleLen, needleLen, needleLen, + needleLen, needleLen, needleLen, needleLen, needleLen, needleLen, + needleLen, needleLen, needleLen, needleLen + ]; + + // Populate occurrence table with analysis of the needle, ignoring the last + // letter. + if (needleLen > 1) { + for (let i = 0; i < needleLen - 1; ++i) + this._occ[needle[i]] = needleLen - 1 - i; + } + } + + reset() { + this.matches = 0; + this._lookbehindSize = 0; + this._bufPos = 0; + } + + push(chunk, pos) { + let result; + if (!Buffer.isBuffer(chunk)) + chunk = Buffer.from(chunk, 'latin1'); + const chunkLen = chunk.length; + this._bufPos = pos || 0; + while (result !== chunkLen && this.matches < this.maxMatches) + result = feed(this, chunk); + return result; + } + + destroy() { + const lbSize = this._lookbehindSize; + if (lbSize) + this._cb(false, this._lookbehind, 0, lbSize, false); + this.reset(); + } +} + +function feed(self, data) { + const len = data.length; + const needle = self._needle; + const needleLen = needle.length; + + // Positive: points to a position in `data` + // pos == 3 points to data[3] + // Negative: points to a position in the lookbehind buffer + // pos == -2 points to lookbehind[lookbehindSize - 2] + let pos = -self._lookbehindSize; + const lastNeedleCharPos = needleLen - 1; + const lastNeedleChar = needle[lastNeedleCharPos]; + const end = len - needleLen; + const occ = self._occ; + const lookbehind = self._lookbehind; + + if (pos < 0) { + // Lookbehind buffer is not empty. Perform Boyer-Moore-Horspool + // search with character lookup code that considers both the + // lookbehind buffer and the current round's haystack data. + // + // Loop until + // there is a match. + // or until + // we've moved past the position that requires the + // lookbehind buffer. In this case we switch to the + // optimized loop. + // or until + // the character to look at lies outside the haystack. + while (pos < 0 && pos <= end) { + const nextPos = pos + lastNeedleCharPos; + const ch = (nextPos < 0 + ? lookbehind[self._lookbehindSize + nextPos] + : data[nextPos]); + + if (ch === lastNeedleChar + && matchNeedle(self, data, pos, lastNeedleCharPos)) { + self._lookbehindSize = 0; + ++self.matches; + if (pos > -self._lookbehindSize) + self._cb(true, lookbehind, 0, self._lookbehindSize + pos, false); + else + self._cb(true, undefined, 0, 0, true); + + return (self._bufPos = pos + needleLen); + } + + pos += occ[ch]; + } + + // No match. + + // There's too few data for Boyer-Moore-Horspool to run, + // so let's use a different algorithm to skip as much as + // we can. + // Forward pos until + // the trailing part of lookbehind + data + // looks like the beginning of the needle + // or until + // pos == 0 + while (pos < 0 && !matchNeedle(self, data, pos, len - pos)) + ++pos; + + if (pos < 0) { + // Cut off part of the lookbehind buffer that has + // been processed and append the entire haystack + // into it. + const bytesToCutOff = self._lookbehindSize + pos; + + if (bytesToCutOff > 0) { + // The cut off data is guaranteed not to contain the needle. + self._cb(false, lookbehind, 0, bytesToCutOff, false); + } + + self._lookbehindSize -= bytesToCutOff; + lookbehind.copy(lookbehind, 0, bytesToCutOff, self._lookbehindSize); + lookbehind.set(data, self._lookbehindSize); + self._lookbehindSize += len; + + self._bufPos = len; + return len; + } + + // Discard lookbehind buffer. + self._cb(false, lookbehind, 0, self._lookbehindSize, false); + self._lookbehindSize = 0; + } + + pos += self._bufPos; + + const firstNeedleChar = needle[0]; + + // Lookbehind buffer is now empty. Perform Boyer-Moore-Horspool + // search with optimized character lookup code that only considers + // the current round's haystack data. + while (pos <= end) { + const ch = data[pos + lastNeedleCharPos]; + + if (ch === lastNeedleChar + && data[pos] === firstNeedleChar + && memcmp(needle, 0, data, pos, lastNeedleCharPos)) { + ++self.matches; + if (pos > 0) + self._cb(true, data, self._bufPos, pos, true); + else + self._cb(true, undefined, 0, 0, true); + + return (self._bufPos = pos + needleLen); + } + + pos += occ[ch]; + } + + // There was no match. If there's trailing haystack data that we cannot + // match yet using the Boyer-Moore-Horspool algorithm (because the trailing + // data is less than the needle size) then match using a modified + // algorithm that starts matching from the beginning instead of the end. + // Whatever trailing data is left after running this algorithm is added to + // the lookbehind buffer. + while (pos < len) { + if (data[pos] !== firstNeedleChar + || !memcmp(data, pos, needle, 0, len - pos)) { + ++pos; + continue; + } + data.copy(lookbehind, 0, pos, len); + self._lookbehindSize = len - pos; + break; + } + + // Everything until `pos` is guaranteed not to contain needle data. + if (pos > 0) + self._cb(false, data, self._bufPos, pos < len ? pos : len, true); + + self._bufPos = len; + return len; +} + +function matchNeedle(self, data, pos, len) { + const lb = self._lookbehind; + const lbSize = self._lookbehindSize; + const needle = self._needle; + + for (let i = 0; i < len; ++i, ++pos) { + const ch = (pos < 0 ? lb[lbSize + pos] : data[pos]); + if (ch !== needle[i]) + return false; + } + return true; +} + +module.exports = SBMH; diff --git a/node_modules/streamsearch/package.json b/node_modules/streamsearch/package.json new file mode 100644 index 0000000..51df8f9 --- /dev/null +++ b/node_modules/streamsearch/package.json @@ -0,0 +1,34 @@ +{ + "name": "streamsearch", + "version": "1.1.0", + "author": "Brian White ", + "description": "Streaming Boyer-Moore-Horspool searching for node.js", + "main": "./lib/sbmh.js", + "engines": { + "node": ">=10.0.0" + }, + "devDependencies": { + "@mscdex/eslint-config": "^1.1.0", + "eslint": "^7.32.0" + }, + "scripts": { + "test": "node test/test.js", + "lint": "eslint --cache --report-unused-disable-directives --ext=.js .eslintrc.js lib test", + "lint:fix": "npm run lint -- --fix" + }, + "keywords": [ + "stream", + "horspool", + "boyer-moore-horspool", + "boyer-moore", + "search" + ], + "licenses": [{ + "type": "MIT", + "url": "http://github.com/mscdex/streamsearch/raw/master/LICENSE" + }], + "repository": { + "type": "git", + "url": "http://github.com/mscdex/streamsearch.git" + } +} diff --git a/node_modules/streamsearch/test/test.js b/node_modules/streamsearch/test/test.js new file mode 100644 index 0000000..39a04d7 --- /dev/null +++ b/node_modules/streamsearch/test/test.js @@ -0,0 +1,70 @@ +'use strict'; + +const assert = require('assert'); + +const StreamSearch = require('../lib/sbmh.js'); + +[ + { + needle: '\r\n', + chunks: [ + 'foo', + ' bar', + '\r', + '\n', + 'baz, hello\r', + '\n world.', + '\r\n Node.JS rules!!\r\n\r\n', + ], + expect: [ + [false, 'foo'], + [false, ' bar'], + [ true, null], + [false, 'baz, hello'], + [ true, null], + [false, ' world.'], + [ true, null], + [ true, ' Node.JS rules!!'], + [ true, ''], + ], + }, + { + needle: '---foobarbaz', + chunks: [ + '---foobarbaz', + 'asdf', + '\r\n', + '---foobarba', + '---foobar', + 'ba', + '\r\n---foobarbaz--\r\n', + ], + expect: [ + [ true, null], + [false, 'asdf'], + [false, '\r\n'], + [false, '---foobarba'], + [false, '---foobarba'], + [ true, '\r\n'], + [false, '--\r\n'], + ], + }, +].forEach((test, i) => { + console.log(`Running test #${i + 1}`); + const { needle, chunks, expect } = test; + + const results = []; + const ss = new StreamSearch(Buffer.from(needle), + (isMatch, data, start, end) => { + if (data) + data = data.toString('latin1', start, end); + else + data = null; + results.push([isMatch, data]); + }); + + for (const chunk of chunks) + ss.push(Buffer.from(chunk)); + + assert.deepStrictEqual(results, expect); +}); diff --git a/node_modules/string_decoder/.travis.yml b/node_modules/string_decoder/.travis.yml new file mode 100644 index 0000000..3347a72 --- /dev/null +++ b/node_modules/string_decoder/.travis.yml @@ -0,0 +1,50 @@ +sudo: false +language: node_js +before_install: + - npm install -g npm@2 + - test $NPM_LEGACY && npm install -g npm@latest-3 || npm install npm -g +notifications: + email: false +matrix: + fast_finish: true + include: + - node_js: '0.8' + env: + - TASK=test + - NPM_LEGACY=true + - node_js: '0.10' + env: + - TASK=test + - NPM_LEGACY=true + - node_js: '0.11' + env: + - TASK=test + - NPM_LEGACY=true + - node_js: '0.12' + env: + - TASK=test + - NPM_LEGACY=true + - node_js: 1 + env: + - TASK=test + - NPM_LEGACY=true + - node_js: 2 + env: + - TASK=test + - NPM_LEGACY=true + - node_js: 3 + env: + - TASK=test + - NPM_LEGACY=true + - node_js: 4 + env: TASK=test + - node_js: 5 + env: TASK=test + - node_js: 6 + env: TASK=test + - node_js: 7 + env: TASK=test + - node_js: 8 + env: TASK=test + - node_js: 9 + env: TASK=test diff --git a/node_modules/string_decoder/LICENSE b/node_modules/string_decoder/LICENSE new file mode 100644 index 0000000..778edb2 --- /dev/null +++ b/node_modules/string_decoder/LICENSE @@ -0,0 +1,48 @@ +Node.js is licensed for use as follows: + +""" +Copyright Node.js contributors. All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to +deal in the Software without restriction, including without limitation the +rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +sell copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +IN THE SOFTWARE. +""" + +This license applies to parts of Node.js originating from the +https://github.com/joyent/node repository: + +""" +Copyright Joyent, Inc. and other Node contributors. All rights reserved. +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to +deal in the Software without restriction, including without limitation the +rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +sell copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +IN THE SOFTWARE. +""" + diff --git a/node_modules/string_decoder/README.md b/node_modules/string_decoder/README.md new file mode 100644 index 0000000..5fd5831 --- /dev/null +++ b/node_modules/string_decoder/README.md @@ -0,0 +1,47 @@ +# string_decoder + +***Node-core v8.9.4 string_decoder for userland*** + + +[![NPM](https://nodei.co/npm/string_decoder.png?downloads=true&downloadRank=true)](https://nodei.co/npm/string_decoder/) +[![NPM](https://nodei.co/npm-dl/string_decoder.png?&months=6&height=3)](https://nodei.co/npm/string_decoder/) + + +```bash +npm install --save string_decoder +``` + +***Node-core string_decoder for userland*** + +This package is a mirror of the string_decoder implementation in Node-core. + +Full documentation may be found on the [Node.js website](https://nodejs.org/dist/v8.9.4/docs/api/). + +As of version 1.0.0 **string_decoder** uses semantic versioning. + +## Previous versions + +Previous version numbers match the versions found in Node core, e.g. 0.10.24 matches Node 0.10.24, likewise 0.11.10 matches Node 0.11.10. + +## Update + +The *build/* directory contains a build script that will scrape the source from the [nodejs/node](https://github.com/nodejs/node) repo given a specific Node version. + +## Streams Working Group + +`string_decoder` is maintained by the Streams Working Group, which +oversees the development and maintenance of the Streams API within +Node.js. The responsibilities of the Streams Working Group include: + +* Addressing stream issues on the Node.js issue tracker. +* Authoring and editing stream documentation within the Node.js project. +* Reviewing changes to stream subclasses within the Node.js project. +* Redirecting changes to streams from the Node.js project to this + project. +* Assisting in the implementation of stream providers within Node.js. +* Recommending versions of `readable-stream` to be included in Node.js. +* Messaging about the future of streams to give the community advance + notice of changes. + +See [readable-stream](https://github.com/nodejs/readable-stream) for +more details. diff --git a/node_modules/string_decoder/lib/string_decoder.js b/node_modules/string_decoder/lib/string_decoder.js new file mode 100644 index 0000000..2e89e63 --- /dev/null +++ b/node_modules/string_decoder/lib/string_decoder.js @@ -0,0 +1,296 @@ +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +'use strict'; + +/**/ + +var Buffer = require('safe-buffer').Buffer; +/**/ + +var isEncoding = Buffer.isEncoding || function (encoding) { + encoding = '' + encoding; + switch (encoding && encoding.toLowerCase()) { + case 'hex':case 'utf8':case 'utf-8':case 'ascii':case 'binary':case 'base64':case 'ucs2':case 'ucs-2':case 'utf16le':case 'utf-16le':case 'raw': + return true; + default: + return false; + } +}; + +function _normalizeEncoding(enc) { + if (!enc) return 'utf8'; + var retried; + while (true) { + switch (enc) { + case 'utf8': + case 'utf-8': + return 'utf8'; + case 'ucs2': + case 'ucs-2': + case 'utf16le': + case 'utf-16le': + return 'utf16le'; + case 'latin1': + case 'binary': + return 'latin1'; + case 'base64': + case 'ascii': + case 'hex': + return enc; + default: + if (retried) return; // undefined + enc = ('' + enc).toLowerCase(); + retried = true; + } + } +}; + +// Do not cache `Buffer.isEncoding` when checking encoding names as some +// modules monkey-patch it to support additional encodings +function normalizeEncoding(enc) { + var nenc = _normalizeEncoding(enc); + if (typeof nenc !== 'string' && (Buffer.isEncoding === isEncoding || !isEncoding(enc))) throw new Error('Unknown encoding: ' + enc); + return nenc || enc; +} + +// StringDecoder provides an interface for efficiently splitting a series of +// buffers into a series of JS strings without breaking apart multi-byte +// characters. +exports.StringDecoder = StringDecoder; +function StringDecoder(encoding) { + this.encoding = normalizeEncoding(encoding); + var nb; + switch (this.encoding) { + case 'utf16le': + this.text = utf16Text; + this.end = utf16End; + nb = 4; + break; + case 'utf8': + this.fillLast = utf8FillLast; + nb = 4; + break; + case 'base64': + this.text = base64Text; + this.end = base64End; + nb = 3; + break; + default: + this.write = simpleWrite; + this.end = simpleEnd; + return; + } + this.lastNeed = 0; + this.lastTotal = 0; + this.lastChar = Buffer.allocUnsafe(nb); +} + +StringDecoder.prototype.write = function (buf) { + if (buf.length === 0) return ''; + var r; + var i; + if (this.lastNeed) { + r = this.fillLast(buf); + if (r === undefined) return ''; + i = this.lastNeed; + this.lastNeed = 0; + } else { + i = 0; + } + if (i < buf.length) return r ? r + this.text(buf, i) : this.text(buf, i); + return r || ''; +}; + +StringDecoder.prototype.end = utf8End; + +// Returns only complete characters in a Buffer +StringDecoder.prototype.text = utf8Text; + +// Attempts to complete a partial non-UTF-8 character using bytes from a Buffer +StringDecoder.prototype.fillLast = function (buf) { + if (this.lastNeed <= buf.length) { + buf.copy(this.lastChar, this.lastTotal - this.lastNeed, 0, this.lastNeed); + return this.lastChar.toString(this.encoding, 0, this.lastTotal); + } + buf.copy(this.lastChar, this.lastTotal - this.lastNeed, 0, buf.length); + this.lastNeed -= buf.length; +}; + +// Checks the type of a UTF-8 byte, whether it's ASCII, a leading byte, or a +// continuation byte. If an invalid byte is detected, -2 is returned. +function utf8CheckByte(byte) { + if (byte <= 0x7F) return 0;else if (byte >> 5 === 0x06) return 2;else if (byte >> 4 === 0x0E) return 3;else if (byte >> 3 === 0x1E) return 4; + return byte >> 6 === 0x02 ? -1 : -2; +} + +// Checks at most 3 bytes at the end of a Buffer in order to detect an +// incomplete multi-byte UTF-8 character. The total number of bytes (2, 3, or 4) +// needed to complete the UTF-8 character (if applicable) are returned. +function utf8CheckIncomplete(self, buf, i) { + var j = buf.length - 1; + if (j < i) return 0; + var nb = utf8CheckByte(buf[j]); + if (nb >= 0) { + if (nb > 0) self.lastNeed = nb - 1; + return nb; + } + if (--j < i || nb === -2) return 0; + nb = utf8CheckByte(buf[j]); + if (nb >= 0) { + if (nb > 0) self.lastNeed = nb - 2; + return nb; + } + if (--j < i || nb === -2) return 0; + nb = utf8CheckByte(buf[j]); + if (nb >= 0) { + if (nb > 0) { + if (nb === 2) nb = 0;else self.lastNeed = nb - 3; + } + return nb; + } + return 0; +} + +// Validates as many continuation bytes for a multi-byte UTF-8 character as +// needed or are available. If we see a non-continuation byte where we expect +// one, we "replace" the validated continuation bytes we've seen so far with +// a single UTF-8 replacement character ('\ufffd'), to match v8's UTF-8 decoding +// behavior. The continuation byte check is included three times in the case +// where all of the continuation bytes for a character exist in the same buffer. +// It is also done this way as a slight performance increase instead of using a +// loop. +function utf8CheckExtraBytes(self, buf, p) { + if ((buf[0] & 0xC0) !== 0x80) { + self.lastNeed = 0; + return '\ufffd'; + } + if (self.lastNeed > 1 && buf.length > 1) { + if ((buf[1] & 0xC0) !== 0x80) { + self.lastNeed = 1; + return '\ufffd'; + } + if (self.lastNeed > 2 && buf.length > 2) { + if ((buf[2] & 0xC0) !== 0x80) { + self.lastNeed = 2; + return '\ufffd'; + } + } + } +} + +// Attempts to complete a multi-byte UTF-8 character using bytes from a Buffer. +function utf8FillLast(buf) { + var p = this.lastTotal - this.lastNeed; + var r = utf8CheckExtraBytes(this, buf, p); + if (r !== undefined) return r; + if (this.lastNeed <= buf.length) { + buf.copy(this.lastChar, p, 0, this.lastNeed); + return this.lastChar.toString(this.encoding, 0, this.lastTotal); + } + buf.copy(this.lastChar, p, 0, buf.length); + this.lastNeed -= buf.length; +} + +// Returns all complete UTF-8 characters in a Buffer. If the Buffer ended on a +// partial character, the character's bytes are buffered until the required +// number of bytes are available. +function utf8Text(buf, i) { + var total = utf8CheckIncomplete(this, buf, i); + if (!this.lastNeed) return buf.toString('utf8', i); + this.lastTotal = total; + var end = buf.length - (total - this.lastNeed); + buf.copy(this.lastChar, 0, end); + return buf.toString('utf8', i, end); +} + +// For UTF-8, a replacement character is added when ending on a partial +// character. +function utf8End(buf) { + var r = buf && buf.length ? this.write(buf) : ''; + if (this.lastNeed) return r + '\ufffd'; + return r; +} + +// UTF-16LE typically needs two bytes per character, but even if we have an even +// number of bytes available, we need to check if we end on a leading/high +// surrogate. In that case, we need to wait for the next two bytes in order to +// decode the last character properly. +function utf16Text(buf, i) { + if ((buf.length - i) % 2 === 0) { + var r = buf.toString('utf16le', i); + if (r) { + var c = r.charCodeAt(r.length - 1); + if (c >= 0xD800 && c <= 0xDBFF) { + this.lastNeed = 2; + this.lastTotal = 4; + this.lastChar[0] = buf[buf.length - 2]; + this.lastChar[1] = buf[buf.length - 1]; + return r.slice(0, -1); + } + } + return r; + } + this.lastNeed = 1; + this.lastTotal = 2; + this.lastChar[0] = buf[buf.length - 1]; + return buf.toString('utf16le', i, buf.length - 1); +} + +// For UTF-16LE we do not explicitly append special replacement characters if we +// end on a partial character, we simply let v8 handle that. +function utf16End(buf) { + var r = buf && buf.length ? this.write(buf) : ''; + if (this.lastNeed) { + var end = this.lastTotal - this.lastNeed; + return r + this.lastChar.toString('utf16le', 0, end); + } + return r; +} + +function base64Text(buf, i) { + var n = (buf.length - i) % 3; + if (n === 0) return buf.toString('base64', i); + this.lastNeed = 3 - n; + this.lastTotal = 3; + if (n === 1) { + this.lastChar[0] = buf[buf.length - 1]; + } else { + this.lastChar[0] = buf[buf.length - 2]; + this.lastChar[1] = buf[buf.length - 1]; + } + return buf.toString('base64', i, buf.length - n); +} + +function base64End(buf) { + var r = buf && buf.length ? this.write(buf) : ''; + if (this.lastNeed) return r + this.lastChar.toString('base64', 0, 3 - this.lastNeed); + return r; +} + +// Pass bytes on through for single-byte encodings (e.g. ascii, latin1, hex) +function simpleWrite(buf) { + return buf.toString(this.encoding); +} + +function simpleEnd(buf) { + return buf && buf.length ? this.write(buf) : ''; +} \ No newline at end of file diff --git a/node_modules/string_decoder/package.json b/node_modules/string_decoder/package.json new file mode 100644 index 0000000..518c3eb --- /dev/null +++ b/node_modules/string_decoder/package.json @@ -0,0 +1,31 @@ +{ + "name": "string_decoder", + "version": "1.1.1", + "description": "The string_decoder module from Node core", + "main": "lib/string_decoder.js", + "dependencies": { + "safe-buffer": "~5.1.0" + }, + "devDependencies": { + "babel-polyfill": "^6.23.0", + "core-util-is": "^1.0.2", + "inherits": "^2.0.3", + "tap": "~0.4.8" + }, + "scripts": { + "test": "tap test/parallel/*.js && node test/verify-dependencies", + "ci": "tap test/parallel/*.js test/ours/*.js --tap | tee test.tap && node test/verify-dependencies.js" + }, + "repository": { + "type": "git", + "url": "git://github.com/nodejs/string_decoder.git" + }, + "homepage": "https://github.com/nodejs/string_decoder", + "keywords": [ + "string", + "decoder", + "browser", + "browserify" + ], + "license": "MIT" +} diff --git a/node_modules/type-is/HISTORY.md b/node_modules/type-is/HISTORY.md new file mode 100644 index 0000000..8de21f7 --- /dev/null +++ b/node_modules/type-is/HISTORY.md @@ -0,0 +1,259 @@ +1.6.18 / 2019-04-26 +=================== + + * Fix regression passing request object to `typeis.is` + +1.6.17 / 2019-04-25 +=================== + + * deps: mime-types@~2.1.24 + - Add Apple file extensions from IANA + - Add extension `.csl` to `application/vnd.citationstyles.style+xml` + - Add extension `.es` to `application/ecmascript` + - Add extension `.nq` to `application/n-quads` + - Add extension `.nt` to `application/n-triples` + - Add extension `.owl` to `application/rdf+xml` + - Add extensions `.siv` and `.sieve` to `application/sieve` + - Add extensions from IANA for `image/*` types + - Add extensions from IANA for `model/*` types + - Add extensions to HEIC image types + - Add new mime types + - Add `text/mdx` with extension `.mdx` + * perf: prevent internal `throw` on invalid type + +1.6.16 / 2018-02-16 +=================== + + * deps: mime-types@~2.1.18 + - Add `application/raml+yaml` with extension `.raml` + - Add `application/wasm` with extension `.wasm` + - Add `text/shex` with extension `.shex` + - Add extensions for JPEG-2000 images + - Add extensions from IANA for `message/*` types + - Add extension `.mjs` to `application/javascript` + - Add extension `.wadl` to `application/vnd.sun.wadl+xml` + - Add extension `.gz` to `application/gzip` + - Add glTF types and extensions + - Add new mime types + - Update extensions `.md` and `.markdown` to be `text/markdown` + - Update font MIME types + - Update `text/hjson` to registered `application/hjson` + +1.6.15 / 2017-03-31 +=================== + + * deps: mime-types@~2.1.15 + - Add new mime types + +1.6.14 / 2016-11-18 +=================== + + * deps: mime-types@~2.1.13 + - Add new mime types + +1.6.13 / 2016-05-18 +=================== + + * deps: mime-types@~2.1.11 + - Add new mime types + +1.6.12 / 2016-02-28 +=================== + + * deps: mime-types@~2.1.10 + - Add new mime types + - Fix extension of `application/dash+xml` + - Update primary extension for `audio/mp4` + +1.6.11 / 2016-01-29 +=================== + + * deps: mime-types@~2.1.9 + - Add new mime types + +1.6.10 / 2015-12-01 +=================== + + * deps: mime-types@~2.1.8 + - Add new mime types + +1.6.9 / 2015-09-27 +================== + + * deps: mime-types@~2.1.7 + - Add new mime types + +1.6.8 / 2015-09-04 +================== + + * deps: mime-types@~2.1.6 + - Add new mime types + +1.6.7 / 2015-08-20 +================== + + * Fix type error when given invalid type to match against + * deps: mime-types@~2.1.5 + - Add new mime types + +1.6.6 / 2015-07-31 +================== + + * deps: mime-types@~2.1.4 + - Add new mime types + +1.6.5 / 2015-07-16 +================== + + * deps: mime-types@~2.1.3 + - Add new mime types + +1.6.4 / 2015-07-01 +================== + + * deps: mime-types@~2.1.2 + - Add new mime types + * perf: enable strict mode + * perf: remove argument reassignment + +1.6.3 / 2015-06-08 +================== + + * deps: mime-types@~2.1.1 + - Add new mime types + * perf: reduce try block size + * perf: remove bitwise operations + +1.6.2 / 2015-05-10 +================== + + * deps: mime-types@~2.0.11 + - Add new mime types + +1.6.1 / 2015-03-13 +================== + + * deps: mime-types@~2.0.10 + - Add new mime types + +1.6.0 / 2015-02-12 +================== + + * fix false-positives in `hasBody` `Transfer-Encoding` check + * support wildcard for both type and subtype (`*/*`) + +1.5.7 / 2015-02-09 +================== + + * fix argument reassignment + * deps: mime-types@~2.0.9 + - Add new mime types + +1.5.6 / 2015-01-29 +================== + + * deps: mime-types@~2.0.8 + - Add new mime types + +1.5.5 / 2014-12-30 +================== + + * deps: mime-types@~2.0.7 + - Add new mime types + - Fix missing extensions + - Fix various invalid MIME type entries + - Remove example template MIME types + - deps: mime-db@~1.5.0 + +1.5.4 / 2014-12-10 +================== + + * deps: mime-types@~2.0.4 + - Add new mime types + - deps: mime-db@~1.3.0 + +1.5.3 / 2014-11-09 +================== + + * deps: mime-types@~2.0.3 + - Add new mime types + - deps: mime-db@~1.2.0 + +1.5.2 / 2014-09-28 +================== + + * deps: mime-types@~2.0.2 + - Add new mime types + - deps: mime-db@~1.1.0 + +1.5.1 / 2014-09-07 +================== + + * Support Node.js 0.6 + * deps: media-typer@0.3.0 + * deps: mime-types@~2.0.1 + - Support Node.js 0.6 + +1.5.0 / 2014-09-05 +================== + + * fix `hasbody` to be true for `content-length: 0` + +1.4.0 / 2014-09-02 +================== + + * update mime-types + +1.3.2 / 2014-06-24 +================== + + * use `~` range on mime-types + +1.3.1 / 2014-06-19 +================== + + * fix global variable leak + +1.3.0 / 2014-06-19 +================== + + * improve type parsing + + - invalid media type never matches + - media type not case-sensitive + - extra LWS does not affect results + +1.2.2 / 2014-06-19 +================== + + * fix behavior on unknown type argument + +1.2.1 / 2014-06-03 +================== + + * switch dependency from `mime` to `mime-types@1.0.0` + +1.2.0 / 2014-05-11 +================== + + * support suffix matching: + + - `+json` matches `application/vnd+json` + - `*/vnd+json` matches `application/vnd+json` + - `application/*+json` matches `application/vnd+json` + +1.1.0 / 2014-04-12 +================== + + * add non-array values support + * expose internal utilities: + + - `.is()` + - `.hasBody()` + - `.normalize()` + - `.match()` + +1.0.1 / 2014-03-30 +================== + + * add `multipart` as a shorthand diff --git a/node_modules/type-is/LICENSE b/node_modules/type-is/LICENSE new file mode 100644 index 0000000..386b7b6 --- /dev/null +++ b/node_modules/type-is/LICENSE @@ -0,0 +1,23 @@ +(The MIT License) + +Copyright (c) 2014 Jonathan Ong +Copyright (c) 2014-2015 Douglas Christopher Wilson + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +'Software'), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/node_modules/type-is/README.md b/node_modules/type-is/README.md new file mode 100644 index 0000000..b85ef8f --- /dev/null +++ b/node_modules/type-is/README.md @@ -0,0 +1,170 @@ +# type-is + +[![NPM Version][npm-version-image]][npm-url] +[![NPM Downloads][npm-downloads-image]][npm-url] +[![Node.js Version][node-version-image]][node-version-url] +[![Build Status][travis-image]][travis-url] +[![Test Coverage][coveralls-image]][coveralls-url] + +Infer the content-type of a request. + +### Install + +This is a [Node.js](https://nodejs.org/en/) module available through the +[npm registry](https://www.npmjs.com/). Installation is done using the +[`npm install` command](https://docs.npmjs.com/getting-started/installing-npm-packages-locally): + +```sh +$ npm install type-is +``` + +## API + +```js +var http = require('http') +var typeis = require('type-is') + +http.createServer(function (req, res) { + var istext = typeis(req, ['text/*']) + res.end('you ' + (istext ? 'sent' : 'did not send') + ' me text') +}) +``` + +### typeis(request, types) + +Checks if the `request` is one of the `types`. If the request has no body, +even if there is a `Content-Type` header, then `null` is returned. If the +`Content-Type` header is invalid or does not matches any of the `types`, then +`false` is returned. Otherwise, a string of the type that matched is returned. + +The `request` argument is expected to be a Node.js HTTP request. The `types` +argument is an array of type strings. + +Each type in the `types` array can be one of the following: + +- A file extension name such as `json`. This name will be returned if matched. +- A mime type such as `application/json`. +- A mime type with a wildcard such as `*/*` or `*/json` or `application/*`. + The full mime type will be returned if matched. +- A suffix such as `+json`. This can be combined with a wildcard such as + `*/vnd+json` or `application/*+json`. The full mime type will be returned + if matched. + +Some examples to illustrate the inputs and returned value: + + + +```js +// req.headers.content-type = 'application/json' + +typeis(req, ['json']) // => 'json' +typeis(req, ['html', 'json']) // => 'json' +typeis(req, ['application/*']) // => 'application/json' +typeis(req, ['application/json']) // => 'application/json' + +typeis(req, ['html']) // => false +``` + +### typeis.hasBody(request) + +Returns a Boolean if the given `request` has a body, regardless of the +`Content-Type` header. + +Having a body has no relation to how large the body is (it may be 0 bytes). +This is similar to how file existence works. If a body does exist, then this +indicates that there is data to read from the Node.js request stream. + + + +```js +if (typeis.hasBody(req)) { + // read the body, since there is one + + req.on('data', function (chunk) { + // ... + }) +} +``` + +### typeis.is(mediaType, types) + +Checks if the `mediaType` is one of the `types`. If the `mediaType` is invalid +or does not matches any of the `types`, then `false` is returned. Otherwise, a +string of the type that matched is returned. + +The `mediaType` argument is expected to be a +[media type](https://tools.ietf.org/html/rfc6838) string. The `types` argument +is an array of type strings. + +Each type in the `types` array can be one of the following: + +- A file extension name such as `json`. This name will be returned if matched. +- A mime type such as `application/json`. +- A mime type with a wildcard such as `*/*` or `*/json` or `application/*`. + The full mime type will be returned if matched. +- A suffix such as `+json`. This can be combined with a wildcard such as + `*/vnd+json` or `application/*+json`. The full mime type will be returned + if matched. + +Some examples to illustrate the inputs and returned value: + + + +```js +var mediaType = 'application/json' + +typeis.is(mediaType, ['json']) // => 'json' +typeis.is(mediaType, ['html', 'json']) // => 'json' +typeis.is(mediaType, ['application/*']) // => 'application/json' +typeis.is(mediaType, ['application/json']) // => 'application/json' + +typeis.is(mediaType, ['html']) // => false +``` + +## Examples + +### Example body parser + +```js +var express = require('express') +var typeis = require('type-is') + +var app = express() + +app.use(function bodyParser (req, res, next) { + if (!typeis.hasBody(req)) { + return next() + } + + switch (typeis(req, ['urlencoded', 'json', 'multipart'])) { + case 'urlencoded': + // parse urlencoded body + throw new Error('implement urlencoded body parsing') + case 'json': + // parse json body + throw new Error('implement json body parsing') + case 'multipart': + // parse multipart body + throw new Error('implement multipart body parsing') + default: + // 415 error code + res.statusCode = 415 + res.end() + break + } +}) +``` + +## License + +[MIT](LICENSE) + +[coveralls-image]: https://badgen.net/coveralls/c/github/jshttp/type-is/master +[coveralls-url]: https://coveralls.io/r/jshttp/type-is?branch=master +[node-version-image]: https://badgen.net/npm/node/type-is +[node-version-url]: https://nodejs.org/en/download +[npm-downloads-image]: https://badgen.net/npm/dm/type-is +[npm-url]: https://npmjs.org/package/type-is +[npm-version-image]: https://badgen.net/npm/v/type-is +[travis-image]: https://badgen.net/travis/jshttp/type-is/master +[travis-url]: https://travis-ci.org/jshttp/type-is diff --git a/node_modules/type-is/index.js b/node_modules/type-is/index.js new file mode 100644 index 0000000..890ad76 --- /dev/null +++ b/node_modules/type-is/index.js @@ -0,0 +1,266 @@ +/*! + * type-is + * Copyright(c) 2014 Jonathan Ong + * Copyright(c) 2014-2015 Douglas Christopher Wilson + * MIT Licensed + */ + +'use strict' + +/** + * Module dependencies. + * @private + */ + +var typer = require('media-typer') +var mime = require('mime-types') + +/** + * Module exports. + * @public + */ + +module.exports = typeofrequest +module.exports.is = typeis +module.exports.hasBody = hasbody +module.exports.normalize = normalize +module.exports.match = mimeMatch + +/** + * Compare a `value` content-type with `types`. + * Each `type` can be an extension like `html`, + * a special shortcut like `multipart` or `urlencoded`, + * or a mime type. + * + * If no types match, `false` is returned. + * Otherwise, the first `type` that matches is returned. + * + * @param {String} value + * @param {Array} types + * @public + */ + +function typeis (value, types_) { + var i + var types = types_ + + // remove parameters and normalize + var val = tryNormalizeType(value) + + // no type or invalid + if (!val) { + return false + } + + // support flattened arguments + if (types && !Array.isArray(types)) { + types = new Array(arguments.length - 1) + for (i = 0; i < types.length; i++) { + types[i] = arguments[i + 1] + } + } + + // no types, return the content type + if (!types || !types.length) { + return val + } + + var type + for (i = 0; i < types.length; i++) { + if (mimeMatch(normalize(type = types[i]), val)) { + return type[0] === '+' || type.indexOf('*') !== -1 + ? val + : type + } + } + + // no matches + return false +} + +/** + * Check if a request has a request body. + * A request with a body __must__ either have `transfer-encoding` + * or `content-length` headers set. + * http://www.w3.org/Protocols/rfc2616/rfc2616-sec4.html#sec4.3 + * + * @param {Object} request + * @return {Boolean} + * @public + */ + +function hasbody (req) { + return req.headers['transfer-encoding'] !== undefined || + !isNaN(req.headers['content-length']) +} + +/** + * Check if the incoming request contains the "Content-Type" + * header field, and it contains any of the give mime `type`s. + * If there is no request body, `null` is returned. + * If there is no content type, `false` is returned. + * Otherwise, it returns the first `type` that matches. + * + * Examples: + * + * // With Content-Type: text/html; charset=utf-8 + * this.is('html'); // => 'html' + * this.is('text/html'); // => 'text/html' + * this.is('text/*', 'application/json'); // => 'text/html' + * + * // When Content-Type is application/json + * this.is('json', 'urlencoded'); // => 'json' + * this.is('application/json'); // => 'application/json' + * this.is('html', 'application/*'); // => 'application/json' + * + * this.is('html'); // => false + * + * @param {String|Array} types... + * @return {String|false|null} + * @public + */ + +function typeofrequest (req, types_) { + var types = types_ + + // no body + if (!hasbody(req)) { + return null + } + + // support flattened arguments + if (arguments.length > 2) { + types = new Array(arguments.length - 1) + for (var i = 0; i < types.length; i++) { + types[i] = arguments[i + 1] + } + } + + // request content type + var value = req.headers['content-type'] + + return typeis(value, types) +} + +/** + * Normalize a mime type. + * If it's a shorthand, expand it to a valid mime type. + * + * In general, you probably want: + * + * var type = is(req, ['urlencoded', 'json', 'multipart']); + * + * Then use the appropriate body parsers. + * These three are the most common request body types + * and are thus ensured to work. + * + * @param {String} type + * @private + */ + +function normalize (type) { + if (typeof type !== 'string') { + // invalid type + return false + } + + switch (type) { + case 'urlencoded': + return 'application/x-www-form-urlencoded' + case 'multipart': + return 'multipart/*' + } + + if (type[0] === '+') { + // "+json" -> "*/*+json" expando + return '*/*' + type + } + + return type.indexOf('/') === -1 + ? mime.lookup(type) + : type +} + +/** + * Check if `expected` mime type + * matches `actual` mime type with + * wildcard and +suffix support. + * + * @param {String} expected + * @param {String} actual + * @return {Boolean} + * @private + */ + +function mimeMatch (expected, actual) { + // invalid type + if (expected === false) { + return false + } + + // split types + var actualParts = actual.split('/') + var expectedParts = expected.split('/') + + // invalid format + if (actualParts.length !== 2 || expectedParts.length !== 2) { + return false + } + + // validate type + if (expectedParts[0] !== '*' && expectedParts[0] !== actualParts[0]) { + return false + } + + // validate suffix wildcard + if (expectedParts[1].substr(0, 2) === '*+') { + return expectedParts[1].length <= actualParts[1].length + 1 && + expectedParts[1].substr(1) === actualParts[1].substr(1 - expectedParts[1].length) + } + + // validate subtype + if (expectedParts[1] !== '*' && expectedParts[1] !== actualParts[1]) { + return false + } + + return true +} + +/** + * Normalize a type and remove parameters. + * + * @param {string} value + * @return {string} + * @private + */ + +function normalizeType (value) { + // parse the type + var type = typer.parse(value) + + // remove the parameters + type.parameters = undefined + + // reformat it + return typer.format(type) +} + +/** + * Try to normalize a type and remove parameters. + * + * @param {string} value + * @return {string} + * @private + */ + +function tryNormalizeType (value) { + if (!value) { + return null + } + + try { + return normalizeType(value) + } catch (err) { + return null + } +} diff --git a/node_modules/type-is/package.json b/node_modules/type-is/package.json new file mode 100644 index 0000000..97ba5f1 --- /dev/null +++ b/node_modules/type-is/package.json @@ -0,0 +1,45 @@ +{ + "name": "type-is", + "description": "Infer the content-type of a request.", + "version": "1.6.18", + "contributors": [ + "Douglas Christopher Wilson ", + "Jonathan Ong (http://jongleberry.com)" + ], + "license": "MIT", + "repository": "jshttp/type-is", + "dependencies": { + "media-typer": "0.3.0", + "mime-types": "~2.1.24" + }, + "devDependencies": { + "eslint": "5.16.0", + "eslint-config-standard": "12.0.0", + "eslint-plugin-import": "2.17.2", + "eslint-plugin-markdown": "1.0.0", + "eslint-plugin-node": "8.0.1", + "eslint-plugin-promise": "4.1.1", + "eslint-plugin-standard": "4.0.0", + "mocha": "6.1.4", + "nyc": "14.0.0" + }, + "engines": { + "node": ">= 0.6" + }, + "files": [ + "LICENSE", + "HISTORY.md", + "index.js" + ], + "scripts": { + "lint": "eslint --plugin markdown --ext js,md .", + "test": "mocha --reporter spec --check-leaks --bail test/", + "test-cov": "nyc --reporter=html --reporter=text npm test", + "test-travis": "nyc --reporter=text npm test" + }, + "keywords": [ + "content", + "type", + "checking" + ] +} diff --git a/node_modules/typedarray/.travis.yml b/node_modules/typedarray/.travis.yml new file mode 100644 index 0000000..cc4dba2 --- /dev/null +++ b/node_modules/typedarray/.travis.yml @@ -0,0 +1,4 @@ +language: node_js +node_js: + - "0.8" + - "0.10" diff --git a/node_modules/typedarray/LICENSE b/node_modules/typedarray/LICENSE new file mode 100644 index 0000000..11adfae --- /dev/null +++ b/node_modules/typedarray/LICENSE @@ -0,0 +1,35 @@ +/* + Copyright (c) 2010, Linden Research, Inc. + Copyright (c) 2012, Joshua Bell + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. + $/LicenseInfo$ + */ + +// Original can be found at: +// https://bitbucket.org/lindenlab/llsd +// Modifications by Joshua Bell inexorabletash@gmail.com +// https://github.com/inexorabletash/polyfill + +// ES3/ES5 implementation of the Krhonos Typed Array Specification +// Ref: http://www.khronos.org/registry/typedarray/specs/latest/ +// Date: 2011-02-01 +// +// Variations: +// * Allows typed_array.get/set() as alias for subscripts (typed_array[]) diff --git a/node_modules/typedarray/example/tarray.js b/node_modules/typedarray/example/tarray.js new file mode 100644 index 0000000..8423d7c --- /dev/null +++ b/node_modules/typedarray/example/tarray.js @@ -0,0 +1,4 @@ +var Uint8Array = require('../').Uint8Array; +var ua = new Uint8Array(5); +ua[1] = 256 + 55; +console.log(ua[1]); diff --git a/node_modules/typedarray/index.js b/node_modules/typedarray/index.js new file mode 100644 index 0000000..5e54084 --- /dev/null +++ b/node_modules/typedarray/index.js @@ -0,0 +1,630 @@ +var undefined = (void 0); // Paranoia + +// Beyond this value, index getters/setters (i.e. array[0], array[1]) are so slow to +// create, and consume so much memory, that the browser appears frozen. +var MAX_ARRAY_LENGTH = 1e5; + +// Approximations of internal ECMAScript conversion functions +var ECMAScript = (function() { + // Stash a copy in case other scripts modify these + var opts = Object.prototype.toString, + ophop = Object.prototype.hasOwnProperty; + + return { + // Class returns internal [[Class]] property, used to avoid cross-frame instanceof issues: + Class: function(v) { return opts.call(v).replace(/^\[object *|\]$/g, ''); }, + HasProperty: function(o, p) { return p in o; }, + HasOwnProperty: function(o, p) { return ophop.call(o, p); }, + IsCallable: function(o) { return typeof o === 'function'; }, + ToInt32: function(v) { return v >> 0; }, + ToUint32: function(v) { return v >>> 0; } + }; +}()); + +// Snapshot intrinsics +var LN2 = Math.LN2, + abs = Math.abs, + floor = Math.floor, + log = Math.log, + min = Math.min, + pow = Math.pow, + round = Math.round; + +// ES5: lock down object properties +function configureProperties(obj) { + if (getOwnPropNames && defineProp) { + var props = getOwnPropNames(obj), i; + for (i = 0; i < props.length; i += 1) { + defineProp(obj, props[i], { + value: obj[props[i]], + writable: false, + enumerable: false, + configurable: false + }); + } + } +} + +// emulate ES5 getter/setter API using legacy APIs +// http://blogs.msdn.com/b/ie/archive/2010/09/07/transitioning-existing-code-to-the-es5-getter-setter-apis.aspx +// (second clause tests for Object.defineProperty() in IE<9 that only supports extending DOM prototypes, but +// note that IE<9 does not support __defineGetter__ or __defineSetter__ so it just renders the method harmless) +var defineProp +if (Object.defineProperty && (function() { + try { + Object.defineProperty({}, 'x', {}); + return true; + } catch (e) { + return false; + } + })()) { + defineProp = Object.defineProperty; +} else { + defineProp = function(o, p, desc) { + if (!o === Object(o)) throw new TypeError("Object.defineProperty called on non-object"); + if (ECMAScript.HasProperty(desc, 'get') && Object.prototype.__defineGetter__) { Object.prototype.__defineGetter__.call(o, p, desc.get); } + if (ECMAScript.HasProperty(desc, 'set') && Object.prototype.__defineSetter__) { Object.prototype.__defineSetter__.call(o, p, desc.set); } + if (ECMAScript.HasProperty(desc, 'value')) { o[p] = desc.value; } + return o; + }; +} + +var getOwnPropNames = Object.getOwnPropertyNames || function (o) { + if (o !== Object(o)) throw new TypeError("Object.getOwnPropertyNames called on non-object"); + var props = [], p; + for (p in o) { + if (ECMAScript.HasOwnProperty(o, p)) { + props.push(p); + } + } + return props; +}; + +// ES5: Make obj[index] an alias for obj._getter(index)/obj._setter(index, value) +// for index in 0 ... obj.length +function makeArrayAccessors(obj) { + if (!defineProp) { return; } + + if (obj.length > MAX_ARRAY_LENGTH) throw new RangeError("Array too large for polyfill"); + + function makeArrayAccessor(index) { + defineProp(obj, index, { + 'get': function() { return obj._getter(index); }, + 'set': function(v) { obj._setter(index, v); }, + enumerable: true, + configurable: false + }); + } + + var i; + for (i = 0; i < obj.length; i += 1) { + makeArrayAccessor(i); + } +} + +// Internal conversion functions: +// pack() - take a number (interpreted as Type), output a byte array +// unpack() - take a byte array, output a Type-like number + +function as_signed(value, bits) { var s = 32 - bits; return (value << s) >> s; } +function as_unsigned(value, bits) { var s = 32 - bits; return (value << s) >>> s; } + +function packI8(n) { return [n & 0xff]; } +function unpackI8(bytes) { return as_signed(bytes[0], 8); } + +function packU8(n) { return [n & 0xff]; } +function unpackU8(bytes) { return as_unsigned(bytes[0], 8); } + +function packU8Clamped(n) { n = round(Number(n)); return [n < 0 ? 0 : n > 0xff ? 0xff : n & 0xff]; } + +function packI16(n) { return [(n >> 8) & 0xff, n & 0xff]; } +function unpackI16(bytes) { return as_signed(bytes[0] << 8 | bytes[1], 16); } + +function packU16(n) { return [(n >> 8) & 0xff, n & 0xff]; } +function unpackU16(bytes) { return as_unsigned(bytes[0] << 8 | bytes[1], 16); } + +function packI32(n) { return [(n >> 24) & 0xff, (n >> 16) & 0xff, (n >> 8) & 0xff, n & 0xff]; } +function unpackI32(bytes) { return as_signed(bytes[0] << 24 | bytes[1] << 16 | bytes[2] << 8 | bytes[3], 32); } + +function packU32(n) { return [(n >> 24) & 0xff, (n >> 16) & 0xff, (n >> 8) & 0xff, n & 0xff]; } +function unpackU32(bytes) { return as_unsigned(bytes[0] << 24 | bytes[1] << 16 | bytes[2] << 8 | bytes[3], 32); } + +function packIEEE754(v, ebits, fbits) { + + var bias = (1 << (ebits - 1)) - 1, + s, e, f, ln, + i, bits, str, bytes; + + function roundToEven(n) { + var w = floor(n), f = n - w; + if (f < 0.5) + return w; + if (f > 0.5) + return w + 1; + return w % 2 ? w + 1 : w; + } + + // Compute sign, exponent, fraction + if (v !== v) { + // NaN + // http://dev.w3.org/2006/webapi/WebIDL/#es-type-mapping + e = (1 << ebits) - 1; f = pow(2, fbits - 1); s = 0; + } else if (v === Infinity || v === -Infinity) { + e = (1 << ebits) - 1; f = 0; s = (v < 0) ? 1 : 0; + } else if (v === 0) { + e = 0; f = 0; s = (1 / v === -Infinity) ? 1 : 0; + } else { + s = v < 0; + v = abs(v); + + if (v >= pow(2, 1 - bias)) { + e = min(floor(log(v) / LN2), 1023); + f = roundToEven(v / pow(2, e) * pow(2, fbits)); + if (f / pow(2, fbits) >= 2) { + e = e + 1; + f = 1; + } + if (e > bias) { + // Overflow + e = (1 << ebits) - 1; + f = 0; + } else { + // Normalized + e = e + bias; + f = f - pow(2, fbits); + } + } else { + // Denormalized + e = 0; + f = roundToEven(v / pow(2, 1 - bias - fbits)); + } + } + + // Pack sign, exponent, fraction + bits = []; + for (i = fbits; i; i -= 1) { bits.push(f % 2 ? 1 : 0); f = floor(f / 2); } + for (i = ebits; i; i -= 1) { bits.push(e % 2 ? 1 : 0); e = floor(e / 2); } + bits.push(s ? 1 : 0); + bits.reverse(); + str = bits.join(''); + + // Bits to bytes + bytes = []; + while (str.length) { + bytes.push(parseInt(str.substring(0, 8), 2)); + str = str.substring(8); + } + return bytes; +} + +function unpackIEEE754(bytes, ebits, fbits) { + + // Bytes to bits + var bits = [], i, j, b, str, + bias, s, e, f; + + for (i = bytes.length; i; i -= 1) { + b = bytes[i - 1]; + for (j = 8; j; j -= 1) { + bits.push(b % 2 ? 1 : 0); b = b >> 1; + } + } + bits.reverse(); + str = bits.join(''); + + // Unpack sign, exponent, fraction + bias = (1 << (ebits - 1)) - 1; + s = parseInt(str.substring(0, 1), 2) ? -1 : 1; + e = parseInt(str.substring(1, 1 + ebits), 2); + f = parseInt(str.substring(1 + ebits), 2); + + // Produce number + if (e === (1 << ebits) - 1) { + return f !== 0 ? NaN : s * Infinity; + } else if (e > 0) { + // Normalized + return s * pow(2, e - bias) * (1 + f / pow(2, fbits)); + } else if (f !== 0) { + // Denormalized + return s * pow(2, -(bias - 1)) * (f / pow(2, fbits)); + } else { + return s < 0 ? -0 : 0; + } +} + +function unpackF64(b) { return unpackIEEE754(b, 11, 52); } +function packF64(v) { return packIEEE754(v, 11, 52); } +function unpackF32(b) { return unpackIEEE754(b, 8, 23); } +function packF32(v) { return packIEEE754(v, 8, 23); } + + +// +// 3 The ArrayBuffer Type +// + +(function() { + + /** @constructor */ + var ArrayBuffer = function ArrayBuffer(length) { + length = ECMAScript.ToInt32(length); + if (length < 0) throw new RangeError('ArrayBuffer size is not a small enough positive integer'); + + this.byteLength = length; + this._bytes = []; + this._bytes.length = length; + + var i; + for (i = 0; i < this.byteLength; i += 1) { + this._bytes[i] = 0; + } + + configureProperties(this); + }; + + exports.ArrayBuffer = exports.ArrayBuffer || ArrayBuffer; + + // + // 4 The ArrayBufferView Type + // + + // NOTE: this constructor is not exported + /** @constructor */ + var ArrayBufferView = function ArrayBufferView() { + //this.buffer = null; + //this.byteOffset = 0; + //this.byteLength = 0; + }; + + // + // 5 The Typed Array View Types + // + + function makeConstructor(bytesPerElement, pack, unpack) { + // Each TypedArray type requires a distinct constructor instance with + // identical logic, which this produces. + + var ctor; + ctor = function(buffer, byteOffset, length) { + var array, sequence, i, s; + + if (!arguments.length || typeof arguments[0] === 'number') { + // Constructor(unsigned long length) + this.length = ECMAScript.ToInt32(arguments[0]); + if (length < 0) throw new RangeError('ArrayBufferView size is not a small enough positive integer'); + + this.byteLength = this.length * this.BYTES_PER_ELEMENT; + this.buffer = new ArrayBuffer(this.byteLength); + this.byteOffset = 0; + } else if (typeof arguments[0] === 'object' && arguments[0].constructor === ctor) { + // Constructor(TypedArray array) + array = arguments[0]; + + this.length = array.length; + this.byteLength = this.length * this.BYTES_PER_ELEMENT; + this.buffer = new ArrayBuffer(this.byteLength); + this.byteOffset = 0; + + for (i = 0; i < this.length; i += 1) { + this._setter(i, array._getter(i)); + } + } else if (typeof arguments[0] === 'object' && + !(arguments[0] instanceof ArrayBuffer || ECMAScript.Class(arguments[0]) === 'ArrayBuffer')) { + // Constructor(sequence array) + sequence = arguments[0]; + + this.length = ECMAScript.ToUint32(sequence.length); + this.byteLength = this.length * this.BYTES_PER_ELEMENT; + this.buffer = new ArrayBuffer(this.byteLength); + this.byteOffset = 0; + + for (i = 0; i < this.length; i += 1) { + s = sequence[i]; + this._setter(i, Number(s)); + } + } else if (typeof arguments[0] === 'object' && + (arguments[0] instanceof ArrayBuffer || ECMAScript.Class(arguments[0]) === 'ArrayBuffer')) { + // Constructor(ArrayBuffer buffer, + // optional unsigned long byteOffset, optional unsigned long length) + this.buffer = buffer; + + this.byteOffset = ECMAScript.ToUint32(byteOffset); + if (this.byteOffset > this.buffer.byteLength) { + throw new RangeError("byteOffset out of range"); + } + + if (this.byteOffset % this.BYTES_PER_ELEMENT) { + // The given byteOffset must be a multiple of the element + // size of the specific type, otherwise an exception is raised. + throw new RangeError("ArrayBuffer length minus the byteOffset is not a multiple of the element size."); + } + + if (arguments.length < 3) { + this.byteLength = this.buffer.byteLength - this.byteOffset; + + if (this.byteLength % this.BYTES_PER_ELEMENT) { + throw new RangeError("length of buffer minus byteOffset not a multiple of the element size"); + } + this.length = this.byteLength / this.BYTES_PER_ELEMENT; + } else { + this.length = ECMAScript.ToUint32(length); + this.byteLength = this.length * this.BYTES_PER_ELEMENT; + } + + if ((this.byteOffset + this.byteLength) > this.buffer.byteLength) { + throw new RangeError("byteOffset and length reference an area beyond the end of the buffer"); + } + } else { + throw new TypeError("Unexpected argument type(s)"); + } + + this.constructor = ctor; + + configureProperties(this); + makeArrayAccessors(this); + }; + + ctor.prototype = new ArrayBufferView(); + ctor.prototype.BYTES_PER_ELEMENT = bytesPerElement; + ctor.prototype._pack = pack; + ctor.prototype._unpack = unpack; + ctor.BYTES_PER_ELEMENT = bytesPerElement; + + // getter type (unsigned long index); + ctor.prototype._getter = function(index) { + if (arguments.length < 1) throw new SyntaxError("Not enough arguments"); + + index = ECMAScript.ToUint32(index); + if (index >= this.length) { + return undefined; + } + + var bytes = [], i, o; + for (i = 0, o = this.byteOffset + index * this.BYTES_PER_ELEMENT; + i < this.BYTES_PER_ELEMENT; + i += 1, o += 1) { + bytes.push(this.buffer._bytes[o]); + } + return this._unpack(bytes); + }; + + // NONSTANDARD: convenience alias for getter: type get(unsigned long index); + ctor.prototype.get = ctor.prototype._getter; + + // setter void (unsigned long index, type value); + ctor.prototype._setter = function(index, value) { + if (arguments.length < 2) throw new SyntaxError("Not enough arguments"); + + index = ECMAScript.ToUint32(index); + if (index >= this.length) { + return undefined; + } + + var bytes = this._pack(value), i, o; + for (i = 0, o = this.byteOffset + index * this.BYTES_PER_ELEMENT; + i < this.BYTES_PER_ELEMENT; + i += 1, o += 1) { + this.buffer._bytes[o] = bytes[i]; + } + }; + + // void set(TypedArray array, optional unsigned long offset); + // void set(sequence array, optional unsigned long offset); + ctor.prototype.set = function(index, value) { + if (arguments.length < 1) throw new SyntaxError("Not enough arguments"); + var array, sequence, offset, len, + i, s, d, + byteOffset, byteLength, tmp; + + if (typeof arguments[0] === 'object' && arguments[0].constructor === this.constructor) { + // void set(TypedArray array, optional unsigned long offset); + array = arguments[0]; + offset = ECMAScript.ToUint32(arguments[1]); + + if (offset + array.length > this.length) { + throw new RangeError("Offset plus length of array is out of range"); + } + + byteOffset = this.byteOffset + offset * this.BYTES_PER_ELEMENT; + byteLength = array.length * this.BYTES_PER_ELEMENT; + + if (array.buffer === this.buffer) { + tmp = []; + for (i = 0, s = array.byteOffset; i < byteLength; i += 1, s += 1) { + tmp[i] = array.buffer._bytes[s]; + } + for (i = 0, d = byteOffset; i < byteLength; i += 1, d += 1) { + this.buffer._bytes[d] = tmp[i]; + } + } else { + for (i = 0, s = array.byteOffset, d = byteOffset; + i < byteLength; i += 1, s += 1, d += 1) { + this.buffer._bytes[d] = array.buffer._bytes[s]; + } + } + } else if (typeof arguments[0] === 'object' && typeof arguments[0].length !== 'undefined') { + // void set(sequence array, optional unsigned long offset); + sequence = arguments[0]; + len = ECMAScript.ToUint32(sequence.length); + offset = ECMAScript.ToUint32(arguments[1]); + + if (offset + len > this.length) { + throw new RangeError("Offset plus length of array is out of range"); + } + + for (i = 0; i < len; i += 1) { + s = sequence[i]; + this._setter(offset + i, Number(s)); + } + } else { + throw new TypeError("Unexpected argument type(s)"); + } + }; + + // TypedArray subarray(long begin, optional long end); + ctor.prototype.subarray = function(start, end) { + function clamp(v, min, max) { return v < min ? min : v > max ? max : v; } + + start = ECMAScript.ToInt32(start); + end = ECMAScript.ToInt32(end); + + if (arguments.length < 1) { start = 0; } + if (arguments.length < 2) { end = this.length; } + + if (start < 0) { start = this.length + start; } + if (end < 0) { end = this.length + end; } + + start = clamp(start, 0, this.length); + end = clamp(end, 0, this.length); + + var len = end - start; + if (len < 0) { + len = 0; + } + + return new this.constructor( + this.buffer, this.byteOffset + start * this.BYTES_PER_ELEMENT, len); + }; + + return ctor; + } + + var Int8Array = makeConstructor(1, packI8, unpackI8); + var Uint8Array = makeConstructor(1, packU8, unpackU8); + var Uint8ClampedArray = makeConstructor(1, packU8Clamped, unpackU8); + var Int16Array = makeConstructor(2, packI16, unpackI16); + var Uint16Array = makeConstructor(2, packU16, unpackU16); + var Int32Array = makeConstructor(4, packI32, unpackI32); + var Uint32Array = makeConstructor(4, packU32, unpackU32); + var Float32Array = makeConstructor(4, packF32, unpackF32); + var Float64Array = makeConstructor(8, packF64, unpackF64); + + exports.Int8Array = exports.Int8Array || Int8Array; + exports.Uint8Array = exports.Uint8Array || Uint8Array; + exports.Uint8ClampedArray = exports.Uint8ClampedArray || Uint8ClampedArray; + exports.Int16Array = exports.Int16Array || Int16Array; + exports.Uint16Array = exports.Uint16Array || Uint16Array; + exports.Int32Array = exports.Int32Array || Int32Array; + exports.Uint32Array = exports.Uint32Array || Uint32Array; + exports.Float32Array = exports.Float32Array || Float32Array; + exports.Float64Array = exports.Float64Array || Float64Array; +}()); + +// +// 6 The DataView View Type +// + +(function() { + function r(array, index) { + return ECMAScript.IsCallable(array.get) ? array.get(index) : array[index]; + } + + var IS_BIG_ENDIAN = (function() { + var u16array = new(exports.Uint16Array)([0x1234]), + u8array = new(exports.Uint8Array)(u16array.buffer); + return r(u8array, 0) === 0x12; + }()); + + // Constructor(ArrayBuffer buffer, + // optional unsigned long byteOffset, + // optional unsigned long byteLength) + /** @constructor */ + var DataView = function DataView(buffer, byteOffset, byteLength) { + if (arguments.length === 0) { + buffer = new exports.ArrayBuffer(0); + } else if (!(buffer instanceof exports.ArrayBuffer || ECMAScript.Class(buffer) === 'ArrayBuffer')) { + throw new TypeError("TypeError"); + } + + this.buffer = buffer || new exports.ArrayBuffer(0); + + this.byteOffset = ECMAScript.ToUint32(byteOffset); + if (this.byteOffset > this.buffer.byteLength) { + throw new RangeError("byteOffset out of range"); + } + + if (arguments.length < 3) { + this.byteLength = this.buffer.byteLength - this.byteOffset; + } else { + this.byteLength = ECMAScript.ToUint32(byteLength); + } + + if ((this.byteOffset + this.byteLength) > this.buffer.byteLength) { + throw new RangeError("byteOffset and length reference an area beyond the end of the buffer"); + } + + configureProperties(this); + }; + + function makeGetter(arrayType) { + return function(byteOffset, littleEndian) { + + byteOffset = ECMAScript.ToUint32(byteOffset); + + if (byteOffset + arrayType.BYTES_PER_ELEMENT > this.byteLength) { + throw new RangeError("Array index out of range"); + } + byteOffset += this.byteOffset; + + var uint8Array = new exports.Uint8Array(this.buffer, byteOffset, arrayType.BYTES_PER_ELEMENT), + bytes = [], i; + for (i = 0; i < arrayType.BYTES_PER_ELEMENT; i += 1) { + bytes.push(r(uint8Array, i)); + } + + if (Boolean(littleEndian) === Boolean(IS_BIG_ENDIAN)) { + bytes.reverse(); + } + + return r(new arrayType(new exports.Uint8Array(bytes).buffer), 0); + }; + } + + DataView.prototype.getUint8 = makeGetter(exports.Uint8Array); + DataView.prototype.getInt8 = makeGetter(exports.Int8Array); + DataView.prototype.getUint16 = makeGetter(exports.Uint16Array); + DataView.prototype.getInt16 = makeGetter(exports.Int16Array); + DataView.prototype.getUint32 = makeGetter(exports.Uint32Array); + DataView.prototype.getInt32 = makeGetter(exports.Int32Array); + DataView.prototype.getFloat32 = makeGetter(exports.Float32Array); + DataView.prototype.getFloat64 = makeGetter(exports.Float64Array); + + function makeSetter(arrayType) { + return function(byteOffset, value, littleEndian) { + + byteOffset = ECMAScript.ToUint32(byteOffset); + if (byteOffset + arrayType.BYTES_PER_ELEMENT > this.byteLength) { + throw new RangeError("Array index out of range"); + } + + // Get bytes + var typeArray = new arrayType([value]), + byteArray = new exports.Uint8Array(typeArray.buffer), + bytes = [], i, byteView; + + for (i = 0; i < arrayType.BYTES_PER_ELEMENT; i += 1) { + bytes.push(r(byteArray, i)); + } + + // Flip if necessary + if (Boolean(littleEndian) === Boolean(IS_BIG_ENDIAN)) { + bytes.reverse(); + } + + // Write them + byteView = new exports.Uint8Array(this.buffer, byteOffset, arrayType.BYTES_PER_ELEMENT); + byteView.set(bytes); + }; + } + + DataView.prototype.setUint8 = makeSetter(exports.Uint8Array); + DataView.prototype.setInt8 = makeSetter(exports.Int8Array); + DataView.prototype.setUint16 = makeSetter(exports.Uint16Array); + DataView.prototype.setInt16 = makeSetter(exports.Int16Array); + DataView.prototype.setUint32 = makeSetter(exports.Uint32Array); + DataView.prototype.setInt32 = makeSetter(exports.Int32Array); + DataView.prototype.setFloat32 = makeSetter(exports.Float32Array); + DataView.prototype.setFloat64 = makeSetter(exports.Float64Array); + + exports.DataView = exports.DataView || DataView; + +}()); diff --git a/node_modules/typedarray/package.json b/node_modules/typedarray/package.json new file mode 100644 index 0000000..a7854a0 --- /dev/null +++ b/node_modules/typedarray/package.json @@ -0,0 +1,55 @@ +{ + "name": "typedarray", + "version": "0.0.6", + "description": "TypedArray polyfill for old browsers", + "main": "index.js", + "devDependencies": { + "tape": "~2.3.2" + }, + "scripts": { + "test": "tape test/*.js test/server/*.js" + }, + "repository": { + "type": "git", + "url": "git://github.com/substack/typedarray.git" + }, + "homepage": "https://github.com/substack/typedarray", + "keywords": [ + "ArrayBuffer", + "DataView", + "Float32Array", + "Float64Array", + "Int8Array", + "Int16Array", + "Int32Array", + "Uint8Array", + "Uint8ClampedArray", + "Uint16Array", + "Uint32Array", + "typed", + "array", + "polyfill" + ], + "author": { + "name": "James Halliday", + "email": "mail@substack.net", + "url": "http://substack.net" + }, + "license": "MIT", + "testling": { + "files": "test/*.js", + "browsers": [ + "ie/6..latest", + "firefox/16..latest", + "firefox/nightly", + "chrome/22..latest", + "chrome/canary", + "opera/12..latest", + "opera/next", + "safari/5.1..latest", + "ipad/6.0..latest", + "iphone/6.0..latest", + "android-browser/4.2..latest" + ] + } +} diff --git a/node_modules/typedarray/readme.markdown b/node_modules/typedarray/readme.markdown new file mode 100644 index 0000000..d18f6f7 --- /dev/null +++ b/node_modules/typedarray/readme.markdown @@ -0,0 +1,61 @@ +# typedarray + +TypedArray polyfill ripped from [this +module](https://raw.github.com/inexorabletash/polyfill). + +[![build status](https://secure.travis-ci.org/substack/typedarray.png)](http://travis-ci.org/substack/typedarray) + +[![testling badge](https://ci.testling.com/substack/typedarray.png)](https://ci.testling.com/substack/typedarray) + +# example + +``` js +var Uint8Array = require('typedarray').Uint8Array; +var ua = new Uint8Array(5); +ua[1] = 256 + 55; +console.log(ua[1]); +``` + +output: + +``` +55 +``` + +# methods + +``` js +var TA = require('typedarray') +``` + +The `TA` object has the following constructors: + +* TA.ArrayBuffer +* TA.DataView +* TA.Float32Array +* TA.Float64Array +* TA.Int8Array +* TA.Int16Array +* TA.Int32Array +* TA.Uint8Array +* TA.Uint8ClampedArray +* TA.Uint16Array +* TA.Uint32Array + +# install + +With [npm](https://npmjs.org) do: + +``` +npm install typedarray +``` + +To use this module in the browser, compile with +[browserify](http://browserify.org) +or download a UMD build from browserify CDN: + +http://wzrd.in/standalone/typedarray@latest + +# license + +MIT diff --git a/node_modules/typedarray/test/server/undef_globals.js b/node_modules/typedarray/test/server/undef_globals.js new file mode 100644 index 0000000..425950f --- /dev/null +++ b/node_modules/typedarray/test/server/undef_globals.js @@ -0,0 +1,19 @@ +var test = require('tape'); +var vm = require('vm'); +var fs = require('fs'); +var src = fs.readFileSync(__dirname + '/../../index.js', 'utf8'); + +test('u8a without globals', function (t) { + var c = { + module: { exports: {} }, + }; + c.exports = c.module.exports; + vm.runInNewContext(src, c); + var TA = c.module.exports; + var ua = new(TA.Uint8Array)(5); + + t.equal(ua.length, 5); + ua[1] = 256 + 55; + t.equal(ua[1], 55); + t.end(); +}); diff --git a/node_modules/typedarray/test/tarray.js b/node_modules/typedarray/test/tarray.js new file mode 100644 index 0000000..df596a3 --- /dev/null +++ b/node_modules/typedarray/test/tarray.js @@ -0,0 +1,10 @@ +var TA = require('../'); +var test = require('tape'); + +test('tiny u8a test', function (t) { + var ua = new(TA.Uint8Array)(5); + t.equal(ua.length, 5); + ua[1] = 256 + 55; + t.equal(ua[1], 55); + t.end(); +}); diff --git a/node_modules/util-deprecate/History.md b/node_modules/util-deprecate/History.md new file mode 100644 index 0000000..acc8675 --- /dev/null +++ b/node_modules/util-deprecate/History.md @@ -0,0 +1,16 @@ + +1.0.2 / 2015-10-07 +================== + + * use try/catch when checking `localStorage` (#3, @kumavis) + +1.0.1 / 2014-11-25 +================== + + * browser: use `console.warn()` for deprecation calls + * browser: more jsdocs + +1.0.0 / 2014-04-30 +================== + + * initial commit diff --git a/node_modules/util-deprecate/LICENSE b/node_modules/util-deprecate/LICENSE new file mode 100644 index 0000000..6a60e8c --- /dev/null +++ b/node_modules/util-deprecate/LICENSE @@ -0,0 +1,24 @@ +(The MIT License) + +Copyright (c) 2014 Nathan Rajlich + +Permission is hereby granted, free of charge, to any person +obtaining a copy of this software and associated documentation +files (the "Software"), to deal in the Software without +restriction, including without limitation the rights to use, +copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the +Software is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +OTHER DEALINGS IN THE SOFTWARE. diff --git a/node_modules/util-deprecate/README.md b/node_modules/util-deprecate/README.md new file mode 100644 index 0000000..75622fa --- /dev/null +++ b/node_modules/util-deprecate/README.md @@ -0,0 +1,53 @@ +util-deprecate +============== +### The Node.js `util.deprecate()` function with browser support + +In Node.js, this module simply re-exports the `util.deprecate()` function. + +In the web browser (i.e. via browserify), a browser-specific implementation +of the `util.deprecate()` function is used. + + +## API + +A `deprecate()` function is the only thing exposed by this module. + +``` javascript +// setup: +exports.foo = deprecate(foo, 'foo() is deprecated, use bar() instead'); + + +// users see: +foo(); +// foo() is deprecated, use bar() instead +foo(); +foo(); +``` + + +## License + +(The MIT License) + +Copyright (c) 2014 Nathan Rajlich + +Permission is hereby granted, free of charge, to any person +obtaining a copy of this software and associated documentation +files (the "Software"), to deal in the Software without +restriction, including without limitation the rights to use, +copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the +Software is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +OTHER DEALINGS IN THE SOFTWARE. diff --git a/node_modules/util-deprecate/browser.js b/node_modules/util-deprecate/browser.js new file mode 100644 index 0000000..549ae2f --- /dev/null +++ b/node_modules/util-deprecate/browser.js @@ -0,0 +1,67 @@ + +/** + * Module exports. + */ + +module.exports = deprecate; + +/** + * Mark that a method should not be used. + * Returns a modified function which warns once by default. + * + * If `localStorage.noDeprecation = true` is set, then it is a no-op. + * + * If `localStorage.throwDeprecation = true` is set, then deprecated functions + * will throw an Error when invoked. + * + * If `localStorage.traceDeprecation = true` is set, then deprecated functions + * will invoke `console.trace()` instead of `console.error()`. + * + * @param {Function} fn - the function to deprecate + * @param {String} msg - the string to print to the console when `fn` is invoked + * @returns {Function} a new "deprecated" version of `fn` + * @api public + */ + +function deprecate (fn, msg) { + if (config('noDeprecation')) { + return fn; + } + + var warned = false; + function deprecated() { + if (!warned) { + if (config('throwDeprecation')) { + throw new Error(msg); + } else if (config('traceDeprecation')) { + console.trace(msg); + } else { + console.warn(msg); + } + warned = true; + } + return fn.apply(this, arguments); + } + + return deprecated; +} + +/** + * Checks `localStorage` for boolean values for the given `name`. + * + * @param {String} name + * @returns {Boolean} + * @api private + */ + +function config (name) { + // accessing global.localStorage can trigger a DOMException in sandboxed iframes + try { + if (!global.localStorage) return false; + } catch (_) { + return false; + } + var val = global.localStorage[name]; + if (null == val) return false; + return String(val).toLowerCase() === 'true'; +} diff --git a/node_modules/util-deprecate/node.js b/node_modules/util-deprecate/node.js new file mode 100644 index 0000000..5e6fcff --- /dev/null +++ b/node_modules/util-deprecate/node.js @@ -0,0 +1,6 @@ + +/** + * For Node.js, simply re-export the core `util.deprecate` function. + */ + +module.exports = require('util').deprecate; diff --git a/node_modules/util-deprecate/package.json b/node_modules/util-deprecate/package.json new file mode 100644 index 0000000..2e79f89 --- /dev/null +++ b/node_modules/util-deprecate/package.json @@ -0,0 +1,27 @@ +{ + "name": "util-deprecate", + "version": "1.0.2", + "description": "The Node.js `util.deprecate()` function with browser support", + "main": "node.js", + "browser": "browser.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "repository": { + "type": "git", + "url": "git://github.com/TooTallNate/util-deprecate.git" + }, + "keywords": [ + "util", + "deprecate", + "browserify", + "browser", + "node" + ], + "author": "Nathan Rajlich (http://n8.io/)", + "license": "MIT", + "bugs": { + "url": "https://github.com/TooTallNate/util-deprecate/issues" + }, + "homepage": "https://github.com/TooTallNate/util-deprecate" +} diff --git a/node_modules/xtend/.jshintrc b/node_modules/xtend/.jshintrc new file mode 100644 index 0000000..77887b5 --- /dev/null +++ b/node_modules/xtend/.jshintrc @@ -0,0 +1,30 @@ +{ + "maxdepth": 4, + "maxstatements": 200, + "maxcomplexity": 12, + "maxlen": 80, + "maxparams": 5, + + "curly": true, + "eqeqeq": true, + "immed": true, + "latedef": false, + "noarg": true, + "noempty": true, + "nonew": true, + "undef": true, + "unused": "vars", + "trailing": true, + + "quotmark": true, + "expr": true, + "asi": true, + + "browser": false, + "esnext": true, + "devel": false, + "node": false, + "nonstandard": false, + + "predef": ["require", "module", "__dirname", "__filename"] +} diff --git a/node_modules/xtend/LICENSE b/node_modules/xtend/LICENSE new file mode 100644 index 0000000..0099f4f --- /dev/null +++ b/node_modules/xtend/LICENSE @@ -0,0 +1,20 @@ +The MIT License (MIT) +Copyright (c) 2012-2014 Raynos. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/node_modules/xtend/README.md b/node_modules/xtend/README.md new file mode 100644 index 0000000..4a2703c --- /dev/null +++ b/node_modules/xtend/README.md @@ -0,0 +1,32 @@ +# xtend + +[![browser support][3]][4] + +[![locked](http://badges.github.io/stability-badges/dist/locked.svg)](http://github.com/badges/stability-badges) + +Extend like a boss + +xtend is a basic utility library which allows you to extend an object by appending all of the properties from each object in a list. When there are identical properties, the right-most property takes precedence. + +## Examples + +```js +var extend = require("xtend") + +// extend returns a new object. Does not mutate arguments +var combination = extend({ + a: "a", + b: "c" +}, { + b: "b" +}) +// { a: "a", b: "b" } +``` + +## Stability status: Locked + +## MIT Licensed + + + [3]: http://ci.testling.com/Raynos/xtend.png + [4]: http://ci.testling.com/Raynos/xtend diff --git a/node_modules/xtend/immutable.js b/node_modules/xtend/immutable.js new file mode 100644 index 0000000..94889c9 --- /dev/null +++ b/node_modules/xtend/immutable.js @@ -0,0 +1,19 @@ +module.exports = extend + +var hasOwnProperty = Object.prototype.hasOwnProperty; + +function extend() { + var target = {} + + for (var i = 0; i < arguments.length; i++) { + var source = arguments[i] + + for (var key in source) { + if (hasOwnProperty.call(source, key)) { + target[key] = source[key] + } + } + } + + return target +} diff --git a/node_modules/xtend/mutable.js b/node_modules/xtend/mutable.js new file mode 100644 index 0000000..72debed --- /dev/null +++ b/node_modules/xtend/mutable.js @@ -0,0 +1,17 @@ +module.exports = extend + +var hasOwnProperty = Object.prototype.hasOwnProperty; + +function extend(target) { + for (var i = 1; i < arguments.length; i++) { + var source = arguments[i] + + for (var key in source) { + if (hasOwnProperty.call(source, key)) { + target[key] = source[key] + } + } + } + + return target +} diff --git a/node_modules/xtend/package.json b/node_modules/xtend/package.json new file mode 100644 index 0000000..f7a39d1 --- /dev/null +++ b/node_modules/xtend/package.json @@ -0,0 +1,55 @@ +{ + "name": "xtend", + "version": "4.0.2", + "description": "extend like a boss", + "keywords": [ + "extend", + "merge", + "options", + "opts", + "object", + "array" + ], + "author": "Raynos ", + "repository": "git://github.com/Raynos/xtend.git", + "main": "immutable", + "scripts": { + "test": "node test" + }, + "dependencies": {}, + "devDependencies": { + "tape": "~1.1.0" + }, + "homepage": "https://github.com/Raynos/xtend", + "contributors": [ + { + "name": "Jake Verbaten" + }, + { + "name": "Matt Esch" + } + ], + "bugs": { + "url": "https://github.com/Raynos/xtend/issues", + "email": "raynos2@gmail.com" + }, + "license": "MIT", + "testling": { + "files": "test.js", + "browsers": [ + "ie/7..latest", + "firefox/16..latest", + "firefox/nightly", + "chrome/22..latest", + "chrome/canary", + "opera/12..latest", + "opera/next", + "safari/5.1..latest", + "ipad/6.0..latest", + "iphone/6.0..latest" + ] + }, + "engines": { + "node": ">=0.4" + } +} diff --git a/node_modules/xtend/test.js b/node_modules/xtend/test.js new file mode 100644 index 0000000..b895b42 --- /dev/null +++ b/node_modules/xtend/test.js @@ -0,0 +1,103 @@ +var test = require("tape") +var extend = require("./") +var mutableExtend = require("./mutable") + +test("merge", function(assert) { + var a = { a: "foo" } + var b = { b: "bar" } + + assert.deepEqual(extend(a, b), { a: "foo", b: "bar" }) + assert.end() +}) + +test("replace", function(assert) { + var a = { a: "foo" } + var b = { a: "bar" } + + assert.deepEqual(extend(a, b), { a: "bar" }) + assert.end() +}) + +test("undefined", function(assert) { + var a = { a: undefined } + var b = { b: "foo" } + + assert.deepEqual(extend(a, b), { a: undefined, b: "foo" }) + assert.deepEqual(extend(b, a), { a: undefined, b: "foo" }) + assert.end() +}) + +test("handle 0", function(assert) { + var a = { a: "default" } + var b = { a: 0 } + + assert.deepEqual(extend(a, b), { a: 0 }) + assert.deepEqual(extend(b, a), { a: "default" }) + assert.end() +}) + +test("is immutable", function (assert) { + var record = {} + + extend(record, { foo: "bar" }) + assert.equal(record.foo, undefined) + assert.end() +}) + +test("null as argument", function (assert) { + var a = { foo: "bar" } + var b = null + var c = void 0 + + assert.deepEqual(extend(b, a, c), { foo: "bar" }) + assert.end() +}) + +test("mutable", function (assert) { + var a = { foo: "bar" } + + mutableExtend(a, { bar: "baz" }) + + assert.equal(a.bar, "baz") + assert.end() +}) + +test("null prototype", function(assert) { + var a = { a: "foo" } + var b = Object.create(null) + b.b = "bar"; + + assert.deepEqual(extend(a, b), { a: "foo", b: "bar" }) + assert.end() +}) + +test("null prototype mutable", function (assert) { + var a = { foo: "bar" } + var b = Object.create(null) + b.bar = "baz"; + + mutableExtend(a, b) + + assert.equal(a.bar, "baz") + assert.end() +}) + +test("prototype pollution", function (assert) { + var a = {} + var maliciousPayload = '{"__proto__":{"oops":"It works!"}}' + + assert.strictEqual(a.oops, undefined) + extend({}, maliciousPayload) + assert.strictEqual(a.oops, undefined) + assert.end() +}) + +test("prototype pollution mutable", function (assert) { + var a = {} + var maliciousPayload = '{"__proto__":{"oops":"It works!"}}' + + assert.strictEqual(a.oops, undefined) + mutableExtend({}, maliciousPayload) + assert.strictEqual(a.oops, undefined) + assert.end() +}) diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..ada0ce3 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,231 @@ +{ + "name": "SistemPakar", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "dependencies": { + "image_picker": "^1.0.0", + "multer": "^1.4.5-lts.2" + } + }, + "node_modules/append-field": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/append-field/-/append-field-1.0.0.tgz", + "integrity": "sha512-klpgFSWLW1ZEs8svjfb7g4qWY0YS5imI82dTg+QahUvJ8YqAY0P10Uk8tTyh9ZGuYEZEMaeJYCF5BFuX552hsw==", + "license": "MIT" + }, + "node_modules/buffer-from": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", + "license": "MIT" + }, + "node_modules/busboy": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/busboy/-/busboy-1.6.0.tgz", + "integrity": "sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==", + "dependencies": { + "streamsearch": "^1.1.0" + }, + "engines": { + "node": ">=10.16.0" + } + }, + "node_modules/concat-stream": { + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz", + "integrity": "sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==", + "engines": [ + "node >= 0.8" + ], + "license": "MIT", + "dependencies": { + "buffer-from": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^2.2.2", + "typedarray": "^0.0.6" + } + }, + "node_modules/core-util-is": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", + "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", + "license": "MIT" + }, + "node_modules/image_picker": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/image_picker/-/image_picker-1.0.0.tgz", + "integrity": "sha512-kMx5lZ1TpgjnUPUJz/wCF8tpcYfZz4cSkuIoWraeaBR8Rb2MTziMqXKlDIS0FCWTdSLrtB3xjw/YXTj97mqOLw==", + "license": "ISC" + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "license": "ISC" + }, + "node_modules/isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", + "license": "MIT" + }, + "node_modules/media-typer": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/mkdirp": { + "version": "0.5.6", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", + "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", + "license": "MIT", + "dependencies": { + "minimist": "^1.2.6" + }, + "bin": { + "mkdirp": "bin/cmd.js" + } + }, + "node_modules/multer": { + "version": "1.4.5-lts.2", + "resolved": "https://registry.npmjs.org/multer/-/multer-1.4.5-lts.2.tgz", + "integrity": "sha512-VzGiVigcG9zUAoCNU+xShztrlr1auZOlurXynNvO9GiWD1/mTBbUljOKY+qMeazBqXgRnjzeEgJI/wyjJUHg9A==", + "license": "MIT", + "dependencies": { + "append-field": "^1.0.0", + "busboy": "^1.0.0", + "concat-stream": "^1.5.2", + "mkdirp": "^0.5.4", + "object-assign": "^4.1.1", + "type-is": "^1.6.4", + "xtend": "^4.0.0" + }, + "engines": { + "node": ">= 6.0.0" + } + }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/process-nextick-args": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", + "license": "MIT" + }, + "node_modules/readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "license": "MIT", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "license": "MIT" + }, + "node_modules/streamsearch": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-1.1.0.tgz", + "integrity": "sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==", + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/type-is": { + "version": "1.6.18", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", + "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", + "license": "MIT", + "dependencies": { + "media-typer": "0.3.0", + "mime-types": "~2.1.24" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/typedarray": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", + "integrity": "sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==", + "license": "MIT" + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "license": "MIT" + }, + "node_modules/xtend": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", + "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", + "license": "MIT", + "engines": { + "node": ">=0.4" + } + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..14e1cc1 --- /dev/null +++ b/package.json @@ -0,0 +1,6 @@ +{ + "dependencies": { + "image_picker": "^1.0.0", + "multer": "^1.4.5-lts.2" + } +}