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                        21x 21x                 21x                                             30x 30x   30x 21x     9x                           30x 30x   30x 2x     28x   28x 58x 58x       58x   58x           58x     58x 58x     58x     28x             58x 58x             58x 58x             116x 116x   116x 146x 146x 116x       116x 116x 116x 115x   1x     116x    
/**
 * @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)
}