1 // TortoiseGit - a Windows shell extension for easy version control
3 // Copyright (C) 2012-2016 - TortoiseGit
4 // Copyright (C) 2003-2008, 2013-2015 - 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 auto buf
= std::make_unique
<TCHAR
[]>(len
);
28 auto internalpathbuf
= std::make_unique
<TCHAR
[]>(len
);
29 TCHAR
* pPath
= internalpathbuf
.get();
30 SECURITY_ATTRIBUTES attribs
= { 0 };
31 attribs
.nLength
= sizeof(SECURITY_ATTRIBUTES
);
32 attribs
.bInheritHandle
= FALSE
;
34 ConvertToBackslash(internalpathbuf
.get(), path
, len
);
37 SecureZeroMemory(buf
.get(), (len
)*sizeof(TCHAR
));
38 TCHAR
* slashpos
= _tcschr(pPath
, '\\');
40 _tcsncpy_s(buf
.get(), len
, internalpathbuf
.get(), slashpos
- internalpathbuf
.get());
42 _tcsncpy_s(buf
.get(), len
, internalpathbuf
.get(), len
);
43 CreateDirectory(buf
.get(), &attribs
);
44 pPath
= _tcschr(pPath
, '\\');
45 } while ((pPath
++)&&(_tcschr(pPath
, '\\')));
47 return CreateDirectory(internalpathbuf
.get(), &attribs
);
50 void CPathUtils::Unescape(char * psz
)
52 char * pszSource
= psz
;
55 static const char szHex
[] = "0123456789ABCDEF";
57 // Unescape special characters. The number of characters
58 // in the *pszDest is assumed to be <= the number of characters
59 // in pszSource (they are both the same string anyway)
61 while (*pszSource
!= '\0' && *pszDest
!= '\0')
63 if (*pszSource
== '%')
65 // The next two chars following '%' should be digits
66 if ( *(pszSource
+ 1) == '\0' ||
67 *(pszSource
+ 2) == '\0' )
76 *pszSource
= (char) toupper(*pszSource
);
77 const char* pszHigh
= strchr(szHex
, *pszSource
);
82 *pszSource
= (char) toupper(*pszSource
);
83 const char* pszLow
= strchr(szHex
, *pszSource
);
87 nValue
= (char) (((pszHigh
- szHex
) << 4) +
99 *pszDest
++ = *pszSource
;
107 static const char iri_escape_chars
[256] = {
108 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
109 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
110 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
111 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
112 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
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,
118 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
119 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
120 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
121 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
122 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
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
128 const char uri_autoescape_chars
[256] = {
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, 1, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
132 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 1, 0, 0,
135 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
136 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1,
137 0, 1, 1, 1, 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, 0, 1, 0,
141 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
142 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
143 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
144 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,
150 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
153 static const char uri_char_validity
[256] = {
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, 1, 0, 0, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
157 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 1, 0, 0,
160 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
161 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1,
162 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
163 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 0,
166 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
167 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
168 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
169 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,
175 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
179 void CPathUtils::ConvertToBackslash(LPTSTR dest
, LPCTSTR src
, size_t len
)
181 _tcscpy_s(dest
, len
, src
);
183 for (; *p
!= '\0'; ++p
)
188 CStringA
CPathUtils::PathEscape(const CStringA
& path
)
193 for (i
=0; path
[i
]; ++i
)
195 c
= (unsigned char)path
[i
];
196 if (iri_escape_chars
[c
])
198 // no escaping needed for that char
199 ret2
+= (unsigned char)path
[i
];
201 else // char needs escaping
202 ret2
.AppendFormat("%%%02X", (unsigned char)c
);
205 for (i
=0; ret2
[i
]; ++i
)
207 c
= (unsigned char)ret2
[i
];
208 if (uri_autoescape_chars
[c
])
210 // no escaping needed for that char
211 ret
+= (unsigned char)ret2
[i
];
213 else // char needs escaping
214 ret
.AppendFormat("%%%02X", (unsigned char)c
);
217 if ((ret
.Left(11).Compare("file:///%5C") == 0) && (ret
.Find('%', 12) < 0))
218 ret
.Replace(("file:///%5C"), ("file://"));
219 ret
.Replace(("file:////%5C"), ("file://"));
224 #ifdef CSTRING_AVAILABLE
225 CString
CPathUtils::GetFileNameFromPath(CString sPath
)
228 sPath
.Replace(_T("/"), _T("\\"));
229 ret
= sPath
.Mid(sPath
.ReverseFind('\\') + 1);
233 CString
CPathUtils::GetFileExtFromPath(const CString
& sPath
)
235 int dotPos
= sPath
.ReverseFind('.');
236 int slashPos
= sPath
.ReverseFind('\\');
238 slashPos
= sPath
.ReverseFind('/');
239 if (dotPos
> slashPos
)
240 return sPath
.Mid(dotPos
);
244 CString
CPathUtils::GetLongPathname(const CString
& path
)
248 TCHAR pathbufcanonicalized
[MAX_PATH
] = {0}; // MAX_PATH ok.
251 if (!PathIsURL(path
) && PathIsRelative(path
))
253 ret
= GetFullPathName(path
, 0, nullptr, nullptr);
256 auto pathbuf
= std::make_unique
<TCHAR
[]>(ret
+ 1);
257 if ((ret
= GetFullPathName(path
, ret
, pathbuf
.get(), nullptr)) != 0)
258 sRet
= CString(pathbuf
.get(), ret
);
261 else if (PathCanonicalize(pathbufcanonicalized
, path
))
263 ret
= ::GetLongPathName(pathbufcanonicalized
, nullptr, 0);
266 auto pathbuf
= std::make_unique
<TCHAR
[]>(ret
+ 2);
267 ret
= ::GetLongPathName(pathbufcanonicalized
, pathbuf
.get(), ret
+ 1);
268 sRet
= CString(pathbuf
.get(), ret
);
272 ret
= ::GetLongPathName(path
, nullptr, 0);
275 auto pathbuf
= std::make_unique
<TCHAR
[]>(ret
+ 2);
276 ret
= ::GetLongPathName(path
, pathbuf
.get(), ret
+ 1);
277 sRet
= CString(pathbuf
.get(), ret
);
284 BOOL
CPathUtils::FileCopy(CString srcPath
, CString destPath
, BOOL force
)
286 srcPath
.Replace('/', '\\');
287 destPath
.Replace('/', '\\');
288 CString destFolder
= destPath
.Left(destPath
.ReverseFind('\\'));
289 MakeSureDirectoryPathExists(destFolder
);
290 return (CopyFile(srcPath
, destPath
, !force
));
293 CString
CPathUtils::ParsePathInString(const CString
& Str
)
297 sToken
= Str
.Tokenize(_T("'\t\r\n"), curPos
);
298 while (!sToken
.IsEmpty())
300 if ((sToken
.Find('/')>=0)||(sToken
.Find('\\')>=0))
302 sToken
.Trim(_T("'\""));
305 sToken
= Str
.Tokenize(_T("'\t\r\n"), curPos
);
311 CString
CPathUtils::GetAppDirectory(HMODULE hMod
/* = nullptr */)
315 DWORD bufferlen
= MAX_PATH
; // MAX_PATH is not the limit here!
316 path
.GetBuffer(bufferlen
);
319 bufferlen
+= MAX_PATH
; // MAX_PATH is not the limit here!
320 path
.ReleaseBuffer(0);
321 len
= GetModuleFileName(hMod
, path
.GetBuffer(bufferlen
+1), bufferlen
);
322 } while(len
== bufferlen
);
323 path
.ReleaseBuffer();
324 path
= path
.Left(path
.ReverseFind('\\')+1);
325 return GetLongPathname(path
);
328 CString
CPathUtils::GetAppParentDirectory(HMODULE hMod
/* = nullptr */)
330 CString path
= GetAppDirectory(hMod
);
331 path
= path
.Left(path
.ReverseFind('\\'));
332 path
= path
.Left(path
.ReverseFind('\\')+1);
336 CString
CPathUtils::GetAppDataDirectory()
338 PWSTR pszPath
= nullptr;
339 if (SHGetKnownFolderPath(FOLDERID_RoamingAppData
, KF_FLAG_CREATE
, nullptr, &pszPath
) != S_OK
)
342 CString path
= pszPath
;
343 CoTaskMemFree(pszPath
);
344 path
+= L
"\\TortoiseGit";
345 if (!PathIsDirectory(path
))
346 CreateDirectory(path
, nullptr);
352 CString
CPathUtils::GetLocalAppDataDirectory()
354 PWSTR pszPath
= nullptr;
355 if (SHGetKnownFolderPath(FOLDERID_LocalAppData
, KF_FLAG_CREATE
, nullptr, &pszPath
) != S_OK
)
357 CString path
= pszPath
;
358 CoTaskMemFree(pszPath
);
359 path
+= L
"\\TortoiseGit";
360 if (!PathIsDirectory(path
))
361 CreateDirectory(path
, nullptr);
367 CStringA
CPathUtils::PathUnescape(const CStringA
& path
)
369 auto urlabuf
= std::make_unique
<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, nullptr, nullptr);
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 #pragma comment(lib, "Version.lib")
401 CString
CPathUtils::GetVersionFromFile(const CString
& p_strFilename
)
410 DWORD dwReserved
= 0;
411 DWORD dwBufferSize
= GetFileVersionInfoSize((LPTSTR
)(LPCTSTR
)p_strFilename
,&dwReserved
);
413 if (dwBufferSize
> 0)
415 auto pBuffer
= std::make_unique
<BYTE
[]>(dwBufferSize
);
421 LPSTR lpVersion
= nullptr;
422 VOID
* lpFixedPointer
;
423 TRANSARRAY
* lpTransArray
;
424 CString strLangProductVersion
;
426 GetFileVersionInfo((LPTSTR
)(LPCTSTR
)p_strFilename
,
431 // Check the current language
432 VerQueryValue(pBuffer
.get(),
433 _T("\\VarFileInfo\\Translation"),
436 lpTransArray
= (TRANSARRAY
*) lpFixedPointer
;
438 strLangProductVersion
.Format(_T("\\StringFileInfo\\%04x%04x\\ProductVersion"),
439 lpTransArray
[0].wLanguageID
, lpTransArray
[0].wCharacterSet
);
441 VerQueryValue(pBuffer
.get(),
442 (LPTSTR
)(LPCTSTR
)strLangProductVersion
,
443 (LPVOID
*)&lpVersion
,
445 if (nInfoSize
&& lpVersion
)
446 strReturn
= (LPCTSTR
)lpVersion
;
453 CString
CPathUtils::PathPatternEscape(const CString
& path
)
455 CString result
= path
;
456 // first remove already escaped patterns to avoid having those
458 result
.Replace(_T("\\["), _T("["));
459 result
.Replace(_T("\\]"), _T("]"));
460 // now escape the patterns (again)
461 result
.Replace(_T("["), _T("\\["));
462 result
.Replace(_T("]"), _T("\\]"));
466 CString
CPathUtils::PathPatternUnEscape(const CString
& path
)
468 CString result
= path
;
469 result
.Replace(_T("\\["), _T("["));
470 result
.Replace(_T("\\]"), _T("]"));