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.

638 lines
18 KiB

  1. /*++
  2. Copyright (c) 1995 Microsoft Corporation
  3. Module Name:
  4. ftpapiu.cxx
  5. Abstract:
  6. Common sub-API level FTP functions (created from dll\parseurl.c)
  7. Contents:
  8. ParseFtpUrl
  9. Author:
  10. Richard L Firth (rfirth) 31-May-1995
  11. Environment:
  12. Win32 user-level DLL
  13. Revision History:
  14. 31-May-1995 rfirth
  15. Created
  16. --*/
  17. #include <wininetp.h>
  18. #include "ftpapih.h"
  19. // because wininet doesnt know IStream
  20. #define NO_SHLWAPI_STREAM
  21. #include <shlwapi.h>
  22. #include <shlwapip.h>
  23. //
  24. // functions
  25. //
  26. DWORD
  27. ParseFtpUrl(
  28. IN OUT LPHINTERNET lphInternet,
  29. IN LPSTR Url,
  30. IN DWORD SchemeLength,
  31. IN LPSTR Headers,
  32. IN DWORD HeadersLength,
  33. IN DWORD OpenFlags,
  34. IN DWORD_PTR Context
  35. )
  36. /*++
  37. Routine Description:
  38. URL parser for FTP URLs. Support function for InternetOpenUrl() and
  39. ParseUrl().
  40. This is a macro function that just cracks the URL and calls FTP APIs to
  41. do the work
  42. Arguments:
  43. lphInternet - IN: pointer to InternetOpen handle
  44. OUT: if successful handle of opened item, else undefined
  45. Url - pointer to string containing FTP URL to open
  46. SchemeLength - length of the URL scheme, exluding "://"
  47. Headers - unused for FTP
  48. HeadersLength - unused for FTP
  49. OpenFlags - optional flags for opening a file (cache/no-cache, etc.)
  50. Context - app-supplied context value for call-backs
  51. Return Value:
  52. DWORD
  53. Success - ERROR_SUCCESS
  54. Failure - ERROR_INTERNET_INVALID_URL
  55. The URL passed in could not be parsed
  56. --*/
  57. {
  58. DEBUG_ENTER((DBG_FTP,
  59. Dword,
  60. "ParseFtpUrl",
  61. "%#x [%#x], %q, %d, %#x, %d, %#x, %#x",
  62. lphInternet,
  63. *lphInternet,
  64. Url,
  65. SchemeLength,
  66. Headers,
  67. HeadersLength,
  68. OpenFlags,
  69. Context
  70. ));
  71. UNREFERENCED_PARAMETER(Headers);
  72. UNREFERENCED_PARAMETER(HeadersLength);
  73. //
  74. // parse out the name[:password] and host[:port] parts
  75. //
  76. DWORD urlLength;
  77. LPSTR pUserName;
  78. DWORD userNameLength;
  79. LPSTR pPassword;
  80. DWORD passwordLength;
  81. LPSTR pHostName;
  82. DWORD hostNameLength;
  83. INTERNET_PORT port;
  84. LPSTR lpszUrl = NULL, lpszBackup = NULL;
  85. char firstUrlPathCharacter;
  86. HINTERNET hConnect = NULL;
  87. DWORD error;
  88. // The passed in Url string gets munged during this function.
  89. // Make a copy, so the proper URL is set to the mapped connection handle.
  90. lpszUrl = NewString((LPCSTR)Url);
  91. if (lpszUrl == NULL)
  92. {
  93. error = ERROR_NOT_ENOUGH_MEMORY;
  94. goto quit;
  95. }
  96. lpszBackup = lpszUrl;
  97. lpszUrl += SchemeLength + sizeof("://") - 1;
  98. error = GetUrlAddress(&lpszUrl,
  99. &urlLength,
  100. &pUserName,
  101. &userNameLength,
  102. &pPassword,
  103. &passwordLength,
  104. &pHostName,
  105. &hostNameLength,
  106. &port,
  107. NULL
  108. );
  109. if (error != ERROR_SUCCESS) {
  110. goto quit;
  111. }
  112. //
  113. // we can safely zero-terminate the address parts - the '/' between address
  114. // info and url-path is not significant
  115. //
  116. //if (*Url == '/') {
  117. // ++Url;
  118. // --urlLength;
  119. //}
  120. if (pUserName != NULL) {
  121. pUserName[userNameLength] = '\0';
  122. }
  123. if (pPassword != NULL) {
  124. pPassword[passwordLength] = '\0';
  125. }
  126. //
  127. // now get the FTP file/directory information
  128. //
  129. BOOL isDirectory;
  130. if ((*lpszUrl == '\0') || (*(lpszUrl + 1) == '\0')) {
  131. //
  132. // if the URL just consisted of ftp://host then by default we are
  133. // referencing an FTP directory (the root directory)
  134. //
  135. isDirectory = TRUE;
  136. } else {
  137. LPSTR pSemiColon;
  138. pSemiColon = strchr(lpszUrl, ';');
  139. if (pSemiColon != NULL) {
  140. //
  141. // if there's not enough space left in the string after ';' for the
  142. // "type=?" substring, then assume this URL is bad
  143. //
  144. if ((urlLength - (pSemiColon - lpszUrl)) < 6) {
  145. error = ERROR_INTERNET_INVALID_URL;
  146. goto quit;
  147. }
  148. if (strnicmp(pSemiColon + 1, "type=", 5) == 0) {
  149. switch (tolower(*(pSemiColon + 6))) {
  150. case 'a':
  151. OpenFlags |= FTP_TRANSFER_TYPE_ASCII;
  152. isDirectory = FALSE;
  153. *pSemiColon = '\0';
  154. break;
  155. case 'i':
  156. OpenFlags |= FTP_TRANSFER_TYPE_BINARY;
  157. isDirectory = FALSE;
  158. *pSemiColon = '\0';
  159. break;
  160. case 'd':
  161. isDirectory = TRUE;
  162. break;
  163. default:
  164. error = ERROR_INTERNET_INVALID_URL;
  165. goto quit;
  166. }
  167. } else {
  168. //
  169. // found a ';', but not "type=". Don't understand this URL
  170. //
  171. error = ERROR_INTERNET_INVALID_URL;
  172. goto quit;
  173. }
  174. urlLength = (DWORD) (pSemiColon - lpszUrl);
  175. } else {
  176. //
  177. // there is no ;type= field to help us out. If the string ends in /
  178. // then it is a directory. Further, if the url-path refers to a
  179. // file, we don't know which mode to use to transfer it - ASCII or
  180. // BINARY. We'll default to binary
  181. //
  182. if (lpszUrl[urlLength - 1] == '/') {
  183. isDirectory = TRUE;
  184. } else {
  185. OpenFlags |= FTP_TRANSFER_TYPE_BINARY;
  186. isDirectory = FALSE;
  187. }
  188. }
  189. //
  190. // decode the url-path
  191. //
  192. if(FAILED(UrlUnescapeInPlace(lpszUrl, 0))){
  193. goto quit;
  194. }
  195. urlLength = lstrlen(lpszUrl);
  196. }
  197. //
  198. // we potentially need to go round this loop 3 times:
  199. //
  200. // 1. try to get the item from the cache
  201. // 2. try to get the item from the origin server
  202. // 3. only if we got an existing connect & the origin server request
  203. // failed, reopen the connect handle & try step 2 again
  204. //
  205. // however, we only need make one attempt if we're in OFFLINE mode - either
  206. // we can get the item from the cache, or we can't
  207. //
  208. HINTERNET hInternetMapped;
  209. //
  210. // BUGBUG - this function should receive the handle already mapped
  211. //
  212. error = MapHandleToAddress(*lphInternet, (LPVOID *)&hInternetMapped, FALSE);
  213. if (error != ERROR_SUCCESS) {
  214. goto quit;
  215. }
  216. INET_ASSERT(hInternetMapped != NULL);
  217. //
  218. // if the InternetOpen() handle was created in OFFLINE mode then is an
  219. // offline request
  220. //
  221. DEBUG_PRINT(FTP,
  222. INFO,
  223. ("ParseFtpUrl: pre-OpenFlags check: OpenFlags = %#x\n",
  224. OpenFlags
  225. ));
  226. OpenFlags |= ((INTERNET_HANDLE_OBJECT *)hInternetMapped)->GetInternetOpenFlags()
  227. & INTERNET_FLAG_OFFLINE;
  228. DEBUG_PRINT(FTP,
  229. INFO,
  230. ("ParseFtpUrl: post-OpenFlags check: OpenFlags = %#x\n",
  231. OpenFlags
  232. ));
  233. DereferenceObject((LPVOID)hInternetMapped);
  234. DWORD limit;
  235. limit = (OpenFlags & INTERNET_FLAG_OFFLINE) ? 1 : 3;
  236. //
  237. // resynchronize same as reload for FTP
  238. //
  239. if (OpenFlags & INTERNET_FLAG_RESYNCHRONIZE) {
  240. OpenFlags |= INTERNET_FLAG_RELOAD;
  241. DEBUG_PRINT(FTP,
  242. INFO,
  243. ("ParseFtpUrl: INTERNET_FLAG_RESYNCHRONIZE set\n"));
  244. }
  245. DWORD i;
  246. BOOL bFromCache;
  247. i = 0;
  248. bFromCache = (OpenFlags & INTERNET_FLAG_RELOAD || (GlobalUrlCacheSyncMode == WININET_SYNC_MODE_ALWAYS)) ? FALSE : TRUE;
  249. while (i < limit) {
  250. //
  251. // ok, all parts present and correct; open a handle to the FTP resource
  252. //
  253. DWORD dwFlags = OpenFlags;
  254. if (bFromCache) {
  255. //
  256. // attempting to get item from cache
  257. //
  258. dwFlags |= INTERNET_FLAG_OFFLINE;
  259. } else {
  260. //
  261. // performing net request
  262. //
  263. dwFlags |= INTERNET_FLAG_RELOAD;
  264. dwFlags &= ~INTERNET_FLAG_OFFLINE;
  265. }
  266. //
  267. // zero-terminating the host name will wipe out the first '/' of the
  268. // URL-path which we must restore before using
  269. //
  270. firstUrlPathCharacter = *lpszUrl;
  271. if (pHostName != NULL) {
  272. pHostName[hostNameLength] = '\0';
  273. }
  274. //
  275. // record current online/offline state
  276. //
  277. BOOL bOffline = IsOffline();
  278. //
  279. // create a connect handle object or find an existing one if using
  280. // INTERNET_FLAG_EXISTING_CONNECT
  281. //
  282. if ( hConnect )
  283. {
  284. _InternetCloseHandle(hConnect); // nuke old connect handle, otherwise we leak.
  285. }
  286. hConnect = InternetConnectA(*lphInternet,
  287. pHostName,
  288. port,
  289. pUserName,
  290. pPassword,
  291. INTERNET_SERVICE_FTP,
  292. dwFlags,
  293. //
  294. // we are creating a "hidden" handle - don't
  295. // tell the app about it
  296. //
  297. INTERNET_NO_CALLBACK
  298. );
  299. //
  300. // restore URL-path, but only if its not '\0' - we may have a const
  301. // string (we can't write to it - we change to "/" below, which is a
  302. // const string)
  303. //
  304. if (*lpszUrl == '\0') {
  305. *(LPSTR)lpszUrl = firstUrlPathCharacter;
  306. }
  307. HINTERNET hConnectMapped = NULL;
  308. if (hConnect != NULL) {
  309. //
  310. // lock the handle by mapping it
  311. //
  312. error = MapHandleToAddress(hConnect,
  313. (LPVOID *)&hConnectMapped,
  314. FALSE
  315. );
  316. INET_ASSERT(error == ERROR_SUCCESS);
  317. INET_ASSERT(hConnectMapped != NULL);
  318. if (error != ERROR_SUCCESS) {
  319. break;
  320. }
  321. INTERNET_CONNECT_HANDLE_OBJECT * pConnectMapped;
  322. pConnectMapped = (INTERNET_CONNECT_HANDLE_OBJECT *)hConnectMapped;
  323. //
  324. // the ref count should be 2: either we created the connect handle
  325. // or we picked up an EXISTING_CONNECT handle which should not be
  326. // used by any other requests
  327. //
  328. INET_ASSERT(pConnectMapped->ReferenceCount() == 2);
  329. //
  330. // first off, associate the last response info, possibly including
  331. // the server welcome message, with the connection
  332. //
  333. pConnectMapped->AttachLastResponseInfo();
  334. pConnectMapped->SetURL(Url);
  335. HINTERNET hRequest;
  336. if (isDirectory) {
  337. if (*lpszUrl == '\0') {
  338. lpszUrl = "/";
  339. }
  340. //
  341. // if we are reading from cache then set the working directory
  342. // locally, else also set the CWD at the server
  343. //
  344. if (bFromCache) {
  345. error = pConnectMapped->SetCurrentWorkingDirectory((LPSTR)lpszUrl);
  346. } else if (FtpSetCurrentDirectory(hConnect, lpszUrl)) {
  347. error = ERROR_SUCCESS;
  348. } else {
  349. error = GetLastError();
  350. }
  351. if (error == ERROR_SUCCESS) {
  352. // if we are not asked to give raw data
  353. // then set htmlfind to TRUE
  354. if (!(dwFlags & INTERNET_FLAG_RAW_DATA)) {
  355. pConnectMapped->SetHtmlFind(TRUE);
  356. }
  357. hRequest = InternalFtpFindFirstFileA(hConnect,
  358. NULL,
  359. NULL,
  360. dwFlags,
  361. Context,
  362. bFromCache,
  363. pConnectMapped->IsHtmlFind() // allow empty
  364. );
  365. } else {
  366. hRequest = NULL;
  367. }
  368. } else /* if (!isDirectory) */ {
  369. hRequest = InternalFtpOpenFileA(hConnect,
  370. lpszUrl,
  371. GENERIC_READ,
  372. dwFlags,
  373. Context,
  374. bFromCache
  375. );
  376. //
  377. // we may have failed because we're not trying to get a file
  378. // after all - we've been given a directory without a trailing
  379. // slash
  380. //
  381. if (hRequest == NULL) {
  382. if (!(dwFlags & INTERNET_FLAG_RAW_DATA)) {
  383. pConnectMapped->SetHtmlFind(TRUE);
  384. }
  385. error = pConnectMapped->SetCurrentWorkingDirectory((LPSTR)lpszUrl);
  386. INET_ASSERT(error == ERROR_SUCCESS);
  387. if (error == ERROR_SUCCESS) {
  388. if (!bFromCache) {
  389. if (!FtpSetCurrentDirectory(hConnect, lpszUrl)) {
  390. error = GetLastError();
  391. }
  392. }
  393. if (error == ERROR_SUCCESS) {
  394. hRequest = InternalFtpFindFirstFileA(
  395. hConnect,
  396. NULL,
  397. NULL,
  398. dwFlags,
  399. Context,
  400. bFromCache,
  401. pConnectMapped->IsHtmlFind()
  402. );
  403. }
  404. }
  405. }
  406. }
  407. //
  408. // link the request and connect handles so that the connect handle
  409. // object will be deleted when the request handle is closed
  410. //
  411. if (hRequest != NULL) {
  412. HINTERNET hRequestMapped = NULL;
  413. error = MapHandleToAddress(hRequest,
  414. (LPVOID *)&hRequestMapped,
  415. FALSE
  416. );
  417. if (error == ERROR_SUCCESS) {
  418. RSetParentHandle(hRequestMapped, hConnectMapped, TRUE);
  419. }
  420. //
  421. // dereference the handles referenced by MapHandleToAddress()
  422. //
  423. if (hRequestMapped != NULL) {
  424. DereferenceObject((LPVOID)hRequestMapped);
  425. }
  426. //
  427. // return the request handle
  428. //
  429. *lphInternet = hRequest;
  430. }
  431. //
  432. // unmap and dereference the connect handle
  433. //
  434. DereferenceObject((LPVOID)hConnectMapped);
  435. //
  436. // if we succeeded in opening the item then we're done
  437. //
  438. if (hRequest != NULL) {
  439. break;
  440. } else {
  441. error = GetLastError();
  442. //
  443. // close the handle without modifying the per-thread handle and
  444. // context values
  445. //
  446. //DWORD closeError = _InternetCloseHandleNoContext(hConnect);
  447. DWORD closeError = ERROR_SUCCESS;
  448. INET_ASSERT(closeError == ERROR_SUCCESS);
  449. //
  450. // if we failed because we went offline then make a cache
  451. // request if we can
  452. //
  453. if (IsOffline() && !bFromCache) {
  454. //
  455. // this will be the last chance
  456. //
  457. bFromCache = TRUE;
  458. continue;
  459. }
  460. }
  461. } else {
  462. //
  463. // InternetConnect() failed. If the offline state didn't change then
  464. // its a real error - quit
  465. //
  466. error = GetLastError();
  467. if (IsOffline() == bOffline) {
  468. break;
  469. }
  470. //
  471. // we must have transitioned offline state. If we went offline then
  472. // attempt to read from cache only
  473. //
  474. if (IsOffline() && !bFromCache) {
  475. bFromCache = TRUE;
  476. continue;
  477. }
  478. }
  479. //
  480. // next iteration - second & subsequent not from cache unless we are
  481. // offline
  482. //
  483. bFromCache = FALSE;
  484. ++i;
  485. }
  486. quit:
  487. if (lpszBackup)
  488. FREE_MEMORY(lpszBackup);
  489. DEBUG_LEAVE(error);
  490. return error;
  491. }