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.

1475 lines
41 KiB

  1. //---------------------------------------------------------------------------
  2. //
  3. // COOKIE.CXX
  4. //
  5. // Cookie Jar
  6. //
  7. // This file implements cookies as defined by Navigator 4 behavior and the
  8. // specification at http://www.netscape.com/newsref/std/cookie_spec.html.
  9. // If Navigator 4 and the specification are not in agreement, we try to
  10. // match the Navigator 4 behavior.
  11. //
  12. // The following describes some interesting aspects of cookie behavior.
  13. //
  14. // SYNTAX
  15. //
  16. // Syntax for cookie is
  17. //
  18. // [[name]=] value [; options]
  19. //
  20. // The name is everything before "=" with leading and trailing whitespace
  21. // removed. The value is everything after "=" and before ";" with leading
  22. // and trailing whitespace removed. The name and value can contain spaces,
  23. // quotes or any other character except ";" and "=". The name and equal
  24. // sign are optional.
  25. //
  26. // Example: =foo -> name: <blank> value: foo
  27. // foo -> name: <blank> value: foo
  28. // foo= -> name: foo value: <blank>
  29. // ; -> name: <blank> value: <blank>
  30. //
  31. // ORDER
  32. //
  33. // Cookies with a more specific path are sent before cookies with
  34. // a less specific path mapping. The domain does not contibute
  35. // to the ordering of cookies.
  36. //
  37. // If the path length of two cookies are equal, then the cookies
  38. // are ordered by time of creation. Navigator maintains this
  39. // ordering across domain and path boundaries. IE maintains this
  40. // ordering for a specific domain and path. It is difficult to match
  41. // the Navigator behavior and there are no known bugs because of
  42. // this difference.
  43. //
  44. // MATCHING
  45. //
  46. // Path matches are done at the character level. Any
  47. // directory structure in the path is ignored.
  48. //
  49. // Navigator matches domains at the character level and ignores
  50. // the structure of the domain name.
  51. //
  52. // Previous versions of IE tossed the leading "." on a domain name.
  53. // With out this information, character by character compares are
  54. // can produce incorrect results. For backwards compatibilty with
  55. // old cookie we continue to match on a component by component basis.
  56. //
  57. // Some examples of the difference are:
  58. //
  59. // Cookie domain Document domain Navigator match IE match
  60. // .foo.com foo.com no yes
  61. // bar.x.com foobar.x.com yes no
  62. //
  63. // ACCEPTING COOKIES
  64. //
  65. // A cookie is rejected if the path specified in the set cookie
  66. // header is not a prefix of document's path.
  67. //
  68. // Navigator rejects a cookie if the domain specified in the
  69. // set cookie header does not contain at least two periods
  70. // or the domain is not a suffix of the document's domain.
  71. // The suffix match is done on a character by character basis.
  72. //
  73. // Navigator ignores all the stuff in the specification about
  74. // three period matching and the seven special top level domains.
  75. //
  76. // IE rejects a cookie if the domain specified by the cookie
  77. // header does not contain at least one embedded period or the
  78. // domain is not a suffix of the documents domain.
  79. //
  80. // Cookies are accepted if the path specified in the set cookie
  81. // header is a prefix of the document's path and the domain
  82. // specified in the set cookie header.
  83. //
  84. // The difference in behavior is a result of the matching rules
  85. // described in the previous section.
  86. //
  87. //---------------------------------------------------------------------------
  88. #include <wininetp.h>
  89. #include "httpp.h"
  90. //#include "..\inc\cookie.h"
  91. #include "cookiejar.h"
  92. #define CCH_COOKIE_MAX (5 * 1024)
  93. static char s_achEmpty[] = "";
  94. // Hard-coded list of special domains. If any of these are present between the
  95. // second-to-last and last dot we will require 2 embedded dots.
  96. // The domain strings are reversed to make the compares easier
  97. static const char *s_pachSpecialRestrictedDomains[] =
  98. {"MOC", "UDE", "TEN", "GRO", "VOG", "LIM", "TNI" };
  99. static const char s_chSpecialAllowedDomains[] = "vt."; // domains ending with ".tv" always only need one dot
  100. struct CookieInfo {
  101. char *pchRDomain;
  102. char *pchPath;
  103. char *pchName;
  104. char *pchValue;
  105. unsigned long dwFlags;
  106. FILETIME ftExpiration;
  107. };
  108. //---------------------------------------------------------------------------
  109. //
  110. // String utilities
  111. //
  112. //---------------------------------------------------------------------------
  113. static char *
  114. StrnDup(const char *pch, int cch)
  115. {
  116. char *pchAlloc = (char *)ALLOCATE_MEMORY(cch + 1);
  117. if (!pchAlloc)
  118. {
  119. SetLastError(ERROR_NOT_ENOUGH_MEMORY);
  120. return NULL;
  121. }
  122. memcpy(pchAlloc, pch, cch);
  123. pchAlloc[cch] = 0;
  124. return pchAlloc;
  125. }
  126. static BOOL
  127. IsPathMatch(const char *pchPrefix, const char *pchStr)
  128. {
  129. while (*pchPrefix == *pchStr && *pchStr)
  130. {
  131. pchPrefix += 1;
  132. pchStr += 1;
  133. }
  134. // if the match is incomplete, its not a valid path match.
  135. if (*pchPrefix != 0)
  136. return FALSE;
  137. //
  138. // Furthermore, reject matches like '/path1/' and '/path1/../path2'
  139. // We could try to canonicalize pchStr, but its sufficient to just
  140. //say any path with a '..' after the matched prefix doesn't match the
  141. //prefix. Usually WinHTTP canonicalizes the path before this point
  142. //anyhow.
  143. //
  144. while (*pchStr != 0)
  145. {
  146. pchStr++;
  147. if (*(pchStr-1) == '.'
  148. && *(pchStr) == '.')
  149. {
  150. return FALSE;
  151. }
  152. }
  153. return TRUE;
  154. }
  155. static BOOL
  156. IsDomainMatch(const char *pchPrefix, const char *pchStr)
  157. {
  158. while (*pchPrefix == *pchStr && *pchStr)
  159. {
  160. pchPrefix += 1;
  161. pchStr += 1;
  162. }
  163. return *pchPrefix == 0 && (*pchStr == 0 || *pchStr == '.');
  164. }
  165. static BOOL
  166. IsPathLegal(const char *pchHeader, const char *pchDocument)
  167. {
  168. UNREFERENCED_PARAMETER(pchHeader);
  169. UNREFERENCED_PARAMETER(pchDocument);
  170. return TRUE;
  171. }
  172. static BOOL
  173. IsSpecialDomain(const char *pch, int nCount)
  174. {
  175. for (int i = 0 ; i < ARRAY_ELEMENTS(s_pachSpecialRestrictedDomains) ; i++ )
  176. {
  177. if (StrCmpNIC(pch, s_pachSpecialRestrictedDomains[i], nCount) == 0)
  178. return TRUE;
  179. }
  180. return FALSE;
  181. }
  182. static BOOL
  183. IsDomainLegal(const char *pchHeader, const char *pchDocument)
  184. {
  185. const char *pchCurrent = pchHeader;
  186. int nSegment = 0;
  187. int dwCharCount = 0;
  188. int rgcch[2] = { 0, 0 }; // How many characters between dots
  189. // Must have at least one period in name.
  190. // and contains nothing but '.' is illegal
  191. int dwSegmentLength = 0;
  192. const char * pchSecondPart = NULL; // for a domain string such as
  193. BOOL fIPAddress = TRUE;
  194. for (; *pchCurrent; pchCurrent++)
  195. {
  196. if (isalpha(*pchCurrent))
  197. {
  198. fIPAddress = FALSE;
  199. }
  200. if (*pchCurrent == '.')
  201. {
  202. if (nSegment < 2)
  203. {
  204. // Remember how many characters we have between the last two dots
  205. // For example if domain header is .microsoft.com
  206. // rgcch[0] should be 3 for "com"
  207. // rgcch[1] should be 9 for "microsoft"
  208. rgcch[nSegment] = dwSegmentLength;
  209. if (nSegment == 1)
  210. {
  211. pchSecondPart = pchCurrent - dwSegmentLength;
  212. }
  213. }
  214. dwSegmentLength = 0;
  215. nSegment += 1;
  216. }
  217. else
  218. {
  219. dwSegmentLength++;
  220. }
  221. dwCharCount++;
  222. }
  223. // The code below depends on the leading dot being removed from the domain header.
  224. // The parse code does that, but an assert here just in case something changes in the
  225. // parse code.
  226. INET_ASSERT(*(pchCurrent - 1) != '.');
  227. if (fIPAddress)
  228. {
  229. // If we're given an IP address, we must match the entire IP address, not just a part
  230. while (*pchHeader == *pchDocument && *pchDocument)
  231. {
  232. pchHeader++;
  233. pchDocument++;
  234. }
  235. return !(*pchHeader || *pchDocument);
  236. }
  237. // Remember the count of the characters between the begining of the header and
  238. // the first dot. So for domain=abc.com this will set rgch[1] = 3.
  239. // Note that this assumes that if domain=.abc.com the leading dot has been stripped
  240. // out in the parse code. See assert above.
  241. if (nSegment < 2 )
  242. {
  243. rgcch[nSegment] = dwSegmentLength;
  244. if (nSegment==1)
  245. {
  246. pchSecondPart = pchCurrent - dwSegmentLength;
  247. }
  248. }
  249. // If the domain name is of the form abc.xx.yy where the number of characters between the last two dots is less than
  250. // 2 we require a minimum of two embedded dots. This is so you are not allowed to set cookies readable by all of .co.nz for
  251. // example. However this rule is not sufficient and we special case things like .edu.nz as well.
  252. // VENKATK: moved in following changes from wininet:
  253. // if last substring <= 2, and not .tv, and
  254. // second-last substring <=2 OR one of restricted special domains,
  255. // then it's not a minimal domain.
  256. int cEmbeddedDotsNeeded = 1;
  257. if(rgcch[0] <= 2
  258. && 0 != StrCmpNIC( pchHeader, s_chSpecialAllowedDomains, sizeof( s_chSpecialAllowedDomains) - 1)
  259. && (rgcch[1] <= 2
  260. || IsSpecialDomain(pchSecondPart, rgcch[1])))
  261. {
  262. cEmbeddedDotsNeeded = 2;
  263. }
  264. if (nSegment < cEmbeddedDotsNeeded || dwCharCount == nSegment)
  265. return FALSE;
  266. // Mismatch between header and document not allowed.
  267. // Must match full components of domain name.
  268. while (*pchHeader == *pchDocument && *pchDocument)
  269. {
  270. pchHeader += 1;
  271. pchDocument += 1;
  272. }
  273. return *pchHeader == 0 && (*pchDocument == 0 || *pchDocument == '.' );
  274. }
  275. void
  276. LowerCaseString(char *pch)
  277. {
  278. for (; *pch; pch++)
  279. {
  280. if (*pch >= 'A' && *pch <= 'Z')
  281. *pch += 'a' - 'A';
  282. }
  283. }
  284. void
  285. ReverseString(char *pchFront)
  286. {
  287. char *pchBack;
  288. char ch;
  289. int cch;
  290. cch = strlen(pchFront);
  291. pchBack = pchFront + cch - 1;
  292. cch = cch / 2;
  293. while (--cch >= 0)
  294. {
  295. ch = tolower(*pchFront);
  296. *pchFront = tolower(*pchBack);
  297. *pchBack = ch;
  298. pchFront += 1;
  299. pchBack -= 1;
  300. }
  301. }
  302. BOOL
  303. PathAndRDomainFromURL(const char *pchURL, char **ppchRDomain, char **ppchPath, BOOL *pfSecure, BOOL bStrip)
  304. {
  305. char *pchDomainBuf;
  306. char *pchRDomain = NULL;
  307. char *pchPathBuf;
  308. char *pchPath = NULL;
  309. char *pchExtra;
  310. DWORD cchDomain;
  311. DWORD cchPath;
  312. DWORD cchExtra;
  313. BOOL fSuccess = FALSE;
  314. DWORD dwError;
  315. INTERNET_SCHEME ustScheme;
  316. dwError = CrackUrl((char *)pchURL,
  317. 0,
  318. FALSE,
  319. &ustScheme,
  320. NULL, // Scheme Name
  321. NULL, // Scheme Lenth
  322. &pchDomainBuf,
  323. &cchDomain,
  324. TRUE,
  325. NULL, // Internet Port
  326. NULL, // UserName
  327. NULL, // UserName Length
  328. NULL, // Password
  329. NULL, // Password Lenth
  330. &pchPathBuf,
  331. &cchPath,
  332. &pchExtra, // Extra
  333. &cchExtra, // Extra Length
  334. NULL);
  335. if (dwError != ERROR_SUCCESS)
  336. {
  337. SetLastError(dwError);
  338. goto Cleanup;
  339. }
  340. if ( ustScheme != INTERNET_SCHEME_HTTP &&
  341. ustScheme != INTERNET_SCHEME_HTTPS &&
  342. ustScheme != INTERNET_SCHEME_UNKNOWN)
  343. {
  344. //
  345. // known scheme should be supported
  346. // e.g. 3rd party pluggable protocol should be able to
  347. // call cookie api to setup cookies...
  348. //
  349. SetLastError(ERROR_INVALID_PARAMETER);
  350. goto Cleanup;
  351. }
  352. *pfSecure = ustScheme == INTERNET_SCHEME_HTTPS;
  353. if(bStrip)
  354. {
  355. while (cchPath > 0)
  356. {
  357. if (pchPathBuf[cchPath - 1] == '/' || pchPathBuf[cchPath - 1] == '\\')
  358. {
  359. break;
  360. }
  361. cchPath -= 1;
  362. }
  363. }
  364. pchRDomain = StrnDup(pchDomainBuf, cchDomain);
  365. if (!pchRDomain)
  366. goto Cleanup;
  367. LowerCaseString(pchRDomain);
  368. ReverseString(pchRDomain);
  369. pchPath = (char *)ALLOCATE_MEMORY(cchPath + 2);
  370. if (!pchPath)
  371. goto Cleanup;
  372. if (*pchPathBuf != '/')
  373. {
  374. *pchPath = '/';
  375. memcpy(pchPath + 1, pchPathBuf, cchPath);
  376. pchPath[cchPath + 1] = TEXT('\0');
  377. }
  378. else
  379. {
  380. memcpy(pchPath, pchPathBuf, cchPath);
  381. pchPath[cchPath] = TEXT('\0');
  382. }
  383. fSuccess = TRUE;
  384. Cleanup:
  385. if (!fSuccess)
  386. {
  387. if (pchRDomain)
  388. FREE_MEMORY(pchRDomain);
  389. if (pchPath)
  390. FREE_MEMORY(pchPath);
  391. }
  392. else
  393. {
  394. *ppchRDomain = pchRDomain;
  395. *ppchPath = pchPath;
  396. }
  397. return fSuccess;
  398. }
  399. //---------------------------------------------------------------------------
  400. //
  401. // CCookieBase implementation
  402. //
  403. //---------------------------------------------------------------------------
  404. void *
  405. CCookieBase::operator new(size_t cb, size_t cbExtra)
  406. {
  407. void *pv = ALLOCATE_MEMORY(cb + cbExtra);
  408. if (!pv)
  409. {
  410. SetLastError(ERROR_NOT_ENOUGH_MEMORY);
  411. return NULL;
  412. }
  413. memset(pv, 0, cb);
  414. return pv;
  415. }
  416. inline void
  417. CCookieBase::operator delete(void *pv)
  418. {
  419. FREE_MEMORY(pv);
  420. }
  421. //---------------------------------------------------------------------------
  422. //
  423. // CCookie implementation
  424. //
  425. //---------------------------------------------------------------------------
  426. CCookie *
  427. CCookie::Construct(const char *pchName)
  428. {
  429. CCookie *pCookie = new(strlen(pchName) + 1) CCookie();
  430. if (!pCookie)
  431. return NULL;
  432. pCookie->_pchName = (char *)(pCookie + 1);
  433. pCookie->_pchValue = s_achEmpty;
  434. strcpy(pCookie->_pchName, pchName);
  435. pCookie->_dwFlags = COOKIE_SESSION;
  436. return pCookie;
  437. }
  438. CCookie::~CCookie()
  439. {
  440. if (_pchValue != s_achEmpty)
  441. FREE_MEMORY(_pchValue);
  442. }
  443. BOOL
  444. CCookie::SetValue(const char *pchValue)
  445. {
  446. int cchValue;
  447. if (_pchValue != s_achEmpty)
  448. FREE_MEMORY(_pchValue);
  449. if (!pchValue || !*pchValue)
  450. {
  451. _pchValue = s_achEmpty;
  452. }
  453. else
  454. {
  455. cchValue = strlen(pchValue) + 1;
  456. _pchValue = (char *)ALLOCATE_MEMORY(cchValue);
  457. if (!_pchValue)
  458. {
  459. _pchValue = s_achEmpty;
  460. SetLastError(ERROR_NOT_ENOUGH_MEMORY);
  461. return FALSE;
  462. }
  463. memcpy(_pchValue, pchValue, cchValue);
  464. }
  465. return TRUE;
  466. }
  467. BOOL
  468. CCookie::CanSend(BOOL fSecure)
  469. {
  470. return (fSecure || !(_dwFlags & COOKIE_SECURE));
  471. }
  472. BOOL CCookie::PurgeAll(void *)
  473. {
  474. return TRUE;
  475. }
  476. BOOL
  477. WriteString(HANDLE hFile, const char *pch)
  478. {
  479. DWORD cb;
  480. return pch && *pch ? WriteFile(hFile, pch, strlen(pch), &cb, NULL) : TRUE;
  481. }
  482. BOOL
  483. WriteStringLF(HANDLE hFile, const char *pch)
  484. {
  485. DWORD cb;
  486. if (!WriteString(hFile, pch)) return FALSE;
  487. return WriteFile(hFile, "\n", 1, &cb, NULL);
  488. }
  489. //---------------------------------------------------------------------------
  490. //
  491. // CCookieLocation implementation
  492. //
  493. //---------------------------------------------------------------------------
  494. CCookieLocation *
  495. CCookieLocation::Construct(const char *pchRDomain, const char *pchPath)
  496. {
  497. int cchPath = strlen(pchPath);
  498. CCookieLocation *pLocation = new(strlen(pchRDomain) + cchPath + 2) CCookieLocation();
  499. if (!pLocation)
  500. return NULL;
  501. pLocation->_cchPath = cchPath;
  502. pLocation->_pchPath = (char *)(pLocation + 1);
  503. pLocation->_pchRDomain = pLocation->_pchPath + cchPath + 1;
  504. strcpy(pLocation->_pchRDomain, pchRDomain);
  505. strcpy(pLocation->_pchPath, pchPath);
  506. return pLocation;
  507. }
  508. CCookieLocation::~CCookieLocation()
  509. {
  510. Purge(CCookie::PurgeAll, NULL);
  511. }
  512. CCookie *
  513. CCookieLocation::GetCookie(const char *pchName, BOOL fCreate)
  514. {
  515. CCookie *pCookie;
  516. CCookie **ppCookie = &_pCookieKids;
  517. for (pCookie = _pCookieKids; pCookie; pCookie = pCookie->_pCookieNext)
  518. {
  519. if (strcmp(pchName, pCookie->_pchName) == 0)
  520. return pCookie;
  521. ppCookie = &pCookie->_pCookieNext;
  522. }
  523. if (!fCreate)
  524. return NULL;
  525. pCookie = CCookie::Construct(pchName);
  526. if (!pCookie)
  527. return NULL;
  528. //
  529. // Insert cookie at end of list to match Navigator's behavior.
  530. //
  531. pCookie->_pCookieNext = NULL;
  532. *ppCookie = pCookie;
  533. return pCookie;
  534. }
  535. void
  536. CCookieLocation::Purge(BOOL (CCookie::*pfnPurge)(void *), void *pv)
  537. {
  538. CCookie **ppCookie = &_pCookieKids;
  539. CCookie *pCookie;
  540. while ((pCookie = *ppCookie) != NULL)
  541. {
  542. if ((pCookie->*pfnPurge)(pv))
  543. {
  544. *ppCookie = pCookie->_pCookieNext;
  545. delete pCookie;
  546. }
  547. else
  548. {
  549. ppCookie = &pCookie->_pCookieNext;
  550. }
  551. }
  552. }
  553. BOOL
  554. CCookieLocation::IsMatch(const char *pchRDomain, const char *pchPath)
  555. {
  556. return IsDomainMatch(_pchRDomain, pchRDomain) &&
  557. IsPathMatch(_pchPath, pchPath);
  558. }
  559. //---------------------------------------------------------------------------
  560. //
  561. // CCookieJar implementation
  562. //
  563. //---------------------------------------------------------------------------
  564. CCookieJar *
  565. CCookieJar::Construct()
  566. {
  567. return new(0) CCookieJar();
  568. }
  569. CCookieJar::CCookieJar()
  570. {
  571. _csCookieJar.Init();
  572. }
  573. CCookieJar::~CCookieJar()
  574. {
  575. for (int i = ARRAY_ELEMENTS(_apLocation); --i >= 0; )
  576. {
  577. CCookieLocation *pLocation = _apLocation[i];
  578. while (pLocation)
  579. {
  580. CCookieLocation *pLocationT = pLocation->_pLocationNext;
  581. delete pLocation;
  582. pLocation = pLocationT;
  583. }
  584. }
  585. }
  586. CCookieLocation **
  587. CCookieJar::GetBucket(const char *pchRDomain)
  588. {
  589. int ch;
  590. int cPeriod = 0;
  591. unsigned int hash = 0;
  592. for (; (ch = *pchRDomain) != 0; pchRDomain++)
  593. {
  594. if (ch == '.')
  595. {
  596. cPeriod += 1;
  597. if (cPeriod >= 2)
  598. break;
  599. }
  600. hash = (hash * 29) + ch;
  601. }
  602. hash = hash % ARRAY_ELEMENTS(_apLocation);
  603. return &_apLocation[hash];
  604. }
  605. CCookieLocation *
  606. CCookieJar::GetLocation(const char *pchRDomain, const char *pchPath, BOOL fCreate)
  607. {
  608. int cchPath = strlen(pchPath);
  609. CCookieLocation *pLocation = NULL;
  610. CCookieLocation **ppLocation = GetBucket(pchRDomain);
  611. // To support sending more specific cookies before less specific,
  612. // we keep list sorted by path length.
  613. while ((pLocation = *ppLocation) != NULL)
  614. {
  615. if (pLocation->_cchPath < cchPath)
  616. break;
  617. if (strcmp(pLocation->_pchPath, pchPath) == 0 &&
  618. strcmp(pLocation->_pchRDomain, pchRDomain) == 0)
  619. return pLocation;
  620. ppLocation = &pLocation->_pLocationNext;
  621. }
  622. if (!fCreate)
  623. goto Cleanup;
  624. pLocation = CCookieLocation::Construct(pchRDomain, pchPath);
  625. if (!pLocation)
  626. goto Cleanup;
  627. pLocation->_pLocationNext = *ppLocation;
  628. *ppLocation = pLocation;
  629. Cleanup:
  630. return pLocation;
  631. }
  632. void
  633. CCookieJar::expireCookies(CCookieLocation *pLocation, FILETIME *pftNow) {
  634. FILETIME ftCurrent;
  635. if (pftNow==NULL)
  636. GetSystemTimeAsFileTime(&ftCurrent);
  637. else
  638. ftCurrent = *pftNow;
  639. CCookie **previous = & pLocation->_pCookieKids;
  640. CCookie *pCookie = pLocation->_pCookieKids;
  641. while (pCookie)
  642. {
  643. /* Session cookies do not expire so we only check persistent cookies */
  644. if ((pCookie->_dwFlags & COOKIE_SESSION) == 0)
  645. {
  646. /* "CompareFileTime" macro returns {+1, 0, -1} similar to "strcmp" */
  647. int cmpresult = CompareFileTime(ftCurrent, pCookie->_ftExpiry);
  648. if (cmpresult==1) /* Cookie has expired: remove from linked list & delete */
  649. {
  650. *previous = pCookie->_pCookieNext;
  651. delete pCookie;
  652. pCookie = *previous;
  653. continue;
  654. }
  655. }
  656. /* Otherwise cookie is still valid: advance to next node */
  657. previous = & (pCookie->_pCookieNext);
  658. pCookie = pCookie->_pCookieNext;
  659. }
  660. }
  661. CCookieLocation*
  662. CCookieJar::GetCookies(const char *pchRDomain, const char *pchPath, CCookieLocation *pLast, FILETIME *ftCurrentTime) {
  663. for (CCookieLocation *pLocation = pLast ? pLast->_pLocationNext : *GetBucket(pchRDomain);
  664. pLocation;
  665. pLocation = pLocation->_pLocationNext)
  666. {
  667. if (pLocation->IsMatch(pchRDomain, pchPath))
  668. {
  669. /* Found matching cookies...
  670. Before returning linked list to the user, check expiration
  671. times, deleting cookies which are no longer valid */
  672. expireCookies(pLocation, ftCurrentTime);
  673. return pLocation;
  674. }
  675. }
  676. /* Reaching this point means no matching cookies were found */
  677. return NULL;
  678. }
  679. void
  680. CCookieJar::Purge()
  681. {
  682. // NOT IMPLEMENTED
  683. }
  684. //
  685. // PARSE tracks both the last parsed token and the current buffer location for ParseToken()
  686. // pchToken points to the parsed token.
  687. // pchBuffer points to the next location in the buffer to be parsed.
  688. // fEqualFound indicates if the token was terminated by an equal sign.
  689. //
  690. struct PARSE
  691. {
  692. char *pchBuffer;
  693. char *pchToken;
  694. BOOL fEqualFound;
  695. };
  696. static char *
  697. SkipWS(char *pch)
  698. {
  699. while (*pch == ' ' || *pch == '\t')
  700. pch += 1;
  701. return pch;
  702. }
  703. static BOOL
  704. ParseToken(PARSE *pParse, BOOL fBreakOnSpecialTokens, BOOL fBreakOnEqual, BOOL fAllowQuotedToken = FALSE)
  705. {
  706. char ch;
  707. char *pch;
  708. char *pchEndToken;
  709. pParse->fEqualFound = FALSE;
  710. pch = SkipWS(pParse->pchBuffer);
  711. if (*pch == 0)
  712. {
  713. pParse->pchToken = pch;
  714. return FALSE;
  715. }
  716. if (*pch == '\"' && fAllowQuotedToken)
  717. {
  718. pch++;
  719. // Return the token as begining after the quote
  720. pParse->pchToken = pch;
  721. // Scan until the next quote.
  722. while(*pch != '\0' && *pch != '\"')
  723. {
  724. pch++;
  725. }
  726. // Expecting final quote. If not found, error.
  727. //If found, return the quoted token.
  728. if (*pch == '\0')
  729. {
  730. pParse->pchBuffer = pch;
  731. return FALSE;
  732. }
  733. else
  734. {
  735. INET_ASSERT(*pch == '\"');
  736. *pch = '\0';
  737. pch++;
  738. pParse->pchBuffer = pch;
  739. return TRUE;
  740. }
  741. }
  742. pParse->pchToken = pch;
  743. pchEndToken = pch;
  744. while ((ch = *pch) != 0)
  745. {
  746. pch += 1;
  747. if (ch == ';')
  748. {
  749. break;
  750. }
  751. else if (fBreakOnEqual && ch == '=')
  752. {
  753. pParse->fEqualFound = TRUE;
  754. break;
  755. }
  756. else if (ch == ' ' || ch == '\t')
  757. {
  758. if (fBreakOnSpecialTokens)
  759. {
  760. if ((strnicmp(pch, "expires", sizeof("expires") - 1) == 0) ||
  761. (strnicmp(pch, "Max-Age", sizeof("Max-Age") - 1) == 0) ||
  762. (strnicmp(pch, "path", sizeof("path") - 1) == 0) ||
  763. (strnicmp(pch, "domain", sizeof("domain") - 1) == 0) ||
  764. (strnicmp(pch, "secure", sizeof("secure") - 1) == 0))
  765. {
  766. break;
  767. }
  768. }
  769. }
  770. else
  771. {
  772. pchEndToken = pch;
  773. }
  774. }
  775. *pchEndToken = 0;
  776. pParse->pchBuffer = pch;
  777. return TRUE;
  778. }
  779. static void
  780. ParseHeader(
  781. char *pchHeader,
  782. CookieInfo *pCookie
  783. )
  784. {
  785. char **ppchName = & (pCookie->pchName);
  786. char **ppchValue = & (pCookie->pchValue);
  787. char **ppchPath = & (pCookie->pchPath);
  788. char **ppchRDomain = & (pCookie->pchRDomain);
  789. PARSE parse;
  790. parse.pchBuffer = pchHeader;
  791. *ppchName = NULL;
  792. *ppchValue = NULL;
  793. *ppchPath = NULL;
  794. *ppchRDomain = NULL;
  795. pCookie->dwFlags = COOKIE_SESSION;
  796. // If only one of name or value is specified, Navigator
  797. // uses name=<blank> and value as what ever was specified.
  798. // Example: =foo -> name: <blank> value: foo
  799. // foo -> name: <blank> value: foo
  800. // foo= -> name: foo value: <blank>
  801. if (ParseToken(&parse, FALSE, TRUE))
  802. {
  803. *ppchName = parse.pchToken;
  804. if (parse.fEqualFound)
  805. {
  806. if (ParseToken(&parse, FALSE, FALSE))
  807. {
  808. *ppchValue = parse.pchToken;
  809. }
  810. else
  811. {
  812. *ppchValue = s_achEmpty;
  813. }
  814. }
  815. else
  816. {
  817. *ppchValue = *ppchName;
  818. *ppchName = s_achEmpty;
  819. }
  820. }
  821. while (ParseToken(&parse, FALSE, TRUE))
  822. {
  823. if (stricmp(parse.pchToken, "expires") == 0)
  824. {
  825. if (parse.fEqualFound && ParseToken(&parse, TRUE, FALSE, TRUE))
  826. {
  827. // WinHttpX treats persistent cookies as session cookies with expiration
  828. if (FParseHttpDate(& pCookie->ftExpiration, parse.pchToken))
  829. {
  830. // Don't make the cookie persistent if the parsing fails
  831. // If the parsing succeeds and we have an expiration time,
  832. //make sure its not session so the expiry is used.
  833. pCookie->dwFlags &= ~COOKIE_SESSION;
  834. }
  835. }
  836. }
  837. else if (stricmp(parse.pchToken, "Max-Age") == 0)
  838. {
  839. // excerpt below from http://www.ietf.org/rfc/rfc2109.txt
  840. //
  841. //Max-Age=delta-seconds
  842. // Optional. The Max-Age attribute defines the lifetime of the
  843. // cookie, in seconds. The delta-seconds value is a decimal non-
  844. // negative integer. After delta-seconds seconds elapse, the client
  845. // should discard the cookie. A value of zero means the cookie
  846. // should be discarded immediately.
  847. //
  848. if (parse.fEqualFound && ParseToken(&parse, TRUE, FALSE, TRUE))
  849. {
  850. LPSTR szValue = parse.pchToken;
  851. DWORD dwMaxAge;
  852. FILETIME ftExpires;
  853. // Try to extract the Max-Age value. If its malformed, set
  854. //MaxAge to 0 to flush the cookie.
  855. if (!ExtractDword( &szValue, strlen(parse.pchToken), &dwMaxAge))
  856. {
  857. dwMaxAge = 0;
  858. }
  859. // Add the max age to the current file time to compute the
  860. //expiration time.
  861. GetSystemTimeAsFileTime(&ftExpires);
  862. AddLongLongToFT(&ftExpires, ((LONGLONG)dwMaxAge) * (LONGLONG)10000000);
  863. pCookie->ftExpiration = ftExpires;
  864. // Make sure the cookie is no longer considered a session
  865. //cookie so that the expiration time is actually applied.
  866. pCookie->dwFlags &= ~COOKIE_SESSION;
  867. }
  868. }
  869. else if (stricmp(parse.pchToken, "domain") == 0)
  870. {
  871. if (parse.fEqualFound )
  872. {
  873. if( ParseToken(&parse, TRUE, FALSE, TRUE))
  874. {
  875. // Previous versions of IE tossed the leading
  876. // "." on domain names. We continue this behavior
  877. // to maintain compatiblity with old cookie files.
  878. // See comments at the top of this file for more
  879. // information.
  880. if (*parse.pchToken == '.') parse.pchToken += 1;
  881. ReverseString(parse.pchToken);
  882. *ppchRDomain = parse.pchToken;
  883. }
  884. else
  885. {
  886. *ppchRDomain = parse.pchToken;
  887. }
  888. }
  889. }
  890. else if (stricmp(parse.pchToken, "path") == 0)
  891. {
  892. if (parse.fEqualFound && ParseToken(&parse, TRUE, FALSE, TRUE))
  893. {
  894. *ppchPath = parse.pchToken;
  895. }
  896. else
  897. {
  898. *ppchPath = s_achEmpty;
  899. }
  900. }
  901. else if (stricmp(parse.pchToken, "secure") == 0)
  902. {
  903. pCookie->dwFlags |= COOKIE_SECURE;
  904. if (parse.fEqualFound)
  905. {
  906. ParseToken(&parse, TRUE, FALSE, TRUE);
  907. }
  908. }
  909. else
  910. {
  911. if (parse.fEqualFound)
  912. {
  913. ParseToken(&parse, TRUE, FALSE, TRUE);
  914. }
  915. }
  916. }
  917. if (!*ppchName)
  918. {
  919. *ppchName = *ppchValue = s_achEmpty;
  920. }
  921. }
  922. DWORD
  923. CCookieJar::SetCookie(HTTP_REQUEST_HANDLE_OBJECT *pRequest, const char *pchURL, char *pchHeader, DWORD dwFlags = 0)
  924. {
  925. char *pchDocumentRDomain = NULL;
  926. char *pchDocumentPath = NULL;
  927. BOOL fDocumentSecure;
  928. DWORD dwRet = SET_COOKIE_FAIL;
  929. CCookieLocation *pLocation;
  930. BOOL fHaveCookieJarLock = FALSE;
  931. CookieInfo cookieStats;
  932. UNREFERENCED_PARAMETER(pRequest);
  933. ParseHeader(pchHeader, &cookieStats);
  934. char *pchName = cookieStats.pchName;
  935. char *pchValue = cookieStats.pchValue;
  936. char *pchHeaderPath = cookieStats.pchPath;
  937. char *pchHeaderRDomain = cookieStats.pchRDomain;
  938. DWORD dwFlagsFromParse = cookieStats.dwFlags;
  939. // merge flags given with those found by the parser.
  940. dwFlags |= dwFlagsFromParse;
  941. if (!PathAndRDomainFromURL(pchURL, &pchDocumentRDomain, &pchDocumentPath, &fDocumentSecure, FALSE))
  942. goto Cleanup;
  943. //
  944. // Verify domain and path
  945. //
  946. if ((pchHeaderRDomain && !IsDomainLegal(pchHeaderRDomain, pchDocumentRDomain)) ||
  947. (pchHeaderPath && !IsPathLegal(pchHeaderPath, pchDocumentPath)))
  948. {
  949. SetLastError(ERROR_INVALID_PARAMETER);
  950. goto Cleanup;
  951. }
  952. if (!pchHeaderRDomain)
  953. pchHeaderRDomain = pchDocumentRDomain;
  954. if (!pchHeaderPath)
  955. pchHeaderPath = pchDocumentPath;
  956. // We need to discard any extra info (i.e. query strings and fragments)
  957. // from the url.
  958. if (pchHeaderPath)
  959. {
  960. PTSTR psz = pchHeaderPath;
  961. while (*psz)
  962. {
  963. if (*psz==TEXT('?') || *psz==TEXT('#'))
  964. {
  965. *psz = TEXT('\0');
  966. break;
  967. }
  968. psz++;
  969. }
  970. }
  971. // WinHttpX treats persistent cookies as session cookies, subject
  972. // to the expiration rules
  973. // Also it does not implement zone policies set by URLMON
  974. //
  975. // Finally, we can add the cookie!
  976. //
  977. {
  978. if (_csCookieJar.Lock())
  979. {
  980. fHaveCookieJarLock = TRUE;
  981. pLocation = GetLocation(pchHeaderRDomain, pchHeaderPath, TRUE);
  982. if (pLocation)
  983. {
  984. CCookie *pCookie;
  985. pCookie = pLocation->GetCookie(pchName, TRUE);
  986. if (!pCookie)
  987. goto Cleanup;
  988. //
  989. // If the cookie's value or flags have changed, update it.
  990. //
  991. if (pCookie->SetValue(pchValue))
  992. {
  993. pCookie->_dwFlags = dwFlags;
  994. pCookie->_ftExpiry = cookieStats.ftExpiration;
  995. }
  996. else
  997. {
  998. // Oops, alloc failed. This cookie is toast, lets expire it.
  999. pCookie->_dwFlags = dwFlags;
  1000. pCookie->_dwFlags &= ~COOKIE_SESSION; // session cookies don't expire..
  1001. pCookie->_ftExpiry.dwLowDateTime = 0;
  1002. pCookie->_ftExpiry.dwHighDateTime = 0;
  1003. }
  1004. }
  1005. _csCookieJar.Unlock();
  1006. fHaveCookieJarLock = FALSE;
  1007. }
  1008. }
  1009. dwRet = SET_COOKIE_SUCCESS;
  1010. Cleanup:
  1011. if (fHaveCookieJarLock)
  1012. _csCookieJar.Unlock();
  1013. if (pchDocumentRDomain)
  1014. FREE_MEMORY(pchDocumentRDomain);
  1015. if (pchDocumentPath)
  1016. FREE_MEMORY(pchDocumentPath);
  1017. return dwRet;
  1018. }
  1019. //---------------------------------------------------------------------------
  1020. //
  1021. // External APIs
  1022. //
  1023. //---------------------------------------------------------------------------
  1024. CCookieJar *
  1025. CreateCookieJar()
  1026. {
  1027. return CCookieJar::Construct();
  1028. }
  1029. void
  1030. CloseCookieJar(CCookieJar * CookieJar)
  1031. {
  1032. if (CookieJar)
  1033. {
  1034. delete CookieJar;
  1035. }
  1036. }
  1037. #ifndef WININET_SERVER_CORE
  1038. void
  1039. PurgeCookieJar()
  1040. {
  1041. }
  1042. #endif
  1043. //
  1044. // rambling comments, delete before checkin...
  1045. //
  1046. // returns struc, and pending, error
  1047. // on subsequent attempts passes back, with index, or index incremented
  1048. // perhaps can store index in fsm, and the rest in UI
  1049. // need to handle multi dlgs, perhaps via checking added Cookie.
  1050. //
  1051. DWORD
  1052. HTTP_REQUEST_HANDLE_OBJECT::ExtractSetCookieHeaders(LPDWORD lpdwHeaderIndex)
  1053. {
  1054. char *pchHeader = NULL;
  1055. DWORD cbHeader;
  1056. DWORD iQuery = 0;
  1057. int cCookies = 0;
  1058. DWORD error = ERROR_WINHTTP_HEADER_NOT_FOUND;
  1059. const DWORD cbHeaderInit = CCH_COOKIE_MAX * sizeof(char) - 1;
  1060. pchHeader = New char[CCH_COOKIE_MAX];
  1061. if (pchHeader == NULL || !_ResponseHeaders.LockHeaders())
  1062. {
  1063. error = ERROR_NOT_ENOUGH_MEMORY;
  1064. goto Cleanup;
  1065. }
  1066. INET_ASSERT(lpdwHeaderIndex);
  1067. cbHeader = cbHeaderInit;
  1068. iQuery = *lpdwHeaderIndex;
  1069. while (QueryResponseHeader(HTTP_QUERY_SET_COOKIE,
  1070. pchHeader,
  1071. &cbHeader,
  1072. 0,
  1073. &iQuery) == ERROR_SUCCESS)
  1074. {
  1075. pchHeader[cbHeader] = 0;
  1076. INTERNET_HANDLE_OBJECT *pRoot = GetRootHandle (this);
  1077. CCookieJar* pcj = pRoot->_CookieJar;
  1078. DWORD dwRet = pcj->SetCookie(this, GetURL(), pchHeader);
  1079. if (dwRet == SET_COOKIE_SUCCESS)
  1080. {
  1081. cCookies += 1;
  1082. *lpdwHeaderIndex = iQuery;
  1083. error = ERROR_SUCCESS;
  1084. }
  1085. else if (dwRet == SET_COOKIE_PENDING)
  1086. {
  1087. error = ERROR_IO_PENDING;
  1088. INET_ASSERT(iQuery != 0);
  1089. *lpdwHeaderIndex = iQuery - 1; // back up and retry this cookie
  1090. break;
  1091. }
  1092. cbHeader = cbHeaderInit;
  1093. }
  1094. _ResponseHeaders.UnlockHeaders();
  1095. Cleanup:
  1096. if (pchHeader)
  1097. delete [] pchHeader;
  1098. return error;
  1099. }
  1100. VOID
  1101. HTTP_REQUEST_HANDLE_OBJECT::CreateCookieHeaderIfNeeded(VOID)
  1102. {
  1103. char * pchRDomain = NULL;
  1104. char * pchPath = NULL;
  1105. BOOL fSecure, fHeadersLocked=FALSE;
  1106. DWORD cch = 0;
  1107. int cchName;
  1108. int cchValue;
  1109. char * pchHeader = NULL;
  1110. char * pchHeaderStart = NULL;
  1111. DWORD dwIndex = 0;
  1112. if (!LockHeaders())
  1113. goto Cleanup;
  1114. fHeadersLocked = TRUE;
  1115. //
  1116. // Before the first call to SendRequest, the WinHttp client may set custom
  1117. //cookies by setting the HTTP_QUERY_COOKIE header. This header, or an empty
  1118. //string, will be saved off to _CachedCookieHeader so it can be reused on subsequent
  1119. //calls to SendRequest on the same request handle.
  1120. //
  1121. // If on subsequent calls the WinHttp client removes the cookies header,
  1122. //we will honor that change by clearing the _CachedCookieHeader to an empty string.
  1123. //All other changes to the cookie header are ignored.
  1124. //
  1125. // (this forces the requirement that the WinHttp client sets custom cookies
  1126. //before the first SendRequest, but does allow the client to clear the custom
  1127. //cookies later if need be, perhaps in response to a redirect)
  1128. //
  1129. switch (QueryRequestHeader(HTTP_QUERY_COOKIE, NULL, &cch, 0, &dwIndex))
  1130. {
  1131. default:
  1132. case ERROR_WINHTTP_HEADER_NOT_FOUND:
  1133. //
  1134. // If there is currently no Cookie request header (perhaps it got
  1135. // discarded by the application during a callback notification),
  1136. // then ensure that the app's cached Cookie request header is also
  1137. // marked as empty.
  1138. //
  1139. if (_CachedCookieHeader && _CachedCookieHeader[0] != '\0')
  1140. {
  1141. FREE_MEMORY(_CachedCookieHeader);
  1142. _CachedCookieHeader = NULL;
  1143. }
  1144. if (_CachedCookieHeader == NULL)
  1145. {
  1146. _CachedCookieHeader = (LPSTR) ALLOCATE_FIXED_MEMORY(1);
  1147. if (_CachedCookieHeader == NULL)
  1148. goto Cleanup;
  1149. _CachedCookieHeader[0] = '\0';
  1150. }
  1151. break;
  1152. case ERROR_INSUFFICIENT_BUFFER:
  1153. // If there is a cached Cookie request header, then set it as the
  1154. // initial value of the Cookie header. Additional cookies from
  1155. // the Cookie Jar will be appended to this.
  1156. if (_CachedCookieHeader)
  1157. {
  1158. if (_CachedCookieHeader[0] == '\0')
  1159. {
  1160. ReplaceRequestHeader(HTTP_QUERY_COOKIE, NULL, 0, 0, 0);
  1161. }
  1162. else
  1163. {
  1164. ReplaceRequestHeader(HTTP_QUERY_COOKIE, _CachedCookieHeader,
  1165. lstrlen(_CachedCookieHeader), 0, 0);
  1166. }
  1167. }
  1168. //
  1169. // If the application sets a Cookie request header before calling
  1170. // SendRequest, then cache its value. After the first SendRequest
  1171. // call, we know _CachedCookieHeader will be non-NULL indicating that
  1172. // the current cookie request header may have been generated by
  1173. // WinHttp and therefore should not be cached.
  1174. //
  1175. else
  1176. {
  1177. _CachedCookieHeader = (LPSTR) ALLOCATE_FIXED_MEMORY(cch + 1);
  1178. if (_CachedCookieHeader == NULL)
  1179. goto Cleanup;
  1180. if (!QueryRequestHeader(HTTP_QUERY_COOKIE, _CachedCookieHeader, &cch, 0, &dwIndex))
  1181. {
  1182. FREE_MEMORY(_CachedCookieHeader);
  1183. _CachedCookieHeader = NULL;
  1184. goto Cleanup;
  1185. }
  1186. }
  1187. break;
  1188. }
  1189. if (!PathAndRDomainFromURL(GetURL(), &pchRDomain, &pchPath, &fSecure, FALSE))
  1190. goto Cleanup;
  1191. fSecure = GetOpenFlags() & WINHTTP_FLAG_SECURE;
  1192. {
  1193. INTERNET_HANDLE_OBJECT *pRoot = GetRootHandle (this);
  1194. CCookieJar* pcj = pRoot->_CookieJar;
  1195. if (pcj->_csCookieJar.Lock())
  1196. {
  1197. FILETIME ftNow;
  1198. GetSystemTimeAsFileTime(&ftNow);
  1199. CCookieLocation *pLocation = pcj->GetCookies(pchRDomain, pchPath, NULL, &ftNow);
  1200. while (pLocation)
  1201. {
  1202. for (CCookie *pCookie = pLocation->_pCookieKids; pCookie; pCookie = pCookie->_pCookieNext)
  1203. {
  1204. if (pCookie->CanSend(fSecure))
  1205. {
  1206. if (pchHeaderStart == NULL)
  1207. {
  1208. pchHeaderStart = (char *) ALLOCATE_FIXED_MEMORY(CCH_COOKIE_MAX * sizeof(char));
  1209. if (pchHeaderStart == NULL)
  1210. break;
  1211. }
  1212. pchHeader = pchHeaderStart;
  1213. cch = 0;
  1214. cch += cchName = strlen(pCookie->_pchName);
  1215. cch += cchValue = strlen(pCookie->_pchValue);
  1216. if (cchName) cch += 1; // for equal sign
  1217. if (cch < CCH_COOKIE_MAX)
  1218. {
  1219. if (cchName > 0)
  1220. {
  1221. memcpy(pchHeader, pCookie->_pchName, cchName);
  1222. pchHeader += cchName;
  1223. *pchHeader++ = '=';
  1224. }
  1225. if (cchValue > 0)
  1226. {
  1227. memcpy(pchHeader, pCookie->_pchValue, cchValue);
  1228. pchHeader += cchValue;
  1229. }
  1230. AddRequestHeader(HTTP_QUERY_COOKIE,
  1231. pchHeaderStart,
  1232. cch,
  1233. 0,
  1234. HTTP_ADDREQ_FLAG_COALESCE_WITH_SEMICOLON);
  1235. }
  1236. } // if (CanSend)
  1237. } // for (pCookie)
  1238. pLocation = pcj->GetCookies(pchRDomain, pchPath, pLocation, &ftNow);
  1239. } // while (pLocation)
  1240. pcj->_csCookieJar.Unlock();
  1241. } // if pcj->_csCookieJar.Lock()
  1242. }
  1243. Cleanup:
  1244. if (fHeadersLocked)
  1245. UnlockHeaders();
  1246. if (pchHeaderStart)
  1247. FREE_MEMORY(pchHeaderStart);
  1248. if (pchRDomain)
  1249. FREE_MEMORY(pchRDomain);
  1250. if (pchPath)
  1251. FREE_MEMORY(pchPath);
  1252. }