All files / worker cookies.ts

92.85% Statements 39/42
81.81% Branches 18/22
100% Functions 7/7
92.85% Lines 39/42

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143                        20x 20x                 20x                                             29x 29x   29x 20x     9x                           29x 29x   29x 2x     27x   27x 55x 55x       55x   55x           55x     55x 55x     55x     27x             55x 55x             55x 55x             110x 110x   110x 138x 138x 110x       110x 110x 110x 109x   1x     110x    
/**
 * @fileoverview
 *
 * Implements utility functions for working with cookies.
 * Based on "cookie" library: https://github.com/jshttp/cookie
 * */
 
/**
 * Represents a cookie with a name and value.
 */
class Cookie {
  constructor(
    public readonly name: string,
    public readonly value: string
  ) {}
 
  /**
   * Converts the cookie's name and value into a string formatted as a valid cookie.
   *
   * @return {string} A string representation of the cookie in "name=value" format.
   */
  toCookieString(): string {
    return `${this.name}=${this.value}`
  }
}
 
/**
 * Finds and extracts a specific cookie from a cookie header string.
 *
 * Searches for a cookie with the specified name in the provided cookie string
 * and returns the complete cookie key-value pair (name=value) if found.
 *
 * @param cookies - The cookie header string containing one or more cookies separated by semicolons
 * @param name - The name of the cookie to search for
 * @returns The complete cookie string (name=value) if found, undefined otherwise
 *
 * @example
 * findCookie('sessionId=abc123; _iidt=xyz789; theme=dark', '_iidt')
 * // returns '_iidt=xyz789'
 *
 * @example
 * findCookie('sessionId=abc123; theme=dark', 'nonexistent')
 * // returns undefined
 */
export function findCookie(cookies: string, name: string): string | undefined {
  const parsedCookies = parseCookies(cookies)
  const value = parsedCookies.get(name)
 
  if (typeof value === 'string') {
    return new Cookie(name, value).toCookieString()
  }
 
  return undefined
}
/**
 * Cookies object.
 */
export type Cookies = Map<string, string | undefined>
 
/**
 * Parse a `Cookie` header.
 *
 * Parse the given cookie header string into an object
 * The object has the various cookies as keys(names) => values
 */
export function parseCookies(str: string): Cookies {
  const obj: Cookies = new Map()
  const len = str.length
  // RFC 6265 sec 4.1.1, RFC 2616 2.2 defines a cookie name consists of one char minimum, plus '='.
  if (len < 2) {
    return obj
  }
 
  let index = 0
 
  do {
    const eqIdx = eqIndex(str, index, len)
    Iif (eqIdx === -1) {
      break
    } // No more cookie pairs.
 
    const endIdx = endIndex(str, index, len)
 
    Iif (eqIdx > endIdx) {
      // backtrack on prior semicolon
      index = str.lastIndexOf(';', eqIdx - 1) + 1
      continue
    }
 
    const key = valueSlice(str, index, eqIdx)
 
    // only assign once
    Eif (obj.get(key) === undefined) {
      obj.set(key, valueSlice(str, eqIdx + 1, endIdx))
    }
 
    index = endIdx + 1
  } while (index < len)
 
  return obj
}
 
/**
 * Find the `;` character between `min` and `len` in str.
 */
function endIndex(str: string, min: number, len: number) {
  const index = str.indexOf(';', min)
  return index === -1 ? len : index
}
 
/**
 * Find the `=` character between `min` and `max` in str.
 */
function eqIndex(str: string, min: number, max: number) {
  const index = str.indexOf('=', min)
  return index < max ? index : -1
}
 
/**
 * Slice out a value between startPod to max.
 */
function valueSlice(str: string, min: number, max: number) {
  let start = min
  let end = max
 
  do {
    const code = str.charCodeAt(start)
    if (code !== 0x20 /*   */ && code !== 0x09 /* \t */) {
      break
    }
  } while (++start < end)
 
  while (end > start) {
    const code = str.charCodeAt(end - 1)
    if (code !== 0x20 /*   */ && code !== 0x09 /* \t */) {
      break
    }
    end--
  }
 
  return str.slice(start, end)
}