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.

1364 lines
38 KiB

4 years ago
  1. /**
  2. * HTTP client-side implementation that uses forge.net sockets.
  3. *
  4. * @author Dave Longley
  5. *
  6. * Copyright (c) 2010-2014 Digital Bazaar, Inc. All rights reserved.
  7. */
  8. var forge = require('./forge');
  9. require('./debug');
  10. require('./tls');
  11. require('./util');
  12. // define http namespace
  13. var http = module.exports = forge.http = forge.http || {};
  14. // logging category
  15. var cat = 'forge.http';
  16. // add array of clients to debug storage
  17. if(forge.debug) {
  18. forge.debug.set('forge.http', 'clients', []);
  19. }
  20. // normalizes an http header field name
  21. var _normalize = function(name) {
  22. return name.toLowerCase().replace(/(^.)|(-.)/g,
  23. function(a) {return a.toUpperCase();});
  24. };
  25. /**
  26. * Gets the local storage ID for the given client.
  27. *
  28. * @param client the client to get the local storage ID for.
  29. *
  30. * @return the local storage ID to use.
  31. */
  32. var _getStorageId = function(client) {
  33. // TODO: include browser in ID to avoid sharing cookies between
  34. // browsers (if this is undesirable)
  35. // navigator.userAgent
  36. return 'forge.http.' +
  37. client.url.scheme + '.' +
  38. client.url.host + '.' +
  39. client.url.port;
  40. };
  41. /**
  42. * Loads persistent cookies from disk for the given client.
  43. *
  44. * @param client the client.
  45. */
  46. var _loadCookies = function(client) {
  47. if(client.persistCookies) {
  48. try {
  49. var cookies = forge.util.getItem(
  50. client.socketPool.flashApi,
  51. _getStorageId(client), 'cookies');
  52. client.cookies = cookies || {};
  53. } catch(ex) {
  54. // no flash storage available, just silently fail
  55. // TODO: i assume we want this logged somewhere or
  56. // should it actually generate an error
  57. //forge.log.error(cat, ex);
  58. }
  59. }
  60. };
  61. /**
  62. * Saves persistent cookies on disk for the given client.
  63. *
  64. * @param client the client.
  65. */
  66. var _saveCookies = function(client) {
  67. if(client.persistCookies) {
  68. try {
  69. forge.util.setItem(
  70. client.socketPool.flashApi,
  71. _getStorageId(client), 'cookies', client.cookies);
  72. } catch(ex) {
  73. // no flash storage available, just silently fail
  74. // TODO: i assume we want this logged somewhere or
  75. // should it actually generate an error
  76. //forge.log.error(cat, ex);
  77. }
  78. }
  79. // FIXME: remove me
  80. _loadCookies(client);
  81. };
  82. /**
  83. * Clears persistent cookies on disk for the given client.
  84. *
  85. * @param client the client.
  86. */
  87. var _clearCookies = function(client) {
  88. if(client.persistCookies) {
  89. try {
  90. // only thing stored is 'cookies', so clear whole storage
  91. forge.util.clearItems(
  92. client.socketPool.flashApi,
  93. _getStorageId(client));
  94. } catch(ex) {
  95. // no flash storage available, just silently fail
  96. // TODO: i assume we want this logged somewhere or
  97. // should it actually generate an error
  98. //forge.log.error(cat, ex);
  99. }
  100. }
  101. };
  102. /**
  103. * Connects and sends a request.
  104. *
  105. * @param client the http client.
  106. * @param socket the socket to use.
  107. */
  108. var _doRequest = function(client, socket) {
  109. if(socket.isConnected()) {
  110. // already connected
  111. socket.options.request.connectTime = +new Date();
  112. socket.connected({
  113. type: 'connect',
  114. id: socket.id
  115. });
  116. } else {
  117. // connect
  118. socket.options.request.connectTime = +new Date();
  119. socket.connect({
  120. host: client.url.host,
  121. port: client.url.port,
  122. policyPort: client.policyPort,
  123. policyUrl: client.policyUrl
  124. });
  125. }
  126. };
  127. /**
  128. * Handles the next request or marks a socket as idle.
  129. *
  130. * @param client the http client.
  131. * @param socket the socket.
  132. */
  133. var _handleNextRequest = function(client, socket) {
  134. // clear buffer
  135. socket.buffer.clear();
  136. // get pending request
  137. var pending = null;
  138. while(pending === null && client.requests.length > 0) {
  139. pending = client.requests.shift();
  140. if(pending.request.aborted) {
  141. pending = null;
  142. }
  143. }
  144. // mark socket idle if no pending requests
  145. if(pending === null) {
  146. if(socket.options !== null) {
  147. socket.options = null;
  148. }
  149. client.idle.push(socket);
  150. } else {
  151. // handle pending request, allow 1 retry
  152. socket.retries = 1;
  153. socket.options = pending;
  154. _doRequest(client, socket);
  155. }
  156. };
  157. /**
  158. * Sets up a socket for use with an http client.
  159. *
  160. * @param client the parent http client.
  161. * @param socket the socket to set up.
  162. * @param tlsOptions if the socket must use TLS, the TLS options.
  163. */
  164. var _initSocket = function(client, socket, tlsOptions) {
  165. // no socket options yet
  166. socket.options = null;
  167. // set up handlers
  168. socket.connected = function(e) {
  169. // socket primed by caching TLS session, handle next request
  170. if(socket.options === null) {
  171. _handleNextRequest(client, socket);
  172. } else {
  173. // socket in use
  174. var request = socket.options.request;
  175. request.connectTime = +new Date() - request.connectTime;
  176. e.socket = socket;
  177. socket.options.connected(e);
  178. if(request.aborted) {
  179. socket.close();
  180. } else {
  181. var out = request.toString();
  182. if(request.body) {
  183. out += request.body;
  184. }
  185. request.time = +new Date();
  186. socket.send(out);
  187. request.time = +new Date() - request.time;
  188. socket.options.response.time = +new Date();
  189. socket.sending = true;
  190. }
  191. }
  192. };
  193. socket.closed = function(e) {
  194. if(socket.sending) {
  195. socket.sending = false;
  196. if(socket.retries > 0) {
  197. --socket.retries;
  198. _doRequest(client, socket);
  199. } else {
  200. // error, closed during send
  201. socket.error({
  202. id: socket.id,
  203. type: 'ioError',
  204. message: 'Connection closed during send. Broken pipe.',
  205. bytesAvailable: 0
  206. });
  207. }
  208. } else {
  209. // handle unspecified content-length transfer
  210. var response = socket.options.response;
  211. if(response.readBodyUntilClose) {
  212. response.time = +new Date() - response.time;
  213. response.bodyReceived = true;
  214. socket.options.bodyReady({
  215. request: socket.options.request,
  216. response: response,
  217. socket: socket
  218. });
  219. }
  220. socket.options.closed(e);
  221. _handleNextRequest(client, socket);
  222. }
  223. };
  224. socket.data = function(e) {
  225. socket.sending = false;
  226. var request = socket.options.request;
  227. if(request.aborted) {
  228. socket.close();
  229. } else {
  230. // receive all bytes available
  231. var response = socket.options.response;
  232. var bytes = socket.receive(e.bytesAvailable);
  233. if(bytes !== null) {
  234. // receive header and then body
  235. socket.buffer.putBytes(bytes);
  236. if(!response.headerReceived) {
  237. response.readHeader(socket.buffer);
  238. if(response.headerReceived) {
  239. socket.options.headerReady({
  240. request: socket.options.request,
  241. response: response,
  242. socket: socket
  243. });
  244. }
  245. }
  246. if(response.headerReceived && !response.bodyReceived) {
  247. response.readBody(socket.buffer);
  248. }
  249. if(response.bodyReceived) {
  250. socket.options.bodyReady({
  251. request: socket.options.request,
  252. response: response,
  253. socket: socket
  254. });
  255. // close connection if requested or by default on http/1.0
  256. var value = response.getField('Connection') || '';
  257. if(value.indexOf('close') != -1 ||
  258. (response.version === 'HTTP/1.0' &&
  259. response.getField('Keep-Alive') === null)) {
  260. socket.close();
  261. } else {
  262. _handleNextRequest(client, socket);
  263. }
  264. }
  265. }
  266. }
  267. };
  268. socket.error = function(e) {
  269. // do error callback, include request
  270. socket.options.error({
  271. type: e.type,
  272. message: e.message,
  273. request: socket.options.request,
  274. response: socket.options.response,
  275. socket: socket
  276. });
  277. socket.close();
  278. };
  279. // wrap socket for TLS
  280. if(tlsOptions) {
  281. socket = forge.tls.wrapSocket({
  282. sessionId: null,
  283. sessionCache: {},
  284. caStore: tlsOptions.caStore,
  285. cipherSuites: tlsOptions.cipherSuites,
  286. socket: socket,
  287. virtualHost: tlsOptions.virtualHost,
  288. verify: tlsOptions.verify,
  289. getCertificate: tlsOptions.getCertificate,
  290. getPrivateKey: tlsOptions.getPrivateKey,
  291. getSignature: tlsOptions.getSignature,
  292. deflate: tlsOptions.deflate || null,
  293. inflate: tlsOptions.inflate || null
  294. });
  295. socket.options = null;
  296. socket.buffer = forge.util.createBuffer();
  297. client.sockets.push(socket);
  298. if(tlsOptions.prime) {
  299. // prime socket by connecting and caching TLS session, will do
  300. // next request from there
  301. socket.connect({
  302. host: client.url.host,
  303. port: client.url.port,
  304. policyPort: client.policyPort,
  305. policyUrl: client.policyUrl
  306. });
  307. } else {
  308. // do not prime socket, just add as idle
  309. client.idle.push(socket);
  310. }
  311. } else {
  312. // no need to prime non-TLS sockets
  313. socket.buffer = forge.util.createBuffer();
  314. client.sockets.push(socket);
  315. client.idle.push(socket);
  316. }
  317. };
  318. /**
  319. * Checks to see if the given cookie has expired. If the cookie's max-age
  320. * plus its created time is less than the time now, it has expired, unless
  321. * its max-age is set to -1 which indicates it will never expire.
  322. *
  323. * @param cookie the cookie to check.
  324. *
  325. * @return true if it has expired, false if not.
  326. */
  327. var _hasCookieExpired = function(cookie) {
  328. var rval = false;
  329. if(cookie.maxAge !== -1) {
  330. var now = _getUtcTime(new Date());
  331. var expires = cookie.created + cookie.maxAge;
  332. if(expires <= now) {
  333. rval = true;
  334. }
  335. }
  336. return rval;
  337. };
  338. /**
  339. * Adds cookies in the given client to the given request.
  340. *
  341. * @param client the client.
  342. * @param request the request.
  343. */
  344. var _writeCookies = function(client, request) {
  345. var expired = [];
  346. var url = client.url;
  347. var cookies = client.cookies;
  348. for(var name in cookies) {
  349. // get cookie paths
  350. var paths = cookies[name];
  351. for(var p in paths) {
  352. var cookie = paths[p];
  353. if(_hasCookieExpired(cookie)) {
  354. // store for clean up
  355. expired.push(cookie);
  356. } else if(request.path.indexOf(cookie.path) === 0) {
  357. // path or path's ancestor must match cookie.path
  358. request.addCookie(cookie);
  359. }
  360. }
  361. }
  362. // clean up expired cookies
  363. for(var i = 0; i < expired.length; ++i) {
  364. var cookie = expired[i];
  365. client.removeCookie(cookie.name, cookie.path);
  366. }
  367. };
  368. /**
  369. * Gets cookies from the given response and adds the to the given client.
  370. *
  371. * @param client the client.
  372. * @param response the response.
  373. */
  374. var _readCookies = function(client, response) {
  375. var cookies = response.getCookies();
  376. for(var i = 0; i < cookies.length; ++i) {
  377. try {
  378. client.setCookie(cookies[i]);
  379. } catch(ex) {
  380. // ignore failure to add other-domain, etc. cookies
  381. }
  382. }
  383. };
  384. /**
  385. * Creates an http client that uses forge.net sockets as a backend and
  386. * forge.tls for security.
  387. *
  388. * @param options:
  389. * url: the url to connect to (scheme://host:port).
  390. * socketPool: the flash socket pool to use.
  391. * policyPort: the flash policy port to use (if other than the
  392. * socket pool default), use 0 for flash default.
  393. * policyUrl: the flash policy file URL to use (if provided will
  394. * be used instead of a policy port).
  395. * connections: number of connections to use to handle requests.
  396. * caCerts: an array of certificates to trust for TLS, certs may
  397. * be PEM-formatted or cert objects produced via forge.pki.
  398. * cipherSuites: an optional array of cipher suites to use,
  399. * see forge.tls.CipherSuites.
  400. * virtualHost: the virtual server name to use in a TLS SNI
  401. * extension, if not provided the url host will be used.
  402. * verify: a custom TLS certificate verify callback to use.
  403. * getCertificate: an optional callback used to get a client-side
  404. * certificate (see forge.tls for details).
  405. * getPrivateKey: an optional callback used to get a client-side
  406. * private key (see forge.tls for details).
  407. * getSignature: an optional callback used to get a client-side
  408. * signature (see forge.tls for details).
  409. * persistCookies: true to use persistent cookies via flash local
  410. * storage, false to only keep cookies in javascript.
  411. * primeTlsSockets: true to immediately connect TLS sockets on
  412. * their creation so that they will cache TLS sessions for reuse.
  413. *
  414. * @return the client.
  415. */
  416. http.createClient = function(options) {
  417. // create CA store to share with all TLS connections
  418. var caStore = null;
  419. if(options.caCerts) {
  420. caStore = forge.pki.createCaStore(options.caCerts);
  421. }
  422. // get scheme, host, and port from url
  423. options.url = (options.url ||
  424. window.location.protocol + '//' + window.location.host);
  425. var url = http.parseUrl(options.url);
  426. if(!url) {
  427. var error = new Error('Invalid url.');
  428. error.details = {url: options.url};
  429. throw error;
  430. }
  431. // default to 1 connection
  432. options.connections = options.connections || 1;
  433. // create client
  434. var sp = options.socketPool;
  435. var client = {
  436. // url
  437. url: url,
  438. // socket pool
  439. socketPool: sp,
  440. // the policy port to use
  441. policyPort: options.policyPort,
  442. // policy url to use
  443. policyUrl: options.policyUrl,
  444. // queue of requests to service
  445. requests: [],
  446. // all sockets
  447. sockets: [],
  448. // idle sockets
  449. idle: [],
  450. // whether or not the connections are secure
  451. secure: (url.scheme === 'https'),
  452. // cookie jar (key'd off of name and then path, there is only 1 domain
  453. // and one setting for secure per client so name+path is unique)
  454. cookies: {},
  455. // default to flash storage of cookies
  456. persistCookies: (typeof(options.persistCookies) === 'undefined') ?
  457. true : options.persistCookies
  458. };
  459. // add client to debug storage
  460. if(forge.debug) {
  461. forge.debug.get('forge.http', 'clients').push(client);
  462. }
  463. // load cookies from disk
  464. _loadCookies(client);
  465. /**
  466. * A default certificate verify function that checks a certificate common
  467. * name against the client's URL host.
  468. *
  469. * @param c the TLS connection.
  470. * @param verified true if cert is verified, otherwise alert number.
  471. * @param depth the chain depth.
  472. * @param certs the cert chain.
  473. *
  474. * @return true if verified and the common name matches the host, error
  475. * otherwise.
  476. */
  477. var _defaultCertificateVerify = function(c, verified, depth, certs) {
  478. if(depth === 0 && verified === true) {
  479. // compare common name to url host
  480. var cn = certs[depth].subject.getField('CN');
  481. if(cn === null || client.url.host !== cn.value) {
  482. verified = {
  483. message: 'Certificate common name does not match url host.'
  484. };
  485. }
  486. }
  487. return verified;
  488. };
  489. // determine if TLS is used
  490. var tlsOptions = null;
  491. if(client.secure) {
  492. tlsOptions = {
  493. caStore: caStore,
  494. cipherSuites: options.cipherSuites || null,
  495. virtualHost: options.virtualHost || url.host,
  496. verify: options.verify || _defaultCertificateVerify,
  497. getCertificate: options.getCertificate || null,
  498. getPrivateKey: options.getPrivateKey || null,
  499. getSignature: options.getSignature || null,
  500. prime: options.primeTlsSockets || false
  501. };
  502. // if socket pool uses a flash api, then add deflate support to TLS
  503. if(sp.flashApi !== null) {
  504. tlsOptions.deflate = function(bytes) {
  505. // strip 2 byte zlib header and 4 byte trailer
  506. return forge.util.deflate(sp.flashApi, bytes, true);
  507. };
  508. tlsOptions.inflate = function(bytes) {
  509. return forge.util.inflate(sp.flashApi, bytes, true);
  510. };
  511. }
  512. }
  513. // create and initialize sockets
  514. for(var i = 0; i < options.connections; ++i) {
  515. _initSocket(client, sp.createSocket(), tlsOptions);
  516. }
  517. /**
  518. * Sends a request. A method 'abort' will be set on the request that
  519. * can be called to attempt to abort the request.
  520. *
  521. * @param options:
  522. * request: the request to send.
  523. * connected: a callback for when the connection is open.
  524. * closed: a callback for when the connection is closed.
  525. * headerReady: a callback for when the response header arrives.
  526. * bodyReady: a callback for when the response body arrives.
  527. * error: a callback for if an error occurs.
  528. */
  529. client.send = function(options) {
  530. // add host header if not set
  531. if(options.request.getField('Host') === null) {
  532. options.request.setField('Host', client.url.fullHost);
  533. }
  534. // set default dummy handlers
  535. var opts = {};
  536. opts.request = options.request;
  537. opts.connected = options.connected || function() {};
  538. opts.closed = options.close || function() {};
  539. opts.headerReady = function(e) {
  540. // read cookies
  541. _readCookies(client, e.response);
  542. if(options.headerReady) {
  543. options.headerReady(e);
  544. }
  545. };
  546. opts.bodyReady = options.bodyReady || function() {};
  547. opts.error = options.error || function() {};
  548. // create response
  549. opts.response = http.createResponse();
  550. opts.response.time = 0;
  551. opts.response.flashApi = client.socketPool.flashApi;
  552. opts.request.flashApi = client.socketPool.flashApi;
  553. // create abort function
  554. opts.request.abort = function() {
  555. // set aborted, clear handlers
  556. opts.request.aborted = true;
  557. opts.connected = function() {};
  558. opts.closed = function() {};
  559. opts.headerReady = function() {};
  560. opts.bodyReady = function() {};
  561. opts.error = function() {};
  562. };
  563. // add cookies to request
  564. _writeCookies(client, opts.request);
  565. // queue request options if there are no idle sockets
  566. if(client.idle.length === 0) {
  567. client.requests.push(opts);
  568. } else {
  569. // use an idle socket, prefer an idle *connected* socket first
  570. var socket = null;
  571. var len = client.idle.length;
  572. for(var i = 0; socket === null && i < len; ++i) {
  573. socket = client.idle[i];
  574. if(socket.isConnected()) {
  575. client.idle.splice(i, 1);
  576. } else {
  577. socket = null;
  578. }
  579. }
  580. // no connected socket available, get unconnected socket
  581. if(socket === null) {
  582. socket = client.idle.pop();
  583. }
  584. socket.options = opts;
  585. _doRequest(client, socket);
  586. }
  587. };
  588. /**
  589. * Destroys this client.
  590. */
  591. client.destroy = function() {
  592. // clear pending requests, close and destroy sockets
  593. client.requests = [];
  594. for(var i = 0; i < client.sockets.length; ++i) {
  595. client.sockets[i].close();
  596. client.sockets[i].destroy();
  597. }
  598. client.socketPool = null;
  599. client.sockets = [];
  600. client.idle = [];
  601. };
  602. /**
  603. * Sets a cookie for use with all connections made by this client. Any
  604. * cookie with the same name will be replaced. If the cookie's value
  605. * is undefined, null, or the blank string, the cookie will be removed.
  606. *
  607. * If the cookie's domain doesn't match this client's url host or the
  608. * cookie's secure flag doesn't match this client's url scheme, then
  609. * setting the cookie will fail with an exception.
  610. *
  611. * @param cookie the cookie with parameters:
  612. * name: the name of the cookie.
  613. * value: the value of the cookie.
  614. * comment: an optional comment string.
  615. * maxAge: the age of the cookie in seconds relative to created time.
  616. * secure: true if the cookie must be sent over a secure protocol.
  617. * httpOnly: true to restrict access to the cookie from javascript
  618. * (inaffective since the cookies are stored in javascript).
  619. * path: the path for the cookie.
  620. * domain: optional domain the cookie belongs to (must start with dot).
  621. * version: optional version of the cookie.
  622. * created: creation time, in UTC seconds, of the cookie.
  623. */
  624. client.setCookie = function(cookie) {
  625. var rval;
  626. if(typeof(cookie.name) !== 'undefined') {
  627. if(cookie.value === null || typeof(cookie.value) === 'undefined' ||
  628. cookie.value === '') {
  629. // remove cookie
  630. rval = client.removeCookie(cookie.name, cookie.path);
  631. } else {
  632. // set cookie defaults
  633. cookie.comment = cookie.comment || '';
  634. cookie.maxAge = cookie.maxAge || 0;
  635. cookie.secure = (typeof(cookie.secure) === 'undefined') ?
  636. true : cookie.secure;
  637. cookie.httpOnly = cookie.httpOnly || true;
  638. cookie.path = cookie.path || '/';
  639. cookie.domain = cookie.domain || null;
  640. cookie.version = cookie.version || null;
  641. cookie.created = _getUtcTime(new Date());
  642. // do secure check
  643. if(cookie.secure !== client.secure) {
  644. var error = new Error('Http client url scheme is incompatible ' +
  645. 'with cookie secure flag.');
  646. error.url = client.url;
  647. error.cookie = cookie;
  648. throw error;
  649. }
  650. // make sure url host is within cookie.domain
  651. if(!http.withinCookieDomain(client.url, cookie)) {
  652. var error = new Error('Http client url scheme is incompatible ' +
  653. 'with cookie secure flag.');
  654. error.url = client.url;
  655. error.cookie = cookie;
  656. throw error;
  657. }
  658. // add new cookie
  659. if(!(cookie.name in client.cookies)) {
  660. client.cookies[cookie.name] = {};
  661. }
  662. client.cookies[cookie.name][cookie.path] = cookie;
  663. rval = true;
  664. // save cookies
  665. _saveCookies(client);
  666. }
  667. }
  668. return rval;
  669. };
  670. /**
  671. * Gets a cookie by its name.
  672. *
  673. * @param name the name of the cookie to retrieve.
  674. * @param path an optional path for the cookie (if there are multiple
  675. * cookies with the same name but different paths).
  676. *
  677. * @return the cookie or null if not found.
  678. */
  679. client.getCookie = function(name, path) {
  680. var rval = null;
  681. if(name in client.cookies) {
  682. var paths = client.cookies[name];
  683. // get path-specific cookie
  684. if(path) {
  685. if(path in paths) {
  686. rval = paths[path];
  687. }
  688. } else {
  689. // get first cookie
  690. for(var p in paths) {
  691. rval = paths[p];
  692. break;
  693. }
  694. }
  695. }
  696. return rval;
  697. };
  698. /**
  699. * Removes a cookie.
  700. *
  701. * @param name the name of the cookie to remove.
  702. * @param path an optional path for the cookie (if there are multiple
  703. * cookies with the same name but different paths).
  704. *
  705. * @return true if a cookie was removed, false if not.
  706. */
  707. client.removeCookie = function(name, path) {
  708. var rval = false;
  709. if(name in client.cookies) {
  710. // delete the specific path
  711. if(path) {
  712. var paths = client.cookies[name];
  713. if(path in paths) {
  714. rval = true;
  715. delete client.cookies[name][path];
  716. // clean up entry if empty
  717. var empty = true;
  718. for(var i in client.cookies[name]) {
  719. empty = false;
  720. break;
  721. }
  722. if(empty) {
  723. delete client.cookies[name];
  724. }
  725. }
  726. } else {
  727. // delete all cookies with the given name
  728. rval = true;
  729. delete client.cookies[name];
  730. }
  731. }
  732. if(rval) {
  733. // save cookies
  734. _saveCookies(client);
  735. }
  736. return rval;
  737. };
  738. /**
  739. * Clears all cookies stored in this client.
  740. */
  741. client.clearCookies = function() {
  742. client.cookies = {};
  743. _clearCookies(client);
  744. };
  745. if(forge.log) {
  746. forge.log.debug('forge.http', 'created client', options);
  747. }
  748. return client;
  749. };
  750. /**
  751. * Trims the whitespace off of the beginning and end of a string.
  752. *
  753. * @param str the string to trim.
  754. *
  755. * @return the trimmed string.
  756. */
  757. var _trimString = function(str) {
  758. return str.replace(/^\s*/, '').replace(/\s*$/, '');
  759. };
  760. /**
  761. * Creates an http header object.
  762. *
  763. * @return the http header object.
  764. */
  765. var _createHeader = function() {
  766. var header = {
  767. fields: {},
  768. setField: function(name, value) {
  769. // normalize field name, trim value
  770. header.fields[_normalize(name)] = [_trimString('' + value)];
  771. },
  772. appendField: function(name, value) {
  773. name = _normalize(name);
  774. if(!(name in header.fields)) {
  775. header.fields[name] = [];
  776. }
  777. header.fields[name].push(_trimString('' + value));
  778. },
  779. getField: function(name, index) {
  780. var rval = null;
  781. name = _normalize(name);
  782. if(name in header.fields) {
  783. index = index || 0;
  784. rval = header.fields[name][index];
  785. }
  786. return rval;
  787. }
  788. };
  789. return header;
  790. };
  791. /**
  792. * Gets the time in utc seconds given a date.
  793. *
  794. * @param d the date to use.
  795. *
  796. * @return the time in utc seconds.
  797. */
  798. var _getUtcTime = function(d) {
  799. var utc = +d + d.getTimezoneOffset() * 60000;
  800. return Math.floor(+new Date() / 1000);
  801. };
  802. /**
  803. * Creates an http request.
  804. *
  805. * @param options:
  806. * version: the version.
  807. * method: the method.
  808. * path: the path.
  809. * body: the body.
  810. * headers: custom header fields to add,
  811. * eg: [{'Content-Length': 0}].
  812. *
  813. * @return the http request.
  814. */
  815. http.createRequest = function(options) {
  816. options = options || {};
  817. var request = _createHeader();
  818. request.version = options.version || 'HTTP/1.1';
  819. request.method = options.method || null;
  820. request.path = options.path || null;
  821. request.body = options.body || null;
  822. request.bodyDeflated = false;
  823. request.flashApi = null;
  824. // add custom headers
  825. var headers = options.headers || [];
  826. if(!forge.util.isArray(headers)) {
  827. headers = [headers];
  828. }
  829. for(var i = 0; i < headers.length; ++i) {
  830. for(var name in headers[i]) {
  831. request.appendField(name, headers[i][name]);
  832. }
  833. }
  834. /**
  835. * Adds a cookie to the request 'Cookie' header.
  836. *
  837. * @param cookie a cookie to add.
  838. */
  839. request.addCookie = function(cookie) {
  840. var value = '';
  841. var field = request.getField('Cookie');
  842. if(field !== null) {
  843. // separate cookies by semi-colons
  844. value = field + '; ';
  845. }
  846. // get current time in utc seconds
  847. var now = _getUtcTime(new Date());
  848. // output cookie name and value
  849. value += cookie.name + '=' + cookie.value;
  850. request.setField('Cookie', value);
  851. };
  852. /**
  853. * Converts an http request into a string that can be sent as an
  854. * HTTP request. Does not include any data.
  855. *
  856. * @return the string representation of the request.
  857. */
  858. request.toString = function() {
  859. /* Sample request header:
  860. GET /some/path/?query HTTP/1.1
  861. Host: www.someurl.com
  862. Connection: close
  863. Accept-Encoding: deflate
  864. Accept: image/gif, text/html
  865. User-Agent: Mozilla 4.0
  866. */
  867. // set default headers
  868. if(request.getField('User-Agent') === null) {
  869. request.setField('User-Agent', 'forge.http 1.0');
  870. }
  871. if(request.getField('Accept') === null) {
  872. request.setField('Accept', '*/*');
  873. }
  874. if(request.getField('Connection') === null) {
  875. request.setField('Connection', 'keep-alive');
  876. request.setField('Keep-Alive', '115');
  877. }
  878. // add Accept-Encoding if not specified
  879. if(request.flashApi !== null &&
  880. request.getField('Accept-Encoding') === null) {
  881. request.setField('Accept-Encoding', 'deflate');
  882. }
  883. // if the body isn't null, deflate it if its larger than 100 bytes
  884. if(request.flashApi !== null && request.body !== null &&
  885. request.getField('Content-Encoding') === null &&
  886. !request.bodyDeflated && request.body.length > 100) {
  887. // use flash to compress data
  888. request.body = forge.util.deflate(request.flashApi, request.body);
  889. request.bodyDeflated = true;
  890. request.setField('Content-Encoding', 'deflate');
  891. request.setField('Content-Length', request.body.length);
  892. } else if(request.body !== null) {
  893. // set content length for body
  894. request.setField('Content-Length', request.body.length);
  895. }
  896. // build start line
  897. var rval =
  898. request.method.toUpperCase() + ' ' + request.path + ' ' +
  899. request.version + '\r\n';
  900. // add each header
  901. for(var name in request.fields) {
  902. var fields = request.fields[name];
  903. for(var i = 0; i < fields.length; ++i) {
  904. rval += name + ': ' + fields[i] + '\r\n';
  905. }
  906. }
  907. // final terminating CRLF
  908. rval += '\r\n';
  909. return rval;
  910. };
  911. return request;
  912. };
  913. /**
  914. * Creates an empty http response header.
  915. *
  916. * @return the empty http response header.
  917. */
  918. http.createResponse = function() {
  919. // private vars
  920. var _first = true;
  921. var _chunkSize = 0;
  922. var _chunksFinished = false;
  923. // create response
  924. var response = _createHeader();
  925. response.version = null;
  926. response.code = 0;
  927. response.message = null;
  928. response.body = null;
  929. response.headerReceived = false;
  930. response.bodyReceived = false;
  931. response.flashApi = null;
  932. /**
  933. * Reads a line that ends in CRLF from a byte buffer.
  934. *
  935. * @param b the byte buffer.
  936. *
  937. * @return the line or null if none was found.
  938. */
  939. var _readCrlf = function(b) {
  940. var line = null;
  941. var i = b.data.indexOf('\r\n', b.read);
  942. if(i != -1) {
  943. // read line, skip CRLF
  944. line = b.getBytes(i - b.read);
  945. b.getBytes(2);
  946. }
  947. return line;
  948. };
  949. /**
  950. * Parses a header field and appends it to the response.
  951. *
  952. * @param line the header field line.
  953. */
  954. var _parseHeader = function(line) {
  955. var tmp = line.indexOf(':');
  956. var name = line.substring(0, tmp++);
  957. response.appendField(
  958. name, (tmp < line.length) ? line.substring(tmp) : '');
  959. };
  960. /**
  961. * Reads an http response header from a buffer of bytes.
  962. *
  963. * @param b the byte buffer to parse the header from.
  964. *
  965. * @return true if the whole header was read, false if not.
  966. */
  967. response.readHeader = function(b) {
  968. // read header lines (each ends in CRLF)
  969. var line = '';
  970. while(!response.headerReceived && line !== null) {
  971. line = _readCrlf(b);
  972. if(line !== null) {
  973. // parse first line
  974. if(_first) {
  975. _first = false;
  976. var tmp = line.split(' ');
  977. if(tmp.length >= 3) {
  978. response.version = tmp[0];
  979. response.code = parseInt(tmp[1], 10);
  980. response.message = tmp.slice(2).join(' ');
  981. } else {
  982. // invalid header
  983. var error = new Error('Invalid http response header.');
  984. error.details = {'line': line};
  985. throw error;
  986. }
  987. } else if(line.length === 0) {
  988. // handle final line, end of header
  989. response.headerReceived = true;
  990. } else {
  991. _parseHeader(line);
  992. }
  993. }
  994. }
  995. return response.headerReceived;
  996. };
  997. /**
  998. * Reads some chunked http response entity-body from the given buffer of
  999. * bytes.
  1000. *
  1001. * @param b the byte buffer to read from.
  1002. *
  1003. * @return true if the whole body was read, false if not.
  1004. */
  1005. var _readChunkedBody = function(b) {
  1006. /* Chunked transfer-encoding sends data in a series of chunks,
  1007. followed by a set of 0-N http trailers.
  1008. The format is as follows:
  1009. chunk-size (in hex) CRLF
  1010. chunk data (with "chunk-size" many bytes) CRLF
  1011. ... (N many chunks)
  1012. chunk-size (of 0 indicating the last chunk) CRLF
  1013. N many http trailers followed by CRLF
  1014. blank line + CRLF (terminates the trailers)
  1015. If there are no http trailers, then after the chunk-size of 0,
  1016. there is still a single CRLF (indicating the blank line + CRLF
  1017. that terminates the trailers). In other words, you always terminate
  1018. the trailers with blank line + CRLF, regardless of 0-N trailers. */
  1019. /* From RFC-2616, section 3.6.1, here is the pseudo-code for
  1020. implementing chunked transfer-encoding:
  1021. length := 0
  1022. read chunk-size, chunk-extension (if any) and CRLF
  1023. while (chunk-size > 0) {
  1024. read chunk-data and CRLF
  1025. append chunk-data to entity-body
  1026. length := length + chunk-size
  1027. read chunk-size and CRLF
  1028. }
  1029. read entity-header
  1030. while (entity-header not empty) {
  1031. append entity-header to existing header fields
  1032. read entity-header
  1033. }
  1034. Content-Length := length
  1035. Remove "chunked" from Transfer-Encoding
  1036. */
  1037. var line = '';
  1038. while(line !== null && b.length() > 0) {
  1039. // if in the process of reading a chunk
  1040. if(_chunkSize > 0) {
  1041. // if there are not enough bytes to read chunk and its
  1042. // trailing CRLF, we must wait for more data to be received
  1043. if(_chunkSize + 2 > b.length()) {
  1044. break;
  1045. }
  1046. // read chunk data, skip CRLF
  1047. response.body += b.getBytes(_chunkSize);
  1048. b.getBytes(2);
  1049. _chunkSize = 0;
  1050. } else if(!_chunksFinished) {
  1051. // more chunks, read next chunk-size line
  1052. line = _readCrlf(b);
  1053. if(line !== null) {
  1054. // parse chunk-size (ignore any chunk extension)
  1055. _chunkSize = parseInt(line.split(';', 1)[0], 16);
  1056. _chunksFinished = (_chunkSize === 0);
  1057. }
  1058. } else {
  1059. // chunks finished, read next trailer
  1060. line = _readCrlf(b);
  1061. while(line !== null) {
  1062. if(line.length > 0) {
  1063. // parse trailer
  1064. _parseHeader(line);
  1065. // read next trailer
  1066. line = _readCrlf(b);
  1067. } else {
  1068. // body received
  1069. response.bodyReceived = true;
  1070. line = null;
  1071. }
  1072. }
  1073. }
  1074. }
  1075. return response.bodyReceived;
  1076. };
  1077. /**
  1078. * Reads an http response body from a buffer of bytes.
  1079. *
  1080. * @param b the byte buffer to read from.
  1081. *
  1082. * @return true if the whole body was read, false if not.
  1083. */
  1084. response.readBody = function(b) {
  1085. var contentLength = response.getField('Content-Length');
  1086. var transferEncoding = response.getField('Transfer-Encoding');
  1087. if(contentLength !== null) {
  1088. contentLength = parseInt(contentLength);
  1089. }
  1090. // read specified length
  1091. if(contentLength !== null && contentLength >= 0) {
  1092. response.body = response.body || '';
  1093. response.body += b.getBytes(contentLength);
  1094. response.bodyReceived = (response.body.length === contentLength);
  1095. } else if(transferEncoding !== null) {
  1096. // read chunked encoding
  1097. if(transferEncoding.indexOf('chunked') != -1) {
  1098. response.body = response.body || '';
  1099. _readChunkedBody(b);
  1100. } else {
  1101. var error = new Error('Unknown Transfer-Encoding.');
  1102. error.details = {'transferEncoding': transferEncoding};
  1103. throw error;
  1104. }
  1105. } else if((contentLength !== null && contentLength < 0) ||
  1106. (contentLength === null &&
  1107. response.getField('Content-Type') !== null)) {
  1108. // read all data in the buffer
  1109. response.body = response.body || '';
  1110. response.body += b.getBytes();
  1111. response.readBodyUntilClose = true;
  1112. } else {
  1113. // no body
  1114. response.body = null;
  1115. response.bodyReceived = true;
  1116. }
  1117. if(response.bodyReceived) {
  1118. response.time = +new Date() - response.time;
  1119. }
  1120. if(response.flashApi !== null &&
  1121. response.bodyReceived && response.body !== null &&
  1122. response.getField('Content-Encoding') === 'deflate') {
  1123. // inflate using flash api
  1124. response.body = forge.util.inflate(
  1125. response.flashApi, response.body);
  1126. }
  1127. return response.bodyReceived;
  1128. };
  1129. /**
  1130. * Parses an array of cookies from the 'Set-Cookie' field, if present.
  1131. *
  1132. * @return the array of cookies.
  1133. */
  1134. response.getCookies = function() {
  1135. var rval = [];
  1136. // get Set-Cookie field
  1137. if('Set-Cookie' in response.fields) {
  1138. var field = response.fields['Set-Cookie'];
  1139. // get current local time in seconds
  1140. var now = +new Date() / 1000;
  1141. // regex for parsing 'name1=value1; name2=value2; name3'
  1142. var regex = /\s*([^=]*)=?([^;]*)(;|$)/g;
  1143. // examples:
  1144. // Set-Cookie: cookie1_name=cookie1_value; max-age=0; path=/
  1145. // Set-Cookie: c2=v2; expires=Thu, 21-Aug-2008 23:47:25 GMT; path=/
  1146. for(var i = 0; i < field.length; ++i) {
  1147. var fv = field[i];
  1148. var m;
  1149. regex.lastIndex = 0;
  1150. var first = true;
  1151. var cookie = {};
  1152. do {
  1153. m = regex.exec(fv);
  1154. if(m !== null) {
  1155. var name = _trimString(m[1]);
  1156. var value = _trimString(m[2]);
  1157. // cookie_name=value
  1158. if(first) {
  1159. cookie.name = name;
  1160. cookie.value = value;
  1161. first = false;
  1162. } else {
  1163. // property_name=value
  1164. name = name.toLowerCase();
  1165. switch(name) {
  1166. case 'expires':
  1167. // replace hyphens w/spaces so date will parse
  1168. value = value.replace(/-/g, ' ');
  1169. var secs = Date.parse(value) / 1000;
  1170. cookie.maxAge = Math.max(0, secs - now);
  1171. break;
  1172. case 'max-age':
  1173. cookie.maxAge = parseInt(value, 10);
  1174. break;
  1175. case 'secure':
  1176. cookie.secure = true;
  1177. break;
  1178. case 'httponly':
  1179. cookie.httpOnly = true;
  1180. break;
  1181. default:
  1182. if(name !== '') {
  1183. cookie[name] = value;
  1184. }
  1185. }
  1186. }
  1187. }
  1188. } while(m !== null && m[0] !== '');
  1189. rval.push(cookie);
  1190. }
  1191. }
  1192. return rval;
  1193. };
  1194. /**
  1195. * Converts an http response into a string that can be sent as an
  1196. * HTTP response. Does not include any data.
  1197. *
  1198. * @return the string representation of the response.
  1199. */
  1200. response.toString = function() {
  1201. /* Sample response header:
  1202. HTTP/1.0 200 OK
  1203. Host: www.someurl.com
  1204. Connection: close
  1205. */
  1206. // build start line
  1207. var rval =
  1208. response.version + ' ' + response.code + ' ' + response.message + '\r\n';
  1209. // add each header
  1210. for(var name in response.fields) {
  1211. var fields = response.fields[name];
  1212. for(var i = 0; i < fields.length; ++i) {
  1213. rval += name + ': ' + fields[i] + '\r\n';
  1214. }
  1215. }
  1216. // final terminating CRLF
  1217. rval += '\r\n';
  1218. return rval;
  1219. };
  1220. return response;
  1221. };
  1222. /**
  1223. * Parses the scheme, host, and port from an http(s) url.
  1224. *
  1225. * @param str the url string.
  1226. *
  1227. * @return the parsed url object or null if the url is invalid.
  1228. */
  1229. http.parseUrl = forge.util.parseUrl;
  1230. /**
  1231. * Returns true if the given url is within the given cookie's domain.
  1232. *
  1233. * @param url the url to check.
  1234. * @param cookie the cookie or cookie domain to check.
  1235. */
  1236. http.withinCookieDomain = function(url, cookie) {
  1237. var rval = false;
  1238. // cookie may be null, a cookie object, or a domain string
  1239. var domain = (cookie === null || typeof cookie === 'string') ?
  1240. cookie : cookie.domain;
  1241. // any domain will do
  1242. if(domain === null) {
  1243. rval = true;
  1244. } else if(domain.charAt(0) === '.') {
  1245. // ensure domain starts with a '.'
  1246. // parse URL as necessary
  1247. if(typeof url === 'string') {
  1248. url = http.parseUrl(url);
  1249. }
  1250. // add '.' to front of URL host to match against domain
  1251. var host = '.' + url.host;
  1252. // if the host ends with domain then it falls within it
  1253. var idx = host.lastIndexOf(domain);
  1254. if(idx !== -1 && (idx + domain.length === host.length)) {
  1255. rval = true;
  1256. }
  1257. }
  1258. return rval;
  1259. };