Leaked source code of windows server 2003
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

716 lines
20 KiB

  1. #include "cstore.hxx"
  2. const WCHAR cwszCRLF[] = L"\r\n";
  3. BOOL GetGpoIdFromClassStorePath(
  4. WCHAR* wszClassStorePath,
  5. GUID* pGpoId)
  6. {
  7. WCHAR* wszGuidStart;
  8. WCHAR wszGpoId[MAX_GUIDSTR_LEN + 1];
  9. wszGuidStart = wcschr(wszClassStorePath, L'{');
  10. if (!wszGuidStart)
  11. {
  12. return FALSE;
  13. }
  14. wcsncpy(wszGpoId, wszGuidStart, MAX_GUIDSTR_LEN);
  15. wszGpoId[MAX_GUIDSTR_LEN] = L'\0';
  16. StringToGuid(wszGpoId, pGpoId);
  17. return TRUE;
  18. }
  19. HRESULT GetUserSid(PSID *ppUserSid, UINT *pCallType);
  20. // set property routines do not do any allocations.
  21. // get properties have 2 different sets of routines
  22. // 1. in which there is no allocation taking place
  23. // and the buffers are freed when the ds data structures
  24. // are freed.
  25. // 2. Allocation takes place and these should be used for
  26. // data that needs to be returned back to the clients.
  27. void FreeAttr(ADS_ATTR_INFO attr)
  28. {
  29. CsMemFree(attr.pADsValues);
  30. }
  31. // Note: None of these APIs copies anything into their own buffers.
  32. // It allocates a buffer for adsvalues though.
  33. // packing a property's value into a attribute structure
  34. // for sending in with a create/modify.
  35. void PackStrArrToAttr(ADS_ATTR_INFO *attr, WCHAR *szProperty,
  36. WCHAR **pszAttr, DWORD num)
  37. {
  38. DWORD i;
  39. attr->pszAttrName = szProperty;
  40. attr->dwNumValues = num;
  41. if (num)
  42. attr->dwControlCode = ADS_ATTR_UPDATE;
  43. else
  44. attr->dwControlCode = ADS_ATTR_CLEAR;
  45. attr->dwADsType = ADSTYPE_DN_STRING;
  46. attr->pADsValues = (ADSVALUE *)CsMemAlloc(sizeof(ADSVALUE)*num);
  47. if (!(attr->pADsValues))
  48. return;
  49. for (i = 0; i < num; i++) {
  50. attr->pADsValues[i].dwType = ADSTYPE_DN_STRING;
  51. attr->pADsValues[i].DNString = pszAttr[i];
  52. }
  53. }
  54. void PackDWArrToAttr(ADS_ATTR_INFO *attr, WCHAR *szProperty, DWORD *pAttr, DWORD num)
  55. {
  56. DWORD i;
  57. attr->pszAttrName = szProperty;
  58. attr->dwNumValues = num;
  59. if (num)
  60. attr->dwControlCode = ADS_ATTR_UPDATE;
  61. else
  62. attr->dwControlCode = ADS_ATTR_CLEAR;
  63. attr->dwADsType = ADSTYPE_INTEGER;
  64. attr->pADsValues = (ADSVALUE *)CsMemAlloc(sizeof(ADSVALUE)*num);
  65. if (!(attr->pADsValues))
  66. return;
  67. for (i = 0; i < num; i++) {
  68. attr->pADsValues[i].dwType = ADSTYPE_INTEGER;
  69. attr->pADsValues[i].Integer = pAttr[i];
  70. }
  71. }
  72. void PackGUIDArrToAttr(ADS_ATTR_INFO *attr, WCHAR *szProperty, GUID *pAttr, DWORD num)
  73. {
  74. DWORD i;
  75. attr->pszAttrName = szProperty;
  76. attr->dwNumValues = num;
  77. if (num)
  78. attr->dwControlCode = ADS_ATTR_UPDATE;
  79. else
  80. attr->dwControlCode = ADS_ATTR_CLEAR;
  81. attr->dwADsType = ADSTYPE_OCTET_STRING;
  82. attr->pADsValues = (ADSVALUE *)CsMemAlloc(sizeof(ADSVALUE)*num);
  83. if (!(attr->pADsValues))
  84. return;
  85. for (i = 0; i < num; i++) {
  86. attr->pADsValues[i].dwType = ADSTYPE_OCTET_STRING;
  87. attr->pADsValues[i].OctetString.dwLength = sizeof(GUID);
  88. attr->pADsValues[i].OctetString.lpValue = (unsigned char *)(pAttr+i);
  89. }
  90. }
  91. void PackBinToAttr(ADS_ATTR_INFO *attr, WCHAR *szProperty, BYTE *pAttr, DWORD sz)
  92. {
  93. attr->pszAttrName = szProperty;
  94. attr->dwNumValues = 1;
  95. attr->dwControlCode = ADS_ATTR_UPDATE;
  96. attr->dwADsType = ADSTYPE_OCTET_STRING;
  97. attr->pADsValues = (ADSVALUE *)CsMemAlloc(sizeof(ADSVALUE));
  98. if (!(attr->pADsValues))
  99. return;
  100. attr->pADsValues[0].dwType = ADSTYPE_OCTET_STRING;
  101. attr->pADsValues[0].OctetString.dwLength = sz;
  102. attr->pADsValues[0].OctetString.lpValue = pAttr;
  103. }
  104. void PackStrToAttr(ADS_ATTR_INFO *attr, WCHAR *szProperty, WCHAR *szAttr)
  105. {
  106. if (szAttr)
  107. PackStrArrToAttr(attr, szProperty, &szAttr, 1);
  108. else
  109. PackStrArrToAttr(attr, szProperty, &szAttr, 0);
  110. }
  111. void PackDWToAttr(ADS_ATTR_INFO *attr, WCHAR *szProperty, DWORD Attr)
  112. {
  113. PackDWArrToAttr(attr, szProperty, &Attr, 1);
  114. }
  115. // passing in a pointer to GUID which is passed down into the LDAP structure.
  116. void PackGUIDToAttr(ADS_ATTR_INFO *attr, WCHAR *szProperty, GUID *pAttr)
  117. {
  118. PackGUIDArrToAttr(attr, szProperty, pAttr, 1);
  119. }
  120. // returns the attribute corresp. to a given property.
  121. DWORD GetPropertyFromAttr(ADS_ATTR_INFO *pattr, DWORD cNum, WCHAR *szProperty)
  122. {
  123. DWORD i;
  124. for (i = 0; i < cNum; i++)
  125. if (_wcsicmp(pattr[i].pszAttrName, szProperty) == 0)
  126. break;
  127. return i;
  128. }
  129. HRESULT GetCategoryLocaleDesc(LPOLESTR *pdesc, ULONG cdesc, LCID *plcid,
  130. LPOLESTR szDescription, ULONG ulSize)
  131. {
  132. LCID plgid;
  133. LPOLESTR ptr;
  134. szDescription[0] = L'\0';
  135. if (!cdesc)
  136. return E_FAIL; // CAT_E_NODESCRIPTION;
  137. // Try locale passed in
  138. if (FindDescription(pdesc, cdesc, plcid, szDescription, 0))
  139. return S_OK;
  140. // Get default sublang local
  141. plgid = PRIMARYLANGID((WORD)*plcid);
  142. *plcid = MAKELCID(MAKELANGID(plgid, SUBLANG_DEFAULT), SORT_DEFAULT);
  143. if (FindDescription(pdesc, cdesc, plcid, szDescription, 0))
  144. return S_OK;
  145. // Get matching lang id
  146. if (FindDescription(pdesc, cdesc, plcid, szDescription, 1))
  147. return S_OK;
  148. // Get User Default
  149. *plcid = GetUserDefaultLCID();
  150. if (FindDescription(pdesc, cdesc, plcid, szDescription, 0))
  151. return S_OK;
  152. // Get System Default
  153. *plcid = GetUserDefaultLCID();
  154. if (FindDescription(pdesc, cdesc, plcid, szDescription, 0))
  155. return S_OK;
  156. // Get the first one
  157. *plcid = wcstoul(pdesc[0], &ptr, 16);
  158. if (szDescription)
  159. {
  160. if ((ptr) && (wcslen(ptr) >= (CAT_DESC_DELIM_LEN+2)))
  161. {
  162. HRESULT hr;
  163. hr = StringCchCopy(szDescription, ulSize, (ptr+CAT_DESC_DELIM_LEN+2));
  164. if (FAILED(hr))
  165. {
  166. return hr;
  167. }
  168. }
  169. else
  170. szDescription = L'\0';
  171. }
  172. return S_OK;
  173. }
  174. //-------------------------------------------
  175. // Returns the description corresp. to a LCID
  176. // desc: list of descs+lcid
  177. // cdesc: number of elements.
  178. // plcid: the lcid in/out
  179. // szDescription:description returned.
  180. // GetPrimary: Match only the primary.
  181. //---------------------------------------
  182. ULONG FindDescription(LPOLESTR *desc, ULONG cdesc, LCID *plcid, LPOLESTR szDescription, BOOL GetPrimary)
  183. {
  184. ULONG i;
  185. LCID newlcid;
  186. LPOLESTR ptr;
  187. for (i = 0; i < cdesc; i++)
  188. {
  189. newlcid = wcstoul(desc[i], &ptr, 16); // to be changed
  190. // error to be checked.
  191. if ((newlcid == *plcid) || ((GetPrimary) &&
  192. (PRIMARYLANGID((WORD)*plcid) == PRIMARYLANGID(LANGIDFROMLCID(newlcid)))))
  193. {
  194. if (szDescription)
  195. {
  196. if ((ptr) && (wcslen(ptr) >= (CAT_DESC_DELIM_LEN+2)))
  197. {
  198. //
  199. // Copy the description, enforcing the maximum size
  200. // so we don't overflow the buffer
  201. //
  202. wcsncpy(szDescription,
  203. (ptr+CAT_DESC_DELIM_LEN+2),
  204. CAT_DESC_MAX_LEN + 1
  205. );
  206. //
  207. // We must null terminate in case the category
  208. // was longer than the maximum. We know the buffer
  209. // is equal in size to the maximum, so we can
  210. // just add the terminator there. In all other cases,
  211. // wcsncpy will have written the null terminator
  212. //
  213. szDescription[CAT_DESC_MAX_LEN] = L'\0';
  214. }
  215. else
  216. szDescription = L'\0';
  217. }
  218. if (GetPrimary)
  219. *plcid = newlcid;
  220. return i+1;
  221. }
  222. }
  223. return 0;
  224. }
  225. DWORD NumDigits10(DWORD Value)
  226. {
  227. if (0 == Value) {
  228. return 1;
  229. }
  230. DWORD ret = 0;
  231. for (ret = 0; Value != 0; ret++)
  232. Value = Value/10;
  233. return ret;
  234. }
  235. void ReportEventCS(HRESULT ErrorCode, HRESULT ExtendedErrorCode, LPOLESTR szContainerName)
  236. {
  237. WCHAR szErrCode[16];
  238. HRESULT hr;
  239. hr = StringCchPrintf(szErrCode, 16, L"0x%x", ExtendedErrorCode);
  240. ASSERT(SUCCEEDED(hr));
  241. CEventsBase* pEvents = (CEventsBase*) gpEvents;
  242. ASSERT(CS_E_NETWORK_ERROR == ErrorCode);
  243. pEvents->Report(
  244. EVENT_CS_NETWORK_ERROR,
  245. FALSE,
  246. 2,
  247. szContainerName,
  248. szErrCode);
  249. }
  250. // remapping Error Codes returned by LDAP to reasonable class store errors.
  251. //
  252. HRESULT RemapErrorCode(HRESULT ErrorCode, LPOLESTR m_szContainerName)
  253. {
  254. HRESULT RetCode;
  255. BOOL fNetError;
  256. fNetError = FALSE;
  257. if (SUCCEEDED(ErrorCode))
  258. return S_OK;
  259. switch (ErrorCode)
  260. {
  261. //
  262. // All kinds of failures due to ObjectNotFound
  263. // due to non-existence of object OR
  264. // non-existent container OR
  265. // invalid path specification
  266. // Other than Access Denials
  267. //
  268. case HRESULT_FROM_WIN32(ERROR_DS_NO_SUCH_OBJECT):
  269. case HRESULT_FROM_WIN32(ERROR_DS_NO_RESULTS_RETURNED): // understand what causes this
  270. case HRESULT_FROM_WIN32(ERROR_DS_NAME_ERROR_NOT_FOUND): // -do-
  271. RetCode = CS_E_OBJECT_NOTFOUND; // which object - specific error
  272. break;
  273. case HRESULT_FROM_WIN32(ERROR_ALREADY_EXISTS):
  274. case HRESULT_FROM_WIN32(ERROR_OBJECT_ALREADY_EXISTS):
  275. case E_ADS_OBJECT_EXISTS:
  276. RetCode = CS_E_OBJECT_ALREADY_EXISTS;
  277. break;
  278. //
  279. // The following errors should not be expected normally.
  280. // Class Store schema mismatched should be handled correctly.
  281. // Errors below may ONLY occur for corrupted data OR out-of-band changes
  282. // to a Class Store content.
  283. case E_ADS_CANT_CONVERT_DATATYPE:
  284. case E_ADS_SCHEMA_VIOLATION:
  285. case HRESULT_FROM_WIN32(ERROR_DS_NO_ATTRIBUTE_OR_VALUE):
  286. case HRESULT_FROM_WIN32(ERROR_DS_CONSTRAINT_VIOLATION):
  287. RetCode = CS_E_SCHEMA_MISMATCH;
  288. break;
  289. //
  290. // Any kinds of Access or Auth Denial
  291. // return ACCESS_DENIED
  292. //
  293. case HRESULT_FROM_WIN32(ERROR_DS_AUTH_METHOD_NOT_SUPPORTED):
  294. case HRESULT_FROM_WIN32(ERROR_DS_STRONG_AUTH_REQUIRED):
  295. case HRESULT_FROM_WIN32(ERROR_DS_CONFIDENTIALITY_REQUIRED):
  296. case HRESULT_FROM_WIN32(ERROR_INVALID_PASSWORD):
  297. case HRESULT_FROM_WIN32(ERROR_ACCESS_DENIED):
  298. case HRESULT_FROM_WIN32(ERROR_DS_AUTH_UNKNOWN):
  299. RetCode = HRESULT_FROM_WIN32(ERROR_ACCESS_DENIED);
  300. break;
  301. case E_ADS_BAD_PATHNAME:
  302. case HRESULT_FROM_WIN32(ERROR_DS_INVALID_ATTRIBUTE_SYNTAX): // this is wrong
  303. RetCode = CS_E_INVALID_PATH;
  304. break;
  305. //
  306. // Out of Memory
  307. //
  308. case HRESULT_FROM_WIN32(ERROR_NOT_ENOUGH_MEMORY):
  309. case HRESULT_FROM_WIN32(ERROR_OUTOFMEMORY):
  310. RetCode = E_OUTOFMEMORY;
  311. break;
  312. case HRESULT_FROM_WIN32(ERROR_DS_NAME_ERROR_RESOLVING):
  313. case HRESULT_FROM_WIN32(ERROR_DS_NAME_ERROR_NOT_UNIQUE):
  314. case HRESULT_FROM_WIN32(ERROR_DS_NAME_ERROR_NO_MAPPING):
  315. case HRESULT_FROM_WIN32(ERROR_DS_NAME_ERROR_DOMAIN_ONLY):
  316. case HRESULT_FROM_WIN32(ERROR_DS_TIMELIMIT_EXCEEDED):
  317. case HRESULT_FROM_WIN32(ERROR_DS_BUSY):
  318. case HRESULT_FROM_WIN32(ERROR_DS_UNAVAILABLE):
  319. case HRESULT_FROM_WIN32(ERROR_DS_UNWILLING_TO_PERFORM):
  320. case HRESULT_FROM_WIN32(ERROR_TIMEOUT):
  321. case HRESULT_FROM_WIN32(ERROR_CONNECTION_REFUSED):
  322. case HRESULT_FROM_WIN32(ERROR_DS_SERVER_DOWN):
  323. case HRESULT_FROM_WIN32(ERROR_NO_SUCH_DOMAIN):
  324. RetCode = ErrorCode;
  325. fNetError = TRUE;
  326. break;
  327. case HRESULT_FROM_WIN32(ERROR_DS_ADMIN_LIMIT_EXCEEDED):
  328. RetCode = CS_E_ADMIN_LIMIT_EXCEEDED;
  329. break;
  330. default:
  331. RetCode = ErrorCode;
  332. }
  333. CSDBGPrint((DM_WARNING,
  334. IDS_CSTORE_REMAP_ERR,
  335. ErrorCode,
  336. RetCode));
  337. if (RetCode == CS_E_NETWORK_ERROR)
  338. {
  339. ReportEventCS(RetCode, ErrorCode, m_szContainerName);
  340. }
  341. return RetCode;
  342. }
  343. // These functions are used to delete a single value from a
  344. // multivalued property or append to a multivalued property
  345. void PackStrArrToAttrEx(ADS_ATTR_INFO *attr, WCHAR *szProperty, WCHAR **pszAttr, DWORD num,
  346. BOOL APPEND)
  347. {
  348. DWORD i;
  349. attr->pszAttrName = szProperty;
  350. attr->dwNumValues = num;
  351. if (APPEND)
  352. attr->dwControlCode = ADS_ATTR_APPEND;
  353. else
  354. attr->dwControlCode = ADS_ATTR_DELETE;
  355. attr->dwADsType = ADSTYPE_DN_STRING;
  356. attr->pADsValues = (ADSVALUE *)CsMemAlloc(sizeof(ADSVALUE)*num);
  357. if (!(attr->pADsValues))
  358. return;
  359. for (i = 0; i < num; i++) {
  360. attr->pADsValues[i].dwType = ADSTYPE_DN_STRING;
  361. attr->pADsValues[i].DNString = pszAttr[i];
  362. }
  363. }
  364. void PackDWArrToAttrEx(ADS_ATTR_INFO *attr, WCHAR *szProperty, DWORD *pAttr, DWORD num,
  365. BOOL APPEND)
  366. {
  367. DWORD i;
  368. attr->pszAttrName = szProperty;
  369. attr->dwNumValues = num;
  370. if (APPEND)
  371. attr->dwControlCode = ADS_ATTR_APPEND;
  372. else
  373. attr->dwControlCode = ADS_ATTR_DELETE;
  374. attr->dwADsType = ADSTYPE_INTEGER;
  375. attr->pADsValues = (ADSVALUE *)CsMemAlloc(sizeof(ADSVALUE)*num);
  376. if (!(attr->pADsValues))
  377. return;
  378. for (i = 0; i < num; i++) {
  379. attr->pADsValues[i].dwType = ADSTYPE_INTEGER;
  380. attr->pADsValues[i].Integer = pAttr[i];
  381. }
  382. }
  383. void PackGUIDArrToAttrEx(ADS_ATTR_INFO *attr, WCHAR *szProperty, GUID *pAttr, DWORD num,
  384. BOOL APPEND)
  385. {
  386. DWORD i;
  387. attr->pszAttrName = szProperty;
  388. attr->dwNumValues = num;
  389. if (APPEND)
  390. attr->dwControlCode = ADS_ATTR_APPEND;
  391. else
  392. attr->dwControlCode = ADS_ATTR_DELETE;
  393. attr->dwADsType = ADSTYPE_OCTET_STRING;
  394. attr->pADsValues = (ADSVALUE *)CsMemAlloc(sizeof(ADSVALUE)*num);
  395. if (!(attr->pADsValues))
  396. return;
  397. for (i = 0; i < num; i++) {
  398. attr->pADsValues[i].dwType = ADSTYPE_OCTET_STRING;
  399. attr->pADsValues[i].OctetString.dwLength = sizeof(GUID);
  400. attr->pADsValues[i].OctetString.lpValue = (unsigned char *)(pAttr+i);
  401. }
  402. }
  403. WCHAR* AllocGpoPathFromClassStorePath( WCHAR* pszClassStorePath )
  404. {
  405. //
  406. // The class store path looks like CN=ClassStore,CN=[Machine | User],<gpopath>
  407. // So we will simply look for two "," starting at the beginning of the path.
  408. // In doing this, we will take care not to access violate due to an incorrect path,
  409. // Since there is the possiblity that an administrator could find the persisted
  410. // class store path and mangle it, giving us an incorrect path that would
  411. // not parse according to the structure above.
  412. //
  413. WCHAR* wszGpoPath;
  414. //
  415. // Check for the first ','
  416. //
  417. wszGpoPath = wcschr(pszClassStorePath, L',');
  418. //
  419. // If we get NULL here, that means there is no ',' and the path is corrupt
  420. //
  421. if ( ! wszGpoPath )
  422. {
  423. return NULL;
  424. }
  425. //
  426. // Now check for the second ','
  427. //
  428. wszGpoPath = wcschr(wszGpoPath + 1, L',');
  429. //
  430. // Again, if we don't find the second ',',
  431. // The path is corrupt
  432. //
  433. if ( ! wszGpoPath )
  434. {
  435. return NULL;
  436. }
  437. //
  438. // Now move one past
  439. //
  440. wszGpoPath++;
  441. //
  442. // The caller desires their own copy, so we will allocate space for it
  443. //
  444. WCHAR* wszGpoPathResult;
  445. ULONG ulNoBytes;
  446. ulNoBytes = (wcslen( wszGpoPath ) + 1) * sizeof( *wszGpoPathResult );
  447. wszGpoPathResult = (WCHAR*) CsMemAlloc(ulNoBytes);
  448. if ( ! wszGpoPathResult )
  449. {
  450. return NULL;
  451. }
  452. //
  453. // Now copy the gpo path
  454. //
  455. HRESULT hr;
  456. hr = StringCbCopy( wszGpoPathResult, ulNoBytes, wszGpoPath );
  457. if (FAILED(hr))
  458. {
  459. CsMemFree(wszGpoPathResult);
  460. wszGpoPathResult = NULL;
  461. }
  462. return wszGpoPathResult;
  463. }
  464. HRESULT
  465. GetEscapedNameFilter( WCHAR* wszName, WCHAR** ppwszEscapedName )
  466. {
  467. DWORD cbLen;
  468. WCHAR* wszCurrent;
  469. HRESULT hr;
  470. ULONG ulCurSizeLeft;
  471. //
  472. // This function escapes package names so that they can be used in
  473. // an ldap search filter. Names containing certain characters must
  474. // be escaped since those characters are contained in the vocabulary
  475. // for the search filter grammar.
  476. //
  477. //
  478. // The set of characters that must be escaped and the appropriate
  479. // escape sequences are described in RFC 2254
  480. //
  481. //
  482. // Determine the maximum size needed for the filter. We include for 3
  483. // times the length of the name since that is the upper bound on the
  484. // length of the escaped name
  485. //
  486. //
  487. // "(PackageAttr=\0" + "<*ppwszEscapedName>" + ")"
  488. //
  489. cbLen = sizeof( L"(" PACKAGENAME L"=" ) + ( lstrlen( wszName ) + 1 ) * sizeof( *wszName ) * 3;
  490. *ppwszEscapedName = (WCHAR*) CsMemAlloc( cbLen );
  491. if ( ! *ppwszEscapedName )
  492. {
  493. return E_OUTOFMEMORY;
  494. }
  495. //
  496. // Add in the LHS of the filter expression
  497. //
  498. hr = StringCbCopy( *ppwszEscapedName, cbLen, L"(" PACKAGENAME L"=" );
  499. if (FAILED(hr))
  500. {
  501. CsMemFree(*ppwszEscapedName);
  502. *ppwszEscapedName = NULL;
  503. return hr;
  504. }
  505. //
  506. // We will escape the name -- move past the end of the filter expression LHS
  507. //
  508. wszCurrent = *ppwszEscapedName + ( sizeof(L"(" PACKAGENAME L"=") - 1 ) / sizeof( WCHAR );
  509. ulCurSizeLeft = (cbLen - (sizeof(L"(" PACKAGENAME L"=") - 1)) / sizeof(WCHAR);
  510. //
  511. // For each character that needs to be escaped, we will append that character's
  512. // escape sequence to the string. For characters that do not need to be escaped,
  513. // we simply simply append the character as-is (i.e. unescaped).
  514. //
  515. for ( ; *wszName; wszName++ )
  516. {
  517. WCHAR* EscapedChar;
  518. //
  519. // Detect characters that need to be escaped and
  520. // map each to its escape sequence
  521. //
  522. switch ( *wszName )
  523. {
  524. case L'*':
  525. EscapedChar = L"\\2a";
  526. break;
  527. case L'(':
  528. EscapedChar = L"\\28";
  529. break;
  530. case L')':
  531. EscapedChar = L"\\29";
  532. break;
  533. case L'\\':
  534. EscapedChar = L"\\5c";
  535. break;
  536. default:
  537. //
  538. // This character does not need to be escaped, just append it
  539. //
  540. *wszCurrent = *wszName;
  541. wszCurrent ++;
  542. ulCurSizeLeft--;
  543. continue;
  544. break;
  545. }
  546. //
  547. // We only get here if the character needed to be escaped --
  548. // we append the string for the escape sequence to the filter string, and
  549. // move our filter string location to the new end of string
  550. //
  551. hr = StringCchCopy( wszCurrent, ulCurSizeLeft, EscapedChar );
  552. wszCurrent += 3;
  553. ulCurSizeLeft-=3;
  554. }
  555. //
  556. // We need to add the closing parenthesis to the filter expression
  557. //
  558. *wszCurrent++ = L')';
  559. *wszCurrent = L'\0';
  560. return S_OK;
  561. }
  562.