Home | History | Annotate | Download | only in Common
      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 
    192 static void ThrowException(const char *errorMessage)
    193 {
    194   throw CArchiveCommandLineException(errorMessage);
    195 }
    196 
    197 static void ThrowUserErrorException()
    198 {
    199   ThrowException(kUserErrorMessage);
    200 }
    201 
    202 // ---------------------------
    203 
    204 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 
    217 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 
    229 bool CArchiveCommand::IsFromUpdateGroup() const
    230 {
    231   return (CommandType == NCommandType::kAdd ||
    232     CommandType == NCommandType::kUpdate ||
    233     CommandType == NCommandType::kDelete);
    234 }
    235 
    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 
    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 
    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 
    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 
    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
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    691 CArchiveCommandLineParser::CArchiveCommandLineParser():
    692   parser(sizeof(kSwitchForms) / sizeof(kSwitchForms[0])) {}
    693 
    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 
    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 
    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 
    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 
    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