LCOV - code coverage report
Current view: top level - dashql-app/src/utils - base64.ts Coverage Total Hit
Test: DashQL 915828b Lines: 91.7 % 169 155
Test Date: 2026-11-06 19:30:32

            Line data    Source code
       1            1 : export const BASE64_TABLE_DEFAULT = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
       2            1 : export const BASE64_TABLE_URL = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_";
       3              : 
       4            1 : export class Base64Codec {
       5            1 :     lookupTable: Uint8Array;
       6              : 
       7            1 :     constructor() {
       8              :         // Build a lookup query_result
       9            1 :         this.lookupTable = new Uint8Array(256);
      10            1 :         this.lookupTable.fill(0xFF);
      11            1 :         for (let i = 0; i < BASE64_TABLE_DEFAULT.length; i++) {
      12            1 :             this.lookupTable[BASE64_TABLE_DEFAULT.charCodeAt(i)] = i;
      13              :         }
      14            1 :     }
      15              : 
      16              :     /// Encode the ArrayBuffer
      17            1 :     public encode(arraybuffer: ArrayBuffer) {
      18            1 :         const bytes = new Uint8Array(arraybuffer);
      19            1 :         let base64 = "";
      20              : 
      21            1 :         let reader = 0;
      22            1 :         for (; (reader + 3) <= bytes.length; reader += 3) {
      23              :             // Upper 6 bits of first bytes
      24            1 :             base64 += BASE64_TABLE_DEFAULT[bytes[reader] >> 2];
      25              :             // Lower 2 bits of first byte and upper 4 bits of second byte
      26            1 :             base64 += BASE64_TABLE_DEFAULT[((bytes[reader] & 3) << 4) | (bytes[reader + 1] >> 4)];
      27              :             // Lower 4 bits of second byte and upper 2 bits of third byte
      28            1 :             base64 += BASE64_TABLE_DEFAULT[((bytes[reader + 1] & 15) << 2) | (bytes[reader + 2] >> 6)];
      29              :             // Lower 4 bits of third byte
      30            1 :             base64 += BASE64_TABLE_DEFAULT[bytes[reader + 2] & 63];
      31            1 :         }
      32              :         // Now map the remainder
      33            1 :         const leftOver = bytes.length - reader;
      34            1 :         switch (leftOver) {
      35              :             // 2 bytes left, pack into 3 bytes with trailing zero
      36            1 :             case 2:
      37            1 :                 base64 += BASE64_TABLE_DEFAULT[bytes[reader] >> 2];
      38            1 :                 base64 += BASE64_TABLE_DEFAULT[((bytes[reader] & 3) << 4) | (bytes[reader + 1] >> 4)];
      39            1 :                 base64 += BASE64_TABLE_DEFAULT[(bytes[reader + 1] & 15) << 2];
      40            1 :                 base64 += "=";
      41            1 :                 break;
      42              :             // 1 bytes left, pack into 2 bytes with trailing zero
      43            1 :             case 1:
      44            1 :                 base64 += BASE64_TABLE_DEFAULT[bytes[reader] >> 2];
      45            1 :                 base64 += BASE64_TABLE_DEFAULT[(bytes[reader] & 3) << 4];
      46            1 :                 base64 += "==";
      47              :                 break;
      48              :             case 0:
      49              :                 break;
      50              : 
      51            1 :         }
      52            1 :         return base64;
      53            1 :     };
      54              : 
      55              :     /// Check if the string is a valid base64 sequence
      56            1 :     public isValidBase64(base64: string) {
      57            1 :         let valid = true;
      58            1 :         let dataLength = base64.length;
      59            1 :         if (base64[base64.length - 1] === "=") {
      60            1 :             dataLength--;
      61            1 :             if (base64[base64.length - 2] === "=") {
      62            1 :                 dataLength--;
      63              :             }
      64            1 :         }
      65            1 :         for (let i = 0; i < dataLength; ++i) {
      66            1 :             const c = base64.charCodeAt(i);
      67            1 :             valid &&= this.lookupTable[c] != 0xFF;
      68            1 :         }
      69            1 :         return valid;
      70            1 :     }
      71              : 
      72              :     /// Decode a Base64 string
      73            1 :     public decode(base64: string) {
      74              :         // Cut the trailing equal signs
      75            1 :         let bufferLength = Math.floor((base64.length / 4) * 3);
      76            1 :         if (base64.length >= 1 && base64[base64.length - 1] === "=") {
      77            1 :             bufferLength--;
      78            1 :             if (base64.length >= 2 && base64[base64.length - 2] === "=") {
      79            1 :                 bufferLength--;
      80              :             }
      81            1 :         }
      82              : 
      83              :         /// Allocate the ArrayBuffer
      84            1 :         const arraybuffer = new ArrayBuffer(bufferLength),
      85            1 :             bytes = new Uint8Array(arraybuffer);
      86              : 
      87            1 :         let writer = 0;
      88            1 :         let reader = 0;
      89            1 :         for (; (reader + 4) <= base64.length; reader += 4) {
      90              :             // Read all bytes, storing 6 bits of data each
      91            1 :             const encoded1 = this.lookupTable[base64.charCodeAt(reader)];
      92            1 :             const encoded2 = this.lookupTable[base64.charCodeAt(reader + 1)];
      93            1 :             const encoded3 = this.lookupTable[base64.charCodeAt(reader + 2)];
      94            1 :             const encoded4 = this.lookupTable[base64.charCodeAt(reader + 3)];
      95              : 
      96              :             // Reconstruct original bytes from 6 bits
      97            1 :             bytes[writer++] = (encoded1 << 2) | (encoded2 >> 4);
      98            1 :             bytes[writer++] = ((encoded2 & 15) << 4) | (encoded3 >> 2);
      99            1 :             bytes[writer++] = ((encoded3 & 3) << 6) | (encoded4 & 63);
     100            1 :         }
     101              : 
     102              :         // Read the remainder
     103            1 :         const leftOver = base64.length - reader;
     104            1 :         switch (leftOver) {
     105            0 :             case 3: {
     106            0 :                 const encoded1 = this.lookupTable[base64.charCodeAt(reader)];
     107            0 :                 const encoded2 = this.lookupTable[base64.charCodeAt(reader + 1)];
     108            0 :                 const encoded3 = this.lookupTable[base64.charCodeAt(reader + 2)];
     109            0 :                 bytes[writer++] = (encoded1 << 2) | (encoded2 >> 4);
     110            0 :                 bytes[writer++] = ((encoded2 & 15) << 4) | (encoded3 >> 2);
     111            0 :                 break;
     112            0 :             }
     113            0 :             case 2: {
     114            0 :                 const encoded1 = this.lookupTable[base64.charCodeAt(reader)];
     115            0 :                 const encoded2 = this.lookupTable[base64.charCodeAt(reader + 1)];
     116            0 :                 bytes[writer++] = (encoded1 << 2) | (encoded2 >> 4);
     117            0 :                 break;
     118            0 :             }
     119              :             case 1: {
     120              :                 // We only ever consume "full" 8 bits from the input.
     121              :                 // 1 base64 character only stores 6 data bits, so we just skip
     122              :                 break;
     123              :             }
     124            1 :         }
     125              : 
     126            1 :         return arraybuffer;
     127            1 :     };
     128            1 : };
     129              : 
     130            1 : export class Base64UrlCodec {
     131            1 :     lookupTable: Uint8Array;
     132              : 
     133            1 :     constructor() {
     134              :         // Build a lookup query_result
     135            1 :         this.lookupTable = new Uint8Array(256);
     136            1 :         this.lookupTable.fill(0xFF);
     137            1 :         for (let i = 0; i < BASE64_TABLE_URL.length; i++) {
     138            1 :             this.lookupTable[BASE64_TABLE_URL.charCodeAt(i)] = i;
     139              :         }
     140            1 :     }
     141              : 
     142              :     /// Encode the ArrayBuffer as Base64URL
     143            1 :     public encode(arraybuffer: ArrayBuffer) {
     144            1 :         const bytes = new Uint8Array(arraybuffer);
     145            1 :         let base64 = "";
     146              : 
     147              :         // Base64 maps 6 input bits to 1 output byte (8 bits).
     148              :         // First map 3 bytes at once (24 bits), to 32 output bits (4 bytes).
     149            1 :         let reader = 0;
     150            1 :         for (; (reader + 3) <= bytes.length; reader += 3) {
     151              :             // Upper 6 bits of first byte
     152            1 :             base64 += BASE64_TABLE_URL[bytes[reader] >> 2];
     153              :             // Lower 2 bits of first byte and upper 4 bits of second byte
     154            1 :             base64 += BASE64_TABLE_URL[((bytes[reader] & 3) << 4) | (bytes[reader + 1] >> 4)];
     155              :             // Lower 4 bits of second byte and upper 2 bits of third byte
     156            1 :             base64 += BASE64_TABLE_URL[((bytes[reader + 1] & 15) << 2) | (bytes[reader + 2] >> 6)];
     157              :             // Lower 4 bits of third byte
     158            1 :             base64 += BASE64_TABLE_URL[bytes[reader + 2] & 63];
     159            1 :         }
     160              :         // Now map the remainder
     161            1 :         const leftOver = bytes.length - reader;
     162            1 :         switch (leftOver) {
     163              :             // 2 bytes left, pack into 3 bytes with trailing zero
     164            1 :             case 2:
     165            1 :                 base64 += BASE64_TABLE_URL[bytes[reader] >> 2];
     166            1 :                 base64 += BASE64_TABLE_URL[((bytes[reader] & 3) << 4) | (bytes[reader + 1] >> 4)];
     167            1 :                 base64 += BASE64_TABLE_URL[(bytes[reader + 1] & 15) << 2];
     168            1 :                 break;
     169              :             // 1 bytes left, pack into 2 bytes with trailing zero
     170            1 :             case 1:
     171            1 :                 base64 += BASE64_TABLE_URL[bytes[reader] >> 2];
     172            1 :                 base64 += BASE64_TABLE_URL[(bytes[reader] & 3) << 4];
     173              :                 break;
     174              :             case 0:
     175              :                 break;
     176              : 
     177            1 :         }
     178            1 :         return base64;
     179            1 :     };
     180              : 
     181              :     /// Check if the string is a valid base64 sequence
     182            1 :     public isValidBase64(base64: string) {
     183            1 :         let valid = true;
     184            1 :         for (let i = 0; i < base64.length; ++i) {
     185            1 :             const c = base64.charCodeAt(i);
     186            1 :             valid &&= this.lookupTable[c] != 0xFF;
     187            1 :         }
     188            1 :         return valid;
     189            1 :     }
     190              : 
     191              :     /// Get the decoded size
     192            1 :     public getDecodedSize(base64: string) {
     193            1 :         const rem = base64.length % 4;
     194            1 :         return Math.floor(base64.length / 4) * 3 + (rem >= 3 ? 2 : rem >= 2 ? 1 : 0);
     195            1 :     }
     196              : 
     197              :     /// Decode a Base64URL string
     198            1 :     public decode(base64: string) {
     199              :         /// Allocate the ArrayBuffer
     200            1 :         const bufferSize = this.getDecodedSize(base64);
     201            1 :         const buffer = new ArrayBuffer(bufferSize);
     202            1 :         const bytes = new Uint8Array(buffer);
     203              : 
     204            1 :         let writer = 0;
     205            1 :         let reader = 0;
     206            1 :         for (; (reader + 4) <= base64.length; reader += 4) {
     207              :             // Read all bytes, storing 6 bits of data each
     208            1 :             const encoded1 = this.lookupTable[base64.charCodeAt(reader)];
     209            1 :             const encoded2 = this.lookupTable[base64.charCodeAt(reader + 1)];
     210            1 :             const encoded3 = this.lookupTable[base64.charCodeAt(reader + 2)];
     211            1 :             const encoded4 = this.lookupTable[base64.charCodeAt(reader + 3)];
     212              : 
     213              :             // Reconstruct original bytes from 6 bits
     214            1 :             bytes[writer++] = (encoded1 << 2) | (encoded2 >> 4);
     215            1 :             bytes[writer++] = ((encoded2 & 15) << 4) | (encoded3 >> 2);
     216            1 :             bytes[writer++] = ((encoded3 & 3) << 6) | (encoded4 & 63);
     217            1 :         }
     218              : 
     219              :         // Read the remainder
     220            1 :         const leftOver = base64.length - reader;
     221            1 :         switch (leftOver) {
     222            1 :             case 3: {
     223            1 :                 const encoded1 = this.lookupTable[base64.charCodeAt(reader)];
     224            1 :                 const encoded2 = this.lookupTable[base64.charCodeAt(reader + 1)];
     225            1 :                 const encoded3 = this.lookupTable[base64.charCodeAt(reader + 2)];
     226            1 :                 bytes[writer++] = (encoded1 << 2) | (encoded2 >> 4);
     227            1 :                 bytes[writer++] = ((encoded2 & 15) << 4) | (encoded3 >> 2);
     228            1 :                 break;
     229            1 :             }
     230            1 :             case 2: {
     231            1 :                 const encoded1 = this.lookupTable[base64.charCodeAt(reader)];
     232            1 :                 const encoded2 = this.lookupTable[base64.charCodeAt(reader + 1)];
     233            1 :                 bytes[writer++] = (encoded1 << 2) | (encoded2 >> 4);
     234            1 :                 break;
     235            1 :             }
     236              :             case 1: {
     237              :                 // We only ever consume "full" 8 bits from the input.
     238              :                 // 1 base64 character only stores 6 data bits, so we just skip
     239              :                 break;
     240              :             }
     241            1 :         }
     242              : 
     243            1 :         return buffer;
     244            1 :     };
     245            1 : };
     246              : 
     247            1 : export const BASE64_CODEC = new Base64Codec();
     248            1 : export const BASE64URL_CODEC = new Base64UrlCodec();
        

Generated by: LCOV version 2.0-1