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