// Copyright (c) 2017-2022 Cloudflare, Inc. // Licensed under the Apache 2.0 license found in the LICENSE file or at: // https://opensource.org/licenses/Apache-2.0 // // Adapted from Deno and Node.js: // Copyright 2018-2022 the Deno authors. All rights reserved. MIT license. // // Adapted from Node.js. Copyright Joyent, Inc. and other Node contributors. // // Permission is hereby granted, free of charge, to any person obtaining a // copy of this software and associated documentation files (the // "Software"), to deal in the Software without restriction, including // without limitation the rights to use, copy, modify, merge, publish, // distribute, sublicense, and/or sell copies of the Software, and to permit // persons to whom the Software is furnished to do so, subject to the // following conditions: // // The above copyright notice and this permission notice shall be included // in all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS // OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN // NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, // DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR // OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE // USE OR OTHER DEALINGS IN THE SOFTWARE. import { default as bufferUtil } from 'node-internal:buffer'; import type { Encoding } from 'node-internal:buffer'; import { validateFunction, validateString } from 'node-internal:validators'; import { ERR_FALSY_VALUE_REJECTION, type Falsy, } from 'node-internal:internal_errors'; const { UTF8, UTF16LE, HEX, ASCII, BASE64, BASE64URL, LATIN1 } = bufferUtil; export function normalizeEncoding(enc?: string): Encoding | undefined { if ( enc == null || enc === 'utf8' || enc === 'utf-8' || enc === 'UTF8' || enc === 'UTF-8' ) return UTF8; return getEncodingOps(enc); } export function getEncodingOps(enc: unknown): Encoding | undefined { if (enc === undefined) return UTF8; // eslint-disable-next-line @typescript-eslint/no-base-to-string let encoding = `${enc}`; switch (encoding.length) { case 4: if (encoding === 'UTF8') return UTF8; if (encoding === 'ucs2' || encoding === 'UCS2') return UTF16LE; encoding = encoding.toLowerCase(); if (encoding === 'utf8') return UTF8; if (encoding === 'ucs2') return UTF16LE; break; case 3: if ( encoding === 'hex' || encoding === 'HEX' || encoding.toLowerCase() === 'hex' ) { return HEX; } break; case 5: if (encoding === 'ascii') return ASCII; if (encoding === 'ucs-2') return UTF16LE; if (encoding === 'UTF-8') return UTF8; if (encoding === 'ASCII') return ASCII; if (encoding === 'UCS-2') return UTF16LE; encoding = encoding.toLowerCase(); if (encoding === 'utf-8') return UTF8; if (encoding === 'ascii') return ASCII; if (encoding === 'ucs-2') return UTF16LE; break; case 6: if (encoding === 'base64') return BASE64; if (encoding === 'latin1' || encoding === 'binary') return LATIN1; if (encoding === 'BASE64') return BASE64; if (encoding === 'LATIN1' || encoding === 'BINARY') return LATIN1; encoding = encoding.toLowerCase(); if (encoding === 'base64') return BASE64; if (encoding === 'latin1' || encoding === 'binary') return LATIN1; break; case 7: if ( encoding === 'utf16le' || encoding === 'UTF16LE' || encoding.toLowerCase() === 'utf16le' ) { return UTF16LE; } break; case 8: if ( encoding === 'utf-16le' || encoding === 'UTF-16LE' || encoding.toLowerCase() === 'utf-16le' ) { return UTF16LE; } break; case 9: if ( encoding === 'base64url' || encoding === 'BASE64URL' || encoding.toLowerCase() === 'base64url' ) { return BASE64URL; } break; default: if (encoding === '') return UTF8; } return undefined; } export function spliceOne(list: unknown[], index: number): void { for (; index + 1 < list.length; index++) list[index] = list[index + 1]; list.pop(); } export const ALL_PROPERTIES = 0; export const ONLY_WRITABLE = 1; export const ONLY_ENUMERABLE = 2; export const ONLY_CONFIGURABLE = 4; export const ONLY_ENUM_WRITABLE = 6; export const SKIP_STRINGS = 8; export const SKIP_SYMBOLS = 16; const isNumericLookup: Record = {}; export function isArrayIndex(value: unknown): value is number | string { switch (typeof value) { case 'number': return value >= 0 && (value | 0) === value; case 'string': { const result = isNumericLookup[value]; if (result !== void 0) { return result; } const length = value.length; if (length === 0) { return (isNumericLookup[value] = false); } let ch = 0; let i = 0; for (; i < length; ++i) { ch = value.charCodeAt(i); if ( (i === 0 && ch === 0x30 && length > 1) /* must not start with 0 */ || ch < 0x30 /* 0 */ || ch > 0x39 /* 9 */ ) { return (isNumericLookup[value] = false); } } return (isNumericLookup[value] = true); } default: return false; } } export function getOwnNonIndexProperties( // deno-lint-ignore ban-types obj: object, filter: number ): (string | symbol)[] { let allProperties = [ ...Object.getOwnPropertyNames(obj), ...Object.getOwnPropertySymbols(obj), ]; if (Array.isArray(obj)) { allProperties = allProperties.filter((k) => !isArrayIndex(k)); } if (filter === ALL_PROPERTIES) { return allProperties; } const result: (string | symbol)[] = []; for (const key of allProperties) { const desc = Object.getOwnPropertyDescriptor(obj, key); if (desc === undefined) { continue; } if (filter & ONLY_WRITABLE && !desc.writable) { continue; } if (filter & ONLY_ENUMERABLE && !desc.enumerable) { continue; } if (filter & ONLY_CONFIGURABLE && !desc.configurable) { continue; } if (filter & SKIP_STRINGS && typeof key === 'string') { continue; } if (filter & SKIP_SYMBOLS && typeof key === 'symbol') { continue; } result.push(key); } return result; } function callbackifyOnRejected( reason: unknown, cb: (error?: unknown) => void ): void { // `!reason` guard inspired by bluebird (https://github.com/petkaantonov/bluebird/blob/2207fae3572f03b089bc92d3a6cefdd278cff7ab/src/nodeify.js#L30-L43). // Because `null` is a special error value in callbacks which means "no error // occurred", we error-wrap so the callback consumer can distinguish between // "the promise rejected with null" or "the promise fulfilled with undefined". if (!reason) { reason = new ERR_FALSY_VALUE_REJECTION(reason as Falsy); } cb(reason); } // Types are taken from @types/node package // https://github.com/DefinitelyTyped/DefinitelyTyped/blob/dccb0e78c4d3265ae06985789156451bd73312c0/types/node/util.d.ts#L1054 export function callbackify( fn: () => Promise ): (callback: (err: Error) => void) => void; export function callbackify( fn: () => Promise ): (callback: (err: Error, result: TResult) => void) => void; export function callbackify( fn: (arg1: T1) => Promise ): (arg1: T1, callback: (err: Error) => void) => void; export function callbackify( fn: (arg1: T1) => Promise ): (arg1: T1, callback: (err: Error, result: TResult) => void) => void; export function callbackify( fn: (arg1: T1, arg2: T2) => Promise ): (arg1: T1, arg2: T2, callback: (err: Error) => void) => void; export function callbackify( fn: (arg1: T1, arg2: T2) => Promise ): ( arg1: T1, arg2: T2, callback: (err: Error | null, result: TResult) => void ) => void; export function callbackify( fn: (arg1: T1, arg2: T2, arg3: T3) => Promise ): (arg1: T1, arg2: T2, arg3: T3, callback: (err: Error) => void) => void; export function callbackify( fn: (arg1: T1, arg2: T2, arg3: T3) => Promise ): ( arg1: T1, arg2: T2, arg3: T3, callback: (err: Error | null, result: TResult) => void ) => void; export function callbackify( fn: (arg1: T1, arg2: T2, arg3: T3, arg4: T4) => Promise ): ( arg1: T1, arg2: T2, arg3: T3, arg4: T4, callback: (err: Error) => void ) => void; export function callbackify( fn: (arg1: T1, arg2: T2, arg3: T3, arg4: T4) => Promise ): ( arg1: T1, arg2: T2, arg3: T3, arg4: T4, callback: (err: Error | null, result: TResult) => void ) => void; export function callbackify( fn: (arg1: T1, arg2: T2, arg3: T3, arg4: T4, arg5: T5) => Promise ): ( arg1: T1, arg2: T2, arg3: T3, arg4: T4, arg5: T5, callback: (err: Error) => void ) => void; export function callbackify( fn: (arg1: T1, arg2: T2, arg3: T3, arg4: T4, arg5: T5) => Promise ): ( arg1: T1, arg2: T2, arg3: T3, arg4: T4, arg5: T5, callback: (err: Error | null, result: TResult) => void ) => void; export function callbackify( fn: ( arg1: T1, arg2: T2, arg3: T3, arg4: T4, arg5: T5, arg6: T6 ) => Promise ): ( arg1: T1, arg2: T2, arg3: T3, arg4: T4, arg5: T5, arg6: T6, callback: (err: Error) => void ) => void; export function callbackify( fn: ( arg1: T1, arg2: T2, arg3: T3, arg4: T4, arg5: T5, arg6: T6 ) => Promise ): ( arg1: T1, arg2: T2, arg3: T3, arg4: T4, arg5: T5, arg6: T6, callback: (err: Error | null, result: TResult) => void ) => void; export function callbackify Promise>( original: T ): T extends (...args: infer TArgs) => Promise ? (...params: [...TArgs, (err: Error, ret: TReturn) => unknown]) => void : never { validateFunction(original, 'original'); function callbackified( this: unknown, ...args: [...unknown[], (err: unknown, ret: unknown) => void] ): void { const maybeCb = args.pop(); validateFunction(maybeCb, 'last argument'); const cb = maybeCb.bind(this); Reflect.apply(original, this, args).then( (ret: unknown) => { queueMicrotask(() => cb(null, ret)); }, (rej: unknown) => { queueMicrotask(() => { callbackifyOnRejected(rej, cb); }); } ); } const descriptors = Object.getOwnPropertyDescriptors(original); if (typeof descriptors.length?.value === 'number') { descriptors.length.value++; } if (typeof descriptors.name?.value === 'string') { descriptors.name.value += 'Callbackified'; } const propertiesValues = Object.values(descriptors); for (let i = 0; i < propertiesValues.length; i++) { Object.setPrototypeOf(propertiesValues[i], null); } Object.defineProperties(callbackified, descriptors); // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-expect-error return callbackified; } export function parseEnv(content: string): Record { validateString(content, 'content'); const result: Record = {}; const lines = content.split('\n'); for (let i = 0; i < lines.length; i++) { let line = lines[i]; if (line === undefined) continue; if (!line.trim()) continue; if (line.trimStart().startsWith('#')) continue; if (line.trimStart().startsWith('export ')) line = line.substring(line.indexOf('export ') + 7); const equalIndex = line.indexOf('='); if (equalIndex === -1) continue; const key = line.substring(0, equalIndex).trim(); if (!key) continue; let value = line.substring(equalIndex + 1).trimStart(); if (value.length > 0) { const maybeQuote = value[0]; if (maybeQuote === '"' || maybeQuote === "'" || maybeQuote === '`') { // Check if the closing quote is on the same line const closeIndex = value.indexOf(maybeQuote, 1); if (closeIndex !== -1) { // Found closing quote on same line value = value.substring(1, closeIndex); // Only handle escape sequences for double quotes if (maybeQuote === '"') value = value.replace(/\\n/g, '\n'); // For single quotes and backticks, keep \n as literal } else { // Check for multiline strings let fullValue = value.substring(1); // Remove opening quote let currentLine = i; let foundClosingQuote = false; // Look for closing quote in subsequent lines while (currentLine < lines.length - 1) { currentLine++; const nextLine = lines[currentLine]; if (nextLine !== undefined) { const closeInNextLine = nextLine.indexOf(maybeQuote); if (closeInNextLine !== -1) { // Found closing quote fullValue += '\n' + nextLine.substring(0, closeInNextLine); value = fullValue; // Only handle escape sequences for double quotes if (maybeQuote === '"') { value = value.replace(/\\n/g, '\n'); } foundClosingQuote = true; i = currentLine; // Update line counter break; } else { // Continue building multiline value fullValue += '\n' + nextLine; } } } if (!foundClosingQuote) { if (value.length === 1) { // Just the quote character, return it as the value value = maybeQuote; } else { // Return content after the opening quote value = value.substring(1); } } } } else { const hashIndex = value.indexOf('#'); if (hashIndex !== -1) value = value.substring(0, hashIndex); value = value.trimEnd(); } } result[key] = value; } return result; } export type NonEmptyArray = [T, ...T[]]; export type PositiveInteger = T extends 0 ? never : `${T}` extends `${infer _}.${infer _}` ? never : `${T}` extends `-${infer _}` ? never : T; export type FixedLengthArray< T, Length extends number, Accumulator extends NonEmptyArray = [T], > = Length extends PositiveInteger ? number extends Length ? NonEmptyArray : Length extends Accumulator['length'] ? Accumulator : FixedLengthArray : never;