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
29extern 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
47using namespace NCommandLineParser;
48using namespace NWindows;
49using namespace NFile;
50
51int g_CodePage = -1;
52
53namespace NKey {
54enum 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
95static const wchar_t kRecursedIDChar = 'R';
96static const wchar_t *kRecursedPostCharSet = L"0-";
97
98namespace NRecursedPostCharIndex {
99  enum EEnum
100  {
101    kWildCardRecursionOnly = 0,
102    kNoRecursion = 1
103  };
104}
105
106static const char kImmediateNameID = '!';
107static const char kMapNameID = '#';
108static const char kFileListID = '@';
109
110static const char kSomeCludePostStringMinSize = 2; // at least <@|!><N>ame must be
111static const char kSomeCludeAfterRecursedPostStringMinSize = 2; // at least <@|!><N>ame must be
112
113static const wchar_t *kOverwritePostCharSet = L"asut";
114
115NExtract::NOverwriteMode::EEnum k_OverwriteModes[] =
116{
117  NExtract::NOverwriteMode::kWithoutPrompt,
118  NExtract::NOverwriteMode::kSkipExisting,
119  NExtract::NOverwriteMode::kAutoRename,
120  NExtract::NOverwriteMode::kAutoRenameExisting
121};
122
123static 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
161static 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
174static const int kNumCommandForms = sizeof(g_CommandForms) /  sizeof(g_CommandForms[0]);
175
176static const wchar_t *kUniversalWildcard = L"*";
177static const int kMinNonSwitchWords = 1;
178static const int kCommandIndex = 0;
179
180// ---------------------------
181// exception messages
182
183static const char *kUserErrorMessage  = "Incorrect command line";
184static const char *kCannotFindListFile = "Cannot find listfile";
185static const char *kIncorrectListFile = "Incorrect item in listfile.\nCheck charset encoding and -scs switch.";
186static const char *kIncorrectWildCardInListFile = "Incorrect wildcard in listfile";
187static const char *kIncorrectWildCardInCommandLine  = "Incorrect wildcard in command line";
188static const char *kTerminalOutError = "I won't write compressed data to a terminal";
189static const char *kSameTerminalError = "I won't write data and program's messages to same terminal";
190static const char *kEmptyFilePath = "Empty file path";
191
192static void ThrowException(const char *errorMessage)
193{
194  throw CArchiveCommandLineException(errorMessage);
195}
196
197static void ThrowUserErrorException()
198{
199  ThrowException(kUserErrorMessage);
200}
201
202// ---------------------------
203
204bool 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
217NExtract::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
229bool CArchiveCommand::IsFromUpdateGroup() const
230{
231  return (CommandType == NCommandType::kAdd ||
232    CommandType == NCommandType::kUpdate ||
233    CommandType == NCommandType::kDelete);
234}
235
236static 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
249static 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
265static 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
282static 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
294static 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
315static 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
378static 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
419static 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
428static 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
439static 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
470static 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
481static 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
493const UString kUpdatePairStateIDSet = L"PQRXYZW";
494const int kUpdatePairStateNotSupportedActions[] = {2, 2, 1, -1, -1, -1, -1};
495
496const UString kUpdatePairActionIDSet = L"0123"; //Ignore, Copy, Compress, Create Anti
497
498const wchar_t *kUpdateIgnoreItselfPostStringID = L"-";
499const wchar_t kUpdateNewArchivePostCharID = '!';
500
501
502static 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
529static 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
572static const char kByteSymbol = 'B';
573static const char kKiloSymbol = 'K';
574static const char kMegaSymbol = 'M';
575static const char kGigaSymbol = 'G';
576
577static 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
617static 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
669static 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
691CArchiveCommandLineParser::CArchiveCommandLineParser():
692  parser(sizeof(kSwitchForms) / sizeof(kSwitchForms[0])) {}
693
694void 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
725struct CCodePagePair
726{
727  const wchar_t *Name;
728  UINT CodePage;
729};
730
731static CCodePagePair g_CodePagePairs[] =
732{
733  { L"UTF-8", CP_UTF8 },
734  { L"WIN", CP_ACP },
735  { L"DOS", CP_OEMCP }
736};
737
738static 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
757static 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
769void 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
815void 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