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.

2140 lines
49 KiB

  1. /*++
  2. Copyright (c) 1994 Microsoft Corporation
  3. Module Name:
  4. dirlist.cxx
  5. Abstract:
  6. Contains functions for parsing directory output from LIST command
  7. Contents:
  8. ParseDirList
  9. IsFilespecWild
  10. ClearFindList
  11. (DetermineDirectoryFormat)
  12. (IsNtDateFormat)
  13. (GetToken)
  14. (IsUnixAttributeFormat)
  15. (ParseNtDirectory)
  16. (ParseUnixDirectory)
  17. (ParseOs2Directory)
  18. (ParseMacDirectory)
  19. (ExtractFileSize)
  20. (_ExtractFilename)
  21. (ExtractNtDate)
  22. (ExtractUnixDate)
  23. (ExtractOs2Attributes)
  24. (ParseWord)
  25. (ExtractInteger)
  26. Author:
  27. Richard L Firth (rfirth) 26-Jul-1995
  28. Environment:
  29. Win32(s) user-mode DLL
  30. Revision History:
  31. 26-Jul-1995 rfirth
  32. Created
  33. --*/
  34. #include <wininetp.h>
  35. #include "ftpapih.h"
  36. //
  37. // private manifests
  38. //
  39. #define MAX_YEAR_SUPPORTED 2100
  40. #define TOKEN_BUFFER_LENGTH 128
  41. #define RELATIVELY_SMALL_AMOUNT_OF_LS_DATA 512 // arbitrary, but allow for
  42. // prolix error text
  43. //
  44. // private types
  45. //
  46. typedef enum {
  47. State_Start,
  48. State_Error,
  49. State_Continue,
  50. State_Done
  51. } PARSE_STATE;
  52. typedef PARSE_STATE (*DIR_PARSER)(LPSTR*, LPDWORD, LPWIN32_FIND_DATA);
  53. //
  54. // private macros
  55. //
  56. #define ClearFileTime(fileTime) \
  57. (fileTime).dwLowDateTime = 0; \
  58. (fileTime).dwHighDateTime = 0;
  59. #define ClearFindDataFields(lpFind) \
  60. ClearFileTime((lpFind)->ftCreationTime); \
  61. ClearFileTime((lpFind)->ftLastAccessTime); \
  62. (lpFind)->dwReserved0 = 0; \
  63. (lpFind)->dwReserved1 = 0; \
  64. (lpFind)->cAlternateFileName[0] = '\0';
  65. //
  66. // private prototypes
  67. //
  68. PRIVATE
  69. BOOL
  70. DetermineDirectoryFormat(
  71. IN OUT LPSTR* lpBuffer,
  72. IN OUT LPDWORD lpBufferLength,
  73. OUT DIR_PARSER* ParserFunction
  74. );
  75. PRIVATE
  76. BOOL
  77. IsNtDateFormat(
  78. IN LPSTR lpBuffer,
  79. IN DWORD dwBufferLength
  80. );
  81. PRIVATE
  82. BOOL
  83. GetToken(
  84. IN LPSTR lpszBuffer,
  85. IN DWORD dwBufferLength,
  86. OUT LPSTR lpszToken,
  87. IN OUT LPDWORD lpdwTokenLength
  88. );
  89. PRIVATE
  90. BOOL
  91. IsUnixAttributeFormat(
  92. IN LPSTR lpBuffer,
  93. IN DWORD dwBufferLength
  94. );
  95. PRIVATE
  96. PARSE_STATE
  97. ParseNtDirectory(
  98. IN OUT LPSTR* lpBuffer,
  99. IN OUT LPDWORD lpBufferLength,
  100. IN OUT LPWIN32_FIND_DATA lpFindData
  101. );
  102. PRIVATE
  103. PARSE_STATE
  104. ParseUnixDirectory(
  105. IN OUT LPSTR* lpBuffer,
  106. IN OUT LPDWORD lpBufferLength,
  107. IN OUT LPWIN32_FIND_DATA lpFindData
  108. );
  109. PRIVATE
  110. PARSE_STATE
  111. ParseOs2Directory(
  112. IN OUT LPSTR* lpBuffer,
  113. IN OUT LPDWORD lpBufferLength,
  114. IN OUT LPWIN32_FIND_DATA lpFindData
  115. );
  116. PRIVATE
  117. PARSE_STATE
  118. ParseMacDirectory(
  119. IN OUT LPSTR* lpBuffer,
  120. IN OUT LPDWORD lpBufferLength,
  121. IN OUT LPWIN32_FIND_DATA lpFindData
  122. );
  123. PRIVATE
  124. BOOL
  125. ExtractFileSize(
  126. IN OUT LPSTR* lpBuffer,
  127. IN OUT LPDWORD lpBufferLength,
  128. IN OUT LPWIN32_FIND_DATA lpFindData
  129. );
  130. PRIVATE
  131. BOOL
  132. _ExtractFilename(
  133. IN OUT LPSTR* lpBuffer,
  134. IN OUT LPDWORD lpBufferLength,
  135. IN OUT LPWIN32_FIND_DATA lpFindData
  136. );
  137. PRIVATE
  138. BOOL
  139. ExtractNtDate(
  140. IN OUT LPSTR* lpBuffer,
  141. IN OUT LPDWORD lpBufferLength,
  142. IN OUT LPWIN32_FIND_DATA lpFindData
  143. );
  144. PRIVATE
  145. BOOL
  146. ExtractUnixDate(
  147. IN OUT LPSTR* lpBuffer,
  148. IN OUT LPDWORD lpBufferLength,
  149. IN OUT LPWIN32_FIND_DATA lpFindData
  150. );
  151. PRIVATE
  152. BOOL
  153. ExtractOs2Attributes(
  154. IN OUT LPSTR* lpBuffer,
  155. IN OUT LPDWORD lpBufferLength,
  156. IN OUT LPWIN32_FIND_DATA lpFindData
  157. );
  158. PRIVATE
  159. BOOL
  160. ParseWord(
  161. IN OUT LPSTR* lpBuffer,
  162. IN OUT LPDWORD lpBufferLength,
  163. IN WORD LowerBound,
  164. IN WORD UpperBound,
  165. OUT LPWORD lpNumber
  166. );
  167. PRIVATE
  168. BOOL
  169. ExtractInteger(
  170. IN OUT LPSTR* lpBuffer,
  171. IN OUT LPDWORD lpBufferLength,
  172. OUT LPINT lpNumber
  173. );
  174. //
  175. // private data
  176. //
  177. //
  178. // DefaultSystemTime - if we fail to parse the time/date field for any reason,
  179. // we will return this default time
  180. //
  181. PRIVATE static SYSTEMTIME DefaultSystemTime = {1980, 1, 0, 1, 12, 0, 0, 0};
  182. //
  183. // functions
  184. //
  185. DWORD
  186. ParseDirList(
  187. IN LPSTR lpBuffer,
  188. IN DWORD dwBufferLength,
  189. IN LPSTR lpszFilespec OPTIONAL,
  190. IN OUT PLIST_ENTRY lpList
  191. )
  192. /*++
  193. Routine Description:
  194. Creates a list of WIN32_FIND_DATA structures given the output from the LIST
  195. command run at the FTP server
  196. Arguments:
  197. lpBuffer - pointer to buffer containing LIST output
  198. lpBufferLength - length of Buffer - no trailing \0
  199. lpszFilespec - pointer to file specification used to generate listing.
  200. May contain path components. May be NULL, in which case
  201. we perform no filtering based on name (results should be
  202. an exact match with request)
  203. lpList - pointer to LIST_ENTRY list to add to
  204. Return Value:
  205. DWORD
  206. Success - ERROR_SUCCESS
  207. Failure - ERROR_NOT_ENOUGH_MEMORY
  208. --*/
  209. {
  210. DEBUG_ENTER((DBG_FTP,
  211. Dword,
  212. "ParseDirList",
  213. "%x, %d, %x",
  214. lpBuffer,
  215. dwBufferLength,
  216. lpList
  217. ));
  218. DWORD error;
  219. DIR_PARSER directoryParser;
  220. BOOL needBuffer;
  221. LPSTR lpszOriginalBuffer;
  222. DWORD dwOriginalBufferLength;
  223. //
  224. // remember the initial buffer pointer and length, in case we can't determine
  225. // the format - DetermineDirectoryFormat() will alter the input buffer pointer
  226. // and length
  227. //
  228. lpszOriginalBuffer = lpBuffer;
  229. dwOriginalBufferLength = dwBufferLength;
  230. //
  231. // find out the format of the directory listing. Currently we understand
  232. // NT and the basic Unix directory listing formats
  233. //
  234. if (!DetermineDirectoryFormat(&lpBuffer, &dwBufferLength, &directoryParser)) {
  235. DEBUG_PRINT(FTP,
  236. ERROR,
  237. ("Can't determine directory format\n"
  238. ));
  239. //
  240. // if we received a relatively small amount of data, then there is a
  241. // good chance that what we actually received is an error message from
  242. // the ls command, or operating system, etc. Make it an extended error.
  243. // This will reduce (but not eliminate) the chances of getting back an
  244. // internal error
  245. //
  246. if (dwBufferLength <= RELATIVELY_SMALL_AMOUNT_OF_LS_DATA) {
  247. error = InternetSetLastError(0,
  248. lpszOriginalBuffer,
  249. dwOriginalBufferLength,
  250. SLE_APPEND | SLE_ZERO_TERMINATE
  251. );
  252. //
  253. // return internal error if we failed to add the text for any reason
  254. //
  255. error = (error == ERROR_SUCCESS)
  256. ? ERROR_INTERNET_EXTENDED_ERROR
  257. : ERROR_INTERNET_INTERNAL_ERROR
  258. ;
  259. } else {
  260. //
  261. // BUGBUG - error code?
  262. //
  263. error = ERROR_INTERNET_INTERNAL_ERROR;
  264. }
  265. goto quit;
  266. }
  267. //
  268. // the list must be currently empty
  269. //
  270. INET_ASSERT(IsListEmpty(lpList));
  271. //
  272. // the app may have specified a path. Chances are that if there are
  273. // wildcards within the path then the server would have returned an
  274. // error. But if the app requested e.g. foo\bar\*.exe then the server
  275. // would have returned the directory results for foo\bar. Therefore,
  276. // we must skip any path components, or all tests against the filespec
  277. // will fail
  278. //
  279. if (ARGUMENT_PRESENT(lpszFilespec)) {
  280. LPSTR lpszSpec;
  281. lpszSpec = strrchr(lpszFilespec, '\\');
  282. if (lpszSpec == NULL) {
  283. lpszSpec = strrchr(lpszFilespec, '/');
  284. }
  285. if (lpszSpec != NULL) {
  286. lpszFilespec = lpszSpec + 1;
  287. }
  288. DEBUG_PRINT(FTP,
  289. INFO,
  290. ("lpszFilespec = %s\n",
  291. lpszFilespec
  292. ));
  293. }
  294. //
  295. // loop round, parsing the listing until we reach the end or get an
  296. // error
  297. //
  298. needBuffer = TRUE;
  299. error = ERROR_SUCCESS;
  300. while ((dwBufferLength != 0) && (error == ERROR_SUCCESS)) {
  301. PLIST_ENTRY dirEntry;
  302. LPWIN32_FIND_DATA lpFind;
  303. //
  304. // we need to allocate a buffer for the WIN32_FIND_DATA structure
  305. // unless we already have one from the previous iteration (because
  306. // the filename didn't match our target criteria)
  307. //
  308. if (needBuffer) {
  309. dirEntry = (PLIST_ENTRY)ALLOCATE_FIXED_MEMORY(
  310. sizeof(LIST_ENTRY) + sizeof(WIN32_FIND_DATA)
  311. );
  312. lpFind = (LPWIN32_FIND_DATA)(dirEntry + 1);
  313. needBuffer = FALSE;
  314. DEBUG_PRINT(FTP,
  315. INFO,
  316. ("Allocated WIN32_FIND_DATA @ %x\n",
  317. lpFind
  318. ));
  319. }
  320. if (dirEntry == NULL) {
  321. error = ERROR_NOT_ENOUGH_MEMORY;
  322. DEBUG_PRINT(FTP,
  323. ERROR,
  324. ("Failed to allocate WIN32_FIND_DATA\n"
  325. ));
  326. } else {
  327. PARSE_STATE state;
  328. //
  329. // zero initialize the WIN32_FIND_DATA fields we don't fill in
  330. // below
  331. //
  332. ClearFindDataFields(lpFind);
  333. //
  334. // and parse the rest of the information out of the returned FTP
  335. // directory listing
  336. //
  337. state = directoryParser(&lpBuffer, &dwBufferLength, lpFind);
  338. //
  339. // if the parser returns State_Continue or State_Done then we need
  340. // to add the structure to the list if the caller wants it, else we
  341. // free it and quit
  342. //
  343. if (state != State_Error) {
  344. BOOL addIt;
  345. //
  346. // before we put this entry on the list, see if the caller wants
  347. // it
  348. //
  349. if (ARGUMENT_PRESENT(lpszFilespec)) {
  350. addIt = MyFsRtlIsNameInExpression(lpszFilespec,
  351. lpFind->cFileName,
  352. TRUE // case-sensitive
  353. );
  354. } else {
  355. addIt = TRUE;
  356. }
  357. if (addIt) {
  358. DEBUG_PRINT(FTP,
  359. INFO,
  360. ("Match: file %q, target %q\n",
  361. lpFind->cFileName,
  362. lpszFilespec
  363. ));
  364. InsertTailList(lpList, (PLIST_ENTRY)dirEntry);
  365. needBuffer = TRUE;
  366. } else {
  367. DEBUG_PRINT(FTP,
  368. INFO,
  369. ("No match: file %q, target %q\n",
  370. lpFind->cFileName,
  371. lpszFilespec
  372. ));
  373. }
  374. }
  375. //
  376. // if we had an error or there's no more buffer to parse but we
  377. // didn't keep the last entry, then we need to free the unused
  378. // WIN32_FIND_DATA and get out
  379. //
  380. if ((state == State_Error) || ((state == State_Done) && !needBuffer)) {
  381. FREE_MEMORY(dirEntry);
  382. if (state == State_Error) {
  383. DEBUG_PRINT(FTP,
  384. ERROR,
  385. ("State_Error\n"
  386. ));
  387. //
  388. // BUGBUG - error code
  389. //
  390. error = ERROR_INTERNET_INTERNAL_ERROR;
  391. }
  392. }
  393. }
  394. }
  395. quit:
  396. //
  397. // if we had an error then free up any data structures that we allocated
  398. //
  399. if (error != ERROR_SUCCESS) {
  400. ClearFindList(lpList);
  401. }
  402. DEBUG_LEAVE(error);
  403. return error;
  404. }
  405. BOOL
  406. IsFilespecWild(
  407. IN LPCSTR lpszFilespec
  408. )
  409. /*++
  410. Routine Description:
  411. Returns TRUE if lpszFilespec is a wild-card file specifier
  412. Arguments:
  413. lpszFilespec - pointer to string containing file specification. Cannot
  414. be a NULL string
  415. Return Value:
  416. BOOL
  417. --*/
  418. {
  419. int len;
  420. int i;
  421. INET_ASSERT(ARGUMENT_PRESENT(lpszFilespec));
  422. //
  423. // check if the file specifier contains a '*' or a '?'. If so, then the
  424. // caller is making a DOS-style search request and we have to perform our
  425. // own filtering, otherwise, we can leave the server to return what the
  426. // caller asked for
  427. //
  428. for (i = 0, len = strlen(lpszFilespec); i < len; ++i) {
  429. if ((lpszFilespec[i] == '*') || (lpszFilespec[i] == '?')) {
  430. return TRUE;
  431. }
  432. }
  433. return FALSE;
  434. }
  435. VOID
  436. ClearFindList(
  437. IN PLIST_ENTRY lpList
  438. )
  439. /*++
  440. Routine Description:
  441. Dequeues and deallocates all WIN32_FIND_DATA structures on a directory list
  442. Arguments:
  443. lpList - pointer to list to clear
  444. Return Value:
  445. None.
  446. --*/
  447. {
  448. DEBUG_ENTER((DBG_FTP,
  449. None,
  450. "ClearFindList",
  451. "%x",
  452. lpList
  453. ));
  454. while (!IsListEmpty(lpList)) {
  455. PLIST_ENTRY lpHead;
  456. lpHead = RemoveHeadList(lpList);
  457. DEBUG_PRINT(FTP,
  458. INFO,
  459. ("Freeing WIN32_FIND_DATA @ %x, FileName=%q\n",
  460. lpHead,
  461. ((LPWIN32_FIND_DATA)(lpHead + 1))->cFileName
  462. ));
  463. FREE_MEMORY(lpHead);
  464. }
  465. DEBUG_LEAVE(0);
  466. }
  467. //
  468. // private functions
  469. //
  470. PRIVATE
  471. BOOL
  472. DetermineDirectoryFormat(
  473. IN LPSTR* lpBuffer,
  474. IN LPDWORD lpdwBufferLength,
  475. OUT DIR_PARSER* lpfnParserFunction
  476. )
  477. /*++
  478. Routine Description:
  479. Determines whether the directory listing is in Unix or NT (or other?) format
  480. and returns a pointer to the parser function to use
  481. The buffer pointer and length may be adjusted past any prologue information
  482. Arguments:
  483. lpBuffer - pointer to pointer to buffer containing directory
  484. listing
  485. lpdwBufferLength - pointer to length of Buffer
  486. lpfnParserFunction - returned directory parser function
  487. Return Value:
  488. BOOL
  489. Success - TRUE
  490. Failure - FALSE
  491. --*/
  492. {
  493. DEBUG_ENTER((DBG_FTP,
  494. Bool,
  495. "DetermineDirectoryFormat",
  496. "%x [%.40q], %x [%d], %x",
  497. lpBuffer,
  498. *lpBuffer,
  499. lpdwBufferLength,
  500. *lpdwBufferLength,
  501. lpfnParserFunction
  502. ));
  503. BOOL success;
  504. if (!SkipWhitespace(lpBuffer, lpdwBufferLength)) {
  505. success = FALSE;
  506. goto quit;
  507. }
  508. if (IsNtDateFormat(*lpBuffer, *lpdwBufferLength)) {
  509. DEBUG_PRINT(FTP,
  510. INFO,
  511. ("format is NT\n"
  512. ));
  513. *lpfnParserFunction = ParseNtDirectory;
  514. success = TRUE;
  515. goto quit;
  516. }
  517. //
  518. // we think the directory output is from Unix. The listing probably
  519. // starts with "total #" or a number, or other random garbage. We
  520. // know that a Unix dir listing starts with the ls attributes, so
  521. // we'll search for those, but keep our search within a reasonable
  522. // distance of the start
  523. //
  524. LPSTR buffer;
  525. DWORD length;
  526. char tokenBuffer[TOKEN_BUFFER_LENGTH];
  527. int lengthChecked;
  528. int iteration;
  529. int dataLength;
  530. buffer = *lpBuffer;
  531. length = *lpdwBufferLength;
  532. lengthChecked = 0;
  533. iteration = 0;
  534. dataLength = min((int)*lpdwBufferLength, RELATIVELY_SMALL_AMOUNT_OF_LS_DATA);
  535. while (lengthChecked < dataLength) {
  536. DWORD tokenLength;
  537. DWORD previousLength;
  538. tokenLength = sizeof(tokenBuffer);
  539. if (!GetToken(buffer,
  540. length,
  541. tokenBuffer,
  542. &tokenLength)) {
  543. success = FALSE;
  544. goto quit;
  545. }
  546. lengthChecked += tokenLength;
  547. if (IsUnixAttributeFormat(tokenBuffer, tokenLength)) {
  548. DEBUG_PRINT(FTP,
  549. INFO,
  550. ("format is Unix\n"
  551. ));
  552. *lpfnParserFunction = ParseUnixDirectory;
  553. *lpBuffer = buffer;
  554. *lpdwBufferLength = length;
  555. success = TRUE;
  556. goto quit;
  557. } else if ((iteration == 0)
  558. && (tokenLength == 5)
  559. && !strnicmp(tokenBuffer, "total", 5)) {
  560. //
  561. // there may be nothing in the directory listing, except
  562. // "total 0". If this is this case, then we recognize the
  563. // format
  564. //
  565. buffer += tokenLength;
  566. length -= tokenLength;
  567. tokenLength = sizeof(tokenBuffer) - 1; // for '\0'
  568. if (!GetToken(buffer,
  569. length,
  570. tokenBuffer,
  571. &tokenLength)) {
  572. success = FALSE;
  573. goto quit;
  574. }
  575. tokenBuffer[tokenLength] = '\0';
  576. if (isdigit(tokenBuffer[0]) && (atoi(tokenBuffer) == 0)) {
  577. DEBUG_PRINT(FTP,
  578. INFO,
  579. ("format is Unix - empty directory\n"
  580. ));
  581. *lpfnParserFunction = ParseUnixDirectory;
  582. SkipLine(&buffer, &length);
  583. *lpBuffer = buffer;
  584. *lpdwBufferLength = length;
  585. success = TRUE;
  586. goto quit;
  587. }
  588. }
  589. //
  590. // try the next line
  591. //
  592. previousLength = length;
  593. SkipLine(&buffer, &length);
  594. lengthChecked += previousLength - length;
  595. ++iteration;
  596. }
  597. //
  598. // not NT or Unix. Lets try for OS/2. The format of an OS/2 directory entry
  599. // is:
  600. //
  601. // [<ws>]<length>[DIR|<attribute>]<date><time><filename>
  602. //
  603. // we just try to parse the first line. If this succeeds, we assume OS/2
  604. // format
  605. //
  606. WIN32_FIND_DATA findData;
  607. PARSE_STATE state;
  608. buffer = *lpBuffer;
  609. length = *lpdwBufferLength;
  610. state = ParseOs2Directory(&buffer, &length, &findData);
  611. if ((state == State_Continue) || (state == State_Done)) {
  612. DEBUG_PRINT(FTP,
  613. INFO,
  614. ("format is OS/2\n"
  615. ));
  616. success = TRUE;
  617. *lpfnParserFunction = ParseOs2Directory;
  618. goto quit;
  619. }
  620. //
  621. // Mac? Mac servers return Unix-like output which will (should) have already
  622. // been handled by the Unix listing check, and a very simple format which
  623. // just consists of names with an optional '/' appended, indicating a
  624. // directory
  625. //
  626. //
  627. // the Telnet 2.6 FTP server (which just reports the ultra-simple listing
  628. // format) returns a weird 'hidden' entry at the start of the listing which
  629. // consists of "\x03\x02\x01". We will skip all leading control characters
  630. //
  631. buffer = *lpBuffer;
  632. length = *lpdwBufferLength;
  633. while (length && (*buffer < ' ')) {
  634. ++buffer;
  635. --length;
  636. }
  637. LPSTR buffer_;
  638. DWORD length_;
  639. buffer_ = buffer;
  640. length_ = length;
  641. state = ParseMacDirectory(&buffer, &length, &findData);
  642. if ((state == State_Continue) || (state == State_Done)) {
  643. DEBUG_PRINT(FTP,
  644. INFO,
  645. ("format is Mac\n"
  646. ));
  647. *lpBuffer = buffer_;
  648. *lpdwBufferLength = length_;
  649. success = TRUE;
  650. *lpfnParserFunction = ParseMacDirectory;
  651. goto quit;
  652. }
  653. //
  654. // failed to determine the format
  655. //
  656. success = FALSE;
  657. quit:
  658. DEBUG_LEAVE(success);
  659. return success;
  660. }
  661. PRIVATE
  662. BOOL
  663. IsNtDateFormat(
  664. IN LPSTR lpBuffer,
  665. IN DWORD dwBufferLength
  666. )
  667. /*++
  668. Routine Description:
  669. Determines if the directory listing starts with an NT-style date field:
  670. MM-DD-YY
  671. Arguments:
  672. lpBuffer - pointer to buffer containing listing
  673. dwBufferLength - number of bytes in lpBuffer
  674. Return Value:
  675. BOOL
  676. TRUE - buffer starts with NT-style date format
  677. FALSE - not NT-listing
  678. --*/
  679. {
  680. if (dwBufferLength > 8) {
  681. LPSTR buffer;
  682. WORD number;
  683. buffer = lpBuffer;
  684. if (!ParseWord(&buffer, &dwBufferLength, 1, 12, &number)) {
  685. return FALSE;
  686. }
  687. if (!((dwBufferLength > 0) && (*buffer == '-'))) {
  688. return FALSE;
  689. }
  690. ++buffer;
  691. --dwBufferLength;
  692. if (!ParseWord(&buffer, &dwBufferLength, 1, 31, &number)) {
  693. return FALSE;
  694. }
  695. if (!((dwBufferLength > 0) && (*buffer == '-'))) {
  696. return FALSE;
  697. }
  698. ++buffer;
  699. --dwBufferLength;
  700. return ParseWord(&buffer, &dwBufferLength, 0, MAX_YEAR_SUPPORTED, &number);
  701. }
  702. return FALSE;
  703. }
  704. PRIVATE
  705. BOOL
  706. GetToken(
  707. IN LPSTR lpszBuffer,
  708. IN DWORD dwBufferLength,
  709. OUT LPSTR lpszToken,
  710. IN OUT LPDWORD lpdwTokenLength
  711. )
  712. /*++
  713. Routine Description:
  714. Copies a token out of the buffer without updating the buffer pointer or length
  715. Arguments:
  716. lpszBuffer - pointer to buffer to copy from
  717. lpBufferLength - length of buffer
  718. lpszToken - buffer to copy to
  719. lpdwTokenLength - length of buffer on input, length of token on output
  720. Return Value:
  721. BOOL
  722. --*/
  723. {
  724. DWORD length;
  725. if (!SkipSpaces(&lpszBuffer, &dwBufferLength)) {
  726. return FALSE;
  727. }
  728. if (dwBufferLength == 0) {
  729. return FALSE;
  730. }
  731. length = *lpdwTokenLength;
  732. while (!isspace(*lpszBuffer) && (dwBufferLength != 0) && (length != 0)) {
  733. *lpszToken++ = *lpszBuffer++;
  734. --dwBufferLength;
  735. --length;
  736. }
  737. *lpdwTokenLength -= length;
  738. return TRUE;
  739. }
  740. PRIVATE
  741. BOOL
  742. IsUnixAttributeFormat(
  743. IN LPSTR lpBuffer,
  744. IN DWORD dwBufferLength
  745. )
  746. /*++
  747. Routine Description:
  748. Checks if the buffer contains a Unix ls attribute field format
  749. Arguments:
  750. lpBuffer - pointer to buffer containing token to check
  751. dwBufferLength - length of buffer
  752. Return Value:
  753. BOOL
  754. TRUE - lpBuffer *probably* contained a Unix attribute field
  755. FALSE - lpBuffer *probably didn't* contain a Unix attribute field
  756. --*/
  757. {
  758. int i;
  759. int hits;
  760. if (dwBufferLength != 10) {
  761. return FALSE;
  762. }
  763. //
  764. // the first character contains 'd' for directory, 'l' for link, '-' for
  765. // file, and may contain other, unspecified characters, so we just ignore
  766. // it. So long as the next 9 characters are in the set [-rwx] then we have
  767. // a Unix ls attribute field.
  768. //
  769. // N.B. it turns out that the first character can be in the set [-bcdlp]
  770. // and the attribute characters can be in the set [-lrsStTwx] (as of
  771. // 08/18/95)
  772. //
  773. ++lpBuffer;
  774. hits = 0;
  775. for (i = 0; i < 9; ++i) {
  776. char ch;
  777. ch = tolower(*lpBuffer);
  778. ++lpBuffer;
  779. if ((ch == '-')
  780. || (ch == 'l')
  781. || (ch == 'r')
  782. || (ch == 's')
  783. || (ch == 't')
  784. || (ch == 'w')
  785. || (ch == 'x')) {
  786. ++hits;
  787. }
  788. }
  789. //
  790. // new scheme: we decide if the token was a Unix attribute field based on
  791. // probability. If the hit rate was greater than 1 in 2 (5 out of 9 or
  792. // higher) then we say that the field was probably a Unix attribute. This
  793. // scheme allows us to accept future enhancements or non-standard Unix
  794. // implementations without changing this code (for a while) (Make the
  795. // attribute set a registry value (!))
  796. //
  797. return hits >= 5;
  798. }
  799. PRIVATE
  800. PARSE_STATE
  801. ParseNtDirectory(
  802. IN OUT LPSTR* lpBuffer,
  803. IN OUT LPDWORD lpBufferLength,
  804. IN OUT LPWIN32_FIND_DATA lpFindData
  805. )
  806. /*++
  807. Routine Description:
  808. Parses a single line of an NT directory listing (output from DIR) into a
  809. WIN32_FIND_DATA structure
  810. The format of an NT directory list line is:
  811. <date> <time> <'<DIR>'|<size>> <filename>
  812. Arguments:
  813. lpBuffer - pointer to pointer to directory listing
  814. lpBufferLength - pointer to number of bytes remaining in lpBuffer
  815. lpFindData - pointer to WIN32_FIND_DATA to update
  816. Return Value:
  817. PARSE_STATE
  818. State_Continue - more listing to parse
  819. State_Done - fin!
  820. --*/
  821. {
  822. //
  823. // not expecting the line to start with spaces, but we check anyway
  824. //
  825. if (!SkipSpaces(lpBuffer, lpBufferLength)) {
  826. goto done;
  827. }
  828. if (!ExtractNtDate(lpBuffer, lpBufferLength, lpFindData)) {
  829. goto done;
  830. }
  831. if (!strnicmp(*lpBuffer, "<DIR>", 5)) {
  832. lpFindData->dwFileAttributes = FILE_ATTRIBUTE_DIRECTORY;
  833. lpFindData->nFileSizeHigh = 0;
  834. lpFindData->nFileSizeLow = 0;
  835. FindToken(lpBuffer, lpBufferLength);
  836. } else {
  837. lpFindData->dwFileAttributes = FILE_ATTRIBUTE_NORMAL;
  838. ExtractFileSize(lpBuffer, lpBufferLength, lpFindData);
  839. SkipSpaces(lpBuffer, lpBufferLength);
  840. }
  841. _ExtractFilename(lpBuffer, lpBufferLength, lpFindData);
  842. //
  843. // we expect the filename to be the last thing on the line
  844. //
  845. done:
  846. return SkipLine(lpBuffer, lpBufferLength) ? State_Continue : State_Done;
  847. }
  848. PRIVATE
  849. void
  850. ReadUnixPermissions(
  851. IN LPCSTR pszBuffer,
  852. IN DWORD cbBufferSize,
  853. OUT LPDWORD pdwPermissions)
  854. {
  855. // Format: rwxrwxrwx <Owner><Group><All>
  856. *pdwPermissions = 0;
  857. if (cbBufferSize > 10)
  858. {
  859. if ('r' == pszBuffer[1])
  860. *pdwPermissions |= 0x00000400;
  861. if ('w' == pszBuffer[2])
  862. *pdwPermissions |= 0x00000200;
  863. if ('x' == pszBuffer[3])
  864. *pdwPermissions |= 0x00000100;
  865. if ('r' == pszBuffer[4])
  866. *pdwPermissions |= 0x00000040;
  867. if ('w' == pszBuffer[5])
  868. *pdwPermissions |= 0x00000020;
  869. if ('x' == pszBuffer[6])
  870. *pdwPermissions |= 0x00000010;
  871. if ('r' == pszBuffer[7])
  872. *pdwPermissions |= 0x00000004;
  873. if ('w' == pszBuffer[8])
  874. *pdwPermissions |= 0x00000002;
  875. if ('x' == pszBuffer[9])
  876. *pdwPermissions |= 0x00000001;
  877. }
  878. }
  879. PRIVATE
  880. PARSE_STATE
  881. ParseUnixDirectory(
  882. IN OUT LPSTR* lpBuffer,
  883. IN OUT LPDWORD lpBufferLength,
  884. IN OUT LPWIN32_FIND_DATA lpFindData
  885. )
  886. /*++
  887. Routine Description:
  888. Parses a single line of a Unix directory listing (output from ls) into a
  889. WIN32_FIND_DATA structure
  890. The format of a Unix directory list line is:
  891. <attributes> <link-count> <owner> <group> <size> <date-time> <filename>
  892. Arguments:
  893. lpBuffer - pointer to pointer to directory listing
  894. lpBufferLength - pointer to number of bytes remaining in lpBuffer
  895. lpFindData - pointer to WIN32_FIND_DATA to update
  896. Return Value:
  897. PARSE_STATE
  898. State_Continue - more listing to parse
  899. State_Done - fin!
  900. --*/
  901. {
  902. DWORD error;
  903. int i;
  904. BOOL symbolicLink;
  905. char ch;
  906. //
  907. // not expecting the line to start with spaces, but we check anyway
  908. //
  909. if (!SkipSpaces(lpBuffer, lpBufferLength)) {
  910. goto done;
  911. }
  912. //
  913. // if the item is a symbolic link then we have to trim the 'filename' below
  914. //
  915. ch = tolower(**lpBuffer);
  916. symbolicLink = (ch == 'l');
  917. //
  918. // attributes are first thing on line
  919. //
  920. lpFindData->dwFileAttributes = (ch == 'd') ? FILE_ATTRIBUTE_DIRECTORY
  921. : (ch == '-') ? FILE_ATTRIBUTE_NORMAL
  922. : 0;
  923. //
  924. // skip over the attributes and over the owner/creator fields to the file
  925. // size
  926. //
  927. // Read the Attributes and put them in the WIN32_FIND_DATA.dwReserved0 attributes.
  928. // It's OK to use FILE_ATTRIBUTE_REPARSE_POINT because it's unused unless
  929. // WIN32_FIND_DATA.dwFileAttributes contains yyy, which we don't set.
  930. ReadUnixPermissions(*lpBuffer, *lpBufferLength, &(lpFindData->dwReserved0));
  931. LPSTR lpszLastToken;
  932. DWORD dwLastToken;
  933. for (i = 0; i < 4; ++i) {
  934. lpszLastToken = *lpBuffer;
  935. dwLastToken = *lpBufferLength;
  936. if (!FindToken(lpBuffer, lpBufferLength)) {
  937. goto done;
  938. }
  939. }
  940. if (!ExtractFileSize(lpBuffer, lpBufferLength, lpFindData)) {
  941. ExtractFileSize(&lpszLastToken, &dwLastToken, lpFindData);
  942. }
  943. SkipSpaces(lpBuffer, lpBufferLength);
  944. if (!ExtractUnixDate(lpBuffer, lpBufferLength, lpFindData)) {
  945. goto done;
  946. }
  947. SkipSpaces(lpBuffer, lpBufferLength);
  948. //
  949. // we expect the filename to be the last thing on the line
  950. //
  951. _ExtractFilename(lpBuffer, lpBufferLength, lpFindData);
  952. //
  953. // if the item is a symbolic link, then remove everything after the " -> "
  954. //
  955. if (symbolicLink) {
  956. LPSTR lpArrow;
  957. lpArrow = strstr(lpFindData->cFileName, " -> ");
  958. if (lpArrow != NULL) {
  959. *lpArrow = '\0';
  960. }
  961. }
  962. done:
  963. return SkipLine(lpBuffer, lpBufferLength) ? State_Continue : State_Done;
  964. }
  965. PRIVATE
  966. PARSE_STATE
  967. ParseOs2Directory(
  968. IN OUT LPSTR* lpBuffer,
  969. IN OUT LPDWORD lpBufferLength,
  970. IN OUT LPWIN32_FIND_DATA lpFindData
  971. )
  972. /*++
  973. Routine Description:
  974. Parses a single line of an OS/2 directory listing (output from dir) into a
  975. WIN32_FIND_DATA structure
  976. The format of an OS/2 directory list line is:
  977. <size> <attributes> <date> <time> <filename>
  978. This function is also used to determine OS/2 directory format
  979. Arguments:
  980. lpBuffer - pointer to pointer to directory listing
  981. lpBufferLength - pointer to number of bytes remaining in lpBuffer
  982. lpFindData - pointer to WIN32_FIND_DATA to update
  983. Return Value:
  984. PARSE_STATE
  985. State_Continue - more listing to parse
  986. State_Done - fin!
  987. State_Error - directory format not recognized
  988. --*/
  989. {
  990. PARSE_STATE state;
  991. if (!SkipSpaces(lpBuffer, lpBufferLength)) {
  992. goto skip;
  993. }
  994. if (!ExtractFileSize(lpBuffer, lpBufferLength, lpFindData)) {
  995. state = State_Error;
  996. goto done;
  997. }
  998. if (!SkipSpaces(lpBuffer, lpBufferLength)) {
  999. goto skip;
  1000. }
  1001. if (!strnicmp(*lpBuffer, "DIR", 3)) {
  1002. lpFindData->dwFileAttributes = FILE_ATTRIBUTE_DIRECTORY;
  1003. FindToken(lpBuffer, lpBufferLength);
  1004. } else if (!isdigit(**lpBuffer)) {
  1005. if (!ExtractOs2Attributes(lpBuffer, lpBufferLength, lpFindData)) {
  1006. state = State_Error;
  1007. goto done;
  1008. }
  1009. SkipSpaces(lpBuffer, lpBufferLength);
  1010. } else {
  1011. lpFindData->dwFileAttributes = FILE_ATTRIBUTE_NORMAL;
  1012. }
  1013. if (!ExtractNtDate(lpBuffer, lpBufferLength, lpFindData)) {
  1014. state = State_Error;
  1015. goto done;
  1016. }
  1017. _ExtractFilename(lpBuffer, lpBufferLength, lpFindData);
  1018. //
  1019. // we expect the filename to be the last thing on the line
  1020. //
  1021. skip:
  1022. state = SkipLine(lpBuffer, lpBufferLength) ? State_Continue : State_Done;
  1023. done:
  1024. return state;
  1025. }
  1026. PRIVATE
  1027. PARSE_STATE
  1028. ParseMacDirectory(
  1029. IN OUT LPSTR* lpBuffer,
  1030. IN OUT LPDWORD lpBufferLength,
  1031. IN OUT LPWIN32_FIND_DATA lpFindData
  1032. )
  1033. /*++
  1034. Routine Description:
  1035. Parses a single line of a Mac directory listing (output from ls) into a
  1036. WIN32_FIND_DATA structure
  1037. The format of a Mac directory list line is:
  1038. <dir-or-file-name>[/]
  1039. This function is also used to determine Mac directory format
  1040. Arguments:
  1041. lpBuffer - pointer to pointer to directory listing
  1042. lpBufferLength - pointer to number of bytes remaining in lpBuffer
  1043. lpFindData - pointer to WIN32_FIND_DATA to update
  1044. Return Value:
  1045. PARSE_STATE
  1046. State_Continue - more listing to parse
  1047. State_Done - fin!
  1048. State_Error - directory format not recognized
  1049. --*/
  1050. {
  1051. PARSE_STATE state;
  1052. if (!SkipSpaces(lpBuffer, lpBufferLength)) {
  1053. goto skip;
  1054. }
  1055. _ExtractFilename(lpBuffer, lpBufferLength, lpFindData);
  1056. int len;
  1057. len = lstrlen(lpFindData->cFileName);
  1058. if (lpFindData->cFileName[len - 1] == '/') {
  1059. lpFindData->cFileName[len - 1] = '\0';
  1060. lpFindData->dwFileAttributes = FILE_ATTRIBUTE_DIRECTORY;
  1061. } else {
  1062. lpFindData->dwFileAttributes = FILE_ATTRIBUTE_NORMAL;
  1063. }
  1064. if ((*lpBufferLength != 0)
  1065. && !((**lpBuffer == '\r') || (**lpBuffer == '\n'))) {
  1066. state = State_Error;
  1067. goto done;
  1068. }
  1069. //
  1070. // this directory format has no size or time information
  1071. //
  1072. lpFindData->nFileSizeLow = 0;
  1073. lpFindData->nFileSizeHigh = 0;
  1074. SystemTimeToFileTime(&DefaultSystemTime, &lpFindData->ftLastWriteTime);
  1075. //
  1076. // we expect the filename to be the last thing on the line
  1077. //
  1078. skip:
  1079. state = SkipLine(lpBuffer, lpBufferLength) ? State_Continue : State_Done;
  1080. done:
  1081. return state;
  1082. }
  1083. PRIVATE
  1084. BOOL
  1085. ExtractFileSize(
  1086. IN OUT LPSTR* lpBuffer,
  1087. IN OUT LPDWORD lpBufferLength,
  1088. IN OUT LPWIN32_FIND_DATA lpFindData
  1089. )
  1090. /*++
  1091. Routine Description:
  1092. Extracts the the next token in the directory listing. The next token is
  1093. expected to be the file size. It is extracted to the WIN32_FIND_DATA
  1094. structure
  1095. Assumes: 1. The file size is <= 32 bits
  1096. Arguments:
  1097. lpBuffer - pointer to pointer to directory listing buffer
  1098. lpBufferLength - pointer to remaining buffer length
  1099. lpFindData - pointer to WIN32_FIND_DATA to update
  1100. Return Value:
  1101. BOOL
  1102. --*/
  1103. {
  1104. INET_ASSERT(*lpBufferLength != 0);
  1105. LPSTR buffer;
  1106. char ch = **lpBuffer;
  1107. if (isdigit(ch)) {
  1108. lpFindData->nFileSizeLow = strtoul(*lpBuffer, &buffer, 10);
  1109. lpFindData->nFileSizeHigh = 0;
  1110. *lpBufferLength -= (DWORD) (buffer - *lpBuffer);
  1111. *lpBuffer = buffer;
  1112. return TRUE;
  1113. }
  1114. return FALSE;
  1115. }
  1116. PRIVATE
  1117. BOOL
  1118. _ExtractFilename(
  1119. IN OUT LPSTR* lpBuffer,
  1120. IN OUT LPDWORD lpBufferLength,
  1121. IN OUT LPWIN32_FIND_DATA lpFindData
  1122. )
  1123. /*++
  1124. Routine Description:
  1125. Extracts the filename from the current directory listing position into the
  1126. WIN32_FIND_DATA structure
  1127. Arguments:
  1128. lpBuffer - pointer to pointer to directory listing buffer
  1129. lpBufferLength - pointer to remaining buffer length
  1130. lpFindData - pointer to WIN32_FIND_DATA to update
  1131. Return Value:
  1132. BOOL
  1133. --*/
  1134. {
  1135. LPSTR dest;
  1136. DWORD destLength;
  1137. dest = lpFindData->cFileName;
  1138. destLength = sizeof(lpFindData->cFileName) - 1;
  1139. while ((*lpBufferLength != 0)
  1140. && (destLength != 0)
  1141. && !((**lpBuffer == '\r') || (**lpBuffer == '\n'))) {
  1142. *dest++ = *(*lpBuffer)++;
  1143. --*lpBufferLength;
  1144. --destLength;
  1145. }
  1146. *dest = '\0';
  1147. return TRUE;
  1148. }
  1149. PRIVATE
  1150. BOOL
  1151. ExtractNtDate(
  1152. IN OUT LPSTR* lpBuffer,
  1153. IN OUT LPDWORD lpBufferLength,
  1154. IN OUT LPWIN32_FIND_DATA lpFindData
  1155. )
  1156. /*++
  1157. Routine Description:
  1158. Extracts an NT date and time from the directory listing. NT dates have a
  1159. specific format:
  1160. MM-DD-YY hh:mmPP
  1161. Arguments:
  1162. lpBuffer - pointer to pointer to current position in directory list
  1163. lpBufferLength - number of bytes til end of directory list
  1164. lpFindData - pointer to WIN32_FIND_DATA to update
  1165. Return Value:
  1166. BOOL
  1167. Success - TRUE
  1168. Failure - FALSE
  1169. --*/
  1170. {
  1171. SYSTEMTIME systemTime;
  1172. LPSTR buffer;
  1173. DWORD buflen;
  1174. int number;
  1175. LPSTR stop;
  1176. LPSYSTEMTIME lpSystemTime;
  1177. lpSystemTime = &DefaultSystemTime;
  1178. //
  1179. // BUGBUG - what about internationalization? E.g. does UK FTP server return
  1180. // the date as e.g. 27/07/95? Other formats?
  1181. //
  1182. //
  1183. // month ::= 1..12
  1184. //
  1185. if (!ParseWord(lpBuffer, lpBufferLength, 1, 12, &systemTime.wMonth)) {
  1186. goto done;
  1187. }
  1188. if (!((*lpBufferLength > 0) && (**lpBuffer == '-'))) {
  1189. goto done;
  1190. }
  1191. ++*lpBuffer;
  1192. --*lpBufferLength;
  1193. //
  1194. // date ::= 1..31
  1195. //
  1196. if (!ParseWord(lpBuffer, lpBufferLength, 1, 31, &systemTime.wDay)) {
  1197. goto done;
  1198. }
  1199. if (!((*lpBufferLength > 0) && (**lpBuffer == '-'))) {
  1200. goto done;
  1201. }
  1202. ++*lpBuffer;
  1203. --*lpBufferLength;
  1204. //
  1205. // year ::= 0..2100
  1206. //
  1207. if (!ParseWord(lpBuffer, lpBufferLength, 0, MAX_YEAR_SUPPORTED, &systemTime.wYear)) {
  1208. goto done;
  1209. }
  1210. if (!((*lpBufferLength > 0) && (**lpBuffer == ' '))) {
  1211. goto done;
  1212. }
  1213. //
  1214. // the oldest file can be dated 1980. We allow the following:
  1215. //
  1216. // 1-1-79 => 1-1-2079
  1217. // 1-1-80..12-31-99 => 1-1-1980..12-31-1999
  1218. // 1-1-00 => 1-1-2000
  1219. // 1-1-1995 => 1-1-1995
  1220. // 1-1-2001 => 1-1-2001
  1221. // etc.
  1222. //
  1223. systemTime.wYear += (systemTime.wYear < 80)
  1224. ? 2000
  1225. : (systemTime.wYear <= 99)
  1226. ? 1900
  1227. : 0
  1228. ;
  1229. //
  1230. // find start of time (er, Professor Hawking..?)
  1231. //
  1232. if (!FindToken(lpBuffer, lpBufferLength)) {
  1233. goto done;
  1234. }
  1235. //
  1236. // hour ::= 0..23 | 1..12
  1237. //
  1238. if (!ParseWord(lpBuffer, lpBufferLength, 0, 23, &systemTime.wHour)) {
  1239. goto done;
  1240. }
  1241. if (!((*lpBufferLength > 0) && (**lpBuffer == ':'))) {
  1242. goto done;
  1243. }
  1244. ++*lpBuffer;
  1245. --*lpBufferLength;
  1246. //
  1247. // minute ::= 0..59
  1248. //
  1249. if (!ParseWord(lpBuffer, lpBufferLength, 0, 59, &systemTime.wMinute)) {
  1250. goto done;
  1251. }
  1252. //
  1253. // if the time is followed by AM or PM then convert to 24-hour time if PM
  1254. // and skip to end of token in both cases
  1255. //
  1256. if (*lpBufferLength >= 2) {
  1257. char ch_p;
  1258. ch_p = tolower(**lpBuffer);
  1259. if ((ch_p == 'p') || (ch_p == 'a')) {
  1260. char ch_m;
  1261. ch_m = tolower(*(*lpBuffer + 1));
  1262. if ((ch_p == 'p') && (ch_m == 'm')) {
  1263. // 12 PM = 12, 1PM = 13, 2PM = 14, etc
  1264. if ( systemTime.wHour < 12 ) {
  1265. systemTime.wHour += 12;
  1266. }
  1267. } else if ( systemTime.wHour == 12 ) {
  1268. // 12 AM == 0:00 24hr
  1269. INET_ASSERT((ch_p == 'a') && (ch_m == 'm'));
  1270. systemTime.wHour = 0;
  1271. }
  1272. }
  1273. }
  1274. //
  1275. // seconds, milliseconds and weekday always zero
  1276. //
  1277. systemTime.wSecond = 0;
  1278. systemTime.wMilliseconds = 0;
  1279. systemTime.wDayOfWeek = 0;
  1280. //
  1281. // get ready to convert the parsed date/time to a FILETIME
  1282. //
  1283. lpSystemTime = &systemTime;
  1284. done:
  1285. //
  1286. // convert the system time to file time and move the buffer pointer/length
  1287. // to the next line
  1288. //
  1289. if (!SystemTimeToFileTime(lpSystemTime, &lpFindData->ftLastWriteTime)) {
  1290. SystemTimeToFileTime(&DefaultSystemTime, &lpFindData->ftLastWriteTime);
  1291. }
  1292. FindToken(lpBuffer, lpBufferLength);
  1293. return TRUE;
  1294. }
  1295. PRIVATE
  1296. BOOL
  1297. ExtractUnixDate(
  1298. IN OUT LPSTR* lpBuffer,
  1299. IN OUT LPDWORD lpBufferLength,
  1300. IN OUT LPWIN32_FIND_DATA lpFindData
  1301. )
  1302. /*++
  1303. Routine Description:
  1304. Extracts a Unix date and time from the directory listing. Unix dates have a
  1305. multitude of formats:
  1306. Jul 3 14:52
  1307. Oct 7 1994
  1308. Jul 26 4:05
  1309. Jul 26 03:51
  1310. Arguments:
  1311. lpBuffer - pointer to pointer to current position in directory list
  1312. lpBufferLength - number of bytes til end of directory list
  1313. lpFindData - pointer to WIN32_FIND_DATA to update
  1314. Return Value:
  1315. BOOL
  1316. Success - TRUE
  1317. Failure - FALSE
  1318. --*/
  1319. {
  1320. SYSTEMTIME systemTime;
  1321. LPSYSTEMTIME lpSystemTime;
  1322. static LPSTR Months = "janfebmaraprmayjunjulaugsepoctnovdec";
  1323. char monthStr[4];
  1324. int i;
  1325. LPSTR offset;
  1326. WORD wnum;
  1327. lpSystemTime = &DefaultSystemTime;
  1328. //
  1329. // month ::= Jan..Dec
  1330. //
  1331. for (i = 0; i < 3; ++i) {
  1332. if (*lpBufferLength == 0) {
  1333. goto done;
  1334. }
  1335. monthStr[i] = *(*lpBuffer)++;
  1336. monthStr[i] = tolower(monthStr[i]);
  1337. --*lpBufferLength;
  1338. }
  1339. monthStr[i] = '\0';
  1340. offset = strstr(Months, monthStr);
  1341. if (offset == NULL) {
  1342. goto done;
  1343. }
  1344. systemTime.wMonth = (unsigned short) ((offset-Months) / 3 + 1);
  1345. FindToken(lpBuffer, lpBufferLength);
  1346. //
  1347. // date ::= 1..31
  1348. //
  1349. if (!ParseWord(lpBuffer, lpBufferLength, 1, 31, &systemTime.wDay)) {
  1350. goto done;
  1351. }
  1352. SkipSpaces(lpBuffer, lpBufferLength);
  1353. //
  1354. // year or hour
  1355. //
  1356. if (!ParseWord(lpBuffer, lpBufferLength, 0, 65535, &wnum)) {
  1357. goto done;
  1358. }
  1359. if ((*lpBufferLength != 0) && (**lpBuffer == ':')) {
  1360. SYSTEMTIME timeNow;
  1361. systemTime.wHour = wnum;
  1362. //
  1363. // we found the hour field, now get the minutes
  1364. //
  1365. ++*lpBuffer;
  1366. --*lpBufferLength;
  1367. if (!ParseWord(lpBuffer, lpBufferLength, 0, 59, &systemTime.wMinute)) {
  1368. goto done;
  1369. }
  1370. //
  1371. // a date-time with an hour:minute field is based in this year. We need
  1372. // to get the current year from the system. There is a slight problem in
  1373. // that if this machine's just had a new year and the FTP server is
  1374. // behind us, then the file can be a year out of date.
  1375. //
  1376. // There is no guarantees about the basis of time used by the FTP server,
  1377. // so we'll get the UTC (aka Greenwich Mean Time) time
  1378. //
  1379. GetSystemTime(&timeNow);
  1380. systemTime.wYear = timeNow.wYear;
  1381. if(!GlobalBypassFtpTimeCheck)
  1382. {
  1383. //
  1384. // apparently its not quite as straightforward as first we'd believed.
  1385. // If the date/month is in the future then the year is last year
  1386. //
  1387. BOOL bLastYear = FALSE;
  1388. if (systemTime.wMonth > timeNow.wMonth) {
  1389. bLastYear = TRUE;
  1390. } else if (systemTime.wMonth == timeNow.wMonth) {
  1391. //
  1392. // BUGBUG - leap year? I believe that in this case, because the time
  1393. // difference is 1 year minus 1 day, then that is great
  1394. // enough for the date format including year to have been
  1395. // used and thus making this moot. Need to prove it.
  1396. // Note, by that logic, everything from here on down should
  1397. // also be moot - we should only have to concern ourselves
  1398. // with the month
  1399. //
  1400. if (systemTime.wDay > timeNow.wDay) {
  1401. bLastYear = TRUE;
  1402. } else if (systemTime.wDay == timeNow.wDay) {
  1403. if (systemTime.wHour > timeNow.wHour) {
  1404. bLastYear = TRUE;
  1405. } else if (systemTime.wHour == timeNow.wHour) {
  1406. if (systemTime.wMinute > timeNow.wMinute) {
  1407. bLastYear = TRUE;
  1408. } else if (systemTime.wMinute == timeNow.wMinute) {
  1409. if (systemTime.wSecond > timeNow.wSecond) {
  1410. bLastYear = TRUE;
  1411. }
  1412. }
  1413. }
  1414. }
  1415. }
  1416. if (bLastYear) {
  1417. --systemTime.wYear;
  1418. }
  1419. }
  1420. } else {
  1421. //
  1422. // next field is the year
  1423. //
  1424. systemTime.wYear = wnum;
  1425. //
  1426. // time for a file with only a year is 00:00 (mitternacht)
  1427. //
  1428. systemTime.wHour = 0;
  1429. systemTime.wMinute = 0;
  1430. }
  1431. //
  1432. // seconds, milliseconds and weekday always zero
  1433. //
  1434. systemTime.wSecond = 0;
  1435. systemTime.wMilliseconds = 0;
  1436. systemTime.wDayOfWeek = 0;
  1437. //
  1438. // get ready to convert the parsed date/time to a FILETIME
  1439. //
  1440. lpSystemTime = &systemTime;
  1441. done:
  1442. //
  1443. // convert the system time to file time and move the buffer pointer/length
  1444. // to the next line
  1445. //
  1446. if (!SystemTimeToFileTime(lpSystemTime, &lpFindData->ftLastWriteTime)) {
  1447. SystemTimeToFileTime(&DefaultSystemTime, &lpFindData->ftLastWriteTime);
  1448. }
  1449. FindToken(lpBuffer, lpBufferLength);
  1450. return TRUE;
  1451. }
  1452. PRIVATE
  1453. BOOL
  1454. ExtractOs2Attributes(
  1455. IN OUT LPSTR* lpBuffer,
  1456. IN OUT LPDWORD lpBufferLength,
  1457. IN OUT LPWIN32_FIND_DATA lpFindData
  1458. )
  1459. /*++
  1460. Routine Description:
  1461. Converts an OS/2 attribute string into Win32 file attribute flags in the
  1462. WIN32_FIND_DATA structure
  1463. Arguments:
  1464. lpBuffer - pointer to pointer to current position in directory list
  1465. lpBufferLength - number of bytes til end of directory list
  1466. lpFindData - pointer to WIN32_FIND_DATA to update
  1467. Return Value:
  1468. BOOL
  1469. Success - TRUE
  1470. Failure - FALSE
  1471. --*/
  1472. {
  1473. DWORD attributes = 0;
  1474. BOOL done = FALSE;
  1475. while (*lpBufferLength && !done) {
  1476. char ch = **lpBuffer;
  1477. switch (toupper(ch)) {
  1478. case 'A':
  1479. attributes |= FILE_ATTRIBUTE_ARCHIVE;
  1480. break;
  1481. case 'H':
  1482. attributes |= FILE_ATTRIBUTE_HIDDEN;
  1483. break;
  1484. case 'R':
  1485. attributes |= FILE_ATTRIBUTE_READONLY;
  1486. break;
  1487. case 'S':
  1488. attributes |= FILE_ATTRIBUTE_SYSTEM;
  1489. break;
  1490. case ' ':
  1491. //
  1492. // if there is only one space, we will be pointing at the next token
  1493. // after this function completes. Okay so long as we will be calling
  1494. // SkipSpaces() next (which doesn't expect to be pointing at a space)
  1495. //
  1496. done = TRUE;
  1497. break;
  1498. }
  1499. --*lpBufferLength;
  1500. ++*lpBuffer;
  1501. }
  1502. //
  1503. // if we are here there must have been some characters which looked like
  1504. // OS/2 file attributes
  1505. //
  1506. INET_ASSERT(attributes != 0);
  1507. lpFindData->dwFileAttributes = attributes;
  1508. return TRUE;
  1509. }
  1510. PRIVATE
  1511. BOOL
  1512. ParseWord(
  1513. IN OUT LPSTR* lpBuffer,
  1514. IN OUT LPDWORD lpBufferLength,
  1515. IN WORD LowerBound,
  1516. IN WORD UpperBound,
  1517. OUT LPWORD lpNumber
  1518. )
  1519. /*++
  1520. Routine Description:
  1521. Extract a WORD value out of the buffer. To be correctly parsed the number
  1522. field must:
  1523. * start with numeric characters
  1524. * not be negative
  1525. * be >= LowerBound and <= UpperBound
  1526. * must end with a non-numeric character or when the buffer is exhausted
  1527. Arguments:
  1528. lpBuffer - pointer to pointer to buffer containing number to parse
  1529. lpBufferLength - pointer to remaining length in buffer
  1530. LowerBound - lowest value converted number can have
  1531. UpperBound - highest value converted number can have
  1532. lpNumber - pointer to returned WORD value
  1533. Return Value:
  1534. BOOL
  1535. Success - TRUE
  1536. Failure - FALSE
  1537. --*/
  1538. {
  1539. int number;
  1540. if (ExtractInteger(lpBuffer, lpBufferLength, &number)) {
  1541. if ((number >= (int)LowerBound) && (number <= (int)UpperBound)) {
  1542. *lpNumber = (WORD)number;
  1543. return TRUE;
  1544. }
  1545. }
  1546. return FALSE;
  1547. }
  1548. PRIVATE
  1549. BOOL
  1550. ExtractInteger(
  1551. IN OUT LPSTR* lpBuffer,
  1552. IN OUT LPDWORD lpBufferLength,
  1553. OUT LPINT lpNumber
  1554. )
  1555. /*++
  1556. Routine Description:
  1557. Performs a strtoul type function, but the input string is not terminated by
  1558. \0. It has an associated length
  1559. Arguments:
  1560. lpBuffer - pointer to pointer to string containing number to extract
  1561. lpBufferLength - pointer to length of string remaining in lpBuffer
  1562. lpNumber - pointer to returned value
  1563. Return Value:
  1564. BOOL
  1565. Success - TRUE
  1566. Failure - FALSE
  1567. --*/
  1568. {
  1569. BOOL success;
  1570. int number;
  1571. number = 0;
  1572. if ((*lpBufferLength > 0) && isdigit(**lpBuffer)) {
  1573. while (isdigit(**lpBuffer) && (*lpBufferLength != 0)) {
  1574. number = number * 10 + (int)(**lpBuffer - '0');
  1575. ++*lpBuffer;
  1576. --*lpBufferLength;
  1577. }
  1578. success = TRUE;
  1579. } else {
  1580. success = FALSE;
  1581. }
  1582. *lpNumber = number;
  1583. return success;
  1584. }