CPatch: New memory management
[TortoiseGit.git] / src / Utils / StringUtils.cpp
blob855f16c45161eba4b8209b9d39e8223692e7d251
1 // TortoiseGit - a Windows shell extension for easy version control
3 // Copyright (C) 2015-2016 - 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.
20 #include "stdafx.h"
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 != '?'))
33 return 0;
34 ++wild;
35 ++string;
37 while (*string)
39 if (*wild == '*')
41 if (!*++wild)
42 return 1;
43 mp = wild;
44 cp = string+1;
46 else if ((*wild == *string) || (*wild == '?'))
48 ++wild;
49 ++string;
51 else
53 wild = mp;
54 string = cp++;
58 while (*wild == '*')
59 ++wild;
60 return !*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 != '?'))
70 return 0;
71 ++wild;
72 ++string;
74 while (*string)
76 if (*wild == '*')
78 if (!*++wild)
79 return 1;
80 mp = wild;
81 cp = string+1;
83 else if ((*wild == *string) || (*wild == '?'))
85 ++wild;
86 ++string;
88 else
90 wild = mp;
91 string = cp++;
95 while (*wild == '*')
96 ++wild;
97 return !*wild;
100 #ifdef _MFC_VER
102 void CStringUtils::RemoveAccelerators(CString& text)
104 int pos = 0;
105 while ((pos=text.Find('&',pos))>=0)
107 if (text.GetLength() > (pos-1))
109 if (text.GetAt(pos+1)!=' ')
110 text.Delete(pos);
112 ++pos;
116 TCHAR CStringUtils::GetAccellerator(const CString& text)
118 int pos = 0;
119 while ((pos = text.Find('&', pos)) >= 0)
121 if (text.GetLength() > (pos - 1))
123 if (text.GetAt(pos + 1) != ' ' && text.GetAt(pos + 1) != '&')
124 return towupper(text.GetAt(pos + 1));
126 ++pos;
128 return L'\0';
131 bool CStringUtils::WriteAsciiStringToClipboard(const CStringA& sClipdata, LCID lcid, HWND hOwningWnd)
133 CClipboardHelper clipboardHelper;
134 if (!clipboardHelper.Open(hOwningWnd))
135 return false;
137 EmptyClipboard();
138 HGLOBAL hClipboardData = CClipboardHelper::GlobalAlloc(sClipdata.GetLength() + 1);
139 if (!hClipboardData)
140 return false;
142 char* pchData = (char*)GlobalLock(hClipboardData);
143 if (!pchData)
144 return false;
146 strcpy_s(pchData, sClipdata.GetLength() + 1, (LPCSTR)sClipdata);
147 GlobalUnlock(hClipboardData);
148 if (!SetClipboardData(CF_TEXT, hClipboardData))
149 return false;
151 HANDLE hlocmem = CClipboardHelper::GlobalAlloc(sizeof(LCID));
152 if (!hlocmem)
153 return false;
155 PLCID plcid = (PLCID)GlobalLock(hlocmem);
156 if (plcid)
158 *plcid = lcid;
159 SetClipboardData(CF_LOCALE, static_cast<HANDLE>(plcid));
161 GlobalUnlock(hlocmem);
163 return true;
166 bool CStringUtils::WriteAsciiStringToClipboard(const CStringW& sClipdata, HWND hOwningWnd)
168 CClipboardHelper clipboardHelper;
169 if (!clipboardHelper.Open(hOwningWnd))
170 return false;
172 EmptyClipboard();
173 HGLOBAL hClipboardData = CClipboardHelper::GlobalAlloc((sClipdata.GetLength() + 1) * sizeof(WCHAR));
174 if (!hClipboardData)
175 return false;
177 WCHAR* pchData = (WCHAR*)GlobalLock(hClipboardData);
178 if (!pchData)
179 return false;
181 wcscpy_s(pchData, sClipdata.GetLength() + 1, (LPCWSTR)sClipdata);
182 GlobalUnlock(hClipboardData);
183 if (!SetClipboardData(CF_UNICODETEXT, hClipboardData))
184 return false;
186 // no need to also set CF_TEXT : the OS does this
187 // automatically.
188 return true;
191 bool CStringUtils::WriteDiffToClipboard(const CStringA& sClipdata, HWND hOwningWnd)
193 UINT cFormat = RegisterClipboardFormat(L"TGIT_UNIFIEDDIFF");
194 if (cFormat == 0)
195 return false;
196 CClipboardHelper clipboardHelper;
197 if (!clipboardHelper.Open(hOwningWnd))
198 return false;
200 EmptyClipboard();
201 HGLOBAL hClipboardData = CClipboardHelper::GlobalAlloc(sClipdata.GetLength() + 1);
202 if (!hClipboardData)
203 return false;
205 char* pchData = (char*)GlobalLock(hClipboardData);
206 if (!pchData)
207 return false;
209 strcpy_s(pchData, sClipdata.GetLength()+1, (LPCSTR)sClipdata);
210 GlobalUnlock(hClipboardData);
211 if (!SetClipboardData(cFormat,hClipboardData))
212 return false;
213 if (!SetClipboardData(CF_TEXT, hClipboardData))
214 return false;
216 CString sClipdataW = CUnicodeUtils::GetUnicode(sClipdata);
217 auto hClipboardDataW = CClipboardHelper::GlobalAlloc((sClipdataW.GetLength() + 1) * sizeof(wchar_t));
218 if (!hClipboardDataW)
219 return false;
221 wchar_t* pchDataW = (wchar_t*)GlobalLock(hClipboardDataW);
222 if (!pchDataW)
223 return false;
225 wcscpy_s(pchDataW, sClipdataW.GetLength() + 1, (LPCWSTR)sClipdataW);
226 GlobalUnlock(hClipboardDataW);
227 if (!SetClipboardData(CF_UNICODETEXT, hClipboardDataW))
228 return false;
230 return true;
233 bool CStringUtils::ReadStringFromTextFile(const CString& path, CString& text)
235 if (!PathFileExists(path))
236 return false;
239 CStdioFile file;
240 // w/o typeBinary for some files \r gets dropped
241 if (!file.Open(path, CFile::typeBinary | CFile::modeRead | CFile::shareDenyWrite))
242 return false;
244 CStringA filecontent;
245 UINT filelength = (UINT)file.GetLength();
246 int bytesread = (int)file.Read(filecontent.GetBuffer(filelength), filelength);
247 filecontent.ReleaseBuffer(bytesread);
248 text = CUnicodeUtils::GetUnicode(filecontent);
249 file.Close();
251 catch (CFileException* pE)
253 text.Empty();
254 pE->Delete();
256 return true;
258 #endif // #ifdef _MFC_VER
260 #if defined(CSTRING_AVAILABLE) || defined(_MFC_VER)
261 BOOL CStringUtils::WildCardMatch(const CString& wildcard, const CString& string)
263 return wcswildcmp(wildcard, string);
266 CString CStringUtils::LinesWrap(const CString& longstring, int limit /* = 80 */, bool bCompactPaths /* = true */)
268 CString retString;
269 if ((longstring.GetLength() < limit) || (limit == 0))
270 return longstring; // no wrapping needed.
271 // now start breaking the string into lines
273 int linepos = 0;
274 int lineposold = 0;
275 CString temp;
276 while ((linepos = longstring.Find('\n', linepos)) >= 0)
278 temp = longstring.Mid(lineposold, linepos-lineposold);
279 if ((linepos+1)<longstring.GetLength())
280 ++linepos;
281 else
282 break;
283 lineposold = linepos;
284 if (!retString.IsEmpty())
285 retString += L'\n';
286 retString += WordWrap(temp, limit, bCompactPaths, false, 4);
288 temp = longstring.Mid(lineposold);
289 if (!temp.IsEmpty())
290 retString += L'\n';
291 retString += WordWrap(temp, limit, bCompactPaths, false, 4);
292 retString.Trim();
293 return retString;
296 CString CStringUtils::WordWrap(const CString& longstring, int limit, bool bCompactPaths, bool bForceWrap, int tabSize)
298 int nLength = longstring.GetLength();
299 CString retString;
301 if (limit < 0)
302 limit = 0;
304 int nLineStart = 0;
305 int nLineEnd = 0;
306 int tabOffset = 0;
307 for (int i = 0; i < nLength; ++i)
309 if (i-nLineStart+tabOffset >= limit)
311 if (nLineEnd == nLineStart)
313 if (bForceWrap)
314 nLineEnd = i;
315 else
317 while ((i < nLength) && (longstring[i] != ' ') && (longstring[i] != '\t'))
318 ++i;
319 nLineEnd = i;
322 if (bCompactPaths)
324 CString longline = longstring.Mid(nLineStart, nLineEnd-nLineStart).Left(MAX_PATH-1);
325 if ((bCompactPaths)&&(longline.GetLength() < MAX_PATH))
327 if (((!PathIsFileSpec(longline))&&longline.Find(':')<3)||(PathIsURL(longline)))
329 TCHAR buf[MAX_PATH] = {0};
330 PathCompactPathEx(buf, longline, limit+1, 0);
331 longline = buf;
334 retString += longline;
336 else
337 retString += longstring.Mid(nLineStart, nLineEnd-nLineStart);
338 retString += L'\n';
339 tabOffset = 0;
340 nLineStart = nLineEnd;
342 if (longstring[i] == ' ')
343 nLineEnd = i;
344 if (longstring[i] == '\t')
346 tabOffset += (tabSize - i % tabSize);
347 nLineEnd = i;
350 if (bCompactPaths)
352 CString longline = longstring.Mid(nLineStart).Left(MAX_PATH-1);
353 if ((bCompactPaths)&&(longline.GetLength() < MAX_PATH))
355 if (((!PathIsFileSpec(longline))&&longline.Find(':')<3)||(PathIsURL(longline)))
357 TCHAR buf[MAX_PATH] = {0};
358 PathCompactPathEx(buf, longline, limit+1, 0);
359 longline = buf;
362 retString += longline;
364 else
365 retString += longstring.Mid(nLineStart);
367 return retString;
369 int CStringUtils::GetMatchingLength (const CString& lhs, const CString& rhs)
371 int lhsLength = lhs.GetLength();
372 int rhsLength = rhs.GetLength();
373 int maxResult = min (lhsLength, rhsLength);
375 LPCTSTR pLhs = lhs;
376 LPCTSTR pRhs = rhs;
378 for (int i = 0; i < maxResult; ++i)
379 if (pLhs[i] != pRhs[i])
380 return i;
382 return maxResult;
385 int CStringUtils::FastCompareNoCase (const CStringW& lhs, const CStringW& rhs)
387 // attempt latin-only comparison
389 INT_PTR count = min (lhs.GetLength(), rhs.GetLength()+1);
390 const wchar_t* left = lhs;
391 const wchar_t* right = rhs;
392 for (const wchar_t* last = left + count+1; left < last; ++left, ++right)
394 int leftChar = *left;
395 int rightChar = *right;
397 int diff = leftChar - rightChar;
398 if (diff != 0)
400 // case-sensitive comparison found a difference
402 if ((leftChar | rightChar) >= 0x80)
404 // non-latin char -> fall back to CRT code
405 // (full comparison required as we might have
406 // skipped special chars / UTF plane selectors)
408 return _wcsicmp (lhs, rhs);
411 // normalize to lower case
413 if ((leftChar >= 'A') && (leftChar <= 'Z'))
414 leftChar += 'a' - 'A';
415 if ((rightChar >= 'A') && (rightChar <= 'Z'))
416 rightChar += 'a' - 'A';
418 // compare again
420 diff = leftChar - rightChar;
421 if (diff != 0)
422 return diff;
426 // must be equal (both ended with a 0)
428 return 0;
431 bool CStringUtils::IsPlainReadableASCII(const CString& text)
433 for (int i = 0; i < text.GetLength(); ++i)
435 if (text[i] < 32 || text[i] >= 127)
436 return false;
438 return true;
441 static void cleanup_space(CString& string)
443 for (int pos = 0; pos < string.GetLength(); ++pos)
445 if (_istspace(string[pos])) {
446 string.SetAt(pos, L' ');
447 int cnt;
448 for (cnt = 0; _istspace(string[pos + cnt + 1]); ++cnt);
449 string.Delete(pos + 1, cnt);
454 static void get_sane_name(CString* out, const CString* name, const CString& email)
456 const CString* src = name;
457 if (name->GetLength() < 3 || 60 < name->GetLength() || wcschr(*name, L'@') || wcschr(*name, L'<') || wcschr(*name, L'>'))
458 src = &email;
459 else if (name == out)
460 return;
461 *out = *src;
464 static void parse_bogus_from(const CString& mailaddress, CString& parsedAddress, CString* parsedName)
466 /* John Doe <johndoe> */
468 int bra = mailaddress.Find(L'<');
469 if (bra < 0)
470 return;
471 int ket = mailaddress.Find(L'>');
472 if (ket < 0)
473 return;
475 parsedAddress = mailaddress.Mid(bra + 1, ket - bra - 1);
477 if (parsedName)
479 *parsedName = mailaddress.Left(bra).Trim();
480 get_sane_name(parsedName, parsedName, parsedAddress);
484 void CStringUtils::ParseEmailAddress(CString mailaddress, CString& parsedAddress, CString* parsedName)
486 if (parsedName)
487 parsedName->Empty();
489 mailaddress = mailaddress.Trim();
490 if (mailaddress.IsEmpty())
492 parsedAddress.Empty();
493 return;
496 if (mailaddress.Left(1) == L'"')
498 mailaddress = mailaddress.TrimLeft();
499 bool escaped = false;
500 bool opened = true;
501 for (int i = 1; i < mailaddress.GetLength(); ++i)
503 if (mailaddress[i] == L'"')
505 if (!escaped)
507 opened = !opened;
508 if (!opened)
510 if (parsedName)
511 *parsedName = mailaddress.Mid(1, i - 1);
512 mailaddress = mailaddress.Mid(i);
513 break;
516 else
518 escaped = false;
519 mailaddress.Delete(i - 1);
520 --i;
523 else if (mailaddress[i] == L'\\')
524 escaped = !escaped;
528 auto buf = mailaddress.GetBuffer();
529 auto at = wcschr(buf, L'@');
530 if (!at)
532 parse_bogus_from(mailaddress, parsedAddress, parsedName);
533 return;
536 /* Pick up the string around '@', possibly delimited with <>
537 * pair; that is the email part.
539 while (at > buf)
541 auto c = at[-1];
542 if (_istspace(c))
543 break;
544 if (c == L'<')
546 at[-1] = L' ';
547 break;
549 at--;
552 mailaddress.ReleaseBuffer();
553 size_t el = wcscspn(at, L" \n\t\r\v\f>");
554 parsedAddress = mailaddress.Mid((int)(at - buf), (int)el);
555 mailaddress.Delete((int)(at - buf), (int)(el + (at[el] ? 1 : 0)));
557 /* The remainder is name. It could be
559 * - "John Doe <john.doe@xz>" (a), or
560 * - "john.doe@xz (John Doe)" (b), or
561 * - "John (zzz) Doe <john.doe@xz> (Comment)" (c)
563 * but we have removed the email part, so
565 * - remove extra spaces which could stay after email (case 'c'), and
566 * - trim from both ends, possibly removing the () pair at the end
567 * (cases 'b' and 'c').
569 cleanup_space(mailaddress);
570 mailaddress.Trim();
571 if (!mailaddress.IsEmpty() && ((mailaddress[0] == L'(' && mailaddress[mailaddress.GetLength() - 1] == L')') || (mailaddress[0] == L'"' && mailaddress[mailaddress.GetLength() - 1] == L'"')))
572 mailaddress = mailaddress.Mid(1, mailaddress.GetLength() - 2);
574 if (parsedName && parsedName->IsEmpty())
575 get_sane_name(parsedName, &mailaddress, parsedAddress);
578 bool CStringUtils::StartsWith(const wchar_t* heystack, const CString& needle)
580 return wcsncmp(heystack, needle, needle.GetLength()) == 0;
583 bool CStringUtils::EndsWith(const CString& heystack, const wchar_t* needle)
585 auto lenNeedle = wcslen(needle);
586 auto lenHeystack = (size_t)heystack.GetLength();
587 if (lenNeedle > lenHeystack)
588 return false;
589 return wcsncmp((LPCTSTR)heystack + (lenHeystack - lenNeedle), needle, lenNeedle) == 0;
592 bool CStringUtils::EndsWith(const CString& heystack, const wchar_t needle)
594 auto lenHeystack = heystack.GetLength();
595 if (!lenHeystack)
596 return false;
597 return *((LPCTSTR)heystack + (lenHeystack - 1)) == needle;
600 bool CStringUtils::EndsWithI(const CString& heystack, const wchar_t* needle)
602 auto lenNeedle = wcslen(needle);
603 auto lenHeystack = (size_t)heystack.GetLength();
604 if (lenNeedle > lenHeystack)
605 return false;
606 return _wcsnicmp((LPCTSTR)heystack + (lenHeystack - lenNeedle), needle, lenNeedle) == 0;
609 bool CStringUtils::StartsWithI(const wchar_t* heystack, const CString& needle)
611 return _wcsnicmp(heystack, needle, needle.GetLength()) == 0;
614 bool CStringUtils::WriteStringToTextFile(LPCTSTR path, LPCTSTR text, bool bUTF8 /* = true */)
616 return WriteStringToTextFile((const std::wstring&)path, (const std::wstring&)text, bUTF8);
619 #endif // #if defined(CSTRING_AVAILABLE) || defined(_MFC_VER)
621 bool CStringUtils::StartsWith(const wchar_t* heystack, const wchar_t* needle)
623 return wcsncmp(heystack, needle, wcslen(needle)) == 0;
626 bool CStringUtils::StartsWith(const char* heystack, const char* needle)
628 return strncmp(heystack, needle, strlen(needle)) == 0;
631 bool CStringUtils::WriteStringToTextFile(const std::wstring& path, const std::wstring& text, bool bUTF8 /* = true */)
633 DWORD dwWritten = 0;
634 CAutoFile hFile = CreateFile(path.c_str(), GENERIC_WRITE, FILE_SHARE_DELETE, nullptr, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, nullptr);
635 if (!hFile)
636 return false;
638 if (bUTF8)
640 std::string buf = CUnicodeUtils::StdGetUTF8(text);
641 if (!WriteFile(hFile, buf.c_str(), (DWORD)buf.length(), &dwWritten, nullptr))
643 return false;
646 else
648 if (!WriteFile(hFile, text.c_str(), (DWORD)text.length(), &dwWritten, nullptr))
650 return false;
653 return true;
656 inline static void PipeToNull(TCHAR* ptr)
658 if (*ptr == '|')
659 *ptr = '\0';
662 void CStringUtils::PipesToNulls(TCHAR* buffer, size_t length)
664 TCHAR* ptr = buffer + length;
665 while (ptr != buffer)
667 PipeToNull(ptr);
668 ptr--;
672 void CStringUtils::PipesToNulls(TCHAR* buffer)
674 TCHAR* ptr = buffer;
675 while (*ptr)
677 PipeToNull(ptr);
678 ++ptr;