1/* 7zMain.c - Test application for 7z Decoder
22010-10-28 : Igor Pavlov : Public domain */
3
4#include <stdio.h>
5#include <string.h>
6
7#include "../../7z.h"
8#include "../../7zAlloc.h"
9#include "../../7zCrc.h"
10#include "../../7zFile.h"
11#include "../../7zVersion.h"
12
13#ifndef USE_WINDOWS_FILE
14/* for mkdir */
15#ifdef _WIN32
16#include <direct.h>
17#else
18#include <sys/stat.h>
19#include <errno.h>
20#endif
21#endif
22
23static ISzAlloc g_Alloc = { SzAlloc, SzFree };
24
25static int Buf_EnsureSize(CBuf *dest, size_t size)
26{
27  if (dest->size >= size)
28    return 1;
29  Buf_Free(dest, &g_Alloc);
30  return Buf_Create(dest, size, &g_Alloc);
31}
32
33#ifndef _WIN32
34
35static Byte kUtf8Limits[5] = { 0xC0, 0xE0, 0xF0, 0xF8, 0xFC };
36
37static Bool Utf16_To_Utf8(Byte *dest, size_t *destLen, const UInt16 *src, size_t srcLen)
38{
39  size_t destPos = 0, srcPos = 0;
40  for (;;)
41  {
42    unsigned numAdds;
43    UInt32 value;
44    if (srcPos == srcLen)
45    {
46      *destLen = destPos;
47      return True;
48    }
49    value = src[srcPos++];
50    if (value < 0x80)
51    {
52      if (dest)
53        dest[destPos] = (char)value;
54      destPos++;
55      continue;
56    }
57    if (value >= 0xD800 && value < 0xE000)
58    {
59      UInt32 c2;
60      if (value >= 0xDC00 || srcPos == srcLen)
61        break;
62      c2 = src[srcPos++];
63      if (c2 < 0xDC00 || c2 >= 0xE000)
64        break;
65      value = (((value - 0xD800) << 10) | (c2 - 0xDC00)) + 0x10000;
66    }
67    for (numAdds = 1; numAdds < 5; numAdds++)
68      if (value < (((UInt32)1) << (numAdds * 5 + 6)))
69        break;
70    if (dest)
71      dest[destPos] = (char)(kUtf8Limits[numAdds - 1] + (value >> (6 * numAdds)));
72    destPos++;
73    do
74    {
75      numAdds--;
76      if (dest)
77        dest[destPos] = (char)(0x80 + ((value >> (6 * numAdds)) & 0x3F));
78      destPos++;
79    }
80    while (numAdds != 0);
81  }
82  *destLen = destPos;
83  return False;
84}
85
86static SRes Utf16_To_Utf8Buf(CBuf *dest, const UInt16 *src, size_t srcLen)
87{
88  size_t destLen = 0;
89  Bool res;
90  Utf16_To_Utf8(NULL, &destLen, src, srcLen);
91  destLen += 1;
92  if (!Buf_EnsureSize(dest, destLen))
93    return SZ_ERROR_MEM;
94  res = Utf16_To_Utf8(dest->data, &destLen, src, srcLen);
95  dest->data[destLen] = 0;
96  return res ? SZ_OK : SZ_ERROR_FAIL;
97}
98#endif
99
100static SRes Utf16_To_Char(CBuf *buf, const UInt16 *s, int fileMode)
101{
102  int len = 0;
103  for (len = 0; s[len] != '\0'; len++);
104
105  #ifdef _WIN32
106  {
107    int size = len * 3 + 100;
108    if (!Buf_EnsureSize(buf, size))
109      return SZ_ERROR_MEM;
110    {
111      char defaultChar = '_';
112      BOOL defUsed;
113      int numChars = WideCharToMultiByte(fileMode ?
114          (
115          #ifdef UNDER_CE
116          CP_ACP
117          #else
118          AreFileApisANSI() ? CP_ACP : CP_OEMCP
119          #endif
120          ) : CP_OEMCP,
121          0, s, len, (char *)buf->data, size, &defaultChar, &defUsed);
122      if (numChars == 0 || numChars >= size)
123        return SZ_ERROR_FAIL;
124      buf->data[numChars] = 0;
125      return SZ_OK;
126    }
127  }
128  #else
129  fileMode = fileMode;
130  return Utf16_To_Utf8Buf(buf, s, len);
131  #endif
132}
133
134static WRes MyCreateDir(const UInt16 *name)
135{
136  #ifdef USE_WINDOWS_FILE
137
138  return CreateDirectoryW(name, NULL) ? 0 : GetLastError();
139
140  #else
141
142  CBuf buf;
143  WRes res;
144  Buf_Init(&buf);
145  RINOK(Utf16_To_Char(&buf, name, 1));
146
147  res =
148  #ifdef _WIN32
149  _mkdir((const char *)buf.data)
150  #else
151  mkdir((const char *)buf.data, 0777)
152  #endif
153  == 0 ? 0 : errno;
154  Buf_Free(&buf, &g_Alloc);
155  return res;
156
157  #endif
158}
159
160static WRes OutFile_OpenUtf16(CSzFile *p, const UInt16 *name)
161{
162  #ifdef USE_WINDOWS_FILE
163  return OutFile_OpenW(p, name);
164  #else
165  CBuf buf;
166  WRes res;
167  Buf_Init(&buf);
168  RINOK(Utf16_To_Char(&buf, name, 1));
169  res = OutFile_Open(p, (const char *)buf.data);
170  Buf_Free(&buf, &g_Alloc);
171  return res;
172  #endif
173}
174
175static SRes PrintString(const UInt16 *s)
176{
177  CBuf buf;
178  SRes res;
179  Buf_Init(&buf);
180  res = Utf16_To_Char(&buf, s, 0);
181  if (res == SZ_OK)
182    fputs((const char *)buf.data, stdout);
183  Buf_Free(&buf, &g_Alloc);
184  return res;
185}
186
187static void UInt64ToStr(UInt64 value, char *s)
188{
189  char temp[32];
190  int pos = 0;
191  do
192  {
193    temp[pos++] = (char)('0' + (unsigned)(value % 10));
194    value /= 10;
195  }
196  while (value != 0);
197  do
198    *s++ = temp[--pos];
199  while (pos);
200  *s = '\0';
201}
202
203static char *UIntToStr(char *s, unsigned value, int numDigits)
204{
205  char temp[16];
206  int pos = 0;
207  do
208    temp[pos++] = (char)('0' + (value % 10));
209  while (value /= 10);
210  for (numDigits -= pos; numDigits > 0; numDigits--)
211    *s++ = '0';
212  do
213    *s++ = temp[--pos];
214  while (pos);
215  *s = '\0';
216  return s;
217}
218
219#define PERIOD_4 (4 * 365 + 1)
220#define PERIOD_100 (PERIOD_4 * 25 - 1)
221#define PERIOD_400 (PERIOD_100 * 4 + 1)
222
223static void ConvertFileTimeToString(const CNtfsFileTime *ft, char *s)
224{
225  unsigned year, mon, day, hour, min, sec;
226  UInt64 v64 = (ft->Low | ((UInt64)ft->High << 32)) / 10000000;
227  Byte ms[] = { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };
228  unsigned t;
229  UInt32 v;
230  sec = (unsigned)(v64 % 60); v64 /= 60;
231  min = (unsigned)(v64 % 60); v64 /= 60;
232  hour = (unsigned)(v64 % 24); v64 /= 24;
233
234  v = (UInt32)v64;
235
236  year = (unsigned)(1601 + v / PERIOD_400 * 400);
237  v %= PERIOD_400;
238
239  t = v / PERIOD_100; if (t ==  4) t =  3; year += t * 100; v -= t * PERIOD_100;
240  t = v / PERIOD_4;   if (t == 25) t = 24; year += t * 4;   v -= t * PERIOD_4;
241  t = v / 365;        if (t ==  4) t =  3; year += t;       v -= t * 365;
242
243  if (year % 4 == 0 && (year % 100 != 0 || year % 400 == 0))
244    ms[1] = 29;
245  for (mon = 1; mon <= 12; mon++)
246  {
247    unsigned s = ms[mon - 1];
248    if (v < s)
249      break;
250    v -= s;
251  }
252  day = (unsigned)v + 1;
253  s = UIntToStr(s, year, 4); *s++ = '-';
254  s = UIntToStr(s, mon, 2);  *s++ = '-';
255  s = UIntToStr(s, day, 2);  *s++ = ' ';
256  s = UIntToStr(s, hour, 2); *s++ = ':';
257  s = UIntToStr(s, min, 2);  *s++ = ':';
258  s = UIntToStr(s, sec, 2);
259}
260
261void PrintError(char *sz)
262{
263  printf("\nERROR: %s\n", sz);
264}
265
266#ifdef USE_WINDOWS_FILE
267#define kEmptyAttribChar '.'
268static void GetAttribString(UInt32 wa, Bool isDir, char *s)
269{
270  s[0] = (char)(((wa & FILE_ATTRIBUTE_DIRECTORY) != 0 || isDir) ? 'D' : kEmptyAttribChar);
271  s[1] = (char)(((wa & FILE_ATTRIBUTE_READONLY) != 0) ? 'R': kEmptyAttribChar);
272  s[2] = (char)(((wa & FILE_ATTRIBUTE_HIDDEN) != 0) ? 'H': kEmptyAttribChar);
273  s[3] = (char)(((wa & FILE_ATTRIBUTE_SYSTEM) != 0) ? 'S': kEmptyAttribChar);
274  s[4] = (char)(((wa & FILE_ATTRIBUTE_ARCHIVE) != 0) ? 'A': kEmptyAttribChar);
275  s[5] = '\0';
276}
277#else
278static void GetAttribString(UInt32, Bool, char *s)
279{
280  s[0] = '\0';
281}
282#endif
283
284int MY_CDECL main(int numargs, char *args[])
285{
286  CFileInStream archiveStream;
287  CLookToRead lookStream;
288  CSzArEx db;
289  SRes res;
290  ISzAlloc allocImp;
291  ISzAlloc allocTempImp;
292  UInt16 *temp = NULL;
293  size_t tempSize = 0;
294
295  printf("\n7z ANSI-C Decoder " MY_VERSION_COPYRIGHT_DATE "\n\n");
296  if (numargs == 1)
297  {
298    printf(
299      "Usage: 7zDec <command> <archive_name>\n\n"
300      "<Commands>\n"
301      "  e: Extract files from archive (without using directory names)\n"
302      "  l: List contents of archive\n"
303      "  t: Test integrity of archive\n"
304      "  x: eXtract files with full paths\n");
305    return 0;
306  }
307  if (numargs < 3)
308  {
309    PrintError("incorrect command");
310    return 1;
311  }
312
313  allocImp.Alloc = SzAlloc;
314  allocImp.Free = SzFree;
315
316  allocTempImp.Alloc = SzAllocTemp;
317  allocTempImp.Free = SzFreeTemp;
318
319  if (InFile_Open(&archiveStream.file, args[2]))
320  {
321    PrintError("can not open input file");
322    return 1;
323  }
324
325  FileInStream_CreateVTable(&archiveStream);
326  LookToRead_CreateVTable(&lookStream, False);
327
328  lookStream.realStream = &archiveStream.s;
329  LookToRead_Init(&lookStream);
330
331  CrcGenerateTable();
332
333  SzArEx_Init(&db);
334  res = SzArEx_Open(&db, &lookStream.s, &allocImp, &allocTempImp);
335  if (res == SZ_OK)
336  {
337    char *command = args[1];
338    int listCommand = 0, testCommand = 0, extractCommand = 0, fullPaths = 0;
339    if (strcmp(command, "l") == 0) listCommand = 1;
340    else if (strcmp(command, "t") == 0) testCommand = 1;
341    else if (strcmp(command, "e") == 0) extractCommand = 1;
342    else if (strcmp(command, "x") == 0) { extractCommand = 1; fullPaths = 1; }
343    else
344    {
345      PrintError("incorrect command");
346      res = SZ_ERROR_FAIL;
347    }
348
349    if (res == SZ_OK)
350    {
351      UInt32 i;
352
353      /*
354      if you need cache, use these 3 variables.
355      if you use external function, you can make these variable as static.
356      */
357      UInt32 blockIndex = 0xFFFFFFFF; /* it can have any value before first call (if outBuffer = 0) */
358      Byte *outBuffer = 0; /* it must be 0 before first call for each new archive. */
359      size_t outBufferSize = 0;  /* it can have any value before first call (if outBuffer = 0) */
360
361      for (i = 0; i < db.db.NumFiles; i++)
362      {
363        size_t offset = 0;
364        size_t outSizeProcessed = 0;
365        const CSzFileItem *f = db.db.Files + i;
366        size_t len;
367        if (listCommand == 0 && f->IsDir && !fullPaths)
368          continue;
369        len = SzArEx_GetFileNameUtf16(&db, i, NULL);
370
371        if (len > tempSize)
372        {
373          SzFree(NULL, temp);
374          tempSize = len;
375          temp = (UInt16 *)SzAlloc(NULL, tempSize * sizeof(temp[0]));
376          if (temp == 0)
377          {
378            res = SZ_ERROR_MEM;
379            break;
380          }
381        }
382
383        SzArEx_GetFileNameUtf16(&db, i, temp);
384        if (listCommand)
385        {
386          char attr[8], s[32], t[32];
387
388          GetAttribString(f->AttribDefined ? f->Attrib : 0, f->IsDir, attr);
389
390          UInt64ToStr(f->Size, s);
391          if (f->MTimeDefined)
392            ConvertFileTimeToString(&f->MTime, t);
393          else
394          {
395            size_t j;
396            for (j = 0; j < 19; j++)
397              t[j] = ' ';
398            t[j] = '\0';
399          }
400
401          printf("%s %s %10s  ", t, attr, s);
402          res = PrintString(temp);
403          if (res != SZ_OK)
404            break;
405          if (f->IsDir)
406            printf("/");
407          printf("\n");
408          continue;
409        }
410        fputs(testCommand ?
411            "Testing    ":
412            "Extracting ",
413            stdout);
414        res = PrintString(temp);
415        if (res != SZ_OK)
416          break;
417        if (f->IsDir)
418          printf("/");
419        else
420        {
421          res = SzArEx_Extract(&db, &lookStream.s, i,
422              &blockIndex, &outBuffer, &outBufferSize,
423              &offset, &outSizeProcessed,
424              &allocImp, &allocTempImp);
425          if (res != SZ_OK)
426            break;
427        }
428        if (!testCommand)
429        {
430          CSzFile outFile;
431          size_t processedSize;
432          size_t j;
433          UInt16 *name = (UInt16 *)temp;
434          const UInt16 *destPath = (const UInt16 *)name;
435          for (j = 0; name[j] != 0; j++)
436            if (name[j] == '/')
437            {
438              if (fullPaths)
439              {
440                name[j] = 0;
441                MyCreateDir(name);
442                name[j] = CHAR_PATH_SEPARATOR;
443              }
444              else
445                destPath = name + j + 1;
446            }
447
448          if (f->IsDir)
449          {
450            MyCreateDir(destPath);
451            printf("\n");
452            continue;
453          }
454          else if (OutFile_OpenUtf16(&outFile, destPath))
455          {
456            PrintError("can not open output file");
457            res = SZ_ERROR_FAIL;
458            break;
459          }
460          processedSize = outSizeProcessed;
461          if (File_Write(&outFile, outBuffer + offset, &processedSize) != 0 || processedSize != outSizeProcessed)
462          {
463            PrintError("can not write output file");
464            res = SZ_ERROR_FAIL;
465            break;
466          }
467          if (File_Close(&outFile))
468          {
469            PrintError("can not close output file");
470            res = SZ_ERROR_FAIL;
471            break;
472          }
473          #ifdef USE_WINDOWS_FILE
474          if (f->AttribDefined)
475            SetFileAttributesW(destPath, f->Attrib);
476          #endif
477        }
478        printf("\n");
479      }
480      IAlloc_Free(&allocImp, outBuffer);
481    }
482  }
483  SzArEx_Free(&db, &allocImp);
484  SzFree(NULL, temp);
485
486  File_Close(&archiveStream.file);
487  if (res == SZ_OK)
488  {
489    printf("\nEverything is Ok\n");
490    return 0;
491  }
492  if (res == SZ_ERROR_UNSUPPORTED)
493    PrintError("decoder doesn't support this archive");
494  else if (res == SZ_ERROR_MEM)
495    PrintError("can not allocate memory");
496  else if (res == SZ_ERROR_CRC)
497    PrintError("CRC error");
498  else
499    printf("\nERROR #%d\n", res);
500  return 1;
501}
502