jpayne@69: // Copyright (c) 2017 Cloudflare, Inc. and contributors jpayne@69: // Licensed under the MIT License: jpayne@69: // jpayne@69: // Permission is hereby granted, free of charge, to any person obtaining a copy jpayne@69: // of this software and associated documentation files (the "Software"), to deal jpayne@69: // in the Software without restriction, including without limitation the rights jpayne@69: // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell jpayne@69: // copies of the Software, and to permit persons to whom the Software is jpayne@69: // furnished to do so, subject to the following conditions: jpayne@69: // jpayne@69: // The above copyright notice and this permission notice shall be included in jpayne@69: // all copies or substantial portions of the Software. jpayne@69: // jpayne@69: // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR jpayne@69: // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, jpayne@69: // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE jpayne@69: // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER jpayne@69: // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, jpayne@69: // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN jpayne@69: // THE SOFTWARE. jpayne@69: jpayne@69: #pragma once jpayne@69: // Functions for encoding/decoding bytes and text in common formats, including: jpayne@69: // - UTF-{8,16,32} jpayne@69: // - Hex jpayne@69: // - URI encoding jpayne@69: // - Base64 jpayne@69: jpayne@69: #include "string.h" jpayne@69: jpayne@69: KJ_BEGIN_HEADER jpayne@69: jpayne@69: namespace kj { jpayne@69: jpayne@69: template jpayne@69: struct EncodingResult: public ResultType { jpayne@69: // Equivalent to ResultType (a String or wide-char array) for all intents and purposes, except jpayne@69: // that the bool `hadErrors` can be inspected to see if any errors were encountered in the input. jpayne@69: // Each encoding/decoding function that returns this type will "work around" errors in some way, jpayne@69: // so an application doesn't strictly have to check for errors. E.g. the Unicode functions jpayne@69: // replace errors with U+FFFD in the output. jpayne@69: // jpayne@69: // Through magic, KJ_IF_MAYBE() and KJ_{REQUIRE,ASSERT}_NONNULL() work on EncodingResult jpayne@69: // exactly if it were a Maybe that is null in case of errors. jpayne@69: jpayne@69: inline EncodingResult(ResultType&& result, bool hadErrors) jpayne@69: : ResultType(kj::mv(result)), hadErrors(hadErrors) {} jpayne@69: jpayne@69: const bool hadErrors; jpayne@69: }; jpayne@69: jpayne@69: template jpayne@69: inline auto KJ_STRINGIFY(const EncodingResult& value) jpayne@69: -> decltype(toCharSequence(implicitCast(value))) { jpayne@69: return toCharSequence(implicitCast(value)); jpayne@69: } jpayne@69: jpayne@69: EncodingResult> encodeUtf16(ArrayPtr text, bool nulTerminate = false); jpayne@69: EncodingResult> encodeUtf32(ArrayPtr text, bool nulTerminate = false); jpayne@69: // Convert UTF-8 text (which KJ strings use) to UTF-16 or UTF-32. jpayne@69: // jpayne@69: // If `nulTerminate` is true, an extra NUL character will be added to the end of the output. jpayne@69: // jpayne@69: // The returned arrays are in platform-native endianness (otherwise they wouldn't really be jpayne@69: // char16_t / char32_t). jpayne@69: // jpayne@69: // Note that the KJ Unicode encoding and decoding functions actually implement jpayne@69: // [WTF-8 encoding](http://simonsapin.github.io/wtf-8/), which affects how invalid input is jpayne@69: // handled. See comments on decodeUtf16() for more info. jpayne@69: jpayne@69: EncodingResult decodeUtf16(ArrayPtr utf16); jpayne@69: EncodingResult decodeUtf32(ArrayPtr utf32); jpayne@69: // Convert UTF-16 or UTF-32 to UTF-8 (which KJ strings use). jpayne@69: // jpayne@69: // The input should NOT include a NUL terminator; any NUL characters in the input array will be jpayne@69: // preserved in the output. jpayne@69: // jpayne@69: // The input must be in platform-native endianness. BOMs are NOT recognized by these functions. jpayne@69: // jpayne@69: // Note that the KJ Unicode encoding and decoding functions actually implement jpayne@69: // [WTF-8 encoding](http://simonsapin.github.io/wtf-8/). This means that if you start with an array jpayne@69: // of char16_t and you pass it through any number of conversions to other Unicode encodings, jpayne@69: // eventually returning it to UTF-16, all the while ignoring `hadErrors`, you will end up with jpayne@69: // exactly the same char16_t array you started with, *even if* the array is not valid UTF-16. This jpayne@69: // is useful because many real-world systems that were designed for UCS-2 (plain 16-bit Unicode) jpayne@69: // and later "upgraded" to UTF-16 do not enforce that their UTF-16 is well-formed. For example, jpayne@69: // file names on Windows NT are encoded using 16-bit characters, without enforcing that the jpayne@69: // character sequence is valid UTF-16. It is important that programs on Windows be able to handle jpayne@69: // such filenames, even if they choose to convert the name to UTF-8 for internal processing. jpayne@69: // jpayne@69: // Specifically, KJ's Unicode handling allows unpaired surrogate code points to round-trip through jpayne@69: // UTF-8 and UTF-32. Unpaired surrogates will be flagged as an error (setting `hadErrors` in the jpayne@69: // result), but will NOT be replaced with the Unicode replacement character as other erroneous jpayne@69: // sequences would be, but rather encoded as an invalid surrogate codepoint in the target encoding. jpayne@69: // jpayne@69: // KJ makes the following guarantees about invalid input: jpayne@69: // - A round trip from UTF-16 to other encodings and back will produce exactly the original input, jpayne@69: // with every leg of the trip raising the `hadErrors` flag if the original input was not valid. jpayne@69: // - A round trip from UTF-8 or UTF-32 to other encodings and back will either produce exactly jpayne@69: // the original input, or will have replaced some invalid sequences with the Unicode replacement jpayne@69: // character, U+FFFD. No code units will ever be removed unless they are replaced with U+FFFD, jpayne@69: // and no code units will ever be added except to encode U+FFFD. If the original input was not jpayne@69: // valid, the `hadErrors` flag will be raised on the first leg of the trip, and will also be jpayne@69: // raised on subsequent legs unless all invalid sequences were replaced with U+FFFD (which, after jpayne@69: // all, is a valid code point). jpayne@69: jpayne@69: EncodingResult> encodeWideString( jpayne@69: ArrayPtr text, bool nulTerminate = false); jpayne@69: EncodingResult decodeWideString(ArrayPtr wide); jpayne@69: // Encode / decode strings of wchar_t, aka "wide strings". Unfortunately, different platforms have jpayne@69: // different definitions for wchar_t. For example, on Windows they are 16-bit and encode UTF-16, jpayne@69: // but on Linux they are 32-bit and encode UTF-32. Some platforms even define wchar_t as 8-bit, jpayne@69: // encoding UTF-8 (e.g. BeOS did this). jpayne@69: // jpayne@69: // KJ assumes that wide strings use the UTF encoding that corresponds to the size of wchar_t on jpayne@69: // the target platform. So, these functions are simple aliases for encodeUtf*/decodeUtf*, above jpayne@69: // (or simply make a copy if wchar_t is 8 bits). jpayne@69: jpayne@69: String encodeHex(ArrayPtr bytes); jpayne@69: EncodingResult> decodeHex(ArrayPtr text); jpayne@69: // Encode/decode bytes as hex strings. jpayne@69: jpayne@69: String encodeUriComponent(ArrayPtr bytes); jpayne@69: String encodeUriComponent(ArrayPtr bytes); jpayne@69: EncodingResult decodeUriComponent(ArrayPtr text); jpayne@69: // Encode/decode URI components using % escapes for characters listed as "reserved" in RFC 2396. jpayne@69: // This is the same behavior as JavaScript's `encodeURIComponent()`. jpayne@69: // jpayne@69: // See https://tools.ietf.org/html/rfc2396#section-2.3 jpayne@69: jpayne@69: String encodeUriFragment(ArrayPtr bytes); jpayne@69: String encodeUriFragment(ArrayPtr bytes); jpayne@69: // Encode URL fragment components using the fragment percent encode set defined by the WHATWG URL jpayne@69: // specification. Use decodeUriComponent() to decode. jpayne@69: // jpayne@69: // Quirk: We also percent-encode the '%' sign itself, because we expect to be called on percent- jpayne@69: // decoded data. In other words, this function is not idempotent, in contrast to the URL spec. jpayne@69: // jpayne@69: // See https://url.spec.whatwg.org/#fragment-percent-encode-set jpayne@69: jpayne@69: String encodeUriPath(ArrayPtr bytes); jpayne@69: String encodeUriPath(ArrayPtr bytes); jpayne@69: // Encode URL path components (not entire paths!) using the path percent encode set defined by the jpayne@69: // WHATWG URL specification. Use decodeUriComponent() to decode. jpayne@69: // jpayne@69: // Quirk: We also percent-encode the '%' sign itself, because we expect to be called on percent- jpayne@69: // decoded data. In other words, this function is not idempotent, in contrast to the URL spec. jpayne@69: // jpayne@69: // Quirk: This percent-encodes '/' and '\' characters as well, which are not actually in the set jpayne@69: // defined by the WHATWG URL spec. Since a conforming URL implementation will only ever call this jpayne@69: // function on individual path components, and never entire paths, augmenting the character set to jpayne@69: // include these separators allows this function to be used to implement a URL class that stores jpayne@69: // its path components in percent-decoded form. jpayne@69: // jpayne@69: // See https://url.spec.whatwg.org/#path-percent-encode-set jpayne@69: jpayne@69: String encodeUriUserInfo(ArrayPtr bytes); jpayne@69: String encodeUriUserInfo(ArrayPtr bytes); jpayne@69: // Encode URL userinfo components using the userinfo percent encode set defined by the WHATWG URL jpayne@69: // specification. Use decodeUriComponent() to decode. jpayne@69: // jpayne@69: // Quirk: We also percent-encode the '%' sign itself, because we expect to be called on percent- jpayne@69: // decoded data. In other words, this function is not idempotent, in contrast to the URL spec. jpayne@69: // jpayne@69: // See https://url.spec.whatwg.org/#userinfo-percent-encode-set jpayne@69: jpayne@69: String encodeWwwForm(ArrayPtr bytes); jpayne@69: String encodeWwwForm(ArrayPtr bytes); jpayne@69: EncodingResult decodeWwwForm(ArrayPtr text); jpayne@69: // Encode/decode URI components using % escapes and '+' (for spaces) according to the jpayne@69: // application/x-www-form-urlencoded format defined by the WHATWG URL specification. jpayne@69: // jpayne@69: // Note: Like the fragment, path, and userinfo percent-encoding functions above, this function is jpayne@69: // not idempotent: we percent-encode '%' signs. However, in this particular case the spec happens jpayne@69: // to agree with us! jpayne@69: // jpayne@69: // See https://url.spec.whatwg.org/#concept-urlencoded-byte-serializer jpayne@69: jpayne@69: struct DecodeUriOptions { jpayne@69: // Parameter to `decodeBinaryUriComponent()`. jpayne@69: jpayne@69: // This struct is intentionally convertible from bool, in order to maintain backwards jpayne@69: // compatibility with code written when `decodeBinaryUriComponent()` took a boolean second jpayne@69: // parameter. jpayne@69: DecodeUriOptions(bool nulTerminate = false, bool plusToSpace = false) jpayne@69: : nulTerminate(nulTerminate), plusToSpace(plusToSpace) {} jpayne@69: jpayne@69: bool nulTerminate; jpayne@69: // Append a terminal NUL byte. jpayne@69: jpayne@69: bool plusToSpace; jpayne@69: // Convert '+' to ' ' characters before percent decoding. Used to decode jpayne@69: // application/x-www-form-urlencoded text, such as query strings. jpayne@69: }; jpayne@69: EncodingResult> decodeBinaryUriComponent( jpayne@69: ArrayPtr text, DecodeUriOptions options = DecodeUriOptions()); jpayne@69: // Decode URI components using % escapes. This is a lower-level interface used to implement both jpayne@69: // `decodeUriComponent()` and `decodeWwwForm()` jpayne@69: jpayne@69: String encodeCEscape(ArrayPtr bytes); jpayne@69: String encodeCEscape(ArrayPtr bytes); jpayne@69: EncodingResult> decodeBinaryCEscape( jpayne@69: ArrayPtr text, bool nulTerminate = false); jpayne@69: EncodingResult decodeCEscape(ArrayPtr text); jpayne@69: jpayne@69: String encodeBase64(ArrayPtr bytes, bool breakLines = false); jpayne@69: // Encode the given bytes as base64 text. If `breakLines` is true, line breaks will be inserted jpayne@69: // into the output every 72 characters (e.g. for encoding e-mail bodies). jpayne@69: jpayne@69: EncodingResult> decodeBase64(ArrayPtr text); jpayne@69: // Decode base64 text. This function reports errors required by the WHATWG HTML/Infra specs: see jpayne@69: // https://html.spec.whatwg.org/multipage/webappapis.html#atob for details. jpayne@69: jpayne@69: String encodeBase64Url(ArrayPtr bytes); jpayne@69: // Encode the given bytes as URL-safe base64 text. (RFC 4648, section 5) jpayne@69: jpayne@69: // ======================================================================================= jpayne@69: // inline implementation details jpayne@69: jpayne@69: namespace _ { // private jpayne@69: jpayne@69: template jpayne@69: NullableValue readMaybe(EncodingResult&& value) { jpayne@69: if (value.hadErrors) { jpayne@69: return nullptr; jpayne@69: } else { jpayne@69: return kj::mv(value); jpayne@69: } jpayne@69: } jpayne@69: jpayne@69: template jpayne@69: T* readMaybe(EncodingResult& value) { jpayne@69: if (value.hadErrors) { jpayne@69: return nullptr; jpayne@69: } else { jpayne@69: return &value; jpayne@69: } jpayne@69: } jpayne@69: jpayne@69: template jpayne@69: const T* readMaybe(const EncodingResult& value) { jpayne@69: if (value.hadErrors) { jpayne@69: return nullptr; jpayne@69: } else { jpayne@69: return &value; jpayne@69: } jpayne@69: } jpayne@69: jpayne@69: String encodeCEscapeImpl(ArrayPtr bytes, bool isBinary); jpayne@69: jpayne@69: } // namespace _ (private) jpayne@69: jpayne@69: inline String encodeUriComponent(ArrayPtr text) { jpayne@69: return encodeUriComponent(text.asBytes()); jpayne@69: } jpayne@69: inline EncodingResult decodeUriComponent(ArrayPtr text) { jpayne@69: auto result = decodeBinaryUriComponent(text, DecodeUriOptions { /*.nulTerminate=*/true }); jpayne@69: return { String(result.releaseAsChars()), result.hadErrors }; jpayne@69: } jpayne@69: jpayne@69: inline String encodeUriFragment(ArrayPtr text) { jpayne@69: return encodeUriFragment(text.asBytes()); jpayne@69: } jpayne@69: inline String encodeUriPath(ArrayPtr text) { jpayne@69: return encodeUriPath(text.asBytes()); jpayne@69: } jpayne@69: inline String encodeUriUserInfo(ArrayPtr text) { jpayne@69: return encodeUriUserInfo(text.asBytes()); jpayne@69: } jpayne@69: jpayne@69: inline String encodeWwwForm(ArrayPtr text) { jpayne@69: return encodeWwwForm(text.asBytes()); jpayne@69: } jpayne@69: inline EncodingResult decodeWwwForm(ArrayPtr text) { jpayne@69: auto result = decodeBinaryUriComponent(text, DecodeUriOptions { /*.nulTerminate=*/true, jpayne@69: /*.plusToSpace=*/true }); jpayne@69: return { String(result.releaseAsChars()), result.hadErrors }; jpayne@69: } jpayne@69: jpayne@69: inline String encodeCEscape(ArrayPtr text) { jpayne@69: return _::encodeCEscapeImpl(text.asBytes(), false); jpayne@69: } jpayne@69: jpayne@69: inline String encodeCEscape(ArrayPtr bytes) { jpayne@69: return _::encodeCEscapeImpl(bytes, true); jpayne@69: } jpayne@69: jpayne@69: inline EncodingResult decodeCEscape(ArrayPtr text) { jpayne@69: auto result = decodeBinaryCEscape(text, true); jpayne@69: return { String(result.releaseAsChars()), result.hadErrors }; jpayne@69: } jpayne@69: jpayne@69: // If you pass a string literal to a function taking ArrayPtr, it'll include the NUL jpayne@69: // termintator, which is surprising. Let's add overloads that avoid that. In practice this probably jpayne@69: // only even matters for encoding-test.c++. jpayne@69: jpayne@69: template jpayne@69: inline EncodingResult> encodeUtf16(const char (&text)[s], bool nulTerminate=false) { jpayne@69: return encodeUtf16(arrayPtr(text, s - 1), nulTerminate); jpayne@69: } jpayne@69: template jpayne@69: inline EncodingResult> encodeUtf32(const char (&text)[s], bool nulTerminate=false) { jpayne@69: return encodeUtf32(arrayPtr(text, s - 1), nulTerminate); jpayne@69: } jpayne@69: template jpayne@69: inline EncodingResult> encodeWideString( jpayne@69: const char (&text)[s], bool nulTerminate=false) { jpayne@69: return encodeWideString(arrayPtr(text, s - 1), nulTerminate); jpayne@69: } jpayne@69: template jpayne@69: inline EncodingResult decodeUtf16(const char16_t (&utf16)[s]) { jpayne@69: return decodeUtf16(arrayPtr(utf16, s - 1)); jpayne@69: } jpayne@69: template jpayne@69: inline EncodingResult decodeUtf32(const char32_t (&utf32)[s]) { jpayne@69: return decodeUtf32(arrayPtr(utf32, s - 1)); jpayne@69: } jpayne@69: template jpayne@69: inline EncodingResult decodeWideString(const wchar_t (&utf32)[s]) { jpayne@69: return decodeWideString(arrayPtr(utf32, s - 1)); jpayne@69: } jpayne@69: template jpayne@69: inline EncodingResult> decodeHex(const char (&text)[s]) { jpayne@69: return decodeHex(arrayPtr(text, s - 1)); jpayne@69: } jpayne@69: template jpayne@69: inline String encodeUriComponent(const char (&text)[s]) { jpayne@69: return encodeUriComponent(arrayPtr(text, s - 1)); jpayne@69: } jpayne@69: template jpayne@69: inline Array decodeBinaryUriComponent(const char (&text)[s]) { jpayne@69: return decodeBinaryUriComponent(arrayPtr(text, s - 1)); jpayne@69: } jpayne@69: template jpayne@69: inline EncodingResult decodeUriComponent(const char (&text)[s]) { jpayne@69: return decodeUriComponent(arrayPtr(text, s-1)); jpayne@69: } jpayne@69: template jpayne@69: inline String encodeUriFragment(const char (&text)[s]) { jpayne@69: return encodeUriFragment(arrayPtr(text, s - 1)); jpayne@69: } jpayne@69: template jpayne@69: inline String encodeUriPath(const char (&text)[s]) { jpayne@69: return encodeUriPath(arrayPtr(text, s - 1)); jpayne@69: } jpayne@69: template jpayne@69: inline String encodeUriUserInfo(const char (&text)[s]) { jpayne@69: return encodeUriUserInfo(arrayPtr(text, s - 1)); jpayne@69: } jpayne@69: template jpayne@69: inline String encodeWwwForm(const char (&text)[s]) { jpayne@69: return encodeWwwForm(arrayPtr(text, s - 1)); jpayne@69: } jpayne@69: template jpayne@69: inline EncodingResult decodeWwwForm(const char (&text)[s]) { jpayne@69: return decodeWwwForm(arrayPtr(text, s-1)); jpayne@69: } jpayne@69: template jpayne@69: inline String encodeCEscape(const char (&text)[s]) { jpayne@69: return encodeCEscape(arrayPtr(text, s - 1)); jpayne@69: } jpayne@69: template jpayne@69: inline EncodingResult> decodeBinaryCEscape(const char (&text)[s]) { jpayne@69: return decodeBinaryCEscape(arrayPtr(text, s - 1)); jpayne@69: } jpayne@69: template jpayne@69: inline EncodingResult decodeCEscape(const char (&text)[s]) { jpayne@69: return decodeCEscape(arrayPtr(text, s-1)); jpayne@69: } jpayne@69: template jpayne@69: EncodingResult> decodeBase64(const char (&text)[s]) { jpayne@69: return decodeBase64(arrayPtr(text, s - 1)); jpayne@69: } jpayne@69: jpayne@69: #if __cpp_char8_t jpayne@69: template jpayne@69: inline EncodingResult> encodeUtf16(const char8_t (&text)[s], bool nulTerminate=false) { jpayne@69: return encodeUtf16(arrayPtr(reinterpret_cast(text), s - 1), nulTerminate); jpayne@69: } jpayne@69: template jpayne@69: inline EncodingResult> encodeUtf32(const char8_t (&text)[s], bool nulTerminate=false) { jpayne@69: return encodeUtf32(arrayPtr(reinterpret_cast(text), s - 1), nulTerminate); jpayne@69: } jpayne@69: template jpayne@69: inline EncodingResult> encodeWideString( jpayne@69: const char8_t (&text)[s], bool nulTerminate=false) { jpayne@69: return encodeWideString(arrayPtr(reinterpret_cast(text), s - 1), nulTerminate); jpayne@69: } jpayne@69: template jpayne@69: inline EncodingResult> decodeHex(const char8_t (&text)[s]) { jpayne@69: return decodeHex(arrayPtr(reinterpret_cast(text), s - 1)); jpayne@69: } jpayne@69: template jpayne@69: inline String encodeUriComponent(const char8_t (&text)[s]) { jpayne@69: return encodeUriComponent(arrayPtr(reinterpret_cast(text), s - 1)); jpayne@69: } jpayne@69: template jpayne@69: inline Array decodeBinaryUriComponent(const char8_t (&text)[s]) { jpayne@69: return decodeBinaryUriComponent(arrayPtr(reinterpret_cast(text), s - 1)); jpayne@69: } jpayne@69: template jpayne@69: inline EncodingResult decodeUriComponent(const char8_t (&text)[s]) { jpayne@69: return decodeUriComponent(arrayPtr(reinterpret_cast(text), s-1)); jpayne@69: } jpayne@69: template jpayne@69: inline String encodeUriFragment(const char8_t (&text)[s]) { jpayne@69: return encodeUriFragment(arrayPtr(reinterpret_cast(text), s - 1)); jpayne@69: } jpayne@69: template jpayne@69: inline String encodeUriPath(const char8_t (&text)[s]) { jpayne@69: return encodeUriPath(arrayPtr(reinterpret_cast(text), s - 1)); jpayne@69: } jpayne@69: template jpayne@69: inline String encodeUriUserInfo(const char8_t (&text)[s]) { jpayne@69: return encodeUriUserInfo(arrayPtr(reinterpret_cast(text), s - 1)); jpayne@69: } jpayne@69: template jpayne@69: inline String encodeWwwForm(const char8_t (&text)[s]) { jpayne@69: return encodeWwwForm(arrayPtr(reinterpret_cast(text), s - 1)); jpayne@69: } jpayne@69: template jpayne@69: inline EncodingResult decodeWwwForm(const char8_t (&text)[s]) { jpayne@69: return decodeWwwForm(arrayPtr(reinterpret_cast(text), s-1)); jpayne@69: } jpayne@69: template jpayne@69: inline String encodeCEscape(const char8_t (&text)[s]) { jpayne@69: return encodeCEscape(arrayPtr(reinterpret_cast(text), s - 1)); jpayne@69: } jpayne@69: template jpayne@69: inline EncodingResult> decodeBinaryCEscape(const char8_t (&text)[s]) { jpayne@69: return decodeBinaryCEscape(arrayPtr(reinterpret_cast(text), s - 1)); jpayne@69: } jpayne@69: template jpayne@69: inline EncodingResult decodeCEscape(const char8_t (&text)[s]) { jpayne@69: return decodeCEscape(arrayPtr(reinterpret_cast(text), s-1)); jpayne@69: } jpayne@69: template jpayne@69: EncodingResult> decodeBase64(const char8_t (&text)[s]) { jpayne@69: return decodeBase64(arrayPtr(reinterpret_cast(text), s - 1)); jpayne@69: } jpayne@69: #endif jpayne@69: jpayne@69: } // namespace kj jpayne@69: jpayne@69: KJ_END_HEADER