1
// TortoiseGit - a Windows shell extension for easy version control
3 // Copyright (C) 2011-2019, 2021-2023 - TortoiseGit
4 // Copyright (C) 2003-2011, 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 "UnicodeUtils.h"
22 #include "StringUtils.h"
23 #include "ClipboardHelper.h"
24 #include "SmartHandle.h"
26 int strwildcmp(const char *wild
, const char *string
)
28 const char* cp
= nullptr;
29 const char* mp
= nullptr;
30 while ((*string
) && (*wild
!= '*'))
32 if ((*wild
!= *string
) && (*wild
!= '?'))
46 else if ((*wild
== *string
) || (*wild
== '?'))
63 int wcswildcmp(const wchar_t *wild
, const wchar_t *string
)
65 const wchar_t* cp
= nullptr;
66 const wchar_t* mp
= nullptr;
67 while ((*string
) && (*wild
!= '*'))
69 if ((*wild
!= *string
) && (*wild
!= '?'))
83 else if ((*wild
== *string
) || (*wild
== '?'))
100 #if defined(CSTRING_AVAILABLE) || defined(_MFC_VER)
101 void CStringUtils::RemoveAccelerators(CString
& text
)
104 while ((pos
=text
.Find('&',pos
))>=0)
106 if (text
.GetLength() > (pos
-1))
108 if (text
.GetAt(pos
+1)!=' ')
115 CString
CStringUtils::EscapeAccellerators(CString
& text
)
117 text
.Replace(L
"&", L
"&&");
121 wchar_t CStringUtils::GetAccellerator(const CString
& text
)
124 while ((pos
= text
.Find(L
'&', pos
)) >= 0 && pos
+ 1 < text
.GetLength())
126 if (text
.GetAt(pos
+ 1) == '&')
128 else if (text
.GetAt(pos
+ 1) != ' ')
129 return towupper(text
.GetAt(pos
+ 1));
136 bool CStringUtils::WriteAsciiStringToClipboard(const CStringA
& sClipdata
, LCID lcid
, HWND hOwningWnd
)
138 CClipboardHelper clipboardHelper
;
139 if (!clipboardHelper
.Open(hOwningWnd
))
143 HGLOBAL hClipboardData
= CClipboardHelper::GlobalAlloc(sClipdata
.GetLength() + 1);
147 auto pchData
= static_cast<char*>(GlobalLock(hClipboardData
));
151 strcpy_s(pchData
, sClipdata
.GetLength() + 1, static_cast<LPCSTR
>(sClipdata
));
152 GlobalUnlock(hClipboardData
);
153 if (!SetClipboardData(CF_TEXT
, hClipboardData
))
156 HANDLE hlocmem
= CClipboardHelper::GlobalAlloc(sizeof(LCID
));
160 auto plcid
= static_cast<PLCID
>(GlobalLock(hlocmem
));
164 SetClipboardData(CF_LOCALE
, static_cast<HANDLE
>(plcid
));
166 GlobalUnlock(hlocmem
);
171 bool CStringUtils::WriteAsciiStringToClipboard(const CStringW
& sClipdata
, HWND hOwningWnd
)
173 CClipboardHelper clipboardHelper
;
174 if (!clipboardHelper
.Open(hOwningWnd
))
178 HGLOBAL hClipboardData
= CClipboardHelper::GlobalAlloc((sClipdata
.GetLength() + 1) * sizeof(WCHAR
));
182 auto pchData
= static_cast<WCHAR
*>(GlobalLock(hClipboardData
));
186 wcscpy_s(pchData
, sClipdata
.GetLength() + 1, static_cast<LPCWSTR
>(sClipdata
));
187 GlobalUnlock(hClipboardData
);
188 if (!SetClipboardData(CF_UNICODETEXT
, hClipboardData
))
191 // no need to also set CF_TEXT : the OS does this
196 bool CStringUtils::WriteDiffToClipboard(const CStringA
& sClipdata
, HWND hOwningWnd
)
198 UINT cFormat
= RegisterClipboardFormat(L
"TGIT_UNIFIEDDIFF");
201 CClipboardHelper clipboardHelper
;
202 if (!clipboardHelper
.Open(hOwningWnd
))
206 HGLOBAL hClipboardData
= CClipboardHelper::GlobalAlloc(sClipdata
.GetLength() + 1);
210 auto pchData
= static_cast<char*>(GlobalLock(hClipboardData
));
214 strcpy_s(pchData
, sClipdata
.GetLength() + 1, static_cast<LPCSTR
>(sClipdata
));
215 GlobalUnlock(hClipboardData
);
216 if (!SetClipboardData(cFormat
,hClipboardData
))
218 if (!SetClipboardData(CF_TEXT
, hClipboardData
))
221 CString sClipdataW
= CUnicodeUtils::GetUnicode(sClipdata
);
222 auto hClipboardDataW
= CClipboardHelper::GlobalAlloc((sClipdataW
.GetLength() + 1) * sizeof(wchar_t));
223 if (!hClipboardDataW
)
226 auto pchDataW
= static_cast<wchar_t*>(GlobalLock(hClipboardDataW
));
230 wcscpy_s(pchDataW
, sClipdataW
.GetLength() + 1, static_cast<LPCWSTR
>(sClipdataW
));
231 GlobalUnlock(hClipboardDataW
);
232 if (!SetClipboardData(CF_UNICODETEXT
, hClipboardDataW
))
240 bool CStringUtils::ReadStringFromTextFile(const CString
& path
, CString
& text
)
242 if (!PathFileExists(path
))
247 // w/o typeBinary for some files \r gets dropped
248 if (!file
.Open(path
, CFile::typeBinary
| CFile::modeRead
| CFile::shareDenyWrite
) || file
.GetLength() >= INT_MAX
)
251 CStringA filecontent
;
252 const UINT filelength
= static_cast<UINT
>(file
.GetLength());
253 const int bytesread
= static_cast<int>(file
.Read(filecontent
.GetBuffer(filelength
), filelength
));
254 filecontent
.ReleaseBuffer(bytesread
);
255 text
= CUnicodeUtils::GetUnicode(filecontent
);
258 catch (CFileException
* pE
)
266 #endif // #ifdef _MFC_VER
268 #if defined(CSTRING_AVAILABLE) || defined(_MFC_VER)
269 BOOL
CStringUtils::WildCardMatch(const CString
& wildcard
, const CString
& string
)
271 return wcswildcmp(wildcard
, string
);
274 CString
CStringUtils::LinesWrap(const CString
& longstring
, int limit
/* = 80 */, bool bCompactPaths
/* = true */)
277 if ((longstring
.GetLength() < limit
) || (limit
== 0))
278 return longstring
; // no wrapping needed.
279 // now start breaking the string into lines
284 while ((linepos
= longstring
.Find('\n', linepos
)) >= 0)
286 temp
= longstring
.Mid(lineposold
, linepos
-lineposold
);
287 if ((linepos
+1)<longstring
.GetLength())
291 lineposold
= linepos
;
292 if (!retString
.IsEmpty())
294 retString
+= WordWrap(temp
, limit
, bCompactPaths
, false, 4);
296 temp
= longstring
.Mid(lineposold
);
299 retString
+= WordWrap(temp
, limit
, bCompactPaths
, false, 4);
304 CString
CStringUtils::WordWrap(const CString
& longstring
, int limit
, bool bCompactPaths
, bool bForceWrap
, int tabSize
)
306 int nLength
= longstring
.GetLength();
315 for (int i
= 0; i
< nLength
; ++i
)
317 if (i
-nLineStart
+tabOffset
>= limit
)
319 if (nLineEnd
== nLineStart
)
325 while ((i
< nLength
) && (longstring
[i
] != ' ') && (longstring
[i
] != '\t'))
332 CString longline
= longstring
.Mid(nLineStart
, nLineEnd
-nLineStart
).Left(MAX_PATH
-1);
333 if ((bCompactPaths
)&&(longline
.GetLength() < MAX_PATH
))
335 if (((!PathIsFileSpec(longline
))&&longline
.Find(':')<3)||(PathIsURL(longline
)))
337 wchar_t buf
[MAX_PATH
] = { 0 };
338 PathCompactPathEx(buf
, longline
, limit
+1, 0);
342 retString
+= longline
;
345 retString
+= longstring
.Mid(nLineStart
, nLineEnd
-nLineStart
);
348 nLineStart
= nLineEnd
;
350 if (longstring
[i
] == ' ')
352 if (longstring
[i
] == '\t')
354 tabOffset
+= (tabSize
- i
% tabSize
);
360 CString longline
= longstring
.Mid(nLineStart
).Left(MAX_PATH
-1);
361 if ((bCompactPaths
)&&(longline
.GetLength() < MAX_PATH
))
363 if (((!PathIsFileSpec(longline
))&&longline
.Find(':')<3)||(PathIsURL(longline
)))
365 wchar_t buf
[MAX_PATH
] = { 0 };
366 PathCompactPathEx(buf
, longline
, limit
+1, 0);
370 retString
+= longline
;
373 retString
+= longstring
.Mid(nLineStart
);
378 std::vector
<CString
> CStringUtils::WordWrap(const CString
& longstring
, int limit
, int tabSize
)
380 int nLength
= longstring
.GetLength();
381 std::vector
<CString
> retVec
;
389 for (int i
= 0; i
< nLength
; ++i
)
391 if (i
- nLineStart
+ tabOffset
>= limit
)
393 if (nLineEnd
== nLineStart
)
396 auto sMid
= longstring
.Mid(nLineStart
, nLineEnd
- nLineStart
);
397 retVec
.push_back(sMid
);
400 nLineStart
= nLineEnd
;
402 if (longstring
[i
] == ' ')
404 if (longstring
[i
] == '\t')
406 tabOffset
+= (tabSize
- i
% tabSize
);
411 auto sMid
= longstring
.Mid(nLineStart
);
412 retVec
.push_back(sMid
);
417 int CStringUtils::GetMatchingLength (const CString
& lhs
, const CString
& rhs
)
419 const int lhsLength
= lhs
.GetLength();
420 const int rhsLength
= rhs
.GetLength();
421 const int maxResult
= min(lhsLength
, rhsLength
);
426 for (int i
= 0; i
< maxResult
; ++i
)
427 if (pLhs
[i
] != pRhs
[i
])
433 int CStringUtils::FastCompareNoCase (const CStringW
& lhs
, const CStringW
& rhs
)
435 // attempt latin-only comparison
437 INT_PTR count
= min (lhs
.GetLength(), rhs
.GetLength()+1);
438 const wchar_t* left
= lhs
;
439 const wchar_t* right
= rhs
;
440 for (const wchar_t* last
= left
+ count
+1; left
< last
; ++left
, ++right
)
442 int leftChar
= *left
;
443 int rightChar
= *right
;
445 int diff
= leftChar
- rightChar
;
448 // case-sensitive comparison found a difference
450 if ((leftChar
| rightChar
) >= 0x80)
452 // non-latin char -> fall back to CRT code
453 // (full comparison required as we might have
454 // skipped special chars / UTF plane selectors)
456 return _wcsicmp (lhs
, rhs
);
459 // normalize to lower case
461 if ((leftChar
>= 'A') && (leftChar
<= 'Z'))
462 leftChar
+= 'a' - 'A';
463 if ((rightChar
>= 'A') && (rightChar
<= 'Z'))
464 rightChar
+= 'a' - 'A';
468 diff
= leftChar
- rightChar
;
474 // must be equal (both ended with a 0)
479 bool CStringUtils::IsPlainReadableASCII(const CString
& text
)
481 for (int i
= 0; i
< text
.GetLength(); ++i
)
483 if (text
[i
] < 32 || text
[i
] >= 127)
489 static void cleanup_space(CString
& string
)
491 for (int pos
= 0; pos
< string
.GetLength(); ++pos
)
493 if (_istspace(string
[pos
])) {
494 string
.SetAt(pos
, L
' ');
496 for (cnt
= 0; _istspace(string
[pos
+ cnt
+ 1]); ++cnt
);
497 string
.Delete(pos
+ 1, cnt
);
502 // similar code in CTortoiseGitBlameData::UnquoteFilename
503 CString
CStringUtils::UnescapeGitQuotePath(const CString
& s
)
506 const int i_size
= s
.GetLength();
507 bool isEscaped
= false;
508 for (int i
= 0; i
< i_size
; ++i
)
513 if (c
>= '0' && c
<= '3')
517 c
= (((c
- L
'0') & 03) << 6) | (((s
[i
+ 1] - L
'0') & 07) << 3) | ((s
[i
+ 2] - L
'0') & 07);
524 // we're on purpose not supporting all possible abbreviations such as \n, \r, \t here as these filenames are invalid on Windows anaway
539 return CUnicodeUtils::GetUnicode(t
);
542 static void get_sane_name(CString
* out
, const CString
* name
, const CString
& email
)
544 const CString
* src
= name
;
545 if (name
->GetLength() < 3 || 60 < name
->GetLength() || wcschr(*name
, L
'@') || wcschr(*name
, L
'<') || wcschr(*name
, L
'>'))
547 else if (name
== out
)
552 static void parse_bogus_from(const CString
& mailaddress
, CString
& parsedAddress
, CString
* parsedName
)
554 /* John Doe <johndoe> */
556 const int bra
= mailaddress
.Find(L
'<');
559 const int ket
= mailaddress
.Find(L
'>');
563 parsedAddress
= mailaddress
.Mid(bra
+ 1, ket
- bra
- 1);
567 *parsedName
= mailaddress
.Left(bra
).Trim();
568 get_sane_name(parsedName
, parsedName
, parsedAddress
);
572 void CStringUtils::ParseEmailAddress(CString mailaddress
, CString
& parsedAddress
, CString
* parsedName
)
577 mailaddress
= mailaddress
.Trim();
578 if (mailaddress
.IsEmpty())
580 parsedAddress
.Empty();
584 if (mailaddress
.Left(1) == L
'"')
586 mailaddress
= mailaddress
.TrimLeft();
587 bool escaped
= false;
589 for (int i
= 1; i
< mailaddress
.GetLength(); ++i
)
591 if (mailaddress
[i
] == L
'"')
599 *parsedName
= mailaddress
.Mid(1, i
- 1);
600 mailaddress
= mailaddress
.Mid(i
);
607 mailaddress
.Delete(i
- 1);
611 else if (mailaddress
[i
] == L
'\\')
616 const auto buf
= mailaddress
.GetBuffer();
617 auto at
= wcschr(buf
, L
'@');
620 parse_bogus_from(mailaddress
, parsedAddress
, parsedName
);
624 /* Pick up the string around '@', possibly delimited with <>
625 * pair; that is the email part.
640 mailaddress
.ReleaseBuffer();
641 const size_t el
= wcscspn(at
, L
" \n\t\r\v\f>");
642 parsedAddress
= mailaddress
.Mid(static_cast<int>(at
- buf
), static_cast<int>(el
));
643 mailaddress
.Delete(static_cast<int>(at
- buf
), static_cast<int>(el
+ (at
[el
] ? 1 : 0)));
645 /* The remainder is name. It could be
647 * - "John Doe <john.doe@xz>" (a), or
648 * - "john.doe@xz (John Doe)" (b), or
649 * - "John (zzz) Doe <john.doe@xz> (Comment)" (c)
651 * but we have removed the email part, so
653 * - remove extra spaces which could stay after email (case 'c'), and
654 * - trim from both ends, possibly removing the () pair at the end
655 * (cases 'b' and 'c').
657 cleanup_space(mailaddress
);
659 if (!mailaddress
.IsEmpty() && ((mailaddress
[0] == L
'(' && mailaddress
[mailaddress
.GetLength() - 1] == L
')') || (mailaddress
[0] == L
'"' && mailaddress
[mailaddress
.GetLength() - 1] == L
'"')))
660 mailaddress
= mailaddress
.Mid(1, mailaddress
.GetLength() - 2);
662 if (parsedName
&& parsedName
->IsEmpty())
663 get_sane_name(parsedName
, &mailaddress
, parsedAddress
);
666 bool CStringUtils::StartsWith(const wchar_t* heystack
, const CString
& needle
)
668 return wcsncmp(heystack
, needle
, needle
.GetLength()) == 0;
671 bool CStringUtils::EndsWith(const CString
& heystack
, const wchar_t* needle
)
673 const auto lenNeedle
= wcslen(needle
);
674 const auto lenHeystack
= static_cast<size_t>(heystack
.GetLength());
675 if (lenNeedle
> lenHeystack
)
677 return wcsncmp(static_cast<LPCWSTR
>(heystack
) + (lenHeystack
- lenNeedle
), needle
, lenNeedle
) == 0;
680 bool CStringUtils::EndsWith(const CString
& heystack
, const wchar_t needle
)
682 const auto lenHeystack
= heystack
.GetLength();
685 return *(static_cast<LPCWSTR
>(heystack
) + (lenHeystack
- 1)) == needle
;
688 bool CStringUtils::EndsWithI(const CString
& heystack
, const wchar_t* needle
)
690 const auto lenNeedle
= wcslen(needle
);
691 const auto lenHeystack
= static_cast<size_t>(heystack
.GetLength());
692 if (lenNeedle
> lenHeystack
)
694 return _wcsnicmp(static_cast<LPCWSTR
>(heystack
) + (lenHeystack
- lenNeedle
), needle
, lenNeedle
) == 0;
697 bool CStringUtils::StartsWithI(const wchar_t* heystack
, const CString
& needle
)
699 return _wcsnicmp(heystack
, needle
, needle
.GetLength()) == 0;
702 bool CStringUtils::WriteStringToTextFile(LPCWSTR path
, LPCWSTR text
, bool bUTF8
/* = true */)
704 return WriteStringToTextFile(static_cast<const std::wstring
&>(path
), static_cast<const std::wstring
&>(text
), bUTF8
);
707 #endif // #if defined(CSTRING_AVAILABLE) || defined(_MFC_VER)
709 bool CStringUtils::StartsWith(const wchar_t* heystack
, const wchar_t* needle
)
711 return wcsncmp(heystack
, needle
, wcslen(needle
)) == 0;
714 bool CStringUtils::StartsWith(const char* heystack
, const char* needle
)
716 return strncmp(heystack
, needle
, strlen(needle
)) == 0;
719 bool CStringUtils::WriteStringToTextFile(const std::wstring
& path
, const std::wstring
& text
, bool bUTF8
/* = true */)
722 CAutoFile hFile
= CreateFile(path
.c_str(), GENERIC_WRITE
, FILE_SHARE_DELETE
, nullptr, CREATE_ALWAYS
, FILE_ATTRIBUTE_NORMAL
, nullptr);
728 std::string buf
= CUnicodeUtils::StdGetUTF8(text
);
729 if (!WriteFile(hFile
, buf
.c_str(), static_cast<DWORD
>(buf
.length()), &dwWritten
, nullptr))
736 if (!WriteFile(hFile
, text
.c_str(), static_cast<DWORD
>(text
.length() * sizeof(wchar_t)), &dwWritten
, nullptr))
744 inline static void PipeToNull(wchar_t* ptr
)
750 void CStringUtils::PipesToNulls(wchar_t* buffer
, size_t length
)
752 wchar_t* ptr
= buffer
+ length
;
753 while (ptr
!= buffer
)
760 void CStringUtils::PipesToNulls(wchar_t* buffer
)
762 wchar_t* ptr
= buffer
;