Merge pull request #9525 from danmosemsft/defines2
[platform/upstream/coreclr.git] / src / utilcode / sortversioning.cpp
1 // Licensed to the .NET Foundation under one or more agreements.
2 // The .NET Foundation licenses this file to you under the MIT license.
3 // See the LICENSE file in the project root for more information.
4 ////////////////////////////////////////////////////////////////////////////
5 //
6 //  File:    SortVersioning.cpp
7 //
8
9
10 //  Purpose:  Provides access of the sort versioning functionality on
11 //            downlevel (pre-Win7) machines.
12 //
13 //       
14 //            This is not used on CoreCLR, where we always go to the OS
15 //            for sorting.
16 //            
17 ////////////////////////////////////////////////////////////////////////////
18
19 #include "stdafx.h"
20 #include "sortversioning.h"
21 #include "newapis.h"
22
23 #include "mscoree.h"
24 #include "clrconfig.h"
25
26 #define SORT_VERSION_V4         0x00060101
27 #define SORT_VERSION_WHIDBEY    0x00001000
28 #define SORT_VERSION_DEFAULT    SORT_VERSION_V4
29 #define SORT_DEFAULT_DLL_NAME   MAKEDLLNAME(W("nlssorting"))
30
31 namespace SortVersioning
32 {
33 #define SORT_HASH_TBL_SIZE  128
34
35     //
36     // Forward Declarations
37     //
38     PSORTHANDLE MakeSortHashNode(
39         __in LPCWSTR    pSortName,
40         __in DWORD      dwVersion);
41
42
43     PSORTHANDLE InsertSortHashNode(
44         __in PSORTHANDLE pHashN);
45
46     static PSORTHANDLE     g_pSortHash[SORT_HASH_TBL_SIZE];    // Sort node hash table
47
48     static HMODULE g_hSortDefault = (HMODULE)-1;
49
50     __encoded_pointer static SORTGETHANDLE   g_pDefaultGetHandle;
51     __encoded_pointer static SORTCLOSEHANDLE g_pDefaultCloseHandle;
52
53     static HMODULE g_hSortCompatV2 = (HMODULE)-1;
54
55     __encoded_pointer static SORTGETHANDLE   g_pV2GetHandle;
56     __encoded_pointer static SORTCLOSEHANDLE g_pV2CloseHandle;
57
58     static HMODULE g_hSortCompatV4 = (HMODULE)-1;
59
60     __encoded_pointer static SORTGETHANDLE   g_pV4GetHandle;
61     __encoded_pointer static SORTCLOSEHANDLE g_pV4CloseHandle;
62
63
64     ////////////////////////////////////////////////////////////////////////////
65     //
66     //  NlsCompareInvariantNoCase
67     //
68     //  This routine does fast caseless comparison without needing the tables.
69     //  This helps us do the comparisons we need to load the tables :-)
70     //
71     //  Returns 0 if identical, <0 if pFirst if first string sorts first.
72     //
73     //  This is only intended to help with our locale name comparisons,
74     //  which are effectively limited to A-Z, 0-9, a-z and - where A-Z and a-z
75     //  compare as equal.
76     //
77     //  WARNING: [\]^_` will be less than A-Z because we make everything lower
78     //           case before comparing them.
79     //
80     //  When bNullEnd is TRUE, both of the strings should be null-terminator to be considered equal.
81     //  When bNullEnd is FALSE, the strings are considered equal when we reach the number of characters specifed by size
82     //  or when null terminators are reached, whichever happens first (strncmp-like behavior)
83     //
84     ////////////////////////////////////////////////////////////////////////////
85     int NlsCompareInvariantNoCase(
86         LPCWSTR pFirst,
87         LPCWSTR pSecond,
88         int     size,
89         BOOL bNullEnd)
90     {
91         int i=0;
92         WCHAR first;
93         WCHAR second;
94
95         for (;
96              size > 0 && (first = *pFirst) != 0 && (second = *pSecond) != 0;
97              size--, pFirst++, pSecond++)
98         {
99             // Make them lower case
100             if ((first >= 'A') && (first <= 'Z')) first |= 0x20;
101             if ((second >= 'A') && (second <= 'Z')) second |= 0x20;
102
103             // Get the diff
104             i = (first - second);
105
106             // Are they the same?
107             if (i == 0)
108                 continue;
109
110             // Otherwise the difference.  Remember we made A-Z into lower case, so
111             // the characters [\]^_` will sort < A-Z and also < a-z.  (Those are the
112             // characters between A-Z and a-Z in ascii)
113             return i;
114         }
115
116         // When we are here, one of these holds:
117         //    size == 0
118         //    or one of the strings has a null terminator
119         //    or both of the string reaches null terminator
120
121         if (bNullEnd || size != 0)
122         {
123             // If bNullEnd is TRUE, always check for null terminator.
124             // If bNullEnd is FALSE, we still have to check if one of the strings is terminated eariler
125             // than another (hense the size != 0 check).
126
127             // See if one string ended first
128             if (*pFirst != 0 || *pSecond != 0)
129             {
130                 // Which one?
131                 return *pFirst == 0 ? -1 : 1;
132             }
133         }
134
135         // Return our difference (0)
136         return i;
137     }
138
139
140     SORTGETHANDLE GetSortGetHandle(__in DWORD dwVersion)
141     {
142         return NULL;
143     }
144
145     void DoSortCloseHandle(__in DWORD dwVersion, __in PSORTHANDLE pSort)
146     {
147     }
148
149
150     ////////////////////////////////////////////////////////////////////////////
151     //
152     //  GetSortHashValue
153     //
154     //  Returns the hash value for given sort name & version.
155     //
156     //  WARNING: This must be case insensitive.  Currently we're expecting only
157     //           a-z, A-Z, 0-9 & -.
158     //
159     ////////////////////////////////////////////////////////////////////////////
160     __inline __range(0, SORT_HASH_TBL_SIZE-1) int GetSortHashValue(
161         __in LPCWSTR    pSortName,
162         __in DWORD      dwVersion)
163     {
164         int iHash = 12; // Seed hash value
165         int iMax;       // Number of characters to count (prevent problems with too-bad strings)
166
167         // Hash the string
168         if (pSortName)
169         {
170             for (iMax = 10; *pSortName != 0 && iMax != 0; pSortName++, iMax--)
171             {
172                 iHash <<= 1;
173                 iHash ^= ((*pSortName) & 0xdf);     // 0x20 will make cases be the same (and other wierd stuff too, but we don't care about that)
174             }
175         }
176
177         // Add the version hash
178         // (the middle 2 bytes are most interesting)
179         iHash ^= dwVersion >> 8;
180
181         // Mix up our bits and hash it with 128
182         _ASSERT(SORT_HASH_TBL_SIZE == 128);
183         return (iHash + (iHash >> 8)) & 0x7f;
184     }
185
186
187     ////////////////////////////////////////////////////////////////////////////
188     //
189     //  InsertSortHashNode
190     //
191     //  Inserts a sort hash node into the global sort hash tables.  It assumes
192     //  that all unused hash values in the table are pointing to NULL.  If
193     //  there is a collision, the new node will be added LAST in the list.
194     //  (Presuming that the most often used are also the first used)
195     //
196     //  We do an interlocked exchange and free the pointer if we can't add it.
197     //
198     //  Warning: We stick stuff in this list, but we never remove it, so it
199     //           get kind of big.  Removing entries would be difficult however
200     //           because it would require some sort of synchronization with the
201     //           reader functions (like GetLocaleInfo), or maybe an in-use flag
202     //           or spin count.
203     //
204     ////////////////////////////////////////////////////////////////////////////
205     PSORTHANDLE InsertSortHashNode(PSORTHANDLE pHashN)
206     {
207         __range(0, SORT_HASH_TBL_SIZE-1) UINT         index;
208         PSORTHANDLE  pSearch;
209         PSORTHANDLE* pNextToUpdate;
210
211         //
212         // Insert the hash node into the list (by name/version)
213         //
214 #ifdef _PREFAST_
215 #pragma warning(push)
216 #pragma warning(disable: 26037) // Prefast warning - Possible precondition violation due to failure to null terminate string -  GetSortHashValue only uses first 10 characters and sortName is null terminated
217 #endif // _PREFAST_
218         index = GetSortHashValue(pHashN->sortName, pHashN->dwNLSVersion);
219 #ifdef _PREFAST_
220 #pragma warning(pop)
221 #endif
222
223         // Get hash node
224         pSearch = g_pSortHash[index];
225
226         // Remember last pointer in case we need to add it
227         pNextToUpdate = &g_pSortHash[index];
228
229         // We'll be the last node when added
230         pHashN->pNext = NULL;
231
232         while(TRUE)
233         {
234             while (pSearch != NULL)
235             {
236                 // See if we already found a node.
237                 if ((pSearch->dwNLSVersion == pHashN->dwNLSVersion) &&
238                     NlsCompareInvariantNoCase( pSearch->sortName, pHashN->sortName,
239                                                LOCALE_NAME_MAX_LENGTH, TRUE) == 0)
240                 {
241                     // Its the same, which is unexpected, return the old one
242                     return pSearch;
243                 }
244
245                 pNextToUpdate = &pSearch->pNext;
246                 pSearch = pSearch->pNext;
247             }
248
249             // At end, try to add our node
250             pSearch = InterlockedCompareExchangeT(pNextToUpdate, pHashN, NULL);
251
252             // If pNextToUpdate isn't NULL then another process snuck in and updated the list
253             // while we were getting ready.
254             if (pSearch == NULL)
255             {
256                 // It was added, stop
257                 break;
258             }
259
260             // It wasn't added, pSearch now points to a new node that snuck in, so
261             // continue and try that one.  This should be really rare, even in a busy
262             // loop, so we don't try a real lock.  Either
263             // a) the snuck in node is the same as pHashN, and we'll return pSearch
264             //    in the first loop, or
265             // b) the snuck in node is new, in which case we'll try to readd.  Very worst
266             //    case we'd collide while someone added ALL of the other locales with our
267             //    hash, but eventually we'd hit case a.  (And there's only a couple hundred
268             //    tries, so this can't lock for long.)
269         }
270
271         // Return the same one we added
272         return pHashN;
273     }
274
275
276     ////////////////////////////////////////////////////////////////////////////
277     //
278     //  FindSortHashNode
279     //
280     //  Searches for the sort hash node for the given sort name & version.
281     //  The result is returned.  If none are found NULL is returned.
282     //
283     //  NOTE: Call GetSortNode() which calls this.
284     //
285     //  Defined as inline.
286     //
287     ////////////////////////////////////////////////////////////////////////////
288
289     __inline PSORTHANDLE FindSortHashNode(
290         __in LPCWSTR    pSortName,
291         __in DWORD      dwVersion)
292     {
293         PSORTHANDLE pHashN;
294         __range(0,SORT_HASH_TBL_SIZE-1) int         index;
295
296         // Get Index
297         index = GetSortHashValue(pSortName, dwVersion);
298
299         // Get hash node
300         pHashN = g_pSortHash[index];
301
302         // Look through the list to see if one matches name and user info
303         // We're sneaky here because we know our length of our hash name string is stored
304         // just before that string.
305         while ((pHashN != NULL) &&
306                ((dwVersion != pHashN->dwNLSVersion) ||
307                 (NlsCompareInvariantNoCase(pSortName, pHashN->sortName, LOCALE_NAME_MAX_LENGTH, TRUE) != 0)))
308         {
309             pHashN = pHashN->pNext;
310         }
311
312         return pHashN;
313     }
314
315
316     ////////////////////////////////////////////////////////////////////////////
317     //
318     //  MakeSortHashNode
319     //
320     //  Builds a sort hash node and sticks it in the hash table.
321     //
322     //  NOTE: Call GetSortNode() which calls this.
323     //
324     //  Defined as inline.
325     //
326     ////////////////////////////////////////////////////////////////////////////
327     PSORTHANDLE MakeSortHashNode(
328         __in LPCWSTR    pSortName,
329         __in DWORD      dwVersion)
330     {
331         NLSVERSIONINFO  sortVersion;
332
333         PSORTHANDLE     pSort = NULL;
334         PSORTHANDLE     pSortInHash;
335
336         // Valid locale, now we need to find out where to point this version at
337         SORTGETHANDLE pGetHandle = GetSortGetHandle(dwVersion);
338         if (pGetHandle == NULL) return NULL;
339
340         sortVersion.dwNLSVersionInfoSize = sizeof(NLSVERSIONINFO);
341         sortVersion.dwNLSVersion = dwVersion;
342         sortVersion.dwDefinedVersion = dwVersion;
343
344         pSort = pGetHandle(pSortName, &sortVersion, NULL);
345
346         // If still missing, fail
347         if (pSort == NULL)
348         {
349             // Invalid sort, fail
350             return NULL;
351         }
352
353         // Now we need to add it
354         pSortInHash = InsertSortHashNode(pSort);
355
356         // If we got a different one back then free the one we added
357         if (pSortInHash != pSort && pSortInHash)
358         {
359             // We got a different one from the hash (someone beat us to the cache)
360             // so use that and discard the new one.
361             DoSortCloseHandle(dwVersion, pSort);
362         }
363
364         return pSortInHash;
365     }
366
367
368     ////////////////////////////////////////////////////////////////////////////
369     //
370     //  GetSortNode
371     //
372     //  Get a sort hash node for the specified sort name & version
373     //
374     ////////////////////////////////////////////////////////////////////////////
375     PSORTHANDLE GetSortNode(
376         __in LPCWSTR    pSortName,
377         __in DWORD      dwVersion)
378     {
379         PSORTHANDLE pSortHashN = NULL;
380
381         // WARNING: We don't bother doing the null/default/system checks
382
383         // Didn't have an obvious one, look in the hash table
384         pSortHashN = FindSortHashNode(pSortName, dwVersion);
385
386         //
387         //  If the hash node does not exist, we may need to get make one
388         //
389         if (pSortHashN == NULL)
390         {
391             //
392             //  Hash node does NOT exist, try to make it
393
394        //
395             pSortHashN = MakeSortHashNode(pSortName, dwVersion);
396         }
397
398         //
399         //  If the hash node still does not exist, we may need to fallback to default
400         //  version
401         //
402         if (pSortHashN == NULL && dwVersion != SORT_VERSION_DEFAULT)
403         {
404             return GetSortNode(pSortName, SORT_VERSION_DEFAULT);
405         }
406
407         //
408         //  Return pointer to hash node
409         //  (null if we still don't have one)
410         //
411         return pSortHashN;
412     }
413
414     ////////////////////////////////////////////////////////////////////////////
415     //
416     //  SortNLSVersion
417     //  Check for the DWORD "CompatSortNLSVersion" CLR config option.
418     //
419     // .Net 4.0 introduces sorting changes that can affect the behavior of any of the methods
420     // in CompareInfo. To mitigate against compatibility problems Applications can enable the
421     // legacy CompareInfo behavior by using the 'SortNLSVersion' configuration option
422     //
423     // There are three ways to use the configuration option:
424     //
425     // 1) Config file (MyApp.exe.config)
426     //        <?xml version ="1.0"?>
427     //        <configuration>
428     //         <runtime>
429     //          <CompatSortNLSVersion enabled="4096"/><!--0x00001000 -->
430     //         </runtime>
431     //        </configuration>
432     // 2) Environment variable
433     //        set COMPlus_CompatSortNLSVersion=4096
434     // 3) RegistryKey
435     //        [HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\.NETFramework]
436     //        "CompatSortNLSVersion"=dword:00001000
437     //
438     ////////////////////////////////////////////////////////////////////////////
439     DWORD SortNLSVersion()
440     {
441         return SORT_VERSION_DEFAULT;
442     }
443
444     ////////////////////////////////////////////////////////////////////////////
445     //
446     //  VersionValue
447     //
448     //  Get the version from a version blob, resolving to the default version
449     //  if NULL
450     //
451     ////////////////////////////////////////////////////////////////////////////
452     __inline DWORD VersionValue(__in_opt const NLSVERSIONINFO * const lpVersionInformation)
453     {
454
455         //
456         //  If the caller passed null or zero we use the default version
457         //
458         if ((lpVersionInformation == NULL) ||
459             ((lpVersionInformation->dwNLSVersion == 0) &&
460              (lpVersionInformation->dwDefinedVersion ==0))
461              )
462         {
463             return SortNLSVersion();
464         }
465
466         // TODO: Will need to review this
467         if(((lpVersionInformation->dwNLSVersion == 0) &&
468             (lpVersionInformation->dwDefinedVersion != 0 )))
469         {
470             return lpVersionInformation->dwDefinedVersion;
471         }
472
473         return lpVersionInformation->dwNLSVersion;
474     }
475
476     ////////////////////////////////////////////////////////////////////////////
477     //
478     //  SortGetSortKey
479     //
480     //  Supposed to call the dll for the appropriate version.  If the default
481     //  version isn't available call the ordinal behavior (for minwin)
482     //
483     //  Just get the sort hash node and call the worker function
484     //
485     ////////////////////////////////////////////////////////////////////////////
486     __success(return != 0) int WINAPI SortGetSortKey(
487         __in LPCWSTR pLocaleName,
488         __in DWORD dwFlags,
489         __in_ecount(cchSrc) LPCWSTR pSrc,
490         __in int cchSrc,
491         __out_bcount_opt(cbDest) LPBYTE pDest,
492         __in int cbDest,
493         __in_opt CONST NLSVERSIONINFO *lpVersionInformation,
494         __in_opt LPVOID lpReserved,
495         __in_opt LPARAM lParam
496     )
497     {
498         PSORTHANDLE pSort = GetSortNode(pLocaleName, VersionValue(lpVersionInformation));
499         return SortDllGetSortKey(pSort, dwFlags, pSrc, cchSrc, pDest, cbDest, lpReserved, lParam);
500     }
501
502     // SortDllGetSortKey handles any modification to flags
503     // necessary before the actual call to the dll
504     __success(return != 0) int WINAPI SortDllGetSortKey(
505         __in PSORTHANDLE pSort,
506         __in DWORD dwFlags,
507         __in_ecount(cchSrc) LPCWSTR pSrc,
508         __in int cchSrc,
509         __out_bcount_opt(cbDest) LPBYTE pDest,
510         __in int cbDest,
511         __in_opt LPVOID lpReserved,
512         __in_opt LPARAM lParam )
513     {
514         if (pSort == NULL)
515         {
516             SetLastError(ERROR_INVALID_PARAMETER);
517             return 0;
518         }
519
520         //
521         // Note that GetSortKey'll have the opposite behavior for the
522         // linguistic casing flag (eg: use flag for bad behavior, linguistic
523         // by default)
524         dwFlags ^= NORM_LINGUISTIC_CASING;
525
526         return pSort->pSortGetSortKey(pSort, dwFlags, pSrc, cchSrc, pDest, cbDest, lpReserved, lParam);
527     }
528
529     __success(return != 0) int SortDllGetHashCode(
530         __in PSORTHANDLE pSort,
531         __in DWORD dwFlags,
532         __in_ecount(cchSrc) LPCWSTR pSrc,
533         __in int cchSrc,
534         __in_opt LPVOID lpReserved,
535         __in_opt LPARAM lParam )
536     {
537         if (pSort == NULL)
538         {
539             SetLastError(ERROR_INVALID_PARAMETER);
540             return 0;
541         }
542         const int SortDllGetHashCodeApiIntroducedVersion = 2;
543         if(pSort->dwSHVersion < SortDllGetHashCodeApiIntroducedVersion)
544         {
545             SetLastError(ERROR_NOT_SUPPORTED);
546             return 0;
547         }
548
549         //
550         // Note that GetSortKey'll have the opposite behavior for the
551         // linguistic casing flag (eg: use flag for bad behavior, linguistic
552         // by default)
553         dwFlags ^= NORM_LINGUISTIC_CASING;
554
555         return pSort->pSortGetHashCode(pSort, dwFlags, pSrc, cchSrc, lpReserved, lParam);
556     }
557
558
559     ////////////////////////////////////////////////////////////////////////////
560     //
561     //  SortChangeCase
562     //
563     //  Supposed to call the dll for the appropriate version.  If the default
564     //  version isn't available call the ordinal behavior (for minwin)
565     //
566     //  NOTE: The linguistic casing flags are backwards (ie: set the flag to
567     //        get the non-linguistic behavior.)  If we expose this then we'll
568     //        need to publish the no-linguistic flag.
569     //
570     //  Just get the sort hash node and call the worker function
571     //
572     ////////////////////////////////////////////////////////////////////////////
573     __success(return != 0) int WINAPI SortChangeCase(
574         __in LPCWSTR pLocaleName,
575         __in DWORD dwFlags,
576         __in_ecount(cchSrc) LPCWSTR pSrc,
577         __in int cchSrc,
578         __out_ecount_opt(cchDest) LPWSTR pDest,
579         __in int cchDest,
580         __in_opt CONST NLSVERSIONINFO * lpVersionInformation,
581         __in_opt LPVOID lpReserved,
582         __in_opt LPARAM lParam)
583     {
584         PSORTHANDLE pSort = GetSortNode(pLocaleName, VersionValue(lpVersionInformation));
585         return SortDllChangeCase(pSort, dwFlags, pSrc, cchSrc, pDest, cchDest, lpReserved, lParam);
586     }
587
588     // SortDllChangeCase handles any modification to flags
589     // necessary before the actual call to the dll
590     __success(return != 0) int WINAPI SortDllChangeCase(
591         __in PSORTHANDLE pSort,
592         __in DWORD dwFlags,
593         __in_ecount(cchSrc) LPCWSTR pSrc,
594         __in int cchSrc,
595         __out_ecount_opt(cchDest) LPWSTR pDest,
596         __in int cchDest,
597         __in_opt LPVOID lpReserved,
598         __in_opt LPARAM lParam)
599     {
600         if (pSort == NULL)
601         {
602             SetLastError(ERROR_INVALID_PARAMETER);
603             return 0;
604         }
605
606         // Note that Change Case'll have the opposite behavior for the
607         // linguistic casing flag (eg: use flag for bad behavior, linguistic
608         // by default)
609         dwFlags ^= LCMAP_LINGUISTIC_CASING;
610 #ifdef _PREFAST_
611 #pragma warning(push)
612 #pragma warning(disable: 26036) // prefast - Possible postcondition violation due to failure to null terminate string
613 #endif // _PREFAST_
614         return pSort->pSortChangeCase(pSort, dwFlags, pSrc, cchSrc, pDest, cchDest, lpReserved, lParam);
615 #ifdef _PREFAST_
616 #pragma warning(pop)
617 #endif
618     }
619
620     ////////////////////////////////////////////////////////////////////////////
621     //
622     //  SortCompareString
623     //
624     //  Supposed to call the dll for the appropriate version.  If the default
625     //  version isn't available call the ordinal behavior (for minwin)
626     //
627     //  Just get the sort hash node and call the worker function
628     //
629     ////////////////////////////////////////////////////////////////////////////
630     __success(return != 0) int WINAPI SortCompareString(
631         __in LPCWSTR lpLocaleName,
632         __in DWORD dwCmpFlags,
633         __in_ecount(cchCount1) LPCWSTR lpString1,
634         __in int cchCount1,
635         __in_ecount(cchCount2) LPCWSTR lpString2,
636         __in int cchCount2,
637         __in_opt CONST NLSVERSIONINFO * lpVersionInformation,
638         __in_opt LPVOID lpReserved,
639         __in_opt LPARAM lParam)
640     {
641         PSORTHANDLE pSort = GetSortNode(lpLocaleName, VersionValue(lpVersionInformation));
642         return SortDllCompareString(pSort, dwCmpFlags, lpString1, cchCount1, lpString2, cchCount2, lpReserved, lParam);
643
644     }
645
646     // SortDllCompareString handles any modification to flags
647     // necessary before the actual call to the dll
648     __success(return != 0) int WINAPI SortDllCompareString(
649         __in PSORTHANDLE pSort,
650         __in DWORD dwCmpFlags,
651         __in_ecount(cchCount1) LPCWSTR lpString1,
652         __in int cchCount1,
653         __in_ecount(cchCount2) LPCWSTR lpString2,
654         __in int cchCount2,
655         __in_opt LPVOID lpReserved,
656         __in_opt LPARAM lParam)
657     {
658         if (pSort == NULL)
659         {
660             SetLastError(ERROR_INVALID_PARAMETER);
661             return 0;
662         }
663
664         // Note that the dll will have the opposite behavior of CompareStringEx for the
665         // linguistic casing flag (eg: use flag for bad behavior, linguistic
666         // by default) because we want new public APIs to have the "right"
667         // behavior by default
668         dwCmpFlags ^= NORM_LINGUISTIC_CASING;
669
670         return pSort->pSortCompareString(pSort, dwCmpFlags, lpString1, cchCount1, lpString2, cchCount2, lpReserved, lParam);
671     }
672
673     ////////////////////////////////////////////////////////////////////////////
674     //
675     //  SortFindString
676     //
677     //  Finds lpStringValue within lpStringSource based on the rules given
678     //  in dwFindNLSStringFlags.
679     //
680     //  Supposed to call the dll for the appropriate version.  If the default
681     //  version isn't available call the ordinal behavior (for minwin)
682     //
683     //  Just get the sort hash node and call the worker function
684     //
685     ////////////////////////////////////////////////////////////////////////////
686     __success(return != 0) int WINAPI SortFindString(
687         __in LPCWSTR lpLocaleName,
688         __in DWORD dwFindNLSStringFlags,
689         __in_ecount(cchSource) LPCWSTR lpStringSource,
690         __in int cchSource,
691         __in_ecount(cchValue) LPCWSTR lpStringValue,
692         __in int cchValue,
693         __out_opt LPINT pcchFound,
694         __in_opt CONST NLSVERSIONINFO * lpVersionInformation,
695         __in_opt LPVOID lpReserved,
696         __in_opt LPARAM lParam)
697     {
698         PSORTHANDLE pSort = GetSortNode(lpLocaleName, VersionValue(lpVersionInformation));
699         return SortDllFindString(pSort, dwFindNLSStringFlags, lpStringSource, cchSource, lpStringValue, cchValue, pcchFound, lpReserved, lParam);
700     }
701
702     // SortDllFindString handles any modification to flags
703     // necessary before the actual call to the dll
704     __success(return != 0) int WINAPI SortDllFindString(
705         __in PSORTHANDLE pSort,
706         __in DWORD dwFindNLSStringFlags,
707         __in_ecount(cchSource) LPCWSTR lpStringSource,
708         __in int cchSource,
709         __in_ecount(cchValue) LPCWSTR lpStringValue,
710         __in int cchValue,
711         __out_opt LPINT pcchFound,
712         __in_opt LPVOID lpReserved,
713         __in_opt LPARAM lParam)
714     {
715         if (pSort == NULL)
716         {
717             SetLastError(ERROR_INVALID_PARAMETER);
718             return 0;
719         }
720
721         // Note that the dll will  have the opposite behavior of FindNlsString for the
722         // linguistic casing flag (eg: use flag for bad behavior, linguistic
723         // by default) because we want new public APIs to have the "right"
724         // behavior by default
725         dwFindNLSStringFlags ^= NORM_LINGUISTIC_CASING;
726
727         int cchFound; // we need to get the length even if the caller doesn't care about it (see below)
728         int result = pSort->pSortFindString(pSort, dwFindNLSStringFlags, lpStringSource, cchSource, lpStringValue, cchValue, &cchFound, lpReserved, lParam);
729         // When searching from end with an empty pattern (either empty string or all ignored characters)
730         // a match is found (result != -1)
731         //      Currently we get a result == 0 but we are hoping this will change
732         //      with Win7 to be the length of the source string (thus pointing past-the-end)
733         // and the length of the match (cchFound) will be 0
734         // For compatibility, we need to return the index of the last character (or 0 if the source is empty)
735         if((dwFindNLSStringFlags & FIND_FROMEND) &&
736             result != -1 &&
737             cchFound == 0 &&
738             cchSource != 0)
739         {
740             result = cchSource - 1;
741         }
742
743         // if the caller cares about the length, give it to them
744         if(pcchFound != NULL)
745         {
746             *pcchFound = cchFound;
747         }
748
749         return result;
750
751     }
752
753     ////////////////////////////////////////////////////////////////////////////
754     //
755     //  SortIsDefinedString
756     //
757     //  This routine looks for code points inside a string to see if they are
758     //  defined within the NSL context. If lpVersionInformation is NULL, the
759     //  version is the current version. Same thing the dwDefinedVersion is equal
760     //  to zero.
761     //
762     //  Supposed to call the dll for the appropriate version.  If the default
763     //  version isn't available call the ordinal behavior (for minwin)
764     //
765     //  Just get the sort hash node and call the worker function
766     //
767     ////////////////////////////////////////////////////////////////////////////
768     BOOL WINAPI SortIsDefinedString(
769         __in NLS_FUNCTION     Function,
770         __in DWORD            dwFlags,
771         __in CONST NLSVERSIONINFOEX * lpVersionInformation,
772         __in_ecount(cchStr) LPCWSTR          lpString,
773         __in INT              cchStr)
774     {
775         // Get an invariant sort node
776         PSORTHANDLE pSort = GetSortNode(W(""), VersionValue((CONST NLSVERSIONINFO *)lpVersionInformation));
777         return SortDllIsDefinedString(pSort, Function, dwFlags, lpString, cchStr);
778     }
779
780     // SortDllIsDefinedString handles any modification to flags
781     // necessary before the actual call to the dll
782     BOOL WINAPI SortDllIsDefinedString(
783         __in PSORTHANDLE      pSort,
784         __in NLS_FUNCTION     Function,
785         __in DWORD            dwFlags,
786         __in_ecount(cchStr) LPCWSTR          lpString,
787         __in INT              cchStr)
788     {
789         // Fail if we couldn't find one
790         if (pSort == NULL)
791         {
792             SetLastError(ERROR_INVALID_PARAMETER);
793             return 0;
794         }
795
796         return pSort->pSortIsDefinedString(pSort, Function, dwFlags, lpString, cchStr);
797     }
798
799     BOOL SortGetNLSVersion(__in PSORTHANDLE pSort,
800                            __in NLS_FUNCTION Function,
801                            __inout NLSVERSIONINFO * lpVersionInformation )
802     {
803         lpVersionInformation->dwNLSVersion = pSort->dwNLSVersion;
804         lpVersionInformation->dwDefinedVersion = pSort->dwDefinedVersion;
805
806         return TRUE;
807     }
808
809     // Wrapper for SortGetSortKey and SortChangeCase, which are both
810     // smushed into LCMapStringEx
811     __success(return != 0) int
812         LCMapStringEx (__in LPCWSTR lpLocaleName,
813                            __in DWORD dwMapFlags,
814                            __in_ecount(cchSrc) LPCWSTR lpSrcStr,
815                            __in int cchSrc,
816                            __out_ecount_opt(cchDest) LPWSTR lpDestStr, // really this should be __out_awcount_opt(dwMapFlags & LCMAP_SORTKEY, cchDest)
817                            __in int cchDest,
818                            __in_opt CONST NLSVERSIONINFO * lpVersionInformation,
819                            __in_opt LPVOID lpReserved,
820                            __in_opt LPARAM lParam )
821     {
822         // Should be either sort key...
823         if (dwMapFlags & LCMAP_SORTKEY)
824         {
825 #ifdef _PREFAST_
826 #pragma warning(push)
827 #pragma warning(disable: 26036) // Prefast - Possible precondition violation due to failure to null terminate string lpDestStr-interpreted differently depending on flag
828 #endif // _PREFAST_
829             return SortGetSortKey(lpLocaleName,
830                                   dwMapFlags & ~(LCMAP_SORTKEY),    // Don't need sort key flag
831                                   lpSrcStr,
832                                   cchSrc,
833                                   (LPBYTE)lpDestStr,        // Sort keys are bytes not WCHARs
834                                   cchDest,                  // Sort keys are bytes not WCHARs
835                                   lpVersionInformation,
836                                   lpReserved,
837                                   lParam);
838 #ifdef _PREFAST_
839 #pragma warning(pop)
840 #endif
841         }
842
843         //
844         // Check for changing case conditions.  This may be combined with Chinese or Japanese
845         // transliteration, but not with sort key nor ignore space/symbols
846         //
847         _ASSERT(dwMapFlags & (LCMAP_TITLECASE | LCMAP_UPPERCASE | LCMAP_LOWERCASE));
848
849         //
850         // Call casing wrapper, which'll either call the correct version dll
851         // or call ordinal behavior in the minwin case
852         //
853         return SortChangeCase(lpLocaleName,
854                               dwMapFlags & ~(LCMAP_BYTEREV),
855                               lpSrcStr,
856                               cchSrc,
857                               lpDestStr,
858                               cchDest,
859                               lpVersionInformation,
860                               lpReserved,
861                               lParam);
862     }
863
864
865     ////////////////////////////////////////////////////////////////////////////
866     //
867     //  IsAvailableVersion()
868     //
869     //  Get the SortGetHandle() function for the proper dll version.
870     //
871     ////////////////////////////////////////////////////////////////////////////
872     BOOL IsAvailableVersion(__in_opt CONST NLSVERSIONINFO * pVersion)
873     {
874         return GetSortGetHandle(VersionValue(pVersion)) != NULL;
875     }
876
877
878     ////////////////////////////////////////////////////////////////////////////
879     //
880     //  GetSortHandle()
881     //
882     //  Get the SortHandle for the given locale and version
883     //
884     ////////////////////////////////////////////////////////////////////////////
885     PSORTHANDLE GetSortHandle(__in LPCWSTR lpLocaleName, __in_opt CONST NLSVERSIONINFO * pVersion)
886     {
887         DWORD version = VersionValue(pVersion);
888         if (GetSortGetHandle(version) == NULL)
889         {
890             return NULL;
891         }
892         return GetSortNode(lpLocaleName, version);
893     }
894
895 }