Fixed issue #3307: Abort Merge on a single file always results in a parameter error...
[TortoiseGit.git] / src / Utils / PathUtils.cpp
blob72d8d8163610c9098e8ea21404e6a74c87557c5e
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.
20 #include "stdafx.h"
21 #include "PathUtils.h"
22 #include <memory>
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'\\');
42 if (slashpos)
43 wcsncpy_s(buf.get(), len, internalpathbuf.get(), slashpos - internalpathbuf.get());
44 else
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);
56 TCHAR * p = dest;
57 for (; *p != '\0'; ++p)
58 if (*p == '/')
59 *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);
71 if (!hFile)
72 return false;
74 FILETIME ft;
75 SYSTEMTIME st;
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
80 (LPFILETIME)nullptr,
81 &ft) != FALSE;
84 CString CPathUtils::GetFileNameFromPath(CString sPath)
86 CString ret;
87 sPath.Replace(L'/', L'\\');
88 ret = sPath.Mid(sPath.ReverseFind('\\') + 1);
89 return ret;
92 CString CPathUtils::GetFileExtFromPath(const CString& sPath)
94 int dotPos = sPath.ReverseFind('.');
95 int slashPos = sPath.ReverseFind('\\');
96 if (slashPos < 0)
97 slashPos = sPath.ReverseFind('/');
98 if (dotPos > slashPos)
99 return sPath.Mid(dotPos);
100 return CString();
103 CString CPathUtils::GetLongPathname(const CString& path)
105 if (path.IsEmpty())
106 return path;
107 TCHAR pathbufcanonicalized[MAX_PATH] = {0}; // MAX_PATH ok.
108 DWORD ret = 0;
109 CString sRet;
110 if (!PathIsURL(path) && PathIsRelative(path))
112 ret = GetFullPathName(path, 0, nullptr, nullptr);
113 if (ret)
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);
123 if (ret == 0)
124 return path;
125 auto pathbuf = std::make_unique<TCHAR[]>(ret + 2);
126 ret = ::GetLongPathName(pathbufcanonicalized, pathbuf.get(), ret + 1);
127 sRet = CString(pathbuf.get(), ret);
129 else
131 ret = ::GetLongPathName(path, nullptr, 0);
132 if (ret == 0)
133 return path;
134 auto pathbuf = std::make_unique<TCHAR[]>(ret + 2);
135 ret = ::GetLongPathName(path, pathbuf.get(), ret + 1);
136 sRet = CString(pathbuf.get(), ret);
138 if (ret == 0)
139 return path;
140 return sRet;
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)
154 int curPos = 0;
155 CString sToken = Str.Tokenize(L"'\t\r\n", curPos);
156 while (!sToken.IsEmpty())
158 if ((sToken.Find('/')>=0)||(sToken.Find('\\')>=0))
160 sToken.Trim(L"'\"");
161 return sToken;
163 sToken = Str.Tokenize(L"'\t\r\n", curPos);
165 sToken.Empty();
166 return sToken;
169 CString CPathUtils::GetAppDirectory(HMODULE hMod /* = nullptr */)
171 CString path;
172 DWORD len = 0;
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);
191 return path;
194 CString CPathUtils::GetAppDataDirectory()
196 PWSTR pszPath = nullptr;
197 if (SHGetKnownFolderPath(FOLDERID_RoamingAppData, KF_FLAG_CREATE, nullptr, &pszPath) != S_OK)
198 return CString();
200 CString path = pszPath;
201 CoTaskMemFree(pszPath);
202 path += L"\\TortoiseGit";
203 if (!PathIsDirectory(path))
204 CreateDirectory(path, nullptr);
206 path += L'\\';
207 return path;
210 CString CPathUtils::GetLocalAppDataDirectory()
212 PWSTR pszPath = nullptr;
213 if (SHGetKnownFolderPath(FOLDERID_LocalAppData, KF_FLAG_CREATE, nullptr, &pszPath) != S_OK)
214 return CString();
215 CString path = pszPath;
216 CoTaskMemFree(pszPath);
217 path += L"\\TortoiseGit";
218 if (!PathIsDirectory(path))
219 CreateDirectory(path, nullptr);
221 path += L'\\';
222 return path;
225 CString CPathUtils::GetDocumentsDirectory()
227 PWSTR pszPath = nullptr;
228 if (SHGetKnownFolderPath(FOLDERID_Documents, KF_FLAG_CREATE, nullptr, &pszPath) != S_OK)
229 return CString();
231 CString path = pszPath;
232 CoTaskMemFree(pszPath);
233 return path;
236 CString CPathUtils::GetProgramsDirectory()
238 PWSTR pszPath = nullptr;
239 if (SHGetKnownFolderPath(FOLDERID_ProgramFiles, KF_FLAG_CREATE, nullptr, &pszPath) != S_OK)
240 return CString();
242 CString path = pszPath;
243 CoTaskMemFree(pszPath);
244 return path;
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);
250 if (!handle)
251 return -1;
253 DWORD ioctl_ret;
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))
257 return -1;
259 if (reparse_buf->ReparseTag != IO_REPARSE_TAG_SYMLINK)
260 return -1;
262 wchar_t* target = reparse_buf->SymbolicLinkReparseBuffer.PathBuffer + (reparse_buf->SymbolicLinkReparseBuffer.SubstituteNameOffset / sizeof(WCHAR));
263 int target_len = reparse_buf->SymbolicLinkReparseBuffer.SubstituteNameLength / sizeof(WCHAR);
264 if (!target_len)
265 return -1;
267 // not a symlink
268 if (wcsncmp(target, L"\\??\\Volume{", 11) == 0)
269 return -1;
271 if (pTargetA)
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);
280 return 0;
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\\";
289 int skip = 0;
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);
295 if (skip)
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);
304 #ifdef _MFC_VER
305 #pragma comment(lib, "Version.lib")
306 CString CPathUtils::GetVersionFromFile(const CString & p_strFilename)
308 struct TRANSARRAY
310 WORD wLanguageID;
311 WORD wCharacterSet;
314 CString strReturn;
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);
322 if (pBuffer)
324 UINT nInfoSize = 0,
325 nFixedLength = 0;
326 LPSTR lpVersion = nullptr;
327 VOID* lpFixedPointer;
328 TRANSARRAY* lpTransArray;
329 CString strLangProductVersion;
331 GetFileVersionInfo((LPTSTR)(LPCTSTR)p_strFilename,
332 dwReserved,
333 dwBufferSize,
334 pBuffer.get());
336 // Check the current language
337 VerQueryValue(pBuffer.get(),
338 L"\\VarFileInfo\\Translation",
339 &lpFixedPointer,
340 &nFixedLength);
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,
349 &nInfoSize);
350 if (nInfoSize && lpVersion)
351 strReturn = (LPCTSTR)lpVersion;
355 return strReturn;
358 CString CPathUtils::GetCopyrightForSelf()
360 DWORD len = 0;
361 DWORD bufferlen = MAX_PATH; // MAX_PATH is not the limit here!
362 CString path;
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();
372 CString strReturn;
373 DWORD dwReserved = 0;
374 DWORD dwBufferSize = GetFileVersionInfoSize((LPTSTR)(LPCTSTR)path, &dwReserved);
376 if (dwBufferSize > 0)
378 auto pBuffer = std::make_unique<BYTE[]>(dwBufferSize);
380 if (pBuffer)
382 GetFileVersionInfo((LPTSTR)(LPCTSTR)path,
383 dwReserved,
384 dwBufferSize,
385 pBuffer.get());
387 UINT nFixedLength = 0;
388 VOID* lpFixedPointer;
389 struct TRANSARRAY
391 WORD wLanguageID;
392 WORD wCharacterSet;
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);
402 UINT nInfoSize = 0;
403 LPSTR lpVersion = nullptr;
404 VerQueryValue(pBuffer.get(), (LPTSTR)(LPCTSTR)strLangLegalCopyright, (LPVOID*)&lpVersion, &nInfoSize);
405 if (nInfoSize && lpVersion)
406 strReturn = (LPCTSTR)lpVersion;
410 return strReturn;
412 #endif
413 CString CPathUtils::PathPatternEscape(const CString& path)
415 CString result = path;
416 // first remove already escaped patterns to avoid having those
417 // escaped twice
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"\\]");
423 return result;
426 CString CPathUtils::PathPatternUnEscape(const CString& path)
428 CString result = path;
429 result.Replace(L"\\[", L"[");
430 result.Replace(L"\\]", L"]");
431 return result;
434 CString CPathUtils::BuildPathWithPathDelimiter(const CString& path)
436 CString result(path);
437 EnsureTrailingPathDelimiter(result);
438 return 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)
454 if (path.IsEmpty())
455 return path;
457 DWORD ret = GetFullPathName(path, 0, nullptr, nullptr);
458 if (!ret)
459 return path;
461 CString sRet;
462 if (GetFullPathName(path, ret, CStrBuf(sRet, ret), nullptr))
463 return sRet;
464 return path;
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);
475 nPath.MakeLower();
477 TrimTrailingPathDelimiter(nPath);
479 return 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())
492 // Different lengths
493 return false;
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);
500 while (length-- > 0)
502 if (_totlower(*pP1--) != _totlower(*pP2--))
503 return false;
505 return true;
508 bool CPathUtils::ArePathStringsEqualWithCase(const CString& sP1, const CString& sP2)
510 int length = sP1.GetLength();
511 if (length != sP2.GetLength())
513 // Different lengths
514 return false;
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);
521 while (length-- > 0)
523 if ((*pP1--) != (*pP2--))
524 return false;
526 return true;
528 #endif