@opra/core 0.20.3 → 0.21.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (77) hide show
  1. package/cjs/adapter/adapter.js +2 -2
  2. package/cjs/adapter/http/express-adapter.js +29 -7
  3. package/cjs/adapter/http/helpers/common.js +66 -0
  4. package/cjs/adapter/http/helpers/convert-to-headers.js +65 -0
  5. package/cjs/adapter/http/helpers/convert-to-raw-headers.js +25 -0
  6. package/cjs/adapter/http/helpers/match-known-fields.js +47 -0
  7. package/cjs/adapter/http/http-adapter.js +76 -438
  8. package/cjs/adapter/http/impl/http-incoming-message-host.js +127 -0
  9. package/cjs/adapter/http/impl/http-outgoing-message-host.js +210 -0
  10. package/cjs/adapter/http/impl/http-server-request.js +125 -0
  11. package/cjs/adapter/http/impl/http-server-response.js +226 -0
  12. package/cjs/adapter/http/request-parsers/batch-request-parser.js +169 -0
  13. package/cjs/adapter/http/request-parsers/parse-collection-request.js +165 -0
  14. package/cjs/adapter/http/request-parsers/parse-request.js +24 -0
  15. package/cjs/adapter/http/request-parsers/parse-singleton-request.js +96 -0
  16. package/cjs/adapter/request-context.host.js +17 -3
  17. package/cjs/adapter/request.host.js +6 -3
  18. package/cjs/adapter/response.host.js +5 -3
  19. package/cjs/index.js +4 -2
  20. package/esm/adapter/adapter.js +2 -2
  21. package/esm/adapter/http/express-adapter.js +6 -6
  22. package/esm/adapter/http/helpers/common.js +60 -0
  23. package/esm/adapter/http/helpers/convert-to-headers.js +60 -0
  24. package/esm/adapter/http/helpers/convert-to-raw-headers.js +21 -0
  25. package/esm/adapter/http/helpers/match-known-fields.js +43 -0
  26. package/esm/adapter/http/http-adapter.js +77 -439
  27. package/esm/adapter/http/impl/http-incoming-message-host.js +122 -0
  28. package/esm/adapter/http/impl/http-outgoing-message-host.js +205 -0
  29. package/esm/adapter/http/impl/http-server-request.js +121 -0
  30. package/esm/adapter/http/impl/http-server-response.js +222 -0
  31. package/esm/adapter/http/request-parsers/batch-request-parser.js +169 -0
  32. package/esm/adapter/http/request-parsers/parse-collection-request.js +161 -0
  33. package/esm/adapter/http/request-parsers/parse-request.js +20 -0
  34. package/esm/adapter/http/request-parsers/parse-singleton-request.js +92 -0
  35. package/esm/adapter/request-context.host.js +17 -3
  36. package/esm/adapter/request.host.js +6 -3
  37. package/esm/adapter/response.host.js +5 -3
  38. package/esm/index.js +4 -2
  39. package/package.json +20 -16
  40. package/types/adapter/adapter.d.ts +1 -1
  41. package/types/adapter/http/helpers/common.d.ts +17 -0
  42. package/types/adapter/http/helpers/convert-to-headers.d.ts +2 -0
  43. package/types/adapter/http/helpers/convert-to-raw-headers.d.ts +3 -0
  44. package/types/adapter/http/helpers/match-known-fields.d.ts +6 -0
  45. package/types/adapter/http/http-adapter.d.ts +7 -12
  46. package/types/adapter/http/impl/http-incoming-message-host.d.ts +58 -0
  47. package/types/adapter/http/impl/http-outgoing-message-host.d.ts +72 -0
  48. package/types/adapter/http/{http-request-message.d.ts → impl/http-server-request.d.ts} +52 -85
  49. package/types/adapter/http/impl/http-server-response.d.ts +137 -0
  50. package/types/adapter/http/request-parsers/batch-request-parser.d.ts +0 -0
  51. package/types/adapter/http/request-parsers/parse-collection-request.d.ts +4 -0
  52. package/types/adapter/http/request-parsers/parse-request.d.ts +4 -0
  53. package/types/adapter/http/request-parsers/parse-singleton-request.d.ts +4 -0
  54. package/types/adapter/interfaces/request-context.interface.d.ts +14 -10
  55. package/types/adapter/interfaces/request.interface.d.ts +3 -2
  56. package/types/adapter/interfaces/response.interface.d.ts +2 -2
  57. package/types/adapter/request-context.host.d.ts +9 -6
  58. package/types/adapter/request.host.d.ts +8 -4
  59. package/types/adapter/response.host.d.ts +6 -4
  60. package/types/index.d.ts +4 -2
  61. package/cjs/adapter/http/http-message.host.js +0 -251
  62. package/cjs/adapter/http/http-request-context.host.js +0 -28
  63. package/cjs/adapter/http/http-request-message.js +0 -152
  64. package/cjs/adapter/http/http-request.host.js +0 -14
  65. package/cjs/adapter/http/http-response-message.js +0 -238
  66. package/cjs/adapter/http/http-response.host.js +0 -14
  67. package/esm/adapter/http/http-message.host.js +0 -246
  68. package/esm/adapter/http/http-request-context.host.js +0 -24
  69. package/esm/adapter/http/http-request-message.js +0 -148
  70. package/esm/adapter/http/http-request.host.js +0 -10
  71. package/esm/adapter/http/http-response-message.js +0 -233
  72. package/esm/adapter/http/http-response.host.js +0 -10
  73. package/types/adapter/http/http-message.host.d.ts +0 -122
  74. package/types/adapter/http/http-request-context.host.d.ts +0 -16
  75. package/types/adapter/http/http-request.host.d.ts +0 -7
  76. package/types/adapter/http/http-response-message.d.ts +0 -321
  77. package/types/adapter/http/http-response.host.d.ts +0 -7
@@ -0,0 +1,122 @@
1
+ /*
2
+ This file contains code blocks from open source NodeJs project
3
+ https://github.com/nodejs/
4
+ */
5
+ import * as stream from 'stream';
6
+ import { HTTPParser } from '@browsery/http-parser';
7
+ import { isReadable } from '@opra/common';
8
+ import { convertToHeaders, convertToHeadersDistinct } from '../helpers/convert-to-headers.js';
9
+ import { convertToRawHeaders } from '../helpers/convert-to-raw-headers.js';
10
+ export const CRLF = Buffer.from('\r\n');
11
+ export const kHeaders = Symbol('kHeaders');
12
+ export const kHeadersDistinct = Symbol('kHeadersDistinct');
13
+ export const kTrailers = Symbol('kTrailers');
14
+ export const kTrailersDistinct = Symbol('kTrailersDistinct');
15
+ /**
16
+ *
17
+ * @class HttpIncomingMessageHost
18
+ */
19
+ export class HttpIncomingMessageHost {
20
+ constructor() {
21
+ this.rawHeaders = [];
22
+ this.rawTrailers = [];
23
+ this.complete = false;
24
+ this.joinDuplicateHeaders = false;
25
+ stream.Readable.apply(this);
26
+ }
27
+ get httpVersion() {
28
+ return this.httpVersionMajor
29
+ ? this.httpVersionMajor + '.' + this.httpVersionMinor
30
+ : '';
31
+ }
32
+ get headers() {
33
+ if (!this[kHeaders])
34
+ this[kHeaders] = convertToHeaders(this.rawHeaders, {}, this.joinDuplicateHeaders);
35
+ return this[kHeaders];
36
+ }
37
+ set headers(headers) {
38
+ this[kHeaders] = headers;
39
+ }
40
+ get headersDistinct() {
41
+ if (!this[kHeadersDistinct])
42
+ this[kHeadersDistinct] = convertToHeadersDistinct(this.rawHeaders, {});
43
+ return this[kHeadersDistinct];
44
+ }
45
+ get trailers() {
46
+ if (!this[kTrailers])
47
+ this[kTrailers] = convertToHeaders(this.rawTrailers, {}, this.joinDuplicateHeaders);
48
+ return this[kTrailers];
49
+ }
50
+ set trailers(trailers) {
51
+ this[kTrailers] = trailers;
52
+ }
53
+ get trailersDistinct() {
54
+ if (!this[kTrailersDistinct])
55
+ this[kTrailersDistinct] = convertToHeadersDistinct(this.rawTrailers, {});
56
+ return this[kTrailersDistinct];
57
+ }
58
+ _readConfig(init) {
59
+ this.complete = true;
60
+ this.httpVersionMajor = init?.httpVersionMajor || 1;
61
+ this.httpVersionMinor = init?.httpVersionMinor || 0;
62
+ this.method = (init.method || 'GET').toUpperCase();
63
+ this.url = init.url || '';
64
+ this.body = init.body;
65
+ if (init.headers)
66
+ this.rawHeaders = Array.isArray(init.headers) ? init.headers : convertToRawHeaders(init.headers);
67
+ if (init.trailers)
68
+ this.rawTrailers = Array.isArray(init.trailers) ? init.trailers : convertToRawHeaders(init.trailers);
69
+ this.ip = init.ip || '';
70
+ this.ips = init.ips || (this.ip ? [this.ip] : []);
71
+ }
72
+ _readBuffer(buf) {
73
+ const parser = new HTTPParser(HTTPParser.REQUEST);
74
+ let bodyChunks;
75
+ parser[HTTPParser.kOnHeadersComplete] = (info) => {
76
+ this.httpVersionMajor = info.versionMajor;
77
+ this.httpVersionMinor = info.versionMinor;
78
+ this.rawHeaders = info.headers;
79
+ this.method = HTTPParser.methods[info.method];
80
+ this.url = info.url;
81
+ };
82
+ parser[HTTPParser.kOnHeaders] = (trailers) => {
83
+ this.rawTrailers = trailers;
84
+ };
85
+ parser[HTTPParser.kOnBody] = (chunk, offset, length) => {
86
+ bodyChunks = bodyChunks || [];
87
+ bodyChunks.push(chunk.subarray(offset, offset + length));
88
+ };
89
+ parser[HTTPParser.kOnMessageComplete] = () => {
90
+ this.complete = true;
91
+ if (bodyChunks)
92
+ this.body = Buffer.concat(bodyChunks);
93
+ };
94
+ const buffer = Buffer.from(buf);
95
+ let x = parser.execute(buffer, 0, buffer.length);
96
+ if (typeof x === 'object')
97
+ throw x;
98
+ if (!this.complete) {
99
+ x = parser.execute(CRLF);
100
+ if (typeof x === 'object')
101
+ throw x;
102
+ }
103
+ parser.finish();
104
+ }
105
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
106
+ _readStream(readable) {
107
+ throw new Error('_readStream is not implemented yet');
108
+ }
109
+ static create(init) {
110
+ const msg = new HttpIncomingMessageHost();
111
+ if (Buffer.isBuffer(init))
112
+ msg._readBuffer(init);
113
+ else if (isReadable(init)) {
114
+ throw new Error('fromStream is not implemented yet');
115
+ }
116
+ else if (init)
117
+ msg._readConfig(init);
118
+ return msg;
119
+ }
120
+ }
121
+ // Apply mixins
122
+ Object.assign(HttpIncomingMessageHost.prototype, stream.Readable.prototype);
@@ -0,0 +1,205 @@
1
+ /*
2
+ This file contains code blocks from open source NodeJs project
3
+ https://github.com/nodejs/
4
+ */
5
+ var _a;
6
+ import * as stream from 'stream';
7
+ import { validateHeaderName, validateHeaderValue, validateString } from '../helpers/common.js';
8
+ export const kOutHeaders = Symbol('kOutHeaders');
9
+ export const kOutTrailers = Symbol('kOutTrailers');
10
+ export const kErrored = Symbol('kErrored');
11
+ // noinspection JSUnusedLocalSymbols
12
+ /**
13
+ *
14
+ * @class HttpOutgoingMessageHost
15
+ */
16
+ export class HttpOutgoingMessageHost {
17
+ constructor() {
18
+ this._headersSent = false;
19
+ this._closed = false;
20
+ this[_a] = null;
21
+ this.finished = false;
22
+ stream.Stream.apply(this);
23
+ }
24
+ get headersSent() {
25
+ return this._headersSent;
26
+ }
27
+ get closed() {
28
+ return this._closed;
29
+ }
30
+ get errored() {
31
+ return this[kErrored];
32
+ }
33
+ appendHeader(name, value) {
34
+ if (this.headersSent)
35
+ throw new Error(`Cannot set headers after they are sent to the client`);
36
+ validateHeaderName(name);
37
+ validateHeaderValue(name, value);
38
+ const field = name.toLowerCase();
39
+ const headers = this[kOutHeaders];
40
+ if (headers == null || !headers[field]) {
41
+ return this.setHeader(name, value);
42
+ }
43
+ // Prepare the field for appending, if required
44
+ if (!Array.isArray(headers[field][1])) {
45
+ headers[field][1] = [headers[field][1]];
46
+ }
47
+ const existingValues = headers[field][1];
48
+ if (Array.isArray(value)) {
49
+ for (let i = 0, length = value.length; i < length; i++) {
50
+ existingValues.push(value[i]);
51
+ }
52
+ }
53
+ else {
54
+ existingValues.push(value);
55
+ }
56
+ return this;
57
+ }
58
+ addTrailers(headers) {
59
+ if (headers && typeof headers === 'object') {
60
+ const entries = typeof headers.entries === 'function'
61
+ ? headers.entries() : Object.entries(headers);
62
+ let trailers = this[kOutTrailers];
63
+ if (trailers == null)
64
+ this[kOutTrailers] = trailers = { __proto__: null };
65
+ for (const [name, value] of entries) {
66
+ validateHeaderName(name);
67
+ validateHeaderValue(name, value);
68
+ trailers[String(name).toLowerCase()] = [String(name), value];
69
+ }
70
+ return;
71
+ }
72
+ throw new TypeError('Invalid "headers" argument. Value must be an object or raw headers array');
73
+ }
74
+ setHeader(name, value) {
75
+ if (this.headersSent)
76
+ throw new Error(`Cannot set headers after they are sent to the client`);
77
+ validateHeaderName(name);
78
+ validateHeaderValue(name, value);
79
+ let headers = this[kOutHeaders];
80
+ if (headers == null)
81
+ this[kOutHeaders] = headers = { __proto__: null };
82
+ headers[name.toLowerCase()] = [name, value];
83
+ return this;
84
+ }
85
+ setHeaders(headers) {
86
+ if (this.headersSent)
87
+ throw new Error(`Cannot set headers after they are sent to the client`);
88
+ if (headers && typeof headers === 'object' && !Array.isArray(headers)) {
89
+ const entries = typeof headers.entries === 'function'
90
+ ? headers.entries() : Object.entries(headers);
91
+ for (const entry of entries) {
92
+ this.setHeader(entry[0], entry[1]);
93
+ }
94
+ return this;
95
+ }
96
+ throw new TypeError('Invalid "headers" argument. Value must be an instance of "Headers" or "Map"');
97
+ }
98
+ getHeader(name) {
99
+ validateString(name);
100
+ const headers = this[kOutHeaders];
101
+ if (headers == null)
102
+ return;
103
+ const entry = headers[name.toLowerCase()];
104
+ return entry && entry[1];
105
+ }
106
+ getHeaderNames() {
107
+ return this[kOutHeaders] != null ? Object.keys(this[kOutHeaders]) : [];
108
+ }
109
+ getRawHeaderNames() {
110
+ const headersMap = this[kOutHeaders];
111
+ if (!headersMap)
112
+ return [];
113
+ const values = Object.values(headersMap);
114
+ const headers = Array(values.length);
115
+ for (let i = 0, l = values.length; i < l; i++) {
116
+ headers[i] = values[i][0];
117
+ }
118
+ return headers;
119
+ }
120
+ getHeaders() {
121
+ const headers = this[kOutHeaders];
122
+ // @ts-ignore
123
+ const ret = { __proto__: null };
124
+ if (headers) {
125
+ const keys = Object.keys(headers);
126
+ let key;
127
+ let val;
128
+ for (let i = 0; i < keys.length; ++i) {
129
+ key = keys[i];
130
+ val = headers[key][1];
131
+ ret[key] = val;
132
+ }
133
+ }
134
+ return ret;
135
+ }
136
+ hasHeader(name) {
137
+ validateString(name);
138
+ return this[kOutHeaders] != null &&
139
+ !!this[kOutHeaders][name.toLowerCase()];
140
+ }
141
+ removeHeader(name) {
142
+ validateString(name);
143
+ if (this.headersSent)
144
+ throw new Error(`Cannot remove headers after they are sent to the client`);
145
+ const key = name.toLowerCase();
146
+ // switch (key) {
147
+ // case 'connection':
148
+ // this._removedConnection = true;
149
+ // break;
150
+ // case 'content-length':
151
+ // this._removedContLen = true;
152
+ // break;
153
+ // case 'transfer-encoding':
154
+ // this._removedTE = true;
155
+ // break;
156
+ // case 'date':
157
+ // this.sendDate = false;
158
+ // break;
159
+ // }
160
+ if (this[kOutHeaders] != null) {
161
+ delete this[kOutHeaders][key];
162
+ }
163
+ }
164
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
165
+ end(arg0, arg1, arg2) {
166
+ // let cb: (() => void) | undefined;
167
+ // let chunk: any;
168
+ // let encoding: BufferEncoding | undefined;
169
+ //
170
+ // if (typeof arg0 === 'function')
171
+ // cb = arg0;
172
+ // else {
173
+ // chunk = arg0;
174
+ // if (typeof arg1 === 'function')
175
+ // cb = arg1;
176
+ // else {
177
+ // encoding = arg1;
178
+ // cb = arg2;
179
+ // }
180
+ // }
181
+ //
182
+ return this;
183
+ }
184
+ _readConfig(init) {
185
+ this.req = init.req;
186
+ this.statusCode = init?.statusCode || 0;
187
+ this.statusMessage = init?.statusMessage || '';
188
+ this.chunkedEncoding = !!init?.chunkedEncoding;
189
+ this.sendDate = !!init?.sendDate;
190
+ this.strictContentLength = !!init?.strictContentLength;
191
+ if (init.headers) {
192
+ this.setHeaders(Array.isArray(init.headers) ? new Map([init.headers]) : init.headers);
193
+ }
194
+ this.body = init.body;
195
+ }
196
+ static create(init) {
197
+ const msg = new HttpOutgoingMessageHost();
198
+ if (init)
199
+ msg._readConfig(init);
200
+ return msg;
201
+ }
202
+ }
203
+ _a = kErrored;
204
+ // Apply mixins
205
+ Object.assign(HttpOutgoingMessageHost.prototype, stream.Stream.prototype);
@@ -0,0 +1,121 @@
1
+ /*
2
+ Some parts of this file contains codes from open source express library
3
+ https://github.com/expressjs
4
+ */
5
+ import accepts from 'accepts';
6
+ import fresh from 'fresh';
7
+ import parseRange from 'range-parser';
8
+ import typeIs from 'type-is';
9
+ import { isReadable, mergePrototype } from '@opra/common';
10
+ import { HttpIncomingMessageHost } from './http-incoming-message-host.js';
11
+ function isHttpIncomingMessage(v) {
12
+ return v && Array.isArray(v.rawHeaders) && isReadable(v);
13
+ }
14
+ export var HttpServerRequest;
15
+ (function (HttpServerRequest) {
16
+ function create(instance) {
17
+ if (!isHttpIncomingMessage(instance))
18
+ instance = HttpIncomingMessageHost.create(instance);
19
+ mergePrototype(instance, HttpServerRequestHost.prototype);
20
+ return instance;
21
+ }
22
+ HttpServerRequest.create = create;
23
+ })(HttpServerRequest || (HttpServerRequest = {}));
24
+ class HttpServerRequestHost {
25
+ get protocol() {
26
+ const proto = this.header('X-Forwarded-Proto') || 'http';
27
+ const index = proto.indexOf(',');
28
+ return index !== -1
29
+ ? proto.substring(0, index).trim()
30
+ : proto.trim();
31
+ }
32
+ get secure() {
33
+ return this.protocol === 'https';
34
+ }
35
+ get hostname() {
36
+ let host = this.get('X-Forwarded-Host');
37
+ if (!host) {
38
+ host = this.get('Host');
39
+ }
40
+ else if (host.indexOf(',') !== -1) {
41
+ // Note: X-Forwarded-Host is normally only ever a
42
+ // single value, but this is to be safe.
43
+ host = host.substring(0, host.indexOf(',')).trim();
44
+ }
45
+ if (!host)
46
+ return;
47
+ // IPv6 literal support
48
+ const offset = host[0] === '['
49
+ ? host.indexOf(']') + 1
50
+ : 0;
51
+ const index = host.indexOf(':', offset);
52
+ return index !== -1
53
+ ? host.substring(0, index)
54
+ : host;
55
+ }
56
+ get fresh() {
57
+ const method = this.method;
58
+ // GET or HEAD for weak freshness validation only
59
+ if ('GET' !== method && 'HEAD' !== method)
60
+ return false;
61
+ const status = this.res?.statusCode;
62
+ // 2xx or 304 as per rfc2616 14.26
63
+ if ((status >= 200 && status < 300) || 304 === status) {
64
+ return fresh(this.headers, {
65
+ 'etag': this.res.getHeader('ETag'),
66
+ 'last-modified': this.res.getHeader('Last-Modified')
67
+ });
68
+ }
69
+ return false;
70
+ }
71
+ get xhr() {
72
+ const val = this.get('X-Requested-With') || '';
73
+ return val.toLowerCase() === 'xmlhttprequest';
74
+ }
75
+ header(name) {
76
+ name = name.toLowerCase();
77
+ const headers = this.headers;
78
+ switch (name) {
79
+ case 'referer':
80
+ return headers.referer || headers.referrer;
81
+ case 'referrer':
82
+ return headers.referrer || headers.referer;
83
+ default:
84
+ return headers[name];
85
+ }
86
+ }
87
+ get(name) {
88
+ return this.header(name);
89
+ }
90
+ accepts(...types) {
91
+ const accept = accepts(this);
92
+ return accept.types.call(accept, ...types);
93
+ }
94
+ acceptsCharsets(...charsets) {
95
+ const accept = accepts(this);
96
+ return accept.charsets.call(accept, ...charsets);
97
+ }
98
+ acceptsEncodings(...encoding) {
99
+ const accept = accepts(this);
100
+ // eslint-disable-next-line prefer-spread
101
+ return accept.encodings.apply(accept, encoding);
102
+ }
103
+ acceptsLanguages(...lang) {
104
+ const accept = accepts(this);
105
+ // eslint-disable-next-line prefer-spread
106
+ return accept.languages.apply(accept, lang);
107
+ }
108
+ is(type, ...otherTypes) {
109
+ const types = Array.isArray(type) ? type : [type];
110
+ if (otherTypes.length)
111
+ types.push(...otherTypes);
112
+ const contentType = this.header('content-type');
113
+ return contentType ? typeIs.is(contentType, types) : null;
114
+ }
115
+ range(size, options) {
116
+ const range = this.header('range');
117
+ if (!range)
118
+ return;
119
+ return parseRange(size, range, options);
120
+ }
121
+ }
@@ -0,0 +1,222 @@
1
+ /*
2
+ Some parts of this file contains codes from open source express library
3
+ https://github.com/expressjs
4
+ */
5
+ import contentDisposition from 'content-disposition';
6
+ import contentType from 'content-type';
7
+ import cookie from 'cookie';
8
+ import cookieSignature from 'cookie-signature';
9
+ import encodeUrl from 'encodeurl';
10
+ import mime from 'mime-types';
11
+ import path from 'path';
12
+ import { toString } from 'putil-varhelpers';
13
+ import vary from 'vary';
14
+ import { HttpStatusCodes, isStream, mergePrototype } from '@opra/common';
15
+ import { HttpOutgoingMessageHost } from './http-outgoing-message-host.js';
16
+ const charsetRegExp = /;\s*charset\s*=/;
17
+ function isHttpIncomingMessage(v) {
18
+ return v && typeof v.getHeaders === 'function' && isStream(v);
19
+ }
20
+ export var HttpServerResponse;
21
+ (function (HttpServerResponse) {
22
+ function create(instance) {
23
+ if (!isHttpIncomingMessage(instance))
24
+ instance = HttpOutgoingMessageHost.create(instance);
25
+ mergePrototype(instance, HttpServerResponseHost.prototype);
26
+ return instance;
27
+ }
28
+ HttpServerResponse.create = create;
29
+ })(HttpServerResponse || (HttpServerResponse = {}));
30
+ class HttpServerResponseHost {
31
+ attachment(filename) {
32
+ if (filename)
33
+ this.contentType(path.extname(filename));
34
+ this.setHeader('Content-Disposition', contentDisposition(filename));
35
+ return this;
36
+ }
37
+ contentType(type) {
38
+ const ct = type.indexOf('/') === -1
39
+ ? mime.lookup(type)
40
+ : type;
41
+ this.setHeader('Content-Type', ct);
42
+ return this;
43
+ }
44
+ setHeader(field, val) {
45
+ const setHeader = Object.getPrototypeOf(this).setHeader;
46
+ if (typeof field === 'string') {
47
+ let value = Array.isArray(val)
48
+ ? val.map(String)
49
+ : (val ? String(val) : '');
50
+ // add charset to content-type
51
+ if (field.toLowerCase() === 'content-type') {
52
+ if (Array.isArray(value)) {
53
+ throw new TypeError('Content-Type cannot be set to an Array');
54
+ }
55
+ if (!charsetRegExp.test(value)) {
56
+ const charset = mime.charsets.lookup(value.split(';')[0]);
57
+ if (charset)
58
+ value += '; charset=' + charset.toLowerCase();
59
+ }
60
+ }
61
+ setHeader.call(this, field, value);
62
+ }
63
+ else {
64
+ for (const [k, v] of Object.entries(field)) {
65
+ this.setHeader(k, v);
66
+ }
67
+ }
68
+ return this;
69
+ }
70
+ clearCookie(name, options) {
71
+ const opts = {
72
+ expires: new Date(1),
73
+ path: '/',
74
+ ...options
75
+ };
76
+ return this.cookie(name, '', opts);
77
+ }
78
+ cookie(name, value, options) {
79
+ const opts = { ...options };
80
+ let val = typeof value === 'object'
81
+ ? 'j:' + JSON.stringify(value)
82
+ : String(value);
83
+ if (opts.signed) {
84
+ const secret = opts.secret || this.req?.secret;
85
+ if (!secret)
86
+ throw new Error('"secret" required for signed cookies');
87
+ val = 's:' + cookieSignature.sign(val, secret);
88
+ }
89
+ if (opts.maxAge != null) {
90
+ const maxAge = opts.maxAge - 0;
91
+ if (!isNaN(maxAge)) {
92
+ opts.expires = new Date(Date.now() + maxAge);
93
+ opts.maxAge = Math.floor(maxAge / 1000);
94
+ }
95
+ }
96
+ if (opts.path == null)
97
+ opts.path = '/';
98
+ this.appendHeader('Set-Cookie', cookie.serialize(name, String(val), opts));
99
+ return this;
100
+ }
101
+ status(code) {
102
+ this.statusCode = code;
103
+ return this;
104
+ }
105
+ sendStatus(statusCode) {
106
+ const body = HttpStatusCodes[statusCode] || String(statusCode);
107
+ this.statusCode = statusCode;
108
+ this.contentType('txt');
109
+ return this.send(body);
110
+ }
111
+ links(links) {
112
+ let link = this.getHeader('Link') || '';
113
+ if (link)
114
+ link += ', ';
115
+ this.setHeader('Link', link +
116
+ Object.keys(links)
117
+ .map(rel => '<' + links[rel] + '>; rel="' + rel + '"')
118
+ .join(', '));
119
+ return this;
120
+ }
121
+ location(url) {
122
+ let loc = url;
123
+ // "back" is an alias for the referrer
124
+ if (url === 'back')
125
+ loc = this.req?.get('Referrer') || '/';
126
+ // set location
127
+ return this.setHeader('Location', encodeUrl(loc));
128
+ }
129
+ redirect(arg0, arg1) {
130
+ const address = String(arg1 || arg0);
131
+ const status = typeof arg0 === 'number' ? arg0 : 302;
132
+ // Set location header
133
+ this.location(address);
134
+ // Respond
135
+ this.statusCode = status;
136
+ this.end();
137
+ }
138
+ send(body) {
139
+ let chunk = body;
140
+ let encoding;
141
+ const req = this.req;
142
+ let ctype = toString(this.getHeader('Content-Type'));
143
+ if (typeof chunk !== 'string') {
144
+ if (chunk === null)
145
+ chunk = '';
146
+ else if (Buffer.isBuffer(chunk)) {
147
+ if (!ctype)
148
+ this.contentType('bin');
149
+ }
150
+ else {
151
+ ctype = 'json';
152
+ chunk = JSON.stringify(chunk);
153
+ }
154
+ }
155
+ // write strings in utf-8
156
+ if (typeof chunk === 'string') {
157
+ encoding = 'utf-8';
158
+ this.setHeader('Content-Type', setCharset(ctype || 'txt', encoding));
159
+ }
160
+ // populate Content-Length
161
+ let len = 0;
162
+ if (chunk !== undefined) {
163
+ if (Buffer.isBuffer(chunk)) {
164
+ // get length of Buffer
165
+ len = chunk.length;
166
+ }
167
+ else if (chunk.length < 1000) {
168
+ // just calculate length when small chunk
169
+ len = Buffer.byteLength(chunk, encoding);
170
+ }
171
+ else {
172
+ // convert chunk to Buffer and calculate
173
+ chunk = Buffer.from(chunk, encoding);
174
+ encoding = undefined;
175
+ len = chunk.length;
176
+ }
177
+ this.setHeader('Content-Length', len);
178
+ }
179
+ // freshness
180
+ if (req?.fresh)
181
+ this.statusCode = 304;
182
+ // strip irrelevant headers
183
+ if (204 === this.statusCode || 304 === this.statusCode) {
184
+ this.removeHeader('Content-Type');
185
+ this.removeHeader('Content-Length');
186
+ this.removeHeader('Transfer-Encoding');
187
+ chunk = '';
188
+ }
189
+ // alter headers for 205
190
+ if (this.statusCode === 205) {
191
+ this.setHeader('Content-Length', '0');
192
+ this.removeHeader('Transfer-Encoding');
193
+ chunk = '';
194
+ }
195
+ if (req?.method === 'HEAD') {
196
+ // skip body for HEAD
197
+ this.end();
198
+ }
199
+ else {
200
+ // respond
201
+ if (encoding)
202
+ this.end(chunk, encoding);
203
+ else
204
+ this.end(chunk);
205
+ }
206
+ return this;
207
+ }
208
+ vary(field) {
209
+ vary(this, field);
210
+ return this;
211
+ }
212
+ }
213
+ function setCharset(type, charset) {
214
+ if (!(type && charset))
215
+ return type;
216
+ // parse type
217
+ const parsed = contentType.parse(type);
218
+ // set charset
219
+ parsed.parameters.charset = charset;
220
+ // format type
221
+ return contentType.format(parsed);
222
+ }