1 /* Copyright  (C) 2010-2020 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 <7zip/7z.h>
36 #include <7zip/7zCrc.h>
37 #include <7zip/7zFile.h>
38 
39 #define SEVENZIP_MAGIC "7z\xBC\xAF\x27\x1C"
40 #define SEVENZIP_MAGIC_LEN 6
41 
42 /* Assume W-functions do not work below Win2K and Xbox platforms */
43 #if defined(_WIN32_WINNT) && _WIN32_WINNT < 0x0500 || defined(_XBOX)
44 #ifndef LEGACY_WIN32
45 #define LEGACY_WIN32
46 #endif
47 #endif
48 
49 struct sevenzip_context_t
50 {
51    uint8_t *output;
52    CFileInStream archiveStream;
53    CLookToRead lookStream;
54    ISzAlloc allocImp;
55    ISzAlloc allocTempImp;
56    CSzArEx db;
57    size_t temp_size;
58    uint32_t block_index;
59    uint32_t parse_index;
60    uint32_t decompress_index;
61    uint32_t packIndex;
62 };
63 
sevenzip_stream_alloc_impl(void * p,size_t size)64 static void *sevenzip_stream_alloc_impl(void *p, size_t size)
65 {
66    if (size == 0)
67       return 0;
68    return malloc(size);
69 }
70 
sevenzip_stream_free_impl(void * p,void * address)71 static void sevenzip_stream_free_impl(void *p, void *address)
72 {
73    (void)p;
74 
75    if (address)
76       free(address);
77 }
78 
sevenzip_stream_alloc_tmp_impl(void * p,size_t size)79 static void *sevenzip_stream_alloc_tmp_impl(void *p, size_t size)
80 {
81    (void)p;
82    if (size == 0)
83       return 0;
84    return malloc(size);
85 }
86 
sevenzip_stream_new(void)87 static void* sevenzip_stream_new(void)
88 {
89    struct sevenzip_context_t *sevenzip_context =
90          (struct sevenzip_context_t*)calloc(1, sizeof(struct sevenzip_context_t));
91 
92    /* These are the allocation routines - currently using
93     * the non-standard 7zip choices. */
94    sevenzip_context->allocImp.Alloc     = sevenzip_stream_alloc_impl;
95    sevenzip_context->allocImp.Free      = sevenzip_stream_free_impl;
96    sevenzip_context->allocTempImp.Alloc = sevenzip_stream_alloc_tmp_impl;
97    sevenzip_context->allocTempImp.Free  = sevenzip_stream_free_impl;
98    sevenzip_context->block_index        = 0xFFFFFFFF;
99    sevenzip_context->output             = NULL;
100 
101    return sevenzip_context;
102 }
103 
sevenzip_parse_file_free(void * context)104 static void sevenzip_parse_file_free(void *context)
105 {
106    struct sevenzip_context_t *sevenzip_context = (struct sevenzip_context_t*)context;
107 
108    if (!sevenzip_context)
109       return;
110 
111    if (sevenzip_context->output)
112    {
113       IAlloc_Free(&sevenzip_context->allocImp, sevenzip_context->output);
114       sevenzip_context->output       = NULL;
115    }
116 
117    SzArEx_Free(&sevenzip_context->db, &sevenzip_context->allocImp);
118    File_Close(&sevenzip_context->archiveStream.file);
119 
120    free(sevenzip_context);
121 }
122 
123 /* Extract the relative path (needle) from a 7z archive
124  * (path) and allocate a buf for it to write it in.
125  * If optional_outfile is set, extract to that instead
126  * and don't allocate buffer.
127  */
sevenzip_file_read(const char * path,const char * needle,void ** buf,const char * optional_outfile)128 static int64_t sevenzip_file_read(
129       const char *path,
130       const char *needle, void **buf,
131       const char *optional_outfile)
132 {
133    CFileInStream archiveStream;
134    CLookToRead lookStream;
135    ISzAlloc allocImp;
136    ISzAlloc allocTempImp;
137    CSzArEx db;
138    uint8_t *output      = 0;
139    int64_t outsize      = -1;
140 
141    /*These are the allocation routines.
142     * Currently using the non-standard 7zip choices. */
143    allocImp.Alloc       = sevenzip_stream_alloc_impl;
144    allocImp.Free        = sevenzip_stream_free_impl;
145    allocTempImp.Alloc   = sevenzip_stream_alloc_tmp_impl;
146    allocTempImp.Free    = sevenzip_stream_free_impl;
147 
148 #if defined(_WIN32) && defined(USE_WINDOWS_FILE) && !defined(LEGACY_WIN32)
149    if (!string_is_empty(path))
150    {
151       wchar_t *pathW = utf8_to_utf16_string_alloc(path);
152 
153       if (pathW)
154       {
155          /* Could not open 7zip archive? */
156          if (InFile_OpenW(&archiveStream.file, pathW))
157          {
158             free(pathW);
159             return -1;
160          }
161 
162          free(pathW);
163       }
164    }
165 #else
166    /* Could not open 7zip archive? */
167    if (InFile_Open(&archiveStream.file, path))
168       return -1;
169 #endif
170 
171    FileInStream_CreateVTable(&archiveStream);
172    LookToRead_CreateVTable(&lookStream, false);
173    lookStream.realStream = &archiveStream.s;
174    LookToRead_Init(&lookStream);
175    CrcGenerateTable();
176 
177    db.db.PackSizes               = NULL;
178    db.db.PackCRCsDefined         = NULL;
179    db.db.PackCRCs                = NULL;
180    db.db.Folders                 = NULL;
181    db.db.Files                   = NULL;
182    db.db.NumPackStreams          = 0;
183    db.db.NumFolders              = 0;
184    db.db.NumFiles                = 0;
185    db.startPosAfterHeader        = 0;
186    db.dataPos                    = 0;
187    db.FolderStartPackStreamIndex = NULL;
188    db.PackStreamStartPositions   = NULL;
189    db.FolderStartFileIndex       = NULL;
190    db.FileIndexToFolderIndexMap  = NULL;
191    db.FileNameOffsets            = NULL;
192    db.FileNames.data             = NULL;
193    db.FileNames.size             = 0;
194 
195    SzArEx_Init(&db);
196 
197    if (SzArEx_Open(&db, &lookStream.s, &allocImp, &allocTempImp) == SZ_OK)
198    {
199       uint32_t i;
200       bool file_found      = false;
201       uint16_t *temp       = NULL;
202       size_t temp_size     = 0;
203       uint32_t block_index = 0xFFFFFFFF;
204       SRes res             = SZ_OK;
205 
206       for (i = 0; i < db.db.NumFiles; i++)
207       {
208          size_t len;
209          char infile[PATH_MAX_LENGTH];
210          size_t offset                = 0;
211          size_t outSizeProcessed      = 0;
212          const CSzFileItem    *f      = db.db.Files + i;
213 
214          /* We skip over everything which is not a directory.
215           * FIXME: Why continue then if f->IsDir is true?*/
216          if (f->IsDir)
217             continue;
218 
219          len = SzArEx_GetFileNameUtf16(&db, i, NULL);
220 
221          if (len > temp_size)
222          {
223             if (temp)
224                free(temp);
225             temp_size = len;
226             temp = (uint16_t *)malloc(temp_size * sizeof(temp[0]));
227 
228             if (temp == 0)
229             {
230                res = SZ_ERROR_MEM;
231                break;
232             }
233          }
234 
235          SzArEx_GetFileNameUtf16(&db, i, temp);
236          res       = SZ_ERROR_FAIL;
237          infile[0] = '\0';
238 
239          if (temp)
240             res = utf16_to_char_string(temp, infile, sizeof(infile))
241                ? SZ_OK : SZ_ERROR_FAIL;
242 
243          if (string_is_equal(infile, needle))
244          {
245             size_t output_size   = 0;
246 
247             /* C LZMA SDK does not support chunked extraction - see here:
248              * sourceforge.net/p/sevenzip/discussion/45798/thread/6fb59aaf/
249              * */
250             file_found = true;
251             res = SzArEx_Extract(&db, &lookStream.s, i, &block_index,
252                   &output, &output_size, &offset, &outSizeProcessed,
253                   &allocImp, &allocTempImp);
254 
255             if (res != SZ_OK)
256                break; /* This goes to the error section. */
257 
258             outsize = (int64_t)outSizeProcessed;
259 
260             if (optional_outfile)
261             {
262                const void *ptr = (const void*)(output + offset);
263 
264                if (!filestream_write_file(optional_outfile, ptr, outsize))
265                {
266                   res        = SZ_OK;
267                   file_found = true;
268                   outsize    = -1;
269                }
270             }
271             else
272             {
273                /*We could either use the 7Zip allocated buffer,
274                 * or create our own and use it.
275                 * We would however need to realloc anyways, because RetroArch
276                 * expects a \0 at the end, therefore we allocate new,
277                 * copy and free the old one. */
278                *buf = malloc((size_t)(outsize + 1));
279                ((char*)(*buf))[outsize] = '\0';
280                memcpy(*buf,output + offset,outsize);
281             }
282             break;
283          }
284       }
285 
286       if (temp)
287          free(temp);
288       IAlloc_Free(&allocImp, output);
289 
290       if (!(file_found && res == SZ_OK))
291       {
292          /* Error handling
293           *
294           * Failed to open compressed file inside 7zip archive.
295           */
296 
297          outsize    = -1;
298       }
299    }
300 
301    SzArEx_Free(&db, &allocImp);
302    File_Close(&archiveStream.file);
303 
304    return outsize;
305 }
306 
sevenzip_stream_decompress_data_to_file_init(void * context,file_archive_file_handle_t * handle,const uint8_t * cdata,unsigned cmode,uint32_t csize,uint32_t size)307 static bool sevenzip_stream_decompress_data_to_file_init(
308       void *context, file_archive_file_handle_t *handle,
309       const uint8_t *cdata, unsigned cmode, uint32_t csize, uint32_t size)
310 {
311    struct sevenzip_context_t *sevenzip_context =
312          (struct sevenzip_context_t*)context;
313 
314    if (!sevenzip_context)
315       return false;
316 
317    sevenzip_context->decompress_index = (uint32_t)(size_t)cdata;
318 
319    return true;
320 }
321 
sevenzip_stream_decompress_data_to_file_iterate(void * context,file_archive_file_handle_t * handle)322 static int sevenzip_stream_decompress_data_to_file_iterate(
323       void *context, file_archive_file_handle_t *handle)
324 {
325    struct sevenzip_context_t *sevenzip_context =
326          (struct sevenzip_context_t*)context;
327 
328    SRes res                = SZ_ERROR_FAIL;
329    size_t output_size      = 0;
330    size_t offset           = 0;
331    size_t outSizeProcessed = 0;
332 
333    res = SzArEx_Extract(&sevenzip_context->db,
334          &sevenzip_context->lookStream.s, sevenzip_context->decompress_index,
335          &sevenzip_context->block_index, &sevenzip_context->output,
336          &output_size, &offset, &outSizeProcessed,
337          &sevenzip_context->allocImp, &sevenzip_context->allocTempImp);
338 
339    if (res != SZ_OK)
340       return 0;
341 
342    if (handle)
343       handle->data = sevenzip_context->output + offset;
344 
345    return 1;
346 }
347 
sevenzip_parse_file_init(file_archive_transfer_t * state,const char * file)348 static int sevenzip_parse_file_init(file_archive_transfer_t *state,
349       const char *file)
350 {
351    uint8_t magic_buf[SEVENZIP_MAGIC_LEN];
352    struct sevenzip_context_t *sevenzip_context = NULL;
353 
354    if (state->archive_size < SEVENZIP_MAGIC_LEN)
355       goto error;
356 
357    filestream_seek(state->archive_file, 0, SEEK_SET);
358    if (filestream_read(state->archive_file, magic_buf, SEVENZIP_MAGIC_LEN) != SEVENZIP_MAGIC_LEN)
359       goto error;
360 
361    if (string_is_not_equal_fast(magic_buf, SEVENZIP_MAGIC, SEVENZIP_MAGIC_LEN))
362       goto error;
363 
364    sevenzip_context = (struct sevenzip_context_t*)sevenzip_stream_new();
365    state->context = sevenzip_context;
366 
367 #if defined(_WIN32) && defined(USE_WINDOWS_FILE) && !defined(LEGACY_WIN32)
368    if (!string_is_empty(file))
369    {
370       wchar_t *fileW = utf8_to_utf16_string_alloc(file);
371 
372       if (fileW)
373       {
374          /* could not open 7zip archive? */
375          if (InFile_OpenW(&sevenzip_context->archiveStream.file, fileW))
376          {
377             free(fileW);
378             goto error;
379          }
380 
381          free(fileW);
382       }
383    }
384 #else
385    /* could not open 7zip archive? */
386    if (InFile_Open(&sevenzip_context->archiveStream.file, file))
387       goto error;
388 #endif
389 
390    FileInStream_CreateVTable(&sevenzip_context->archiveStream);
391    LookToRead_CreateVTable(&sevenzip_context->lookStream, false);
392    sevenzip_context->lookStream.realStream = &sevenzip_context->archiveStream.s;
393    LookToRead_Init(&sevenzip_context->lookStream);
394    CrcGenerateTable();
395    SzArEx_Init(&sevenzip_context->db);
396 
397    if (SzArEx_Open(&sevenzip_context->db, &sevenzip_context->lookStream.s,
398          &sevenzip_context->allocImp, &sevenzip_context->allocTempImp) != SZ_OK)
399       goto error;
400 
401    state->step_total = sevenzip_context->db.db.NumFiles;
402 
403    return 0;
404 
405 error:
406    if (sevenzip_context)
407       sevenzip_parse_file_free(sevenzip_context);
408    return -1;
409 }
410 
sevenzip_parse_file_iterate_step_internal(struct sevenzip_context_t * sevenzip_context,char * filename,const uint8_t ** cdata,unsigned * cmode,uint32_t * size,uint32_t * csize,uint32_t * checksum,unsigned * payback,struct archive_extract_userdata * userdata)411 static int sevenzip_parse_file_iterate_step_internal(
412       struct sevenzip_context_t *sevenzip_context, char *filename,
413       const uint8_t **cdata, unsigned *cmode,
414       uint32_t *size, uint32_t *csize, uint32_t *checksum,
415       unsigned *payback, struct archive_extract_userdata *userdata)
416 {
417    const CSzFileItem *file = sevenzip_context->db.db.Files + sevenzip_context->parse_index;
418 
419    if (sevenzip_context->parse_index < sevenzip_context->db.db.NumFiles)
420    {
421       size_t len = SzArEx_GetFileNameUtf16(&sevenzip_context->db,
422             sevenzip_context->parse_index, NULL);
423       uint64_t compressed_size = 0;
424 
425       if (sevenzip_context->packIndex < sevenzip_context->db.db.NumPackStreams)
426       {
427          compressed_size = sevenzip_context->db.db.PackSizes[sevenzip_context->packIndex];
428          sevenzip_context->packIndex++;
429       }
430 
431       if (len < PATH_MAX_LENGTH && !file->IsDir)
432       {
433          char infile[PATH_MAX_LENGTH];
434          SRes res                     = SZ_ERROR_FAIL;
435          uint16_t *temp               = (uint16_t*)malloc(len * sizeof(uint16_t));
436 
437          if (!temp)
438             return -1;
439 
440          infile[0] = '\0';
441 
442          SzArEx_GetFileNameUtf16(&sevenzip_context->db, sevenzip_context->parse_index,
443                temp);
444 
445          if (temp)
446          {
447             res  = utf16_to_char_string(temp, infile, sizeof(infile))
448                ? SZ_OK : SZ_ERROR_FAIL;
449             free(temp);
450          }
451 
452          if (res != SZ_OK)
453             return -1;
454 
455          strlcpy(filename, infile, PATH_MAX_LENGTH);
456 
457          *cmode    = 0; /* unused for 7zip */
458          *checksum = file->Crc;
459          *size     = (uint32_t)file->Size;
460          *csize    = (uint32_t)compressed_size;
461 
462          *cdata    = (uint8_t *)(size_t)sevenzip_context->parse_index;
463       }
464    }
465    else
466       return 0;
467 
468    *payback = 1;
469 
470    return 1;
471 }
472 
sevenzip_parse_file_iterate_step(void * context,const char * valid_exts,struct archive_extract_userdata * userdata,file_archive_file_cb file_cb)473 static int sevenzip_parse_file_iterate_step(void *context,
474       const char *valid_exts,
475       struct archive_extract_userdata *userdata, file_archive_file_cb file_cb)
476 {
477    const uint8_t *cdata = NULL;
478    uint32_t checksum    = 0;
479    uint32_t size        = 0;
480    uint32_t csize       = 0;
481    unsigned cmode       = 0;
482    unsigned payload     = 0;
483    struct sevenzip_context_t *sevenzip_context = (struct sevenzip_context_t*)context;
484    int ret;
485 
486    userdata->current_file_path[0] = '\0';
487 
488    ret = sevenzip_parse_file_iterate_step_internal(sevenzip_context,
489          userdata->current_file_path,
490          &cdata, &cmode, &size, &csize,
491          &checksum, &payload, userdata);
492 
493    if (ret != 1)
494       return ret;
495 
496    userdata->crc                 = checksum;
497 
498    if (file_cb && !file_cb(userdata->current_file_path, valid_exts,
499             cdata, cmode,
500             csize, size, checksum, userdata))
501       return 0;
502 
503    sevenzip_context->parse_index += payload;
504 
505    return 1;
506 }
507 
sevenzip_stream_crc32_calculate(uint32_t crc,const uint8_t * data,size_t length)508 static uint32_t sevenzip_stream_crc32_calculate(uint32_t crc,
509       const uint8_t *data, size_t length)
510 {
511    return encoding_crc32(crc, data, length);
512 }
513 
514 const struct file_archive_file_backend sevenzip_backend = {
515    sevenzip_parse_file_init,
516    sevenzip_parse_file_iterate_step,
517    sevenzip_parse_file_free,
518    sevenzip_stream_decompress_data_to_file_init,
519    sevenzip_stream_decompress_data_to_file_iterate,
520    sevenzip_stream_crc32_calculate,
521    sevenzip_file_read,
522    "7z"
523 };
524