"Fossies" - the Fresh Open Source Software Archive

Member "unrar/pathfn.cpp" (4 May 2022, 27127 Bytes) of package /linux/misc/unrarsrc-6.1.7.tar.gz:


As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) C and C++ source code syntax highlighting (style: standard) with prefixed line numbers and code folding option. Alternatively you can here view or download the uninterpreted source code file. For more information about "pathfn.cpp" see the Fossies "Dox" file reference documentation and the last Fossies "Diffs" side-by-side code changes report: 6.1.4_vs_6.1.5.

    1 #include "rar.hpp"
    2 
    3 wchar* PointToName(const wchar *Path)
    4 {
    5   for (int I=(int)wcslen(Path)-1;I>=0;I--)
    6     if (IsPathDiv(Path[I]))
    7       return (wchar*)&Path[I+1];
    8   return (wchar*)((*Path!=0 && IsDriveDiv(Path[1])) ? Path+2:Path);
    9 }
   10 
   11 
   12 wchar* PointToLastChar(const wchar *Path)
   13 {
   14   size_t Length=wcslen(Path);
   15   return (wchar*)(Length>0 ? Path+Length-1:Path);
   16 }
   17 
   18 
   19 wchar* ConvertPath(const wchar *SrcPath,wchar *DestPath,size_t DestSize)
   20 {
   21   const wchar *DestPtr=SrcPath;
   22 
   23   // Prevent \..\ in any part of path string.
   24   for (const wchar *s=DestPtr;*s!=0;s++)
   25     if (IsPathDiv(s[0]) && s[1]=='.' && s[2]=='.' && IsPathDiv(s[3]))
   26       DestPtr=s+4;
   27 
   28   // Remove any amount of <d>:\ and any sequence of . and \ in the beginning of path string.
   29   while (*DestPtr!=0)
   30   {
   31     const wchar *s=DestPtr;
   32     if (s[0]!=0 && IsDriveDiv(s[1]))
   33       s+=2;
   34     if (s[0]=='\\' && s[1]=='\\')
   35     {
   36       const wchar *Slash=wcschr(s+2,'\\');
   37       if (Slash!=NULL && (Slash=wcschr(Slash+1,'\\'))!=NULL)
   38         s=Slash+1;
   39     }
   40     for (const wchar *t=s;*t!=0;t++)
   41       if (IsPathDiv(*t))
   42         s=t+1;
   43       else
   44         if (*t!='.')
   45           break;
   46     if (s==DestPtr)
   47       break;
   48     DestPtr=s;
   49   }
   50 
   51   // Code above does not remove last "..", doing here.
   52   if (DestPtr[0]=='.' && DestPtr[1]=='.' && DestPtr[2]==0)
   53     DestPtr+=2;
   54   
   55   if (DestPath!=NULL)
   56   {
   57     // SrcPath and DestPath can point to same memory area,
   58     // so we use the temporary buffer for copying.
   59     wchar TmpStr[NM];
   60     wcsncpyz(TmpStr,DestPtr,ASIZE(TmpStr));
   61     wcsncpyz(DestPath,TmpStr,DestSize);
   62   }
   63   return (wchar *)DestPtr;
   64 }
   65 
   66 
   67 void SetName(wchar *FullName,const wchar *Name,size_t MaxSize)
   68 {
   69   wchar *NamePtr=PointToName(FullName);
   70   wcsncpyz(NamePtr,Name,MaxSize-(NamePtr-FullName));
   71 }
   72 
   73 
   74 void SetExt(wchar *Name,const wchar *NewExt,size_t MaxSize)
   75 {
   76   if (Name==NULL || *Name==0)
   77     return;
   78   wchar *Dot=GetExt(Name);
   79   if (Dot!=NULL)
   80     *Dot=0;
   81   if (NewExt!=NULL)
   82   {
   83     wcsncatz(Name,L".",MaxSize);
   84     wcsncatz(Name,NewExt,MaxSize);
   85   }
   86 }
   87 
   88 
   89 #ifndef SFX_MODULE
   90 void SetSFXExt(wchar *SFXName,size_t MaxSize)
   91 {
   92   if (SFXName==NULL || *SFXName==0)
   93     return;
   94 
   95 #ifdef _UNIX
   96   SetExt(SFXName,L"sfx",MaxSize);
   97 #endif
   98 
   99 #if defined(_WIN_ALL) || defined(_EMX)
  100   SetExt(SFXName,L"exe",MaxSize);
  101 #endif
  102 }
  103 #endif
  104 
  105 
  106 // 'Ext' is an extension with the leading dot, like L".rar".
  107 wchar *GetExt(const wchar *Name)
  108 {
  109   return Name==NULL ? NULL:wcsrchr(PointToName(Name),'.');
  110 }
  111 
  112 
  113 // 'Ext' is an extension without the leading dot, like L"rar".
  114 bool CmpExt(const wchar *Name,const wchar *Ext)
  115 {
  116   wchar *NameExt=GetExt(Name);
  117   return NameExt!=NULL && wcsicomp(NameExt+1,Ext)==0;
  118 }
  119 
  120 
  121 bool IsWildcard(const wchar *Str)
  122 {
  123   if (Str==NULL)
  124     return false;
  125 #ifdef _WIN_ALL
  126   // Not treat the special NTFS \\?\d: path prefix as a wildcard.
  127   if (Str[0]=='\\' && Str[1]=='\\' && Str[2]=='?' && Str[3]=='\\')
  128     Str+=4;
  129 #endif
  130   return wcspbrk(Str,L"*?")!=NULL;
  131 }
  132 
  133 
  134 bool IsPathDiv(int Ch)
  135 {
  136 #ifdef _WIN_ALL
  137   return Ch=='\\' || Ch=='/';
  138 #else
  139   return Ch==CPATHDIVIDER;
  140 #endif
  141 }
  142 
  143 
  144 bool IsDriveDiv(int Ch)
  145 {
  146 #ifdef _UNIX
  147   return false;
  148 #else
  149   return Ch==':';
  150 #endif
  151 }
  152 
  153 
  154 bool IsDriveLetter(const wchar *Path)
  155 {
  156   wchar Letter=etoupperw(Path[0]);
  157   return Letter>='A' && Letter<='Z' && IsDriveDiv(Path[1]);
  158 }
  159 
  160 
  161 int GetPathDisk(const wchar *Path)
  162 {
  163   if (IsDriveLetter(Path))
  164     return etoupperw(*Path)-'A';
  165   else
  166     return -1;
  167 }
  168 
  169 
  170 void AddEndSlash(wchar *Path,size_t MaxLength)
  171 {
  172   size_t Length=wcslen(Path);
  173   if (Length>0 && Path[Length-1]!=CPATHDIVIDER && Length+1<MaxLength)
  174   {
  175     Path[Length]=CPATHDIVIDER;
  176     Path[Length+1]=0;
  177   }
  178 }
  179 
  180 
  181 void MakeName(const wchar *Path,const wchar *Name,wchar *Pathname,size_t MaxSize)
  182 {
  183   // 'Path', 'Name' and 'Pathname' can point to same memory area. So we use
  184   // the temporary buffer instead of constructing the name in 'Pathname'.
  185   wchar OutName[NM];
  186   wcsncpyz(OutName,Path,ASIZE(OutName));
  187   // Do not add slash to d:, we want to allow relative paths like d:filename.
  188   if (!IsDriveLetter(Path) || Path[2]!=0)
  189     AddEndSlash(OutName,ASIZE(OutName));
  190   wcsncatz(OutName,Name,ASIZE(OutName));
  191   wcsncpyz(Pathname,OutName,MaxSize);
  192 }
  193 
  194 
  195 // Returns file path including the trailing path separator symbol.
  196 void GetFilePath(const wchar *FullName,wchar *Path,size_t MaxLength)
  197 {
  198   if (MaxLength==0)
  199     return;
  200   size_t PathLength=Min(MaxLength-1,size_t(PointToName(FullName)-FullName));
  201   wcsncpy(Path,FullName,PathLength);
  202   Path[PathLength]=0;
  203 }
  204 
  205 
  206 // Removes name and returns file path without the trailing
  207 // path separator symbol.
  208 void RemoveNameFromPath(wchar *Path)
  209 {
  210   wchar *Name=PointToName(Path);
  211   if (Name>=Path+2 && (!IsDriveDiv(Path[1]) || Name>=Path+4))
  212     Name--;
  213   *Name=0;
  214 }
  215 
  216 
  217 #if defined(_WIN_ALL) && !defined(SFX_MODULE)
  218 bool GetAppDataPath(wchar *Path,size_t MaxSize,bool Create)
  219 {
  220   LPMALLOC g_pMalloc;
  221   SHGetMalloc(&g_pMalloc);
  222   LPITEMIDLIST ppidl;
  223   *Path=0;
  224   bool Success=false;
  225   if (SHGetSpecialFolderLocation(NULL,CSIDL_APPDATA,&ppidl)==NOERROR &&
  226       SHGetPathFromIDList(ppidl,Path) && *Path!=0)
  227   {
  228     AddEndSlash(Path,MaxSize);
  229     wcsncatz(Path,L"WinRAR",MaxSize);
  230     Success=FileExist(Path);
  231     if (!Success && Create)
  232       Success=MakeDir(Path,false,0)==MKDIR_SUCCESS;
  233   }
  234   g_pMalloc->Free(ppidl);
  235   return Success;
  236 }
  237 #endif
  238 
  239 
  240 #if defined(_WIN_ALL) && !defined(SFX_MODULE)
  241 void GetRarDataPath(wchar *Path,size_t MaxSize,bool Create)
  242 {
  243   *Path=0;
  244 
  245   HKEY hKey;
  246   if (RegOpenKeyEx(HKEY_CURRENT_USER,L"Software\\WinRAR\\Paths",0,
  247                    KEY_QUERY_VALUE,&hKey)==ERROR_SUCCESS)
  248   {
  249     DWORD DataSize=(DWORD)MaxSize,Type;
  250     RegQueryValueEx(hKey,L"AppData",0,&Type,(BYTE *)Path,&DataSize);
  251     RegCloseKey(hKey);
  252   }
  253 
  254   if (*Path==0 || !FileExist(Path))
  255     if (!GetAppDataPath(Path,MaxSize,Create))
  256     {
  257       GetModuleFileName(NULL,Path,(DWORD)MaxSize);
  258       RemoveNameFromPath(Path);
  259     }
  260 }
  261 #endif
  262 
  263 
  264 #ifndef SFX_MODULE
  265 bool EnumConfigPaths(uint Number,wchar *Path,size_t MaxSize,bool Create)
  266 {
  267 #ifdef _UNIX
  268   static const wchar *ConfPath[]={
  269     L"/etc", L"/etc/rar", L"/usr/lib", L"/usr/local/lib", L"/usr/local/etc"
  270   };
  271   if (Number==0)
  272   {
  273     char *EnvStr=getenv("HOME");
  274     if (EnvStr!=NULL)
  275       CharToWide(EnvStr,Path,MaxSize);
  276     else
  277       wcsncpyz(Path,ConfPath[0],MaxSize);
  278     return true;
  279   }
  280   Number--;
  281   if (Number>=ASIZE(ConfPath))
  282     return false;
  283   wcsncpyz(Path,ConfPath[Number], MaxSize);
  284   return true;
  285 #elif defined(_WIN_ALL)
  286   if (Number>1)
  287     return false;
  288   if (Number==0)
  289     GetRarDataPath(Path,MaxSize,Create);
  290   else
  291   {
  292     GetModuleFileName(NULL,Path,(DWORD)MaxSize);
  293     RemoveNameFromPath(Path);
  294   }
  295   return true;
  296 #else
  297   return false;
  298 #endif
  299 }
  300 #endif
  301 
  302 
  303 #ifndef SFX_MODULE
  304 void GetConfigName(const wchar *Name,wchar *FullName,size_t MaxSize,bool CheckExist,bool Create)
  305 {
  306   *FullName=0;
  307   for (uint I=0;EnumConfigPaths(I,FullName,MaxSize,Create);I++)
  308   {
  309     AddEndSlash(FullName,MaxSize);
  310     wcsncatz(FullName,Name,MaxSize);
  311     if (!CheckExist || WildFileExist(FullName))
  312       break;
  313   }
  314 }
  315 #endif
  316 
  317 
  318 // Returns a pointer to rightmost digit of volume number or to beginning
  319 // of file name if numeric part is missing.
  320 wchar* GetVolNumPart(const wchar *ArcName)
  321 {
  322   // We do not want to increment any characters in path component.
  323   ArcName=PointToName(ArcName);
  324 
  325   if (*ArcName==0)
  326     return (wchar *)ArcName;
  327 
  328   // Pointing to last name character.
  329   const wchar *ChPtr=ArcName+wcslen(ArcName)-1;
  330 
  331   // Skipping the archive extension.
  332   while (!IsDigit(*ChPtr) && ChPtr>ArcName)
  333     ChPtr--;
  334 
  335   // Skipping the numeric part of name.
  336   const wchar *NumPtr=ChPtr;
  337   while (IsDigit(*NumPtr) && NumPtr>ArcName)
  338     NumPtr--;
  339 
  340   // Searching for first numeric part in names like name.part##of##.rar.
  341   // Stop search on the first dot.
  342   while (NumPtr>ArcName && *NumPtr!='.')
  343   {
  344     if (IsDigit(*NumPtr))
  345     {
  346       // Validate the first numeric part only if it has a dot somewhere 
  347       // before it.
  348       const wchar *Dot=wcschr(ArcName,'.');
  349       if (Dot!=NULL && Dot<NumPtr)
  350         ChPtr=NumPtr;
  351       break;
  352     }
  353     NumPtr--;
  354   }
  355   return (wchar *)ChPtr;
  356 }
  357 
  358 
  359 void NextVolumeName(wchar *ArcName,uint MaxLength,bool OldNumbering)
  360 {
  361   wchar *ChPtr;
  362   if ((ChPtr=GetExt(ArcName))==NULL)
  363   {
  364     wcsncatz(ArcName,L".rar",MaxLength);
  365     ChPtr=GetExt(ArcName);
  366   }
  367   else
  368     if (ChPtr[1]==0 || wcsicomp(ChPtr,L".exe")==0 || wcsicomp(ChPtr,L".sfx")==0)
  369       wcsncpyz(ChPtr,L".rar",MaxLength-(ChPtr-ArcName));
  370 
  371   if (ChPtr==NULL || *ChPtr!='.' || ChPtr[1]==0)
  372   {
  373     // Normally we shall have some extension here. If we don't, it means
  374     // the name has no extension and buffer has no free space to append one.
  375     // Let's clear the name to prevent a new call with same name and return.
  376     *ArcName=0;
  377     return;
  378   }
  379 
  380   if (!OldNumbering)
  381   {
  382     ChPtr=GetVolNumPart(ArcName);
  383 
  384     // We should not check for IsDigit(*ChPtr) here and should increment
  385     // even non-digits. If we got a corrupt archive with volume flag,
  386     // but without numeric part, we still need to modify its name somehow,
  387     // so while (exist(name)) {NextVolumeName()} loops do not run infinitely.
  388     while ((++(*ChPtr))=='9'+1)
  389     {
  390       *ChPtr='0';
  391       ChPtr--;
  392       if (ChPtr<ArcName || !IsDigit(*ChPtr))
  393       {
  394         // Convert .part:.rar (.part9.rar after increment) to part10.rar.
  395         for (wchar *EndPtr=ArcName+wcslen(ArcName);EndPtr!=ChPtr;EndPtr--)
  396           *(EndPtr+1)=*EndPtr;
  397         *(ChPtr+1)='1';
  398         break;
  399       }
  400     }
  401   }
  402   else
  403     if (!IsDigit(ChPtr[2]) || !IsDigit(ChPtr[3]))
  404       wcsncpyz(ChPtr+2,L"00",MaxLength-(ChPtr-ArcName)-2); // From .rar to .r00.
  405     else
  406     {
  407       ChPtr+=wcslen(ChPtr)-1; // Set to last character.
  408       while (++(*ChPtr)=='9'+1)
  409         if (ChPtr<=ArcName || *(ChPtr-1)=='.')
  410         {
  411           *ChPtr='a'; // From .999 to .a00 if started from .001 or for too short names.
  412           break;
  413         }
  414         else
  415         {
  416           *ChPtr='0';
  417           ChPtr--;
  418         }
  419     }
  420 }
  421 
  422 
  423 bool IsNameUsable(const wchar *Name)
  424 {
  425 #ifndef _UNIX
  426   if (Name[0] && Name[1] && wcschr(Name+2,':')!=NULL)
  427     return false;
  428   for (const wchar *s=Name;*s!=0;s++)
  429   {
  430     if ((uint)*s<32)
  431       return false;
  432     if ((*s==' ' || *s=='.') && IsPathDiv(s[1]))
  433       return false;
  434   }
  435 #endif
  436   return *Name!=0 && wcspbrk(Name,L"?*<>|\"")==NULL;
  437 }
  438 
  439 
  440 void MakeNameUsable(char *Name,bool Extended)
  441 {
  442 #ifdef _WIN_ALL
  443   // In Windows we also need to convert characters not defined in current
  444   // code page. This double conversion changes them to '?', which is
  445   // catched by code below.
  446   size_t NameLength=strlen(Name);
  447   wchar NameW[NM];
  448   CharToWide(Name,NameW,ASIZE(NameW));
  449   WideToChar(NameW,Name,NameLength+1);
  450   Name[NameLength]=0;
  451 #endif
  452   for (char *s=Name;*s!=0;s=charnext(s))
  453   {
  454     if (strchr(Extended ? "?*<>|\"":"?*",*s)!=NULL || Extended && (byte)*s<32)
  455       *s='_';
  456 #ifdef _EMX
  457     if (*s=='=')
  458       *s='_';
  459 #endif
  460 #ifndef _UNIX
  461     if (s-Name>1 && *s==':')
  462       *s='_';
  463     // Remove ' ' and '.' before path separator, but allow .\ and ..\.
  464     if ((*s==' ' || *s=='.' && s>Name && !IsPathDiv(s[-1]) && s[-1]!='.') && IsPathDiv(s[1]))
  465       *s='_';
  466 #endif
  467   }
  468 }
  469 
  470 
  471 void MakeNameUsable(wchar *Name,bool Extended)
  472 {
  473   for (wchar *s=Name;*s!=0;s++)
  474   {
  475     if (wcschr(Extended ? L"?*<>|\"":L"?*",*s)!=NULL || Extended && (uint)*s<32)
  476       *s='_';
  477 #ifndef _UNIX
  478     if (s-Name>1 && *s==':')
  479       *s='_';
  480 #if 0  // We already can create such files.
  481     // Remove ' ' and '.' before path separator, but allow .\ and ..\.
  482     if (IsPathDiv(s[1]) && (*s==' ' || *s=='.' && s>Name &&
  483         !IsPathDiv(s[-1]) && (s[-1]!='.' || s>Name+1 && !IsPathDiv(s[-2]))))
  484       *s='_';
  485 #endif
  486 #endif
  487   }
  488 }
  489 
  490 
  491 void UnixSlashToDos(const char *SrcName,char *DestName,size_t MaxLength)
  492 {
  493   size_t Copied=0;
  494   for (;Copied<MaxLength-1 && SrcName[Copied]!=0;Copied++)
  495     DestName[Copied]=SrcName[Copied]=='/' ? '\\':SrcName[Copied];
  496   DestName[Copied]=0;
  497 }
  498 
  499 
  500 void DosSlashToUnix(const char *SrcName,char *DestName,size_t MaxLength)
  501 {
  502   size_t Copied=0;
  503   for (;Copied<MaxLength-1 && SrcName[Copied]!=0;Copied++)
  504     DestName[Copied]=SrcName[Copied]=='\\' ? '/':SrcName[Copied];
  505   DestName[Copied]=0;
  506 }
  507 
  508 
  509 void UnixSlashToDos(const wchar *SrcName,wchar *DestName,size_t MaxLength)
  510 {
  511   size_t Copied=0;
  512   for (;Copied<MaxLength-1 && SrcName[Copied]!=0;Copied++)
  513     DestName[Copied]=SrcName[Copied]=='/' ? '\\':SrcName[Copied];
  514   DestName[Copied]=0;
  515 }
  516 
  517 
  518 void DosSlashToUnix(const wchar *SrcName,wchar *DestName,size_t MaxLength)
  519 {
  520   size_t Copied=0;
  521   for (;Copied<MaxLength-1 && SrcName[Copied]!=0;Copied++)
  522     DestName[Copied]=SrcName[Copied]=='\\' ? '/':SrcName[Copied];
  523   DestName[Copied]=0;
  524 }
  525 
  526 
  527 void ConvertNameToFull(const wchar *Src,wchar *Dest,size_t MaxSize)
  528 {
  529   if (Src==NULL || *Src==0)
  530   {
  531     if (MaxSize>0)
  532       *Dest=0;
  533     return;
  534   }
  535 #ifdef _WIN_ALL
  536   {
  537     wchar FullName[NM],*NamePtr;
  538     DWORD Code=GetFullPathName(Src,ASIZE(FullName),FullName,&NamePtr);
  539     if (Code==0 || Code>ASIZE(FullName))
  540     {
  541       wchar LongName[NM];
  542       if (GetWinLongPath(Src,LongName,ASIZE(LongName)))
  543         Code=GetFullPathName(LongName,ASIZE(FullName),FullName,&NamePtr);
  544     }
  545     if (Code!=0 && Code<ASIZE(FullName))
  546       wcsncpyz(Dest,FullName,MaxSize);
  547     else
  548       if (Src!=Dest)
  549         wcsncpyz(Dest,Src,MaxSize);
  550   }
  551 #elif defined(_UNIX)
  552   if (IsFullPath(Src))
  553     *Dest=0;
  554   else
  555   {
  556     char CurDirA[NM];
  557     if (getcwd(CurDirA,ASIZE(CurDirA))==NULL)
  558       *CurDirA=0;
  559     CharToWide(CurDirA,Dest,MaxSize);
  560     AddEndSlash(Dest,MaxSize);
  561   }
  562   wcsncatz(Dest,Src,MaxSize);
  563 #else
  564   wcsncpyz(Dest,Src,MaxSize);
  565 #endif
  566 }
  567 
  568 
  569 bool IsFullPath(const wchar *Path)
  570 {
  571 /*
  572   wchar PathOnly[NM];
  573   GetFilePath(Path,PathOnly,ASIZE(PathOnly));
  574   if (IsWildcard(PathOnly))
  575     return true;
  576 */
  577 #if defined(_WIN_ALL) || defined(_EMX)
  578   return Path[0]=='\\' && Path[1]=='\\' || IsDriveLetter(Path) && IsPathDiv(Path[2]);
  579 #else
  580   return IsPathDiv(Path[0]);
  581 #endif
  582 }
  583 
  584 
  585 bool IsFullRootPath(const wchar *Path)
  586 {
  587   return IsFullPath(Path) || IsPathDiv(Path[0]);
  588 }
  589 
  590 
  591 void GetPathRoot(const wchar *Path,wchar *Root,size_t MaxSize)
  592 {
  593   *Root=0;
  594   if (IsDriveLetter(Path))
  595     swprintf(Root,MaxSize,L"%c:\\",*Path);
  596   else
  597     if (Path[0]=='\\' && Path[1]=='\\')
  598     {
  599       const wchar *Slash=wcschr(Path+2,'\\');
  600       if (Slash!=NULL)
  601       {
  602         size_t Length;
  603         if ((Slash=wcschr(Slash+1,'\\'))!=NULL)
  604           Length=Slash-Path+1;
  605         else
  606           Length=wcslen(Path);
  607         if (Length>=MaxSize)
  608           Length=0;
  609         wcsncpy(Root,Path,Length);
  610         Root[Length]=0;
  611       }
  612     }
  613 }
  614 
  615 
  616 int ParseVersionFileName(wchar *Name,bool Truncate)
  617 {
  618   int Version=0;
  619   wchar *VerText=wcsrchr(Name,';');
  620   if (VerText!=NULL)
  621   {
  622     Version=atoiw(VerText+1);
  623     if (Truncate)
  624       *VerText=0;
  625   }
  626   return Version;
  627 }
  628 
  629 
  630 #if !defined(SFX_MODULE)
  631 // Get the name of first volume. Return the leftmost digit of volume number.
  632 wchar* VolNameToFirstName(const wchar *VolName,wchar *FirstName,size_t MaxSize,bool NewNumbering)
  633 {
  634   if (FirstName!=VolName)
  635     wcsncpyz(FirstName,VolName,MaxSize);
  636   wchar *VolNumStart=FirstName;
  637   if (NewNumbering)
  638   {
  639     wchar N='1';
  640 
  641     // From the rightmost digit of volume number to the left.
  642     for (wchar *ChPtr=GetVolNumPart(FirstName);ChPtr>FirstName;ChPtr--)
  643       if (IsDigit(*ChPtr))
  644       {
  645         *ChPtr=N; // Set the rightmost digit to '1' and others to '0'.
  646         N='0';
  647       }
  648       else
  649         if (N=='0')
  650         {
  651           VolNumStart=ChPtr+1; // Store the position of leftmost digit in volume number.
  652           break;
  653         }
  654   }
  655   else
  656   {
  657     // Old volume numbering scheme. Just set the extension to ".rar".
  658     SetExt(FirstName,L"rar",MaxSize);
  659     VolNumStart=GetExt(FirstName);
  660   }
  661   if (!FileExist(FirstName))
  662   {
  663     // If the first volume, which name we just generated, does not exist,
  664     // check if volume with same name and any other extension is available.
  665     // It can help in case of *.exe or *.sfx first volume.
  666     wchar Mask[NM];
  667     wcsncpyz(Mask,FirstName,ASIZE(Mask));
  668     SetExt(Mask,L"*",ASIZE(Mask));
  669     FindFile Find;
  670     Find.SetMask(Mask);
  671     FindData FD;
  672     while (Find.Next(&FD))
  673     {
  674       Archive Arc;
  675       if (Arc.Open(FD.Name,0) && Arc.IsArchive(true) && Arc.FirstVolume)
  676       {
  677         wcsncpyz(FirstName,FD.Name,MaxSize);
  678         break;
  679       }
  680     }
  681   }
  682   return VolNumStart;
  683 }
  684 #endif
  685 
  686 
  687 #ifndef SFX_MODULE
  688 static void GenArcName(wchar *ArcName,size_t MaxSize,const wchar *GenerateMask,uint ArcNumber,bool &ArcNumPresent)
  689 {
  690   bool Prefix=false;
  691   if (*GenerateMask=='+')
  692   {
  693     Prefix=true;    // Add the time string before the archive name.
  694     GenerateMask++; // Skip '+' in the beginning of time mask.
  695   }
  696 
  697   wchar Mask[MAX_GENERATE_MASK];
  698   wcsncpyz(Mask,*GenerateMask!=0 ? GenerateMask:L"yyyymmddhhmmss",ASIZE(Mask));
  699 
  700   bool QuoteMode=false;
  701   uint MAsMinutes=0; // By default we treat 'M' as months.
  702   for (uint I=0;Mask[I]!=0;I++)
  703   {
  704     if (Mask[I]=='{' || Mask[I]=='}')
  705     {
  706       QuoteMode=(Mask[I]=='{');
  707       continue;
  708     }
  709     if (QuoteMode)
  710       continue;
  711     int CurChar=toupperw(Mask[I]);
  712     if (CurChar=='H')
  713       MAsMinutes=2; // Treat next two 'M' after 'H' as minutes.
  714     if (CurChar=='D' || CurChar=='Y')
  715       MAsMinutes=0; // Treat 'M' in HHDDMMYY and HHYYMMDD as month.
  716 
  717     if (MAsMinutes>0 && CurChar=='M')
  718     {
  719       // Replace minutes with 'I'. We use 'M' both for months and minutes,
  720       // so we treat as minutes only those 'M', which are found after hours.
  721       Mask[I]='I';
  722       MAsMinutes--;
  723     }
  724     if (CurChar=='N')
  725     {
  726       uint Digits=GetDigits(ArcNumber);
  727       uint NCount=0;
  728       while (toupperw(Mask[I+NCount])=='N')
  729         NCount++;
  730 
  731       // Here we ensure that we have enough 'N' characters to fit all digits
  732       // of archive number. We'll replace them by actual number later
  733       // in this function.
  734       if (NCount<Digits)
  735       {
  736         wmemmove(Mask+I+Digits,Mask+I+NCount,wcslen(Mask+I+NCount)+1);
  737         wmemset(Mask+I,'N',Digits);
  738       }
  739       I+=Max(Digits,NCount)-1;
  740       ArcNumPresent=true;
  741       continue;
  742     }
  743   }
  744 
  745   RarTime CurTime;
  746   CurTime.SetCurrentTime();
  747   RarLocalTime rlt;
  748   CurTime.GetLocal(&rlt);
  749 
  750   wchar Ext[NM],*Dot=GetExt(ArcName);
  751   *Ext=0;
  752   if (Dot==NULL)
  753     wcsncpyz(Ext,*PointToName(ArcName)==0 ? L".rar":L"",ASIZE(Ext));
  754   else
  755   {
  756     wcsncpyz(Ext,Dot,ASIZE(Ext));
  757     *Dot=0;
  758   }
  759 
  760   int WeekDay=rlt.wDay==0 ? 6:rlt.wDay-1;
  761   int StartWeekDay=rlt.yDay-WeekDay;
  762   if (StartWeekDay<0)
  763     if (StartWeekDay<=-4)
  764       StartWeekDay+=IsLeapYear(rlt.Year-1) ? 366:365;
  765     else
  766       StartWeekDay=0;
  767   int CurWeek=StartWeekDay/7+1;
  768   if (StartWeekDay%7>=4)
  769     CurWeek++;
  770 
  771   char Field[10][6];
  772 
  773   sprintf(Field[0],"%04u",rlt.Year);
  774   sprintf(Field[1],"%02u",rlt.Month);
  775   sprintf(Field[2],"%02u",rlt.Day);
  776   sprintf(Field[3],"%02u",rlt.Hour);
  777   sprintf(Field[4],"%02u",rlt.Minute);
  778   sprintf(Field[5],"%02u",rlt.Second);
  779   sprintf(Field[6],"%02u",(uint)CurWeek);
  780   sprintf(Field[7],"%u",(uint)WeekDay+1);
  781   sprintf(Field[8],"%03u",rlt.yDay+1);
  782   sprintf(Field[9],"%05u",ArcNumber);
  783 
  784   const wchar *MaskChars=L"YMDHISWAEN";
  785 
  786   // How many times every modifier character was encountered in the mask.
  787   int CField[sizeof(Field)/sizeof(Field[0])];
  788 
  789   memset(CField,0,sizeof(CField));
  790   QuoteMode=false;
  791   for (uint I=0;Mask[I]!=0;I++)
  792   {
  793     if (Mask[I]=='{' || Mask[I]=='}')
  794     {
  795       QuoteMode=(Mask[I]=='{');
  796       continue;
  797     }
  798     if (QuoteMode)
  799       continue;
  800     const wchar *ChPtr=wcschr(MaskChars,toupperw(Mask[I]));
  801     if (ChPtr!=NULL)
  802       CField[ChPtr-MaskChars]++;
  803    }
  804 
  805   wchar DateText[MAX_GENERATE_MASK];
  806   *DateText=0;
  807   QuoteMode=false;
  808   for (size_t I=0,J=0;Mask[I]!=0 && J<ASIZE(DateText)-1;I++)
  809   {
  810     if (Mask[I]=='{' || Mask[I]=='}')
  811     {
  812       QuoteMode=(Mask[I]=='{');
  813       continue;
  814     }
  815     const wchar *ChPtr=wcschr(MaskChars,toupperw(Mask[I]));
  816     if (ChPtr==NULL || QuoteMode)
  817     {
  818       DateText[J]=Mask[I];
  819 #ifdef _WIN_ALL
  820       // We do not allow ':' in Windows because of NTFS streams.
  821       // Users had problems after specifying hh:mm mask.
  822       if (DateText[J]==':')
  823         DateText[J]='_';
  824 #endif
  825     }
  826     else
  827     {
  828       size_t FieldPos=ChPtr-MaskChars;
  829       int CharPos=(int)strlen(Field[FieldPos])-CField[FieldPos]--;
  830 
  831       // CField[FieldPos] shall have exactly 3 "MMM" symbols, so we do not
  832       // repeat the month name in case "MMMMMMMM" mask. But since we
  833       // decremented CField[FieldPos] above, we compared it with 2.
  834       if (FieldPos==1 && CField[FieldPos]==2 &&
  835           toupperw(Mask[I+1])=='M' && toupperw(Mask[I+2])=='M')
  836       {
  837         wcsncpyz(DateText+J,GetMonthName(rlt.Month-1),ASIZE(DateText)-J);
  838         J=wcslen(DateText);
  839         I+=2;
  840         continue;
  841       }
  842       // If CharPos is negative, we have more modifier characters than
  843       // matching time data. We prefer to issue a modifier character
  844       // instead of repeating time data from beginning, so user can notice
  845       // excessive modifiers added by mistake.
  846       if (CharPos<0)
  847         DateText[J]=Mask[I];
  848       else
  849         DateText[J]=Field[FieldPos][CharPos];
  850     }
  851     DateText[++J]=0;
  852   }
  853 
  854   if (Prefix)
  855   {
  856     wchar NewName[NM];
  857     GetFilePath(ArcName,NewName,ASIZE(NewName));
  858     AddEndSlash(NewName,ASIZE(NewName));
  859     wcsncatz(NewName,DateText,ASIZE(NewName));
  860     wcsncatz(NewName,PointToName(ArcName),ASIZE(NewName));
  861     wcsncpyz(ArcName,NewName,MaxSize);
  862   }
  863   else
  864     wcsncatz(ArcName,DateText,MaxSize);
  865   wcsncatz(ArcName,Ext,MaxSize);
  866 }
  867 
  868 
  869 void GenerateArchiveName(wchar *ArcName,size_t MaxSize,const wchar *GenerateMask,bool Archiving)
  870 {
  871   wchar NewName[NM];
  872 
  873   uint ArcNumber=1;
  874   while (true) // Loop for 'N' (archive number) processing.
  875   {
  876     wcsncpyz(NewName,ArcName,ASIZE(NewName));
  877     
  878     bool ArcNumPresent=false;
  879 
  880     GenArcName(NewName,ASIZE(NewName),GenerateMask,ArcNumber,ArcNumPresent);
  881     
  882     if (!ArcNumPresent)
  883       break;
  884     if (!FileExist(NewName))
  885     {
  886       if (!Archiving && ArcNumber>1)
  887       {
  888         // If we perform non-archiving operation, we need to use the last
  889         // existing archive before the first unused name. So we generate
  890         // the name for (ArcNumber-1) below.
  891         wcsncpyz(NewName,NullToEmpty(ArcName),ASIZE(NewName));
  892         GenArcName(NewName,ASIZE(NewName),GenerateMask,ArcNumber-1,ArcNumPresent);
  893       }
  894       break;
  895     }
  896     ArcNumber++;
  897   }
  898   wcsncpyz(ArcName,NewName,MaxSize);
  899 }
  900 #endif
  901 
  902 
  903 wchar* GetWideName(const char *Name,const wchar *NameW,wchar *DestW,size_t DestSize)
  904 {
  905   if (NameW!=NULL && *NameW!=0)
  906   {
  907     if (DestW!=NameW)
  908       wcsncpy(DestW,NameW,DestSize);
  909   }
  910   else
  911     if (Name!=NULL)
  912       CharToWide(Name,DestW,DestSize);
  913     else
  914       *DestW=0;
  915 
  916   // Ensure that we return a zero terminate string for security reasons.
  917   if (DestSize>0)
  918     DestW[DestSize-1]=0;
  919 
  920   return DestW;
  921 }
  922 
  923 
  924 #ifdef _WIN_ALL
  925 // We should return 'true' even if resulting path is shorter than MAX_PATH,
  926 // because we can also use this function to open files with non-standard
  927 // characters, even if their path length is normal.
  928 bool GetWinLongPath(const wchar *Src,wchar *Dest,size_t MaxSize)
  929 {
  930   if (*Src==0)
  931     return false;
  932   const wchar *Prefix=L"\\\\?\\";
  933   const size_t PrefixLength=4;
  934   bool FullPath=IsDriveLetter(Src) && IsPathDiv(Src[2]);
  935   size_t SrcLength=wcslen(Src);
  936   if (IsFullPath(Src)) // Paths in d:\path\name format.
  937   {
  938     if (IsDriveLetter(Src))
  939     {
  940       if (MaxSize<=PrefixLength+SrcLength)
  941         return false;
  942       wcsncpyz(Dest,Prefix,MaxSize);
  943       wcsncatz(Dest,Src,MaxSize); // "\\?\D:\very long path".
  944       return true;
  945     }
  946     else
  947       if (Src[0]=='\\' && Src[1]=='\\')
  948       {
  949         if (MaxSize<=PrefixLength+SrcLength+2)
  950           return false;
  951         wcsncpyz(Dest,Prefix,MaxSize);
  952         wcsncatz(Dest,L"UNC",MaxSize);
  953         wcsncatz(Dest,Src+1,MaxSize); // "\\?\UNC\server\share".
  954         return true;
  955       }
  956     // We may be here only if we modify IsFullPath in the future.
  957     return false;
  958   }
  959   else
  960   {
  961     wchar CurDir[NM];
  962     DWORD DirCode=GetCurrentDirectory(ASIZE(CurDir)-1,CurDir);
  963     if (DirCode==0 || DirCode>ASIZE(CurDir)-1)
  964       return false;
  965 
  966     if (IsPathDiv(Src[0])) // Paths in \path\name format.
  967     {
  968       if (MaxSize<=PrefixLength+SrcLength+2)
  969         return false;
  970       wcsncpyz(Dest,Prefix,MaxSize);
  971       CurDir[2]=0;
  972       wcsncatz(Dest,CurDir,MaxSize); // Copy drive letter 'd:'.
  973       wcsncatz(Dest,Src,MaxSize);
  974       return true;
  975     }
  976     else  // Paths in path\name format.
  977     {
  978       AddEndSlash(CurDir,ASIZE(CurDir));
  979       if (MaxSize<=PrefixLength+wcslen(CurDir)+SrcLength)
  980         return false;
  981       wcsncpyz(Dest,Prefix,MaxSize);
  982       wcsncatz(Dest,CurDir,MaxSize);
  983 
  984       if (Src[0]=='.' && IsPathDiv(Src[1])) // Remove leading .\ in pathname.
  985         Src+=2;
  986 
  987       wcsncatz(Dest,Src,MaxSize);
  988       return true;
  989     }
  990   }
  991   return false;
  992 }
  993 
  994 
  995 // Convert Unix, OS X and Android decomposed chracters to Windows precomposed.
  996 void ConvertToPrecomposed(wchar *Name,size_t NameSize)
  997 {
  998   wchar FileName[NM];
  999   if (WinNT()>=WNT_VISTA && // MAP_PRECOMPOSED is not supported in XP.
 1000       FoldString(MAP_PRECOMPOSED,Name,-1,FileName,ASIZE(FileName))!=0)
 1001   {
 1002     FileName[ASIZE(FileName)-1]=0;
 1003     wcsncpyz(Name,FileName,NameSize);
 1004   }
 1005 }
 1006 
 1007 
 1008 void MakeNameCompatible(wchar *Name,size_t MaxSize)
 1009 {
 1010   // Remove trailing spaces and dots in file name and in dir names in path.
 1011   int Src=0,Dest=0;
 1012   while (true)
 1013   {
 1014     if (IsPathDiv(Name[Src]) || Name[Src]==0)
 1015       for (int I=Dest-1;I>0 && (Name[I]==' ' || Name[I]=='.');I--)
 1016       {
 1017         // Permit path1/./path2 and ../path1 paths.
 1018         if (Name[I]=='.' && (IsPathDiv(Name[I-1]) || Name[I-1]=='.' && I==1))
 1019           break;
 1020         Dest--;
 1021       }
 1022     Name[Dest]=Name[Src];
 1023     if (Name[Src]==0)
 1024       break;
 1025     Src++;
 1026     Dest++;
 1027   }
 1028 
 1029   // Rename reserved device names, such as aux.txt to _aux.txt.
 1030   // We check them in path components too, where they are also prohibited.
 1031   for (uint I=0;Name[I]!=0;I++)
 1032     if (I==0 || I>0 && IsPathDiv(Name[I-1]))
 1033     {
 1034       static const wchar *Devices[]={L"CON",L"PRN",L"AUX",L"NUL",L"COM#",L"LPT#"};
 1035       wchar *s=Name+I;
 1036       bool MatchFound=false;
 1037       for (uint J=0;J<ASIZE(Devices);J++)
 1038         for (uint K=0;;K++)
 1039           if (Devices[J][K]=='#')
 1040           {
 1041             if (!IsDigit(s[K]))
 1042               break;
 1043           }
 1044           else
 1045             if (Devices[J][K]==0)
 1046             {
 1047               // Names like aux.txt are accessible without \\?\ prefix
 1048               // since Windows 11. Pure aux is still prohibited.
 1049               MatchFound=s[K]==0 || s[K]=='.' && !IsWindows11OrGreater() || IsPathDiv(s[K]);
 1050               break;
 1051             }
 1052             else
 1053               if (Devices[J][K]!=toupperw(s[K]))
 1054                 break;
 1055       if (MatchFound)
 1056       {
 1057         wchar OrigName[NM];
 1058         wcsncpyz(OrigName,Name,ASIZE(OrigName));
 1059         if (MaxSize>I+1) // I+1, because we do not move the trailing 0.
 1060           memmove(s+1,s,(MaxSize-I-1)*sizeof(*s));
 1061         *s='_';
 1062 #ifndef SFX_MODULE
 1063         uiMsg(UIMSG_CORRECTINGNAME,nullptr);
 1064         uiMsg(UIERROR_RENAMING,nullptr,OrigName,Name);
 1065 #endif
 1066       }
 1067     }
 1068 }
 1069 #endif
 1070 
 1071