1
// TortoiseGit - a Windows shell extension for easy version control
3 // Copyright (C) 2012-2018 - 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"
23 #include "StringUtils.h"
24 #include "../../ext/libgit2/src/win32/reparse.h"
25 #include "SmartHandle.h"
27 BOOL
CPathUtils::MakeSureDirectoryPathExists(LPCTSTR path
)
29 size_t len
= wcslen(path
) + 10;
30 auto buf
= std::make_unique
<TCHAR
[]>(len
);
31 auto internalpathbuf
= std::make_unique
<TCHAR
[]>(len
);
32 TCHAR
* pPath
= internalpathbuf
.get();
33 SECURITY_ATTRIBUTES attribs
= { 0 };
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
= wcschr(pPath
, L
'\\');
43 wcsncpy_s(buf
.get(), len
, internalpathbuf
.get(), slashpos
- internalpathbuf
.get());
45 wcsncpy_s(buf
.get(), len
, internalpathbuf
.get(), len
);
46 CreateDirectory(buf
.get(), &attribs
);
47 pPath
= wcschr(pPath
, L
'\\');
48 } while ((pPath
++) && (wcschr(pPath
, L
'\\')));
50 return CreateDirectory(internalpathbuf
.get(), &attribs
);
53 void CPathUtils::ConvertToBackslash(LPTSTR dest
, LPCTSTR src
, size_t len
)
55 wcscpy_s(dest
, len
, src
);
57 for (; *p
!= '\0'; ++p
)
62 #ifdef CSTRING_AVAILABLE
63 void CPathUtils::ConvertToBackslash(CString
& path
)
65 path
.Replace(L
'/', L
'\\');
68 bool CPathUtils::Touch(const CString
& path
)
70 CAutoFile hFile
= CreateFile(path
, GENERIC_WRITE
, FILE_SHARE_DELETE
, nullptr, OPEN_EXISTING
, FILE_ATTRIBUTE_NORMAL
, nullptr);
76 GetSystemTime(&st
); // Gets the current system time
77 SystemTimeToFileTime(&st
, &ft
); // Converts the current system time to file time format
78 return SetFileTime(hFile
, // Sets last-write time of the file
79 (LPFILETIME
)nullptr, // to the converted current system time
84 CString
CPathUtils::GetFileNameFromPath(CString sPath
)
87 sPath
.Replace(L
'/', L
'\\');
88 ret
= sPath
.Mid(sPath
.ReverseFind('\\') + 1);
92 CString
CPathUtils::GetFileExtFromPath(const CString
& sPath
)
94 int dotPos
= sPath
.ReverseFind('.');
95 int slashPos
= sPath
.ReverseFind('\\');
97 slashPos
= sPath
.ReverseFind('/');
98 if (dotPos
> slashPos
)
99 return sPath
.Mid(dotPos
);
103 CString
CPathUtils::GetLongPathname(const CString
& path
)
107 TCHAR pathbufcanonicalized
[MAX_PATH
] = {0}; // MAX_PATH ok.
110 if (!PathIsURL(path
) && PathIsRelative(path
))
112 ret
= GetFullPathName(path
, 0, nullptr, nullptr);
115 auto pathbuf
= std::make_unique
<TCHAR
[]>(ret
+ 1);
116 if ((ret
= GetFullPathName(path
, ret
, pathbuf
.get(), nullptr)) != 0)
117 sRet
= CString(pathbuf
.get(), ret
);
120 else if (PathCanonicalize(pathbufcanonicalized
, path
))
122 ret
= ::GetLongPathName(pathbufcanonicalized
, nullptr, 0);
125 auto pathbuf
= std::make_unique
<TCHAR
[]>(ret
+ 2);
126 ret
= ::GetLongPathName(pathbufcanonicalized
, pathbuf
.get(), ret
+ 1);
127 sRet
= CString(pathbuf
.get(), ret
);
131 ret
= ::GetLongPathName(path
, nullptr, 0);
134 auto pathbuf
= std::make_unique
<TCHAR
[]>(ret
+ 2);
135 ret
= ::GetLongPathName(path
, pathbuf
.get(), ret
+ 1);
136 sRet
= CString(pathbuf
.get(), ret
);
143 BOOL
CPathUtils::FileCopy(CString srcPath
, CString destPath
, BOOL force
)
145 srcPath
.Replace('/', '\\');
146 destPath
.Replace('/', '\\');
147 CString destFolder
= destPath
.Left(destPath
.ReverseFind('\\'));
148 MakeSureDirectoryPathExists(destFolder
);
149 return (CopyFile(srcPath
, destPath
, !force
));
152 CString
CPathUtils::ParsePathInString(const CString
& Str
)
155 CString sToken
= Str
.Tokenize(L
"'\t\r\n", curPos
);
156 while (!sToken
.IsEmpty())
158 if ((sToken
.Find('/')>=0)||(sToken
.Find('\\')>=0))
163 sToken
= Str
.Tokenize(L
"'\t\r\n", curPos
);
169 CString
CPathUtils::GetAppDirectory(HMODULE hMod
/* = nullptr */)
173 DWORD bufferlen
= MAX_PATH
; // MAX_PATH is not the limit here!
174 path
.GetBuffer(bufferlen
);
177 bufferlen
+= MAX_PATH
; // MAX_PATH is not the limit here!
178 path
.ReleaseBuffer(0);
179 len
= GetModuleFileName(hMod
, path
.GetBuffer(bufferlen
+1), bufferlen
);
180 } while(len
== bufferlen
);
181 path
.ReleaseBuffer();
182 path
= path
.Left(path
.ReverseFind('\\')+1);
183 return GetLongPathname(path
);
186 CString
CPathUtils::GetAppParentDirectory(HMODULE hMod
/* = nullptr */)
188 CString path
= GetAppDirectory(hMod
);
189 path
= path
.Left(path
.ReverseFind('\\'));
190 path
= path
.Left(path
.ReverseFind('\\')+1);
194 CString
CPathUtils::GetAppDataDirectory()
196 PWSTR pszPath
= nullptr;
197 if (SHGetKnownFolderPath(FOLDERID_RoamingAppData
, KF_FLAG_CREATE
, nullptr, &pszPath
) != S_OK
)
200 CString path
= pszPath
;
201 CoTaskMemFree(pszPath
);
202 path
+= L
"\\TortoiseGit";
203 if (!PathIsDirectory(path
))
204 CreateDirectory(path
, nullptr);
210 CString
CPathUtils::GetLocalAppDataDirectory()
212 PWSTR pszPath
= nullptr;
213 if (SHGetKnownFolderPath(FOLDERID_LocalAppData
, KF_FLAG_CREATE
, nullptr, &pszPath
) != S_OK
)
215 CString path
= pszPath
;
216 CoTaskMemFree(pszPath
);
217 path
+= L
"\\TortoiseGit";
218 if (!PathIsDirectory(path
))
219 CreateDirectory(path
, nullptr);
225 CString
CPathUtils::GetDocumentsDirectory()
227 PWSTR pszPath
= nullptr;
228 if (SHGetKnownFolderPath(FOLDERID_Documents
, KF_FLAG_CREATE
, nullptr, &pszPath
) != S_OK
)
231 CString path
= pszPath
;
232 CoTaskMemFree(pszPath
);
236 CString
CPathUtils::GetProgramsDirectory()
238 PWSTR pszPath
= nullptr;
239 if (SHGetKnownFolderPath(FOLDERID_ProgramFiles
, KF_FLAG_CREATE
, nullptr, &pszPath
) != S_OK
)
242 CString path
= pszPath
;
243 CoTaskMemFree(pszPath
);
247 int CPathUtils::ReadLink(LPCTSTR filename
, CStringA
* pTargetA
)
249 CAutoFile handle
= CreateFileW(filename
, GENERIC_READ
, FILE_SHARE_READ
| FILE_SHARE_DELETE
, nullptr, OPEN_EXISTING
, FILE_FLAG_OPEN_REPARSE_POINT
| FILE_FLAG_BACKUP_SEMANTICS
, nullptr);
254 BYTE buf
[MAXIMUM_REPARSE_DATA_BUFFER_SIZE
] = { 0 };
255 auto reparse_buf
= reinterpret_cast<GIT_REPARSE_DATA_BUFFER
*>(&buf
);
256 if (!DeviceIoControl(handle
, FSCTL_GET_REPARSE_POINT
, nullptr, 0, reparse_buf
, sizeof(buf
), &ioctl_ret
, nullptr))
259 if (reparse_buf
->ReparseTag
!= IO_REPARSE_TAG_SYMLINK
)
262 wchar_t* target
= reparse_buf
->SymbolicLinkReparseBuffer
.PathBuffer
+ (reparse_buf
->SymbolicLinkReparseBuffer
.SubstituteNameOffset
/ sizeof(WCHAR
));
263 int target_len
= reparse_buf
->SymbolicLinkReparseBuffer
.SubstituteNameLength
/ sizeof(WCHAR
);
268 if (wcsncmp(target
, L
"\\??\\Volume{", 11) == 0)
273 CString
targetW(target
, target_len
);
274 // The path may need to have a prefix removed
275 DropPathPrefixes(targetW
);
276 targetW
.Replace(L
'\\', L
'/');
277 *pTargetA
= CUnicodeUtils::GetUTF8(targetW
);
283 void CPathUtils::DropPathPrefixes(CString
& path
)
285 static const wchar_t dosdevices_prefix
[] = L
"\\\?\?\\";
286 static const wchar_t nt_prefix
[] = L
"\\\\?\\";
287 static const wchar_t unc_prefix
[] = L
"UNC\\";
290 if (CStringUtils::StartsWith(path
, dosdevices_prefix
))
291 skip
+= (int)wcslen(dosdevices_prefix
);
292 else if (CStringUtils::StartsWith(path
, nt_prefix
))
293 skip
+= (int)wcslen(nt_prefix
);
297 if (path
.GetLength() - skip
> (int)wcslen(unc_prefix
) && CStringUtils::StartsWith(path
.GetString() + skip
, unc_prefix
))
298 skip
+= (int)wcslen(unc_prefix
);
300 path
= path
.Mid(skip
);
305 #pragma comment(lib, "Version.lib")
306 CString
CPathUtils::GetVersionFromFile(const CString
& p_strFilename
)
315 DWORD dwReserved
= 0;
316 DWORD dwBufferSize
= GetFileVersionInfoSize((LPTSTR
)(LPCTSTR
)p_strFilename
,&dwReserved
);
318 if (dwBufferSize
> 0)
320 auto pBuffer
= std::make_unique
<BYTE
[]>(dwBufferSize
);
326 LPSTR lpVersion
= nullptr;
327 VOID
* lpFixedPointer
;
328 TRANSARRAY
* lpTransArray
;
329 CString strLangProductVersion
;
331 GetFileVersionInfo((LPTSTR
)(LPCTSTR
)p_strFilename
,
336 // Check the current language
337 VerQueryValue(pBuffer
.get(),
338 L
"\\VarFileInfo\\Translation",
341 lpTransArray
= (TRANSARRAY
*) lpFixedPointer
;
343 strLangProductVersion
.Format(L
"\\StringFileInfo\\%04x%04x\\ProductVersion",
344 lpTransArray
[0].wLanguageID
, lpTransArray
[0].wCharacterSet
);
346 VerQueryValue(pBuffer
.get(),
347 (LPTSTR
)(LPCTSTR
)strLangProductVersion
,
348 (LPVOID
*)&lpVersion
,
350 if (nInfoSize
&& lpVersion
)
351 strReturn
= (LPCTSTR
)lpVersion
;
358 CString
CPathUtils::GetCopyrightForSelf()
361 DWORD bufferlen
= MAX_PATH
; // MAX_PATH is not the limit here!
363 path
.GetBuffer(bufferlen
);
366 bufferlen
+= MAX_PATH
; // MAX_PATH is not the limit here!
367 path
.ReleaseBuffer(0);
368 len
= GetModuleFileName(nullptr, path
.GetBuffer(bufferlen
+ 1), bufferlen
);
369 } while (len
== bufferlen
);
370 path
.ReleaseBuffer();
373 DWORD dwReserved
= 0;
374 DWORD dwBufferSize
= GetFileVersionInfoSize((LPTSTR
)(LPCTSTR
)path
, &dwReserved
);
376 if (dwBufferSize
> 0)
378 auto pBuffer
= std::make_unique
<BYTE
[]>(dwBufferSize
);
382 GetFileVersionInfo((LPTSTR
)(LPCTSTR
)path
,
387 UINT nFixedLength
= 0;
388 VOID
* lpFixedPointer
;
394 TRANSARRAY
* lpTransArray
;
395 // Check the current language
396 VerQueryValue(pBuffer
.get(), L
"\\VarFileInfo\\Translation", &lpFixedPointer
, &nFixedLength
);
397 lpTransArray
= (TRANSARRAY
*)lpFixedPointer
;
399 CString strLangLegalCopyright
;
400 strLangLegalCopyright
.Format(L
"\\StringFileInfo\\%04x%04x\\LegalCopyright", lpTransArray
[0].wLanguageID
, lpTransArray
[0].wCharacterSet
);
403 LPSTR lpVersion
= nullptr;
404 VerQueryValue(pBuffer
.get(), (LPTSTR
)(LPCTSTR
)strLangLegalCopyright
, (LPVOID
*)&lpVersion
, &nInfoSize
);
405 if (nInfoSize
&& lpVersion
)
406 strReturn
= (LPCTSTR
)lpVersion
;
413 CString
CPathUtils::PathPatternEscape(const CString
& path
)
415 CString result
= path
;
416 // first remove already escaped patterns to avoid having those
418 result
.Replace(L
"\\[", L
"[");
419 result
.Replace(L
"\\]", L
"]");
420 // now escape the patterns (again)
421 result
.Replace(L
"[", L
"\\[");
422 result
.Replace(L
"]", L
"\\]");
426 CString
CPathUtils::PathPatternUnEscape(const CString
& path
)
428 CString result
= path
;
429 result
.Replace(L
"\\[", L
"[");
430 result
.Replace(L
"\\]", L
"]");
434 CString
CPathUtils::BuildPathWithPathDelimiter(const CString
& path
)
436 CString
result(path
);
437 EnsureTrailingPathDelimiter(result
);
441 void CPathUtils::EnsureTrailingPathDelimiter(CString
& path
)
443 if (!path
.IsEmpty() && !CStringUtils::EndsWith(path
, L
'\\'))
444 path
.AppendChar(L
'\\');
447 void CPathUtils::TrimTrailingPathDelimiter(CString
& path
)
449 path
.TrimRight(L
'\\');
452 CString
CPathUtils::ExpandFileName(const CString
& path
)
457 DWORD ret
= GetFullPathName(path
, 0, nullptr, nullptr);
462 if (GetFullPathName(path
, ret
, CStrBuf(sRet
, ret
), nullptr))
467 CString
CPathUtils::NormalizePath(const CString
& path
)
469 // Account DOS 8.3 file/folder names
470 CString nPath
= GetLongPathname(path
);
472 // Account for ..\ and .\ that may occur in each path
473 nPath
= ExpandFileName(nPath
);
477 TrimTrailingPathDelimiter(nPath
);
482 bool CPathUtils::IsSamePath(const CString
& path1
, const CString
& path2
)
484 return ArePathStringsEqualWithCase(NormalizePath(path1
), NormalizePath(path2
));
487 bool CPathUtils::ArePathStringsEqual(const CString
& sP1
, const CString
& sP2
)
489 int length
= sP1
.GetLength();
490 if (length
!= sP2
.GetLength())
495 // We work from the end of the strings, because path differences
496 // are more likely to occur at the far end of a string
497 LPCTSTR pP1Start
= sP1
;
498 LPCTSTR pP1
= pP1Start
+ (length
- 1);
499 LPCTSTR pP2
= ((LPCTSTR
)sP2
) + (length
- 1);
502 if (_totlower(*pP1
--) != _totlower(*pP2
--))
508 bool CPathUtils::ArePathStringsEqualWithCase(const CString
& sP1
, const CString
& sP2
)
510 int length
= sP1
.GetLength();
511 if (length
!= sP2
.GetLength())
516 // We work from the end of the strings, because path differences
517 // are more likely to occur at the far end of a string
518 LPCTSTR pP1Start
= sP1
;
519 LPCTSTR pP1
= pP1Start
+ (length
- 1);
520 LPCTSTR pP2
= ((LPCTSTR
)sP2
) + (length
- 1);
523 if ((*pP1
--) != (*pP2
--))