File
Blob: src/workerd/util/mimetype.h
| 1 | // Copyright (c) 2023 Cloudflare, Inc. |
| 2 | // Licensed under the Apache 2.0 license found in the LICENSE file or at: |
| 3 | // https://opensource.org/licenses/Apache-2.0 |
| 4 | #pragma once |
| 5 | |
| 6 | #include <workerd/jsg/memory.h> |
| 7 | #include <workerd/util/strings.h> |
| 8 | |
| 9 | #include <kj/common.h> |
| 10 | #include <kj/map.h> |
| 11 | #include <kj/string.h> |
| 12 | |
| 13 | namespace workerd { |
| 14 | |
| 15 | template <size_t> |
| 16 | class StringBuffer; |
| 17 | class MimeType; |
| 18 | class ConstMimeType final { |
| 19 | public: |
| 20 | constexpr ConstMimeType(kj::StringPtr type, kj::StringPtr subtype) |
| 21 | : type_(type), |
| 22 | subtype_(subtype) {} |
| 23 | |
| 24 | constexpr kj::StringPtr type() const { |
| 25 | return type_; |
| 26 | } |
| 27 | constexpr kj::StringPtr subtype() const { |
| 28 | return subtype_; |
| 29 | } |
| 30 | |
| 31 | constexpr bool operator==(const ConstMimeType& other) const { |
| 32 | return this == &other || (type_ == other.type_ && subtype_ == other.subtype_); |
| 33 | } |
| 34 | |
| 35 | bool operator==(const MimeType& other) const; |
| 36 | operator MimeType() const; |
| 37 | MimeType clone() const; |
| 38 | |
| 39 | inline kj::String toString() const { |
| 40 | return kj::str(type_, "/", subtype_); |
| 41 | } |
| 42 | |
| 43 | inline kj::String essence() const { |
| 44 | return toString(); |
| 45 | } |
| 46 | |
| 47 | private: |
| 48 | kj::StringPtr type_; |
| 49 | kj::StringPtr subtype_; |
| 50 | }; |
| 51 | |
| 52 | template <typename T> |
| 53 | concept IsMimeType = kj::isSameType<T, MimeType>() || kj::isSameType<T, ConstMimeType>(); |
| 54 | static_assert(IsMimeType<MimeType>); |
| 55 | static_assert(IsMimeType<ConstMimeType>); |
| 56 | |
| 57 | class MimeType final { |
| 58 | public: |
| 59 | using MimeParams = kj::HashMap<kj::String, kj::String>; |
| 60 | |
| 61 | enum ParseOptions { |
| 62 | DEFAULT, |
| 63 | IGNORE_PARAMS, |
| 64 | }; |
| 65 | |
| 66 | // Returning nullptr implies that the input is not a valid mime type construction. |
| 67 | // If the ParseOptions::IGNORE_PARAMS option is set then the mime type parameters |
| 68 | // will be ignored and will not be included in the parsed result. |
| 69 | static kj::Maybe<MimeType> tryParse( |
| 70 | kj::ArrayPtr<const char> input, ParseOptions options = ParseOptions::DEFAULT); |
| 71 | |
| 72 | // Asserts if the input could not be parsed as a valid MimeType. tryParse should |
| 73 | // be preferred for most cases. |
| 74 | static MimeType parse(kj::StringPtr input, ParseOptions options = ParseOptions::DEFAULT); |
| 75 | |
| 76 | explicit MimeType( |
| 77 | kj::StringPtr type, kj::StringPtr subtype, kj::Maybe<MimeParams> params = kj::none); |
| 78 | explicit MimeType(kj::String type, kj::String subtype, kj::Maybe<MimeParams> params = kj::none); |
| 79 | |
| 80 | MimeType(MimeType&&) = default; |
| 81 | MimeType& operator=(MimeType&&) = default; |
| 82 | KJ_DISALLOW_COPY(MimeType); |
| 83 | |
| 84 | kj::StringPtr type() const; |
| 85 | kj::StringPtr subtype() const; |
| 86 | |
| 87 | const MimeParams& params() const; |
| 88 | |
| 89 | bool setType(kj::StringPtr type); |
| 90 | bool setSubtype(kj::StringPtr type); |
| 91 | bool addParam(kj::ArrayPtr<const char> name, kj::ArrayPtr<const char> value); |
| 92 | void eraseParam(kj::StringPtr name); |
| 93 | |
| 94 | // Returns only the type/subtype |
| 95 | kj::String essence() const; |
| 96 | |
| 97 | // Returns the type/subtype and all params. |
| 98 | kj::String toString() const; |
| 99 | |
| 100 | kj::String paramsToString() const; |
| 101 | |
| 102 | // Copy this MimeType. If the IGNORE_PARAMS option is set the clone |
| 103 | // will copy only the type and subtype and will omit all of the parameters. |
| 104 | MimeType clone(ParseOptions options = ParseOptions::DEFAULT) const; |
| 105 | |
| 106 | // Compares only the essence of the MimeType (type and subtype). Ignores |
| 107 | // parameters in the comparison. |
| 108 | bool operator==(const MimeType& other) const; |
| 109 | |
| 110 | operator kj::String() const; |
| 111 | |
| 112 | template <IsMimeType T> |
| 113 | static constexpr bool isXml(const T& mimeType) { |
| 114 | auto type = mimeType.type(); |
| 115 | auto subtype = mimeType.subtype(); |
| 116 | // Bare "xml" subtype is only valid for text/xml and application/xml. |
| 117 | // The "+xml" structured syntax suffix (RFC 6838 §4.2.8, RFC 6839) indicates |
| 118 | // XML-based content regardless of top-level type (e.g. image/svg+xml). |
| 119 | return ((type == "text" || type == "application") && subtype == "xml") || |
| 120 | subtype.endsWith("+xml"); |
| 121 | } |
| 122 | |
| 123 | template <IsMimeType T> |
| 124 | static constexpr bool isJson(const T& mimeType) { |
| 125 | auto type = mimeType.type(); |
| 126 | auto subtype = mimeType.subtype(); |
| 127 | return (type == "text" || type == "application") && |
| 128 | (subtype == "json" || subtype.endsWith("+json")); |
| 129 | } |
| 130 | |
| 131 | template <IsMimeType T> |
| 132 | static constexpr bool isFont(const T& mimeType) { |
| 133 | auto type = mimeType.type(); |
| 134 | auto subtype = mimeType.subtype(); |
| 135 | return (type == "font" || type == "application") && |
| 136 | (subtype.startsWith("font-") || subtype.startsWith("x-font-")); |
| 137 | } |
| 138 | |
| 139 | template <IsMimeType T> |
| 140 | static constexpr bool isJavascript(const T& mimeType) { |
| 141 | return JAVASCRIPT == mimeType || XJAVASCRIPT == mimeType || TEXT_JAVASCRIPT == mimeType; |
| 142 | } |
| 143 | |
| 144 | template <IsMimeType T> |
| 145 | static constexpr bool isText(const T& mimeType) { |
| 146 | auto type = mimeType.type(); |
| 147 | auto subtype = mimeType.subtype(); |
| 148 | return type == "text" || isXml(mimeType) || isJson(mimeType) || isJavascript(mimeType) || |
| 149 | (type == "application" && subtype == "dns-json"); |
| 150 | } |
| 151 | |
| 152 | template <IsMimeType T> |
| 153 | static constexpr bool isImage(const T& mimeType) { |
| 154 | return mimeType.type() == "image"; |
| 155 | } |
| 156 | |
| 157 | template <IsMimeType T> |
| 158 | static constexpr bool isVideo(const T& mimeType) { |
| 159 | return mimeType.type() == "video"; |
| 160 | } |
| 161 | |
| 162 | template <IsMimeType T> |
| 163 | static constexpr bool isAudio(const T& mimeType) { |
| 164 | return mimeType.type() == "audio"; |
| 165 | } |
| 166 | |
| 167 | static const MimeType PLAINTEXT; |
| 168 | static const MimeType PLAINTEXT_ASCII; |
| 169 | static constexpr ConstMimeType JSON = ConstMimeType("application"_kj, "json"_kj); |
| 170 | static constexpr ConstMimeType FORM_URLENCODED = |
| 171 | ConstMimeType("application"_kj, "x-www-form-urlencoded"_kj); |
| 172 | static constexpr ConstMimeType FORM_DATA = ConstMimeType("multipart"_kj, "form-data"_kj); |
| 173 | static constexpr ConstMimeType OCTET_STREAM = ConstMimeType("application"_kj, "octet-stream"_kj); |
| 174 | static constexpr ConstMimeType XHTML = ConstMimeType("application"_kj, "xhtml+xml"_kj); |
| 175 | static constexpr ConstMimeType JAVASCRIPT = ConstMimeType("application"_kj, "javascript"_kj); |
| 176 | static constexpr ConstMimeType XJAVASCRIPT = ConstMimeType("application"_kj, "x-javascript"_kj); |
| 177 | static constexpr ConstMimeType TEXT_JAVASCRIPT = ConstMimeType("text"_kj, "javascript"_kj); |
| 178 | static constexpr ConstMimeType HTML = ConstMimeType("text"_kj, "html"_kj); |
| 179 | static constexpr ConstMimeType CSS = ConstMimeType("text"_kj, "css"_kj); |
| 180 | static constexpr ConstMimeType MANIFEST_JSON = |
| 181 | ConstMimeType("application"_kj, "manifest+json"_kj); |
| 182 | static constexpr ConstMimeType VTT = ConstMimeType("text"_kj, "vtt"_kj); |
| 183 | static constexpr ConstMimeType EVENT_STREAM = ConstMimeType("text"_kj, "event-stream"_kj); |
| 184 | static constexpr ConstMimeType WILDCARD = ConstMimeType("*"_kj, "*"_kj); |
| 185 | |
| 186 | // exposed directly for performance reasons |
| 187 | static constexpr kj::StringPtr PLAINTEXT_STRING = "text/plain;charset=UTF-8"_kj; |
| 188 | static constexpr kj::StringPtr PLAINTEXT_ASCII_STRING = "text/plain;charset=US-ASCII"_kj; |
| 189 | |
| 190 | static kj::String formDataWithBoundary(kj::StringPtr boundary); |
| 191 | static kj::String formUrlEncodedWithCharset(kj::StringPtr charset); |
| 192 | |
| 193 | // Extracts a mime type from a concatenated list of content-type values |
| 194 | // per the algorithm defined in the fetch spec: |
| 195 | // https://fetch.spec.whatwg.org/#concept-header-extract-mime-type |
| 196 | static kj::Maybe<MimeType> extract(kj::StringPtr input); |
| 197 | |
| 198 | void visitForMemoryInfo(jsg::MemoryTracker& tracker) const { |
| 199 | tracker.trackFieldWithSize("type", type_.size()); |
| 200 | tracker.trackFieldWithSize("subtype", subtype_.size()); |
| 201 | tracker.trackFieldWithSize("params", params_.size()); |
| 202 | } |
| 203 | |
| 204 | private: |
| 205 | kj::String type_; |
| 206 | kj::String subtype_; |
| 207 | MimeParams params_; |
| 208 | |
| 209 | using ToStringBuffer = StringBuffer<128>; |
| 210 | // 128 bytes will keep all reasonable mimetypes on the stack. |
| 211 | |
| 212 | void paramsToString(ToStringBuffer& buffer) const; |
| 213 | }; |
| 214 | |
| 215 | inline ConstMimeType::operator MimeType() const { |
| 216 | return MimeType(type_, subtype_); |
| 217 | } |
| 218 | |
| 219 | inline MimeType ConstMimeType::clone() const { |
| 220 | return *this; |
| 221 | } |
| 222 | |
| 223 | inline bool ConstMimeType::operator==(const MimeType& other) const { |
| 224 | return type_ == other.type() && subtype_ == other.subtype(); |
| 225 | } |
| 226 | |
| 227 | kj::String KJ_STRINGIFY(const MimeType& state); |
| 228 | kj::String KJ_STRINGIFY(const ConstMimeType& state); |
| 229 | |
| 230 | } // namespace workerd |