xref: /reactos/dll/win32/comctl32/string.c (revision 1de09c47)
1 /*
2  * String manipulation functions
3  *
4  * Copyright 1998 Eric Kohl
5  *           1998 Juergen Schmied <j.schmied@metronet.de>
6  *           2000 Eric Kohl for CodeWeavers
7  * Copyright 2002 Jon Griffiths
8  *
9  * This library is free software; you can redistribute it and/or
10  * modify it under the terms of the GNU Lesser General Public
11  * License as published by the Free Software Foundation; either
12  * version 2.1 of the License, or (at your option) any later version.
13  *
14  * This library is distributed in the hope that it will be useful,
15  * but WITHOUT ANY WARRANTY; without even the implied warranty of
16  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
17  * Lesser General Public License for more details.
18  *
19  * You should have received a copy of the GNU Lesser General Public
20  * License along with this library; if not, write to the Free Software
21  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
22  *
23  */
24 
25 #include <stdarg.h>
26 #include <string.h>
27 #include <stdlib.h> /* atoi */
28 
29 #include "windef.h"
30 #include "winbase.h"
31 #include "winuser.h"
32 #include "winnls.h"
33 
34 #include "comctl32.h"
35 
36 
37 #include "wine/debug.h"
38 
39 WINE_DEFAULT_DEBUG_CHANNEL(commctrl);
40 
41 /*************************************************************************
42  * COMCTL32_ChrCmpHelperA
43  *
44  * Internal helper for ChrCmpA/COMCTL32_ChrCmpIA.
45  *
46  * NOTES
47  *  Both this function and its Unicode counterpart are very inefficient. To
48  *  fix this, CompareString must be completely implemented and optimised
49  *  first. Then the core character test can be taken out of that function and
50  *  placed here, so that it need never be called at all. Until then, do not
51  *  attempt to optimise this code unless you are willing to test that it
52  *  still performs correctly.
53  */
54 static BOOL COMCTL32_ChrCmpHelperA(WORD ch1, WORD ch2, DWORD dwFlags)
55 {
56   char str1[3], str2[3];
57 
58   str1[0] = LOBYTE(ch1);
59   if (IsDBCSLeadByte(str1[0]))
60   {
61     str1[1] = HIBYTE(ch1);
62     str1[2] = '\0';
63   }
64   else
65     str1[1] = '\0';
66 
67   str2[0] = LOBYTE(ch2);
68   if (IsDBCSLeadByte(str2[0]))
69   {
70     str2[1] = HIBYTE(ch2);
71     str2[2] = '\0';
72   }
73   else
74     str2[1] = '\0';
75 
76   return CompareStringA(GetThreadLocale(), dwFlags, str1, -1, str2, -1) - CSTR_EQUAL;
77 }
78 
79 /*************************************************************************
80  * COMCTL32_ChrCmpA (internal)
81  *
82  * Internal helper function.
83  */
84 static BOOL COMCTL32_ChrCmpA(WORD ch1, WORD ch2)
85 {
86   return COMCTL32_ChrCmpHelperA(ch1, ch2, 0);
87 }
88 
89 /*************************************************************************
90  * COMCTL32_ChrCmpIA	(internal)
91  *
92  * Compare two characters, ignoring case.
93  *
94  * PARAMS
95  *  ch1 [I] First character to compare
96  *  ch2 [I] Second character to compare
97  *
98  * RETURNS
99  *  FALSE, if the characters are equal.
100  *  Non-zero otherwise.
101  */
102 static BOOL COMCTL32_ChrCmpIA(WORD ch1, WORD ch2)
103 {
104   TRACE("(%d,%d)\n", ch1, ch2);
105 
106   return COMCTL32_ChrCmpHelperA(ch1, ch2, NORM_IGNORECASE);
107 }
108 
109 /*************************************************************************
110  * COMCTL32_ChrCmpIW
111  *
112  * Internal helper function.
113  */
114 static inline BOOL COMCTL32_ChrCmpIW(WCHAR ch1, WCHAR ch2)
115 {
116   return CompareStringW(GetThreadLocale(), NORM_IGNORECASE, &ch1, 1, &ch2, 1) - CSTR_EQUAL;
117 }
118 
119 /**************************************************************************
120  * Str_GetPtrA [COMCTL32.233]
121  *
122  * Copies a string into a destination buffer.
123  *
124  * PARAMS
125  *     lpSrc   [I] Source string
126  *     lpDest  [O] Destination buffer
127  *     nMaxLen [I] Size of buffer in characters
128  *
129  * RETURNS
130  *     The number of characters copied.
131  */
132 INT WINAPI Str_GetPtrA (LPCSTR lpSrc, LPSTR lpDest, INT nMaxLen)
133 {
134     INT len;
135 
136     TRACE("(%p %p %d)\n", lpSrc, lpDest, nMaxLen);
137 
138     if ((!lpDest || nMaxLen == 0) && lpSrc)
139         return (strlen(lpSrc) + 1);
140 
141     if (nMaxLen == 0)
142         return 0;
143 
144     if (lpSrc == NULL) {
145         lpDest[0] = '\0';
146         return 0;
147     }
148 
149     len = strlen(lpSrc) + 1;
150     if (len >= nMaxLen)
151         len = nMaxLen;
152 
153     RtlMoveMemory (lpDest, lpSrc, len - 1);
154     lpDest[len - 1] = '\0';
155 
156     return len;
157 }
158 
159 /**************************************************************************
160  * Str_SetPtrA [COMCTL32.234]
161  *
162  * Makes a copy of a string, allocating memory if necessary.
163  *
164  * PARAMS
165  *     lppDest [O] Pointer to destination string
166  *     lpSrc   [I] Source string
167  *
168  * RETURNS
169  *     Success: TRUE
170  *     Failure: FALSE
171  *
172  * NOTES
173  *     Set lpSrc to NULL to free the memory allocated by a previous call
174  *     to this function.
175  */
176 BOOL WINAPI Str_SetPtrA (LPSTR *lppDest, LPCSTR lpSrc)
177 {
178     TRACE("(%p %p)\n", lppDest, lpSrc);
179 
180     if (lpSrc) {
181         LPSTR ptr = ReAlloc (*lppDest, strlen (lpSrc) + 1);
182         if (!ptr)
183             return FALSE;
184         strcpy (ptr, lpSrc);
185         *lppDest = ptr;
186     }
187     else {
188         Free (*lppDest);
189         *lppDest = NULL;
190     }
191 
192     return TRUE;
193 }
194 
195 /**************************************************************************
196  * Str_GetPtrW [COMCTL32.235]
197  *
198  * See Str_GetPtrA.
199  */
200 INT WINAPI Str_GetPtrW (LPCWSTR lpSrc, LPWSTR lpDest, INT nMaxLen)
201 {
202     INT len;
203 
204     TRACE("(%p %p %d)\n", lpSrc, lpDest, nMaxLen);
205 
206     if (!lpDest && lpSrc)
207         return lstrlenW (lpSrc);
208 
209     if (nMaxLen == 0)
210         return 0;
211 
212     if (lpSrc == NULL) {
213         lpDest[0] = '\0';
214         return 0;
215     }
216 
217     len = lstrlenW (lpSrc);
218     if (len >= nMaxLen)
219         len = nMaxLen - 1;
220 
221     RtlMoveMemory (lpDest, lpSrc, len*sizeof(WCHAR));
222     lpDest[len] = '\0';
223 
224     return len;
225 }
226 
227 /**************************************************************************
228  * Str_SetPtrW [COMCTL32.236]
229  *
230  * See Str_SetPtrA.
231  */
232 BOOL WINAPI Str_SetPtrW (LPWSTR *lppDest, LPCWSTR lpSrc)
233 {
234     TRACE("(%p %s)\n", lppDest, debugstr_w(lpSrc));
235 
236     if (lpSrc) {
237         INT len = lstrlenW (lpSrc) + 1;
238         LPWSTR ptr = ReAlloc (*lppDest, len * sizeof(WCHAR));
239         if (!ptr)
240             return FALSE;
241         lstrcpyW (ptr, lpSrc);
242         *lppDest = ptr;
243     }
244     else {
245         Free (*lppDest);
246         *lppDest = NULL;
247     }
248 
249     return TRUE;
250 }
251 
252 /**************************************************************************
253  * StrChrA [COMCTL32.350]
254  *
255  * Find a given character in a string.
256  *
257  * PARAMS
258  *  lpszStr [I] String to search in.
259  *  ch      [I] Character to search for.
260  *
261  * RETURNS
262  *  Success: A pointer to the first occurrence of ch in lpszStr, or NULL if
263  *           not found.
264  *  Failure: NULL, if any arguments are invalid.
265  */
266 LPSTR WINAPI StrChrA(LPCSTR lpszStr, WORD ch)
267 {
268   TRACE("(%s,%i)\n", debugstr_a(lpszStr), ch);
269 
270   if (lpszStr)
271   {
272     while (*lpszStr)
273     {
274       if (!COMCTL32_ChrCmpA(*lpszStr, ch))
275         return (LPSTR)lpszStr;
276       lpszStr = CharNextA(lpszStr);
277     }
278   }
279   return NULL;
280 }
281 
282 /**************************************************************************
283  * StrCmpNIA [COMCTL32.353]
284  *
285  * Compare two strings, up to a maximum length, ignoring case.
286  *
287  * PARAMS
288  *  lpszStr  [I] First string to compare
289  *  lpszComp [I] Second string to compare
290  *  iLen     [I] Number of chars to compare
291  *
292  * RETURNS
293  *  An integer less than, equal to or greater than 0, indicating that
294  *  lpszStr is less than, the same, or greater than lpszComp.
295  */
296 INT WINAPI StrCmpNIA(LPCSTR lpszStr, LPCSTR lpszComp, INT iLen)
297 {
298   TRACE("(%s,%s,%i)\n", debugstr_a(lpszStr), debugstr_a(lpszComp), iLen);
299   return CompareStringA(GetThreadLocale(), NORM_IGNORECASE, lpszStr, iLen, lpszComp, iLen) - CSTR_EQUAL;
300 }
301 
302 /*************************************************************************
303  * StrCmpNIW	[COMCTL32.361]
304  *
305  * See StrCmpNIA.
306  */
307 INT WINAPI StrCmpNIW(LPCWSTR lpszStr, LPCWSTR lpszComp, INT iLen)
308 {
309   TRACE("(%s,%s,%i)\n", debugstr_w(lpszStr), debugstr_w(lpszComp), iLen);
310   return CompareStringW(GetThreadLocale(), NORM_IGNORECASE, lpszStr, iLen, lpszComp, iLen) - CSTR_EQUAL;
311 }
312 
313 /*************************************************************************
314  * COMCTL32_StrStrHelperA
315  *
316  * Internal implementation of StrStrA/StrStrIA
317  */
318 static LPSTR COMCTL32_StrStrHelperA(LPCSTR lpszStr, LPCSTR lpszSearch,
319                                     INT (WINAPI *pStrCmpFn)(LPCSTR,LPCSTR,INT))
320 {
321   size_t iLen;
322   LPCSTR end;
323 
324   if (!lpszStr || !lpszSearch || !*lpszSearch)
325     return NULL;
326 
327   iLen = strlen(lpszSearch);
328   end = lpszStr + strlen(lpszStr);
329 
330   while (lpszStr + iLen <= end)
331   {
332     if (!pStrCmpFn(lpszStr, lpszSearch, iLen))
333       return (LPSTR)lpszStr;
334     lpszStr = CharNextA(lpszStr);
335   }
336   return NULL;
337 }
338 
339 /**************************************************************************
340  * StrStrIA [COMCTL32.355]
341  *
342  * Find a substring within a string, ignoring case.
343  *
344  * PARAMS
345  *  lpszStr    [I] String to search in
346  *  lpszSearch [I] String to look for
347  *
348  * RETURNS
349  *  The start of lpszSearch within lpszStr, or NULL if not found.
350  */
351 LPSTR WINAPI StrStrIA(LPCSTR lpszStr, LPCSTR lpszSearch)
352 {
353   TRACE("(%s,%s)\n", debugstr_a(lpszStr), debugstr_a(lpszSearch));
354 
355   return COMCTL32_StrStrHelperA(lpszStr, lpszSearch, StrCmpNIA);
356 }
357 
358 /**************************************************************************
359  * StrToIntA [COMCTL32.357]
360  *
361  * Read a signed integer from a string.
362  *
363  * PARAMS
364  *  lpszStr [I] String to read integer from
365  *
366  * RETURNS
367  *   The signed integer value represented by the string, or 0 if no integer is
368  *   present.
369  */
370 INT WINAPI StrToIntA (LPCSTR lpszStr)
371 {
372     return atoi(lpszStr);
373 }
374 
375 /**************************************************************************
376  * StrStrIW [COMCTL32.363]
377  *
378  * See StrStrIA.
379  */
380 LPWSTR WINAPI StrStrIW(LPCWSTR lpszStr, LPCWSTR lpszSearch)
381 {
382   int iLen;
383   LPCWSTR end;
384 
385   TRACE("(%s,%s)\n", debugstr_w(lpszStr), debugstr_w(lpszSearch));
386 
387   if (!lpszStr || !lpszSearch || !*lpszSearch)
388     return NULL;
389 
390   iLen = lstrlenW(lpszSearch);
391   end = lpszStr + lstrlenW(lpszStr);
392 
393   while (lpszStr + iLen <= end)
394   {
395     if (!StrCmpNIW(lpszStr, lpszSearch, iLen))
396       return (LPWSTR)lpszStr;
397     lpszStr++;
398   }
399   return NULL;
400 }
401 
402 /**************************************************************************
403  * StrToIntW [COMCTL32.365]
404  *
405  * See StrToIntA.
406  */
407 INT WINAPI StrToIntW (LPCWSTR lpString)
408 {
409     return wcstol(lpString, NULL, 10);
410 }
411 
412 /*************************************************************************
413  * COMCTL32_StrSpnHelperA (internal)
414  *
415  * Internal implementation of StrSpnA/StrCSpnA/StrCSpnIA
416  */
417 static int COMCTL32_StrSpnHelperA(LPCSTR lpszStr, LPCSTR lpszMatch,
418                                   LPSTR (WINAPI *pStrChrFn)(LPCSTR,WORD),
419                                   BOOL bInvert)
420 {
421   LPCSTR lpszRead = lpszStr;
422   if (lpszStr && *lpszStr && lpszMatch)
423   {
424     while (*lpszRead)
425     {
426       LPCSTR lpszTest = pStrChrFn(lpszMatch, *lpszRead);
427 
428       if (!bInvert && !lpszTest)
429         break;
430       if (bInvert && lpszTest)
431         break;
432       lpszRead = CharNextA(lpszRead);
433     };
434   }
435   return lpszRead - lpszStr;
436 }
437 
438 /**************************************************************************
439  * StrCSpnA [COMCTL32.356]
440  *
441  * Find the length of the start of a string that does not contain certain
442  * characters.
443  *
444  * PARAMS
445  *  lpszStr   [I] String to search
446  *  lpszMatch [I] Characters that cannot be in the substring
447  *
448  * RETURNS
449  *  The length of the part of lpszStr containing only chars not in lpszMatch,
450  *  or 0 if any parameter is invalid.
451  */
452 int WINAPI StrCSpnA(LPCSTR lpszStr, LPCSTR lpszMatch)
453 {
454   TRACE("(%s,%s)\n",debugstr_a(lpszStr), debugstr_a(lpszMatch));
455 
456   return COMCTL32_StrSpnHelperA(lpszStr, lpszMatch, StrChrA, TRUE);
457 }
458 
459 /**************************************************************************
460  * StrChrW [COMCTL32.358]
461  *
462  * See StrChrA.
463  */
464 LPWSTR WINAPI StrChrW(LPCWSTR lpszStr, WCHAR ch)
465 {
466   LPWSTR lpszRet = NULL;
467 
468   TRACE("(%s,%i)\n", debugstr_w(lpszStr), ch);
469 
470   if (lpszStr)
471     lpszRet = wcschr(lpszStr, ch);
472   return lpszRet;
473 }
474 
475 /**************************************************************************
476  * StrCmpNA [COMCTL32.352]
477  *
478  * Compare two strings, up to a maximum length.
479  *
480  * PARAMS
481  *  lpszStr  [I] First string to compare
482  *  lpszComp [I] Second string to compare
483  *  iLen     [I] Number of chars to compare
484  *
485  * RETURNS
486  *  An integer less than, equal to or greater than 0, indicating that
487  *  lpszStr is less than, the same, or greater than lpszComp.
488  */
489 INT WINAPI StrCmpNA(LPCSTR lpszStr, LPCSTR lpszComp, INT iLen)
490 {
491   TRACE("(%s,%s,%i)\n", debugstr_a(lpszStr), debugstr_a(lpszComp), iLen);
492   return CompareStringA(GetThreadLocale(), 0, lpszStr, iLen, lpszComp, iLen) - CSTR_EQUAL;
493 }
494 
495 /**************************************************************************
496  * StrCmpNW [COMCTL32.360]
497  *
498  * See StrCmpNA.
499  */
500 INT WINAPI StrCmpNW(LPCWSTR lpszStr, LPCWSTR lpszComp, INT iLen)
501 {
502   TRACE("(%s,%s,%i)\n", debugstr_w(lpszStr), debugstr_w(lpszComp), iLen);
503   return CompareStringW(GetThreadLocale(), 0, lpszStr, iLen, lpszComp, iLen) - CSTR_EQUAL;
504 }
505 
506 /**************************************************************************
507  * StrRChrA [COMCTL32.351]
508  *
509  * Find the last occurrence of a character in string.
510  *
511  * PARAMS
512  *  lpszStr [I] String to search in
513  *  lpszEnd [I] Place to end search, or NULL to search until the end of lpszStr
514  *  ch      [I] Character to search for.
515  *
516  * RETURNS
517  *  Success: A pointer to the last occurrence of ch in lpszStr before lpszEnd,
518  *           or NULL if not found.
519  *  Failure: NULL, if any arguments are invalid.
520  */
521 LPSTR WINAPI StrRChrA(LPCSTR lpszStr, LPCSTR lpszEnd, WORD ch)
522 {
523   LPCSTR lpszRet = NULL;
524 
525   TRACE("(%s,%s,%x)\n", debugstr_a(lpszStr), debugstr_a(lpszEnd), ch);
526 
527   if (lpszStr)
528   {
529     WORD ch2;
530 
531     if (!lpszEnd)
532       lpszEnd = lpszStr + lstrlenA(lpszStr);
533 
534     while (*lpszStr && lpszStr <= lpszEnd)
535     {
536       ch2 = IsDBCSLeadByte(*lpszStr)? *lpszStr << 8 | lpszStr[1] : *lpszStr;
537 
538       if (!COMCTL32_ChrCmpA(ch, ch2))
539         lpszRet = lpszStr;
540       lpszStr = CharNextA(lpszStr);
541     }
542   }
543   return (LPSTR)lpszRet;
544 }
545 
546 
547 /**************************************************************************
548  * StrRChrW [COMCTL32.359]
549  *
550  * See StrRChrA.
551  */
552 LPWSTR WINAPI StrRChrW(LPCWSTR str, LPCWSTR end, WORD ch)
553 {
554     WCHAR *ret = NULL;
555 
556     if (!str) return NULL;
557     if (!end) end = str + lstrlenW(str);
558     while (str < end)
559     {
560         if (*str == ch) ret = (WCHAR *)str;
561         str++;
562     }
563     return ret;
564 }
565 
566 /**************************************************************************
567  * StrStrA [COMCTL32.354]
568  *
569  * Find a substring within a string.
570  *
571  * PARAMS
572  *  lpszStr    [I] String to search in
573  *  lpszSearch [I] String to look for
574  *
575  * RETURNS
576  *  The start of lpszSearch within lpszStr, or NULL if not found.
577  */
578 LPSTR WINAPI StrStrA(LPCSTR lpszStr, LPCSTR lpszSearch)
579 {
580   TRACE("(%s,%s)\n", debugstr_a(lpszStr), debugstr_a(lpszSearch));
581 
582   return COMCTL32_StrStrHelperA(lpszStr, lpszSearch, StrCmpNA);
583 }
584 
585 /**************************************************************************
586  * StrStrW [COMCTL32.362]
587  *
588  * See StrStrA.
589  */
590 LPWSTR WINAPI StrStrW(LPCWSTR lpszStr, LPCWSTR lpszSearch)
591 {
592     if (!lpszStr || !lpszSearch) return NULL;
593     return wcsstr( lpszStr, lpszSearch );
594 }
595 
596 /*************************************************************************
597  * StrChrIA	[COMCTL32.366]
598  *
599  * Find a given character in a string, ignoring case.
600  *
601  * PARAMS
602  *  lpszStr [I] String to search in.
603  *  ch      [I] Character to search for.
604  *
605  * RETURNS
606  *  Success: A pointer to the first occurrence of ch in lpszStr, or NULL if
607  *           not found.
608  *  Failure: NULL, if any arguments are invalid.
609  */
610 LPSTR WINAPI StrChrIA(LPCSTR lpszStr, WORD ch)
611 {
612   TRACE("(%s,%i)\n", debugstr_a(lpszStr), ch);
613 
614   if (lpszStr)
615   {
616     while (*lpszStr)
617     {
618       if (!COMCTL32_ChrCmpIA(*lpszStr, ch))
619         return (LPSTR)lpszStr;
620       lpszStr = CharNextA(lpszStr);
621     }
622   }
623   return NULL;
624 }
625 
626 /*************************************************************************
627  * StrChrIW	[COMCTL32.367]
628  *
629  * See StrChrA.
630  */
631 LPWSTR WINAPI StrChrIW(LPCWSTR lpszStr, WCHAR ch)
632 {
633   TRACE("(%s,%i)\n", debugstr_w(lpszStr), ch);
634 
635   if (lpszStr)
636   {
637     ch = towupper(ch);
638     while (*lpszStr)
639     {
640       if (towupper(*lpszStr) == ch)
641         return (LPWSTR)lpszStr;
642       lpszStr++;
643     }
644     lpszStr = NULL;
645   }
646   return (LPWSTR)lpszStr;
647 }
648 
649 /*************************************************************************
650  * StrRStrIA	[COMCTL32.372]
651  *
652  * Find the last occurrence of a substring within a string.
653  *
654  * PARAMS
655  *  lpszStr    [I] String to search in
656  *  lpszEnd    [I] End of lpszStr
657  *  lpszSearch [I] String to look for
658  *
659  * RETURNS
660  *  The last occurrence lpszSearch within lpszStr, or NULL if not found.
661  */
662 LPSTR WINAPI StrRStrIA(LPCSTR lpszStr, LPCSTR lpszEnd, LPCSTR lpszSearch)
663 {
664   LPSTR lpszRet = NULL;
665   WORD ch1, ch2;
666   INT iLen;
667 
668   TRACE("(%s,%s)\n", debugstr_a(lpszStr), debugstr_a(lpszSearch));
669 
670   if (!lpszStr || !lpszSearch || !*lpszSearch)
671     return NULL;
672 
673   if (IsDBCSLeadByte(*lpszSearch))
674     ch1 = *lpszSearch << 8 | (UCHAR)lpszSearch[1];
675   else
676     ch1 = *lpszSearch;
677   iLen = lstrlenA(lpszSearch);
678 
679   if (!lpszEnd)
680     lpszEnd = lpszStr + lstrlenA(lpszStr);
681   else /* reproduce the broken behaviour on Windows */
682     lpszEnd += min(iLen - 1, lstrlenA(lpszEnd));
683 
684   while (lpszStr + iLen <= lpszEnd && *lpszStr)
685   {
686     ch2 = IsDBCSLeadByte(*lpszStr)? *lpszStr << 8 | (UCHAR)lpszStr[1] : *lpszStr;
687     if (!COMCTL32_ChrCmpIA(ch1, ch2))
688     {
689       if (!StrCmpNIA(lpszStr, lpszSearch, iLen))
690         lpszRet = (LPSTR)lpszStr;
691     }
692     lpszStr = CharNextA(lpszStr);
693   }
694   return lpszRet;
695 }
696 
697 /*************************************************************************
698  * StrRStrIW	[COMCTL32.373]
699  *
700  * See StrRStrIA.
701  */
702 LPWSTR WINAPI StrRStrIW(LPCWSTR lpszStr, LPCWSTR lpszEnd, LPCWSTR lpszSearch)
703 {
704   LPWSTR lpszRet = NULL;
705   INT iLen;
706 
707   TRACE("(%s,%s)\n", debugstr_w(lpszStr), debugstr_w(lpszSearch));
708 
709   if (!lpszStr || !lpszSearch || !*lpszSearch)
710     return NULL;
711 
712   iLen = lstrlenW(lpszSearch);
713 
714   if (!lpszEnd)
715     lpszEnd = lpszStr + lstrlenW(lpszStr);
716   else /* reproduce the broken behaviour on Windows */
717     lpszEnd += min(iLen - 1, lstrlenW(lpszEnd));
718 
719 
720   while (lpszStr + iLen <= lpszEnd && *lpszStr)
721   {
722     if (!COMCTL32_ChrCmpIW(*lpszSearch, *lpszStr))
723     {
724       if (!StrCmpNIW(lpszStr, lpszSearch, iLen))
725         lpszRet = (LPWSTR)lpszStr;
726     }
727     lpszStr++;
728   }
729   return lpszRet;
730 }
731 
732 /*************************************************************************
733  * StrCSpnIA	[COMCTL32.374]
734  *
735  * Find the length of the start of a string that does not contain certain
736  * characters, ignoring case.
737  *
738  * PARAMS
739  *  lpszStr   [I] String to search
740  *  lpszMatch [I] Characters that cannot be in the substring
741  *
742  * RETURNS
743  *  The length of the part of lpszStr containing only chars not in lpszMatch,
744  *  or 0 if any parameter is invalid.
745  */
746 int WINAPI StrCSpnIA(LPCSTR lpszStr, LPCSTR lpszMatch)
747 {
748   TRACE("(%s,%s)\n",debugstr_a(lpszStr), debugstr_a(lpszMatch));
749 
750   return COMCTL32_StrSpnHelperA(lpszStr, lpszMatch, StrChrIA, TRUE);
751 }
752 
753 /*************************************************************************
754  * StrCSpnIW	[COMCTL32.375]
755  *
756  * See StrCSpnIA.
757  */
758 int WINAPI StrCSpnIW(LPCWSTR lpszStr, LPCWSTR lpszMatch)
759 {
760   LPCWSTR lpszRead = lpszStr;
761 
762   TRACE("(%s,%s)\n",debugstr_w(lpszStr), debugstr_w(lpszMatch));
763 
764   if (lpszStr && *lpszStr && lpszMatch)
765   {
766     while (*lpszRead)
767     {
768       if (StrChrIW(lpszMatch, *lpszRead)) break;
769       lpszRead++;
770     }
771   }
772   return lpszRead - lpszStr;
773 }
774 
775 /**************************************************************************
776  * StrRChrIA	[COMCTL32.368]
777  *
778  * Find the last occurrence of a character in string, ignoring case.
779  *
780  * PARAMS
781  *  lpszStr [I] String to search in
782  *  lpszEnd [I] Place to end search, or NULL to search until the end of lpszStr
783  *  ch      [I] Character to search for.
784  *
785  * RETURNS
786  *  Success: A pointer to the last occurrence of ch in lpszStr before lpszEnd,
787  *           or NULL if not found.
788  *  Failure: NULL, if any arguments are invalid.
789  */
790 LPSTR WINAPI StrRChrIA(LPCSTR lpszStr, LPCSTR lpszEnd, WORD ch)
791 {
792   LPCSTR lpszRet = NULL;
793 
794   TRACE("(%s,%s,%x)\n", debugstr_a(lpszStr), debugstr_a(lpszEnd), ch);
795 
796   if (lpszStr)
797   {
798     WORD ch2;
799 
800     if (!lpszEnd)
801       lpszEnd = lpszStr + lstrlenA(lpszStr);
802 
803     while (*lpszStr && lpszStr <= lpszEnd)
804     {
805       ch2 = IsDBCSLeadByte(*lpszStr)? *lpszStr << 8 | lpszStr[1] : *lpszStr;
806 
807       if (ch == ch2)
808         lpszRet = lpszStr;
809       lpszStr = CharNextA(lpszStr);
810     }
811   }
812   return (LPSTR)lpszRet;
813 }
814 
815 /**************************************************************************
816  * StrRChrIW	[COMCTL32.369]
817  *
818  * See StrRChrIA.
819  */
820 LPWSTR WINAPI StrRChrIW(LPCWSTR str, LPCWSTR end, WORD ch)
821 {
822     WCHAR *ret = NULL;
823 
824     if (!str) return NULL;
825     if (!end) end = str + lstrlenW(str);
826     while (str < end)
827     {
828         if (!COMCTL32_ChrCmpIW(*str, ch)) ret = (WCHAR *)str;
829         str++;
830     }
831     return ret;
832 }
833 
834 /*************************************************************************
835  * StrCSpnW	[COMCTL32.364]
836  *
837  * See StrCSpnA.
838  */
839 int WINAPI StrCSpnW(LPCWSTR lpszStr, LPCWSTR lpszMatch)
840 {
841     if (!lpszStr || !lpszMatch) return 0;
842     return wcscspn( lpszStr, lpszMatch );
843 }
844 
845 /*************************************************************************
846  * IntlStrEqWorkerA	[COMCTL32.376]
847  *
848  * Compare two strings.
849  *
850  * PARAMS
851  *  bCase    [I] Whether to compare case sensitively
852  *  lpszStr  [I] First string to compare
853  *  lpszComp [I] Second string to compare
854  *  iLen     [I] Length to compare
855  *
856  * RETURNS
857  *  TRUE  If the strings are equal.
858  *  FALSE Otherwise.
859  */
860 BOOL WINAPI IntlStrEqWorkerA(BOOL bCase, LPCSTR lpszStr, LPCSTR lpszComp,
861                              int iLen)
862 {
863   DWORD dwFlags;
864   int iRet;
865 
866   TRACE("(%d,%s,%s,%d)\n", bCase,
867         debugstr_a(lpszStr), debugstr_a(lpszComp), iLen);
868 
869   /* FIXME: This flag is undocumented and unknown by our CompareString.
870    */
871   dwFlags = LOCALE_RETURN_GENITIVE_NAMES;
872   if (!bCase) dwFlags |= NORM_IGNORECASE;
873 
874   iRet = CompareStringA(GetThreadLocale(),
875                         dwFlags, lpszStr, iLen, lpszComp, iLen);
876 
877   if (!iRet)
878     iRet = CompareStringA(2048, dwFlags, lpszStr, iLen, lpszComp, iLen);
879 
880   return iRet == CSTR_EQUAL;
881 }
882 
883 /*************************************************************************
884  * IntlStrEqWorkerW	[COMCTL32.377]
885  *
886  * See IntlStrEqWorkerA.
887  */
888 BOOL WINAPI IntlStrEqWorkerW(BOOL bCase, LPCWSTR lpszStr, LPCWSTR lpszComp,
889                              int iLen)
890 {
891   DWORD dwFlags;
892   int iRet;
893 
894   TRACE("(%d,%s,%s,%d)\n", bCase,
895         debugstr_w(lpszStr),debugstr_w(lpszComp), iLen);
896 
897   /* FIXME: This flag is undocumented and unknown by our CompareString.
898    */
899   dwFlags = LOCALE_RETURN_GENITIVE_NAMES;
900   if (!bCase) dwFlags |= NORM_IGNORECASE;
901 
902   iRet = CompareStringW(GetThreadLocale(),
903                         dwFlags, lpszStr, iLen, lpszComp, iLen);
904 
905   if (!iRet)
906     iRet = CompareStringW(2048, dwFlags, lpszStr, iLen, lpszComp, iLen);
907 
908   return iRet == CSTR_EQUAL;
909 }
910