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.

1496 lines
46 KiB

  1. /*++
  2. Copyright (c) 1995 Microsoft Corporation
  3. Module Name:
  4. readhtml.cxx
  5. Abstract:
  6. Contains functions for returning files and directory listings (gopher and
  7. FTP) as HTML documents
  8. Contents:
  9. ReadHtmlUrlData
  10. QueryHtmlDataAvailable
  11. (MakeHtmlDirEntry)
  12. Author:
  13. Richard L Firth (rfirth) 23-Jun-1995
  14. Environment:
  15. Win32 user-mode
  16. Revision History:
  17. 23-Jun-1995 rfirth
  18. Created
  19. --*/
  20. #include <wininetp.h>
  21. //
  22. // private manifests
  23. //
  24. //
  25. // HTML encapsulation strings - we generate HTML documents using the following
  26. // strings. Although we don't need carriage-return, line-feed at the end of each
  27. // line, we add them anyway since it allows View Source and Save As commands in
  28. // the viewer to create human-sensible documents
  29. //
  30. //
  31. // HTML_DOCUMENT_HEADER - every HTML document should have a header. It must have
  32. // a title. This string defines the header, title and start of the document body
  33. //
  34. #define HTML_DOCUMENT_HEADER "<!DOCTYPE HTML PUBLIC \"-//IETF//DTD HTML//EN\">\r\n" \
  35. "<HTML>\r\n" \
  36. "<HEAD>\r\n" \
  37. "<TITLE>%s</TITLE>\r\n" \
  38. "</HEAD>\r\n" \
  39. "<BODY>\r\n" \
  40. "<H2>%s</H2>\r\n"
  41. #ifdef EXTENDED_ERROR_HTML
  42. //
  43. // HTML_ERROR_DOCUMENT_HEADER - error variant of standard document header
  44. //
  45. #define HTML_ERROR_DOCUMENT_HEADER "<!DOCTYPE HTML PUBLIC \"-//IETF//DTD HTML//EN\">\r\n" \
  46. "<HTML>\r\n" \
  47. "<HEAD>\r\n" \
  48. "<TITLE>%s</TITLE>\r\n" \
  49. "</HEAD>\r\n" \
  50. "<BODY>\r\n" \
  51. "<H2>%s</H2>\r\n" \
  52. "<HR>\r\n"
  53. #endif
  54. //
  55. // HTML_DOCUMENT_PREFORMAT - directory entries are preformatted to stop the HTML
  56. // viewer putting its own interpretation on the listing
  57. //
  58. #define HTML_DOCUMENT_PREFORMAT "<PRE>\r\n"
  59. //
  60. // HTML_DOCUMENT_END_PREFORMAT - this is required after the last directory entry
  61. // to allow the viewer to resume HTML formatting
  62. //
  63. #define HTML_DOCUMENT_END_PREFORMAT "</PRE>\r\n"
  64. //
  65. // HTML_HORIZONTAL_RULE - we separate each part of the document with a horizontal
  66. // rule line
  67. //
  68. #define HTML_HORIZONTAL_RULE "<HR>\r\n"
  69. //
  70. // HTML_FTP_WELCOME_START - for FTP directories, we display the FTP welcome
  71. // message
  72. //
  73. #define HTML_FTP_WELCOME_START HTML_HORIZONTAL_RULE \
  74. "<H4><PRE>\r\n"
  75. //
  76. // HTML_FTP_WELCOME_END - finish off at end of FTP welcome message
  77. //
  78. #define HTML_FTP_WELCOME_END "</PRE></H4>\r\n"
  79. //
  80. // HTML_DOCUMENT_DIR_START - when creating a directory listing document, we
  81. // follow the header with a Horizontal Rule (<HR>)
  82. //
  83. #define HTML_DOCUMENT_DIR_START HTML_HORIZONTAL_RULE \
  84. HTML_DOCUMENT_PREFORMAT
  85. //
  86. // HTML_DOCUMENT_FTP_DIR_START - same as HTML_DOCUMENT_DIR_START, but used to
  87. // add an additional "back up one level" if FTP and not the root
  88. //
  89. #define HTML_DOCUMENT_FTP_DIR_START HTML_HORIZONTAL_RULE \
  90. "%s" \
  91. HTML_DOCUMENT_PREFORMAT
  92. //
  93. // HTML_DOCUMENT_DIR_END - string that appears at the end of the preformatted
  94. // directory list, but before the end of the document
  95. //
  96. #define HTML_DOCUMENT_DIR_END HTML_DOCUMENT_END_PREFORMAT \
  97. HTML_HORIZONTAL_RULE
  98. //
  99. // HTML_ISINDEX - string that causes browser to display search form
  100. //
  101. #define HTML_ISINDEX "<ISINDEX>"
  102. //
  103. // HTML_DOCUMENT_FOOTER - this goes at the end of every HTML document we create
  104. //
  105. #define HTML_DOCUMENT_FOOTER "</BODY>\r\n" \
  106. "</HTML>\r\n"
  107. //
  108. // HTML_DOCUMENT_DIR_ENTRY - each directory entry is formatted using the
  109. // following string. Directories are bold
  110. //
  111. #define HTML_DOCUMENT_DIR_ENTRY "%s %s <A HREF=\"%s\"><B>%s</B></A>\r\n"
  112. //
  113. // HTML_DOCUMENT_FILE_ENTRY - same as HTML_DOCUMENT_DIR_ENTRY, except link is
  114. // not bold
  115. //
  116. #define HTML_DOCUMENT_FILE_ENTRY "%s %s <A HREF=\"%s\">%s</A>\r\n"
  117. #define SCRATCH_PAD_SIZE 1024
  118. #define FTP_WELCOME_INTRO "230-"
  119. #define FTP_WELCOME_INTRO_LENGTH (sizeof(FTP_WELCOME_INTRO) - 1)
  120. #define MAX_FMT_BUF 200 // size of buffer for loading title format string resource
  121. char szDir[32];
  122. char szSearch[32];
  123. //
  124. // private prototypes
  125. //
  126. PRIVATE
  127. DWORD
  128. MakeHtmlDirEntry(
  129. IN HINTERNET_HANDLE_TYPE HandleType,
  130. IN LPSTR PathPrefix,
  131. IN LPVOID DirBuffer,
  132. IN LPBYTE EntryBuffer,
  133. IN OUT LPDWORD BufferLength,
  134. OUT LPSTR* DirEntry
  135. );
  136. //
  137. // functions
  138. //
  139. BOOL
  140. ReadHtmlUrlData(
  141. IN HINTERNET hInternet,
  142. IN LPVOID lpBuffer,
  143. IN DWORD dwBufferLength,
  144. OUT LPDWORD lpdwBytesReturned
  145. )
  146. /*++
  147. Routine Description:
  148. Converts an FTP or gopher file or directory listing to a HTML document. This
  149. function is a wrapper for InternetReadFile() and InternetFindNext(), and so
  150. returns BOOL: the error code proper is set by the appropriate API
  151. Arguments:
  152. hInternet - handle of file or find object
  153. lpBuffer - pointer to caller's buffer
  154. dwBufferLength - size of lpBuffer on input
  155. lpdwBytesReturned - pointer to returned number of bytes read into lpBuffer
  156. Return Value:
  157. BOOL
  158. Success - TRUE
  159. Failure - FALSE. Call GetLastError() for more info
  160. --*/
  161. {
  162. DEBUG_ENTER((DBG_INET,
  163. Bool,
  164. "ReadHtmlUrlData",
  165. "%#x, %#x, %d, %#x",
  166. hInternet,
  167. lpBuffer,
  168. dwBufferLength,
  169. lpdwBytesReturned
  170. ));
  171. DWORD error;
  172. HINTERNET_HANDLE_TYPE handleType;
  173. BOOL success;
  174. LPSTR url;
  175. DWORD charsCopied;
  176. LPBYTE buffer;
  177. BYTE dirBuffer[max(sizeof(WIN32_FIND_DATA), sizeof(GOPHER_FIND_DATA))];
  178. BOOL done;
  179. HTML_STATE htmlState;
  180. HTML_STATE previousHtmlState;
  181. BYTE scratchPad[SCRATCH_PAD_SIZE];
  182. BOOL dataCopied;
  183. LPSTR lastDirEntry;
  184. LPSTR urlCopy;
  185. //
  186. // initialize variables in case we quit early
  187. //
  188. htmlState = previousHtmlState = HTML_STATE_INVALID;
  189. urlCopy = NULL;
  190. //
  191. // retrieve some information from the file/find handle - the handle type,
  192. // URL string, current HTML document state and previous failed dir entry
  193. //
  194. error = RGetHandleType(hInternet, &handleType);
  195. if (error != ERROR_SUCCESS) {
  196. goto quit;
  197. }
  198. error = RGetUrl(hInternet, &url);
  199. if (error != ERROR_SUCCESS) {
  200. goto quit;
  201. }
  202. error = RGetHtmlState(hInternet, &htmlState);
  203. previousHtmlState = htmlState;
  204. if (error != ERROR_SUCCESS) {
  205. goto quit;
  206. }
  207. error = RGetDirEntry(hInternet, &lastDirEntry);
  208. if (error != ERROR_SUCCESS) {
  209. goto quit;
  210. }
  211. //
  212. // initialize variables for loop
  213. //
  214. *lpdwBytesReturned = dwBufferLength;
  215. charsCopied = 0;
  216. buffer = (LPBYTE)lpBuffer;
  217. done = FALSE;
  218. success = TRUE;
  219. dataCopied = FALSE;
  220. //
  221. // first, maybe we already have data available from a previous
  222. // QueryDataAvailable operation
  223. //
  224. #ifdef EXTENDED_ERROR_HTML
  225. INET_ASSERT((handleType == TypeFtpFindHandleHtml)
  226. || (handleType == TypeFtpFileHandleHtml)
  227. || (handleType == TypeGopherFindHandleHtml));
  228. #else
  229. INET_ASSERT((handleType == TypeFtpFindHandleHtml)
  230. || (handleType == TypeGopherFindHandleHtml));
  231. #endif
  232. if (handleType == TypeFtpFindHandleHtml) {
  233. FTP_FIND_HANDLE_OBJECT * pObject = (FTP_FIND_HANDLE_OBJECT *)hInternet;
  234. if (pObject->HaveQueryData()) {
  235. dwBufferLength -= pObject->CopyQueriedData(lpBuffer, dwBufferLength);
  236. goto quit;
  237. }
  238. }
  239. #ifdef EXTENDED_ERROR_HTML
  240. else if (handleType == TypeFtpFileHandleHtml) {
  241. FTP_ERROR_HANDLE_OBJECT * pObject = (FTP_ERROR_HANDLE_OBJECT *)hInternet;
  242. if (pObject->HaveQueryData()) {
  243. dwBufferLength -= pObject->CopyQueriedData(lpBuffer, dwBufferLength);
  244. goto quit;
  245. }
  246. }
  247. #endif
  248. else
  249. {
  250. GOPHER_FIND_HANDLE_OBJECT * pObject = (GOPHER_FIND_HANDLE_OBJECT *)hInternet;
  251. if (pObject->HaveQueryData()) {
  252. dwBufferLength -= pObject->CopyQueriedData(lpBuffer, dwBufferLength);
  253. goto quit;
  254. }
  255. }
  256. //
  257. // loop round, writing as much data as we are able to the caller's buffer
  258. //
  259. do {
  260. switch (htmlState) {
  261. case HTML_STATE_START:
  262. //
  263. // if we are dealing with files then we go straight to copying the
  264. // file contents - we don't encapsulate files in HTML
  265. //
  266. #ifdef EXTENDED_ERROR_HTML
  267. if (handleType == TypeFtpFileHandleHtml) {
  268. //
  269. // FTP file handle is used for FTP errors
  270. //
  271. charsCopied = (DWORD)wsprintf((LPSTR)scratchPad,
  272. HTML_ERROR_DOCUMENT_HEADER,
  273. "FTP Error",
  274. "FTP Error"
  275. );
  276. if (charsCopied > dwBufferLength) {
  277. //
  278. // caller's buffer not large enough to fit header - clean up
  279. // and allow the caller to retry
  280. //
  281. success = FALSE;
  282. error = ERROR_INSUFFICIENT_BUFFER;
  283. done = TRUE;
  284. } else {
  285. //
  286. // copy the header to the caller's buffer
  287. //
  288. memcpy(buffer, scratchPad, charsCopied);
  289. dataCopied = TRUE;
  290. }
  291. htmlState = HTML_STATE_ERROR_BODY;
  292. } else if (handleType == TypeGopherFileHandleHtml)
  293. #else
  294. if ((handleType == TypeFtpFileHandleHtml)
  295. || (handleType == TypeGopherFileHandleHtml))
  296. #endif
  297. {
  298. htmlState = HTML_STATE_BODY;
  299. }
  300. else
  301. {
  302. htmlState = (HTML_STATE)((int)htmlState + 1);
  303. }
  304. break;
  305. case HTML_STATE_HEADER: {
  306. //
  307. // crack the URL for the info we need (i) for the header (ii) for
  308. // FTP file paths
  309. //
  310. DWORD urlLength;
  311. LPSTR host;
  312. DWORD hostLength;
  313. char title[MAX_FMT_BUF + INTERNET_MAX_PATH_LENGTH + 1];
  314. char hostName[INTERNET_MAX_HOST_NAME_LENGTH + 1];
  315. //
  316. // first, make a copy of the URL, reserving 2 extra characters in
  317. // case this is an FTP request and we need to add a root directory
  318. // path
  319. //
  320. urlLength = strlen(url);
  321. urlCopy = NEW_MEMORY(urlLength + 2, char);
  322. if (urlCopy == NULL) {
  323. error = ERROR_NOT_ENOUGH_MEMORY;
  324. goto quit;
  325. }
  326. memcpy(urlCopy, url, urlLength + 1);
  327. //
  328. // get the start of the URL path and the host name. CrackUrl() will
  329. // destroy url but leave urlCopy intact
  330. //
  331. error = CrackUrl(urlCopy,
  332. 0, // dwUrlLength
  333. FALSE, // decode the url-path
  334. NULL, // we don't care about the scheme
  335. NULL, // or scheme name
  336. NULL,
  337. &host, // we want the host name (for title)
  338. &hostLength,
  339. NULL, // or the port
  340. NULL, // or the user name
  341. NULL,
  342. NULL, // or the password
  343. NULL,
  344. &url, // we want the url-path
  345. &urlLength,
  346. NULL,
  347. NULL,
  348. NULL
  349. );
  350. if (error != ERROR_SUCCESS) {
  351. //
  352. // handle still has previous URL pointer, but it will be deleted
  353. // when the handle is closed
  354. //
  355. goto quit;
  356. }
  357. //
  358. // ensure the host name is zero-terminated
  359. //
  360. hostLength = (DWORD)min(hostLength, sizeof(hostName) - 1);
  361. memcpy(hostName, host, hostLength);
  362. hostName[hostLength] = '\0';
  363. //
  364. // same for the URL-path
  365. //
  366. url[urlLength] = '\0';
  367. //
  368. // create the header/title
  369. //
  370. char szFmt[MAX_FMT_BUF];
  371. szFmt[0] = 0; // guard against LoadString failing (why would it?)
  372. if (handleType == TypeGopherFindHandleHtml) {
  373. GOPHER_FIND_HANDLE_OBJECT* pGopherFind =
  374. (GOPHER_FIND_HANDLE_OBJECT *) hInternet;
  375. switch (pGopherFind->GetFixedType()) {
  376. case GOPHER_TYPE_CSO:
  377. LoadString (GlobalDllHandle, IDS_GOPHER_CSO,
  378. szFmt, sizeof(szFmt));
  379. wsprintf (title, szFmt, hostName);
  380. break;
  381. case GOPHER_TYPE_INDEX_SERVER:
  382. LoadString (GlobalDllHandle, IDS_GOPHER_INDEX,
  383. szFmt, sizeof(szFmt));
  384. wsprintf (title, szFmt, hostName);
  385. break;
  386. default:
  387. if ((*url == '\0') || (memcmp(url, "/", 2) == 0)) {
  388. LoadString (GlobalDllHandle, IDS_GOPHER_ROOT,
  389. szFmt, sizeof(szFmt));
  390. wsprintf(title, szFmt, hostName);
  391. } else {
  392. LoadString (GlobalDllHandle, IDS_GOPHER_DIR,
  393. szFmt, sizeof(szFmt));
  394. wsprintf(title, szFmt, hostName);
  395. }
  396. }
  397. } else if (handleType == TypeFtpFindHandleHtml) {
  398. if ((*url == '\0') || (memcmp(url, "/", 2) == 0)) {
  399. LoadString (GlobalDllHandle, IDS_FTP_ROOT,
  400. szFmt, sizeof(szFmt));
  401. wsprintf(title, szFmt, hostName);
  402. } else {
  403. LoadString (GlobalDllHandle, IDS_FTP_DIR,
  404. szFmt, sizeof(szFmt));
  405. wsprintf(title, szFmt, url, hostName);
  406. }
  407. } else {
  408. title[0] = '\0';
  409. }
  410. charsCopied = (DWORD)wsprintf((LPSTR)scratchPad,
  411. HTML_DOCUMENT_HEADER,
  412. title,
  413. title
  414. );
  415. if (charsCopied > dwBufferLength) {
  416. //
  417. // caller's buffer not large enough to fit header - clean up
  418. // and allow the caller to retry
  419. //
  420. success = FALSE;
  421. error = ERROR_INSUFFICIENT_BUFFER;
  422. done = TRUE;
  423. } else {
  424. //
  425. // copy the header to the caller's buffer
  426. //
  427. memcpy(buffer, scratchPad, charsCopied);
  428. dataCopied = TRUE;
  429. //
  430. // if the URL contains a NULL path then point it at the FTP
  431. // root. Likewise, end any and all directory paths with '/'
  432. //
  433. if (handleType == TypeFtpFindHandleHtml) {
  434. if (urlLength == 0) {
  435. *url = '/';
  436. ++urlLength;
  437. } else if ((url[urlLength - 1] != '/')
  438. && (url[urlLength - 1] != '\\')) {
  439. url[urlLength++] = '/';
  440. }
  441. url[urlLength] = '\0';
  442. } else {
  443. url = NULL;
  444. }
  445. //
  446. // free the URL in the handle object if gopher, else (FTP) set
  447. // it to be the path part of the URL. The previous string will
  448. // be deleted, and a new one allocated
  449. //
  450. RSetUrl(hInternet, url);
  451. //
  452. // we can now start on the dir header
  453. //
  454. htmlState = (HTML_STATE)((int)htmlState + 1);
  455. }
  456. break;
  457. }
  458. case HTML_STATE_WELCOME: {
  459. LPSTR lastInfo;
  460. DWORD lastInfoLength;
  461. INTERNET_CONNECT_HANDLE_OBJECT * pConnect;
  462. BOOL freeLastResponseInfo;
  463. if ( handleType == TypeGopherFindHandleHtml
  464. && ((GOPHER_FIND_HANDLE_OBJECT *) hInternet)->GetFixedType()) {
  465. //
  466. // BUGBUG - wimp out on CSO searches for now.
  467. //
  468. if (((GOPHER_FIND_HANDLE_OBJECT *) hInternet)->GetFixedType()
  469. == GOPHER_TYPE_CSO) {
  470. success = TRUE;
  471. dataCopied = FALSE;
  472. htmlState = HTML_STATE_FOOTER;
  473. break;
  474. }
  475. charsCopied = sizeof(HTML_ISINDEX);
  476. if (dwBufferLength < charsCopied) {
  477. success = FALSE;
  478. } else {
  479. memcpy (buffer, HTML_ISINDEX, charsCopied);
  480. dataCopied = TRUE;
  481. success = TRUE;
  482. htmlState = HTML_STATE_FOOTER;
  483. }
  484. break;
  485. }
  486. pConnect = (INTERNET_CONNECT_HANDLE_OBJECT *)
  487. ((HANDLE_OBJECT *)hInternet)->GetParent();
  488. lastInfo = pConnect->GetLastResponseInfo(&lastInfoLength);
  489. //
  490. // if not an FTP find operation then we don't check for any welcome
  491. // message, or other text from the server. Just go to the next state
  492. //
  493. if ((handleType == TypeFtpFindHandleHtml) && (lastInfo != NULL)) {
  494. //
  495. // find the welcome message. This starts with "230-" and can
  496. // continue over multiple lines, each of which may start with
  497. // "230-", or each may start with a line beginning only with a
  498. // space
  499. //
  500. LPSTR p = strstr(lastInfo, FTP_WELCOME_INTRO);
  501. if (p != NULL) {
  502. //
  503. // first, copy the separator
  504. //
  505. LPBYTE pBuffer = buffer;
  506. DWORD bufferLeft = dwBufferLength;
  507. if (bufferLeft >= (sizeof(HTML_FTP_WELCOME_START) - 1)) {
  508. memcpy(pBuffer,
  509. HTML_FTP_WELCOME_START,
  510. sizeof(HTML_FTP_WELCOME_START) - 1
  511. );
  512. pBuffer += sizeof(HTML_FTP_WELCOME_START) - 1;
  513. bufferLeft -= sizeof(HTML_FTP_WELCOME_START) - 1;
  514. charsCopied = sizeof(HTML_FTP_WELCOME_START) - 1;
  515. } else {
  516. //
  517. // caller's buffer not large enough to fit header -
  518. // clean up and allow the caller to retry
  519. //
  520. success = FALSE;
  521. error = ERROR_INSUFFICIENT_BUFFER;
  522. done = TRUE;
  523. freeLastResponseInfo = FALSE;
  524. goto quit_welcome;
  525. }
  526. //
  527. // then copy each welcome message line, dropping the "230-"
  528. // from each
  529. //
  530. do {
  531. if (!strncmp(p, FTP_WELCOME_INTRO, FTP_WELCOME_INTRO_LENGTH)) {
  532. p += FTP_WELCOME_INTRO_LENGTH;
  533. } else if (*p != ' ') {
  534. //
  535. // line doesn't start with "230-" or a space. We're
  536. // done
  537. //
  538. break;
  539. }
  540. LPSTR lastChar;
  541. DWORD len;
  542. lastChar = strchr(p, '\n');
  543. if (lastChar != NULL) {
  544. len = (DWORD)(lastChar - p) + 1;
  545. } else {
  546. len = strlen(p);
  547. }
  548. if (bufferLeft >= len) {
  549. memcpy(pBuffer, p, len);
  550. pBuffer += len;
  551. p += len;
  552. bufferLeft -= len;
  553. charsCopied += len;
  554. //
  555. // find start of next line, or end of buffer, in
  556. // case there are multiple line terminators
  557. //
  558. while ((*p == '\r') || (*p == '\n')) {
  559. ++p;
  560. }
  561. } else {
  562. //
  563. // caller's buffer not large enough to fit header -
  564. // clean up and allow the caller to retry
  565. //
  566. success = FALSE;
  567. error = ERROR_INSUFFICIENT_BUFFER;
  568. done = TRUE;
  569. freeLastResponseInfo = FALSE;
  570. break;
  571. }
  572. } while (TRUE);
  573. //
  574. // finish off
  575. //
  576. if (bufferLeft >= (sizeof(HTML_FTP_WELCOME_END) - 1)) {
  577. memcpy(pBuffer,
  578. HTML_FTP_WELCOME_END,
  579. sizeof(HTML_FTP_WELCOME_END) - 1
  580. );
  581. pBuffer += sizeof(HTML_FTP_WELCOME_END) - 1;
  582. bufferLeft -= sizeof(HTML_FTP_WELCOME_END) - 1;
  583. charsCopied += sizeof(HTML_FTP_WELCOME_END) - 1;
  584. //
  585. // successfully completed welcome message part
  586. //
  587. dataCopied = TRUE;
  588. freeLastResponseInfo = TRUE;
  589. htmlState = (HTML_STATE)((int)htmlState + 1);
  590. } else {
  591. //
  592. // caller's buffer not large enough to fit header -
  593. // clean up and allow the caller to retry
  594. //
  595. success = FALSE;
  596. error = ERROR_INSUFFICIENT_BUFFER;
  597. done = TRUE;
  598. freeLastResponseInfo = FALSE;
  599. }
  600. } else {
  601. //
  602. // last response info, but no welcome text. Continue
  603. //
  604. htmlState = (HTML_STATE)((int)htmlState + 1);
  605. freeLastResponseInfo = TRUE;
  606. charsCopied = 0;
  607. }
  608. } else {
  609. //
  610. // no info from server. Continue
  611. //
  612. htmlState = (HTML_STATE)((int)htmlState + 1);
  613. freeLastResponseInfo = FALSE;
  614. charsCopied = 0;
  615. }
  616. quit_welcome:
  617. if (freeLastResponseInfo) {
  618. pConnect->FreeLastResponseInfo();
  619. }
  620. break;
  621. }
  622. case HTML_STATE_DIR_HEADER:
  623. //
  624. // copy the directory listing introduction. N.B. this is mainly here
  625. // from the time when we had directory listings AND files being HTML
  626. // encapsulated. This is no longer true, so I could change things
  627. // somewhat
  628. //
  629. //
  630. // if FTP directory AND not root directory, display link to higher
  631. // level
  632. //
  633. DWORD cbTotal;
  634. char szUp[64];
  635. LPSTR lpszHtml;
  636. szUp[0] = 0;
  637. if (handleType == TypeFtpFindHandleHtml) {
  638. lpszHtml = HTML_DOCUMENT_FTP_DIR_START;
  639. cbTotal = sizeof(HTML_DOCUMENT_FTP_DIR_START);
  640. if ((*url != '\0') && (memcmp(url, "/", 2) != 0)) {
  641. cbTotal += LoadString(GlobalDllHandle,
  642. IDS_FTP_UPLEVEL,
  643. szUp,
  644. sizeof(szUp)
  645. );
  646. }
  647. } else {
  648. lpszHtml = HTML_DOCUMENT_DIR_START;
  649. cbTotal = sizeof(HTML_DOCUMENT_DIR_START);
  650. }
  651. if (dwBufferLength >= cbTotal) {
  652. charsCopied = wsprintf((char *)buffer, lpszHtml, szUp);
  653. dataCopied = TRUE;
  654. htmlState = (HTML_STATE)((int)htmlState + 1);
  655. } else {
  656. error = ERROR_INSUFFICIENT_BUFFER;
  657. success = FALSE;
  658. }
  659. break;
  660. case HTML_STATE_BODY:
  661. //
  662. // if we already have a formatted dir entry from last time (it
  663. // wouldn't fit in the buffer), then copy it now
  664. //
  665. if (lastDirEntry != NULL) {
  666. charsCopied = strlen(lastDirEntry);
  667. if (dwBufferLength >= charsCopied) {
  668. memcpy(buffer, lastDirEntry, charsCopied);
  669. //
  670. // we no longer require the string
  671. //
  672. RSetDirEntry(hInternet, NULL);
  673. lastDirEntry = NULL;
  674. success = TRUE;
  675. } else {
  676. //
  677. // still not enough room
  678. //
  679. error = ERROR_INSUFFICIENT_BUFFER;
  680. success = FALSE;
  681. done = TRUE;
  682. }
  683. //
  684. // in both cases fall out of the switch()
  685. //
  686. break;
  687. }
  688. //
  689. // read the next part of the HTML document
  690. //
  691. switch (handleType) {
  692. case TypeFtpFindHandleHtml:
  693. success = FtpFindNextFileA(hInternet,
  694. (LPWIN32_FIND_DATA)dirBuffer
  695. );
  696. goto create_dir_entry;
  697. case TypeGopherFindHandleHtml:
  698. //
  699. // Check if we simply can return fixed text for a search
  700. //
  701. success = GopherFindNextA(hInternet,
  702. (LPGOPHER_FIND_DATA)dirBuffer
  703. );
  704. //
  705. // directory listing - get the next directory entry, convert
  706. // to HTML and write to the caller's buffer. If there's not
  707. // enough space, return an error
  708. //
  709. create_dir_entry:
  710. if (success) {
  711. charsCopied = dwBufferLength;
  712. error = MakeHtmlDirEntry(handleType,
  713. url,
  714. dirBuffer,
  715. buffer,
  716. &charsCopied,
  717. &lastDirEntry
  718. );
  719. if (error != ERROR_SUCCESS) {
  720. //
  721. // if MakeHtmlDirEntry() created a copy of the directory
  722. // entry then store it in the handle object for next
  723. // time
  724. //
  725. if (lastDirEntry != NULL) {
  726. RSetDirEntry(hInternet, lastDirEntry);
  727. //
  728. // no longer need our copy of the string -
  729. // RSetLastDirEntry() also makes a copy and adds it
  730. // to the handle object
  731. //
  732. DEL_STRING(lastDirEntry);
  733. }
  734. //
  735. // probably out of buffer space - we're done
  736. //
  737. success = FALSE;
  738. done = TRUE;
  739. } else {
  740. dataCopied = TRUE;
  741. }
  742. } else if (GetLastError() == ERROR_NO_MORE_FILES) {
  743. //
  744. // change the error to success - ReadFile() doesn't return
  745. // ERROR_NO_MORE_FILES
  746. //
  747. SetLastError(ERROR_SUCCESS);
  748. //
  749. // reached the end of the directory listing - time to
  750. // add the footer
  751. //
  752. charsCopied = 0;
  753. success = TRUE;
  754. htmlState = (HTML_STATE)((int)htmlState + 1);
  755. }
  756. break;
  757. case TypeFtpFileHandleHtml:
  758. case TypeGopherFileHandleHtml:
  759. //
  760. // BUGBUG - this path never taken because we do not encapsulate
  761. // files?
  762. //
  763. //
  764. // file data - just read the next chunk
  765. //
  766. success = InternetReadFile(hInternet,
  767. (LPVOID)buffer,
  768. dwBufferLength,
  769. &charsCopied
  770. );
  771. if (success) {
  772. if (charsCopied == 0) {
  773. //
  774. // read all of file - we're done (no HTML to add for
  775. // files)
  776. //
  777. htmlState = HTML_STATE_END;
  778. } else {
  779. buffer += charsCopied;
  780. dwBufferLength -= charsCopied;
  781. dataCopied = TRUE;
  782. }
  783. }
  784. break;
  785. }
  786. break;
  787. case HTML_STATE_DIR_FOOTER:
  788. if (dwBufferLength >= sizeof(HTML_DOCUMENT_DIR_END) - 1) {
  789. memcpy(buffer,
  790. HTML_DOCUMENT_DIR_END,
  791. sizeof(HTML_DOCUMENT_DIR_END) - 1
  792. );
  793. charsCopied = sizeof(HTML_DOCUMENT_DIR_END) - 1;
  794. dataCopied = TRUE;
  795. htmlState = (HTML_STATE)((int)htmlState + 1);
  796. } else {
  797. error = ERROR_INSUFFICIENT_BUFFER;
  798. success = FALSE;
  799. }
  800. break;
  801. case HTML_STATE_FOOTER:
  802. //
  803. // copy the HTML document footer - don't copy the end-of-string
  804. // character ('\0')
  805. //
  806. if (dwBufferLength >= sizeof(HTML_DOCUMENT_FOOTER) - 1) {
  807. memcpy(buffer,
  808. HTML_DOCUMENT_FOOTER,
  809. sizeof(HTML_DOCUMENT_FOOTER) - 1
  810. );
  811. charsCopied = sizeof(HTML_DOCUMENT_FOOTER) - 1;
  812. //
  813. // the document is done
  814. //
  815. htmlState = (HTML_STATE)((int)htmlState + 1);
  816. dataCopied = TRUE;
  817. done = TRUE;
  818. } else if (charsCopied==0) {
  819. error = ERROR_INSUFFICIENT_BUFFER;
  820. success = FALSE;
  821. } else {
  822. // We're out of buffer space. but we've got everything else
  823. // so we'll just leave
  824. done = TRUE;
  825. }
  826. break;
  827. case HTML_STATE_END:
  828. done = TRUE;
  829. break;
  830. #ifdef EXTENDED_ERROR_HTML
  831. case HTML_STATE_ERROR_BODY:
  832. htmlState = HTML_STATE_FOOTER;
  833. break;
  834. #endif
  835. default:
  836. INET_ASSERT(FALSE);
  837. break;
  838. }
  839. if (success) {
  840. //
  841. // update the buffer pointer and remaining length by the number of
  842. // bytes read this time round the loop
  843. //
  844. buffer += charsCopied;
  845. dwBufferLength -= charsCopied;
  846. }
  847. } while ((dwBufferLength != 0) && success && !done);
  848. //
  849. // if we succeeded, update the amount of data returned, else set the last
  850. // error, which may be different from the last error set by the underlying
  851. // API (if any were called)
  852. //
  853. quit:
  854. //
  855. // remember the current state
  856. //
  857. if (htmlState != previousHtmlState) {
  858. RSetHtmlState(hInternet, htmlState);
  859. }
  860. //
  861. // if we quit because we ran out of buffer then only return an error if we
  862. // didn't copy *any* data
  863. //
  864. if ((error == ERROR_INSUFFICIENT_BUFFER) && dataCopied) {
  865. error = ERROR_SUCCESS;
  866. success = TRUE;
  867. }
  868. //
  869. // if we succeeded then return the amount of data read, else reset the last
  870. // error
  871. //
  872. if (error == ERROR_SUCCESS) {
  873. *lpdwBytesReturned -= dwBufferLength;
  874. } else {
  875. *lpdwBytesReturned = 0;
  876. SetLastError(error);
  877. success = FALSE;
  878. DEBUG_ERROR(INET, error);
  879. }
  880. //
  881. // clean up
  882. //
  883. if (urlCopy != NULL) {
  884. DEL(urlCopy);
  885. }
  886. //
  887. // return API-level success or failure indication
  888. //
  889. DEBUG_LEAVE(success);
  890. return success;
  891. }
  892. DWORD
  893. QueryHtmlDataAvailable(
  894. IN HINTERNET hInternet,
  895. OUT LPDWORD lpdwNumberOfBytesAvailable
  896. )
  897. /*++
  898. Routine Description:
  899. This function is called when the app is querying available data for a cooked
  900. (HTML) find handle. Because we have to cook the data before we can determine
  901. how much there is, we need to allocate a buffer and cook the data there
  902. Arguments:
  903. hInternet - MAPPED address of handle object
  904. lpdwNumberOfBytesAvailable - pointer to returned bytes available
  905. Return Value:
  906. DWORD
  907. Success - ERROR_SUCCESS
  908. Failure - ERROR_INTERNET_INCORRECT_HANDLE_TYPE
  909. Can only handle TypeFtpFindHandleHtml and
  910. TypeGopherFindHandleHtml
  911. ERROR_NOT_ENOUGH_MEMORY
  912. Ran out of memory allocating HTML buffer
  913. --*/
  914. {
  915. DEBUG_ENTER((DBG_INET,
  916. Dword,
  917. "QueryHtmlDataAvailable",
  918. "%#x, %#x",
  919. hInternet,
  920. lpdwNumberOfBytesAvailable
  921. ));
  922. DWORD error;
  923. switch (((HANDLE_OBJECT *)hInternet)->GetHandleType()) {
  924. case TypeFtpFindHandleHtml:
  925. error = ((FTP_FIND_HANDLE_OBJECT *)hInternet)->
  926. QueryHtmlDataAvailable(lpdwNumberOfBytesAvailable);
  927. break;
  928. #ifdef EXTENDED_ERROR_HTML
  929. case TypeFtpFileHandleHtml:
  930. error = ((FTP_ERROR_HANDLE_OBJECT *)hInternet)->
  931. QueryHtmlDataAvailable(lpdwNumberOfBytesAvailable);
  932. break;
  933. #endif
  934. case TypeGopherFindHandleHtml:
  935. error = ((GOPHER_FIND_HANDLE_OBJECT *)hInternet)->
  936. QueryHtmlDataAvailable(lpdwNumberOfBytesAvailable);
  937. break;
  938. default:
  939. error = ERROR_INTERNET_INCORRECT_HANDLE_TYPE;
  940. INET_ASSERT(FALSE);
  941. break;
  942. }
  943. DEBUG_LEAVE(error);
  944. return error;
  945. }
  946. //
  947. // private functions
  948. //
  949. PRIVATE
  950. DWORD
  951. MakeHtmlDirEntry(
  952. IN HINTERNET_HANDLE_TYPE HandleType,
  953. IN LPSTR PathPrefix,
  954. IN LPVOID DirBuffer,
  955. IN LPBYTE EntryBuffer,
  956. IN OUT LPDWORD BufferLength,
  957. OUT LPSTR* DirEntry
  958. )
  959. /*++
  960. Routine Description:
  961. Creates a single-line directory entry for a HTML document. The line is
  962. formatted thus (e.g.):
  963. " 12,345,678 Fri Jun 23 95 4:43pm pagefile.sys "
  964. " Directory Fri Jun 23 95 10:00am foobar.directory "
  965. " unknown.filesize "
  966. Arguments:
  967. HandleType - type of the handle - gopher or FTP (find + HTML)
  968. PathPrefix - string used to build (FTP) absolute paths
  969. DirBuffer - pointer to buffer containing GOPHER_FIND_DATA or
  970. WIN32_FIND_DATA
  971. EntryBuffer - pointer to buffer where HTML dir entry will be written
  972. BufferLength - IN: number of bytes available in EntryBuffer
  973. OUT: number of bytes written to EntryBuffer
  974. DirEntry - if we couldn't copy the directory entry to the HTML buffer
  975. then we return a pointer to a copy of the formatted
  976. directory entry. The caller should remember this and use
  977. it the next time the function is called, before getting
  978. the next dir entry
  979. Return Value:
  980. DWORD
  981. Success - ERROR_SUCCESS
  982. Failure - ERROR_INSUFFICIENT_BUFFER
  983. EntryBuffer is not large enough to hold this entry
  984. --*/
  985. {
  986. DEBUG_ENTER((DBG_INET,
  987. Dword,
  988. "MakeHtmlDirEntry",
  989. "%s (%d), %q, %#x, %#x, %#x [%d], %#x",
  990. InternetMapHandleType(HandleType),
  991. HandleType,
  992. PathPrefix,
  993. DirBuffer,
  994. EntryBuffer,
  995. BufferLength,
  996. *BufferLength,
  997. DirEntry
  998. ));
  999. BOOL isDir;
  1000. BOOL isSearch;
  1001. LPSTR entryName;
  1002. DWORD entrySize;
  1003. FILETIME entryTime;
  1004. SYSTEMTIME systemTime;
  1005. char timeBuf[80];
  1006. char sizeBuf[15]; // 4,294,967,295
  1007. char entryBuf[sizeof(HTML_DOCUMENT_DIR_ENTRY) + INTERNET_MAX_PATH_LENGTH];
  1008. char urlBuf[INTERNET_MAX_URL_LENGTH];
  1009. LPSTR url;
  1010. LPSTR pSizeDir;
  1011. DWORD nPrinted;
  1012. DWORD error;
  1013. BOOL haveTimeAndSize;
  1014. DWORD urlLength;
  1015. //
  1016. // ensure we are dealing with the correct handle type
  1017. //
  1018. INET_ASSERT((HandleType == TypeFtpFindHandleHtml)
  1019. || (HandleType == TypeGopherFindHandleHtml));
  1020. //
  1021. // get the common information we will use to create a directory entry line
  1022. //
  1023. if (HandleType == TypeFtpFindHandleHtml) {
  1024. isDir = ((LPWIN32_FIND_DATA)DirBuffer)->dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY;
  1025. isSearch = FALSE;
  1026. entryName = ((LPWIN32_FIND_DATA)DirBuffer)->cFileName;
  1027. entryTime = ((LPWIN32_FIND_DATA)DirBuffer)->ftLastWriteTime;
  1028. entrySize = ((LPWIN32_FIND_DATA)DirBuffer)->nFileSizeLow;
  1029. haveTimeAndSize = TRUE;
  1030. DWORD slen = strlen(PathPrefix);
  1031. DWORD len = (DWORD)min(slen, sizeof(urlBuf) - 1);
  1032. memcpy(urlBuf, PathPrefix, len);
  1033. slen = strlen(entryName);
  1034. slen = (DWORD)min(slen, (sizeof(urlBuf) - 1) - len);
  1035. memcpy(&urlBuf[len], entryName, slen);
  1036. len += slen;
  1037. if (isDir && (len < sizeof(urlBuf) - 1)) {
  1038. urlBuf[len] = '/';
  1039. ++len;
  1040. }
  1041. urlBuf[len] = '\0';
  1042. url = urlBuf;
  1043. } else {
  1044. isDir = IS_GOPHER_DIRECTORY(((LPGOPHER_FIND_DATA)DirBuffer)->GopherType);
  1045. isSearch = IS_GOPHER_INDEX_SERVER(((LPGOPHER_FIND_DATA)DirBuffer)->GopherType);
  1046. entryName = ((LPGOPHER_FIND_DATA)DirBuffer)->DisplayString;
  1047. GopherLocatorToUrl(((LPGOPHER_FIND_DATA)DirBuffer)->Locator,
  1048. urlBuf,
  1049. sizeof(urlBuf),
  1050. &urlLength
  1051. );
  1052. url = urlBuf;
  1053. if (IS_GOPHER_PLUS(((LPGOPHER_FIND_DATA)DirBuffer)->GopherType)) {
  1054. haveTimeAndSize = TRUE;
  1055. entryTime = ((LPGOPHER_FIND_DATA)DirBuffer)->LastModificationTime;
  1056. entrySize = ((LPGOPHER_FIND_DATA)DirBuffer)->SizeLow;
  1057. } else {
  1058. haveTimeAndSize = FALSE;
  1059. entryTime.dwLowDateTime = 0;
  1060. entryTime.dwHighDateTime = 0;
  1061. entrySize = 0;
  1062. }
  1063. }
  1064. //
  1065. // if we have a non-zero entry time AND we can convert it to a system time
  1066. // then create an internationalized time string
  1067. //
  1068. if ((entryTime.dwLowDateTime != 0)
  1069. && (entryTime.dwHighDateTime != 0)
  1070. && FileTimeToSystemTime(&entryTime, &systemTime)) {
  1071. int n;
  1072. //
  1073. // BUGBUG - if either of these fail for any reason, we should fill the
  1074. // string with the requisite-t-t-t-t-t number of spaces
  1075. //
  1076. n = GetDateFormat(0, // lcid
  1077. 0, // dwFlags
  1078. &systemTime,
  1079. "MM/dd/yyyy ",
  1080. timeBuf,
  1081. sizeof(timeBuf)
  1082. );
  1083. if (n > 0) {
  1084. //
  1085. // number of characters written to string becomes index in string
  1086. // at which to write time string
  1087. //
  1088. --n;
  1089. }
  1090. GetTimeFormat(0, // lcid
  1091. TIME_NOSECONDS,
  1092. &systemTime,
  1093. "hh:mmtt",
  1094. &timeBuf[n],
  1095. sizeof(timeBuf) - n
  1096. );
  1097. //
  1098. // convert any non-ANSI characters to the OEM charset
  1099. //
  1100. CharToOem(timeBuf, timeBuf);
  1101. } else {
  1102. memcpy(timeBuf, " ", 17);
  1103. }
  1104. //
  1105. // convert the entry size to a human-readable form
  1106. //
  1107. if (isDir) {
  1108. //
  1109. // directories show up as DOS-style <DIR> entries, except we use the
  1110. // full "Directory" text
  1111. //
  1112. //
  1113. // BUGBUG - needs to be in a resource. Also, if we are adding images
  1114. // <IMG> then we don't need to specify dir - it will be obvious
  1115. // from the display (but what about text-only? what about
  1116. // viewers that don't care to/can't load the image?)
  1117. //
  1118. if (!szDir[0]) {
  1119. LoadString(GlobalDllHandle, IDS_TAG_DIRECTORY, szDir, sizeof(szDir));
  1120. }
  1121. pSizeDir = szDir;
  1122. } else if (isSearch) {
  1123. //
  1124. // if this is a gopher item and it is an index server (7) then indicate
  1125. // the fact using this string
  1126. //
  1127. if (!szSearch[0]) {
  1128. LoadString(GlobalDllHandle, IDS_TAG_SEARCH, szSearch, sizeof(szSearch));
  1129. }
  1130. pSizeDir = szSearch;
  1131. } else if (haveTimeAndSize) {
  1132. pSizeDir = NiceNum(sizeBuf, entrySize, 14);
  1133. } else {
  1134. pSizeDir = " ";
  1135. }
  1136. //
  1137. // now create the HTML directory entry
  1138. //
  1139. //
  1140. // BUGBUG - at this point we need to call the MIME function to retrieve the
  1141. // URL for the image file that goes with this file type
  1142. //
  1143. nPrinted = wsprintf(entryBuf,
  1144. isDir
  1145. ? HTML_DOCUMENT_DIR_ENTRY
  1146. : HTML_DOCUMENT_FILE_ENTRY,
  1147. timeBuf,
  1148. pSizeDir,
  1149. url,
  1150. entryName
  1151. );
  1152. if (nPrinted <= *BufferLength) {
  1153. memcpy(EntryBuffer, entryBuf, nPrinted);
  1154. *BufferLength = nPrinted;
  1155. error = ERROR_SUCCESS;
  1156. } else {
  1157. //
  1158. // there is not enough space in the buffer to store this formatted
  1159. // directory entry. So since we've gone to the trouble of creating
  1160. // the string, we make a copy of it and return it to the caller. If
  1161. // NEW_STRING fails, then we will lose this directory entry, but it's
  1162. // not really a problem. (The alternative would be to return e.g.
  1163. // ERROR_NOT_ENOUGH_MEMORY and probably fail the entire request)
  1164. //
  1165. DEBUG_PRINT(INET,
  1166. WARNING,
  1167. ("failed to copy %q\n",
  1168. entryBuf
  1169. ));
  1170. *DirEntry = NEW_STRING(entryBuf);
  1171. if (*DirEntry == NULL) {
  1172. DEBUG_PRINT(INET,
  1173. WARNING,
  1174. ("failed to make copy of %q!\n",
  1175. entryBuf
  1176. ));
  1177. }
  1178. error = ERROR_INSUFFICIENT_BUFFER;
  1179. }
  1180. DEBUG_LEAVE(error);
  1181. return error;
  1182. }