Merge pull request #2599 from unxed/iterm_fix
[far2l.git] / multiarc / src / arccmd.cpp
blob5708002104ae4b403c0076d827dc32c2f9dd5432
1 #include <windows.h>
2 #include "MultiArc.hpp"
3 #include "marclng.hpp"
4 #include <farkeys.h>
5 #include <fcntl.h>
6 #include "utils.h"
8 ArcCommand::ArcCommand(struct PluginPanelItem *PanelItem, int ItemsNumber, const std::string &FormatString,
9 const std::string &ArcName, const std::string &ArcDir, const std::string &Password, const std::string &AllFilesMask,
10 int IgnoreErrors, int CommandType, int ASilent, const char *RealArcDir, int DefaultCodepage)
12 NeedSudo = false;
13 Silent = ASilent;
14 ExecCode = (DWORD)-1;
16 ArcCommand::DefaultCodepage = DefaultCodepage;
18 // fprintf(stderr, "ArcCommand::ArcCommand = %d, FormatString=%s\n", ArcCommand::DefaultCodepage, FormatString);
19 if (FormatString.empty())
20 return;
22 bool arc_modify =
23 (CommandType != CMD_EXTRACT && CommandType != CMD_EXTRACTWITHOUTPATH && CommandType != CMD_TEST);
25 if (arc_modify) {
26 if (!ArcName.empty()) {
27 const auto &ArcPath = ExtractFilePath(ArcName);
28 if ((sudo_client_is_required_for(ArcName.c_str(), true) == 1) || // no write perms to archive itself?
29 (sudo_client_is_required_for(ArcPath.c_str(), true)==1)) { // no write perms to archive dir?
30 NeedSudo = true;
33 if(!NeedSudo && (CommandType==CMD_ADD || CommandType==CMD_ADDRECURSE)) { // adding files to the archive,
34 for(int i=0; i<ItemsNumber; ++i) { // check if we have read access to all of them
35 if(sudo_client_is_required_for(PanelItem[i].FindData.cFileName,false)==1) {
36 NeedSudo = true;
37 break;
42 } else {
43 if((sudo_client_is_required_for(ArcName.c_str(), false) == 1) // do we have read access to the archive?
44 || ((CommandType == CMD_EXTRACT || CommandType == CMD_EXTRACTWITHOUTPATH) // extraction from the archive,
45 && (sudo_client_is_required_for(".", true) == 1))) { // check if we have write access to dest dir
46 NeedSudo = true;
50 // char QPassword[NM+5],QTempPath[NM+5];
51 ArcCommand::PanelItem = PanelItem;
52 ArcCommand::ItemsNumber = ItemsNumber;
53 ArcCommand::ArcName = ArcName;
54 ArcCommand::ArcDir = ArcDir;
55 ArcCommand::RealArcDir = RealArcDir ? RealArcDir : "";
56 ArcCommand::Password = Password;
57 QuoteCmdArgIfNeed(ArcCommand::Password);
58 ArcCommand::AllFilesMask = AllFilesMask;
59 // WINPORT(GetTempPath)(ARRAYSIZE(TempPath),TempPath);
60 ArcCommand::TempPath = InMyTemp();
61 NameNumber = -1;
62 NextFileName.clear();
63 do {
64 PrevFileNameNumber = -1;
65 if (!ProcessCommand(FormatString, CommandType, IgnoreErrors, ListFileName.c_str()))
66 NameNumber = -1;
67 if (!ListFileName.empty()) {
68 if (!Opt.Background)
69 sdc_remove(ListFileName.c_str());
70 ListFileName.clear();
72 } while (NameNumber != -1 && NameNumber < ItemsNumber);
75 bool ArcCommand::ProcessCommand(std::string FormatString, int CommandType, int IgnoreErrors, const char *pcListFileName)
77 MaxAllowedExitCode = 0;
78 DeleteBraces(FormatString);
80 // for (char *CurPtr=Command; *CurPtr;)
81 std::string tmp, NonVar, Command;
82 while (!FormatString.empty()) {
83 tmp = FormatString;
84 int r = ReplaceVar(tmp);
85 // fprintf(stderr, "ReplaceVar: %d '%s' -> '%s'\n", r, FormatString.c_str(), tmp.c_str());
86 if (r < 0)
87 return false;
88 if (r == 0) {
89 r = 1;
90 NonVar+= FormatString[0];
91 } else {
92 Command+= ExpandEnv(NonVar);
93 Command+= tmp;
94 NonVar.clear();
97 FormatString.erase(0, r);
99 Command+= ExpandEnv(NonVar);
100 // fprintf(stderr, "Command='%s'\n", Command.c_str());
102 if (Command.empty()) {
103 if (!Silent) {
104 const char *MsgItems[] = {GetMsg(MError), GetMsg(MArcCommandNotFound), GetMsg(MOk)};
105 Info.Message(Info.ModuleNumber, FMSG_WARNING, NULL, MsgItems, ARRAYSIZE(MsgItems), 1);
107 return false;
110 int Hide = Opt.HideOutput;
111 if ((Hide == 1 && CommandType == 0) || CommandType == 2)
112 Hide = 0;
114 ExecCode = Execute(this, Command, Hide, Silent, NeedSudo, Password.empty(), ListFileName.c_str());
115 fprintf(stderr, "ArcCommand::ProcessCommand: ExecCode=%d for '%s'\n", ExecCode, Command.c_str());
117 // Unzip in MacOS definitely doesn't have -I and -O options, so dont even try encoding workarounds
118 #ifndef __WXOSX__
119 if (ExecCode == 11 && strncmp(Command.c_str(), "unzip ", 6) == 0) {
120 // trying as utf8
121 std::string CommandRetry = Command;
122 CommandRetry.insert(6, "-I utf8 -O utf8 ");
123 ExecCode = Execute(this, CommandRetry, Hide, Silent, NeedSudo, Password.empty(), ListFileName.c_str());
124 if (ExecCode == 11) {
125 // "11" means file was not found in archive. retrying as oem
126 CommandRetry = Command;
127 unsigned int retry_cp = (DefaultCodepage > 0) ? DefaultCodepage : WINPORT(GetOEMCP)();
128 CommandRetry.insert(6, StrPrintf("-I CP%u -O CP%u ", retry_cp, retry_cp));
129 ExecCode = Execute(this, CommandRetry, Hide, Silent, NeedSudo, Password.empty(), ListFileName.c_str());
131 if (ExecCode == 1) {
132 // "1" exit code for unzip is warning only, no need to bother user
133 ExecCode = 0;
135 } else if (ExecCode == 12 && strncmp(Command.c_str(), "zip -d", 6) == 0) {
137 for (size_t i_entries = 6; i_entries + 1 < Command.size(); ++i_entries) {
138 if (Command[i_entries] == ' ' && Command[i_entries + 1] != ' ' && Command[i_entries + 1] != '-') {
139 i_entries = Command.find(' ', i_entries + 1);
140 if (i_entries != std::string::npos) {
141 std::wstring wstr = StrMB2Wide(Command.substr(i_entries));
142 std::vector<char> oemstr(wstr.size() * 6 + 2);
143 char def_char = '\x01';
144 BOOL def_char_used = FALSE;
145 unsigned int retry_cp = (DefaultCodepage > 0) ? DefaultCodepage : WINPORT(GetOEMCP)();
146 WINPORT(WideCharToMultiByte)
147 (retry_cp, 0, wstr.c_str(), wstr.size() + 1, &oemstr[0], oemstr.size() - 1, &def_char,
148 &def_char_used);
149 if (!def_char_used) {
150 std::string CommandRetry = Command.substr(0, i_entries);
151 CommandRetry.append(&oemstr[0]);
152 ExecCode = Execute(this, CommandRetry.c_str(),
153 Hide, Silent, NeedSudo, Password.empty(), ListFileName.c_str());
154 fprintf(stderr, "ArcCommand::ProcessCommand: retry ExecCode=%d for '%s'\n", ExecCode,
155 CommandRetry.c_str());
156 } else {
157 fprintf(stderr, "ArcCommand::ProcessCommand: can't translate to CP%u: '%s'\n",
158 retry_cp, Command.c_str());
161 break;
165 #endif
166 if (ExecCode == RETEXEC_ARCNOTFOUND)
167 return false;
169 if (ExecCode <= MaxAllowedExitCode)
170 ExecCode = 0;
172 if (!IgnoreErrors && ExecCode != 0) {
173 if (!Silent) {
174 const auto &ErrMsg = StrPrintf(GetMsg(MArcNonZero), ExecCode);
175 const auto &NameMsg = FormatMessagePath(ArcName.c_str(), false);
176 const char *MsgItems[] = {GetMsg(MError), NameMsg.c_str(), ErrMsg.c_str(), GetMsg(MOk)};
177 Info.Message(Info.ModuleNumber, FMSG_WARNING, NULL, MsgItems, ARRAYSIZE(MsgItems), 1);
179 return false;
182 return true;
185 void ArcCommand::DeleteBraces(std::string &Command)
187 std::string CheckStr;
188 for (size_t left = std::string::npos;;) {
189 size_t right = Command.rfind('}', left);
190 if (right == std::string::npos || right == 0)
191 return;
193 left = Command.rfind('{', right - 1);
194 if (left == std::string::npos)
195 return;
197 bool NonEmptyVar = false;
198 for (size_t i = left + 1; i + 2 < right; ++i) {
199 if (Command[i] == '%' && Command[i + 1] == '%' && strchr("FfLl", Command[i + 2]) != NULL) {
200 NonEmptyVar = (ItemsNumber > 0);
201 break;
203 CheckStr.assign(Command.c_str() + i, std::min((size_t)4, right - i));
204 if (ReplaceVar(CheckStr) && CheckStr.size() > 0) {
205 NonEmptyVar = true;
206 break;
210 if (NonEmptyVar) {
211 Command[left] = ' ';
212 Command[right] = ' ';
213 } else {
214 Command.erase(left, right - left + 1);
219 static void CutToPathOrSpace(std::string &Path)
221 size_t slash = Path.rfind(GOOD_SLASH);
222 if (slash != std::string::npos)
223 Path.resize(slash);
224 else
225 Path = ' ';
228 int ArcCommand::ReplaceVar(std::string &Command)
230 int MaxNamesLength = 0x10000;
231 std::string LocalAllFilesMask = AllFilesMask;
232 bool UseSlash = false;
233 bool FolderMask = false;
234 bool FolderName = false;
235 bool NameOnly = false;
236 bool PathOnly = false;
237 int QuoteName = 0;
239 if (Command.size() < 3)
240 return 0;
242 char Chr = Command[2] & (~0x20);
243 if (Command[0] != '%' || Command[1] != '%' || Chr < 'A' || Chr > 'Z')
244 return 0;
246 size_t VarLength = 3;
248 while (VarLength < Command.size()) {
249 bool BreakScan = false;
250 Chr = Command[VarLength];
251 if (Command[2] == 'F' && Chr >= '0' && Chr <= '9') {
252 MaxNamesLength = FSF.atoi(Command.c_str() + VarLength);
253 while (Chr >= '0' && Chr <= '9' && VarLength < Command.size())
254 Chr = Command[++VarLength];
255 continue;
257 if (Command[2] == 'E' && Chr >= '0' && Chr <= '9') {
258 MaxAllowedExitCode = FSF.atoi(Command.c_str() + VarLength);
259 while (Chr >= '0' && Chr <= '9' && VarLength < Command.size())
260 Chr = Command[++VarLength];
261 continue;
263 switch (Command[VarLength]) {
264 case 'A':
265 break; /* deprecated AnsiCode = true; */
266 case 'Q':
267 QuoteName = 1;
268 break;
269 case 'q':
270 QuoteName = 2;
271 break;
272 case 'S':
273 UseSlash = true;
274 break;
275 case 'M':
276 FolderMask = true;
277 break;
278 case 'N':
279 FolderName = true;
280 break;
281 case 'W':
282 NameOnly = true;
283 break;
284 case 'P':
285 PathOnly = true;
286 break;
287 case '*':
288 LocalAllFilesMask = "*";
289 break;
290 default:
291 BreakScan = true;
293 if (BreakScan)
294 break;
295 VarLength++;
298 if ((MaxNamesLength-= (int)Command.size()) <= 0)
299 MaxNamesLength = 1;
301 if (!FolderMask && !FolderName)
302 FolderName = true;
304 /////////////////////////////////
305 switch (Command[2]) {
306 case 'T': /* charset, if known */
307 Command.clear();
308 for (int N = 0; N < ItemsNumber; ++N) {
309 const ArcItemAttributes *Attrs = (const ArcItemAttributes *)PanelItem[N].UserData;
310 if (Attrs && Attrs->Codepage > 0) {
311 Command = StrPrintf("CP%u", Attrs->Codepage);
312 break;
316 if (Command.empty() && DefaultCodepage > 0)
317 Command = StrPrintf("CP%u", DefaultCodepage);
320 break;
322 case 'A':
323 case 'a': /* deprecated: short name - works same as normal name */
324 Command = ArcName;
325 if (PathOnly)
326 CutToPathOrSpace(Command);
327 QuoteCmdArgIfNeed(Command);
328 break;
330 case 'D':
331 case 'E':
332 Command.clear();
333 break;
335 case 'L':
336 case 'l':
337 if (!MakeListFile(QuoteName, UseSlash, FolderName, NameOnly, PathOnly, FolderMask, LocalAllFilesMask.c_str())) {
338 return -1;
340 Command = ListFileName;
341 QuoteCmdArgIfNeed(Command);
342 break;
344 case 'P':
345 Command = Password;
346 break;
348 case 'C':
349 if (!CommentFileName.empty()) {// второй раз сюда не лезем
350 Command.clear();
351 char Buf[MAX_PATH];
352 if (FSF.MkTemp(Buf, "FAR")) {
353 CharArrayAssignToStr(CommentFileName, Buf);
354 if (Info.InputBox(GetMsg(MComment), GetMsg(MInputComment), NULL, "", Buf, sizeof(Buf), NULL, 0)) {
355 //??тут можно и заполнить строку комментарием, но надо знать, файловый
356 //?? он или архивный. да и имя файла в архиве тоже надо знать...
357 if (WriteWholeFile(CommentFileName.c_str(), Buf, strnlen(Buf, ARRAYSIZE(Buf)))) {
358 Command = CommentFileName;
361 WINPORT(FlushConsoleInputBuffer)(NULL); // GetStdHandle(STD_INPUT_HANDLE));
364 break;
366 case 'r':
367 Command = RealArcDir;
368 if (!Command.empty())
369 Command+= '/';
370 break;
371 case 'R':
372 Command = RealArcDir;
373 if (UseSlash) {
375 QuoteCmdArgIfNeed(Command);
376 break;
378 case 'W':
379 Command = TempPath;
380 break;
382 case 'F':
383 case 'f':
384 if (PanelItem != NULL) {
385 std::string CurArcDir = ArcDir;
386 if (!CurArcDir.empty() && CurArcDir[CurArcDir.size() - 1] != GOOD_SLASH)
387 CurArcDir+= GOOD_SLASH;
389 std::string Names, Name;
391 if (NameNumber == -1)
392 NameNumber = 0;
394 while (NameNumber < ItemsNumber || Command[2] == 'f') {
395 int IncreaseNumber = 0;
396 DWORD FileAttr;
397 if (!NextFileName.empty()) {
398 Name = PrefixFileName;
399 Name+= CurArcDir;
400 Name+= NextFileName;
401 NextFileName.clear();
402 FileAttr = 0;
403 } else {
404 int N;
405 if (Command[2] == 'f' && PrevFileNameNumber != -1)
406 N = PrevFileNameNumber;
407 else {
408 N = NameNumber;
409 IncreaseNumber = 1;
411 if (N >= ItemsNumber)
412 break;
414 PrefixFileName.clear();
415 const char *cFileName = PanelItem[N].FindData.cFileName;
416 const ArcItemAttributes *Attrs = (const ArcItemAttributes *)PanelItem[N].UserData;
417 if (Attrs) {
418 if (Attrs->Prefix)
419 PrefixFileName = *Attrs->Prefix;
420 if (Attrs->LinkName)
421 cFileName = Attrs->LinkName->c_str();
423 // CHECK for BUGS!!
424 Name = PrefixFileName;
425 if (*cFileName != GOOD_SLASH) {
426 Name+= CurArcDir;
427 Name+= cFileName;
428 } else
429 Name+= cFileName + 1;
430 NormalizePath(Name);
431 FileAttr = PanelItem[N].FindData.dwFileAttributes;
432 PrevFileNameNumber = N;
434 if (NameOnly) {
435 size_t slash = Name.rfind(GOOD_SLASH);
436 if (slash != std::string::npos)
437 Name.erase(0, slash + 1);
439 if (PathOnly)
440 CutToPathOrSpace(Name);
441 if (Names.empty()
442 || (int(Names.size() + Name.size()) < MaxNamesLength && Command[2] != 'f')) {
443 NameNumber+= IncreaseNumber;
444 if (FileAttr & FILE_ATTRIBUTE_DIRECTORY) {
445 std::string FolderMaskName = Name;
446 if (!PathOnly) {
447 FolderMaskName+= GOOD_SLASH;
448 FolderMaskName+= LocalAllFilesMask;
449 } else
450 CutToPathOrSpace(FolderMaskName);
451 if (FolderMask) {
452 if (FolderName)
453 NextFileName.swap(FolderMaskName);
454 else
455 Name.swap(FolderMaskName);
459 if (QuoteName == 1)
460 QuoteCmdArgIfNeed(Name);
461 else if (QuoteName == 2)
462 QuoteCmdArg(Name);
464 if (!Names.empty())
465 Names+= ' ';
466 Names+= Name;
467 } else
468 break;
470 Command.swap(Names);
471 } else
472 Command.clear();
473 break;
474 default:
475 return 0;
478 return VarLength;
481 int ArcCommand::MakeListFile(int QuoteName, int UseSlash, int FolderName, int NameOnly,
482 int PathOnly, int FolderMask, const char *LocalAllFilesMask)
484 // FILE *ListFile;
485 HANDLE ListFile = INVALID_HANDLE_VALUE;
486 DWORD WriteSize;
487 /*SECURITY_ATTRIBUTES sa;
489 sa.nLength=sizeof(sa);
490 sa.lpSecurityDescriptor=NULL;
491 sa.bInheritHandle=TRUE; //WTF???
493 char TmpListFileName[MAX_PATH + 1] = {0};
494 if (FSF.MkTemp(TmpListFileName, "FAR") == NULL
495 || (ListFile = WINPORT(CreateFile)(MB2Wide(TmpListFileName).c_str(), GENERIC_WRITE,
496 FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, CREATE_ALWAYS, //&sa
497 FILE_FLAG_SEQUENTIAL_SCAN, NULL)) == INVALID_HANDLE_VALUE)
499 if (!Silent) {
500 const auto &MsgListFileName = FormatMessagePath(TmpListFileName, false);
501 const char *MsgItems[] = {GetMsg(MError), GetMsg(MCannotCreateListFile), MsgListFileName.c_str(), GetMsg(MOk)};
502 Info.Message(Info.ModuleNumber, FMSG_WARNING, NULL, MsgItems, ARRAYSIZE(MsgItems), 1);
504 /* $ 25.07.2001 AA
505 if(ListFile != INVALID_HANDLE_VALUE)
506 WINPORT(CloseHandle)(ListFile);
507 25.07.2001 AA $*/
508 return FALSE;
510 std::string CurArcDir, FileName, OutName;
511 // char Buf[3*NM];
513 if (!NameOnly)
514 CurArcDir = ArcDir;
516 if (!CurArcDir.empty() && CurArcDir[CurArcDir.size() - 1] != GOOD_SLASH)
517 CurArcDir+= GOOD_SLASH;
519 // if (UseSlash);
521 for (int I = 0; I < ItemsNumber; I++) {
522 if (NameOnly)
523 FileName = FSF.PointToName(PanelItem[I].FindData.cFileName);
524 else if (PathOnly)
525 FileName.assign(PanelItem[I].FindData.cFileName,
526 FSF.PointToName(PanelItem[I].FindData.cFileName) - PanelItem[I].FindData.cFileName);
527 else
528 FileName = PanelItem[I].FindData.cFileName;
530 int FileAttr = PanelItem[I].FindData.dwFileAttributes;
532 PrefixFileName.clear();
533 const ArcItemAttributes *Attrs = (const ArcItemAttributes *)PanelItem[I].UserData;
534 if (Attrs) {
535 if (Attrs->Prefix)
536 PrefixFileName = *Attrs->Prefix;
537 if (Attrs->LinkName)
538 FileName = *Attrs->LinkName;
541 int Error = FALSE;
542 if (((FileAttr & FILE_ATTRIBUTE_DIRECTORY) == 0 || FolderName)) {
543 // CHECK for BUGS!!
544 OutName = PrefixFileName;
545 if (*FileName.c_str() != '/') {
546 OutName+= CurArcDir;
547 OutName+= FileName;
548 } else
549 OutName+= FileName.c_str() + 1;
551 NormalizePath(OutName);
553 if (QuoteName == 1)
554 QuoteCmdArgIfNeed(OutName);
555 else if (QuoteName == 2)
556 QuoteCmdArg(OutName);
558 OutName+= NATIVE_EOL;
560 Error = WINPORT(WriteFile)(ListFile, OutName.c_str(), OutName.size(), &WriteSize, NULL) == FALSE;
561 // Error=fwrite(Buf,1,strlen(Buf),ListFile) != strlen(Buf);
563 if (!Error && (FileAttr & FILE_ATTRIBUTE_DIRECTORY) && FolderMask) {
564 OutName = PrefixFileName;
565 OutName+= CurArcDir;
566 OutName+= FileName;
567 OutName+= '/';
568 OutName+= LocalAllFilesMask;
569 if (QuoteName == 1)
570 QuoteCmdArgIfNeed(OutName);
571 else if (QuoteName == 2)
572 QuoteCmdArg(OutName);
573 OutName+= NATIVE_EOL;
574 Error = WINPORT(WriteFile)(ListFile, OutName.c_str(), OutName.size(), &WriteSize, NULL) == FALSE;
575 // Error=fwrite(Buf,1,strlen(Buf),ListFile) != strlen(Buf);
577 if (Error) {
578 WINPORT(CloseHandle)(ListFile);
579 sdc_remove(TmpListFileName);
580 if (!Silent) {
581 const char *MsgItems[] = {GetMsg(MError), GetMsg(MCannotCreateListFile), GetMsg(MOk)};
582 Info.Message(Info.ModuleNumber, FMSG_WARNING, NULL, MsgItems, ARRAYSIZE(MsgItems), 1);
584 return FALSE;
588 WINPORT(CloseHandle)(ListFile);
589 CharArrayAssignToStr(ListFileName, TmpListFileName);
592 if (!WINPORT(CloseHandle)(ListFile))
594 // clearerr(ListFile);
595 WINPORT(CloseHandle)(ListFile);
596 DeleteFile(ListFileName);
597 if(!Silent)
599 char *MsgItems[]={GetMsg(MError),GetMsg(MCannotCreateListFile),GetMsg(MOk)};
600 Info.Message(Info.ModuleNumber,FMSG_WARNING,NULL,MsgItems,ARRAYSIZE(MsgItems),1);
602 return FALSE;
605 return TRUE;
608 ArcCommand::~ArcCommand() //$ AA 25.11.2001
610 /* if(CommentFile!=INVALID_HANDLE_VALUE)
611 WINPORT(CloseHandle)(CommentFile);*/
612 if (!CommentFileName.empty())
613 sdc_remove(CommentFileName.c_str());