| 
1123
 | 
     1 import zippy/adler32, zippy/common, zippy/crc, zippy/deflate,
 | 
| 
 | 
     2     zippy/gzip, zippy/inflate, zippy/internal
 | 
| 
 | 
     3 
 | 
| 
 | 
     4 when (NimMajor, NimMinor, NimPatch) >= (1, 6, 0):
 | 
| 
 | 
     5   import std/sysrand
 | 
| 
 | 
     6 else:
 | 
| 
 | 
     7   import std/random, std/times
 | 
| 
 | 
     8 
 | 
| 
 | 
     9 export common
 | 
| 
 | 
    10 
 | 
| 
 | 
    11 proc compress*(
 | 
| 
 | 
    12   src: pointer,
 | 
| 
 | 
    13   len: int,
 | 
| 
 | 
    14   level = DefaultCompression,
 | 
| 
 | 
    15   dataFormat = dfGzip
 | 
| 
 | 
    16 ): string {.raises: [ZippyError].} =
 | 
| 
 | 
    17   ## Compresses src and returns the compressed data.
 | 
| 
 | 
    18   let src = cast[ptr UncheckedArray[uint8]](src)
 | 
| 
 | 
    19 
 | 
| 
 | 
    20   case dataFormat:
 | 
| 
 | 
    21   of dfGzip:
 | 
| 
 | 
    22     result.setLen(10)
 | 
| 
 | 
    23     result[0] = 31.char
 | 
| 
 | 
    24     result[1] = 139.char
 | 
| 
 | 
    25     result[2] = 8.char
 | 
| 
 | 
    26     result[3] = (1.uint8 shl 3).char # Set the fname flag
 | 
| 
 | 
    27 
 | 
| 
 | 
    28     block: # https://github.com/guzba/zippy/issues/61
 | 
| 
 | 
    29       let htbLen =
 | 
| 
 | 
    30         when (NimMajor, NimMinor, NimPatch) >= (1, 6, 0):
 | 
| 
 | 
    31           var urand: array[1, uint8]
 | 
| 
 | 
    32           if not urandom(urand):
 | 
| 
 | 
    33             raise newException(ZippyError, "Failed to generate random number")
 | 
| 
 | 
    34           (urand[0] mod 26).int
 | 
| 
 | 
    35         else:
 | 
| 
 | 
    36           let now = getTime()
 | 
| 
 | 
    37           var rand = initRand(now.toUnix * 1_000_000_000 + now.nanosecond)
 | 
| 
 | 
    38           (rand.next() mod 26).int # mod the uint first to ensure a positive int
 | 
| 
 | 
    39       # Add up to 26 characters as the gzip header file name
 | 
| 
 | 
    40       for i in 0 ..< htbLen:
 | 
| 
 | 
    41         result.add (97 + i).char
 | 
| 
 | 
    42       result.add '\0'
 | 
| 
 | 
    43 
 | 
| 
 | 
    44     deflate(result, src, len, level)
 | 
| 
 | 
    45 
 | 
| 
 | 
    46     let
 | 
| 
 | 
    47       checksum = crc32(src, len)
 | 
| 
 | 
    48       isize = len
 | 
| 
 | 
    49 
 | 
| 
 | 
    50     result.add(((checksum shr 0) and 255).char)
 | 
| 
 | 
    51     result.add(((checksum shr 8) and 255).char)
 | 
| 
 | 
    52     result.add(((checksum shr 16) and 255).char)
 | 
| 
 | 
    53     result.add(((checksum shr 24) and 255).char)
 | 
| 
 | 
    54 
 | 
| 
 | 
    55     result.add(((isize shr 0) and 255).char)
 | 
| 
 | 
    56     result.add(((isize shr 8) and 255).char)
 | 
| 
 | 
    57     result.add(((isize shr 16) and 255).char)
 | 
| 
 | 
    58     result.add(((isize shr 24) and 255).char)
 | 
| 
 | 
    59 
 | 
| 
 | 
    60   of dfZlib:
 | 
| 
 | 
    61     const
 | 
| 
 | 
    62       cm = 8.uint8
 | 
| 
 | 
    63       cinfo = 7.uint8
 | 
| 
 | 
    64       cmf = (cinfo shl 4) or cm
 | 
| 
 | 
    65       fcheck = (31.uint32 - (cmf.uint32 * 256) mod 31).uint8
 | 
| 
 | 
    66 
 | 
| 
 | 
    67     result.setLen(2)
 | 
| 
 | 
    68     result[0] = cmf.char
 | 
| 
 | 
    69     result[1] = fcheck.char
 | 
| 
 | 
    70 
 | 
| 
 | 
    71     deflate(result, src, len, level)
 | 
| 
 | 
    72 
 | 
| 
 | 
    73     let checksum = adler32(src, len)
 | 
| 
 | 
    74 
 | 
| 
 | 
    75     result.add(((checksum shr 24) and 255).char)
 | 
| 
 | 
    76     result.add(((checksum shr 16) and 255).char)
 | 
| 
 | 
    77     result.add(((checksum shr 8) and 255).char)
 | 
| 
 | 
    78     result.add(((checksum shr 0) and 255).char)
 | 
| 
 | 
    79 
 | 
| 
 | 
    80   of dfDeflate:
 | 
| 
 | 
    81     deflate(result, src, len, level)
 | 
| 
 | 
    82 
 | 
| 
 | 
    83   else:
 | 
| 
 | 
    84     raise newException(ZippyError, "Invalid data format " & $dfDetect)
 | 
| 
 | 
    85 
 | 
| 
 | 
    86 proc compress*(
 | 
| 
 | 
    87   src: string,
 | 
| 
 | 
    88   level = DefaultCompression,
 | 
| 
 | 
    89   dataFormat = dfGzip
 | 
| 
 | 
    90 ): string {.raises: [ZippyError].} =
 | 
| 
 | 
    91   compress(src.cstring, src.len, level, dataFormat)
 | 
| 
 | 
    92 
 | 
| 
 | 
    93 proc compress*(
 | 
| 
 | 
    94   src: seq[uint8],
 | 
| 
 | 
    95   level = DefaultCompression,
 | 
| 
 | 
    96   dataFormat = dfGzip
 | 
| 
 | 
    97 ): seq[uint8] {.raises: [ZippyError].} =
 | 
| 
 | 
    98   cast[seq[uint8]](compress(cast[string](src).cstring, src.len, level, dataFormat))
 | 
| 
 | 
    99 
 | 
| 
 | 
   100 proc uncompress*(
 | 
| 
 | 
   101   src: pointer,
 | 
| 
 | 
   102   len: int,
 | 
| 
 | 
   103   dataFormat = dfDetect
 | 
| 
 | 
   104 ): string {.raises: [ZippyError].} =
 | 
| 
 | 
   105   ## Uncompresses src and returns the uncompressed data.
 | 
| 
 | 
   106   let src = cast[ptr UncheckedArray[uint8]](src)
 | 
| 
 | 
   107 
 | 
| 
 | 
   108   case dataFormat:
 | 
| 
 | 
   109   of dfDetect:
 | 
| 
 | 
   110     if (
 | 
| 
 | 
   111       len > 18 and
 | 
| 
 | 
   112       src[0].uint8 == 31 and src[1].uint8 == 139 and src[2].uint8 == 8 and
 | 
| 
 | 
   113       (src[3].uint8 and 0b11100000) == 0
 | 
| 
 | 
   114     ):
 | 
| 
 | 
   115       return uncompress(src, len, dfGzip)
 | 
| 
 | 
   116 
 | 
| 
 | 
   117     if (
 | 
| 
 | 
   118       len > 6 and
 | 
| 
 | 
   119       (src[0].uint8 and 0b00001111) == 8 and
 | 
| 
 | 
   120       (src[0].uint8 shr 4) <= 7 and
 | 
| 
 | 
   121       ((src[0].uint16 * 256) + src[1].uint8) mod 31 == 0
 | 
| 
 | 
   122     ):
 | 
| 
 | 
   123       return uncompress(src, len, dfZlib)
 | 
| 
 | 
   124 
 | 
| 
 | 
   125     raise newException(ZippyError, "Unable to detect compressed data format")
 | 
| 
 | 
   126 
 | 
| 
 | 
   127   of dfGzip:
 | 
| 
 | 
   128     uncompressGzip(result, src, len)
 | 
| 
 | 
   129 
 | 
| 
 | 
   130   of dfZlib:
 | 
| 
 | 
   131     if len < 6:
 | 
| 
 | 
   132       failUncompress()
 | 
| 
 | 
   133 
 | 
| 
 | 
   134     let
 | 
| 
 | 
   135       cmf = src[0].uint8
 | 
| 
 | 
   136       flg = src[1].uint8
 | 
| 
 | 
   137       cm = cmf and 0b00001111
 | 
| 
 | 
   138       cinfo = cmf shr 4
 | 
| 
 | 
   139 
 | 
| 
 | 
   140     if cm != 8: # DEFLATE
 | 
| 
 | 
   141       raise newException(ZippyError, "Unsupported compression method")
 | 
| 
 | 
   142 
 | 
| 
 | 
   143     if cinfo > 7.uint8:
 | 
| 
 | 
   144       raise newException(ZippyError, "Invalid compression info")
 | 
| 
 | 
   145 
 | 
| 
 | 
   146     if ((cmf.uint16 * 256) + flg.uint16) mod 31 != 0:
 | 
| 
 | 
   147       raise newException(ZippyError, "Invalid header")
 | 
| 
 | 
   148 
 | 
| 
 | 
   149     if (flg and 0b00100000) != 0: # FDICT
 | 
| 
 | 
   150       raise newException(ZippyError, "Preset dictionary is not yet supported")
 | 
| 
 | 
   151 
 | 
| 
 | 
   152     inflate(result, src, len, 2)
 | 
| 
 | 
   153 
 | 
| 
 | 
   154     let checksum = (
 | 
| 
 | 
   155       src[len - 4].uint32 shl 24 or
 | 
| 
 | 
   156       src[len - 3].uint32 shl 16 or
 | 
| 
 | 
   157       src[len - 2].uint32 shl 8 or
 | 
| 
 | 
   158       src[len - 1].uint32
 | 
| 
 | 
   159     )
 | 
| 
 | 
   160 
 | 
| 
 | 
   161     if checksum != adler32(result):
 | 
| 
 | 
   162       raise newException(ZippyError, "Checksum verification failed")
 | 
| 
 | 
   163 
 | 
| 
 | 
   164   of dfDeflate:
 | 
| 
 | 
   165     inflate(result, src, len, 0)
 | 
| 
 | 
   166 
 | 
| 
 | 
   167 proc uncompress*(
 | 
| 
 | 
   168   src: string,
 | 
| 
 | 
   169   dataFormat = dfDetect
 | 
| 
 | 
   170 ): string {.raises: [ZippyError].} =
 | 
| 
 | 
   171   uncompress(src.cstring, src.len, dataFormat)
 | 
| 
 | 
   172 
 | 
| 
 | 
   173 proc uncompress*(
 | 
| 
 | 
   174   src: seq[uint8],
 | 
| 
 | 
   175   dataFormat = dfDetect
 | 
| 
 | 
   176 ): seq[uint8] {.raises: [ZippyError].} =
 | 
| 
 | 
   177   cast[seq[uint8]](uncompress(cast[string](src).cstring, src.len, dataFormat))
 |