1 // ArchiveCommandLine.cpp
2 
3 #include "StdAfx.h"
4 
5 #ifdef _WIN32
6 #ifndef UNDER_CE
7 #include <io.h>
8 #endif
9 #endif
10 #include <stdio.h>
11 
12 #include "Common/ListFileUtils.h"
13 #include "Common/StringConvert.h"
14 #include "Common/StringToInt.h"
15 
16 #include "Windows/FileDir.h"
17 #include "Windows/FileName.h"
18 #ifdef _WIN32
19 #include "Windows/FileMapping.h"
20 #include "Windows/Synchronization.h"
21 #endif
22 
23 #include "ArchiveCommandLine.h"
24 #include "EnumDirItems.h"
25 #include "SortUtils.h"
26 #include "Update.h"
27 #include "UpdateAction.h"
28 
29 extern bool g_CaseSensitive;
30 
31 #ifdef UNDER_CE
32 
33 #define MY_IS_TERMINAL(x) false;
34 
35 #else
36 
37 #if _MSC_VER >= 1400
38 #define MY_isatty_fileno(x) _isatty(_fileno(x))
39 #else
40 #define MY_isatty_fileno(x) isatty(fileno(x))
41 #endif
42 
43 #define MY_IS_TERMINAL(x) (MY_isatty_fileno(x) != 0);
44 
45 #endif
46 
47 using namespace NCommandLineParser;
48 using namespace NWindows;
49 using namespace NFile;
50 
51 int g_CodePage = -1;
52 
53 namespace NKey {
54 enum Enum
55 {
56   kHelp1 = 0,
57   kHelp2,
58   kHelp3,
59   kDisableHeaders,
60   kDisablePercents,
61   kArchiveType,
62   kYes,
63   #ifndef _NO_CRYPTO
64   kPassword,
65   #endif
66   kProperty,
67   kOutputDir,
68   kWorkingDir,
69   kInclude,
70   kExclude,
71   kArInclude,
72   kArExclude,
73   kNoArName,
74   kUpdate,
75   kVolume,
76   kRecursed,
77   kSfx,
78   kStdIn,
79   kStdOut,
80   kOverwrite,
81   kEmail,
82   kShowDialog,
83   kLargePages,
84   kListfileCharSet,
85   kConsoleCharSet,
86   kTechMode,
87   kShareForWrite,
88   kCaseSensitive,
89   kCalcCrc
90 };
91 
92 }
93 
94 
95 static const wchar_t kRecursedIDChar = 'R';
96 static const wchar_t *kRecursedPostCharSet = L"0-";
97 
98 namespace NRecursedPostCharIndex {
99   enum EEnum
100   {
101     kWildCardRecursionOnly = 0,
102     kNoRecursion = 1
103   };
104 }
105 
106 static const char kImmediateNameID = '!';
107 static const char kMapNameID = '#';
108 static const char kFileListID = '@';
109 
110 static const char kSomeCludePostStringMinSize = 2; // at least <@|!><N>ame must be
111 static const char kSomeCludeAfterRecursedPostStringMinSize = 2; // at least <@|!><N>ame must be
112 
113 static const wchar_t *kOverwritePostCharSet = L"asut";
114 
115 NExtract::NOverwriteMode::EEnum k_OverwriteModes[] =
116 {
117   NExtract::NOverwriteMode::kWithoutPrompt,
118   NExtract::NOverwriteMode::kSkipExisting,
119   NExtract::NOverwriteMode::kAutoRename,
120   NExtract::NOverwriteMode::kAutoRenameExisting
121 };
122 
123 static const CSwitchForm kSwitchForms[] =
124   {
125     { L"?",  NSwitchType::kSimple, false },
126     { L"H",  NSwitchType::kSimple, false },
127     { L"-HELP",  NSwitchType::kSimple, false },
128     { L"BA", NSwitchType::kSimple, false },
129     { L"BD", NSwitchType::kSimple, false },
130     { L"T",  NSwitchType::kUnLimitedPostString, false, 1 },
131     { L"Y",  NSwitchType::kSimple, false },
132     #ifndef _NO_CRYPTO
133     { L"P",  NSwitchType::kUnLimitedPostString, false, 0 },
134     #endif
135     { L"M",  NSwitchType::kUnLimitedPostString, true, 1 },
136     { L"O",  NSwitchType::kUnLimitedPostString, false, 1 },
137     { L"W",  NSwitchType::kUnLimitedPostString, false, 0 },
138     { L"I",  NSwitchType::kUnLimitedPostString, true, kSomeCludePostStringMinSize},
139     { L"X",  NSwitchType::kUnLimitedPostString, true, kSomeCludePostStringMinSize},
140     { L"AI", NSwitchType::kUnLimitedPostString, true, kSomeCludePostStringMinSize},
141     { L"AX", NSwitchType::kUnLimitedPostString, true, kSomeCludePostStringMinSize},
142     { L"AN", NSwitchType::kSimple, false },
143     { L"U",  NSwitchType::kUnLimitedPostString, true, 1},
144     { L"V",  NSwitchType::kUnLimitedPostString, true, 1},
145     { L"R",  NSwitchType::kPostChar, false, 0, 0, kRecursedPostCharSet },
146     { L"SFX", NSwitchType::kUnLimitedPostString, false, 0 },
147     { L"SI", NSwitchType::kUnLimitedPostString, false, 0 },
148     { L"SO", NSwitchType::kSimple, false, 0 },
149     { L"AO", NSwitchType::kPostChar, false, 1, 1, kOverwritePostCharSet},
150     { L"SEML", NSwitchType::kUnLimitedPostString, false, 0},
151     { L"AD",  NSwitchType::kSimple, false },
152     { L"SLP", NSwitchType::kUnLimitedPostString, false, 0},
153     { L"SCS", NSwitchType::kUnLimitedPostString, false, 0},
154     { L"SCC", NSwitchType::kUnLimitedPostString, false, 0},
155     { L"SLT", NSwitchType::kSimple, false },
156     { L"SSW", NSwitchType::kSimple, false },
157     { L"SSC", NSwitchType::kPostChar, false, 0, 0, L"-" },
158     { L"SCRC", NSwitchType::kSimple, false }
159   };
160 
161 static const CCommandForm g_CommandForms[] =
162 {
163   { L"A", false },
164   { L"U", false },
165   { L"D", false },
166   { L"T", false },
167   { L"E", false },
168   { L"X", false },
169   { L"L", false },
170   { L"B", false },
171   { L"I", false }
172 };
173 
174 static const int kNumCommandForms = sizeof(g_CommandForms) /  sizeof(g_CommandForms[0]);
175 
176 static const wchar_t *kUniversalWildcard = L"*";
177 static const int kMinNonSwitchWords = 1;
178 static const int kCommandIndex = 0;
179 
180 // ---------------------------
181 // exception messages
182 
183 static const char *kUserErrorMessage  = "Incorrect command line";
184 static const char *kCannotFindListFile = "Cannot find listfile";
185 static const char *kIncorrectListFile = "Incorrect item in listfile.\nCheck charset encoding and -scs switch.";
186 static const char *kIncorrectWildCardInListFile = "Incorrect wildcard in listfile";
187 static const char *kIncorrectWildCardInCommandLine  = "Incorrect wildcard in command line";
188 static const char *kTerminalOutError = "I won't write compressed data to a terminal";
189 static const char *kSameTerminalError = "I won't write data and program's messages to same terminal";
190 static const char *kEmptyFilePath = "Empty file path";
191 
ThrowException(const char * errorMessage)192 static void ThrowException(const char *errorMessage)
193 {
194   throw CArchiveCommandLineException(errorMessage);
195 }
196 
ThrowUserErrorException()197 static void ThrowUserErrorException()
198 {
199   ThrowException(kUserErrorMessage);
200 }
201 
202 // ---------------------------
203 
IsFromExtractGroup() const204 bool CArchiveCommand::IsFromExtractGroup() const
205 {
206   switch(CommandType)
207   {
208     case NCommandType::kTest:
209     case NCommandType::kExtract:
210     case NCommandType::kFullExtract:
211       return true;
212     default:
213       return false;
214   }
215 }
216 
GetPathMode() const217 NExtract::NPathMode::EEnum CArchiveCommand::GetPathMode() const
218 {
219   switch(CommandType)
220   {
221     case NCommandType::kTest:
222     case NCommandType::kFullExtract:
223       return NExtract::NPathMode::kFullPathnames;
224     default:
225       return NExtract::NPathMode::kNoPathnames;
226   }
227 }
228 
IsFromUpdateGroup() const229 bool CArchiveCommand::IsFromUpdateGroup() const
230 {
231   return (CommandType == NCommandType::kAdd ||
232     CommandType == NCommandType::kUpdate ||
233     CommandType == NCommandType::kDelete);
234 }
235 
GetRecursedTypeFromIndex(int index)236 static NRecursedType::EEnum GetRecursedTypeFromIndex(int index)
237 {
238   switch (index)
239   {
240     case NRecursedPostCharIndex::kWildCardRecursionOnly:
241       return NRecursedType::kWildCardOnlyRecursed;
242     case NRecursedPostCharIndex::kNoRecursion:
243       return NRecursedType::kNonRecursed;
244     default:
245       return NRecursedType::kRecursed;
246   }
247 }
248 
ParseArchiveCommand(const UString & commandString,CArchiveCommand & command)249 static bool ParseArchiveCommand(const UString &commandString, CArchiveCommand &command)
250 {
251   UString commandStringUpper = commandString;
252   commandStringUpper.MakeUpper();
253   UString postString;
254   int commandIndex = ParseCommand(kNumCommandForms, g_CommandForms, commandStringUpper,
255       postString) ;
256   if (commandIndex < 0)
257     return false;
258   command.CommandType = (NCommandType::EEnum)commandIndex;
259   return true;
260 }
261 
262 // ------------------------------------------------------------------
263 // filenames functions
264 
AddNameToCensor(NWildcard::CCensor & wildcardCensor,const UString & name,bool include,NRecursedType::EEnum type)265 static void AddNameToCensor(NWildcard::CCensor &wildcardCensor,
266     const UString &name, bool include, NRecursedType::EEnum type)
267 {
268   bool recursed = false;
269 
270   switch (type)
271   {
272     case NRecursedType::kWildCardOnlyRecursed:
273       recursed = DoesNameContainWildCard(name);
274       break;
275     case NRecursedType::kRecursed:
276       recursed = true;
277       break;
278   }
279   wildcardCensor.AddItem(include, name, recursed);
280 }
281 
AddToCensorFromListFile(NWildcard::CCensor & wildcardCensor,LPCWSTR fileName,bool include,NRecursedType::EEnum type,UINT codePage)282 static void AddToCensorFromListFile(NWildcard::CCensor &wildcardCensor,
283     LPCWSTR fileName, bool include, NRecursedType::EEnum type, UINT codePage)
284 {
285   UStringVector names;
286   if (!NFind::DoesFileExist(fileName))
287     throw kCannotFindListFile;
288   if (!ReadNamesFromListFile(fileName, names, codePage))
289     throw kIncorrectListFile;
290   for (int i = 0; i < names.Size(); i++)
291     AddNameToCensor(wildcardCensor, names[i], include, type);
292 }
293 
AddToCensorFromNonSwitchesStrings(int startIndex,NWildcard::CCensor & wildcardCensor,const UStringVector & nonSwitchStrings,NRecursedType::EEnum type,bool thereAreSwitchIncludes,UINT codePage)294 static void AddToCensorFromNonSwitchesStrings(
295     int startIndex,
296     NWildcard::CCensor &wildcardCensor,
297     const UStringVector &nonSwitchStrings, NRecursedType::EEnum type,
298     bool thereAreSwitchIncludes, UINT codePage)
299 {
300   if (nonSwitchStrings.Size() == startIndex && (!thereAreSwitchIncludes))
301     AddNameToCensor(wildcardCensor, kUniversalWildcard, true, type);
302   for (int i = startIndex; i < nonSwitchStrings.Size(); i++)
303   {
304     const UString &s = nonSwitchStrings[i];
305     if (s.IsEmpty())
306       throw kEmptyFilePath;
307     if (s[0] == kFileListID)
308       AddToCensorFromListFile(wildcardCensor, s.Mid(1), true, type, codePage);
309     else
310       AddNameToCensor(wildcardCensor, s, true, type);
311   }
312 }
313 
314 #ifdef _WIN32
ParseMapWithPaths(NWildcard::CCensor & wildcardCensor,const UString & switchParam,bool include,NRecursedType::EEnum commonRecursedType)315 static void ParseMapWithPaths(NWildcard::CCensor &wildcardCensor,
316     const UString &switchParam, bool include,
317     NRecursedType::EEnum commonRecursedType)
318 {
319   int splitPos = switchParam.Find(L':');
320   if (splitPos < 0)
321     ThrowUserErrorException();
322   UString mappingName = switchParam.Left(splitPos);
323 
324   UString switchParam2 = switchParam.Mid(splitPos + 1);
325   splitPos = switchParam2.Find(L':');
326   if (splitPos < 0)
327     ThrowUserErrorException();
328 
329   UString mappingSize = switchParam2.Left(splitPos);
330   UString eventName = switchParam2.Mid(splitPos + 1);
331 
332   UInt64 dataSize64 = ConvertStringToUInt64(mappingSize, NULL);
333   UInt32 dataSize = (UInt32)dataSize64;
334   {
335     CFileMapping fileMapping;
336     if (fileMapping.Open(FILE_MAP_READ, GetSystemString(mappingName)) != 0)
337       ThrowException("Can not open mapping");
338     LPVOID data = fileMapping.Map(FILE_MAP_READ, 0, dataSize);
339     if (data == NULL)
340       ThrowException("MapViewOfFile error");
341     try
342     {
343       const wchar_t *curData = (const wchar_t *)data;
344       if (*curData != 0)
345         ThrowException("Incorrect mapping data");
346       UInt32 numChars = dataSize / sizeof(wchar_t);
347       UString name;
348       for (UInt32 i = 1; i < numChars; i++)
349       {
350         wchar_t c = curData[i];
351         if (c == L'\0')
352         {
353           AddNameToCensor(wildcardCensor, name, include, commonRecursedType);
354           name.Empty();
355         }
356         else
357           name += c;
358       }
359       if (!name.IsEmpty())
360         ThrowException("data error");
361     }
362     catch(...)
363     {
364       UnmapViewOfFile(data);
365       throw;
366     }
367     UnmapViewOfFile(data);
368   }
369 
370   {
371     NSynchronization::CManualResetEvent event;
372     if (event.Open(EVENT_MODIFY_STATE, false, GetSystemString(eventName)) == S_OK)
373       event.Set();
374   }
375 }
376 #endif
377 
AddSwitchWildCardsToCensor(NWildcard::CCensor & wildcardCensor,const UStringVector & strings,bool include,NRecursedType::EEnum commonRecursedType,UINT codePage)378 static void AddSwitchWildCardsToCensor(NWildcard::CCensor &wildcardCensor,
379     const UStringVector &strings, bool include,
380     NRecursedType::EEnum commonRecursedType, UINT codePage)
381 {
382   for (int i = 0; i < strings.Size(); i++)
383   {
384     const UString &name = strings[i];
385     NRecursedType::EEnum recursedType;
386     int pos = 0;
387     if (name.Length() < kSomeCludePostStringMinSize)
388       ThrowUserErrorException();
389     if (::MyCharUpper(name[pos]) == kRecursedIDChar)
390     {
391       pos++;
392       int index = UString(kRecursedPostCharSet).Find(name[pos]);
393       recursedType = GetRecursedTypeFromIndex(index);
394       if (index >= 0)
395         pos++;
396     }
397     else
398       recursedType = commonRecursedType;
399     if (name.Length() < pos + kSomeCludeAfterRecursedPostStringMinSize)
400       ThrowUserErrorException();
401     UString tail = name.Mid(pos + 1);
402     if (name[pos] == kImmediateNameID)
403       AddNameToCensor(wildcardCensor, tail, include, recursedType);
404     else if (name[pos] == kFileListID)
405       AddToCensorFromListFile(wildcardCensor, tail, include, recursedType, codePage);
406     #ifdef _WIN32
407     else if (name[pos] == kMapNameID)
408       ParseMapWithPaths(wildcardCensor, tail, include, recursedType);
409     #endif
410     else
411       ThrowUserErrorException();
412   }
413 }
414 
415 #ifdef _WIN32
416 
417 // This code converts all short file names to long file names.
418 
ConvertToLongName(const UString & prefix,UString & name)419 static void ConvertToLongName(const UString &prefix, UString &name)
420 {
421   if (name.IsEmpty() || DoesNameContainWildCard(name))
422     return;
423   NFind::CFileInfoW fi;
424   if (fi.Find(prefix + name))
425     name = fi.Name;
426 }
427 
ConvertToLongNames(const UString & prefix,CObjectVector<NWildcard::CItem> & items)428 static void ConvertToLongNames(const UString &prefix, CObjectVector<NWildcard::CItem> &items)
429 {
430   for (int i = 0; i < items.Size(); i++)
431   {
432     NWildcard::CItem &item = items[i];
433     if (item.Recursive || item.PathParts.Size() != 1)
434       continue;
435     ConvertToLongName(prefix, item.PathParts.Front());
436   }
437 }
438 
ConvertToLongNames(const UString & prefix,NWildcard::CCensorNode & node)439 static void ConvertToLongNames(const UString &prefix, NWildcard::CCensorNode &node)
440 {
441   ConvertToLongNames(prefix, node.IncludeItems);
442   ConvertToLongNames(prefix, node.ExcludeItems);
443   int i;
444   for (i = 0; i < node.SubNodes.Size(); i++)
445     ConvertToLongName(prefix, node.SubNodes[i].Name);
446   // mix folders with same name
447   for (i = 0; i < node.SubNodes.Size(); i++)
448   {
449     NWildcard::CCensorNode &nextNode1 = node.SubNodes[i];
450     for (int j = i + 1; j < node.SubNodes.Size();)
451     {
452       const NWildcard::CCensorNode &nextNode2 = node.SubNodes[j];
453       if (nextNode1.Name.CompareNoCase(nextNode2.Name) == 0)
454       {
455         nextNode1.IncludeItems += nextNode2.IncludeItems;
456         nextNode1.ExcludeItems += nextNode2.ExcludeItems;
457         node.SubNodes.Delete(j);
458       }
459       else
460         j++;
461     }
462   }
463   for (i = 0; i < node.SubNodes.Size(); i++)
464   {
465     NWildcard::CCensorNode &nextNode = node.SubNodes[i];
466     ConvertToLongNames(prefix + nextNode.Name + wchar_t(NFile::NName::kDirDelimiter), nextNode);
467   }
468 }
469 
ConvertToLongNames(NWildcard::CCensor & censor)470 static void ConvertToLongNames(NWildcard::CCensor &censor)
471 {
472   for (int i = 0; i < censor.Pairs.Size(); i++)
473   {
474     NWildcard::CPair &pair = censor.Pairs[i];
475     ConvertToLongNames(pair.Prefix, pair.Head);
476   }
477 }
478 
479 #endif
480 
GetUpdatePairActionType(int i)481 static NUpdateArchive::NPairAction::EEnum GetUpdatePairActionType(int i)
482 {
483   switch(i)
484   {
485     case NUpdateArchive::NPairAction::kIgnore: return NUpdateArchive::NPairAction::kIgnore;
486     case NUpdateArchive::NPairAction::kCopy: return NUpdateArchive::NPairAction::kCopy;
487     case NUpdateArchive::NPairAction::kCompress: return NUpdateArchive::NPairAction::kCompress;
488     case NUpdateArchive::NPairAction::kCompressAsAnti: return NUpdateArchive::NPairAction::kCompressAsAnti;
489   }
490   throw 98111603;
491 }
492 
493 const UString kUpdatePairStateIDSet = L"PQRXYZW";
494 const int kUpdatePairStateNotSupportedActions[] = {2, 2, 1, -1, -1, -1, -1};
495 
496 const UString kUpdatePairActionIDSet = L"0123"; //Ignore, Copy, Compress, Create Anti
497 
498 const wchar_t *kUpdateIgnoreItselfPostStringID = L"-";
499 const wchar_t kUpdateNewArchivePostCharID = '!';
500 
501 
ParseUpdateCommandString2(const UString & command,NUpdateArchive::CActionSet & actionSet,UString & postString)502 static bool ParseUpdateCommandString2(const UString &command,
503     NUpdateArchive::CActionSet &actionSet, UString &postString)
504 {
505   for (int i = 0; i < command.Length();)
506   {
507     wchar_t c = MyCharUpper(command[i]);
508     int statePos = kUpdatePairStateIDSet.Find(c);
509     if (statePos < 0)
510     {
511       postString = command.Mid(i);
512       return true;
513     }
514     i++;
515     if (i >= command.Length())
516       return false;
517     int actionPos = kUpdatePairActionIDSet.Find(::MyCharUpper(command[i]));
518     if (actionPos < 0)
519       return false;
520     actionSet.StateActions[statePos] = GetUpdatePairActionType(actionPos);
521     if (kUpdatePairStateNotSupportedActions[statePos] == actionPos)
522       return false;
523     i++;
524   }
525   postString.Empty();
526   return true;
527 }
528 
ParseUpdateCommandString(CUpdateOptions & options,const UStringVector & updatePostStrings,const NUpdateArchive::CActionSet & defaultActionSet)529 static void ParseUpdateCommandString(CUpdateOptions &options,
530     const UStringVector &updatePostStrings,
531     const NUpdateArchive::CActionSet &defaultActionSet)
532 {
533   for (int i = 0; i < updatePostStrings.Size(); i++)
534   {
535     const UString &updateString = updatePostStrings[i];
536     if (updateString.CompareNoCase(kUpdateIgnoreItselfPostStringID) == 0)
537     {
538       if (options.UpdateArchiveItself)
539       {
540         options.UpdateArchiveItself = false;
541         options.Commands.Delete(0);
542       }
543     }
544     else
545     {
546       NUpdateArchive::CActionSet actionSet = defaultActionSet;
547 
548       UString postString;
549       if (!ParseUpdateCommandString2(updateString, actionSet, postString))
550         ThrowUserErrorException();
551       if (postString.IsEmpty())
552       {
553         if (options.UpdateArchiveItself)
554           options.Commands[0].ActionSet = actionSet;
555       }
556       else
557       {
558         if (MyCharUpper(postString[0]) != kUpdateNewArchivePostCharID)
559           ThrowUserErrorException();
560         CUpdateArchiveCommand uc;
561         UString archivePath = postString.Mid(1);
562         if (archivePath.IsEmpty())
563           ThrowUserErrorException();
564         uc.UserArchivePath = archivePath;
565         uc.ActionSet = actionSet;
566         options.Commands.Add(uc);
567       }
568     }
569   }
570 }
571 
572 static const char kByteSymbol = 'B';
573 static const char kKiloSymbol = 'K';
574 static const char kMegaSymbol = 'M';
575 static const char kGigaSymbol = 'G';
576 
ParseComplexSize(const UString & src,UInt64 & result)577 static bool ParseComplexSize(const UString &src, UInt64 &result)
578 {
579   UString s = src;
580   s.MakeUpper();
581 
582   const wchar_t *start = s;
583   const wchar_t *end;
584   UInt64 number = ConvertStringToUInt64(start, &end);
585   int numDigits = (int)(end - start);
586   if (numDigits == 0 || s.Length() > numDigits + 1)
587     return false;
588   if (s.Length() == numDigits)
589   {
590     result = number;
591     return true;
592   }
593   int numBits;
594   switch (s[numDigits])
595   {
596     case kByteSymbol:
597       result = number;
598       return true;
599     case kKiloSymbol:
600       numBits = 10;
601       break;
602     case kMegaSymbol:
603       numBits = 20;
604       break;
605     case kGigaSymbol:
606       numBits = 30;
607       break;
608     default:
609       return false;
610   }
611   if (number >= ((UInt64)1 << (64 - numBits)))
612     return false;
613   result = number << numBits;
614   return true;
615 }
616 
SetAddCommandOptions(NCommandType::EEnum commandType,const CParser & parser,CUpdateOptions & options)617 static void SetAddCommandOptions(
618     NCommandType::EEnum commandType,
619     const CParser &parser,
620     CUpdateOptions &options)
621 {
622   NUpdateArchive::CActionSet defaultActionSet;
623   switch(commandType)
624   {
625     case NCommandType::kAdd:
626       defaultActionSet = NUpdateArchive::kAddActionSet;
627       break;
628     case NCommandType::kDelete:
629       defaultActionSet = NUpdateArchive::kDeleteActionSet;
630       break;
631     default:
632       defaultActionSet = NUpdateArchive::kUpdateActionSet;
633   }
634 
635   options.UpdateArchiveItself = true;
636 
637   options.Commands.Clear();
638   CUpdateArchiveCommand updateMainCommand;
639   updateMainCommand.ActionSet = defaultActionSet;
640   options.Commands.Add(updateMainCommand);
641   if (parser[NKey::kUpdate].ThereIs)
642     ParseUpdateCommandString(options, parser[NKey::kUpdate].PostStrings,
643         defaultActionSet);
644   if (parser[NKey::kWorkingDir].ThereIs)
645   {
646     const UString &postString = parser[NKey::kWorkingDir].PostStrings[0];
647     if (postString.IsEmpty())
648       NDirectory::MyGetTempPath(options.WorkingDir);
649     else
650       options.WorkingDir = postString;
651   }
652   options.SfxMode = parser[NKey::kSfx].ThereIs;
653   if (options.SfxMode)
654     options.SfxModule = parser[NKey::kSfx].PostStrings[0];
655 
656   if (parser[NKey::kVolume].ThereIs)
657   {
658     const UStringVector &sv = parser[NKey::kVolume].PostStrings;
659     for (int i = 0; i < sv.Size(); i++)
660     {
661       UInt64 size;
662       if (!ParseComplexSize(sv[i], size))
663         ThrowException("Incorrect volume size");
664       options.VolumesSizes.Add(size);
665     }
666   }
667 }
668 
SetMethodOptions(const CParser & parser,CObjectVector<CProperty> & properties)669 static void SetMethodOptions(const CParser &parser, CObjectVector<CProperty> &properties)
670 {
671   if (parser[NKey::kProperty].ThereIs)
672   {
673     // options.MethodMode.Properties.Clear();
674     for (int i = 0; i < parser[NKey::kProperty].PostStrings.Size(); i++)
675     {
676       CProperty property;
677       const UString &postString = parser[NKey::kProperty].PostStrings[i];
678       int index = postString.Find(L'=');
679       if (index < 0)
680         property.Name = postString;
681       else
682       {
683         property.Name = postString.Left(index);
684         property.Value = postString.Mid(index + 1);
685       }
686       properties.Add(property);
687     }
688   }
689 }
690 
CArchiveCommandLineParser()691 CArchiveCommandLineParser::CArchiveCommandLineParser():
692   parser(sizeof(kSwitchForms) / sizeof(kSwitchForms[0])) {}
693 
Parse1(const UStringVector & commandStrings,CArchiveCommandLineOptions & options)694 void CArchiveCommandLineParser::Parse1(const UStringVector &commandStrings,
695     CArchiveCommandLineOptions &options)
696 {
697   try
698   {
699     parser.ParseStrings(kSwitchForms, commandStrings);
700   }
701   catch(...)
702   {
703     ThrowUserErrorException();
704   }
705 
706   options.IsInTerminal = MY_IS_TERMINAL(stdin);
707   options.IsStdOutTerminal = MY_IS_TERMINAL(stdout);
708   options.IsStdErrTerminal = MY_IS_TERMINAL(stderr);
709   options.StdInMode = parser[NKey::kStdIn].ThereIs;
710   options.StdOutMode = parser[NKey::kStdOut].ThereIs;
711   options.EnableHeaders = !parser[NKey::kDisableHeaders].ThereIs;
712   options.HelpMode = parser[NKey::kHelp1].ThereIs || parser[NKey::kHelp2].ThereIs  || parser[NKey::kHelp3].ThereIs;
713 
714   #ifdef _WIN32
715   options.LargePages = false;
716   if (parser[NKey::kLargePages].ThereIs)
717   {
718     const UString &postString = parser[NKey::kLargePages].PostStrings.Front();
719     if (postString.IsEmpty())
720       options.LargePages = true;
721   }
722   #endif
723 }
724 
725 struct CCodePagePair
726 {
727   const wchar_t *Name;
728   UINT CodePage;
729 };
730 
731 static CCodePagePair g_CodePagePairs[] =
732 {
733   { L"UTF-8", CP_UTF8 },
734   { L"WIN", CP_ACP },
735   { L"DOS", CP_OEMCP }
736 };
737 
FindCharset(const NCommandLineParser::CParser & parser,int keyIndex,int defaultVal)738 static int FindCharset(const NCommandLineParser::CParser &parser, int keyIndex, int defaultVal)
739 {
740   if (!parser[keyIndex].ThereIs)
741     return defaultVal;
742 
743   UString name = parser[keyIndex].PostStrings.Back();
744   name.MakeUpper();
745   int i;
746   for (i = 0; i < sizeof(g_CodePagePairs) / sizeof(g_CodePagePairs[0]); i++)
747   {
748     const CCodePagePair &pair = g_CodePagePairs[i];
749     if (name.Compare(pair.Name) == 0)
750       return pair.CodePage;
751   }
752   if (i == sizeof(g_CodePagePairs) / sizeof(g_CodePagePairs[0]))
753     ThrowUserErrorException();
754   return -1;
755 }
756 
ConvertStringToUInt32(const wchar_t * s,UInt32 & v)757 static bool ConvertStringToUInt32(const wchar_t *s, UInt32 &v)
758 {
759   const wchar_t *end;
760   UInt64 number = ConvertStringToUInt64(s, &end);
761   if (*end != 0)
762     return false;
763   if (number > (UInt32)0xFFFFFFFF)
764     return false;
765   v = (UInt32)number;
766   return true;
767 }
768 
EnumerateDirItemsAndSort(NWildcard::CCensor & wildcardCensor,UStringVector & sortedPaths,UStringVector & sortedFullPaths)769 void EnumerateDirItemsAndSort(NWildcard::CCensor &wildcardCensor,
770     UStringVector &sortedPaths,
771     UStringVector &sortedFullPaths)
772 {
773   UStringVector paths;
774   {
775     CDirItems dirItems;
776     {
777       UStringVector errorPaths;
778       CRecordVector<DWORD> errorCodes;
779       HRESULT res = EnumerateItems(wildcardCensor, dirItems, NULL, errorPaths, errorCodes);
780       if (res != S_OK || errorPaths.Size() > 0)
781         throw "cannot find archive";
782     }
783     for (int i = 0; i < dirItems.Items.Size(); i++)
784     {
785       const CDirItem &dirItem = dirItems.Items[i];
786       if (!dirItem.IsDir())
787         paths.Add(dirItems.GetPhyPath(i));
788     }
789   }
790 
791   if (paths.Size() == 0)
792     throw "there is no such archive";
793 
794   UStringVector fullPaths;
795 
796   int i;
797   for (i = 0; i < paths.Size(); i++)
798   {
799     UString fullPath;
800     NFile::NDirectory::MyGetFullPathName(paths[i], fullPath);
801     fullPaths.Add(fullPath);
802   }
803   CIntVector indices;
804   SortFileNames(fullPaths, indices);
805   sortedPaths.Reserve(indices.Size());
806   sortedFullPaths.Reserve(indices.Size());
807   for (i = 0; i < indices.Size(); i++)
808   {
809     int index = indices[i];
810     sortedPaths.Add(paths[index]);
811     sortedFullPaths.Add(fullPaths[index]);
812   }
813 }
814 
Parse2(CArchiveCommandLineOptions & options)815 void CArchiveCommandLineParser::Parse2(CArchiveCommandLineOptions &options)
816 {
817   const UStringVector &nonSwitchStrings = parser.NonSwitchStrings;
818   int numNonSwitchStrings = nonSwitchStrings.Size();
819   if (numNonSwitchStrings < kMinNonSwitchWords)
820     ThrowUserErrorException();
821 
822   if (!ParseArchiveCommand(nonSwitchStrings[kCommandIndex], options.Command))
823     ThrowUserErrorException();
824 
825   options.TechMode = parser[NKey::kTechMode].ThereIs;
826   options.CalcCrc = parser[NKey::kCalcCrc].ThereIs;
827 
828   if (parser[NKey::kCaseSensitive].ThereIs)
829     g_CaseSensitive = (parser[NKey::kCaseSensitive].PostCharIndex < 0);
830 
831   NRecursedType::EEnum recursedType;
832   if (parser[NKey::kRecursed].ThereIs)
833     recursedType = GetRecursedTypeFromIndex(parser[NKey::kRecursed].PostCharIndex);
834   else
835     recursedType = NRecursedType::kNonRecursed;
836 
837   g_CodePage = FindCharset(parser, NKey::kConsoleCharSet, -1);
838   UINT codePage = FindCharset(parser, NKey::kListfileCharSet, CP_UTF8);
839 
840   bool thereAreSwitchIncludes = false;
841   if (parser[NKey::kInclude].ThereIs)
842   {
843     thereAreSwitchIncludes = true;
844     AddSwitchWildCardsToCensor(options.WildcardCensor,
845         parser[NKey::kInclude].PostStrings, true, recursedType, codePage);
846   }
847   if (parser[NKey::kExclude].ThereIs)
848     AddSwitchWildCardsToCensor(options.WildcardCensor,
849         parser[NKey::kExclude].PostStrings, false, recursedType, codePage);
850 
851   int curCommandIndex = kCommandIndex + 1;
852   bool thereIsArchiveName = !parser[NKey::kNoArName].ThereIs &&
853       options.Command.CommandType != NCommandType::kBenchmark &&
854       options.Command.CommandType != NCommandType::kInfo;
855 
856   bool isExtractGroupCommand = options.Command.IsFromExtractGroup();
857   bool isExtractOrList = isExtractGroupCommand || options.Command.CommandType == NCommandType::kList;
858 
859   if (isExtractOrList && options.StdInMode)
860     thereIsArchiveName = false;
861 
862   if (thereIsArchiveName)
863   {
864     if (curCommandIndex >= numNonSwitchStrings)
865       ThrowUserErrorException();
866     options.ArchiveName = nonSwitchStrings[curCommandIndex++];
867     if (options.ArchiveName.IsEmpty())
868       ThrowUserErrorException();
869   }
870 
871   AddToCensorFromNonSwitchesStrings(
872       curCommandIndex, options.WildcardCensor,
873       nonSwitchStrings, recursedType, thereAreSwitchIncludes, codePage);
874 
875   options.YesToAll = parser[NKey::kYes].ThereIs;
876 
877 
878   #ifndef _NO_CRYPTO
879   options.PasswordEnabled = parser[NKey::kPassword].ThereIs;
880   if (options.PasswordEnabled)
881     options.Password = parser[NKey::kPassword].PostStrings[0];
882   #endif
883 
884   options.ShowDialog = parser[NKey::kShowDialog].ThereIs;
885 
886   if (parser[NKey::kArchiveType].ThereIs)
887     options.ArcType = parser[NKey::kArchiveType].PostStrings[0];
888 
889   if (isExtractOrList)
890   {
891     if (!options.WildcardCensor.AllAreRelative())
892       ThrowException("Cannot use absolute pathnames for this command");
893 
894     NWildcard::CCensor archiveWildcardCensor;
895 
896     if (parser[NKey::kArInclude].ThereIs)
897       AddSwitchWildCardsToCensor(archiveWildcardCensor,
898           parser[NKey::kArInclude].PostStrings, true, NRecursedType::kNonRecursed, codePage);
899     if (parser[NKey::kArExclude].ThereIs)
900       AddSwitchWildCardsToCensor(archiveWildcardCensor,
901           parser[NKey::kArExclude].PostStrings, false, NRecursedType::kNonRecursed, codePage);
902 
903     if (thereIsArchiveName)
904       AddNameToCensor(archiveWildcardCensor, options.ArchiveName, true, NRecursedType::kNonRecursed);
905 
906     #ifdef _WIN32
907     ConvertToLongNames(archiveWildcardCensor);
908     #endif
909 
910     archiveWildcardCensor.ExtendExclude();
911 
912     if (options.StdInMode)
913     {
914       UString arcName = parser[NKey::kStdIn].PostStrings.Front();
915       options.ArchivePathsSorted.Add(arcName);
916       options.ArchivePathsFullSorted.Add(arcName);
917     }
918     else
919     {
920       EnumerateDirItemsAndSort(archiveWildcardCensor,
921         options.ArchivePathsSorted,
922         options.ArchivePathsFullSorted);
923     }
924 
925     if (isExtractGroupCommand)
926     {
927       SetMethodOptions(parser, options.ExtractProperties);
928       if (options.StdOutMode && options.IsStdOutTerminal && options.IsStdErrTerminal)
929         throw kSameTerminalError;
930       if (parser[NKey::kOutputDir].ThereIs)
931       {
932         options.OutputDir = parser[NKey::kOutputDir].PostStrings[0];
933         NFile::NName::NormalizeDirPathPrefix(options.OutputDir);
934       }
935 
936       options.OverwriteMode = NExtract::NOverwriteMode::kAskBefore;
937       if (parser[NKey::kOverwrite].ThereIs)
938         options.OverwriteMode = k_OverwriteModes[parser[NKey::kOverwrite].PostCharIndex];
939       else if (options.YesToAll)
940         options.OverwriteMode = NExtract::NOverwriteMode::kWithoutPrompt;
941     }
942   }
943   else if (options.Command.IsFromUpdateGroup())
944   {
945     CUpdateOptions &updateOptions = options.UpdateOptions;
946 
947     SetAddCommandOptions(options.Command.CommandType, parser, updateOptions);
948 
949     SetMethodOptions(parser, updateOptions.MethodMode.Properties);
950 
951     if (parser[NKey::kShareForWrite].ThereIs)
952       updateOptions.OpenShareForWrite = true;
953 
954     options.EnablePercents = !parser[NKey::kDisablePercents].ThereIs;
955 
956     if (options.EnablePercents)
957     {
958       if ((options.StdOutMode && !options.IsStdErrTerminal) ||
959          (!options.StdOutMode && !options.IsStdOutTerminal))
960         options.EnablePercents = false;
961     }
962 
963     updateOptions.EMailMode = parser[NKey::kEmail].ThereIs;
964     if (updateOptions.EMailMode)
965     {
966       updateOptions.EMailAddress = parser[NKey::kEmail].PostStrings.Front();
967       if (updateOptions.EMailAddress.Length() > 0)
968         if (updateOptions.EMailAddress[0] == L'.')
969         {
970           updateOptions.EMailRemoveAfter = true;
971           updateOptions.EMailAddress.Delete(0);
972         }
973     }
974 
975     updateOptions.StdOutMode = options.StdOutMode;
976     updateOptions.StdInMode = options.StdInMode;
977 
978     if (updateOptions.StdOutMode && updateOptions.EMailMode)
979       throw "stdout mode and email mode cannot be combined";
980     if (updateOptions.StdOutMode && options.IsStdOutTerminal)
981       throw kTerminalOutError;
982     if (updateOptions.StdInMode)
983       updateOptions.StdInFileName = parser[NKey::kStdIn].PostStrings.Front();
984 
985     #ifdef _WIN32
986     ConvertToLongNames(options.WildcardCensor);
987     #endif
988   }
989   else if (options.Command.CommandType == NCommandType::kBenchmark)
990   {
991     options.NumThreads = (UInt32)-1;
992     options.DictionarySize = (UInt32)-1;
993     options.NumIterations = 1;
994     if (curCommandIndex < numNonSwitchStrings)
995     {
996       if (!ConvertStringToUInt32(nonSwitchStrings[curCommandIndex++], options.NumIterations))
997         ThrowUserErrorException();
998     }
999     for (int i = 0; i < parser[NKey::kProperty].PostStrings.Size(); i++)
1000     {
1001       UString postString = parser[NKey::kProperty].PostStrings[i];
1002       postString.MakeUpper();
1003       if (postString.Length() < 2)
1004         ThrowUserErrorException();
1005       if (postString[0] == 'D')
1006       {
1007         int pos = 1;
1008         if (postString[pos] == '=')
1009           pos++;
1010         UInt32 logSize;
1011         if (!ConvertStringToUInt32((const wchar_t *)postString + pos, logSize))
1012           ThrowUserErrorException();
1013         if (logSize > 31)
1014           ThrowUserErrorException();
1015         options.DictionarySize = 1 << logSize;
1016       }
1017       else if (postString[0] == 'M' && postString[1] == 'T' )
1018       {
1019         int pos = 2;
1020         if (postString[pos] == '=')
1021           pos++;
1022         if (postString[pos] != 0)
1023           if (!ConvertStringToUInt32((const wchar_t *)postString + pos, options.NumThreads))
1024             ThrowUserErrorException();
1025       }
1026       else if (postString[0] == 'M' && postString[1] == '=' )
1027       {
1028         int pos = 2;
1029         if (postString[pos] != 0)
1030           options.Method = postString.Mid(2);
1031       }
1032       else
1033         ThrowUserErrorException();
1034     }
1035   }
1036   else if (options.Command.CommandType == NCommandType::kInfo)
1037   {
1038   }
1039   else
1040     ThrowUserErrorException();
1041   options.WildcardCensor.ExtendExclude();
1042 }
1043