Source code of Windows XP (NT5)
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.

884 lines
25 KiB

  1. /*++
  2. Copyright (c) 1989-91 Microsoft Corporation
  3. Module Name:
  4. listfunc.c
  5. Abstract:
  6. This module contains functions which canonicalize and traverse lists.
  7. The following functions are defined:
  8. NetpwListCanonicalize
  9. NetpwListTraverse
  10. (FixupAPIListElement)
  11. Author:
  12. Danny Glasser (dannygl) 14 June 1989
  13. Notes:
  14. There are currently four types of lists supported by these
  15. functions:
  16. UI/Service input list - Leading and trailing delimiters
  17. are allowed, multiple delimiters are allowed between
  18. elements, and the full set of delimiter characters is
  19. allowed (space, tab, comma, and semicolon). Note that
  20. elements which contain a delimiter character must be
  21. quoted. Unless explicitly specified otherwise, all UIs
  22. and services must accept all input lists in this format.
  23. API list - Leading and trailing delimiters are not allowed,
  24. multiple delimiters are not allowed between elements,
  25. and there is only one delimiter character (space). Elements
  26. which contain a delimiter character must be quoted.
  27. Unless explicitly specified otherwise, all lists provided
  28. as input to API functions must be in this format, and all
  29. lists generated as output by API functions will be in this
  30. format.
  31. Search-path list - The same format as an API list, except that
  32. the delimiter is a semicolon. This list is designed as
  33. input to the DosSearchPath API.
  34. Null-null list - Each element is terminated by a null
  35. byte and the list is terminated by a null string
  36. (that is, a null byte immediately following the null
  37. byte which terminates the last element). Clearly,
  38. multiple, leading, and trailing delimiters are not
  39. supported. Elements do not need to be quoted. An empty
  40. null-null list is simply a null string. This list format
  41. is designed for internal use.
  42. NetpListCanonicalize() accepts all UI/Service, API, and null-null
  43. lists on input and produces API, search-path, and null-null lists
  44. on output. NetpListTraverse() supports null-null lists only.
  45. Revision History:
  46. --*/
  47. #include "nticanon.h"
  48. //
  49. // prototypes
  50. //
  51. STATIC
  52. DWORD
  53. FixupAPIListElement(
  54. IN LPTSTR Element,
  55. IN LPTSTR* pElementTerm,
  56. IN LPTSTR BufferEnd,
  57. IN TCHAR cDelimiter
  58. );
  59. //
  60. // routines
  61. //
  62. NET_API_STATUS
  63. NetpwListCanonicalize(
  64. IN LPTSTR List,
  65. IN LPTSTR Delimiters OPTIONAL,
  66. OUT LPTSTR Outbuf,
  67. IN DWORD OutbufLen,
  68. OUT LPDWORD OutCount,
  69. OUT LPDWORD PathTypes,
  70. IN DWORD PathTypesLen,
  71. IN DWORD Flags
  72. )
  73. /*++
  74. Routine Description:
  75. NetpListCanonicalize produces the specified canonical version of
  76. the list, validating and/or canonicalizing the individual list
  77. elements as specified by <Flags>.
  78. Arguments:
  79. List - The list to canonicalize.
  80. Delimiters - A string of valid delimiters for the input list.
  81. A null pointer or null string indicates that the
  82. input list is in null-null format.
  83. Outbuf - The place to store the canonicalized version of the list.
  84. OutbufLen - The size, in bytes, of <Outbuf>.
  85. OutCount - The place to store the number of elements in the
  86. canonicalized list.
  87. PathTypes - The array in which to store the types of each of the paths
  88. in the canonicalized list. This parameter is only used if
  89. the NAMETYPE portion of the flags parameter is set to
  90. NAMETYPE_PATH.
  91. PathTypesLen- The number of elements in <pflPathTypes>.
  92. Flags - Flags to determine operation. Currently defined values are:
  93. rrrrrrrrrrrrrrrrrrrrmcootttttttt
  94. where:
  95. r = Reserved. MBZ.
  96. m = If set, multiple, leading, and trailing delimiters are
  97. allowed in the input list.
  98. c = If set, each of the individual list elements are
  99. validated and canonicalized. If not set, each of the
  100. individual list elements are validated only. This
  101. bit is ignored if the NAMETYPE portion of the flags is
  102. set to NAMETYPE_COPYONLY.
  103. o = Type of output list. Currently defined types are
  104. API, search-path, and null-null.
  105. t = The type of the objects in the list, for use in
  106. canonicalization or validation. If this value is
  107. NAMETYPE_COPYONLY, type is irrelevant; a canonical list
  108. is generated but no interpretation of the list elements
  109. is done. If this value is NAMETYPE_PATH, the list
  110. elements are assumed to be pathnames; NetpPathType is
  111. run on each element, the results are stored in
  112. <pflPathTypes>, and NetpPathCanonicalize is run on
  113. each element (if appropriate). Any other values for
  114. this is considered to be the type of the list elements
  115. and is passed to NetpName{Validate,Canonicalize} as
  116. appropriate.
  117. Manifest values for these flags are defined in NET\H\ICANON.H.
  118. Return Value:
  119. 0 if successful.
  120. The error number (> 0) if unsuccessful.
  121. Possible error returns include:
  122. ERROR_INVALID_PARAMETER
  123. NERR_TooManyEntries
  124. NERR_BufTooSmall
  125. --*/
  126. {
  127. NET_API_STATUS rc = 0;
  128. BOOL NullInputDelimiter;
  129. LPTSTR next_string;
  130. DWORD len;
  131. DWORD list_len = 0; // cumulative input buffer length
  132. LPTSTR Input;
  133. LPTSTR OutPtr;
  134. LPTSTR OutbufEnd;
  135. DWORD OutElementCount;
  136. DWORD DelimiterLen;
  137. LPTSTR NextQuote;
  138. LPTSTR ElementBegin;
  139. LPTSTR ElementEnd;
  140. TCHAR cElementEndBackup;
  141. LPTSTR OutElementEnd;
  142. BOOL DelimiterInElement;
  143. DWORD OutListType;
  144. TCHAR OutListDelimiter;
  145. #ifdef CANONDBG
  146. DbgPrint("NetpwListCanonicalize\n");
  147. #endif
  148. if (Flags & INLC_FLAGS_MASK_RESERVED) {
  149. return ERROR_INVALID_PARAMETER;
  150. }
  151. //
  152. // Determine if our input list is in null-null format. We do
  153. // this first because we need to use it to do proper GP-fault probing
  154. // on <List>.
  155. //
  156. NullInputDelimiter = !ARGUMENT_PRESENT(Delimiters) || (*Delimiters == TCHAR_EOS);
  157. //
  158. // Validate address parameters (i.e. GP-fault tests) and accumulate string
  159. // lengths (accumulate: now there's a word from a past I'd rather forget)
  160. //
  161. list_len = STRLEN(List) + 1;
  162. if (NullInputDelimiter) {
  163. //
  164. // This is a null-null list; stop when we find a null string.
  165. //
  166. next_string = List + list_len;
  167. do {
  168. //
  169. // Q: Is the compiler smart enough to do the right thing with
  170. // these +1s?
  171. //
  172. len = STRLEN(next_string);
  173. list_len += len + 1;
  174. next_string += len + 1;
  175. } while (len);
  176. }
  177. if (ARGUMENT_PRESENT(Delimiters)) {
  178. STRLEN(Delimiters);
  179. }
  180. if ((Flags & INLC_FLAGS_MASK_NAMETYPE) == NAMETYPE_PATH && PathTypesLen > 0) {
  181. PathTypes[0] = PathTypes[PathTypesLen - 1] = 0;
  182. }
  183. *OutCount = 0;
  184. //
  185. // Initialize variables
  186. //
  187. Input = List;
  188. OutPtr = Outbuf;
  189. OutbufEnd = Outbuf + OutbufLen;
  190. OutElementCount = 0;
  191. NullInputDelimiter = !ARGUMENT_PRESENT(Delimiters) || (*Delimiters == TCHAR_EOS);
  192. OutListType = Flags & INLC_FLAGS_MASK_OUTLIST_TYPE;
  193. //
  194. // Skip leading delimiters
  195. //
  196. // NOTE: We don't have to both to do this for a null-null list,
  197. // because if it has a leading delimiter then it's an
  198. // empty list.
  199. //
  200. if (!NullInputDelimiter) {
  201. DelimiterLen = STRSPN(Input, Delimiters);
  202. if (DelimiterLen > 0 && !(Flags & INLC_FLAGS_MULTIPLE_DELIMITERS)) {
  203. return ERROR_INVALID_PARAMETER;
  204. }
  205. Input += DelimiterLen;
  206. }
  207. //
  208. // We validate the output list type here are store the delimiter
  209. // character.
  210. //
  211. // NOTE: Later on, we rely on the fact that the delimiter character
  212. // is not zero if the output list is either API or search-path.
  213. //
  214. if (OutListType == OUTLIST_TYPE_API) {
  215. OutListDelimiter = LIST_DELIMITER_CHAR_API;
  216. } else if (OutListType == OUTLIST_TYPE_SEARCHPATH) {
  217. OutListDelimiter = LIST_DELIMITER_CHAR_SEARCHPATH;
  218. } else if (OutListType == OUTLIST_TYPE_NULL_NULL) {
  219. OutListDelimiter = TCHAR_EOS;
  220. } else {
  221. return ERROR_INVALID_PARAMETER;
  222. }
  223. //
  224. // Loop until we've reached the end of the input list
  225. // OR until we encounter an error
  226. //
  227. while (*Input != TCHAR_EOS) {
  228. //
  229. // Find the beginning and ending characters of the list element
  230. //
  231. //
  232. // Handle quoted strings separately
  233. //
  234. if (!NullInputDelimiter && *Input == LIST_QUOTE_CHAR) {
  235. //
  236. // Find the next quote; return an error if there is none
  237. // or if it's the next character.
  238. //
  239. NextQuote = STRCHR(Input + 1, LIST_QUOTE_CHAR);
  240. if (NextQuote == NULL || NextQuote == Input + 1) {
  241. return ERROR_INVALID_PARAMETER;
  242. }
  243. ElementBegin = Input + 1;
  244. ElementEnd = NextQuote;
  245. } else {
  246. ElementBegin = Input;
  247. ElementEnd = Input
  248. + (NullInputDelimiter
  249. ? STRLEN(Input)
  250. : STRCSPN(Input, Delimiters)
  251. );
  252. }
  253. //
  254. // Set the end character to null so that we can treat the list
  255. // element as a string, saving its real value for later.
  256. //
  257. // WARNING: Once we have done this, we should not return from
  258. // this function until we've restored this character,
  259. // since we don't want to trash the string the caller
  260. // passed us. If we are above the label
  261. // <INLC_RestoreEndChar> and we encounter an error
  262. // we should set <rc> to the error code and jump
  263. // to that label (which will restore the character and
  264. // return if the error is non-zero).
  265. //
  266. cElementEndBackup = *ElementEnd;
  267. *ElementEnd = TCHAR_EOS;
  268. //
  269. // Copy the list element to the output buffer, validating its
  270. // name or canonicalizing it as specified by the user.
  271. //
  272. switch(Flags & INLC_FLAGS_MASK_NAMETYPE) {
  273. case NAMETYPE_PATH:
  274. //
  275. // Make sure that that the <PathTypes> array is big enough
  276. //
  277. if (OutElementCount >= PathTypesLen) {
  278. rc = NERR_TooManyEntries;
  279. goto INLC_RestoreEndChar;
  280. }
  281. //
  282. // Determine if we only want to validate or if we also
  283. // want to canonicalize.
  284. //
  285. if (Flags & INLC_FLAGS_CANONICALIZE) {
  286. //
  287. // We need to set the type to 0 before calling
  288. // NetpwPathCanonicalize.
  289. //
  290. PathTypes[OutElementCount] = 0;
  291. //
  292. // Call NetICanonicalize and abort if it fails
  293. //
  294. rc = NetpwPathCanonicalize(
  295. ElementBegin,
  296. OutPtr,
  297. (DWORD)(OutbufEnd - OutPtr),
  298. NULL,
  299. &PathTypes[OutElementCount],
  300. 0L
  301. );
  302. } else {
  303. //
  304. // Just validate the name and determine its type
  305. //
  306. rc = NetpwPathType(
  307. ElementBegin,
  308. &PathTypes[OutElementCount],
  309. 0L
  310. );
  311. //
  312. // If this succeeded, attempt to copy it into the buffer
  313. //
  314. if (rc == 0) {
  315. if (OutbufEnd - OutPtr < ElementEnd - ElementBegin + 1) {
  316. rc = NERR_BufTooSmall;
  317. } else {
  318. STRCPY(OutPtr, ElementBegin);
  319. }
  320. }
  321. }
  322. if (rc) {
  323. goto INLC_RestoreEndChar;
  324. }
  325. //
  326. // Determine the end of the element (for use below)
  327. //
  328. OutElementEnd = STRCHR(OutPtr, TCHAR_EOS);
  329. //
  330. // Do fix-ups for API list format (enclose element in
  331. // quotes, if necessary, and replace terminating null
  332. // with the list delimiter).
  333. //
  334. if (OutListDelimiter != TCHAR_EOS) {
  335. rc = FixupAPIListElement(
  336. OutPtr,
  337. &OutElementEnd,
  338. OutbufEnd,
  339. OutListDelimiter
  340. );
  341. if (rc) {
  342. goto INLC_RestoreEndChar;
  343. }
  344. }
  345. break;
  346. case NAMETYPE_COPYONLY:
  347. //
  348. // Determine if this element needs to be quoted
  349. //
  350. DelimiterInElement = (OutListDelimiter != TCHAR_EOS)
  351. && (STRCHR(ElementBegin, OutListDelimiter) != NULL);
  352. //
  353. // See if there's enough room in the output buffer for
  354. // this element; abort if there isn't.
  355. //
  356. // (ElementEnd - ElementBegin) is the number of bytes
  357. // in the element. We add 1 for the element separator and
  358. // an additional 2 if we need to enclose the element in quotes.
  359. //
  360. if (OutbufEnd - OutPtr < ElementEnd - ElementBegin + 1 + DelimiterInElement * 2) {
  361. rc = NERR_BufTooSmall;
  362. goto INLC_RestoreEndChar;
  363. }
  364. //
  365. // Start the copying; set pointer to output string
  366. //
  367. OutElementEnd = OutPtr;
  368. //
  369. // Put in leading quote, if appropriate
  370. //
  371. if (DelimiterInElement) {
  372. *OutElementEnd++ = LIST_QUOTE_CHAR;
  373. }
  374. //
  375. // Copy input to output and advance end pointer
  376. //
  377. STRCPY(OutElementEnd, ElementBegin);
  378. OutElementEnd += ElementEnd - ElementBegin;
  379. //
  380. // Put in trailing quote, if appropriate
  381. //
  382. if (DelimiterInElement) {
  383. *OutElementEnd++ = LIST_QUOTE_CHAR;
  384. }
  385. //
  386. // Store delimiter
  387. //
  388. *OutElementEnd = OutListDelimiter;
  389. break;
  390. default:
  391. //
  392. // If this isn't one of the special types, we assume that it's
  393. // a type with meaning to NetpwNameValidate and
  394. // NetpwNameCanonicalize. We call the appropriate one of these
  395. // functions and let it validate the name type and the name,
  396. // passing back any error it returns.
  397. //
  398. //
  399. // Determine if we only want to validate or if we also
  400. // want to canonicalize.
  401. //
  402. if (Flags & INLC_FLAGS_CANONICALIZE) {
  403. rc = NetpwNameCanonicalize(
  404. ElementBegin,
  405. OutPtr,
  406. (DWORD)(OutbufEnd - OutPtr),
  407. Flags & INLC_FLAGS_MASK_NAMETYPE,
  408. 0L
  409. );
  410. } else {
  411. rc = NetpwNameValidate(
  412. ElementBegin,
  413. Flags & INLC_FLAGS_MASK_NAMETYPE,
  414. 0L
  415. );
  416. //
  417. // If this succeeded, attempt to copy it into the buffer
  418. //
  419. if (rc == 0) {
  420. if (OutbufEnd - OutPtr < ElementEnd - ElementBegin + 1) {
  421. rc = NERR_BufTooSmall;
  422. } else {
  423. STRCPY(OutPtr, ElementBegin);
  424. }
  425. }
  426. }
  427. if (rc) {
  428. goto INLC_RestoreEndChar;
  429. }
  430. //
  431. // Determine the end of the element (for use below)
  432. //
  433. OutElementEnd = STRCHR(OutPtr, TCHAR_EOS);
  434. //
  435. // Do fix-ups for API list format (enclose element in
  436. // quotes, if necessary, and replace terminating null
  437. // with the list delimiter).
  438. //
  439. if (OutListDelimiter != TCHAR_EOS) {
  440. rc = FixupAPIListElement(
  441. OutPtr,
  442. &OutElementEnd,
  443. OutbufEnd,
  444. OutListDelimiter
  445. );
  446. if (rc) {
  447. goto INLC_RestoreEndChar;
  448. }
  449. }
  450. break;
  451. }
  452. //
  453. // End of switch statement
  454. //
  455. INLC_RestoreEndChar:
  456. //
  457. // Restore the character at <ElementEnd>; return if one of the
  458. // above tasks failed.
  459. //
  460. *ElementEnd = cElementEndBackup;
  461. if (rc) {
  462. return rc;
  463. }
  464. //
  465. // Skip past the last input character if it's a quote character
  466. //
  467. if (*ElementEnd == LIST_QUOTE_CHAR) {
  468. ElementEnd++;
  469. }
  470. //
  471. // Skip delimiter(s)
  472. //
  473. if (!NullInputDelimiter) {
  474. //
  475. // Determine the number of delimiters and set the input
  476. // pointer to point past them.
  477. //
  478. DelimiterLen = STRSPN(ElementEnd, Delimiters);
  479. Input = ElementEnd + DelimiterLen;
  480. //
  481. // Return an error if:
  482. //
  483. // - there are multiple delimiters and the multiple delimiters
  484. // flag isn't set
  485. // - we aren't at the end of the list and there are no
  486. // delimiters
  487. // - we are at the end of the list, there is a delimiter
  488. // and the multiple delimiters flag isn't set
  489. //
  490. if (DelimiterLen > 1 && !(Flags & INLC_FLAGS_MULTIPLE_DELIMITERS)) {
  491. return ERROR_INVALID_PARAMETER;
  492. }
  493. if (*Input != TCHAR_EOS && DelimiterLen == 0) {
  494. return ERROR_INVALID_PARAMETER;
  495. }
  496. if (*Input == TCHAR_EOS && DelimiterLen > 0 && !(Flags & INLC_FLAGS_MULTIPLE_DELIMITERS)) {
  497. return ERROR_INVALID_PARAMETER;
  498. }
  499. } else {
  500. //
  501. // Since this is a null-null list, we know we've already
  502. // found at least one delimiter. We don't have to worry about
  503. // multiple delimiters, because a second delimiter indicates
  504. // the end of the list.
  505. //
  506. Input = ElementEnd + 1;
  507. }
  508. //
  509. // Update output list pointer and output list count
  510. //
  511. OutPtr = OutElementEnd + 1;
  512. OutElementCount++;
  513. }
  514. //
  515. // End of while loop
  516. //
  517. //
  518. // If the input list was empty, set the output buffer to be a null
  519. // string. Otherwise, stick the list terminator at the end of the
  520. // output buffer.
  521. //
  522. if (OutElementCount == 0) {
  523. if (OutbufLen < 1) {
  524. return NERR_BufTooSmall;
  525. }
  526. *Outbuf = TCHAR_EOS;
  527. } else {
  528. if (OutListType == OUTLIST_TYPE_NULL_NULL) {
  529. //
  530. // Make sure there's room for one more byte
  531. //
  532. if (OutPtr >= OutbufEnd) {
  533. return NERR_BufTooSmall;
  534. }
  535. *OutPtr = TCHAR_EOS;
  536. } else {
  537. //
  538. // NOTE: It's OK to move backwards in the string here because
  539. // we know then OutPtr points one byte past the
  540. // delimiter which follows the last element in the list.
  541. // This does not violate DBCS as long as the delimiter
  542. // is a single-byte character.
  543. //
  544. *(OutPtr - 1) = TCHAR_EOS;
  545. }
  546. }
  547. //
  548. // Set the count of elements in the list
  549. //
  550. *OutCount = OutElementCount;
  551. //
  552. // We're done; return with success
  553. //
  554. return 0;
  555. }
  556. LPTSTR
  557. NetpwListTraverse(
  558. IN LPTSTR Reserved OPTIONAL,
  559. IN LPTSTR* pList,
  560. IN DWORD Flags
  561. )
  562. /*++
  563. Routine Description:
  564. Traverse a list which has been converted to null-null form by
  565. NetpwListCanonicalize. NetpwListTraverse returns a pointer to the first
  566. element in the list, and modifies the list pointer parameter to point to the
  567. next element of the list.
  568. Arguments:
  569. Reserved- A reserved far pointer. Must be NULL.
  570. pList - A pointer to the pointer to the beginning of the list. On return
  571. this will point to the next element in the list or NULL if we
  572. have reached the end
  573. Flags - Flags to determine operation. Currently MBZ.
  574. Return Value:
  575. A pointer to the first element in the list, or NULL if the list
  576. is empty.
  577. --*/
  578. {
  579. LPTSTR FirstElement;
  580. UNREFERENCED_PARAMETER(Reserved);
  581. UNREFERENCED_PARAMETER(Flags);
  582. //
  583. // Produce an assertion error if the reserved parameter is not NULL
  584. // or the flags parameter is no zero.
  585. //
  586. //
  587. // KEEP - This code is ifdef'd out because NETAPI.DLL won't build
  588. // with the standard C version of assert(). This code
  589. // should either be replaced or the #if 0 should be removed
  590. // when we get a standard Net assert function.
  591. //
  592. #ifdef CANONDBG
  593. NetpAssert((Reserved == NULL) && (Flags == 0));
  594. #endif
  595. //
  596. // Return immediately if the pointer to the list pointer is NULL,
  597. // if the list pointer itself is NULL, or if the list is a null
  598. // string (which marks the end of the null-null list).
  599. //
  600. if (pList == NULL || *pList == NULL || **pList == TCHAR_EOS) {
  601. return NULL;
  602. }
  603. //
  604. // Save a pointer to the first element
  605. //
  606. FirstElement = *pList;
  607. //
  608. // Update the list pointer to point to the next element
  609. //
  610. // *pList += STRLEN(FirstElement) + 1;
  611. *pList = STRCHR(FirstElement, TCHAR_EOS) + 1;
  612. //
  613. // Return the pointer to the first element
  614. //
  615. return FirstElement;
  616. }
  617. STATIC
  618. DWORD
  619. FixupAPIListElement(
  620. IN LPTSTR Element,
  621. IN LPTSTR* pElementTerm,
  622. IN LPTSTR BufferEnd,
  623. IN TCHAR DelimiterChar
  624. )
  625. /*++
  626. Routine Description:
  627. FixupAPIListElement Fixes-up a list element which has been copied into the
  628. output buffer so that it conforms to API list format.
  629. FixupAPIListElement takes an unquoted, null-terminated list element
  630. (normally, which has been copied into the output buffer by strcpy() or by
  631. Netpw{Name,Path}Canonicalize) and translates it into the format expected by
  632. API and search-path lists. Specifically, it surrounds the element with quote
  633. characters if it contains a list delimiter character, and it replaces the
  634. null terminator with the API list delimiter.
  635. Arguments:
  636. Element - A pointer to the beginning of the null-terminated element.
  637. pElementTerm- A pointer to the pointer to the element's (null) terminator.
  638. BufferEnd - A pointer to the end of the output buffer (actually, one byte
  639. past the end of the buffer).
  640. DelimiterChar- The list delimiter character.
  641. Return Value:
  642. 0 if successful.
  643. NERR_BufTooSmall if the buffer doesn't have room for the additional
  644. quote characters.
  645. --*/
  646. {
  647. //
  648. // See if the element contains a delimiter; if it does, it needs to
  649. // be quoted.
  650. //
  651. if (STRCHR(Element, DelimiterChar) != NULL) {
  652. //
  653. // Make sure that the output buffer has room for two more
  654. // characters (the quotes).
  655. //
  656. if (BufferEnd - *pElementTerm <= 2 * sizeof(*BufferEnd)) {
  657. return NERR_BufTooSmall;
  658. }
  659. //
  660. // Shift the string one byte to the right, stick quotes on either
  661. // side, and update the end pointer. The element itself with be
  662. // stored in the range [Element + 1, *pElementTerm].
  663. //
  664. MEMMOVE(Element + sizeof(*Element), Element, (int)(*pElementTerm - Element));
  665. *Element = LIST_QUOTE_CHAR;
  666. *(*pElementTerm + 1) = LIST_QUOTE_CHAR;
  667. *pElementTerm += 2;
  668. }
  669. //
  670. // Put a delimiter at the end of the element
  671. //
  672. **pElementTerm = DelimiterChar;
  673. //
  674. // Return with success
  675. //
  676. return 0;
  677. }