UNPKG

3.33 kBJavaScriptView Raw
1'use strict'
2
3const http = require('http')
4const https = require('https')
5const headersFactory = require('../mock/headers')
6
7const http2ForbiddenResponseHeaders = [
8 'transfer-encoding',
9 'connection',
10 'keep-alive'
11]
12
13function protocol (url) {
14 if (url.startsWith('https')) {
15 return https
16 }
17 return http
18}
19
20function unsecureCookies (headers) {
21 const setCookie = headers['set-cookie']
22 if (setCookie) {
23 headers['set-cookie'] = setCookie.map(cookie => cookie.replace(/\s*secure;/i, ''))
24 }
25}
26
27function noop () {}
28
29function validateHook (mapping, hookName) {
30 if (typeof mapping[hookName] === 'string') {
31 mapping[hookName] = require(mapping[hookName])
32 }
33}
34
35module.exports = {
36 schema: {
37 'unsecure-cookies': {
38 type: 'boolean',
39 defaultValue: false
40 },
41 'forward-request': {
42 types: ['function', 'string'],
43 defaultValue: noop
44 },
45 'forward-response': {
46 types: ['function', 'string'],
47 defaultValue: noop
48 },
49 'ignore-unverifiable-certificate': {
50 type: 'boolean',
51 defaultValue: false
52 }
53 },
54 validate: async mapping => {
55 validateHook(mapping, 'forward-request')
56 validateHook(mapping, 'forward-response')
57 },
58 redirect: async ({ configuration, mapping, match, redirect: url, request, response }) => {
59 let done
60 let fail
61 const promise = new Promise((resolve, reject) => {
62 done = resolve
63 fail = reject
64 })
65 const { method, headers } = request
66 const options = {
67 method,
68 url,
69 headers: headersFactory(headers)
70 }
71 delete options.headers.host // Some websites rely on the host header
72 if (mapping['ignore-unverifiable-certificate']) {
73 options.rejectUnauthorized = false
74 }
75 const context = {}
76 const hookParams = {
77 configuration,
78 context,
79 mapping,
80 match,
81 request: options,
82 incoming: request
83 }
84 await mapping['forward-request'](hookParams)
85 Object.keys(options.headers)
86 .filter(header => header.startsWith(':'))
87 .forEach(header => delete options.headers[header])
88 const redirectedRequest = protocol(options.url).request(options.url, options, async redirectedResponse => {
89 if (mapping['unsecure-cookies']) {
90 unsecureCookies(redirectedResponse.headers)
91 }
92 const { headers: responseHeaders } = redirectedResponse
93 const result = await mapping['forward-response']({
94 ...hookParams,
95 statusCode: redirectedResponse.statusCode,
96 headers: responseHeaders
97 })
98 if (result !== undefined) {
99 if (!['GET', 'HEAD'].includes(request.method)) {
100 return fail(new Error('Internal redirection impossible because the body is already consumed'))
101 }
102 return done(result)
103 }
104 if (configuration.http2) {
105 http2ForbiddenResponseHeaders.forEach(header => delete responseHeaders[header])
106 }
107 response.writeHead(redirectedResponse.statusCode, responseHeaders)
108 if (request.aborted) {
109 response.end()
110 return done()
111 }
112 response.on('finish', () => done(result))
113 redirectedResponse
114 .on('error', fail)
115 .pipe(response)
116 })
117 redirectedRequest.on('error', fail)
118 request
119 .on('error', fail)
120 .pipe(redirectedRequest)
121 return promise
122 }
123}