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.
21 #include "PathUtils.h"
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
, '\\');
43 _tcsncpy_s(buf
.get(), len
, internalpathbuf
.get(), slashpos
- internalpathbuf
.get());
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
;
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' )
77 const char * pszLow
= NULL
;
78 const char * pszHigh
= NULL
;
81 *pszSource
= (char) toupper(*pszSource
);
82 pszHigh
= strchr(szHex
, *pszSource
);
87 *pszSource
= (char) toupper(*pszSource
);
88 pszLow
= strchr(szHex
, *pszSource
);
92 nValue
= (char) (((pszHigh
- szHex
) << 4) +
104 *pszDest
++ = *pszSource
;
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,
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,
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,
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,
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,
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,
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,
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
);
188 for (; *p
!= '\0'; ++p
)
193 CStringA
CPathUtils::PathEscape(const CStringA
& path
)
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
];
208 // char needs escaping
210 temp
.Format("%%%02X", (unsigned char)c
);
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
];
225 // char needs escaping
227 temp
.Format("%%%02X", (unsigned char)c
);
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://"));
239 #ifdef CSTRING_AVAILABLE
240 CString
CPathUtils::GetFileNameFromPath(CString sPath
)
243 sPath
.Replace(_T("/"), _T("\\"));
244 ret
= sPath
.Mid(sPath
.ReverseFind('\\') + 1);
248 CString
CPathUtils::GetFileExtFromPath(const CString
& sPath
)
250 int dotPos
= sPath
.ReverseFind('.');
251 int slashPos
= sPath
.ReverseFind('\\');
253 slashPos
= sPath
.ReverseFind('/');
254 if (dotPos
> slashPos
)
255 return sPath
.Mid(dotPos
);
259 CString
CPathUtils::GetLongPathname(const CString
& path
)
263 TCHAR pathbufcanonicalized
[MAX_PATH
]; // MAX_PATH ok.
266 if (!PathIsURL(path
) && PathIsRelative(path
))
268 ret
= GetFullPathName(path
, 0, NULL
, NULL
);
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
);
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
);
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
)
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("'\""));
316 sToken
= Str
.Tokenize(_T("'\t\r\n"), curPos
);
322 CString
CPathUtils::GetAppDirectory(HMODULE hMod
/* = NULL */)
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);
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);
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
)
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
)
375 int len
= path
.GetLength();
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
);
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
);
394 CString
CPathUtils::GetVersionFromFile(const CString
& p_strDateiname
)
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
)
414 LPSTR lpVersion
= NULL
;
415 VOID
* lpFixedPointer
;
416 TRANSARRAY
* lpTransArray
;
417 CString strLangProduktVersion
;
419 GetFileVersionInfo((LPTSTR
)(LPCTSTR
)p_strDateiname
,
424 // Check the current language
425 VerQueryValue( pBuffer
,
426 _T("\\VarFileInfo\\Translation"),
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
,
438 if (nInfoSize
&& lpVersion
)
439 strReturn
= (LPCTSTR
)lpVersion
;
447 CString
CPathUtils::PathPatternEscape(const CString
& path
)
449 CString result
= path
;
450 // first remove already escaped patterns to avoid having those
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("\\]"));
460 CString
CPathUtils::PathPatternUnEscape(const CString
& path
)
462 CString result
= path
;
463 result
.Replace(_T("\\["), _T("["));
464 result
.Replace(_T("\\]"), _T("]"));
470 #if defined(_DEBUG) && defined(_MFC_VER)
471 // Some test cases for these classes
472 static class CPathTests
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);
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());
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);