All files tar.ts

97.92% Statements 47/48
87.5% Branches 14/16
100% Functions 9/9
97.83% Lines 45/46

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 971x 1x   1x 1x 1x                 1x   112x 112x 112x         1x 116x   1x   1x 2x   1x 173x     1x 2x 2x   2x 112x 84x     2x 2x     1x 114x 2x     112x 112x 112x 112x       112x 112x 80x       112x 112x 8x           112x   112x       1x     112x 112x       112x 112x           1x  
import * as path from "./path"
import { Reader } from "./reader"
 
enum Type {
  Dir,
  File,
}
 
export interface TarHeader {
  readonly size: number
  readonly name: string
  readonly type: Type
}
 
export class TarFile {
  public constructor(
    public readonly header: TarHeader,
    private readonly offset: number,
    private readonly reader: Reader
  ) {}
 
  public read(): Uint8Array
  public read(encoding: "utf8"): string
  public read(encoding?: "utf8"): Uint8Array | string {
    return this.reader.jump(this.offset).read(this.header.size, encoding as any) // eslint-disable-line @typescript-eslint/no-explicit-any
  }
}
 
export class Tar {
  private readonly files = new Map<string, TarFile>()
 
  public getFile(filePath: string): TarFile | undefined {
    return this.files.get(filePath)
  }
 
  public static fromUint8Array(array: Uint8Array): Tar {
    const reader = new Reader(array)
    const tar = new Tar()
    let file: TarFile | undefined
    while ((file = Tar.getNextFile(reader))) {
      if (file.header.type === Type.File) {
        tar.files.set(path.normalize(file.header.name), file)
      }
    }
    reader.unclamp()
    return tar
  }
 
  private static getNextFile(reader: Reader): TarFile | undefined {
    if (reader.eof()) {
      return undefined
    }
 
    const header = Tar.parseHeader(reader)
    reader.jump(512)
    const offset = reader.offset
    reader.skip(header.size)
 
    // Blocks are 512 in size and the remaining will be padding so we need to
    // skip past however much padding there is for the last block.
    const overflow = header.size & 511
    if (overflow > 0) {
      reader.skip(512 - overflow)
    }
 
    // There can also be empty padding block(s) after a file.
    try {
      while (reader.peek(1)[0] === 0x00) {
        reader.skip(512)
      }
    } catch (error) {
      // EOF
    }
 
    reader.clamp()
 
    return new TarFile(header, offset, reader)
  }
 
  // See https://www.gnu.org/software/tar/manual/html_node/Standard.html
  private static parseHeader(reader: Reader): TarHeader {
    // Tar uses base256 encoding for very large numbers. 0xff is a negative
    // number and 0x80 is a positive number.
    const sign = reader.jump(124).peek(1)[0]
    Iif (sign === 0xff || sign === 0x80) {
      throw new Error("base256 encoding not supported")
    }
 
    const prefix = reader.jump(345).read(155, "utf8")
    return {
      name: (prefix ? prefix + "/" : "") + reader.jump(0).read(100, "utf8"),
      size: parseInt(reader.jump(124).read(12, "utf8"), 8),
      type: reader.jump(156).read(1)[0] === 53 ? Type.Dir : Type.File,
    }
  }
}