pax_global_header00006660000000000000000000000064131225453560014520gustar00rootroot0000000000000052 comment=c3770de92eb2898ccc638f269a1046919329bd81 follow-redirects-1.2.4/000077500000000000000000000000001312254535600150105ustar00rootroot00000000000000follow-redirects-1.2.4/.editorconfig000066400000000000000000000001211312254535600174570ustar00rootroot00000000000000root = true [*] indent_style = tab end_of_line = lf insert_final_newline = true follow-redirects-1.2.4/.gitignore000066400000000000000000000000421312254535600167740ustar00rootroot00000000000000node_modules coverage .nyc_output follow-redirects-1.2.4/.travis.yml000066400000000000000000000003021312254535600171140ustar00rootroot00000000000000language: node_js sudo: false node_js: - '6' - '5' - '4' after_script: - cat ./coverage/lcov.info | ./node_modules/coveralls/bin/coveralls.js cache: directories: - node_modules follow-redirects-1.2.4/LICENSE000066400000000000000000000021451312254535600160170ustar00rootroot00000000000000Copyright 2017 Olivier Lalonde , James Talmage , Ruben Verborgh Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.follow-redirects-1.2.4/README.md000066400000000000000000000134341312254535600162740ustar00rootroot00000000000000## Follow Redirects Drop-in replacement for Nodes `http` and `https` that automatically follows redirects. [![npm version](https://badge.fury.io/js/follow-redirects.svg)](https://www.npmjs.com/package/follow-redirects) [![Build Status](https://travis-ci.org/olalonde/follow-redirects.svg?branch=master)](https://travis-ci.org/olalonde/follow-redirects) [![Coverage Status](https://coveralls.io/repos/olalonde/follow-redirects/badge.svg?branch=master)](https://coveralls.io/r/olalonde/follow-redirects?branch=master) [![Code Climate](https://codeclimate.com/github/olalonde/follow-redirects/badges/gpa.svg)](https://codeclimate.com/github/olalonde/follow-redirects) [![Dependency Status](https://david-dm.org/olalonde/follow-redirects.svg)](https://david-dm.org/olalonde/follow-redirects) [![devDependency Status](https://david-dm.org/olalonde/follow-redirects/dev-status.svg)](https://david-dm.org/olalonde/follow-redirects#info=devDependencies) [![NPM](https://nodei.co/npm/follow-redirects.png?downloads=true)](https://nodei.co/npm/follow-redirects/) `follow-redirects` provides [request](https://nodejs.org/api/http.html#http_http_request_options_callback) and [get](https://nodejs.org/api/http.html#http_http_get_options_callback) methods that behave identically to those found on the native [http](https://nodejs.org/api/http.html#http_http_request_options_callback) and [https](https://nodejs.org/api/https.html#https_https_request_options_callback) modules, with the exception that they will seamlessly follow redirects. ```javascript var http = require('follow-redirects').http; var https = require('follow-redirects').https; http.get('http://bit.ly/900913', function (response) { response.on('data', function (chunk) { console.log(chunk); }); }).on('error', function (err) { console.error(err); }); ``` You can inspect the final redirected URL through the `responseUrl` property on the `response`. If no redirection happened, `responseUrl` is the original request URL. ```javascript https.request({ host: 'bitly.com', path: '/UHfDGO', }, function (response) { console.log(response.responseUrl); // 'http://duckduckgo.com/robots.txt' }); ``` ## Options ### Global options Global options are set directly on the `follow-redirects` module: ```javascript var followRedirects = require('follow-redirects'); followRedirects.maxRedirects = 10; ``` The following global options are supported: - `maxRedirects` (default: `21`) – sets the maximum number of allowed redirects; if exceeded, an error will be emitted. ### Per-request options Per-request options are set by passing an `options` object: ```javascript var url = require('url'); var followRedirects = require('follow-redirects'); var options = url.parse('http://bit.ly/900913'); options.maxRedirects = 10; http.request(options); ``` In addition to the [standard HTTP](https://nodejs.org/api/http.html#http_http_request_options_callback) and [HTTPS options](https://nodejs.org/api/https.html#https_https_request_options_callback), the following per-request options are supported: - `followRedirects` (default: `true`) – whether redirects should be followed. - `maxRedirects` (default: `21`) – sets the maximum number of allowed redirects; if exceeded, an error will be emitted. - `agents` (default: `undefined`) – sets the `agent` option per protocol, since HTTP and HTTPS use different agents. Example value: `{ http: new http.Agent(), https: new https.Agent() }` ## Browserify Usage Due to the way `XMLHttpRequest` works, the `browserify` versions of `http` and `https` already follow redirects. If you are *only* targeting the browser, then this library has little value for you. If you want to write cross platform code for node and the browser, `follow-redirects` provides a great solution for making the native node modules behave the same as they do in browserified builds in the browser. To avoid bundling unnecessary code you should tell browserify to swap out `follow-redirects` with the standard modules when bundling. To make this easier, you need to change how you require the modules: ```javascript var http = require('follow-redirects/http'); var https = require('follow-redirects/https'); ``` You can then replace `follow-redirects` in your browserify configuration like so: ```javascript "browser": { "follow-redirects/http" : "http", "follow-redirects/https" : "https" } ``` The `browserify-http` module has not kept pace with node development, and no long behaves identically to the native module when running in the browser. If you are experiencing problems, you may want to check out [browserify-http-2](https://www.npmjs.com/package/http-browserify-2). It is more actively maintained and attempts to address a few of the shortcomings of `browserify-http`. In that case, your browserify config should look something like this: ```javascript "browser": { "follow-redirects/http" : "browserify-http-2/http", "follow-redirects/https" : "browserify-http-2/https" } ``` ## Contributing Pull Requests are always welcome. Please [file an issue](https://github.com/olalonde/follow-redirects/issues) detailing your proposal before you invest your valuable time. Additional features and bug fixes should be accompanied by tests. You can run the test suite locally with a simple `npm test` command. ## Debug Logging `follow-redirects` uses the excellent [debug](https://www.npmjs.com/package/debug) for logging. To turn on logging set the environment variable `DEBUG=follow-redirects` for debug output from just this module. When running the test suite it is sometimes advantageous to set `DEBUG=*` to see output from the express server as well. ## Authors - Olivier Lalonde (olalonde@gmail.com) - James Talmage (james@talmage.io) - [Ruben Verborgh](https://ruben.verborgh.org/) ## License MIT: [http://olalonde.mit-license.org](http://olalonde.mit-license.org) follow-redirects-1.2.4/http.js000066400000000000000000000000451312254535600163240ustar00rootroot00000000000000module.exports = require('./').http; follow-redirects-1.2.4/https.js000066400000000000000000000000461312254535600165100ustar00rootroot00000000000000module.exports = require('./').https; follow-redirects-1.2.4/index.js000066400000000000000000000173701312254535600164650ustar00rootroot00000000000000'use strict'; var url = require('url'); var assert = require('assert'); var http = require('http'); var https = require('https'); var Writable = require('stream').Writable; var debug = require('debug')('follow-redirects'); var nativeProtocols = {'http:': http, 'https:': https}; var schemes = {}; var exports = module.exports = { maxRedirects: 21 }; // RFC7231§4.2.1: Of the request methods defined by this specification, // the GET, HEAD, OPTIONS, and TRACE methods are defined to be safe. var safeMethods = {GET: true, HEAD: true, OPTIONS: true, TRACE: true}; // Create handlers that pass events from native requests var eventHandlers = Object.create(null); ['abort', 'aborted', 'error', 'socket'].forEach(function (event) { eventHandlers[event] = function (arg) { this._redirectable.emit(event, arg); }; }); // An HTTP(S) request that can be redirected function RedirectableRequest(options, responseCallback) { // Initialize the request Writable.call(this); this._options = options; this._redirectCount = 0; this._bufferedWrites = []; // Attach a callback if passed if (responseCallback) { this.on('response', responseCallback); } // React to responses of native requests var self = this; this._onNativeResponse = function (response) { self._processResponse(response); }; // Complete the URL object when necessary if (!options.pathname && options.path) { var searchPos = options.path.indexOf('?'); if (searchPos < 0) { options.pathname = options.path; } else { options.pathname = options.path.substring(0, searchPos); options.search = options.path.substring(searchPos); } } // Perform the first request this._performRequest(); } RedirectableRequest.prototype = Object.create(Writable.prototype); // Executes the next native request (initial or redirect) RedirectableRequest.prototype._performRequest = function () { // If specified, use the agent corresponding to the protocol // (HTTP and HTTPS use different types of agents) var protocol = this._options.protocol; if (this._options.agents) { this._options.agent = this._options.agents[schemes[protocol]]; } // Create the native request var nativeProtocol = nativeProtocols[protocol]; var request = this._currentRequest = nativeProtocol.request(this._options, this._onNativeResponse); this._currentUrl = url.format(this._options); // Set up event handlers request._redirectable = this; for (var event in eventHandlers) { /* istanbul ignore else */ if (event) { request.on(event, eventHandlers[event]); } } // End a redirected request // (The first request must be ended explicitly with RedirectableRequest#end) if (this._isRedirect) { // If the request doesn't have en entity, end directly. var bufferedWrites = this._bufferedWrites; if (bufferedWrites.length === 0) { request.end(); // Otherwise, write the request entity and end afterwards. } else { var i = 0; (function writeNext() { if (i < bufferedWrites.length) { var bufferedWrite = bufferedWrites[i++]; request.write(bufferedWrite.data, bufferedWrite.encoding, writeNext); } else { request.end(); } })(); } } }; // Processes a response from the current native request RedirectableRequest.prototype._processResponse = function (response) { // RFC7231§6.4: The 3xx (Redirection) class of status code indicates // that further action needs to be taken by the user agent in order to // fulfill the request. If a Location header field is provided, // the user agent MAY automatically redirect its request to the URI // referenced by the Location field value, // even if the specific status code is not understood. var location = response.headers.location; if (location && this._options.followRedirects !== false && response.statusCode >= 300 && response.statusCode < 400) { // RFC7231§6.4: A client SHOULD detect and intervene // in cyclical redirections (i.e., "infinite" redirection loops). if (++this._redirectCount > this._options.maxRedirects) { return this.emit('error', new Error('Max redirects exceeded.')); } // RFC7231§6.4: Automatic redirection needs to done with // care for methods not known to be safe […], // since the user might not wish to redirect an unsafe request. // RFC7231§6.4.7: The 307 (Temporary Redirect) status code indicates // that the target resource resides temporarily under a different URI // and the user agent MUST NOT change the request method // if it performs an automatic redirection to that URI. var header; var headers = this._options.headers; if (response.statusCode !== 307 && !(this._options.method in safeMethods)) { this._options.method = 'GET'; // Drop a possible entity and headers related to it this._bufferedWrites = []; for (header in headers) { if (/^content-/i.test(header)) { delete headers[header]; } } } // Drop the Host header, as the redirect might lead to a different host if (!this._isRedirect) { for (header in headers) { if (/^host$/i.test(header)) { delete headers[header]; } } } // Perform the redirected request var redirectUrl = url.resolve(this._currentUrl, location); debug('redirecting to', redirectUrl); Object.assign(this._options, url.parse(redirectUrl)); this._isRedirect = true; this._performRequest(); } else { // The response is not a redirect; return it as-is response.responseUrl = this._currentUrl; this.emit('response', response); // Clean up delete this._options; delete this._bufferedWrites; } }; // Aborts the current native request RedirectableRequest.prototype.abort = function () { this._currentRequest.abort(); }; // Flushes the headers of the current native request RedirectableRequest.prototype.flushHeaders = function () { this._currentRequest.flushHeaders(); }; // Sets the noDelay option of the current native request RedirectableRequest.prototype.setNoDelay = function (noDelay) { this._currentRequest.setNoDelay(noDelay); }; // Sets the socketKeepAlive option of the current native request RedirectableRequest.prototype.setSocketKeepAlive = function (enable, initialDelay) { this._currentRequest.setSocketKeepAlive(enable, initialDelay); }; // Sets the timeout option of the current native request RedirectableRequest.prototype.setTimeout = function (timeout, callback) { this._currentRequest.setTimeout(timeout, callback); }; // Writes buffered data to the current native request RedirectableRequest.prototype.write = function (data, encoding, callback) { this._currentRequest.write(data, encoding, callback); this._bufferedWrites.push({data: data, encoding: encoding}); }; // Ends the current native request RedirectableRequest.prototype.end = function (data, encoding, callback) { this._currentRequest.end(data, encoding, callback); if (data) { this._bufferedWrites.push({data: data, encoding: encoding}); } }; // Export a redirecting wrapper for each native protocol Object.keys(nativeProtocols).forEach(function (protocol) { var scheme = schemes[protocol] = protocol.substr(0, protocol.length - 1); var nativeProtocol = nativeProtocols[protocol]; var wrappedProtocol = exports[scheme] = Object.create(nativeProtocol); // Executes an HTTP request, following redirects wrappedProtocol.request = function (options, callback) { if (typeof options === 'string') { options = url.parse(options); options.maxRedirects = exports.maxRedirects; } else { options = Object.assign({ maxRedirects: exports.maxRedirects, protocol: protocol }, options); } assert.equal(options.protocol, protocol, 'protocol mismatch'); debug('options', options); return new RedirectableRequest(options, callback); }; // Executes a GET request, following redirects wrappedProtocol.get = function (options, callback) { var request = wrappedProtocol.request(options, callback); request.end(); return request; }; }); follow-redirects-1.2.4/package.json000066400000000000000000000024721312254535600173030ustar00rootroot00000000000000{ "name": "follow-redirects", "version": "1.2.4", "description": "HTTP and HTTPS modules that follow redirects.", "main": "index.js", "engines": { "node": ">=4.0" }, "scripts": { "test": "xo && BLUEBIRD_DEBUG=1 nyc mocha" }, "repository": { "type": "git", "url": "git@github.com:olalonde/follow-redirects.git" }, "homepage": "https://github.com/olalonde/follow-redirects", "bugs": { "url": "https://github.com/olalonde/follow-redirects/issues" }, "keywords": [ "http", "https", "url", "redirect", "client", "location", "utility" ], "author": { "name": "Olivier Lalonde", "email": "olalonde@gmail.com", "url": "http://www.syskall.com" }, "contributors": [ "James Talmage ", "Ruben Verborgh (https://ruben.verborgh.org/)" ], "files": [ "index.js", "create.js", "http.js", "https.js" ], "dependencies": { "debug": "^2.4.5" }, "devDependencies": { "bluebird": "^3.4.0", "concat-stream": "^1.5.2", "coveralls": "^2.11.15", "express": "^4.13.0", "mocha": "^3.2.0", "nyc": "^10.0.0", "xo": "^0.17.1" }, "license": "MIT", "nyc": { "reporter": [ "lcov", "text" ] }, "xo": { "envs": [ "mocha" ] } } follow-redirects-1.2.4/test/000077500000000000000000000000001312254535600157675ustar00rootroot00000000000000follow-redirects-1.2.4/test/.jscsrc000066400000000000000000000000371312254535600172570ustar00rootroot00000000000000{ "maximumLineLength" : 120 }follow-redirects-1.2.4/test/lib/000077500000000000000000000000001312254535600165355ustar00rootroot00000000000000follow-redirects-1.2.4/test/lib/TestCA.crt000066400000000000000000000021431312254535600203720ustar00rootroot00000000000000-----BEGIN CERTIFICATE----- MIIDETCCAfmgAwIBAgIBATANBgkqhkiG9w0BAQUFADARMQ8wDQYDVQQDEwZUZXN0 Q0EwIBcNMTUwNzAzMTMzMTAwWhgPMjExNTA3MDMxMzMxMDBaMBExDzANBgNVBAMT BlRlc3RDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALdwplapjYyZ atba3kXm9UDTTUVDh9iasU1wTcK9RSvAuSKfQShI769X9EvbJ0EHGXhooe/Ncmhg U0wAsUNnPWc3YKgbVTpUr0VTfZdG9vkYa7p1Zpt1Xet3cVmcZo1IH1SJlyBFN7K1 heEDbvXzvF+4ukP6stcOIwWXa274N026a6SLf0M3pIeRcD3cgLxygV550pknOhmJ N7g1BlvBbEYRkVKhSVLcDwrz+K25KPiVzLOFvHaf5aZAs7w1F1BAc58FTyQmu05s T4mpVWronU/2ZzA8qW1Wrsglv9k1FtkJgNoKVXQkv7KuHjKvdV/try8umUwmr+Xg aFS1S+d/OIcCAwEAAaNyMHAwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUw+ww y/RQde3KShsgBYmvH0WCdL8wCwYDVR0PBAQDAgEGMBEGCWCGSAGG+EIBAQQEAwIA BzAeBglghkgBhvhCAQ0EERYPeGNhIGNlcnRpZmljYXRlMA0GCSqGSIb3DQEBBQUA A4IBAQCLaZrEHTiCfoMITpDprWwV0QDj0t2vedI585N6//FH61OQOgc75nkmR6Wh +gPCfc6qOYC+UHKuz4MkodiTozw7d8cf+1pbkOL9iTh4o7EDiiRzTfIFmBR2u/dB kj8acTSdWR99TCtYEXZ22TV/+PdL0uB2ZJKKT37gPW3iDGAvBIux0iXaOnbdQokt 600YyF5uhqihWrHZ/XK4bbyEay5yUqOEJfEYT0fLyIP59+xiJP24DhDXnxP8Pyjm 1Y1z4bEk46aewlPKIDPOWGVM8wRA7tM2n5UBPsJa2nYfDgISI9etvqHBFBDlZ7/9 ZxQCtlp7TcQuWJ57z/Ew4pj/wTSI -----END CERTIFICATE----- follow-redirects-1.2.4/test/lib/TestCA.pem000066400000000000000000000032131312254535600203620ustar00rootroot00000000000000-----BEGIN RSA PRIVATE KEY----- MIIEowIBAAKCAQEAt3CmVqmNjJlq1treReb1QNNNRUOH2JqxTXBNwr1FK8C5Ip9B KEjvr1f0S9snQQcZeGih781yaGBTTACxQ2c9ZzdgqBtVOlSvRVN9l0b2+RhrunVm m3Vd63dxWZxmjUgfVImXIEU3srWF4QNu9fO8X7i6Q/qy1w4jBZdrbvg3TbprpIt/ Qzekh5FwPdyAvHKBXnnSmSc6GYk3uDUGW8FsRhGRUqFJUtwPCvP4rbko+JXMs4W8 dp/lpkCzvDUXUEBznwVPJCa7TmxPialVauidT/ZnMDypbVauyCW/2TUW2QmA2gpV dCS/sq4eMq91X+2vLy6ZTCav5eBoVLVL5384hwIDAQABAoIBAB+3P1sud4W+pMU/ FD0bwH2TywFVurANlugfqaIo3UPCT4AX7skLSz2SxG81FeVdV98EoDL433YCaYRz fjJ8MBafcXu/Ng4exCsFU2qLX3oT6dRUJghs0285uIRY0TZOKPSR0WpH7WhdkCcm U9H8cS5PnwzP1InwLMfztSR7j/xFGuWx4yVGbNk+Usib2YWv83HMw+eFA0ZhstTj ir3w3ZlI5WILUEIB6L5kyA+2hoBB4RkoASsgaRHGw7hWJIdhHuZk1Y5k0DgiGNIN rR2t70WzWQHAJMm+kv6s1WBgRUInBBIgcA1lcGV4rp5O8nR4LSuG4J+xO170ZDP9 3OCQ2eECgYEA6KGR6Z3/zU/WblTqtmPzg5jcJ3M07vjdkfPQDhUxrLTkqAD1wro3 LEZBQcWyYLEhFvrziIXa2CFhTpDDsQu7WHGcuGMK85v4LJulTlmim1s2YF6Tw/B0 GeRYTVIGXsKcURZrcYK9pSXJr0iPT8zNBwZJLRc62fPh7gUJkWQEF/ECgYEAyd4Q IseL4ij+/ST1oNSWjX2s5yA7efnyZ1RqZoScRnF3aFw0ha9vwMJb8pAgfPtghr/j jXNX8oQtDu4slafeaNcQhd9OjhYgVyk+ORpJcV6slK1woMNGvePrW/P5qBrZOIEe SaMnCPPYqQvYDueWjgD+/pK667KxtB4lTcl1D/cCgYEAuTncil0JjpphLFxkvnD7 Ne8CbE7o+NYi5dFx+aSadt9ZqQOKq0/GGnSeBRa6nw6qQDf0rOGXjLuXVQ0jI9cp hpR/qrkBwVKQ2kPb7XSlannH5BeqJDtS1kLgpEAJXm4qNM5bInhtNA/QdMabhXiW T1+L6GE0tNHhNLnCEofGUqECgYBd5mrREstRz0oVTNzpDBpgBKap+EdSTN7iC9VW jLb42Yz2wXU4/ktYedA1e69xE6lp5NaflJjDWZJwEIAyuRxu3iN0clRjKY2vnaKx AzZIgwZ6PldWoJ9Gcz+mzU1DUgHbE8yX6kmdE3ij4buF61oQAgybNHu4aa3Zn/Aw R9apLwKBgFIL+IRofB4ZrgFpNH3+BL2H3mk7LKxXZYfToRTOSje82YR6c8MX48GD 5JTgySbaHUERhcFJF+nyymXDy+Kt0ReBeZQhe1pImLEVB9rTe7H2sGs1iyy4P75G 6mI+q4CEKiLIClCEnarSUZy4yfObLd6IsaIIwU2d5wtEqJPoT6E6 -----END RSA PRIVATE KEY----- follow-redirects-1.2.4/test/lib/TestClient.crt000066400000000000000000000021471312254535600213310ustar00rootroot00000000000000-----BEGIN CERTIFICATE----- MIIDEjCCAfqgAwIBAgIBAzANBgkqhkiG9w0BAQUFADARMQ8wDQYDVQQDEwZUZXN0 Q0EwIBcNMTUwNzAzMTMzNjAwWhgPMjExNTA3MDMxMzMxMDBaMBUxEzARBgNVBAMT ClRlc3RDbGllbnQwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDD+CRf igY2z8Ef+jczFaYJXaJGCEJE4+OSJ7OW46hYFjWRif1WdjsJmS5F8TyW30aXpiwC KAn0P7VCKOACqBXd18y2PR9r62G9GAO//d/iLASOMLp08StTbS+Vo0el5Q0leJAB 3JfHWcyDSgkfSvahlO3msefZGWo1P0+yj5KpJWTdlqwGKyE9J4KgoF4c6tSMgvcN Fy6uIP3IhXI4ee+1kWpCJ+EIkEs+KIz7zhQVMmVmqEwWWD3HBom6jmQyi6lP5NGf 7SFdzESHNsRbGJs3oTOKDd0bYQ4eZe0FCrLml39GWS99NDNaUn7qfgJH8tH0VUq1 +9Y1GxZyEg9UCJgHAgMBAAGjbzBtMAwGA1UdEwEB/wQCMAAwHQYDVR0OBBYEFJ/Z d8rQ4gpslTmsPXBfsYrDfIJvMAsGA1UdDwQEAwIEsDARBglghkgBhvhCAQEEBAMC BaAwHgYJYIZIAYb4QgENBBEWD3hjYSBjZXJ0aWZpY2F0ZTANBgkqhkiG9w0BAQUF AAOCAQEAb3hZPtzRuTfD6MGzl+UAaZF6UR265JlW42YiyjpMU0ZdB+Ekz8ci271u ouCAkTQSYjyDu+MTdvGQYMQwcZp8l2fe7etP/O/NMwfCIv0xc8mzKKlZVNiY2QFf ohULsU57K5XwbxYlx3uuQ4D1JhtYiXHPF17zIW+zhuhbXDF4/S+oP4pLefDX3+f9 T+RFa4BZWlOfFMoRuD0iMMTPyQiN252p2fd5x0i1xgfxpkq1mPRGbmgq5lXr+pQs gn3RkTBOQBT7A/SeduCTJxlPrwTx0dg79PJ4Pqs1cIpv6fsWgR+LVi4m31xhjJ86 q9USxrTe5dalIqCwYV/rp9KOjcdPLw== -----END CERTIFICATE----- follow-redirects-1.2.4/test/lib/TestClient.pem000066400000000000000000000032131312254535600213150ustar00rootroot00000000000000-----BEGIN RSA PRIVATE KEY----- MIIEowIBAAKCAQEAw/gkX4oGNs/BH/o3MxWmCV2iRghCROPjkiezluOoWBY1kYn9 VnY7CZkuRfE8lt9Gl6YsAigJ9D+1QijgAqgV3dfMtj0fa+thvRgDv/3f4iwEjjC6 dPErU20vlaNHpeUNJXiQAdyXx1nMg0oJH0r2oZTt5rHn2RlqNT9Pso+SqSVk3Zas BishPSeCoKBeHOrUjIL3DRcuriD9yIVyOHnvtZFqQifhCJBLPiiM+84UFTJlZqhM Flg9xwaJuo5kMoupT+TRn+0hXcxEhzbEWxibN6Ezig3dG2EOHmXtBQqy5pd/Rlkv fTQzWlJ+6n4CR/LR9FVKtfvWNRsWchIPVAiYBwIDAQABAoIBAChh/RTW+3rWCwUM 6c0UG/f4HJVz5DwP/fhDSCXC2hD02qsYFWV4zaensjplgMfSUNi1PIJhP9PeQPrW M3s4qfDiJGQ1akEx6x0tUrCAn18bFQE8F2A7xmLVsCWU1RHhhnj/RCWuEHTbE2LG oeSZ7QLVDsUoSg+ZxiIc3s57+YvzNNqoF1bLKmMiTzDNbqGmLWZfkh0/yGcfVqdG UFbuJ1xi8WpbZ7luIgdoD7KO5W4EA7d3lUfonhf9rXipy62HIb7S9KJMfT+cLTE3 HRAaev4o69dDEnn44fscz4kLBG8tPi8AR5c9KXVwV9kOxOXo7S9Z2WH3D/9FRSMC 4s0BlQkCgYEA+iOgp+4qJyEbR4lZTwu7gLPp/TivjISP/kpluNTd/knHeOyukY8G HvtY5CTKQ/Yx9mJMuQBY1hF2R3Ho1X+9vw7jY7siwM41QBmlSZGFzKSGnKFHhS8E t177o5lQSxlUasU9+6U2LhiJCQFS98JjlN6m7JWXVAhvizShAoMHOTUCgYEAyI+Y cxU+4jgqSe5qbCOmzk9baq2HM6HPEGPGmcQV5mqWvxhOcxO/wK0AKnLqkFrgBime kdNU4J4ejWst64UCqg0NkkPtlkLd/fm93oKv2Hn4TFVHvd4UfylwkWFeI0qV/I7v cOQXQbIzDweBQOmTDAV9+mv7AByIlNhn75HHr8sCgYA6D4L+9705D+oxFKRyQJrp KNmrOTkmOK7jq4ko+d/9Ykq2utlaWARntJfRfOimFcNLGU4NsxLrvWni84HD7L6y VlZZk9phPnwFwO4owWp5Mcc9HAJp25wQpC9sre2BQQPjNJ1Kh/KniHJmcLQ9ZhWP PI8qYx6ZFswtV63300od8QKBgD22ocxXrz/g1HJnYGviFIPLO2/58W5bzeFXMldC VRLPnY8zpYAXhZt+IFzve2nr7J8400g/5RAw12ngnwKnrQgyeAiXylXAQZrS7C+5 uu47qcGjRC4SVfBDChZSBauKWdibCuaL6PQYJ0nqn0UgGlVKo1cZPuTr4sMaS/EV xII9AoGBAOrbbaJY/xeBdlRunb1Pg2hXd+RE88nTzLXWcrm0ceXMzZHxrFSpIbEq MNFrERKeq+vVaTCMd4ruFi6B9A0ru2b3VKs6I4wp0/D0qedlnDC4VLGxb4rLd/By jkirINqIl5/+LQ83B1LMoOdec+kwG0O+JUDD+b2ezzmgaQv3p9tu -----END RSA PRIVATE KEY----- follow-redirects-1.2.4/test/lib/TestServer.crt000066400000000000000000000021431312254535600213550ustar00rootroot00000000000000-----BEGIN CERTIFICATE----- MIIDETCCAfmgAwIBAgIBAjANBgkqhkiG9w0BAQUFADARMQ8wDQYDVQQDEwZUZXN0 Q0EwIBcNMTUwNzAzMTMzMzAwWhgPMjExNTA3MDMxMzMxMDBaMBQxEjAQBgNVBAMT CWxvY2FsaG9zdDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAJo/M+zk QH75MO0uzes6DFtdenROcqxIsIH5rUm4hLmd3FP1200y0O+SWEgMlGcL5rV3mQaO RzGFHfLXYPqLRsJoc0giwEqeicfde7U8tsTndYzYyHdIF1u7ZPvWg0bLRam9OHRW O5JTby9/0DSoea6cM2yuc3mAs64KR55yCo6KfbWRyCf2jDgqyHtqd89TJVMkOlYw MNSCZidDsPXb+CtOa+YSbyFFWQyLL8rh7NtPsPswWv02EMWlE7aYW9om8Oo2Aj1J qZNOs/Z+p99/sQyWOTEnaZ66tA+0gCg2S+bLySRIgflgh53UwfpV/Mlcve9bKBQW akpO5RsNmuWCPUUCAwEAAaNvMG0wDAYDVR0TAQH/BAIwADAdBgNVHQ4EFgQUgLde qYAJpvwe0ViLCbowhp7n8xQwCwYDVR0PBAQDAgXgMBEGCWCGSAGG+EIBAQQEAwIG QDAeBglghkgBhvhCAQ0EERYPeGNhIGNlcnRpZmljYXRlMA0GCSqGSIb3DQEBBQUA A4IBAQAsHOgq1MRD0FB2zQaSRQwcs0EFDxRtSVROySpTscQLdDhz1UYB66iA/AQi E2P8FyzNNd07C7lAV+f3M0SfwbnqeaotBBATDf6rhsrNaoFlBN4nW96fZIn3HLkV pwN3vm7TRddY3xY8mCdgvVwUCjXM9GORXKMZgfAyUWEBqhMGe/3MtHwp6mX5s1B2 PUK4vKv/SfdG38537P4ubncbqDLUkMmBhdU13NKN18V1hnRPQxyGDFRns7ZDWMSV rXM0bVQ1jJ8jLkYo8N1Y3gMTrFpd5jBpoo5KERh4RbVVl/BWH9azkbTl9XkE6ahp Ec/ShGaaHjV4t1dwGS5nkeio7oG+ -----END CERTIFICATE----- follow-redirects-1.2.4/test/lib/TestServer.pem000066400000000000000000000032171312254535600213510ustar00rootroot00000000000000-----BEGIN RSA PRIVATE KEY----- MIIEpAIBAAKCAQEAmj8z7ORAfvkw7S7N6zoMW116dE5yrEiwgfmtSbiEuZ3cU/Xb TTLQ75JYSAyUZwvmtXeZBo5HMYUd8tdg+otGwmhzSCLASp6Jx917tTy2xOd1jNjI d0gXW7tk+9aDRstFqb04dFY7klNvL3/QNKh5rpwzbK5zeYCzrgpHnnIKjop9tZHI J/aMOCrIe2p3z1MlUyQ6VjAw1IJmJ0Ow9dv4K05r5hJvIUVZDIsvyuHs20+w+zBa /TYQxaUTtphb2ibw6jYCPUmpk06z9n6n33+xDJY5MSdpnrq0D7SAKDZL5svJJEiB +WCHndTB+lX8yVy971soFBZqSk7lGw2a5YI9RQIDAQABAoIBAD30rRK2DAH0beaX KjcVtejs/0bAS+dPuzY555T4/Lbh5aID2vaDNtFD4xntk55gdWbmhxzPAWGuqnzk bhu03dMwyT1sV/ig/Y6+M/Z0UKV+owTT/pGK73b1KnbNjyVceLrZOlfgW0VHJh3a JPNByL6OF0nMOK3ROFrr3iCShhMChiCcu4fH1OEPiieuxqnfdsdN2GaiIPDAiB5E M+qh2Iz2a9cY7rjr8JV6YXy2bolH+6nLWebXjEm/s7TnwJi3yesqP5c3PLSMRTmv q0jFd09T5g9/0A+DAOiHVtBKkWnzh5hY41yFOdeH4eaDF2oN+Fmr8x0C6tmoF7Q8 7cQEYfECgYEAx8IMQs05X7UVhB2ViPwztP1AiZ6HJ6a0li9O+7u5ACJet1UoiTUb naDB8YcjJEuoe9CWXtoDvG5sFtKvrURQHZC6aJBDLylN+RhcvPoReFRA0rtiuvyQ PCG44qbsimZIscfVWG2kqTH548iyz0zN3oSYTIHxGUjtV2TJYqz6W18CgYEAxaza ApFwOTk/P2UY1Enm/SBHgp3OYkU/Cpo0P8LGUT5vBEvH9bKkpBsyFqJtpUghdfTQ BRApFGpmfI3AQBrpbtesHtrSZFwlH3uNMaaZmBhEst1++lbTcgV52glsl/OvdgF1 V/COfRYkTAYZTD7FiuQgNbFI6UD+HL5Z++q1zdsCgYEAm5IkumHaLQ96kkn4C5df LbWObZ9o4xiRy+VMPgEVM2WRHnbT+wfBWiG46cG1PK8vqD3q3jQsdLQKujubVfW9 70in5Id1y2T/tN+kWgjnUi9LzNLjMX1BV5Nryhj2X1MJimt3XEcH1j11wFCt8YR/ UK0zb2JDws1pCocT0CWnVUECgYBG16ad6v98Eo8fKh0W35Ffv/TA08tGHqj0JMV6 4FPwg96HfJ0h6a7lfTWbNQT0igAr5Nu0JNGEFom6M5blon9sEPEHccaa3wcS3FWC qME8592e+JBsKTfIEjxULrJzVorYULd8F6MJIylI8ZqZX5muKAl/FJUbuwYWyQU4 qioTpwKBgQDDaM4tsT5Ykkhmjvj1FZEKdU+g8Pu3M+tQF75z3A3afOPbtJT0qVD1 cYJ2Co5pBuGhGP7Wx1Bdk7Y8G88u8eoBZEQt5A33Ltwsz9sPD/UJKz0LlUJjgA5O H18k7TAfpGBpdjPUy6u4YB+jBAHoqaRVQPk09682bk82yK0ENJp68g== -----END RSA PRIVATE KEY----- follow-redirects-1.2.4/test/lib/test-server.js000066400000000000000000000040541312254535600213610ustar00rootroot00000000000000// provides boilerplate for managing http and https servers during tests var http = require('http'); var https = require('https'); var assert = require('assert'); var BPromise = require('bluebird'); module.exports = function (defaultPorts) { // set default ports for each protocol i.e. {http: 80, https: 443} defaultPorts = defaultPorts || {}; var servers = []; /** * Starts a server * * If options is a function, uses that the request handler for a `http` server on the default port. * options.protocol - the protocol to use (`http` or `https`). Defaults to `http`. * options.port - the port to use, will fall back to defaultPorts[protocol]. * options.app - the request handler passed to http|https.createServer * * the options object will also be passed to as the https config for https servers * * @param options * @returns {Promise} that resolves when the server successfully started */ function start(options) { return BPromise.fromNode(function (callback) { if (typeof options === 'function') { options = { app: options }; } assert(typeof options.app, 'function', 'app'); var server; var port; var protocol = options.protocol; if (!protocol || protocol.trim().match(/^http(:)?$/)) { server = http.createServer(options.app); port = options.port || defaultPorts.http; } else if (protocol.trim().match(/^https(:)?$/)) { server = https.createServer(options, options.app); port = options.port || defaultPorts.https; } assert(typeof port, 'number', 'port'); servers.push(server); server.listen(port, callback); }); } /** * Stops all the currently running servers previously created with `start` * @returns {Promise} that resolves when all servers have successfully shut down. */ function stop() { return BPromise.all(servers.map(stopServer)).finally(clearServers); } function stopServer(server) { return BPromise.fromNode(function (callback) { server.close(callback); }); } function clearServers() { servers = []; } return { start: start, stop: stop }; }; follow-redirects-1.2.4/test/lib/util.js000066400000000000000000000016571312254535600200610ustar00rootroot00000000000000var concat = require('concat-stream'); var BPromise = require('bluebird'); function redirectsTo(/* opt_status, path */) { var args = Array.prototype.slice.call(arguments); return function (req, res) { res.redirect.apply(res, args); }; } function sendsJson(json) { return function (req, res) { res.json(json); }; } function concatJson(resolve, reject) { return function (res) { res.pipe(concat({encoding: 'string'}, function (string) { try { res.parsedJson = JSON.parse(string); resolve(res); } catch (err) { reject(new Error('error parsing ' + JSON.stringify(string) + '\n caused by: ' + err.message)); } })).on('error', reject); }; } function asPromise(cb) { return function (result) { return new BPromise(function (resolve, reject) { cb(resolve, reject, result); }); }; } module.exports = { redirectsTo: redirectsTo, sendsJson: sendsJson, concatJson: concatJson, asPromise: asPromise }; follow-redirects-1.2.4/test/test-with-server.js000066400000000000000000000540731312254535600215720ustar00rootroot00000000000000describe('follow-redirects ', function () { var express = require('express'); var assert = require('assert'); var net = require('net'); var server = require('./lib/test-server')({ https: 3601, http: 3600 }); var url = require('url'); var followRedirects = require('..'); var http = followRedirects.http; var https = followRedirects.https; var BPromise = require('bluebird'); var util = require('./lib/util'); var concat = require('concat-stream'); var concatJson = util.concatJson; var redirectsTo = util.redirectsTo; var sendsJson = util.sendsJson; var asPromise = util.asPromise; var fs = require('fs'); var path = require('path'); function httpsOptions(app) { return { app: app, protocol: 'https', cert: fs.readFileSync(path.join(__dirname, 'lib/TestServer.crt')), key: fs.readFileSync(path.join(__dirname, 'lib/TestServer.pem')) }; } var ca = fs.readFileSync(path.join(__dirname, 'lib/TestCA.crt')); var app; var app2; var originalMaxRedirects; beforeEach(function () { originalMaxRedirects = followRedirects.maxRedirects; app = express(); app2 = express(); }); afterEach(function (done) { followRedirects.maxRedirects = originalMaxRedirects; server.stop().nodeify(done); }); it('http.get with string and callback - redirect', function (done) { app.get('/a', redirectsTo('/b')); app.get('/b', redirectsTo('/c')); app.get('/c', redirectsTo('/d')); app.get('/d', redirectsTo('/e')); app.get('/e', redirectsTo('/f')); app.get('/f', sendsJson({a: 'b'})); server.start(app) .then(asPromise(function (resolve, reject) { http.get('http://localhost:3600/a', concatJson(resolve, reject)).on('error', reject); })) .then(function (res) { assert.deepEqual(res.parsedJson, {a: 'b'}); assert.deepEqual(res.responseUrl, 'http://localhost:3600/f'); }) .nodeify(done); }); it('http.get with options object and callback - redirect', function (done) { app.get('/a', redirectsTo('/b')); app.get('/b', redirectsTo('/c')); app.get('/c', redirectsTo('/d')); app.get('/d', redirectsTo('/e')); app.get('/e', redirectsTo('/f')); app.get('/f', sendsJson({a: 'b'})); server.start(app) .then(asPromise(function (resolve, reject) { var options = { hostname: 'localhost', port: 3600, path: '/a', method: 'GET' }; http.get(options, concatJson(resolve, reject)).on('error', reject); })) .then(function (res) { assert.deepEqual(res.parsedJson, {a: 'b'}); assert.deepEqual(res.responseUrl, 'http://localhost:3600/f'); }) .nodeify(done); }); it('http.get with string and callback - no redirect', function (done) { app.get('/a', sendsJson({a: 'b'})); server.start(app) .then(asPromise(function (resolve, reject) { http.get('http://localhost:3600/a', concatJson(resolve, reject)).on('error', reject); })) .then(function (res) { assert.deepEqual(res.parsedJson, {a: 'b'}); assert.deepEqual(res.responseUrl, 'http://localhost:3600/a'); }) .nodeify(done); }); it('http.get with options object and callback - no redirect', function (done) { app.get('/a', sendsJson({a: 'b'})); server.start(app) .then(asPromise(function (resolve, reject) { var options = { hostname: 'localhost', port: 3600, path: '/a?xyz', method: 'GET' }; http.get(options, concatJson(resolve, reject)).on('error', reject); })) .then(function (res) { assert.deepEqual(res.parsedJson, {a: 'b'}); assert.deepEqual(res.responseUrl, 'http://localhost:3600/a?xyz'); }) .nodeify(done); }); it('http.get with response event', function (done) { app.get('/a', redirectsTo('/b')); app.get('/b', redirectsTo('/c')); app.get('/c', redirectsTo('/d')); app.get('/d', redirectsTo('/e')); app.get('/e', redirectsTo('/f')); app.get('/f', sendsJson({a: 'b'})); server.start(app) .then(asPromise(function (resolve, reject) { http.get('http://localhost:3600/a') .on('response', concatJson(resolve, reject)) .on('error', reject); })) .then(function (res) { assert.deepEqual(res.parsedJson, {a: 'b'}); assert.deepEqual(res.responseUrl, 'http://localhost:3600/f'); }) .nodeify(done); }); it('should return with the original status code if the response does not contain a location header', function (done) { app.get('/a', function (req, res) { res.status(307).end(); }); server.start(app) .then(asPromise(function (resolve, reject) { http.get('http://localhost:3600/a', resolve).on('error', reject); })) .then(function (res) { assert.equal(res.statusCode, 307); assert.deepEqual(res.responseUrl, 'http://localhost:3600/a'); res.on('data', function () { // noop to consume the stream (server won't shut down otherwise). }); }) .nodeify(done); }); it('should emit connection errors on the returned stream', function (done) { app.get('/a', redirectsTo('http://localhost:36002/b')); server.start(app) .then(asPromise(function (resolve, reject) { http.get('http://localhost:3600/a', reject).on('error', resolve); })) .then(function (error) { assert.equal(error.code, 'ECONNREFUSED'); }) .nodeify(done); }); it('should emit socket events on the returned stream', function (done) { app.get('/a', sendsJson({a: 'b'})); server.start(app) .then(asPromise(function (resolve, reject) { http.get('http://localhost:3600/a') .on('socket', resolve) .on('error', reject); })) .then(function (socket) { assert(socket instanceof net.Socket, 'socket event should emit with socket'); }) .nodeify(done); }); it('should follow redirects over https', function (done) { app.get('/a', redirectsTo('/b')); app.get('/b', redirectsTo('/c')); app.get('/c', sendsJson({baz: 'quz'})); server.start(httpsOptions(app)) .then(asPromise(function (resolve, reject) { var opts = url.parse('https://localhost:3601/a'); opts.ca = ca; https.get(opts, concatJson(resolve, reject)).on('error', reject); })) .then(function (res) { assert.deepEqual(res.parsedJson, {baz: 'quz'}); assert.deepEqual(res.responseUrl, 'https://localhost:3601/c'); }) .nodeify(done); }); it('should honor query params in redirects', function (done) { app.get('/a', redirectsTo('/b?greeting=hello')); app.get('/b', function (req, res) { res.json({greeting: req.query.greeting}); }); server.start(app) .then(asPromise(function (resolve, reject) { http.get('http://localhost:3600/a', concatJson(resolve, reject)).on('error', reject); })) .then(function (res) { assert.deepEqual(res.parsedJson, {greeting: 'hello'}); assert.deepEqual(res.responseUrl, 'http://localhost:3600/b?greeting=hello'); }) .nodeify(done); }); it('should allow aborting', function (done) { var request; app.get('/a', redirectsTo('/b')); app.get('/b', redirectsTo('/c')); app.get('/c', callAbort); server.start(app) .then(asPromise(function (resolve, reject) { request = http.get('http://localhost:3600/a', resolve); request.on('response', reject); request.on('error', reject); request.on('abort', onAbort); function onAbort() { request.removeListener('error', reject); request.on('error', noop); resolve(); } })) .nodeify(done); function callAbort() { request.abort(); } }); it('should provide flushHeaders', function (done) { app.get('/a', redirectsTo('/b')); app.get('/b', sendsJson({foo: 'bar'})); server.start(app) .then(asPromise(function (resolve, reject) { var request = http.get('http://localhost:3600/a', resolve); request.flushHeaders(); request.on('response', resolve); request.on('error', reject); })) .nodeify(done); }); it('should provide setNoDelay', function (done) { app.get('/a', redirectsTo('/b')); app.get('/b', sendsJson({foo: 'bar'})); server.start(app) .then(asPromise(function (resolve, reject) { var request = http.get('http://localhost:3600/a', resolve); request.setNoDelay(true); request.on('response', resolve); request.on('error', reject); })) .nodeify(done); }); it('should provide setSocketKeepAlive', function (done) { app.get('/a', redirectsTo('/b')); app.get('/b', sendsJson({foo: 'bar'})); server.start(app) .then(asPromise(function (resolve) { var request = http.get('http://localhost:3600/a', resolve); request.setSocketKeepAlive(true); })) .nodeify(done); }); it('should provide setTimeout', function (done) { app.get('/a', redirectsTo('/b')); app.get('/b', sendsJson({foo: 'bar'})); server.start(app) .then(asPromise(function (resolve) { var request = http.get('http://localhost:3600/a', resolve); request.setTimeout(1000); })) .nodeify(done); }); describe('should obey a `maxRedirects` property', function () { beforeEach(function () { var i = 22; while (i > 0) { app.get('/r' + i, redirectsTo('/r' + --i)); } app.get('/r0', sendsJson({foo: 'bar'})); }); it('which defaults to 21', function (done) { server.start(app) // 21 redirects should work fine .then(asPromise(function (resolve, reject) { http.get('http://localhost:3600/r21', concatJson(resolve, reject)).on('error', reject); })) .then(function (res) { assert.deepEqual(res.parsedJson, {foo: 'bar'}); assert.deepEqual(res.responseUrl, 'http://localhost:3600/r0'); }) // 22 redirects should fail .then(asPromise(function (resolve, reject) { http.get('http://localhost:3600/r22', reject).on('error', resolve); })) .then(function (err) { assert.ok(err.toString().match(/Max redirects exceeded/)); }) .nodeify(done); }); it('which can be set globally', function (done) { followRedirects.maxRedirects = 22; server.start(app) .then(asPromise(function (resolve, reject) { http.get('http://localhost:3600/r22', concatJson(resolve, reject)).on('error', reject); })) .then(function (res) { assert.deepEqual(res.parsedJson, {foo: 'bar'}); assert.deepEqual(res.responseUrl, 'http://localhost:3600/r0'); }) .nodeify(done); }); it('set as an option on an individual request', function (done) { var u = url.parse('http://localhost:3600/r2'); u.maxRedirects = 1; server.start(app) .then(asPromise(function (resolve, reject) { http.get(u, reject).on('error', resolve); })) .then(function (err) { assert.ok(err.toString().match(/Max redirects exceeded/)); }) .nodeify(done); }); }); describe('should switch to safe methods when appropriate', function () { function mustUseSameMethod(statusCode, useSameMethod) { describe('when redirecting with status code ' + statusCode, function () { itRedirectsWith(statusCode, 'GET', 'GET'); itRedirectsWith(statusCode, 'HEAD', 'HEAD'); itRedirectsWith(statusCode, 'OPTIONS', 'OPTIONS'); itRedirectsWith(statusCode, 'TRACE', 'TRACE'); itRedirectsWith(statusCode, 'POST', useSameMethod ? 'POST' : 'GET'); itRedirectsWith(statusCode, 'PUT', useSameMethod ? 'PUT' : 'GET'); }); } function itRedirectsWith(statusCode, originalMethod, redirectedMethod) { var description = 'should ' + (originalMethod === redirectedMethod ? 'reuse ' + originalMethod : 'switch from ' + originalMethod + ' to ' + redirectedMethod); it(description, function (done) { app[originalMethod.toLowerCase()]('/a', redirectsTo(statusCode, '/b')); app[redirectedMethod.toLowerCase()]('/b', sendsJson({a: 'b'})); server.start(app) .then(asPromise(function (resolve, reject) { var opts = url.parse('http://localhost:3600/a'); opts.method = originalMethod; http.request(opts, resolve).on('error', reject).end(); })) .then(function (res) { assert.deepEqual(res.responseUrl, 'http://localhost:3600/b'); if (res.statusCode !== 200) { throw new Error('Did not use ' + redirectedMethod); } }) .nodeify(done); }); } mustUseSameMethod(300, false); mustUseSameMethod(301, false); mustUseSameMethod(302, false); mustUseSameMethod(303, false); mustUseSameMethod(307, true); }); describe('should handle cross protocol redirects ', function () { it('(https -> http -> https)', function (done) { app.get('/a', redirectsTo('http://localhost:3600/b')); app2.get('/b', redirectsTo('https://localhost:3601/c')); app.get('/c', sendsJson({yes: 'no'})); BPromise.all([server.start(httpsOptions(app)), server.start(app2)]) .then(asPromise(function (resolve, reject) { var opts = url.parse('https://localhost:3601/a'); opts.ca = ca; https.get(opts, concatJson(resolve, reject)).on('error', reject); })) .then(function (res) { assert.deepEqual(res.parsedJson, {yes: 'no'}); assert.deepEqual(res.responseUrl, 'https://localhost:3601/c'); }) .nodeify(done); }); it('(http -> https -> http)', function (done) { app.get('/a', redirectsTo('https://localhost:3601/b')); app2.get('/b', redirectsTo('http://localhost:3600/c')); app.get('/c', sendsJson({hello: 'goodbye'})); BPromise.all([server.start(app), server.start(httpsOptions(app2))]) .then(asPromise(function (resolve, reject) { var opts = url.parse('http://localhost:3600/a'); opts.ca = ca; http.get(opts, concatJson(resolve, reject)).on('error', reject); })) .then(function (res) { assert.deepEqual(res.parsedJson, {hello: 'goodbye'}); assert.deepEqual(res.responseUrl, 'http://localhost:3600/c'); }) .nodeify(done); }); }); it('should support writing into request stream without redirects', function (done) { app.post('/a', function (req, res) { req.pipe(res); }); var opts = url.parse('http://localhost:3600/a'); opts.method = 'POST'; server.start(app) .then(asPromise(function (resolve, reject) { var req = http.request(opts, resolve); req.end(fs.readFileSync(__filename), 'buffer'); req.on('error', reject); })) .then(asPromise(function (resolve, reject, res) { assert.deepEqual(res.responseUrl, 'http://localhost:3600/a'); res.pipe(concat({encoding: 'string'}, resolve)).on('error', reject); })) .then(function (str) { assert.equal(str, fs.readFileSync(__filename, 'utf8')); }) .nodeify(done); }); it('should support writing into request stream with redirects', function (done) { app.post('/a', redirectsTo(307, 'http://localhost:3600/b')); app.post('/b', function (req, res) { req.pipe(res); }); var opts = url.parse('http://localhost:3600/a'); opts.method = 'POST'; server.start(app) .then(asPromise(function (resolve, reject) { var req = http.request(opts, resolve); req.end(fs.readFileSync(__filename), 'buffer'); req.on('error', reject); })) .then(asPromise(function (resolve, reject, res) { res.pipe(concat({encoding: 'string'}, resolve)).on('error', reject); })) .then(function (str) { assert.equal(str, fs.readFileSync(__filename, 'utf8')); }) .nodeify(done); }); it('should support piping into request stream without redirects', function (done) { app.post('/a', function (req, res) { req.pipe(res); }); var opts = url.parse('http://localhost:3600/a'); opts.method = 'POST'; server.start(app) .then(asPromise(function (resolve, reject) { var req = http.request(opts, resolve); fs.createReadStream(__filename).pipe(req); req.on('error', reject); })) .then(asPromise(function (resolve, reject, res) { assert.deepEqual(res.responseUrl, 'http://localhost:3600/a'); res.pipe(concat({encoding: 'string'}, resolve)).on('error', reject); })) .then(function (str) { assert.equal(str, fs.readFileSync(__filename, 'utf8')); }) .nodeify(done); }); it('should support piping into request stream with redirects', function (done) { app.post('/a', redirectsTo(307, 'http://localhost:3600/b')); app.post('/b', function (req, res) { req.pipe(res); }); var opts = url.parse('http://localhost:3600/a'); opts.method = 'POST'; server.start(app) .then(asPromise(function (resolve, reject) { var req = http.request(opts, resolve); fs.createReadStream(__filename).pipe(req); req.on('error', reject); })) .then(asPromise(function (resolve, reject, res) { res.pipe(concat({encoding: 'string'}, resolve)).on('error', reject); })) .then(function (str) { assert.equal(str, fs.readFileSync(__filename, 'utf8')); }) .nodeify(done); }); it('should support piping into request stream with explicit Content-Length without redirects', function (done) { app.post('/a', function (req, res) { req.pipe(res); }); var opts = url.parse('http://localhost:3600/a'); opts.method = 'POST'; opts.headers = { 'Content-Length': fs.readFileSync(__filename).byteLength }; server.start(app) .then(asPromise(function (resolve, reject) { var req = http.request(opts, resolve); fs.createReadStream(__filename).pipe(req); req.on('error', reject); })) .then(asPromise(function (resolve, reject, res) { assert.deepEqual(res.responseUrl, 'http://localhost:3600/a'); res.pipe(concat({encoding: 'string'}, resolve)).on('error', reject); })) .then(function (str) { assert.equal(str, fs.readFileSync(__filename, 'utf8')); }) .nodeify(done); }); it('should support piping into request stream with explicit Content-Length with redirects', function (done) { app.post('/a', redirectsTo(307, 'http://localhost:3600/b')); app.post('/b', function (req, res) { req.pipe(res); }); var opts = url.parse('http://localhost:3600/a'); opts.method = 'POST'; opts.headers = { 'Content-Length': fs.readFileSync(__filename).byteLength }; server.start(app) .then(asPromise(function (resolve, reject) { var req = http.request(opts, resolve); fs.createReadStream(__filename).pipe(req); req.on('error', reject); })) .then(asPromise(function (resolve, reject, res) { res.pipe(concat({encoding: 'string'}, resolve)).on('error', reject); })) .then(function (str) { assert.equal(str, fs.readFileSync(__filename, 'utf8')); }) .nodeify(done); }); describe('should drop the entity and associated headers', function () { function itDropsBodyAndHeaders(originalMethod) { it('when switching from ' + originalMethod + ' to GET', function (done) { app[originalMethod.toLowerCase()]('/a', redirectsTo(302, 'http://localhost:3600/b')); app.get('/b', function (req, res) { res.write(JSON.stringify(req.headers)); req.pipe(res); // will invalidate JSON if non-empty }); var opts = url.parse('http://localhost:3600/a'); opts.method = originalMethod; opts.headers = { other: 'value', 'content-type': 'application/javascript', 'Content-Length': fs.readFileSync(__filename).byteLength }; server.start(app) .then(asPromise(function (resolve, reject) { var req = http.request(opts, resolve); fs.createReadStream(__filename).pipe(req); req.on('error', reject); })) .then(asPromise(function (resolve, reject, res) { res.pipe(concat({encoding: 'string'}, resolve)).on('error', reject); })) .then(function (str) { var body = JSON.parse(str); assert.equal(body.host, 'localhost:3600'); assert.equal(body.other, 'value'); assert.equal(body['content-type'], undefined); assert.equal(body['content-length'], undefined); }) .nodeify(done); }); } itDropsBodyAndHeaders('POST'); itDropsBodyAndHeaders('PUT'); }); describe('when redirecting to a different host while the host header is set', function () { it('uses the new host header', function (done) { app.get('/a', redirectsTo(302, 'http://localhost:3600/b')); app.get('/b', function (req, res) { res.write(JSON.stringify(req.headers)); req.pipe(res); // will invalidate JSON if non-empty }); server.start(app) .then(asPromise(function (resolve, reject) { var opts = url.parse('http://localhost:3600/a'); opts.headers = {hOsT: 'otherhost.com'}; http.get(opts, resolve).on('error', reject); })) .then(asPromise(function (resolve, reject, res) { assert.deepEqual(res.statusCode, 200); assert.deepEqual(res.responseUrl, 'http://localhost:3600/b'); res.pipe(concat({encoding: 'string'}, resolve)).on('error', reject); })) .then(function (str) { var body = JSON.parse(str); assert.equal(body.host, 'localhost:3600'); }) .nodeify(done); }); }); describe('when the followRedirects option is set to false', function () { it('does not redirect', function (done) { app.get('/a', redirectsTo(302, '/b')); app.get('/b', sendsJson({a: 'b'})); server.start(app) .then(asPromise(function (resolve, reject) { var opts = url.parse('http://localhost:3600/a'); opts.followRedirects = false; http.get(opts, resolve).on('error', reject); })) .then(function (res) { assert.deepEqual(res.statusCode, 302); assert.deepEqual(res.responseUrl, 'http://localhost:3600/a'); }) .nodeify(done); }); }); describe('should choose the right agent per protocol', function () { it('(https -> http -> https)', function (done) { app.get('/a', redirectsTo('http://localhost:3600/b')); app2.get('/b', redirectsTo('https://localhost:3601/c')); app.get('/c', sendsJson({yes: 'no'})); var httpAgent = addRequestLogging(new http.Agent()); var httpsAgent = addRequestLogging(new https.Agent()); function addRequestLogging(agent) { agent._requests = []; agent._addRequest = agent.addRequest; agent.addRequest = function (request, options) { this._requests.push(options.path); this._addRequest(request, options); }; return agent; } BPromise.all([server.start(httpsOptions(app)), server.start(app2)]) .then(asPromise(function (resolve, reject) { var opts = url.parse('https://localhost:3601/a'); opts.ca = ca; opts.agents = {http: httpAgent, https: httpsAgent}; https.get(opts, concatJson(resolve, reject)).on('error', reject); })) .then(function (res) { assert.deepEqual(httpAgent._requests, ['/b']); assert.deepEqual(httpsAgent._requests, ['/a', '/c']); assert.deepEqual(res.parsedJson, {yes: 'no'}); assert.deepEqual(res.responseUrl, 'https://localhost:3601/c'); }) .nodeify(done); }); }); }); function noop() {}