Add context menu to delete all tags in Reference Browser
[TortoiseGit.git] / src / Utils / PathUtils.cpp
blobc2f6f19b1bfff4a2568849fd9d1b94e440b62fc8
1 // TortoiseGit - a Windows shell extension for easy version control
3 // Copyright (C) 2012 - TortoiseGit
4 // Copyright (C) 2003-2008, 2013 - 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.
20 #include "stdafx.h"
21 #include "PathUtils.h"
22 #include <memory>
24 BOOL CPathUtils::MakeSureDirectoryPathExists(LPCTSTR path)
26 size_t len = _tcslen(path) + 10;
27 std::unique_ptr<TCHAR[]> buf(new TCHAR[len]);
28 std::unique_ptr<TCHAR[]> internalpathbuf(new TCHAR[len]);
29 TCHAR * pPath = internalpathbuf.get();
30 SECURITY_ATTRIBUTES attribs;
32 SecureZeroMemory(&attribs, sizeof(SECURITY_ATTRIBUTES));
34 attribs.nLength = sizeof(SECURITY_ATTRIBUTES);
35 attribs.bInheritHandle = FALSE;
37 ConvertToBackslash(internalpathbuf.get(), path, len);
40 SecureZeroMemory(buf.get(), (len)*sizeof(TCHAR));
41 TCHAR * slashpos = _tcschr(pPath, '\\');
42 if (slashpos)
43 _tcsncpy_s(buf.get(), len, internalpathbuf.get(), slashpos - internalpathbuf.get());
44 else
45 _tcsncpy_s(buf.get(), len, internalpathbuf.get(), len);
46 CreateDirectory(buf.get(), &attribs);
47 pPath = _tcschr(pPath, '\\');
48 } while ((pPath++)&&(_tcschr(pPath, '\\')));
50 return CreateDirectory(internalpathbuf.get(), &attribs);
53 void CPathUtils::Unescape(char * psz)
55 char * pszSource = psz;
56 char * pszDest = psz;
58 static const char szHex[] = "0123456789ABCDEF";
60 // Unescape special characters. The number of characters
61 // in the *pszDest is assumed to be <= the number of characters
62 // in pszSource (they are both the same string anyway)
64 while (*pszSource != '\0' && *pszDest != '\0')
66 if (*pszSource == '%')
68 // The next two chars following '%' should be digits
69 if ( *(pszSource + 1) == '\0' ||
70 *(pszSource + 2) == '\0' )
72 // nothing left to do
73 break;
76 char nValue = '?';
77 const char * pszLow = NULL;
78 const char * pszHigh = NULL;
79 pszSource++;
81 *pszSource = (char) toupper(*pszSource);
82 pszHigh = strchr(szHex, *pszSource);
84 if (pszHigh != NULL)
86 pszSource++;
87 *pszSource = (char) toupper(*pszSource);
88 pszLow = strchr(szHex, *pszSource);
90 if (pszLow != NULL)
92 nValue = (char) (((pszHigh - szHex) << 4) +
93 (pszLow - szHex));
96 else
98 pszSource--;
99 nValue = *pszSource;
101 *pszDest++ = nValue;
103 else
104 *pszDest++ = *pszSource;
106 pszSource++;
109 *pszDest = '\0';
112 static const char iri_escape_chars[256] = {
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,
116 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
117 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
118 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
119 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
120 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
122 /* 128 */
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,
126 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
127 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
128 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
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
133 const char uri_autoescape_chars[256] = {
134 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
135 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
136 0, 1, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
137 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 1, 0, 0,
139 /* 64 */
140 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
141 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1,
142 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
143 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 0,
145 /* 128 */
146 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,
151 /* 192 */
152 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
153 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
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,
158 static const char uri_char_validity[256] = {
159 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
160 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
161 0, 1, 0, 0, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
162 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 1, 0, 0,
164 /* 64 */
165 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
166 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1,
167 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
168 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 0,
170 /* 128 */
171 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,
176 /* 192 */
177 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
178 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
179 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
180 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
184 void CPathUtils::ConvertToBackslash(LPTSTR dest, LPCTSTR src, size_t len)
186 _tcscpy_s(dest, len, src);
187 TCHAR * p = dest;
188 for (; *p != '\0'; ++p)
189 if (*p == '/')
190 *p = '\\';
193 CStringA CPathUtils::PathEscape(const CStringA& path)
195 CStringA ret2;
196 int c;
197 int i;
198 for (i=0; path[i]; ++i)
200 c = (unsigned char)path[i];
201 if (iri_escape_chars[c])
203 // no escaping needed for that char
204 ret2 += (unsigned char)path[i];
206 else
208 // char needs escaping
209 CStringA temp;
210 temp.Format("%%%02X", (unsigned char)c);
211 ret2 += temp;
214 CStringA ret;
215 for (i=0; ret2[i]; ++i)
217 c = (unsigned char)ret2[i];
218 if (uri_autoescape_chars[c])
220 // no escaping needed for that char
221 ret += (unsigned char)ret2[i];
223 else
225 // char needs escaping
226 CStringA temp;
227 temp.Format("%%%02X", (unsigned char)c);
228 ret += temp;
232 if ((ret.Left(11).Compare("file:///%5C") == 0) && (ret.Find('%', 12) < 0))
233 ret.Replace(("file:///%5C"), ("file://"));
234 ret.Replace(("file:////%5C"), ("file://"));
236 return ret;
239 #ifdef CSTRING_AVAILABLE
240 CString CPathUtils::GetFileNameFromPath(CString sPath)
242 CString ret;
243 sPath.Replace(_T("/"), _T("\\"));
244 ret = sPath.Mid(sPath.ReverseFind('\\') + 1);
245 return ret;
248 CString CPathUtils::GetFileExtFromPath(const CString& sPath)
250 int dotPos = sPath.ReverseFind('.');
251 int slashPos = sPath.ReverseFind('\\');
252 if (slashPos < 0)
253 slashPos = sPath.ReverseFind('/');
254 if (dotPos > slashPos)
255 return sPath.Mid(dotPos);
256 return CString();
259 CString CPathUtils::GetLongPathname(const CString& path)
261 if (path.IsEmpty())
262 return path;
263 TCHAR pathbufcanonicalized[MAX_PATH]; // MAX_PATH ok.
264 DWORD ret = 0;
265 CString sRet;
266 if (!PathIsURL(path) && PathIsRelative(path))
268 ret = GetFullPathName(path, 0, NULL, NULL);
269 if (ret)
271 std::unique_ptr<TCHAR[]> pathbuf(new TCHAR[ret + 1]);
272 if ((ret = GetFullPathName(path, ret, pathbuf.get(), NULL)) != 0)
273 sRet = CString(pathbuf.get(), ret);
276 else if (PathCanonicalize(pathbufcanonicalized, path))
278 ret = ::GetLongPathName(pathbufcanonicalized, NULL, 0);
279 std::unique_ptr<TCHAR[]> pathbuf(new TCHAR[ret + 2]);
280 ret = ::GetLongPathName(pathbufcanonicalized, pathbuf.get(), ret + 1);
281 sRet = CString(pathbuf.get(), ret);
283 else
285 ret = ::GetLongPathName(path, NULL, 0);
286 std::unique_ptr<TCHAR[]> pathbuf(new TCHAR[ret + 2]);
287 ret = ::GetLongPathName(path, pathbuf.get(), ret + 1);
288 sRet = CString(pathbuf.get(), ret);
290 if (ret == 0)
291 return path;
292 return sRet;
295 BOOL CPathUtils::FileCopy(CString srcPath, CString destPath, BOOL force)
297 srcPath.Replace('/', '\\');
298 destPath.Replace('/', '\\');
299 CString destFolder = destPath.Left(destPath.ReverseFind('\\'));
300 MakeSureDirectoryPathExists(destFolder);
301 return (CopyFile(srcPath, destPath, !force));
304 CString CPathUtils::ParsePathInString(const CString& Str)
306 CString sToken;
307 int curPos = 0;
308 sToken = Str.Tokenize(_T("'\t\r\n"), curPos);
309 while (!sToken.IsEmpty())
311 if ((sToken.Find('/')>=0)||(sToken.Find('\\')>=0))
313 sToken.Trim(_T("'\""));
314 return sToken;
316 sToken = Str.Tokenize(_T("'\t\r\n"), curPos);
318 sToken.Empty();
319 return sToken;
322 CString CPathUtils::GetAppDirectory(HMODULE hMod /* = NULL */)
324 CString path;
325 DWORD len = 0;
326 DWORD bufferlen = MAX_PATH; // MAX_PATH is not the limit here!
327 path.GetBuffer(bufferlen);
330 bufferlen += MAX_PATH; // MAX_PATH is not the limit here!
331 path.ReleaseBuffer(0);
332 len = GetModuleFileName(hMod, path.GetBuffer(bufferlen+1), bufferlen);
333 } while(len == bufferlen);
334 path.ReleaseBuffer();
335 path = path.Left(path.ReverseFind('\\')+1);
336 return path;
339 CString CPathUtils::GetAppParentDirectory(HMODULE hMod /* = NULL */)
341 CString path = GetAppDirectory(hMod);
342 path = path.Left(path.ReverseFind('\\'));
343 path = path.Left(path.ReverseFind('\\')+1);
344 return path;
347 CString CPathUtils::GetAppDataDirectory()
349 TCHAR path[MAX_PATH]; //MAX_PATH ok here.
350 if (SHGetFolderPath(NULL, CSIDL_APPDATA, NULL, SHGFP_TYPE_CURRENT, path)!=S_OK)
351 return CString();
353 _tcscat_s(path, MAX_PATH, _T("\\TortoiseGit"));
354 if (!PathIsDirectory(path))
355 CreateDirectory(path, NULL);
357 return CString (path) + _T('\\');
361 CStringA CPathUtils::PathUnescape(const CStringA& path)
363 std::auto_ptr<char> urlabuf (new char[path.GetLength()+1]);
365 strcpy_s(urlabuf.get(), path.GetLength()+1, path);
366 Unescape(urlabuf.get());
368 return urlabuf.get();
371 CStringW CPathUtils::PathUnescape(const CStringW& path)
373 char * buf;
374 CStringA patha;
375 int len = path.GetLength();
376 if (len==0)
377 return CStringW();
378 buf = patha.GetBuffer(len*4 + 1);
379 int lengthIncTerminator = WideCharToMultiByte(CP_UTF8, 0, path, -1, buf, len*4, NULL, NULL);
380 patha.ReleaseBuffer(lengthIncTerminator-1);
382 patha = PathUnescape(patha);
384 WCHAR * bufw;
385 len = patha.GetLength();
386 bufw = new WCHAR[len*4 + 1];
387 SecureZeroMemory(bufw, (len*4 + 1)*sizeof(WCHAR));
388 MultiByteToWideChar(CP_UTF8, 0, patha, -1, bufw, len*4);
389 CStringW ret = CStringW(bufw);
390 delete [] bufw;
391 return ret;
393 #ifdef _MFC_VER
394 CString CPathUtils::GetVersionFromFile(const CString & p_strDateiname)
396 struct TRANSARRAY
398 WORD wLanguageID;
399 WORD wCharacterSet;
402 CString strReturn;
403 DWORD dwReserved,dwBufferSize;
404 dwBufferSize = GetFileVersionInfoSize((LPTSTR)(LPCTSTR)p_strDateiname,&dwReserved);
406 if (dwBufferSize > 0)
408 LPVOID pBuffer = (void*) malloc(dwBufferSize);
410 if (pBuffer != (void*) NULL)
412 UINT nInfoSize = 0,
413 nFixedLength = 0;
414 LPSTR lpVersion = NULL;
415 VOID* lpFixedPointer;
416 TRANSARRAY* lpTransArray;
417 CString strLangProduktVersion;
419 GetFileVersionInfo((LPTSTR)(LPCTSTR)p_strDateiname,
420 dwReserved,
421 dwBufferSize,
422 pBuffer);
424 // Check the current language
425 VerQueryValue( pBuffer,
426 _T("\\VarFileInfo\\Translation"),
427 &lpFixedPointer,
428 &nFixedLength);
429 lpTransArray = (TRANSARRAY*) lpFixedPointer;
431 strLangProduktVersion.Format(_T("\\StringFileInfo\\%04x%04x\\ProductVersion"),
432 lpTransArray[0].wLanguageID, lpTransArray[0].wCharacterSet);
434 VerQueryValue(pBuffer,
435 (LPTSTR)(LPCTSTR)strLangProduktVersion,
436 (LPVOID *)&lpVersion,
437 &nInfoSize);
438 if (nInfoSize && lpVersion)
439 strReturn = (LPCTSTR)lpVersion;
440 free(pBuffer);
444 return strReturn;
446 #endif
447 CString CPathUtils::PathPatternEscape(const CString& path)
449 CString result = path;
450 // first remove already escaped patterns to avoid having those
451 // escaped twice
452 result.Replace(_T("\\["), _T("["));
453 result.Replace(_T("\\]"), _T("]"));
454 // now escape the patterns (again)
455 result.Replace(_T("["), _T("\\["));
456 result.Replace(_T("]"), _T("\\]"));
457 return result;
460 CString CPathUtils::PathPatternUnEscape(const CString& path)
462 CString result = path;
463 result.Replace(_T("\\["), _T("["));
464 result.Replace(_T("\\]"), _T("]"));
465 return result;
468 #endif
470 #if defined(_DEBUG) && defined(_MFC_VER)
471 // Some test cases for these classes
472 static class CPathTests
474 public:
475 CPathTests()
477 UnescapeTest();
478 ExtTest();
479 ParseTests();
482 private:
483 void UnescapeTest()
485 CString test(_T("file:///d:/REpos1/uCOS-100/Trunk/name%20with%20spaces/NewTest%20%%20NewTest"));
486 CString test2 = CPathUtils::PathUnescape(test);
487 ATLASSERT(test2.Compare(_T("file:///d:/REpos1/uCOS-100/Trunk/name with spaces/NewTest % NewTest")) == 0);
488 CStringA test3 = CPathUtils::PathEscape("file:///d:/REpos1/uCOS-100/Trunk/name with spaces/NewTest % NewTest");
489 ATLASSERT(test3.Compare("file:///d:/REpos1/uCOS-100/Trunk/name%20with%20spaces/NewTest%20%%20NewTest") == 0);
491 void ExtTest()
493 CString test(_T("d:\\test\filename.ext"));
494 ATLASSERT(CPathUtils::GetFileExtFromPath(test).Compare(_T(".ext")) == 0);
495 test = _T("filename.ext");
496 ATLASSERT(CPathUtils::GetFileExtFromPath(test).Compare(_T(".ext")) == 0);
497 test = _T("d:\\test\filename");
498 ATLASSERT(CPathUtils::GetFileExtFromPath(test).IsEmpty());
499 test = _T("filename");
500 ATLASSERT(CPathUtils::GetFileExtFromPath(test).IsEmpty());
502 void ParseTests()
504 CString test(_T("test 'd:\\testpath with spaces' test"));
505 ATLASSERT(CPathUtils::ParsePathInString(test).Compare(_T("d:\\testpath with spaces")) == 0);
506 test = _T("d:\\testpath with spaces");
507 ATLASSERT(CPathUtils::ParsePathInString(test).Compare(_T("d:\\testpath with spaces")) == 0);
510 } CPathTests;
511 #endif