Home | History | Annotate | Download | only in C
      1 /* XzIn.c - Xz input
      2 2018-07-04 : Igor Pavlov : Public domain */
      3 
      4 #include "Precomp.h"
      5 
      6 #include <string.h>
      7 
      8 #include "7zCrc.h"
      9 #include "CpuArch.h"
     10 #include "Xz.h"
     11 
     12 /*
     13 #define XZ_FOOTER_SIG_CHECK(p) (memcmp((p), XZ_FOOTER_SIG, XZ_FOOTER_SIG_SIZE) == 0)
     14 */
     15 #define XZ_FOOTER_SIG_CHECK(p) ((p)[0] == XZ_FOOTER_SIG_0 && (p)[1] == XZ_FOOTER_SIG_1)
     16 
     17 
     18 SRes Xz_ReadHeader(CXzStreamFlags *p, ISeqInStream *inStream)
     19 {
     20   Byte sig[XZ_STREAM_HEADER_SIZE];
     21   RINOK(SeqInStream_Read2(inStream, sig, XZ_STREAM_HEADER_SIZE, SZ_ERROR_NO_ARCHIVE));
     22   if (memcmp(sig, XZ_SIG, XZ_SIG_SIZE) != 0)
     23     return SZ_ERROR_NO_ARCHIVE;
     24   return Xz_ParseHeader(p, sig);
     25 }
     26 
     27 #define READ_VARINT_AND_CHECK(buf, pos, size, res) \
     28   { unsigned s = Xz_ReadVarInt(buf + pos, size - pos, res); \
     29   if (s == 0) return SZ_ERROR_ARCHIVE; pos += s; }
     30 
     31 SRes XzBlock_ReadHeader(CXzBlock *p, ISeqInStream *inStream, BoolInt *isIndex, UInt32 *headerSizeRes)
     32 {
     33   Byte header[XZ_BLOCK_HEADER_SIZE_MAX];
     34   unsigned headerSize;
     35   *headerSizeRes = 0;
     36   RINOK(SeqInStream_ReadByte(inStream, &header[0]));
     37   headerSize = (unsigned)header[0];
     38   if (headerSize == 0)
     39   {
     40     *headerSizeRes = 1;
     41     *isIndex = True;
     42     return SZ_OK;
     43   }
     44 
     45   *isIndex = False;
     46   headerSize = (headerSize << 2) + 4;
     47   *headerSizeRes = headerSize;
     48   RINOK(SeqInStream_Read(inStream, header + 1, headerSize - 1));
     49   return XzBlock_Parse(p, header);
     50 }
     51 
     52 #define ADD_SIZE_CHECK(size, val) \
     53   { UInt64 newSize = size + (val); if (newSize < size) return XZ_SIZE_OVERFLOW; size = newSize; }
     54 
     55 UInt64 Xz_GetUnpackSize(const CXzStream *p)
     56 {
     57   UInt64 size = 0;
     58   size_t i;
     59   for (i = 0; i < p->numBlocks; i++)
     60     ADD_SIZE_CHECK(size, p->blocks[i].unpackSize);
     61   return size;
     62 }
     63 
     64 UInt64 Xz_GetPackSize(const CXzStream *p)
     65 {
     66   UInt64 size = 0;
     67   size_t i;
     68   for (i = 0; i < p->numBlocks; i++)
     69     ADD_SIZE_CHECK(size, (p->blocks[i].totalSize + 3) & ~(UInt64)3);
     70   return size;
     71 }
     72 
     73 /*
     74 SRes XzBlock_ReadFooter(CXzBlock *p, CXzStreamFlags f, ISeqInStream *inStream)
     75 {
     76   return SeqInStream_Read(inStream, p->check, XzFlags_GetCheckSize(f));
     77 }
     78 */
     79 
     80 static SRes Xz_ReadIndex2(CXzStream *p, const Byte *buf, size_t size, ISzAllocPtr alloc)
     81 {
     82   size_t numBlocks, pos = 1;
     83   UInt32 crc;
     84 
     85   if (size < 5 || buf[0] != 0)
     86     return SZ_ERROR_ARCHIVE;
     87 
     88   size -= 4;
     89   crc = CrcCalc(buf, size);
     90   if (crc != GetUi32(buf + size))
     91     return SZ_ERROR_ARCHIVE;
     92 
     93   {
     94     UInt64 numBlocks64;
     95     READ_VARINT_AND_CHECK(buf, pos, size, &numBlocks64);
     96     numBlocks = (size_t)numBlocks64;
     97     if (numBlocks != numBlocks64 || numBlocks * 2 > size)
     98       return SZ_ERROR_ARCHIVE;
     99   }
    100 
    101   Xz_Free(p, alloc);
    102   if (numBlocks != 0)
    103   {
    104     size_t i;
    105     p->numBlocks = numBlocks;
    106     p->blocks = (CXzBlockSizes *)ISzAlloc_Alloc(alloc, sizeof(CXzBlockSizes) * numBlocks);
    107     if (!p->blocks)
    108       return SZ_ERROR_MEM;
    109     for (i = 0; i < numBlocks; i++)
    110     {
    111       CXzBlockSizes *block = &p->blocks[i];
    112       READ_VARINT_AND_CHECK(buf, pos, size, &block->totalSize);
    113       READ_VARINT_AND_CHECK(buf, pos, size, &block->unpackSize);
    114       if (block->totalSize == 0)
    115         return SZ_ERROR_ARCHIVE;
    116     }
    117   }
    118   while ((pos & 3) != 0)
    119     if (buf[pos++] != 0)
    120       return SZ_ERROR_ARCHIVE;
    121   return (pos == size) ? SZ_OK : SZ_ERROR_ARCHIVE;
    122 }
    123 
    124 static SRes Xz_ReadIndex(CXzStream *p, ILookInStream *stream, UInt64 indexSize, ISzAllocPtr alloc)
    125 {
    126   SRes res;
    127   size_t size;
    128   Byte *buf;
    129   if (indexSize > ((UInt32)1 << 31))
    130     return SZ_ERROR_UNSUPPORTED;
    131   size = (size_t)indexSize;
    132   if (size != indexSize)
    133     return SZ_ERROR_UNSUPPORTED;
    134   buf = (Byte *)ISzAlloc_Alloc(alloc, size);
    135   if (!buf)
    136     return SZ_ERROR_MEM;
    137   res = LookInStream_Read2(stream, buf, size, SZ_ERROR_UNSUPPORTED);
    138   if (res == SZ_OK)
    139     res = Xz_ReadIndex2(p, buf, size, alloc);
    140   ISzAlloc_Free(alloc, buf);
    141   return res;
    142 }
    143 
    144 static SRes LookInStream_SeekRead_ForArc(ILookInStream *stream, UInt64 offset, void *buf, size_t size)
    145 {
    146   RINOK(LookInStream_SeekTo(stream, offset));
    147   return LookInStream_Read(stream, buf, size);
    148   /* return LookInStream_Read2(stream, buf, size, SZ_ERROR_NO_ARCHIVE); */
    149 }
    150 
    151 static SRes Xz_ReadBackward(CXzStream *p, ILookInStream *stream, Int64 *startOffset, ISzAllocPtr alloc)
    152 {
    153   UInt64 indexSize;
    154   Byte buf[XZ_STREAM_FOOTER_SIZE];
    155   UInt64 pos = *startOffset;
    156 
    157   if ((pos & 3) != 0 || pos < XZ_STREAM_FOOTER_SIZE)
    158     return SZ_ERROR_NO_ARCHIVE;
    159 
    160   pos -= XZ_STREAM_FOOTER_SIZE;
    161   RINOK(LookInStream_SeekRead_ForArc(stream, pos, buf, XZ_STREAM_FOOTER_SIZE));
    162 
    163   if (!XZ_FOOTER_SIG_CHECK(buf + 10))
    164   {
    165     UInt32 total = 0;
    166     pos += XZ_STREAM_FOOTER_SIZE;
    167 
    168     for (;;)
    169     {
    170       size_t i;
    171       #define TEMP_BUF_SIZE (1 << 10)
    172       Byte temp[TEMP_BUF_SIZE];
    173 
    174       i = (pos > TEMP_BUF_SIZE) ? TEMP_BUF_SIZE : (size_t)pos;
    175       pos -= i;
    176       RINOK(LookInStream_SeekRead_ForArc(stream, pos, temp, i));
    177       total += (UInt32)i;
    178       for (; i != 0; i--)
    179         if (temp[i - 1] != 0)
    180           break;
    181       if (i != 0)
    182       {
    183         if ((i & 3) != 0)
    184           return SZ_ERROR_NO_ARCHIVE;
    185         pos += i;
    186         break;
    187       }
    188       if (pos < XZ_STREAM_FOOTER_SIZE || total > (1 << 16))
    189         return SZ_ERROR_NO_ARCHIVE;
    190     }
    191 
    192     if (pos < XZ_STREAM_FOOTER_SIZE)
    193       return SZ_ERROR_NO_ARCHIVE;
    194     pos -= XZ_STREAM_FOOTER_SIZE;
    195     RINOK(LookInStream_SeekRead_ForArc(stream, pos, buf, XZ_STREAM_FOOTER_SIZE));
    196     if (!XZ_FOOTER_SIG_CHECK(buf + 10))
    197       return SZ_ERROR_NO_ARCHIVE;
    198   }
    199 
    200   p->flags = (CXzStreamFlags)GetBe16(buf + 8);
    201 
    202   if (!XzFlags_IsSupported(p->flags))
    203     return SZ_ERROR_UNSUPPORTED;
    204 
    205   if (GetUi32(buf) != CrcCalc(buf + 4, 6))
    206     return SZ_ERROR_ARCHIVE;
    207 
    208   indexSize = ((UInt64)GetUi32(buf + 4) + 1) << 2;
    209 
    210   if (pos < indexSize)
    211     return SZ_ERROR_ARCHIVE;
    212 
    213   pos -= indexSize;
    214   RINOK(LookInStream_SeekTo(stream, pos));
    215   RINOK(Xz_ReadIndex(p, stream, indexSize, alloc));
    216 
    217   {
    218     UInt64 totalSize = Xz_GetPackSize(p);
    219     if (totalSize == XZ_SIZE_OVERFLOW
    220         || totalSize >= ((UInt64)1 << 63)
    221         || pos < totalSize + XZ_STREAM_HEADER_SIZE)
    222       return SZ_ERROR_ARCHIVE;
    223     pos -= (totalSize + XZ_STREAM_HEADER_SIZE);
    224     RINOK(LookInStream_SeekTo(stream, pos));
    225     *startOffset = pos;
    226   }
    227   {
    228     CXzStreamFlags headerFlags;
    229     CSecToRead secToRead;
    230     SecToRead_CreateVTable(&secToRead);
    231     secToRead.realStream = stream;
    232 
    233     RINOK(Xz_ReadHeader(&headerFlags, &secToRead.vt));
    234     return (p->flags == headerFlags) ? SZ_OK : SZ_ERROR_ARCHIVE;
    235   }
    236 }
    237 
    238 
    239 /* ---------- Xz Streams ---------- */
    240 
    241 void Xzs_Construct(CXzs *p)
    242 {
    243   p->num = p->numAllocated = 0;
    244   p->streams = 0;
    245 }
    246 
    247 void Xzs_Free(CXzs *p, ISzAllocPtr alloc)
    248 {
    249   size_t i;
    250   for (i = 0; i < p->num; i++)
    251     Xz_Free(&p->streams[i], alloc);
    252   ISzAlloc_Free(alloc, p->streams);
    253   p->num = p->numAllocated = 0;
    254   p->streams = 0;
    255 }
    256 
    257 UInt64 Xzs_GetNumBlocks(const CXzs *p)
    258 {
    259   UInt64 num = 0;
    260   size_t i;
    261   for (i = 0; i < p->num; i++)
    262     num += p->streams[i].numBlocks;
    263   return num;
    264 }
    265 
    266 UInt64 Xzs_GetUnpackSize(const CXzs *p)
    267 {
    268   UInt64 size = 0;
    269   size_t i;
    270   for (i = 0; i < p->num; i++)
    271     ADD_SIZE_CHECK(size, Xz_GetUnpackSize(&p->streams[i]));
    272   return size;
    273 }
    274 
    275 /*
    276 UInt64 Xzs_GetPackSize(const CXzs *p)
    277 {
    278   UInt64 size = 0;
    279   size_t i;
    280   for (i = 0; i < p->num; i++)
    281     ADD_SIZE_CHECK(size, Xz_GetTotalSize(&p->streams[i]));
    282   return size;
    283 }
    284 */
    285 
    286 SRes Xzs_ReadBackward(CXzs *p, ILookInStream *stream, Int64 *startOffset, ICompressProgress *progress, ISzAllocPtr alloc)
    287 {
    288   Int64 endOffset = 0;
    289   RINOK(ILookInStream_Seek(stream, &endOffset, SZ_SEEK_END));
    290   *startOffset = endOffset;
    291   for (;;)
    292   {
    293     CXzStream st;
    294     SRes res;
    295     Xz_Construct(&st);
    296     res = Xz_ReadBackward(&st, stream, startOffset, alloc);
    297     st.startOffset = *startOffset;
    298     RINOK(res);
    299     if (p->num == p->numAllocated)
    300     {
    301       size_t newNum = p->num + p->num / 4 + 1;
    302       Byte *data = (Byte *)ISzAlloc_Alloc(alloc, newNum * sizeof(CXzStream));
    303       if (!data)
    304         return SZ_ERROR_MEM;
    305       p->numAllocated = newNum;
    306       if (p->num != 0)
    307         memcpy(data, p->streams, p->num * sizeof(CXzStream));
    308       ISzAlloc_Free(alloc, p->streams);
    309       p->streams = (CXzStream *)data;
    310     }
    311     p->streams[p->num++] = st;
    312     if (*startOffset == 0)
    313       break;
    314     RINOK(LookInStream_SeekTo(stream, *startOffset));
    315     if (progress && ICompressProgress_Progress(progress, endOffset - *startOffset, (UInt64)(Int64)-1) != SZ_OK)
    316       return SZ_ERROR_PROGRESS;
    317   }
    318   return SZ_OK;
    319 }
    320