| |
| |
| |
| |
| |
|
|
| import { Uint8Array_ } from './webauthn.dom' |
|
|
| |
| |
| |
| |
| const TO_BASE64URL = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_'.split('') |
|
|
| |
| |
| |
| |
| const IGNORE_BASE64URL = ' \t\n\r='.split('') |
|
|
| |
| |
| |
| |
| const FROM_BASE64URL = (() => { |
| const charMap: number[] = new Array(128) |
|
|
| for (let i = 0; i < charMap.length; i += 1) { |
| charMap[i] = -1 |
| } |
|
|
| for (let i = 0; i < IGNORE_BASE64URL.length; i += 1) { |
| charMap[IGNORE_BASE64URL[i].charCodeAt(0)] = -2 |
| } |
|
|
| for (let i = 0; i < TO_BASE64URL.length; i += 1) { |
| charMap[TO_BASE64URL[i].charCodeAt(0)] = i |
| } |
|
|
| return charMap |
| })() |
|
|
| |
| |
| |
| |
| |
| |
| |
| export function byteToBase64URL( |
| byte: number | null, |
| state: { queue: number; queuedBits: number }, |
| emit: (char: string) => void |
| ) { |
| if (byte !== null) { |
| state.queue = (state.queue << 8) | byte |
| state.queuedBits += 8 |
|
|
| while (state.queuedBits >= 6) { |
| const pos = (state.queue >> (state.queuedBits - 6)) & 63 |
| emit(TO_BASE64URL[pos]) |
| state.queuedBits -= 6 |
| } |
| } else if (state.queuedBits > 0) { |
| state.queue = state.queue << (6 - state.queuedBits) |
| state.queuedBits = 6 |
|
|
| while (state.queuedBits >= 6) { |
| const pos = (state.queue >> (state.queuedBits - 6)) & 63 |
| emit(TO_BASE64URL[pos]) |
| state.queuedBits -= 6 |
| } |
| } |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| export function byteFromBase64URL( |
| charCode: number, |
| state: { queue: number; queuedBits: number }, |
| emit: (byte: number) => void |
| ) { |
| const bits = FROM_BASE64URL[charCode] |
|
|
| if (bits > -1) { |
| |
| state.queue = (state.queue << 6) | bits |
| state.queuedBits += 6 |
|
|
| while (state.queuedBits >= 8) { |
| emit((state.queue >> (state.queuedBits - 8)) & 0xff) |
| state.queuedBits -= 8 |
| } |
| } else if (bits === -2) { |
| |
| return |
| } else { |
| throw new Error(`Invalid Base64-URL character "${String.fromCharCode(charCode)}"`) |
| } |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| export function stringToBase64URL(str: string) { |
| const base64: string[] = [] |
|
|
| const emitter = (char: string) => { |
| base64.push(char) |
| } |
|
|
| const state = { queue: 0, queuedBits: 0 } |
|
|
| stringToUTF8(str, (byte: number) => { |
| byteToBase64URL(byte, state, emitter) |
| }) |
|
|
| byteToBase64URL(null, state, emitter) |
|
|
| return base64.join('') |
| } |
|
|
| |
| |
| |
| |
| |
| |
| export function stringFromBase64URL(str: string) { |
| const conv: string[] = [] |
|
|
| const utf8Emit = (codepoint: number) => { |
| conv.push(String.fromCodePoint(codepoint)) |
| } |
|
|
| const utf8State = { |
| utf8seq: 0, |
| codepoint: 0, |
| } |
|
|
| const b64State = { queue: 0, queuedBits: 0 } |
|
|
| const byteEmit = (byte: number) => { |
| stringFromUTF8(byte, utf8State, utf8Emit) |
| } |
|
|
| for (let i = 0; i < str.length; i += 1) { |
| byteFromBase64URL(str.charCodeAt(i), b64State, byteEmit) |
| } |
|
|
| return conv.join('') |
| } |
|
|
| |
| |
| |
| |
| |
| |
| export function codepointToUTF8(codepoint: number, emit: (byte: number) => void) { |
| if (codepoint <= 0x7f) { |
| emit(codepoint) |
| return |
| } else if (codepoint <= 0x7ff) { |
| emit(0xc0 | (codepoint >> 6)) |
| emit(0x80 | (codepoint & 0x3f)) |
| return |
| } else if (codepoint <= 0xffff) { |
| emit(0xe0 | (codepoint >> 12)) |
| emit(0x80 | ((codepoint >> 6) & 0x3f)) |
| emit(0x80 | (codepoint & 0x3f)) |
| return |
| } else if (codepoint <= 0x10ffff) { |
| emit(0xf0 | (codepoint >> 18)) |
| emit(0x80 | ((codepoint >> 12) & 0x3f)) |
| emit(0x80 | ((codepoint >> 6) & 0x3f)) |
| emit(0x80 | (codepoint & 0x3f)) |
| return |
| } |
|
|
| throw new Error(`Unrecognized Unicode codepoint: ${codepoint.toString(16)}`) |
| } |
|
|
| |
| |
| |
| |
| |
| |
| export function stringToUTF8(str: string, emit: (byte: number) => void) { |
| for (let i = 0; i < str.length; i += 1) { |
| let codepoint = str.charCodeAt(i) |
|
|
| if (codepoint > 0xd7ff && codepoint <= 0xdbff) { |
| |
| |
| |
| const highSurrogate = ((codepoint - 0xd800) * 0x400) & 0xffff |
| const lowSurrogate = (str.charCodeAt(i + 1) - 0xdc00) & 0xffff |
| codepoint = (lowSurrogate | highSurrogate) + 0x10000 |
| i += 1 |
| } |
|
|
| codepointToUTF8(codepoint, emit) |
| } |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| export function stringFromUTF8( |
| byte: number, |
| state: { utf8seq: number; codepoint: number }, |
| emit: (codepoint: number) => void |
| ) { |
| if (state.utf8seq === 0) { |
| if (byte <= 0x7f) { |
| emit(byte) |
| return |
| } |
|
|
| |
| for (let leadingBit = 1; leadingBit < 6; leadingBit += 1) { |
| if (((byte >> (7 - leadingBit)) & 1) === 0) { |
| state.utf8seq = leadingBit |
| break |
| } |
| } |
|
|
| if (state.utf8seq === 2) { |
| state.codepoint = byte & 31 |
| } else if (state.utf8seq === 3) { |
| state.codepoint = byte & 15 |
| } else if (state.utf8seq === 4) { |
| state.codepoint = byte & 7 |
| } else { |
| throw new Error('Invalid UTF-8 sequence') |
| } |
|
|
| state.utf8seq -= 1 |
| } else if (state.utf8seq > 0) { |
| if (byte <= 0x7f) { |
| throw new Error('Invalid UTF-8 sequence') |
| } |
|
|
| state.codepoint = (state.codepoint << 6) | (byte & 63) |
| state.utf8seq -= 1 |
|
|
| if (state.utf8seq === 0) { |
| emit(state.codepoint) |
| } |
| } |
| } |
|
|
| |
| |
| |
|
|
| export function base64UrlToUint8Array(str: string): Uint8Array_ { |
| const result: number[] = [] |
| const state = { queue: 0, queuedBits: 0 } |
|
|
| const onByte = (byte: number) => { |
| result.push(byte) |
| } |
|
|
| for (let i = 0; i < str.length; i += 1) { |
| byteFromBase64URL(str.charCodeAt(i), state, onByte) |
| } |
|
|
| return new Uint8Array(result) |
| } |
|
|
| export function stringToUint8Array(str: string): Uint8Array_ { |
| const result: number[] = [] |
| stringToUTF8(str, (byte: number) => result.push(byte)) |
| return new Uint8Array(result) |
| } |
|
|
| export function bytesToBase64URL(bytes: Uint8Array) { |
| const result: string[] = [] |
| const state = { queue: 0, queuedBits: 0 } |
|
|
| const onChar = (char: string) => { |
| result.push(char) |
| } |
|
|
| bytes.forEach((byte) => byteToBase64URL(byte, state, onChar)) |
|
|
| |
| byteToBase64URL(null, state, onChar) |
|
|
| return result.join('') |
| } |
|
|