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.
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
, '\\');
41 _tcsncpy_s(buf
, len
+10, internalpathbuf
, slashpos
- internalpathbuf
);
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
);
50 delete[] internalpathbuf
;
54 void CPathUtils::Unescape(char * psz
)
56 char * pszSource
= 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' )
78 const char * pszLow
= NULL
;
79 const char * pszHigh
= NULL
;
82 *pszSource
= (char) toupper(*pszSource
);
83 pszHigh
= strchr(szHex
, *pszSource
);
88 *pszSource
= (char) toupper(*pszSource
);
89 pszLow
= strchr(szHex
, *pszSource
);
93 nValue
= (char) (((pszHigh
- szHex
) << 4) +
105 *pszDest
++ = *pszSource
;
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,
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,
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,
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,
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,
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,
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,
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
);
189 for (; *p
!= '\0'; ++p
)
194 CStringA
CPathUtils::PathEscape(const CStringA
& path
)
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
];
209 // char needs escaping
211 temp
.Format("%%%02X", (unsigned char)c
);
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
];
226 // char needs escaping
228 temp
.Format("%%%02X", (unsigned char)c
);
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://"));
240 #ifdef CSTRING_AVAILABLE
241 CString
CPathUtils::GetFileNameFromPath(CString sPath
)
244 sPath
.Replace(_T("/"), _T("\\"));
245 ret
= sPath
.Mid(sPath
.ReverseFind('\\') + 1);
249 CString
CPathUtils::GetFileExtFromPath(const CString
& sPath
)
251 int dotPos
= sPath
.ReverseFind('.');
252 int slashPos
= sPath
.ReverseFind('\\');
254 slashPos
= sPath
.ReverseFind('/');
255 if (dotPos
> slashPos
)
256 return sPath
.Mid(dotPos
);
260 CString
CPathUtils::GetLongPathname(const CString
& path
)
264 TCHAR pathbufcanonicalized
[MAX_PATH
]; // MAX_PATH ok.
267 if (!PathIsURL(path
) && PathIsRelative(path
))
269 ret
= GetFullPathName(path
, 0, NULL
, NULL
);
272 TCHAR
* pathbuf
= new TCHAR
[ret
+1];
273 if ((ret
= GetFullPathName(path
, ret
, pathbuf
, NULL
))!=0)
275 sRet
= CString(pathbuf
, ret
);
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
);
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
);
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
)
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("'\""));
322 sToken
= Str
.Tokenize(_T("'\t\r\n"), curPos
);
328 CString
CPathUtils::GetAppDirectory(HMODULE hMod
/* = NULL */)
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);
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);
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
)
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
)
381 int len
= path
.GetLength();
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
);
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
);
400 CString
CPathUtils::GetVersionFromFile(const CString
& p_strDateiname
)
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
)
420 LPSTR lpVersion
= NULL
;
421 VOID
* lpFixedPointer
;
422 TRANSARRAY
* lpTransArray
;
423 CString strLangProduktVersion
;
425 GetFileVersionInfo((LPTSTR
)(LPCTSTR
)p_strDateiname
,
430 // Check the current language
431 VerQueryValue( pBuffer
,
432 _T("\\VarFileInfo\\Translation"),
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
,
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);