1 // TortoiseGit - a Windows shell extension for easy version control
3 // Copyright (C) 2012-2016 - TortoiseGit
4 // Copyright (C) 2003-2008, 2013-2015 - TortoiseSVN
6 // This program is free software; you can redistribute it and/or
7 // modify it under the terms of the GNU General Public License
8 // as published by the Free Software Foundation; either version 2
9 // of the License, or (at your option) any later version.
11 // This program is distributed in the hope that it will be useful,
12 // but WITHOUT ANY WARRANTY; without even the implied warranty of
13 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 // GNU General Public License for more details.
16 // You should have received a copy of the GNU General Public License
17 // along with this program; if not, write to the Free Software Foundation,
18 // 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
21 #include "PathUtils.h"
24 BOOL
CPathUtils::MakeSureDirectoryPathExists(LPCTSTR path
)
26 size_t len
= wcslen(path
) + 10;
27 auto buf
= std::make_unique
<TCHAR
[]>(len
);
28 auto internalpathbuf
= std::make_unique
<TCHAR
[]>(len
);
29 TCHAR
* pPath
= internalpathbuf
.get();
30 SECURITY_ATTRIBUTES attribs
= { 0 };
31 attribs
.nLength
= sizeof(SECURITY_ATTRIBUTES
);
32 attribs
.bInheritHandle
= FALSE
;
34 ConvertToBackslash(internalpathbuf
.get(), path
, len
);
37 SecureZeroMemory(buf
.get(), (len
)*sizeof(TCHAR
));
38 TCHAR
* slashpos
= wcschr(pPath
, L
'\\');
40 wcsncpy_s(buf
.get(), len
, internalpathbuf
.get(), slashpos
- internalpathbuf
.get());
42 wcsncpy_s(buf
.get(), len
, internalpathbuf
.get(), len
);
43 CreateDirectory(buf
.get(), &attribs
);
44 pPath
= wcschr(pPath
, L
'\\');
45 } while ((pPath
++) && (wcschr(pPath
, L
'\\')));
47 return CreateDirectory(internalpathbuf
.get(), &attribs
);
50 void CPathUtils::Unescape(char * psz
)
52 char * pszSource
= psz
;
55 static const char szHex
[] = "0123456789ABCDEF";
57 // Unescape special characters. The number of characters
58 // in the *pszDest is assumed to be <= the number of characters
59 // in pszSource (they are both the same string anyway)
61 while (*pszSource
!= '\0' && *pszDest
!= '\0')
63 if (*pszSource
== '%')
65 // The next two chars following '%' should be digits
66 if ( *(pszSource
+ 1) == '\0' ||
67 *(pszSource
+ 2) == '\0' )
76 *pszSource
= (char) toupper(*pszSource
);
77 const char* pszHigh
= strchr(szHex
, *pszSource
);
82 *pszSource
= (char) toupper(*pszSource
);
83 const char* pszLow
= strchr(szHex
, *pszSource
);
87 nValue
= (char) (((pszHigh
- szHex
) << 4) +
99 *pszDest
++ = *pszSource
;
107 static const char iri_escape_chars
[256] = {
108 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
109 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
110 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
111 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
112 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
113 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
114 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
115 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
118 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
119 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
120 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
121 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
122 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
123 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
124 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
125 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
128 const char uri_autoescape_chars
[256] = {
129 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
130 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
131 0, 1, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
132 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 1, 0, 0,
135 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
136 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1,
137 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
138 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 0,
141 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
142 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
143 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
144 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
147 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
148 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
149 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
150 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
153 static const char uri_char_validity
[256] = {
154 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
155 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
156 0, 1, 0, 0, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
157 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 1, 0, 0,
160 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
161 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1,
162 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
163 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 0,
166 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
167 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
168 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
169 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
172 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
173 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
174 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
175 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
179 void CPathUtils::ConvertToBackslash(LPTSTR dest
, LPCTSTR src
, size_t len
)
181 wcscpy_s(dest
, len
, src
);
183 for (; *p
!= '\0'; ++p
)
188 CStringA
CPathUtils::PathEscape(const CStringA
& path
)
193 for (i
=0; path
[i
]; ++i
)
195 c
= (unsigned char)path
[i
];
196 if (iri_escape_chars
[c
])
198 // no escaping needed for that char
199 ret2
+= (unsigned char)path
[i
];
201 else // char needs escaping
202 ret2
.AppendFormat("%%%02X", (unsigned char)c
);
205 for (i
=0; ret2
[i
]; ++i
)
207 c
= (unsigned char)ret2
[i
];
208 if (uri_autoescape_chars
[c
])
210 // no escaping needed for that char
211 ret
+= (unsigned char)ret2
[i
];
213 else // char needs escaping
214 ret
.AppendFormat("%%%02X", (unsigned char)c
);
217 if ((ret
.Left(11).Compare("file:///%5C") == 0) && (ret
.Find('%', 12) < 0))
218 ret
.Replace(("file:///%5C"), ("file://"));
219 ret
.Replace(("file:////%5C"), ("file://"));
224 #ifdef CSTRING_AVAILABLE
225 CString
CPathUtils::GetFileNameFromPath(CString sPath
)
228 sPath
.Replace(L
'/', L
'\\');
229 ret
= sPath
.Mid(sPath
.ReverseFind('\\') + 1);
233 CString
CPathUtils::GetFileExtFromPath(const CString
& sPath
)
235 int dotPos
= sPath
.ReverseFind('.');
236 int slashPos
= sPath
.ReverseFind('\\');
238 slashPos
= sPath
.ReverseFind('/');
239 if (dotPos
> slashPos
)
240 return sPath
.Mid(dotPos
);
244 CString
CPathUtils::GetLongPathname(const CString
& path
)
248 TCHAR pathbufcanonicalized
[MAX_PATH
] = {0}; // MAX_PATH ok.
251 if (!PathIsURL(path
) && PathIsRelative(path
))
253 ret
= GetFullPathName(path
, 0, nullptr, nullptr);
256 auto pathbuf
= std::make_unique
<TCHAR
[]>(ret
+ 1);
257 if ((ret
= GetFullPathName(path
, ret
, pathbuf
.get(), nullptr)) != 0)
258 sRet
= CString(pathbuf
.get(), ret
);
261 else if (PathCanonicalize(pathbufcanonicalized
, path
))
263 ret
= ::GetLongPathName(pathbufcanonicalized
, nullptr, 0);
266 auto pathbuf
= std::make_unique
<TCHAR
[]>(ret
+ 2);
267 ret
= ::GetLongPathName(pathbufcanonicalized
, pathbuf
.get(), ret
+ 1);
268 sRet
= CString(pathbuf
.get(), ret
);
272 ret
= ::GetLongPathName(path
, nullptr, 0);
275 auto pathbuf
= std::make_unique
<TCHAR
[]>(ret
+ 2);
276 ret
= ::GetLongPathName(path
, pathbuf
.get(), ret
+ 1);
277 sRet
= CString(pathbuf
.get(), ret
);
284 BOOL
CPathUtils::FileCopy(CString srcPath
, CString destPath
, BOOL force
)
286 srcPath
.Replace('/', '\\');
287 destPath
.Replace('/', '\\');
288 CString destFolder
= destPath
.Left(destPath
.ReverseFind('\\'));
289 MakeSureDirectoryPathExists(destFolder
);
290 return (CopyFile(srcPath
, destPath
, !force
));
293 CString
CPathUtils::ParsePathInString(const CString
& Str
)
296 CString sToken
= Str
.Tokenize(L
"'\t\r\n", curPos
);
297 while (!sToken
.IsEmpty())
299 if ((sToken
.Find('/')>=0)||(sToken
.Find('\\')>=0))
304 sToken
= Str
.Tokenize(L
"'\t\r\n", curPos
);
310 CString
CPathUtils::GetAppDirectory(HMODULE hMod
/* = nullptr */)
314 DWORD bufferlen
= MAX_PATH
; // MAX_PATH is not the limit here!
315 path
.GetBuffer(bufferlen
);
318 bufferlen
+= MAX_PATH
; // MAX_PATH is not the limit here!
319 path
.ReleaseBuffer(0);
320 len
= GetModuleFileName(hMod
, path
.GetBuffer(bufferlen
+1), bufferlen
);
321 } while(len
== bufferlen
);
322 path
.ReleaseBuffer();
323 path
= path
.Left(path
.ReverseFind('\\')+1);
324 return GetLongPathname(path
);
327 CString
CPathUtils::GetAppParentDirectory(HMODULE hMod
/* = nullptr */)
329 CString path
= GetAppDirectory(hMod
);
330 path
= path
.Left(path
.ReverseFind('\\'));
331 path
= path
.Left(path
.ReverseFind('\\')+1);
335 CString
CPathUtils::GetAppDataDirectory()
337 PWSTR pszPath
= nullptr;
338 if (SHGetKnownFolderPath(FOLDERID_RoamingAppData
, KF_FLAG_CREATE
, nullptr, &pszPath
) != S_OK
)
341 CString path
= pszPath
;
342 CoTaskMemFree(pszPath
);
343 path
+= L
"\\TortoiseGit";
344 if (!PathIsDirectory(path
))
345 CreateDirectory(path
, nullptr);
351 CString
CPathUtils::GetLocalAppDataDirectory()
353 PWSTR pszPath
= nullptr;
354 if (SHGetKnownFolderPath(FOLDERID_LocalAppData
, KF_FLAG_CREATE
, nullptr, &pszPath
) != S_OK
)
356 CString path
= pszPath
;
357 CoTaskMemFree(pszPath
);
358 path
+= L
"\\TortoiseGit";
359 if (!PathIsDirectory(path
))
360 CreateDirectory(path
, nullptr);
366 CString
CPathUtils::GetDocumentsDirectory()
368 PWSTR pszPath
= nullptr;
369 if (SHGetKnownFolderPath(FOLDERID_Documents
, KF_FLAG_CREATE
, nullptr, &pszPath
) != S_OK
)
372 CString path
= pszPath
;
373 CoTaskMemFree(pszPath
);
377 CString
CPathUtils::GetProgramsDirectory()
379 PWSTR pszPath
= nullptr;
380 if (SHGetKnownFolderPath(FOLDERID_ProgramFiles
, KF_FLAG_CREATE
, nullptr, &pszPath
) != S_OK
)
383 CString path
= pszPath
;
384 CoTaskMemFree(pszPath
);
388 CStringA
CPathUtils::PathUnescape(const CStringA
& path
)
390 auto urlabuf
= std::make_unique
<char[]>(path
.GetLength() + 1);
392 strcpy_s(urlabuf
.get(), path
.GetLength()+1, path
);
393 Unescape(urlabuf
.get());
395 return urlabuf
.get();
398 CStringW
CPathUtils::PathUnescape(const CStringW
& path
)
402 int len
= path
.GetLength();
405 buf
= patha
.GetBuffer(len
*4 + 1);
406 int lengthIncTerminator
= WideCharToMultiByte(CP_UTF8
, 0, path
, -1, buf
, len
* 4, nullptr, nullptr);
407 patha
.ReleaseBuffer(lengthIncTerminator
-1);
409 patha
= PathUnescape(patha
);
412 len
= patha
.GetLength();
413 bufw
= new WCHAR
[len
*4 + 1];
414 SecureZeroMemory(bufw
, (len
*4 + 1)*sizeof(WCHAR
));
415 MultiByteToWideChar(CP_UTF8
, 0, patha
, -1, bufw
, len
*4);
416 CStringW ret
= CStringW(bufw
);
421 #pragma comment(lib, "Version.lib")
422 CString
CPathUtils::GetVersionFromFile(const CString
& p_strFilename
)
431 DWORD dwReserved
= 0;
432 DWORD dwBufferSize
= GetFileVersionInfoSize((LPTSTR
)(LPCTSTR
)p_strFilename
,&dwReserved
);
434 if (dwBufferSize
> 0)
436 auto pBuffer
= std::make_unique
<BYTE
[]>(dwBufferSize
);
442 LPSTR lpVersion
= nullptr;
443 VOID
* lpFixedPointer
;
444 TRANSARRAY
* lpTransArray
;
445 CString strLangProductVersion
;
447 GetFileVersionInfo((LPTSTR
)(LPCTSTR
)p_strFilename
,
452 // Check the current language
453 VerQueryValue(pBuffer
.get(),
454 L
"\\VarFileInfo\\Translation",
457 lpTransArray
= (TRANSARRAY
*) lpFixedPointer
;
459 strLangProductVersion
.Format(L
"\\StringFileInfo\\%04x%04x\\ProductVersion",
460 lpTransArray
[0].wLanguageID
, lpTransArray
[0].wCharacterSet
);
462 VerQueryValue(pBuffer
.get(),
463 (LPTSTR
)(LPCTSTR
)strLangProductVersion
,
464 (LPVOID
*)&lpVersion
,
466 if (nInfoSize
&& lpVersion
)
467 strReturn
= (LPCTSTR
)lpVersion
;
474 CString
CPathUtils::PathPatternEscape(const CString
& path
)
476 CString result
= path
;
477 // first remove already escaped patterns to avoid having those
479 result
.Replace(L
"\\[", L
"[");
480 result
.Replace(L
"\\]", L
"]");
481 // now escape the patterns (again)
482 result
.Replace(L
"[", L
"\\[");
483 result
.Replace(L
"]", L
"\\]");
487 CString
CPathUtils::PathPatternUnEscape(const CString
& path
)
489 CString result
= path
;
490 result
.Replace(L
"\\[", L
"[");
491 result
.Replace(L
"\\]", L
"]");