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.
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
] = {0}; // 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);
281 std::unique_ptr
<TCHAR
[]> pathbuf(new TCHAR
[ret
+ 2]);
282 ret
= ::GetLongPathName(pathbufcanonicalized
, pathbuf
.get(), ret
+ 1);
283 sRet
= CString(pathbuf
.get(), ret
);
287 ret
= ::GetLongPathName(path
, NULL
, 0);
290 std::unique_ptr
<TCHAR
[]> pathbuf(new TCHAR
[ret
+ 2]);
291 ret
= ::GetLongPathName(path
, pathbuf
.get(), ret
+ 1);
292 sRet
= CString(pathbuf
.get(), ret
);
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
)
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("'\""));
320 sToken
= Str
.Tokenize(_T("'\t\r\n"), curPos
);
326 CString
CPathUtils::GetAppDirectory(HMODULE hMod
/* = NULL */)
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);
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);
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
)
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
)
380 int len
= path
.GetLength();
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
);
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
);
399 CString
CPathUtils::GetVersionFromFile(const CString
& p_strFilename
)
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
)
419 LPSTR lpVersion
= NULL
;
420 VOID
* lpFixedPointer
;
421 TRANSARRAY
* lpTransArray
;
422 CString strLangProductVersion
;
424 GetFileVersionInfo((LPTSTR
)(LPCTSTR
)p_strFilename
,
429 // Check the current language
430 VerQueryValue( pBuffer
,
431 _T("\\VarFileInfo\\Translation"),
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
,
443 if (nInfoSize
&& lpVersion
)
444 strReturn
= (LPCTSTR
)lpVersion
;
452 CString
CPathUtils::PathPatternEscape(const CString
& path
)
454 CString result
= path
;
455 // first remove already escaped patterns to avoid having those
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("\\]"));
465 CString
CPathUtils::PathPatternUnEscape(const CString
& path
)
467 CString result
= path
;
468 result
.Replace(_T("\\["), _T("["));
469 result
.Replace(_T("\\]"), _T("]"));
475 #if defined(_DEBUG) && defined(_MFC_VER)
476 // Some test cases for these classes
477 static class CPathTests
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);
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());
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);