Initialize more buffers
[TortoiseGit.git] / src / Utils / PathUtils.cpp
blob0b3783f398ce297cb0237d6c8eedab502c96f39a
1 // TortoiseGit - a Windows shell extension for easy version control
3 // Copyright (C) 2012-2014 - TortoiseGit
4 // Copyright (C) 2003-2008, 2013-2014 - 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] = {0}; // 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 if (ret == 0)
280 return path;
281 std::unique_ptr<TCHAR[]> pathbuf(new TCHAR[ret + 2]);
282 ret = ::GetLongPathName(pathbufcanonicalized, pathbuf.get(), ret + 1);
283 sRet = CString(pathbuf.get(), ret);
285 else
287 ret = ::GetLongPathName(path, NULL, 0);
288 if (ret == 0)
289 return path;
290 std::unique_ptr<TCHAR[]> pathbuf(new TCHAR[ret + 2]);
291 ret = ::GetLongPathName(path, pathbuf.get(), ret + 1);
292 sRet = CString(pathbuf.get(), ret);
294 if (ret == 0)
295 return path;
296 return sRet;
299 BOOL CPathUtils::FileCopy(CString srcPath, CString destPath, BOOL force)
301 srcPath.Replace('/', '\\');
302 destPath.Replace('/', '\\');
303 CString destFolder = destPath.Left(destPath.ReverseFind('\\'));
304 MakeSureDirectoryPathExists(destFolder);
305 return (CopyFile(srcPath, destPath, !force));
308 CString CPathUtils::ParsePathInString(const CString& Str)
310 CString sToken;
311 int curPos = 0;
312 sToken = Str.Tokenize(_T("'\t\r\n"), curPos);
313 while (!sToken.IsEmpty())
315 if ((sToken.Find('/')>=0)||(sToken.Find('\\')>=0))
317 sToken.Trim(_T("'\""));
318 return sToken;
320 sToken = Str.Tokenize(_T("'\t\r\n"), curPos);
322 sToken.Empty();
323 return sToken;
326 CString CPathUtils::GetAppDirectory(HMODULE hMod /* = NULL */)
328 CString path;
329 DWORD len = 0;
330 DWORD bufferlen = MAX_PATH; // MAX_PATH is not the limit here!
331 path.GetBuffer(bufferlen);
334 bufferlen += MAX_PATH; // MAX_PATH is not the limit here!
335 path.ReleaseBuffer(0);
336 len = GetModuleFileName(hMod, path.GetBuffer(bufferlen+1), bufferlen);
337 } while(len == bufferlen);
338 path.ReleaseBuffer();
339 path = path.Left(path.ReverseFind('\\')+1);
340 return path;
343 CString CPathUtils::GetAppParentDirectory(HMODULE hMod /* = NULL */)
345 CString path = GetAppDirectory(hMod);
346 path = path.Left(path.ReverseFind('\\'));
347 path = path.Left(path.ReverseFind('\\')+1);
348 return path;
351 CString CPathUtils::GetAppDataDirectory()
353 TCHAR path[MAX_PATH] = { 0 }; //MAX_PATH ok here.
354 SecureZeroMemory(path, sizeof(path));
355 if (SHGetFolderPath(NULL, CSIDL_APPDATA, NULL, SHGFP_TYPE_CURRENT, path)!=S_OK)
356 return CString();
358 _tcscat_s(path, MAX_PATH, _T("\\TortoiseGit"));
359 if (!PathIsDirectory(path))
360 CreateDirectory(path, NULL);
362 return CString (path) + _T('\\');
366 CStringA CPathUtils::PathUnescape(const CStringA& path)
368 std::unique_ptr<char[]> urlabuf (new char[path.GetLength() + 1]);
370 strcpy_s(urlabuf.get(), path.GetLength()+1, path);
371 Unescape(urlabuf.get());
373 return urlabuf.get();
376 CStringW CPathUtils::PathUnescape(const CStringW& path)
378 char * buf;
379 CStringA patha;
380 int len = path.GetLength();
381 if (len==0)
382 return CStringW();
383 buf = patha.GetBuffer(len*4 + 1);
384 int lengthIncTerminator = WideCharToMultiByte(CP_UTF8, 0, path, -1, buf, len*4, NULL, NULL);
385 patha.ReleaseBuffer(lengthIncTerminator-1);
387 patha = PathUnescape(patha);
389 WCHAR * bufw;
390 len = patha.GetLength();
391 bufw = new WCHAR[len*4 + 1];
392 SecureZeroMemory(bufw, (len*4 + 1)*sizeof(WCHAR));
393 MultiByteToWideChar(CP_UTF8, 0, patha, -1, bufw, len*4);
394 CStringW ret = CStringW(bufw);
395 delete [] bufw;
396 return ret;
398 #ifdef _MFC_VER
399 CString CPathUtils::GetVersionFromFile(const CString & p_strFilename)
401 struct TRANSARRAY
403 WORD wLanguageID;
404 WORD wCharacterSet;
407 CString strReturn;
408 DWORD dwReserved = 0;
409 DWORD dwBufferSize = GetFileVersionInfoSize((LPTSTR)(LPCTSTR)p_strFilename,&dwReserved);
411 if (dwBufferSize > 0)
413 LPVOID pBuffer = (void*) malloc(dwBufferSize);
415 if (pBuffer != (void*) NULL)
417 UINT nInfoSize = 0,
418 nFixedLength = 0;
419 LPSTR lpVersion = NULL;
420 VOID* lpFixedPointer;
421 TRANSARRAY* lpTransArray;
422 CString strLangProductVersion;
424 GetFileVersionInfo((LPTSTR)(LPCTSTR)p_strFilename,
425 dwReserved,
426 dwBufferSize,
427 pBuffer);
429 // Check the current language
430 VerQueryValue( pBuffer,
431 _T("\\VarFileInfo\\Translation"),
432 &lpFixedPointer,
433 &nFixedLength);
434 lpTransArray = (TRANSARRAY*) lpFixedPointer;
436 strLangProductVersion.Format(_T("\\StringFileInfo\\%04x%04x\\ProductVersion"),
437 lpTransArray[0].wLanguageID, lpTransArray[0].wCharacterSet);
439 VerQueryValue(pBuffer,
440 (LPTSTR)(LPCTSTR)strLangProductVersion,
441 (LPVOID *)&lpVersion,
442 &nInfoSize);
443 if (nInfoSize && lpVersion)
444 strReturn = (LPCTSTR)lpVersion;
445 free(pBuffer);
449 return strReturn;
451 #endif
452 CString CPathUtils::PathPatternEscape(const CString& path)
454 CString result = path;
455 // first remove already escaped patterns to avoid having those
456 // escaped twice
457 result.Replace(_T("\\["), _T("["));
458 result.Replace(_T("\\]"), _T("]"));
459 // now escape the patterns (again)
460 result.Replace(_T("["), _T("\\["));
461 result.Replace(_T("]"), _T("\\]"));
462 return result;
465 CString CPathUtils::PathPatternUnEscape(const CString& path)
467 CString result = path;
468 result.Replace(_T("\\["), _T("["));
469 result.Replace(_T("\\]"), _T("]"));
470 return result;
473 #endif
475 #if defined(_DEBUG) && defined(_MFC_VER)
476 // Some test cases for these classes
477 static class CPathTests
479 public:
480 CPathTests()
482 UnescapeTest();
483 ExtTest();
484 ParseTests();
487 private:
488 void UnescapeTest()
490 CString test(_T("file:///d:/REpos1/uCOS-100/Trunk/name%20with%20spaces/NewTest%20%%20NewTest"));
491 CString test2 = CPathUtils::PathUnescape(test);
492 ATLASSERT(test2.Compare(_T("file:///d:/REpos1/uCOS-100/Trunk/name with spaces/NewTest % NewTest")) == 0);
493 CStringA test3 = CPathUtils::PathEscape("file:///d:/REpos1/uCOS-100/Trunk/name with spaces/NewTest % NewTest");
494 ATLASSERT(test3.Compare("file:///d:/REpos1/uCOS-100/Trunk/name%20with%20spaces/NewTest%20%%20NewTest") == 0);
496 void ExtTest()
498 CString test(_T("d:\\test\filename.ext"));
499 ATLASSERT(CPathUtils::GetFileExtFromPath(test).Compare(_T(".ext")) == 0);
500 test = _T("filename.ext");
501 ATLASSERT(CPathUtils::GetFileExtFromPath(test).Compare(_T(".ext")) == 0);
502 test = _T("d:\\test\filename");
503 ATLASSERT(CPathUtils::GetFileExtFromPath(test).IsEmpty());
504 test = _T("filename");
505 ATLASSERT(CPathUtils::GetFileExtFromPath(test).IsEmpty());
507 void ParseTests()
509 CString test(_T("test 'd:\\testpath with spaces' test"));
510 ATLASSERT(CPathUtils::ParsePathInString(test).Compare(_T("d:\\testpath with spaces")) == 0);
511 test = _T("d:\\testpath with spaces");
512 ATLASSERT(CPathUtils::ParsePathInString(test).Compare(_T("d:\\testpath with spaces")) == 0);
515 } CPathTests;
516 #endif