Filenames in FILE_NOTIFY_INFORMATION are not null terminated, so don't use _TRUNCATE...
[TortoiseGit.git] / src / Utils / PathUtils.cpp
blobc227c94a7dbaba40bcd29129f623897cd46b30ad
1 // TortoiseGit - a Windows shell extension for easy version control
3 // Copyright (C) 2003-2008 - TortoiseSVN
5 // This program is free software; you can redistribute it and/or
6 // modify it under the terms of the GNU General Public License
7 // as published by the Free Software Foundation; either version 2
8 // of the License, or (at your option) any later version.
10 // This program is distributed in the hope that it will be useful,
11 // but WITHOUT ANY WARRANTY; without even the implied warranty of
12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 // GNU General Public License for more details.
15 // You should have received a copy of the GNU General Public License
16 // along with this program; if not, write to the Free Software Foundation,
17 // 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
19 #include "StdAfx.h"
20 #include "PathUtils.h"
22 BOOL CPathUtils::MakeSureDirectoryPathExists(LPCTSTR path)
24 size_t len = _tcslen(path);
25 TCHAR * buf = new TCHAR[len+10];
26 TCHAR * internalpathbuf = new TCHAR[len+10];
27 TCHAR * pPath = internalpathbuf;
28 SECURITY_ATTRIBUTES attribs;
30 SecureZeroMemory(&attribs, sizeof(SECURITY_ATTRIBUTES));
32 attribs.nLength = sizeof(SECURITY_ATTRIBUTES);
33 attribs.bInheritHandle = FALSE;
35 ConvertToBackslash(internalpathbuf, path, len+10);
38 SecureZeroMemory(buf, (len+10)*sizeof(TCHAR));
39 TCHAR * slashpos = _tcschr(pPath, '\\');
40 if (slashpos)
41 _tcsncpy_s(buf, len+10, internalpathbuf, slashpos - internalpathbuf);
42 else
43 _tcsncpy_s(buf, len+10, internalpathbuf, len+10);
44 CreateDirectory(buf, &attribs);
45 pPath = _tcschr(pPath, '\\');
46 } while ((pPath++)&&(_tcschr(pPath, '\\')));
48 BOOL bRet = CreateDirectory(internalpathbuf, &attribs);
49 delete[] buf;
50 delete[] internalpathbuf;
51 return bRet;
54 void CPathUtils::Unescape(char * psz)
56 char * pszSource = psz;
57 char * pszDest = psz;
59 static const char szHex[] = "0123456789ABCDEF";
61 // Unescape special characters. The number of characters
62 // in the *pszDest is assumed to be <= the number of characters
63 // in pszSource (they are both the same string anyway)
65 while (*pszSource != '\0' && *pszDest != '\0')
67 if (*pszSource == '%')
69 // The next two chars following '%' should be digits
70 if ( *(pszSource + 1) == '\0' ||
71 *(pszSource + 2) == '\0' )
73 // nothing left to do
74 break;
77 char nValue = '?';
78 const char * pszLow = NULL;
79 const char * pszHigh = NULL;
80 pszSource++;
82 *pszSource = (char) toupper(*pszSource);
83 pszHigh = strchr(szHex, *pszSource);
85 if (pszHigh != NULL)
87 pszSource++;
88 *pszSource = (char) toupper(*pszSource);
89 pszLow = strchr(szHex, *pszSource);
91 if (pszLow != NULL)
93 nValue = (char) (((pszHigh - szHex) << 4) +
94 (pszLow - szHex));
97 else
99 pszSource--;
100 nValue = *pszSource;
102 *pszDest++ = nValue;
104 else
105 *pszDest++ = *pszSource;
107 pszSource++;
110 *pszDest = '\0';
113 static const char iri_escape_chars[256] = {
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,
121 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
123 /* 128 */
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,
131 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
134 const char uri_autoescape_chars[256] = {
135 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
136 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
137 0, 1, 0, 0, 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, 1, 0, 0,
140 /* 64 */
141 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
142 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1,
143 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
144 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 0,
146 /* 128 */
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,
152 /* 192 */
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,
156 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
159 static const char uri_char_validity[256] = {
160 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
161 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
162 0, 1, 0, 0, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
163 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 1, 0, 0,
165 /* 64 */
166 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
167 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1,
168 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
169 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 0,
171 /* 128 */
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,
177 /* 192 */
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,
181 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
185 void CPathUtils::ConvertToBackslash(LPTSTR dest, LPCTSTR src, size_t len)
187 _tcscpy_s(dest, len, src);
188 TCHAR * p = dest;
189 for (; *p != '\0'; ++p)
190 if (*p == '/')
191 *p = '\\';
194 CStringA CPathUtils::PathEscape(const CStringA& path)
196 CStringA ret2;
197 int c;
198 int i;
199 for (i=0; path[i]; ++i)
201 c = (unsigned char)path[i];
202 if (iri_escape_chars[c])
204 // no escaping needed for that char
205 ret2 += (unsigned char)path[i];
207 else
209 // char needs escaping
210 CStringA temp;
211 temp.Format("%%%02X", (unsigned char)c);
212 ret2 += temp;
215 CStringA ret;
216 for (i=0; ret2[i]; ++i)
218 c = (unsigned char)ret2[i];
219 if (uri_autoescape_chars[c])
221 // no escaping needed for that char
222 ret += (unsigned char)ret2[i];
224 else
226 // char needs escaping
227 CStringA temp;
228 temp.Format("%%%02X", (unsigned char)c);
229 ret += temp;
233 if ((ret.Left(11).Compare("file:///%5C") == 0) && (ret.Find('%', 12) < 0))
234 ret.Replace(("file:///%5C"), ("file://"));
235 ret.Replace(("file:////%5C"), ("file://"));
237 return ret;
240 #ifdef CSTRING_AVAILABLE
241 CString CPathUtils::GetFileNameFromPath(CString sPath)
243 CString ret;
244 sPath.Replace(_T("/"), _T("\\"));
245 ret = sPath.Mid(sPath.ReverseFind('\\') + 1);
246 return ret;
249 CString CPathUtils::GetFileExtFromPath(const CString& sPath)
251 int dotPos = sPath.ReverseFind('.');
252 int slashPos = sPath.ReverseFind('\\');
253 if (slashPos < 0)
254 slashPos = sPath.ReverseFind('/');
255 if (dotPos > slashPos)
256 return sPath.Mid(dotPos);
257 return CString();
260 CString CPathUtils::GetLongPathname(const CString& path)
262 if (path.IsEmpty())
263 return path;
264 TCHAR pathbufcanonicalized[MAX_PATH]; // MAX_PATH ok.
265 DWORD ret = 0;
266 CString sRet;
267 if (!PathIsURL(path) && PathIsRelative(path))
269 ret = GetFullPathName(path, 0, NULL, NULL);
270 if (ret)
272 TCHAR * pathbuf = new TCHAR[ret+1];
273 if ((ret = GetFullPathName(path, ret, pathbuf, NULL))!=0)
275 sRet = CString(pathbuf, ret);
277 delete [] pathbuf;
280 else if (PathCanonicalize(pathbufcanonicalized, path))
282 ret = ::GetLongPathName(pathbufcanonicalized, NULL, 0);
283 TCHAR * pathbuf = new TCHAR[ret+2];
284 ret = ::GetLongPathName(pathbufcanonicalized, pathbuf, ret+1);
285 sRet = CString(pathbuf, ret);
286 delete[] pathbuf;
288 else
290 ret = ::GetLongPathName(path, NULL, 0);
291 TCHAR * pathbuf = new TCHAR[ret+2];
292 ret = ::GetLongPathName(path, pathbuf, ret+1);
293 sRet = CString(pathbuf, ret);
294 delete[] pathbuf;
296 if (ret == 0)
297 return path;
298 return sRet;
301 BOOL CPathUtils::FileCopy(CString srcPath, CString destPath, BOOL force)
303 srcPath.Replace('/', '\\');
304 destPath.Replace('/', '\\');
305 CString destFolder = destPath.Left(destPath.ReverseFind('\\'));
306 MakeSureDirectoryPathExists(destFolder);
307 return (CopyFile(srcPath, destPath, !force));
310 CString CPathUtils::ParsePathInString(const CString& Str)
312 CString sToken;
313 int curPos = 0;
314 sToken = Str.Tokenize(_T("'\t\r\n"), curPos);
315 while (!sToken.IsEmpty())
317 if ((sToken.Find('/')>=0)||(sToken.Find('\\')>=0))
319 sToken.Trim(_T("'\""));
320 return sToken;
322 sToken = Str.Tokenize(_T("'\t\r\n"), curPos);
324 sToken.Empty();
325 return sToken;
328 CString CPathUtils::GetAppDirectory(HMODULE hMod /* = NULL */)
330 CString path;
331 DWORD len = 0;
332 DWORD bufferlen = MAX_PATH; // MAX_PATH is not the limit here!
333 path.GetBuffer(bufferlen);
336 bufferlen += MAX_PATH; // MAX_PATH is not the limit here!
337 path.ReleaseBuffer(0);
338 len = GetModuleFileName(hMod, path.GetBuffer(bufferlen+1), bufferlen);
339 } while(len == bufferlen);
340 path.ReleaseBuffer();
341 path = path.Left(path.ReverseFind('\\')+1);
342 return path;
345 CString CPathUtils::GetAppParentDirectory(HMODULE hMod /* = NULL */)
347 CString path = GetAppDirectory(hMod);
348 path = path.Left(path.ReverseFind('\\'));
349 path = path.Left(path.ReverseFind('\\')+1);
350 return path;
353 CString CPathUtils::GetAppDataDirectory()
355 TCHAR path[MAX_PATH]; //MAX_PATH ok here.
356 if (SHGetFolderPath(NULL, CSIDL_APPDATA, NULL, SHGFP_TYPE_CURRENT, path)!=S_OK)
357 return CString();
359 _tcscat_s(path, MAX_PATH, _T("\\TortoiseGit"));
360 if (!PathIsDirectory(path))
361 CreateDirectory(path, NULL);
363 return CString (path) + _T('\\');
367 CStringA CPathUtils::PathUnescape(const CStringA& path)
369 std::auto_ptr<char> urlabuf (new char[path.GetLength()+1]);
371 strcpy_s(urlabuf.get(), path.GetLength()+1, path);
372 Unescape(urlabuf.get());
374 return urlabuf.get();
377 CStringW CPathUtils::PathUnescape(const CStringW& path)
379 char * buf;
380 CStringA patha;
381 int len = path.GetLength();
382 if (len==0)
383 return CStringW();
384 buf = patha.GetBuffer(len*4 + 1);
385 int lengthIncTerminator = WideCharToMultiByte(CP_UTF8, 0, path, -1, buf, len*4, NULL, NULL);
386 patha.ReleaseBuffer(lengthIncTerminator-1);
388 patha = PathUnescape(patha);
390 WCHAR * bufw;
391 len = patha.GetLength();
392 bufw = new WCHAR[len*4 + 1];
393 SecureZeroMemory(bufw, (len*4 + 1)*sizeof(WCHAR));
394 MultiByteToWideChar(CP_UTF8, 0, patha, -1, bufw, len*4);
395 CStringW ret = CStringW(bufw);
396 delete [] bufw;
397 return ret;
399 #ifdef _MFC_VER
400 CString CPathUtils::GetVersionFromFile(const CString & p_strDateiname)
402 struct TRANSARRAY
404 WORD wLanguageID;
405 WORD wCharacterSet;
408 CString strReturn;
409 DWORD dwReserved,dwBufferSize;
410 dwBufferSize = GetFileVersionInfoSize((LPTSTR)(LPCTSTR)p_strDateiname,&dwReserved);
412 if (dwBufferSize > 0)
414 LPVOID pBuffer = (void*) malloc(dwBufferSize);
416 if (pBuffer != (void*) NULL)
418 UINT nInfoSize = 0,
419 nFixedLength = 0;
420 LPSTR lpVersion = NULL;
421 VOID* lpFixedPointer;
422 TRANSARRAY* lpTransArray;
423 CString strLangProduktVersion;
425 GetFileVersionInfo((LPTSTR)(LPCTSTR)p_strDateiname,
426 dwReserved,
427 dwBufferSize,
428 pBuffer);
430 // Check the current language
431 VerQueryValue( pBuffer,
432 _T("\\VarFileInfo\\Translation"),
433 &lpFixedPointer,
434 &nFixedLength);
435 lpTransArray = (TRANSARRAY*) lpFixedPointer;
437 strLangProduktVersion.Format(_T("\\StringFileInfo\\%04x%04x\\ProductVersion"),
438 lpTransArray[0].wLanguageID, lpTransArray[0].wCharacterSet);
440 VerQueryValue(pBuffer,
441 (LPTSTR)(LPCTSTR)strLangProduktVersion,
442 (LPVOID *)&lpVersion,
443 &nInfoSize);
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