1 /* Copyright  (C) 2010-2017 The RetroArch team
2  *
3  * ---------------------------------------------------------------------------------------
4  * The following license statement only applies to this file (archive_file_sevenzip.c).
5  * ---------------------------------------------------------------------------------------
6  *
7  * Permission is hereby granted, free of charge,
8  * to any person obtaining a copy of this software and associated documentation files (the "Software"),
9  * to deal in the Software without restriction, including without limitation the rights to
10  * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software,
11  * and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
12  *
13  * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
14  *
15  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
16  * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
18  * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
19  * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20  * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
21  */
22 
23 #include <stdlib.h>
24 
25 #include <boolean.h>
26 #include <file/archive_file.h>
27 #include <streams/file_stream.h>
28 #include <retro_miscellaneous.h>
29 #include <encodings/utf.h>
30 #include <encodings/crc32.h>
31 #include <string/stdstring.h>
32 #include <lists/string_list.h>
33 #include <file/file_path.h>
34 #include <compat/strl.h>
35 #include "../../deps/7zip/7z.h"
36 #include "../../deps/7zip/7zCrc.h"
37 #include "../../deps/7zip/7zFile.h"
38 
39 #define SEVENZIP_MAGIC "7z\xBC\xAF\x27\x1C"
40 #define SEVENZIP_MAGIC_LEN 6
41 
42 struct sevenzip_context_t {
43    CFileInStream archiveStream;
44    CLookToRead lookStream;
45    ISzAlloc allocImp;
46    ISzAlloc allocTempImp;
47    CSzArEx db;
48    size_t temp_size;
49    uint32_t block_index;
50    uint32_t index;
51    uint32_t packIndex;
52    uint8_t *output;
53    file_archive_file_handle_t *handle;
54 };
55 
sevenzip_stream_alloc_impl(void * p,size_t size)56 static void *sevenzip_stream_alloc_impl(void *p, size_t size)
57 {
58    if (size == 0)
59       return 0;
60    return malloc(size);
61 }
62 
sevenzip_stream_free_impl(void * p,void * address)63 static void sevenzip_stream_free_impl(void *p, void *address)
64 {
65    (void)p;
66    free(address);
67 }
68 
sevenzip_stream_alloc_tmp_impl(void * p,size_t size)69 static void *sevenzip_stream_alloc_tmp_impl(void *p, size_t size)
70 {
71    (void)p;
72    if (size == 0)
73       return 0;
74    return malloc(size);
75 }
76 
sevenzip_stream_new(void)77 static void* sevenzip_stream_new(void)
78 {
79    struct sevenzip_context_t *sevenzip_context =
80          (struct sevenzip_context_t*)calloc(1, sizeof(struct sevenzip_context_t));
81 
82    /* These are the allocation routines - currently using
83     * the non-standard 7zip choices. */
84    sevenzip_context->allocImp.Alloc     = sevenzip_stream_alloc_impl;
85    sevenzip_context->allocImp.Free      = sevenzip_stream_free_impl;
86    sevenzip_context->allocTempImp.Alloc = sevenzip_stream_alloc_tmp_impl;
87    sevenzip_context->allocTempImp.Free  = sevenzip_stream_free_impl;
88    sevenzip_context->block_index        = 0xFFFFFFFF;
89    sevenzip_context->output             = NULL;
90    sevenzip_context->handle             = NULL;
91 
92    return sevenzip_context;
93 }
94 
sevenzip_stream_free(void * data)95 static void sevenzip_stream_free(void *data)
96 {
97    struct sevenzip_context_t *sevenzip_context = (struct sevenzip_context_t*)data;
98 
99    if (!sevenzip_context)
100       return;
101 
102    if (sevenzip_context->output)
103    {
104       IAlloc_Free(&sevenzip_context->allocImp, sevenzip_context->output);
105       sevenzip_context->output       = NULL;
106       sevenzip_context->handle->data = NULL;
107    }
108 
109    SzArEx_Free(&sevenzip_context->db, &sevenzip_context->allocImp);
110    File_Close(&sevenzip_context->archiveStream.file);
111 }
112 
113 /* Extract the relative path (needle) from a 7z archive
114  * (path) and allocate a buf for it to write it in.
115  * If optional_outfile is set, extract to that instead
116  * and don't allocate buffer.
117  */
sevenzip_file_read(const char * path,const char * needle,void ** buf,const char * optional_outfile)118 static int sevenzip_file_read(
119       const char *path,
120       const char *needle, void **buf,
121       const char *optional_outfile)
122 {
123    CFileInStream archiveStream;
124    CLookToRead lookStream;
125    ISzAlloc allocImp;
126    ISzAlloc allocTempImp;
127    CSzArEx db;
128    uint8_t *output      = 0;
129    long outsize         = -1;
130 
131    /*These are the allocation routines.
132     * Currently using the non-standard 7zip choices. */
133    allocImp.Alloc       = sevenzip_stream_alloc_impl;
134    allocImp.Free        = sevenzip_stream_free_impl;
135    allocTempImp.Alloc   = sevenzip_stream_alloc_tmp_impl;
136    allocTempImp.Free    = sevenzip_stream_free_impl;
137 
138    /* Could not open 7zip archive? */
139    if (InFile_Open(&archiveStream.file, path))
140       return -1;
141 
142    FileInStream_CreateVTable(&archiveStream);
143    LookToRead_CreateVTable(&lookStream, false);
144    lookStream.realStream = &archiveStream.s;
145    LookToRead_Init(&lookStream);
146    CrcGenerateTable();
147 
148    db.db.PackSizes               = NULL;
149    db.db.PackCRCsDefined         = NULL;
150    db.db.PackCRCs                = NULL;
151    db.db.Folders                 = NULL;
152    db.db.Files                   = NULL;
153    db.db.NumPackStreams          = 0;
154    db.db.NumFolders              = 0;
155    db.db.NumFiles                = 0;
156    db.startPosAfterHeader        = 0;
157    db.dataPos                    = 0;
158    db.FolderStartPackStreamIndex = NULL;
159    db.PackStreamStartPositions   = NULL;
160    db.FolderStartFileIndex       = NULL;
161    db.FileIndexToFolderIndexMap  = NULL;
162    db.FileNameOffsets            = NULL;
163    db.FileNames.data             = NULL;
164    db.FileNames.size             = 0;
165 
166    SzArEx_Init(&db);
167 
168    if (SzArEx_Open(&db, &lookStream.s, &allocImp, &allocTempImp) == SZ_OK)
169    {
170       uint32_t i;
171       bool file_found      = false;
172       uint16_t *temp       = NULL;
173       size_t temp_size     = 0;
174       uint32_t block_index = 0xFFFFFFFF;
175       SRes res             = SZ_OK;
176 
177       for (i = 0; i < db.db.NumFiles; i++)
178       {
179          size_t len;
180          char infile[PATH_MAX_LENGTH];
181          size_t offset                = 0;
182          size_t outSizeProcessed      = 0;
183          const CSzFileItem    *f      = db.db.Files + i;
184 
185          /* We skip over everything which is not a directory.
186           * FIXME: Why continue then if f->IsDir is true?*/
187          if (f->IsDir)
188             continue;
189 
190          len = SzArEx_GetFileNameUtf16(&db, i, NULL);
191 
192          if (len > temp_size)
193          {
194             if (temp)
195                free(temp);
196             temp_size = len;
197             temp = (uint16_t *)malloc(temp_size * sizeof(temp[0]));
198 
199             if (temp == 0)
200             {
201                res = SZ_ERROR_MEM;
202                break;
203             }
204          }
205 
206          SzArEx_GetFileNameUtf16(&db, i, temp);
207          res       = SZ_ERROR_FAIL;
208          infile[0] = '\0';
209 
210          if (temp)
211             res = utf16_to_char_string(temp, infile, sizeof(infile))
212                ? SZ_OK : SZ_ERROR_FAIL;
213 
214          if (string_is_equal(infile, needle))
215          {
216             size_t output_size   = 0;
217 
218             /*RARCH_LOG_OUTPUT("Opened archive %s. Now trying to extract %s\n",
219                   path, needle);*/
220 
221             /* C LZMA SDK does not support chunked extraction - see here:
222              * sourceforge.net/p/sevenzip/discussion/45798/thread/6fb59aaf/
223              * */
224             file_found = true;
225             res = SzArEx_Extract(&db, &lookStream.s, i, &block_index,
226                   &output, &output_size, &offset, &outSizeProcessed,
227                   &allocImp, &allocTempImp);
228 
229             if (res != SZ_OK)
230                break; /* This goes to the error section. */
231 
232             outsize = outSizeProcessed;
233 
234             if (optional_outfile != NULL)
235             {
236                const void *ptr = (const void*)(output + offset);
237 
238                if (!filestream_write_file(optional_outfile, ptr, outsize))
239                {
240                   /*RARCH_ERR("Could not open outfilepath %s.\n",
241                         optional_outfile);*/
242                   res        = SZ_OK;
243                   file_found = true;
244                   outsize    = -1;
245                }
246             }
247             else
248             {
249                /*We could either use the 7Zip allocated buffer,
250                 * or create our own and use it.
251                 * We would however need to realloc anyways, because RetroArch
252                 * expects a \0 at the end, therefore we allocate new,
253                 * copy and free the old one. */
254                *buf = malloc(outsize + 1);
255                ((char*)(*buf))[outsize] = '\0';
256                memcpy(*buf,output + offset,outsize);
257             }
258             break;
259          }
260       }
261 
262       if (temp)
263          free(temp);
264       IAlloc_Free(&allocImp, output);
265 
266       if (!(file_found && res == SZ_OK))
267       {
268          /* Error handling
269           *
270           * Failed to open compressed file inside 7zip archive.
271           */
272 
273          outsize    = -1;
274       }
275    }
276 
277    SzArEx_Free(&db, &allocImp);
278    File_Close(&archiveStream.file);
279 
280    return (int)outsize;
281 }
282 
sevenzip_stream_decompress_data_to_file_init(file_archive_file_handle_t * handle,const uint8_t * cdata,uint32_t csize,uint32_t size)283 static bool sevenzip_stream_decompress_data_to_file_init(
284       file_archive_file_handle_t *handle,
285       const uint8_t *cdata,  uint32_t csize, uint32_t size)
286 {
287    struct sevenzip_context_t *sevenzip_context =
288          (struct sevenzip_context_t*)handle->stream;
289 
290    if (!sevenzip_context)
291       return false;
292 
293    sevenzip_context->handle = handle;
294 
295    return true;
296 }
297 
sevenzip_stream_decompress_data_to_file_iterate(void * data)298 static int sevenzip_stream_decompress_data_to_file_iterate(void *data)
299 {
300    struct sevenzip_context_t *sevenzip_context =
301          (struct sevenzip_context_t*)data;
302 
303    SRes res                = SZ_ERROR_FAIL;
304    size_t output_size      = 0;
305    size_t offset           = 0;
306    size_t outSizeProcessed = 0;
307 
308    res = SzArEx_Extract(&sevenzip_context->db,
309          &sevenzip_context->lookStream.s, sevenzip_context->index,
310          &sevenzip_context->block_index, &sevenzip_context->output,
311          &output_size, &offset, &outSizeProcessed,
312          &sevenzip_context->allocImp, &sevenzip_context->allocTempImp);
313 
314    if (res != SZ_OK)
315       return 0;
316 
317    if (sevenzip_context->handle)
318       sevenzip_context->handle->data = sevenzip_context->output + offset;
319 
320    return 1;
321 }
322 
sevenzip_parse_file_init(file_archive_transfer_t * state,const char * file)323 static int sevenzip_parse_file_init(file_archive_transfer_t *state,
324       const char *file)
325 {
326    struct sevenzip_context_t *sevenzip_context =
327          (struct sevenzip_context_t*)sevenzip_stream_new();
328 
329    if (state->archive_size < SEVENZIP_MAGIC_LEN)
330       goto error;
331 
332    if (string_is_not_equal_fast(state->data, SEVENZIP_MAGIC, SEVENZIP_MAGIC_LEN))
333       goto error;
334 
335    state->stream = sevenzip_context;
336 
337    /* could not open 7zip archive? */
338    if (InFile_Open(&sevenzip_context->archiveStream.file, file))
339       goto error;
340 
341    FileInStream_CreateVTable(&sevenzip_context->archiveStream);
342    LookToRead_CreateVTable(&sevenzip_context->lookStream, false);
343    sevenzip_context->lookStream.realStream = &sevenzip_context->archiveStream.s;
344    LookToRead_Init(&sevenzip_context->lookStream);
345    CrcGenerateTable();
346    SzArEx_Init(&sevenzip_context->db);
347 
348    if (SzArEx_Open(&sevenzip_context->db, &sevenzip_context->lookStream.s,
349          &sevenzip_context->allocImp, &sevenzip_context->allocTempImp) != SZ_OK)
350       goto error;
351 
352    return 0;
353 
354 error:
355    if (sevenzip_context)
356       sevenzip_stream_free(sevenzip_context);
357    return -1;
358 }
359 
sevenzip_parse_file_iterate_step_internal(file_archive_transfer_t * state,char * filename,const uint8_t ** cdata,unsigned * cmode,uint32_t * size,uint32_t * csize,uint32_t * checksum,unsigned * payback,struct archive_extract_userdata * userdata)360 static int sevenzip_parse_file_iterate_step_internal(
361       file_archive_transfer_t *state, char *filename,
362       const uint8_t **cdata, unsigned *cmode,
363       uint32_t *size, uint32_t *csize, uint32_t *checksum,
364       unsigned *payback, struct archive_extract_userdata *userdata)
365 {
366    struct sevenzip_context_t *sevenzip_context = (struct sevenzip_context_t*)state->stream;
367    const CSzFileItem *file = sevenzip_context->db.db.Files + sevenzip_context->index;
368 
369    if (sevenzip_context->index < sevenzip_context->db.db.NumFiles)
370    {
371       size_t len = SzArEx_GetFileNameUtf16(&sevenzip_context->db,
372             sevenzip_context->index, NULL);
373       uint64_t compressed_size = 0;
374 
375       if (sevenzip_context->packIndex < sevenzip_context->db.db.NumPackStreams)
376       {
377          compressed_size = sevenzip_context->db.db.PackSizes[sevenzip_context->packIndex];
378          sevenzip_context->packIndex++;
379       }
380 
381       if (len < PATH_MAX_LENGTH && !file->IsDir)
382       {
383          char infile[PATH_MAX_LENGTH];
384          SRes res                     = SZ_ERROR_FAIL;
385          uint16_t *temp               = (uint16_t*)malloc(len * sizeof(uint16_t));
386 
387          if (!temp)
388             return -1;
389 
390          infile[0] = '\0';
391 
392          SzArEx_GetFileNameUtf16(&sevenzip_context->db, sevenzip_context->index,
393                temp);
394 
395          if (temp)
396          {
397             res  = utf16_to_char_string(temp, infile, sizeof(infile))
398                ? SZ_OK : SZ_ERROR_FAIL;
399             free(temp);
400          }
401 
402          if (res != SZ_OK)
403             return -1;
404 
405          strlcpy(filename, infile, PATH_MAX_LENGTH);
406 
407          *cmode    = ARCHIVE_MODE_COMPRESSED;
408          *checksum = file->Crc;
409          *size     = (uint32_t)file->Size;
410          *csize    = (uint32_t)compressed_size;
411       }
412    }
413 
414    *payback = 1;
415 
416    return 1;
417 }
418 
sevenzip_parse_file_iterate_step(file_archive_transfer_t * state,const char * valid_exts,struct archive_extract_userdata * userdata,file_archive_file_cb file_cb)419 static int sevenzip_parse_file_iterate_step(file_archive_transfer_t *state,
420       const char *valid_exts,
421       struct archive_extract_userdata *userdata, file_archive_file_cb file_cb)
422 {
423    char filename[PATH_MAX_LENGTH];
424    const uint8_t *cdata = NULL;
425    uint32_t checksum    = 0;
426    uint32_t size        = 0;
427    uint32_t csize       = 0;
428    unsigned cmode       = 0;
429    unsigned payload     = 0;
430    struct sevenzip_context_t *sevenzip_context = NULL;
431    int ret;
432 
433    filename[0]                   = '\0';
434 
435    ret = sevenzip_parse_file_iterate_step_internal(state, filename,
436          &cdata, &cmode, &size, &csize,
437          &checksum, &payload, userdata);
438 
439    if (ret != 1)
440       return ret;
441 
442    userdata->extracted_file_path = filename;
443    userdata->crc                 = checksum;
444 
445    if (file_cb && !file_cb(filename, valid_exts, cdata, cmode,
446             csize, size, checksum, userdata))
447       return 0;
448 
449    sevenzip_context = (struct sevenzip_context_t*)state->stream;
450 
451    sevenzip_context->index += payload;
452 
453    return 1;
454 }
455 
sevenzip_stream_crc32_calculate(uint32_t crc,const uint8_t * data,size_t length)456 static uint32_t sevenzip_stream_crc32_calculate(uint32_t crc,
457       const uint8_t *data, size_t length)
458 {
459    return encoding_crc32(crc, data, length);
460 }
461 
462 const struct file_archive_file_backend sevenzip_backend = {
463    sevenzip_stream_new,
464    sevenzip_stream_free,
465    sevenzip_stream_decompress_data_to_file_init,
466    sevenzip_stream_decompress_data_to_file_iterate,
467    sevenzip_stream_crc32_calculate,
468    sevenzip_file_read,
469    sevenzip_parse_file_init,
470    sevenzip_parse_file_iterate_step,
471    "7z"
472 };
473