'use strict';

Object.defineProperty(exports, “__esModule”, {

value: true

});

var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if (“value” in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();

var _http = require('http');

var _http2 = _interopRequireDefault(_http);

var _tools = require('./tools');

var _handler_base = require('./handler_base');

var _handler_base2 = _interopRequireDefault(_handler_base);

var _server = require('./server');

function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }

function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError(“Cannot call a class as a function”); } }

function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError(“this hasn't been initialised - super() hasn't been called”); } return call && (typeof call === “object” || typeof call === “function”) ? call : self; }

function _inherits(subClass, superClass) { if (typeof superClass !== “function” && superClass !== null) { throw new TypeError(“Super expression must either be null or a function, not ” + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; }

/**

* Represents a proxied request to a HTTP server, either direct or via another upstream proxy.
*/

var HandlerForward = function (_HandlerBase) {

_inherits(HandlerForward, _HandlerBase);

function HandlerForward(options) {
    _classCallCheck(this, HandlerForward);

    var _this = _possibleConstructorReturn(this, (HandlerForward.__proto__ || Object.getPrototypeOf(HandlerForward)).call(this, options));

    _this.bindHandlersToThis(['onTrgResponse', 'onTrgError']);
    return _this;
}

_createClass(HandlerForward, [{
    key: 'run',
    value: function run() {
        var reqOpts = this.trgParsed;
        reqOpts.method = this.srcRequest.method;
        reqOpts.headers = {};

        // setup outbound proxy request HTTP headers
        // TODO: var hasXForwardedFor = false;
        // var hasVia = false;
        // var via = '1.1 ' + hostname + ' (proxy/' + version + ')';

        var hostHeaderFound = false;

        // TODO: We should probably use a raw HTTP message via socket instead of http.request(),
        // since Node transforms the headers to lower case and thus makes it easy to detect the proxy
        for (var i = 0; i < this.srcRequest.rawHeaders.length; i += 2) {
            var headerName = this.srcRequest.rawHeaders[i];
            var headerValue = this.srcRequest.rawHeaders[i + 1];

            if (/^connection$/i.test(headerName) && /^keep-alive$/i.test(headerValue)) {
                // Keep the "Connection: keep-alive" header, to reduce the chance that the server
                // will detect we're not a browser and also to improve performance
            } else if ((0, _tools.isHopByHopHeader)(headerName)) {
                continue;
            } else if ((0, _tools.isInvalidHeader)(headerName, headerValue)) {
                continue;
            } else if (/^host$/i.test(headerName)) {
                // If Host header was used multiple times, only consider the first one.
                // This is to prevent "TypeError: hostHeader.startsWith is not a function at calculateServerName (_http_agent.js:240:20)"
                if (hostHeaderFound) continue;
                hostHeaderFound = true;
            }

            /*
            if (!hasXForwardedFor && 'x-forwarded-for' === keyLower) {
                // append to existing "X-Forwarded-For" header
                // http://en.wikipedia.org/wiki/X-Forwarded-For
                hasXForwardedFor = true;
                value += ', ' + socket.remoteAddress;
                debug.proxyRequest('appending to existing "%s" header: "%s"', key, value);
            }
             if (!hasVia && 'via' === keyLower) {
                // append to existing "Via" header
                hasVia = true;
                value += ', ' + via;
                debug.proxyRequest('appending to existing "%s" header: "%s"', key, value);
            }
            */

            (0, _tools.addHeader)(reqOpts.headers, headerName, headerValue);
        }

        /*
        // add "X-Forwarded-For" header if it's still not here by now
        // http://en.wikipedia.org/wiki/X-Forwarded-For
        if (!hasXForwardedFor) {
            headers['X-Forwarded-For'] = socket.remoteAddress;
            debug.proxyRequest('adding new "X-Forwarded-For" header: "%s"', headers['X-Forwarded-For']);
        }
         // add "Via" header if still not set by now
        if (!hasVia) {
            headers.Via = via;
            debug.proxyRequest('adding new "Via" header: "%s"', headers.Via);
        }
         // custom `http.Agent` support, set `server.agent`
        var agent = server.agent;
        if (null != agent) {
            debug.proxyRequest('setting custom `http.Agent` option for proxy request: %s', agent);
            parsed.agent = agent;
            agent = null;
        }
         */

        // If desired, send the request via proxy
        if (this.upstreamProxyUrlParsed) {
            reqOpts.host = this.upstreamProxyUrlParsed.hostname;
            reqOpts.hostname = reqOpts.host;
            reqOpts.port = this.upstreamProxyUrlParsed.port;

            // HTTP requests to proxy contain the full URL in path, for example:
            // "GET http://www.example.com HTTP/1.1\r\n"
            // So we need to replicate it here
            reqOpts.path = this.srcRequest.url;

            (0, _tools.maybeAddProxyAuthorizationHeader)(this.upstreamProxyUrlParsed, reqOpts.headers);

            this.log('Connecting to upstream proxy ' + reqOpts.host + ':' + reqOpts.port);
        } else {
            this.log('Connecting to target ' + reqOpts.host);
        }

        // console.dir(requestOptions);

        this.trgRequest = _http2.default.request(reqOpts);
        this.trgRequest.on('response', this.onTrgResponse);
        this.trgRequest.on('error', this.onTrgError);
        this.trgRequest.on('socket', this.onTrgSocket);

        // this.srcRequest.pipe(tee('to trg')).pipe(this.trgRequest);
        this.srcRequest.pipe(this.trgRequest);
    }
}, {
    key: 'onTrgResponse',
    value: function onTrgResponse(response) {
        if (this.isClosed) return;
        this.log('Received response from target (' + response.statusCode + ')');

        if (this.checkUpstreamProxy407(response)) return;

        // Prepare response headers
        var headers = {};
        for (var i = 0; i < response.rawHeaders.length; i += 2) {
            var name = response.rawHeaders[i];
            var value = response.rawHeaders[i + 1];

            if ((0, _tools.isHopByHopHeader)(name)) continue;
            if ((0, _tools.isInvalidHeader)(name, value)) continue;

            (0, _tools.addHeader)(headers, name, value);
        }

        // Ensure status code is in the range accepted by Node, otherwise proxy will crash with
        // "RangeError: Invalid status code: 0" (see writeHead in Node's _http_server.js)
        // Fixes https://github.com/apifytech/proxy-chain/issues/35
        if (response.statusCode < 100 || response.statusCode > 999) {
            this.fail(new _server.RequestError('Target server responded with an invalid HTTP status code (' + response.statusCode + ')', 500));
            return;
        }

        this.srcGotResponse = true;

        this.srcResponse.writeHead(response.statusCode, headers);
        response.pipe(this.srcResponse);
    }
}, {
    key: 'onTrgError',
    value: function onTrgError(err) {
        if (this.isClosed) return;
        this.log('Target socket failed: ' + (err.stack || err));
        this.fail(err);
    }
}]);

return HandlerForward;

}(_handler_base2.default);

exports.default = HandlerForward;