From 17a1b4bed3b7f98396dd2805fcfb327fb87c4b03 Mon Sep 17 00:00:00 2001 From: didier amyot Date: Sun, 11 Jun 2023 15:43:45 -0400 Subject: [PATCH] Started to implement server --- flake.nix | 1 + src/fileservice.ts | 79 +- src/restapi.ts | 48 + src/server.ts | 21 +- src/utils.ts | 69 + tests/fileservice_test.ts | 73 +- tests/utils.ts | 56 + tests/utils_test.ts | 83 + vendor/deno.land/std@0.178.0/_util/asserts.ts | 25 + vendor/deno.land/std@0.178.0/_util/os.ts | 23 + .../deno.land/std@0.178.0/async/deferred.ts | 48 + .../deno.land/std@0.178.0/bytes/bytes_list.ts | 161 + vendor/deno.land/std@0.178.0/bytes/concat.ts | 26 + vendor/deno.land/std@0.178.0/bytes/copy.ts | 38 + .../deno.land/std@0.178.0/bytes/ends_with.ts | 25 + vendor/deno.land/std@0.178.0/bytes/equals.ts | 44 + .../std@0.178.0/bytes/includes_needle.ts | 27 + .../std@0.178.0/bytes/index_of_needle.ts | 49 + .../std@0.178.0/bytes/last_index_of_needle.ts | 49 + vendor/deno.land/std@0.178.0/bytes/mod.ts | 20 + vendor/deno.land/std@0.178.0/bytes/repeat.ts | 40 + .../std@0.178.0/bytes/starts_with.ts | 21 + .../deno.land/std@0.178.0/crypto/keystack.ts | 169 + .../std@0.178.0/crypto/timing_safe_equal.ts | 33 + .../deno.land/std@0.178.0/encoding/base64.ts | 144 + .../std@0.178.0/encoding/base64url.ts | 70 + .../std@0.178.0/http/_negotiation/common.ts | 51 + .../std@0.178.0/http/_negotiation/encoding.ts | 162 + .../std@0.178.0/http/_negotiation/language.ts | 144 + .../http/_negotiation/media_type.ts | 237 + .../deno.land/std@0.178.0/http/cookie_map.ts | 805 ++ .../deno.land/std@0.178.0/http/http_errors.ts | 204 + .../deno.land/std@0.178.0/http/http_status.ts | 343 + .../deno.land/std@0.178.0/http/negotiation.ts | 186 + vendor/deno.land/std@0.178.0/io/buf_reader.ts | 434 + vendor/deno.land/std@0.178.0/io/buf_writer.ts | 221 + vendor/deno.land/std@0.178.0/io/buffer.ts | 247 + vendor/deno.land/std@0.178.0/io/copy_n.ts | 40 + .../std@0.178.0/io/limited_reader.ts | 29 + vendor/deno.land/std@0.178.0/io/mod.ts | 27 + .../deno.land/std@0.178.0/io/multi_reader.ts | 24 + vendor/deno.land/std@0.178.0/io/read_delim.ts | 79 + vendor/deno.land/std@0.178.0/io/read_int.ts | 16 + vendor/deno.land/std@0.178.0/io/read_lines.ts | 48 + vendor/deno.land/std@0.178.0/io/read_long.ts | 25 + vendor/deno.land/std@0.178.0/io/read_range.ts | 91 + vendor/deno.land/std@0.178.0/io/read_short.ts | 15 + .../std@0.178.0/io/read_string_delim.ts | 36 + .../std@0.178.0/io/slice_long_to_bytes.ts | 18 + .../deno.land/std@0.178.0/io/string_reader.ts | 38 + .../deno.land/std@0.178.0/io/string_writer.ts | 73 + .../deno.land/std@0.178.0/media_types/_db.ts | 46 + .../std@0.178.0/media_types/_util.ts | 152 + .../std@0.178.0/media_types/content_type.ts | 75 + .../std@0.178.0/media_types/extension.ts | 26 + .../media_types/extensions_by_type.ts | 31 + .../media_types/format_media_type.ts | 68 + .../std@0.178.0/media_types/get_charset.ts | 37 + .../deno.land/std@0.178.0/media_types/mod.ts | 24 + .../media_types/parse_media_type.ts | 129 + .../media_types/type_by_extension.ts | 24 + .../media_types/vendor/mime-db.v1.52.0.ts | 8556 +++++++++++++++++ .../deno.land/std@0.178.0/path/_constants.ts | 49 + .../deno.land/std@0.178.0/path/_interface.ts | 30 + vendor/deno.land/std@0.178.0/path/_util.ts | 194 + vendor/deno.land/std@0.178.0/path/common.ts | 40 + vendor/deno.land/std@0.178.0/path/glob.ts | 418 + vendor/deno.land/std@0.178.0/path/mod.ts | 53 + vendor/deno.land/std@0.178.0/path/posix.ts | 487 + .../deno.land/std@0.178.0/path/separator.ts | 7 + vendor/deno.land/std@0.178.0/path/win32.ts | 962 ++ .../deno.land/std@0.178.0/streams/_common.ts | 26 + .../deno.land/std@0.178.0/streams/buffer.ts | 167 + .../std@0.178.0/streams/byte_slice_stream.ts | 44 + vendor/deno.land/std@0.178.0/streams/copy.ts | 47 + .../std@0.178.0/streams/delimiter_stream.ts | 113 + .../streams/early_zip_readable_streams.ts | 33 + .../std@0.178.0/streams/iterate_reader.ts | 101 + .../streams/limited_bytes_transform_stream.ts | 36 + .../streams/limited_transform_stream.ts | 32 + .../streams/merge_readable_streams.ts | 33 + vendor/deno.land/std@0.178.0/streams/mod.ts | 31 + .../deno.land/std@0.178.0/streams/read_all.ts | 60 + .../streams/readable_stream_from_iterable.ts | 62 + .../streams/readable_stream_from_reader.ts | 80 + .../streams/reader_from_iterable.ts | 55 + .../streams/reader_from_stream_reader.ts | 42 + .../streams/text_delimiter_stream.ts | 67 + .../std@0.178.0/streams/text_line_stream.ts | 73 + .../streams/to_transform_stream.ts | 74 + .../streams/writable_stream_from_writer.ts | 51 + .../std@0.178.0/streams/write_all.ts | 64 + .../streams/writer_from_stream_writer.ts | 34 + .../streams/zip_readable_streams.ts | 34 + vendor/deno.land/std@0.178.0/types.d.ts | 89 + vendor/deno.land/std@0.189.0/_util/asserts.ts | 25 + .../deno.land/std@0.189.0/async/deferred.ts | 48 + .../deno.land/std@0.189.0/bytes/bytes_list.ts | 165 + vendor/deno.land/std@0.189.0/bytes/copy.ts | 38 + vendor/deno.land/std@0.189.0/io/buffer.ts | 249 + .../deno.land/std@0.189.0/streams/_common.ts | 26 + .../deno.land/std@0.189.0/streams/buffer.ts | 169 + .../std@0.189.0/streams/byte_slice_stream.ts | 45 + vendor/deno.land/std@0.189.0/streams/copy.ts | 48 + .../std@0.189.0/streams/delimiter_stream.ts | 114 + .../streams/early_zip_readable_streams.ts | 34 + .../std@0.189.0/streams/iterate_reader.ts | 102 + .../streams/limited_bytes_transform_stream.ts | 37 + .../streams/limited_transform_stream.ts | 33 + .../streams/merge_readable_streams.ts | 33 + vendor/deno.land/std@0.189.0/streams/mod.ts | 31 + .../deno.land/std@0.189.0/streams/read_all.ts | 61 + .../streams/readable_stream_from_iterable.ts | 63 + .../streams/readable_stream_from_reader.ts | 81 + .../streams/reader_from_iterable.ts | 56 + .../streams/reader_from_stream_reader.ts | 43 + .../streams/text_delimiter_stream.ts | 78 + .../std@0.189.0/streams/text_line_stream.ts | 74 + .../streams/to_transform_stream.ts | 75 + .../streams/writable_stream_from_writer.ts | 52 + .../std@0.189.0/streams/write_all.ts | 65 + .../streams/writer_from_stream_writer.ts | 35 + .../streams/zip_readable_streams.ts | 35 + vendor/deno.land/std@0.189.0/types.d.ts | 90 + vendor/deno.land/std@0.191.0/_util/asserts.ts | 25 + .../deno.land/std@0.191.0/bytes/bytes_list.ts | 165 + vendor/deno.land/std@0.191.0/bytes/concat.ts | 26 + vendor/deno.land/std@0.191.0/bytes/copy.ts | 38 + vendor/deno.land/std@0.191.0/io/buf_reader.ts | 414 + vendor/deno.land/std@0.191.0/io/buf_writer.ts | 222 + vendor/deno.land/std@0.191.0/io/buffer.ts | 249 + vendor/deno.land/std@0.191.0/io/copy_n.ts | 41 + .../std@0.191.0/io/limited_reader.ts | 31 + vendor/deno.land/std@0.191.0/io/mod.ts | 27 + .../deno.land/std@0.191.0/io/multi_reader.ts | 25 + vendor/deno.land/std@0.191.0/io/read_delim.ts | 80 + vendor/deno.land/std@0.191.0/io/read_int.ts | 16 + vendor/deno.land/std@0.191.0/io/read_lines.ts | 49 + vendor/deno.land/std@0.191.0/io/read_long.ts | 25 + vendor/deno.land/std@0.191.0/io/read_range.ts | 91 + vendor/deno.land/std@0.191.0/io/read_short.ts | 15 + .../std@0.191.0/io/read_string_delim.ts | 37 + .../std@0.191.0/io/slice_long_to_bytes.ts | 19 + .../deno.land/std@0.191.0/io/string_reader.ts | 39 + .../deno.land/std@0.191.0/io/string_writer.ts | 73 + .../deno.land/std@0.191.0/streams/read_all.ts | 61 + vendor/deno.land/x/oak@v12.1.0/application.ts | 699 ++ vendor/deno.land/x/oak@v12.1.0/body.ts | 407 + vendor/deno.land/x/oak@v12.1.0/buf_reader.ts | 183 + .../x/oak@v12.1.0/content_disposition.ts | 143 + vendor/deno.land/x/oak@v12.1.0/context.ts | 348 + vendor/deno.land/x/oak@v12.1.0/deps.ts | 71 + vendor/deno.land/x/oak@v12.1.0/etag.ts | 182 + vendor/deno.land/x/oak@v12.1.0/headers.ts | 78 + vendor/deno.land/x/oak@v12.1.0/helpers.ts | 53 + .../deno.land/x/oak@v12.1.0/http_request.ts | 102 + .../x/oak@v12.1.0/http_server_flash.ts | 121 + .../x/oak@v12.1.0/http_server_native.ts | 162 + .../oak@v12.1.0/http_server_native_request.ts | 143 + vendor/deno.land/x/oak@v12.1.0/isMediaType.ts | 89 + vendor/deno.land/x/oak@v12.1.0/mediaTyper.ts | 82 + vendor/deno.land/x/oak@v12.1.0/middleware.ts | 46 + .../x/oak@v12.1.0/middleware/proxy.ts | 273 + vendor/deno.land/x/oak@v12.1.0/mod.ts | 141 + vendor/deno.land/x/oak@v12.1.0/multipart.ts | 512 + vendor/deno.land/x/oak@v12.1.0/range.ts | 169 + vendor/deno.land/x/oak@v12.1.0/request.ts | 264 + vendor/deno.land/x/oak@v12.1.0/response.ts | 356 + vendor/deno.land/x/oak@v12.1.0/router.ts | 1374 +++ vendor/deno.land/x/oak@v12.1.0/send.ts | 361 + .../x/oak@v12.1.0/server_sent_event.ts | 361 + .../x/oak@v12.1.0/structured_clone.ts | 177 + vendor/deno.land/x/oak@v12.1.0/testing.ts | 179 + vendor/deno.land/x/oak@v12.1.0/types.d.ts | 134 + vendor/deno.land/x/oak@v12.1.0/util.ts | 472 + .../x/path_to_regexp@v6.2.1/index.ts | 621 ++ vendor/import_map.json | 1 + 177 files changed, 29894 insertions(+), 74 deletions(-) create mode 100644 src/restapi.ts create mode 100644 src/utils.ts create mode 100644 tests/utils.ts create mode 100644 tests/utils_test.ts create mode 100644 vendor/deno.land/std@0.178.0/_util/asserts.ts create mode 100644 vendor/deno.land/std@0.178.0/_util/os.ts create mode 100644 vendor/deno.land/std@0.178.0/async/deferred.ts create mode 100644 vendor/deno.land/std@0.178.0/bytes/bytes_list.ts create mode 100644 vendor/deno.land/std@0.178.0/bytes/concat.ts create mode 100644 vendor/deno.land/std@0.178.0/bytes/copy.ts create mode 100644 vendor/deno.land/std@0.178.0/bytes/ends_with.ts create mode 100644 vendor/deno.land/std@0.178.0/bytes/equals.ts create mode 100644 vendor/deno.land/std@0.178.0/bytes/includes_needle.ts create mode 100644 vendor/deno.land/std@0.178.0/bytes/index_of_needle.ts create mode 100644 vendor/deno.land/std@0.178.0/bytes/last_index_of_needle.ts create mode 100644 vendor/deno.land/std@0.178.0/bytes/mod.ts create mode 100644 vendor/deno.land/std@0.178.0/bytes/repeat.ts create mode 100644 vendor/deno.land/std@0.178.0/bytes/starts_with.ts create mode 100644 vendor/deno.land/std@0.178.0/crypto/keystack.ts create mode 100644 vendor/deno.land/std@0.178.0/crypto/timing_safe_equal.ts create mode 100644 vendor/deno.land/std@0.178.0/encoding/base64.ts create mode 100644 vendor/deno.land/std@0.178.0/encoding/base64url.ts create mode 100644 vendor/deno.land/std@0.178.0/http/_negotiation/common.ts create mode 100644 vendor/deno.land/std@0.178.0/http/_negotiation/encoding.ts create mode 100644 vendor/deno.land/std@0.178.0/http/_negotiation/language.ts create mode 100644 vendor/deno.land/std@0.178.0/http/_negotiation/media_type.ts create mode 100644 vendor/deno.land/std@0.178.0/http/cookie_map.ts create mode 100644 vendor/deno.land/std@0.178.0/http/http_errors.ts create mode 100644 vendor/deno.land/std@0.178.0/http/http_status.ts create mode 100644 vendor/deno.land/std@0.178.0/http/negotiation.ts create mode 100644 vendor/deno.land/std@0.178.0/io/buf_reader.ts create mode 100644 vendor/deno.land/std@0.178.0/io/buf_writer.ts create mode 100644 vendor/deno.land/std@0.178.0/io/buffer.ts create mode 100644 vendor/deno.land/std@0.178.0/io/copy_n.ts create mode 100644 vendor/deno.land/std@0.178.0/io/limited_reader.ts create mode 100644 vendor/deno.land/std@0.178.0/io/mod.ts create mode 100644 vendor/deno.land/std@0.178.0/io/multi_reader.ts create mode 100644 vendor/deno.land/std@0.178.0/io/read_delim.ts create mode 100644 vendor/deno.land/std@0.178.0/io/read_int.ts create mode 100644 vendor/deno.land/std@0.178.0/io/read_lines.ts create mode 100644 vendor/deno.land/std@0.178.0/io/read_long.ts create mode 100644 vendor/deno.land/std@0.178.0/io/read_range.ts create mode 100644 vendor/deno.land/std@0.178.0/io/read_short.ts create mode 100644 vendor/deno.land/std@0.178.0/io/read_string_delim.ts create mode 100644 vendor/deno.land/std@0.178.0/io/slice_long_to_bytes.ts create mode 100644 vendor/deno.land/std@0.178.0/io/string_reader.ts create mode 100644 vendor/deno.land/std@0.178.0/io/string_writer.ts create mode 100644 vendor/deno.land/std@0.178.0/media_types/_db.ts create mode 100644 vendor/deno.land/std@0.178.0/media_types/_util.ts create mode 100644 vendor/deno.land/std@0.178.0/media_types/content_type.ts create mode 100644 vendor/deno.land/std@0.178.0/media_types/extension.ts create mode 100644 vendor/deno.land/std@0.178.0/media_types/extensions_by_type.ts create mode 100644 vendor/deno.land/std@0.178.0/media_types/format_media_type.ts create mode 100644 vendor/deno.land/std@0.178.0/media_types/get_charset.ts create mode 100644 vendor/deno.land/std@0.178.0/media_types/mod.ts create mode 100644 vendor/deno.land/std@0.178.0/media_types/parse_media_type.ts create mode 100644 vendor/deno.land/std@0.178.0/media_types/type_by_extension.ts create mode 100644 vendor/deno.land/std@0.178.0/media_types/vendor/mime-db.v1.52.0.ts create mode 100644 vendor/deno.land/std@0.178.0/path/_constants.ts create mode 100644 vendor/deno.land/std@0.178.0/path/_interface.ts create mode 100644 vendor/deno.land/std@0.178.0/path/_util.ts create mode 100644 vendor/deno.land/std@0.178.0/path/common.ts create mode 100644 vendor/deno.land/std@0.178.0/path/glob.ts create mode 100644 vendor/deno.land/std@0.178.0/path/mod.ts create mode 100644 vendor/deno.land/std@0.178.0/path/posix.ts create mode 100644 vendor/deno.land/std@0.178.0/path/separator.ts create mode 100644 vendor/deno.land/std@0.178.0/path/win32.ts create mode 100644 vendor/deno.land/std@0.178.0/streams/_common.ts create mode 100644 vendor/deno.land/std@0.178.0/streams/buffer.ts create mode 100644 vendor/deno.land/std@0.178.0/streams/byte_slice_stream.ts create mode 100644 vendor/deno.land/std@0.178.0/streams/copy.ts create mode 100644 vendor/deno.land/std@0.178.0/streams/delimiter_stream.ts create mode 100644 vendor/deno.land/std@0.178.0/streams/early_zip_readable_streams.ts create mode 100644 vendor/deno.land/std@0.178.0/streams/iterate_reader.ts create mode 100644 vendor/deno.land/std@0.178.0/streams/limited_bytes_transform_stream.ts create mode 100644 vendor/deno.land/std@0.178.0/streams/limited_transform_stream.ts create mode 100644 vendor/deno.land/std@0.178.0/streams/merge_readable_streams.ts create mode 100644 vendor/deno.land/std@0.178.0/streams/mod.ts create mode 100644 vendor/deno.land/std@0.178.0/streams/read_all.ts create mode 100644 vendor/deno.land/std@0.178.0/streams/readable_stream_from_iterable.ts create mode 100644 vendor/deno.land/std@0.178.0/streams/readable_stream_from_reader.ts create mode 100644 vendor/deno.land/std@0.178.0/streams/reader_from_iterable.ts create mode 100644 vendor/deno.land/std@0.178.0/streams/reader_from_stream_reader.ts create mode 100644 vendor/deno.land/std@0.178.0/streams/text_delimiter_stream.ts create mode 100644 vendor/deno.land/std@0.178.0/streams/text_line_stream.ts create mode 100644 vendor/deno.land/std@0.178.0/streams/to_transform_stream.ts create mode 100644 vendor/deno.land/std@0.178.0/streams/writable_stream_from_writer.ts create mode 100644 vendor/deno.land/std@0.178.0/streams/write_all.ts create mode 100644 vendor/deno.land/std@0.178.0/streams/writer_from_stream_writer.ts create mode 100644 vendor/deno.land/std@0.178.0/streams/zip_readable_streams.ts create mode 100644 vendor/deno.land/std@0.178.0/types.d.ts create mode 100644 vendor/deno.land/std@0.189.0/_util/asserts.ts create mode 100644 vendor/deno.land/std@0.189.0/async/deferred.ts create mode 100644 vendor/deno.land/std@0.189.0/bytes/bytes_list.ts create mode 100644 vendor/deno.land/std@0.189.0/bytes/copy.ts create mode 100644 vendor/deno.land/std@0.189.0/io/buffer.ts create mode 100644 vendor/deno.land/std@0.189.0/streams/_common.ts create mode 100644 vendor/deno.land/std@0.189.0/streams/buffer.ts create mode 100644 vendor/deno.land/std@0.189.0/streams/byte_slice_stream.ts create mode 100644 vendor/deno.land/std@0.189.0/streams/copy.ts create mode 100644 vendor/deno.land/std@0.189.0/streams/delimiter_stream.ts create mode 100644 vendor/deno.land/std@0.189.0/streams/early_zip_readable_streams.ts create mode 100644 vendor/deno.land/std@0.189.0/streams/iterate_reader.ts create mode 100644 vendor/deno.land/std@0.189.0/streams/limited_bytes_transform_stream.ts create mode 100644 vendor/deno.land/std@0.189.0/streams/limited_transform_stream.ts create mode 100644 vendor/deno.land/std@0.189.0/streams/merge_readable_streams.ts create mode 100644 vendor/deno.land/std@0.189.0/streams/mod.ts create mode 100644 vendor/deno.land/std@0.189.0/streams/read_all.ts create mode 100644 vendor/deno.land/std@0.189.0/streams/readable_stream_from_iterable.ts create mode 100644 vendor/deno.land/std@0.189.0/streams/readable_stream_from_reader.ts create mode 100644 vendor/deno.land/std@0.189.0/streams/reader_from_iterable.ts create mode 100644 vendor/deno.land/std@0.189.0/streams/reader_from_stream_reader.ts create mode 100644 vendor/deno.land/std@0.189.0/streams/text_delimiter_stream.ts create mode 100644 vendor/deno.land/std@0.189.0/streams/text_line_stream.ts create mode 100644 vendor/deno.land/std@0.189.0/streams/to_transform_stream.ts create mode 100644 vendor/deno.land/std@0.189.0/streams/writable_stream_from_writer.ts create mode 100644 vendor/deno.land/std@0.189.0/streams/write_all.ts create mode 100644 vendor/deno.land/std@0.189.0/streams/writer_from_stream_writer.ts create mode 100644 vendor/deno.land/std@0.189.0/streams/zip_readable_streams.ts create mode 100644 vendor/deno.land/std@0.189.0/types.d.ts create mode 100644 vendor/deno.land/std@0.191.0/_util/asserts.ts create mode 100644 vendor/deno.land/std@0.191.0/bytes/bytes_list.ts create mode 100644 vendor/deno.land/std@0.191.0/bytes/concat.ts create mode 100644 vendor/deno.land/std@0.191.0/bytes/copy.ts create mode 100644 vendor/deno.land/std@0.191.0/io/buf_reader.ts create mode 100644 vendor/deno.land/std@0.191.0/io/buf_writer.ts create mode 100644 vendor/deno.land/std@0.191.0/io/buffer.ts create mode 100644 vendor/deno.land/std@0.191.0/io/copy_n.ts create mode 100644 vendor/deno.land/std@0.191.0/io/limited_reader.ts create mode 100644 vendor/deno.land/std@0.191.0/io/mod.ts create mode 100644 vendor/deno.land/std@0.191.0/io/multi_reader.ts create mode 100644 vendor/deno.land/std@0.191.0/io/read_delim.ts create mode 100644 vendor/deno.land/std@0.191.0/io/read_int.ts create mode 100644 vendor/deno.land/std@0.191.0/io/read_lines.ts create mode 100644 vendor/deno.land/std@0.191.0/io/read_long.ts create mode 100644 vendor/deno.land/std@0.191.0/io/read_range.ts create mode 100644 vendor/deno.land/std@0.191.0/io/read_short.ts create mode 100644 vendor/deno.land/std@0.191.0/io/read_string_delim.ts create mode 100644 vendor/deno.land/std@0.191.0/io/slice_long_to_bytes.ts create mode 100644 vendor/deno.land/std@0.191.0/io/string_reader.ts create mode 100644 vendor/deno.land/std@0.191.0/io/string_writer.ts create mode 100644 vendor/deno.land/std@0.191.0/streams/read_all.ts create mode 100644 vendor/deno.land/x/oak@v12.1.0/application.ts create mode 100644 vendor/deno.land/x/oak@v12.1.0/body.ts create mode 100644 vendor/deno.land/x/oak@v12.1.0/buf_reader.ts create mode 100644 vendor/deno.land/x/oak@v12.1.0/content_disposition.ts create mode 100644 vendor/deno.land/x/oak@v12.1.0/context.ts create mode 100644 vendor/deno.land/x/oak@v12.1.0/deps.ts create mode 100644 vendor/deno.land/x/oak@v12.1.0/etag.ts create mode 100644 vendor/deno.land/x/oak@v12.1.0/headers.ts create mode 100644 vendor/deno.land/x/oak@v12.1.0/helpers.ts create mode 100644 vendor/deno.land/x/oak@v12.1.0/http_request.ts create mode 100644 vendor/deno.land/x/oak@v12.1.0/http_server_flash.ts create mode 100644 vendor/deno.land/x/oak@v12.1.0/http_server_native.ts create mode 100644 vendor/deno.land/x/oak@v12.1.0/http_server_native_request.ts create mode 100644 vendor/deno.land/x/oak@v12.1.0/isMediaType.ts create mode 100644 vendor/deno.land/x/oak@v12.1.0/mediaTyper.ts create mode 100644 vendor/deno.land/x/oak@v12.1.0/middleware.ts create mode 100644 vendor/deno.land/x/oak@v12.1.0/middleware/proxy.ts create mode 100644 vendor/deno.land/x/oak@v12.1.0/mod.ts create mode 100644 vendor/deno.land/x/oak@v12.1.0/multipart.ts create mode 100644 vendor/deno.land/x/oak@v12.1.0/range.ts create mode 100644 vendor/deno.land/x/oak@v12.1.0/request.ts create mode 100644 vendor/deno.land/x/oak@v12.1.0/response.ts create mode 100644 vendor/deno.land/x/oak@v12.1.0/router.ts create mode 100644 vendor/deno.land/x/oak@v12.1.0/send.ts create mode 100644 vendor/deno.land/x/oak@v12.1.0/server_sent_event.ts create mode 100644 vendor/deno.land/x/oak@v12.1.0/structured_clone.ts create mode 100644 vendor/deno.land/x/oak@v12.1.0/testing.ts create mode 100644 vendor/deno.land/x/oak@v12.1.0/types.d.ts create mode 100644 vendor/deno.land/x/oak@v12.1.0/util.ts create mode 100644 vendor/deno.land/x/path_to_regexp@v6.2.1/index.ts diff --git a/flake.nix b/flake.nix index ad8928d..0e5ecd2 100644 --- a/flake.nix +++ b/flake.nix @@ -31,6 +31,7 @@ }; devShells.default = pkgs.mkShell { buildInputs = with pkgs; [deno + hey psrecord]; }; }); diff --git a/src/fileservice.ts b/src/fileservice.ts index 26336f0..7caa8cb 100644 --- a/src/fileservice.ts +++ b/src/fileservice.ts @@ -1,22 +1,31 @@ import { sha1 } from 'https://denopkg.com/chiefbiiko/sha1/mod.ts'; import { iterateReader } from "https://deno.land/std@0.191.0/streams/iterate_reader.ts"; +import { AsyncIterableReader, concatUint8Array } from "./utils.ts"; export interface FileService { + + list(): AsyncIterable; + createFromFullBody( path: string, - body: string | Uint8Array, + body: string | Uint8Array | Deno.Reader, ): Promise; - list(): AsyncIterable; - deleteByPath(path: string): Promise; + createFromReader(path: string, body: Deno.Reader): Promise; + + getFullBodyByPath(path: string): Promise; getRawBodyByPath(path: string): Promise; + getMetaDataByID(id: string): Promise; + renameById(id: string, newPath: string): Promise; renameByPath( originalPath: string, newPath: string, ): Promise; + + deleteByPath(path: string): Promise; } interface SerializedFile { @@ -70,28 +79,26 @@ export class DenoKVBlobService { } - async persistBlob(index: string, body: Uint8Array): Promise { + async persistBlob(index: string, body: Uint8Array): Promise { let blobCount = 0; + let size = 0; for (const txt of this.splitText(body)) { await this.unsafePersistBlob(index, blobCount, txt); + size += txt.length; blobCount += 1; } + return size; } - async persistReader(index: string, body: Deno.Reader): Promise{ + async persistReader(index: string, body: Deno.Reader): Promise{ let i = 0; + let size = 0; for await (const chunk of iterateReader(body, {bufSize: this.blobSize})){ this.unsafePersistBlob(index, i, chunk); + size += chunk.length; i++; } - } - - // todo: remove - private concatUint8Array(arr1: Uint8Array, arr2: Uint8Array): Uint8Array { - const mergedArray = new Uint8Array(arr1.length + arr2.length); - mergedArray.set(arr1); - mergedArray.set(arr2, arr1.length); - return mergedArray; + return size; } async getBlob(index: string): Promise { @@ -105,11 +112,25 @@ export class DenoKVBlobService { `Inconsistant blob index for key "${foo.value}", this is very bad.`, ); } - ret = this.concatUint8Array(ret, foo.value); + ret = concatUint8Array(ret, foo.value); } return ret; } + private async *iterRawBlobs(index: string): AsyncIterable{ + const prefix = ['blob_index', index]; + for await (const itm of this.kv.list({ prefix })) { + const foo = await this.kv.get(['blob', itm.value]); + if (foo.value === null) { + throw new Error( + `Inconsistant blob index for key "${foo.value}", this is very bad.`, + ); + } + yield foo.value; + + } + +} async getNumberOfBlobs(index: string): Promise { let ret = 0; for await ( @@ -119,6 +140,10 @@ export class DenoKVBlobService { } return ret; } + + streamBlob(index: string): Deno.Reader{ + return new AsyncIterableReader(this.iterRawBlobs(index)); + } } export class DenoKVFileService implements FileService { @@ -135,17 +160,35 @@ export class DenoKVFileService implements FileService { body: string | Uint8Array, ): Promise { const newId = crypto.randomUUID(); + const bodyBytes: Uint8Array = body instanceof Uint8Array ? body : new TextEncoder().encode(body); + const size = await this.blobService.persistBlob(newId, bodyBytes); + const serializedFile: SerializedFile = { id: newId, path, - bodySize: body.length, + bodySize: size, }; const idx: SerializedFilePathIndex = { fileId: newId }; await this.kv.set(['file', newId], serializedFile); await this.kv.set(['file_index_path', path], idx); - const bodyBytes: Uint8Array = body instanceof Uint8Array ? body : new TextEncoder().encode(body); - await this.blobService.persistBlob(newId, bodyBytes); - return { id: newId, path, bodySize: body.length }; + + return { id: newId, path, bodySize: size }; + } + + async createFromReader(path: string, body: Deno.Reader): Promise{ + const newId = crypto.randomUUID(); + const size = await this.blobService.persistReader(newId, body); + const serializedFile: SerializedFile = { + id: newId, + path, + bodySize: size, + }; + const idx: SerializedFilePathIndex = { fileId: newId }; + await this.kv.set(['file', newId], serializedFile); + await this.kv.set(['file_index_path', path], idx); + + return { id: newId, path, bodySize: size }; + } async *list(): AsyncIterable { diff --git a/src/restapi.ts b/src/restapi.ts new file mode 100644 index 0000000..4640fd7 --- /dev/null +++ b/src/restapi.ts @@ -0,0 +1,48 @@ +import { Context, Router } from "https://deno.land/x/oak/mod.ts"; +import { FileService } from "./fileservice.ts"; +import { readerFromStreamReader } from "https://deno.land/std@0.189.0/streams/mod.ts"; +import { asyncIterableToArray } from "./utils.ts"; + + + +type Handler = (ctx: Context) => void; + + +function listFileHandlerFactory(fileService: FileService): Handler { + return async (ctx: Context) => { + ctx.response.type = "application/json"; + ctx.response.body = await asyncIterableToArray(fileService.list()); + }; +} + + +function createFileHandlerFactory(fileService: FileService): Handler { + return async (ctx: Context) => { + const untypedCtx: any = ctx; + ctx.response.body = 'This is a post'; + const path = untypedCtx.request.url.searchParams.get('path'); + + const reqBody = await ctx.request.body({ type: "stream" }).value; + const reader = readerFromStreamReader(reqBody.getReader()) + fileService.createFromReader(path, reader); + }; +} + + +function getFileHandlerFactory(fileService: FileService): Handler { + return (ctx: Context) => { + const untypedCtx: any = ctx; + const fileId = untypedCtx.params.file_id; + // ctx.response.body = await fileService.getRawBodyByPath() + }; +} + + +export function buildAPI(router: Router, fileService: FileService): Router { + + router.get('/files', listFileHandlerFactory(fileService)); + router.get('/files/:file_id', getFileHandlerFactory(fileService)); + router.post('/files', createFileHandlerFactory(fileService)); + + return router; +} \ No newline at end of file diff --git a/src/server.ts b/src/server.ts index 5645be3..c87112b 100644 --- a/src/server.ts +++ b/src/server.ts @@ -1,9 +1,18 @@ -import { Application } from 'https://deno.land/x/oak/mod.ts'; +import { Application, Router } from 'https://deno.land/x/oak/mod.ts'; +import { buildAPI } from './restapi.ts'; +import { DenoKVFileService } from "./fileservice.ts"; -const app = new Application(); +async function main(){ + const app = new Application(); + const kv = await Deno.openKv(); + const fileService = new DenoKVFileService(kv); + const router = new Router({ prefix: '/v1' }); -app.use((ctx) => { - ctx.response.body = 'Hello World!'; -}); + app.use( + buildAPI(router, fileService).routes(), + ); -await app.listen({ port: 9000 }); + await app.listen({ port: 9000 }); +} + +await main(); diff --git a/src/utils.ts b/src/utils.ts new file mode 100644 index 0000000..2f50492 --- /dev/null +++ b/src/utils.ts @@ -0,0 +1,69 @@ +export async function asyncIterableToArray(ite: AsyncIterable): Promise { + const ret: T[] = []; + for await (const e of ite) { + ret.push(e); + } + return ret; +} + + +export function concatUint8Array(arr1: Uint8Array, arr2: Uint8Array): Uint8Array { + const mergedArray = new Uint8Array(arr1.length + arr2.length); + mergedArray.set(arr1); + mergedArray.set(arr2, arr1.length); + return mergedArray; +} + +export class AsyncIterableReader implements Deno.Reader { + private readonly source: AsyncIterable; + private interReadBuffer = new Uint8Array(); + private isSourceEmpty = false; + constructor(source: AsyncIterable) { + this.source = source; + } + + private async readMinimumOf(n: number): Promise { + let ret = new Uint8Array(); + console.log('))) ' + n ) + + for await(const piece of this.source){ + console.log(piece.length) + ret = concatUint8Array(ret, piece); + if(ret.length >= n){ + console.log('*******************************') + return ret; + } + } + console.log('---------------'); + console.log(this.source); + this.isSourceEmpty = true; + return ret; + } + + private transferData(source: Uint8Array, dest: Uint8Array): number { + const limit = Math.min(source.length, dest.length); + for(let i = 0; i < limit; i ++){ + dest[i] = source[i]; + } + return limit; + } + + private readFromInternalReadBuffer(buffer: Uint8Array): number { + const n = this.transferData(this.interReadBuffer, buffer); + this.interReadBuffer = this.interReadBuffer.subarray(n); + return n; + } + + async read(buffer: Uint8Array): Promise { + if(this.isSourceEmpty && this.interReadBuffer.length < 1){ + console.log("done mother fucker"); + return null; + } + const internalReadBufferTransferdSize = this.readFromInternalReadBuffer(buffer); + + const data = await this.readMinimumOf(buffer.length - internalReadBufferTransferdSize); + const transferedSize = this.transferData(data.subarray(internalReadBufferTransferdSize), buffer); + this.interReadBuffer = data.subarray(transferedSize); + return internalReadBufferTransferdSize + transferedSize; + } +} diff --git a/tests/fileservice_test.ts b/tests/fileservice_test.ts index 12b0b5f..c3f63a5 100644 --- a/tests/fileservice_test.ts +++ b/tests/fileservice_test.ts @@ -6,50 +6,10 @@ import { FileMetaData, FileService, } from "../src/fileservice.ts"; +import { asyncIterableToArray } from "../src/utils.ts"; +import { generateLargeText } from "./utils.ts"; -function generateLargeText(seed: number, size: number): string { - const words = [ - "Norway", - "Germany", - "Ecuador", - "Chile", - "Colombia", - "Sri Lanka", - "Guinea", - "United Kingdom", - "Egypt", - "Palau", - "Latvia", - "Congo, Democratic Republic of the", - "Malawi", - "Senegal", - "Trinidad and Tobago", - "Guatemala", - "Chad", - "New Zealand", - "Malaysia", - "Lithuania", - "Taiwan", - "Solomon Islands", - "Grenada", - "Hungary", - "Korea, South", - ]; - let ret: string[] = []; - for (let i = 0; i < size; i++) { - seed = (seed * 4931 + 2753) % words.length; - ret.push(words[seed]); - } - return ret.join(" "); -} -async function asyncIterableToArray(ite: AsyncIterable): Promise { - const ret: T[] = []; - for await (const e of ite) { - ret.push(e); - } - return ret; -} Deno.test("DenoKVBlobService happy path", async (t) => { localStorage.clear(); @@ -62,25 +22,29 @@ Deno.test("DenoKVBlobService happy path", async (t) => { } await t.step("read write small text", async () => { - await bs.persistBlob("0002", toUint8Array("hello world")); + const size = await bs.persistBlob("0002", toUint8Array("hello world")); const body = await bs.getBlob("0002"); assertEquals(body, toUint8Array("hello world")); assertEquals(await bs.getNumberOfBlobs("0002"), 1); + assertEquals(size, "hello world".length); }); await t.step("read write large text", async () => { const largeText = toUint8Array(generateLargeText(33, 100)); - await bs.persistBlob("0001", largeText); + const size = await bs.persistBlob("0001", largeText); const body = await bs.getBlob("0001"); assertEquals(body, largeText); assertEquals(await bs.getNumberOfBlobs("0001"), 21); + assertEquals(size, largeText.length); }); await t.step("read write large file", async () => { - const f = await Deno.open('tests/examples/teapot.obj'); - await bs.persistReader("large-stream", f); + const fileName = 'tests/examples/teapot.obj'; + const f = await Deno.open(fileName); + const size = await bs.persistReader("large-stream", f); assertEquals(await bs.getNumberOfBlobs("large-stream"), 1030); + assertEquals((await Deno.readFile(fileName)).length, size); f.close(); }); @@ -95,12 +59,12 @@ Deno.test("DenoKVFileService happy path", async (t) => { let file1: FileMetaData | undefined; let file2: FileMetaData | undefined; - await t.step("Create 2 files", async () => { + await t.step("Create 2 files xyzabc", async () => { file1 = await fs.createFromFullBody(fileName1, "bob le chef"); file2 = await fs.createFromFullBody(fileName2, "le geant vert ☀️"); assertEquals(file1.path, fileName1); - assertEquals(file2.bodySize, "le geant vert ".length + 2); + assertEquals(file2.bodySize, 20); }); await t.step("Get file metadata", async () => { @@ -133,14 +97,23 @@ Deno.test("DenoKVFileService happy path", async (t) => { assertEquals(await asyncIterableToArray(ret), expected); }); + await t.step("Create File from reader", async () => { + const newPath = "boris/streamed-file.txt"; + const file = await Deno.open('tests/examples/teapot.obj'); + + const ret = await fs.createFromReader(newPath, file); + assertEquals(ret.bodySize, 51493); + file.close(); + }); + await t.step("Rename file", async () => { const newPath = "boris/file2.txt"; const ret = await fs.renameById(file2?.id ?? "404", newPath); assertEquals(ret?.path, newPath); assertEquals( - (await fs.getFullBodyByPath(newPath))?.length, - file2?.bodySize ?? -1, + await fs.getFullBodyByPath(newPath), + 'le geant vert ☀️', ); }); diff --git a/tests/utils.ts b/tests/utils.ts new file mode 100644 index 0000000..086509b --- /dev/null +++ b/tests/utils.ts @@ -0,0 +1,56 @@ + + +export function* randomNumbers(seed: number, max: number) : Generator { + const a = 48271; + const m = 2147483647; + max = Math.floor(max); + while (true) { + seed = (a * seed) % m; + yield seed % max; + } +} + +export function range(min: number, max: number): number[]{ + const ret = [] + for(let i = min; i < max; i ++){ + ret.push(i); + } + return ret; +} + +export function generateLargeText(seed: number, size: number): string { + const words = [ + "Norway", + "Germany", + "Ecuador", + "Chile", + "Colombia", + "Sri Lanka", + "Guinea", + "United Kingdom", + "Egypt", + "Palau", + "Latvia", + "Congo, Democratic Republic of the", + "Malawi", + "Senegal", + "Trinidad and Tobago", + "Guatemala", + "Chad", + "New Zealand", + "Malaysia", + "Lithuania", + "Taiwan", + "Solomon Islands", + "Grenada", + "Hungary", + "Korea, South", + ]; + let ret: string[] = []; + for (let i = 0; i < size; i++) { + seed = (seed * 4931 + 2753) % words.length; + ret.push(words[seed]); + } + return ret.join(" "); + } + diff --git a/tests/utils_test.ts b/tests/utils_test.ts new file mode 100644 index 0000000..7f2a2cc --- /dev/null +++ b/tests/utils_test.ts @@ -0,0 +1,83 @@ +import { readAll } from "https://deno.land/std@0.191.0/streams/read_all.ts"; +import { assertEquals } from "https://deno.land/std/testing/asserts.ts"; +import { AsyncIterableReader } from "../src/utils.ts"; +import { randomNumbers, range } from "./utils.ts"; + + +Deno.test("AsyncIterableReader", async (t) => { + + async function* miniSource(){ + yield Promise.resolve(new Uint8Array([1, 2, 3])); + yield Promise.resolve(new Uint8Array([4, 5, 6, 7])); + yield Promise.resolve(new Uint8Array([8, 9])); + } + + async function* largeSource(size: number): AsyncIterable{ + let val = 0 + const random = randomNumbers(77, size / 4); + while(val < size){ + const newVal = Math.min(val + random.next().value, size); + // console.log(range(val, newVal)); + console.log("<<<<<<<<<<<<<<<<< " + newVal + "/" + size + " ( " + (newVal - val) ) + yield Promise.resolve(new Uint8Array(range(val, newVal))); + + val = newVal; + + + } + yield Promise.resolve(new Uint8Array([9])); + console.log(">>>>>>>>>>>>>>>>>>>>>>> "); + console.log(">>>>>>>>>>>>>>>>>>>>>>> "); + console.log(">>>>>>>>>>>>>>>>>>>>>>> "); + console.log(">>>>>>>>>>>>>>>>>>>>>>> "); + } + + // await t.step('read all mini source', async () => { + // const reader = new AsyncIterableReader(miniSource()); + // const expt = new Uint8Array([1, 2 ,3 ,4 ,5, 6,7,8,9]) + // assertEquals(await readAll(reader), expt); + // }); + + await t.step('largeSource', async () =>{ + const source = largeSource(32920); + for await(const _piece of source){ + // + } + }); + + + await t.step('read all large source', async () => { + // for(let i = 101; i < (32 * 1024 * 50 + 101); i += 1){ + let n = 32 * 1024 * 50 + 101 // 32920; //32 * 1024 * 2; + n = 32919 + const reader = new AsyncIterableReader(largeSource(n)); + const ret = await readAll(reader); + assertEquals(ret.length, n); + // } + }); + +}); + + +// <<<<<<<<<<<<<<<<< 5137/32920 +// <<<<<<<<<<<<<<<<< 11593/32920 +// <<<<<<<<<<<<<<<<< 12563/32920 +// <<<<<<<<<<<<<<<<< 15976/32920 +// <<<<<<<<<<<<<<<<< 19155/32920 +// <<<<<<<<<<<<<<<<< 20038/32920 +// <<<<<<<<<<<<<<<<< 26452/32920 +// <<<<<<<<<<<<<<<<< 31797/32920 +// <<<<<<<<<<<<<<<<< 31994/32920 +// <<<<<<<<<<<<<<<<< 32912/32920 +// <<<<<<<<<<<<<<<<< 32920/32920 + + +// <<<<<<<<<<<<<<<<< 5137/32920 +// <<<<<<<<<<<<<<<<< 11593/32920 +// <<<<<<<<<<<<<<<<< 12563/32920 +// <<<<<<<<<<<<<<<<< 15976/32920 +// <<<<<<<<<<<<<<<<< 19155/32920 +// <<<<<<<<<<<<<<<<< 20038/32920 +// <<<<<<<<<<<<<<<<< 26452/32920 +// <<<<<<<<<<<<<<<<< 31797/32920 +// <<<<<<<<<<<<<<<<< 31994/32920 \ No newline at end of file diff --git a/vendor/deno.land/std@0.178.0/_util/asserts.ts b/vendor/deno.land/std@0.178.0/_util/asserts.ts new file mode 100644 index 0000000..3c5c346 --- /dev/null +++ b/vendor/deno.land/std@0.178.0/_util/asserts.ts @@ -0,0 +1,25 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. +// This module is browser compatible. + +/** + * All internal non-test code, that is files that do not have `test` or `bench` in the name, must use the assertion functions within `_utils/asserts.ts` and not `testing/asserts.ts`. This is to create a separation of concerns between internal and testing assertions. + */ + +export class DenoStdInternalError extends Error { + constructor(message: string) { + super(message); + this.name = "DenoStdInternalError"; + } +} + +/** Make an assertion, if not `true`, then throw. */ +export function assert(expr: unknown, msg = ""): asserts expr { + if (!expr) { + throw new DenoStdInternalError(msg); + } +} + +/** Use this to assert unreachable code. */ +export function unreachable(): never { + throw new DenoStdInternalError("unreachable"); +} diff --git a/vendor/deno.land/std@0.178.0/_util/os.ts b/vendor/deno.land/std@0.178.0/_util/os.ts new file mode 100644 index 0000000..a272eae --- /dev/null +++ b/vendor/deno.land/std@0.178.0/_util/os.ts @@ -0,0 +1,23 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. +// This module is browser compatible. + +export type OSType = "windows" | "linux" | "darwin" | "freebsd"; + +export const osType: OSType = (() => { + // deno-lint-ignore no-explicit-any + const { Deno } = globalThis as any; + if (typeof Deno?.build?.os === "string") { + return Deno.build.os; + } + + // deno-lint-ignore no-explicit-any + const { navigator } = globalThis as any; + if (navigator?.appVersion?.includes?.("Win")) { + return "windows"; + } + + return "linux"; +})(); + +export const isWindows = osType === "windows"; +export const isLinux = osType === "linux"; diff --git a/vendor/deno.land/std@0.178.0/async/deferred.ts b/vendor/deno.land/std@0.178.0/async/deferred.ts new file mode 100644 index 0000000..cd6c0aa --- /dev/null +++ b/vendor/deno.land/std@0.178.0/async/deferred.ts @@ -0,0 +1,48 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. +// This module is browser compatible. + +// TODO(ry) It'd be better to make Deferred a class that inherits from +// Promise, rather than an interface. This is possible in ES2016, however +// typescript produces broken code when targeting ES5 code. +// See https://github.com/Microsoft/TypeScript/issues/15202 +// At the time of writing, the github issue is closed but the problem remains. +export interface Deferred extends Promise { + readonly state: "pending" | "fulfilled" | "rejected"; + resolve(value?: T | PromiseLike): void; + // deno-lint-ignore no-explicit-any + reject(reason?: any): void; +} + +/** + * Creates a Promise with the `reject` and `resolve` functions placed as methods + * on the promise object itself. + * + * @example + * ```typescript + * import { deferred } from "https://deno.land/std@$STD_VERSION/async/deferred.ts"; + * + * const p = deferred(); + * // ... + * p.resolve(42); + * ``` + */ +export function deferred(): Deferred { + let methods; + let state = "pending"; + const promise = new Promise((resolve, reject) => { + methods = { + async resolve(value: T | PromiseLike) { + await value; + state = "fulfilled"; + resolve(value); + }, + // deno-lint-ignore no-explicit-any + reject(reason?: any) { + state = "rejected"; + reject(reason); + }, + }; + }); + Object.defineProperty(promise, "state", { get: () => state }); + return Object.assign(promise, methods) as Deferred; +} diff --git a/vendor/deno.land/std@0.178.0/bytes/bytes_list.ts b/vendor/deno.land/std@0.178.0/bytes/bytes_list.ts new file mode 100644 index 0000000..0b6a1cb --- /dev/null +++ b/vendor/deno.land/std@0.178.0/bytes/bytes_list.ts @@ -0,0 +1,161 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. +// This module is browser compatible. + +/** + * An abstraction of multiple Uint8Arrays + */ +export class BytesList { + #len = 0; + #chunks: { + value: Uint8Array; + start: number; // start offset from head of chunk + end: number; // end offset from head of chunk + offset: number; // offset of head in all bytes + }[] = []; + constructor() {} + + /** + * Total size of bytes + */ + size() { + return this.#len; + } + /** + * Push bytes with given offset infos + */ + add(value: Uint8Array, start = 0, end = value.byteLength) { + if (value.byteLength === 0 || end - start === 0) { + return; + } + checkRange(start, end, value.byteLength); + this.#chunks.push({ + value, + end, + start, + offset: this.#len, + }); + this.#len += end - start; + } + + /** + * Drop head `n` bytes. + */ + shift(n: number) { + if (n === 0) { + return; + } + if (this.#len <= n) { + this.#chunks = []; + this.#len = 0; + return; + } + const idx = this.getChunkIndex(n); + this.#chunks.splice(0, idx); + const [chunk] = this.#chunks; + if (chunk) { + const diff = n - chunk.offset; + chunk.start += diff; + } + let offset = 0; + for (const chunk of this.#chunks) { + chunk.offset = offset; + offset += chunk.end - chunk.start; + } + this.#len = offset; + } + + /** + * Find chunk index in which `pos` locates by binary-search + * returns -1 if out of range + */ + getChunkIndex(pos: number): number { + let max = this.#chunks.length; + let min = 0; + while (true) { + const i = min + Math.floor((max - min) / 2); + if (i < 0 || this.#chunks.length <= i) { + return -1; + } + const { offset, start, end } = this.#chunks[i]; + const len = end - start; + if (offset <= pos && pos < offset + len) { + return i; + } else if (offset + len <= pos) { + min = i + 1; + } else { + max = i - 1; + } + } + } + + /** + * Get indexed byte from chunks + */ + get(i: number): number { + if (i < 0 || this.#len <= i) { + throw new Error("out of range"); + } + const idx = this.getChunkIndex(i); + const { value, offset, start } = this.#chunks[idx]; + return value[start + i - offset]; + } + + /** + * Iterator of bytes from given position + */ + *iterator(start = 0): IterableIterator { + const startIdx = this.getChunkIndex(start); + if (startIdx < 0) return; + const first = this.#chunks[startIdx]; + let firstOffset = start - first.offset; + for (let i = startIdx; i < this.#chunks.length; i++) { + const chunk = this.#chunks[i]; + for (let j = chunk.start + firstOffset; j < chunk.end; j++) { + yield chunk.value[j]; + } + firstOffset = 0; + } + } + + /** + * Returns subset of bytes copied + */ + slice(start: number, end: number = this.#len): Uint8Array { + if (end === start) { + return new Uint8Array(); + } + checkRange(start, end, this.#len); + const result = new Uint8Array(end - start); + const startIdx = this.getChunkIndex(start); + const endIdx = this.getChunkIndex(end - 1); + let written = 0; + for (let i = startIdx; i < endIdx; i++) { + const chunk = this.#chunks[i]; + const len = chunk.end - chunk.start; + result.set(chunk.value.subarray(chunk.start, chunk.end), written); + written += len; + } + const last = this.#chunks[endIdx]; + const rest = end - start - written; + result.set(last.value.subarray(last.start, last.start + rest), written); + return result; + } + /** + * Concatenate chunks into single Uint8Array copied. + */ + concat(): Uint8Array { + const result = new Uint8Array(this.#len); + let sum = 0; + for (const { value, start, end } of this.#chunks) { + result.set(value.subarray(start, end), sum); + sum += end - start; + } + return result; + } +} + +function checkRange(start: number, end: number, len: number) { + if (start < 0 || len < start || end < 0 || len < end || end < start) { + throw new Error("invalid range"); + } +} diff --git a/vendor/deno.land/std@0.178.0/bytes/concat.ts b/vendor/deno.land/std@0.178.0/bytes/concat.ts new file mode 100644 index 0000000..d45e5a3 --- /dev/null +++ b/vendor/deno.land/std@0.178.0/bytes/concat.ts @@ -0,0 +1,26 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. +// This module is browser compatible. + +/** Concatenate the given arrays into a new Uint8Array. + * + * ```ts + * import { concat } from "https://deno.land/std@$STD_VERSION/bytes/concat.ts"; + * const a = new Uint8Array([0, 1, 2]); + * const b = new Uint8Array([3, 4, 5]); + * console.log(concat(a, b)); // [0, 1, 2, 3, 4, 5] + */ +export function concat(...buf: Uint8Array[]): Uint8Array { + let length = 0; + for (const b of buf) { + length += b.length; + } + + const output = new Uint8Array(length); + let index = 0; + for (const b of buf) { + output.set(b, index); + index += b.length; + } + + return output; +} diff --git a/vendor/deno.land/std@0.178.0/bytes/copy.ts b/vendor/deno.land/std@0.178.0/bytes/copy.ts new file mode 100644 index 0000000..e92c98d --- /dev/null +++ b/vendor/deno.land/std@0.178.0/bytes/copy.ts @@ -0,0 +1,38 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. +// This module is browser compatible. + +/** Copy bytes from the `src` array to the `dst` array. Returns the number of + * bytes copied. + * + * If the `src` array is larger than what the `dst` array can hold, only the + * amount of bytes that fit in the `dst` array are copied. + * + * An offset can be specified as the third argument that begins the copy at + * that given index in the `dst` array. The offset defaults to the beginning of + * the array. + * + * ```ts + * import { copy } from "https://deno.land/std@$STD_VERSION/bytes/copy.ts"; + * const src = new Uint8Array([9, 8, 7]); + * const dst = new Uint8Array([0, 1, 2, 3, 4, 5]); + * console.log(copy(src, dst)); // 3 + * console.log(dst); // [9, 8, 7, 3, 4, 5] + * ``` + * + * ```ts + * import { copy } from "https://deno.land/std@$STD_VERSION/bytes/copy.ts"; + * const src = new Uint8Array([1, 1, 1, 1]); + * const dst = new Uint8Array([0, 0, 0, 0]); + * console.log(copy(src, dst, 1)); // 3 + * console.log(dst); // [0, 1, 1, 1] + * ``` + */ +export function copy(src: Uint8Array, dst: Uint8Array, off = 0): number { + off = Math.max(0, Math.min(off, dst.byteLength)); + const dstBytesAvailable = dst.byteLength - off; + if (src.byteLength > dstBytesAvailable) { + src = src.subarray(0, dstBytesAvailable); + } + dst.set(src, off); + return src.byteLength; +} diff --git a/vendor/deno.land/std@0.178.0/bytes/ends_with.ts b/vendor/deno.land/std@0.178.0/bytes/ends_with.ts new file mode 100644 index 0000000..570fcdc --- /dev/null +++ b/vendor/deno.land/std@0.178.0/bytes/ends_with.ts @@ -0,0 +1,25 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. +// This module is browser compatible. + +/** Returns true if the suffix array appears at the end of the source array, + * false otherwise. + * + * The complexity of this function is O(suffix.length). + * + * ```ts + * import { endsWith } from "https://deno.land/std@$STD_VERSION/bytes/ends_with.ts"; + * const source = new Uint8Array([0, 1, 2, 1, 2, 1, 2, 3]); + * const suffix = new Uint8Array([1, 2, 3]); + * console.log(endsWith(source, suffix)); // true + * ``` + */ +export function endsWith(source: Uint8Array, suffix: Uint8Array): boolean { + for ( + let srci = source.length - 1, sfxi = suffix.length - 1; + sfxi >= 0; + srci--, sfxi-- + ) { + if (source[srci] !== suffix[sfxi]) return false; + } + return true; +} diff --git a/vendor/deno.land/std@0.178.0/bytes/equals.ts b/vendor/deno.land/std@0.178.0/bytes/equals.ts new file mode 100644 index 0000000..661d678 --- /dev/null +++ b/vendor/deno.land/std@0.178.0/bytes/equals.ts @@ -0,0 +1,44 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. +// This module is browser compatible. + +/** Check whether binary arrays are equal to each other using 8-bit comparisons. + * @private + * @param a first array to check equality + * @param b second array to check equality + */ +function equalsNaive(a: Uint8Array, b: Uint8Array): boolean { + for (let i = 0; i < b.length; i++) { + if (a[i] !== b[i]) return false; + } + return true; +} + +/** Check whether binary arrays are equal to each other using 32-bit comparisons. + * @private + * @param a first array to check equality + * @param b second array to check equality + */ +function equals32Bit(a: Uint8Array, b: Uint8Array): boolean { + const len = a.length; + const compressable = Math.floor(len / 4); + const compressedA = new Uint32Array(a.buffer, 0, compressable); + const compressedB = new Uint32Array(b.buffer, 0, compressable); + for (let i = compressable * 4; i < len; i++) { + if (a[i] !== b[i]) return false; + } + for (let i = 0; i < compressedA.length; i++) { + if (compressedA[i] !== compressedB[i]) return false; + } + return true; +} + +/** Check whether binary arrays are equal to each other. + * @param a first array to check equality + * @param b second array to check equality + */ +export function equals(a: Uint8Array, b: Uint8Array): boolean { + if (a.length !== b.length) { + return false; + } + return a.length < 1000 ? equalsNaive(a, b) : equals32Bit(a, b); +} diff --git a/vendor/deno.land/std@0.178.0/bytes/includes_needle.ts b/vendor/deno.land/std@0.178.0/bytes/includes_needle.ts new file mode 100644 index 0000000..57750f0 --- /dev/null +++ b/vendor/deno.land/std@0.178.0/bytes/includes_needle.ts @@ -0,0 +1,27 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. +// This module is browser compatible. + +import { indexOfNeedle } from "./index_of_needle.ts"; + +/** Returns true if the source array contains the needle array, false otherwise. + * + * A start index can be specified as the third argument that begins the search + * at that given index. The start index defaults to the beginning of the array. + * + * The complexity of this function is O(source.length * needle.length). + * + * ```ts + * import { includesNeedle } from "https://deno.land/std@$STD_VERSION/bytes/includes_needle.ts"; + * const source = new Uint8Array([0, 1, 2, 1, 2, 1, 2, 3]); + * const needle = new Uint8Array([1, 2]); + * console.log(includesNeedle(source, needle)); // true + * console.log(includesNeedle(source, needle, 6)); // false + * ``` + */ +export function includesNeedle( + source: Uint8Array, + needle: Uint8Array, + start = 0, +): boolean { + return indexOfNeedle(source, needle, start) !== -1; +} diff --git a/vendor/deno.land/std@0.178.0/bytes/index_of_needle.ts b/vendor/deno.land/std@0.178.0/bytes/index_of_needle.ts new file mode 100644 index 0000000..c50dd3d --- /dev/null +++ b/vendor/deno.land/std@0.178.0/bytes/index_of_needle.ts @@ -0,0 +1,49 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. +// This module is browser compatible. + +/** Returns the index of the first occurrence of the needle array in the source + * array, or -1 if it is not present. + * + * A start index can be specified as the third argument that begins the search + * at that given index. The start index defaults to the start of the array. + * + * The complexity of this function is O(source.lenth * needle.length). + * + * ```ts + * import { indexOfNeedle } from "https://deno.land/std@$STD_VERSION/bytes/index_of_needle.ts"; + * const source = new Uint8Array([0, 1, 2, 1, 2, 1, 2, 3]); + * const needle = new Uint8Array([1, 2]); + * console.log(indexOfNeedle(source, needle)); // 1 + * console.log(indexOfNeedle(source, needle, 2)); // 3 + * ``` + */ +export function indexOfNeedle( + source: Uint8Array, + needle: Uint8Array, + start = 0, +): number { + if (start >= source.length) { + return -1; + } + if (start < 0) { + start = Math.max(0, source.length + start); + } + const s = needle[0]; + for (let i = start; i < source.length; i++) { + if (source[i] !== s) continue; + const pin = i; + let matched = 1; + let j = i; + while (matched < needle.length) { + j++; + if (source[j] !== needle[j - pin]) { + break; + } + matched++; + } + if (matched === needle.length) { + return pin; + } + } + return -1; +} diff --git a/vendor/deno.land/std@0.178.0/bytes/last_index_of_needle.ts b/vendor/deno.land/std@0.178.0/bytes/last_index_of_needle.ts new file mode 100644 index 0000000..9de3733 --- /dev/null +++ b/vendor/deno.land/std@0.178.0/bytes/last_index_of_needle.ts @@ -0,0 +1,49 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. +// This module is browser compatible. + +/** Returns the index of the last occurrence of the needle array in the source + * array, or -1 if it is not present. + * + * A start index can be specified as the third argument that begins the search + * at that given index. The start index defaults to the end of the array. + * + * The complexity of this function is O(source.lenth * needle.length). + * + * ```ts + * import { lastIndexOfNeedle } from "https://deno.land/std@$STD_VERSION/bytes/last_index_of_needle.ts"; + * const source = new Uint8Array([0, 1, 2, 1, 2, 1, 2, 3]); + * const needle = new Uint8Array([1, 2]); + * console.log(lastIndexOfNeedle(source, needle)); // 5 + * console.log(lastIndexOfNeedle(source, needle, 4)); // 3 + * ``` + */ +export function lastIndexOfNeedle( + source: Uint8Array, + needle: Uint8Array, + start = source.length - 1, +): number { + if (start < 0) { + return -1; + } + if (start >= source.length) { + start = source.length - 1; + } + const e = needle[needle.length - 1]; + for (let i = start; i >= 0; i--) { + if (source[i] !== e) continue; + const pin = i; + let matched = 1; + let j = i; + while (matched < needle.length) { + j--; + if (source[j] !== needle[needle.length - 1 - (pin - j)]) { + break; + } + matched++; + } + if (matched === needle.length) { + return pin - needle.length + 1; + } + } + return -1; +} diff --git a/vendor/deno.land/std@0.178.0/bytes/mod.ts b/vendor/deno.land/std@0.178.0/bytes/mod.ts new file mode 100644 index 0000000..92dd7ca --- /dev/null +++ b/vendor/deno.land/std@0.178.0/bytes/mod.ts @@ -0,0 +1,20 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. +// This module is browser compatible. + +/** + * Provides helper functions to manipulate `Uint8Array` byte slices that are not + * included on the `Uint8Array` prototype. + * + * @module + */ + +export * from "./bytes_list.ts"; +export * from "./concat.ts"; +export * from "./copy.ts"; +export * from "./ends_with.ts"; +export * from "./equals.ts"; +export * from "./includes_needle.ts"; +export * from "./index_of_needle.ts"; +export * from "./last_index_of_needle.ts"; +export * from "./repeat.ts"; +export * from "./starts_with.ts"; diff --git a/vendor/deno.land/std@0.178.0/bytes/repeat.ts b/vendor/deno.land/std@0.178.0/bytes/repeat.ts new file mode 100644 index 0000000..9e2b7b7 --- /dev/null +++ b/vendor/deno.land/std@0.178.0/bytes/repeat.ts @@ -0,0 +1,40 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. +// This module is browser compatible. +import { copy } from "./copy.ts"; + +/** Returns a new Uint8Array composed of `count` repetitions of the `source` + * array. + * + * If `count` is negative, a `RangeError` is thrown. + * + * ```ts + * import { repeat } from "https://deno.land/std@$STD_VERSION/bytes/repeat.ts"; + * const source = new Uint8Array([0, 1, 2]); + * console.log(repeat(source, 3)); // [0, 1, 2, 0, 1, 2, 0, 1, 2] + * console.log(repeat(source, 0)); // [] + * console.log(repeat(source, -1)); // RangeError + * ``` + */ +export function repeat(source: Uint8Array, count: number): Uint8Array { + if (count === 0) { + return new Uint8Array(); + } + + if (count < 0) { + throw new RangeError("bytes: negative repeat count"); + } + + if (!Number.isInteger(count)) { + throw new Error("bytes: repeat count must be an integer"); + } + + const nb = new Uint8Array(source.length * count); + + let bp = copy(source, nb); + + for (; bp < nb.length; bp *= 2) { + copy(nb.slice(0, bp), nb, bp); + } + + return nb; +} diff --git a/vendor/deno.land/std@0.178.0/bytes/starts_with.ts b/vendor/deno.land/std@0.178.0/bytes/starts_with.ts new file mode 100644 index 0000000..188a915 --- /dev/null +++ b/vendor/deno.land/std@0.178.0/bytes/starts_with.ts @@ -0,0 +1,21 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. +// This module is browser compatible. + +/** Returns true if the prefix array appears at the start of the source array, + * false otherwise. + * + * The complexity of this function is O(prefix.length). + * + * ```ts + * import { startsWith } from "https://deno.land/std@$STD_VERSION/bytes/starts_with.ts"; + * const source = new Uint8Array([0, 1, 2, 1, 2, 1, 2, 3]); + * const prefix = new Uint8Array([0, 1, 2]); + * console.log(startsWith(source, prefix)); // true + * ``` + */ +export function startsWith(source: Uint8Array, prefix: Uint8Array): boolean { + for (let i = 0, max = prefix.length; i < max; i++) { + if (source[i] !== prefix[i]) return false; + } + return true; +} diff --git a/vendor/deno.land/std@0.178.0/crypto/keystack.ts b/vendor/deno.land/std@0.178.0/crypto/keystack.ts new file mode 100644 index 0000000..7340896 --- /dev/null +++ b/vendor/deno.land/std@0.178.0/crypto/keystack.ts @@ -0,0 +1,169 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. +// This module is browser compatible. + +/** + * Provides the {@linkcode KeyStack} class which implements the + * {@linkcode KeyRing} interface for managing rotatable keys. + * + * @module + */ + +import { timingSafeEqual } from "./timing_safe_equal.ts"; +import * as base64url from "../encoding/base64url.ts"; + +/** Types of data that can be signed cryptographically. */ +export type Data = string | number[] | ArrayBuffer | Uint8Array; + +/** Types of keys that can be used to sign data. */ +export type Key = string | number[] | ArrayBuffer | Uint8Array; + +const encoder = new TextEncoder(); + +function importKey(key: Key): Promise { + if (typeof key === "string") { + key = encoder.encode(key); + } else if (Array.isArray(key)) { + key = new Uint8Array(key); + } + return crypto.subtle.importKey( + "raw", + key, + { + name: "HMAC", + hash: { name: "SHA-256" }, + }, + true, + ["sign", "verify"], + ); +} + +function sign(data: Data, key: CryptoKey): Promise { + if (typeof data === "string") { + data = encoder.encode(data); + } else if (Array.isArray(data)) { + data = Uint8Array.from(data); + } + return crypto.subtle.sign("HMAC", key, data); +} + +/** Compare two strings, Uint8Arrays, ArrayBuffers, or arrays of numbers in a + * way that avoids timing based attacks on the comparisons on the values. + * + * The function will return `true` if the values match, or `false`, if they + * do not match. + * + * This was inspired by https://github.com/suryagh/tsscmp which provides a + * timing safe string comparison to avoid timing attacks as described in + * https://codahale.com/a-lesson-in-timing-attacks/. + */ +async function compare(a: Data, b: Data): Promise { + const key = new Uint8Array(32); + globalThis.crypto.getRandomValues(key); + const cryptoKey = await importKey(key); + const ah = await sign(a, cryptoKey); + const bh = await sign(b, cryptoKey); + return timingSafeEqual(ah, bh); +} + +/** A cryptographic key chain which allows signing of data to prevent tampering, + * but also allows for easy key rotation without needing to re-sign the data. + * + * Data is signed as SHA256 HMAC. + * + * This was inspired by [keygrip](https://github.com/crypto-utils/keygrip/). + * + * @example + * ```ts + * import { KeyStack } from "https://deno.land/std@$STD_VERSION/crypto/keystack.ts"; + * + * const keyStack = new KeyStack(["hello", "world"]); + * const digest = await keyStack.sign("some data"); + * + * const rotatedStack = new KeyStack(["deno", "says", "hello", "world"]); + * await rotatedStack.verify("some data", digest); // true + * ``` + */ +export class KeyStack { + #cryptoKeys = new Map(); + #keys: Key[]; + + async #toCryptoKey(key: Key): Promise { + if (!this.#cryptoKeys.has(key)) { + this.#cryptoKeys.set(key, await importKey(key)); + } + return this.#cryptoKeys.get(key)!; + } + + get length(): number { + return this.#keys.length; + } + + /** A class which accepts an array of keys that are used to sign and verify + * data and allows easy key rotation without invalidation of previously signed + * data. + * + * @param keys An iterable of keys, of which the index 0 will be used to sign + * data, but verification can happen against any key. + */ + constructor(keys: Iterable) { + const values = Array.isArray(keys) ? keys : [...keys]; + if (!(values.length)) { + throw new TypeError("keys must contain at least one value"); + } + this.#keys = values; + } + + /** Take `data` and return a SHA256 HMAC digest that uses the current 0 index + * of the `keys` passed to the constructor. This digest is in the form of a + * URL safe base64 encoded string. */ + async sign(data: Data): Promise { + const key = await this.#toCryptoKey(this.#keys[0]); + return base64url.encode(await sign(data, key)); + } + + /** Given `data` and a `digest`, verify that one of the `keys` provided the + * constructor was used to generate the `digest`. Returns `true` if one of + * the keys was used, otherwise `false`. */ + async verify(data: Data, digest: string): Promise { + return (await this.indexOf(data, digest)) > -1; + } + + /** Given `data` and a `digest`, return the current index of the key in the + * `keys` passed the constructor that was used to generate the digest. If no + * key can be found, the method returns `-1`. */ + async indexOf(data: Data, digest: string): Promise { + for (let i = 0; i < this.#keys.length; i++) { + const cryptoKey = await this.#toCryptoKey(this.#keys[i]); + if ( + await compare(digest, base64url.encode(await sign(data, cryptoKey))) + ) { + return i; + } + } + return -1; + } + + [Symbol.for("Deno.customInspect")](inspect: (value: unknown) => string) { + const { length } = this; + return `${this.constructor.name} ${inspect({ length })}`; + } + + [Symbol.for("nodejs.util.inspect.custom")]( + depth: number, + // deno-lint-ignore no-explicit-any + options: any, + inspect: (value: unknown, options?: unknown) => string, + ) { + if (depth < 0) { + return options.stylize(`[${this.constructor.name}]`, "special"); + } + + const newOptions = Object.assign({}, options, { + depth: options.depth === null ? null : options.depth - 1, + }); + const { length } = this; + return `${options.stylize(this.constructor.name, "special")} ${ + inspect({ length }, newOptions) + }`; + } +} diff --git a/vendor/deno.land/std@0.178.0/crypto/timing_safe_equal.ts b/vendor/deno.land/std@0.178.0/crypto/timing_safe_equal.ts new file mode 100644 index 0000000..4cc8e81 --- /dev/null +++ b/vendor/deno.land/std@0.178.0/crypto/timing_safe_equal.ts @@ -0,0 +1,33 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. + +import { assert } from "../_util/asserts.ts"; + +/** Compare to array buffers or data views in a way that timing based attacks + * cannot gain information about the platform. */ +export function timingSafeEqual( + a: ArrayBufferView | ArrayBufferLike | DataView, + b: ArrayBufferView | ArrayBufferLike | DataView, +): boolean { + if (a.byteLength !== b.byteLength) { + return false; + } + if (!(a instanceof DataView)) { + a = ArrayBuffer.isView(a) + ? new DataView(a.buffer, a.byteOffset, a.byteLength) + : new DataView(a); + } + if (!(b instanceof DataView)) { + b = ArrayBuffer.isView(b) + ? new DataView(b.buffer, b.byteOffset, b.byteLength) + : new DataView(b); + } + assert(a instanceof DataView); + assert(b instanceof DataView); + const length = a.byteLength; + let out = 0; + let i = -1; + while (++i < length) { + out |= a.getUint8(i) ^ b.getUint8(i); + } + return out === 0; +} diff --git a/vendor/deno.land/std@0.178.0/encoding/base64.ts b/vendor/deno.land/std@0.178.0/encoding/base64.ts new file mode 100644 index 0000000..deb803c --- /dev/null +++ b/vendor/deno.land/std@0.178.0/encoding/base64.ts @@ -0,0 +1,144 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. + +/** + * {@linkcode encode} and {@linkcode decode} for + * [base64](https://en.wikipedia.org/wiki/Base64) encoding. + * + * This module is browser compatible. + * + * @example + * ```ts + * import { + * decode, + * encode, + * } from "https://deno.land/std@$STD_VERSION/encoding/base64.ts"; + * + * const b64Repr = "Zm9vYg=="; + * + * const binaryData = decode(b64Repr); + * console.log(binaryData); + * // => Uint8Array [ 102, 111, 111, 98 ] + * + * console.log(encode(binaryData)); + * // => Zm9vYg== + * ``` + * + * @module + */ + +const base64abc = [ + "A", + "B", + "C", + "D", + "E", + "F", + "G", + "H", + "I", + "J", + "K", + "L", + "M", + "N", + "O", + "P", + "Q", + "R", + "S", + "T", + "U", + "V", + "W", + "X", + "Y", + "Z", + "a", + "b", + "c", + "d", + "e", + "f", + "g", + "h", + "i", + "j", + "k", + "l", + "m", + "n", + "o", + "p", + "q", + "r", + "s", + "t", + "u", + "v", + "w", + "x", + "y", + "z", + "0", + "1", + "2", + "3", + "4", + "5", + "6", + "7", + "8", + "9", + "+", + "/", +]; + +/** + * CREDIT: https://gist.github.com/enepomnyaschih/72c423f727d395eeaa09697058238727 + * Encodes a given Uint8Array, ArrayBuffer or string into RFC4648 base64 representation + * @param data + */ +export function encode(data: ArrayBuffer | string): string { + const uint8 = typeof data === "string" + ? new TextEncoder().encode(data) + : data instanceof Uint8Array + ? data + : new Uint8Array(data); + let result = "", + i; + const l = uint8.length; + for (i = 2; i < l; i += 3) { + result += base64abc[uint8[i - 2] >> 2]; + result += base64abc[((uint8[i - 2] & 0x03) << 4) | (uint8[i - 1] >> 4)]; + result += base64abc[((uint8[i - 1] & 0x0f) << 2) | (uint8[i] >> 6)]; + result += base64abc[uint8[i] & 0x3f]; + } + if (i === l + 1) { + // 1 octet yet to write + result += base64abc[uint8[i - 2] >> 2]; + result += base64abc[(uint8[i - 2] & 0x03) << 4]; + result += "=="; + } + if (i === l) { + // 2 octets yet to write + result += base64abc[uint8[i - 2] >> 2]; + result += base64abc[((uint8[i - 2] & 0x03) << 4) | (uint8[i - 1] >> 4)]; + result += base64abc[(uint8[i - 1] & 0x0f) << 2]; + result += "="; + } + return result; +} + +/** + * Decodes a given RFC4648 base64 encoded string + * @param b64 + */ +export function decode(b64: string): Uint8Array { + const binString = atob(b64); + const size = binString.length; + const bytes = new Uint8Array(size); + for (let i = 0; i < size; i++) { + bytes[i] = binString.charCodeAt(i); + } + return bytes; +} diff --git a/vendor/deno.land/std@0.178.0/encoding/base64url.ts b/vendor/deno.land/std@0.178.0/encoding/base64url.ts new file mode 100644 index 0000000..8f5af21 --- /dev/null +++ b/vendor/deno.land/std@0.178.0/encoding/base64url.ts @@ -0,0 +1,70 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. + +/** + * {@linkcode encode} and {@linkcode decode} for + * [base64 URL safe](https://en.wikipedia.org/wiki/Base64#URL_applications) encoding. + * + * This module is browser compatible. + * + * @example + * ```ts + * import { + * decode, + * encode, + * } from "https://deno.land/std@$STD_VERSION/encoding/base64url.ts"; + * + * const binary = new TextEncoder().encode("foobar"); + * const encoded = encode(binary); + * console.log(encoded); + * // => "Zm9vYmFy" + * + * console.log(decode(encoded)); + * // => Uint8Array(6) [ 102, 111, 111, 98, 97, 114 ] + * ``` + * + * @module + */ + +import * as base64 from "./base64.ts"; + +/* + * Some variants allow or require omitting the padding '=' signs: + * https://en.wikipedia.org/wiki/Base64#The_URL_applications + * @param base64url + */ +function addPaddingToBase64url(base64url: string): string { + if (base64url.length % 4 === 2) return base64url + "=="; + if (base64url.length % 4 === 3) return base64url + "="; + if (base64url.length % 4 === 1) { + throw new TypeError("Illegal base64url string!"); + } + return base64url; +} + +function convertBase64urlToBase64(b64url: string): string { + if (!/^[-_A-Z0-9]*?={0,2}$/i.test(b64url)) { + // Contains characters not part of base64url spec. + throw new TypeError("Failed to decode base64url: invalid character"); + } + return addPaddingToBase64url(b64url).replace(/\-/g, "+").replace(/_/g, "/"); +} + +function convertBase64ToBase64url(b64: string): string { + return b64.replace(/=/g, "").replace(/\+/g, "-").replace(/\//g, "_"); +} + +/** + * Encodes a given ArrayBuffer or string into a base64url representation + * @param data + */ +export function encode(data: ArrayBuffer | string): string { + return convertBase64ToBase64url(base64.encode(data)); +} + +/** + * Converts given base64url encoded data back to original + * @param b64url + */ +export function decode(b64url: string): Uint8Array { + return base64.decode(convertBase64urlToBase64(b64url)); +} diff --git a/vendor/deno.land/std@0.178.0/http/_negotiation/common.ts b/vendor/deno.land/std@0.178.0/http/_negotiation/common.ts new file mode 100644 index 0000000..e6f7fa9 --- /dev/null +++ b/vendor/deno.land/std@0.178.0/http/_negotiation/common.ts @@ -0,0 +1,51 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. +/*! + * Adapted directly from negotiator at https://github.com/jshttp/negotiator/ + * which is licensed as follows: + * + * (The MIT License) + * + * Copyright (c) 2012-2014 Federico Romero + * Copyright (c) 2012-2014 Isaac Z. Schlueter + * 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. + */ + +export interface Specificity { + i: number; + o?: number; + q: number; + s?: number; +} + +export function compareSpecs(a: Specificity, b: Specificity): number { + return ( + b.q - a.q || + (b.s ?? 0) - (a.s ?? 0) || + (a.o ?? 0) - (b.o ?? 0) || + a.i - b.i || + 0 + ); +} + +export function isQuality(spec: Specificity): boolean { + return spec.q > 0; +} diff --git a/vendor/deno.land/std@0.178.0/http/_negotiation/encoding.ts b/vendor/deno.land/std@0.178.0/http/_negotiation/encoding.ts new file mode 100644 index 0000000..7cb6429 --- /dev/null +++ b/vendor/deno.land/std@0.178.0/http/_negotiation/encoding.ts @@ -0,0 +1,162 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. +/*! + * Adapted directly from negotiator at https://github.com/jshttp/negotiator/ + * which is licensed as follows: + * + * (The MIT License) + * + * Copyright (c) 2012-2014 Federico Romero + * Copyright (c) 2012-2014 Isaac Z. Schlueter + * 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. + */ + +import { compareSpecs, isQuality, Specificity } from "./common.ts"; + +interface EncodingSpecificity extends Specificity { + encoding?: string; +} + +const simpleEncodingRegExp = /^\s*([^\s;]+)\s*(?:;(.*))?$/; + +function parseEncoding( + str: string, + i: number, +): EncodingSpecificity | undefined { + const match = simpleEncodingRegExp.exec(str); + if (!match) { + return undefined; + } + + const encoding = match[1]; + let q = 1; + if (match[2]) { + const params = match[2].split(";"); + for (const param of params) { + const p = param.trim().split("="); + if (p[0] === "q") { + q = parseFloat(p[1]); + break; + } + } + } + + return { encoding, q, i }; +} + +function specify( + encoding: string, + spec: EncodingSpecificity, + i = -1, +): Specificity | undefined { + if (!spec.encoding) { + return; + } + let s = 0; + if (spec.encoding.toLocaleLowerCase() === encoding.toLocaleLowerCase()) { + s = 1; + } else if (spec.encoding !== "*") { + return; + } + + return { + i, + o: spec.i, + q: spec.q, + s, + }; +} + +function parseAcceptEncoding(accept: string): EncodingSpecificity[] { + const accepts = accept.split(","); + const parsedAccepts: EncodingSpecificity[] = []; + let hasIdentity = false; + let minQuality = 1; + + for (let i = 0; i < accepts.length; i++) { + const encoding = parseEncoding(accepts[i].trim(), i); + + if (encoding) { + parsedAccepts.push(encoding); + hasIdentity = hasIdentity || !!specify("identity", encoding); + minQuality = Math.min(minQuality, encoding.q || 1); + } + } + + if (!hasIdentity) { + parsedAccepts.push({ + encoding: "identity", + q: minQuality, + i: accepts.length - 1, + }); + } + + return parsedAccepts; +} + +function getEncodingPriority( + encoding: string, + accepted: Specificity[], + index: number, +): Specificity { + let priority: Specificity = { o: -1, q: 0, s: 0, i: 0 }; + + for (const s of accepted) { + const spec = specify(encoding, s, index); + + if ( + spec && + (priority.s! - spec.s! || priority.q - spec.q || + priority.o! - spec.o!) < + 0 + ) { + priority = spec; + } + } + + return priority; +} + +/** Given an `Accept-Encoding` string, parse out the encoding returning a + * negotiated encoding based on the `provided` encodings otherwise just a + * prioritized array of encodings. */ +export function preferredEncodings( + accept: string, + provided?: string[], +): string[] { + const accepts = parseAcceptEncoding(accept); + + if (!provided) { + return accepts + .filter(isQuality) + .sort(compareSpecs) + .map((spec) => spec.encoding!); + } + + const priorities = provided.map((type, index) => + getEncodingPriority(type, accepts, index) + ); + + return priorities + .filter(isQuality) + .sort(compareSpecs) + .map((priority) => provided[priorities.indexOf(priority)]); +} diff --git a/vendor/deno.land/std@0.178.0/http/_negotiation/language.ts b/vendor/deno.land/std@0.178.0/http/_negotiation/language.ts new file mode 100644 index 0000000..61def86 --- /dev/null +++ b/vendor/deno.land/std@0.178.0/http/_negotiation/language.ts @@ -0,0 +1,144 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. +/*! + * Adapted directly from negotiator at https://github.com/jshttp/negotiator/ + * which is licensed as follows: + * + * (The MIT License) + * + * Copyright (c) 2012-2014 Federico Romero + * Copyright (c) 2012-2014 Isaac Z. Schlueter + * 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. + */ + +import { compareSpecs, isQuality, Specificity } from "./common.ts"; + +interface LanguageSpecificity extends Specificity { + prefix: string; + suffix?: string; + full: string; +} + +const SIMPLE_LANGUAGE_REGEXP = /^\s*([^\s\-;]+)(?:-([^\s;]+))?\s*(?:;(.*))?$/; + +function parseLanguage( + str: string, + i: number, +): LanguageSpecificity | undefined { + const match = SIMPLE_LANGUAGE_REGEXP.exec(str); + if (!match) { + return undefined; + } + + const [, prefix, suffix] = match; + const full = suffix ? `${prefix}-${suffix}` : prefix; + + let q = 1; + if (match[3]) { + const params = match[3].split(";"); + for (const param of params) { + const [key, value] = param.trim().split("="); + if (key === "q") { + q = parseFloat(value); + break; + } + } + } + + return { prefix, suffix, full, q, i }; +} + +function parseAcceptLanguage(accept: string): LanguageSpecificity[] { + const accepts = accept.split(","); + const result: LanguageSpecificity[] = []; + + for (let i = 0; i < accepts.length; i++) { + const language = parseLanguage(accepts[i].trim(), i); + if (language) { + result.push(language); + } + } + return result; +} + +function specify( + language: string, + spec: LanguageSpecificity, + i: number, +): Specificity | undefined { + const p = parseLanguage(language, i); + if (!p) { + return undefined; + } + let s = 0; + if (spec.full.toLowerCase() === p.full.toLowerCase()) { + s |= 4; + } else if (spec.prefix.toLowerCase() === p.prefix.toLowerCase()) { + s |= 2; + } else if (spec.full.toLowerCase() === p.prefix.toLowerCase()) { + s |= 1; + } else if (spec.full !== "*") { + return; + } + + return { i, o: spec.i, q: spec.q, s }; +} + +function getLanguagePriority( + language: string, + accepted: LanguageSpecificity[], + index: number, +): Specificity { + let priority: Specificity = { i: -1, o: -1, q: 0, s: 0 }; + for (const accepts of accepted) { + const spec = specify(language, accepts, index); + if ( + spec && + ((priority.s ?? 0) - (spec.s ?? 0) || priority.q - spec.q || + (priority.o ?? 0) - (spec.o ?? 0)) < 0 + ) { + priority = spec; + } + } + return priority; +} + +export function preferredLanguages( + accept = "*", + provided?: string[], +): string[] { + const accepts = parseAcceptLanguage(accept); + + if (!provided) { + return accepts + .filter(isQuality) + .sort(compareSpecs) + .map((spec) => spec.full); + } + + const priorities = provided + .map((type, index) => getLanguagePriority(type, accepts, index)); + + return priorities + .filter(isQuality) + .sort(compareSpecs) + .map((priority) => provided[priorities.indexOf(priority)]); +} diff --git a/vendor/deno.land/std@0.178.0/http/_negotiation/media_type.ts b/vendor/deno.land/std@0.178.0/http/_negotiation/media_type.ts new file mode 100644 index 0000000..53a157f --- /dev/null +++ b/vendor/deno.land/std@0.178.0/http/_negotiation/media_type.ts @@ -0,0 +1,237 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. +/*! + * Adapted directly from negotiator at https://github.com/jshttp/negotiator/ + * which is licensed as follows: + * + * (The MIT License) + * + * Copyright (c) 2012-2014 Federico Romero + * Copyright (c) 2012-2014 Isaac Z. Schlueter + * 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. + */ + +import { compareSpecs, isQuality, Specificity } from "./common.ts"; + +interface MediaTypeSpecificity extends Specificity { + type: string; + subtype: string; + params: { [param: string]: string | undefined }; +} + +const simpleMediaTypeRegExp = /^\s*([^\s\/;]+)\/([^;\s]+)\s*(?:;(.*))?$/; + +function quoteCount(str: string): number { + let count = 0; + let index = 0; + + while ((index = str.indexOf(`"`, index)) !== -1) { + count++; + index++; + } + + return count; +} + +function splitMediaTypes(accept: string): string[] { + const accepts = accept.split(","); + + let j = 0; + for (let i = 1; i < accepts.length; i++) { + if (quoteCount(accepts[j]) % 2 === 0) { + accepts[++j] = accepts[i]; + } else { + accepts[j] += `,${accepts[i]}`; + } + } + + accepts.length = j + 1; + + return accepts; +} + +function splitParameters(str: string): string[] { + const parameters = str.split(";"); + + let j = 0; + for (let i = 1; i < parameters.length; i++) { + if (quoteCount(parameters[j]) % 2 === 0) { + parameters[++j] = parameters[i]; + } else { + parameters[j] += `;${parameters[i]}`; + } + } + + parameters.length = j + 1; + + return parameters.map((p) => p.trim()); +} + +function splitKeyValuePair(str: string): [string, string | undefined] { + const [key, value] = str.split("="); + return [key.toLowerCase(), value]; +} + +function parseMediaType( + str: string, + i: number, +): MediaTypeSpecificity | undefined { + const match = simpleMediaTypeRegExp.exec(str); + + if (!match) { + return; + } + + const params: { [param: string]: string | undefined } = Object.create(null); + let q = 1; + const [, type, subtype, parameters] = match; + + if (parameters) { + const kvps = splitParameters(parameters).map(splitKeyValuePair); + + for (const [key, val] of kvps) { + const value = val && val[0] === `"` && val[val.length - 1] === `"` + ? val.slice(1, val.length - 1) + : val; + + if (key === "q" && value) { + q = parseFloat(value); + break; + } + + params[key] = value; + } + } + + return { type, subtype, params, q, i }; +} + +function parseAccept(accept: string): MediaTypeSpecificity[] { + const accepts = splitMediaTypes(accept); + + const mediaTypes: MediaTypeSpecificity[] = []; + for (let i = 0; i < accepts.length; i++) { + const mediaType = parseMediaType(accepts[i].trim(), i); + + if (mediaType) { + mediaTypes.push(mediaType); + } + } + + return mediaTypes; +} + +function getFullType(spec: MediaTypeSpecificity) { + return `${spec.type}/${spec.subtype}`; +} + +function specify( + type: string, + spec: MediaTypeSpecificity, + index: number, +): Specificity | undefined { + const p = parseMediaType(type, index); + + if (!p) { + return; + } + + let s = 0; + + if (spec.type.toLowerCase() === p.type.toLowerCase()) { + s |= 4; + } else if (spec.type !== "*") { + return; + } + + if (spec.subtype.toLowerCase() === p.subtype.toLowerCase()) { + s |= 2; + } else if (spec.subtype !== "*") { + return; + } + + const keys = Object.keys(spec.params); + if (keys.length) { + if ( + keys.every((key) => + (spec.params[key] || "").toLowerCase() === + (p.params[key] || "").toLowerCase() + ) + ) { + s |= 1; + } else { + return; + } + } + + return { + i: index, + o: spec.o, + q: spec.q, + s, + }; +} + +function getMediaTypePriority( + type: string, + accepted: MediaTypeSpecificity[], + index: number, +) { + let priority: Specificity = { o: -1, q: 0, s: 0, i: index }; + + for (const accepts of accepted) { + const spec = specify(type, accepts, index); + + if ( + spec && + ((priority.s || 0) - (spec.s || 0) || + (priority.q || 0) - (spec.q || 0) || + (priority.o || 0) - (spec.o || 0)) < 0 + ) { + priority = spec; + } + } + + return priority; +} + +export function preferredMediaTypes( + accept?: string | null, + provided?: string[], +): string[] { + const accepts = parseAccept(accept === undefined ? "*/*" : accept || ""); + + if (!provided) { + return accepts + .filter(isQuality) + .sort(compareSpecs) + .map(getFullType); + } + + const priorities = provided.map((type, index) => { + return getMediaTypePriority(type, accepts, index); + }); + + return priorities + .filter(isQuality) + .sort(compareSpecs) + .map((priority) => provided[priorities.indexOf(priority)]); +} diff --git a/vendor/deno.land/std@0.178.0/http/cookie_map.ts b/vendor/deno.land/std@0.178.0/http/cookie_map.ts new file mode 100644 index 0000000..8a23b79 --- /dev/null +++ b/vendor/deno.land/std@0.178.0/http/cookie_map.ts @@ -0,0 +1,805 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. + +/** Provides a iterable map interfaces for managing cookies server side. + * + * @example + * To access the keys in a request and have any set keys available for creating + * a response: + * + * ```ts + * import { + * CookieMap, + * mergeHeaders + * } from "https://deno.land/std@$STD_VERSION/http/cookie_map.ts"; + * + * const request = new Request("https://localhost/", { + * headers: { "cookie": "foo=bar; bar=baz;"} + * }); + * + * const cookies = new CookieMap(request, { secure: true }); + * console.log(cookies.get("foo")); // logs "bar" + * cookies.set("session", "1234567", { secure: true }); + * + * const response = new Response("hello", { + * headers: mergeHeaders({ + * "content-type": "text/plain", + * }, cookies), + * }); + * ``` + * + * @example + * To have automatic management of cryptographically signed cookies, you can use + * the {@linkcode SecureCookieMap} instead of {@linkcode CookieMap}. The biggest + * difference is that the methods operate async in order to be able to support + * async signing and validation of cookies: + * + * ```ts + * import { + * SecureCookieMap, + * mergeHeaders, + * type KeyRing, + * } from "https://deno.land/std@$STD_VERSION/http/cookie_map.ts"; + * + * const request = new Request("https://localhost/", { + * headers: { "cookie": "foo=bar; bar=baz;"} + * }); + * + * // The keys must implement the `KeyRing` interface. + * declare const keys: KeyRing; + * + * const cookies = new SecureCookieMap(request, { keys, secure: true }); + * console.log(await cookies.get("foo")); // logs "bar" + * // the cookie will be automatically signed using the supplied key ring. + * await cookies.set("session", "1234567"); + * + * const response = new Response("hello", { + * headers: mergeHeaders({ + * "content-type": "text/plain", + * }, cookies), + * }); + * ``` + * + * In addition, if you have a {@linkcode Response} or {@linkcode Headers} for a + * response at construction of the cookies object, they can be passed and any + * set cookies will be added directly to those headers: + * + * ```ts + * import { CookieMap } from "https://deno.land/std@$STD_VERSION/http/cookie_map.ts"; + * + * const request = new Request("https://localhost/", { + * headers: { "cookie": "foo=bar; bar=baz;"} + * }); + * + * const response = new Response("hello", { + * headers: { "content-type": "text/plain" }, + * }); + * + * const cookies = new CookieMap(request, { response }); + * console.log(cookies.get("foo")); // logs "bar" + * cookies.set("session", "1234567"); + * ``` + * + * @module + */ + +export interface CookieMapOptions { + /** The {@linkcode Response} or the headers that will be used with the + * response. When provided, `Set-Cookie` headers will be set in the headers + * when cookies are set or deleted in the map. + * + * An alternative way to extract the headers is to pass the cookie map to the + * {@linkcode mergeHeaders} function to merge various sources of the + * headers to be provided when creating or updating a response. + */ + response?: Headered | Headers; + /** A flag that indicates if the request and response are being handled over + * a secure (e.g. HTTPS/TLS) connection. + * + * @default {false} + */ + secure?: boolean; +} + +export interface CookieMapSetDeleteOptions { + /** The domain to scope the cookie for. */ + domain?: string; + /** When the cookie expires. */ + expires?: Date; + /** A flag that indicates if the cookie is valid over HTTP only. */ + httpOnly?: boolean; + /** Do not error when signing and validating cookies over an insecure + * connection. */ + ignoreInsecure?: boolean; + /** Overwrite an existing value. */ + overwrite?: boolean; + /** The path the cookie is valid for. */ + path?: string; + /** Override the flag that was set when the instance was created. */ + secure?: boolean; + /** Set the same-site indicator for a cookie. */ + sameSite?: "strict" | "lax" | "none" | boolean; +} + +/** An object which contains a `headers` property which has a value of an + * instance of {@linkcode Headers}, like {@linkcode Request} and + * {@linkcode Response}. */ +export interface Headered { + headers: Headers; +} + +export interface Mergeable { + [cookieMapHeadersInitSymbol](): [string, string][]; +} + +export interface SecureCookieMapOptions { + /** Keys which will be used to validate and sign cookies. The key ring should + * implement the {@linkcode KeyRing} interface. */ + keys?: KeyRing; + + /** The {@linkcode Response} or the headers that will be used with the + * response. When provided, `Set-Cookie` headers will be set in the headers + * when cookies are set or deleted in the map. + * + * An alternative way to extract the headers is to pass the cookie map to the + * {@linkcode mergeHeaders} function to merge various sources of the + * headers to be provided when creating or updating a response. + */ + response?: Headered | Headers; + + /** A flag that indicates if the request and response are being handled over + * a secure (e.g. HTTPS/TLS) connection. + * + * @default {false} + */ + secure?: boolean; +} + +export interface SecureCookieMapGetOptions { + /** Overrides the flag that was set when the instance was created. */ + signed?: boolean; +} + +export interface SecureCookieMapSetDeleteOptions { + /** The domain to scope the cookie for. */ + domain?: string; + /** When the cookie expires. */ + expires?: Date; + /** A flag that indicates if the cookie is valid over HTTP only. */ + httpOnly?: boolean; + /** Do not error when signing and validating cookies over an insecure + * connection. */ + ignoreInsecure?: boolean; + /** Overwrite an existing value. */ + overwrite?: boolean; + /** The path the cookie is valid for. */ + path?: string; + /** Override the flag that was set when the instance was created. */ + secure?: boolean; + /** Set the same-site indicator for a cookie. */ + sameSite?: "strict" | "lax" | "none" | boolean; + /** Override the default behavior of signing the cookie. */ + signed?: boolean; +} + +type CookieAttributes = SecureCookieMapSetDeleteOptions; + +// deno-lint-ignore no-control-regex +const FIELD_CONTENT_REGEXP = /^[\u0009\u0020-\u007e\u0080-\u00ff]+$/; +const KEY_REGEXP = /(?:^|;) *([^=]*)=[^;]*/g; +const SAME_SITE_REGEXP = /^(?:lax|none|strict)$/i; + +const matchCache: Record = {}; +function getPattern(name: string): RegExp { + if (name in matchCache) { + return matchCache[name]; + } + + return matchCache[name] = new RegExp( + `(?:^|;) *${name.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, "\\$&")}=([^;]*)`, + ); +} + +function pushCookie(values: string[], cookie: Cookie) { + if (cookie.overwrite) { + for (let i = values.length - 1; i >= 0; i--) { + if (values[i].indexOf(`${cookie.name}=`) === 0) { + values.splice(i, 1); + } + } + } + values.push(cookie.toHeaderValue()); +} + +function validateCookieProperty( + key: string, + value: string | undefined | null, +) { + if (value && !FIELD_CONTENT_REGEXP.test(value)) { + throw new TypeError(`The "${key}" of the cookie (${value}) is invalid.`); + } +} + +/** An internal abstraction to manage cookies. */ +class Cookie implements CookieAttributes { + domain?: string; + expires?: Date; + httpOnly = true; + maxAge?: number; + name: string; + overwrite = false; + path = "/"; + sameSite: "strict" | "lax" | "none" | boolean = false; + secure = false; + signed?: boolean; + value: string; + + constructor( + name: string, + value: string | null, + attributes: CookieAttributes, + ) { + validateCookieProperty("name", name); + this.name = name; + validateCookieProperty("value", value); + this.value = value ?? ""; + Object.assign(this, attributes); + if (!this.value) { + this.expires = new Date(0); + this.maxAge = undefined; + } + + validateCookieProperty("path", this.path); + validateCookieProperty("domain", this.domain); + if ( + this.sameSite && typeof this.sameSite === "string" && + !SAME_SITE_REGEXP.test(this.sameSite) + ) { + throw new TypeError( + `The "sameSite" of the cookie ("${this.sameSite}") is invalid.`, + ); + } + } + + toHeaderValue(): string { + let value = this.toString(); + if (this.maxAge) { + this.expires = new Date(Date.now() + (this.maxAge * 1000)); + } + if (this.path) { + value += `; path=${this.path}`; + } + if (this.expires) { + value += `; expires=${this.expires.toUTCString()}`; + } + if (this.domain) { + value += `; domain=${this.domain}`; + } + if (this.sameSite) { + value += `; samesite=${ + this.sameSite === true ? "strict" : this.sameSite.toLowerCase() + }`; + } + if (this.secure) { + value += "; secure"; + } + if (this.httpOnly) { + value += "; httponly"; + } + return value; + } + + toString(): string { + return `${this.name}=${this.value}`; + } +} + +/** Symbol which is used in {@link mergeHeaders} to extract a + * `[string | string][]` from an instance to generate the final set of + * headers. */ +export const cookieMapHeadersInitSymbol = Symbol.for( + "Deno.std.cookieMap.headersInit", +); + +function isMergeable(value: unknown): value is Mergeable { + return value != null && typeof value === "object" && + cookieMapHeadersInitSymbol in value; +} + +/** Allows merging of various sources of headers into a final set of headers + * which can be used in a {@linkcode Response}. + * + * Note, that unlike when passing a `Response` or {@linkcode Headers} used in a + * response to {@linkcode CookieMap} or {@linkcode SecureCookieMap}, merging + * will not ensure that there are no other `Set-Cookie` headers from other + * sources, it will simply append the various headers together. */ +export function mergeHeaders( + ...sources: (Headered | HeadersInit | Mergeable)[] +): Headers { + const headers = new Headers(); + for (const source of sources) { + let entries: Iterable<[string, string]>; + if (source instanceof Headers) { + entries = source; + } else if ("headers" in source && source.headers instanceof Headers) { + entries = source.headers; + } else if (isMergeable(source)) { + entries = source[cookieMapHeadersInitSymbol](); + } else if (Array.isArray(source)) { + entries = source as [string, string][]; + } else { + entries = Object.entries(source); + } + for (const [key, value] of entries) { + headers.append(key, value); + } + } + return headers; +} + +const keys = Symbol("#keys"); +const requestHeaders = Symbol("#requestHeaders"); +const responseHeaders = Symbol("#responseHeaders"); +const isSecure = Symbol("#secure"); +const requestKeys = Symbol("#requestKeys"); + +/** An internal abstract class which provides common functionality for + * {@link CookieMap} and {@link SecureCookieMap}. */ +abstract class CookieMapBase implements Mergeable { + [keys]?: string[]; + [requestHeaders]: Headers; + [responseHeaders]: Headers; + [isSecure]: boolean; + + [requestKeys](): string[] { + if (this[keys]) { + return this[keys]; + } + const result = this[keys] = [] as string[]; + const header = this[requestHeaders].get("cookie"); + if (!header) { + return result; + } + let matches: RegExpExecArray | null; + while ((matches = KEY_REGEXP.exec(header))) { + const [, key] = matches; + result.push(key); + } + return result; + } + + constructor(request: Headers | Headered, options: CookieMapOptions) { + this[requestHeaders] = "headers" in request ? request.headers : request; + const { secure = false, response = new Headers() } = options; + this[responseHeaders] = "headers" in response ? response.headers : response; + this[isSecure] = secure; + } + + /** A method used by {@linkcode mergeHeaders} to be able to merge + * headers from various sources when forming a {@linkcode Response}. */ + [cookieMapHeadersInitSymbol](): [string, string][] { + const init: [string, string][] = []; + for (const [key, value] of this[responseHeaders]) { + if (key === "set-cookie") { + init.push([key, value]); + } + } + return init; + } + + [Symbol.for("Deno.customInspect")]() { + return `${this.constructor.name} []`; + } + + [Symbol.for("nodejs.util.inspect.custom")]( + depth: number, + // deno-lint-ignore no-explicit-any + options: any, + inspect: (value: unknown, options?: unknown) => string, + ) { + if (depth < 0) { + return options.stylize(`[${this.constructor.name}]`, "special"); + } + + const newOptions = Object.assign({}, options, { + depth: options.depth === null ? null : options.depth - 1, + }); + return `${options.stylize(this.constructor.name, "special")} ${ + inspect([], newOptions) + }`; + } +} + +/** + * Provides a way to manage cookies in a request and response on the server + * as a single iterable collection. + * + * The methods and properties align to {@linkcode Map}. When constructing a + * {@linkcode Request} or {@linkcode Headers} from the request need to be + * provided, as well as optionally the {@linkcode Response} or `Headers` for the + * response can be provided. Alternatively the {@linkcode mergeHeaders} + * function can be used to generate a final set of headers for sending in the + * response. */ +export class CookieMap extends CookieMapBase { + /** Contains the number of valid cookies in the request headers. */ + get size(): number { + return [...this].length; + } + + constructor(request: Headers | Headered, options: CookieMapOptions = {}) { + super(request, options); + } + + /** Deletes all the cookies from the {@linkcode Request} in the response. */ + clear(options: CookieMapSetDeleteOptions = {}) { + for (const key of this.keys()) { + this.set(key, null, options); + } + } + + /** Set a cookie to be deleted in the response. + * + * This is a convenience function for `set(key, null, options?)`. + */ + delete(key: string, options: CookieMapSetDeleteOptions = {}): boolean { + this.set(key, null, options); + return true; + } + + /** Return the value of a matching key present in the {@linkcode Request}. If + * the key is not present `undefined` is returned. */ + get(key: string): string | undefined { + const headerValue = this[requestHeaders].get("cookie"); + if (!headerValue) { + return undefined; + } + const match = headerValue.match(getPattern(key)); + if (!match) { + return undefined; + } + const [, value] = match; + return value; + } + + /** Returns `true` if the matching key is present in the {@linkcode Request}, + * otherwise `false`. */ + has(key: string): boolean { + const headerValue = this[requestHeaders].get("cookie"); + if (!headerValue) { + return false; + } + return getPattern(key).test(headerValue); + } + + /** Set a named cookie in the response. The optional + * {@linkcode CookieMapSetDeleteOptions} are applied to the cookie being set. + */ + set( + key: string, + value: string | null, + options: CookieMapSetDeleteOptions = {}, + ): this { + const resHeaders = this[responseHeaders]; + const values: string[] = []; + for (const [key, value] of resHeaders) { + if (key === "set-cookie") { + values.push(value); + } + } + const secure = this[isSecure]; + + if (!secure && options.secure && !options.ignoreInsecure) { + throw new TypeError( + "Cannot send secure cookie over unencrypted connection.", + ); + } + + const cookie = new Cookie(key, value, options); + cookie.secure = options.secure ?? secure; + pushCookie(values, cookie); + + resHeaders.delete("set-cookie"); + for (const value of values) { + resHeaders.append("set-cookie", value); + } + return this; + } + + /** Iterate over the cookie keys and values that are present in the + * {@linkcode Request}. This is an alias of the `[Symbol.iterator]` method + * present on the class. */ + entries(): IterableIterator<[string, string]> { + return this[Symbol.iterator](); + } + + /** Iterate over the cookie keys that are present in the + * {@linkcode Request}. */ + *keys(): IterableIterator { + for (const [key] of this) { + yield key; + } + } + + /** Iterate over the cookie values that are present in the + * {@linkcode Request}. */ + *values(): IterableIterator { + for (const [, value] of this) { + yield value; + } + } + + /** Iterate over the cookie keys and values that are present in the + * {@linkcode Request}. */ + *[Symbol.iterator](): IterableIterator<[string, string]> { + const keys = this[requestKeys](); + for (const key of keys) { + const value = this.get(key); + if (value) { + yield [key, value]; + } + } + } +} + +/** Types of data that can be signed cryptographically. */ +export type Data = string | number[] | ArrayBuffer | Uint8Array; + +/** An interface which describes the methods that {@linkcode SecureCookieMap} + * uses to sign and verify cookies. */ +export interface KeyRing { + /** Given a set of data and a digest, return the key index of the key used + * to sign the data. The index is 0 based. A non-negative number indices the + * digest is valid and a key was found. */ + indexOf(data: Data, digest: string): Promise | number; + /** Sign the data, returning a string based digest of the data. */ + sign(data: Data): Promise | string; + /** Verifies the digest matches the provided data, indicating the data was + * signed by the keyring and has not been tampered with. */ + verify(data: Data, digest: string): Promise | boolean; +} + +/** Provides an way to manage cookies in a request and response on the server + * as a single iterable collection, as well as the ability to sign and verify + * cookies to prevent tampering. + * + * The methods and properties align to {@linkcode Map}, but due to the need to + * support asynchronous cryptographic keys, all the APIs operate async. When + * constructing a {@linkcode Request} or {@linkcode Headers} from the request + * need to be provided, as well as optionally the {@linkcode Response} or + * `Headers` for the response can be provided. Alternatively the + * {@linkcode mergeHeaders} function can be used to generate a final set + * of headers for sending in the response. + * + * On construction, the optional set of keys implementing the + * {@linkcode KeyRing} interface. While it is optional, if you don't plan to use + * keys, you might want to consider using just the {@linkcode CookieMap}. + * + * @example + */ +export class SecureCookieMap extends CookieMapBase { + #keyRing?: KeyRing; + + /** Is set to a promise which resolves with the number of cookies in the + * {@linkcode Request}. */ + get size(): Promise { + return (async () => { + let size = 0; + for await (const _ of this) { + size++; + } + return size; + })(); + } + + constructor( + request: Headers | Headered, + options: SecureCookieMapOptions = {}, + ) { + super(request, options); + const { keys } = options; + this.#keyRing = keys; + } + + /** Sets all cookies in the {@linkcode Request} to be deleted in the + * response. */ + async clear(options: SecureCookieMapSetDeleteOptions) { + for await (const key of this.keys()) { + await this.set(key, null, options); + } + } + + /** Set a cookie to be deleted in the response. + * + * This is a convenience function for `set(key, null, options?)`. */ + async delete( + key: string, + options: SecureCookieMapSetDeleteOptions = {}, + ): Promise { + await this.set(key, null, options); + return true; + } + + /** Get the value of a cookie from the {@linkcode Request}. + * + * If the cookie is signed, and the signature is invalid, `undefined` will be + * returned and the cookie will be set to be deleted in the response. If the + * cookie is using an "old" key from the keyring, the cookie will be re-signed + * with the current key and be added to the response to be updated. */ + async get( + key: string, + options: SecureCookieMapGetOptions = {}, + ): Promise { + const signed = options.signed ?? !!this.#keyRing; + const nameSig = `${key}.sig`; + + const header = this[requestHeaders].get("cookie"); + if (!header) { + return; + } + const match = header.match(getPattern(key)); + if (!match) { + return; + } + const [, value] = match; + if (!signed) { + return value; + } + const digest = await this.get(nameSig, { signed: false }); + if (!digest) { + return; + } + const data = `${key}=${value}`; + if (!this.#keyRing) { + throw new TypeError("key ring required for signed cookies"); + } + const index = await this.#keyRing.indexOf(data, digest); + + if (index < 0) { + await this.delete(nameSig, { path: "/", signed: false }); + } else { + if (index) { + await this.set(nameSig, await this.#keyRing.sign(data), { + signed: false, + }); + } + return value; + } + } + + /** Returns `true` if the key is in the {@linkcode Request}. + * + * If the cookie is signed, and the signature is invalid, `false` will be + * returned and the cookie will be set to be deleted in the response. If the + * cookie is using an "old" key from the keyring, the cookie will be re-signed + * with the current key and be added to the response to be updated. */ + async has( + key: string, + options: SecureCookieMapGetOptions = {}, + ): Promise { + const signed = options.signed ?? !!this.#keyRing; + const nameSig = `${key}.sig`; + + const header = this[requestHeaders].get("cookie"); + if (!header) { + return false; + } + const match = header.match(getPattern(key)); + if (!match) { + return false; + } + if (!signed) { + return true; + } + const digest = await this.get(nameSig, { signed: false }); + if (!digest) { + return false; + } + const [, value] = match; + const data = `${key}=${value}`; + if (!this.#keyRing) { + throw new TypeError("key ring required for signed cookies"); + } + const index = await this.#keyRing.indexOf(data, digest); + + if (index < 0) { + await this.delete(nameSig, { path: "/", signed: false }); + return false; + } else { + if (index) { + await this.set(nameSig, await this.#keyRing.sign(data), { + signed: false, + }); + } + return true; + } + } + + /** Set a cookie in the response headers. + * + * If there was a keyring set, cookies will be automatically signed, unless + * overridden by the passed options. Cookies can be deleted by setting the + * value to `null`. */ + async set( + key: string, + value: string | null, + options: SecureCookieMapSetDeleteOptions = {}, + ): Promise { + const resHeaders = this[responseHeaders]; + const headers: string[] = []; + for (const [key, value] of resHeaders.entries()) { + if (key === "set-cookie") { + headers.push(value); + } + } + const secure = this[isSecure]; + const signed = options.signed ?? !!this.#keyRing; + + if (!secure && options.secure && !options.ignoreInsecure) { + throw new TypeError( + "Cannot send secure cookie over unencrypted connection.", + ); + } + + const cookie = new Cookie(key, value, options); + cookie.secure = options.secure ?? secure; + pushCookie(headers, cookie); + + if (signed) { + if (!this.#keyRing) { + throw new TypeError("keys required for signed cookies."); + } + cookie.value = await this.#keyRing.sign(cookie.toString()); + cookie.name += ".sig"; + pushCookie(headers, cookie); + } + + resHeaders.delete("set-cookie"); + for (const header of headers) { + resHeaders.append("set-cookie", header); + } + return this; + } + + /** Iterate over the {@linkcode Request} cookies, yielding up a tuple + * containing the key and value of each cookie. + * + * If a key ring was provided, only properly signed cookie keys and values are + * returned. */ + entries(): AsyncIterableIterator<[string, string]> { + return this[Symbol.asyncIterator](); + } + + /** Iterate over the request's cookies, yielding up the key of each cookie. + * + * If a keyring was provided, only properly signed cookie keys are + * returned. */ + async *keys(): AsyncIterableIterator { + for await (const [key] of this) { + yield key; + } + } + + /** Iterate over the request's cookies, yielding up the value of each cookie. + * + * If a keyring was provided, only properly signed cookie values are + * returned. */ + async *values(): AsyncIterableIterator { + for await (const [, value] of this) { + yield value; + } + } + + /** Iterate over the {@linkcode Request} cookies, yielding up a tuple + * containing the key and value of each cookie. + * + * If a key ring was provided, only properly signed cookie keys and values are + * returned. */ + async *[Symbol.asyncIterator](): AsyncIterableIterator<[string, string]> { + const keys = this[requestKeys](); + for (const key of keys) { + const value = await this.get(key); + if (value) { + yield [key, value]; + } + } + } +} diff --git a/vendor/deno.land/std@0.178.0/http/http_errors.ts b/vendor/deno.land/std@0.178.0/http/http_errors.ts new file mode 100644 index 0000000..9ebc104 --- /dev/null +++ b/vendor/deno.land/std@0.178.0/http/http_errors.ts @@ -0,0 +1,204 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. + +/** A collection of HTTP errors and utilities. + * + * The export {@linkcode errors} contains an individual class that extends + * {@linkcode HttpError} which makes handling HTTP errors in a structured way. + * + * The function {@linkcode createHttpError} provides a way to create instances + * of errors in a factory pattern. + * + * The function {@linkcode isHttpError} is a type guard that will narrow a value + * to an `HttpError` instance. + * + * @example + * ```ts + * import { errors, isHttpError } from "https://deno.land/std@$STD_VERSION/http/http_errors.ts"; + * + * try { + * throw new errors.NotFound(); + * } catch (e) { + * if (isHttpError(e)) { + * const response = new Response(e.message, { status: e.status }); + * } else { + * throw e; + * } + * } + * ``` + * + * @example + * ```ts + * import { createHttpError } from "https://deno.land/std@$STD_VERSION/http/http_errors.ts"; + * import { Status } from "https://deno.land/std@$STD_VERSION/http/http_status.ts"; + * + * try { + * throw createHttpError( + * Status.BadRequest, + * "The request was bad.", + * { expose: false } + * ); + * } catch (e) { + * // handle errors + * } + * ``` + * + * @module + */ + +import { + type ErrorStatus, + isClientErrorStatus, + Status, + STATUS_TEXT, +} from "./http_status.ts"; + +const ERROR_STATUS_MAP = { + "BadRequest": 400, + "Unauthorized": 401, + "PaymentRequired": 402, + "Forbidden": 403, + "NotFound": 404, + "MethodNotAllowed": 405, + "NotAcceptable": 406, + "ProxyAuthRequired": 407, + "RequestTimeout": 408, + "Conflict": 409, + "Gone": 410, + "LengthRequired": 411, + "PreconditionFailed": 412, + "RequestEntityTooLarge": 413, + "RequestURITooLong": 414, + "UnsupportedMediaType": 415, + "RequestedRangeNotSatisfiable": 416, + "ExpectationFailed": 417, + "Teapot": 418, + "MisdirectedRequest": 421, + "UnprocessableEntity": 422, + "Locked": 423, + "FailedDependency": 424, + "UpgradeRequired": 426, + "PreconditionRequired": 428, + "TooManyRequests": 429, + "RequestHeaderFieldsTooLarge": 431, + "UnavailableForLegalReasons": 451, + "InternalServerError": 500, + "NotImplemented": 501, + "BadGateway": 502, + "ServiceUnavailable": 503, + "GatewayTimeout": 504, + "HTTPVersionNotSupported": 505, + "VariantAlsoNegotiates": 506, + "InsufficientStorage": 507, + "LoopDetected": 508, + "NotExtended": 510, + "NetworkAuthenticationRequired": 511, +} as const; + +export type ErrorStatusKeys = keyof typeof ERROR_STATUS_MAP; + +export interface HttpErrorOptions extends ErrorOptions { + expose?: boolean; + headers?: HeadersInit; +} + +/** The base class that all derivative HTTP extend, providing a `status` and an + * `expose` property. */ +export class HttpError extends Error { + #status: ErrorStatus = Status.InternalServerError; + #expose: boolean; + #headers?: Headers; + constructor( + message = "Http Error", + options?: HttpErrorOptions, + ) { + super(message, options); + this.#expose = options?.expose === undefined + ? isClientErrorStatus(this.status) + : options.expose; + if (options?.headers) { + this.#headers = new Headers(options.headers); + } + } + /** A flag to indicate if the internals of the error, like the stack, should + * be exposed to a client, or if they are "private" and should not be leaked. + * By default, all client errors are `true` and all server errors are + * `false`. */ + get expose(): boolean { + return this.#expose; + } + /** The optional headers object that is set on the error. */ + get headers(): Headers | undefined { + return this.#headers; + } + /** The error status that is set on the error. */ + get status(): ErrorStatus { + return this.#status; + } +} + +function createHttpErrorConstructor(status: ErrorStatus): typeof HttpError { + const name = `${Status[status]}Error`; + const ErrorCtor = class extends HttpError { + constructor( + message = STATUS_TEXT[status], + options?: HttpErrorOptions, + ) { + super(message, options); + Object.defineProperty(this, "name", { + configurable: true, + enumerable: false, + value: name, + writable: true, + }); + } + + override get status() { + return status; + } + }; + return ErrorCtor; +} + +/** + * A namespace that contains each error constructor. Each error extends + * `HTTPError` and provides `.status` and `.expose` properties, where the + * `.status` will be an error `Status` value and `.expose` indicates if + * information, like a stack trace, should be shared in the response. + * + * By default, `.expose` is set to false in server errors, and true for client + * errors. + * + * @example + * ```ts + * import { errors } from "https://deno.land/std@$STD_VERSION/http/http_errors.ts"; + * + * throw new errors.InternalServerError("Ooops!"); + * ``` + */ +export const errors: Record = {} as Record< + ErrorStatusKeys, + typeof HttpError +>; + +for (const [key, value] of Object.entries(ERROR_STATUS_MAP)) { + errors[key as ErrorStatusKeys] = createHttpErrorConstructor(value); +} + +/** + * A factory function which provides a way to create errors. It takes up to 3 + * arguments, the error `Status`, an message, which defaults to the status text + * and error options, which incudes the `expose` property to set the `.expose` + * value on the error. + */ +export function createHttpError( + status: ErrorStatus = Status.InternalServerError, + message?: string, + options?: HttpErrorOptions, +): HttpError { + return new errors[Status[status] as ErrorStatusKeys](message, options); +} + +/** A type guard that determines if the value is an HttpError or not. */ +export function isHttpError(value: unknown): value is HttpError { + return value instanceof HttpError; +} diff --git a/vendor/deno.land/std@0.178.0/http/http_status.ts b/vendor/deno.land/std@0.178.0/http/http_status.ts new file mode 100644 index 0000000..12c467c --- /dev/null +++ b/vendor/deno.land/std@0.178.0/http/http_status.ts @@ -0,0 +1,343 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. +// This module is browser compatible. + +/** + * Contains the enum {@linkcode Status} which enumerates standard HTTP status + * codes and provides several type guards for handling status codes with type + * safety. + * + * @example + * ```ts + * import { + * Status, + * STATUS_TEXT, + * } from "https://deno.land/std@$STD_VERSION/http/http_status.ts"; + * + * console.log(Status.NotFound); //=> 404 + * console.log(STATUS_TEXT[Status.NotFound]); //=> "Not Found" + * ``` + * + * ```ts + * import { isErrorStatus } from "https://deno.land/std@$STD_VERSION/http/http_status.ts"; + * + * const res = await fetch("https://example.com/"); + * + * if (isErrorStatus(res.status)) { + * // error handling here... + * } + * ``` + * + * @module + */ + +/** Standard HTTP status codes. */ +export enum Status { + /** RFC 7231, 6.2.1 */ + Continue = 100, + /** RFC 7231, 6.2.2 */ + SwitchingProtocols = 101, + /** RFC 2518, 10.1 */ + Processing = 102, + /** RFC 8297 **/ + EarlyHints = 103, + + /** RFC 7231, 6.3.1 */ + OK = 200, + /** RFC 7231, 6.3.2 */ + Created = 201, + /** RFC 7231, 6.3.3 */ + Accepted = 202, + /** RFC 7231, 6.3.4 */ + NonAuthoritativeInfo = 203, + /** RFC 7231, 6.3.5 */ + NoContent = 204, + /** RFC 7231, 6.3.6 */ + ResetContent = 205, + /** RFC 7233, 4.1 */ + PartialContent = 206, + /** RFC 4918, 11.1 */ + MultiStatus = 207, + /** RFC 5842, 7.1 */ + AlreadyReported = 208, + /** RFC 3229, 10.4.1 */ + IMUsed = 226, + + /** RFC 7231, 6.4.1 */ + MultipleChoices = 300, + /** RFC 7231, 6.4.2 */ + MovedPermanently = 301, + /** RFC 7231, 6.4.3 */ + Found = 302, + /** RFC 7231, 6.4.4 */ + SeeOther = 303, + /** RFC 7232, 4.1 */ + NotModified = 304, + /** RFC 7231, 6.4.5 */ + UseProxy = 305, + /** RFC 7231, 6.4.7 */ + TemporaryRedirect = 307, + /** RFC 7538, 3 */ + PermanentRedirect = 308, + + /** RFC 7231, 6.5.1 */ + BadRequest = 400, + /** RFC 7235, 3.1 */ + Unauthorized = 401, + /** RFC 7231, 6.5.2 */ + PaymentRequired = 402, + /** RFC 7231, 6.5.3 */ + Forbidden = 403, + /** RFC 7231, 6.5.4 */ + NotFound = 404, + /** RFC 7231, 6.5.5 */ + MethodNotAllowed = 405, + /** RFC 7231, 6.5.6 */ + NotAcceptable = 406, + /** RFC 7235, 3.2 */ + ProxyAuthRequired = 407, + /** RFC 7231, 6.5.7 */ + RequestTimeout = 408, + /** RFC 7231, 6.5.8 */ + Conflict = 409, + /** RFC 7231, 6.5.9 */ + Gone = 410, + /** RFC 7231, 6.5.10 */ + LengthRequired = 411, + /** RFC 7232, 4.2 */ + PreconditionFailed = 412, + /** RFC 7231, 6.5.11 */ + RequestEntityTooLarge = 413, + /** RFC 7231, 6.5.12 */ + RequestURITooLong = 414, + /** RFC 7231, 6.5.13 */ + UnsupportedMediaType = 415, + /** RFC 7233, 4.4 */ + RequestedRangeNotSatisfiable = 416, + /** RFC 7231, 6.5.14 */ + ExpectationFailed = 417, + /** RFC 7168, 2.3.3 */ + Teapot = 418, + /** RFC 7540, 9.1.2 */ + MisdirectedRequest = 421, + /** RFC 4918, 11.2 */ + UnprocessableEntity = 422, + /** RFC 4918, 11.3 */ + Locked = 423, + /** RFC 4918, 11.4 */ + FailedDependency = 424, + /** RFC 8470, 5.2 */ + TooEarly = 425, + /** RFC 7231, 6.5.15 */ + UpgradeRequired = 426, + /** RFC 6585, 3 */ + PreconditionRequired = 428, + /** RFC 6585, 4 */ + TooManyRequests = 429, + /** RFC 6585, 5 */ + RequestHeaderFieldsTooLarge = 431, + /** RFC 7725, 3 */ + UnavailableForLegalReasons = 451, + + /** RFC 7231, 6.6.1 */ + InternalServerError = 500, + /** RFC 7231, 6.6.2 */ + NotImplemented = 501, + /** RFC 7231, 6.6.3 */ + BadGateway = 502, + /** RFC 7231, 6.6.4 */ + ServiceUnavailable = 503, + /** RFC 7231, 6.6.5 */ + GatewayTimeout = 504, + /** RFC 7231, 6.6.6 */ + HTTPVersionNotSupported = 505, + /** RFC 2295, 8.1 */ + VariantAlsoNegotiates = 506, + /** RFC 4918, 11.5 */ + InsufficientStorage = 507, + /** RFC 5842, 7.2 */ + LoopDetected = 508, + /** RFC 2774, 7 */ + NotExtended = 510, + /** RFC 6585, 6 */ + NetworkAuthenticationRequired = 511, +} + +/** A record of all the status codes text. */ +export const STATUS_TEXT: Readonly> = { + [Status.Accepted]: "Accepted", + [Status.AlreadyReported]: "Already Reported", + [Status.BadGateway]: "Bad Gateway", + [Status.BadRequest]: "Bad Request", + [Status.Conflict]: "Conflict", + [Status.Continue]: "Continue", + [Status.Created]: "Created", + [Status.EarlyHints]: "Early Hints", + [Status.ExpectationFailed]: "Expectation Failed", + [Status.FailedDependency]: "Failed Dependency", + [Status.Forbidden]: "Forbidden", + [Status.Found]: "Found", + [Status.GatewayTimeout]: "Gateway Timeout", + [Status.Gone]: "Gone", + [Status.HTTPVersionNotSupported]: "HTTP Version Not Supported", + [Status.IMUsed]: "IM Used", + [Status.InsufficientStorage]: "Insufficient Storage", + [Status.InternalServerError]: "Internal Server Error", + [Status.LengthRequired]: "Length Required", + [Status.Locked]: "Locked", + [Status.LoopDetected]: "Loop Detected", + [Status.MethodNotAllowed]: "Method Not Allowed", + [Status.MisdirectedRequest]: "Misdirected Request", + [Status.MovedPermanently]: "Moved Permanently", + [Status.MultiStatus]: "Multi Status", + [Status.MultipleChoices]: "Multiple Choices", + [Status.NetworkAuthenticationRequired]: "Network Authentication Required", + [Status.NoContent]: "No Content", + [Status.NonAuthoritativeInfo]: "Non Authoritative Info", + [Status.NotAcceptable]: "Not Acceptable", + [Status.NotExtended]: "Not Extended", + [Status.NotFound]: "Not Found", + [Status.NotImplemented]: "Not Implemented", + [Status.NotModified]: "Not Modified", + [Status.OK]: "OK", + [Status.PartialContent]: "Partial Content", + [Status.PaymentRequired]: "Payment Required", + [Status.PermanentRedirect]: "Permanent Redirect", + [Status.PreconditionFailed]: "Precondition Failed", + [Status.PreconditionRequired]: "Precondition Required", + [Status.Processing]: "Processing", + [Status.ProxyAuthRequired]: "Proxy Auth Required", + [Status.RequestEntityTooLarge]: "Request Entity Too Large", + [Status.RequestHeaderFieldsTooLarge]: "Request Header Fields Too Large", + [Status.RequestTimeout]: "Request Timeout", + [Status.RequestURITooLong]: "Request URI Too Long", + [Status.RequestedRangeNotSatisfiable]: "Requested Range Not Satisfiable", + [Status.ResetContent]: "Reset Content", + [Status.SeeOther]: "See Other", + [Status.ServiceUnavailable]: "Service Unavailable", + [Status.SwitchingProtocols]: "Switching Protocols", + [Status.Teapot]: "I'm a teapot", + [Status.TemporaryRedirect]: "Temporary Redirect", + [Status.TooEarly]: "Too Early", + [Status.TooManyRequests]: "Too Many Requests", + [Status.Unauthorized]: "Unauthorized", + [Status.UnavailableForLegalReasons]: "Unavailable For Legal Reasons", + [Status.UnprocessableEntity]: "Unprocessable Entity", + [Status.UnsupportedMediaType]: "Unsupported Media Type", + [Status.UpgradeRequired]: "Upgrade Required", + [Status.UseProxy]: "Use Proxy", + [Status.VariantAlsoNegotiates]: "Variant Also Negotiates", +}; + +/** An HTTP status that is a informational (1XX). */ +export type InformationalStatus = + | Status.Continue + | Status.SwitchingProtocols + | Status.Processing + | Status.EarlyHints; + +/** An HTTP status that is a success (2XX). */ +export type SuccessfulStatus = + | Status.OK + | Status.Created + | Status.Accepted + | Status.NonAuthoritativeInfo + | Status.NoContent + | Status.ResetContent + | Status.PartialContent + | Status.MultiStatus + | Status.AlreadyReported + | Status.IMUsed; + +/** An HTTP status that is a redirect (3XX). */ +export type RedirectStatus = + | Status.MultipleChoices // 300 + | Status.MovedPermanently // 301 + | Status.Found // 302 + | Status.SeeOther // 303 + | Status.UseProxy // 305 - DEPRECATED + | Status.TemporaryRedirect // 307 + | Status.PermanentRedirect; // 308 + +/** An HTTP status that is a client error (4XX). */ +export type ClientErrorStatus = + | Status.BadRequest + | Status.Unauthorized + | Status.PaymentRequired + | Status.Forbidden + | Status.NotFound + | Status.MethodNotAllowed + | Status.NotAcceptable + | Status.ProxyAuthRequired + | Status.RequestTimeout + | Status.Conflict + | Status.Gone + | Status.LengthRequired + | Status.PreconditionFailed + | Status.RequestEntityTooLarge + | Status.RequestURITooLong + | Status.UnsupportedMediaType + | Status.RequestedRangeNotSatisfiable + | Status.ExpectationFailed + | Status.Teapot + | Status.MisdirectedRequest + | Status.UnprocessableEntity + | Status.Locked + | Status.FailedDependency + | Status.UpgradeRequired + | Status.PreconditionRequired + | Status.TooManyRequests + | Status.RequestHeaderFieldsTooLarge + | Status.UnavailableForLegalReasons; + +/** An HTTP status that is a server error (5XX). */ +export type ServerErrorStatus = + | Status.InternalServerError + | Status.NotImplemented + | Status.BadGateway + | Status.ServiceUnavailable + | Status.GatewayTimeout + | Status.HTTPVersionNotSupported + | Status.VariantAlsoNegotiates + | Status.InsufficientStorage + | Status.LoopDetected + | Status.NotExtended + | Status.NetworkAuthenticationRequired; + +/** An HTTP status that is an error (4XX and 5XX). */ +export type ErrorStatus = ClientErrorStatus | ServerErrorStatus; + +/** A type guard that determines if the status code is informational. */ +export function isInformationalStatus( + status: Status, +): status is InformationalStatus { + return status >= 100 && status < 200; +} + +/** A type guard that determines if the status code is successful. */ +export function isSuccessfulStatus(status: Status): status is SuccessfulStatus { + return status >= 200 && status < 300; +} + +/** A type guard that determines if the status code is a redirection. */ +export function isRedirectStatus(status: Status): status is RedirectStatus { + return status >= 300 && status < 400; +} + +/** A type guard that determines if the status code is a client error. */ +export function isClientErrorStatus( + status: Status, +): status is ClientErrorStatus { + return status >= 400 && status < 500; +} + +/** A type guard that determines if the status code is a server error. */ +export function isServerErrorStatus( + status: Status, +): status is ServerErrorStatus { + return status >= 500 && status < 600; +} + +/** A type guard that determines if the status code is an error. */ +export function isErrorStatus(status: Status): status is ErrorStatus { + return status >= 400 && status < 600; +} diff --git a/vendor/deno.land/std@0.178.0/http/negotiation.ts b/vendor/deno.land/std@0.178.0/http/negotiation.ts new file mode 100644 index 0000000..1f89ac2 --- /dev/null +++ b/vendor/deno.land/std@0.178.0/http/negotiation.ts @@ -0,0 +1,186 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. + +/** + * Contains the functions {@linkcode accepts}, {@linkcode acceptsEncodings}, and + * {@linkcode acceptsLanguages} to provide content negotiation capabilities. + * + * @module + */ + +import { preferredEncodings } from "./_negotiation/encoding.ts"; +import { preferredLanguages } from "./_negotiation/language.ts"; +import { preferredMediaTypes } from "./_negotiation/media_type.ts"; + +export type Request = { + headers: { + get(key: string): string | null; + }; +}; + +/** + * Returns an array of media types accepted by the request, in order of + * preference. If there are no media types supplied in the request, then any + * media type selector will be returned. + * + * @example + * ```ts + * import { accepts } from "https://deno.land/std@$STD_VERSION/http/negotiation.ts"; + * + * const req = new Request("https://example.com/", { + * headers: { + * "accept": + * "text/html, application/xhtml+xml, application/xml;q=0.9, image/webp, *\/*;q=0.8", + * }, + * }); + * + * console.log(accepts(req)); + * // [ + * // "text/html", + * // "application/xhtml+xml", + * // "image/webp", + * // "application/xml", + * // "*\/*", + * // ] + * ``` + */ +export function accepts(request: Request): string[]; +/** + * For a given set of media types, return the best match accepted in the + * request. If no media type matches, then the function returns `undefined`. + * + * @example + * ```ts + * import { accepts } from "https://deno.land/std@$STD_VERSION/http/negotiation.ts"; + * + * const req = new Request("https://example.com/", { + * headers: { + * "accept": + * "text/html, application/xhtml+xml, application/xml;q=0.9, image/webp, *\/*;q=0.8", + * }, + * }); + * + * accepts(req, "text/html", "image/webp"); // "text/html"; + * ``` + */ +export function accepts( + request: Request, + ...types: string[] +): string | undefined; +export function accepts( + request: Request, + ...types: string[] +): string | string[] | undefined { + const accept = request.headers.get("accept"); + return types.length + ? accept ? preferredMediaTypes(accept, types)[0] : types[0] + : accept + ? preferredMediaTypes(accept) + : ["*/*"]; +} + +/** + * Returns an array of content encodings accepted by the request, in order of + * preference. If there are no encoding supplied in the request, then `["*"]` + * is returned, implying any encoding is accepted. + * + * @example + * ```ts + * import { acceptsEncodings } from "https://deno.land/std@$STD_VERSION/http/negotiation.ts"; + * + * const req = new Request("https://example.com/", { + * headers: { "accept-encoding": "deflate, gzip;q=1.0, *;q=0.5" }, + * }); + * + * acceptsEncodings(req); // ["deflate", "gzip", "*"] + * ``` + */ +export function acceptsEncodings(request: Request): string[]; +/** + * For a given set of content encodings, return the best match accepted in the + * request. If no content encodings match, then the function returns + * `undefined`. + * + * **NOTE:** You should always supply `identity` as one of the encodings + * to ensure that there is a match when the `Accept-Encoding` header is part + * of the request. + * + * @example + * ```ts + * import { acceptsEncodings } from "https://deno.land/std@$STD_VERSION/http/negotiation.ts"; + * + * const req = new Request("https://example.com/", { + * headers: { "accept-encoding": "deflate, gzip;q=1.0, *;q=0.5" }, + * }); + * + * acceptsEncodings(req, "gzip", "identity"); // "gzip" + * ``` + */ +export function acceptsEncodings( + request: Request, + ...encodings: string[] +): string | undefined; +export function acceptsEncodings( + request: Request, + ...encodings: string[] +): string | string[] | undefined { + const acceptEncoding = request.headers.get("accept-encoding"); + return encodings.length + ? acceptEncoding + ? preferredEncodings(acceptEncoding, encodings)[0] + : encodings[0] + : acceptEncoding + ? preferredEncodings(acceptEncoding) + : ["*"]; +} + +/** + * Returns an array of languages accepted by the request, in order of + * preference. If there are no languages supplied in the request, then `["*"]` + * is returned, imply any language is accepted. + * + * @example + * ```ts + * import { acceptsLanguages } from "https://deno.land/std@$STD_VERSION/http/negotiation.ts"; + * + * const req = new Request("https://example.com/", { + * headers: { + * "accept-language": "fr-CH, fr;q=0.9, en;q=0.8, de;q=0.7, *;q=0.5", + * }, + * }); + * + * acceptsLanguages(req); // ["fr-CH", "fr", "en", "de", "*"] + * ``` + */ +export function acceptsLanguages(request: Request): string[]; +/** + * For a given set of languages, return the best match accepted in the request. + * If no languages match, then the function returns `undefined`. + * + * @example + * ```ts + * import { acceptsLanguages } from "https://deno.land/std@$STD_VERSION/http/negotiation.ts"; + * + * const req = new Request("https://example.com/", { + * headers: { + * "accept-language": "fr-CH, fr;q=0.9, en;q=0.8, de;q=0.7, *;q=0.5", + * }, + * }); + * + * acceptsLanguages(req, "en-gb", "en-us", "en"); // "en" + * ``` + */ +export function acceptsLanguages( + request: Request, + ...langs: string[] +): string | undefined; +export function acceptsLanguages( + request: Request, + ...langs: string[] +): string | string[] | undefined { + const acceptLanguage = request.headers.get("accept-language"); + return langs.length + ? acceptLanguage ? preferredLanguages(acceptLanguage, langs)[0] : langs[0] + : acceptLanguage + ? preferredLanguages(acceptLanguage) + : ["*"]; +} diff --git a/vendor/deno.land/std@0.178.0/io/buf_reader.ts b/vendor/deno.land/std@0.178.0/io/buf_reader.ts new file mode 100644 index 0000000..9cbdc6d --- /dev/null +++ b/vendor/deno.land/std@0.178.0/io/buf_reader.ts @@ -0,0 +1,434 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. + +import { assert } from "../_util/asserts.ts"; +import { copy } from "../bytes/copy.ts"; +import type { Reader } from "../types.d.ts"; + +const DEFAULT_BUF_SIZE = 4096; +const MIN_BUF_SIZE = 16; +const MAX_CONSECUTIVE_EMPTY_READS = 100; +const CR = "\r".charCodeAt(0); +const LF = "\n".charCodeAt(0); + +export class BufferFullError extends Error { + override name = "BufferFullError"; + constructor(public partial: Uint8Array) { + super("Buffer full"); + } +} + +export class PartialReadError extends Error { + override name = "PartialReadError"; + partial?: Uint8Array; + constructor() { + super("Encountered UnexpectedEof, data only partially read"); + } +} + +/** Result type returned by of BufReader.readLine(). */ +export interface ReadLineResult { + line: Uint8Array; + more: boolean; +} + +export class BufReader implements Reader { + #buf!: Uint8Array; + #rd!: Reader; // Reader provided by caller. + #r = 0; // buf read position. + #w = 0; // buf write position. + #eof = false; + // private lastByte: number; + // private lastCharSize: number; + + /** return new BufReader unless r is BufReader */ + static create(r: Reader, size: number = DEFAULT_BUF_SIZE): BufReader { + return r instanceof BufReader ? r : new BufReader(r, size); + } + + constructor(rd: Reader, size: number = DEFAULT_BUF_SIZE) { + if (size < MIN_BUF_SIZE) { + size = MIN_BUF_SIZE; + } + this.#reset(new Uint8Array(size), rd); + } + + /** Returns the size of the underlying buffer in bytes. */ + size(): number { + return this.#buf.byteLength; + } + + buffered(): number { + return this.#w - this.#r; + } + + // Reads a new chunk into the buffer. + #fill = async () => { + // Slide existing data to beginning. + if (this.#r > 0) { + this.#buf.copyWithin(0, this.#r, this.#w); + this.#w -= this.#r; + this.#r = 0; + } + + if (this.#w >= this.#buf.byteLength) { + throw Error("bufio: tried to fill full buffer"); + } + + // Read new data: try a limited number of times. + for (let i = MAX_CONSECUTIVE_EMPTY_READS; i > 0; i--) { + const rr = await this.#rd.read(this.#buf.subarray(this.#w)); + if (rr === null) { + this.#eof = true; + return; + } + assert(rr >= 0, "negative read"); + this.#w += rr; + if (rr > 0) { + return; + } + } + + throw new Error( + `No progress after ${MAX_CONSECUTIVE_EMPTY_READS} read() calls`, + ); + }; + + /** Discards any buffered data, resets all state, and switches + * the buffered reader to read from r. + */ + reset(r: Reader) { + this.#reset(this.#buf, r); + } + + #reset = (buf: Uint8Array, rd: Reader) => { + this.#buf = buf; + this.#rd = rd; + this.#eof = false; + // this.lastByte = -1; + // this.lastCharSize = -1; + }; + + /** reads data into p. + * It returns the number of bytes read into p. + * The bytes are taken from at most one Read on the underlying Reader, + * hence n may be less than len(p). + * To read exactly len(p) bytes, use io.ReadFull(b, p). + */ + async read(p: Uint8Array): Promise { + let rr: number | null = p.byteLength; + if (p.byteLength === 0) return rr; + + if (this.#r === this.#w) { + if (p.byteLength >= this.#buf.byteLength) { + // Large read, empty buffer. + // Read directly into p to avoid copy. + const rr = await this.#rd.read(p); + const nread = rr ?? 0; + assert(nread >= 0, "negative read"); + // if (rr.nread > 0) { + // this.lastByte = p[rr.nread - 1]; + // this.lastCharSize = -1; + // } + return rr; + } + + // One read. + // Do not use this.fill, which will loop. + this.#r = 0; + this.#w = 0; + rr = await this.#rd.read(this.#buf); + if (rr === 0 || rr === null) return rr; + assert(rr >= 0, "negative read"); + this.#w += rr; + } + + // copy as much as we can + const copied = copy(this.#buf.subarray(this.#r, this.#w), p, 0); + this.#r += copied; + // this.lastByte = this.buf[this.r - 1]; + // this.lastCharSize = -1; + return copied; + } + + /** reads exactly `p.length` bytes into `p`. + * + * If successful, `p` is returned. + * + * If the end of the underlying stream has been reached, and there are no more + * bytes available in the buffer, `readFull()` returns `null` instead. + * + * An error is thrown if some bytes could be read, but not enough to fill `p` + * entirely before the underlying stream reported an error or EOF. Any error + * thrown will have a `partial` property that indicates the slice of the + * buffer that has been successfully filled with data. + * + * Ported from https://golang.org/pkg/io/#ReadFull + */ + async readFull(p: Uint8Array): Promise { + let bytesRead = 0; + while (bytesRead < p.length) { + try { + const rr = await this.read(p.subarray(bytesRead)); + if (rr === null) { + if (bytesRead === 0) { + return null; + } else { + throw new PartialReadError(); + } + } + bytesRead += rr; + } catch (err) { + if (err instanceof PartialReadError) { + err.partial = p.subarray(0, bytesRead); + } else if (err instanceof Error) { + const e = new PartialReadError(); + e.partial = p.subarray(0, bytesRead); + e.stack = err.stack; + e.message = err.message; + e.cause = err.cause; + throw err; + } + throw err; + } + } + return p; + } + + /** Returns the next byte [0, 255] or `null`. */ + async readByte(): Promise { + while (this.#r === this.#w) { + if (this.#eof) return null; + await this.#fill(); // buffer is empty. + } + const c = this.#buf[this.#r]; + this.#r++; + // this.lastByte = c; + return c; + } + + /** readString() reads until the first occurrence of delim in the input, + * returning a string containing the data up to and including the delimiter. + * If ReadString encounters an error before finding a delimiter, + * it returns the data read before the error and the error itself + * (often `null`). + * ReadString returns err != nil if and only if the returned data does not end + * in delim. + * For simple uses, a Scanner may be more convenient. + */ + async readString(delim: string): Promise { + if (delim.length !== 1) { + throw new Error("Delimiter should be a single character"); + } + const buffer = await this.readSlice(delim.charCodeAt(0)); + if (buffer === null) return null; + return new TextDecoder().decode(buffer); + } + + /** `readLine()` is a low-level line-reading primitive. Most callers should + * use `readString('\n')` instead or use a Scanner. + * + * `readLine()` tries to return a single line, not including the end-of-line + * bytes. If the line was too long for the buffer then `more` is set and the + * beginning of the line is returned. The rest of the line will be returned + * from future calls. `more` will be false when returning the last fragment + * of the line. The returned buffer is only valid until the next call to + * `readLine()`. + * + * The text returned from ReadLine does not include the line end ("\r\n" or + * "\n"). + * + * When the end of the underlying stream is reached, the final bytes in the + * stream are returned. No indication or error is given if the input ends + * without a final line end. When there are no more trailing bytes to read, + * `readLine()` returns `null`. + * + * Calling `unreadByte()` after `readLine()` will always unread the last byte + * read (possibly a character belonging to the line end) even if that byte is + * not part of the line returned by `readLine()`. + */ + async readLine(): Promise { + let line: Uint8Array | null = null; + + try { + line = await this.readSlice(LF); + } catch (err) { + let partial; + if (err instanceof PartialReadError) { + partial = err.partial; + assert( + partial instanceof Uint8Array, + "bufio: caught error from `readSlice()` without `partial` property", + ); + } + + // Don't throw if `readSlice()` failed with `BufferFullError`, instead we + // just return whatever is available and set the `more` flag. + if (!(err instanceof BufferFullError)) { + throw err; + } + + partial = err.partial; + + // Handle the case where "\r\n" straddles the buffer. + if ( + !this.#eof && partial && + partial.byteLength > 0 && + partial[partial.byteLength - 1] === CR + ) { + // Put the '\r' back on buf and drop it from line. + // Let the next call to ReadLine check for "\r\n". + assert(this.#r > 0, "bufio: tried to rewind past start of buffer"); + this.#r--; + partial = partial.subarray(0, partial.byteLength - 1); + } + + if (partial) { + return { line: partial, more: !this.#eof }; + } + } + + if (line === null) { + return null; + } + + if (line.byteLength === 0) { + return { line, more: false }; + } + + if (line[line.byteLength - 1] == LF) { + let drop = 1; + if (line.byteLength > 1 && line[line.byteLength - 2] === CR) { + drop = 2; + } + line = line.subarray(0, line.byteLength - drop); + } + return { line, more: false }; + } + + /** `readSlice()` reads until the first occurrence of `delim` in the input, + * returning a slice pointing at the bytes in the buffer. The bytes stop + * being valid at the next read. + * + * If `readSlice()` encounters an error before finding a delimiter, or the + * buffer fills without finding a delimiter, it throws an error with a + * `partial` property that contains the entire buffer. + * + * If `readSlice()` encounters the end of the underlying stream and there are + * any bytes left in the buffer, the rest of the buffer is returned. In other + * words, EOF is always treated as a delimiter. Once the buffer is empty, + * it returns `null`. + * + * Because the data returned from `readSlice()` will be overwritten by the + * next I/O operation, most clients should use `readString()` instead. + */ + async readSlice(delim: number): Promise { + let s = 0; // search start index + let slice: Uint8Array | undefined; + + while (true) { + // Search buffer. + let i = this.#buf.subarray(this.#r + s, this.#w).indexOf(delim); + if (i >= 0) { + i += s; + slice = this.#buf.subarray(this.#r, this.#r + i + 1); + this.#r += i + 1; + break; + } + + // EOF? + if (this.#eof) { + if (this.#r === this.#w) { + return null; + } + slice = this.#buf.subarray(this.#r, this.#w); + this.#r = this.#w; + break; + } + + // Buffer full? + if (this.buffered() >= this.#buf.byteLength) { + this.#r = this.#w; + // #4521 The internal buffer should not be reused across reads because it causes corruption of data. + const oldbuf = this.#buf; + const newbuf = this.#buf.slice(0); + this.#buf = newbuf; + throw new BufferFullError(oldbuf); + } + + s = this.#w - this.#r; // do not rescan area we scanned before + + // Buffer is not full. + try { + await this.#fill(); + } catch (err) { + if (err instanceof PartialReadError) { + err.partial = slice; + } else if (err instanceof Error) { + const e = new PartialReadError(); + e.partial = slice; + e.stack = err.stack; + e.message = err.message; + e.cause = err.cause; + throw err; + } + throw err; + } + } + + // Handle last byte, if any. + // const i = slice.byteLength - 1; + // if (i >= 0) { + // this.lastByte = slice[i]; + // this.lastCharSize = -1 + // } + + return slice; + } + + /** `peek()` returns the next `n` bytes without advancing the reader. The + * bytes stop being valid at the next read call. + * + * When the end of the underlying stream is reached, but there are unread + * bytes left in the buffer, those bytes are returned. If there are no bytes + * left in the buffer, it returns `null`. + * + * If an error is encountered before `n` bytes are available, `peek()` throws + * an error with the `partial` property set to a slice of the buffer that + * contains the bytes that were available before the error occurred. + */ + async peek(n: number): Promise { + if (n < 0) { + throw Error("negative count"); + } + + let avail = this.#w - this.#r; + while (avail < n && avail < this.#buf.byteLength && !this.#eof) { + try { + await this.#fill(); + } catch (err) { + if (err instanceof PartialReadError) { + err.partial = this.#buf.subarray(this.#r, this.#w); + } else if (err instanceof Error) { + const e = new PartialReadError(); + e.partial = this.#buf.subarray(this.#r, this.#w); + e.stack = err.stack; + e.message = err.message; + e.cause = err.cause; + throw err; + } + throw err; + } + avail = this.#w - this.#r; + } + + if (avail === 0 && this.#eof) { + return null; + } else if (avail < n && this.#eof) { + return this.#buf.subarray(this.#r, this.#r + avail); + } else if (avail < n) { + throw new BufferFullError(this.#buf.subarray(this.#r, this.#w)); + } + + return this.#buf.subarray(this.#r, this.#r + n); + } +} diff --git a/vendor/deno.land/std@0.178.0/io/buf_writer.ts b/vendor/deno.land/std@0.178.0/io/buf_writer.ts new file mode 100644 index 0000000..8682198 --- /dev/null +++ b/vendor/deno.land/std@0.178.0/io/buf_writer.ts @@ -0,0 +1,221 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. + +import { copy } from "../bytes/copy.ts"; +import type { Writer, WriterSync } from "../types.d.ts"; + +const DEFAULT_BUF_SIZE = 4096; + +abstract class AbstractBufBase { + buf: Uint8Array; + usedBufferBytes = 0; + err: Error | null = null; + + constructor(buf: Uint8Array) { + this.buf = buf; + } + + /** Size returns the size of the underlying buffer in bytes. */ + size(): number { + return this.buf.byteLength; + } + + /** Returns how many bytes are unused in the buffer. */ + available(): number { + return this.buf.byteLength - this.usedBufferBytes; + } + + /** buffered returns the number of bytes that have been written into the + * current buffer. + */ + buffered(): number { + return this.usedBufferBytes; + } +} + +/** BufWriter implements buffering for an deno.Writer object. + * If an error occurs writing to a Writer, no more data will be + * accepted and all subsequent writes, and flush(), will return the error. + * After all data has been written, the client should call the + * flush() method to guarantee all data has been forwarded to + * the underlying deno.Writer. + */ +export class BufWriter extends AbstractBufBase implements Writer { + #writer: Writer; + + /** return new BufWriter unless writer is BufWriter */ + static create(writer: Writer, size: number = DEFAULT_BUF_SIZE): BufWriter { + return writer instanceof BufWriter ? writer : new BufWriter(writer, size); + } + + constructor(writer: Writer, size: number = DEFAULT_BUF_SIZE) { + super(new Uint8Array(size <= 0 ? DEFAULT_BUF_SIZE : size)); + this.#writer = writer; + } + + /** Discards any unflushed buffered data, clears any error, and + * resets buffer to write its output to w. + */ + reset(w: Writer) { + this.err = null; + this.usedBufferBytes = 0; + this.#writer = w; + } + + /** Flush writes any buffered data to the underlying io.Writer. */ + async flush() { + if (this.err !== null) throw this.err; + if (this.usedBufferBytes === 0) return; + + try { + const p = this.buf.subarray(0, this.usedBufferBytes); + let nwritten = 0; + while (nwritten < p.length) { + nwritten += await this.#writer.write(p.subarray(nwritten)); + } + } catch (e) { + if (e instanceof Error) { + this.err = e; + } + throw e; + } + + this.buf = new Uint8Array(this.buf.length); + this.usedBufferBytes = 0; + } + + /** Writes the contents of `data` into the buffer. If the contents won't fully + * fit into the buffer, those bytes that are copied into the buffer will be flushed + * to the writer and the remaining bytes are then copied into the now empty buffer. + * + * @return the number of bytes written to the buffer. + */ + async write(data: Uint8Array): Promise { + if (this.err !== null) throw this.err; + if (data.length === 0) return 0; + + let totalBytesWritten = 0; + let numBytesWritten = 0; + while (data.byteLength > this.available()) { + if (this.buffered() === 0) { + // Large write, empty buffer. + // Write directly from data to avoid copy. + try { + numBytesWritten = await this.#writer.write(data); + } catch (e) { + if (e instanceof Error) { + this.err = e; + } + throw e; + } + } else { + numBytesWritten = copy(data, this.buf, this.usedBufferBytes); + this.usedBufferBytes += numBytesWritten; + await this.flush(); + } + totalBytesWritten += numBytesWritten; + data = data.subarray(numBytesWritten); + } + + numBytesWritten = copy(data, this.buf, this.usedBufferBytes); + this.usedBufferBytes += numBytesWritten; + totalBytesWritten += numBytesWritten; + return totalBytesWritten; + } +} + +/** BufWriterSync implements buffering for a deno.WriterSync object. + * If an error occurs writing to a WriterSync, no more data will be + * accepted and all subsequent writes, and flush(), will return the error. + * After all data has been written, the client should call the + * flush() method to guarantee all data has been forwarded to + * the underlying deno.WriterSync. + */ +export class BufWriterSync extends AbstractBufBase implements WriterSync { + #writer: WriterSync; + + /** return new BufWriterSync unless writer is BufWriterSync */ + static create( + writer: WriterSync, + size: number = DEFAULT_BUF_SIZE, + ): BufWriterSync { + return writer instanceof BufWriterSync + ? writer + : new BufWriterSync(writer, size); + } + + constructor(writer: WriterSync, size: number = DEFAULT_BUF_SIZE) { + super(new Uint8Array(size <= 0 ? DEFAULT_BUF_SIZE : size)); + this.#writer = writer; + } + + /** Discards any unflushed buffered data, clears any error, and + * resets buffer to write its output to w. + */ + reset(w: WriterSync) { + this.err = null; + this.usedBufferBytes = 0; + this.#writer = w; + } + + /** Flush writes any buffered data to the underlying io.WriterSync. */ + flush() { + if (this.err !== null) throw this.err; + if (this.usedBufferBytes === 0) return; + + try { + const p = this.buf.subarray(0, this.usedBufferBytes); + let nwritten = 0; + while (nwritten < p.length) { + nwritten += this.#writer.writeSync(p.subarray(nwritten)); + } + } catch (e) { + if (e instanceof Error) { + this.err = e; + } + throw e; + } + + this.buf = new Uint8Array(this.buf.length); + this.usedBufferBytes = 0; + } + + /** Writes the contents of `data` into the buffer. If the contents won't fully + * fit into the buffer, those bytes that can are copied into the buffer, the + * buffer is the flushed to the writer and the remaining bytes are copied into + * the now empty buffer. + * + * @return the number of bytes written to the buffer. + */ + writeSync(data: Uint8Array): number { + if (this.err !== null) throw this.err; + if (data.length === 0) return 0; + + let totalBytesWritten = 0; + let numBytesWritten = 0; + while (data.byteLength > this.available()) { + if (this.buffered() === 0) { + // Large write, empty buffer. + // Write directly from data to avoid copy. + try { + numBytesWritten = this.#writer.writeSync(data); + } catch (e) { + if (e instanceof Error) { + this.err = e; + } + throw e; + } + } else { + numBytesWritten = copy(data, this.buf, this.usedBufferBytes); + this.usedBufferBytes += numBytesWritten; + this.flush(); + } + totalBytesWritten += numBytesWritten; + data = data.subarray(numBytesWritten); + } + + numBytesWritten = copy(data, this.buf, this.usedBufferBytes); + this.usedBufferBytes += numBytesWritten; + totalBytesWritten += numBytesWritten; + return totalBytesWritten; + } +} diff --git a/vendor/deno.land/std@0.178.0/io/buffer.ts b/vendor/deno.land/std@0.178.0/io/buffer.ts new file mode 100644 index 0000000..35f4f72 --- /dev/null +++ b/vendor/deno.land/std@0.178.0/io/buffer.ts @@ -0,0 +1,247 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. +import { assert } from "../_util/asserts.ts"; +import { copy } from "../bytes/copy.ts"; +import type { Reader, ReaderSync } from "../types.d.ts"; + +// MIN_READ is the minimum ArrayBuffer size passed to a read call by +// buffer.ReadFrom. As long as the Buffer has at least MIN_READ bytes beyond +// what is required to hold the contents of r, readFrom() will not grow the +// underlying buffer. +const MIN_READ = 32 * 1024; +const MAX_SIZE = 2 ** 32 - 2; + +/** A variable-sized buffer of bytes with `read()` and `write()` methods. + * + * Buffer is almost always used with some I/O like files and sockets. It allows + * one to buffer up a download from a socket. Buffer grows and shrinks as + * necessary. + * + * Buffer is NOT the same thing as Node's Buffer. Node's Buffer was created in + * 2009 before JavaScript had the concept of ArrayBuffers. It's simply a + * non-standard ArrayBuffer. + * + * ArrayBuffer is a fixed memory allocation. Buffer is implemented on top of + * ArrayBuffer. + * + * Based on [Go Buffer](https://golang.org/pkg/bytes/#Buffer). */ + +export class Buffer { + #buf: Uint8Array; // contents are the bytes buf[off : len(buf)] + #off = 0; // read at buf[off], write at buf[buf.byteLength] + + constructor(ab?: ArrayBufferLike | ArrayLike) { + this.#buf = ab === undefined ? new Uint8Array(0) : new Uint8Array(ab); + } + + /** Returns a slice holding the unread portion of the buffer. + * + * The slice is valid for use only until the next buffer modification (that + * is, only until the next call to a method like `read()`, `write()`, + * `reset()`, or `truncate()`). If `options.copy` is false the slice aliases the buffer content at + * least until the next buffer modification, so immediate changes to the + * slice will affect the result of future reads. + * @param [options={ copy: true }] + */ + bytes(options = { copy: true }): Uint8Array { + if (options.copy === false) return this.#buf.subarray(this.#off); + return this.#buf.slice(this.#off); + } + + /** Returns whether the unread portion of the buffer is empty. */ + empty(): boolean { + return this.#buf.byteLength <= this.#off; + } + + /** A read only number of bytes of the unread portion of the buffer. */ + get length(): number { + return this.#buf.byteLength - this.#off; + } + + /** The read only capacity of the buffer's underlying byte slice, that is, + * the total space allocated for the buffer's data. */ + get capacity(): number { + return this.#buf.buffer.byteLength; + } + + /** Discards all but the first `n` unread bytes from the buffer but + * continues to use the same allocated storage. It throws if `n` is + * negative or greater than the length of the buffer. */ + truncate(n: number) { + if (n === 0) { + this.reset(); + return; + } + if (n < 0 || n > this.length) { + throw Error("bytes.Buffer: truncation out of range"); + } + this.#reslice(this.#off + n); + } + + reset() { + this.#reslice(0); + this.#off = 0; + } + + #tryGrowByReslice(n: number) { + const l = this.#buf.byteLength; + if (n <= this.capacity - l) { + this.#reslice(l + n); + return l; + } + return -1; + } + + #reslice(len: number) { + assert(len <= this.#buf.buffer.byteLength); + this.#buf = new Uint8Array(this.#buf.buffer, 0, len); + } + + /** Reads the next `p.length` bytes from the buffer or until the buffer is + * drained. Returns the number of bytes read. If the buffer has no data to + * return, the return is EOF (`null`). */ + readSync(p: Uint8Array): number | null { + if (this.empty()) { + // Buffer is empty, reset to recover space. + this.reset(); + if (p.byteLength === 0) { + // this edge case is tested in 'bufferReadEmptyAtEOF' test + return 0; + } + return null; + } + const nread = copy(this.#buf.subarray(this.#off), p); + this.#off += nread; + return nread; + } + + /** Reads the next `p.length` bytes from the buffer or until the buffer is + * drained. Resolves to the number of bytes read. If the buffer has no + * data to return, resolves to EOF (`null`). + * + * NOTE: This methods reads bytes synchronously; it's provided for + * compatibility with `Reader` interfaces. + */ + read(p: Uint8Array): Promise { + const rr = this.readSync(p); + return Promise.resolve(rr); + } + + writeSync(p: Uint8Array): number { + const m = this.#grow(p.byteLength); + return copy(p, this.#buf, m); + } + + /** NOTE: This methods writes bytes synchronously; it's provided for + * compatibility with `Writer` interface. */ + write(p: Uint8Array): Promise { + const n = this.writeSync(p); + return Promise.resolve(n); + } + + #grow(n: number) { + const m = this.length; + // If buffer is empty, reset to recover space. + if (m === 0 && this.#off !== 0) { + this.reset(); + } + // Fast: Try to grow by means of a reslice. + const i = this.#tryGrowByReslice(n); + if (i >= 0) { + return i; + } + const c = this.capacity; + if (n <= Math.floor(c / 2) - m) { + // We can slide things down instead of allocating a new + // ArrayBuffer. We only need m+n <= c to slide, but + // we instead let capacity get twice as large so we + // don't spend all our time copying. + copy(this.#buf.subarray(this.#off), this.#buf); + } else if (c + n > MAX_SIZE) { + throw new Error("The buffer cannot be grown beyond the maximum size."); + } else { + // Not enough space anywhere, we need to allocate. + const buf = new Uint8Array(Math.min(2 * c + n, MAX_SIZE)); + copy(this.#buf.subarray(this.#off), buf); + this.#buf = buf; + } + // Restore this.#off and len(this.#buf). + this.#off = 0; + this.#reslice(Math.min(m + n, MAX_SIZE)); + return m; + } + + /** Grows the buffer's capacity, if necessary, to guarantee space for + * another `n` bytes. After `.grow(n)`, at least `n` bytes can be written to + * the buffer without another allocation. If `n` is negative, `.grow()` will + * throw. If the buffer can't grow it will throw an error. + * + * Based on Go Lang's + * [Buffer.Grow](https://golang.org/pkg/bytes/#Buffer.Grow). */ + grow(n: number) { + if (n < 0) { + throw Error("Buffer.grow: negative count"); + } + const m = this.#grow(n); + this.#reslice(m); + } + + /** Reads data from `r` until EOF (`null`) and appends it to the buffer, + * growing the buffer as needed. It resolves to the number of bytes read. + * If the buffer becomes too large, `.readFrom()` will reject with an error. + * + * Based on Go Lang's + * [Buffer.ReadFrom](https://golang.org/pkg/bytes/#Buffer.ReadFrom). */ + async readFrom(r: Reader): Promise { + let n = 0; + const tmp = new Uint8Array(MIN_READ); + while (true) { + const shouldGrow = this.capacity - this.length < MIN_READ; + // read into tmp buffer if there's not enough room + // otherwise read directly into the internal buffer + const buf = shouldGrow + ? tmp + : new Uint8Array(this.#buf.buffer, this.length); + + const nread = await r.read(buf); + if (nread === null) { + return n; + } + + // write will grow if needed + if (shouldGrow) this.writeSync(buf.subarray(0, nread)); + else this.#reslice(this.length + nread); + + n += nread; + } + } + + /** Reads data from `r` until EOF (`null`) and appends it to the buffer, + * growing the buffer as needed. It returns the number of bytes read. If the + * buffer becomes too large, `.readFromSync()` will throw an error. + * + * Based on Go Lang's + * [Buffer.ReadFrom](https://golang.org/pkg/bytes/#Buffer.ReadFrom). */ + readFromSync(r: ReaderSync): number { + let n = 0; + const tmp = new Uint8Array(MIN_READ); + while (true) { + const shouldGrow = this.capacity - this.length < MIN_READ; + // read into tmp buffer if there's not enough room + // otherwise read directly into the internal buffer + const buf = shouldGrow + ? tmp + : new Uint8Array(this.#buf.buffer, this.length); + + const nread = r.readSync(buf); + if (nread === null) { + return n; + } + + // write will grow if needed + if (shouldGrow) this.writeSync(buf.subarray(0, nread)); + else this.#reslice(this.length + nread); + + n += nread; + } + } +} diff --git a/vendor/deno.land/std@0.178.0/io/copy_n.ts b/vendor/deno.land/std@0.178.0/io/copy_n.ts new file mode 100644 index 0000000..a25d419 --- /dev/null +++ b/vendor/deno.land/std@0.178.0/io/copy_n.ts @@ -0,0 +1,40 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. + +import { assert } from "../_util/asserts.ts"; +import type { Reader, Writer } from "../types.d.ts"; + +const DEFAULT_BUFFER_SIZE = 32 * 1024; + +/** + * Copy N size at the most. If read size is lesser than N, then returns nread + * @param r Reader + * @param dest Writer + * @param size Read size + */ +export async function copyN( + r: Reader, + dest: Writer, + size: number, +): Promise { + let bytesRead = 0; + let buf = new Uint8Array(DEFAULT_BUFFER_SIZE); + while (bytesRead < size) { + if (size - bytesRead < DEFAULT_BUFFER_SIZE) { + buf = new Uint8Array(size - bytesRead); + } + const result = await r.read(buf); + const nread = result ?? 0; + bytesRead += nread; + if (nread > 0) { + let n = 0; + while (n < nread) { + n += await dest.write(buf.slice(n, nread)); + } + assert(n === nread, "could not write"); + } + if (result === null) { + break; + } + } + return bytesRead; +} diff --git a/vendor/deno.land/std@0.178.0/io/limited_reader.ts b/vendor/deno.land/std@0.178.0/io/limited_reader.ts new file mode 100644 index 0000000..355e7e5 --- /dev/null +++ b/vendor/deno.land/std@0.178.0/io/limited_reader.ts @@ -0,0 +1,29 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. +/** + * A `LimitedReader` reads from `reader` but limits the amount of data returned to just `limit` bytes. + * Each call to `read` updates `limit` to reflect the new amount remaining. + * `read` returns `null` when `limit` <= `0` or + * when the underlying `reader` returns `null`. + */ +import type { Reader } from "../types.d.ts"; + +export class LimitedReader implements Reader { + constructor(public reader: Reader, public limit: number) {} + + async read(p: Uint8Array): Promise { + if (this.limit <= 0) { + return null; + } + + if (p.length > this.limit) { + p = p.subarray(0, this.limit); + } + const n = await this.reader.read(p); + if (n == null) { + return null; + } + + this.limit -= n; + return n; + } +} diff --git a/vendor/deno.land/std@0.178.0/io/mod.ts b/vendor/deno.land/std@0.178.0/io/mod.ts new file mode 100644 index 0000000..8efa9a3 --- /dev/null +++ b/vendor/deno.land/std@0.178.0/io/mod.ts @@ -0,0 +1,27 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. + +/** + * Utilities for working with Deno's readers, writers, and web streams. + * + * `Reader` and `Writer` interfaces are deprecated in Deno, and so many of these + * utilities are also deprecated. Consider using web streams instead. + * + * @module + */ + +export * from "./buf_reader.ts"; +export * from "./buf_writer.ts"; +export * from "./buffer.ts"; +export * from "./copy_n.ts"; +export * from "./limited_reader.ts"; +export * from "./multi_reader.ts"; +export * from "./read_delim.ts"; +export * from "./read_int.ts"; +export * from "./read_lines.ts"; +export * from "./read_long.ts"; +export * from "./read_range.ts"; +export * from "./read_short.ts"; +export * from "./read_string_delim.ts"; +export * from "./slice_long_to_bytes.ts"; +export * from "./string_reader.ts"; +export * from "./string_writer.ts"; diff --git a/vendor/deno.land/std@0.178.0/io/multi_reader.ts b/vendor/deno.land/std@0.178.0/io/multi_reader.ts new file mode 100644 index 0000000..65a02c8 --- /dev/null +++ b/vendor/deno.land/std@0.178.0/io/multi_reader.ts @@ -0,0 +1,24 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. + +import type { Reader } from "../types.d.ts"; + +/** Reader utility for combining multiple readers */ +export class MultiReader implements Reader { + readonly #readers: Reader[]; + #currentIndex = 0; + + constructor(readers: Reader[]) { + this.#readers = [...readers]; + } + + async read(p: Uint8Array): Promise { + const r = this.#readers[this.#currentIndex]; + if (!r) return null; + const result = await r.read(p); + if (result === null) { + this.#currentIndex++; + return 0; + } + return result; + } +} diff --git a/vendor/deno.land/std@0.178.0/io/read_delim.ts b/vendor/deno.land/std@0.178.0/io/read_delim.ts new file mode 100644 index 0000000..a749865 --- /dev/null +++ b/vendor/deno.land/std@0.178.0/io/read_delim.ts @@ -0,0 +1,79 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. + +import { BytesList } from "../bytes/bytes_list.ts"; +import type { Reader } from "../types.d.ts"; + +/** Generate longest proper prefix which is also suffix array. */ +function createLPS(pat: Uint8Array): Uint8Array { + const lps = new Uint8Array(pat.length); + lps[0] = 0; + let prefixEnd = 0; + let i = 1; + while (i < lps.length) { + if (pat[i] == pat[prefixEnd]) { + prefixEnd++; + lps[i] = prefixEnd; + i++; + } else if (prefixEnd === 0) { + lps[i] = 0; + i++; + } else { + prefixEnd = lps[prefixEnd - 1]; + } + } + return lps; +} + +/** Read delimited bytes from a Reader. */ +export async function* readDelim( + reader: Reader, + delim: Uint8Array, +): AsyncIterableIterator { + // Avoid unicode problems + const delimLen = delim.length; + const delimLPS = createLPS(delim); + const chunks = new BytesList(); + const bufSize = Math.max(1024, delimLen + 1); + + // Modified KMP + let inspectIndex = 0; + let matchIndex = 0; + while (true) { + const inspectArr = new Uint8Array(bufSize); + const result = await reader.read(inspectArr); + if (result === null) { + // Yield last chunk. + yield chunks.concat(); + return; + } else if (result < 0) { + // Discard all remaining and silently fail. + return; + } + chunks.add(inspectArr, 0, result); + let localIndex = 0; + while (inspectIndex < chunks.size()) { + if (inspectArr[localIndex] === delim[matchIndex]) { + inspectIndex++; + localIndex++; + matchIndex++; + if (matchIndex === delimLen) { + // Full match + const matchEnd = inspectIndex - delimLen; + const readyBytes = chunks.slice(0, matchEnd); + yield readyBytes; + // Reset match, different from KMP. + chunks.shift(inspectIndex); + inspectIndex = 0; + matchIndex = 0; + } + } else { + if (matchIndex === 0) { + inspectIndex++; + localIndex++; + } else { + matchIndex = delimLPS[matchIndex - 1]; + } + } + } + } +} diff --git a/vendor/deno.land/std@0.178.0/io/read_int.ts b/vendor/deno.land/std@0.178.0/io/read_int.ts new file mode 100644 index 0000000..ce580de --- /dev/null +++ b/vendor/deno.land/std@0.178.0/io/read_int.ts @@ -0,0 +1,16 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. + +import { type BufReader } from "./buf_reader.ts"; +import { readShort } from "./read_short.ts"; + +/** + * Read big endian 32bit integer from BufReader + * @param buf + */ +export async function readInt(buf: BufReader): Promise { + const high = await readShort(buf); + if (high === null) return null; + const low = await readShort(buf); + if (low === null) throw new Deno.errors.UnexpectedEof(); + return (high << 16) | low; +} diff --git a/vendor/deno.land/std@0.178.0/io/read_lines.ts b/vendor/deno.land/std@0.178.0/io/read_lines.ts new file mode 100644 index 0000000..1ee25d5 --- /dev/null +++ b/vendor/deno.land/std@0.178.0/io/read_lines.ts @@ -0,0 +1,48 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. + +import { type Reader } from "../types.d.ts"; +import { BufReader } from "./buf_reader.ts"; +import { concat } from "../bytes/concat.ts"; + +/** + * Read strings line-by-line from a Reader. + * + * @example + * ```ts + * import { readLines } from "https://deno.land/std@$STD_VERSION/io/read_lines.ts"; + * import * as path from "https://deno.land/std@$STD_VERSION/path/mod.ts"; + * + * const filename = path.join(Deno.cwd(), "std/io/README.md"); + * let fileReader = await Deno.open(filename); + * + * for await (let line of readLines(fileReader)) { + * console.log(line); + * } + * ``` + */ +export async function* readLines( + reader: Reader, + decoderOpts?: { + encoding?: string; + fatal?: boolean; + ignoreBOM?: boolean; + }, +): AsyncIterableIterator { + const bufReader = new BufReader(reader); + let chunks: Uint8Array[] = []; + const decoder = new TextDecoder(decoderOpts?.encoding, decoderOpts); + while (true) { + const res = await bufReader.readLine(); + if (!res) { + if (chunks.length > 0) { + yield decoder.decode(concat(...chunks)); + } + break; + } + chunks.push(res.line); + if (!res.more) { + yield decoder.decode(concat(...chunks)); + chunks = []; + } + } +} diff --git a/vendor/deno.land/std@0.178.0/io/read_long.ts b/vendor/deno.land/std@0.178.0/io/read_long.ts new file mode 100644 index 0000000..acc5a0a --- /dev/null +++ b/vendor/deno.land/std@0.178.0/io/read_long.ts @@ -0,0 +1,25 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. + +import { type BufReader } from "./buf_reader.ts"; +import { readInt } from "./read_int.ts"; + +const MAX_SAFE_INTEGER = BigInt(Number.MAX_SAFE_INTEGER); + +/** + * Read big endian 64bit long from BufReader + * @param buf + */ +export async function readLong(buf: BufReader): Promise { + const high = await readInt(buf); + if (high === null) return null; + const low = await readInt(buf); + if (low === null) throw new Deno.errors.UnexpectedEof(); + const big = (BigInt(high) << 32n) | BigInt(low); + // We probably should provide a similar API that returns BigInt values. + if (big > MAX_SAFE_INTEGER) { + throw new RangeError( + "Long value too big to be represented as a JavaScript number.", + ); + } + return Number(big); +} diff --git a/vendor/deno.land/std@0.178.0/io/read_range.ts b/vendor/deno.land/std@0.178.0/io/read_range.ts new file mode 100644 index 0000000..41a45d9 --- /dev/null +++ b/vendor/deno.land/std@0.178.0/io/read_range.ts @@ -0,0 +1,91 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. + +import { copy as copyBytes } from "../bytes/copy.ts"; +import { assert } from "../_util/asserts.ts"; +import type { Reader, ReaderSync } from "../types.d.ts"; + +const DEFAULT_BUFFER_SIZE = 32 * 1024; + +export interface ByteRange { + /** The 0 based index of the start byte for a range. */ + start: number; + + /** The 0 based index of the end byte for a range, which is inclusive. */ + end: number; +} + +/** + * Read a range of bytes from a file or other resource that is readable and + * seekable. The range start and end are inclusive of the bytes within that + * range. + * + * ```ts + * import { assertEquals } from "https://deno.land/std@$STD_VERSION/testing/asserts.ts"; + * import { readRange } from "https://deno.land/std@$STD_VERSION/io/read_range.ts"; + * + * // Read the first 10 bytes of a file + * const file = await Deno.open("example.txt", { read: true }); + * const bytes = await readRange(file, { start: 0, end: 9 }); + * assertEquals(bytes.length, 10); + * ``` + */ +export async function readRange( + r: Reader & Deno.Seeker, + range: ByteRange, +): Promise { + // byte ranges are inclusive, so we have to add one to the end + let length = range.end - range.start + 1; + assert(length > 0, "Invalid byte range was passed."); + await r.seek(range.start, Deno.SeekMode.Start); + const result = new Uint8Array(length); + let off = 0; + while (length) { + const p = new Uint8Array(Math.min(length, DEFAULT_BUFFER_SIZE)); + const nread = await r.read(p); + assert(nread !== null, "Unexpected EOF reach while reading a range."); + assert(nread > 0, "Unexpected read of 0 bytes while reading a range."); + copyBytes(p, result, off); + off += nread; + length -= nread; + assert(length >= 0, "Unexpected length remaining after reading range."); + } + return result; +} + +/** + * Read a range of bytes synchronously from a file or other resource that is + * readable and seekable. The range start and end are inclusive of the bytes + * within that range. + * + * ```ts + * import { assertEquals } from "https://deno.land/std@$STD_VERSION/testing/asserts.ts"; + * import { readRangeSync } from "https://deno.land/std@$STD_VERSION/io/read_range.ts"; + * + * // Read the first 10 bytes of a file + * const file = Deno.openSync("example.txt", { read: true }); + * const bytes = readRangeSync(file, { start: 0, end: 9 }); + * assertEquals(bytes.length, 10); + * ``` + */ +export function readRangeSync( + r: ReaderSync & Deno.SeekerSync, + range: ByteRange, +): Uint8Array { + // byte ranges are inclusive, so we have to add one to the end + let length = range.end - range.start + 1; + assert(length > 0, "Invalid byte range was passed."); + r.seekSync(range.start, Deno.SeekMode.Start); + const result = new Uint8Array(length); + let off = 0; + while (length) { + const p = new Uint8Array(Math.min(length, DEFAULT_BUFFER_SIZE)); + const nread = r.readSync(p); + assert(nread !== null, "Unexpected EOF reach while reading a range."); + assert(nread > 0, "Unexpected read of 0 bytes while reading a range."); + copyBytes(p, result, off); + off += nread; + length -= nread; + assert(length >= 0, "Unexpected length remaining after reading range."); + } + return result; +} diff --git a/vendor/deno.land/std@0.178.0/io/read_short.ts b/vendor/deno.land/std@0.178.0/io/read_short.ts new file mode 100644 index 0000000..92835f5 --- /dev/null +++ b/vendor/deno.land/std@0.178.0/io/read_short.ts @@ -0,0 +1,15 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. + +import { type BufReader } from "./buf_reader.ts"; + +/** + * Read big endian 16bit short from BufReader + * @param buf + */ +export async function readShort(buf: BufReader): Promise { + const high = await buf.readByte(); + if (high === null) return null; + const low = await buf.readByte(); + if (low === null) throw new Deno.errors.UnexpectedEof(); + return (high << 8) | low; +} diff --git a/vendor/deno.land/std@0.178.0/io/read_string_delim.ts b/vendor/deno.land/std@0.178.0/io/read_string_delim.ts new file mode 100644 index 0000000..0474ed0 --- /dev/null +++ b/vendor/deno.land/std@0.178.0/io/read_string_delim.ts @@ -0,0 +1,36 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. + +import { type Reader } from "../types.d.ts"; +import { readDelim } from "./read_delim.ts"; + +/** + * Read Reader chunk by chunk, splitting based on delimiter. + * + * @example + * ```ts + * import { readStringDelim } from "https://deno.land/std@$STD_VERSION/io/read_string_delim.ts"; + * import * as path from "https://deno.land/std@$STD_VERSION/path/mod.ts"; + * + * const filename = path.join(Deno.cwd(), "std/io/README.md"); + * let fileReader = await Deno.open(filename); + * + * for await (let line of readStringDelim(fileReader, "\n")) { + * console.log(line); + * } + * ``` + */ +export async function* readStringDelim( + reader: Reader, + delim: string, + decoderOpts?: { + encoding?: string; + fatal?: boolean; + ignoreBOM?: boolean; + }, +): AsyncIterableIterator { + const encoder = new TextEncoder(); + const decoder = new TextDecoder(decoderOpts?.encoding, decoderOpts); + for await (const chunk of readDelim(reader, encoder.encode(delim))) { + yield decoder.decode(chunk); + } +} diff --git a/vendor/deno.land/std@0.178.0/io/slice_long_to_bytes.ts b/vendor/deno.land/std@0.178.0/io/slice_long_to_bytes.ts new file mode 100644 index 0000000..dc7ba74 --- /dev/null +++ b/vendor/deno.land/std@0.178.0/io/slice_long_to_bytes.ts @@ -0,0 +1,18 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. + +/** + * Slice number into 64bit big endian byte array + * @param d The number to be sliced + * @param dest The sliced array + */ +export function sliceLongToBytes( + d: number, + dest = Array.from({ length: 8 }), +): number[] { + let big = BigInt(d); + for (let i = 0; i < 8; i++) { + dest[7 - i] = Number(big & 0xffn); + big >>= 8n; + } + return dest; +} diff --git a/vendor/deno.land/std@0.178.0/io/string_reader.ts b/vendor/deno.land/std@0.178.0/io/string_reader.ts new file mode 100644 index 0000000..e30c9c6 --- /dev/null +++ b/vendor/deno.land/std@0.178.0/io/string_reader.ts @@ -0,0 +1,38 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. + +import { Buffer } from "./buffer.ts"; + +/** + * Reader utility for strings. + * + * @example + * ```ts + * import { StringReader } from "https://deno.land/std@$STD_VERSION/io/string_reader.ts"; + * + * const data = new Uint8Array(6); + * const r = new StringReader("abcdef"); + * const res0 = await r.read(data); + * const res1 = await r.read(new Uint8Array(6)); + * + * // Number of bytes read + * console.log(res0); // 6 + * console.log(res1); // null, no byte left to read. EOL + * + * // text + * + * console.log(new TextDecoder().decode(data)); // abcdef + * ``` + * + * **Output:** + * + * ```text + * 6 + * null + * abcdef + * ``` + */ +export class StringReader extends Buffer { + constructor(s: string) { + super(new TextEncoder().encode(s).buffer); + } +} diff --git a/vendor/deno.land/std@0.178.0/io/string_writer.ts b/vendor/deno.land/std@0.178.0/io/string_writer.ts new file mode 100644 index 0000000..85488ee --- /dev/null +++ b/vendor/deno.land/std@0.178.0/io/string_writer.ts @@ -0,0 +1,73 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. +// This module is browser compatible. + +import type { Writer, WriterSync } from "../types.d.ts"; + +const decoder = new TextDecoder(); + +/** + * Writer utility for buffering string chunks. + * + * @example + * ```ts + * import { + * copyN, + * StringReader, + * StringWriter, + * } from "https://deno.land/std@$STD_VERSION/io/mod.ts"; + * import { copy } from "https://deno.land/std@$STD_VERSION/streams/copy.ts"; + * + * const w = new StringWriter("base"); + * const r = new StringReader("0123456789"); + * await copyN(r, w, 4); // copy 4 bytes + * + * // Number of bytes read + * console.log(w.toString()); //base0123 + * + * await copy(r, w); // copy all + * console.log(w.toString()); // base0123456789 + * ``` + * + * **Output:** + * + * ```text + * base0123 + * base0123456789 + * ``` + */ +export class StringWriter implements Writer, WriterSync { + #chunks: Uint8Array[] = []; + #byteLength = 0; + #cache: string | undefined; + + constructor(private base: string = "") { + const c = new TextEncoder().encode(base); + this.#chunks.push(c); + this.#byteLength += c.byteLength; + } + + write(p: Uint8Array): Promise { + return Promise.resolve(this.writeSync(p)); + } + + writeSync(p: Uint8Array): number { + this.#chunks.push(new Uint8Array(p)); + this.#byteLength += p.byteLength; + this.#cache = undefined; + return p.byteLength; + } + + toString(): string { + if (this.#cache) { + return this.#cache; + } + const buf = new Uint8Array(this.#byteLength); + let offs = 0; + for (const chunk of this.#chunks) { + buf.set(chunk, offs); + offs += chunk.byteLength; + } + this.#cache = decoder.decode(buf); + return this.#cache; + } +} diff --git a/vendor/deno.land/std@0.178.0/media_types/_db.ts b/vendor/deno.land/std@0.178.0/media_types/_db.ts new file mode 100644 index 0000000..91308cb --- /dev/null +++ b/vendor/deno.land/std@0.178.0/media_types/_db.ts @@ -0,0 +1,46 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. +import db from "./vendor/mime-db.v1.52.0.ts"; +import { type DBEntry, extensions } from "./_util.ts"; + +export type KeyOfDb = keyof typeof db; + +/** A map of the media type for a given extension */ +export const types = new Map(); + +/** Internal function to populate the maps based on the Mime DB. */ +(function populateMaps() { + const preference = ["nginx", "apache", undefined, "iana"]; + + for (const type of Object.keys(db) as KeyOfDb[]) { + const mime = db[type] as DBEntry; + const exts = mime.extensions; + + if (!exts || !exts.length) { + continue; + } + + // @ts-ignore work around denoland/dnt#148 + extensions.set(type, exts); + + for (const ext of exts) { + const current = types.get(ext); + if (current) { + const from = preference.indexOf((db[current] as DBEntry).source); + const to = preference.indexOf(mime.source); + + if ( + current !== "application/octet-stream" && + (from > to || + // @ts-ignore work around denoland/dnt#148 + (from === to && current.startsWith("application/"))) + ) { + continue; + } + } + + types.set(ext, type); + } + } +})(); + +export { db }; diff --git a/vendor/deno.land/std@0.178.0/media_types/_util.ts b/vendor/deno.land/std@0.178.0/media_types/_util.ts new file mode 100644 index 0000000..290fc41 --- /dev/null +++ b/vendor/deno.land/std@0.178.0/media_types/_util.ts @@ -0,0 +1,152 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. + +/** Supporting functions for media_types that do not make part of the public + * API. + * + * @module + * @private + */ +export interface DBEntry { + source: string; + compressible?: boolean; + charset?: string; + extensions?: string[]; +} + +/** A map of extensions for a given media type. */ +export const extensions = new Map(); + +export function consumeToken(v: string): [token: string, rest: string] { + const notPos = indexOf(v, isNotTokenChar); + if (notPos == -1) { + return [v, ""]; + } + if (notPos == 0) { + return ["", v]; + } + return [v.slice(0, notPos), v.slice(notPos)]; +} + +export function consumeValue(v: string): [value: string, rest: string] { + if (!v) { + return ["", v]; + } + if (v[0] !== `"`) { + return consumeToken(v); + } + let value = ""; + for (let i = 1; i < v.length; i++) { + const r = v[i]; + if (r === `"`) { + return [value, v.slice(i + 1)]; + } + if (r === "\\" && i + 1 < v.length && isTSpecial(v[i + 1])) { + value += v[i + 1]; + i++; + continue; + } + if (r === "\r" || r === "\n") { + return ["", v]; + } + value += v[i]; + } + return ["", v]; +} + +export function consumeMediaParam( + v: string, +): [key: string, value: string, rest: string] { + let rest = v.trimStart(); + if (!rest.startsWith(";")) { + return ["", "", v]; + } + rest = rest.slice(1); + rest = rest.trimStart(); + let param: string; + [param, rest] = consumeToken(rest); + param = param.toLowerCase(); + if (!param) { + return ["", "", v]; + } + rest = rest.slice(1); + rest = rest.trimStart(); + const [value, rest2] = consumeValue(rest); + if (value == "" && rest2 === rest) { + return ["", "", v]; + } + rest = rest2; + return [param, value, rest]; +} + +export function decode2331Encoding(v: string): string | undefined { + const sv = v.split(`'`, 3); + if (sv.length !== 3) { + return undefined; + } + const charset = sv[0].toLowerCase(); + if (!charset) { + return undefined; + } + if (charset != "us-ascii" && charset != "utf-8") { + return undefined; + } + const encv = decodeURI(sv[2]); + if (!encv) { + return undefined; + } + return encv; +} + +function indexOf(s: Iterable, fn: (s: T) => boolean): number { + let i = -1; + for (const v of s) { + i++; + if (fn(v)) { + return i; + } + } + return -1; +} + +export function isIterator(obj: unknown): obj is Iterable { + if (obj == null) { + return false; + } + // deno-lint-ignore no-explicit-any + return typeof (obj as any)[Symbol.iterator] === "function"; +} + +export function isToken(s: string): boolean { + if (!s) { + return false; + } + return indexOf(s, isNotTokenChar) < 0; +} + +function isNotTokenChar(r: string): boolean { + return !isTokenChar(r); +} + +function isTokenChar(r: string): boolean { + const code = r.charCodeAt(0); + return code > 0x20 && code < 0x7f && !isTSpecial(r); +} + +function isTSpecial(r: string): boolean { + return `()<>@,;:\\"/[]?=`.includes(r[0]); +} + +const CHAR_CODE_SPACE = " ".charCodeAt(0); +const CHAR_CODE_TILDE = "~".charCodeAt(0); + +export function needsEncoding(s: string): boolean { + for (const b of s) { + const charCode = b.charCodeAt(0); + if ( + (charCode < CHAR_CODE_SPACE || charCode > CHAR_CODE_TILDE) && b !== "\t" + ) { + return true; + } + } + return false; +} diff --git a/vendor/deno.land/std@0.178.0/media_types/content_type.ts b/vendor/deno.land/std@0.178.0/media_types/content_type.ts new file mode 100644 index 0000000..6634504 --- /dev/null +++ b/vendor/deno.land/std@0.178.0/media_types/content_type.ts @@ -0,0 +1,75 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. +import { parseMediaType } from "./parse_media_type.ts"; +import { typeByExtension } from "./type_by_extension.ts"; +import { getCharset } from "./get_charset.ts"; +import { formatMediaType } from "./format_media_type.ts"; +import type { db } from "./_db.ts"; + +type DB = typeof db; +type ContentTypeToExtension = { + [K in keyof DB]: DB[K] extends { "extensions": readonly string[] } + ? DB[K]["extensions"][number] + : never; +}; + +type KnownExtensionOrType = + | keyof ContentTypeToExtension + | ContentTypeToExtension[keyof ContentTypeToExtension] + | `.${ContentTypeToExtension[keyof ContentTypeToExtension]}`; + +/** + * Given an extension or media type, return a full `Content-Type` or + * `Content-Disposition` header value. + * + * The function will treat the `extensionOrType` as a media type when it + * contains a `/`, otherwise it will process it as an extension, with or without + * the leading `.`. + * + * Returns `undefined` if unable to resolve the media type. + * + * > Note: a side effect of `deno/x/media_types` was that you could pass a file + * > name (e.g. `file.json`) and it would return the content type. This behavior + * > is intentionally not supported here. If you want to get an extension for a + * > file name, use `extname()` from `std/path/mod.ts` to determine the + * > extension and pass it here. + * + * @example + * ```ts + * import { contentType } from "https://deno.land/std@$STD_VERSION/media_types/content_type.ts"; + * + * contentType(".json"); // `application/json; charset=UTF-8` + * contentType("text/html"); // `text/html; charset=UTF-8` + * contentType("text/html; charset=UTF-8"); // `text/html; charset=UTF-8` + * contentType("txt"); // `text/plain; charset=UTF-8` + * contentType("foo"); // undefined + * contentType("file.json"); // undefined + * ``` + */ +export function contentType< + // Workaround to autocomplete for parameters: https://github.com/microsoft/TypeScript/issues/29729#issuecomment-567871939 + // deno-lint-ignore ban-types + T extends (string & {}) | KnownExtensionOrType, +>( + extensionOrType: T, +): Lowercase extends KnownExtensionOrType ? string : string | undefined { + try { + const [mediaType, params = {}] = extensionOrType.includes("/") + ? parseMediaType(extensionOrType) + : [typeByExtension(extensionOrType), undefined]; + if (!mediaType) { + return undefined as Lowercase extends KnownExtensionOrType ? string + : string | undefined; + } + if (!("charset" in params)) { + const charset = getCharset(mediaType); + if (charset) { + params.charset = charset; + } + } + return formatMediaType(mediaType, params); + } catch { + // just swallow returning undefined + } + return undefined as Lowercase extends KnownExtensionOrType ? string + : string | undefined; +} diff --git a/vendor/deno.land/std@0.178.0/media_types/extension.ts b/vendor/deno.land/std@0.178.0/media_types/extension.ts new file mode 100644 index 0000000..c98101c --- /dev/null +++ b/vendor/deno.land/std@0.178.0/media_types/extension.ts @@ -0,0 +1,26 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. +import { extensionsByType } from "./extensions_by_type.ts"; + +/** + * For a given media type, return the most relevant extension, or `undefined` + * if no extension can be found. + * + * Extensions are returned without a leading `.`. + * + * @example + * ```ts + * import { extension } from "https://deno.land/std@$STD_VERSION/media_types/extension.ts"; + * + * extension("text/plain"); // `txt` + * extension("application/json"); // `json` + * extension("text/html; charset=UTF-8"); // `html` + * extension("application/foo"); // undefined + * ``` + */ +export function extension(type: string): string | undefined { + const exts = extensionsByType(type); + if (exts) { + return exts[0]; + } + return undefined; +} diff --git a/vendor/deno.land/std@0.178.0/media_types/extensions_by_type.ts b/vendor/deno.land/std@0.178.0/media_types/extensions_by_type.ts new file mode 100644 index 0000000..7d56dc4 --- /dev/null +++ b/vendor/deno.land/std@0.178.0/media_types/extensions_by_type.ts @@ -0,0 +1,31 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. +import { parseMediaType } from "./parse_media_type.ts"; +import { extensions } from "./_util.ts"; + +export { extensions }; + +/** + * Returns the extensions known to be associated with the media type `type`. + * The returned extensions will each begin with a leading dot, as in `.html`. + * + * When `type` has no associated extensions, the function returns `undefined`. + * + * Extensions are returned without a leading `.`. + * + * @example + * ```ts + * import { extensionsByType } from "https://deno.land/std@$STD_VERSION/media_types/extensions_by_type.ts"; + * + * extensionsByType("application/json"); // ["json", "map"] + * extensionsByType("text/html; charset=UTF-8"); // ["html", "htm", "shtml"] + * extensionsByType("application/foo"); // undefined + * ``` + */ +export function extensionsByType(type: string): string[] | undefined { + try { + const [mediaType] = parseMediaType(type); + return extensions.get(mediaType); + } catch { + // just swallow errors, returning undefined + } +} diff --git a/vendor/deno.land/std@0.178.0/media_types/format_media_type.ts b/vendor/deno.land/std@0.178.0/media_types/format_media_type.ts new file mode 100644 index 0000000..7df4cb7 --- /dev/null +++ b/vendor/deno.land/std@0.178.0/media_types/format_media_type.ts @@ -0,0 +1,68 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. +import { isIterator, isToken, needsEncoding } from "./_util.ts"; + +/** Serializes the media type and the optional parameters as a media type + * conforming to RFC 2045 and RFC 2616. + * + * The type and parameter names are written in lower-case. + * + * When any of the arguments results in a standard violation then the return + * value will be an empty string (`""`). + * + * @example + * ```ts + * import { formatMediaType } from "https://deno.land/std@$STD_VERSION/media_types/format_media_type.ts"; + * + * formatMediaType("text/plain", { charset: "UTF-8" }); // `text/plain; charset=UTF-8` + * ``` + */ +export function formatMediaType( + type: string, + param?: Record | Iterable<[string, string]>, +): string { + let b = ""; + const [major, sub] = type.split("/"); + if (!sub) { + if (!isToken(type)) { + return ""; + } + b += type.toLowerCase(); + } else { + if (!isToken(major) || !isToken(sub)) { + return ""; + } + b += `${major.toLowerCase()}/${sub.toLowerCase()}`; + } + + if (param) { + param = isIterator(param) ? Object.fromEntries(param) : param; + const attrs = Object.keys(param); + attrs.sort(); + + for (const attribute of attrs) { + if (!isToken(attribute)) { + return ""; + } + const value = param[attribute]; + b += `; ${attribute.toLowerCase()}`; + + const needEnc = needsEncoding(value); + if (needEnc) { + b += "*"; + } + b += "="; + + if (needEnc) { + b += `utf-8''${encodeURIComponent(value)}`; + continue; + } + + if (isToken(value)) { + b += value; + continue; + } + b += `"${value.replace(/["\\]/gi, (m) => `\\${m}`)}"`; + } + } + return b; +} diff --git a/vendor/deno.land/std@0.178.0/media_types/get_charset.ts b/vendor/deno.land/std@0.178.0/media_types/get_charset.ts new file mode 100644 index 0000000..158322d --- /dev/null +++ b/vendor/deno.land/std@0.178.0/media_types/get_charset.ts @@ -0,0 +1,37 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. +import { parseMediaType } from "./parse_media_type.ts"; +import { type DBEntry } from "./_util.ts"; +import { db, type KeyOfDb } from "./_db.ts"; + +/** + * Given a media type or header value, identify the encoding charset. If the + * charset cannot be determined, the function returns `undefined`. + * + * @example + * ```ts + * import { getCharset } from "https://deno.land/std@$STD_VERSION/media_types/get_charset.ts"; + * + * getCharset("text/plain"); // `UTF-8` + * getCharset("application/foo"); // undefined + * getCharset("application/news-checkgroups"); // `US-ASCII` + * getCharset("application/news-checkgroups; charset=UTF-8"); // `UTF-8` + * ``` + */ +export function getCharset(type: string): string | undefined { + try { + const [mediaType, params] = parseMediaType(type); + if (params && params["charset"]) { + return params["charset"]; + } + const entry = db[mediaType as KeyOfDb] as DBEntry; + if (entry && entry.charset) { + return entry.charset; + } + if (mediaType.startsWith("text/")) { + return "UTF-8"; + } + } catch { + // just swallow errors, returning undefined + } + return undefined; +} diff --git a/vendor/deno.land/std@0.178.0/media_types/mod.ts b/vendor/deno.land/std@0.178.0/media_types/mod.ts new file mode 100644 index 0000000..135e063 --- /dev/null +++ b/vendor/deno.land/std@0.178.0/media_types/mod.ts @@ -0,0 +1,24 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. +// This module is browser compatible. + +/** Utility functions for media types (MIME types). + * + * This API is inspired by the GoLang [`mime`](https://pkg.go.dev/mime) package + * and [jshttp/mime-types](https://github.com/jshttp/mime-types), and is + * designed to integrate and improve the APIs from + * [deno.land/x/media_types](https://deno.land/x/media_types). + * + * The `vendor` folder contains copy of the + * [jshttp/mime-db](https://github.com/jshttp/mime-types) `db.json` file along + * with its license. + * + * @module + */ + +export * from "./content_type.ts"; +export * from "./extension.ts"; +export * from "./extensions_by_type.ts"; +export * from "./format_media_type.ts"; +export * from "./get_charset.ts"; +export * from "./parse_media_type.ts"; +export * from "./type_by_extension.ts"; diff --git a/vendor/deno.land/std@0.178.0/media_types/parse_media_type.ts b/vendor/deno.land/std@0.178.0/media_types/parse_media_type.ts new file mode 100644 index 0000000..c8edcb6 --- /dev/null +++ b/vendor/deno.land/std@0.178.0/media_types/parse_media_type.ts @@ -0,0 +1,129 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. +import { consumeMediaParam, decode2331Encoding } from "./_util.ts"; + +/** + * Parses the media type and any optional parameters, per + * [RFC 1521](https://datatracker.ietf.org/doc/html/rfc1521). Media types are + * the values in `Content-Type` and `Content-Disposition` headers. On success + * the function returns a tuple where the first element is the media type and + * the second element is the optional parameters or `undefined` if there are + * none. + * + * The function will throw if the parsed value is invalid. + * + * The returned media type will be normalized to be lower case, and returned + * params keys will be normalized to lower case, but preserves the casing of + * the value. + * + * @example + * ```ts + * import { parseMediaType } from "https://deno.land/std@$STD_VERSION/media_types/parse_media_type.ts"; + * import { assertEquals } from "https://deno.land/std@$STD_VERSION/testing/asserts.ts"; + * + * assertEquals( + * parseMediaType("application/JSON"), + * [ + * "application/json", + * undefined + * ] + * ); + * + * assertEquals( + * parseMediaType("text/html; charset=UTF-8"), + * [ + * "application/json", + * { charset: "UTF-8" }, + * ] + * ); + * ``` + */ +export function parseMediaType( + v: string, +): [mediaType: string, params: Record | undefined] { + const [base] = v.split(";"); + const mediaType = base.toLowerCase().trim(); + + const params: Record = {}; + // Map of base parameter name -> parameter name -> value + // for parameters containing a '*' character. + const continuation = new Map>(); + + v = v.slice(base.length); + while (v.length) { + v = v.trimStart(); + if (v.length === 0) { + break; + } + const [key, value, rest] = consumeMediaParam(v); + if (!key) { + if (rest.trim() === ";") { + // ignore trailing semicolons + break; + } + throw new TypeError("Invalid media parameter."); + } + + let pmap = params; + const [baseName, rest2] = key.split("*"); + if (baseName && rest2 != null) { + if (!continuation.has(baseName)) { + continuation.set(baseName, {}); + } + pmap = continuation.get(baseName)!; + } + if (key in pmap) { + throw new TypeError("Duplicate key parsed."); + } + pmap[key] = value; + v = rest; + } + + // Stitch together any continuations or things with stars + // (i.e. RFC 2231 things with stars: "foo*0" or "foo*") + let str = ""; + for (const [key, pieceMap] of continuation) { + const singlePartKey = `${key}*`; + const v = pieceMap[singlePartKey]; + if (v) { + const decv = decode2331Encoding(v); + if (decv) { + params[key] = decv; + } + continue; + } + + str = ""; + let valid = false; + for (let n = 0;; n++) { + const simplePart = `${key}*${n}`; + let v = pieceMap[simplePart]; + if (v) { + valid = true; + str += v; + continue; + } + const encodedPart = `${simplePart}*`; + v = pieceMap[encodedPart]; + if (!v) { + break; + } + valid = true; + if (n === 0) { + const decv = decode2331Encoding(v); + if (decv) { + str += decv; + } + } else { + const decv = decodeURI(v); + str += decv; + } + } + if (valid) { + params[key] = str; + } + } + + return Object.keys(params).length + ? [mediaType, params] + : [mediaType, undefined]; +} diff --git a/vendor/deno.land/std@0.178.0/media_types/type_by_extension.ts b/vendor/deno.land/std@0.178.0/media_types/type_by_extension.ts new file mode 100644 index 0000000..2670d69 --- /dev/null +++ b/vendor/deno.land/std@0.178.0/media_types/type_by_extension.ts @@ -0,0 +1,24 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. +import { types } from "./_db.ts"; + +/** + * Returns the media type associated with the file extension. Values are + * normalized to lower case and matched irrespective of a leading `.`. + * + * When `extension` has no associated type, the function returns `undefined`. + * + * @example + * ```ts + * import { typeByExtension } from "https://deno.land/std@$STD_VERSION/media_types/type_by_extension.ts"; + * + * typeByExtension("js"); // `application/json` + * typeByExtension(".HTML"); // `text/html` + * typeByExtension("foo"); // undefined + * typeByExtension("file.json"); // undefined + * ``` + */ +export function typeByExtension(extension: string): string | undefined { + extension = extension.startsWith(".") ? extension.slice(1) : extension; + // @ts-ignore workaround around denoland/dnt#148 + return types.get(extension.toLowerCase()); +} diff --git a/vendor/deno.land/std@0.178.0/media_types/vendor/mime-db.v1.52.0.ts b/vendor/deno.land/std@0.178.0/media_types/vendor/mime-db.v1.52.0.ts new file mode 100644 index 0000000..2bd02a5 --- /dev/null +++ b/vendor/deno.land/std@0.178.0/media_types/vendor/mime-db.v1.52.0.ts @@ -0,0 +1,8556 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. +// This module is browser compatible. +// deno-fmt-ignore-file + +export default { + "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 + } +} as const; diff --git a/vendor/deno.land/std@0.178.0/path/_constants.ts b/vendor/deno.land/std@0.178.0/path/_constants.ts new file mode 100644 index 0000000..8da22a8 --- /dev/null +++ b/vendor/deno.land/std@0.178.0/path/_constants.ts @@ -0,0 +1,49 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. +// Copyright the Browserify authors. MIT License. +// Ported from https://github.com/browserify/path-browserify/ +// This module is browser compatible. + +// Alphabet chars. +export const CHAR_UPPERCASE_A = 65; /* A */ +export const CHAR_LOWERCASE_A = 97; /* a */ +export const CHAR_UPPERCASE_Z = 90; /* Z */ +export const CHAR_LOWERCASE_Z = 122; /* z */ + +// Non-alphabetic chars. +export const CHAR_DOT = 46; /* . */ +export const CHAR_FORWARD_SLASH = 47; /* / */ +export const CHAR_BACKWARD_SLASH = 92; /* \ */ +export const CHAR_VERTICAL_LINE = 124; /* | */ +export const CHAR_COLON = 58; /* : */ +export const CHAR_QUESTION_MARK = 63; /* ? */ +export const CHAR_UNDERSCORE = 95; /* _ */ +export const CHAR_LINE_FEED = 10; /* \n */ +export const CHAR_CARRIAGE_RETURN = 13; /* \r */ +export const CHAR_TAB = 9; /* \t */ +export const CHAR_FORM_FEED = 12; /* \f */ +export const CHAR_EXCLAMATION_MARK = 33; /* ! */ +export const CHAR_HASH = 35; /* # */ +export const CHAR_SPACE = 32; /* */ +export const CHAR_NO_BREAK_SPACE = 160; /* \u00A0 */ +export const CHAR_ZERO_WIDTH_NOBREAK_SPACE = 65279; /* \uFEFF */ +export const CHAR_LEFT_SQUARE_BRACKET = 91; /* [ */ +export const CHAR_RIGHT_SQUARE_BRACKET = 93; /* ] */ +export const CHAR_LEFT_ANGLE_BRACKET = 60; /* < */ +export const CHAR_RIGHT_ANGLE_BRACKET = 62; /* > */ +export const CHAR_LEFT_CURLY_BRACKET = 123; /* { */ +export const CHAR_RIGHT_CURLY_BRACKET = 125; /* } */ +export const CHAR_HYPHEN_MINUS = 45; /* - */ +export const CHAR_PLUS = 43; /* + */ +export const CHAR_DOUBLE_QUOTE = 34; /* " */ +export const CHAR_SINGLE_QUOTE = 39; /* ' */ +export const CHAR_PERCENT = 37; /* % */ +export const CHAR_SEMICOLON = 59; /* ; */ +export const CHAR_CIRCUMFLEX_ACCENT = 94; /* ^ */ +export const CHAR_GRAVE_ACCENT = 96; /* ` */ +export const CHAR_AT = 64; /* @ */ +export const CHAR_AMPERSAND = 38; /* & */ +export const CHAR_EQUAL = 61; /* = */ + +// Digits +export const CHAR_0 = 48; /* 0 */ +export const CHAR_9 = 57; /* 9 */ diff --git a/vendor/deno.land/std@0.178.0/path/_interface.ts b/vendor/deno.land/std@0.178.0/path/_interface.ts new file mode 100644 index 0000000..153f3de --- /dev/null +++ b/vendor/deno.land/std@0.178.0/path/_interface.ts @@ -0,0 +1,30 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. +// This module is browser compatible. + +/** + * A parsed path object generated by path.parse() or consumed by path.format(). + */ +export interface ParsedPath { + /** + * The root of the path such as '/' or 'c:\' + */ + root: string; + /** + * The full directory path such as '/home/user/dir' or 'c:\path\dir' + */ + dir: string; + /** + * The file name including extension (if any) such as 'index.html' + */ + base: string; + /** + * The file extension (if any) such as '.html' + */ + ext: string; + /** + * The file name without extension (if any) such as 'index' + */ + name: string; +} + +export type FormatInputPathObject = Partial; diff --git a/vendor/deno.land/std@0.178.0/path/_util.ts b/vendor/deno.land/std@0.178.0/path/_util.ts new file mode 100644 index 0000000..b84ff91 --- /dev/null +++ b/vendor/deno.land/std@0.178.0/path/_util.ts @@ -0,0 +1,194 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. +// Copyright the Browserify authors. MIT License. +// Ported from https://github.com/browserify/path-browserify/ +// This module is browser compatible. + +import type { FormatInputPathObject } from "./_interface.ts"; +import { + CHAR_BACKWARD_SLASH, + CHAR_DOT, + CHAR_FORWARD_SLASH, + CHAR_LOWERCASE_A, + CHAR_LOWERCASE_Z, + CHAR_UPPERCASE_A, + CHAR_UPPERCASE_Z, +} from "./_constants.ts"; + +export function assertPath(path: string) { + if (typeof path !== "string") { + throw new TypeError( + `Path must be a string. Received ${JSON.stringify(path)}`, + ); + } +} + +export function isPosixPathSeparator(code: number): boolean { + return code === CHAR_FORWARD_SLASH; +} + +export function isPathSeparator(code: number): boolean { + return isPosixPathSeparator(code) || code === CHAR_BACKWARD_SLASH; +} + +export function isWindowsDeviceRoot(code: number): boolean { + return ( + (code >= CHAR_LOWERCASE_A && code <= CHAR_LOWERCASE_Z) || + (code >= CHAR_UPPERCASE_A && code <= CHAR_UPPERCASE_Z) + ); +} + +// Resolves . and .. elements in a path with directory names +export function normalizeString( + path: string, + allowAboveRoot: boolean, + separator: string, + isPathSeparator: (code: number) => boolean, +): string { + let res = ""; + let lastSegmentLength = 0; + let lastSlash = -1; + let dots = 0; + let code: number | undefined; + for (let i = 0, len = path.length; i <= len; ++i) { + if (i < len) code = path.charCodeAt(i); + else if (isPathSeparator(code!)) break; + else code = CHAR_FORWARD_SLASH; + + if (isPathSeparator(code!)) { + if (lastSlash === i - 1 || dots === 1) { + // NOOP + } else if (lastSlash !== i - 1 && dots === 2) { + if ( + res.length < 2 || + lastSegmentLength !== 2 || + res.charCodeAt(res.length - 1) !== CHAR_DOT || + res.charCodeAt(res.length - 2) !== CHAR_DOT + ) { + if (res.length > 2) { + const lastSlashIndex = res.lastIndexOf(separator); + if (lastSlashIndex === -1) { + res = ""; + lastSegmentLength = 0; + } else { + res = res.slice(0, lastSlashIndex); + lastSegmentLength = res.length - 1 - res.lastIndexOf(separator); + } + lastSlash = i; + dots = 0; + continue; + } else if (res.length === 2 || res.length === 1) { + res = ""; + lastSegmentLength = 0; + lastSlash = i; + dots = 0; + continue; + } + } + if (allowAboveRoot) { + if (res.length > 0) res += `${separator}..`; + else res = ".."; + lastSegmentLength = 2; + } + } else { + if (res.length > 0) res += separator + path.slice(lastSlash + 1, i); + else res = path.slice(lastSlash + 1, i); + lastSegmentLength = i - lastSlash - 1; + } + lastSlash = i; + dots = 0; + } else if (code === CHAR_DOT && dots !== -1) { + ++dots; + } else { + dots = -1; + } + } + return res; +} + +export function _format( + sep: string, + pathObject: FormatInputPathObject, +): string { + const dir: string | undefined = pathObject.dir || pathObject.root; + const base: string = pathObject.base || + (pathObject.name || "") + (pathObject.ext || ""); + if (!dir) return base; + if (base === sep) return dir; + if (dir === pathObject.root) return dir + base; + return dir + sep + base; +} + +const WHITESPACE_ENCODINGS: Record = { + "\u0009": "%09", + "\u000A": "%0A", + "\u000B": "%0B", + "\u000C": "%0C", + "\u000D": "%0D", + "\u0020": "%20", +}; + +export function encodeWhitespace(string: string): string { + return string.replaceAll(/[\s]/g, (c) => { + return WHITESPACE_ENCODINGS[c] ?? c; + }); +} + +export function lastPathSegment( + path: string, + isSep: (char: number) => boolean, + start = 0, +): string { + let matchedNonSeparator = false; + let end = path.length; + + for (let i = path.length - 1; i >= start; --i) { + if (isSep(path.charCodeAt(i))) { + if (matchedNonSeparator) { + start = i + 1; + break; + } + } else if (!matchedNonSeparator) { + matchedNonSeparator = true; + end = i + 1; + } + } + + return path.slice(start, end); +} + +export function stripTrailingSeparators( + segment: string, + isSep: (char: number) => boolean, +): string { + if (segment.length <= 1) { + return segment; + } + + let end = segment.length; + + for (let i = segment.length - 1; i > 0; i--) { + if (isSep(segment.charCodeAt(i))) { + end = i; + } else { + break; + } + } + + return segment.slice(0, end); +} + +export function stripSuffix(name: string, suffix: string): string { + if (suffix.length >= name.length) { + return name; + } + + const lenDiff = name.length - suffix.length; + + for (let i = suffix.length - 1; i >= 0; --i) { + if (name.charCodeAt(lenDiff + i) !== suffix.charCodeAt(i)) { + return name; + } + } + + return name.slice(0, -suffix.length); +} diff --git a/vendor/deno.land/std@0.178.0/path/common.ts b/vendor/deno.land/std@0.178.0/path/common.ts new file mode 100644 index 0000000..791105a --- /dev/null +++ b/vendor/deno.land/std@0.178.0/path/common.ts @@ -0,0 +1,40 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. +// This module is browser compatible. + +import { SEP } from "./separator.ts"; + +/** Determines the common path from a set of paths, using an optional separator, + * which defaults to the OS default separator. + * + * ```ts + * import { common } from "https://deno.land/std@$STD_VERSION/path/mod.ts"; + * const p = common([ + * "./deno/std/path/mod.ts", + * "./deno/std/fs/mod.ts", + * ]); + * console.log(p); // "./deno/std/" + * ``` + */ +export function common(paths: string[], sep = SEP): string { + const [first = "", ...remaining] = paths; + if (first === "" || remaining.length === 0) { + return first.substring(0, first.lastIndexOf(sep) + 1); + } + const parts = first.split(sep); + + let endOfPrefix = parts.length; + for (const path of remaining) { + const compare = path.split(sep); + for (let i = 0; i < endOfPrefix; i++) { + if (compare[i] !== parts[i]) { + endOfPrefix = i; + } + } + + if (endOfPrefix === 0) { + return ""; + } + } + const prefix = parts.slice(0, endOfPrefix).join(sep); + return prefix.endsWith(sep) ? prefix : `${prefix}${sep}`; +} diff --git a/vendor/deno.land/std@0.178.0/path/glob.ts b/vendor/deno.land/std@0.178.0/path/glob.ts new file mode 100644 index 0000000..265dc4a --- /dev/null +++ b/vendor/deno.land/std@0.178.0/path/glob.ts @@ -0,0 +1,418 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. +// This module is browser compatible. + +import { isWindows, osType } from "../_util/os.ts"; +import { SEP, SEP_PATTERN } from "./separator.ts"; +import * as _win32 from "./win32.ts"; +import * as _posix from "./posix.ts"; +import type { OSType } from "../_util/os.ts"; + +const path = isWindows ? _win32 : _posix; +const { join, normalize } = path; + +export interface GlobOptions { + /** Extended glob syntax. + * See https://www.linuxjournal.com/content/bash-extended-globbing. + * + * @default {true} + */ + extended?: boolean; + /** Globstar syntax. + * See https://www.linuxjournal.com/content/globstar-new-bash-globbing-option. + * If false, `**` is treated like `*`. + * + * @default {true} + */ + globstar?: boolean; + /** Whether globstar should be case-insensitive. */ + caseInsensitive?: boolean; + /** Operating system. Defaults to the native OS. */ + os?: OSType; +} + +export type GlobToRegExpOptions = GlobOptions; + +const regExpEscapeChars = [ + "!", + "$", + "(", + ")", + "*", + "+", + ".", + "=", + "?", + "[", + "\\", + "^", + "{", + "|", +]; +const rangeEscapeChars = ["-", "\\", "]"]; + +/** Convert a glob string to a regular expression. + * + * Tries to match bash glob expansion as closely as possible. + * + * Basic glob syntax: + * - `*` - Matches everything without leaving the path segment. + * - `?` - Matches any single character. + * - `{foo,bar}` - Matches `foo` or `bar`. + * - `[abcd]` - Matches `a`, `b`, `c` or `d`. + * - `[a-d]` - Matches `a`, `b`, `c` or `d`. + * - `[!abcd]` - Matches any single character besides `a`, `b`, `c` or `d`. + * - `[[::]]` - Matches any character belonging to ``. + * - `[[:alnum:]]` - Matches any digit or letter. + * - `[[:digit:]abc]` - Matches any digit, `a`, `b` or `c`. + * - See https://facelessuser.github.io/wcmatch/glob/#posix-character-classes + * for a complete list of supported character classes. + * - `\` - Escapes the next character for an `os` other than `"windows"`. + * - \` - Escapes the next character for `os` set to `"windows"`. + * - `/` - Path separator. + * - `\` - Additional path separator only for `os` set to `"windows"`. + * + * Extended syntax: + * - Requires `{ extended: true }`. + * - `?(foo|bar)` - Matches 0 or 1 instance of `{foo,bar}`. + * - `@(foo|bar)` - Matches 1 instance of `{foo,bar}`. They behave the same. + * - `*(foo|bar)` - Matches _n_ instances of `{foo,bar}`. + * - `+(foo|bar)` - Matches _n > 0_ instances of `{foo,bar}`. + * - `!(foo|bar)` - Matches anything other than `{foo,bar}`. + * - See https://www.linuxjournal.com/content/bash-extended-globbing. + * + * Globstar syntax: + * - Requires `{ globstar: true }`. + * - `**` - Matches any number of any path segments. + * - Must comprise its entire path segment in the provided glob. + * - See https://www.linuxjournal.com/content/globstar-new-bash-globbing-option. + * + * Note the following properties: + * - The generated `RegExp` is anchored at both start and end. + * - Repeating and trailing separators are tolerated. Trailing separators in the + * provided glob have no meaning and are discarded. + * - Absolute globs will only match absolute paths, etc. + * - Empty globs will match nothing. + * - Any special glob syntax must be contained to one path segment. For example, + * `?(foo|bar/baz)` is invalid. The separator will take precedence and the + * first segment ends with an unclosed group. + * - If a path segment ends with unclosed groups or a dangling escape prefix, a + * parse error has occurred. Every character for that segment is taken + * literally in this event. + * + * Limitations: + * - A negative group like `!(foo|bar)` will wrongly be converted to a negative + * look-ahead followed by a wildcard. This means that `!(foo).js` will wrongly + * fail to match `foobar.js`, even though `foobar` is not `foo`. Effectively, + * `!(foo|bar)` is treated like `!(@(foo|bar)*)`. This will work correctly if + * the group occurs not nested at the end of the segment. */ +export function globToRegExp( + glob: string, + { + extended = true, + globstar: globstarOption = true, + os = osType, + caseInsensitive = false, + }: GlobToRegExpOptions = {}, +): RegExp { + if (glob == "") { + return /(?!)/; + } + + const sep = os == "windows" ? "(?:\\\\|/)+" : "/+"; + const sepMaybe = os == "windows" ? "(?:\\\\|/)*" : "/*"; + const seps = os == "windows" ? ["\\", "/"] : ["/"]; + const globstar = os == "windows" + ? "(?:[^\\\\/]*(?:\\\\|/|$)+)*" + : "(?:[^/]*(?:/|$)+)*"; + const wildcard = os == "windows" ? "[^\\\\/]*" : "[^/]*"; + const escapePrefix = os == "windows" ? "`" : "\\"; + + // Remove trailing separators. + let newLength = glob.length; + for (; newLength > 1 && seps.includes(glob[newLength - 1]); newLength--); + glob = glob.slice(0, newLength); + + let regExpString = ""; + + // Terminates correctly. Trust that `j` is incremented every iteration. + for (let j = 0; j < glob.length;) { + let segment = ""; + const groupStack: string[] = []; + let inRange = false; + let inEscape = false; + let endsWithSep = false; + let i = j; + + // Terminates with `i` at the non-inclusive end of the current segment. + for (; i < glob.length && !seps.includes(glob[i]); i++) { + if (inEscape) { + inEscape = false; + const escapeChars = inRange ? rangeEscapeChars : regExpEscapeChars; + segment += escapeChars.includes(glob[i]) ? `\\${glob[i]}` : glob[i]; + continue; + } + + if (glob[i] == escapePrefix) { + inEscape = true; + continue; + } + + if (glob[i] == "[") { + if (!inRange) { + inRange = true; + segment += "["; + if (glob[i + 1] == "!") { + i++; + segment += "^"; + } else if (glob[i + 1] == "^") { + i++; + segment += "\\^"; + } + continue; + } else if (glob[i + 1] == ":") { + let k = i + 1; + let value = ""; + while (glob[k + 1] != null && glob[k + 1] != ":") { + value += glob[k + 1]; + k++; + } + if (glob[k + 1] == ":" && glob[k + 2] == "]") { + i = k + 2; + if (value == "alnum") segment += "\\dA-Za-z"; + else if (value == "alpha") segment += "A-Za-z"; + else if (value == "ascii") segment += "\x00-\x7F"; + else if (value == "blank") segment += "\t "; + else if (value == "cntrl") segment += "\x00-\x1F\x7F"; + else if (value == "digit") segment += "\\d"; + else if (value == "graph") segment += "\x21-\x7E"; + else if (value == "lower") segment += "a-z"; + else if (value == "print") segment += "\x20-\x7E"; + else if (value == "punct") { + segment += "!\"#$%&'()*+,\\-./:;<=>?@[\\\\\\]^_‘{|}~"; + } else if (value == "space") segment += "\\s\v"; + else if (value == "upper") segment += "A-Z"; + else if (value == "word") segment += "\\w"; + else if (value == "xdigit") segment += "\\dA-Fa-f"; + continue; + } + } + } + + if (glob[i] == "]" && inRange) { + inRange = false; + segment += "]"; + continue; + } + + if (inRange) { + if (glob[i] == "\\") { + segment += `\\\\`; + } else { + segment += glob[i]; + } + continue; + } + + if ( + glob[i] == ")" && groupStack.length > 0 && + groupStack[groupStack.length - 1] != "BRACE" + ) { + segment += ")"; + const type = groupStack.pop()!; + if (type == "!") { + segment += wildcard; + } else if (type != "@") { + segment += type; + } + continue; + } + + if ( + glob[i] == "|" && groupStack.length > 0 && + groupStack[groupStack.length - 1] != "BRACE" + ) { + segment += "|"; + continue; + } + + if (glob[i] == "+" && extended && glob[i + 1] == "(") { + i++; + groupStack.push("+"); + segment += "(?:"; + continue; + } + + if (glob[i] == "@" && extended && glob[i + 1] == "(") { + i++; + groupStack.push("@"); + segment += "(?:"; + continue; + } + + if (glob[i] == "?") { + if (extended && glob[i + 1] == "(") { + i++; + groupStack.push("?"); + segment += "(?:"; + } else { + segment += "."; + } + continue; + } + + if (glob[i] == "!" && extended && glob[i + 1] == "(") { + i++; + groupStack.push("!"); + segment += "(?!"; + continue; + } + + if (glob[i] == "{") { + groupStack.push("BRACE"); + segment += "(?:"; + continue; + } + + if (glob[i] == "}" && groupStack[groupStack.length - 1] == "BRACE") { + groupStack.pop(); + segment += ")"; + continue; + } + + if (glob[i] == "," && groupStack[groupStack.length - 1] == "BRACE") { + segment += "|"; + continue; + } + + if (glob[i] == "*") { + if (extended && glob[i + 1] == "(") { + i++; + groupStack.push("*"); + segment += "(?:"; + } else { + const prevChar = glob[i - 1]; + let numStars = 1; + while (glob[i + 1] == "*") { + i++; + numStars++; + } + const nextChar = glob[i + 1]; + if ( + globstarOption && numStars == 2 && + [...seps, undefined].includes(prevChar) && + [...seps, undefined].includes(nextChar) + ) { + segment += globstar; + endsWithSep = true; + } else { + segment += wildcard; + } + } + continue; + } + + segment += regExpEscapeChars.includes(glob[i]) ? `\\${glob[i]}` : glob[i]; + } + + // Check for unclosed groups or a dangling backslash. + if (groupStack.length > 0 || inRange || inEscape) { + // Parse failure. Take all characters from this segment literally. + segment = ""; + for (const c of glob.slice(j, i)) { + segment += regExpEscapeChars.includes(c) ? `\\${c}` : c; + endsWithSep = false; + } + } + + regExpString += segment; + if (!endsWithSep) { + regExpString += i < glob.length ? sep : sepMaybe; + endsWithSep = true; + } + + // Terminates with `i` at the start of the next segment. + while (seps.includes(glob[i])) i++; + + // Check that the next value of `j` is indeed higher than the current value. + if (!(i > j)) { + throw new Error("Assertion failure: i > j (potential infinite loop)"); + } + j = i; + } + + regExpString = `^${regExpString}$`; + return new RegExp(regExpString, caseInsensitive ? "i" : ""); +} + +/** Test whether the given string is a glob */ +export function isGlob(str: string): boolean { + const chars: Record = { "{": "}", "(": ")", "[": "]" }; + const regex = + /\\(.)|(^!|\*|\?|[\].+)]\?|\[[^\\\]]+\]|\{[^\\}]+\}|\(\?[:!=][^\\)]+\)|\([^|]+\|[^\\)]+\))/; + + if (str === "") { + return false; + } + + let match: RegExpExecArray | null; + + while ((match = regex.exec(str))) { + if (match[2]) return true; + let idx = match.index + match[0].length; + + // if an open bracket/brace/paren is escaped, + // set the index to the next closing character + const open = match[1]; + const close = open ? chars[open] : null; + if (open && close) { + const n = str.indexOf(close, idx); + if (n !== -1) { + idx = n + 1; + } + } + + str = str.slice(idx); + } + + return false; +} + +/** Like normalize(), but doesn't collapse "**\/.." when `globstar` is true. */ +export function normalizeGlob( + glob: string, + { globstar = false }: GlobOptions = {}, +): string { + if (glob.match(/\0/g)) { + throw new Error(`Glob contains invalid characters: "${glob}"`); + } + if (!globstar) { + return normalize(glob); + } + const s = SEP_PATTERN.source; + const badParentPattern = new RegExp( + `(?<=(${s}|^)\\*\\*${s})\\.\\.(?=${s}|$)`, + "g", + ); + return normalize(glob.replace(badParentPattern, "\0")).replace(/\0/g, ".."); +} + +/** Like join(), but doesn't collapse "**\/.." when `globstar` is true. */ +export function joinGlobs( + globs: string[], + { extended = true, globstar = false }: GlobOptions = {}, +): string { + if (!globstar || globs.length == 0) { + return join(...globs); + } + if (globs.length === 0) return "."; + let joined: string | undefined; + for (const glob of globs) { + const path = glob; + if (path.length > 0) { + if (!joined) joined = path; + else joined += `${SEP}${path}`; + } + } + if (!joined) return "."; + return normalizeGlob(joined, { extended, globstar }); +} diff --git a/vendor/deno.land/std@0.178.0/path/mod.ts b/vendor/deno.land/std@0.178.0/path/mod.ts new file mode 100644 index 0000000..7250754 --- /dev/null +++ b/vendor/deno.land/std@0.178.0/path/mod.ts @@ -0,0 +1,53 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. +// Copyright the Browserify authors. MIT License. +// Ported mostly from https://github.com/browserify/path-browserify/ + +/** + * Utilities for working with OS-specific file paths. + * + * Codes in the examples uses POSIX path but it automatically use Windows path + * on Windows. Use methods under `posix` or `win32` object instead to handle non + * platform specific path like: + * ```ts + * import { posix, win32 } from "https://deno.land/std@$STD_VERSION/path/mod.ts"; + * const p1 = posix.fromFileUrl("file:///home/foo"); + * const p2 = win32.fromFileUrl("file:///home/foo"); + * console.log(p1); // "/home/foo" + * console.log(p2); // "\\home\\foo" + * ``` + * + * This module is browser compatible. + * + * @module + */ + +import { isWindows } from "../_util/os.ts"; +import * as _win32 from "./win32.ts"; +import * as _posix from "./posix.ts"; + +const path = isWindows ? _win32 : _posix; + +export const win32 = _win32; +export const posix = _posix; +export const { + basename, + delimiter, + dirname, + extname, + format, + fromFileUrl, + isAbsolute, + join, + normalize, + parse, + relative, + resolve, + sep, + toFileUrl, + toNamespacedPath, +} = path; + +export * from "./common.ts"; +export { SEP, SEP_PATTERN } from "./separator.ts"; +export * from "./_interface.ts"; +export * from "./glob.ts"; diff --git a/vendor/deno.land/std@0.178.0/path/posix.ts b/vendor/deno.land/std@0.178.0/path/posix.ts new file mode 100644 index 0000000..472ccaf --- /dev/null +++ b/vendor/deno.land/std@0.178.0/path/posix.ts @@ -0,0 +1,487 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. +// Copyright the Browserify authors. MIT License. +// Ported from https://github.com/browserify/path-browserify/ +// This module is browser compatible. + +import type { FormatInputPathObject, ParsedPath } from "./_interface.ts"; +import { CHAR_DOT } from "./_constants.ts"; + +import { + _format, + assertPath, + encodeWhitespace, + isPosixPathSeparator, + lastPathSegment, + normalizeString, + stripSuffix, + stripTrailingSeparators, +} from "./_util.ts"; + +export const sep = "/"; +export const delimiter = ":"; + +// path.resolve([from ...], to) +/** + * Resolves `pathSegments` into an absolute path. + * @param pathSegments an array of path segments + */ +export function resolve(...pathSegments: string[]): string { + let resolvedPath = ""; + let resolvedAbsolute = false; + + for (let i = pathSegments.length - 1; i >= -1 && !resolvedAbsolute; i--) { + let path: string; + + if (i >= 0) path = pathSegments[i]; + else { + // deno-lint-ignore no-explicit-any + const { Deno } = globalThis as any; + if (typeof Deno?.cwd !== "function") { + throw new TypeError("Resolved a relative path without a CWD."); + } + path = Deno.cwd(); + } + + assertPath(path); + + // Skip empty entries + if (path.length === 0) { + continue; + } + + resolvedPath = `${path}/${resolvedPath}`; + resolvedAbsolute = isPosixPathSeparator(path.charCodeAt(0)); + } + + // At this point the path should be resolved to a full absolute path, but + // handle relative paths to be safe (might happen when process.cwd() fails) + + // Normalize the path + resolvedPath = normalizeString( + resolvedPath, + !resolvedAbsolute, + "/", + isPosixPathSeparator, + ); + + if (resolvedAbsolute) { + if (resolvedPath.length > 0) return `/${resolvedPath}`; + else return "/"; + } else if (resolvedPath.length > 0) return resolvedPath; + else return "."; +} + +/** + * Normalize the `path`, resolving `'..'` and `'.'` segments. + * Note that resolving these segments does not necessarily mean that all will be eliminated. + * A `'..'` at the top-level will be preserved, and an empty path is canonically `'.'`. + * @param path to be normalized + */ +export function normalize(path: string): string { + assertPath(path); + + if (path.length === 0) return "."; + + const isAbsolute = isPosixPathSeparator(path.charCodeAt(0)); + const trailingSeparator = isPosixPathSeparator( + path.charCodeAt(path.length - 1), + ); + + // Normalize the path + path = normalizeString(path, !isAbsolute, "/", isPosixPathSeparator); + + if (path.length === 0 && !isAbsolute) path = "."; + if (path.length > 0 && trailingSeparator) path += "/"; + + if (isAbsolute) return `/${path}`; + return path; +} + +/** + * Verifies whether provided path is absolute + * @param path to be verified as absolute + */ +export function isAbsolute(path: string): boolean { + assertPath(path); + return path.length > 0 && isPosixPathSeparator(path.charCodeAt(0)); +} + +/** + * Join all given a sequence of `paths`,then normalizes the resulting path. + * @param paths to be joined and normalized + */ +export function join(...paths: string[]): string { + if (paths.length === 0) return "."; + let joined: string | undefined; + for (let i = 0, len = paths.length; i < len; ++i) { + const path = paths[i]; + assertPath(path); + if (path.length > 0) { + if (!joined) joined = path; + else joined += `/${path}`; + } + } + if (!joined) return "."; + return normalize(joined); +} + +/** + * Return the relative path from `from` to `to` based on current working directory. + * @param from path in current working directory + * @param to path in current working directory + */ +export function relative(from: string, to: string): string { + assertPath(from); + assertPath(to); + + if (from === to) return ""; + + from = resolve(from); + to = resolve(to); + + if (from === to) return ""; + + // Trim any leading backslashes + let fromStart = 1; + const fromEnd = from.length; + for (; fromStart < fromEnd; ++fromStart) { + if (!isPosixPathSeparator(from.charCodeAt(fromStart))) break; + } + const fromLen = fromEnd - fromStart; + + // Trim any leading backslashes + let toStart = 1; + const toEnd = to.length; + for (; toStart < toEnd; ++toStart) { + if (!isPosixPathSeparator(to.charCodeAt(toStart))) break; + } + const toLen = toEnd - toStart; + + // Compare paths to find the longest common path from root + const length = fromLen < toLen ? fromLen : toLen; + let lastCommonSep = -1; + let i = 0; + for (; i <= length; ++i) { + if (i === length) { + if (toLen > length) { + if (isPosixPathSeparator(to.charCodeAt(toStart + i))) { + // We get here if `from` is the exact base path for `to`. + // For example: from='/foo/bar'; to='/foo/bar/baz' + return to.slice(toStart + i + 1); + } else if (i === 0) { + // We get here if `from` is the root + // For example: from='/'; to='/foo' + return to.slice(toStart + i); + } + } else if (fromLen > length) { + if (isPosixPathSeparator(from.charCodeAt(fromStart + i))) { + // We get here if `to` is the exact base path for `from`. + // For example: from='/foo/bar/baz'; to='/foo/bar' + lastCommonSep = i; + } else if (i === 0) { + // We get here if `to` is the root. + // For example: from='/foo'; to='/' + lastCommonSep = 0; + } + } + break; + } + const fromCode = from.charCodeAt(fromStart + i); + const toCode = to.charCodeAt(toStart + i); + if (fromCode !== toCode) break; + else if (isPosixPathSeparator(fromCode)) lastCommonSep = i; + } + + let out = ""; + // Generate the relative path based on the path difference between `to` + // and `from` + for (i = fromStart + lastCommonSep + 1; i <= fromEnd; ++i) { + if (i === fromEnd || isPosixPathSeparator(from.charCodeAt(i))) { + if (out.length === 0) out += ".."; + else out += "/.."; + } + } + + // Lastly, append the rest of the destination (`to`) path that comes after + // the common path parts + if (out.length > 0) return out + to.slice(toStart + lastCommonSep); + else { + toStart += lastCommonSep; + if (isPosixPathSeparator(to.charCodeAt(toStart))) ++toStart; + return to.slice(toStart); + } +} + +/** + * Resolves path to a namespace path + * @param path to resolve to namespace + */ +export function toNamespacedPath(path: string): string { + // Non-op on posix systems + return path; +} + +/** + * Return the directory path of a `path`. + * @param path - path to extract the directory from. + */ +export function dirname(path: string): string { + if (path.length === 0) return "."; + + let end = -1; + let matchedNonSeparator = false; + + for (let i = path.length - 1; i >= 1; --i) { + if (isPosixPathSeparator(path.charCodeAt(i))) { + if (matchedNonSeparator) { + end = i; + break; + } + } else { + matchedNonSeparator = true; + } + } + + // No matches. Fallback based on provided path: + // + // - leading slashes paths + // "/foo" => "/" + // "///foo" => "/" + // - no slash path + // "foo" => "." + if (end === -1) { + return isPosixPathSeparator(path.charCodeAt(0)) ? "/" : "."; + } + + return stripTrailingSeparators( + path.slice(0, end), + isPosixPathSeparator, + ); +} + +/** + * Return the last portion of a `path`. + * Trailing directory separators are ignored, and optional suffix is removed. + * + * @param path - path to extract the name from. + * @param [suffix] - suffix to remove from extracted name. + */ +export function basename(path: string, suffix = ""): string { + assertPath(path); + + if (path.length === 0) return path; + + if (typeof suffix !== "string") { + throw new TypeError( + `Suffix must be a string. Received ${JSON.stringify(suffix)}`, + ); + } + + const lastSegment = lastPathSegment(path, isPosixPathSeparator); + const strippedSegment = stripTrailingSeparators( + lastSegment, + isPosixPathSeparator, + ); + return suffix ? stripSuffix(strippedSegment, suffix) : strippedSegment; +} + +/** + * Return the extension of the `path` with leading period. + * @param path with extension + * @returns extension (ex. for `file.ts` returns `.ts`) + */ +export function extname(path: string): string { + assertPath(path); + let startDot = -1; + let startPart = 0; + let end = -1; + let matchedSlash = true; + // Track the state of characters (if any) we see before our first dot and + // after any path separator we find + let preDotState = 0; + for (let i = path.length - 1; i >= 0; --i) { + const code = path.charCodeAt(i); + if (isPosixPathSeparator(code)) { + // If we reached a path separator that was not part of a set of path + // separators at the end of the string, stop now + if (!matchedSlash) { + startPart = i + 1; + break; + } + continue; + } + if (end === -1) { + // We saw the first non-path separator, mark this as the end of our + // extension + matchedSlash = false; + end = i + 1; + } + if (code === CHAR_DOT) { + // If this is our first dot, mark it as the start of our extension + if (startDot === -1) startDot = i; + else if (preDotState !== 1) preDotState = 1; + } else if (startDot !== -1) { + // We saw a non-dot and non-path separator before our dot, so we should + // have a good chance at having a non-empty extension + preDotState = -1; + } + } + + if ( + startDot === -1 || + end === -1 || + // We saw a non-dot character immediately before the dot + preDotState === 0 || + // The (right-most) trimmed path component is exactly '..' + (preDotState === 1 && startDot === end - 1 && startDot === startPart + 1) + ) { + return ""; + } + return path.slice(startDot, end); +} + +/** + * Generate a path from `FormatInputPathObject` object. + * @param pathObject with path + */ +export function format(pathObject: FormatInputPathObject): string { + if (pathObject === null || typeof pathObject !== "object") { + throw new TypeError( + `The "pathObject" argument must be of type Object. Received type ${typeof pathObject}`, + ); + } + return _format("/", pathObject); +} + +/** + * Return a `ParsedPath` object of the `path`. + * @param path to process + */ +export function parse(path: string): ParsedPath { + assertPath(path); + + const ret: ParsedPath = { root: "", dir: "", base: "", ext: "", name: "" }; + if (path.length === 0) return ret; + const isAbsolute = isPosixPathSeparator(path.charCodeAt(0)); + let start: number; + if (isAbsolute) { + ret.root = "/"; + start = 1; + } else { + start = 0; + } + let startDot = -1; + let startPart = 0; + let end = -1; + let matchedSlash = true; + let i = path.length - 1; + + // Track the state of characters (if any) we see before our first dot and + // after any path separator we find + let preDotState = 0; + + // Get non-dir info + for (; i >= start; --i) { + const code = path.charCodeAt(i); + if (isPosixPathSeparator(code)) { + // If we reached a path separator that was not part of a set of path + // separators at the end of the string, stop now + if (!matchedSlash) { + startPart = i + 1; + break; + } + continue; + } + if (end === -1) { + // We saw the first non-path separator, mark this as the end of our + // extension + matchedSlash = false; + end = i + 1; + } + if (code === CHAR_DOT) { + // If this is our first dot, mark it as the start of our extension + if (startDot === -1) startDot = i; + else if (preDotState !== 1) preDotState = 1; + } else if (startDot !== -1) { + // We saw a non-dot and non-path separator before our dot, so we should + // have a good chance at having a non-empty extension + preDotState = -1; + } + } + + if ( + startDot === -1 || + end === -1 || + // We saw a non-dot character immediately before the dot + preDotState === 0 || + // The (right-most) trimmed path component is exactly '..' + (preDotState === 1 && startDot === end - 1 && startDot === startPart + 1) + ) { + if (end !== -1) { + if (startPart === 0 && isAbsolute) { + ret.base = ret.name = path.slice(1, end); + } else { + ret.base = ret.name = path.slice(startPart, end); + } + } + // Fallback to '/' in case there is no basename + ret.base = ret.base || "/"; + } else { + if (startPart === 0 && isAbsolute) { + ret.name = path.slice(1, startDot); + ret.base = path.slice(1, end); + } else { + ret.name = path.slice(startPart, startDot); + ret.base = path.slice(startPart, end); + } + ret.ext = path.slice(startDot, end); + } + + if (startPart > 0) { + ret.dir = stripTrailingSeparators( + path.slice(0, startPart - 1), + isPosixPathSeparator, + ); + } else if (isAbsolute) ret.dir = "/"; + + return ret; +} + +/** + * Converts a file URL to a path string. + * + * ```ts + * import { fromFileUrl } from "https://deno.land/std@$STD_VERSION/path/posix.ts"; + * fromFileUrl("file:///home/foo"); // "/home/foo" + * ``` + * @param url of a file URL + */ +export function fromFileUrl(url: string | URL): string { + url = url instanceof URL ? url : new URL(url); + if (url.protocol != "file:") { + throw new TypeError("Must be a file URL."); + } + return decodeURIComponent( + url.pathname.replace(/%(?![0-9A-Fa-f]{2})/g, "%25"), + ); +} + +/** + * Converts a path string to a file URL. + * + * ```ts + * import { toFileUrl } from "https://deno.land/std@$STD_VERSION/path/posix.ts"; + * toFileUrl("/home/foo"); // new URL("file:///home/foo") + * ``` + * @param path to convert to file URL + */ +export function toFileUrl(path: string): URL { + if (!isAbsolute(path)) { + throw new TypeError("Must be an absolute path."); + } + const url = new URL("file:///"); + url.pathname = encodeWhitespace( + path.replace(/%/g, "%25").replace(/\\/g, "%5C"), + ); + return url; +} diff --git a/vendor/deno.land/std@0.178.0/path/separator.ts b/vendor/deno.land/std@0.178.0/path/separator.ts new file mode 100644 index 0000000..99e4dff --- /dev/null +++ b/vendor/deno.land/std@0.178.0/path/separator.ts @@ -0,0 +1,7 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. +// This module is browser compatible. + +import { isWindows } from "../_util/os.ts"; + +export const SEP = isWindows ? "\\" : "/"; +export const SEP_PATTERN = isWindows ? /[\\/]+/ : /\/+/; diff --git a/vendor/deno.land/std@0.178.0/path/win32.ts b/vendor/deno.land/std@0.178.0/path/win32.ts new file mode 100644 index 0000000..de4bd58 --- /dev/null +++ b/vendor/deno.land/std@0.178.0/path/win32.ts @@ -0,0 +1,962 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. +// Copyright the Browserify authors. MIT License. +// Ported from https://github.com/browserify/path-browserify/ +// This module is browser compatible. + +import type { FormatInputPathObject, ParsedPath } from "./_interface.ts"; +import { + CHAR_BACKWARD_SLASH, + CHAR_COLON, + CHAR_DOT, + CHAR_QUESTION_MARK, +} from "./_constants.ts"; + +import { + _format, + assertPath, + encodeWhitespace, + isPathSeparator, + isPosixPathSeparator, + isWindowsDeviceRoot, + lastPathSegment, + normalizeString, + stripSuffix, + stripTrailingSeparators, +} from "./_util.ts"; +import { assert } from "../_util/asserts.ts"; + +export const sep = "\\"; +export const delimiter = ";"; + +/** + * Resolves path segments into a `path` + * @param pathSegments to process to path + */ +export function resolve(...pathSegments: string[]): string { + let resolvedDevice = ""; + let resolvedTail = ""; + let resolvedAbsolute = false; + + for (let i = pathSegments.length - 1; i >= -1; i--) { + let path: string; + // deno-lint-ignore no-explicit-any + const { Deno } = globalThis as any; + if (i >= 0) { + path = pathSegments[i]; + } else if (!resolvedDevice) { + if (typeof Deno?.cwd !== "function") { + throw new TypeError("Resolved a drive-letter-less path without a CWD."); + } + path = Deno.cwd(); + } else { + if ( + typeof Deno?.env?.get !== "function" || typeof Deno?.cwd !== "function" + ) { + throw new TypeError("Resolved a relative path without a CWD."); + } + path = Deno.cwd(); + + // Verify that a cwd was found and that it actually points + // to our drive. If not, default to the drive's root. + if ( + path === undefined || + path.slice(0, 3).toLowerCase() !== `${resolvedDevice.toLowerCase()}\\` + ) { + path = `${resolvedDevice}\\`; + } + } + + assertPath(path); + + const len = path.length; + + // Skip empty entries + if (len === 0) continue; + + let rootEnd = 0; + let device = ""; + let isAbsolute = false; + const code = path.charCodeAt(0); + + // Try to match a root + if (len > 1) { + if (isPathSeparator(code)) { + // Possible UNC root + + // If we started with a separator, we know we at least have an + // absolute path of some kind (UNC or otherwise) + isAbsolute = true; + + if (isPathSeparator(path.charCodeAt(1))) { + // Matched double path separator at beginning + let j = 2; + let last = j; + // Match 1 or more non-path separators + for (; j < len; ++j) { + if (isPathSeparator(path.charCodeAt(j))) break; + } + if (j < len && j !== last) { + const firstPart = path.slice(last, j); + // Matched! + last = j; + // Match 1 or more path separators + for (; j < len; ++j) { + if (!isPathSeparator(path.charCodeAt(j))) break; + } + if (j < len && j !== last) { + // Matched! + last = j; + // Match 1 or more non-path separators + for (; j < len; ++j) { + if (isPathSeparator(path.charCodeAt(j))) break; + } + if (j === len) { + // We matched a UNC root only + device = `\\\\${firstPart}\\${path.slice(last)}`; + rootEnd = j; + } else if (j !== last) { + // We matched a UNC root with leftovers + + device = `\\\\${firstPart}\\${path.slice(last, j)}`; + rootEnd = j; + } + } + } + } else { + rootEnd = 1; + } + } else if (isWindowsDeviceRoot(code)) { + // Possible device root + + if (path.charCodeAt(1) === CHAR_COLON) { + device = path.slice(0, 2); + rootEnd = 2; + if (len > 2) { + if (isPathSeparator(path.charCodeAt(2))) { + // Treat separator following drive name as an absolute path + // indicator + isAbsolute = true; + rootEnd = 3; + } + } + } + } + } else if (isPathSeparator(code)) { + // `path` contains just a path separator + rootEnd = 1; + isAbsolute = true; + } + + if ( + device.length > 0 && + resolvedDevice.length > 0 && + device.toLowerCase() !== resolvedDevice.toLowerCase() + ) { + // This path points to another device so it is not applicable + continue; + } + + if (resolvedDevice.length === 0 && device.length > 0) { + resolvedDevice = device; + } + if (!resolvedAbsolute) { + resolvedTail = `${path.slice(rootEnd)}\\${resolvedTail}`; + resolvedAbsolute = isAbsolute; + } + + if (resolvedAbsolute && resolvedDevice.length > 0) break; + } + + // At this point the path should be resolved to a full absolute path, + // but handle relative paths to be safe (might happen when process.cwd() + // fails) + + // Normalize the tail path + resolvedTail = normalizeString( + resolvedTail, + !resolvedAbsolute, + "\\", + isPathSeparator, + ); + + return resolvedDevice + (resolvedAbsolute ? "\\" : "") + resolvedTail || "."; +} + +/** + * Normalizes a `path` + * @param path to normalize + */ +export function normalize(path: string): string { + assertPath(path); + const len = path.length; + if (len === 0) return "."; + let rootEnd = 0; + let device: string | undefined; + let isAbsolute = false; + const code = path.charCodeAt(0); + + // Try to match a root + if (len > 1) { + if (isPathSeparator(code)) { + // Possible UNC root + + // If we started with a separator, we know we at least have an absolute + // path of some kind (UNC or otherwise) + isAbsolute = true; + + if (isPathSeparator(path.charCodeAt(1))) { + // Matched double path separator at beginning + let j = 2; + let last = j; + // Match 1 or more non-path separators + for (; j < len; ++j) { + if (isPathSeparator(path.charCodeAt(j))) break; + } + if (j < len && j !== last) { + const firstPart = path.slice(last, j); + // Matched! + last = j; + // Match 1 or more path separators + for (; j < len; ++j) { + if (!isPathSeparator(path.charCodeAt(j))) break; + } + if (j < len && j !== last) { + // Matched! + last = j; + // Match 1 or more non-path separators + for (; j < len; ++j) { + if (isPathSeparator(path.charCodeAt(j))) break; + } + if (j === len) { + // We matched a UNC root only + // Return the normalized version of the UNC root since there + // is nothing left to process + + return `\\\\${firstPart}\\${path.slice(last)}\\`; + } else if (j !== last) { + // We matched a UNC root with leftovers + + device = `\\\\${firstPart}\\${path.slice(last, j)}`; + rootEnd = j; + } + } + } + } else { + rootEnd = 1; + } + } else if (isWindowsDeviceRoot(code)) { + // Possible device root + + if (path.charCodeAt(1) === CHAR_COLON) { + device = path.slice(0, 2); + rootEnd = 2; + if (len > 2) { + if (isPathSeparator(path.charCodeAt(2))) { + // Treat separator following drive name as an absolute path + // indicator + isAbsolute = true; + rootEnd = 3; + } + } + } + } + } else if (isPathSeparator(code)) { + // `path` contains just a path separator, exit early to avoid unnecessary + // work + return "\\"; + } + + let tail: string; + if (rootEnd < len) { + tail = normalizeString( + path.slice(rootEnd), + !isAbsolute, + "\\", + isPathSeparator, + ); + } else { + tail = ""; + } + if (tail.length === 0 && !isAbsolute) tail = "."; + if (tail.length > 0 && isPathSeparator(path.charCodeAt(len - 1))) { + tail += "\\"; + } + if (device === undefined) { + if (isAbsolute) { + if (tail.length > 0) return `\\${tail}`; + else return "\\"; + } else if (tail.length > 0) { + return tail; + } else { + return ""; + } + } else if (isAbsolute) { + if (tail.length > 0) return `${device}\\${tail}`; + else return `${device}\\`; + } else if (tail.length > 0) { + return device + tail; + } else { + return device; + } +} + +/** + * Verifies whether path is absolute + * @param path to verify + */ +export function isAbsolute(path: string): boolean { + assertPath(path); + const len = path.length; + if (len === 0) return false; + + const code = path.charCodeAt(0); + if (isPathSeparator(code)) { + return true; + } else if (isWindowsDeviceRoot(code)) { + // Possible device root + + if (len > 2 && path.charCodeAt(1) === CHAR_COLON) { + if (isPathSeparator(path.charCodeAt(2))) return true; + } + } + return false; +} + +/** + * Join all given a sequence of `paths`,then normalizes the resulting path. + * @param paths to be joined and normalized + */ +export function join(...paths: string[]): string { + const pathsCount = paths.length; + if (pathsCount === 0) return "."; + + let joined: string | undefined; + let firstPart: string | null = null; + for (let i = 0; i < pathsCount; ++i) { + const path = paths[i]; + assertPath(path); + if (path.length > 0) { + if (joined === undefined) joined = firstPart = path; + else joined += `\\${path}`; + } + } + + if (joined === undefined) return "."; + + // Make sure that the joined path doesn't start with two slashes, because + // normalize() will mistake it for an UNC path then. + // + // This step is skipped when it is very clear that the user actually + // intended to point at an UNC path. This is assumed when the first + // non-empty string arguments starts with exactly two slashes followed by + // at least one more non-slash character. + // + // Note that for normalize() to treat a path as an UNC path it needs to + // have at least 2 components, so we don't filter for that here. + // This means that the user can use join to construct UNC paths from + // a server name and a share name; for example: + // path.join('//server', 'share') -> '\\\\server\\share\\') + let needsReplace = true; + let slashCount = 0; + assert(firstPart != null); + if (isPathSeparator(firstPart.charCodeAt(0))) { + ++slashCount; + const firstLen = firstPart.length; + if (firstLen > 1) { + if (isPathSeparator(firstPart.charCodeAt(1))) { + ++slashCount; + if (firstLen > 2) { + if (isPathSeparator(firstPart.charCodeAt(2))) ++slashCount; + else { + // We matched a UNC path in the first part + needsReplace = false; + } + } + } + } + } + if (needsReplace) { + // Find any more consecutive slashes we need to replace + for (; slashCount < joined.length; ++slashCount) { + if (!isPathSeparator(joined.charCodeAt(slashCount))) break; + } + + // Replace the slashes if needed + if (slashCount >= 2) joined = `\\${joined.slice(slashCount)}`; + } + + return normalize(joined); +} + +/** + * It will solve the relative path from `from` to `to`, for instance: + * from = 'C:\\orandea\\test\\aaa' + * to = 'C:\\orandea\\impl\\bbb' + * The output of the function should be: '..\\..\\impl\\bbb' + * @param from relative path + * @param to relative path + */ +export function relative(from: string, to: string): string { + assertPath(from); + assertPath(to); + + if (from === to) return ""; + + const fromOrig = resolve(from); + const toOrig = resolve(to); + + if (fromOrig === toOrig) return ""; + + from = fromOrig.toLowerCase(); + to = toOrig.toLowerCase(); + + if (from === to) return ""; + + // Trim any leading backslashes + let fromStart = 0; + let fromEnd = from.length; + for (; fromStart < fromEnd; ++fromStart) { + if (from.charCodeAt(fromStart) !== CHAR_BACKWARD_SLASH) break; + } + // Trim trailing backslashes (applicable to UNC paths only) + for (; fromEnd - 1 > fromStart; --fromEnd) { + if (from.charCodeAt(fromEnd - 1) !== CHAR_BACKWARD_SLASH) break; + } + const fromLen = fromEnd - fromStart; + + // Trim any leading backslashes + let toStart = 0; + let toEnd = to.length; + for (; toStart < toEnd; ++toStart) { + if (to.charCodeAt(toStart) !== CHAR_BACKWARD_SLASH) break; + } + // Trim trailing backslashes (applicable to UNC paths only) + for (; toEnd - 1 > toStart; --toEnd) { + if (to.charCodeAt(toEnd - 1) !== CHAR_BACKWARD_SLASH) break; + } + const toLen = toEnd - toStart; + + // Compare paths to find the longest common path from root + const length = fromLen < toLen ? fromLen : toLen; + let lastCommonSep = -1; + let i = 0; + for (; i <= length; ++i) { + if (i === length) { + if (toLen > length) { + if (to.charCodeAt(toStart + i) === CHAR_BACKWARD_SLASH) { + // We get here if `from` is the exact base path for `to`. + // For example: from='C:\\foo\\bar'; to='C:\\foo\\bar\\baz' + return toOrig.slice(toStart + i + 1); + } else if (i === 2) { + // We get here if `from` is the device root. + // For example: from='C:\\'; to='C:\\foo' + return toOrig.slice(toStart + i); + } + } + if (fromLen > length) { + if (from.charCodeAt(fromStart + i) === CHAR_BACKWARD_SLASH) { + // We get here if `to` is the exact base path for `from`. + // For example: from='C:\\foo\\bar'; to='C:\\foo' + lastCommonSep = i; + } else if (i === 2) { + // We get here if `to` is the device root. + // For example: from='C:\\foo\\bar'; to='C:\\' + lastCommonSep = 3; + } + } + break; + } + const fromCode = from.charCodeAt(fromStart + i); + const toCode = to.charCodeAt(toStart + i); + if (fromCode !== toCode) break; + else if (fromCode === CHAR_BACKWARD_SLASH) lastCommonSep = i; + } + + // We found a mismatch before the first common path separator was seen, so + // return the original `to`. + if (i !== length && lastCommonSep === -1) { + return toOrig; + } + + let out = ""; + if (lastCommonSep === -1) lastCommonSep = 0; + // Generate the relative path based on the path difference between `to` and + // `from` + for (i = fromStart + lastCommonSep + 1; i <= fromEnd; ++i) { + if (i === fromEnd || from.charCodeAt(i) === CHAR_BACKWARD_SLASH) { + if (out.length === 0) out += ".."; + else out += "\\.."; + } + } + + // Lastly, append the rest of the destination (`to`) path that comes after + // the common path parts + if (out.length > 0) { + return out + toOrig.slice(toStart + lastCommonSep, toEnd); + } else { + toStart += lastCommonSep; + if (toOrig.charCodeAt(toStart) === CHAR_BACKWARD_SLASH) ++toStart; + return toOrig.slice(toStart, toEnd); + } +} + +/** + * Resolves path to a namespace path + * @param path to resolve to namespace + */ +export function toNamespacedPath(path: string): string { + // Note: this will *probably* throw somewhere. + if (typeof path !== "string") return path; + if (path.length === 0) return ""; + + const resolvedPath = resolve(path); + + if (resolvedPath.length >= 3) { + if (resolvedPath.charCodeAt(0) === CHAR_BACKWARD_SLASH) { + // Possible UNC root + + if (resolvedPath.charCodeAt(1) === CHAR_BACKWARD_SLASH) { + const code = resolvedPath.charCodeAt(2); + if (code !== CHAR_QUESTION_MARK && code !== CHAR_DOT) { + // Matched non-long UNC root, convert the path to a long UNC path + return `\\\\?\\UNC\\${resolvedPath.slice(2)}`; + } + } + } else if (isWindowsDeviceRoot(resolvedPath.charCodeAt(0))) { + // Possible device root + + if ( + resolvedPath.charCodeAt(1) === CHAR_COLON && + resolvedPath.charCodeAt(2) === CHAR_BACKWARD_SLASH + ) { + // Matched device root, convert the path to a long UNC path + return `\\\\?\\${resolvedPath}`; + } + } + } + + return path; +} + +/** + * Return the directory path of a `path`. + * @param path - path to extract the directory from. + */ +export function dirname(path: string): string { + assertPath(path); + const len = path.length; + if (len === 0) return "."; + let rootEnd = -1; + let end = -1; + let matchedSlash = true; + let offset = 0; + const code = path.charCodeAt(0); + + // Try to match a root + if (len > 1) { + if (isPathSeparator(code)) { + // Possible UNC root + + rootEnd = offset = 1; + + if (isPathSeparator(path.charCodeAt(1))) { + // Matched double path separator at beginning + let j = 2; + let last = j; + // Match 1 or more non-path separators + for (; j < len; ++j) { + if (isPathSeparator(path.charCodeAt(j))) break; + } + if (j < len && j !== last) { + // Matched! + last = j; + // Match 1 or more path separators + for (; j < len; ++j) { + if (!isPathSeparator(path.charCodeAt(j))) break; + } + if (j < len && j !== last) { + // Matched! + last = j; + // Match 1 or more non-path separators + for (; j < len; ++j) { + if (isPathSeparator(path.charCodeAt(j))) break; + } + if (j === len) { + // We matched a UNC root only + return path; + } + if (j !== last) { + // We matched a UNC root with leftovers + + // Offset by 1 to include the separator after the UNC root to + // treat it as a "normal root" on top of a (UNC) root + rootEnd = offset = j + 1; + } + } + } + } + } else if (isWindowsDeviceRoot(code)) { + // Possible device root + + if (path.charCodeAt(1) === CHAR_COLON) { + rootEnd = offset = 2; + if (len > 2) { + if (isPathSeparator(path.charCodeAt(2))) rootEnd = offset = 3; + } + } + } + } else if (isPathSeparator(code)) { + // `path` contains just a path separator, exit early to avoid + // unnecessary work + return path; + } + + for (let i = len - 1; i >= offset; --i) { + if (isPathSeparator(path.charCodeAt(i))) { + if (!matchedSlash) { + end = i; + break; + } + } else { + // We saw the first non-path separator + matchedSlash = false; + } + } + + if (end === -1) { + if (rootEnd === -1) return "."; + else end = rootEnd; + } + return stripTrailingSeparators(path.slice(0, end), isPosixPathSeparator); +} + +/** + * Return the last portion of a `path`. + * Trailing directory separators are ignored, and optional suffix is removed. + * + * @param path - path to extract name from. + * @param [suffix] - suffix to remove from extracted name. + */ +export function basename(path: string, suffix = ""): string { + assertPath(path); + + if (path.length === 0) return path; + + if (typeof suffix !== "string") { + throw new TypeError( + `Suffix must be a string. Received ${JSON.stringify(suffix)}`, + ); + } + + // Check for a drive letter prefix so as not to mistake the following + // path separator as an extra separator at the end of the path that can be + // disregarded + let start = 0; + if (path.length >= 2) { + const drive = path.charCodeAt(0); + if (isWindowsDeviceRoot(drive)) { + if (path.charCodeAt(1) === CHAR_COLON) start = 2; + } + } + + const lastSegment = lastPathSegment(path, isPathSeparator, start); + const strippedSegment = stripTrailingSeparators(lastSegment, isPathSeparator); + return suffix ? stripSuffix(strippedSegment, suffix) : strippedSegment; +} + +/** + * Return the extension of the `path` with leading period. + * @param path with extension + * @returns extension (ex. for `file.ts` returns `.ts`) + */ +export function extname(path: string): string { + assertPath(path); + let start = 0; + let startDot = -1; + let startPart = 0; + let end = -1; + let matchedSlash = true; + // Track the state of characters (if any) we see before our first dot and + // after any path separator we find + let preDotState = 0; + + // Check for a drive letter prefix so as not to mistake the following + // path separator as an extra separator at the end of the path that can be + // disregarded + + if ( + path.length >= 2 && + path.charCodeAt(1) === CHAR_COLON && + isWindowsDeviceRoot(path.charCodeAt(0)) + ) { + start = startPart = 2; + } + + for (let i = path.length - 1; i >= start; --i) { + const code = path.charCodeAt(i); + if (isPathSeparator(code)) { + // If we reached a path separator that was not part of a set of path + // separators at the end of the string, stop now + if (!matchedSlash) { + startPart = i + 1; + break; + } + continue; + } + if (end === -1) { + // We saw the first non-path separator, mark this as the end of our + // extension + matchedSlash = false; + end = i + 1; + } + if (code === CHAR_DOT) { + // If this is our first dot, mark it as the start of our extension + if (startDot === -1) startDot = i; + else if (preDotState !== 1) preDotState = 1; + } else if (startDot !== -1) { + // We saw a non-dot and non-path separator before our dot, so we should + // have a good chance at having a non-empty extension + preDotState = -1; + } + } + + if ( + startDot === -1 || + end === -1 || + // We saw a non-dot character immediately before the dot + preDotState === 0 || + // The (right-most) trimmed path component is exactly '..' + (preDotState === 1 && startDot === end - 1 && startDot === startPart + 1) + ) { + return ""; + } + return path.slice(startDot, end); +} + +/** + * Generate a path from `FormatInputPathObject` object. + * @param pathObject with path + */ +export function format(pathObject: FormatInputPathObject): string { + if (pathObject === null || typeof pathObject !== "object") { + throw new TypeError( + `The "pathObject" argument must be of type Object. Received type ${typeof pathObject}`, + ); + } + return _format("\\", pathObject); +} + +/** + * Return a `ParsedPath` object of the `path`. + * @param path to process + */ +export function parse(path: string): ParsedPath { + assertPath(path); + + const ret: ParsedPath = { root: "", dir: "", base: "", ext: "", name: "" }; + + const len = path.length; + if (len === 0) return ret; + + let rootEnd = 0; + let code = path.charCodeAt(0); + + // Try to match a root + if (len > 1) { + if (isPathSeparator(code)) { + // Possible UNC root + + rootEnd = 1; + if (isPathSeparator(path.charCodeAt(1))) { + // Matched double path separator at beginning + let j = 2; + let last = j; + // Match 1 or more non-path separators + for (; j < len; ++j) { + if (isPathSeparator(path.charCodeAt(j))) break; + } + if (j < len && j !== last) { + // Matched! + last = j; + // Match 1 or more path separators + for (; j < len; ++j) { + if (!isPathSeparator(path.charCodeAt(j))) break; + } + if (j < len && j !== last) { + // Matched! + last = j; + // Match 1 or more non-path separators + for (; j < len; ++j) { + if (isPathSeparator(path.charCodeAt(j))) break; + } + if (j === len) { + // We matched a UNC root only + + rootEnd = j; + } else if (j !== last) { + // We matched a UNC root with leftovers + + rootEnd = j + 1; + } + } + } + } + } else if (isWindowsDeviceRoot(code)) { + // Possible device root + + if (path.charCodeAt(1) === CHAR_COLON) { + rootEnd = 2; + if (len > 2) { + if (isPathSeparator(path.charCodeAt(2))) { + if (len === 3) { + // `path` contains just a drive root, exit early to avoid + // unnecessary work + ret.root = ret.dir = path; + ret.base = "\\"; + return ret; + } + rootEnd = 3; + } + } else { + // `path` contains just a relative drive root, exit early to avoid + // unnecessary work + ret.root = ret.dir = path; + return ret; + } + } + } + } else if (isPathSeparator(code)) { + // `path` contains just a path separator, exit early to avoid + // unnecessary work + ret.root = ret.dir = path; + ret.base = "\\"; + return ret; + } + + if (rootEnd > 0) ret.root = path.slice(0, rootEnd); + + let startDot = -1; + let startPart = rootEnd; + let end = -1; + let matchedSlash = true; + let i = path.length - 1; + + // Track the state of characters (if any) we see before our first dot and + // after any path separator we find + let preDotState = 0; + + // Get non-dir info + for (; i >= rootEnd; --i) { + code = path.charCodeAt(i); + if (isPathSeparator(code)) { + // If we reached a path separator that was not part of a set of path + // separators at the end of the string, stop now + if (!matchedSlash) { + startPart = i + 1; + break; + } + continue; + } + if (end === -1) { + // We saw the first non-path separator, mark this as the end of our + // extension + matchedSlash = false; + end = i + 1; + } + if (code === CHAR_DOT) { + // If this is our first dot, mark it as the start of our extension + if (startDot === -1) startDot = i; + else if (preDotState !== 1) preDotState = 1; + } else if (startDot !== -1) { + // We saw a non-dot and non-path separator before our dot, so we should + // have a good chance at having a non-empty extension + preDotState = -1; + } + } + + if ( + startDot === -1 || + end === -1 || + // We saw a non-dot character immediately before the dot + preDotState === 0 || + // The (right-most) trimmed path component is exactly '..' + (preDotState === 1 && startDot === end - 1 && startDot === startPart + 1) + ) { + if (end !== -1) { + ret.base = ret.name = path.slice(startPart, end); + } + } else { + ret.name = path.slice(startPart, startDot); + ret.base = path.slice(startPart, end); + ret.ext = path.slice(startDot, end); + } + + // Fallback to '\' in case there is no basename + ret.base = ret.base || "\\"; + + // If the directory is the root, use the entire root as the `dir` including + // the trailing slash if any (`C:\abc` -> `C:\`). Otherwise, strip out the + // trailing slash (`C:\abc\def` -> `C:\abc`). + if (startPart > 0 && startPart !== rootEnd) { + ret.dir = path.slice(0, startPart - 1); + } else ret.dir = ret.root; + + return ret; +} + +/** + * Converts a file URL to a path string. + * + * ```ts + * import { fromFileUrl } from "https://deno.land/std@$STD_VERSION/path/win32.ts"; + * fromFileUrl("file:///home/foo"); // "\\home\\foo" + * fromFileUrl("file:///C:/Users/foo"); // "C:\\Users\\foo" + * fromFileUrl("file://localhost/home/foo"); // "\\\\localhost\\home\\foo" + * ``` + * @param url of a file URL + */ +export function fromFileUrl(url: string | URL): string { + url = url instanceof URL ? url : new URL(url); + if (url.protocol != "file:") { + throw new TypeError("Must be a file URL."); + } + let path = decodeURIComponent( + url.pathname.replace(/\//g, "\\").replace(/%(?![0-9A-Fa-f]{2})/g, "%25"), + ).replace(/^\\*([A-Za-z]:)(\\|$)/, "$1\\"); + if (url.hostname != "") { + // Note: The `URL` implementation guarantees that the drive letter and + // hostname are mutually exclusive. Otherwise it would not have been valid + // to append the hostname and path like this. + path = `\\\\${url.hostname}${path}`; + } + return path; +} + +/** + * Converts a path string to a file URL. + * + * ```ts + * import { toFileUrl } from "https://deno.land/std@$STD_VERSION/path/win32.ts"; + * toFileUrl("\\home\\foo"); // new URL("file:///home/foo") + * toFileUrl("C:\\Users\\foo"); // new URL("file:///C:/Users/foo") + * toFileUrl("\\\\127.0.0.1\\home\\foo"); // new URL("file://127.0.0.1/home/foo") + * ``` + * @param path to convert to file URL + */ +export function toFileUrl(path: string): URL { + if (!isAbsolute(path)) { + throw new TypeError("Must be an absolute path."); + } + const [, hostname, pathname] = path.match( + /^(?:[/\\]{2}([^/\\]+)(?=[/\\](?:[^/\\]|$)))?(.*)/, + )!; + const url = new URL("file:///"); + url.pathname = encodeWhitespace(pathname.replace(/%/g, "%25")); + if (hostname != null && hostname != "localhost") { + url.hostname = hostname; + if (!url.hostname) { + throw new TypeError("Invalid hostname."); + } + } + return url; +} diff --git a/vendor/deno.land/std@0.178.0/streams/_common.ts b/vendor/deno.land/std@0.178.0/streams/_common.ts new file mode 100644 index 0000000..7825ba0 --- /dev/null +++ b/vendor/deno.land/std@0.178.0/streams/_common.ts @@ -0,0 +1,26 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. +// This module is browser compatible. + +export const DEFAULT_CHUNK_SIZE = 16_640; +export const DEFAULT_BUFFER_SIZE = 32 * 1024; + +/** Generate longest proper prefix which is also suffix array. */ +export function createLPS(pat: Uint8Array): Uint8Array { + const lps = new Uint8Array(pat.length); + lps[0] = 0; + let prefixEnd = 0; + let i = 1; + while (i < lps.length) { + if (pat[i] == pat[prefixEnd]) { + prefixEnd++; + lps[i] = prefixEnd; + i++; + } else if (prefixEnd === 0) { + lps[i] = 0; + i++; + } else { + prefixEnd = lps[prefixEnd - 1]; + } + } + return lps; +} diff --git a/vendor/deno.land/std@0.178.0/streams/buffer.ts b/vendor/deno.land/std@0.178.0/streams/buffer.ts new file mode 100644 index 0000000..a8b78cb --- /dev/null +++ b/vendor/deno.land/std@0.178.0/streams/buffer.ts @@ -0,0 +1,167 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. +import { assert } from "../_util/asserts.ts"; +import { copy } from "../bytes/copy.ts"; + +const MAX_SIZE = 2 ** 32 - 2; +const DEFAULT_CHUNK_SIZE = 16_640; + +/** A variable-sized buffer of bytes with `read()` and `write()` methods. + * + * Buffer is almost always used with some I/O like files and sockets. It allows + * one to buffer up a download from a socket. Buffer grows and shrinks as + * necessary. + * + * Buffer is NOT the same thing as Node's Buffer. Node's Buffer was created in + * 2009 before JavaScript had the concept of ArrayBuffers. It's simply a + * non-standard ArrayBuffer. + * + * ArrayBuffer is a fixed memory allocation. Buffer is implemented on top of + * ArrayBuffer. + * + * Based on [Go Buffer](https://golang.org/pkg/bytes/#Buffer). */ +export class Buffer { + #buf: Uint8Array; // contents are the bytes buf[off : len(buf)] + #off = 0; // read at buf[off], write at buf[buf.byteLength] + #readable: ReadableStream = new ReadableStream({ + type: "bytes", + pull: (controller) => { + const view = new Uint8Array(controller.byobRequest!.view!.buffer); + if (this.empty()) { + // Buffer is empty, reset to recover space. + this.reset(); + controller.close(); + controller.byobRequest!.respond(0); + return; + } + const nread = copy(this.#buf.subarray(this.#off), view); + this.#off += nread; + controller.byobRequest!.respond(nread); + }, + autoAllocateChunkSize: DEFAULT_CHUNK_SIZE, + }); + get readable() { + return this.#readable; + } + #writable = new WritableStream({ + write: (chunk) => { + const m = this.#grow(chunk.byteLength); + copy(chunk, this.#buf, m); + }, + }); + get writable() { + return this.#writable; + } + + constructor(ab?: ArrayBufferLike | ArrayLike) { + this.#buf = ab === undefined ? new Uint8Array(0) : new Uint8Array(ab); + } + + /** Returns a slice holding the unread portion of the buffer. + * + * The slice is valid for use only until the next buffer modification (that + * is, only until the next call to a method like `read()`, `write()`, + * `reset()`, or `truncate()`). If `options.copy` is false the slice aliases + * the buffer content at least until the next buffer modification, so + * immediate changes to the slice will affect the result of future reads. + */ + bytes(options = { copy: true }): Uint8Array { + if (options.copy === false) return this.#buf.subarray(this.#off); + return this.#buf.slice(this.#off); + } + + /** Returns whether the unread portion of the buffer is empty. */ + empty(): boolean { + return this.#buf.byteLength <= this.#off; + } + + /** A read only number of bytes of the unread portion of the buffer. */ + get length(): number { + return this.#buf.byteLength - this.#off; + } + + /** The read only capacity of the buffer's underlying byte slice, that is, + * the total space allocated for the buffer's data. */ + get capacity(): number { + return this.#buf.buffer.byteLength; + } + + /** Discards all but the first `n` unread bytes from the buffer but + * continues to use the same allocated storage. It throws if `n` is + * negative or greater than the length of the buffer. */ + truncate(n: number) { + if (n === 0) { + this.reset(); + return; + } + if (n < 0 || n > this.length) { + throw Error("bytes.Buffer: truncation out of range"); + } + this.#reslice(this.#off + n); + } + + reset() { + this.#reslice(0); + this.#off = 0; + } + + #tryGrowByReslice(n: number) { + const l = this.#buf.byteLength; + if (n <= this.capacity - l) { + this.#reslice(l + n); + return l; + } + return -1; + } + + #reslice(len: number) { + assert(len <= this.#buf.buffer.byteLength); + this.#buf = new Uint8Array(this.#buf.buffer, 0, len); + } + + #grow(n: number) { + const m = this.length; + // If buffer is empty, reset to recover space. + if (m === 0 && this.#off !== 0) { + this.reset(); + } + // Fast: Try to grow by means of a reslice. + const i = this.#tryGrowByReslice(n); + if (i >= 0) { + return i; + } + const c = this.capacity; + if (n <= Math.floor(c / 2) - m) { + // We can slide things down instead of allocating a new + // ArrayBuffer. We only need m+n <= c to slide, but + // we instead let capacity get twice as large so we + // don't spend all our time copying. + copy(this.#buf.subarray(this.#off), this.#buf); + } else if (c + n > MAX_SIZE) { + throw new Error("The buffer cannot be grown beyond the maximum size."); + } else { + // Not enough space anywhere, we need to allocate. + const buf = new Uint8Array(Math.min(2 * c + n, MAX_SIZE)); + copy(this.#buf.subarray(this.#off), buf); + this.#buf = buf; + } + // Restore this.#off and len(this.#buf). + this.#off = 0; + this.#reslice(Math.min(m + n, MAX_SIZE)); + return m; + } + + /** Grows the buffer's capacity, if necessary, to guarantee space for + * another `n` bytes. After `.grow(n)`, at least `n` bytes can be written to + * the buffer without another allocation. If `n` is negative, `.grow()` will + * throw. If the buffer can't grow it will throw an error. + * + * Based on Go Lang's + * [Buffer.Grow](https://golang.org/pkg/bytes/#Buffer.Grow). */ + grow(n: number) { + if (n < 0) { + throw Error("Buffer.grow: negative count"); + } + const m = this.#grow(n); + this.#reslice(m); + } +} diff --git a/vendor/deno.land/std@0.178.0/streams/byte_slice_stream.ts b/vendor/deno.land/std@0.178.0/streams/byte_slice_stream.ts new file mode 100644 index 0000000..b6531fd --- /dev/null +++ b/vendor/deno.land/std@0.178.0/streams/byte_slice_stream.ts @@ -0,0 +1,44 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. + +import { assert } from "../_util/asserts.ts"; + +/** + * A transform stream that only transforms from the zero-indexed `start` and `end` bytes (both inclusive). + * + * @example + * ```ts + * import { ByteSliceStream } from "https://deno.land/std@$STD_VERSION/streams/byte_slice_stream.ts"; + * const response = await fetch("https://example.com"); + * const rangedStream = response.body! + * .pipeThrough(new ByteSliceStream(3, 8)); + * ``` + */ +export class ByteSliceStream extends TransformStream { + #offsetStart = 0; + #offsetEnd = 0; + + constructor(start = 0, end = Infinity) { + super({ + start: () => { + assert(start >= 0, "`start` must be greater than 0"); + end += 1; + }, + transform: (chunk, controller) => { + this.#offsetStart = this.#offsetEnd; + this.#offsetEnd += chunk.byteLength; + if (this.#offsetEnd > start) { + if (this.#offsetStart < start) { + chunk = chunk.slice(start - this.#offsetStart); + } + if (this.#offsetEnd >= end) { + chunk = chunk.slice(0, chunk.byteLength - this.#offsetEnd + end); + controller.enqueue(chunk); + controller.terminate(); + } else { + controller.enqueue(chunk); + } + } + }, + }); + } +} diff --git a/vendor/deno.land/std@0.178.0/streams/copy.ts b/vendor/deno.land/std@0.178.0/streams/copy.ts new file mode 100644 index 0000000..4833a9f --- /dev/null +++ b/vendor/deno.land/std@0.178.0/streams/copy.ts @@ -0,0 +1,47 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. + +import { DEFAULT_BUFFER_SIZE } from "./_common.ts"; +import type { Reader, Writer } from "../types.d.ts"; + +/** Copies from `src` to `dst` until either EOF (`null`) is read from `src` or + * an error occurs. It resolves to the number of bytes copied or rejects with + * the first error encountered while copying. + * + * ```ts + * import { copy } from "https://deno.land/std@$STD_VERSION/streams/copy.ts"; + * + * const source = await Deno.open("my_file.txt"); + * const bytesCopied1 = await copy(source, Deno.stdout); + * const destination = await Deno.create("my_file_2.txt"); + * const bytesCopied2 = await copy(source, destination); + * ``` + * + * @param src The source to copy from + * @param dst The destination to copy to + * @param options Can be used to tune size of the buffer. Default size is 32kB + */ +export async function copy( + src: Reader, + dst: Writer, + options?: { + bufSize?: number; + }, +): Promise { + let n = 0; + const bufSize = options?.bufSize ?? DEFAULT_BUFFER_SIZE; + const b = new Uint8Array(bufSize); + let gotEOF = false; + while (gotEOF === false) { + const result = await src.read(b); + if (result === null) { + gotEOF = true; + } else { + let nwritten = 0; + while (nwritten < result) { + nwritten += await dst.write(b.subarray(nwritten, result)); + } + n += nwritten; + } + } + return n; +} diff --git a/vendor/deno.land/std@0.178.0/streams/delimiter_stream.ts b/vendor/deno.land/std@0.178.0/streams/delimiter_stream.ts new file mode 100644 index 0000000..67f4531 --- /dev/null +++ b/vendor/deno.land/std@0.178.0/streams/delimiter_stream.ts @@ -0,0 +1,113 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. + +import { BytesList } from "../bytes/bytes_list.ts"; +import { createLPS } from "./_common.ts"; + +/** Disposition of the delimiter. */ +export type DelimiterDisposition = + /** Include delimiter in the found chunk. */ + | "suffix" + /** Include delimiter in the subsequent chunk. */ + | "prefix" + /** Discard the delimiter. */ + | "discard" // delimiter discarded +; + +export interface DelimiterStreamOptions { + /** Disposition of the delimiter. */ + disposition?: DelimiterDisposition; +} + +/** + * Divide a stream into chunks delimited by a given byte sequence. + * + * @example + * Divide a CSV stream by commas, discarding the commas: + * ```ts + * import { DelimiterStream } from "https://deno.land/std@$STD_VERSION/streams/delimiter_stream.ts"; + * const res = await fetch("https://example.com/data.csv"); + * const parts = res.body! + * .pipeThrough(new DelimiterStream(new TextEncoder().encode(","))) + * .pipeThrough(new TextDecoderStream()); + * ``` + * + * @example + * Divide a stream after semi-colons, keeping the semi-colons in the output: + * ```ts + * import { DelimiterStream } from "https://deno.land/std@$STD_VERSION/streams/delimiter_stream.ts"; + * const res = await fetch("https://example.com/file.js"); + * const parts = res.body! + * .pipeThrough( + * new DelimiterStream( + * new TextEncoder().encode(";"), + * { disposition: "suffix" }, + * ) + * ) + * .pipeThrough(new TextDecoderStream()); + * ``` + * + * @param delimiter Delimiter byte sequence + * @param options Options for the transform stream + * @returns Transform stream + */ +export class DelimiterStream extends TransformStream { + #bufs = new BytesList(); + #delimiter: Uint8Array; + #inspectIndex = 0; + #matchIndex = 0; + #delimLen: number; + #delimLPS: Uint8Array; + #disp?: DelimiterDisposition; + + constructor( + delimiter: Uint8Array, + options?: DelimiterStreamOptions, + ) { + super({ + transform: (chunk, controller) => { + this.#handle(chunk, controller); + }, + flush: (controller) => { + controller.enqueue(this.#bufs.concat()); + }, + }); + + this.#delimiter = delimiter; + this.#delimLen = delimiter.length; + this.#delimLPS = createLPS(delimiter); + this.#disp = options?.disposition ?? "discard"; + } + + #handle( + chunk: Uint8Array, + controller: TransformStreamDefaultController, + ) { + this.#bufs.add(chunk); + let localIndex = 0; + while (this.#inspectIndex < this.#bufs.size()) { + if (chunk[localIndex] === this.#delimiter[this.#matchIndex]) { + this.#inspectIndex++; + localIndex++; + this.#matchIndex++; + if (this.#matchIndex === this.#delimLen) { + // Full match + const start = this.#inspectIndex - this.#delimLen; + const end = this.#disp == "suffix" ? this.#inspectIndex : start; + const copy = this.#bufs.slice(0, end); + controller.enqueue(copy); + const shift = this.#disp == "prefix" ? start : this.#inspectIndex; + this.#bufs.shift(shift); + this.#inspectIndex = this.#disp == "prefix" ? this.#delimLen : 0; + this.#matchIndex = 0; + } + } else { + if (this.#matchIndex === 0) { + this.#inspectIndex++; + localIndex++; + } else { + this.#matchIndex = this.#delimLPS[this.#matchIndex - 1]; + } + } + } + } +} diff --git a/vendor/deno.land/std@0.178.0/streams/early_zip_readable_streams.ts b/vendor/deno.land/std@0.178.0/streams/early_zip_readable_streams.ts new file mode 100644 index 0000000..8aaa6d7 --- /dev/null +++ b/vendor/deno.land/std@0.178.0/streams/early_zip_readable_streams.ts @@ -0,0 +1,33 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. + +/** + * Merge multiple streams into a single one, taking order into account, and each stream + * will wait for a chunk to enqueue before the next stream can append another chunk. + * If a stream ends before other ones, the others will be cancelled. + */ +export function earlyZipReadableStreams( + ...streams: ReadableStream[] +): ReadableStream { + const readers = streams.map((s) => s.getReader()); + return new ReadableStream({ + async start(controller) { + try { + loop: + while (true) { + for (const reader of readers) { + const { value, done } = await reader.read(); + if (!done) { + controller.enqueue(value!); + } else { + await Promise.all(readers.map((reader) => reader.cancel())); + break loop; + } + } + } + controller.close(); + } catch (e) { + controller.error(e); + } + }, + }); +} diff --git a/vendor/deno.land/std@0.178.0/streams/iterate_reader.ts b/vendor/deno.land/std@0.178.0/streams/iterate_reader.ts new file mode 100644 index 0000000..888b241 --- /dev/null +++ b/vendor/deno.land/std@0.178.0/streams/iterate_reader.ts @@ -0,0 +1,101 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. + +import { DEFAULT_BUFFER_SIZE } from "./_common.ts"; +import type { Reader, ReaderSync } from "../types.d.ts"; + +/** Turns a Reader, `r`, into an async iterator. + * + * ```ts + * import { iterateReader } from "https://deno.land/std@$STD_VERSION/streams/iterate_reader.ts"; + * + * let f = await Deno.open("/etc/passwd"); + * for await (const chunk of iterateReader(f)) { + * console.log(chunk); + * } + * f.close(); + * ``` + * + * Second argument can be used to tune size of a buffer. + * Default size of the buffer is 32kB. + * + * ```ts + * import { iterateReader } from "https://deno.land/std@$STD_VERSION/streams/iterate_reader.ts"; + * + * let f = await Deno.open("/etc/passwd"); + * const it = iterateReader(f, { + * bufSize: 1024 * 1024 + * }); + * for await (const chunk of it) { + * console.log(chunk); + * } + * f.close(); + * ``` + */ +export async function* iterateReader( + r: Reader, + options?: { + bufSize?: number; + }, +): AsyncIterableIterator { + const bufSize = options?.bufSize ?? DEFAULT_BUFFER_SIZE; + const b = new Uint8Array(bufSize); + while (true) { + const result = await r.read(b); + if (result === null) { + break; + } + + yield b.slice(0, result); + } +} + +/** Turns a ReaderSync, `r`, into an iterator. + * + * ```ts + * import { iterateReaderSync } from "https://deno.land/std@$STD_VERSION/streams/iterate_reader.ts"; + * + * let f = Deno.openSync("/etc/passwd"); + * for (const chunk of iterateReaderSync(f)) { + * console.log(chunk); + * } + * f.close(); + * ``` + * + * Second argument can be used to tune size of a buffer. + * Default size of the buffer is 32kB. + * + * ```ts + * import { iterateReaderSync } from "https://deno.land/std@$STD_VERSION/streams/iterate_reader.ts"; + + * let f = await Deno.open("/etc/passwd"); + * const iter = iterateReaderSync(f, { + * bufSize: 1024 * 1024 + * }); + * for (const chunk of iter) { + * console.log(chunk); + * } + * f.close(); + * ``` + * + * Iterator uses an internal buffer of fixed size for efficiency; it returns + * a view on that buffer on each iteration. It is therefore caller's + * responsibility to copy contents of the buffer if needed; otherwise the + * next iteration will overwrite contents of previously returned chunk. + */ +export function* iterateReaderSync( + r: ReaderSync, + options?: { + bufSize?: number; + }, +): IterableIterator { + const bufSize = options?.bufSize ?? DEFAULT_BUFFER_SIZE; + const b = new Uint8Array(bufSize); + while (true) { + const result = r.readSync(b); + if (result === null) { + break; + } + + yield b.slice(0, result); + } +} diff --git a/vendor/deno.land/std@0.178.0/streams/limited_bytes_transform_stream.ts b/vendor/deno.land/std@0.178.0/streams/limited_bytes_transform_stream.ts new file mode 100644 index 0000000..ba68e72 --- /dev/null +++ b/vendor/deno.land/std@0.178.0/streams/limited_bytes_transform_stream.ts @@ -0,0 +1,36 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. + +/** A TransformStream that will only read & enqueue `size` amount of bytes. + * This operation is chunk based and not BYOB based, + * and as such will read more than needed. + * + * if options.error is set, then instead of terminating the stream, + * an error will be thrown. + * + * ```ts + * import { LimitedBytesTransformStream } from "https://deno.land/std@$STD_VERSION/streams/limited_bytes_transform_stream.ts"; + * const res = await fetch("https://example.com"); + * const parts = res.body! + * .pipeThrough(new LimitedBytesTransformStream(512 * 1024)); + * ``` + */ +export class LimitedBytesTransformStream + extends TransformStream { + #read = 0; + constructor(size: number, options: { error?: boolean } = {}) { + super({ + transform: (chunk, controller) => { + if ((this.#read + chunk.byteLength) > size) { + if (options.error) { + throw new RangeError(`Exceeded byte size limit of '${size}'`); + } else { + controller.terminate(); + } + } else { + this.#read += chunk.byteLength; + controller.enqueue(chunk); + } + }, + }); + } +} diff --git a/vendor/deno.land/std@0.178.0/streams/limited_transform_stream.ts b/vendor/deno.land/std@0.178.0/streams/limited_transform_stream.ts new file mode 100644 index 0000000..033020b --- /dev/null +++ b/vendor/deno.land/std@0.178.0/streams/limited_transform_stream.ts @@ -0,0 +1,32 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. + +/** A TransformStream that will only read & enqueue `size` amount of chunks. + * + * if options.error is set, then instead of terminating the stream, + * an error will be thrown. + * + * ```ts + * import { LimitedTransformStream } from "https://deno.land/std@$STD_VERSION/streams/limited_transform_stream.ts"; + * const res = await fetch("https://example.com"); + * const parts = res.body!.pipeThrough(new LimitedTransformStream(50)); + * ``` + */ +export class LimitedTransformStream extends TransformStream { + #read = 0; + constructor(size: number, options: { error?: boolean } = {}) { + super({ + transform: (chunk, controller) => { + if ((this.#read + 1) > size) { + if (options.error) { + throw new RangeError(`Exceeded chunk limit of '${size}'`); + } else { + controller.terminate(); + } + } else { + this.#read++; + controller.enqueue(chunk); + } + }, + }); + } +} diff --git a/vendor/deno.land/std@0.178.0/streams/merge_readable_streams.ts b/vendor/deno.land/std@0.178.0/streams/merge_readable_streams.ts new file mode 100644 index 0000000..fc0eb5f --- /dev/null +++ b/vendor/deno.land/std@0.178.0/streams/merge_readable_streams.ts @@ -0,0 +1,33 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. + +import { deferred } from "../async/deferred.ts"; + +/** + * Merge multiple streams into a single one, not taking order into account. + * If a stream ends before other ones, the other will continue adding data, + * and the finished one will not add any more data. + */ +export function mergeReadableStreams( + ...streams: ReadableStream[] +): ReadableStream { + const resolvePromises = streams.map(() => deferred()); + return new ReadableStream({ + start(controller) { + Promise.all(resolvePromises).then(() => { + controller.close(); + }); + try { + for (const [key, stream] of Object.entries(streams)) { + (async () => { + for await (const data of stream) { + controller.enqueue(data); + } + resolvePromises[+key].resolve(); + })(); + } + } catch (e) { + controller.error(e); + } + }, + }); +} diff --git a/vendor/deno.land/std@0.178.0/streams/mod.ts b/vendor/deno.land/std@0.178.0/streams/mod.ts new file mode 100644 index 0000000..b5ba3f6 --- /dev/null +++ b/vendor/deno.land/std@0.178.0/streams/mod.ts @@ -0,0 +1,31 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. +/** + * Utilities for working with the + * [Streams API](https://developer.mozilla.org/en-US/docs/Web/API/Streams_API). + * + * Includes buffering and conversion. + * + * @module + */ + +export * from "./buffer.ts"; +export * from "./byte_slice_stream.ts"; +export * from "./copy.ts"; +export * from "./delimiter_stream.ts"; +export * from "./early_zip_readable_streams.ts"; +export * from "./iterate_reader.ts"; +export * from "./limited_bytes_transform_stream.ts"; +export * from "./limited_transform_stream.ts"; +export * from "./merge_readable_streams.ts"; +export * from "./read_all.ts"; +export * from "./readable_stream_from_iterable.ts"; +export * from "./readable_stream_from_reader.ts"; +export * from "./reader_from_iterable.ts"; +export * from "./reader_from_stream_reader.ts"; +export * from "./text_delimiter_stream.ts"; +export * from "./text_line_stream.ts"; +export * from "./to_transform_stream.ts"; +export * from "./writable_stream_from_writer.ts"; +export * from "./write_all.ts"; +export * from "./writer_from_stream_writer.ts"; +export * from "./zip_readable_streams.ts"; diff --git a/vendor/deno.land/std@0.178.0/streams/read_all.ts b/vendor/deno.land/std@0.178.0/streams/read_all.ts new file mode 100644 index 0000000..8b16bc5 --- /dev/null +++ b/vendor/deno.land/std@0.178.0/streams/read_all.ts @@ -0,0 +1,60 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. + +import { Buffer } from "../io/buffer.ts"; +import type { Reader, ReaderSync } from "../types.d.ts"; + +/** Read Reader `r` until EOF (`null`) and resolve to the content as + * Uint8Array`. + * + * ```ts + * import { Buffer } from "https://deno.land/std@$STD_VERSION/io/buffer.ts"; + * import { readAll } from "https://deno.land/std@$STD_VERSION/streams/read_all.ts"; + * + * // Example from stdin + * const stdinContent = await readAll(Deno.stdin); + * + * // Example from file + * const file = await Deno.open("my_file.txt", {read: true}); + * const myFileContent = await readAll(file); + * file.close(); + * + * // Example from buffer + * const myData = new Uint8Array(100); + * // ... fill myData array with data + * const reader = new Buffer(myData.buffer); + * const bufferContent = await readAll(reader); + * ``` + */ +export async function readAll(r: Reader): Promise { + const buf = new Buffer(); + await buf.readFrom(r); + return buf.bytes(); +} + +/** Synchronously reads Reader `r` until EOF (`null`) and returns the content + * as `Uint8Array`. + * + * ```ts + * import { Buffer } from "https://deno.land/std@$STD_VERSION/io/buffer.ts"; + * import { readAllSync } from "https://deno.land/std@$STD_VERSION/streams/read_all.ts"; + * + * // Example from stdin + * const stdinContent = readAllSync(Deno.stdin); + * + * // Example from file + * const file = Deno.openSync("my_file.txt", {read: true}); + * const myFileContent = readAllSync(file); + * file.close(); + * + * // Example from buffer + * const myData = new Uint8Array(100); + * // ... fill myData array with data + * const reader = new Buffer(myData.buffer); + * const bufferContent = readAllSync(reader); + * ``` + */ +export function readAllSync(r: ReaderSync): Uint8Array { + const buf = new Buffer(); + buf.readFromSync(r); + return buf.bytes(); +} diff --git a/vendor/deno.land/std@0.178.0/streams/readable_stream_from_iterable.ts b/vendor/deno.land/std@0.178.0/streams/readable_stream_from_iterable.ts new file mode 100644 index 0000000..430718f --- /dev/null +++ b/vendor/deno.land/std@0.178.0/streams/readable_stream_from_iterable.ts @@ -0,0 +1,62 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. + +/** Create a `ReadableStream` from any kind of iterable. + * + * ```ts + * import { readableStreamFromIterable } from "https://deno.land/std@$STD_VERSION/streams/readable_stream_from_iterable.ts"; + * + * const r1 = readableStreamFromIterable(["foo, bar, baz"]); + * const r2 = readableStreamFromIterable(async function* () { + * await new Promise(((r) => setTimeout(r, 1000))); + * yield "foo"; + * await new Promise(((r) => setTimeout(r, 1000))); + * yield "bar"; + * await new Promise(((r) => setTimeout(r, 1000))); + * yield "baz"; + * }()); + * ``` + * + * If the produced iterator (`iterable[Symbol.asyncIterator]()` or + * `iterable[Symbol.iterator]()`) is a generator, or more specifically is found + * to have a `.throw()` method on it, that will be called upon + * `readableStream.cancel()`. This is the case for the second input type above: + * + * ```ts + * import { readableStreamFromIterable } from "https://deno.land/std@$STD_VERSION/streams/readable_stream_from_iterable.ts"; + * + * const r3 = readableStreamFromIterable(async function* () { + * try { + * yield "foo"; + * } catch (error) { + * console.log(error); // Error: Cancelled by consumer. + * } + * }()); + * const reader = r3.getReader(); + * console.log(await reader.read()); // { value: "foo", done: false } + * await reader.cancel(new Error("Cancelled by consumer.")); + * ``` + */ +export function readableStreamFromIterable( + iterable: Iterable | AsyncIterable, +): ReadableStream { + const iterator: Iterator | AsyncIterator = + (iterable as AsyncIterable)[Symbol.asyncIterator]?.() ?? + (iterable as Iterable)[Symbol.iterator]?.(); + return new ReadableStream({ + async pull(controller) { + const { value, done } = await iterator.next(); + if (done) { + controller.close(); + } else { + controller.enqueue(value); + } + }, + async cancel(reason) { + if (typeof iterator.throw == "function") { + try { + await iterator.throw(reason); + } catch { /* `iterator.throw()` always throws on site. We catch it. */ } + } + }, + }); +} diff --git a/vendor/deno.land/std@0.178.0/streams/readable_stream_from_reader.ts b/vendor/deno.land/std@0.178.0/streams/readable_stream_from_reader.ts new file mode 100644 index 0000000..32e2b9a --- /dev/null +++ b/vendor/deno.land/std@0.178.0/streams/readable_stream_from_reader.ts @@ -0,0 +1,80 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. + +import { DEFAULT_CHUNK_SIZE } from "./_common.ts"; +import type { Closer, Reader } from "../types.d.ts"; + +function isCloser(value: unknown): value is Closer { + return typeof value === "object" && value != null && "close" in value && + // deno-lint-ignore no-explicit-any + typeof (value as Record)["close"] === "function"; +} + +export interface ReadableStreamFromReaderOptions { + /** If the `reader` is also a `Closer`, automatically close the `reader` + * when `EOF` is encountered, or a read error occurs. + * + * @default {true} + */ + autoClose?: boolean; + + /** The size of chunks to allocate to read, the default is ~16KiB, which is + * the maximum size that Deno operations can currently support. */ + chunkSize?: number; + + /** The queuing strategy to create the `ReadableStream` with. */ + strategy?: { highWaterMark?: number | undefined; size?: undefined }; +} + +/** + * Create a `ReadableStream` from a `Reader`. + * + * When the pull algorithm is called on the stream, a chunk from the reader + * will be read. When `null` is returned from the reader, the stream will be + * closed along with the reader (if it is also a `Closer`). + * + * An example converting a `Deno.FsFile` into a readable stream: + * + * ```ts + * import { readableStreamFromReader } from "https://deno.land/std@$STD_VERSION/streams/readable_stream_from_reader.ts"; + * + * const file = await Deno.open("./file.txt", { read: true }); + * const fileStream = readableStreamFromReader(file); + * ``` + */ +export function readableStreamFromReader( + reader: Reader | (Reader & Closer), + options: ReadableStreamFromReaderOptions = {}, +): ReadableStream { + const { + autoClose = true, + chunkSize = DEFAULT_CHUNK_SIZE, + strategy, + } = options; + + return new ReadableStream({ + async pull(controller) { + const chunk = new Uint8Array(chunkSize); + try { + const read = await reader.read(chunk); + if (read === null) { + if (isCloser(reader) && autoClose) { + reader.close(); + } + controller.close(); + return; + } + controller.enqueue(chunk.subarray(0, read)); + } catch (e) { + controller.error(e); + if (isCloser(reader)) { + reader.close(); + } + } + }, + cancel() { + if (isCloser(reader) && autoClose) { + reader.close(); + } + }, + }, strategy); +} diff --git a/vendor/deno.land/std@0.178.0/streams/reader_from_iterable.ts b/vendor/deno.land/std@0.178.0/streams/reader_from_iterable.ts new file mode 100644 index 0000000..a71c1e1 --- /dev/null +++ b/vendor/deno.land/std@0.178.0/streams/reader_from_iterable.ts @@ -0,0 +1,55 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. + +import { Buffer } from "../io/buffer.ts"; +import { writeAll } from "./write_all.ts"; +import { Reader } from "../types.d.ts"; + +/** Create a `Reader` from an iterable of `Uint8Array`s. + * + * ```ts + * import { readerFromIterable } from "https://deno.land/std@$STD_VERSION/streams/reader_from_iterable.ts"; + * import { copy } from "https://deno.land/std@$STD_VERSION/streams/copy.ts"; + * + * const file = await Deno.open("metrics.txt", { write: true }); + * const reader = readerFromIterable((async function* () { + * while (true) { + * await new Promise((r) => setTimeout(r, 1000)); + * const message = `data: ${JSON.stringify(Deno.metrics())}\n\n`; + * yield new TextEncoder().encode(message); + * } + * })()); + * await copy(reader, file); + * ``` + */ +export function readerFromIterable( + iterable: Iterable | AsyncIterable, +): Reader { + const iterator: Iterator | AsyncIterator = + (iterable as AsyncIterable)[Symbol.asyncIterator]?.() ?? + (iterable as Iterable)[Symbol.iterator]?.(); + const buffer = new Buffer(); + return { + async read(p: Uint8Array): Promise { + if (buffer.length == 0) { + const result = await iterator.next(); + if (result.done) { + return null; + } else { + if (result.value.byteLength <= p.byteLength) { + p.set(result.value); + return result.value.byteLength; + } + p.set(result.value.subarray(0, p.byteLength)); + await writeAll(buffer, result.value.subarray(p.byteLength)); + return p.byteLength; + } + } else { + const n = await buffer.read(p); + if (n == null) { + return this.read(p); + } + return n; + } + }, + }; +} diff --git a/vendor/deno.land/std@0.178.0/streams/reader_from_stream_reader.ts b/vendor/deno.land/std@0.178.0/streams/reader_from_stream_reader.ts new file mode 100644 index 0000000..afbc7be --- /dev/null +++ b/vendor/deno.land/std@0.178.0/streams/reader_from_stream_reader.ts @@ -0,0 +1,42 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. + +import { Buffer } from "../io/buffer.ts"; +import { writeAll } from "./write_all.ts"; +import type { Reader } from "../types.d.ts"; + +/** + * Create a `Reader` from a `ReadableStreamDefaultReader`. + * + * @example + * ```ts + * import { copy } from "https://deno.land/std@$STD_VERSION/streams/copy.ts"; + * import { readerFromStreamReader } from "https://deno.land/std@$STD_VERSION/streams/reader_from_stream_reader.ts"; + * + * const res = await fetch("https://deno.land"); + * const file = await Deno.open("./deno.land.html", { create: true, write: true }); + * + * const reader = readerFromStreamReader(res.body!.getReader()); + * await copy(reader, file); + * file.close(); + * ``` + */ +export function readerFromStreamReader( + streamReader: ReadableStreamDefaultReader, +): Reader { + const buffer = new Buffer(); + + return { + async read(p: Uint8Array): Promise { + if (buffer.empty()) { + const res = await streamReader.read(); + if (res.done) { + return null; // EOF + } + + await writeAll(buffer, res.value); + } + + return buffer.read(p); + }, + }; +} diff --git a/vendor/deno.land/std@0.178.0/streams/text_delimiter_stream.ts b/vendor/deno.land/std@0.178.0/streams/text_delimiter_stream.ts new file mode 100644 index 0000000..a3049b5 --- /dev/null +++ b/vendor/deno.land/std@0.178.0/streams/text_delimiter_stream.ts @@ -0,0 +1,67 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. + +import { createLPS } from "./_common.ts"; + +/** Transform a stream into a stream where each chunk is divided by a given delimiter. + * + * ```ts + * import { TextDelimiterStream } from "https://deno.land/std@$STD_VERSION/streams/text_delimiter_stream.ts"; + * const res = await fetch("https://example.com"); + * const parts = res.body! + * .pipeThrough(new TextDecoderStream()) + * .pipeThrough(new TextDelimiterStream("foo")); + * ``` + */ +export class TextDelimiterStream extends TransformStream { + #buf = ""; + #delimiter: string; + #inspectIndex = 0; + #matchIndex = 0; + #delimLPS: Uint8Array; + + constructor(delimiter: string) { + super({ + transform: (chunk, controller) => { + this.#handle(chunk, controller); + }, + flush: (controller) => { + controller.enqueue(this.#buf); + }, + }); + + this.#delimiter = delimiter; + this.#delimLPS = createLPS(new TextEncoder().encode(delimiter)); + } + + #handle( + chunk: string, + controller: TransformStreamDefaultController, + ) { + this.#buf += chunk; + let localIndex = 0; + while (this.#inspectIndex < this.#buf.length) { + if (chunk[localIndex] === this.#delimiter[this.#matchIndex]) { + this.#inspectIndex++; + localIndex++; + this.#matchIndex++; + if (this.#matchIndex === this.#delimiter.length) { + // Full match + const matchEnd = this.#inspectIndex - this.#delimiter.length; + const readyString = this.#buf.slice(0, matchEnd); + controller.enqueue(readyString); + // Reset match, different from KMP. + this.#buf = this.#buf.slice(this.#inspectIndex); + this.#inspectIndex = 0; + this.#matchIndex = 0; + } + } else { + if (this.#matchIndex === 0) { + this.#inspectIndex++; + localIndex++; + } else { + this.#matchIndex = this.#delimLPS[this.#matchIndex - 1]; + } + } + } + } +} diff --git a/vendor/deno.land/std@0.178.0/streams/text_line_stream.ts b/vendor/deno.land/std@0.178.0/streams/text_line_stream.ts new file mode 100644 index 0000000..fab4d87 --- /dev/null +++ b/vendor/deno.land/std@0.178.0/streams/text_line_stream.ts @@ -0,0 +1,73 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. + +interface TextLineStreamOptions { + /** Allow splitting by solo \r */ + allowCR: boolean; +} + +/** Transform a stream into a stream where each chunk is divided by a newline, + * be it `\n` or `\r\n`. `\r` can be enabled via the `allowCR` option. + * + * ```ts + * import { TextLineStream } from "https://deno.land/std@$STD_VERSION/streams/text_line_stream.ts"; + * const res = await fetch("https://example.com"); + * const lines = res.body! + * .pipeThrough(new TextDecoderStream()) + * .pipeThrough(new TextLineStream()); + * ``` + */ +export class TextLineStream extends TransformStream { + readonly #allowCR: boolean; + #buf = ""; + + constructor(options?: TextLineStreamOptions) { + super({ + transform: (chunk, controller) => this.#handle(chunk, controller), + flush: (controller) => { + if (this.#buf.length > 0) { + if ( + this.#allowCR && + this.#buf[this.#buf.length - 1] === "\r" + ) controller.enqueue(this.#buf.slice(0, -1)); + else controller.enqueue(this.#buf); + } + }, + }); + this.#allowCR = options?.allowCR ?? false; + } + + #handle(chunk: string, controller: TransformStreamDefaultController) { + chunk = this.#buf + chunk; + + for (;;) { + const lfIndex = chunk.indexOf("\n"); + + if (this.#allowCR) { + const crIndex = chunk.indexOf("\r"); + + if ( + crIndex !== -1 && crIndex !== (chunk.length - 1) && + (lfIndex === -1 || (lfIndex - 1) > crIndex) + ) { + controller.enqueue(chunk.slice(0, crIndex)); + chunk = chunk.slice(crIndex + 1); + continue; + } + } + + if (lfIndex !== -1) { + let crOrLfIndex = lfIndex; + if (chunk[lfIndex - 1] === "\r") { + crOrLfIndex--; + } + controller.enqueue(chunk.slice(0, crOrLfIndex)); + chunk = chunk.slice(lfIndex + 1); + continue; + } + + break; + } + + this.#buf = chunk; + } +} diff --git a/vendor/deno.land/std@0.178.0/streams/to_transform_stream.ts b/vendor/deno.land/std@0.178.0/streams/to_transform_stream.ts new file mode 100644 index 0000000..36cabe4 --- /dev/null +++ b/vendor/deno.land/std@0.178.0/streams/to_transform_stream.ts @@ -0,0 +1,74 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. + +/** + * Convert the generator function into a TransformStream. + * + * ```ts + * import { readableStreamFromIterable } from "https://deno.land/std@$STD_VERSION/streams/readable_stream_from_iterable.ts"; + * import { toTransformStream } from "https://deno.land/std@$STD_VERSION/streams/to_transform_stream.ts"; + * + * const readable = readableStreamFromIterable([0, 1, 2]) + * .pipeThrough(toTransformStream(async function* (src) { + * for await (const chunk of src) { + * yield chunk * 100; + * } + * })); + * + * for await (const chunk of readable) { + * console.log(chunk); + * } + * // output: 0, 100, 200 + * ``` + * + * @param transformer A function to transform. + * @param writableStrategy An object that optionally defines a queuing strategy for the stream. + * @param readableStrategy An object that optionally defines a queuing strategy for the stream. + */ +export function toTransformStream( + transformer: (src: ReadableStream) => Iterable | AsyncIterable, + writableStrategy?: QueuingStrategy, + readableStrategy?: QueuingStrategy, +): TransformStream { + const { + writable, + readable, + } = new TransformStream(undefined, writableStrategy); + + const iterable = transformer(readable); + const iterator: Iterator | AsyncIterator = + (iterable as AsyncIterable)[Symbol.asyncIterator]?.() ?? + (iterable as Iterable)[Symbol.iterator]?.(); + return { + writable, + readable: new ReadableStream({ + async pull(controller) { + let result: IteratorResult; + try { + result = await iterator.next(); + } catch (error) { + // Propagate error to stream from iterator + // If the stream status is "errored", it will be thrown, but ignore. + await readable.cancel(error).catch(() => {}); + controller.error(error); + return; + } + if (result.done) { + controller.close(); + return; + } + controller.enqueue(result.value); + }, + async cancel(reason) { + // Propagate cancellation to readable and iterator + if (typeof iterator.throw == "function") { + try { + await iterator.throw(reason); + } catch { + /* `iterator.throw()` always throws on site. We catch it. */ + } + } + await readable.cancel(reason); + }, + }, readableStrategy), + }; +} diff --git a/vendor/deno.land/std@0.178.0/streams/writable_stream_from_writer.ts b/vendor/deno.land/std@0.178.0/streams/writable_stream_from_writer.ts new file mode 100644 index 0000000..aafcd92 --- /dev/null +++ b/vendor/deno.land/std@0.178.0/streams/writable_stream_from_writer.ts @@ -0,0 +1,51 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. + +import { writeAll } from "./write_all.ts"; +import type { Closer, Writer } from "../types.d.ts"; + +function isCloser(value: unknown): value is Closer { + return typeof value === "object" && value != null && "close" in value && + // deno-lint-ignore no-explicit-any + typeof (value as Record)["close"] === "function"; +} + +export interface WritableStreamFromWriterOptions { + /** + * If the `writer` is also a `Closer`, automatically close the `writer` + * when the stream is closed, aborted, or a write error occurs. + * + * @default {true} + */ + autoClose?: boolean; +} + +/** Create a `WritableStream` from a `Writer`. */ +export function writableStreamFromWriter( + writer: Writer, + options: WritableStreamFromWriterOptions = {}, +): WritableStream { + const { autoClose = true } = options; + + return new WritableStream({ + async write(chunk, controller) { + try { + await writeAll(writer, chunk); + } catch (e) { + controller.error(e); + if (isCloser(writer) && autoClose) { + writer.close(); + } + } + }, + close() { + if (isCloser(writer) && autoClose) { + writer.close(); + } + }, + abort() { + if (isCloser(writer) && autoClose) { + writer.close(); + } + }, + }); +} diff --git a/vendor/deno.land/std@0.178.0/streams/write_all.ts b/vendor/deno.land/std@0.178.0/streams/write_all.ts new file mode 100644 index 0000000..a6aaf88 --- /dev/null +++ b/vendor/deno.land/std@0.178.0/streams/write_all.ts @@ -0,0 +1,64 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. + +import type { Writer, WriterSync } from "../types.d.ts"; + +/** Write all the content of the array buffer (`arr`) to the writer (`w`). + * + * ```ts + * import { Buffer } from "https://deno.land/std@$STD_VERSION/io/buffer.ts"; + * import { writeAll } from "https://deno.land/std@$STD_VERSION/streams/write_all.ts"; + + * // Example writing to stdout + * let contentBytes = new TextEncoder().encode("Hello World"); + * await writeAll(Deno.stdout, contentBytes); + * + * // Example writing to file + * contentBytes = new TextEncoder().encode("Hello World"); + * const file = await Deno.open('test.file', {write: true}); + * await writeAll(file, contentBytes); + * file.close(); + * + * // Example writing to buffer + * contentBytes = new TextEncoder().encode("Hello World"); + * const writer = new Buffer(); + * await writeAll(writer, contentBytes); + * console.log(writer.bytes().length); // 11 + * ``` + */ +export async function writeAll(w: Writer, arr: Uint8Array) { + let nwritten = 0; + while (nwritten < arr.length) { + nwritten += await w.write(arr.subarray(nwritten)); + } +} + +/** Synchronously write all the content of the array buffer (`arr`) to the + * writer (`w`). + * + * ```ts + * import { Buffer } from "https://deno.land/std@$STD_VERSION/io/buffer.ts"; + * import { writeAllSync } from "https://deno.land/std@$STD_VERSION/streams/write_all.ts"; + * + * // Example writing to stdout + * let contentBytes = new TextEncoder().encode("Hello World"); + * writeAllSync(Deno.stdout, contentBytes); + * + * // Example writing to file + * contentBytes = new TextEncoder().encode("Hello World"); + * const file = Deno.openSync('test.file', {write: true}); + * writeAllSync(file, contentBytes); + * file.close(); + * + * // Example writing to buffer + * contentBytes = new TextEncoder().encode("Hello World"); + * const writer = new Buffer(); + * writeAllSync(writer, contentBytes); + * console.log(writer.bytes().length); // 11 + * ``` + */ +export function writeAllSync(w: WriterSync, arr: Uint8Array) { + let nwritten = 0; + while (nwritten < arr.length) { + nwritten += w.writeSync(arr.subarray(nwritten)); + } +} diff --git a/vendor/deno.land/std@0.178.0/streams/writer_from_stream_writer.ts b/vendor/deno.land/std@0.178.0/streams/writer_from_stream_writer.ts new file mode 100644 index 0000000..51f6f8a --- /dev/null +++ b/vendor/deno.land/std@0.178.0/streams/writer_from_stream_writer.ts @@ -0,0 +1,34 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. + +import type { Writer } from "../types.d.ts"; + +/** Create a `Writer` from a `WritableStreamDefaultWriter`. + * + * @example + * ```ts + * import { copy } from "https://deno.land/std@$STD_VERSION/streams/copy.ts"; + * import { writerFromStreamWriter } from "https://deno.land/std@$STD_VERSION/streams/writer_from_stream_writer.ts"; + * + * const file = await Deno.open("./deno.land.html", { read: true }); + * + * const writableStream = new WritableStream({ + * write(chunk): void { + * console.log(chunk); + * }, + * }); + * const writer = writerFromStreamWriter(writableStream.getWriter()); + * await copy(file, writer); + * file.close(); + * ``` + */ +export function writerFromStreamWriter( + streamWriter: WritableStreamDefaultWriter, +): Writer { + return { + async write(p: Uint8Array): Promise { + await streamWriter.ready; + await streamWriter.write(p); + return p.length; + }, + }; +} diff --git a/vendor/deno.land/std@0.178.0/streams/zip_readable_streams.ts b/vendor/deno.land/std@0.178.0/streams/zip_readable_streams.ts new file mode 100644 index 0000000..d3fe0de --- /dev/null +++ b/vendor/deno.land/std@0.178.0/streams/zip_readable_streams.ts @@ -0,0 +1,34 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. + +/** + * Merge multiple streams into a single one, taking order into account, and each stream + * will wait for a chunk to enqueue before the next stream can append another chunk. + * If a stream ends before other ones, the others will continue adding data in order, + * and the finished one will not add any more data. + */ +export function zipReadableStreams( + ...streams: ReadableStream[] +): ReadableStream { + const readers = streams.map((s) => s.getReader()); + return new ReadableStream({ + async start(controller) { + try { + let resolved = 0; + while (resolved != streams.length) { + for (const [key, reader] of Object.entries(readers)) { + const { value, done } = await reader.read(); + if (!done) { + controller.enqueue(value!); + } else { + resolved++; + readers.splice(+key, 1); + } + } + } + controller.close(); + } catch (e) { + controller.error(e); + } + }, + }); +} diff --git a/vendor/deno.land/std@0.178.0/types.d.ts b/vendor/deno.land/std@0.178.0/types.d.ts new file mode 100644 index 0000000..aefb653 --- /dev/null +++ b/vendor/deno.land/std@0.178.0/types.d.ts @@ -0,0 +1,89 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. + +/** See the Contributing > Types section in the README for an explanation of this file. */ + +/** An abstract interface which when implemented provides an interface to read bytes into an array buffer asynchronously. */ +export interface Reader { + /** Reads up to `p.byteLength` bytes into `p`. It resolves to the number of + * bytes read (`0` < `n` <= `p.byteLength`) and rejects if any error + * encountered. Even if `read()` resolves to `n` < `p.byteLength`, it may + * use all of `p` as scratch space during the call. If some data is + * available but not `p.byteLength` bytes, `read()` conventionally resolves + * to what is available instead of waiting for more. + * + * When `read()` encounters end-of-file condition, it resolves to EOF + * (`null`). + * + * When `read()` encounters an error, it rejects with an error. + * + * Callers should always process the `n` > `0` bytes returned before + * considering the EOF (`null`). Doing so correctly handles I/O errors that + * happen after reading some bytes and also both of the allowed EOF + * behaviors. + * + * Implementations should not retain a reference to `p`. + * + * Use iterateReader() from https://deno.land/std@$STD_VERSION/streams/iterate_reader.ts to turn a Reader into an + * AsyncIterator. + */ + read(p: Uint8Array): Promise; +} + +/** An abstract interface which when implemented provides an interface to read bytes into an array buffer synchronously. */ +export interface ReaderSync { + /** Reads up to `p.byteLength` bytes into `p`. It resolves to the number + * of bytes read (`0` < `n` <= `p.byteLength`) and rejects if any error + * encountered. Even if `read()` returns `n` < `p.byteLength`, it may use + * all of `p` as scratch space during the call. If some data is available + * but not `p.byteLength` bytes, `read()` conventionally returns what is + * available instead of waiting for more. + * + * When `readSync()` encounters end-of-file condition, it returns EOF + * (`null`). + * + * When `readSync()` encounters an error, it throws with an error. + * + * Callers should always process the `n` > `0` bytes returned before + * considering the EOF (`null`). Doing so correctly handles I/O errors that happen + * after reading some bytes and also both of the allowed EOF behaviors. + * + * Implementations should not retain a reference to `p`. + * + * Use iterateReaderSync() from https://deno.land/std@$STD_VERSION/streams/iterate_reader.ts to turn a ReaderSync + * into an Iterator. + */ + readSync(p: Uint8Array): number | null; +} + +/** An abstract interface which when implemented provides an interface to write bytes from an array buffer to a file/resource asynchronously. */ +export interface Writer { + /** Writes `p.byteLength` bytes from `p` to the underlying data stream. It + * resolves to the number of bytes written from `p` (`0` <= `n` <= + * `p.byteLength`) or reject with the error encountered that caused the + * write to stop early. `write()` must reject with a non-null error if + * would resolve to `n` < `p.byteLength`. `write()` must not modify the + * slice data, even temporarily. + * + * Implementations should not retain a reference to `p`. + */ + write(p: Uint8Array): Promise; +} +/** An abstract interface which when implemented provides an interface to write bytes from an array buffer to a file/resource synchronously. */ +export interface WriterSync { + /** Writes `p.byteLength` bytes from `p` to the underlying data + * stream. It returns the number of bytes written from `p` (`0` <= `n` + * <= `p.byteLength`) and any error encountered that caused the write to + * stop early. `writeSync()` must throw a non-null error if it returns `n` < + * `p.byteLength`. `writeSync()` must not modify the slice data, even + * temporarily. + * + * Implementations should not retain a reference to `p`. + */ + writeSync(p: Uint8Array): number; +} + +/** An abstract interface which when implemented provides an interface to close files/resources that were previously opened. */ +export interface Closer { + /** Closes the resource, "freeing" the backing file/resource. */ + close(): void; +} diff --git a/vendor/deno.land/std@0.189.0/_util/asserts.ts b/vendor/deno.land/std@0.189.0/_util/asserts.ts new file mode 100644 index 0000000..3c5c346 --- /dev/null +++ b/vendor/deno.land/std@0.189.0/_util/asserts.ts @@ -0,0 +1,25 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. +// This module is browser compatible. + +/** + * All internal non-test code, that is files that do not have `test` or `bench` in the name, must use the assertion functions within `_utils/asserts.ts` and not `testing/asserts.ts`. This is to create a separation of concerns between internal and testing assertions. + */ + +export class DenoStdInternalError extends Error { + constructor(message: string) { + super(message); + this.name = "DenoStdInternalError"; + } +} + +/** Make an assertion, if not `true`, then throw. */ +export function assert(expr: unknown, msg = ""): asserts expr { + if (!expr) { + throw new DenoStdInternalError(msg); + } +} + +/** Use this to assert unreachable code. */ +export function unreachable(): never { + throw new DenoStdInternalError("unreachable"); +} diff --git a/vendor/deno.land/std@0.189.0/async/deferred.ts b/vendor/deno.land/std@0.189.0/async/deferred.ts new file mode 100644 index 0000000..cd6c0aa --- /dev/null +++ b/vendor/deno.land/std@0.189.0/async/deferred.ts @@ -0,0 +1,48 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. +// This module is browser compatible. + +// TODO(ry) It'd be better to make Deferred a class that inherits from +// Promise, rather than an interface. This is possible in ES2016, however +// typescript produces broken code when targeting ES5 code. +// See https://github.com/Microsoft/TypeScript/issues/15202 +// At the time of writing, the github issue is closed but the problem remains. +export interface Deferred extends Promise { + readonly state: "pending" | "fulfilled" | "rejected"; + resolve(value?: T | PromiseLike): void; + // deno-lint-ignore no-explicit-any + reject(reason?: any): void; +} + +/** + * Creates a Promise with the `reject` and `resolve` functions placed as methods + * on the promise object itself. + * + * @example + * ```typescript + * import { deferred } from "https://deno.land/std@$STD_VERSION/async/deferred.ts"; + * + * const p = deferred(); + * // ... + * p.resolve(42); + * ``` + */ +export function deferred(): Deferred { + let methods; + let state = "pending"; + const promise = new Promise((resolve, reject) => { + methods = { + async resolve(value: T | PromiseLike) { + await value; + state = "fulfilled"; + resolve(value); + }, + // deno-lint-ignore no-explicit-any + reject(reason?: any) { + state = "rejected"; + reject(reason); + }, + }; + }); + Object.defineProperty(promise, "state", { get: () => state }); + return Object.assign(promise, methods) as Deferred; +} diff --git a/vendor/deno.land/std@0.189.0/bytes/bytes_list.ts b/vendor/deno.land/std@0.189.0/bytes/bytes_list.ts new file mode 100644 index 0000000..da71708 --- /dev/null +++ b/vendor/deno.land/std@0.189.0/bytes/bytes_list.ts @@ -0,0 +1,165 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. +// This module is browser compatible. + +/** + * An abstraction of multiple Uint8Arrays + */ +export class BytesList { + #len = 0; + #chunks: { + value: Uint8Array; + start: number; // start offset from head of chunk + end: number; // end offset from head of chunk + offset: number; // offset of head in all bytes + }[] = []; + constructor() {} + + /** + * Total size of bytes + */ + size() { + return this.#len; + } + /** + * Push bytes with given offset infos + */ + add(value: Uint8Array, start = 0, end = value.byteLength) { + if (value.byteLength === 0 || end - start === 0) { + return; + } + checkRange(start, end, value.byteLength); + this.#chunks.push({ + value, + end, + start, + offset: this.#len, + }); + this.#len += end - start; + } + + /** + * Drop head `n` bytes. + */ + shift(n: number) { + if (n === 0) { + return; + } + if (this.#len <= n) { + this.#chunks = []; + this.#len = 0; + return; + } + const idx = this.getChunkIndex(n); + this.#chunks.splice(0, idx); + const [chunk] = this.#chunks; + if (chunk) { + const diff = n - chunk.offset; + chunk.start += diff; + } + let offset = 0; + for (const chunk of this.#chunks) { + chunk.offset = offset; + offset += chunk.end - chunk.start; + } + this.#len = offset; + } + + /** + * Find chunk index in which `pos` locates by binary-search + * returns -1 if out of range + */ + getChunkIndex(pos: number): number { + let max = this.#chunks.length; + let min = 0; + while (true) { + const i = min + Math.floor((max - min) / 2); + if (i < 0 || this.#chunks.length <= i) { + return -1; + } + const { offset, start, end } = this.#chunks[i]; + const len = end - start; + if (offset <= pos && pos < offset + len) { + return i; + } else if (offset + len <= pos) { + min = i + 1; + } else { + max = i - 1; + } + } + } + + /** + * Get indexed byte from chunks + */ + get(i: number): number { + if (i < 0 || this.#len <= i) { + throw new Error("out of range"); + } + const idx = this.getChunkIndex(i); + const { value, offset, start } = this.#chunks[idx]; + return value[start + i - offset]; + } + + /** + * Iterator of bytes from given position + */ + *iterator(start = 0): IterableIterator { + const startIdx = this.getChunkIndex(start); + if (startIdx < 0) return; + const first = this.#chunks[startIdx]; + let firstOffset = start - first.offset; + for (let i = startIdx; i < this.#chunks.length; i++) { + const chunk = this.#chunks[i]; + for (let j = chunk.start + firstOffset; j < chunk.end; j++) { + yield chunk.value[j]; + } + firstOffset = 0; + } + } + + /** + * Returns subset of bytes copied + */ + slice(start: number, end: number = this.#len): Uint8Array { + if (end === start) { + return new Uint8Array(); + } + checkRange(start, end, this.#len); + const result = new Uint8Array(end - start); + const startIdx = this.getChunkIndex(start); + const endIdx = this.getChunkIndex(end - 1); + let written = 0; + for (let i = startIdx; i <= endIdx; i++) { + const { + value: chunkValue, + start: chunkStart, + end: chunkEnd, + offset: chunkOffset, + } = this.#chunks[i]; + const readStart = chunkStart + (i === startIdx ? start - chunkOffset : 0); + const readEnd = i === endIdx ? end - chunkOffset + chunkStart : chunkEnd; + const len = readEnd - readStart; + result.set(chunkValue.subarray(readStart, readEnd), written); + written += len; + } + return result; + } + /** + * Concatenate chunks into single Uint8Array copied. + */ + concat(): Uint8Array { + const result = new Uint8Array(this.#len); + let sum = 0; + for (const { value, start, end } of this.#chunks) { + result.set(value.subarray(start, end), sum); + sum += end - start; + } + return result; + } +} + +function checkRange(start: number, end: number, len: number) { + if (start < 0 || len < start || end < 0 || len < end || end < start) { + throw new Error("invalid range"); + } +} diff --git a/vendor/deno.land/std@0.189.0/bytes/copy.ts b/vendor/deno.land/std@0.189.0/bytes/copy.ts new file mode 100644 index 0000000..e92c98d --- /dev/null +++ b/vendor/deno.land/std@0.189.0/bytes/copy.ts @@ -0,0 +1,38 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. +// This module is browser compatible. + +/** Copy bytes from the `src` array to the `dst` array. Returns the number of + * bytes copied. + * + * If the `src` array is larger than what the `dst` array can hold, only the + * amount of bytes that fit in the `dst` array are copied. + * + * An offset can be specified as the third argument that begins the copy at + * that given index in the `dst` array. The offset defaults to the beginning of + * the array. + * + * ```ts + * import { copy } from "https://deno.land/std@$STD_VERSION/bytes/copy.ts"; + * const src = new Uint8Array([9, 8, 7]); + * const dst = new Uint8Array([0, 1, 2, 3, 4, 5]); + * console.log(copy(src, dst)); // 3 + * console.log(dst); // [9, 8, 7, 3, 4, 5] + * ``` + * + * ```ts + * import { copy } from "https://deno.land/std@$STD_VERSION/bytes/copy.ts"; + * const src = new Uint8Array([1, 1, 1, 1]); + * const dst = new Uint8Array([0, 0, 0, 0]); + * console.log(copy(src, dst, 1)); // 3 + * console.log(dst); // [0, 1, 1, 1] + * ``` + */ +export function copy(src: Uint8Array, dst: Uint8Array, off = 0): number { + off = Math.max(0, Math.min(off, dst.byteLength)); + const dstBytesAvailable = dst.byteLength - off; + if (src.byteLength > dstBytesAvailable) { + src = src.subarray(0, dstBytesAvailable); + } + dst.set(src, off); + return src.byteLength; +} diff --git a/vendor/deno.land/std@0.189.0/io/buffer.ts b/vendor/deno.land/std@0.189.0/io/buffer.ts new file mode 100644 index 0000000..e24ed1e --- /dev/null +++ b/vendor/deno.land/std@0.189.0/io/buffer.ts @@ -0,0 +1,249 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. +// This module is browser compatible. + +import { assert } from "../_util/asserts.ts"; +import { copy } from "../bytes/copy.ts"; +import type { Reader, ReaderSync } from "../types.d.ts"; + +// MIN_READ is the minimum ArrayBuffer size passed to a read call by +// buffer.ReadFrom. As long as the Buffer has at least MIN_READ bytes beyond +// what is required to hold the contents of r, readFrom() will not grow the +// underlying buffer. +const MIN_READ = 32 * 1024; +const MAX_SIZE = 2 ** 32 - 2; + +/** A variable-sized buffer of bytes with `read()` and `write()` methods. + * + * Buffer is almost always used with some I/O like files and sockets. It allows + * one to buffer up a download from a socket. Buffer grows and shrinks as + * necessary. + * + * Buffer is NOT the same thing as Node's Buffer. Node's Buffer was created in + * 2009 before JavaScript had the concept of ArrayBuffers. It's simply a + * non-standard ArrayBuffer. + * + * ArrayBuffer is a fixed memory allocation. Buffer is implemented on top of + * ArrayBuffer. + * + * Based on [Go Buffer](https://golang.org/pkg/bytes/#Buffer). */ + +export class Buffer { + #buf: Uint8Array; // contents are the bytes buf[off : len(buf)] + #off = 0; // read at buf[off], write at buf[buf.byteLength] + + constructor(ab?: ArrayBufferLike | ArrayLike) { + this.#buf = ab === undefined ? new Uint8Array(0) : new Uint8Array(ab); + } + + /** Returns a slice holding the unread portion of the buffer. + * + * The slice is valid for use only until the next buffer modification (that + * is, only until the next call to a method like `read()`, `write()`, + * `reset()`, or `truncate()`). If `options.copy` is false the slice aliases the buffer content at + * least until the next buffer modification, so immediate changes to the + * slice will affect the result of future reads. + * @param [options={ copy: true }] + */ + bytes(options = { copy: true }): Uint8Array { + if (options.copy === false) return this.#buf.subarray(this.#off); + return this.#buf.slice(this.#off); + } + + /** Returns whether the unread portion of the buffer is empty. */ + empty(): boolean { + return this.#buf.byteLength <= this.#off; + } + + /** A read only number of bytes of the unread portion of the buffer. */ + get length(): number { + return this.#buf.byteLength - this.#off; + } + + /** The read only capacity of the buffer's underlying byte slice, that is, + * the total space allocated for the buffer's data. */ + get capacity(): number { + return this.#buf.buffer.byteLength; + } + + /** Discards all but the first `n` unread bytes from the buffer but + * continues to use the same allocated storage. It throws if `n` is + * negative or greater than the length of the buffer. */ + truncate(n: number) { + if (n === 0) { + this.reset(); + return; + } + if (n < 0 || n > this.length) { + throw Error("bytes.Buffer: truncation out of range"); + } + this.#reslice(this.#off + n); + } + + reset() { + this.#reslice(0); + this.#off = 0; + } + + #tryGrowByReslice(n: number) { + const l = this.#buf.byteLength; + if (n <= this.capacity - l) { + this.#reslice(l + n); + return l; + } + return -1; + } + + #reslice(len: number) { + assert(len <= this.#buf.buffer.byteLength); + this.#buf = new Uint8Array(this.#buf.buffer, 0, len); + } + + /** Reads the next `p.length` bytes from the buffer or until the buffer is + * drained. Returns the number of bytes read. If the buffer has no data to + * return, the return is EOF (`null`). */ + readSync(p: Uint8Array): number | null { + if (this.empty()) { + // Buffer is empty, reset to recover space. + this.reset(); + if (p.byteLength === 0) { + // this edge case is tested in 'bufferReadEmptyAtEOF' test + return 0; + } + return null; + } + const nread = copy(this.#buf.subarray(this.#off), p); + this.#off += nread; + return nread; + } + + /** Reads the next `p.length` bytes from the buffer or until the buffer is + * drained. Resolves to the number of bytes read. If the buffer has no + * data to return, resolves to EOF (`null`). + * + * NOTE: This methods reads bytes synchronously; it's provided for + * compatibility with `Reader` interfaces. + */ + read(p: Uint8Array): Promise { + const rr = this.readSync(p); + return Promise.resolve(rr); + } + + writeSync(p: Uint8Array): number { + const m = this.#grow(p.byteLength); + return copy(p, this.#buf, m); + } + + /** NOTE: This methods writes bytes synchronously; it's provided for + * compatibility with `Writer` interface. */ + write(p: Uint8Array): Promise { + const n = this.writeSync(p); + return Promise.resolve(n); + } + + #grow(n: number) { + const m = this.length; + // If buffer is empty, reset to recover space. + if (m === 0 && this.#off !== 0) { + this.reset(); + } + // Fast: Try to grow by means of a reslice. + const i = this.#tryGrowByReslice(n); + if (i >= 0) { + return i; + } + const c = this.capacity; + if (n <= Math.floor(c / 2) - m) { + // We can slide things down instead of allocating a new + // ArrayBuffer. We only need m+n <= c to slide, but + // we instead let capacity get twice as large so we + // don't spend all our time copying. + copy(this.#buf.subarray(this.#off), this.#buf); + } else if (c + n > MAX_SIZE) { + throw new Error("The buffer cannot be grown beyond the maximum size."); + } else { + // Not enough space anywhere, we need to allocate. + const buf = new Uint8Array(Math.min(2 * c + n, MAX_SIZE)); + copy(this.#buf.subarray(this.#off), buf); + this.#buf = buf; + } + // Restore this.#off and len(this.#buf). + this.#off = 0; + this.#reslice(Math.min(m + n, MAX_SIZE)); + return m; + } + + /** Grows the buffer's capacity, if necessary, to guarantee space for + * another `n` bytes. After `.grow(n)`, at least `n` bytes can be written to + * the buffer without another allocation. If `n` is negative, `.grow()` will + * throw. If the buffer can't grow it will throw an error. + * + * Based on Go Lang's + * [Buffer.Grow](https://golang.org/pkg/bytes/#Buffer.Grow). */ + grow(n: number) { + if (n < 0) { + throw Error("Buffer.grow: negative count"); + } + const m = this.#grow(n); + this.#reslice(m); + } + + /** Reads data from `r` until EOF (`null`) and appends it to the buffer, + * growing the buffer as needed. It resolves to the number of bytes read. + * If the buffer becomes too large, `.readFrom()` will reject with an error. + * + * Based on Go Lang's + * [Buffer.ReadFrom](https://golang.org/pkg/bytes/#Buffer.ReadFrom). */ + async readFrom(r: Reader): Promise { + let n = 0; + const tmp = new Uint8Array(MIN_READ); + while (true) { + const shouldGrow = this.capacity - this.length < MIN_READ; + // read into tmp buffer if there's not enough room + // otherwise read directly into the internal buffer + const buf = shouldGrow + ? tmp + : new Uint8Array(this.#buf.buffer, this.length); + + const nread = await r.read(buf); + if (nread === null) { + return n; + } + + // write will grow if needed + if (shouldGrow) this.writeSync(buf.subarray(0, nread)); + else this.#reslice(this.length + nread); + + n += nread; + } + } + + /** Reads data from `r` until EOF (`null`) and appends it to the buffer, + * growing the buffer as needed. It returns the number of bytes read. If the + * buffer becomes too large, `.readFromSync()` will throw an error. + * + * Based on Go Lang's + * [Buffer.ReadFrom](https://golang.org/pkg/bytes/#Buffer.ReadFrom). */ + readFromSync(r: ReaderSync): number { + let n = 0; + const tmp = new Uint8Array(MIN_READ); + while (true) { + const shouldGrow = this.capacity - this.length < MIN_READ; + // read into tmp buffer if there's not enough room + // otherwise read directly into the internal buffer + const buf = shouldGrow + ? tmp + : new Uint8Array(this.#buf.buffer, this.length); + + const nread = r.readSync(buf); + if (nread === null) { + return n; + } + + // write will grow if needed + if (shouldGrow) this.writeSync(buf.subarray(0, nread)); + else this.#reslice(this.length + nread); + + n += nread; + } + } +} diff --git a/vendor/deno.land/std@0.189.0/streams/_common.ts b/vendor/deno.land/std@0.189.0/streams/_common.ts new file mode 100644 index 0000000..7825ba0 --- /dev/null +++ b/vendor/deno.land/std@0.189.0/streams/_common.ts @@ -0,0 +1,26 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. +// This module is browser compatible. + +export const DEFAULT_CHUNK_SIZE = 16_640; +export const DEFAULT_BUFFER_SIZE = 32 * 1024; + +/** Generate longest proper prefix which is also suffix array. */ +export function createLPS(pat: Uint8Array): Uint8Array { + const lps = new Uint8Array(pat.length); + lps[0] = 0; + let prefixEnd = 0; + let i = 1; + while (i < lps.length) { + if (pat[i] == pat[prefixEnd]) { + prefixEnd++; + lps[i] = prefixEnd; + i++; + } else if (prefixEnd === 0) { + lps[i] = 0; + i++; + } else { + prefixEnd = lps[prefixEnd - 1]; + } + } + return lps; +} diff --git a/vendor/deno.land/std@0.189.0/streams/buffer.ts b/vendor/deno.land/std@0.189.0/streams/buffer.ts new file mode 100644 index 0000000..74f4633 --- /dev/null +++ b/vendor/deno.land/std@0.189.0/streams/buffer.ts @@ -0,0 +1,169 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. +// This module is browser compatible. + +import { assert } from "../_util/asserts.ts"; +import { copy } from "../bytes/copy.ts"; + +const MAX_SIZE = 2 ** 32 - 2; +const DEFAULT_CHUNK_SIZE = 16_640; + +/** A variable-sized buffer of bytes with `read()` and `write()` methods. + * + * Buffer is almost always used with some I/O like files and sockets. It allows + * one to buffer up a download from a socket. Buffer grows and shrinks as + * necessary. + * + * Buffer is NOT the same thing as Node's Buffer. Node's Buffer was created in + * 2009 before JavaScript had the concept of ArrayBuffers. It's simply a + * non-standard ArrayBuffer. + * + * ArrayBuffer is a fixed memory allocation. Buffer is implemented on top of + * ArrayBuffer. + * + * Based on [Go Buffer](https://golang.org/pkg/bytes/#Buffer). */ +export class Buffer { + #buf: Uint8Array; // contents are the bytes buf[off : len(buf)] + #off = 0; // read at buf[off], write at buf[buf.byteLength] + #readable: ReadableStream = new ReadableStream({ + type: "bytes", + pull: (controller) => { + const view = new Uint8Array(controller.byobRequest!.view!.buffer); + if (this.empty()) { + // Buffer is empty, reset to recover space. + this.reset(); + controller.close(); + controller.byobRequest!.respond(0); + return; + } + const nread = copy(this.#buf.subarray(this.#off), view); + this.#off += nread; + controller.byobRequest!.respond(nread); + }, + autoAllocateChunkSize: DEFAULT_CHUNK_SIZE, + }); + get readable() { + return this.#readable; + } + #writable = new WritableStream({ + write: (chunk) => { + const m = this.#grow(chunk.byteLength); + copy(chunk, this.#buf, m); + }, + }); + get writable() { + return this.#writable; + } + + constructor(ab?: ArrayBufferLike | ArrayLike) { + this.#buf = ab === undefined ? new Uint8Array(0) : new Uint8Array(ab); + } + + /** Returns a slice holding the unread portion of the buffer. + * + * The slice is valid for use only until the next buffer modification (that + * is, only until the next call to a method like `read()`, `write()`, + * `reset()`, or `truncate()`). If `options.copy` is false the slice aliases + * the buffer content at least until the next buffer modification, so + * immediate changes to the slice will affect the result of future reads. + */ + bytes(options = { copy: true }): Uint8Array { + if (options.copy === false) return this.#buf.subarray(this.#off); + return this.#buf.slice(this.#off); + } + + /** Returns whether the unread portion of the buffer is empty. */ + empty(): boolean { + return this.#buf.byteLength <= this.#off; + } + + /** A read only number of bytes of the unread portion of the buffer. */ + get length(): number { + return this.#buf.byteLength - this.#off; + } + + /** The read only capacity of the buffer's underlying byte slice, that is, + * the total space allocated for the buffer's data. */ + get capacity(): number { + return this.#buf.buffer.byteLength; + } + + /** Discards all but the first `n` unread bytes from the buffer but + * continues to use the same allocated storage. It throws if `n` is + * negative or greater than the length of the buffer. */ + truncate(n: number) { + if (n === 0) { + this.reset(); + return; + } + if (n < 0 || n > this.length) { + throw Error("bytes.Buffer: truncation out of range"); + } + this.#reslice(this.#off + n); + } + + reset() { + this.#reslice(0); + this.#off = 0; + } + + #tryGrowByReslice(n: number) { + const l = this.#buf.byteLength; + if (n <= this.capacity - l) { + this.#reslice(l + n); + return l; + } + return -1; + } + + #reslice(len: number) { + assert(len <= this.#buf.buffer.byteLength); + this.#buf = new Uint8Array(this.#buf.buffer, 0, len); + } + + #grow(n: number) { + const m = this.length; + // If buffer is empty, reset to recover space. + if (m === 0 && this.#off !== 0) { + this.reset(); + } + // Fast: Try to grow by means of a reslice. + const i = this.#tryGrowByReslice(n); + if (i >= 0) { + return i; + } + const c = this.capacity; + if (n <= Math.floor(c / 2) - m) { + // We can slide things down instead of allocating a new + // ArrayBuffer. We only need m+n <= c to slide, but + // we instead let capacity get twice as large so we + // don't spend all our time copying. + copy(this.#buf.subarray(this.#off), this.#buf); + } else if (c + n > MAX_SIZE) { + throw new Error("The buffer cannot be grown beyond the maximum size."); + } else { + // Not enough space anywhere, we need to allocate. + const buf = new Uint8Array(Math.min(2 * c + n, MAX_SIZE)); + copy(this.#buf.subarray(this.#off), buf); + this.#buf = buf; + } + // Restore this.#off and len(this.#buf). + this.#off = 0; + this.#reslice(Math.min(m + n, MAX_SIZE)); + return m; + } + + /** Grows the buffer's capacity, if necessary, to guarantee space for + * another `n` bytes. After `.grow(n)`, at least `n` bytes can be written to + * the buffer without another allocation. If `n` is negative, `.grow()` will + * throw. If the buffer can't grow it will throw an error. + * + * Based on Go Lang's + * [Buffer.Grow](https://golang.org/pkg/bytes/#Buffer.Grow). */ + grow(n: number) { + if (n < 0) { + throw Error("Buffer.grow: negative count"); + } + const m = this.#grow(n); + this.#reslice(m); + } +} diff --git a/vendor/deno.land/std@0.189.0/streams/byte_slice_stream.ts b/vendor/deno.land/std@0.189.0/streams/byte_slice_stream.ts new file mode 100644 index 0000000..545d870 --- /dev/null +++ b/vendor/deno.land/std@0.189.0/streams/byte_slice_stream.ts @@ -0,0 +1,45 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. +// This module is browser compatible. + +import { assert } from "../_util/asserts.ts"; + +/** + * A transform stream that only transforms from the zero-indexed `start` and `end` bytes (both inclusive). + * + * @example + * ```ts + * import { ByteSliceStream } from "https://deno.land/std@$STD_VERSION/streams/byte_slice_stream.ts"; + * const response = await fetch("https://example.com"); + * const rangedStream = response.body! + * .pipeThrough(new ByteSliceStream(3, 8)); + * ``` + */ +export class ByteSliceStream extends TransformStream { + #offsetStart = 0; + #offsetEnd = 0; + + constructor(start = 0, end = Infinity) { + super({ + start: () => { + assert(start >= 0, "`start` must be greater than 0"); + end += 1; + }, + transform: (chunk, controller) => { + this.#offsetStart = this.#offsetEnd; + this.#offsetEnd += chunk.byteLength; + if (this.#offsetEnd > start) { + if (this.#offsetStart < start) { + chunk = chunk.slice(start - this.#offsetStart); + } + if (this.#offsetEnd >= end) { + chunk = chunk.slice(0, chunk.byteLength - this.#offsetEnd + end); + controller.enqueue(chunk); + controller.terminate(); + } else { + controller.enqueue(chunk); + } + } + }, + }); + } +} diff --git a/vendor/deno.land/std@0.189.0/streams/copy.ts b/vendor/deno.land/std@0.189.0/streams/copy.ts new file mode 100644 index 0000000..a2bea5c --- /dev/null +++ b/vendor/deno.land/std@0.189.0/streams/copy.ts @@ -0,0 +1,48 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. +// This module is browser compatible. + +import { DEFAULT_BUFFER_SIZE } from "./_common.ts"; +import type { Reader, Writer } from "../types.d.ts"; + +/** Copies from `src` to `dst` until either EOF (`null`) is read from `src` or + * an error occurs. It resolves to the number of bytes copied or rejects with + * the first error encountered while copying. + * + * ```ts + * import { copy } from "https://deno.land/std@$STD_VERSION/streams/copy.ts"; + * + * const source = await Deno.open("my_file.txt"); + * const bytesCopied1 = await copy(source, Deno.stdout); + * const destination = await Deno.create("my_file_2.txt"); + * const bytesCopied2 = await copy(source, destination); + * ``` + * + * @param src The source to copy from + * @param dst The destination to copy to + * @param options Can be used to tune size of the buffer. Default size is 32kB + */ +export async function copy( + src: Reader, + dst: Writer, + options?: { + bufSize?: number; + }, +): Promise { + let n = 0; + const bufSize = options?.bufSize ?? DEFAULT_BUFFER_SIZE; + const b = new Uint8Array(bufSize); + let gotEOF = false; + while (gotEOF === false) { + const result = await src.read(b); + if (result === null) { + gotEOF = true; + } else { + let nwritten = 0; + while (nwritten < result) { + nwritten += await dst.write(b.subarray(nwritten, result)); + } + n += nwritten; + } + } + return n; +} diff --git a/vendor/deno.land/std@0.189.0/streams/delimiter_stream.ts b/vendor/deno.land/std@0.189.0/streams/delimiter_stream.ts new file mode 100644 index 0000000..eb89f2c --- /dev/null +++ b/vendor/deno.land/std@0.189.0/streams/delimiter_stream.ts @@ -0,0 +1,114 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. +// This module is browser compatible. + +import { BytesList } from "../bytes/bytes_list.ts"; +import { createLPS } from "./_common.ts"; + +/** Disposition of the delimiter. */ +export type DelimiterDisposition = + /** Include delimiter in the found chunk. */ + | "suffix" + /** Include delimiter in the subsequent chunk. */ + | "prefix" + /** Discard the delimiter. */ + | "discard" // delimiter discarded +; + +export interface DelimiterStreamOptions { + /** Disposition of the delimiter. */ + disposition?: DelimiterDisposition; +} + +/** + * Divide a stream into chunks delimited by a given byte sequence. + * + * @example + * Divide a CSV stream by commas, discarding the commas: + * ```ts + * import { DelimiterStream } from "https://deno.land/std@$STD_VERSION/streams/delimiter_stream.ts"; + * const res = await fetch("https://example.com/data.csv"); + * const parts = res.body! + * .pipeThrough(new DelimiterStream(new TextEncoder().encode(","))) + * .pipeThrough(new TextDecoderStream()); + * ``` + * + * @example + * Divide a stream after semi-colons, keeping the semi-colons in the output: + * ```ts + * import { DelimiterStream } from "https://deno.land/std@$STD_VERSION/streams/delimiter_stream.ts"; + * const res = await fetch("https://example.com/file.js"); + * const parts = res.body! + * .pipeThrough( + * new DelimiterStream( + * new TextEncoder().encode(";"), + * { disposition: "suffix" }, + * ) + * ) + * .pipeThrough(new TextDecoderStream()); + * ``` + * + * @param delimiter Delimiter byte sequence + * @param options Options for the transform stream + * @returns Transform stream + */ +export class DelimiterStream extends TransformStream { + #bufs = new BytesList(); + #delimiter: Uint8Array; + #inspectIndex = 0; + #matchIndex = 0; + #delimLen: number; + #delimLPS: Uint8Array; + #disp?: DelimiterDisposition; + + constructor( + delimiter: Uint8Array, + options?: DelimiterStreamOptions, + ) { + super({ + transform: (chunk, controller) => { + this.#handle(chunk, controller); + }, + flush: (controller) => { + controller.enqueue(this.#bufs.concat()); + }, + }); + + this.#delimiter = delimiter; + this.#delimLen = delimiter.length; + this.#delimLPS = createLPS(delimiter); + this.#disp = options?.disposition ?? "discard"; + } + + #handle( + chunk: Uint8Array, + controller: TransformStreamDefaultController, + ) { + this.#bufs.add(chunk); + let localIndex = 0; + while (this.#inspectIndex < this.#bufs.size()) { + if (chunk[localIndex] === this.#delimiter[this.#matchIndex]) { + this.#inspectIndex++; + localIndex++; + this.#matchIndex++; + if (this.#matchIndex === this.#delimLen) { + // Full match + const start = this.#inspectIndex - this.#delimLen; + const end = this.#disp == "suffix" ? this.#inspectIndex : start; + const copy = this.#bufs.slice(0, end); + controller.enqueue(copy); + const shift = this.#disp == "prefix" ? start : this.#inspectIndex; + this.#bufs.shift(shift); + this.#inspectIndex = this.#disp == "prefix" ? this.#delimLen : 0; + this.#matchIndex = 0; + } + } else { + if (this.#matchIndex === 0) { + this.#inspectIndex++; + localIndex++; + } else { + this.#matchIndex = this.#delimLPS[this.#matchIndex - 1]; + } + } + } + } +} diff --git a/vendor/deno.land/std@0.189.0/streams/early_zip_readable_streams.ts b/vendor/deno.land/std@0.189.0/streams/early_zip_readable_streams.ts new file mode 100644 index 0000000..fd27a52 --- /dev/null +++ b/vendor/deno.land/std@0.189.0/streams/early_zip_readable_streams.ts @@ -0,0 +1,34 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. +// This module is browser compatible. + +/** + * Merge multiple streams into a single one, taking order into account, and each stream + * will wait for a chunk to enqueue before the next stream can append another chunk. + * If a stream ends before other ones, the others will be cancelled. + */ +export function earlyZipReadableStreams( + ...streams: ReadableStream[] +): ReadableStream { + const readers = streams.map((s) => s.getReader()); + return new ReadableStream({ + async start(controller) { + try { + loop: + while (true) { + for (const reader of readers) { + const { value, done } = await reader.read(); + if (!done) { + controller.enqueue(value!); + } else { + await Promise.all(readers.map((reader) => reader.cancel())); + break loop; + } + } + } + controller.close(); + } catch (e) { + controller.error(e); + } + }, + }); +} diff --git a/vendor/deno.land/std@0.189.0/streams/iterate_reader.ts b/vendor/deno.land/std@0.189.0/streams/iterate_reader.ts new file mode 100644 index 0000000..8d3fe73 --- /dev/null +++ b/vendor/deno.land/std@0.189.0/streams/iterate_reader.ts @@ -0,0 +1,102 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. +// This module is browser compatible. + +import { DEFAULT_BUFFER_SIZE } from "./_common.ts"; +import type { Reader, ReaderSync } from "../types.d.ts"; + +/** Turns a Reader, `r`, into an async iterator. + * + * ```ts + * import { iterateReader } from "https://deno.land/std@$STD_VERSION/streams/iterate_reader.ts"; + * + * let f = await Deno.open("/etc/passwd"); + * for await (const chunk of iterateReader(f)) { + * console.log(chunk); + * } + * f.close(); + * ``` + * + * Second argument can be used to tune size of a buffer. + * Default size of the buffer is 32kB. + * + * ```ts + * import { iterateReader } from "https://deno.land/std@$STD_VERSION/streams/iterate_reader.ts"; + * + * let f = await Deno.open("/etc/passwd"); + * const it = iterateReader(f, { + * bufSize: 1024 * 1024 + * }); + * for await (const chunk of it) { + * console.log(chunk); + * } + * f.close(); + * ``` + */ +export async function* iterateReader( + r: Reader, + options?: { + bufSize?: number; + }, +): AsyncIterableIterator { + const bufSize = options?.bufSize ?? DEFAULT_BUFFER_SIZE; + const b = new Uint8Array(bufSize); + while (true) { + const result = await r.read(b); + if (result === null) { + break; + } + + yield b.slice(0, result); + } +} + +/** Turns a ReaderSync, `r`, into an iterator. + * + * ```ts + * import { iterateReaderSync } from "https://deno.land/std@$STD_VERSION/streams/iterate_reader.ts"; + * + * let f = Deno.openSync("/etc/passwd"); + * for (const chunk of iterateReaderSync(f)) { + * console.log(chunk); + * } + * f.close(); + * ``` + * + * Second argument can be used to tune size of a buffer. + * Default size of the buffer is 32kB. + * + * ```ts + * import { iterateReaderSync } from "https://deno.land/std@$STD_VERSION/streams/iterate_reader.ts"; + + * let f = await Deno.open("/etc/passwd"); + * const iter = iterateReaderSync(f, { + * bufSize: 1024 * 1024 + * }); + * for (const chunk of iter) { + * console.log(chunk); + * } + * f.close(); + * ``` + * + * Iterator uses an internal buffer of fixed size for efficiency; it returns + * a view on that buffer on each iteration. It is therefore caller's + * responsibility to copy contents of the buffer if needed; otherwise the + * next iteration will overwrite contents of previously returned chunk. + */ +export function* iterateReaderSync( + r: ReaderSync, + options?: { + bufSize?: number; + }, +): IterableIterator { + const bufSize = options?.bufSize ?? DEFAULT_BUFFER_SIZE; + const b = new Uint8Array(bufSize); + while (true) { + const result = r.readSync(b); + if (result === null) { + break; + } + + yield b.slice(0, result); + } +} diff --git a/vendor/deno.land/std@0.189.0/streams/limited_bytes_transform_stream.ts b/vendor/deno.land/std@0.189.0/streams/limited_bytes_transform_stream.ts new file mode 100644 index 0000000..6835efb --- /dev/null +++ b/vendor/deno.land/std@0.189.0/streams/limited_bytes_transform_stream.ts @@ -0,0 +1,37 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. +// This module is browser compatible. + +/** A TransformStream that will only read & enqueue `size` amount of bytes. + * This operation is chunk based and not BYOB based, + * and as such will read more than needed. + * + * if options.error is set, then instead of terminating the stream, + * an error will be thrown. + * + * ```ts + * import { LimitedBytesTransformStream } from "https://deno.land/std@$STD_VERSION/streams/limited_bytes_transform_stream.ts"; + * const res = await fetch("https://example.com"); + * const parts = res.body! + * .pipeThrough(new LimitedBytesTransformStream(512 * 1024)); + * ``` + */ +export class LimitedBytesTransformStream + extends TransformStream { + #read = 0; + constructor(size: number, options: { error?: boolean } = {}) { + super({ + transform: (chunk, controller) => { + if ((this.#read + chunk.byteLength) > size) { + if (options.error) { + throw new RangeError(`Exceeded byte size limit of '${size}'`); + } else { + controller.terminate(); + } + } else { + this.#read += chunk.byteLength; + controller.enqueue(chunk); + } + }, + }); + } +} diff --git a/vendor/deno.land/std@0.189.0/streams/limited_transform_stream.ts b/vendor/deno.land/std@0.189.0/streams/limited_transform_stream.ts new file mode 100644 index 0000000..a85303d --- /dev/null +++ b/vendor/deno.land/std@0.189.0/streams/limited_transform_stream.ts @@ -0,0 +1,33 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. +// This module is browser compatible. + +/** A TransformStream that will only read & enqueue `size` amount of chunks. + * + * if options.error is set, then instead of terminating the stream, + * an error will be thrown. + * + * ```ts + * import { LimitedTransformStream } from "https://deno.land/std@$STD_VERSION/streams/limited_transform_stream.ts"; + * const res = await fetch("https://example.com"); + * const parts = res.body!.pipeThrough(new LimitedTransformStream(50)); + * ``` + */ +export class LimitedTransformStream extends TransformStream { + #read = 0; + constructor(size: number, options: { error?: boolean } = {}) { + super({ + transform: (chunk, controller) => { + if ((this.#read + 1) > size) { + if (options.error) { + throw new RangeError(`Exceeded chunk limit of '${size}'`); + } else { + controller.terminate(); + } + } else { + this.#read++; + controller.enqueue(chunk); + } + }, + }); + } +} diff --git a/vendor/deno.land/std@0.189.0/streams/merge_readable_streams.ts b/vendor/deno.land/std@0.189.0/streams/merge_readable_streams.ts new file mode 100644 index 0000000..fc0eb5f --- /dev/null +++ b/vendor/deno.land/std@0.189.0/streams/merge_readable_streams.ts @@ -0,0 +1,33 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. + +import { deferred } from "../async/deferred.ts"; + +/** + * Merge multiple streams into a single one, not taking order into account. + * If a stream ends before other ones, the other will continue adding data, + * and the finished one will not add any more data. + */ +export function mergeReadableStreams( + ...streams: ReadableStream[] +): ReadableStream { + const resolvePromises = streams.map(() => deferred()); + return new ReadableStream({ + start(controller) { + Promise.all(resolvePromises).then(() => { + controller.close(); + }); + try { + for (const [key, stream] of Object.entries(streams)) { + (async () => { + for await (const data of stream) { + controller.enqueue(data); + } + resolvePromises[+key].resolve(); + })(); + } + } catch (e) { + controller.error(e); + } + }, + }); +} diff --git a/vendor/deno.land/std@0.189.0/streams/mod.ts b/vendor/deno.land/std@0.189.0/streams/mod.ts new file mode 100644 index 0000000..b5ba3f6 --- /dev/null +++ b/vendor/deno.land/std@0.189.0/streams/mod.ts @@ -0,0 +1,31 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. +/** + * Utilities for working with the + * [Streams API](https://developer.mozilla.org/en-US/docs/Web/API/Streams_API). + * + * Includes buffering and conversion. + * + * @module + */ + +export * from "./buffer.ts"; +export * from "./byte_slice_stream.ts"; +export * from "./copy.ts"; +export * from "./delimiter_stream.ts"; +export * from "./early_zip_readable_streams.ts"; +export * from "./iterate_reader.ts"; +export * from "./limited_bytes_transform_stream.ts"; +export * from "./limited_transform_stream.ts"; +export * from "./merge_readable_streams.ts"; +export * from "./read_all.ts"; +export * from "./readable_stream_from_iterable.ts"; +export * from "./readable_stream_from_reader.ts"; +export * from "./reader_from_iterable.ts"; +export * from "./reader_from_stream_reader.ts"; +export * from "./text_delimiter_stream.ts"; +export * from "./text_line_stream.ts"; +export * from "./to_transform_stream.ts"; +export * from "./writable_stream_from_writer.ts"; +export * from "./write_all.ts"; +export * from "./writer_from_stream_writer.ts"; +export * from "./zip_readable_streams.ts"; diff --git a/vendor/deno.land/std@0.189.0/streams/read_all.ts b/vendor/deno.land/std@0.189.0/streams/read_all.ts new file mode 100644 index 0000000..76dd0ff --- /dev/null +++ b/vendor/deno.land/std@0.189.0/streams/read_all.ts @@ -0,0 +1,61 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. +// This module is browser compatible. + +import { Buffer } from "../io/buffer.ts"; +import type { Reader, ReaderSync } from "../types.d.ts"; + +/** Read Reader `r` until EOF (`null`) and resolve to the content as + * Uint8Array`. + * + * ```ts + * import { Buffer } from "https://deno.land/std@$STD_VERSION/io/buffer.ts"; + * import { readAll } from "https://deno.land/std@$STD_VERSION/streams/read_all.ts"; + * + * // Example from stdin + * const stdinContent = await readAll(Deno.stdin); + * + * // Example from file + * const file = await Deno.open("my_file.txt", {read: true}); + * const myFileContent = await readAll(file); + * file.close(); + * + * // Example from buffer + * const myData = new Uint8Array(100); + * // ... fill myData array with data + * const reader = new Buffer(myData.buffer); + * const bufferContent = await readAll(reader); + * ``` + */ +export async function readAll(r: Reader): Promise { + const buf = new Buffer(); + await buf.readFrom(r); + return buf.bytes(); +} + +/** Synchronously reads Reader `r` until EOF (`null`) and returns the content + * as `Uint8Array`. + * + * ```ts + * import { Buffer } from "https://deno.land/std@$STD_VERSION/io/buffer.ts"; + * import { readAllSync } from "https://deno.land/std@$STD_VERSION/streams/read_all.ts"; + * + * // Example from stdin + * const stdinContent = readAllSync(Deno.stdin); + * + * // Example from file + * const file = Deno.openSync("my_file.txt", {read: true}); + * const myFileContent = readAllSync(file); + * file.close(); + * + * // Example from buffer + * const myData = new Uint8Array(100); + * // ... fill myData array with data + * const reader = new Buffer(myData.buffer); + * const bufferContent = readAllSync(reader); + * ``` + */ +export function readAllSync(r: ReaderSync): Uint8Array { + const buf = new Buffer(); + buf.readFromSync(r); + return buf.bytes(); +} diff --git a/vendor/deno.land/std@0.189.0/streams/readable_stream_from_iterable.ts b/vendor/deno.land/std@0.189.0/streams/readable_stream_from_iterable.ts new file mode 100644 index 0000000..80851e4 --- /dev/null +++ b/vendor/deno.land/std@0.189.0/streams/readable_stream_from_iterable.ts @@ -0,0 +1,63 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. +// This module is browser compatible. + +/** Create a `ReadableStream` from any kind of iterable. + * + * ```ts + * import { readableStreamFromIterable } from "https://deno.land/std@$STD_VERSION/streams/readable_stream_from_iterable.ts"; + * + * const r1 = readableStreamFromIterable(["foo, bar, baz"]); + * const r2 = readableStreamFromIterable(async function* () { + * await new Promise(((r) => setTimeout(r, 1000))); + * yield "foo"; + * await new Promise(((r) => setTimeout(r, 1000))); + * yield "bar"; + * await new Promise(((r) => setTimeout(r, 1000))); + * yield "baz"; + * }()); + * ``` + * + * If the produced iterator (`iterable[Symbol.asyncIterator]()` or + * `iterable[Symbol.iterator]()`) is a generator, or more specifically is found + * to have a `.throw()` method on it, that will be called upon + * `readableStream.cancel()`. This is the case for the second input type above: + * + * ```ts + * import { readableStreamFromIterable } from "https://deno.land/std@$STD_VERSION/streams/readable_stream_from_iterable.ts"; + * + * const r3 = readableStreamFromIterable(async function* () { + * try { + * yield "foo"; + * } catch (error) { + * console.log(error); // Error: Cancelled by consumer. + * } + * }()); + * const reader = r3.getReader(); + * console.log(await reader.read()); // { value: "foo", done: false } + * await reader.cancel(new Error("Cancelled by consumer.")); + * ``` + */ +export function readableStreamFromIterable( + iterable: Iterable | AsyncIterable, +): ReadableStream { + const iterator: Iterator | AsyncIterator = + (iterable as AsyncIterable)[Symbol.asyncIterator]?.() ?? + (iterable as Iterable)[Symbol.iterator]?.(); + return new ReadableStream({ + async pull(controller) { + const { value, done } = await iterator.next(); + if (done) { + controller.close(); + } else { + controller.enqueue(value); + } + }, + async cancel(reason) { + if (typeof iterator.throw == "function") { + try { + await iterator.throw(reason); + } catch { /* `iterator.throw()` always throws on site. We catch it. */ } + } + }, + }); +} diff --git a/vendor/deno.land/std@0.189.0/streams/readable_stream_from_reader.ts b/vendor/deno.land/std@0.189.0/streams/readable_stream_from_reader.ts new file mode 100644 index 0000000..e987082 --- /dev/null +++ b/vendor/deno.land/std@0.189.0/streams/readable_stream_from_reader.ts @@ -0,0 +1,81 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. +// This module is browser compatible. + +import { DEFAULT_CHUNK_SIZE } from "./_common.ts"; +import type { Closer, Reader } from "../types.d.ts"; + +function isCloser(value: unknown): value is Closer { + return typeof value === "object" && value != null && "close" in value && + // deno-lint-ignore no-explicit-any + typeof (value as Record)["close"] === "function"; +} + +export interface ReadableStreamFromReaderOptions { + /** If the `reader` is also a `Closer`, automatically close the `reader` + * when `EOF` is encountered, or a read error occurs. + * + * @default {true} + */ + autoClose?: boolean; + + /** The size of chunks to allocate to read, the default is ~16KiB, which is + * the maximum size that Deno operations can currently support. */ + chunkSize?: number; + + /** The queuing strategy to create the `ReadableStream` with. */ + strategy?: { highWaterMark?: number | undefined; size?: undefined }; +} + +/** + * Create a `ReadableStream` from a `Reader`. + * + * When the pull algorithm is called on the stream, a chunk from the reader + * will be read. When `null` is returned from the reader, the stream will be + * closed along with the reader (if it is also a `Closer`). + * + * An example converting a `Deno.FsFile` into a readable stream: + * + * ```ts + * import { readableStreamFromReader } from "https://deno.land/std@$STD_VERSION/streams/readable_stream_from_reader.ts"; + * + * const file = await Deno.open("./file.txt", { read: true }); + * const fileStream = readableStreamFromReader(file); + * ``` + */ +export function readableStreamFromReader( + reader: Reader | (Reader & Closer), + options: ReadableStreamFromReaderOptions = {}, +): ReadableStream { + const { + autoClose = true, + chunkSize = DEFAULT_CHUNK_SIZE, + strategy, + } = options; + + return new ReadableStream({ + async pull(controller) { + const chunk = new Uint8Array(chunkSize); + try { + const read = await reader.read(chunk); + if (read === null) { + if (isCloser(reader) && autoClose) { + reader.close(); + } + controller.close(); + return; + } + controller.enqueue(chunk.subarray(0, read)); + } catch (e) { + controller.error(e); + if (isCloser(reader)) { + reader.close(); + } + } + }, + cancel() { + if (isCloser(reader) && autoClose) { + reader.close(); + } + }, + }, strategy); +} diff --git a/vendor/deno.land/std@0.189.0/streams/reader_from_iterable.ts b/vendor/deno.land/std@0.189.0/streams/reader_from_iterable.ts new file mode 100644 index 0000000..f63523e --- /dev/null +++ b/vendor/deno.land/std@0.189.0/streams/reader_from_iterable.ts @@ -0,0 +1,56 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. +// This module is browser compatible. + +import { Buffer } from "../io/buffer.ts"; +import { writeAll } from "./write_all.ts"; +import { Reader } from "../types.d.ts"; + +/** Create a `Reader` from an iterable of `Uint8Array`s. + * + * ```ts + * import { readerFromIterable } from "https://deno.land/std@$STD_VERSION/streams/reader_from_iterable.ts"; + * import { copy } from "https://deno.land/std@$STD_VERSION/streams/copy.ts"; + * + * const file = await Deno.open("metrics.txt", { write: true }); + * const reader = readerFromIterable((async function* () { + * while (true) { + * await new Promise((r) => setTimeout(r, 1000)); + * const message = `data: ${JSON.stringify(Deno.metrics())}\n\n`; + * yield new TextEncoder().encode(message); + * } + * })()); + * await copy(reader, file); + * ``` + */ +export function readerFromIterable( + iterable: Iterable | AsyncIterable, +): Reader { + const iterator: Iterator | AsyncIterator = + (iterable as AsyncIterable)[Symbol.asyncIterator]?.() ?? + (iterable as Iterable)[Symbol.iterator]?.(); + const buffer = new Buffer(); + return { + async read(p: Uint8Array): Promise { + if (buffer.length == 0) { + const result = await iterator.next(); + if (result.done) { + return null; + } else { + if (result.value.byteLength <= p.byteLength) { + p.set(result.value); + return result.value.byteLength; + } + p.set(result.value.subarray(0, p.byteLength)); + await writeAll(buffer, result.value.subarray(p.byteLength)); + return p.byteLength; + } + } else { + const n = await buffer.read(p); + if (n == null) { + return this.read(p); + } + return n; + } + }, + }; +} diff --git a/vendor/deno.land/std@0.189.0/streams/reader_from_stream_reader.ts b/vendor/deno.land/std@0.189.0/streams/reader_from_stream_reader.ts new file mode 100644 index 0000000..e1d3113 --- /dev/null +++ b/vendor/deno.land/std@0.189.0/streams/reader_from_stream_reader.ts @@ -0,0 +1,43 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. +// This module is browser compatible. + +import { Buffer } from "../io/buffer.ts"; +import { writeAll } from "./write_all.ts"; +import type { Reader } from "../types.d.ts"; + +/** + * Create a `Reader` from a `ReadableStreamDefaultReader`. + * + * @example + * ```ts + * import { copy } from "https://deno.land/std@$STD_VERSION/streams/copy.ts"; + * import { readerFromStreamReader } from "https://deno.land/std@$STD_VERSION/streams/reader_from_stream_reader.ts"; + * + * const res = await fetch("https://deno.land"); + * const file = await Deno.open("./deno.land.html", { create: true, write: true }); + * + * const reader = readerFromStreamReader(res.body!.getReader()); + * await copy(reader, file); + * file.close(); + * ``` + */ +export function readerFromStreamReader( + streamReader: ReadableStreamDefaultReader, +): Reader { + const buffer = new Buffer(); + + return { + async read(p: Uint8Array): Promise { + if (buffer.empty()) { + const res = await streamReader.read(); + if (res.done) { + return null; // EOF + } + + await writeAll(buffer, res.value); + } + + return buffer.read(p); + }, + }; +} diff --git a/vendor/deno.land/std@0.189.0/streams/text_delimiter_stream.ts b/vendor/deno.land/std@0.189.0/streams/text_delimiter_stream.ts new file mode 100644 index 0000000..85d7725 --- /dev/null +++ b/vendor/deno.land/std@0.189.0/streams/text_delimiter_stream.ts @@ -0,0 +1,78 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. +// This module is browser compatible. + +import { createLPS } from "./_common.ts"; + +import type { + DelimiterDisposition, + DelimiterStreamOptions, +} from "./delimiter_stream.ts"; + +/** Transform a stream into a stream where each chunk is divided by a given delimiter. + * + * ```ts + * import { TextDelimiterStream } from "https://deno.land/std@$STD_VERSION/streams/text_delimiter_stream.ts"; + * const res = await fetch("https://example.com"); + * const parts = res.body! + * .pipeThrough(new TextDecoderStream()) + * .pipeThrough(new TextDelimiterStream("foo")); + * ``` + */ +export class TextDelimiterStream extends TransformStream { + #buf = ""; + #delimiter: string; + #inspectIndex = 0; + #matchIndex = 0; + #delimLPS: Uint8Array; + #disp: DelimiterDisposition; + + constructor(delimiter: string, options?: DelimiterStreamOptions) { + super({ + transform: (chunk, controller) => { + this.#handle(chunk, controller); + }, + flush: (controller) => { + controller.enqueue(this.#buf); + }, + }); + + this.#delimiter = delimiter; + this.#delimLPS = createLPS(new TextEncoder().encode(delimiter)); + this.#disp = options?.disposition ?? "discard"; + } + + #handle( + chunk: string, + controller: TransformStreamDefaultController, + ) { + this.#buf += chunk; + let localIndex = 0; + while (this.#inspectIndex < this.#buf.length) { + if (chunk[localIndex] === this.#delimiter[this.#matchIndex]) { + this.#inspectIndex++; + localIndex++; + this.#matchIndex++; + if (this.#matchIndex === this.#delimiter.length) { + // Full match + const start = this.#inspectIndex - this.#delimiter.length; + const end = this.#disp === "suffix" ? this.#inspectIndex : start; + const copy = this.#buf.slice(0, end); + controller.enqueue(copy); + const shift = this.#disp == "prefix" ? start : this.#inspectIndex; + this.#buf = this.#buf.slice(shift); + this.#inspectIndex = this.#disp == "prefix" + ? this.#delimiter.length + : 0; + this.#matchIndex = 0; + } + } else { + if (this.#matchIndex === 0) { + this.#inspectIndex++; + localIndex++; + } else { + this.#matchIndex = this.#delimLPS[this.#matchIndex - 1]; + } + } + } + } +} diff --git a/vendor/deno.land/std@0.189.0/streams/text_line_stream.ts b/vendor/deno.land/std@0.189.0/streams/text_line_stream.ts new file mode 100644 index 0000000..2d6c093 --- /dev/null +++ b/vendor/deno.land/std@0.189.0/streams/text_line_stream.ts @@ -0,0 +1,74 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. +// This module is browser compatible. + +interface TextLineStreamOptions { + /** Allow splitting by solo \r */ + allowCR: boolean; +} + +/** Transform a stream into a stream where each chunk is divided by a newline, + * be it `\n` or `\r\n`. `\r` can be enabled via the `allowCR` option. + * + * ```ts + * import { TextLineStream } from "https://deno.land/std@$STD_VERSION/streams/text_line_stream.ts"; + * const res = await fetch("https://example.com"); + * const lines = res.body! + * .pipeThrough(new TextDecoderStream()) + * .pipeThrough(new TextLineStream()); + * ``` + */ +export class TextLineStream extends TransformStream { + readonly #allowCR: boolean; + #buf = ""; + + constructor(options?: TextLineStreamOptions) { + super({ + transform: (chunk, controller) => this.#handle(chunk, controller), + flush: (controller) => { + if (this.#buf.length > 0) { + if ( + this.#allowCR && + this.#buf[this.#buf.length - 1] === "\r" + ) controller.enqueue(this.#buf.slice(0, -1)); + else controller.enqueue(this.#buf); + } + }, + }); + this.#allowCR = options?.allowCR ?? false; + } + + #handle(chunk: string, controller: TransformStreamDefaultController) { + chunk = this.#buf + chunk; + + for (;;) { + const lfIndex = chunk.indexOf("\n"); + + if (this.#allowCR) { + const crIndex = chunk.indexOf("\r"); + + if ( + crIndex !== -1 && crIndex !== (chunk.length - 1) && + (lfIndex === -1 || (lfIndex - 1) > crIndex) + ) { + controller.enqueue(chunk.slice(0, crIndex)); + chunk = chunk.slice(crIndex + 1); + continue; + } + } + + if (lfIndex !== -1) { + let crOrLfIndex = lfIndex; + if (chunk[lfIndex - 1] === "\r") { + crOrLfIndex--; + } + controller.enqueue(chunk.slice(0, crOrLfIndex)); + chunk = chunk.slice(lfIndex + 1); + continue; + } + + break; + } + + this.#buf = chunk; + } +} diff --git a/vendor/deno.land/std@0.189.0/streams/to_transform_stream.ts b/vendor/deno.land/std@0.189.0/streams/to_transform_stream.ts new file mode 100644 index 0000000..5003c72 --- /dev/null +++ b/vendor/deno.land/std@0.189.0/streams/to_transform_stream.ts @@ -0,0 +1,75 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. +// This module is browser compatible. + +/** + * Convert the generator function into a TransformStream. + * + * ```ts + * import { readableStreamFromIterable } from "https://deno.land/std@$STD_VERSION/streams/readable_stream_from_iterable.ts"; + * import { toTransformStream } from "https://deno.land/std@$STD_VERSION/streams/to_transform_stream.ts"; + * + * const readable = readableStreamFromIterable([0, 1, 2]) + * .pipeThrough(toTransformStream(async function* (src) { + * for await (const chunk of src) { + * yield chunk * 100; + * } + * })); + * + * for await (const chunk of readable) { + * console.log(chunk); + * } + * // output: 0, 100, 200 + * ``` + * + * @param transformer A function to transform. + * @param writableStrategy An object that optionally defines a queuing strategy for the stream. + * @param readableStrategy An object that optionally defines a queuing strategy for the stream. + */ +export function toTransformStream( + transformer: (src: ReadableStream) => Iterable | AsyncIterable, + writableStrategy?: QueuingStrategy, + readableStrategy?: QueuingStrategy, +): TransformStream { + const { + writable, + readable, + } = new TransformStream(undefined, writableStrategy); + + const iterable = transformer(readable); + const iterator: Iterator | AsyncIterator = + (iterable as AsyncIterable)[Symbol.asyncIterator]?.() ?? + (iterable as Iterable)[Symbol.iterator]?.(); + return { + writable, + readable: new ReadableStream({ + async pull(controller) { + let result: IteratorResult; + try { + result = await iterator.next(); + } catch (error) { + // Propagate error to stream from iterator + // If the stream status is "errored", it will be thrown, but ignore. + await readable.cancel(error).catch(() => {}); + controller.error(error); + return; + } + if (result.done) { + controller.close(); + return; + } + controller.enqueue(result.value); + }, + async cancel(reason) { + // Propagate cancellation to readable and iterator + if (typeof iterator.throw == "function") { + try { + await iterator.throw(reason); + } catch { + /* `iterator.throw()` always throws on site. We catch it. */ + } + } + await readable.cancel(reason); + }, + }, readableStrategy), + }; +} diff --git a/vendor/deno.land/std@0.189.0/streams/writable_stream_from_writer.ts b/vendor/deno.land/std@0.189.0/streams/writable_stream_from_writer.ts new file mode 100644 index 0000000..ffdd90e --- /dev/null +++ b/vendor/deno.land/std@0.189.0/streams/writable_stream_from_writer.ts @@ -0,0 +1,52 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. +// This module is browser compatible. + +import { writeAll } from "./write_all.ts"; +import type { Closer, Writer } from "../types.d.ts"; + +function isCloser(value: unknown): value is Closer { + return typeof value === "object" && value != null && "close" in value && + // deno-lint-ignore no-explicit-any + typeof (value as Record)["close"] === "function"; +} + +export interface WritableStreamFromWriterOptions { + /** + * If the `writer` is also a `Closer`, automatically close the `writer` + * when the stream is closed, aborted, or a write error occurs. + * + * @default {true} + */ + autoClose?: boolean; +} + +/** Create a `WritableStream` from a `Writer`. */ +export function writableStreamFromWriter( + writer: Writer, + options: WritableStreamFromWriterOptions = {}, +): WritableStream { + const { autoClose = true } = options; + + return new WritableStream({ + async write(chunk, controller) { + try { + await writeAll(writer, chunk); + } catch (e) { + controller.error(e); + if (isCloser(writer) && autoClose) { + writer.close(); + } + } + }, + close() { + if (isCloser(writer) && autoClose) { + writer.close(); + } + }, + abort() { + if (isCloser(writer) && autoClose) { + writer.close(); + } + }, + }); +} diff --git a/vendor/deno.land/std@0.189.0/streams/write_all.ts b/vendor/deno.land/std@0.189.0/streams/write_all.ts new file mode 100644 index 0000000..c23a837 --- /dev/null +++ b/vendor/deno.land/std@0.189.0/streams/write_all.ts @@ -0,0 +1,65 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. +// This module is browser compatible. + +import type { Writer, WriterSync } from "../types.d.ts"; + +/** Write all the content of the array buffer (`arr`) to the writer (`w`). + * + * ```ts + * import { Buffer } from "https://deno.land/std@$STD_VERSION/io/buffer.ts"; + * import { writeAll } from "https://deno.land/std@$STD_VERSION/streams/write_all.ts"; + + * // Example writing to stdout + * let contentBytes = new TextEncoder().encode("Hello World"); + * await writeAll(Deno.stdout, contentBytes); + * + * // Example writing to file + * contentBytes = new TextEncoder().encode("Hello World"); + * const file = await Deno.open('test.file', {write: true}); + * await writeAll(file, contentBytes); + * file.close(); + * + * // Example writing to buffer + * contentBytes = new TextEncoder().encode("Hello World"); + * const writer = new Buffer(); + * await writeAll(writer, contentBytes); + * console.log(writer.bytes().length); // 11 + * ``` + */ +export async function writeAll(w: Writer, arr: Uint8Array) { + let nwritten = 0; + while (nwritten < arr.length) { + nwritten += await w.write(arr.subarray(nwritten)); + } +} + +/** Synchronously write all the content of the array buffer (`arr`) to the + * writer (`w`). + * + * ```ts + * import { Buffer } from "https://deno.land/std@$STD_VERSION/io/buffer.ts"; + * import { writeAllSync } from "https://deno.land/std@$STD_VERSION/streams/write_all.ts"; + * + * // Example writing to stdout + * let contentBytes = new TextEncoder().encode("Hello World"); + * writeAllSync(Deno.stdout, contentBytes); + * + * // Example writing to file + * contentBytes = new TextEncoder().encode("Hello World"); + * const file = Deno.openSync('test.file', {write: true}); + * writeAllSync(file, contentBytes); + * file.close(); + * + * // Example writing to buffer + * contentBytes = new TextEncoder().encode("Hello World"); + * const writer = new Buffer(); + * writeAllSync(writer, contentBytes); + * console.log(writer.bytes().length); // 11 + * ``` + */ +export function writeAllSync(w: WriterSync, arr: Uint8Array) { + let nwritten = 0; + while (nwritten < arr.length) { + nwritten += w.writeSync(arr.subarray(nwritten)); + } +} diff --git a/vendor/deno.land/std@0.189.0/streams/writer_from_stream_writer.ts b/vendor/deno.land/std@0.189.0/streams/writer_from_stream_writer.ts new file mode 100644 index 0000000..bbedf63 --- /dev/null +++ b/vendor/deno.land/std@0.189.0/streams/writer_from_stream_writer.ts @@ -0,0 +1,35 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. +// This module is browser compatible. + +import type { Writer } from "../types.d.ts"; + +/** Create a `Writer` from a `WritableStreamDefaultWriter`. + * + * @example + * ```ts + * import { copy } from "https://deno.land/std@$STD_VERSION/streams/copy.ts"; + * import { writerFromStreamWriter } from "https://deno.land/std@$STD_VERSION/streams/writer_from_stream_writer.ts"; + * + * const file = await Deno.open("./deno.land.html", { read: true }); + * + * const writableStream = new WritableStream({ + * write(chunk): void { + * console.log(chunk); + * }, + * }); + * const writer = writerFromStreamWriter(writableStream.getWriter()); + * await copy(file, writer); + * file.close(); + * ``` + */ +export function writerFromStreamWriter( + streamWriter: WritableStreamDefaultWriter, +): Writer { + return { + async write(p: Uint8Array): Promise { + await streamWriter.ready; + await streamWriter.write(p); + return p.length; + }, + }; +} diff --git a/vendor/deno.land/std@0.189.0/streams/zip_readable_streams.ts b/vendor/deno.land/std@0.189.0/streams/zip_readable_streams.ts new file mode 100644 index 0000000..90ee9db --- /dev/null +++ b/vendor/deno.land/std@0.189.0/streams/zip_readable_streams.ts @@ -0,0 +1,35 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. +// This module is browser compatible. + +/** + * Merge multiple streams into a single one, taking order into account, and each stream + * will wait for a chunk to enqueue before the next stream can append another chunk. + * If a stream ends before other ones, the others will continue adding data in order, + * and the finished one will not add any more data. + */ +export function zipReadableStreams( + ...streams: ReadableStream[] +): ReadableStream { + const readers = streams.map((s) => s.getReader()); + return new ReadableStream({ + async start(controller) { + try { + let resolved = 0; + while (resolved != streams.length) { + for (const [key, reader] of Object.entries(readers)) { + const { value, done } = await reader.read(); + if (!done) { + controller.enqueue(value!); + } else { + resolved++; + readers.splice(+key, 1); + } + } + } + controller.close(); + } catch (e) { + controller.error(e); + } + }, + }); +} diff --git a/vendor/deno.land/std@0.189.0/types.d.ts b/vendor/deno.land/std@0.189.0/types.d.ts new file mode 100644 index 0000000..f3769a8 --- /dev/null +++ b/vendor/deno.land/std@0.189.0/types.d.ts @@ -0,0 +1,90 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. +// This module is browser compatible. + +/** See the Contributing > Types section in the README for an explanation of this file. */ + +/** An abstract interface which when implemented provides an interface to read bytes into an array buffer asynchronously. */ +export interface Reader { + /** Reads up to `p.byteLength` bytes into `p`. It resolves to the number of + * bytes read (`0` < `n` <= `p.byteLength`) and rejects if any error + * encountered. Even if `read()` resolves to `n` < `p.byteLength`, it may + * use all of `p` as scratch space during the call. If some data is + * available but not `p.byteLength` bytes, `read()` conventionally resolves + * to what is available instead of waiting for more. + * + * When `read()` encounters end-of-file condition, it resolves to EOF + * (`null`). + * + * When `read()` encounters an error, it rejects with an error. + * + * Callers should always process the `n` > `0` bytes returned before + * considering the EOF (`null`). Doing so correctly handles I/O errors that + * happen after reading some bytes and also both of the allowed EOF + * behaviors. + * + * Implementations should not retain a reference to `p`. + * + * Use iterateReader() from https://deno.land/std@$STD_VERSION/streams/iterate_reader.ts to turn a Reader into an + * AsyncIterator. + */ + read(p: Uint8Array): Promise; +} + +/** An abstract interface which when implemented provides an interface to read bytes into an array buffer synchronously. */ +export interface ReaderSync { + /** Reads up to `p.byteLength` bytes into `p`. It resolves to the number + * of bytes read (`0` < `n` <= `p.byteLength`) and rejects if any error + * encountered. Even if `read()` returns `n` < `p.byteLength`, it may use + * all of `p` as scratch space during the call. If some data is available + * but not `p.byteLength` bytes, `read()` conventionally returns what is + * available instead of waiting for more. + * + * When `readSync()` encounters end-of-file condition, it returns EOF + * (`null`). + * + * When `readSync()` encounters an error, it throws with an error. + * + * Callers should always process the `n` > `0` bytes returned before + * considering the EOF (`null`). Doing so correctly handles I/O errors that happen + * after reading some bytes and also both of the allowed EOF behaviors. + * + * Implementations should not retain a reference to `p`. + * + * Use iterateReaderSync() from https://deno.land/std@$STD_VERSION/streams/iterate_reader.ts to turn a ReaderSync + * into an Iterator. + */ + readSync(p: Uint8Array): number | null; +} + +/** An abstract interface which when implemented provides an interface to write bytes from an array buffer to a file/resource asynchronously. */ +export interface Writer { + /** Writes `p.byteLength` bytes from `p` to the underlying data stream. It + * resolves to the number of bytes written from `p` (`0` <= `n` <= + * `p.byteLength`) or reject with the error encountered that caused the + * write to stop early. `write()` must reject with a non-null error if + * would resolve to `n` < `p.byteLength`. `write()` must not modify the + * slice data, even temporarily. + * + * Implementations should not retain a reference to `p`. + */ + write(p: Uint8Array): Promise; +} +/** An abstract interface which when implemented provides an interface to write bytes from an array buffer to a file/resource synchronously. */ +export interface WriterSync { + /** Writes `p.byteLength` bytes from `p` to the underlying data + * stream. It returns the number of bytes written from `p` (`0` <= `n` + * <= `p.byteLength`) and any error encountered that caused the write to + * stop early. `writeSync()` must throw a non-null error if it returns `n` < + * `p.byteLength`. `writeSync()` must not modify the slice data, even + * temporarily. + * + * Implementations should not retain a reference to `p`. + */ + writeSync(p: Uint8Array): number; +} + +/** An abstract interface which when implemented provides an interface to close files/resources that were previously opened. */ +export interface Closer { + /** Closes the resource, "freeing" the backing file/resource. */ + close(): void; +} diff --git a/vendor/deno.land/std@0.191.0/_util/asserts.ts b/vendor/deno.land/std@0.191.0/_util/asserts.ts new file mode 100644 index 0000000..3c5c346 --- /dev/null +++ b/vendor/deno.land/std@0.191.0/_util/asserts.ts @@ -0,0 +1,25 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. +// This module is browser compatible. + +/** + * All internal non-test code, that is files that do not have `test` or `bench` in the name, must use the assertion functions within `_utils/asserts.ts` and not `testing/asserts.ts`. This is to create a separation of concerns between internal and testing assertions. + */ + +export class DenoStdInternalError extends Error { + constructor(message: string) { + super(message); + this.name = "DenoStdInternalError"; + } +} + +/** Make an assertion, if not `true`, then throw. */ +export function assert(expr: unknown, msg = ""): asserts expr { + if (!expr) { + throw new DenoStdInternalError(msg); + } +} + +/** Use this to assert unreachable code. */ +export function unreachable(): never { + throw new DenoStdInternalError("unreachable"); +} diff --git a/vendor/deno.land/std@0.191.0/bytes/bytes_list.ts b/vendor/deno.land/std@0.191.0/bytes/bytes_list.ts new file mode 100644 index 0000000..da71708 --- /dev/null +++ b/vendor/deno.land/std@0.191.0/bytes/bytes_list.ts @@ -0,0 +1,165 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. +// This module is browser compatible. + +/** + * An abstraction of multiple Uint8Arrays + */ +export class BytesList { + #len = 0; + #chunks: { + value: Uint8Array; + start: number; // start offset from head of chunk + end: number; // end offset from head of chunk + offset: number; // offset of head in all bytes + }[] = []; + constructor() {} + + /** + * Total size of bytes + */ + size() { + return this.#len; + } + /** + * Push bytes with given offset infos + */ + add(value: Uint8Array, start = 0, end = value.byteLength) { + if (value.byteLength === 0 || end - start === 0) { + return; + } + checkRange(start, end, value.byteLength); + this.#chunks.push({ + value, + end, + start, + offset: this.#len, + }); + this.#len += end - start; + } + + /** + * Drop head `n` bytes. + */ + shift(n: number) { + if (n === 0) { + return; + } + if (this.#len <= n) { + this.#chunks = []; + this.#len = 0; + return; + } + const idx = this.getChunkIndex(n); + this.#chunks.splice(0, idx); + const [chunk] = this.#chunks; + if (chunk) { + const diff = n - chunk.offset; + chunk.start += diff; + } + let offset = 0; + for (const chunk of this.#chunks) { + chunk.offset = offset; + offset += chunk.end - chunk.start; + } + this.#len = offset; + } + + /** + * Find chunk index in which `pos` locates by binary-search + * returns -1 if out of range + */ + getChunkIndex(pos: number): number { + let max = this.#chunks.length; + let min = 0; + while (true) { + const i = min + Math.floor((max - min) / 2); + if (i < 0 || this.#chunks.length <= i) { + return -1; + } + const { offset, start, end } = this.#chunks[i]; + const len = end - start; + if (offset <= pos && pos < offset + len) { + return i; + } else if (offset + len <= pos) { + min = i + 1; + } else { + max = i - 1; + } + } + } + + /** + * Get indexed byte from chunks + */ + get(i: number): number { + if (i < 0 || this.#len <= i) { + throw new Error("out of range"); + } + const idx = this.getChunkIndex(i); + const { value, offset, start } = this.#chunks[idx]; + return value[start + i - offset]; + } + + /** + * Iterator of bytes from given position + */ + *iterator(start = 0): IterableIterator { + const startIdx = this.getChunkIndex(start); + if (startIdx < 0) return; + const first = this.#chunks[startIdx]; + let firstOffset = start - first.offset; + for (let i = startIdx; i < this.#chunks.length; i++) { + const chunk = this.#chunks[i]; + for (let j = chunk.start + firstOffset; j < chunk.end; j++) { + yield chunk.value[j]; + } + firstOffset = 0; + } + } + + /** + * Returns subset of bytes copied + */ + slice(start: number, end: number = this.#len): Uint8Array { + if (end === start) { + return new Uint8Array(); + } + checkRange(start, end, this.#len); + const result = new Uint8Array(end - start); + const startIdx = this.getChunkIndex(start); + const endIdx = this.getChunkIndex(end - 1); + let written = 0; + for (let i = startIdx; i <= endIdx; i++) { + const { + value: chunkValue, + start: chunkStart, + end: chunkEnd, + offset: chunkOffset, + } = this.#chunks[i]; + const readStart = chunkStart + (i === startIdx ? start - chunkOffset : 0); + const readEnd = i === endIdx ? end - chunkOffset + chunkStart : chunkEnd; + const len = readEnd - readStart; + result.set(chunkValue.subarray(readStart, readEnd), written); + written += len; + } + return result; + } + /** + * Concatenate chunks into single Uint8Array copied. + */ + concat(): Uint8Array { + const result = new Uint8Array(this.#len); + let sum = 0; + for (const { value, start, end } of this.#chunks) { + result.set(value.subarray(start, end), sum); + sum += end - start; + } + return result; + } +} + +function checkRange(start: number, end: number, len: number) { + if (start < 0 || len < start || end < 0 || len < end || end < start) { + throw new Error("invalid range"); + } +} diff --git a/vendor/deno.land/std@0.191.0/bytes/concat.ts b/vendor/deno.land/std@0.191.0/bytes/concat.ts new file mode 100644 index 0000000..d45e5a3 --- /dev/null +++ b/vendor/deno.land/std@0.191.0/bytes/concat.ts @@ -0,0 +1,26 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. +// This module is browser compatible. + +/** Concatenate the given arrays into a new Uint8Array. + * + * ```ts + * import { concat } from "https://deno.land/std@$STD_VERSION/bytes/concat.ts"; + * const a = new Uint8Array([0, 1, 2]); + * const b = new Uint8Array([3, 4, 5]); + * console.log(concat(a, b)); // [0, 1, 2, 3, 4, 5] + */ +export function concat(...buf: Uint8Array[]): Uint8Array { + let length = 0; + for (const b of buf) { + length += b.length; + } + + const output = new Uint8Array(length); + let index = 0; + for (const b of buf) { + output.set(b, index); + index += b.length; + } + + return output; +} diff --git a/vendor/deno.land/std@0.191.0/bytes/copy.ts b/vendor/deno.land/std@0.191.0/bytes/copy.ts new file mode 100644 index 0000000..e92c98d --- /dev/null +++ b/vendor/deno.land/std@0.191.0/bytes/copy.ts @@ -0,0 +1,38 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. +// This module is browser compatible. + +/** Copy bytes from the `src` array to the `dst` array. Returns the number of + * bytes copied. + * + * If the `src` array is larger than what the `dst` array can hold, only the + * amount of bytes that fit in the `dst` array are copied. + * + * An offset can be specified as the third argument that begins the copy at + * that given index in the `dst` array. The offset defaults to the beginning of + * the array. + * + * ```ts + * import { copy } from "https://deno.land/std@$STD_VERSION/bytes/copy.ts"; + * const src = new Uint8Array([9, 8, 7]); + * const dst = new Uint8Array([0, 1, 2, 3, 4, 5]); + * console.log(copy(src, dst)); // 3 + * console.log(dst); // [9, 8, 7, 3, 4, 5] + * ``` + * + * ```ts + * import { copy } from "https://deno.land/std@$STD_VERSION/bytes/copy.ts"; + * const src = new Uint8Array([1, 1, 1, 1]); + * const dst = new Uint8Array([0, 0, 0, 0]); + * console.log(copy(src, dst, 1)); // 3 + * console.log(dst); // [0, 1, 1, 1] + * ``` + */ +export function copy(src: Uint8Array, dst: Uint8Array, off = 0): number { + off = Math.max(0, Math.min(off, dst.byteLength)); + const dstBytesAvailable = dst.byteLength - off; + if (src.byteLength > dstBytesAvailable) { + src = src.subarray(0, dstBytesAvailable); + } + dst.set(src, off); + return src.byteLength; +} diff --git a/vendor/deno.land/std@0.191.0/io/buf_reader.ts b/vendor/deno.land/std@0.191.0/io/buf_reader.ts new file mode 100644 index 0000000..91f3e40 --- /dev/null +++ b/vendor/deno.land/std@0.191.0/io/buf_reader.ts @@ -0,0 +1,414 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. +// This module is browser compatible. + +import { assert } from "../_util/asserts.ts"; +import { copy } from "../bytes/copy.ts"; +import type { Reader } from "../types.d.ts"; + +const DEFAULT_BUF_SIZE = 4096; +const MIN_BUF_SIZE = 16; +const MAX_CONSECUTIVE_EMPTY_READS = 100; +const CR = "\r".charCodeAt(0); +const LF = "\n".charCodeAt(0); + +export class BufferFullError extends Error { + override name = "BufferFullError"; + constructor(public partial: Uint8Array) { + super("Buffer full"); + } +} + +export class PartialReadError extends Error { + override name = "PartialReadError"; + partial?: Uint8Array; + constructor() { + super("Encountered UnexpectedEof, data only partially read"); + } +} + +/** Result type returned by of BufReader.readLine(). */ +export interface ReadLineResult { + line: Uint8Array; + more: boolean; +} + +export class BufReader implements Reader { + #buf!: Uint8Array; + #rd!: Reader; // Reader provided by caller. + #r = 0; // buf read position. + #w = 0; // buf write position. + #eof = false; + // private lastByte: number; + // private lastCharSize: number; + + /** return new BufReader unless r is BufReader */ + static create(r: Reader, size: number = DEFAULT_BUF_SIZE): BufReader { + return r instanceof BufReader ? r : new BufReader(r, size); + } + + constructor(rd: Reader, size: number = DEFAULT_BUF_SIZE) { + if (size < MIN_BUF_SIZE) { + size = MIN_BUF_SIZE; + } + this.#reset(new Uint8Array(size), rd); + } + + /** Returns the size of the underlying buffer in bytes. */ + size(): number { + return this.#buf.byteLength; + } + + buffered(): number { + return this.#w - this.#r; + } + + // Reads a new chunk into the buffer. + #fill = async () => { + // Slide existing data to beginning. + if (this.#r > 0) { + this.#buf.copyWithin(0, this.#r, this.#w); + this.#w -= this.#r; + this.#r = 0; + } + + if (this.#w >= this.#buf.byteLength) { + throw Error("bufio: tried to fill full buffer"); + } + + // Read new data: try a limited number of times. + for (let i = MAX_CONSECUTIVE_EMPTY_READS; i > 0; i--) { + const rr = await this.#rd.read(this.#buf.subarray(this.#w)); + if (rr === null) { + this.#eof = true; + return; + } + assert(rr >= 0, "negative read"); + this.#w += rr; + if (rr > 0) { + return; + } + } + + throw new Error( + `No progress after ${MAX_CONSECUTIVE_EMPTY_READS} read() calls`, + ); + }; + + /** Discards any buffered data, resets all state, and switches + * the buffered reader to read from r. + */ + reset(r: Reader) { + this.#reset(this.#buf, r); + } + + #reset = (buf: Uint8Array, rd: Reader) => { + this.#buf = buf; + this.#rd = rd; + this.#eof = false; + // this.lastByte = -1; + // this.lastCharSize = -1; + }; + + /** reads data into p. + * It returns the number of bytes read into p. + * The bytes are taken from at most one Read on the underlying Reader, + * hence n may be less than len(p). + * To read exactly len(p) bytes, use io.ReadFull(b, p). + */ + async read(p: Uint8Array): Promise { + let rr: number | null = p.byteLength; + if (p.byteLength === 0) return rr; + + if (this.#r === this.#w) { + if (p.byteLength >= this.#buf.byteLength) { + // Large read, empty buffer. + // Read directly into p to avoid copy. + const rr = await this.#rd.read(p); + const nread = rr ?? 0; + assert(nread >= 0, "negative read"); + // if (rr.nread > 0) { + // this.lastByte = p[rr.nread - 1]; + // this.lastCharSize = -1; + // } + return rr; + } + + // One read. + // Do not use this.fill, which will loop. + this.#r = 0; + this.#w = 0; + rr = await this.#rd.read(this.#buf); + if (rr === 0 || rr === null) return rr; + assert(rr >= 0, "negative read"); + this.#w += rr; + } + + // copy as much as we can + const copied = copy(this.#buf.subarray(this.#r, this.#w), p, 0); + this.#r += copied; + // this.lastByte = this.buf[this.r - 1]; + // this.lastCharSize = -1; + return copied; + } + + /** reads exactly `p.length` bytes into `p`. + * + * If successful, `p` is returned. + * + * If the end of the underlying stream has been reached, and there are no more + * bytes available in the buffer, `readFull()` returns `null` instead. + * + * An error is thrown if some bytes could be read, but not enough to fill `p` + * entirely before the underlying stream reported an error or EOF. Any error + * thrown will have a `partial` property that indicates the slice of the + * buffer that has been successfully filled with data. + * + * Ported from https://golang.org/pkg/io/#ReadFull + */ + async readFull(p: Uint8Array): Promise { + let bytesRead = 0; + while (bytesRead < p.length) { + try { + const rr = await this.read(p.subarray(bytesRead)); + if (rr === null) { + if (bytesRead === 0) { + return null; + } else { + throw new PartialReadError(); + } + } + bytesRead += rr; + } catch (err) { + if (err instanceof PartialReadError) { + err.partial = p.subarray(0, bytesRead); + } + throw err; + } + } + return p; + } + + /** Returns the next byte [0, 255] or `null`. */ + async readByte(): Promise { + while (this.#r === this.#w) { + if (this.#eof) return null; + await this.#fill(); // buffer is empty. + } + const c = this.#buf[this.#r]; + this.#r++; + // this.lastByte = c; + return c; + } + + /** readString() reads until the first occurrence of delim in the input, + * returning a string containing the data up to and including the delimiter. + * If ReadString encounters an error before finding a delimiter, + * it returns the data read before the error and the error itself + * (often `null`). + * ReadString returns err != nil if and only if the returned data does not end + * in delim. + * For simple uses, a Scanner may be more convenient. + */ + async readString(delim: string): Promise { + if (delim.length !== 1) { + throw new Error("Delimiter should be a single character"); + } + const buffer = await this.readSlice(delim.charCodeAt(0)); + if (buffer === null) return null; + return new TextDecoder().decode(buffer); + } + + /** `readLine()` is a low-level line-reading primitive. Most callers should + * use `readString('\n')` instead or use a Scanner. + * + * `readLine()` tries to return a single line, not including the end-of-line + * bytes. If the line was too long for the buffer then `more` is set and the + * beginning of the line is returned. The rest of the line will be returned + * from future calls. `more` will be false when returning the last fragment + * of the line. The returned buffer is only valid until the next call to + * `readLine()`. + * + * The text returned from ReadLine does not include the line end ("\r\n" or + * "\n"). + * + * When the end of the underlying stream is reached, the final bytes in the + * stream are returned. No indication or error is given if the input ends + * without a final line end. When there are no more trailing bytes to read, + * `readLine()` returns `null`. + * + * Calling `unreadByte()` after `readLine()` will always unread the last byte + * read (possibly a character belonging to the line end) even if that byte is + * not part of the line returned by `readLine()`. + */ + async readLine(): Promise { + let line: Uint8Array | null = null; + + try { + line = await this.readSlice(LF); + } catch (err) { + let partial; + if (err instanceof PartialReadError) { + partial = err.partial; + assert( + partial instanceof Uint8Array, + "bufio: caught error from `readSlice()` without `partial` property", + ); + } + + // Don't throw if `readSlice()` failed with `BufferFullError`, instead we + // just return whatever is available and set the `more` flag. + if (!(err instanceof BufferFullError)) { + throw err; + } + + partial = err.partial; + + // Handle the case where "\r\n" straddles the buffer. + if ( + !this.#eof && partial && + partial.byteLength > 0 && + partial[partial.byteLength - 1] === CR + ) { + // Put the '\r' back on buf and drop it from line. + // Let the next call to ReadLine check for "\r\n". + assert(this.#r > 0, "bufio: tried to rewind past start of buffer"); + this.#r--; + partial = partial.subarray(0, partial.byteLength - 1); + } + + if (partial) { + return { line: partial, more: !this.#eof }; + } + } + + if (line === null) { + return null; + } + + if (line.byteLength === 0) { + return { line, more: false }; + } + + if (line[line.byteLength - 1] == LF) { + let drop = 1; + if (line.byteLength > 1 && line[line.byteLength - 2] === CR) { + drop = 2; + } + line = line.subarray(0, line.byteLength - drop); + } + return { line, more: false }; + } + + /** `readSlice()` reads until the first occurrence of `delim` in the input, + * returning a slice pointing at the bytes in the buffer. The bytes stop + * being valid at the next read. + * + * If `readSlice()` encounters an error before finding a delimiter, or the + * buffer fills without finding a delimiter, it throws an error with a + * `partial` property that contains the entire buffer. + * + * If `readSlice()` encounters the end of the underlying stream and there are + * any bytes left in the buffer, the rest of the buffer is returned. In other + * words, EOF is always treated as a delimiter. Once the buffer is empty, + * it returns `null`. + * + * Because the data returned from `readSlice()` will be overwritten by the + * next I/O operation, most clients should use `readString()` instead. + */ + async readSlice(delim: number): Promise { + let s = 0; // search start index + let slice: Uint8Array | undefined; + + while (true) { + // Search buffer. + let i = this.#buf.subarray(this.#r + s, this.#w).indexOf(delim); + if (i >= 0) { + i += s; + slice = this.#buf.subarray(this.#r, this.#r + i + 1); + this.#r += i + 1; + break; + } + + // EOF? + if (this.#eof) { + if (this.#r === this.#w) { + return null; + } + slice = this.#buf.subarray(this.#r, this.#w); + this.#r = this.#w; + break; + } + + // Buffer full? + if (this.buffered() >= this.#buf.byteLength) { + this.#r = this.#w; + // #4521 The internal buffer should not be reused across reads because it causes corruption of data. + const oldbuf = this.#buf; + const newbuf = this.#buf.slice(0); + this.#buf = newbuf; + throw new BufferFullError(oldbuf); + } + + s = this.#w - this.#r; // do not rescan area we scanned before + + // Buffer is not full. + try { + await this.#fill(); + } catch (err) { + if (err instanceof PartialReadError) { + err.partial = slice; + } + throw err; + } + } + + // Handle last byte, if any. + // const i = slice.byteLength - 1; + // if (i >= 0) { + // this.lastByte = slice[i]; + // this.lastCharSize = -1 + // } + + return slice; + } + + /** `peek()` returns the next `n` bytes without advancing the reader. The + * bytes stop being valid at the next read call. + * + * When the end of the underlying stream is reached, but there are unread + * bytes left in the buffer, those bytes are returned. If there are no bytes + * left in the buffer, it returns `null`. + * + * If an error is encountered before `n` bytes are available, `peek()` throws + * an error with the `partial` property set to a slice of the buffer that + * contains the bytes that were available before the error occurred. + */ + async peek(n: number): Promise { + if (n < 0) { + throw Error("negative count"); + } + + let avail = this.#w - this.#r; + while (avail < n && avail < this.#buf.byteLength && !this.#eof) { + try { + await this.#fill(); + } catch (err) { + if (err instanceof PartialReadError) { + err.partial = this.#buf.subarray(this.#r, this.#w); + } + throw err; + } + avail = this.#w - this.#r; + } + + if (avail === 0 && this.#eof) { + return null; + } else if (avail < n && this.#eof) { + return this.#buf.subarray(this.#r, this.#r + avail); + } else if (avail < n) { + throw new BufferFullError(this.#buf.subarray(this.#r, this.#w)); + } + + return this.#buf.subarray(this.#r, this.#r + n); + } +} diff --git a/vendor/deno.land/std@0.191.0/io/buf_writer.ts b/vendor/deno.land/std@0.191.0/io/buf_writer.ts new file mode 100644 index 0000000..1391c68 --- /dev/null +++ b/vendor/deno.land/std@0.191.0/io/buf_writer.ts @@ -0,0 +1,222 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. +// This module is browser compatible. + +import { copy } from "../bytes/copy.ts"; +import type { Writer, WriterSync } from "../types.d.ts"; + +const DEFAULT_BUF_SIZE = 4096; + +abstract class AbstractBufBase { + buf: Uint8Array; + usedBufferBytes = 0; + err: Error | null = null; + + constructor(buf: Uint8Array) { + this.buf = buf; + } + + /** Size returns the size of the underlying buffer in bytes. */ + size(): number { + return this.buf.byteLength; + } + + /** Returns how many bytes are unused in the buffer. */ + available(): number { + return this.buf.byteLength - this.usedBufferBytes; + } + + /** buffered returns the number of bytes that have been written into the + * current buffer. + */ + buffered(): number { + return this.usedBufferBytes; + } +} + +/** BufWriter implements buffering for an deno.Writer object. + * If an error occurs writing to a Writer, no more data will be + * accepted and all subsequent writes, and flush(), will return the error. + * After all data has been written, the client should call the + * flush() method to guarantee all data has been forwarded to + * the underlying deno.Writer. + */ +export class BufWriter extends AbstractBufBase implements Writer { + #writer: Writer; + + /** return new BufWriter unless writer is BufWriter */ + static create(writer: Writer, size: number = DEFAULT_BUF_SIZE): BufWriter { + return writer instanceof BufWriter ? writer : new BufWriter(writer, size); + } + + constructor(writer: Writer, size: number = DEFAULT_BUF_SIZE) { + super(new Uint8Array(size <= 0 ? DEFAULT_BUF_SIZE : size)); + this.#writer = writer; + } + + /** Discards any unflushed buffered data, clears any error, and + * resets buffer to write its output to w. + */ + reset(w: Writer) { + this.err = null; + this.usedBufferBytes = 0; + this.#writer = w; + } + + /** Flush writes any buffered data to the underlying io.Writer. */ + async flush() { + if (this.err !== null) throw this.err; + if (this.usedBufferBytes === 0) return; + + try { + const p = this.buf.subarray(0, this.usedBufferBytes); + let nwritten = 0; + while (nwritten < p.length) { + nwritten += await this.#writer.write(p.subarray(nwritten)); + } + } catch (e) { + if (e instanceof Error) { + this.err = e; + } + throw e; + } + + this.buf = new Uint8Array(this.buf.length); + this.usedBufferBytes = 0; + } + + /** Writes the contents of `data` into the buffer. If the contents won't fully + * fit into the buffer, those bytes that are copied into the buffer will be flushed + * to the writer and the remaining bytes are then copied into the now empty buffer. + * + * @return the number of bytes written to the buffer. + */ + async write(data: Uint8Array): Promise { + if (this.err !== null) throw this.err; + if (data.length === 0) return 0; + + let totalBytesWritten = 0; + let numBytesWritten = 0; + while (data.byteLength > this.available()) { + if (this.buffered() === 0) { + // Large write, empty buffer. + // Write directly from data to avoid copy. + try { + numBytesWritten = await this.#writer.write(data); + } catch (e) { + if (e instanceof Error) { + this.err = e; + } + throw e; + } + } else { + numBytesWritten = copy(data, this.buf, this.usedBufferBytes); + this.usedBufferBytes += numBytesWritten; + await this.flush(); + } + totalBytesWritten += numBytesWritten; + data = data.subarray(numBytesWritten); + } + + numBytesWritten = copy(data, this.buf, this.usedBufferBytes); + this.usedBufferBytes += numBytesWritten; + totalBytesWritten += numBytesWritten; + return totalBytesWritten; + } +} + +/** BufWriterSync implements buffering for a deno.WriterSync object. + * If an error occurs writing to a WriterSync, no more data will be + * accepted and all subsequent writes, and flush(), will return the error. + * After all data has been written, the client should call the + * flush() method to guarantee all data has been forwarded to + * the underlying deno.WriterSync. + */ +export class BufWriterSync extends AbstractBufBase implements WriterSync { + #writer: WriterSync; + + /** return new BufWriterSync unless writer is BufWriterSync */ + static create( + writer: WriterSync, + size: number = DEFAULT_BUF_SIZE, + ): BufWriterSync { + return writer instanceof BufWriterSync + ? writer + : new BufWriterSync(writer, size); + } + + constructor(writer: WriterSync, size: number = DEFAULT_BUF_SIZE) { + super(new Uint8Array(size <= 0 ? DEFAULT_BUF_SIZE : size)); + this.#writer = writer; + } + + /** Discards any unflushed buffered data, clears any error, and + * resets buffer to write its output to w. + */ + reset(w: WriterSync) { + this.err = null; + this.usedBufferBytes = 0; + this.#writer = w; + } + + /** Flush writes any buffered data to the underlying io.WriterSync. */ + flush() { + if (this.err !== null) throw this.err; + if (this.usedBufferBytes === 0) return; + + try { + const p = this.buf.subarray(0, this.usedBufferBytes); + let nwritten = 0; + while (nwritten < p.length) { + nwritten += this.#writer.writeSync(p.subarray(nwritten)); + } + } catch (e) { + if (e instanceof Error) { + this.err = e; + } + throw e; + } + + this.buf = new Uint8Array(this.buf.length); + this.usedBufferBytes = 0; + } + + /** Writes the contents of `data` into the buffer. If the contents won't fully + * fit into the buffer, those bytes that can are copied into the buffer, the + * buffer is the flushed to the writer and the remaining bytes are copied into + * the now empty buffer. + * + * @return the number of bytes written to the buffer. + */ + writeSync(data: Uint8Array): number { + if (this.err !== null) throw this.err; + if (data.length === 0) return 0; + + let totalBytesWritten = 0; + let numBytesWritten = 0; + while (data.byteLength > this.available()) { + if (this.buffered() === 0) { + // Large write, empty buffer. + // Write directly from data to avoid copy. + try { + numBytesWritten = this.#writer.writeSync(data); + } catch (e) { + if (e instanceof Error) { + this.err = e; + } + throw e; + } + } else { + numBytesWritten = copy(data, this.buf, this.usedBufferBytes); + this.usedBufferBytes += numBytesWritten; + this.flush(); + } + totalBytesWritten += numBytesWritten; + data = data.subarray(numBytesWritten); + } + + numBytesWritten = copy(data, this.buf, this.usedBufferBytes); + this.usedBufferBytes += numBytesWritten; + totalBytesWritten += numBytesWritten; + return totalBytesWritten; + } +} diff --git a/vendor/deno.land/std@0.191.0/io/buffer.ts b/vendor/deno.land/std@0.191.0/io/buffer.ts new file mode 100644 index 0000000..e24ed1e --- /dev/null +++ b/vendor/deno.land/std@0.191.0/io/buffer.ts @@ -0,0 +1,249 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. +// This module is browser compatible. + +import { assert } from "../_util/asserts.ts"; +import { copy } from "../bytes/copy.ts"; +import type { Reader, ReaderSync } from "../types.d.ts"; + +// MIN_READ is the minimum ArrayBuffer size passed to a read call by +// buffer.ReadFrom. As long as the Buffer has at least MIN_READ bytes beyond +// what is required to hold the contents of r, readFrom() will not grow the +// underlying buffer. +const MIN_READ = 32 * 1024; +const MAX_SIZE = 2 ** 32 - 2; + +/** A variable-sized buffer of bytes with `read()` and `write()` methods. + * + * Buffer is almost always used with some I/O like files and sockets. It allows + * one to buffer up a download from a socket. Buffer grows and shrinks as + * necessary. + * + * Buffer is NOT the same thing as Node's Buffer. Node's Buffer was created in + * 2009 before JavaScript had the concept of ArrayBuffers. It's simply a + * non-standard ArrayBuffer. + * + * ArrayBuffer is a fixed memory allocation. Buffer is implemented on top of + * ArrayBuffer. + * + * Based on [Go Buffer](https://golang.org/pkg/bytes/#Buffer). */ + +export class Buffer { + #buf: Uint8Array; // contents are the bytes buf[off : len(buf)] + #off = 0; // read at buf[off], write at buf[buf.byteLength] + + constructor(ab?: ArrayBufferLike | ArrayLike) { + this.#buf = ab === undefined ? new Uint8Array(0) : new Uint8Array(ab); + } + + /** Returns a slice holding the unread portion of the buffer. + * + * The slice is valid for use only until the next buffer modification (that + * is, only until the next call to a method like `read()`, `write()`, + * `reset()`, or `truncate()`). If `options.copy` is false the slice aliases the buffer content at + * least until the next buffer modification, so immediate changes to the + * slice will affect the result of future reads. + * @param [options={ copy: true }] + */ + bytes(options = { copy: true }): Uint8Array { + if (options.copy === false) return this.#buf.subarray(this.#off); + return this.#buf.slice(this.#off); + } + + /** Returns whether the unread portion of the buffer is empty. */ + empty(): boolean { + return this.#buf.byteLength <= this.#off; + } + + /** A read only number of bytes of the unread portion of the buffer. */ + get length(): number { + return this.#buf.byteLength - this.#off; + } + + /** The read only capacity of the buffer's underlying byte slice, that is, + * the total space allocated for the buffer's data. */ + get capacity(): number { + return this.#buf.buffer.byteLength; + } + + /** Discards all but the first `n` unread bytes from the buffer but + * continues to use the same allocated storage. It throws if `n` is + * negative or greater than the length of the buffer. */ + truncate(n: number) { + if (n === 0) { + this.reset(); + return; + } + if (n < 0 || n > this.length) { + throw Error("bytes.Buffer: truncation out of range"); + } + this.#reslice(this.#off + n); + } + + reset() { + this.#reslice(0); + this.#off = 0; + } + + #tryGrowByReslice(n: number) { + const l = this.#buf.byteLength; + if (n <= this.capacity - l) { + this.#reslice(l + n); + return l; + } + return -1; + } + + #reslice(len: number) { + assert(len <= this.#buf.buffer.byteLength); + this.#buf = new Uint8Array(this.#buf.buffer, 0, len); + } + + /** Reads the next `p.length` bytes from the buffer or until the buffer is + * drained. Returns the number of bytes read. If the buffer has no data to + * return, the return is EOF (`null`). */ + readSync(p: Uint8Array): number | null { + if (this.empty()) { + // Buffer is empty, reset to recover space. + this.reset(); + if (p.byteLength === 0) { + // this edge case is tested in 'bufferReadEmptyAtEOF' test + return 0; + } + return null; + } + const nread = copy(this.#buf.subarray(this.#off), p); + this.#off += nread; + return nread; + } + + /** Reads the next `p.length` bytes from the buffer or until the buffer is + * drained. Resolves to the number of bytes read. If the buffer has no + * data to return, resolves to EOF (`null`). + * + * NOTE: This methods reads bytes synchronously; it's provided for + * compatibility with `Reader` interfaces. + */ + read(p: Uint8Array): Promise { + const rr = this.readSync(p); + return Promise.resolve(rr); + } + + writeSync(p: Uint8Array): number { + const m = this.#grow(p.byteLength); + return copy(p, this.#buf, m); + } + + /** NOTE: This methods writes bytes synchronously; it's provided for + * compatibility with `Writer` interface. */ + write(p: Uint8Array): Promise { + const n = this.writeSync(p); + return Promise.resolve(n); + } + + #grow(n: number) { + const m = this.length; + // If buffer is empty, reset to recover space. + if (m === 0 && this.#off !== 0) { + this.reset(); + } + // Fast: Try to grow by means of a reslice. + const i = this.#tryGrowByReslice(n); + if (i >= 0) { + return i; + } + const c = this.capacity; + if (n <= Math.floor(c / 2) - m) { + // We can slide things down instead of allocating a new + // ArrayBuffer. We only need m+n <= c to slide, but + // we instead let capacity get twice as large so we + // don't spend all our time copying. + copy(this.#buf.subarray(this.#off), this.#buf); + } else if (c + n > MAX_SIZE) { + throw new Error("The buffer cannot be grown beyond the maximum size."); + } else { + // Not enough space anywhere, we need to allocate. + const buf = new Uint8Array(Math.min(2 * c + n, MAX_SIZE)); + copy(this.#buf.subarray(this.#off), buf); + this.#buf = buf; + } + // Restore this.#off and len(this.#buf). + this.#off = 0; + this.#reslice(Math.min(m + n, MAX_SIZE)); + return m; + } + + /** Grows the buffer's capacity, if necessary, to guarantee space for + * another `n` bytes. After `.grow(n)`, at least `n` bytes can be written to + * the buffer without another allocation. If `n` is negative, `.grow()` will + * throw. If the buffer can't grow it will throw an error. + * + * Based on Go Lang's + * [Buffer.Grow](https://golang.org/pkg/bytes/#Buffer.Grow). */ + grow(n: number) { + if (n < 0) { + throw Error("Buffer.grow: negative count"); + } + const m = this.#grow(n); + this.#reslice(m); + } + + /** Reads data from `r` until EOF (`null`) and appends it to the buffer, + * growing the buffer as needed. It resolves to the number of bytes read. + * If the buffer becomes too large, `.readFrom()` will reject with an error. + * + * Based on Go Lang's + * [Buffer.ReadFrom](https://golang.org/pkg/bytes/#Buffer.ReadFrom). */ + async readFrom(r: Reader): Promise { + let n = 0; + const tmp = new Uint8Array(MIN_READ); + while (true) { + const shouldGrow = this.capacity - this.length < MIN_READ; + // read into tmp buffer if there's not enough room + // otherwise read directly into the internal buffer + const buf = shouldGrow + ? tmp + : new Uint8Array(this.#buf.buffer, this.length); + + const nread = await r.read(buf); + if (nread === null) { + return n; + } + + // write will grow if needed + if (shouldGrow) this.writeSync(buf.subarray(0, nread)); + else this.#reslice(this.length + nread); + + n += nread; + } + } + + /** Reads data from `r` until EOF (`null`) and appends it to the buffer, + * growing the buffer as needed. It returns the number of bytes read. If the + * buffer becomes too large, `.readFromSync()` will throw an error. + * + * Based on Go Lang's + * [Buffer.ReadFrom](https://golang.org/pkg/bytes/#Buffer.ReadFrom). */ + readFromSync(r: ReaderSync): number { + let n = 0; + const tmp = new Uint8Array(MIN_READ); + while (true) { + const shouldGrow = this.capacity - this.length < MIN_READ; + // read into tmp buffer if there's not enough room + // otherwise read directly into the internal buffer + const buf = shouldGrow + ? tmp + : new Uint8Array(this.#buf.buffer, this.length); + + const nread = r.readSync(buf); + if (nread === null) { + return n; + } + + // write will grow if needed + if (shouldGrow) this.writeSync(buf.subarray(0, nread)); + else this.#reslice(this.length + nread); + + n += nread; + } + } +} diff --git a/vendor/deno.land/std@0.191.0/io/copy_n.ts b/vendor/deno.land/std@0.191.0/io/copy_n.ts new file mode 100644 index 0000000..127b336 --- /dev/null +++ b/vendor/deno.land/std@0.191.0/io/copy_n.ts @@ -0,0 +1,41 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. +// This module is browser compatible. + +import { assert } from "../_util/asserts.ts"; +import type { Reader, Writer } from "../types.d.ts"; + +const DEFAULT_BUFFER_SIZE = 32 * 1024; + +/** + * Copy N size at the most. If read size is lesser than N, then returns nread + * @param r Reader + * @param dest Writer + * @param size Read size + */ +export async function copyN( + r: Reader, + dest: Writer, + size: number, +): Promise { + let bytesRead = 0; + let buf = new Uint8Array(DEFAULT_BUFFER_SIZE); + while (bytesRead < size) { + if (size - bytesRead < DEFAULT_BUFFER_SIZE) { + buf = new Uint8Array(size - bytesRead); + } + const result = await r.read(buf); + const nread = result ?? 0; + bytesRead += nread; + if (nread > 0) { + let n = 0; + while (n < nread) { + n += await dest.write(buf.slice(n, nread)); + } + assert(n === nread, "could not write"); + } + if (result === null) { + break; + } + } + return bytesRead; +} diff --git a/vendor/deno.land/std@0.191.0/io/limited_reader.ts b/vendor/deno.land/std@0.191.0/io/limited_reader.ts new file mode 100644 index 0000000..5be9629 --- /dev/null +++ b/vendor/deno.land/std@0.191.0/io/limited_reader.ts @@ -0,0 +1,31 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. +// This module is browser compatible. + +/** + * A `LimitedReader` reads from `reader` but limits the amount of data returned to just `limit` bytes. + * Each call to `read` updates `limit` to reflect the new amount remaining. + * `read` returns `null` when `limit` <= `0` or + * when the underlying `reader` returns `null`. + */ +import type { Reader } from "../types.d.ts"; + +export class LimitedReader implements Reader { + constructor(public reader: Reader, public limit: number) {} + + async read(p: Uint8Array): Promise { + if (this.limit <= 0) { + return null; + } + + if (p.length > this.limit) { + p = p.subarray(0, this.limit); + } + const n = await this.reader.read(p); + if (n == null) { + return null; + } + + this.limit -= n; + return n; + } +} diff --git a/vendor/deno.land/std@0.191.0/io/mod.ts b/vendor/deno.land/std@0.191.0/io/mod.ts new file mode 100644 index 0000000..8efa9a3 --- /dev/null +++ b/vendor/deno.land/std@0.191.0/io/mod.ts @@ -0,0 +1,27 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. + +/** + * Utilities for working with Deno's readers, writers, and web streams. + * + * `Reader` and `Writer` interfaces are deprecated in Deno, and so many of these + * utilities are also deprecated. Consider using web streams instead. + * + * @module + */ + +export * from "./buf_reader.ts"; +export * from "./buf_writer.ts"; +export * from "./buffer.ts"; +export * from "./copy_n.ts"; +export * from "./limited_reader.ts"; +export * from "./multi_reader.ts"; +export * from "./read_delim.ts"; +export * from "./read_int.ts"; +export * from "./read_lines.ts"; +export * from "./read_long.ts"; +export * from "./read_range.ts"; +export * from "./read_short.ts"; +export * from "./read_string_delim.ts"; +export * from "./slice_long_to_bytes.ts"; +export * from "./string_reader.ts"; +export * from "./string_writer.ts"; diff --git a/vendor/deno.land/std@0.191.0/io/multi_reader.ts b/vendor/deno.land/std@0.191.0/io/multi_reader.ts new file mode 100644 index 0000000..e0402b4 --- /dev/null +++ b/vendor/deno.land/std@0.191.0/io/multi_reader.ts @@ -0,0 +1,25 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. +// This module is browser compatible. + +import type { Reader } from "../types.d.ts"; + +/** Reader utility for combining multiple readers */ +export class MultiReader implements Reader { + readonly #readers: Reader[]; + #currentIndex = 0; + + constructor(readers: Reader[]) { + this.#readers = [...readers]; + } + + async read(p: Uint8Array): Promise { + const r = this.#readers[this.#currentIndex]; + if (!r) return null; + const result = await r.read(p); + if (result === null) { + this.#currentIndex++; + return 0; + } + return result; + } +} diff --git a/vendor/deno.land/std@0.191.0/io/read_delim.ts b/vendor/deno.land/std@0.191.0/io/read_delim.ts new file mode 100644 index 0000000..26a0c07 --- /dev/null +++ b/vendor/deno.land/std@0.191.0/io/read_delim.ts @@ -0,0 +1,80 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. +// This module is browser compatible. + +import { BytesList } from "../bytes/bytes_list.ts"; +import type { Reader } from "../types.d.ts"; + +/** Generate longest proper prefix which is also suffix array. */ +function createLPS(pat: Uint8Array): Uint8Array { + const lps = new Uint8Array(pat.length); + lps[0] = 0; + let prefixEnd = 0; + let i = 1; + while (i < lps.length) { + if (pat[i] == pat[prefixEnd]) { + prefixEnd++; + lps[i] = prefixEnd; + i++; + } else if (prefixEnd === 0) { + lps[i] = 0; + i++; + } else { + prefixEnd = lps[prefixEnd - 1]; + } + } + return lps; +} + +/** Read delimited bytes from a Reader. */ +export async function* readDelim( + reader: Reader, + delim: Uint8Array, +): AsyncIterableIterator { + // Avoid unicode problems + const delimLen = delim.length; + const delimLPS = createLPS(delim); + const chunks = new BytesList(); + const bufSize = Math.max(1024, delimLen + 1); + + // Modified KMP + let inspectIndex = 0; + let matchIndex = 0; + while (true) { + const inspectArr = new Uint8Array(bufSize); + const result = await reader.read(inspectArr); + if (result === null) { + // Yield last chunk. + yield chunks.concat(); + return; + } else if (result < 0) { + // Discard all remaining and silently fail. + return; + } + chunks.add(inspectArr, 0, result); + let localIndex = 0; + while (inspectIndex < chunks.size()) { + if (inspectArr[localIndex] === delim[matchIndex]) { + inspectIndex++; + localIndex++; + matchIndex++; + if (matchIndex === delimLen) { + // Full match + const matchEnd = inspectIndex - delimLen; + const readyBytes = chunks.slice(0, matchEnd); + yield readyBytes; + // Reset match, different from KMP. + chunks.shift(inspectIndex); + inspectIndex = 0; + matchIndex = 0; + } + } else { + if (matchIndex === 0) { + inspectIndex++; + localIndex++; + } else { + matchIndex = delimLPS[matchIndex - 1]; + } + } + } + } +} diff --git a/vendor/deno.land/std@0.191.0/io/read_int.ts b/vendor/deno.land/std@0.191.0/io/read_int.ts new file mode 100644 index 0000000..ce580de --- /dev/null +++ b/vendor/deno.land/std@0.191.0/io/read_int.ts @@ -0,0 +1,16 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. + +import { type BufReader } from "./buf_reader.ts"; +import { readShort } from "./read_short.ts"; + +/** + * Read big endian 32bit integer from BufReader + * @param buf + */ +export async function readInt(buf: BufReader): Promise { + const high = await readShort(buf); + if (high === null) return null; + const low = await readShort(buf); + if (low === null) throw new Deno.errors.UnexpectedEof(); + return (high << 16) | low; +} diff --git a/vendor/deno.land/std@0.191.0/io/read_lines.ts b/vendor/deno.land/std@0.191.0/io/read_lines.ts new file mode 100644 index 0000000..f158aba --- /dev/null +++ b/vendor/deno.land/std@0.191.0/io/read_lines.ts @@ -0,0 +1,49 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. +// This module is browser compatible. + +import { type Reader } from "../types.d.ts"; +import { BufReader } from "./buf_reader.ts"; +import { concat } from "../bytes/concat.ts"; + +/** + * Read strings line-by-line from a Reader. + * + * @example + * ```ts + * import { readLines } from "https://deno.land/std@$STD_VERSION/io/read_lines.ts"; + * import * as path from "https://deno.land/std@$STD_VERSION/path/mod.ts"; + * + * const filename = path.join(Deno.cwd(), "std/io/README.md"); + * let fileReader = await Deno.open(filename); + * + * for await (let line of readLines(fileReader)) { + * console.log(line); + * } + * ``` + */ +export async function* readLines( + reader: Reader, + decoderOpts?: { + encoding?: string; + fatal?: boolean; + ignoreBOM?: boolean; + }, +): AsyncIterableIterator { + const bufReader = new BufReader(reader); + let chunks: Uint8Array[] = []; + const decoder = new TextDecoder(decoderOpts?.encoding, decoderOpts); + while (true) { + const res = await bufReader.readLine(); + if (!res) { + if (chunks.length > 0) { + yield decoder.decode(concat(...chunks)); + } + break; + } + chunks.push(res.line); + if (!res.more) { + yield decoder.decode(concat(...chunks)); + chunks = []; + } + } +} diff --git a/vendor/deno.land/std@0.191.0/io/read_long.ts b/vendor/deno.land/std@0.191.0/io/read_long.ts new file mode 100644 index 0000000..acc5a0a --- /dev/null +++ b/vendor/deno.land/std@0.191.0/io/read_long.ts @@ -0,0 +1,25 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. + +import { type BufReader } from "./buf_reader.ts"; +import { readInt } from "./read_int.ts"; + +const MAX_SAFE_INTEGER = BigInt(Number.MAX_SAFE_INTEGER); + +/** + * Read big endian 64bit long from BufReader + * @param buf + */ +export async function readLong(buf: BufReader): Promise { + const high = await readInt(buf); + if (high === null) return null; + const low = await readInt(buf); + if (low === null) throw new Deno.errors.UnexpectedEof(); + const big = (BigInt(high) << 32n) | BigInt(low); + // We probably should provide a similar API that returns BigInt values. + if (big > MAX_SAFE_INTEGER) { + throw new RangeError( + "Long value too big to be represented as a JavaScript number.", + ); + } + return Number(big); +} diff --git a/vendor/deno.land/std@0.191.0/io/read_range.ts b/vendor/deno.land/std@0.191.0/io/read_range.ts new file mode 100644 index 0000000..41a45d9 --- /dev/null +++ b/vendor/deno.land/std@0.191.0/io/read_range.ts @@ -0,0 +1,91 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. + +import { copy as copyBytes } from "../bytes/copy.ts"; +import { assert } from "../_util/asserts.ts"; +import type { Reader, ReaderSync } from "../types.d.ts"; + +const DEFAULT_BUFFER_SIZE = 32 * 1024; + +export interface ByteRange { + /** The 0 based index of the start byte for a range. */ + start: number; + + /** The 0 based index of the end byte for a range, which is inclusive. */ + end: number; +} + +/** + * Read a range of bytes from a file or other resource that is readable and + * seekable. The range start and end are inclusive of the bytes within that + * range. + * + * ```ts + * import { assertEquals } from "https://deno.land/std@$STD_VERSION/testing/asserts.ts"; + * import { readRange } from "https://deno.land/std@$STD_VERSION/io/read_range.ts"; + * + * // Read the first 10 bytes of a file + * const file = await Deno.open("example.txt", { read: true }); + * const bytes = await readRange(file, { start: 0, end: 9 }); + * assertEquals(bytes.length, 10); + * ``` + */ +export async function readRange( + r: Reader & Deno.Seeker, + range: ByteRange, +): Promise { + // byte ranges are inclusive, so we have to add one to the end + let length = range.end - range.start + 1; + assert(length > 0, "Invalid byte range was passed."); + await r.seek(range.start, Deno.SeekMode.Start); + const result = new Uint8Array(length); + let off = 0; + while (length) { + const p = new Uint8Array(Math.min(length, DEFAULT_BUFFER_SIZE)); + const nread = await r.read(p); + assert(nread !== null, "Unexpected EOF reach while reading a range."); + assert(nread > 0, "Unexpected read of 0 bytes while reading a range."); + copyBytes(p, result, off); + off += nread; + length -= nread; + assert(length >= 0, "Unexpected length remaining after reading range."); + } + return result; +} + +/** + * Read a range of bytes synchronously from a file or other resource that is + * readable and seekable. The range start and end are inclusive of the bytes + * within that range. + * + * ```ts + * import { assertEquals } from "https://deno.land/std@$STD_VERSION/testing/asserts.ts"; + * import { readRangeSync } from "https://deno.land/std@$STD_VERSION/io/read_range.ts"; + * + * // Read the first 10 bytes of a file + * const file = Deno.openSync("example.txt", { read: true }); + * const bytes = readRangeSync(file, { start: 0, end: 9 }); + * assertEquals(bytes.length, 10); + * ``` + */ +export function readRangeSync( + r: ReaderSync & Deno.SeekerSync, + range: ByteRange, +): Uint8Array { + // byte ranges are inclusive, so we have to add one to the end + let length = range.end - range.start + 1; + assert(length > 0, "Invalid byte range was passed."); + r.seekSync(range.start, Deno.SeekMode.Start); + const result = new Uint8Array(length); + let off = 0; + while (length) { + const p = new Uint8Array(Math.min(length, DEFAULT_BUFFER_SIZE)); + const nread = r.readSync(p); + assert(nread !== null, "Unexpected EOF reach while reading a range."); + assert(nread > 0, "Unexpected read of 0 bytes while reading a range."); + copyBytes(p, result, off); + off += nread; + length -= nread; + assert(length >= 0, "Unexpected length remaining after reading range."); + } + return result; +} diff --git a/vendor/deno.land/std@0.191.0/io/read_short.ts b/vendor/deno.land/std@0.191.0/io/read_short.ts new file mode 100644 index 0000000..92835f5 --- /dev/null +++ b/vendor/deno.land/std@0.191.0/io/read_short.ts @@ -0,0 +1,15 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. + +import { type BufReader } from "./buf_reader.ts"; + +/** + * Read big endian 16bit short from BufReader + * @param buf + */ +export async function readShort(buf: BufReader): Promise { + const high = await buf.readByte(); + if (high === null) return null; + const low = await buf.readByte(); + if (low === null) throw new Deno.errors.UnexpectedEof(); + return (high << 8) | low; +} diff --git a/vendor/deno.land/std@0.191.0/io/read_string_delim.ts b/vendor/deno.land/std@0.191.0/io/read_string_delim.ts new file mode 100644 index 0000000..bceade0 --- /dev/null +++ b/vendor/deno.land/std@0.191.0/io/read_string_delim.ts @@ -0,0 +1,37 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. +// This module is browser compatible. + +import { type Reader } from "../types.d.ts"; +import { readDelim } from "./read_delim.ts"; + +/** + * Read Reader chunk by chunk, splitting based on delimiter. + * + * @example + * ```ts + * import { readStringDelim } from "https://deno.land/std@$STD_VERSION/io/read_string_delim.ts"; + * import * as path from "https://deno.land/std@$STD_VERSION/path/mod.ts"; + * + * const filename = path.join(Deno.cwd(), "std/io/README.md"); + * let fileReader = await Deno.open(filename); + * + * for await (let line of readStringDelim(fileReader, "\n")) { + * console.log(line); + * } + * ``` + */ +export async function* readStringDelim( + reader: Reader, + delim: string, + decoderOpts?: { + encoding?: string; + fatal?: boolean; + ignoreBOM?: boolean; + }, +): AsyncIterableIterator { + const encoder = new TextEncoder(); + const decoder = new TextDecoder(decoderOpts?.encoding, decoderOpts); + for await (const chunk of readDelim(reader, encoder.encode(delim))) { + yield decoder.decode(chunk); + } +} diff --git a/vendor/deno.land/std@0.191.0/io/slice_long_to_bytes.ts b/vendor/deno.land/std@0.191.0/io/slice_long_to_bytes.ts new file mode 100644 index 0000000..6c4b141 --- /dev/null +++ b/vendor/deno.land/std@0.191.0/io/slice_long_to_bytes.ts @@ -0,0 +1,19 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. +// This module is browser compatible. + +/** + * Slice number into 64bit big endian byte array + * @param d The number to be sliced + * @param dest The sliced array + */ +export function sliceLongToBytes( + d: number, + dest = Array.from({ length: 8 }), +): number[] { + let big = BigInt(d); + for (let i = 0; i < 8; i++) { + dest[7 - i] = Number(big & 0xffn); + big >>= 8n; + } + return dest; +} diff --git a/vendor/deno.land/std@0.191.0/io/string_reader.ts b/vendor/deno.land/std@0.191.0/io/string_reader.ts new file mode 100644 index 0000000..55d376f --- /dev/null +++ b/vendor/deno.land/std@0.191.0/io/string_reader.ts @@ -0,0 +1,39 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. +// This module is browser compatible. + +import { Buffer } from "./buffer.ts"; + +/** + * Reader utility for strings. + * + * @example + * ```ts + * import { StringReader } from "https://deno.land/std@$STD_VERSION/io/string_reader.ts"; + * + * const data = new Uint8Array(6); + * const r = new StringReader("abcdef"); + * const res0 = await r.read(data); + * const res1 = await r.read(new Uint8Array(6)); + * + * // Number of bytes read + * console.log(res0); // 6 + * console.log(res1); // null, no byte left to read. EOL + * + * // text + * + * console.log(new TextDecoder().decode(data)); // abcdef + * ``` + * + * **Output:** + * + * ```text + * 6 + * null + * abcdef + * ``` + */ +export class StringReader extends Buffer { + constructor(s: string) { + super(new TextEncoder().encode(s).buffer); + } +} diff --git a/vendor/deno.land/std@0.191.0/io/string_writer.ts b/vendor/deno.land/std@0.191.0/io/string_writer.ts new file mode 100644 index 0000000..85488ee --- /dev/null +++ b/vendor/deno.land/std@0.191.0/io/string_writer.ts @@ -0,0 +1,73 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. +// This module is browser compatible. + +import type { Writer, WriterSync } from "../types.d.ts"; + +const decoder = new TextDecoder(); + +/** + * Writer utility for buffering string chunks. + * + * @example + * ```ts + * import { + * copyN, + * StringReader, + * StringWriter, + * } from "https://deno.land/std@$STD_VERSION/io/mod.ts"; + * import { copy } from "https://deno.land/std@$STD_VERSION/streams/copy.ts"; + * + * const w = new StringWriter("base"); + * const r = new StringReader("0123456789"); + * await copyN(r, w, 4); // copy 4 bytes + * + * // Number of bytes read + * console.log(w.toString()); //base0123 + * + * await copy(r, w); // copy all + * console.log(w.toString()); // base0123456789 + * ``` + * + * **Output:** + * + * ```text + * base0123 + * base0123456789 + * ``` + */ +export class StringWriter implements Writer, WriterSync { + #chunks: Uint8Array[] = []; + #byteLength = 0; + #cache: string | undefined; + + constructor(private base: string = "") { + const c = new TextEncoder().encode(base); + this.#chunks.push(c); + this.#byteLength += c.byteLength; + } + + write(p: Uint8Array): Promise { + return Promise.resolve(this.writeSync(p)); + } + + writeSync(p: Uint8Array): number { + this.#chunks.push(new Uint8Array(p)); + this.#byteLength += p.byteLength; + this.#cache = undefined; + return p.byteLength; + } + + toString(): string { + if (this.#cache) { + return this.#cache; + } + const buf = new Uint8Array(this.#byteLength); + let offs = 0; + for (const chunk of this.#chunks) { + buf.set(chunk, offs); + offs += chunk.byteLength; + } + this.#cache = decoder.decode(buf); + return this.#cache; + } +} diff --git a/vendor/deno.land/std@0.191.0/streams/read_all.ts b/vendor/deno.land/std@0.191.0/streams/read_all.ts new file mode 100644 index 0000000..76dd0ff --- /dev/null +++ b/vendor/deno.land/std@0.191.0/streams/read_all.ts @@ -0,0 +1,61 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. +// This module is browser compatible. + +import { Buffer } from "../io/buffer.ts"; +import type { Reader, ReaderSync } from "../types.d.ts"; + +/** Read Reader `r` until EOF (`null`) and resolve to the content as + * Uint8Array`. + * + * ```ts + * import { Buffer } from "https://deno.land/std@$STD_VERSION/io/buffer.ts"; + * import { readAll } from "https://deno.land/std@$STD_VERSION/streams/read_all.ts"; + * + * // Example from stdin + * const stdinContent = await readAll(Deno.stdin); + * + * // Example from file + * const file = await Deno.open("my_file.txt", {read: true}); + * const myFileContent = await readAll(file); + * file.close(); + * + * // Example from buffer + * const myData = new Uint8Array(100); + * // ... fill myData array with data + * const reader = new Buffer(myData.buffer); + * const bufferContent = await readAll(reader); + * ``` + */ +export async function readAll(r: Reader): Promise { + const buf = new Buffer(); + await buf.readFrom(r); + return buf.bytes(); +} + +/** Synchronously reads Reader `r` until EOF (`null`) and returns the content + * as `Uint8Array`. + * + * ```ts + * import { Buffer } from "https://deno.land/std@$STD_VERSION/io/buffer.ts"; + * import { readAllSync } from "https://deno.land/std@$STD_VERSION/streams/read_all.ts"; + * + * // Example from stdin + * const stdinContent = readAllSync(Deno.stdin); + * + * // Example from file + * const file = Deno.openSync("my_file.txt", {read: true}); + * const myFileContent = readAllSync(file); + * file.close(); + * + * // Example from buffer + * const myData = new Uint8Array(100); + * // ... fill myData array with data + * const reader = new Buffer(myData.buffer); + * const bufferContent = readAllSync(reader); + * ``` + */ +export function readAllSync(r: ReaderSync): Uint8Array { + const buf = new Buffer(); + buf.readFromSync(r); + return buf.bytes(); +} diff --git a/vendor/deno.land/x/oak@v12.1.0/application.ts b/vendor/deno.land/x/oak@v12.1.0/application.ts new file mode 100644 index 0000000..92ff30e --- /dev/null +++ b/vendor/deno.land/x/oak@v12.1.0/application.ts @@ -0,0 +1,699 @@ +// Copyright 2018-2022 the oak authors. All rights reserved. MIT license. + +import { Context } from "./context.ts"; +import { KeyStack, Status, STATUS_TEXT } from "./deps.ts"; +import { FlashServer } from "./http_server_flash.ts"; +import { HttpServer } from "./http_server_native.ts"; +import { NativeRequest } from "./http_server_native_request.ts"; +import { compose, Middleware } from "./middleware.ts"; +import { cloneState } from "./structured_clone.ts"; +import { + Key, + Listener, + Server, + ServerConstructor, + ServerRequest, +} from "./types.d.ts"; +import { assert, isConn } from "./util.ts"; + +export interface ListenOptionsBase { + /** The port to listen on. If not specified, defaults to `0`, which allows the + * operating system to determine the value. */ + port?: number; + /** A literal IP address or host name that can be resolved to an IP address. + * If not specified, defaults to `0.0.0.0`. + * + * __Note about `0.0.0.0`__ While listening `0.0.0.0` works on all platforms, + * the browsers on Windows don't work with the address `0.0.0.0`. + * You should show the message like `server running on localhost:8080` instead of + * `server running on 0.0.0.0:8080` if your program supports Windows. */ + hostname?: string; + secure?: false; + /** An optional abort signal which can be used to close the listener. */ + signal?: AbortSignal; +} + +export interface ListenOptionsTls extends Deno.ListenTlsOptions { + /** Application-Layer Protocol Negotiation (ALPN) protocols to announce to + * the client. If not specified, no ALPN extension will be included in the + * TLS handshake. + * + * **NOTE** this is part of the native HTTP server in Deno 1.9 or later, + * which requires the `--unstable` flag to be available. + */ + alpnProtocols?: string[]; + secure: true; + /** An optional abort signal which can be used to close the listener. */ + signal?: AbortSignal; +} + +export interface HandleMethod { + /** Handle an individual server request, returning the server response. This + * is similar to `.listen()`, but opening the connection and retrieving + * requests are not the responsibility of the application. If the generated + * context gets set to not to respond, then the method resolves with + * `undefined`, otherwise it resolves with a DOM `Response` object. */ + ( + request: Request, + conn?: Deno.Conn, + secure?: boolean, + ): Promise; +} + +export type ListenOptions = ListenOptionsTls | ListenOptionsBase; + +interface ApplicationErrorEventListener { + (evt: ApplicationErrorEvent): void | Promise; +} + +interface ApplicationErrorEventListenerObject { + handleEvent(evt: ApplicationErrorEvent): void | Promise; +} + +interface ApplicationErrorEventInit + extends ErrorEventInit { + context?: Context; +} + +type ApplicationErrorEventListenerOrEventListenerObject< + S extends AS, + AS extends State, +> = + | ApplicationErrorEventListener + | ApplicationErrorEventListenerObject; + +interface ApplicationListenEventListener { + (evt: ApplicationListenEvent): void | Promise; +} + +interface ApplicationListenEventListenerObject { + handleEvent(evt: ApplicationListenEvent): void | Promise; +} + +interface ApplicationListenEventInit extends EventInit { + hostname: string; + listener: Listener; + port: number; + secure: boolean; + serverType: "native" | "flash" | "custom"; +} + +type ApplicationListenEventListenerOrEventListenerObject = + | ApplicationListenEventListener + | ApplicationListenEventListenerObject; + +/** Available options that are used when creating a new instance of + * {@linkcode Application}. */ +export interface ApplicationOptions { + /** Determine how when creating a new context, the state from the application + * should be applied. A value of `"clone"` will set the state as a clone of + * the app state. Any non-cloneable or non-enumerable properties will not be + * copied. A value of `"prototype"` means that the application's state will be + * used as the prototype of the the context's state, meaning shallow + * properties on the context's state will not be reflected in the + * application's state. A value of `"alias"` means that application's `.state` + * and the context's `.state` will be a reference to the same object. A value + * of `"empty"` will initialize the context's `.state` with an empty object. + * + * The default value is `"clone"`. + */ + contextState?: "clone" | "prototype" | "alias" | "empty"; + + /** An optional replacer function to be used when serializing a JSON + * response. The replacer will be used with `JSON.stringify()` to encode any + * response bodies that need to be converted before sending the response. + * + * This is intended to allow responses to contain bigints and circular + * references and encoding other values which JSON does not support directly. + * + * This can be used in conjunction with `jsonBodyReviver` to handle decoding + * of request bodies if the same semantics are used for client requests. + * + * If more detailed or conditional usage is required, then serialization + * should be implemented directly in middleware. */ + jsonBodyReplacer?: ( + key: string, + value: unknown, + context: Context, + ) => unknown; + + /** An optional reviver function to be used when parsing a JSON request. The + * reviver will be used with `JSON.parse()` to decode any response bodies that + * are being converted as JSON. + * + * This is intended to allow requests to deserialize to bigints, circular + * references, or other values which JSON does not support directly. + * + * This can be used in conjunction with `jsonBodyReplacer` to handle decoding + * of response bodies if the same semantics are used for responses. + * + * If more detailed or conditional usage is required, then deserialization + * should be implemented directly in the middleware. + */ + jsonBodyReviver?: ( + key: string, + value: unknown, + context: Context, + ) => unknown; + + /** An initial set of keys (or instance of {@linkcode KeyStack}) to be used for signing + * cookies produced by the application. */ + keys?: KeyStack | Key[]; + + /** If `true`, any errors handled by the application will be logged to the + * stderr. If `false` nothing will be logged. The default is `true`. + * + * All errors are available as events on the application of type `"error"` and + * can be accessed for custom logging/application management via adding an + * event listener to the application: + * + * ```ts + * const app = new Application({ logErrors: false }); + * app.addEventListener("error", (evt) => { + * // evt.error will contain what error was thrown + * }); + * ``` + */ + logErrors?: boolean; + + /** If set to `true`, proxy headers will be trusted when processing requests. + * This defaults to `false`. */ + proxy?: boolean; + + /** A server constructor to use instead of the default server for receiving + * requests. + * + * Generally this is only used for testing. */ + serverConstructor?: ServerConstructor; + + /** The initial state object for the application, of which the type can be + * used to infer the type of the state for both the application and any of the + * application's context. */ + state?: S; +} + +interface RequestState { + handling: Set>; + closing: boolean; + closed: boolean; + server: Server; +} + +// deno-lint-ignore no-explicit-any +export type State = Record; + +const ADDR_REGEXP = /^\[?([^\]]*)\]?:([0-9]{1,5})$/; + +const DEFAULT_SERVER: ServerConstructor = HttpServer; + +export class ApplicationErrorEvent + extends ErrorEvent { + context?: Context; + + constructor(eventInitDict: ApplicationErrorEventInit) { + super("error", eventInitDict); + this.context = eventInitDict.context; + } +} + +function logErrorListener( + { error, context }: ApplicationErrorEvent, +) { + if (error instanceof Error) { + console.error( + `[uncaught application error]: ${error.name} - ${error.message}`, + ); + } else { + console.error(`[uncaught application error]\n`, error); + } + if (context) { + let url: string; + try { + url = context.request.url.toString(); + } catch { + url = "[malformed url]"; + } + console.error(`\nrequest:`, { + url, + method: context.request.method, + hasBody: context.request.hasBody, + }); + console.error(`response:`, { + status: context.response.status, + type: context.response.type, + hasBody: !!context.response.body, + writable: context.response.writable, + }); + } + if (error instanceof Error && error.stack) { + console.error(`\n${error.stack.split("\n").slice(1).join("\n")}`); + } +} + +export class ApplicationListenEvent extends Event { + hostname: string; + listener: Listener; + port: number; + secure: boolean; + serverType: "native" | "flash" | "custom"; + + constructor(eventInitDict: ApplicationListenEventInit) { + super("listen", eventInitDict); + this.hostname = eventInitDict.hostname; + this.listener = eventInitDict.listener; + this.port = eventInitDict.port; + this.secure = eventInitDict.secure; + this.serverType = eventInitDict.serverType; + } +} + +/** A class which registers middleware (via `.use()`) and then processes + * inbound requests against that middleware (via `.listen()`). + * + * The `context.state` can be typed via passing a generic argument when + * constructing an instance of `Application`. It can also be inferred by setting + * the {@linkcode ApplicationOptions.state} option when constructing the + * application. + * + * ### Basic example + * + * ```ts + * import { Application } from "https://deno.land/x/oak/mod.ts"; + * + * const app = new Application(); + * + * app.use((ctx, next) => { + * // called on each request with the context (`ctx`) of the request, + * // response, and other data. + * // `next()` is use to modify the flow control of the middleware stack. + * }); + * + * app.listen({ port: 8080 }); + * ``` + * + * @template AS the type of the application state which extends + * {@linkcode State} and defaults to a simple string record. + */ +// deno-lint-ignore no-explicit-any +export class Application> + extends EventTarget { + #composedMiddleware?: (context: Context) => Promise; + #contextOptions: Pick< + ApplicationOptions, + "jsonBodyReplacer" | "jsonBodyReviver" + >; + #contextState: "clone" | "prototype" | "alias" | "empty"; + #keys?: KeyStack; + #middleware: Middleware>[] = []; + #serverConstructor: ServerConstructor; + + /** A set of keys, or an instance of `KeyStack` which will be used to sign + * cookies read and set by the application to avoid tampering with the + * cookies. */ + get keys(): KeyStack | Key[] | undefined { + return this.#keys; + } + + set keys(keys: KeyStack | Key[] | undefined) { + if (!keys) { + this.#keys = undefined; + return; + } else if (Array.isArray(keys)) { + this.#keys = new KeyStack(keys); + } else { + this.#keys = keys; + } + } + + /** If `true`, proxy headers will be trusted when processing requests. This + * defaults to `false`. */ + proxy: boolean; + + /** Generic state of the application, which can be specified by passing the + * generic argument when constructing: + * + * const app = new Application<{ foo: string }>(); + * + * Or can be contextually inferred based on setting an initial state object: + * + * const app = new Application({ state: { foo: "bar" } }); + * + * When a new context is created, the application's state is cloned and the + * state is unique to that request/response. Changes can be made to the + * application state that will be shared with all contexts. + */ + state: AS; + + constructor(options: ApplicationOptions = {}) { + super(); + const { + state, + keys, + proxy, + serverConstructor = DEFAULT_SERVER, + contextState = "clone", + logErrors = true, + ...contextOptions + } = options; + + this.proxy = proxy ?? false; + this.keys = keys; + this.state = state ?? {} as AS; + this.#serverConstructor = serverConstructor; + this.#contextOptions = contextOptions; + this.#contextState = contextState; + + if (logErrors) { + this.addEventListener("error", logErrorListener); + } + } + + #getComposed(): (context: Context) => Promise { + if (!this.#composedMiddleware) { + this.#composedMiddleware = compose(this.#middleware); + } + return this.#composedMiddleware; + } + + #getContextState(): AS { + switch (this.#contextState) { + case "alias": + return this.state; + case "clone": + return cloneState(this.state); + case "empty": + return {} as AS; + case "prototype": + return Object.create(this.state); + } + } + + /** Deal with uncaught errors in either the middleware or sending the + * response. */ + // deno-lint-ignore no-explicit-any + #handleError(context: Context, error: any): void { + if (!(error instanceof Error)) { + error = new Error(`non-error thrown: ${JSON.stringify(error)}`); + } + const { message } = error; + this.dispatchEvent(new ApplicationErrorEvent({ context, message, error })); + if (!context.response.writable) { + return; + } + for (const key of [...context.response.headers.keys()]) { + context.response.headers.delete(key); + } + if (error.headers && error.headers instanceof Headers) { + for (const [key, value] of error.headers) { + context.response.headers.set(key, value); + } + } + context.response.type = "text"; + const status: Status = context.response.status = + Deno.errors && error instanceof Deno.errors.NotFound + ? 404 + : error.status && typeof error.status === "number" + ? error.status + : 500; + context.response.body = error.expose ? error.message : STATUS_TEXT[status]; + } + + /** Processing registered middleware on each request. */ + async #handleRequest( + request: ServerRequest, + secure: boolean, + state: RequestState, + ): Promise { + let context: Context | undefined; + try { + context = new Context( + this, + request, + this.#getContextState(), + { secure, ...this.#contextOptions }, + ); + } catch (e) { + const error = e instanceof Error + ? e + : new Error(`non-error thrown: ${JSON.stringify(e)}`); + const { message } = error; + this.dispatchEvent(new ApplicationErrorEvent({ message, error })); + return; + } + assert(context, "Context was not created."); + let resolve: () => void; + const handlingPromise = new Promise((res) => resolve = res); + state.handling.add(handlingPromise); + if (!state.closing && !state.closed) { + try { + await this.#getComposed()(context); + } catch (err) { + this.#handleError(context, err); + } + } + if (context.respond === false) { + context.response.destroy(); + resolve!(); + state.handling.delete(handlingPromise); + return; + } + let closeResources = true; + let response: Response; + try { + closeResources = false; + response = await context.response.toDomResponse(); + } catch (err) { + this.#handleError(context, err); + response = await context.response.toDomResponse(); + } + assert(response); + try { + await request.respond(response); + } catch (err) { + this.#handleError(context, err); + } finally { + context.response.destroy(closeResources); + resolve!(); + state.handling.delete(handlingPromise); + if (state.closing) { + await state.server.close(); + state.closed = true; + } + } + } + + /** Add an event listener for an `"error"` event which occurs when an + * un-caught error occurs when processing the middleware or during processing + * of the response. */ + addEventListener( + type: "error", + listener: ApplicationErrorEventListenerOrEventListenerObject | null, + options?: boolean | AddEventListenerOptions, + ): void; + /** Add an event listener for a `"listen"` event which occurs when the server + * has successfully opened but before any requests start being processed. */ + addEventListener( + type: "listen", + listener: ApplicationListenEventListenerOrEventListenerObject | null, + options?: boolean | AddEventListenerOptions, + ): void; + /** Add an event listener for an event. Currently valid event types are + * `"error"` and `"listen"`. */ + addEventListener( + type: "error" | "listen", + listener: EventListenerOrEventListenerObject | null, + options?: boolean | AddEventListenerOptions, + ): void { + super.addEventListener(type, listener, options); + } + + /** Handle an individual server request, returning the server response. This + * is similar to `.listen()`, but opening the connection and retrieving + * requests are not the responsibility of the application. If the generated + * context gets set to not to respond, then the method resolves with + * `undefined`, otherwise it resolves with a request that is compatible with + * `std/http/server`. */ + handle = (async ( + request: Request, + secureOrConn: Deno.Conn | boolean | undefined, + secure: boolean | undefined = false, + ): Promise => { + if (!this.#middleware.length) { + throw new TypeError("There is no middleware to process requests."); + } + assert(isConn(secureOrConn) || typeof secureOrConn === "undefined"); + const contextRequest = new NativeRequest({ + request, + respondWith() { + return Promise.resolve(undefined); + }, + }, { conn: secureOrConn }); + const context = new Context( + this, + contextRequest, + this.#getContextState(), + { secure, ...this.#contextOptions }, + ); + try { + await this.#getComposed()(context); + } catch (err) { + this.#handleError(context, err); + } + if (context.respond === false) { + context.response.destroy(); + return; + } + try { + const response = await context.response.toDomResponse(); + context.response.destroy(false); + return response; + } catch (err) { + this.#handleError(context, err); + throw err; + } + }) as HandleMethod; + + /** Start listening for requests, processing registered middleware on each + * request. If the options `.secure` is undefined or `false`, the listening + * will be over HTTP. If the options `.secure` property is `true`, a + * `.certFile` and a `.keyFile` property need to be supplied and requests + * will be processed over HTTPS. */ + async listen(addr: string): Promise; + /** Start listening for requests, processing registered middleware on each + * request. If the options `.secure` is undefined or `false`, the listening + * will be over HTTP. If the options `.secure` property is `true`, a + * `.certFile` and a `.keyFile` property need to be supplied and requests + * will be processed over HTTPS. + * + * Omitting options will default to `{ port: 0 }` which allows the operating + * system to select the port. */ + async listen(options?: ListenOptions): Promise; + async listen(options: string | ListenOptions = { port: 0 }): Promise { + if (!this.#middleware.length) { + throw new TypeError("There is no middleware to process requests."); + } + if (typeof options === "string") { + const match = ADDR_REGEXP.exec(options); + if (!match) { + throw TypeError(`Invalid address passed: "${options}"`); + } + const [, hostname, portStr] = match; + options = { hostname, port: parseInt(portStr, 10) }; + } + options = Object.assign({ port: 0 }, options); + const server = new this.#serverConstructor( + this, + options as Deno.ListenOptions, + ); + const { signal } = options; + const state = { + closed: false, + closing: false, + handling: new Set>(), + server, + }; + if (signal) { + signal.addEventListener("abort", () => { + if (!state.handling.size) { + server.close(); + state.closed = true; + } + state.closing = true; + }); + } + const { secure = false } = options; + const serverType = server instanceof HttpServer + ? "native" + : server instanceof FlashServer + ? "flash" + : "custom"; + const listener = await server.listen(); + const { hostname, port } = listener.addr as Deno.NetAddr; + this.dispatchEvent( + new ApplicationListenEvent({ + hostname, + listener, + port, + secure, + serverType, + }), + ); + try { + for await (const request of server) { + this.#handleRequest(request, secure, state); + } + await Promise.all(state.handling); + } catch (error) { + const message = error instanceof Error + ? error.message + : "Application Error"; + this.dispatchEvent( + new ApplicationErrorEvent({ message, error }), + ); + } + } + + /** Register middleware to be used with the application. Middleware will + * be processed in the order it is added, but middleware can control the flow + * of execution via the use of the `next()` function that the middleware + * function will be called with. The `context` object provides information + * about the current state of the application. + * + * Basic usage: + * + * ```ts + * const import { Application } from "https://deno.land/x/oak/mod.ts"; + * + * const app = new Application(); + * + * app.use((ctx, next) => { + * ctx.request; // contains request information + * ctx.response; // setups up information to use in the response; + * await next(); // manages the flow control of the middleware execution + * }); + * + * await app.listen({ port: 80 }); + * ``` + */ + use( + middleware: Middleware>, + ...middlewares: Middleware>[] + ): Application; + use( + ...middleware: Middleware>[] + ): Application { + this.#middleware.push(...middleware); + this.#composedMiddleware = undefined; + // deno-lint-ignore no-explicit-any + return this as Application; + } + + [Symbol.for("Deno.customInspect")](inspect: (value: unknown) => string) { + const { keys, proxy, state } = this; + return `${this.constructor.name} ${ + inspect({ "#middleware": this.#middleware, keys, proxy, state }) + }`; + } + + [Symbol.for("nodejs.util.inspect.custom")]( + depth: number, + // deno-lint-ignore no-explicit-any + options: any, + inspect: (value: unknown, options?: unknown) => string, + ) { + if (depth < 0) { + return options.stylize(`[${this.constructor.name}]`, "special"); + } + + const newOptions = Object.assign({}, options, { + depth: options.depth === null ? null : options.depth - 1, + }); + const { keys, proxy, state } = this; + return `${options.stylize(this.constructor.name, "special")} ${ + inspect( + { "#middleware": this.#middleware, keys, proxy, state }, + newOptions, + ) + }`; + } +} diff --git a/vendor/deno.land/x/oak@v12.1.0/body.ts b/vendor/deno.land/x/oak@v12.1.0/body.ts new file mode 100644 index 0000000..75f01b8 --- /dev/null +++ b/vendor/deno.land/x/oak@v12.1.0/body.ts @@ -0,0 +1,407 @@ +// Copyright 2018-2022 the oak authors. All rights reserved. MIT license. + +import { errors, readerFromStreamReader } from "./deps.ts"; +import { isMediaType } from "./isMediaType.ts"; +import { FormDataReader } from "./multipart.ts"; +import type { ServerRequestBody } from "./types.d.ts"; +import { assert } from "./util.ts"; + +/** The type of the body, where: + * + * - `"bytes"` - the body is provided as a promise which resolves to an + * {@linkcode Uint8Array}. This is essentially a "raw" body type. + * - `"form"` - the body was decoded as a form with the contents provided as a + * promise which resolves with a {@linkcode URLSearchParams}. + * - `"form-data"` - the body was decoded as a multi-part form data and the + * contents are provided as a promise which resolves with a + * {@linkcode FormDataReader}. + * - `"json"` - the body was decoded as JSON, where the contents are provided as + * the result of using `JSON.parse()` on the string contents of the body. + * - `"text"` - the body was decoded as text, where the contents are provided as + * a string. + * - `"reader"` - the body is provided as {@linkcode Deno.Reader} interface for + * reading the "raw" body. + * - `"stream"` - the body is provided as a + * {@linkcode ReadableStream} for reading the "raw" body. + * - `"undefined"` - there is no request body or it could not be decoded. + */ +export type BodyType = + | "bytes" + | "form" + | "form-data" + | "json" + | "text" + | "reader" + | "stream" + | "undefined"; + +/** The tagged type for `"bytes"` bodies. */ +export type BodyBytes = { + readonly type: "bytes"; + readonly value: Promise; +}; +/** The tagged type for `"json"` bodies. */ +export type BodyJson = { + readonly type: "json"; + // deno-lint-ignore no-explicit-any + readonly value: Promise; +}; +/** The tagged type for `"form"` bodies. */ +export type BodyForm = { + readonly type: "form"; + readonly value: Promise; +}; +/** The tagged type for `"form-data"` bodies. */ +export type BodyFormData = { + readonly type: "form-data"; + readonly value: FormDataReader; +}; +/** The tagged type for `"text"` bodies. */ +export type BodyText = { + readonly type: "text"; + readonly value: Promise; +}; +/** The tagged type for `"undefined"` bodies. */ +export type BodyUndefined = { + readonly type: "undefined"; + readonly value: undefined; +}; +/** The tagged type for `"reader"` bodies. */ +export type BodyReader = { + readonly type: "reader"; + readonly value: Deno.Reader; +}; +/** The tagged type for `"stream"` bodies. */ +export type BodyStream = { + readonly type: "stream"; + readonly value: ReadableStream; +}; + +/** The type returned from the `.body()` function, which is a tagged union type + * of all the different types of bodies which can be identified by the `.type` + * property which will be of type {@linkcode BodyType} and the `.value` + * property being a `Promise` which resolves with the appropriate value, or + * `undefined` if there is no body. */ +export type Body = + | BodyBytes + | BodyJson + | BodyForm + | BodyFormData + | BodyText + | BodyUndefined; + +type BodyValueGetter = () => Body["value"]; + +/** When setting the `contentTypes` property of {@linkcode BodyOptions}, provide + * additional content types which can influence how the body is decoded. This + * is specifically designed to allow a server to support custom or specialized + * media types that are not part of the public database. */ +export interface BodyOptionsContentTypes { + /** Content types listed here will always return an Uint8Array. */ + bytes?: string[]; + /** Content types listed here will be parsed as a JSON string. */ + json?: string[]; + /** Content types listed here will be parsed as form data and return + * `URLSearchParameters` as the value of the body. */ + form?: string[]; + /** Content types listed here will be parsed as from data and return a + * `FormDataBody` interface as the value of the body. */ + formData?: string[]; + /** Content types listed here will be parsed as text. */ + text?: string[]; +} + +/** Options which can be used when accessing the `.body()` of a request. + * + * @template T the {@linkcode BodyType} to attempt to use when decoding the + * request body. + */ +export interface BodyOptions { + /** When reading a non-streaming body, set a limit whereby if the content + * length is greater then the limit or not set, reading the body will throw. + * + * This is to prevent malicious requests where the body exceeds the capacity + * of the server. Set the limit to 0 to allow unbounded reads. The default + * is 10 Mib. */ + limit?: number; + /** Instead of utilizing the content type of the request, attempt to parse the + * body as the type specified. The value has to be of {@linkcode BodyType}. */ + type?: T; + /** A map of extra content types to determine how to parse the body. */ + contentTypes?: BodyOptionsContentTypes; +} + +export interface BodyContentTypes { + json?: string[]; + form?: string[]; + text?: string[]; +} + +const DEFAULT_LIMIT = 10_485_760; // 10mb + +const defaultBodyContentTypes = { + json: ["json", "application/*+json", "application/csp-report"], + form: ["urlencoded"], + formData: ["multipart"], + text: ["text"], +}; + +function resolveType( + contentType: string, + contentTypes: BodyOptionsContentTypes, +): BodyType { + const contentTypesJson = [ + ...defaultBodyContentTypes.json, + ...(contentTypes.json ?? []), + ]; + const contentTypesForm = [ + ...defaultBodyContentTypes.form, + ...(contentTypes.form ?? []), + ]; + const contentTypesFormData = [ + ...defaultBodyContentTypes.formData, + ...(contentTypes.formData ?? []), + ]; + const contentTypesText = [ + ...defaultBodyContentTypes.text, + ...(contentTypes.text ?? []), + ]; + if (contentTypes.bytes && isMediaType(contentType, contentTypes.bytes)) { + return "bytes"; + } else if (isMediaType(contentType, contentTypesJson)) { + return "json"; + } else if (isMediaType(contentType, contentTypesForm)) { + return "form"; + } else if (isMediaType(contentType, contentTypesFormData)) { + return "form-data"; + } else if (isMediaType(contentType, contentTypesText)) { + return "text"; + } + return "bytes"; +} + +const decoder = new TextDecoder(); + +export class RequestBody { + #body: ReadableStream | null; + #formDataReader?: FormDataReader; + #headers: Headers; + #jsonBodyReviver?: (key: string, value: unknown) => unknown; + #stream?: ReadableStream; + #readAllBody?: Promise; + #readBody: () => Promise; + #type?: "bytes" | "form-data" | "reader" | "stream" | "undefined"; + + #exceedsLimit(limit: number): boolean { + if (!limit || limit === Infinity) { + return false; + } + if (!this.#body) { + return false; + } + const contentLength = this.#headers.get("content-length") ?? "0"; + const parsed = parseInt(contentLength, 10); + if (isNaN(parsed)) { + return true; + } + return parsed > limit; + } + + #parse(type: BodyType, limit: number): BodyValueGetter { + switch (type) { + case "form": + this.#type = "bytes"; + if (this.#exceedsLimit(limit)) { + return () => + Promise.reject(new RangeError(`Body exceeds a limit of ${limit}.`)); + } + return async () => + new URLSearchParams( + decoder.decode(await this.#valuePromise()).replace(/\+/g, " "), + ); + case "form-data": + this.#type = "form-data"; + return () => { + const contentType = this.#headers.get("content-type"); + assert(contentType); + const readableStream = this.#body ?? new ReadableStream(); + return this.#formDataReader ?? + (this.#formDataReader = new FormDataReader( + contentType, + readerFromStreamReader( + (readableStream as ReadableStream).getReader(), + ), + )); + }; + case "json": + this.#type = "bytes"; + if (this.#exceedsLimit(limit)) { + return () => + Promise.reject(new RangeError(`Body exceeds a limit of ${limit}.`)); + } + return async () => { + const value = await this.#valuePromise(); + return value.length + ? JSON.parse( + decoder.decode(await this.#valuePromise()), + this.#jsonBodyReviver, + ) + : null; + }; + case "bytes": + this.#type = "bytes"; + if (this.#exceedsLimit(limit)) { + return () => + Promise.reject(new RangeError(`Body exceeds a limit of ${limit}.`)); + } + return () => this.#valuePromise(); + case "text": + this.#type = "bytes"; + if (this.#exceedsLimit(limit)) { + return () => + Promise.reject(new RangeError(`Body exceeds a limit of ${limit}.`)); + } + return async () => decoder.decode(await this.#valuePromise()); + default: + throw new TypeError(`Invalid body type: "${type}"`); + } + } + + #validateGetArgs( + type: BodyType | undefined, + contentTypes: BodyOptionsContentTypes, + ) { + if (type === "reader" && this.#type && this.#type !== "reader") { + throw new TypeError( + `Body already consumed as "${this.#type}" and cannot be returned as a reader.`, + ); + } + if (type === "stream" && this.#type && this.#type !== "stream") { + throw new TypeError( + `Body already consumed as "${this.#type}" and cannot be returned as a stream.`, + ); + } + if (type === "form-data" && this.#type && this.#type !== "form-data") { + throw new TypeError( + `Body already consumed as "${this.#type}" and cannot be returned as a stream.`, + ); + } + if (this.#type === "reader" && type !== "reader") { + throw new TypeError( + "Body already consumed as a reader and can only be returned as a reader.", + ); + } + if (this.#type === "stream" && type !== "stream") { + throw new TypeError( + "Body already consumed as a stream and can only be returned as a stream.", + ); + } + if (this.#type === "form-data" && type !== "form-data") { + throw new TypeError( + "Body already consumed as form data and can only be returned as form data.", + ); + } + if (type && Object.keys(contentTypes).length) { + throw new TypeError( + `"type" and "contentTypes" cannot be specified at the same time`, + ); + } + } + + #valuePromise() { + return this.#readAllBody ?? (this.#readAllBody = this.#readBody()); + } + + constructor( + { body, readBody }: ServerRequestBody, + headers: Headers, + jsonBodyReviver?: (key: string, value: unknown) => unknown, + ) { + this.#body = body; + this.#headers = headers; + this.#jsonBodyReviver = jsonBodyReviver; + this.#readBody = readBody; + } + + get( + { limit = DEFAULT_LIMIT, type, contentTypes = {} }: BodyOptions = {}, + ): Body | BodyReader | BodyStream { + this.#validateGetArgs(type, contentTypes); + if (type === "reader") { + if (!this.#body) { + this.#type = "undefined"; + throw new TypeError( + `Body is undefined and cannot be returned as "reader".`, + ); + } + this.#type = "reader"; + return { + type, + value: readerFromStreamReader(this.#body.getReader()), + }; + } + if (type === "stream") { + if (!this.#body) { + this.#type = "undefined"; + throw new TypeError( + `Body is undefined and cannot be returned as "stream".`, + ); + } + this.#type = "stream"; + const streams = + ((this.#stream ?? this.#body) as ReadableStream) + .tee(); + this.#stream = streams[1]; + return { type, value: streams[0] }; + } + if (!this.has()) { + this.#type = "undefined"; + } else if (!this.#type) { + const encoding = this.#headers.get("content-encoding") ?? + "identity"; + if (encoding !== "identity") { + throw new errors.UnsupportedMediaType( + `Unsupported content-encoding: ${encoding}`, + ); + } + } + if (this.#type === "undefined" && (!type || type === "undefined")) { + return { type: "undefined", value: undefined }; + } + if (!type) { + const contentType = this.#headers.get("content-type"); + assert( + contentType, + "The Content-Type header is missing from the request", + ); + type = resolveType(contentType, contentTypes); + } + assert(type); + const body: Body = Object.create(null); + Object.defineProperties(body, { + type: { + value: type, + configurable: true, + enumerable: true, + }, + value: { + get: this.#parse(type, limit), + configurable: true, + enumerable: true, + }, + }); + return body; + } + + /** Returns if the request might have a body or not, without attempting to + * consume it. + * + * **WARNING** This is an unreliable API. In HTTP/2 it is not possible to + * determine if certain HTTP methods have a body or not without attempting to + * read the body. As of Deno 1.16.1 and later, for HTTP/1.1 aligns to the + * HTTP/2 behaviour. + */ + has(): boolean { + return this.#body != null; + } +} diff --git a/vendor/deno.land/x/oak@v12.1.0/buf_reader.ts b/vendor/deno.land/x/oak@v12.1.0/buf_reader.ts new file mode 100644 index 0000000..a8d2854 --- /dev/null +++ b/vendor/deno.land/x/oak@v12.1.0/buf_reader.ts @@ -0,0 +1,183 @@ +// Copyright 2018-2022 the oak authors. All rights reserved. MIT license. + +import { assert, stripEol } from "./util.ts"; + +export interface ReadLineResult { + bytes: Uint8Array; + eol: boolean; +} + +const DEFAULT_BUF_SIZE = 4096; +const MIN_BUF_SIZE = 16; +const MAX_CONSECUTIVE_EMPTY_READS = 100; +const CR = "\r".charCodeAt(0); +const LF = "\n".charCodeAt(0); + +export class BufferFullError extends Error { + name = "BufferFullError"; + constructor(public partial: Uint8Array) { + super("Buffer full"); + } +} + +/** BufReader implements buffering for a Reader object. */ +export class BufReader { + #buffer!: Uint8Array; + #reader!: Deno.Reader; + #posRead = 0; + #posWrite = 0; + #eof = false; + + // Reads a new chunk into the buffer. + async #fill(): Promise { + // Slide existing data to beginning. + if (this.#posRead > 0) { + this.#buffer.copyWithin(0, this.#posRead, this.#posWrite); + this.#posWrite -= this.#posRead; + this.#posRead = 0; + } + + if (this.#posWrite >= this.#buffer.byteLength) { + throw Error("bufio: tried to fill full buffer"); + } + + // Read new data: try a limited number of times. + for (let i = MAX_CONSECUTIVE_EMPTY_READS; i > 0; i--) { + const rr = await this.#reader.read(this.#buffer.subarray(this.#posWrite)); + if (rr === null) { + this.#eof = true; + return; + } + assert(rr >= 0, "negative read"); + this.#posWrite += rr; + if (rr > 0) { + return; + } + } + + throw new Error( + `No progress after ${MAX_CONSECUTIVE_EMPTY_READS} read() calls`, + ); + } + + #reset(buffer: Uint8Array, reader: Deno.Reader): void { + this.#buffer = buffer; + this.#reader = reader; + this.#eof = false; + } + + constructor(rd: Deno.Reader, size: number = DEFAULT_BUF_SIZE) { + if (size < MIN_BUF_SIZE) { + size = MIN_BUF_SIZE; + } + this.#reset(new Uint8Array(size), rd); + } + + buffered(): number { + return this.#posWrite - this.#posRead; + } + + async readLine( + strip = true, + ): Promise<{ bytes: Uint8Array; eol: boolean } | null> { + let line: Uint8Array | null; + + try { + line = await this.readSlice(LF); + } catch (err) { + assert(err instanceof Error); + let { partial } = err as Error & { partial?: Uint8Array }; + assert( + partial instanceof Uint8Array, + "Caught error from `readSlice()` without `partial` property", + ); + + // Don't throw if `readSlice()` failed with `BufferFullError`, instead we + // just return whatever is available and set the `more` flag. + if (!(err instanceof BufferFullError)) { + throw err; + } + + // Handle the case where "\r\n" straddles the buffer. + if ( + !this.#eof && + partial.byteLength > 0 && + partial[partial.byteLength - 1] === CR + ) { + // Put the '\r' back on buf and drop it from line. + // Let the next call to ReadLine check for "\r\n". + assert( + this.#posRead > 0, + "Tried to rewind past start of buffer", + ); + this.#posRead--; + partial = partial.subarray(0, partial.byteLength - 1); + } + + return { bytes: partial, eol: this.#eof }; + } + + if (line === null) { + return null; + } + + if (line.byteLength === 0) { + return { bytes: line, eol: true }; + } + + if (strip) { + line = stripEol(line); + } + return { bytes: line, eol: true }; + } + + async readSlice(delim: number): Promise { + let s = 0; // search start index + let slice: Uint8Array | undefined; + + while (true) { + // Search buffer. + let i = this.#buffer.subarray(this.#posRead + s, this.#posWrite).indexOf( + delim, + ); + if (i >= 0) { + i += s; + slice = this.#buffer.subarray(this.#posRead, this.#posRead + i + 1); + this.#posRead += i + 1; + break; + } + + // EOF? + if (this.#eof) { + if (this.#posRead === this.#posWrite) { + return null; + } + slice = this.#buffer.subarray(this.#posRead, this.#posWrite); + this.#posRead = this.#posWrite; + break; + } + + // Buffer full? + if (this.buffered() >= this.#buffer.byteLength) { + this.#posRead = this.#posWrite; + // #4521 The internal buffer should not be reused across reads because it causes corruption of data. + const oldbuf = this.#buffer; + const newbuf = this.#buffer.slice(0); + this.#buffer = newbuf; + throw new BufferFullError(oldbuf); + } + + s = this.#posWrite - this.#posRead; // do not rescan area we scanned before + + // Buffer is not full. + try { + await this.#fill(); + } catch (err) { + const e = err instanceof Error ? err : new Error("[non-object thrown]"); + (e as Error & { partial: unknown }).partial = slice; + throw err; + } + } + return slice; + } +} diff --git a/vendor/deno.land/x/oak@v12.1.0/content_disposition.ts b/vendor/deno.land/x/oak@v12.1.0/content_disposition.ts new file mode 100644 index 0000000..a54355d --- /dev/null +++ b/vendor/deno.land/x/oak@v12.1.0/content_disposition.ts @@ -0,0 +1,143 @@ +/** + * Adapted directly from content-disposition.js at + * https://github.com/Rob--W/open-in-browser/blob/master/extension/content-disposition.js + * which is licensed as: + * + * (c) 2017 Rob Wu (https://robwu.nl) + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +import { toParamRegExp, unquote } from "./headers.ts"; + +let needsEncodingFixup = false; + +function fixupEncoding(value: string): string { + if (needsEncodingFixup && /[\x80-\xff]/.test(value)) { + value = textDecode("utf-8", value); + if (needsEncodingFixup) { + value = textDecode("iso-8859-1", value); + } + } + return value; +} + +const FILENAME_STAR_REGEX = toParamRegExp("filename\\*", "i"); +const FILENAME_START_ITER_REGEX = toParamRegExp( + "filename\\*((?!0\\d)\\d+)(\\*?)", + "ig", +); +const FILENAME_REGEX = toParamRegExp("filename", "i"); + +function rfc2047decode(value: string): string { + // deno-lint-ignore no-control-regex + if (!value.startsWith("=?") || /[\x00-\x19\x80-\xff]/.test(value)) { + return value; + } + return value.replace( + /=\?([\w-]*)\?([QqBb])\?((?:[^?]|\?(?!=))*)\?=/g, + (_: string, charset: string, encoding: string, text: string) => { + if (encoding === "q" || encoding === "Q") { + text = text.replace(/_/g, " "); + text = text.replace( + /=([0-9a-fA-F]{2})/g, + (_, hex) => String.fromCharCode(parseInt(hex, 16)), + ); + return textDecode(charset, text); + } + try { + text = atob(text); + // deno-lint-ignore no-empty + } catch {} + return textDecode(charset, text); + }, + ); +} + +function rfc2231getParam(header: string): string { + const matches: [string, string][] = []; + let match: RegExpExecArray | null; + while ((match = FILENAME_START_ITER_REGEX.exec(header))) { + const [, ns, quote, part] = match; + const n = parseInt(ns, 10); + if (n in matches) { + if (n === 0) { + break; + } + continue; + } + matches[n] = [quote, part]; + } + const parts: string[] = []; + for (let n = 0; n < matches.length; ++n) { + if (!(n in matches)) { + break; + } + let [quote, part] = matches[n]; + part = unquote(part); + if (quote) { + part = unescape(part); + if (n === 0) { + part = rfc5987decode(part); + } + } + parts.push(part); + } + return parts.join(""); +} + +function rfc5987decode(value: string): string { + const encodingEnd = value.indexOf(`'`); + if (encodingEnd === -1) { + return value; + } + const encoding = value.slice(0, encodingEnd); + const langValue = value.slice(encodingEnd + 1); + return textDecode(encoding, langValue.replace(/^[^']*'/, "")); +} + +function textDecode(encoding: string, value: string): string { + if (encoding) { + try { + const decoder = new TextDecoder(encoding, { fatal: true }); + const bytes = Array.from(value, (c) => c.charCodeAt(0)); + if (bytes.every((code) => code <= 0xFF)) { + value = decoder.decode(new Uint8Array(bytes)); + needsEncodingFixup = false; + } + // deno-lint-ignore no-empty + } catch {} + } + return value; +} + +export function getFilename(header: string): string { + needsEncodingFixup = true; + + // filename*=ext-value ("ext-value" from RFC 5987, referenced by RFC 6266). + let matches = FILENAME_STAR_REGEX.exec(header); + if (matches) { + const [, filename] = matches; + return fixupEncoding( + rfc2047decode(rfc5987decode(unescape(unquote(filename)))), + ); + } + + // Continuations (RFC 2231 section 3, referenced by RFC 5987 section 3.1). + // filename*n*=part + // filename*n=part + const filename = rfc2231getParam(header); + if (filename) { + return fixupEncoding(rfc2047decode(filename)); + } + + // filename=value (RFC 5987, section 4.1). + matches = FILENAME_REGEX.exec(header); + if (matches) { + const [, filename] = matches; + return fixupEncoding(rfc2047decode(unquote(filename))); + } + + return ""; +} diff --git a/vendor/deno.land/x/oak@v12.1.0/context.ts b/vendor/deno.land/x/oak@v12.1.0/context.ts new file mode 100644 index 0000000..a0ff1c5 --- /dev/null +++ b/vendor/deno.land/x/oak@v12.1.0/context.ts @@ -0,0 +1,348 @@ +// Copyright 2018-2022 the oak authors. All rights reserved. MIT license. + +import type { Application, State } from "./application.ts"; +import { createHttpError, KeyStack, SecureCookieMap } from "./deps.ts"; +import { Request } from "./request.ts"; +import { Response } from "./response.ts"; +import { send, SendOptions } from "./send.ts"; +import { + ServerSentEventTargetOptions, + SSEStreamTarget, +} from "./server_sent_event.ts"; +import type { ServerSentEventTarget } from "./server_sent_event.ts"; +import type { + ErrorStatus, + ServerRequest, + UpgradeWebSocketOptions, +} from "./types.d.ts"; + +export interface ContextOptions< + S extends AS = State, + // deno-lint-ignore no-explicit-any + AS extends State = Record, +> { + jsonBodyReplacer?: ( + key: string, + value: unknown, + context: Context, + ) => unknown; + jsonBodyReviver?: ( + key: string, + value: unknown, + context: Context, + ) => unknown; + secure?: boolean; +} + +export interface ContextSendOptions extends SendOptions { + /** The filename to send, which will be resolved based on the other options. + * If this property is omitted, the current context's `.request.url.pathname` + * will be used. */ + path?: string; +} + +/** Provides context about the current request and response to middleware + * functions, and the current instance being processed is the first argument + * provided a {@linkcode Middleware} function. + * + * _Typically this is only used as a type annotation and shouldn't be + * constructed directly._ + * + * ### Example + * + * ```ts + * import { Application, Context } from "https://deno.land/x/oak/mod.ts"; + * + * const app = new Application(); + * + * app.use((ctx) => { + * // information about the request is here: + * ctx.request; + * // information about the response is here: + * ctx.response; + * // the cookie store is here: + * ctx.cookies; + * }); + * + * // Needs a type annotation because it cannot be inferred. + * function mw(ctx: Context) { + * // process here... + * } + * + * app.use(mw); + * ``` + * + * @template S the state which extends the application state (`AS`) + * @template AS the type of the state derived from the application + */ +export class Context< + S extends AS = State, + // deno-lint-ignore no-explicit-any + AS extends State = Record, +> { + #socket?: WebSocket; + #sse?: ServerSentEventTarget; + + #wrapReviverReplacer( + reviver?: (key: string, value: unknown, context: this) => unknown, + ): undefined | ((key: string, value: unknown) => unknown) { + return reviver + ? (key: string, value: unknown) => reviver(key, value, this) + : undefined; + } + + /** A reference to the current application. */ + app: Application; + + /** An object which allows access to cookies, mediating both the request and + * response. */ + cookies: SecureCookieMap; + + /** Is `true` if the current connection is upgradeable to a web socket. + * Otherwise the value is `false`. Use `.upgrade()` to upgrade the connection + * and return the web socket. */ + get isUpgradable(): boolean { + const upgrade = this.request.headers.get("upgrade"); + if (!upgrade || upgrade.toLowerCase() !== "websocket") { + return false; + } + const secKey = this.request.headers.get("sec-websocket-key"); + return typeof secKey === "string" && secKey != ""; + } + + /** Determines if the request should be responded to. If `false` when the + * middleware completes processing, the response will not be sent back to the + * requestor. Typically this is used if the middleware will take over low + * level processing of requests and responses, for example if using web + * sockets. This automatically gets set to `false` when the context is + * upgraded to a web socket via the `.upgrade()` method. + * + * The default is `true`. */ + respond: boolean; + + /** An object which contains information about the current request. */ + request: Request; + + /** An object which contains information about the response that will be sent + * when the middleware finishes processing. */ + response: Response; + + /** If the the current context has been upgraded, then this will be set to + * with the current web socket, otherwise it is `undefined`. */ + get socket(): WebSocket | undefined { + return this.#socket; + } + + /** The object to pass state to front-end views. This can be typed by + * supplying the generic state argument when creating a new app. For + * example: + * + * ```ts + * const app = new Application<{ foo: string }>(); + * ``` + * + * Or can be contextually inferred based on setting an initial state object: + * + * ```ts + * const app = new Application({ state: { foo: "bar" } }); + * ``` + * + * On each request/response cycle, the context's state is cloned from the + * application state. This means changes to the context's `.state` will be + * dropped when the request drops, but "defaults" can be applied to the + * application's state. Changes to the application's state though won't be + * reflected until the next request in the context's state. + */ + state: S; + + constructor( + app: Application, + serverRequest: ServerRequest, + state: S, + { + secure = false, + jsonBodyReplacer, + jsonBodyReviver, + }: ContextOptions = {}, + ) { + this.app = app; + this.state = state; + const { proxy } = app; + this.request = new Request( + serverRequest, + { + proxy, + secure, + jsonBodyReviver: this.#wrapReviverReplacer(jsonBodyReviver), + }, + ); + this.respond = true; + this.response = new Response( + this.request, + this.#wrapReviverReplacer(jsonBodyReplacer), + ); + this.cookies = new SecureCookieMap(serverRequest, { + keys: this.app.keys as KeyStack | undefined, + response: this.response, + secure: this.request.secure, + }); + } + + /** Asserts the condition and if the condition fails, creates an HTTP error + * with the provided status (which defaults to `500`). The error status by + * default will be set on the `.response.status`. + * + * Because of limitation of TypeScript, any assertion type function requires + * specific type annotations, so the {@linkcode Context} type should be used + * even if it can be inferred from the context. + * + * ### Example + * + * ```ts + * import { Context, Status } from "https://deno.land/x/oak/mod.ts"; + * + * export function mw(ctx: Context) { + * const body = ctx.request.body(); + * ctx.assert(body.type === "json", Status.NotAcceptable); + * // process the body and send a response... + * } + * ``` + */ + assert( + // deno-lint-ignore no-explicit-any + condition: any, + errorStatus: ErrorStatus = 500, + message?: string, + props?: Record, + ): asserts condition { + if (condition) { + return; + } + const err = createHttpError(errorStatus, message); + if (props) { + Object.assign(err, props); + } + throw err; + } + + /** Asynchronously fulfill a response with a file from the local file + * system. + * + * If the `options.path` is not supplied, the file to be sent will default + * to this `.request.url.pathname`. + * + * Requires Deno read permission. */ + send(options: ContextSendOptions): Promise { + const { path = this.request.url.pathname, ...sendOptions } = options; + return send(this, path, sendOptions); + } + + /** Convert the connection to stream events, returning an event target for + * sending server sent events. Events dispatched on the returned target will + * be sent to the client and be available in the client's `EventSource` that + * initiated the connection. + * + * This will set `.respond` to `false`. */ + sendEvents(options?: ServerSentEventTargetOptions): ServerSentEventTarget { + if (!this.#sse) { + this.#sse = new SSEStreamTarget(this, options); + } + return this.#sse; + } + + /** Create and throw an HTTP Error, which can be used to pass status + * information which can be caught by other middleware to send more + * meaningful error messages back to the client. The passed error status will + * be set on the `.response.status` by default as well. + */ + throw( + errorStatus: ErrorStatus, + message?: string, + props?: Record, + ): never { + const err = createHttpError(errorStatus, message); + if (props) { + Object.assign(err, props); + } + throw err; + } + + /** Take the current request and upgrade it to a web socket, resolving with + * the a web standard `WebSocket` object. This will set `.respond` to + * `false`. If the socket cannot be upgraded, this method will throw. */ + upgrade(options?: UpgradeWebSocketOptions): WebSocket { + if (this.#socket) { + return this.#socket; + } + if (!this.request.originalRequest.upgrade) { + throw new TypeError( + "Web socket upgrades not currently supported for this type of server.", + ); + } + this.#socket = this.request.originalRequest.upgrade(options); + this.respond = false; + return this.#socket; + } + + [Symbol.for("Deno.customInspect")](inspect: (value: unknown) => string) { + const { + app, + cookies, + isUpgradable, + respond, + request, + response, + socket, + state, + } = this; + return `${this.constructor.name} ${ + inspect({ + app, + cookies, + isUpgradable, + respond, + request, + response, + socket, + state, + }) + }`; + } + + [Symbol.for("nodejs.util.inspect.custom")]( + depth: number, + // deno-lint-ignore no-explicit-any + options: any, + inspect: (value: unknown, options?: unknown) => string, + ) { + if (depth < 0) { + return options.stylize(`[${this.constructor.name}]`, "special"); + } + + const newOptions = Object.assign({}, options, { + depth: options.depth === null ? null : options.depth - 1, + }); + const { + app, + cookies, + isUpgradable, + respond, + request, + response, + socket, + state, + } = this; + return `${options.stylize(this.constructor.name, "special")} ${ + inspect({ + app, + cookies, + isUpgradable, + respond, + request, + response, + socket, + state, + }, newOptions) + }`; + } +} diff --git a/vendor/deno.land/x/oak@v12.1.0/deps.ts b/vendor/deno.land/x/oak@v12.1.0/deps.ts new file mode 100644 index 0000000..38c1d18 --- /dev/null +++ b/vendor/deno.land/x/oak@v12.1.0/deps.ts @@ -0,0 +1,71 @@ +// Copyright 2018-2022 the oak authors. All rights reserved. MIT license. + +// This file contains the external dependencies that oak depends upon + +// `std` dependencies + +export { + type Deferred, + deferred, +} from "https://deno.land/std@0.178.0/async/deferred.ts"; +export { + concat, + copy as copyBytes, + equals, +} from "https://deno.land/std@0.178.0/bytes/mod.ts"; +export { timingSafeEqual } from "https://deno.land/std@0.178.0/crypto/timing_safe_equal.ts"; +export { KeyStack } from "https://deno.land/std@0.178.0/crypto/keystack.ts"; +export * as base64 from "https://deno.land/std@0.178.0/encoding/base64.ts"; +export { + mergeHeaders, + SecureCookieMap, + type SecureCookieMapGetOptions, + type SecureCookieMapSetDeleteOptions, +} from "https://deno.land/std@0.178.0/http/cookie_map.ts"; +export { + createHttpError, + errors, + HttpError, + isHttpError, +} from "https://deno.land/std@0.178.0/http/http_errors.ts"; +export { + Status, + STATUS_TEXT, +} from "https://deno.land/std@0.178.0/http/http_status.ts"; +export { + accepts, + acceptsEncodings, + acceptsLanguages, +} from "https://deno.land/std@0.178.0/http/negotiation.ts"; +export { LimitedReader } from "https://deno.land/std@0.178.0/io/mod.ts"; +export { + contentType, + extension, + typeByExtension, +} from "https://deno.land/std@0.178.0/media_types/mod.ts"; +export { + readAll, + readerFromStreamReader, + writeAll, +} from "https://deno.land/std@0.178.0/streams/mod.ts"; +export { + basename, + extname, + isAbsolute, + join, + normalize, + parse, + sep, +} from "https://deno.land/std@0.178.0/path/mod.ts"; + +// 3rd party dependencies + +export { + compile, + type Key, + match as pathMatch, + parse as pathParse, + type ParseOptions, + pathToRegexp, + type TokensToRegexpOptions, +} from "https://deno.land/x/path_to_regexp@v6.2.1/index.ts"; diff --git a/vendor/deno.land/x/oak@v12.1.0/etag.ts b/vendor/deno.land/x/oak@v12.1.0/etag.ts new file mode 100644 index 0000000..e63a175 --- /dev/null +++ b/vendor/deno.land/x/oak@v12.1.0/etag.ts @@ -0,0 +1,182 @@ +// Copyright 2018-2022 the oak authors. All rights reserved. MIT license. + +/** + * A collection of APIs for dealing with ETags in requests and responses. + * + * @module + */ + +import type { State } from "./application.ts"; +import type { Context } from "./context.ts"; +import { base64 } from "./deps.ts"; +import type { Middleware } from "./middleware.ts"; +import { BODY_TYPES, isAsyncIterable, isReader } from "./util.ts"; + +export interface ETagOptions { + /** Override the default behavior of calculating the `ETag`, either forcing + * a tag to be labelled weak or not. */ + weak?: boolean; +} + +/** + * Just the part of `Deno.FileInfo` that is required to calculate an `ETag`, + * so partial or user generated file information can be passed. + */ +export interface FileInfo { + mtime: Date | null; + size: number; +} + +function isFileInfo(value: unknown): value is FileInfo { + return Boolean( + value && typeof value === "object" && "mtime" in value && "size" in value, + ); +} + +function calcStatTag(entity: FileInfo): string { + const mtime = entity.mtime?.getTime().toString(16) ?? "0"; + const size = entity.size.toString(16); + + return `"${size}-${mtime}"`; +} + +const encoder = new TextEncoder(); + +async function calcEntityTag(entity: string | Uint8Array): Promise { + if (entity.length === 0) { + return `"0-2jmj7l5rSw0yVb/vlWAYkK/YBwk="`; + } + + if (typeof entity === "string") { + entity = encoder.encode(entity); + } + + const hash = base64.encode(await crypto.subtle.digest("SHA-1", entity)) + .substring(0, 27); + + return `"${entity.length.toString(16)}-${hash}"`; +} + +function fstat(file: Deno.FsFile): Promise { + if ("fstat" in Deno) { + // deno-lint-ignore no-explicit-any + return (Deno as any).fstat(file.rid); + } + return Promise.resolve(undefined); +} + +/** For a given Context, try to determine the response body entity that an ETag + * can be calculated from. */ +// deno-lint-ignore no-explicit-any +export function getEntity>( + context: Context, +): Promise { + const { body } = context.response; + if (body instanceof Deno.FsFile) { + return fstat(body); + } + if (body instanceof Uint8Array) { + return Promise.resolve(body); + } + if (BODY_TYPES.includes(typeof body)) { + return Promise.resolve(String(body)); + } + if (isAsyncIterable(body) || isReader(body)) { + return Promise.resolve(undefined); + } + if (typeof body === "object" && body !== null) { + try { + const bodyText = JSON.stringify(body); + return Promise.resolve(bodyText); + } catch { + // We don't really care about errors here + } + } + return Promise.resolve(undefined); +} + +/** + * Calculate an ETag value for an entity. If the entity is `FileInfo`, then the + * tag will default to a _weak_ ETag. `options.weak` overrides any default + * behavior in generating the tag. + * + * @param entity A string, Uint8Array, or file info to use to generate the ETag + * @param options + */ +export async function calculate( + entity: string | Uint8Array | FileInfo, + options: ETagOptions = {}, +): Promise { + const weak = options.weak ?? isFileInfo(entity); + const tag = isFileInfo(entity) + ? calcStatTag(entity) + : await calcEntityTag(entity); + + return weak ? `W/${tag}` : tag; +} + +/** + * Create middleware that will attempt to decode the response.body into + * something that can be used to generate an `ETag` and add the `ETag` header to + * the response. + */ +// deno-lint-ignore no-explicit-any +export function factory>( + options?: ETagOptions, +): Middleware { + return async function etag(context: Context, next) { + await next(); + if (!context.response.headers.has("ETag")) { + const entity = await getEntity(context); + if (entity) { + context.response.headers.set("ETag", await calculate(entity, options)); + } + } + }; +} + +/** + * A helper function that takes the value from the `If-Match` header and an + * entity and returns `true` if the `ETag` for the entity matches the supplied + * value, otherwise `false`. + * + * See MDN's [`If-Match`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/If-Match) + * article for more information on how to use this function. + */ +export async function ifMatch( + value: string, + entity: string | Uint8Array | FileInfo, + options: ETagOptions = {}, +): Promise { + const etag = await calculate(entity, options); + // Weak tags cannot be matched and return false. + if (etag.startsWith("W/")) { + return false; + } + if (value.trim() === "*") { + return true; + } + const tags = value.split(/\s*,\s*/); + return tags.includes(etag); +} + +/** + * A helper function that takes the value from the `If-No-Match` header and + * an entity and returns `false` if the `ETag` for the entity matches the + * supplied value, otherwise `false`. + * + * See MDN's [`If-None-Match`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/If-None-Match) + * article for more information on how to use this function. + */ +export async function ifNoneMatch( + value: string, + entity: string | Uint8Array | FileInfo, + options: ETagOptions = {}, +): Promise { + if (value.trim() === "*") { + return false; + } + const etag = await calculate(entity, options); + const tags = value.split(/\s*,\s*/); + return !tags.includes(etag); +} diff --git a/vendor/deno.land/x/oak@v12.1.0/headers.ts b/vendor/deno.land/x/oak@v12.1.0/headers.ts new file mode 100644 index 0000000..e816393 --- /dev/null +++ b/vendor/deno.land/x/oak@v12.1.0/headers.ts @@ -0,0 +1,78 @@ +// Copyright 2018-2022 the oak authors. All rights reserved. MIT license. + +import type { BufReader } from "./buf_reader.ts"; +import { errors } from "./deps.ts"; + +const COLON = ":".charCodeAt(0); +const HTAB = "\t".charCodeAt(0); +const SPACE = " ".charCodeAt(0); + +const decoder = new TextDecoder(); + +/** With a provided attribute pattern, return a RegExp which will match and + * capture in the first group the value of the attribute from a header value. */ +export function toParamRegExp( + attributePattern: string, + flags?: string, +): RegExp { + // deno-fmt-ignore + return new RegExp( + `(?:^|;)\\s*${attributePattern}\\s*=\\s*` + + `(` + + `[^";\\s][^;\\s]*` + + `|` + + `"(?:[^"\\\\]|\\\\"?)+"?` + + `)`, + flags + ); +} + +/** Asynchronously read the headers out of body request and resolve with them as + * a `Headers` object. */ +export async function readHeaders( + body: BufReader, +): Promise> { + const headers: Record = {}; + let readResult = await body.readLine(); + while (readResult) { + const { bytes } = readResult; + if (!bytes.length) { + return headers; + } + let i = bytes.indexOf(COLON); + if (i === -1) { + throw new errors.BadRequest( + `Malformed header: ${decoder.decode(bytes)}`, + ); + } + const key = decoder.decode(bytes.subarray(0, i)).trim().toLowerCase(); + if (key === "") { + throw new errors.BadRequest("Invalid header key."); + } + i++; + while (i < bytes.byteLength && (bytes[i] === SPACE || bytes[i] === HTAB)) { + i++; + } + const value = decoder.decode(bytes.subarray(i)).trim(); + headers[key] = value; + readResult = await body.readLine(); + } + throw new errors.BadRequest("Unexpected end of body reached."); +} + +/** Unquotes attribute values that might be pass as part of a header. */ +export function unquote(value: string): string { + if (value.startsWith(`"`)) { + const parts = value.slice(1).split(`\\"`); + for (let i = 0; i < parts.length; ++i) { + const quoteIndex = parts[i].indexOf(`"`); + if (quoteIndex !== -1) { + parts[i] = parts[i].slice(0, quoteIndex); + parts.length = i + 1; // Truncates and stops the loop + } + parts[i] = parts[i].replace(/\\(.)/g, "$1"); + } + value = parts.join(`"`); + } + return value; +} diff --git a/vendor/deno.land/x/oak@v12.1.0/helpers.ts b/vendor/deno.land/x/oak@v12.1.0/helpers.ts new file mode 100644 index 0000000..e55f847 --- /dev/null +++ b/vendor/deno.land/x/oak@v12.1.0/helpers.ts @@ -0,0 +1,53 @@ +// Copyright 2018-2022 the oak authors. All rights reserved. MIT license. + +/** + * A collection of APIs to help assist in creating middleware. + * + * @module + */ + +import type { Context } from "./context.ts"; +import type { RouterContext } from "./router.ts"; +import { isRouterContext } from "./util.ts"; + +interface GetQueryOptionsBase { + /** The return value should be a `Map` instead of a record object. */ + asMap?: boolean; + + /** Merge in the context's `.params`. This only works when a `RouterContext` + * is passed. */ + mergeParams?: boolean; +} + +interface GetQueryOptionsAsMap extends GetQueryOptionsBase { + /** The return value should be a `Map` instead of a record object. */ + asMap: true; +} + +export type GetParamsOptions = GetQueryOptionsBase | GetQueryOptionsAsMap; + +/** Given a context, return the `.request.url.searchParams` as a `Map` of keys + * and values of the params. */ +export function getQuery( + ctx: Context | RouterContext, + options: GetQueryOptionsAsMap, +): Map; +/** Given a context, return the `.request.url.searchParams` as a record object + * of keys and values of the params. */ +export function getQuery( + ctx: Context | RouterContext, + options?: GetQueryOptionsBase, +): Record; +export function getQuery( + ctx: Context | RouterContext, + { mergeParams, asMap }: GetParamsOptions = {}, +): Map | Record { + const result: Record = {}; + if (mergeParams && isRouterContext(ctx)) { + Object.assign(result, ctx.params); + } + for (const [key, value] of ctx.request.url.searchParams) { + result[key] = value; + } + return asMap ? new Map(Object.entries(result)) : result; +} diff --git a/vendor/deno.land/x/oak@v12.1.0/http_request.ts b/vendor/deno.land/x/oak@v12.1.0/http_request.ts new file mode 100644 index 0000000..d98f7b7 --- /dev/null +++ b/vendor/deno.land/x/oak@v12.1.0/http_request.ts @@ -0,0 +1,102 @@ +// Copyright 2018-2022 the oak authors. All rights reserved. MIT license. + +import { type Deferred } from "./deps.ts"; +import type { + ServerRequest, + ServerRequestBody, + UpgradeWebSocketFn, + UpgradeWebSocketOptions, +} from "./types.d.ts"; + +const maybeUpgradeWebSocket: UpgradeWebSocketFn | undefined = + "upgradeWebSocket" in Deno + // deno-lint-ignore no-explicit-any + ? (Deno as any).upgradeWebSocket.bind(Deno) + : undefined; + +/** An abstraction which wraps a {@linkcode Request} from Deno's flash server. + * The constructor takes a {@linkcode Deferred} which it will resolve when the + * response is ready. + * + * This request can be used in situations where there isn't a specific method + * to respond with, but where a `Promise` is accepted as a value. It + * is specifically designed to work with Deno's flash server. + */ +export class HttpRequest implements ServerRequest { + #deferred: Deferred; + #request: Request; + #resolved = false; + #upgradeWebSocket?: UpgradeWebSocketFn; + + get remoteAddr(): string | undefined { + return undefined; + } + + get headers(): Headers { + return this.#request.headers; + } + + get method(): string { + return this.#request.method; + } + + get url(): string { + try { + const url = new URL(this.#request.url); + return this.#request.url.replace(url.origin, ""); + } catch { + // we don't care about errors, we just want to fall back + } + return this.#request.url; + } + + constructor( + request: Request, + deferred: Deferred, + upgradeWebSocket?: UpgradeWebSocketFn, + ) { + this.#deferred = deferred; + this.#request = request; + this.#upgradeWebSocket = upgradeWebSocket ?? maybeUpgradeWebSocket; + } + + // deno-lint-ignore no-explicit-any + error(reason?: any): void { + if (this.#resolved) { + throw new Error("Request already responded to."); + } + this.#deferred.reject(reason); + this.#resolved = true; + } + + getBody(): ServerRequestBody { + return { + body: this.#request.body, + readBody: async () => { + const ab = await this.#request.arrayBuffer(); + return new Uint8Array(ab); + }, + }; + } + + respond(response: Response): Promise { + if (this.#resolved) { + throw new Error("Request already responded to."); + } + this.#deferred.resolve(response); + this.#resolved = true; + return Promise.resolve(); + } + + upgrade(options?: UpgradeWebSocketOptions): WebSocket { + if (this.#resolved) { + throw new Error("Request already responded to."); + } + if (!this.#upgradeWebSocket) { + throw new TypeError("Upgrading web sockets not supported."); + } + const { response, socket } = this.#upgradeWebSocket(this.#request, options); + this.#deferred.resolve(response); + return socket; + } +} diff --git a/vendor/deno.land/x/oak@v12.1.0/http_server_flash.ts b/vendor/deno.land/x/oak@v12.1.0/http_server_flash.ts new file mode 100644 index 0000000..255cbb1 --- /dev/null +++ b/vendor/deno.land/x/oak@v12.1.0/http_server_flash.ts @@ -0,0 +1,121 @@ +// Copyright 2018-2022 the oak authors. All rights reserved. MIT license. + +import { type Application } from "./application.ts"; +import { deferred, Status, STATUS_TEXT } from "./deps.ts"; +import { HttpRequest } from "./http_request.ts"; +import type { Listener, Server } from "./types.d.ts"; +import { assert } from "./util.ts"; + +type ServeHandler = ( + request: Request, +) => Response | Promise | void | Promise; + +interface ServeOptions { + port?: number; + hostname?: string; + signal?: AbortSignal; + onError?: (error: unknown) => Response | Promise; + onListen?: (params: { hostname: string; port: number }) => void; +} + +interface ServeTlsOptions extends ServeOptions { + cert: string; + key: string; +} + +type FlashServerOptions = Omit, "onListen" | "signal">; + +const serve: ( + handler: ServeHandler, + options?: ServeOptions | ServeTlsOptions, +) => Promise = "serve" in Deno + // deno-lint-ignore no-explicit-any + ? (Deno as any).serve.bind(Deno) + : undefined; + +/** A function that determines if the current environment supports Deno flash.*/ +export function hasFlash(): boolean { + return Boolean(serve); +} + +/** A server abstraction which manages requests from Deno's flash server. + * + * You can pass the class as the `server` property when constructing a new + * application to force the application to use Deno's flash server. + */ +export class FlashServer implements Server { + // deno-lint-ignore no-explicit-any + #app: Application; + #closed = false; + #controller?: ReadableStreamDefaultController; + #abortController = new AbortController(); + #options: FlashServerOptions; + #servePromise?: Promise; + #stream?: ReadableStream; + + // deno-lint-ignore no-explicit-any + constructor(app: Application, options: FlashServerOptions) { + if (!serve) { + throw new Error("The flash bindings for serving HTTP are not available."); + } + this.#app = app; + this.#options = options; + } + + async close(): Promise { + if (this.#closed) { + return; + } + this.#closed = true; + try { + this.#controller?.close(); + this.#controller = undefined; + this.#stream = undefined; + this.#abortController.abort(); + if (this.#servePromise) { + await this.#servePromise; + this.#servePromise = undefined; + } + } catch { + // just swallow here + } + } + + listen(): Promise { + const p = deferred(); + const start: ReadableStreamDefaultControllerCallback = ( + controller, + ) => { + this.#controller = controller; + const options: ServeOptions | ServeTlsOptions = { + ...this.#options, + signal: this.#abortController.signal, + onListen: (addr) => p.resolve({ addr }), + onError: (error) => { + this.#app.dispatchEvent(new ErrorEvent("error", { error })); + return new Response("Internal server error", { + status: Status.InternalServerError, + statusText: STATUS_TEXT[Status.InternalServerError], + }); + }, + }; + const handler: ServeHandler = (request) => { + const resolve = deferred(); + const flashRequest = new HttpRequest(request, resolve); + controller.enqueue(flashRequest); + return resolve; + }; + this.#servePromise = serve(handler, options); + }; + this.#stream = new ReadableStream({ start }); + return p; + } + + [Symbol.asyncIterator](): AsyncIterableIterator { + assert( + this.#stream, + ".listen() was not called before iterating or server is closed.", + ); + return this.#stream[Symbol.asyncIterator](); + } +} diff --git a/vendor/deno.land/x/oak@v12.1.0/http_server_native.ts b/vendor/deno.land/x/oak@v12.1.0/http_server_native.ts new file mode 100644 index 0000000..8772e80 --- /dev/null +++ b/vendor/deno.land/x/oak@v12.1.0/http_server_native.ts @@ -0,0 +1,162 @@ +// Copyright 2018-2022 the oak authors. All rights reserved. MIT license. + +import type { Application, State } from "./application.ts"; +import { NativeRequest } from "./http_server_native_request.ts"; +import type { HttpConn, Listener, Server } from "./types.d.ts"; +import { assert, isListenTlsOptions } from "./util.ts"; + +// this is included so when down-emitting to npm/Node.js, ReadableStream has +// async iterators +declare global { + // deno-lint-ignore no-explicit-any + interface ReadableStream { + [Symbol.asyncIterator](options?: { + preventCancel?: boolean; + }): AsyncIterableIterator; + } +} + +export type Respond = (r: Response | Promise) => void; + +// This type is part of Deno, but not part of lib.dom.d.ts, therefore add it here +// so that type checking can occur properly under `lib.dom.d.ts`. +interface ReadableStreamDefaultControllerCallback { + (controller: ReadableStreamDefaultController): void | PromiseLike; +} + +const serveHttp: (conn: Deno.Conn) => HttpConn = "serveHttp" in Deno + // deno-lint-ignore no-explicit-any + ? (Deno as any).serveHttp.bind(Deno) + : undefined; + +/** The oak abstraction of the Deno native HTTP server which is used internally + * for handling native HTTP requests. Generally users of oak do not need to + * worry about this class. */ +// deno-lint-ignore no-explicit-any +export class HttpServer> + implements Server { + #app: Application; + #closed = false; + #listener?: Deno.Listener; + #httpConnections: Set = new Set(); + #options: Deno.ListenOptions | Deno.ListenTlsOptions; + + constructor( + app: Application, + options: Deno.ListenOptions | Deno.ListenTlsOptions, + ) { + if (!("serveHttp" in Deno)) { + throw new Error( + "The native bindings for serving HTTP are not available.", + ); + } + this.#app = app; + this.#options = options; + } + + get app(): Application { + return this.#app; + } + + get closed(): boolean { + return this.#closed; + } + + close(): void { + this.#closed = true; + + if (this.#listener) { + this.#listener.close(); + this.#listener = undefined; + } + + for (const httpConn of this.#httpConnections) { + try { + httpConn.close(); + } catch (error) { + if (!(error instanceof Deno.errors.BadResource)) { + throw error; + } + } + } + + this.#httpConnections.clear(); + } + + listen(): Listener { + return (this.#listener = isListenTlsOptions(this.#options) + ? Deno.listenTls(this.#options) + : Deno.listen(this.#options)) as Listener; + } + + #trackHttpConnection(httpConn: HttpConn): void { + this.#httpConnections.add(httpConn); + } + + #untrackHttpConnection(httpConn: HttpConn): void { + this.#httpConnections.delete(httpConn); + } + + [Symbol.asyncIterator](): AsyncIterableIterator { + const start: ReadableStreamDefaultControllerCallback = ( + controller, + ) => { + // deno-lint-ignore no-this-alias + const server = this; + async function serve(conn: Deno.Conn) { + const httpConn = serveHttp(conn); + server.#trackHttpConnection(httpConn); + + while (true) { + try { + const requestEvent = await httpConn.nextRequest(); + + if (requestEvent === null) { + return; + } + + const nativeRequest = new NativeRequest(requestEvent, { conn }); + controller.enqueue(nativeRequest); + // if we await here, this becomes blocking, and really all we want + // it to dispatch any errors that occur on the promise + nativeRequest.donePromise.catch((error) => { + server.app.dispatchEvent(new ErrorEvent("error", { error })); + }); + } catch (error) { + server.app.dispatchEvent(new ErrorEvent("error", { error })); + } + + if (server.closed) { + server.#untrackHttpConnection(httpConn); + httpConn.close(); + controller.close(); + } + } + } + + const listener = this.#listener; + assert(listener); + async function accept() { + while (true) { + try { + const conn = await listener!.accept(); + serve(conn); + } catch (error) { + if (!server.closed) { + server.app.dispatchEvent(new ErrorEvent("error", { error })); + } + } + if (server.closed) { + controller.close(); + return; + } + } + } + + accept(); + }; + const stream = new ReadableStream({ start }); + + return stream[Symbol.asyncIterator](); + } +} diff --git a/vendor/deno.land/x/oak@v12.1.0/http_server_native_request.ts b/vendor/deno.land/x/oak@v12.1.0/http_server_native_request.ts new file mode 100644 index 0000000..cb71009 --- /dev/null +++ b/vendor/deno.land/x/oak@v12.1.0/http_server_native_request.ts @@ -0,0 +1,143 @@ +// Copyright 2018-2022 the oak authors. All rights reserved. MIT license. + +import type { + RequestEvent, + ServerRequest, + ServerRequestBody, + UpgradeWebSocketFn, + UpgradeWebSocketOptions, +} from "./types.d.ts"; + +// deno-lint-ignore no-explicit-any +export const DomResponse: typeof Response = (globalThis as any).Response ?? + class MockResponse {}; + +const maybeUpgradeWebSocket: UpgradeWebSocketFn | undefined = + "upgradeWebSocket" in Deno + // deno-lint-ignore no-explicit-any + ? (Deno as any).upgradeWebSocket.bind(Deno) + : undefined; + +export interface NativeRequestOptions { + conn?: Deno.Conn; + upgradeWebSocket?: UpgradeWebSocketFn; +} + +/** An internal oak abstraction for handling a Deno native request. Most users + * of oak do not need to worry about this abstraction. */ +export class NativeRequest implements ServerRequest { + #conn?: Deno.Conn; + // deno-lint-ignore no-explicit-any + #reject!: (reason?: any) => void; + #request: Request; + #requestPromise: Promise; + #resolve!: (value: Response) => void; + #resolved = false; + #upgradeWebSocket?: UpgradeWebSocketFn; + + constructor( + requestEvent: RequestEvent, + options: NativeRequestOptions = {}, + ) { + const { conn } = options; + this.#conn = conn; + // this allows for the value to be explicitly undefined in the options + this.#upgradeWebSocket = "upgradeWebSocket" in options + ? options["upgradeWebSocket"] + : maybeUpgradeWebSocket; + this.#request = requestEvent.request; + const p = new Promise((resolve, reject) => { + this.#resolve = resolve; + this.#reject = reject; + }); + this.#requestPromise = requestEvent.respondWith(p); + } + + get body(): ReadableStream | null { + // when shimming with undici under Node.js, this is a + // `ControlledAsyncIterable` + // deno-lint-ignore no-explicit-any + return this.#request.body as any; + } + + get donePromise(): Promise { + return this.#requestPromise; + } + + get headers(): Headers { + return this.#request.headers; + } + + get method(): string { + return this.#request.method; + } + + get remoteAddr(): string | undefined { + return (this.#conn?.remoteAddr as Deno.NetAddr)?.hostname; + } + + get request(): Request { + return this.#request; + } + + get url(): string { + try { + const url = new URL(this.#request.url); + return this.#request.url.replace(url.origin, ""); + } catch { + // we don't care about errors, we just want to fall back + } + return this.#request.url; + } + + get rawUrl(): string { + return this.#request.url; + } + + // deno-lint-ignore no-explicit-any + error(reason?: any): void { + if (this.#resolved) { + throw new Error("Request already responded to."); + } + this.#reject(reason); + this.#resolved = true; + } + + getBody(): ServerRequestBody { + return { + // when emitting to Node.js, the body is not compatible, and thought it + // doesn't run at runtime, it still gets type checked. + // deno-lint-ignore no-explicit-any + body: this.#request.body as any, + readBody: async () => { + const ab = await this.#request.arrayBuffer(); + return new Uint8Array(ab); + }, + }; + } + + respond(response: Response): Promise { + if (this.#resolved) { + throw new Error("Request already responded to."); + } + this.#resolve(response); + this.#resolved = true; + return this.#requestPromise; + } + + upgrade(options?: UpgradeWebSocketOptions): WebSocket { + if (this.#resolved) { + throw new Error("Request already responded to."); + } + if (!this.#upgradeWebSocket) { + throw new TypeError("Upgrading web sockets not supported."); + } + const { response, socket } = this.#upgradeWebSocket( + this.#request, + options, + ); + this.#resolve(response); + this.#resolved = true; + return socket; + } +} diff --git a/vendor/deno.land/x/oak@v12.1.0/isMediaType.ts b/vendor/deno.land/x/oak@v12.1.0/isMediaType.ts new file mode 100644 index 0000000..6ecd934 --- /dev/null +++ b/vendor/deno.land/x/oak@v12.1.0/isMediaType.ts @@ -0,0 +1,89 @@ +/*! + * Adapted directly from type-is at https://github.com/jshttp/type-is/ + * which is licensed as follows: + * + * Copyright(c) 2014 Jonathan Ong + * Copyright(c) 2014-2015 Douglas Christopher Wilson + * MIT Licensed + */ + +import { typeByExtension } from "./deps.ts"; +import { format, parse } from "./mediaTyper.ts"; + +function mimeMatch(expected: string | undefined, actual: string): boolean { + if (expected === undefined) { + return false; + } + + const actualParts = actual.split("/"); + const expectedParts = expected.split("/"); + + if (actualParts.length !== 2 || expectedParts.length !== 2) { + return false; + } + + const [actualType, actualSubtype] = actualParts; + const [expectedType, expectedSubtype] = expectedParts; + + if (expectedType !== "*" && expectedType !== actualType) { + return false; + } + + if (expectedSubtype.substr(0, 2) === "*+") { + return ( + expectedSubtype.length <= actualSubtype.length + 1 && + expectedSubtype.substr(1) === + actualSubtype.substr(1 - expectedSubtype.length) + ); + } + + if (expectedSubtype !== "*" && expectedSubtype !== actualSubtype) { + return false; + } + + return true; +} + +function normalize(type: string): string | undefined { + if (type === "urlencoded") { + return "application/x-www-form-urlencoded"; + } else if (type === "multipart") { + return "multipart/*"; + } else if (type[0] === "+") { + return `*/*${type}`; + } + return type.includes("/") ? type : typeByExtension(type); +} + +function normalizeType(value: string): string | undefined { + try { + const val = value.split(";"); + const type = parse(val[0]); + return format(type); + } catch { + return; + } +} + +/** Given a value of the content type of a request and an array of types, + * provide the matching type or `false` if no types are matched. + */ +export function isMediaType(value: string, types: string[]): string | false { + const val = normalizeType(value); + + if (!val) { + return false; + } + + if (!types.length) { + return val; + } + + for (const type of types) { + if (mimeMatch(normalize(type), val)) { + return type[0] === "+" || type.includes("*") ? val : type; + } + } + + return false; +} diff --git a/vendor/deno.land/x/oak@v12.1.0/mediaTyper.ts b/vendor/deno.land/x/oak@v12.1.0/mediaTyper.ts new file mode 100644 index 0000000..e8204f6 --- /dev/null +++ b/vendor/deno.land/x/oak@v12.1.0/mediaTyper.ts @@ -0,0 +1,82 @@ +/*! + * Adapted directly from media-typer at https://github.com/jshttp/media-typer/ + * which is licensed as follows: + * + * media-typer + * Copyright(c) 2014-2017 Douglas Christopher Wilson + * MIT Licensed + */ + +const SUBTYPE_NAME_REGEXP = /^[A-Za-z0-9][A-Za-z0-9!#$&^_.-]{0,126}$/; +const TYPE_NAME_REGEXP = /^[A-Za-z0-9][A-Za-z0-9!#$&^_-]{0,126}$/; +const TYPE_REGEXP = + /^ *([A-Za-z0-9][A-Za-z0-9!#$&^_-]{0,126})\/([A-Za-z0-9][A-Za-z0-9!#$&^_.+-]{0,126}) *$/; + +class MediaType { + constructor( + /** The type of the media type. */ + public type: string, + /** The subtype of the media type. */ + public subtype: string, + /** The optional suffix of the media type. */ + public suffix?: string, + ) {} +} + +/** Given a media type object, return a media type string. + * + * format({ + * type: "text", + * subtype: "html" + * }); // returns "text/html" + */ +export function format(obj: MediaType): string { + const { subtype, suffix, type } = obj; + + if (!TYPE_NAME_REGEXP.test(type)) { + throw new TypeError("Invalid type."); + } + if (!SUBTYPE_NAME_REGEXP.test(subtype)) { + throw new TypeError("Invalid subtype."); + } + + let str = `${type}/${subtype}`; + + if (suffix) { + if (!TYPE_NAME_REGEXP.test(suffix)) { + throw new TypeError("Invalid suffix."); + } + + str += `+${suffix}`; + } + + return str; +} + +/** Given a media type string, return a media type object. + * + * parse("application/json-patch+json"); + * // returns { + * // type: "application", + * // subtype: "json-patch", + * // suffix: "json" + * // } + */ +export function parse(str: string): MediaType { + const match = TYPE_REGEXP.exec(str.toLowerCase()); + + if (!match) { + throw new TypeError("Invalid media type."); + } + + let [, type, subtype] = match; + let suffix: string | undefined; + + const idx = subtype.lastIndexOf("+"); + if (idx !== -1) { + suffix = subtype.substr(idx + 1); + subtype = subtype.substr(0, idx); + } + + return new MediaType(type, subtype, suffix); +} diff --git a/vendor/deno.land/x/oak@v12.1.0/middleware.ts b/vendor/deno.land/x/oak@v12.1.0/middleware.ts new file mode 100644 index 0000000..ba4bf0b --- /dev/null +++ b/vendor/deno.land/x/oak@v12.1.0/middleware.ts @@ -0,0 +1,46 @@ +// Copyright 2018-2022 the oak authors. All rights reserved. MIT license. + +// deno-lint-ignore-file + +import type { State } from "./application.ts"; +import type { Context } from "./context.ts"; + +/** Middleware are functions which are chained together to deal with requests. */ +export interface Middleware< + S extends State = Record, + T extends Context = Context, +> { + (context: T, next: () => Promise): Promise | unknown; +} + +/** Compose multiple middleware functions into a single middleware function. */ +export function compose< + S extends State = Record, + T extends Context = Context, +>( + middleware: Middleware[], +): (context: T, next?: () => Promise) => Promise { + return function composedMiddleware( + context: T, + next?: () => Promise, + ): Promise { + let index = -1; + + async function dispatch(i: number): Promise { + if (i <= index) { + throw new Error("next() called multiple times."); + } + index = i; + let fn: Middleware | undefined = middleware[i]; + if (i === middleware.length) { + fn = next; + } + if (!fn) { + return; + } + await fn(context, dispatch.bind(null, i + 1)); + } + + return dispatch(0); + }; +} diff --git a/vendor/deno.land/x/oak@v12.1.0/middleware/proxy.ts b/vendor/deno.land/x/oak@v12.1.0/middleware/proxy.ts new file mode 100644 index 0000000..a8f618d --- /dev/null +++ b/vendor/deno.land/x/oak@v12.1.0/middleware/proxy.ts @@ -0,0 +1,273 @@ +// Copyright 2018-2022 the oak authors. All rights reserved. MIT license. + +import type { State } from "../application.ts"; +import type { Context } from "../context.ts"; +import type { Middleware } from "../middleware.ts"; +import type { + RouteParams, + RouterContext, + RouterMiddleware, +} from "../router.ts"; +import { isRouterContext } from "../util.ts"; + +export type Fetch = (input: Request) => Promise; + +export type ProxyMatchFunction< + R extends string, + P extends RouteParams = RouteParams, + // deno-lint-ignore no-explicit-any + S extends State = Record, +> = (ctx: Context | RouterContext) => boolean; + +export type ProxyMapFunction> = ( + path: R, + params?: P, +) => R; + +export type ProxyHeadersFunction = ( + ctx: Context, +) => HeadersInit | Promise; + +export type ProxyRouterHeadersFunction< + R extends string, + P extends RouteParams, + S extends State, +> = (ctx: RouterContext) => HeadersInit | Promise; + +export interface ProxyOptions< + R extends string, + P extends RouteParams = RouteParams, + // deno-lint-ignore no-explicit-any + S extends State = Record, +> { + /** A callback hook that is called after the response is received which allows + * the response content type to be adjusted. This is for situations where the + * content type provided by the proxy server might not be suitable for + * responding with. */ + contentType?( + url: string, + contentType?: string, + ): Promise | string | undefined; + /** The fetch function to use to proxy the request. This defaults to the + * global `fetch` function. This is designed for test mocking purposes. */ + fetch?: Fetch; + /** Additional headers that should be set in the response. The value can + * be a headers init value or a function that returns or resolves with a + * headers init value. */ + headers?: + | HeadersInit + | ProxyHeadersFunction + | ProxyRouterHeadersFunction; + /** Either a record or a proxy map function that will allow proxied requests + * being handled by the middleware to be remapped to a different remote + * path. */ + map?: Record | ProxyMapFunction; + /** A string, regular expression or proxy match function what determines if + * the proxy middleware should proxy the request. + * + * If the value is a string the match will be true if the requests pathname + * starts with the string. In the case of a regular expression, if the + * pathname + */ + match?: + | string + | RegExp + | ProxyMatchFunction; + /** A flag that indicates if traditional proxy headers should be set in the + * response. This defaults to `true`. + */ + proxyHeaders?: boolean; + /** A callback hook which will be called before each proxied fetch request + * to allow the native `Request` to be modified or replaced. */ + request?(req: Request): Request | Promise; + /** A callback hook which will be called after each proxied fetch response + * is received to allow the native `Response` to be modified or replaced. */ + response?(res: Response): Response | Promise; +} + +const FORWARDED_RE = + /^(,[ \\t]*)*([!#$%&'*+.^_`|~0-9A-Za-z-]+=([!#$%&'*+.^_`|~0-9A-Za-z-]+|\"([\\t \\x21\\x23-\\x5B\\x5D-\\x7E\\x80-\\xFF]|\\\\[\\t \\x21-\\x7E\\x80-\\xFF])*\"))?(;([!#$%&'*+.^_`|~0-9A-Za-z-]+=([!#$%&'*+.^_`|~0-9A-Za-z-]+|\"([\\t \\x21\\x23-\\x5B\\x5D-\\x7E\\x80-\\xFF]|\\\\[\\t \\x21-\\x7E\\x80-\\xFF])*\"))?)*([ \\t]*,([ \\t]*([!#$%&'*+.^_`|~0-9A-Za-z-]+=([!#$%&'*+.^_`|~0-9A-Za-z-]+|\"([\\t \\x21\\x23-\\x5B\\x5D-\\x7E\\x80-\\xFF]|\\\\[\\t \\x21-\\x7E\\x80-\\xFF])*\"))?(;([!#$%&'*+.^_`|~0-9A-Za-z-]+=([!#$%&'*+.^_`|~0-9A-Za-z-]+|\"([\\t \\x21\\x23-\\x5B\\x5D-\\x7E\\x80-\\xFF]|\\\\[\\t \\x21-\\x7E\\x80-\\xFF])*\"))?)*)?)*$/; + +function createMatcher< + R extends string, + P extends RouteParams, + S extends State, +>( + { match }: ProxyOptions, +) { + return function matches(ctx: RouterContext): boolean { + if (!match) { + return true; + } + if (typeof match === "string") { + return ctx.request.url.pathname.startsWith(match); + } + if (match instanceof RegExp) { + return match.test(ctx.request.url.pathname); + } + return match(ctx); + }; +} + +async function createRequest< + R extends string, + P extends RouteParams, + S extends State, +>( + target: string | URL, + ctx: Context | RouterContext, + { headers: optHeaders, map, proxyHeaders = true, request: reqFn }: + ProxyOptions, +): Promise { + let path = ctx.request.url.pathname as R; + let params: P | undefined; + if (isRouterContext(ctx)) { + params = ctx.params; + } + if (map && typeof map === "function") { + path = map(path, params); + } else if (map) { + path = map[path] ?? path; + } + const url = new URL(String(target)); + if (url.pathname.endsWith("/") && path.startsWith("/")) { + url.pathname = `${url.pathname}${path.slice(1)}`; + } else if (!url.pathname.endsWith("/") && !path.startsWith("/")) { + url.pathname = `${url.pathname}/${path}`; + } else { + url.pathname = `${url.pathname}${path}`; + } + url.search = ctx.request.url.search; + + const body = getBodyInit(ctx); + const headers = new Headers(ctx.request.headers); + if (optHeaders) { + if (typeof optHeaders === "function") { + optHeaders = await optHeaders(ctx as RouterContext); + } + for (const [key, value] of iterableHeaders(optHeaders)) { + headers.set(key, value); + } + } + if (proxyHeaders) { + const maybeForwarded = headers.get("forwarded"); + const ip = ctx.request.ip.startsWith("[") + ? `"${ctx.request.ip}"` + : ctx.request.ip; + const host = headers.get("host"); + if (maybeForwarded && FORWARDED_RE.test(maybeForwarded)) { + let value = `for=${ip}`; + if (host) { + value += `;host=${host}`; + } + headers.append("forwarded", value); + } else { + headers.append("x-forwarded-for", ip); + if (host) { + headers.append("x-forwarded-host", host); + } + } + } + + const init: RequestInit = { + body, + headers, + method: ctx.request.method, + redirect: "follow", + }; + let request = new Request(url.toString(), init); + if (reqFn) { + request = await reqFn(request); + } + return request; +} + +function getBodyInit< + R extends string, + P extends RouteParams, + S extends State, +>( + ctx: Context | RouterContext, +): BodyInit | null { + if (!ctx.request.hasBody) { + return null; + } + return ctx.request.body({ type: "stream" }).value; +} + +function iterableHeaders( + headers: HeadersInit, +): IterableIterator<[string, string]> { + if (headers instanceof Headers) { + return headers.entries(); + } else if (Array.isArray(headers)) { + return headers.values() as IterableIterator<[string, string]>; + } else { + return Object.entries(headers).values() as IterableIterator< + [string, string] + >; + } +} + +async function processResponse< + R extends string, + P extends RouteParams, + S extends State, +>( + response: Response, + ctx: Context | RouterContext, + { contentType: contentTypeFn, response: resFn }: ProxyOptions, +) { + if (resFn) { + response = await resFn(response); + } + if (response.body) { + ctx.response.body = response.body; + } else { + ctx.response.body = null; + } + ctx.response.status = response.status; + for (const [key, value] of response.headers) { + ctx.response.headers.append(key, value); + } + if (contentTypeFn) { + const value = await contentTypeFn( + response.url, + ctx.response.headers.get("content-type") ?? undefined, + ); + if (value != null) { + ctx.response.headers.set("content-type", value); + } + } +} + +/** + * Middleware that provides a back-to-back proxy for requests. + * + * @param target + * @param options + */ +export function proxy( + target: string | URL, + options?: ProxyOptions, S>, +): Middleware; +export function proxy< + R extends string, + P extends RouteParams, + S extends State, +>( + target: string | URL, + options: ProxyOptions = {}, +): RouterMiddleware { + const matches = createMatcher(options); + return async function proxy(ctx, next) { + if (!matches(ctx)) { + return next(); + } + const request = await createRequest(target, ctx, options); + const { fetch = globalThis.fetch } = options; + const response = await fetch(request); + await processResponse(response, ctx, options); + return next(); + }; +} diff --git a/vendor/deno.land/x/oak@v12.1.0/mod.ts b/vendor/deno.land/x/oak@v12.1.0/mod.ts new file mode 100644 index 0000000..d470225 --- /dev/null +++ b/vendor/deno.land/x/oak@v12.1.0/mod.ts @@ -0,0 +1,141 @@ +// Copyright 2018-2022 the oak authors. All rights reserved. MIT license. + +/** + * A middleware framework for handling HTTP with Deno. + * + * oak works well on both Deno CLI and Deno deploy, and is inspired by + * [koa](https://koajs.com/). It works well with both the Deno CLI and + * [Deno Deploy](https://deno.com/deploy). + * + * ### Example server + * + * A minimal router server which responds with content on `/`. With Deno CLI + * this will listen on port 8080 and on Deploy, this will simply serve requests + * received on the application. + * + * ```ts + * import { Application, Router } from "https://deno.land/x/oak/mod.ts"; + * + * const router = new Router(); + * router.get("/", (ctx) => { + * ctx.response.body = ` + * + * Hello oak! + * + *

Hello oak!

+ * + * + * `; + * }); + * + * const app = new Application(); + * app.use(router.routes()); + * app.use(router.allowedMethods()); + * + * app.listen({ port: 8080 }); + * ``` + * + * ### Using Deno's flash server + * + * Currently, Deno's flash server is not the default, even with the `--unstable` + * flag. In order to use the flash server, you need to provide the + * {@linkcode FlashServer} to the {@linkcode Application} constructor: + * + * ```ts + * import { Application, FlashServer } from "https://deno.land/x/oak/mod.ts"; + * + * const app = new Application({ serverConstructor: FlashServer }); + * + * // register middleware + * + * app.listen({ port: 8080 }); + * ``` + * + * Note the currently Deno's flash server requires the `--unstable` flag. If it + * isn't present, the application will error on listening. + * + * @module + */ + +export { Application } from "./application.ts"; +export type { + ApplicationOptions, + ListenOptions, + ListenOptionsBase, + ListenOptionsTls, + State, +} from "./application.ts"; +export type { + BodyBytes, + BodyContentTypes, + BodyForm, + BodyFormData, + BodyJson, + BodyOptions, + BodyOptionsContentTypes, + BodyReader, + BodyStream, + BodyText, + BodyType, + BodyUndefined, +} from "./body.ts"; +export { Context, type ContextSendOptions } from "./context.ts"; +export * as helpers from "./helpers.ts"; +export * as etag from "./etag.ts"; +export { HttpRequest } from "./http_request.ts"; +export { FlashServer, hasFlash } from "./http_server_flash.ts"; +export { HttpServer as HttpServerNative } from "./http_server_native.ts"; +export { type NativeRequest } from "./http_server_native_request.ts"; +export { proxy } from "./middleware/proxy.ts"; +export type { ProxyOptions } from "./middleware/proxy.ts"; +export { compose as composeMiddleware } from "./middleware.ts"; +export type { Middleware } from "./middleware.ts"; +export { FormDataReader } from "./multipart.ts"; +export type { + FormDataBody, + FormDataFile, + FormDataReadOptions, +} from "./multipart.ts"; +export { ifRange, MultiPartStream, parseRange } from "./range.ts"; +export type { ByteRange } from "./range.ts"; +export { Request } from "./request.ts"; +export { REDIRECT_BACK, Response } from "./response.ts"; +export { Router } from "./router.ts"; +export type { + Route, + RouteParams, + RouterAllowedMethodsOptions, + RouterContext, + RouterMiddleware, + RouterOptions, + RouterParamMiddleware, +} from "./router.ts"; +export { send } from "./send.ts"; +export type { SendOptions } from "./send.ts"; +export { ServerSentEvent } from "./server_sent_event.ts"; +export type { + ServerSentEventInit, + ServerSentEventTarget, +} from "./server_sent_event.ts"; +/** Utilities for making testing oak servers easier. */ +export * as testing from "./testing.ts"; +export type { + ErrorStatus, + HTTPMethods, + RedirectStatus, + ServerConstructor, +} from "./types.d.ts"; +export { isErrorStatus, isRedirectStatus } from "./util.ts"; + +// Re-exported from `std/http` +export { + createHttpError, + errors as httpErrors, + HttpError, + isHttpError, + SecureCookieMap as Cookies, + type SecureCookieMapGetOptions as CookiesGetOptions, + type SecureCookieMapSetDeleteOptions as CookiesSetDeleteOptions, + Status, + STATUS_TEXT, +} from "./deps.ts"; diff --git a/vendor/deno.land/x/oak@v12.1.0/multipart.ts b/vendor/deno.land/x/oak@v12.1.0/multipart.ts new file mode 100644 index 0000000..a563900 --- /dev/null +++ b/vendor/deno.land/x/oak@v12.1.0/multipart.ts @@ -0,0 +1,512 @@ +// Copyright 2018-2022 the oak authors. All rights reserved. MIT license. + +import { BufReader, ReadLineResult } from "./buf_reader.ts"; +import { getFilename } from "./content_disposition.ts"; +import { equals, errors, extension, writeAll } from "./deps.ts"; +import { readHeaders, toParamRegExp, unquote } from "./headers.ts"; +import { getRandomFilename, skipLWSPChar, stripEol } from "./util.ts"; + +const decoder = new TextDecoder(); +const encoder = new TextEncoder(); + +const BOUNDARY_PARAM_REGEX = toParamRegExp("boundary", "i"); +const DEFAULT_BUFFER_SIZE = 1_048_576; // 1mb +const DEFAULT_MAX_FILE_SIZE = 10_485_760; // 10mb +const DEFAULT_MAX_SIZE = 0; // all files written to disc +const NAME_PARAM_REGEX = toParamRegExp("name", "i"); + +/** When reading a body in full via `.read()` from a {@linkcode FormDataReader} + * this is what is what the value is resolved, providing a split between any + * fields, and multi-part files that were provided. */ +export interface FormDataBody { + /** A record of form parts where the key was the `name` of the part and the + * value was the value of the part. This record does not include any files + * that were part of the form data. + * + * *Note*: Duplicate names are not included in this record, if there are + * duplicates, the last value will be the value that is set here. If there + * is a possibility of duplicate values, use the `.stream()` method on + * {@linkcode FormDataReader} to iterate over the values. */ + fields: Record; + + /** An array of any files that were part of the form data. */ + files?: FormDataFile[]; +} + +/** A representation of a file that has been read from a form data body. Based + * on the {@linkcode FormDataReadOptions} that were passed when reading will + * determine if files are written to disk or not and how they are written to + * disk. When written to disk, the extension of the file will be determined by + * the content type, with the `.filename` property containing the full path to + * the file. + * + * The original filename as part of the form data is available in + * `originalName`, but for security and stability reasons, it is not used to + * determine the name of the file on disk. If further processing or renaming + * is required, the implementor should do that processing. */ +export interface FormDataFile { + /** When the file has not been written out to disc, the contents of the file + * as a {@linkcode Uint8Array}. */ + content?: Uint8Array; + + /** The content type of the form data file. */ + contentType: string; + + /** When the file has been written out to disc, the full path to the file. */ + filename?: string; + + /** The `name` that was assigned to the form data file. */ + name: string; + + /** The `filename` that was provided in the form data file. */ + originalName: string; +} + +/** Options which impact how the form data is decoded for a + * {@linkcode FormDataReader}. All these options have sensible defaults for + * most applications, but can be modified for different use cases. Many of these + * options can have an impact on the stability of a server, especially if there + * is someone attempting a denial of service attack on your server, so be + * careful when changing the defaults. */ +export interface FormDataReadOptions { + /** The size of the buffer to read from the request body at a single time. + * This defaults to 1mb. */ + bufferSize?: number; + + /** A mapping of custom media types that are supported, mapped to their + * extension when determining the extension for a file. The key should be an + * all lowercase media type with the value being the extension (without an + * initial period), to be used when decoding the file. + * + * ### Example + * + * Form data that is sent with content having a type of `text/vdn.custom` will + * be decoded and assigned a filename ending with `.txt`: + * + * ```ts + * import { Application } from "https://deno.land/x/oak/mod.ts"; + * + * const app = new Application(); + * app.use(async (ctx) => { + * const body = ctx.request.body(); + * if (body.type === "form-data") { + * const formatData = await body.value.read({ + * customContentTypes: { + * "text/vnd.custom": "txt" + * } + * }); + * console.log(formData); + * } + * }); + * ``` + */ + customContentTypes?: Record; + + /** The maximum file size (in bytes) that can be handled. This defaults to + * 10MB when not specified. This is to try to avoid DOS attacks where + * someone would continue to try to send a "file" continuously until a host + * limit was reached crashing the server or the host. Also see `maxSize`. */ + maxFileSize?: number; + + /** The maximum size (in bytes) of a file to hold in memory, and not write + * to disk. This defaults to `0`, so that all multipart form files are + * written to disk. When set to a positive integer, if the form data file is + * smaller, it will be retained in memory and available in the `.content` + * property of the `FormDataFile` object. If the file exceeds the `maxSize` + * it will be written to disk and the `.filename` property will contain the + * full path to the output file. */ + maxSize?: number; + + /** When writing form data files to disk, the output path. This will default + * to a temporary path generated by `Deno.makeTempDir()`. */ + outPath?: string; + + /** When a form data file is written to disk, it will be generated with a + * random filename and have an extension based off the content type for the + * file. `prefix` can be specified though to prepend to the file name. */ + prefix?: string; +} + +interface PartsOptions { + body: BufReader; + customContentTypes?: Record; + final: Uint8Array; + maxFileSize: number; + maxSize: number; + outPath?: string; + part: Uint8Array; + prefix?: string; +} + +function append(a: Uint8Array, b: Uint8Array): Uint8Array { + const ab = new Uint8Array(a.length + b.length); + ab.set(a, 0); + ab.set(b, a.length); + return ab; +} + +function isEqual(a: Uint8Array, b: Uint8Array): boolean { + return equals(skipLWSPChar(a), b); +} + +async function readToStartOrEnd( + body: BufReader, + start: Uint8Array, + end: Uint8Array, +): Promise { + let lineResult: ReadLineResult | null; + while ((lineResult = await body.readLine())) { + if (isEqual(lineResult.bytes, start)) { + return true; + } + if (isEqual(lineResult.bytes, end)) { + return false; + } + } + throw new errors.BadRequest( + "Unable to find multi-part boundary.", + ); +} + +/** Yield up individual parts by reading the body and parsing out the ford + * data values. */ +async function* parts( + { + body, + customContentTypes = {}, + final, + part, + maxFileSize, + maxSize, + outPath, + prefix, + }: PartsOptions, +): AsyncIterableIterator<[string, string | FormDataFile]> { + async function getFile(contentType: string): Promise<[string, Deno.FsFile]> { + const ext = customContentTypes[contentType.toLowerCase()] ?? + extension(contentType); + if (!ext) { + throw new errors.BadRequest( + `The form contained content type "${contentType}" which is not supported by the server.`, + ); + } + if (!outPath) { + outPath = await Deno.makeTempDir(); + } + const filename = `${outPath}/${await getRandomFilename(prefix, ext)}`; + const file = await Deno.open(filename, { write: true, createNew: true }); + return [filename, file]; + } + + while (true) { + const headers = await readHeaders(body); + const contentType = headers["content-type"]; + const contentDisposition = headers["content-disposition"]; + if (!contentDisposition) { + throw new errors.BadRequest( + "Form data part missing content-disposition header", + ); + } + if (!contentDisposition.match(/^form-data;/i)) { + throw new errors.BadRequest( + `Unexpected content-disposition header: "${contentDisposition}"`, + ); + } + const matches = NAME_PARAM_REGEX.exec(contentDisposition); + if (!matches) { + throw new errors.BadRequest( + `Unable to determine name of form body part`, + ); + } + let [, name] = matches; + name = unquote(name); + if (contentType) { + const originalName = getFilename(contentDisposition); + let byteLength = 0; + let file: Deno.FsFile | undefined; + let filename: string | undefined; + let buf: Uint8Array | undefined; + if (maxSize) { + buf = new Uint8Array(); + } else { + const result = await getFile(contentType); + filename = result[0]; + file = result[1]; + } + while (true) { + const readResult = await body.readLine(false); + if (!readResult) { + throw new errors.BadRequest("Unexpected EOF reached"); + } + const { bytes } = readResult; + const strippedBytes = stripEol(bytes); + if (isEqual(strippedBytes, part) || isEqual(strippedBytes, final)) { + if (file) { + // remove extra 2 bytes ([CR, LF]) from result file + const bytesDiff = bytes.length - strippedBytes.length; + if (bytesDiff) { + const originalBytesSize = await file.seek( + -bytesDiff, + Deno.SeekMode.Current, + ); + await file.truncate(originalBytesSize); + } + + file.close(); + } + yield [ + name, + { + content: buf, + contentType, + name, + filename, + originalName, + } as FormDataFile, + ]; + if (isEqual(strippedBytes, final)) { + return; + } + break; + } + byteLength += bytes.byteLength; + if (byteLength > maxFileSize) { + if (file) { + file.close(); + } + throw new errors.RequestEntityTooLarge( + `File size exceeds limit of ${maxFileSize} bytes.`, + ); + } + if (buf) { + if (byteLength > maxSize) { + const result = await getFile(contentType); + filename = result[0]; + file = result[1]; + await writeAll(file, buf); + buf = undefined; + } else { + buf = append(buf, bytes); + } + } + if (file) { + await writeAll(file, bytes); + } + } + } else { + const lines: string[] = []; + while (true) { + const readResult = await body.readLine(); + if (!readResult) { + throw new errors.BadRequest("Unexpected EOF reached"); + } + const { bytes } = readResult; + if (isEqual(bytes, part) || isEqual(bytes, final)) { + yield [name, lines.join("\n")]; + if (isEqual(bytes, final)) { + return; + } + break; + } + lines.push(decoder.decode(bytes)); + } + } + } +} + +/** An interface which provides an interface to access the fields of a + * `multipart/form-data` body. + * + * Normally an instance of this is accessed when handling a request body, and + * dealing with decoding it. There are options that can be set when attempting + * to read a multi-part body (see: {@linkcode FormDataReadOptions}). + * + * If you `.read()` the value, then a promise is provided of type + * {@linkcode FormDataBody}. If you use the `.stream()` property, it is an async + * iterator which yields up a tuple of with the first element being a + * + * ### Examples + * + * Using `.read()`: + * + * ```ts + * import { Application } from "https://deno.land/x/oak/mod.ts"; + * + * const app = new Application(); + * + * app.use(async (ctx) => { + * const body = ctx.request.body(); + * if (body.type === "form-data") { + * const value = body.value; + * const formData = await value.read(); + * // the form data is fully available + * } + * }); + * ``` + * + * Using `.stream()`: + * + * ```ts + * import { Application } from "https://deno.land/x/oak/mod.ts"; + * + * const app = new Application(); + * + * app.use(async (ctx) => { + * const body = ctx.request.body(); + * if (body.type === "form-data") { + * const value = body.value; + * for await (const [name, value] of value.stream()) { + * // asynchronously iterate each part of the body + * } + * } + * }); + * ``` + */ +export class FormDataReader { + #body: Deno.Reader; + #boundaryFinal: Uint8Array; + #boundaryPart: Uint8Array; + #reading = false; + + constructor(contentType: string, body: Deno.Reader) { + const matches = contentType.match(BOUNDARY_PARAM_REGEX); + if (!matches) { + throw new errors.BadRequest( + `Content type "${contentType}" does not contain a valid boundary.`, + ); + } + let [, boundary] = matches; + boundary = unquote(boundary); + this.#boundaryPart = encoder.encode(`--${boundary}`); + this.#boundaryFinal = encoder.encode(`--${boundary}--`); + this.#body = body; + } + + /** Reads the multipart body of the response and resolves with an object which + * contains fields and files that were part of the response. + * + * *Note*: this method handles multiple files with the same `name` attribute + * in the request, but by design it does not handle multiple fields that share + * the same `name`. If you expect the request body to contain multiple form + * data fields with the same name, it is better to use the `.stream()` method + * which will iterate over each form data field individually. */ + async read(options: FormDataReadOptions = {}): Promise { + if (this.#reading) { + throw new Error("Body is already being read."); + } + this.#reading = true; + const { + outPath, + maxFileSize = DEFAULT_MAX_FILE_SIZE, + maxSize = DEFAULT_MAX_SIZE, + bufferSize = DEFAULT_BUFFER_SIZE, + customContentTypes, + } = options; + const body = new BufReader(this.#body, bufferSize); + const result: FormDataBody = { fields: {} }; + if ( + !(await readToStartOrEnd(body, this.#boundaryPart, this.#boundaryFinal)) + ) { + return result; + } + try { + for await ( + const part of parts({ + body, + customContentTypes, + part: this.#boundaryPart, + final: this.#boundaryFinal, + maxFileSize, + maxSize, + outPath, + }) + ) { + const [key, value] = part; + if (typeof value === "string") { + result.fields[key] = value; + } else { + if (!result.files) { + result.files = []; + } + result.files.push(value); + } + } + } catch (err) { + if (err instanceof Deno.errors.PermissionDenied) { + console.error(err.stack ? err.stack : `${err.name}: ${err.message}`); + } else { + throw err; + } + } + return result; + } + + /** Returns an iterator which will asynchronously yield each part of the form + * data. The yielded value is a tuple, where the first element is the name + * of the part and the second element is a `string` or a `FormDataFile` + * object. */ + async *stream( + options: FormDataReadOptions = {}, + ): AsyncIterableIterator<[name: string, value: string | FormDataFile]> { + if (this.#reading) { + throw new Error("Body is already being read."); + } + this.#reading = true; + const { + outPath, + customContentTypes, + maxFileSize = DEFAULT_MAX_FILE_SIZE, + maxSize = DEFAULT_MAX_SIZE, + bufferSize = 32000, + } = options; + const body = new BufReader(this.#body, bufferSize); + if ( + !(await readToStartOrEnd(body, this.#boundaryPart, this.#boundaryFinal)) + ) { + return; + } + try { + for await ( + const part of parts({ + body, + customContentTypes, + part: this.#boundaryPart, + final: this.#boundaryFinal, + maxFileSize, + maxSize, + outPath, + }) + ) { + yield part; + } + } catch (err) { + if (err instanceof Deno.errors.PermissionDenied) { + console.error(err.stack ? err.stack : `${err.name}: ${err.message}`); + } else { + throw err; + } + } + } + + [Symbol.for("Deno.customInspect")](inspect: (value: unknown) => string) { + return `${this.constructor.name} ${inspect({})}`; + } + + [Symbol.for("nodejs.util.inspect.custom")]( + depth: number, + // deno-lint-ignore no-explicit-any + options: any, + inspect: (value: unknown, options?: unknown) => string, + ) { + if (depth < 0) { + return options.stylize(`[${this.constructor.name}]`, "special"); + } + + const newOptions = Object.assign({}, options, { + depth: options.depth === null ? null : options.depth - 1, + }); + return `${options.stylize(this.constructor.name, "special")} ${ + inspect({}, newOptions) + }`; + } +} diff --git a/vendor/deno.land/x/oak@v12.1.0/range.ts b/vendor/deno.land/x/oak@v12.1.0/range.ts new file mode 100644 index 0000000..f475d26 --- /dev/null +++ b/vendor/deno.land/x/oak@v12.1.0/range.ts @@ -0,0 +1,169 @@ +// Copyright 2018-2022 the oak authors. All rights reserved. MIT license. + +import { + concat, + contentType, + copyBytes, + createHttpError, + Status, +} from "./deps.ts"; +import { calculate } from "./etag.ts"; +import type { FileInfo } from "./etag.ts"; +import { assert, DEFAULT_CHUNK_SIZE } from "./util.ts"; + +const ETAG_RE = /(?:W\/)?"[ !#-\x7E\x80-\xFF]+"/; + +export interface ByteRange { + start: number; + end: number; +} + +/** Determine, by the value of an `If-Range` header, if a `Range` header should + * be applied to a request, returning `true` if it should or otherwise + * `false`. */ +export async function ifRange( + value: string, + mtime: number, + entity: Uint8Array | FileInfo, +): Promise { + if (value) { + const matches = value.match(ETAG_RE); + if (matches) { + const [match] = matches; + if (await calculate(entity) === match) { + return true; + } + } else { + return new Date(value).getTime() >= mtime; + } + } + return false; +} + +export function parseRange(value: string, size: number): ByteRange[] { + const ranges: ByteRange[] = []; + const [unit, rangesStr] = value.split("="); + if (unit !== "bytes") { + throw createHttpError(Status.RequestedRangeNotSatisfiable); + } + for (const range of rangesStr.split(/\s*,\s+/)) { + const item = range.split("-"); + if (item.length !== 2) { + throw createHttpError(Status.RequestedRangeNotSatisfiable); + } + const [startStr, endStr] = item; + let start: number; + let end: number; + try { + if (startStr === "") { + start = size - parseInt(endStr, 10) - 1; + end = size - 1; + } else if (endStr === "") { + start = parseInt(startStr, 10); + end = size - 1; + } else { + start = parseInt(startStr, 10); + end = parseInt(endStr, 10); + } + } catch { + throw createHttpError(); + } + if (start < 0 || start >= size || end < 0 || end >= size || start > end) { + throw createHttpError(Status.RequestedRangeNotSatisfiable); + } + ranges.push({ start, end }); + } + return ranges; +} + +/** A reader */ +async function readRange( + file: Deno.Reader & Deno.Seeker, + range: ByteRange, +): Promise { + let length = range.end - range.start + 1; + assert(length); + await file.seek(range.start, Deno.SeekMode.Start); + const result = new Uint8Array(length); + let off = 0; + while (length) { + const p = new Uint8Array(Math.min(length, DEFAULT_CHUNK_SIZE)); + const nread = await file.read(p); + assert(nread !== null, "Unexpected EOF encountered when reading a range."); + assert(nread > 0, "Unexpected read of 0 bytes while reading a range."); + copyBytes(p, result, off); + off += nread; + length -= nread; + assert(length >= 0, "Unexpected length remaining."); + } + return result; +} + +const encoder = new TextEncoder(); + +/** A class that takes a file (either a Deno.FsFile or Uint8Array) and bytes + * and streams the ranges as a multi-part encoded HTTP body. + * + * This is specifically used by the `.send()` functionality to fulfill range + * requests it receives, and could be used by others when trying to deal with + * range requests, but is generally a low level API that most users of oak + * would not need to worry about. */ +export class MultiPartStream extends ReadableStream { + #contentLength: number; + #postscript: Uint8Array; + #preamble: Uint8Array; + + constructor( + file: (Deno.Reader & Deno.Seeker & Deno.Closer) | Uint8Array, + type: string, + ranges: ByteRange[], + size: number, + boundary: string, + ) { + super({ + pull: async (controller) => { + const range = ranges.shift(); + if (!range) { + controller.enqueue(this.#postscript); + controller.close(); + if (!(file instanceof Uint8Array)) { + file.close(); + } + return; + } + let bytes: Uint8Array; + if (file instanceof Uint8Array) { + bytes = file.subarray(range.start, range.end + 1); + } else { + bytes = await readRange(file, range); + } + const rangeHeader = encoder.encode( + `Content-Range: ${range.start}-${range.end}/${size}\n\n`, + ); + controller.enqueue(concat(this.#preamble, rangeHeader, bytes)); + }, + }); + + const resolvedType = contentType(type); + if (!resolvedType) { + throw new TypeError(`Could not resolve media type for "${type}"`); + } + this.#preamble = encoder.encode( + `\n--${boundary}\nContent-Type: ${resolvedType}\n`, + ); + + this.#postscript = encoder.encode(`\n--${boundary}--\n`); + this.#contentLength = ranges.reduce( + (prev, { start, end }): number => { + return prev + this.#preamble.length + String(start).length + + String(end).length + String(size).length + 20 + (end - start); + }, + this.#postscript.length, + ); + } + + /** The content length of the entire streamed body. */ + contentLength() { + return this.#contentLength; + } +} diff --git a/vendor/deno.land/x/oak@v12.1.0/request.ts b/vendor/deno.land/x/oak@v12.1.0/request.ts new file mode 100644 index 0000000..43216a0 --- /dev/null +++ b/vendor/deno.land/x/oak@v12.1.0/request.ts @@ -0,0 +1,264 @@ +// Copyright 2018-2022 the oak authors. All rights reserved. MIT license. + +import type { + Body, + BodyBytes, + BodyForm, + BodyFormData, + BodyJson, + BodyOptions, + BodyReader, + BodyStream, + BodyText, +} from "./body.ts"; +import { RequestBody } from "./body.ts"; +import { accepts, acceptsEncodings, acceptsLanguages } from "./deps.ts"; +import type { HTTPMethods, ServerRequest } from "./types.d.ts"; + +export interface OakRequestOptions { + jsonBodyReviver?: (key: string, value: unknown) => unknown; + proxy?: boolean; + secure?: boolean; +} + +/** An interface which provides information about the current request. The + * instance related to the current request is available on the + * {@linkcode Context}'s `.request` property. + * + * The interface contains several properties to get information about the + * request as well as several methods, which include content negotiation and + * the ability to decode a request body. + */ +export class Request { + #body: RequestBody; + #proxy: boolean; + #secure: boolean; + #serverRequest: ServerRequest; + #url?: URL; + + #getRemoteAddr(): string { + return this.#serverRequest.remoteAddr ?? ""; + } + + /** Is `true` if the request might have a body, otherwise `false`. + * + * **WARNING** this is an unreliable API. In HTTP/2 in many situations you + * cannot determine if a request has a body or not unless you attempt to read + * the body, due to the streaming nature of HTTP/2. As of Deno 1.16.1, for + * HTTP/1.1, Deno also reflects that behaviour. The only reliable way to + * determine if a request has a body or not is to attempt to read the body. + */ + get hasBody(): boolean { + return this.#body.has(); + } + + /** The `Headers` supplied in the request. */ + get headers(): Headers { + return this.#serverRequest.headers; + } + + /** Request remote address. When the application's `.proxy` is true, the + * `X-Forwarded-For` will be used to determine the requesting remote address. + */ + get ip(): string { + return (this.#proxy ? this.ips[0] : this.#getRemoteAddr()) ?? ""; + } + + /** When the application's `.proxy` is `true`, this will be set to an array of + * IPs, ordered from upstream to downstream, based on the value of the header + * `X-Forwarded-For`. When `false` an empty array is returned. */ + get ips(): string[] { + return this.#proxy + ? (this.#serverRequest.headers.get("x-forwarded-for") ?? + this.#getRemoteAddr()).split(/\s*,\s*/) + : []; + } + + /** The HTTP Method used by the request. */ + get method(): HTTPMethods { + return this.#serverRequest.method as HTTPMethods; + } + + /** Shortcut to `request.url.protocol === "https:"`. */ + get secure(): boolean { + return this.#secure; + } + + /** Set to the value of the _original_ Deno server request. */ + get originalRequest(): ServerRequest { + return this.#serverRequest; + } + + /** A parsed URL for the request which complies with the browser standards. + * When the application's `.proxy` is `true`, this value will be based off of + * the `X-Forwarded-Proto` and `X-Forwarded-Host` header values if present in + * the request. */ + get url(): URL { + if (!this.#url) { + const serverRequest = this.#serverRequest; + if (!this.#proxy) { + // between 1.9.0 and 1.9.1 the request.url of the native HTTP started + // returning the full URL, where previously it only returned the path + // so we will try to use that URL here, but default back to old logic + // if the URL isn't valid. + try { + if (serverRequest.rawUrl) { + this.#url = new URL(serverRequest.rawUrl); + return this.#url; + } + } catch { + // we don't care about errors here + } + } + let proto: string; + let host: string; + if (this.#proxy) { + proto = serverRequest + .headers.get("x-forwarded-proto")?.split(/\s*,\s*/, 1)[0] ?? + "http"; + host = serverRequest.headers.get("x-forwarded-host") ?? + serverRequest.headers.get("host") ?? ""; + } else { + proto = this.#secure ? "https" : "http"; + host = serverRequest.headers.get("host") ?? ""; + } + try { + this.#url = new URL(`${proto}://${host}${serverRequest.url}`); + } catch { + throw new TypeError( + `The server request URL of "${proto}://${host}${serverRequest.url}" is invalid.`, + ); + } + } + return this.#url; + } + + constructor( + serverRequest: ServerRequest, + { proxy = false, secure = false, jsonBodyReviver }: OakRequestOptions = {}, + ) { + this.#proxy = proxy; + this.#secure = secure; + this.#serverRequest = serverRequest; + this.#body = new RequestBody( + serverRequest.getBody(), + serverRequest.headers, + jsonBodyReviver, + ); + } + + /** Returns an array of media types, accepted by the requestor, in order of + * preference. If there are no encodings supplied by the requestor, + * then accepting any is implied is returned. + */ + accepts(): string[] | undefined; + /** For a given set of media types, return the best match accepted by the + * requestor. If there are no encoding that match, then the method returns + * `undefined`. + */ + accepts(...types: string[]): string | undefined; + accepts(...types: string[]): string | string[] | undefined { + if (!this.#serverRequest.headers.has("Accept")) { + return types.length ? types[0] : ["*/*"]; + } + if (types.length) { + return accepts(this.#serverRequest, ...types); + } + return accepts(this.#serverRequest); + } + + /** Returns an array of encodings, accepted by the requestor, in order of + * preference. If there are no encodings supplied by the requestor, + * then `["*"]` is returned, matching any. + */ + acceptsEncodings(): string[] | undefined; + /** For a given set of encodings, return the best match accepted by the + * requestor. If there are no encodings that match, then the method returns + * `undefined`. + * + * **NOTE:** You should always supply `identity` as one of the encodings + * to ensure that there is a match when the `Accept-Encoding` header is part + * of the request. + */ + acceptsEncodings(...encodings: string[]): string | undefined; + acceptsEncodings(...encodings: string[]): string[] | string | undefined { + if (!this.#serverRequest.headers.has("Accept-Encoding")) { + return encodings.length ? encodings[0] : ["*"]; + } + if (encodings.length) { + return acceptsEncodings(this.#serverRequest, ...encodings); + } + return acceptsEncodings(this.#serverRequest); + } + + /** Returns an array of languages, accepted by the requestor, in order of + * preference. If there are no languages supplied by the requestor, + * `["*"]` is returned, indicating any language is accepted. + */ + acceptsLanguages(): string[] | undefined; + /** For a given set of languages, return the best match accepted by the + * requestor. If there are no languages that match, then the method returns + * `undefined`. */ + acceptsLanguages(...langs: string[]): string | undefined; + acceptsLanguages(...langs: string[]): string[] | string | undefined { + if (!this.#serverRequest.headers.get("Accept-Language")) { + return langs.length ? langs[0] : ["*"]; + } + if (langs.length) { + return acceptsLanguages(this.#serverRequest, ...langs); + } + return acceptsLanguages(this.#serverRequest); + } + + body(options: BodyOptions<"bytes">): BodyBytes; + body(options: BodyOptions<"form">): BodyForm; + body(options: BodyOptions<"form-data">): BodyFormData; + body(options: BodyOptions<"json">): BodyJson; + body(options: BodyOptions<"reader">): BodyReader; + body(options: BodyOptions<"stream">): BodyStream; + body(options: BodyOptions<"text">): BodyText; + body(options?: BodyOptions): Body; + /** Access the body of the request. This is a method, because there are + * several options which can be provided which can influence how the body is + * handled. */ + body(options: BodyOptions = {}): Body | BodyReader | BodyStream { + return this.#body.get(options); + } + + [Symbol.for("Deno.customInspect")](inspect: (value: unknown) => string) { + const { hasBody, headers, ip, ips, method, secure, url } = this; + return `${this.constructor.name} ${ + inspect({ + hasBody, + headers, + ip, + ips, + method, + secure, + url: url.toString(), + }) + }`; + } + + [Symbol.for("nodejs.util.inspect.custom")]( + depth: number, + // deno-lint-ignore no-explicit-any + options: any, + inspect: (value: unknown, options?: unknown) => string, + ) { + if (depth < 0) { + return options.stylize(`[${this.constructor.name}]`, "special"); + } + + const newOptions = Object.assign({}, options, { + depth: options.depth === null ? null : options.depth - 1, + }); + const { hasBody, headers, ip, ips, method, secure, url } = this; + return `${options.stylize(this.constructor.name, "special")} ${ + inspect( + { hasBody, headers, ip, ips, method, secure, url }, + newOptions, + ) + }`; + } +} diff --git a/vendor/deno.land/x/oak@v12.1.0/response.ts b/vendor/deno.land/x/oak@v12.1.0/response.ts new file mode 100644 index 0000000..611289f --- /dev/null +++ b/vendor/deno.land/x/oak@v12.1.0/response.ts @@ -0,0 +1,356 @@ +// Copyright 2018-2022 the oak authors. All rights reserved. MIT license. + +import { contentType, Status, STATUS_TEXT } from "./deps.ts"; +import { DomResponse } from "./http_server_native_request.ts"; +import type { Request } from "./request.ts"; +import { + BODY_TYPES, + encodeUrl, + isAsyncIterable, + isHtml, + isReader, + isRedirectStatus, + readableStreamFromAsyncIterable, + readableStreamFromReader, + Uint8ArrayTransformStream, +} from "./util.ts"; + +export type ResponseBody = + | string + | number + | bigint + | boolean + | symbol + // deno-lint-ignore ban-types + | object + | undefined + | null; +export type ResponseBodyFunction = () => ResponseBody | Promise; + +/** A symbol that indicates to `response.redirect()` to attempt to redirect + * back to the request referrer. For example: + * + * ```ts + * import { Application, REDIRECT_BACK } from "https://deno.land/x/oak/mod.ts"; + * + * const app = new Application(); + * + * app.use((ctx) => { + * if (ctx.request.url.pathName === "/back") { + * ctx.response.redirect(REDIRECT_BACK, "/"); + * } + * }); + * + * await app.listen({ port: 80 }); + * ``` + */ +export const REDIRECT_BACK = Symbol("redirect backwards"); + +export async function convertBodyToBodyInit( + body: ResponseBody | ResponseBodyFunction, + type?: string, + jsonBodyReplacer?: (key: string, value: unknown) => unknown, +): Promise<[globalThis.BodyInit | undefined, string | undefined]> { + let result: globalThis.BodyInit | undefined; + if (BODY_TYPES.includes(typeof body)) { + result = String(body); + type = type ?? (isHtml(result) ? "html" : "text/plain"); + } else if (isReader(body)) { + result = readableStreamFromReader(body); + } else if ( + ArrayBuffer.isView(body) || body instanceof ArrayBuffer || + body instanceof Blob || body instanceof URLSearchParams + ) { + // deno-lint-ignore no-explicit-any + result = body as any; + } else if (body instanceof ReadableStream) { + result = body.pipeThrough(new Uint8ArrayTransformStream()); + } else if (body instanceof FormData) { + result = body; + type = "multipart/form-data"; + } else if (isAsyncIterable(body)) { + result = readableStreamFromAsyncIterable(body); + } else if (body && typeof body === "object") { + result = JSON.stringify(body, jsonBodyReplacer); + type = type ?? "json"; + } else if (typeof body === "function") { + const result = body.call(null); + return convertBodyToBodyInit(await result, type, jsonBodyReplacer); + } else if (body) { + throw new TypeError("Response body was set but could not be converted."); + } + return [result, type]; +} + +/** An interface to control what response will be sent when the middleware + * finishes processing the request. + * + * The response is usually accessed via the context's `.response` property. + * + * ### Example + * + * ```ts + * import { Application, Status } from "https://deno.land/x/oak/mod.ts"; + * + * const app = new Application(); + * + * app.use((ctx) => { + * ctx.response.body = { hello: "oak" }; + * ctx.response.type = "json"; + * ctx.response.status = Status.OK; + * }); + * ``` + */ +export class Response { + #body?: ResponseBody | ResponseBodyFunction; + #bodySet = false; + #domResponse?: globalThis.Response; + #headers = new Headers(); + #jsonBodyReplacer?: (key: string, value: unknown) => unknown; + #request: Request; + #resources: number[] = []; + #status?: Status; + #type?: string; + #writable = true; + + async #getBodyInit(): Promise { + const [body, type] = await convertBodyToBodyInit( + this.body, + this.type, + this.#jsonBodyReplacer, + ); + this.type = type; + return body; + } + + #setContentType(): void { + if (this.type) { + const contentTypeString = contentType(this.type); + if (contentTypeString && !this.headers.has("Content-Type")) { + this.headers.append("Content-Type", contentTypeString); + } + } + } + + /** The body of the response. The body will be automatically processed when + * the response is being sent and converted to a `Uint8Array` or a + * `Deno.Reader`. + * + * Automatic conversion to a `Deno.Reader` occurs for async iterables. */ + get body(): ResponseBody | ResponseBodyFunction { + return this.#body; + } + + /** The body of the response. The body will be automatically processed when + * the response is being sent and converted to a `Uint8Array` or a + * `Deno.Reader`. + * + * Automatic conversion to a `Deno.Reader` occurs for async iterables. */ + set body(value: ResponseBody | ResponseBodyFunction) { + if (!this.#writable) { + throw new Error("The response is not writable."); + } + this.#bodySet = true; + this.#body = value; + } + + /** Headers that will be returned in the response. */ + get headers(): Headers { + return this.#headers; + } + + /** Headers that will be returned in the response. */ + set headers(value: Headers) { + if (!this.#writable) { + throw new Error("The response is not writable."); + } + this.#headers = value; + } + + /** The HTTP status of the response. If this has not been explicitly set, + * reading the value will return what would be the value of status if the + * response were sent at this point in processing the middleware. If the body + * has been set, the status will be `200 OK`. If a value for the body has + * not been set yet, the status will be `404 Not Found`. */ + get status(): Status { + if (this.#status) { + return this.#status; + } + return this.body != null + ? Status.OK + : this.#bodySet + ? Status.NoContent + : Status.NotFound; + } + + /** The HTTP status of the response. If this has not been explicitly set, + * reading the value will return what would be the value of status if the + * response were sent at this point in processing the middleware. If the body + * has been set, the status will be `200 OK`. If a value for the body has + * not been set yet, the status will be `404 Not Found`. */ + set status(value: Status) { + if (!this.#writable) { + throw new Error("The response is not writable."); + } + this.#status = value; + } + + /** The media type, or extension of the response. Setting this value will + * ensure an appropriate `Content-Type` header is added to the response. */ + get type(): string | undefined { + return this.#type; + } + /** The media type, or extension of the response. Setting this value will + * ensure an appropriate `Content-Type` header is added to the response. */ + set type(value: string | undefined) { + if (!this.#writable) { + throw new Error("The response is not writable."); + } + this.#type = value; + } + + /** A read-only property which determines if the response is writable or not. + * Once the response has been processed, this value is set to `false`. */ + get writable(): boolean { + return this.#writable; + } + + constructor( + request: Request, + jsonBodyReplacer?: (key: string, value: unknown) => unknown, + ) { + this.#request = request; + this.#jsonBodyReplacer = jsonBodyReplacer; + } + + /** Add a resource to the list of resources that will be closed when the + * request is destroyed. */ + addResource(rid: number): void { + this.#resources.push(rid); + } + + /** Release any resources that are being tracked by the response. + * + * @param closeResources close any resource IDs registered with the response + */ + destroy(closeResources = true): void { + this.#writable = false; + this.#body = undefined; + this.#domResponse = undefined; + if (closeResources) { + for (const rid of this.#resources) { + try { + Deno.close(rid); + } catch { + // we don't care about errors here + } + } + } + } + + /** Sets the response to redirect to the supplied `url`. + * + * If the `.status` is not currently a redirect status, the status will be set + * to `302 Found`. + * + * The body will be set to a message indicating the redirection is occurring. + */ + redirect(url: string | URL): void; + /** Sets the response to redirect back to the referrer if available, with an + * optional `alt` URL if there is no referrer header on the request. If there + * is no referrer header, nor an `alt` parameter, the redirect is set to `/`. + * + * If the `.status` is not currently a redirect status, the status will be set + * to `302 Found`. + * + * The body will be set to a message indicating the redirection is occurring. + */ + redirect(url: typeof REDIRECT_BACK, alt?: string | URL): void; + redirect( + url: string | URL | typeof REDIRECT_BACK, + alt: string | URL = "/", + ): void { + if (url === REDIRECT_BACK) { + url = this.#request.headers.get("Referer") ?? String(alt); + } else if (typeof url === "object") { + url = String(url); + } + this.headers.set("Location", encodeUrl(url)); + if (!this.status || !isRedirectStatus(this.status)) { + this.status = Status.Found; + } + + if (this.#request.accepts("html")) { + url = encodeURI(url); + this.type = "text/html; charset=UTF-8"; + this.body = `Redirecting to ${url}.`; + return; + } + this.type = "text/plain; charset=UTF-8"; + this.body = `Redirecting to ${url}.`; + } + + async toDomResponse(): Promise { + if (this.#domResponse) { + return this.#domResponse; + } + + const bodyInit = await this.#getBodyInit(); + + this.#setContentType(); + + const { headers } = this; + + // If there is no body and no content type and no set length, then set the + // content length to 0 + if ( + !( + bodyInit || + headers.has("Content-Type") || + headers.has("Content-Length") + ) + ) { + headers.append("Content-Length", "0"); + } + + this.#writable = false; + + const status = this.status; + const responseInit: ResponseInit = { + headers, + status, + statusText: STATUS_TEXT[status], + }; + + return this.#domResponse = new DomResponse(bodyInit, responseInit); + } + + [Symbol.for("Deno.customInspect")](inspect: (value: unknown) => string) { + const { body, headers, status, type, writable } = this; + return `${this.constructor.name} ${ + inspect({ body, headers, status, type, writable }) + }`; + } + + [Symbol.for("nodejs.util.inspect.custom")]( + depth: number, + // deno-lint-ignore no-explicit-any + options: any, + inspect: (value: unknown, options?: unknown) => string, + ) { + if (depth < 0) { + return options.stylize(`[${this.constructor.name}]`, "special"); + } + + const newOptions = Object.assign({}, options, { + depth: options.depth === null ? null : options.depth - 1, + }); + const { body, headers, status, type, writable } = this; + return `${options.stylize(this.constructor.name, "special")} ${ + inspect( + { body, headers, status, type, writable }, + newOptions, + ) + }`; + } +} diff --git a/vendor/deno.land/x/oak@v12.1.0/router.ts b/vendor/deno.land/x/oak@v12.1.0/router.ts new file mode 100644 index 0000000..e5b2456 --- /dev/null +++ b/vendor/deno.land/x/oak@v12.1.0/router.ts @@ -0,0 +1,1374 @@ +/** + * Adapted directly from @koa/router at + * https://github.com/koajs/router/ which is licensed as: + * + * The MIT License (MIT) + * + * Copyright (c) 2015 Alexander C. Mingoia + * + * 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. + */ + +import type { State } from "./application.ts"; +import type { Context } from "./context.ts"; +import { + compile, + errors, + Key, + ParseOptions, + pathParse, + pathToRegexp, + Status, + TokensToRegexpOptions, +} from "./deps.ts"; +import { compose, Middleware } from "./middleware.ts"; +import type { HTTPMethods, RedirectStatus } from "./types.d.ts"; +import { assert, decodeComponent } from "./util.ts"; + +interface Matches { + path: Layer[]; + pathAndMethod: Layer[]; + route: boolean; + name?: string; +} + +export interface RouterAllowedMethodsOptions { + /** Use the value returned from this function instead of an HTTP error + * `MethodNotAllowed`. */ + // deno-lint-ignore no-explicit-any + methodNotAllowed?(): any; + + /** Use the value returned from this function instead of an HTTP error + * `NotImplemented`. */ + // deno-lint-ignore no-explicit-any + notImplemented?(): any; + + /** When dealing with a non-implemented method or a method not allowed, throw + * an error instead of setting the status and header for the response. */ + throw?: boolean; +} + +export interface Route< + R extends string, + P extends RouteParams = RouteParams, + // deno-lint-ignore no-explicit-any + S extends State = Record, +> { + /** The HTTP methods that this route handles. */ + methods: HTTPMethods[]; + + /** The middleware that will be applied to this route. */ + middleware: RouterMiddleware[]; + + /** An optional name for the route. */ + name?: string; + + /** Options that were used to create the route. */ + options: LayerOptions; + + /** The parameters that are identified in the route that will be parsed out + * on matched requests. */ + paramNames: (keyof P)[]; + + /** The path that this route manages. */ + path: string; + + /** The regular expression used for matching and parsing parameters for the + * route. */ + regexp: RegExp; +} + +/** The context passed router middleware. */ +export interface RouterContext< + R extends string, + P extends RouteParams = RouteParams, + // deno-lint-ignore no-explicit-any + S extends State = Record, +> extends Context { + /** When matching the route, an array of the capturing groups from the regular + * expression. */ + captures: string[]; + + /** The routes that were matched for this request. */ + matched?: Layer[]; + + /** Any parameters parsed from the route when matched. */ + params: P; + + /** A reference to the router instance. */ + router: Router; + + /** If the matched route has a `name`, the matched route name is provided + * here. */ + routeName?: string; + + /** Overrides the matched path for future route middleware, when a + * `routerPath` option is not defined on the `Router` options. */ + routerPath?: string; +} + +export interface RouterMiddleware< + R extends string, + P extends RouteParams = RouteParams, + // deno-lint-ignore no-explicit-any + S extends State = Record, +> { + (context: RouterContext, next: () => Promise): + | Promise + | unknown; + /** For route parameter middleware, the `param` key for this parameter will + * be set. */ + param?: keyof P; + // deno-lint-ignore no-explicit-any + router?: Router; +} + +export interface RouterOptions { + /** Override the default set of methods supported by the router. */ + methods?: HTTPMethods[]; + + /** Only handle routes where the requested path starts with the prefix. */ + prefix?: string; + + /** Override the `request.url.pathname` when matching middleware to run. */ + routerPath?: string; + + /** Determines if routes are matched in a case sensitive way. Defaults to + * `false`. */ + sensitive?: boolean; + + /** Determines if routes are matched strictly, where the trailing `/` is not + * optional. Defaults to `false`. */ + strict?: boolean; +} + +/** Middleware that will be called by the router when handling a specific + * parameter, which the middleware will be called when a request matches the + * route parameter. */ +export interface RouterParamMiddleware< + R extends string, + P extends RouteParams = RouteParams, + // deno-lint-ignore no-explicit-any + S extends State = Record, +> { + ( + param: string, + context: RouterContext, + next: () => Promise, + ): Promise | unknown; + // deno-lint-ignore no-explicit-any + router?: Router; +} + +interface ParamsDictionary { + [key: string]: string; +} + +type RemoveTail = S extends + `${infer P}${Tail}` ? P : S; + +type GetRouteParams = RemoveTail< + RemoveTail, `-${string}`>, + `.${string}` +>; + +export type RouteParams = string extends Route + ? ParamsDictionary + : Route extends `${string}(${string}` ? ParamsDictionary + : Route extends `${string}:${infer Rest}` ? + & ( + GetRouteParams extends never ? ParamsDictionary + : GetRouteParams extends `${infer ParamName}?` + ? { [P in ParamName]?: string } + : { [P in GetRouteParams]: string } + ) + & (Rest extends `${GetRouteParams}${infer Next}` ? RouteParams + : unknown) + : Record; + +type LayerOptions = TokensToRegexpOptions & ParseOptions & { + ignoreCaptures?: boolean; + name?: string; +}; + +type RegisterOptions = LayerOptions & { + ignorePrefix?: boolean; +}; + +type UrlOptions = TokensToRegexpOptions & ParseOptions & { + /** When generating a URL from a route, add the query to the URL. If an + * object */ + query?: URLSearchParams | Record | string; +}; + +/** Generate a URL from a string, potentially replace route params with + * values. */ +function toUrl( + url: string, + params = {} as RouteParams, + options?: UrlOptions, +) { + const tokens = pathParse(url); + let replace = {} as RouteParams; + + if (tokens.some((token) => typeof token === "object")) { + replace = params; + } else { + options = params; + } + + const toPath = compile(url, options); + const replaced = toPath(replace); + + if (options && options.query) { + const url = new URL(replaced, "http://oak"); + if (typeof options.query === "string") { + url.search = options.query; + } else { + url.search = String( + options.query instanceof URLSearchParams + ? options.query + : new URLSearchParams(options.query), + ); + } + return `${url.pathname}${url.search}${url.hash}`; + } + return replaced; +} + +class Layer< + R extends string, + P extends RouteParams = RouteParams, + // deno-lint-ignore no-explicit-any + S extends State = Record, +> { + #opts: LayerOptions; + #paramNames: Key[] = []; + #regexp: RegExp; + + methods: HTTPMethods[]; + name?: string; + path: string; + stack: RouterMiddleware[]; + + constructor( + path: string, + methods: HTTPMethods[], + middleware: RouterMiddleware | RouterMiddleware[], + { name, ...opts }: LayerOptions = {}, + ) { + this.#opts = opts; + this.name = name; + this.methods = [...methods]; + if (this.methods.includes("GET")) { + this.methods.unshift("HEAD"); + } + this.stack = Array.isArray(middleware) ? middleware.slice() : [middleware]; + this.path = path; + this.#regexp = pathToRegexp(path, this.#paramNames, this.#opts); + } + + clone(): Layer { + return new Layer( + this.path, + this.methods, + this.stack, + { name: this.name, ...this.#opts }, + ); + } + + match(path: string): boolean { + return this.#regexp.test(path); + } + + params( + captures: string[], + existingParams = {} as RouteParams, + ): RouteParams { + const params = existingParams; + for (let i = 0; i < captures.length; i++) { + if (this.#paramNames[i]) { + const c = captures[i]; + params[this.#paramNames[i].name] = c ? decodeComponent(c) : c; + } + } + return params; + } + + captures(path: string): string[] { + if (this.#opts.ignoreCaptures) { + return []; + } + return path.match(this.#regexp)?.slice(1) ?? []; + } + + url( + params = {} as RouteParams, + options?: UrlOptions, + ): string { + const url = this.path.replace(/\(\.\*\)/g, ""); + return toUrl(url, params, options); + } + + param( + param: string, + // deno-lint-ignore no-explicit-any + fn: RouterParamMiddleware, + ) { + const stack = this.stack; + const params = this.#paramNames; + const middleware: RouterMiddleware = function ( + this: Router, + ctx, + next, + ): Promise | unknown { + const p = ctx.params[param]; + assert(p); + return fn.call(this, p, ctx, next); + }; + middleware.param = param; + + const names = params.map((p) => p.name); + + const x = names.indexOf(param); + if (x >= 0) { + for (let i = 0; i < stack.length; i++) { + const fn = stack[i]; + if (!fn.param || names.indexOf(fn.param as (string | number)) > x) { + stack.splice(i, 0, middleware); + break; + } + } + } + return this; + } + + setPrefix(prefix: string): this { + if (this.path) { + this.path = this.path !== "/" || this.#opts.strict === true + ? `${prefix}${this.path}` + : prefix; + this.#paramNames = []; + this.#regexp = pathToRegexp(this.path, this.#paramNames, this.#opts); + } + return this; + } + + // deno-lint-ignore no-explicit-any + toJSON(): Route { + return { + methods: [...this.methods], + middleware: [...this.stack], + paramNames: this.#paramNames.map((key) => key.name), + path: this.path, + regexp: this.#regexp, + options: { ...this.#opts }, + }; + } + + [Symbol.for("Deno.customInspect")](inspect: (value: unknown) => string) { + return `${this.constructor.name} ${ + inspect({ + methods: this.methods, + middleware: this.stack, + options: this.#opts, + paramNames: this.#paramNames.map((key) => key.name), + path: this.path, + regexp: this.#regexp, + }) + }`; + } + + [Symbol.for("nodejs.util.inspect.custom")]( + depth: number, + // deno-lint-ignore no-explicit-any + options: any, + inspect: (value: unknown, options?: unknown) => string, + ) { + if (depth < 0) { + return options.stylize(`[${this.constructor.name}]`, "special"); + } + + const newOptions = Object.assign({}, options, { + depth: options.depth === null ? null : options.depth - 1, + }); + return `${options.stylize(this.constructor.name, "special")} ${ + inspect( + { + methods: this.methods, + middleware: this.stack, + options: this.#opts, + paramNames: this.#paramNames.map((key) => key.name), + path: this.path, + regexp: this.#regexp, + }, + newOptions, + ) + }`; + } +} + +/** An interface for registering middleware that will run when certain HTTP + * methods and paths are requested, as well as provides a way to parameterize + * parts of the requested path. + * + * ### Basic example + * + * ```ts + * import { Application, Router } from "https://deno.land/x/oak/mod.ts"; + * + * const router = new Router(); + * router.get("/", (ctx, next) => { + * // handle the GET endpoint here + * }); + * router.all("/item/:item", (ctx, next) => { + * // called for all HTTP verbs/requests + * ctx.params.item; // contains the value of `:item` from the parsed URL + * }); + * + * const app = new Application(); + * app.use(router.routes()); + * app.use(router.allowedMethods()); + * + * app.listen({ port: 8080 }); + * ``` + */ +export class Router< + // deno-lint-ignore no-explicit-any + RS extends State = Record, +> { + #opts: RouterOptions; + #methods: HTTPMethods[]; + // deno-lint-ignore no-explicit-any + #params: Record> = {}; + #stack: Layer[] = []; + + #match(path: string, method: HTTPMethods): Matches { + const matches: Matches = { + path: [], + pathAndMethod: [], + route: false, + }; + + for (const route of this.#stack) { + if (route.match(path)) { + matches.path.push(route); + if (route.methods.length === 0 || route.methods.includes(method)) { + matches.pathAndMethod.push(route); + if (route.methods.length) { + matches.route = true; + matches.name = route.name; + } + } + } + } + + return matches; + } + + #register( + path: string | string[], + middlewares: RouterMiddleware[], + methods: HTTPMethods[], + options: RegisterOptions = {}, + ): void { + if (Array.isArray(path)) { + for (const p of path) { + this.#register(p, middlewares, methods, options); + } + return; + } + + let layerMiddlewares: RouterMiddleware[] = []; + for (const middleware of middlewares) { + if (!middleware.router) { + layerMiddlewares.push(middleware); + continue; + } + + if (layerMiddlewares.length) { + this.#addLayer(path, layerMiddlewares, methods, options); + layerMiddlewares = []; + } + + const router = middleware.router.#clone(); + + for (const layer of router.#stack) { + if (!options.ignorePrefix) { + layer.setPrefix(path); + } + if (this.#opts.prefix) { + layer.setPrefix(this.#opts.prefix); + } + this.#stack.push(layer); + } + + for (const [param, mw] of Object.entries(this.#params)) { + router.param(param, mw); + } + } + + if (layerMiddlewares.length) { + this.#addLayer(path, layerMiddlewares, methods, options); + } + } + + #addLayer( + path: string, + middlewares: RouterMiddleware[], + methods: HTTPMethods[], + options: LayerOptions = {}, + ) { + const { + end, + name, + sensitive = this.#opts.sensitive, + strict = this.#opts.strict, + ignoreCaptures, + } = options; + const route = new Layer(path, methods, middlewares, { + end, + name, + sensitive, + strict, + ignoreCaptures, + }); + + if (this.#opts.prefix) { + route.setPrefix(this.#opts.prefix); + } + + for (const [param, mw] of Object.entries(this.#params)) { + route.param(param, mw); + } + + this.#stack.push(route); + } + + #route(name: string): Layer | undefined { + for (const route of this.#stack) { + if (route.name === name) { + return route; + } + } + } + + #useVerb( + nameOrPath: string, + pathOrMiddleware: string | RouterMiddleware, + middleware: RouterMiddleware[], + methods: HTTPMethods[], + ): void { + let name: string | undefined = undefined; + let path: string; + if (typeof pathOrMiddleware === "string") { + name = nameOrPath; + path = pathOrMiddleware; + } else { + path = nameOrPath; + middleware.unshift(pathOrMiddleware); + } + + this.#register(path, middleware, methods, { name }); + } + + #clone(): Router { + const router = new Router(this.#opts); + router.#methods = router.#methods.slice(); + router.#params = { ...this.#params }; + router.#stack = this.#stack.map((layer) => layer.clone()); + return router; + } + + constructor(opts: RouterOptions = {}) { + this.#opts = opts; + this.#methods = opts.methods ?? [ + "DELETE", + "GET", + "HEAD", + "OPTIONS", + "PATCH", + "POST", + "PUT", + ]; + } + + /** Register named middleware for the specified routes when the `DELETE`, + * `GET`, `POST`, or `PUT` method is requested. */ + all< + R extends string, + P extends RouteParams = RouteParams, + S extends State = RS, + >( + name: string, + path: R, + middleware: RouterMiddleware, + ...middlewares: RouterMiddleware[] + ): Router; + /** Register middleware for the specified routes when the `DELETE`, + * `GET`, `POST`, or `PUT` method is requested. */ + all< + R extends string, + P extends RouteParams = RouteParams, + S extends State = RS, + >( + path: R, + middleware: RouterMiddleware, + ...middlewares: RouterMiddleware[] + ): Router; + /** Register middleware for the specified routes when the `DELETE`, + * `GET`, `POST`, or `PUT` method is requested with explicit path parameters. + */ + all< + P extends RouteParams, + S extends State = RS, + >( + nameOrPath: string, + pathOrMiddleware: string | RouterMiddleware, + ...middleware: RouterMiddleware[] + ): Router; + all< + P extends RouteParams = RouteParams, + S extends State = RS, + >( + nameOrPath: string, + pathOrMiddleware: string | RouterMiddleware, + ...middleware: RouterMiddleware[] + ): Router { + this.#useVerb( + nameOrPath, + pathOrMiddleware as (string | RouterMiddleware), + middleware as RouterMiddleware[], + this.#methods.filter((method) => method !== "OPTIONS"), + ); + return this; + } + + /** Middleware that handles requests for HTTP methods registered with the + * router. If none of the routes handle a method, then "not allowed" logic + * will be used. If a method is supported by some routes, but not the + * particular matched router, then "not implemented" will be returned. + * + * The middleware will also automatically handle the `OPTIONS` method, + * responding with a `200 OK` when the `Allowed` header sent to the allowed + * methods for a given route. + * + * By default, a "not allowed" request will respond with a `405 Not Allowed` + * and a "not implemented" will respond with a `501 Not Implemented`. Setting + * the option `.throw` to `true` will cause the middleware to throw an + * `HTTPError` instead of setting the response status. The error can be + * overridden by providing a `.notImplemented` or `.notAllowed` method in the + * options, of which the value will be returned will be thrown instead of the + * HTTP error. */ + allowedMethods( + options: RouterAllowedMethodsOptions = {}, + ): Middleware { + const implemented = this.#methods; + + const allowedMethods: Middleware = async (context, next) => { + const ctx = context as RouterContext; + await next(); + if (!ctx.response.status || ctx.response.status === Status.NotFound) { + assert(ctx.matched); + const allowed = new Set(); + for (const route of ctx.matched) { + for (const method of route.methods) { + allowed.add(method); + } + } + + const allowedStr = [...allowed].join(", "); + if (!implemented.includes(ctx.request.method)) { + if (options.throw) { + throw options.notImplemented + ? options.notImplemented() + : new errors.NotImplemented(); + } else { + ctx.response.status = Status.NotImplemented; + ctx.response.headers.set("Allow", allowedStr); + } + } else if (allowed.size) { + if (ctx.request.method === "OPTIONS") { + ctx.response.status = Status.OK; + ctx.response.headers.set("Allow", allowedStr); + } else if (!allowed.has(ctx.request.method)) { + if (options.throw) { + throw options.methodNotAllowed + ? options.methodNotAllowed() + : new errors.MethodNotAllowed(); + } else { + ctx.response.status = Status.MethodNotAllowed; + ctx.response.headers.set("Allow", allowedStr); + } + } + } + } + }; + + return allowedMethods; + } + + /** Register named middleware for the specified routes when the `DELETE`, + * method is requested. */ + delete< + R extends string, + P extends RouteParams = RouteParams, + S extends State = RS, + >( + name: string, + path: R, + middleware: RouterMiddleware, + ...middlewares: RouterMiddleware[] + ): Router; + /** Register middleware for the specified routes when the `DELETE`, + * method is requested. */ + delete< + R extends string, + P extends RouteParams = RouteParams, + S extends State = RS, + >( + path: R, + middleware: RouterMiddleware, + ...middlewares: RouterMiddleware[] + ): Router; + /** Register middleware for the specified routes when the `DELETE`, + * method is requested with explicit path parameters. */ + delete< + P extends RouteParams, + S extends State = RS, + >( + nameOrPath: string, + pathOrMiddleware: string | RouterMiddleware, + ...middleware: RouterMiddleware[] + ): Router; + delete< + P extends RouteParams = RouteParams, + S extends State = RS, + >( + nameOrPath: string, + pathOrMiddleware: string | RouterMiddleware, + ...middleware: RouterMiddleware[] + ): Router { + this.#useVerb( + nameOrPath, + pathOrMiddleware as (string | RouterMiddleware), + middleware as RouterMiddleware[], + ["DELETE"], + ); + return this; + } + + /** Iterate over the routes currently added to the router. To be compatible + * with the iterable interfaces, both the key and value are set to the value + * of the route. */ + *entries(): IterableIterator<[Route, Route]> { + for (const route of this.#stack) { + const value = route.toJSON(); + yield [value, value]; + } + } + + /** Iterate over the routes currently added to the router, calling the + * `callback` function for each value. */ + forEach( + callback: ( + value1: Route, + value2: Route, + router: this, + ) => void, + // deno-lint-ignore no-explicit-any + thisArg: any = null, + ): void { + for (const route of this.#stack) { + const value = route.toJSON(); + callback.call(thisArg, value, value, this); + } + } + + /** Register named middleware for the specified routes when the `GET`, + * method is requested. */ + get< + R extends string, + P extends RouteParams = RouteParams, + S extends State = RS, + >( + name: string, + path: R, + middleware: RouterMiddleware, + ...middlewares: RouterMiddleware[] + ): Router; + /** Register middleware for the specified routes when the `GET`, + * method is requested. */ + get< + R extends string, + P extends RouteParams = RouteParams, + S extends State = RS, + >( + path: R, + middleware: RouterMiddleware, + ...middlewares: RouterMiddleware[] + ): Router; + /** Register middleware for the specified routes when the `GET`, + * method is requested with explicit path parameters. */ + get< + P extends RouteParams, + S extends State = RS, + >( + nameOrPath: string, + pathOrMiddleware: string | RouterMiddleware, + ...middleware: RouterMiddleware[] + ): Router; + get< + P extends RouteParams = RouteParams, + S extends State = RS, + >( + nameOrPath: string, + pathOrMiddleware: string | RouterMiddleware, + ...middleware: RouterMiddleware[] + ): Router { + this.#useVerb( + nameOrPath, + pathOrMiddleware as (string | RouterMiddleware), + middleware as RouterMiddleware[], + ["GET"], + ); + return this; + } + + /** Register named middleware for the specified routes when the `HEAD`, + * method is requested. */ + head< + R extends string, + P extends RouteParams = RouteParams, + S extends State = RS, + >( + name: string, + path: R, + middleware: RouterMiddleware, + ...middlewares: RouterMiddleware[] + ): Router; + /** Register middleware for the specified routes when the `HEAD`, + * method is requested. */ + head< + R extends string, + P extends RouteParams = RouteParams, + S extends State = RS, + >( + path: R, + middleware: RouterMiddleware, + ...middlewares: RouterMiddleware[] + ): Router; + /** Register middleware for the specified routes when the `HEAD`, + * method is requested with explicit path parameters. */ + head< + P extends RouteParams, + S extends State = RS, + >( + nameOrPath: string, + pathOrMiddleware: string | RouterMiddleware, + ...middleware: RouterMiddleware[] + ): Router; + head< + P extends RouteParams = RouteParams, + S extends State = RS, + >( + nameOrPath: string, + pathOrMiddleware: string | RouterMiddleware, + ...middleware: RouterMiddleware[] + ): Router { + this.#useVerb( + nameOrPath, + pathOrMiddleware as (string | RouterMiddleware), + middleware as RouterMiddleware[], + ["HEAD"], + ); + return this; + } + + /** Iterate over the routes currently added to the router. To be compatible + * with the iterable interfaces, the key is set to the value of the route. */ + *keys(): IterableIterator> { + for (const route of this.#stack) { + yield route.toJSON(); + } + } + + /** Register named middleware for the specified routes when the `OPTIONS`, + * method is requested. */ + options< + R extends string, + P extends RouteParams = RouteParams, + S extends State = RS, + >( + name: string, + path: R, + middleware: RouterMiddleware, + ...middlewares: RouterMiddleware[] + ): Router; + /** Register middleware for the specified routes when the `OPTIONS`, + * method is requested. */ + options< + R extends string, + P extends RouteParams = RouteParams, + S extends State = RS, + >( + path: R, + middleware: RouterMiddleware, + ...middlewares: RouterMiddleware[] + ): Router; + /** Register middleware for the specified routes when the `OPTIONS`, + * method is requested with explicit path parameters. */ + options< + P extends RouteParams, + S extends State = RS, + >( + nameOrPath: string, + pathOrMiddleware: string | RouterMiddleware, + ...middleware: RouterMiddleware[] + ): Router; + options< + P extends RouteParams = RouteParams, + S extends State = RS, + >( + nameOrPath: string, + pathOrMiddleware: string | RouterMiddleware, + ...middleware: RouterMiddleware[] + ): Router { + this.#useVerb( + nameOrPath, + pathOrMiddleware as (string | RouterMiddleware), + middleware as RouterMiddleware[], + ["OPTIONS"], + ); + return this; + } + + /** Register param middleware, which will be called when the particular param + * is parsed from the route. */ + param( + param: keyof RouteParams, + middleware: RouterParamMiddleware, S>, + ): Router { + this.#params[param as string] = middleware; + for (const route of this.#stack) { + route.param(param as string, middleware); + } + return this; + } + + /** Register named middleware for the specified routes when the `PATCH`, + * method is requested. */ + patch< + R extends string, + P extends RouteParams = RouteParams, + S extends State = RS, + >( + name: string, + path: R, + middleware: RouterMiddleware, + ...middlewares: RouterMiddleware[] + ): Router; + /** Register middleware for the specified routes when the `PATCH`, + * method is requested. */ + patch< + R extends string, + P extends RouteParams = RouteParams, + S extends State = RS, + >( + path: R, + middleware: RouterMiddleware, + ...middlewares: RouterMiddleware[] + ): Router; + /** Register middleware for the specified routes when the `PATCH`, + * method is requested with explicit path parameters. */ + patch< + P extends RouteParams, + S extends State = RS, + >( + nameOrPath: string, + pathOrMiddleware: string | RouterMiddleware, + ...middleware: RouterMiddleware[] + ): Router; + patch< + P extends RouteParams = RouteParams, + S extends State = RS, + >( + nameOrPath: string, + pathOrMiddleware: string | RouterMiddleware, + ...middleware: RouterMiddleware[] + ): Router { + this.#useVerb( + nameOrPath, + pathOrMiddleware as (string | RouterMiddleware), + middleware as RouterMiddleware[], + ["PATCH"], + ); + return this; + } + + /** Register named middleware for the specified routes when the `POST`, + * method is requested. */ + post< + R extends string, + P extends RouteParams = RouteParams, + S extends State = RS, + >( + name: string, + path: R, + middleware: RouterMiddleware, + ...middlewares: RouterMiddleware[] + ): Router; + /** Register middleware for the specified routes when the `POST`, + * method is requested. */ + post< + R extends string, + P extends RouteParams = RouteParams, + S extends State = RS, + >( + path: R, + middleware: RouterMiddleware, + ...middlewares: RouterMiddleware[] + ): Router; + /** Register middleware for the specified routes when the `POST`, + * method is requested with explicit path parameters. */ + post< + P extends RouteParams, + S extends State = RS, + >( + nameOrPath: string, + pathOrMiddleware: string | RouterMiddleware, + ...middleware: RouterMiddleware[] + ): Router; + post< + P extends RouteParams = RouteParams, + S extends State = RS, + >( + nameOrPath: string, + pathOrMiddleware: string | RouterMiddleware, + ...middleware: RouterMiddleware[] + ): Router { + this.#useVerb( + nameOrPath, + pathOrMiddleware as (string | RouterMiddleware), + middleware as RouterMiddleware[], + ["POST"], + ); + return this; + } + + /** Set the router prefix for this router. */ + prefix(prefix: string): this { + prefix = prefix.replace(/\/$/, ""); + this.#opts.prefix = prefix; + for (const route of this.#stack) { + route.setPrefix(prefix); + } + return this; + } + + /** Register named middleware for the specified routes when the `PUT` + * method is requested. */ + put< + R extends string, + P extends RouteParams = RouteParams, + S extends State = RS, + >( + name: string, + path: R, + middleware: RouterMiddleware, + ...middlewares: RouterMiddleware[] + ): Router; + /** Register middleware for the specified routes when the `PUT` + * method is requested. */ + put< + R extends string, + P extends RouteParams = RouteParams, + S extends State = RS, + >( + path: R, + middleware: RouterMiddleware, + ...middlewares: RouterMiddleware[] + ): Router; + /** Register middleware for the specified routes when the `PUT` + * method is requested with explicit path parameters. */ + put< + P extends RouteParams, + S extends State = RS, + >( + nameOrPath: string, + pathOrMiddleware: string | RouterMiddleware, + ...middleware: RouterMiddleware[] + ): Router; + put< + P extends RouteParams = RouteParams, + S extends State = RS, + >( + nameOrPath: string, + pathOrMiddleware: string | RouterMiddleware, + ...middleware: RouterMiddleware[] + ): Router { + this.#useVerb( + nameOrPath, + pathOrMiddleware as (string | RouterMiddleware), + middleware as RouterMiddleware[], + ["PUT"], + ); + return this; + } + + /** Register a direction middleware, where when the `source` path is matched + * the router will redirect the request to the `destination` path. A `status` + * of `302 Found` will be set by default. + * + * The `source` and `destination` can be named routes. */ + redirect( + source: string, + destination: string | URL, + status: RedirectStatus = Status.Found, + ): this { + if (source[0] !== "/") { + const s = this.url(source); + if (!s) { + throw new RangeError(`Could not resolve named route: "${source}"`); + } + source = s; + } + if (typeof destination === "string") { + if (destination[0] !== "/") { + const d = this.url(destination); + if (!d) { + try { + const url = new URL(destination); + destination = url; + } catch { + throw new RangeError(`Could not resolve named route: "${source}"`); + } + } else { + destination = d; + } + } + } + + this.all(source, async (ctx, next) => { + await next(); + ctx.response.redirect(destination); + ctx.response.status = status; + }); + return this; + } + + /** Return middleware that will do all the route processing that the router + * has been configured to handle. Typical usage would be something like this: + * + * ```ts + * import { Application, Router } from "https://deno.land/x/oak/mod.ts"; + * + * const app = new Application(); + * const router = new Router(); + * + * // register routes + * + * app.use(router.routes()); + * app.use(router.allowedMethods()); + * await app.listen({ port: 80 }); + * ``` + */ + routes(): Middleware { + const dispatch = ( + context: Context, + next: () => Promise, + ): Promise => { + const ctx = context as RouterContext; + let pathname: string; + let method: HTTPMethods; + try { + const { url: { pathname: p }, method: m } = ctx.request; + pathname = p; + method = m; + } catch (e) { + return Promise.reject(e); + } + const path = this.#opts.routerPath ?? ctx.routerPath ?? + decodeURI(pathname); + const matches = this.#match(path, method); + + if (ctx.matched) { + ctx.matched.push(...matches.path); + } else { + ctx.matched = [...matches.path]; + } + + // deno-lint-ignore no-explicit-any + ctx.router = this as Router; + + if (!matches.route) return next(); + + ctx.routeName = matches.name; + + const { pathAndMethod: matchedRoutes } = matches; + + const chain = matchedRoutes.reduce( + (prev, route) => [ + ...prev, + (ctx, next) => { + ctx.captures = route.captures(path); + ctx.params = route.params(ctx.captures, ctx.params); + return next(); + }, + ...route.stack, + ], + [] as RouterMiddleware[], + ); + return compose(chain)(ctx, next); + }; + dispatch.router = this; + return dispatch; + } + + /** Generate a URL pathname for a named route, interpolating the optional + * params provided. Also accepts an optional set of options. */ + url

= RouteParams>( + name: string, + params?: P, + options?: UrlOptions, + ): string | undefined { + const route = this.#route(name); + + if (route) { + return route.url(params, options); + } + } + + /** Register middleware to be used on every matched route. */ + use< + P extends RouteParams = RouteParams, + S extends State = RS, + >( + middleware: RouterMiddleware, + ...middlewares: RouterMiddleware[] + ): Router; + /** Register middleware to be used on every route that matches the supplied + * `path`. */ + use< + R extends string, + P extends RouteParams = RouteParams, + S extends State = RS, + >( + path: R, + middleware: RouterMiddleware, + ...middlewares: RouterMiddleware[] + ): Router; + /** Register middleware to be used on every route that matches the supplied + * `path` with explicit path parameters. */ + use< + P extends RouteParams, + S extends State = RS, + >( + path: string, + middleware: RouterMiddleware, + ...middlewares: RouterMiddleware[] + ): Router; + use< + P extends RouteParams = RouteParams, + S extends State = RS, + >( + path: string[], + middleware: RouterMiddleware, + ...middlewares: RouterMiddleware[] + ): Router; + use< + P extends RouteParams = RouteParams, + S extends State = RS, + >( + pathOrMiddleware: string | string[] | RouterMiddleware, + ...middleware: RouterMiddleware[] + ): Router { + let path: string | string[] | undefined; + if ( + typeof pathOrMiddleware === "string" || Array.isArray(pathOrMiddleware) + ) { + path = pathOrMiddleware; + } else { + middleware.unshift(pathOrMiddleware); + } + + this.#register( + path ?? "(.*)", + middleware as RouterMiddleware[], + [], + { end: false, ignoreCaptures: !path, ignorePrefix: !path }, + ); + + return this; + } + + /** Iterate over the routes currently added to the router. */ + *values(): IterableIterator, RS>> { + for (const route of this.#stack) { + yield route.toJSON(); + } + } + + /** Provide an iterator interface that iterates over the routes registered + * with the router. */ + *[Symbol.iterator](): IterableIterator< + Route, RS> + > { + for (const route of this.#stack) { + yield route.toJSON(); + } + } + + /** Generate a URL pathname based on the provided path, interpolating the + * optional params provided. Also accepts an optional set of options. */ + static url( + path: R, + params?: RouteParams, + options?: UrlOptions, + ): string { + return toUrl(path, params, options); + } + + [Symbol.for("Deno.customInspect")](inspect: (value: unknown) => string) { + return `${this.constructor.name} ${ + inspect({ "#params": this.#params, "#stack": this.#stack }) + }`; + } + + [Symbol.for("nodejs.util.inspect.custom")]( + depth: number, + // deno-lint-ignore no-explicit-any + options: any, + inspect: (value: unknown, options?: unknown) => string, + ) { + if (depth < 0) { + return options.stylize(`[${this.constructor.name}]`, "special"); + } + + const newOptions = Object.assign({}, options, { + depth: options.depth === null ? null : options.depth - 1, + }); + return `${options.stylize(this.constructor.name, "special")} ${ + inspect( + { "#params": this.#params, "#stack": this.#stack }, + newOptions, + ) + }`; + } +} diff --git a/vendor/deno.land/x/oak@v12.1.0/send.ts b/vendor/deno.land/x/oak@v12.1.0/send.ts new file mode 100644 index 0000000..d794b15 --- /dev/null +++ b/vendor/deno.land/x/oak@v12.1.0/send.ts @@ -0,0 +1,361 @@ +/*! + * Adapted from koa-send at https://github.com/koajs/send and which is licensed + * with the MIT license. + */ + +import type { Context } from "./context.ts"; +import { calculate, FileInfo, ifNoneMatch } from "./etag.ts"; +import { + basename, + createHttpError, + extname, + LimitedReader, + parse, + readAll, + Status, +} from "./deps.ts"; +import { ifRange, MultiPartStream, parseRange } from "./range.ts"; +import type { Response } from "./response.ts"; +import { assert, decodeComponent, getBoundary, resolvePath } from "./util.ts"; + +const MAXBUFFER_DEFAULT = 1_048_576; // 1MiB; + +// this will be lazily set as it needs to be done asynchronously and we want to +// avoid top level await +let boundary: string | undefined; + +export interface SendOptions { + /** Try to serve the brotli version of a file automatically when brotli is + * supported by a client and if the requested file with `.br` extension + * exists. (defaults to `true`) */ + brotli?: boolean; + + /** A record of extensions and content types that should be used when + * determining the content of a file being served. By default, the + * [`media_type`](https://github.com/oakserver/media_types/) database is used + * to map an extension to the served content-type. The keys of the map are + * extensions, and values are the content types to use. The content type can + * be a partial content type, which will be resolved to a full content type + * header. + * + * Any extensions matched will override the default behavior. Key should + * include the leading dot (e.g. `.ext` instead of just `ext`). + * + * ### Example + * + * ```ts + * app.use((ctx) => { + * return send(ctx, ctx.request.url.pathname, { + * contentTypes: { + * ".importmap": "application/importmap+json" + * }, + * root: ".", + * }) + * }); + * ``` + */ + contentTypes?: Record; + + /** Try to match extensions from passed array to search for file when no + * extension is sufficed in URL. First found is served. (defaults to + * `undefined`) */ + extensions?: string[]; + + /** If `true`, format the path to serve static file servers and not require a + * trailing slash for directories, so that you can do both `/directory` and + * `/directory/`. (defaults to `true`) */ + format?: boolean; + + /** Try to serve the gzipped version of a file automatically when gzip is + * supported by a client and if the requested file with `.gz` extension + * exists. (defaults to `true`). */ + gzip?: boolean; + + /** Allow transfer of hidden files. (defaults to `false`) */ + hidden?: boolean; + + /** Tell the browser the resource is immutable and can be cached + * indefinitely. (defaults to `false`) */ + immutable?: boolean; + + /** Name of the index file to serve automatically when visiting the root + * location. (defaults to none) */ + index?: string; + + /** Browser cache max-age in milliseconds. (defaults to `0`) */ + maxage?: number; + + /** A size in bytes where if the file is less than this size, the file will + * be read into memory by send instead of returning a file handle. Files less + * than the byte size will send an "strong" `ETag` header while those larger + * than the bytes size will only be able to send a "weak" `ETag` header (as + * they cannot hash the contents of the file). (defaults to 1MiB) + */ + maxbuffer?: number; + + /** Root directory to restrict file access. */ + root: string; +} + +function isHidden(path: string) { + const pathArr = path.split("/"); + for (const segment of pathArr) { + if (segment[0] === "." && segment !== "." && segment !== "..") { + return true; + } + return false; + } +} + +async function exists(path: string): Promise { + try { + return (await Deno.stat(path)).isFile; + } catch { + return false; + } +} + +async function getEntity( + path: string, + mtime: number, + stats: Deno.FileInfo, + maxbuffer: number, + response: Response, +): Promise<[Uint8Array | Deno.FsFile, Uint8Array | FileInfo]> { + let body: Uint8Array | Deno.FsFile; + let entity: Uint8Array | FileInfo; + const file = await Deno.open(path, { read: true }); + if (stats.size < maxbuffer) { + const buffer = await readAll(file); + file.close(); + body = entity = buffer; + } else { + response.addResource(file.rid); + body = file; + entity = { + mtime: new Date(mtime!), + size: stats.size, + }; + } + return [body, entity]; +} + +async function sendRange( + response: Response, + body: Uint8Array | Deno.FsFile, + range: string, + size: number, +) { + const ranges = parseRange(range, size); + if (ranges.length === 0) { + throw createHttpError(Status.RequestedRangeNotSatisfiable); + } + response.status = Status.PartialContent; + if (ranges.length === 1) { + const [byteRange] = ranges; + response.headers.set( + "Content-Range", + `bytes ${byteRange.start}-${byteRange.end}/${size}`, + ); + if (body instanceof Uint8Array) { + response.body = body.slice(byteRange.start, byteRange.end + 1); + } else { + await body.seek(byteRange.start, Deno.SeekMode.Start); + response.body = new LimitedReader( + body, + byteRange.end - byteRange.start + 1, + ); + } + } else { + assert(response.type); + if (!boundary) { + boundary = await getBoundary(); + } + response.headers.set( + "content-type", + `multipart/byteranges; boundary=${boundary}`, + ); + const multipartBody = new MultiPartStream( + body, + response.type, + ranges, + size, + boundary, + ); + response.body = multipartBody; + } +} + +/** Asynchronously fulfill a response with a file from the local file + * system. + * + * Requires Deno read permission for the `root` directory. */ +export async function send( + // deno-lint-ignore no-explicit-any + { request, response }: Context, + path: string, + options: SendOptions = { root: "" }, +): Promise { + const { + brotli = true, + contentTypes = {}, + extensions, + format = true, + gzip = true, + hidden = false, + immutable = false, + index, + maxbuffer = MAXBUFFER_DEFAULT, + maxage = 0, + root, + } = options; + const trailingSlash = path[path.length - 1] === "/"; + path = decodeComponent(path.substr(parse(path).root.length)); + if (index && trailingSlash) { + path += index; + } + + if (!hidden && isHidden(path)) { + throw createHttpError(403); + } + + path = resolvePath(root, path); + + let encodingExt = ""; + if ( + brotli && + request.acceptsEncodings("br", "identity") === "br" && + (await exists(`${path}.br`)) + ) { + path = `${path}.br`; + response.headers.set("Content-Encoding", "br"); + response.headers.delete("Content-Length"); + encodingExt = ".br"; + } else if ( + gzip && + request.acceptsEncodings("gzip", "identity") === "gzip" && + (await exists(`${path}.gz`)) + ) { + path = `${path}.gz`; + response.headers.set("Content-Encoding", "gzip"); + response.headers.delete("Content-Length"); + encodingExt = ".gz"; + } + + if (extensions && !/\.[^/]*$/.exec(path)) { + for (let ext of extensions) { + if (!/^\./.exec(ext)) { + ext = `.${ext}`; + } + if (await exists(`${path}${ext}`)) { + path += ext; + break; + } + } + } + + let stats: Deno.FileInfo; + try { + stats = await Deno.stat(path); + + if (stats.isDirectory) { + if (format && index) { + path += `/${index}`; + stats = await Deno.stat(path); + } else { + return; + } + } + } catch (err) { + if (err instanceof Deno.errors.NotFound) { + throw createHttpError(404, err.message); + } + // TODO(@kitsonk) remove when https://github.com/denoland/node_deno_shims/issues/87 resolved + if (err instanceof Error && err.message.startsWith("ENOENT:")) { + throw createHttpError(404, err.message); + } + throw createHttpError( + 500, + err instanceof Error ? err.message : "[non-error thrown]", + ); + } + + let mtime: number | null = null; + if (response.headers.has("Last-Modified")) { + mtime = new Date(response.headers.get("Last-Modified")!).getTime(); + } else if (stats.mtime) { + // Round down to second because it's the precision of the UTC string. + mtime = stats.mtime.getTime(); + mtime -= mtime % 1000; + response.headers.set("Last-Modified", new Date(mtime).toUTCString()); + } + + if (!response.headers.has("Cache-Control")) { + const directives = [`max-age=${(maxage / 1000) | 0}`]; + if (immutable) { + directives.push("immutable"); + } + response.headers.set("Cache-Control", directives.join(",")); + } + if (!response.type) { + response.type = encodingExt !== "" + ? extname(basename(path, encodingExt)) + : contentTypes[extname(path)] ?? extname(path); + } + + let entity: Uint8Array | FileInfo | null = null; + let body: Uint8Array | Deno.FsFile | null = null; + + if (request.headers.has("If-None-Match") && mtime) { + [body, entity] = await getEntity(path, mtime, stats, maxbuffer, response); + if (!await ifNoneMatch(request.headers.get("If-None-Match")!, entity)) { + response.headers.set("ETag", await calculate(entity)); + response.status = 304; + return path; + } + } + + if (request.headers.has("If-Modified-Since") && mtime) { + const ifModifiedSince = new Date(request.headers.get("If-Modified-Since")!); + if (ifModifiedSince.getTime() >= mtime) { + response.status = 304; + return path; + } + } + + if (!body || !entity) { + [body, entity] = await getEntity( + path, + mtime ?? 0, + stats, + maxbuffer, + response, + ); + } + + if ( + request.headers.has("If-Range") && mtime && + await ifRange(request.headers.get("If-Range")!, mtime, entity) && + request.headers.has("Range") + ) { + await sendRange(response, body, request.headers.get("Range")!, stats.size); + return path; + } + + if (request.headers.has("Range")) { + await sendRange(response, body, request.headers.get("Range")!, stats.size); + return path; + } + + response.body = body; + + if (!response.headers.has("ETag")) { + response.headers.set("ETag", await calculate(entity)); + } + + if (!response.headers.has("Accept-Ranges")) { + response.headers.set("Accept-Ranges", "bytes"); + } + + return path; +} diff --git a/vendor/deno.land/x/oak@v12.1.0/server_sent_event.ts b/vendor/deno.land/x/oak@v12.1.0/server_sent_event.ts new file mode 100644 index 0000000..c8468d0 --- /dev/null +++ b/vendor/deno.land/x/oak@v12.1.0/server_sent_event.ts @@ -0,0 +1,361 @@ +// Copyright 2018-2022 the oak authors. All rights reserved. MIT license. + +import type { Context } from "./context.ts"; +import { assert } from "./util.ts"; + +const encoder = new TextEncoder(); + +const DEFAULT_KEEP_ALIVE_INTERVAL = 30_000; + +export interface ServerSentEventInit extends EventInit { + /** An optional `id` which will be sent with the event and exposed in the + * client `EventSource`. */ + id?: number; + + /** The replacer is passed to `JSON.stringify` when converting the `data` + * property to a JSON string. */ + replacer?: + | (string | number)[] + // deno-lint-ignore no-explicit-any + | ((this: any, key: string, value: any) => any); + + /** Space is passed to `JSON.stringify` when converting the `data` property + * to a JSON string. */ + space?: string | number; +} + +export interface ServerSentEventTargetOptions { + /** Additional headers to send to the client during startup. These headers + * will overwrite any of the default headers if the key is duplicated. */ + headers?: Headers; + /** Keep client connections alive by sending a comment event to the client + * at a specified interval. If `true`, then it polls every 30000 milliseconds + * (30 seconds). If set to a number, then it polls that number of + * milliseconds. The feature is disabled if set to `false`. It defaults to + * `false`. */ + keepAlive?: boolean | number; +} + +class CloseEvent extends Event { + constructor(eventInit: EventInit) { + super("close", eventInit); + } +} + +/** An event which contains information which will be sent to the remote + * connection and be made available in an `EventSource` as an event. A server + * creates new events and dispatches them on the target which will then be + * sent to a client. + * + * See more about Server-sent events on [MDN](https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events/Using_server-sent_events) + * + * ### Example + * + * ```ts + * import { Application, ServerSentEvent } from "https://deno.land/x/oak/mod.ts"; + * + * const app = new Application(); + * + * app.use((ctx) => { + * const target = ctx.sendEvents(); + * const evt = new ServerSentEvent( + * "message", + * { hello: "world" }, + * { id: 1 }, + * ); + * target.dispatchEvent(evt); + * }); + * ``` + */ +export class ServerSentEvent extends Event { + #data: string; + #id?: number; + #type: string; + + /** + * @param type the event type that will be available on the client. The type + * of `"message"` will be handled specifically as a message + * server-side event. + * @param data arbitrary data to send to the client, data this is a string + * will be sent unmodified, otherwise `JSON.parse()` will be used + * to serialize the value + * @param eventInit initialization options for the event + */ + constructor( + type: string, + data: unknown, + eventInit: ServerSentEventInit = {}, + ) { + super(type, eventInit); + const { replacer, space } = eventInit; + this.#type = type; + try { + this.#data = typeof data === "string" + ? data + : JSON.stringify(data, replacer as (string | number)[], space); + } catch (e) { + assert(e instanceof Error); + throw new TypeError( + `data could not be coerced into a serialized string.\n ${e.message}`, + ); + } + const { id } = eventInit; + this.#id = id; + } + + /** The data associated with the event, which will be sent to the client and + * be made available in the `EventSource`. */ + get data(): string { + return this.#data; + } + + /** The optional ID associated with the event that will be sent to the client + * and be made available in the `EventSource`. */ + get id(): number | undefined { + return this.#id; + } + + toString(): string { + const data = `data: ${this.#data.split("\n").join("\ndata: ")}\n`; + return `${this.#type === "__message" ? "" : `event: ${this.#type}\n`}${ + this.#id ? `id: ${String(this.#id)}\n` : "" + }${data}\n`; + } +} + +const RESPONSE_HEADERS = [ + ["Connection", "Keep-Alive"], + ["Content-Type", "text/event-stream"], + ["Cache-Control", "no-cache"], + ["Keep-Alive", `timeout=${Number.MAX_SAFE_INTEGER}`], +] as const; + +export interface ServerSentEventTarget extends EventTarget { + /** Is set to `true` if events cannot be sent to the remote connection. + * Otherwise it is set to `false`. + * + * *Note*: This flag is lazily set, and might not reflect a closed state until + * another event, comment or message is attempted to be processed. */ + readonly closed: boolean; + + /** Close the target, refusing to accept any more events. */ + close(): Promise; + + /** Send a comment to the remote connection. Comments are not exposed to the + * client `EventSource` but are used for diagnostics and helping ensure a + * connection is kept alive. + * + * ```ts + * import { Application } from "https://deno.land/x/oak/mod.ts"; + * + * const app = new Application(); + * + * app.use((ctx) => { + * const sse = ctx.getSSETarget(); + * sse.dispatchComment("this is a comment"); + * }); + * + * await app.listen(); + * ``` + */ + dispatchComment(comment: string): boolean; + + /** Dispatch a message to the client. This message will contain `data: ` only + * and be available on the client `EventSource` on the `onmessage` or an event + * listener of type `"message"`. */ + dispatchMessage(data: unknown): boolean; + + /** Dispatch a server sent event to the client. The event `type` will be + * sent as `event: ` to the client which will be raised as a `MessageEvent` + * on the `EventSource` in the client. + * + * Any local event handlers will be dispatched to first, and if the event + * is cancelled, it will not be sent to the client. + * + * ```ts + * import { Application, ServerSentEvent } from "https://deno.land/x/oak/mod.ts"; + * + * const app = new Application(); + * + * app.use((ctx) => { + * const sse = ctx.getSSETarget(); + * const evt = new ServerSentEvent("ping", "hello"); + * sse.dispatchEvent(evt); + * }); + * + * await app.listen(); + * ``` + */ + dispatchEvent(event: ServerSentEvent): boolean; + + /** Dispatch a server sent event to the client. The event `type` will be + * sent as `event: ` to the client which will be raised as a `MessageEvent` + * on the `EventSource` in the client. + * + * Any local event handlers will be dispatched to first, and if the event + * is cancelled, it will not be sent to the client. + * + * ```ts + * import { Application, ServerSentEvent } from "https://deno.land/x/oak/mod.ts"; + * + * const app = new Application(); + * + * app.use((ctx) => { + * const sse = ctx.getSSETarget(); + * const evt = new ServerSentEvent("ping", "hello"); + * sse.dispatchEvent(evt); + * }); + * + * await app.listen(); + * ``` + */ + dispatchEvent(event: CloseEvent | ErrorEvent): boolean; +} + +export class SSEStreamTarget extends EventTarget + implements ServerSentEventTarget { + #closed = false; + #context: Context; + #controller?: ReadableStreamDefaultController; + // we are ignoring any here, because when exporting to npn/Node.js, the timer + // handle isn't a number. + // deno-lint-ignore no-explicit-any + #keepAliveId?: any; + + // deno-lint-ignore no-explicit-any + #error(error: any) { + console.log("error", error); + this.dispatchEvent(new CloseEvent({ cancelable: false })); + const errorEvent = new ErrorEvent("error", { error }); + this.dispatchEvent(errorEvent); + this.#context.app.dispatchEvent(errorEvent); + } + + #push(payload: string) { + if (!this.#controller) { + this.#error(new Error("The controller has not been set.")); + return; + } + if (this.#closed) { + return; + } + this.#controller.enqueue(encoder.encode(payload)); + } + + get closed(): boolean { + return this.#closed; + } + + constructor( + context: Context, + { headers, keepAlive = false }: ServerSentEventTargetOptions = {}, + ) { + super(); + + this.#context = context; + + context.response.body = new ReadableStream({ + start: (controller) => { + this.#controller = controller; + }, + cancel: (error) => { + // connections closing are considered "normal" for SSE events and just + // mean the far side has closed. + if ( + error instanceof Error && error.message.includes("connection closed") + ) { + this.close(); + } else { + this.#error(error); + } + }, + }); + + if (headers) { + for (const [key, value] of headers) { + context.response.headers.set(key, value); + } + } + for (const [key, value] of RESPONSE_HEADERS) { + context.response.headers.set(key, value); + } + + this.addEventListener("close", () => { + this.#closed = true; + if (this.#keepAliveId != null) { + clearInterval(this.#keepAliveId); + this.#keepAliveId = undefined; + } + if (this.#controller) { + try { + this.#controller.close(); + } catch { + // we ignore any errors here, as it is likely that the controller + // is already closed + } + } + }); + + if (keepAlive) { + const interval = typeof keepAlive === "number" + ? keepAlive + : DEFAULT_KEEP_ALIVE_INTERVAL; + this.#keepAliveId = setInterval(() => { + this.dispatchComment("keep-alive comment"); + }, interval); + } + } + + close(): Promise { + this.dispatchEvent(new CloseEvent({ cancelable: false })); + return Promise.resolve(); + } + + dispatchComment(comment: string): boolean { + this.#push(`: ${comment.split("\n").join("\n: ")}\n\n`); + return true; + } + + // deno-lint-ignore no-explicit-any + dispatchMessage(data: any): boolean { + const event = new ServerSentEvent("__message", data); + return this.dispatchEvent(event); + } + + dispatchEvent(event: ServerSentEvent): boolean; + dispatchEvent(event: CloseEvent | ErrorEvent): boolean; + dispatchEvent(event: ServerSentEvent | CloseEvent | ErrorEvent): boolean { + const dispatched = super.dispatchEvent(event); + if (dispatched && event instanceof ServerSentEvent) { + this.#push(String(event)); + } + return dispatched; + } + + [Symbol.for("Deno.customInspect")](inspect: (value: unknown) => string) { + return `${this.constructor.name} ${ + inspect({ "#closed": this.#closed, "#context": this.#context }) + }`; + } + + [Symbol.for("nodejs.util.inspect.custom")]( + depth: number, + // deno-lint-ignore no-explicit-any + options: any, + inspect: (value: unknown, options?: unknown) => string, + ) { + if (depth < 0) { + return options.stylize(`[${this.constructor.name}]`, "special"); + } + + const newOptions = Object.assign({}, options, { + depth: options.depth === null ? null : options.depth - 1, + }); + return `${options.stylize(this.constructor.name, "special")} ${ + inspect( + { "#closed": this.#closed, "#context": this.#context }, + newOptions, + ) + }`; + } +} diff --git a/vendor/deno.land/x/oak@v12.1.0/structured_clone.ts b/vendor/deno.land/x/oak@v12.1.0/structured_clone.ts new file mode 100644 index 0000000..556dd64 --- /dev/null +++ b/vendor/deno.land/x/oak@v12.1.0/structured_clone.ts @@ -0,0 +1,177 @@ +// Copyright 2018-2022 the oak authors. All rights reserved. MIT license. + +export type StructuredClonable = + | { [key: string]: StructuredClonable } + | Array + | ArrayBuffer + | ArrayBufferView + // deno-lint-ignore ban-types + | BigInt + | bigint + | Blob + // deno-lint-ignore ban-types + | Boolean + | boolean + | Date + | Error + | EvalError + | Map + // deno-lint-ignore ban-types + | Number + | number + | RangeError + | ReferenceError + | RegExp + | Set + // deno-lint-ignore ban-types + | String + | string + | SyntaxError + | TypeError + | URIError; + +/** Internal functions on the `Deno.core` namespace */ +interface DenoCore { + deserialize(value: unknown): StructuredClonable; + serialize(value: StructuredClonable): unknown; +} + +const objectCloneMemo = new WeakMap(); + +function cloneArrayBuffer( + srcBuffer: ArrayBuffer, + srcByteOffset: number, + srcLength: number, + // deno-lint-ignore no-explicit-any + _cloneConstructor: any, +) { + // this function fudges the return type but SharedArrayBuffer is disabled for a while anyway + return srcBuffer.slice( + srcByteOffset, + srcByteOffset + srcLength, + ); +} + +/** A loose approximation for structured cloning, used when the `Deno.core` + * APIs are not available. */ +// deno-lint-ignore no-explicit-any +function cloneValue(value: any): any { + switch (typeof value) { + case "number": + case "string": + case "boolean": + case "undefined": + case "bigint": + return value; + case "object": { + if (objectCloneMemo.has(value)) { + return objectCloneMemo.get(value); + } + if (value === null) { + return value; + } + if (value instanceof Date) { + return new Date(value.valueOf()); + } + if (value instanceof RegExp) { + return new RegExp(value); + } + if (value instanceof SharedArrayBuffer) { + return value; + } + if (value instanceof ArrayBuffer) { + const cloned = cloneArrayBuffer( + value, + 0, + value.byteLength, + ArrayBuffer, + ); + objectCloneMemo.set(value, cloned); + return cloned; + } + if (ArrayBuffer.isView(value)) { + const clonedBuffer = cloneValue(value.buffer); + // Use DataViewConstructor type purely for type-checking, can be a + // DataView or TypedArray. They use the same constructor signature, + // only DataView has a length in bytes and TypedArrays use a length in + // terms of elements, so we adjust for that. + let length; + if (value instanceof DataView) { + length = value.byteLength; + } else { + // deno-lint-ignore no-explicit-any + length = (value as any).length; + } + // deno-lint-ignore no-explicit-any + return new (value.constructor as any)( + clonedBuffer, + value.byteOffset, + length, + ); + } + if (value instanceof Map) { + const clonedMap = new Map(); + objectCloneMemo.set(value, clonedMap); + value.forEach((v, k) => { + clonedMap.set(cloneValue(k), cloneValue(v)); + }); + return clonedMap; + } + if (value instanceof Set) { + // assumes that cloneValue still takes only one argument + const clonedSet = new Set([...value].map(cloneValue)); + objectCloneMemo.set(value, clonedSet); + return clonedSet; + } + + // default for objects + // deno-lint-ignore no-explicit-any + const clonedObj: Record = {}; + objectCloneMemo.set(value, clonedObj); + const sourceKeys = Object.getOwnPropertyNames(value); + for (const key of sourceKeys) { + clonedObj[key] = cloneValue(value[key]); + } + Reflect.setPrototypeOf(clonedObj, Reflect.getPrototypeOf(value)); + return clonedObj; + } + case "symbol": + case "function": + default: + throw new DOMException("Uncloneable value in stream", "DataCloneError"); + } +} + +// deno-lint-ignore no-explicit-any +const core = (Deno as any)?.core as DenoCore | undefined; +const structuredClone: ((value: unknown) => unknown) | undefined = + // deno-lint-ignore no-explicit-any + (globalThis as any).structuredClone; + +/** + * Provides structured cloning + * @param value + * @returns + */ +function sc(value: T): T { + return structuredClone + ? structuredClone(value) + : core + ? core.deserialize(core.serialize(value)) + : cloneValue(value); +} + +/** Clones a state object, skipping any values that cannot be cloned. */ +// deno-lint-ignore no-explicit-any +export function cloneState>(state: S): S { + const clone = {} as S; + for (const [key, value] of Object.entries(state)) { + try { + const clonedValue = sc(value); + clone[key as keyof S] = clonedValue; + } catch { + // we just no-op values that cannot be cloned + } + } + return clone; +} diff --git a/vendor/deno.land/x/oak@v12.1.0/testing.ts b/vendor/deno.land/x/oak@v12.1.0/testing.ts new file mode 100644 index 0000000..c7aaf93 --- /dev/null +++ b/vendor/deno.land/x/oak@v12.1.0/testing.ts @@ -0,0 +1,179 @@ +// Copyright 2018-2022 the oak authors. All rights reserved. MIT license. + +// deno-lint-ignore-file no-explicit-any + +/** + * A collection of utility APIs which can make testing of an oak application + * easier. + * + * @module + */ + +import type { Application, State } from "./application.ts"; +import { accepts, createHttpError, SecureCookieMap } from "./deps.ts"; +import type { RouteParams, RouterContext } from "./router.ts"; +import type { ErrorStatus } from "./types.d.ts"; +import { Request } from "./request.ts"; +import { Response } from "./response.ts"; + +/** Creates a mock of `Application`. */ +export function createMockApp< + S extends Record = Record, +>( + state = {} as S, +): Application { + const app = { + state, + use() { + return app; + }, + [Symbol.for("Deno.customInspect")]() { + return "MockApplication {}"; + }, + [Symbol.for("nodejs.util.inspect.custom")]( + depth: number, + options: any, + inspect: (value: unknown, options?: unknown) => string, + ) { + if (depth < 0) { + return options.stylize(`[MockApplication]`, "special"); + } + + const newOptions = Object.assign({}, options, { + depth: options.depth === null ? null : options.depth - 1, + }); + return `${options.stylize("MockApplication", "special")} ${ + inspect({}, newOptions) + }`; + }, + } as any; + return app; +} + +/** Options that can be set in a mock context. */ +export interface MockContextOptions< + R extends string, + P extends RouteParams = RouteParams, + S extends State = Record, +> { + app?: Application; + ip?: string; + method?: string; + params?: P; + path?: string; + state?: S; + headers?: [string, string][]; +} + +/** Allows external parties to modify the context state. */ +export const mockContextState = { + /** Adjusts the return value of the `acceptedEncodings` in the context's + * `request` object. */ + encodingsAccepted: "identity", +}; + +/** Create a mock of `Context` or `RouterContext`. */ +export function createMockContext< + R extends string, + P extends RouteParams = RouteParams, + S extends State = Record, +>( + { + ip = "127.0.0.1", + method = "GET", + params, + path = "/", + state, + app = createMockApp(state), + headers: requestHeaders, + }: MockContextOptions = {}, +) { + function createMockRequest(): Request { + const headers = new Headers(requestHeaders); + return { + accepts(...types: string[]) { + if (!headers.has("Accept")) { + return; + } + if (types.length) { + return accepts({ headers }, ...types); + } + return accepts({ headers }); + }, + acceptsEncodings() { + return mockContextState.encodingsAccepted; + }, + headers, + ip, + method, + path, + search: undefined, + searchParams: new URLSearchParams(), + url: new URL(path, "http://localhost/"), + } as any; + } + + const request = createMockRequest(); + const response = new Response(request); + const cookies = new SecureCookieMap(request, { response }); + + return ({ + app, + params, + request, + cookies, + response, + state: Object.assign({}, app.state), + assert( + condition: any, + errorStatus: ErrorStatus = 500, + message?: string, + props?: Record, + ): asserts condition { + if (condition) { + return; + } + const err = createHttpError(errorStatus, message); + if (props) { + Object.assign(err, props); + } + throw err; + }, + throw( + errorStatus: ErrorStatus, + message?: string, + props?: Record, + ): never { + const err = createHttpError(errorStatus, message); + if (props) { + Object.assign(err, props); + } + throw err; + }, + [Symbol.for("Deno.customInspect")]() { + return `MockContext {}`; + }, + [Symbol.for("nodejs.util.inspect.custom")]( + depth: number, + options: any, + inspect: (value: unknown, options?: unknown) => string, + ) { + if (depth < 0) { + return options.stylize(`[MockContext]`, "special"); + } + + const newOptions = Object.assign({}, options, { + depth: options.depth === null ? null : options.depth - 1, + }); + return `${options.stylize("MockContext", "special")} ${ + inspect({}, newOptions) + }`; + }, + } as unknown) as RouterContext; +} + +/** Creates a mock `next()` function which can be used when calling + * middleware. */ +export function createMockNext() { + return async function next() {}; +} diff --git a/vendor/deno.land/x/oak@v12.1.0/types.d.ts b/vendor/deno.land/x/oak@v12.1.0/types.d.ts new file mode 100644 index 0000000..82758e4 --- /dev/null +++ b/vendor/deno.land/x/oak@v12.1.0/types.d.ts @@ -0,0 +1,134 @@ +// Copyright 2018-2022 the oak authors. All rights reserved. MIT license. + +import type { Application, State } from "./application.ts"; +import type { Status } from "./deps.ts"; + +/** A HTTP status that is an error (4XX and 5XX). */ +export type ErrorStatus = + | Status.BadRequest + | Status.Unauthorized + | Status.PaymentRequired + | Status.Forbidden + | Status.NotFound + | Status.MethodNotAllowed + | Status.NotAcceptable + | Status.ProxyAuthRequired + | Status.RequestTimeout + | Status.Conflict + | Status.Gone + | Status.LengthRequired + | Status.PreconditionFailed + | Status.RequestEntityTooLarge + | Status.RequestURITooLong + | Status.UnsupportedMediaType + | Status.RequestedRangeNotSatisfiable + | Status.ExpectationFailed + | Status.Teapot + | Status.MisdirectedRequest + | Status.UnprocessableEntity + | Status.Locked + | Status.FailedDependency + | Status.UpgradeRequired + | Status.PreconditionRequired + | Status.TooManyRequests + | Status.RequestHeaderFieldsTooLarge + | Status.UnavailableForLegalReasons + | Status.InternalServerError + | Status.NotImplemented + | Status.BadGateway + | Status.ServiceUnavailable + | Status.GatewayTimeout + | Status.HTTPVersionNotSupported + | Status.VariantAlsoNegotiates + | Status.InsufficientStorage + | Status.LoopDetected + | Status.NotExtended + | Status.NetworkAuthenticationRequired; + +/** A HTTP status that is a redirect (3XX). */ +export type RedirectStatus = + | Status.MultipleChoices // 300 + | Status.MovedPermanently // 301 + | Status.Found // 302 + | Status.SeeOther // 303 + | Status.UseProxy // 305 - DEPRECATED + | Status.TemporaryRedirect // 307 + | Status.PermanentRedirect; // 308 + +export type HTTPMethods = + | "HEAD" + | "OPTIONS" + | "GET" + | "PUT" + | "PATCH" + | "POST" + | "DELETE"; + +export interface Listener { + addr: { hostname: string; port: number }; +} + +export interface Server extends AsyncIterable { + close(): void | Promise; + listen(): Listener | Promise; + [Symbol.asyncIterator](): AsyncIterableIterator; +} + +export interface ServerRequestBody { + body: ReadableStream | null; + readBody: () => Promise; +} + +export interface ServerRequest { + readonly remoteAddr: string | undefined; + readonly headers: Headers; + readonly method: string; + readonly rawUrl?: string; + readonly url: string; + // deno-lint-ignore no-explicit-any + error(reason?: any): void; + getBody(): ServerRequestBody; + respond(response: Response): Promise; + upgrade?(options?: UpgradeWebSocketOptions): WebSocket; +} + +export interface ServerConstructor { + // deno-lint-ignore no-explicit-any + new >( + app: Application, + options: Deno.ListenOptions | Deno.ListenTlsOptions, + ): Server; + prototype: Server; +} + +export type Data = string | number[] | ArrayBuffer | Uint8Array; +export type Key = string | number[] | ArrayBuffer | Uint8Array; + +export interface UpgradeWebSocketOptions { + protocol?: string; +} + +export type UpgradeWebSocketFn = ( + request: Request, + options?: UpgradeWebSocketOptions, +) => WebSocketUpgrade; + +interface WebSocketUpgrade { + response: Response; + socket: WebSocket; +} + +// Since the native bindings are currently unstable in Deno, we will add the +// interfaces here, so that we can type check oak without requiring the +// `--unstable` flag to be used. + +export interface RequestEvent { + readonly request: Request; + respondWith(r: Response | Promise): Promise; +} + +export interface HttpConn extends AsyncIterable { + readonly rid: number; + nextRequest(): Promise; + close(): void; +} diff --git a/vendor/deno.land/x/oak@v12.1.0/util.ts b/vendor/deno.land/x/oak@v12.1.0/util.ts new file mode 100644 index 0000000..c40a2a0 --- /dev/null +++ b/vendor/deno.land/x/oak@v12.1.0/util.ts @@ -0,0 +1,472 @@ +// Copyright 2018-2022 the oak authors. All rights reserved. MIT license. + +import type { State } from "./application.ts"; +import type { Context } from "./context.ts"; +import { + base64, + createHttpError, + isAbsolute, + join, + normalize, + sep, + Status, +} from "./deps.ts"; +import type { RouteParams, RouterContext } from "./router.ts"; +import type { Data, ErrorStatus, Key, RedirectStatus } from "./types.d.ts"; + +const ENCODE_CHARS_REGEXP = + /(?:[^\x21\x25\x26-\x3B\x3D\x3F-\x5B\x5D\x5F\x61-\x7A\x7E]|%(?:[^0-9A-Fa-f]|[0-9A-Fa-f][^0-9A-Fa-f]|$))+/g; +const HTAB = "\t".charCodeAt(0); +const SPACE = " ".charCodeAt(0); +const CR = "\r".charCodeAt(0); +const LF = "\n".charCodeAt(0); +const UNMATCHED_SURROGATE_PAIR_REGEXP = + /(^|[^\uD800-\uDBFF])[\uDC00-\uDFFF]|[\uD800-\uDBFF]([^\uDC00-\uDFFF]|$)/g; +const UNMATCHED_SURROGATE_PAIR_REPLACE = "$1\uFFFD$2"; +export const DEFAULT_CHUNK_SIZE = 16_640; // 17 Kib + +/** Body types which will be coerced into strings before being sent. */ +export const BODY_TYPES = ["string", "number", "bigint", "boolean", "symbol"]; + +export function assert(cond: unknown, msg = "Assertion failed"): asserts cond { + if (!cond) { + throw new Error(msg); + } +} + +/** Safely decode a URI component, where if it fails, instead of throwing, + * just returns the original string + */ +export function decodeComponent(text: string) { + try { + return decodeURIComponent(text); + } catch { + return text; + } +} + +/** Encodes the url preventing double enconding */ +export function encodeUrl(url: string) { + return String(url) + .replace(UNMATCHED_SURROGATE_PAIR_REGEXP, UNMATCHED_SURROGATE_PAIR_REPLACE) + .replace(ENCODE_CHARS_REGEXP, encodeURI); +} + +function bufferToHex(buffer: ArrayBuffer): string { + const arr = Array.from(new Uint8Array(buffer)); + return arr.map((b) => b.toString(16).padStart(2, "0")).join(""); +} + +export async function getRandomFilename( + prefix = "", + extension = "", +): Promise { + const buffer = await crypto.subtle.digest( + "SHA-1", + crypto.getRandomValues(new Uint8Array(256)), + ); + return `${prefix}${bufferToHex(buffer)}${extension ? `.${extension}` : ""}`; +} + +export async function getBoundary(): Promise { + const buffer = await crypto.subtle.digest( + "SHA-1", + crypto.getRandomValues(new Uint8Array(256)), + ); + return `oak_${bufferToHex(buffer)}`; +} + +/** Guard for Async Iterables */ +export function isAsyncIterable( + value: unknown, +): value is AsyncIterable { + return typeof value === "object" && value !== null && + Symbol.asyncIterator in value && + // deno-lint-ignore no-explicit-any + typeof (value as any)[Symbol.asyncIterator] === "function"; +} + +export function isRouterContext< + R extends string, + P extends RouteParams, + S extends State, +>( + value: Context, +): value is RouterContext { + return "params" in value; +} + +/** Guard for `Deno.Reader`. */ +export function isReader(value: unknown): value is Deno.Reader { + return typeof value === "object" && value !== null && "read" in value && + typeof (value as Record).read === "function"; +} + +function isCloser(value: unknown): value is Deno.Closer { + return typeof value === "object" && value != null && "close" in value && + // deno-lint-ignore no-explicit-any + typeof (value as Record)["close"] === "function"; +} + +export function isConn(value: unknown): value is Deno.Conn { + return typeof value === "object" && value != null && "rid" in value && + // deno-lint-ignore no-explicit-any + typeof (value as any).rid === "number" && "localAddr" in value && + "remoteAddr" in value; +} + +export function isListenTlsOptions( + value: unknown, +): value is Deno.ListenTlsOptions { + return typeof value === "object" && value !== null && + ("cert" in value || "certFile" in value) && + ("key" in value || "keyFile" in value) && "port" in value; +} + +export interface ReadableStreamFromReaderOptions { + /** If the `reader` is also a `Deno.Closer`, automatically close the `reader` + * when `EOF` is encountered, or a read error occurs. + * + * Defaults to `true`. */ + autoClose?: boolean; + + /** The size of chunks to allocate to read, the default is ~16KiB, which is + * the maximum size that Deno operations can currently support. */ + chunkSize?: number; + + /** The queuing strategy to create the `ReadableStream` with. */ + strategy?: { highWaterMark?: number | undefined; size?: undefined }; +} + +/** + * Create a `ReadableStream` from an `AsyncIterable`. + */ +export function readableStreamFromAsyncIterable( + source: AsyncIterable, +): ReadableStream { + return new ReadableStream({ + async start(controller) { + for await (const chunk of source) { + if (BODY_TYPES.includes(typeof chunk)) { + controller.enqueue(encoder.encode(String(chunk))); + } else if (chunk instanceof Uint8Array) { + controller.enqueue(chunk); + } else if (ArrayBuffer.isView(chunk)) { + controller.enqueue(new Uint8Array(chunk.buffer)); + } else if (chunk instanceof ArrayBuffer) { + controller.enqueue(new Uint8Array(chunk)); + } else { + try { + controller.enqueue(encoder.encode(JSON.stringify(chunk))); + } catch { + // we just swallow errors here + } + } + } + controller.close(); + }, + }); +} + +/** + * Create a `ReadableStream` from a `Deno.Reader`. + * + * When the pull algorithm is called on the stream, a chunk from the reader + * will be read. When `null` is returned from the reader, the stream will be + * closed along with the reader (if it is also a `Deno.Closer`). + * + * An example converting a `Deno.FsFile` into a readable stream: + * + * ```ts + * import { readableStreamFromReader } from "https://deno.land/std/io/mod.ts"; + * + * const file = await Deno.open("./file.txt", { read: true }); + * const fileStream = readableStreamFromReader(file); + * ``` + */ +export function readableStreamFromReader( + reader: Deno.Reader | (Deno.Reader & Deno.Closer), + options: ReadableStreamFromReaderOptions = {}, +): ReadableStream { + const { + autoClose = true, + chunkSize = DEFAULT_CHUNK_SIZE, + strategy, + } = options; + + return new ReadableStream({ + async pull(controller) { + const chunk = new Uint8Array(chunkSize); + try { + const read = await reader.read(chunk); + if (read === null) { + if (isCloser(reader) && autoClose) { + reader.close(); + } + controller.close(); + return; + } + controller.enqueue(chunk.subarray(0, read)); + } catch (e) { + controller.error(e); + if (isCloser(reader)) { + reader.close(); + } + } + }, + cancel() { + if (isCloser(reader) && autoClose) { + reader.close(); + } + }, + }, strategy); +} + +/** Determines if a HTTP `Status` is an `ErrorStatus` (4XX or 5XX). */ +export function isErrorStatus(value: Status): value is ErrorStatus { + return [ + Status.BadRequest, + Status.Unauthorized, + Status.PaymentRequired, + Status.Forbidden, + Status.NotFound, + Status.MethodNotAllowed, + Status.NotAcceptable, + Status.ProxyAuthRequired, + Status.RequestTimeout, + Status.Conflict, + Status.Gone, + Status.LengthRequired, + Status.PreconditionFailed, + Status.RequestEntityTooLarge, + Status.RequestURITooLong, + Status.UnsupportedMediaType, + Status.RequestedRangeNotSatisfiable, + Status.ExpectationFailed, + Status.Teapot, + Status.MisdirectedRequest, + Status.UnprocessableEntity, + Status.Locked, + Status.FailedDependency, + Status.UpgradeRequired, + Status.PreconditionRequired, + Status.TooManyRequests, + Status.RequestHeaderFieldsTooLarge, + Status.UnavailableForLegalReasons, + Status.InternalServerError, + Status.NotImplemented, + Status.BadGateway, + Status.ServiceUnavailable, + Status.GatewayTimeout, + Status.HTTPVersionNotSupported, + Status.VariantAlsoNegotiates, + Status.InsufficientStorage, + Status.LoopDetected, + Status.NotExtended, + Status.NetworkAuthenticationRequired, + ].includes(value); +} + +/** Determines if a HTTP `Status` is a `RedirectStatus` (3XX). */ +export function isRedirectStatus(value: Status): value is RedirectStatus { + return [ + Status.MultipleChoices, + Status.MovedPermanently, + Status.Found, + Status.SeeOther, + Status.UseProxy, + Status.TemporaryRedirect, + Status.PermanentRedirect, + ].includes(value); +} + +/** Determines if a string "looks" like HTML */ +export function isHtml(value: string): boolean { + return /^\s*<(?:!DOCTYPE|html|body)/i.test(value); +} + +/** Returns `u8` with leading white space removed. */ +export function skipLWSPChar(u8: Uint8Array): Uint8Array { + const result = new Uint8Array(u8.length); + let j = 0; + for (let i = 0; i < u8.length; i++) { + if (u8[i] === SPACE || u8[i] === HTAB) continue; + result[j++] = u8[i]; + } + return result.slice(0, j); +} + +export function stripEol(value: Uint8Array): Uint8Array { + if (value[value.byteLength - 1] == LF) { + let drop = 1; + if (value.byteLength > 1 && value[value.byteLength - 2] === CR) { + drop = 2; + } + return value.subarray(0, value.byteLength - drop); + } + return value; +} + +/*! + * Adapted directly from https://github.com/pillarjs/resolve-path + * which is licensed as follows: + * + * The MIT License (MIT) + * + * Copyright (c) 2014 Jonathan Ong + * Copyright (c) 2015-2018 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. + */ + +const UP_PATH_REGEXP = /(?:^|[\\/])\.\.(?:[\\/]|$)/; + +export function resolvePath(relativePath: string): string; +export function resolvePath(rootPath: string, relativePath: string): string; +export function resolvePath(rootPath: string, relativePath?: string): string { + let path = relativePath; + let root = rootPath; + + // root is optional, similar to root.resolve + if (relativePath === undefined) { + path = rootPath; + root = "."; + } + + if (path == null) { + throw new TypeError("Argument relativePath is required."); + } + + // containing NULL bytes is malicious + if (path.includes("\0")) { + throw createHttpError(400, "Malicious Path"); + } + + // path should never be absolute + if (isAbsolute(path)) { + throw createHttpError(400, "Malicious Path"); + } + + // path outside root + if (UP_PATH_REGEXP.test(normalize("." + sep + path))) { + throw createHttpError(403); + } + + // join the relative path + return normalize(join(root, path)); +} + +/** A utility class that transforms "any" chunk into an `Uint8Array`. */ +export class Uint8ArrayTransformStream + extends TransformStream { + constructor() { + const init = { + async transform( + chunk: unknown, + controller: TransformStreamDefaultController, + ) { + chunk = await chunk; + switch (typeof chunk) { + case "object": + if (chunk === null) { + controller.terminate(); + } else if (ArrayBuffer.isView(chunk)) { + controller.enqueue( + new Uint8Array( + chunk.buffer, + chunk.byteOffset, + chunk.byteLength, + ), + ); + } else if ( + Array.isArray(chunk) && + chunk.every((value) => typeof value === "number") + ) { + controller.enqueue(new Uint8Array(chunk)); + } else if ( + typeof chunk.valueOf === "function" && chunk.valueOf() !== chunk + ) { + this.transform(chunk.valueOf(), controller); + } else if ("toJSON" in chunk) { + this.transform(JSON.stringify(chunk), controller); + } + break; + case "symbol": + controller.error( + new TypeError("Cannot transform a symbol to a Uint8Array"), + ); + break; + case "undefined": + controller.error( + new TypeError("Cannot transform undefined to a Uint8Array"), + ); + break; + default: + controller.enqueue(this.encoder.encode(String(chunk))); + } + }, + encoder: new TextEncoder(), + }; + super(init); + } +} + +const replacements: Record = { + "/": "_", + "+": "-", + "=": "", +}; + +const encoder = new TextEncoder(); + +export function encodeBase64Safe(data: string | ArrayBuffer): string { + return base64.encode(data).replace(/\/|\+|=/g, (c) => replacements[c]); +} + +export function isNode(): boolean { + return "process" in globalThis && "global" in globalThis; +} + +export function importKey(key: Key): Promise { + if (typeof key === "string") { + key = encoder.encode(key); + } else if (Array.isArray(key)) { + key = new Uint8Array(key); + } + return crypto.subtle.importKey( + "raw", + key, + { + name: "HMAC", + hash: { name: "SHA-256" }, + }, + true, + ["sign", "verify"], + ); +} + +export function sign(data: Data, key: CryptoKey): Promise { + if (typeof data === "string") { + data = encoder.encode(data); + } else if (Array.isArray(data)) { + data = Uint8Array.from(data); + } + return crypto.subtle.sign("HMAC", key, data); +} diff --git a/vendor/deno.land/x/path_to_regexp@v6.2.1/index.ts b/vendor/deno.land/x/path_to_regexp@v6.2.1/index.ts new file mode 100644 index 0000000..d754fee --- /dev/null +++ b/vendor/deno.land/x/path_to_regexp@v6.2.1/index.ts @@ -0,0 +1,621 @@ +/** + * Tokenizer results. + */ +interface LexToken { + type: + | "OPEN" + | "CLOSE" + | "PATTERN" + | "NAME" + | "CHAR" + | "ESCAPED_CHAR" + | "MODIFIER" + | "END"; + index: number; + value: string; +} + +/** + * Tokenize input string. + */ +function lexer(str: string): LexToken[] { + const tokens: LexToken[] = []; + let i = 0; + + while (i < str.length) { + const char = str[i]; + + if (char === "*" || char === "+" || char === "?") { + tokens.push({ type: "MODIFIER", index: i, value: str[i++] }); + continue; + } + + if (char === "\\") { + tokens.push({ type: "ESCAPED_CHAR", index: i++, value: str[i++] }); + continue; + } + + if (char === "{") { + tokens.push({ type: "OPEN", index: i, value: str[i++] }); + continue; + } + + if (char === "}") { + tokens.push({ type: "CLOSE", index: i, value: str[i++] }); + continue; + } + + if (char === ":") { + let name = ""; + let j = i + 1; + + while (j < str.length) { + const code = str.charCodeAt(j); + + if ( + // `0-9` + (code >= 48 && code <= 57) || + // `A-Z` + (code >= 65 && code <= 90) || + // `a-z` + (code >= 97 && code <= 122) || + // `_` + code === 95 + ) { + name += str[j++]; + continue; + } + + break; + } + + if (!name) throw new TypeError(`Missing parameter name at ${i}`); + + tokens.push({ type: "NAME", index: i, value: name }); + i = j; + continue; + } + + if (char === "(") { + let count = 1; + let pattern = ""; + let j = i + 1; + + if (str[j] === "?") { + throw new TypeError(`Pattern cannot start with "?" at ${j}`); + } + + while (j < str.length) { + if (str[j] === "\\") { + pattern += str[j++] + str[j++]; + continue; + } + + if (str[j] === ")") { + count--; + if (count === 0) { + j++; + break; + } + } else if (str[j] === "(") { + count++; + if (str[j + 1] !== "?") { + throw new TypeError(`Capturing groups are not allowed at ${j}`); + } + } + + pattern += str[j++]; + } + + if (count) throw new TypeError(`Unbalanced pattern at ${i}`); + if (!pattern) throw new TypeError(`Missing pattern at ${i}`); + + tokens.push({ type: "PATTERN", index: i, value: pattern }); + i = j; + continue; + } + + tokens.push({ type: "CHAR", index: i, value: str[i++] }); + } + + tokens.push({ type: "END", index: i, value: "" }); + + return tokens; +} + +export interface ParseOptions { + /** + * Set the default delimiter for repeat parameters. (default: `'/'`) + */ + delimiter?: string; + /** + * List of characters to automatically consider prefixes when parsing. + */ + prefixes?: string; +} + +/** + * Parse a string for the raw tokens. + */ +export function parse(str: string, options: ParseOptions = {}): Token[] { + const tokens = lexer(str); + const { prefixes = "./" } = options; + const defaultPattern = `[^${escapeString(options.delimiter || "/#?")}]+?`; + const result: Token[] = []; + let key = 0; + let i = 0; + let path = ""; + + const tryConsume = (type: LexToken["type"]): string | undefined => { + if (i < tokens.length && tokens[i].type === type) return tokens[i++].value; + }; + + const mustConsume = (type: LexToken["type"]): string => { + const value = tryConsume(type); + if (value !== undefined) return value; + const { type: nextType, index } = tokens[i]; + throw new TypeError(`Unexpected ${nextType} at ${index}, expected ${type}`); + }; + + const consumeText = (): string => { + let result = ""; + let value: string | undefined; + while ((value = tryConsume("CHAR") || tryConsume("ESCAPED_CHAR"))) { + result += value; + } + return result; + }; + + while (i < tokens.length) { + const char = tryConsume("CHAR"); + const name = tryConsume("NAME"); + const pattern = tryConsume("PATTERN"); + + if (name || pattern) { + let prefix = char || ""; + + if (prefixes.indexOf(prefix) === -1) { + path += prefix; + prefix = ""; + } + + if (path) { + result.push(path); + path = ""; + } + + result.push({ + name: name || key++, + prefix, + suffix: "", + pattern: pattern || defaultPattern, + modifier: tryConsume("MODIFIER") || "", + }); + continue; + } + + const value = char || tryConsume("ESCAPED_CHAR"); + if (value) { + path += value; + continue; + } + + if (path) { + result.push(path); + path = ""; + } + + const open = tryConsume("OPEN"); + if (open) { + const prefix = consumeText(); + const name = tryConsume("NAME") || ""; + const pattern = tryConsume("PATTERN") || ""; + const suffix = consumeText(); + + mustConsume("CLOSE"); + + result.push({ + name: name || (pattern ? key++ : ""), + pattern: name && !pattern ? defaultPattern : pattern, + prefix, + suffix, + modifier: tryConsume("MODIFIER") || "", + }); + continue; + } + + mustConsume("END"); + } + + return result; +} + +export interface TokensToFunctionOptions { + /** + * When `true` the regexp will be case sensitive. (default: `false`) + */ + sensitive?: boolean; + /** + * Function for encoding input strings for output. + */ + encode?: (value: string, token: Key) => string; + /** + * When `false` the function can produce an invalid (unmatched) path. (default: `true`) + */ + validate?: boolean; +} + +/** + * Compile a string to a template function for the path. + */ +export function compile

( + str: string, + options?: ParseOptions & TokensToFunctionOptions +) { + return tokensToFunction

(parse(str, options), options); +} + +export type PathFunction

= (data?: P) => string; + +/** + * Expose a method for transforming tokens into the path function. + */ +export function tokensToFunction

( + tokens: Token[], + options: TokensToFunctionOptions = {} +): PathFunction

{ + const reFlags = flags(options); + const { encode = (x: string) => x, validate = true } = options; + + // Compile all the tokens into regexps. + const matches = tokens.map((token) => { + if (typeof token === "object") { + return new RegExp(`^(?:${token.pattern})$`, reFlags); + } + }); + + return (data: Record | null | undefined) => { + let path = ""; + + for (let i = 0; i < tokens.length; i++) { + const token = tokens[i]; + + if (typeof token === "string") { + path += token; + continue; + } + + const value = data ? data[token.name] : undefined; + const optional = token.modifier === "?" || token.modifier === "*"; + const repeat = token.modifier === "*" || token.modifier === "+"; + + if (Array.isArray(value)) { + if (!repeat) { + throw new TypeError( + `Expected "${token.name}" to not repeat, but got an array` + ); + } + + if (value.length === 0) { + if (optional) continue; + + throw new TypeError(`Expected "${token.name}" to not be empty`); + } + + for (let j = 0; j < value.length; j++) { + const segment = encode(value[j], token); + + if (validate && !(matches[i] as RegExp).test(segment)) { + throw new TypeError( + `Expected all "${token.name}" to match "${token.pattern}", but got "${segment}"` + ); + } + + path += token.prefix + segment + token.suffix; + } + + continue; + } + + if (typeof value === "string" || typeof value === "number") { + const segment = encode(String(value), token); + + if (validate && !(matches[i] as RegExp).test(segment)) { + throw new TypeError( + `Expected "${token.name}" to match "${token.pattern}", but got "${segment}"` + ); + } + + path += token.prefix + segment + token.suffix; + continue; + } + + if (optional) continue; + + const typeOfMessage = repeat ? "an array" : "a string"; + throw new TypeError(`Expected "${token.name}" to be ${typeOfMessage}`); + } + + return path; + }; +} + +export interface RegexpToFunctionOptions { + /** + * Function for decoding strings for params. + */ + decode?: (value: string, token: Key) => string; +} + +/** + * A match result contains data about the path match. + */ +export interface MatchResult

{ + path: string; + index: number; + params: P; +} + +/** + * A match is either `false` (no match) or a match result. + */ +export type Match

= false | MatchResult

; + +/** + * The match function takes a string and returns whether it matched the path. + */ +export type MatchFunction

= ( + path: string +) => Match

; + +/** + * Create path match function from `path-to-regexp` spec. + */ +export function match

( + str: Path, + options?: ParseOptions & TokensToRegexpOptions & RegexpToFunctionOptions +) { + const keys: Key[] = []; + const re = pathToRegexp(str, keys, options); + return regexpToFunction

(re, keys, options); +} + +/** + * Create a path match function from `path-to-regexp` output. + */ +export function regexpToFunction

( + re: RegExp, + keys: Key[], + options: RegexpToFunctionOptions = {} +): MatchFunction

{ + const { decode = (x: string) => x } = options; + + return function (pathname: string) { + const m = re.exec(pathname); + if (!m) return false; + + const { 0: path, index } = m; + const params = Object.create(null); + + for (let i = 1; i < m.length; i++) { + if (m[i] === undefined) continue; + + const key = keys[i - 1]; + + if (key.modifier === "*" || key.modifier === "+") { + params[key.name] = m[i].split(key.prefix + key.suffix).map((value) => { + return decode(value, key); + }); + } else { + params[key.name] = decode(m[i], key); + } + } + + return { path, index, params }; + }; +} + +/** + * Escape a regular expression string. + */ +function escapeString(str: string) { + return str.replace(/([.+*?=^!:${}()[\]|/\\])/g, "\\$1"); +} + +/** + * Get the flags for a regexp from the options. + */ +function flags(options?: { sensitive?: boolean }) { + return options && options.sensitive ? "" : "i"; +} + +/** + * Metadata about a key. + */ +export interface Key { + name: string | number; + prefix: string; + suffix: string; + pattern: string; + modifier: string; +} + +/** + * A token is a string (nothing special) or key metadata (capture group). + */ +export type Token = string | Key; + +/** + * Pull out keys from a regexp. + */ +function regexpToRegexp(path: RegExp, keys?: Key[]): RegExp { + if (!keys) return path; + + const groupsRegex = /\((?:\?<(.*?)>)?(?!\?)/g; + + let index = 0; + let execResult = groupsRegex.exec(path.source); + while (execResult) { + keys.push({ + // Use parenthesized substring match if available, index otherwise + name: execResult[1] || index++, + prefix: "", + suffix: "", + modifier: "", + pattern: "", + }); + execResult = groupsRegex.exec(path.source); + } + + return path; +} + +/** + * Transform an array into a regexp. + */ +function arrayToRegexp( + paths: Array, + keys?: Key[], + options?: TokensToRegexpOptions & ParseOptions +): RegExp { + const parts = paths.map((path) => pathToRegexp(path, keys, options).source); + return new RegExp(`(?:${parts.join("|")})`, flags(options)); +} + +/** + * Create a path regexp from string input. + */ +function stringToRegexp( + path: string, + keys?: Key[], + options?: TokensToRegexpOptions & ParseOptions +) { + return tokensToRegexp(parse(path, options), keys, options); +} + +export interface TokensToRegexpOptions { + /** + * When `true` the regexp will be case sensitive. (default: `false`) + */ + sensitive?: boolean; + /** + * When `true` the regexp won't allow an optional trailing delimiter to match. (default: `false`) + */ + strict?: boolean; + /** + * When `true` the regexp will match to the end of the string. (default: `true`) + */ + end?: boolean; + /** + * When `true` the regexp will match from the beginning of the string. (default: `true`) + */ + start?: boolean; + /** + * Sets the final character for non-ending optimistic matches. (default: `/`) + */ + delimiter?: string; + /** + * List of characters that can also be "end" characters. + */ + endsWith?: string; + /** + * Encode path tokens for use in the `RegExp`. + */ + encode?: (value: string) => string; +} + +/** + * Expose a function for taking tokens and returning a RegExp. + */ +export function tokensToRegexp( + tokens: Token[], + keys?: Key[], + options: TokensToRegexpOptions = {} +) { + const { + strict = false, + start = true, + end = true, + encode = (x: string) => x, + delimiter = "/#?", + endsWith = "", + } = options; + const endsWithRe = `[${escapeString(endsWith)}]|$`; + const delimiterRe = `[${escapeString(delimiter)}]`; + let route = start ? "^" : ""; + + // Iterate over the tokens and create our regexp string. + for (const token of tokens) { + if (typeof token === "string") { + route += escapeString(encode(token)); + } else { + const prefix = escapeString(encode(token.prefix)); + const suffix = escapeString(encode(token.suffix)); + + if (token.pattern) { + if (keys) keys.push(token); + + if (prefix || suffix) { + if (token.modifier === "+" || token.modifier === "*") { + const mod = token.modifier === "*" ? "?" : ""; + route += `(?:${prefix}((?:${token.pattern})(?:${suffix}${prefix}(?:${token.pattern}))*)${suffix})${mod}`; + } else { + route += `(?:${prefix}(${token.pattern})${suffix})${token.modifier}`; + } + } else { + if (token.modifier === "+" || token.modifier === "*") { + route += `((?:${token.pattern})${token.modifier})`; + } else { + route += `(${token.pattern})${token.modifier}`; + } + } + } else { + route += `(?:${prefix}${suffix})${token.modifier}`; + } + } + } + + if (end) { + if (!strict) route += `${delimiterRe}?`; + + route += !options.endsWith ? "$" : `(?=${endsWithRe})`; + } else { + const endToken = tokens[tokens.length - 1]; + const isEndDelimited = + typeof endToken === "string" + ? delimiterRe.indexOf(endToken[endToken.length - 1]) > -1 + : endToken === undefined; + + if (!strict) { + route += `(?:${delimiterRe}(?=${endsWithRe}))?`; + } + + if (!isEndDelimited) { + route += `(?=${delimiterRe}|${endsWithRe})`; + } + } + + return new RegExp(route, flags(options)); +} + +/** + * Supported `path-to-regexp` input types. + */ +export type Path = string | RegExp | Array; + +/** + * Normalize the given path string, returning a regular expression. + * + * An empty array can be passed in for the keys, which will hold the + * placeholder key descriptions. For example, using `/user/:id`, `keys` will + * contain `[{ name: 'id', delimiter: '/', optional: false, repeat: false }]`. + */ +export function pathToRegexp( + path: Path, + keys?: Key[], + options?: TokensToRegexpOptions & ParseOptions +) { + if (path instanceof RegExp) return regexpToRegexp(path, keys); + if (Array.isArray(path)) return arrayToRegexp(path, keys, options); + return stringToRegexp(path, keys, options); +} diff --git a/vendor/import_map.json b/vendor/import_map.json index dcbdeb0..5d2a804 100644 --- a/vendor/import_map.json +++ b/vendor/import_map.json @@ -1,6 +1,7 @@ { "imports": { "https://denopkg.com/chiefbiiko/sha1/mod.ts": "./denopkg.com/chiefbiiko/sha1@master/mod.ts", + "https://deno.land/x/oak/mod.ts": "./deno.land/x/oak@v12.1.0/mod.ts", "https://deno.land/std/testing/asserts.ts": "./deno.land/std@0.191.0/testing/asserts.ts", "https://deno.land/": "./deno.land/", "https://denopkg.com/": "./denopkg.com/"