@salesforce/pwa-kit-runtime 3.7.0 → 3.7.1

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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@salesforce/pwa-kit-runtime",
3
- "version": "3.7.0",
3
+ "version": "3.7.1",
4
4
  "description": "The PWAKit Runtime",
5
5
  "homepage": "https://github.com/SalesforceCommerceCloud/pwa-kit/tree/develop/packages/pwa-kit-runtime#readme",
6
6
  "bugs": {
@@ -46,11 +46,11 @@
46
46
  },
47
47
  "devDependencies": {
48
48
  "@loadable/component": "^5.15.3",
49
- "@salesforce/pwa-kit-dev": "3.7.0",
49
+ "@salesforce/pwa-kit-dev": "3.7.1",
50
50
  "@serverless/event-mocks": "^1.1.1",
51
51
  "aws-lambda-mock-context": "^3.2.1",
52
52
  "fs-extra": "^11.1.1",
53
- "internal-lib-build": "3.7.0",
53
+ "internal-lib-build": "3.7.1",
54
54
  "nock": "^13.3.0",
55
55
  "nodemon": "^2.0.22",
56
56
  "sinon": "^13.0.2",
@@ -58,7 +58,7 @@
58
58
  "supertest": "^4.0.2"
59
59
  },
60
60
  "peerDependencies": {
61
- "@salesforce/pwa-kit-dev": "3.7.0"
61
+ "@salesforce/pwa-kit-dev": "3.7.1"
62
62
  },
63
63
  "peerDependenciesMeta": {
64
64
  "@salesforce/pwa-kit-dev": {
@@ -72,5 +72,5 @@
72
72
  "publishConfig": {
73
73
  "directory": "dist"
74
74
  },
75
- "gitHead": "7a47e6b91a5a0edc69c999115345b9bd5154935f"
75
+ "gitHead": "9dde49caa598178c7a8dd406966f0d635fa8102c"
76
76
  }
@@ -641,6 +641,9 @@ const RemoteServerFactory = exports.RemoteServerFactory = {
641
641
  // to add in their projects, like in any regular Express app.
642
642
  app.use(ssrMiddleware);
643
643
  app.use(errorHandlerMiddleware);
644
+ if (options !== null && options !== void 0 && options.encodeNonAsciiHttpHeaders) {
645
+ app.use(encodeNonAsciiMiddleware);
646
+ }
644
647
  applyPatches(options);
645
648
  },
646
649
  /**
@@ -799,13 +802,29 @@ const RemoteServerFactory = exports.RemoteServerFactory = {
799
802
  * @param app {Express} - an Express App
800
803
  * @private
801
804
  */
802
- _createHandler(app) {
805
+ _createHandler(app, options) {
803
806
  // This flag is initially false, and is set true on the first request
804
807
  // handled by a Lambda. If it is true on entry to the handler function,
805
808
  // it indicates that the Lambda container has been reused.
806
809
  let lambdaContainerReused = false;
807
810
  const server = _awsServerlessExpress.default.createServer(app, null, binaryMimeTypes);
808
811
  const handler = (event, context, callback) => {
812
+ // encode non ASCII request headers
813
+ if (options !== null && options !== void 0 && options.encodeNonAsciiHttpHeaders) {
814
+ Object.keys(event.headers).forEach(key => {
815
+ if (!isASCII(event.headers[key])) {
816
+ event.headers[key] = encodeURIComponent(event.headers[key]);
817
+ // x-encoded-headers keeps track of which headers have been modified and encoded
818
+ if (event.headers[_constants.X_ENCODED_HEADERS]) {
819
+ // append header key
820
+ event.headers[_constants.X_ENCODED_HEADERS] = `${event.headers[_constants.X_ENCODED_HEADERS]},${key}`;
821
+ } else {
822
+ event.headers[_constants.X_ENCODED_HEADERS] = key;
823
+ }
824
+ }
825
+ });
826
+ }
827
+
809
828
  // We don't want to wait for an empty event loop once the response
810
829
  // has been sent. Setting this to false will "send the response
811
830
  // right away when the callback executes", but any pending events
@@ -907,8 +926,8 @@ const RemoteServerFactory = exports.RemoteServerFactory = {
907
926
  createHandler(options, customizeApp) {
908
927
  process.on('unhandledRejection', _ssrServer.catchAndLog);
909
928
  const app = this._createApp(options);
910
- customizeApp(app);
911
- return this._createHandler(app);
929
+ customizeApp(app, options);
930
+ return this._createHandler(app, options);
912
931
  },
913
932
  /**
914
933
  * @private
@@ -991,6 +1010,37 @@ const errorHandlerMiddleware = (err, req, res, next) => {
991
1010
  res.sendStatus(500);
992
1011
  };
993
1012
 
1013
+ /**
1014
+ * Helper function that checks if a string is composed of ASCII characters
1015
+ * We only check printable ASCII characters and not special ASCII characters
1016
+ * such as NULL
1017
+ *
1018
+ * @private
1019
+ */
1020
+ const isASCII = str => {
1021
+ return /^[\x20-\x7E]*$/.test(str);
1022
+ };
1023
+
1024
+ /**
1025
+ * Express Middleware applied to responses that encode any non ASCII headers
1026
+ *
1027
+ * @private
1028
+ */
1029
+ const encodeNonAsciiMiddleware = (req, res, next) => {
1030
+ const originalSetHeader = res.setHeader;
1031
+ res.setHeader = function (key, value) {
1032
+ if (!isASCII(value)) {
1033
+ originalSetHeader.call(this, key, encodeURIComponent(value));
1034
+ let encodedHeaders = res.getHeader(_constants.X_ENCODED_HEADERS);
1035
+ encodedHeaders = encodedHeaders ? `${encodedHeaders},${key}` : key;
1036
+ originalSetHeader.call(this, _constants.X_ENCODED_HEADERS, encodedHeaders);
1037
+ } else {
1038
+ originalSetHeader.call(this, key, value);
1039
+ }
1040
+ };
1041
+ next();
1042
+ };
1043
+
994
1044
  /**
995
1045
  * Wrap the function fn in such a way that it will be called at most once. Subsequent
996
1046
  * calls will always return the same value.
@@ -1,13 +1,25 @@
1
1
  "use strict";
2
2
 
3
3
  var _buildRemoteServer = require("./build-remote-server");
4
- /*
4
+ var _constants = require("./constants");
5
+ var _awsServerlessExpress = _interopRequireDefault(require("aws-serverless-express"));
6
+ function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
7
+ function ownKeys(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; }
8
+ function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys(Object(t), !0).forEach(function (r) { _defineProperty(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; }
9
+ function _defineProperty(e, r, t) { return (r = _toPropertyKey(r)) in e ? Object.defineProperty(e, r, { value: t, enumerable: !0, configurable: !0, writable: !0 }) : e[r] = t, e; }
10
+ function _toPropertyKey(t) { var i = _toPrimitive(t, "string"); return "symbol" == typeof i ? i : i + ""; }
11
+ function _toPrimitive(t, r) { if ("object" != typeof t || !t) return t; var e = t[Symbol.toPrimitive]; if (void 0 !== e) { var i = e.call(t, r || "default"); if ("object" != typeof i) return i; throw new TypeError("@@toPrimitive must return a primitive value."); } return ("string" === r ? String : Number)(t); } /*
5
12
  * Copyright (c) 2021, salesforce.com, inc.
6
13
  * All rights reserved.
7
14
  * SPDX-License-Identifier: BSD-3-Clause
8
15
  * For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/BSD-3-Clause
9
16
  */
10
-
17
+ jest.mock('aws-serverless-express', () => {
18
+ return {
19
+ createServer: jest.fn(),
20
+ proxy: jest.fn()
21
+ };
22
+ });
11
23
  describe('the once function', () => {
12
24
  test('should prevent a function being called more than once', () => {
13
25
  const fn = jest.fn(() => ({
@@ -33,4 +45,70 @@ describe('remote server factory test coverage', () => {
33
45
  });
34
46
  expect(endpoint).toBeDefined();
35
47
  });
48
+ });
49
+ describe('encodeNonAsciiHttpHeaders flag in options to createHandler', () => {
50
+ test('encodes request headers', () => {
51
+ const mockApp = {
52
+ sendMetric: jest.fn()
53
+ };
54
+ const mockOptions = {
55
+ encodeNonAsciiHttpHeaders: true
56
+ };
57
+ const originalHeaders = {
58
+ 'x-non-ascii-header-one': 'テスト',
59
+ 'x-non-ascii-header-two': '测试',
60
+ 'x-regular-header': 'ascii-str'
61
+ };
62
+ const event = {
63
+ headers: _objectSpread({}, originalHeaders)
64
+ };
65
+ const expectedHeaders = {
66
+ 'x-non-ascii-header-one': '%E3%83%86%E3%82%B9%E3%83%88',
67
+ 'x-non-ascii-header-two': '%E6%B5%8B%E8%AF%95',
68
+ 'x-encoded-headers': 'x-non-ascii-header-one,x-non-ascii-header-two',
69
+ 'x-regular-header': 'ascii-str'
70
+ };
71
+ const {
72
+ handler
73
+ } = _buildRemoteServer.RemoteServerFactory._createHandler(mockApp, mockOptions);
74
+ expect(event.headers).toEqual(originalHeaders);
75
+ handler(event, {}, {});
76
+ expect(event.headers).toEqual(expectedHeaders);
77
+ expect(decodeURIComponent(event.headers['x-non-ascii-header-one'])).toEqual(originalHeaders['x-non-ascii-header-one']);
78
+ });
79
+ test('encodes response headers', () => {
80
+ const mockApp = {
81
+ use: jest.fn()
82
+ };
83
+ const mockOptions = {
84
+ encodeNonAsciiHttpHeaders: true
85
+ };
86
+ const res = {
87
+ headers: {},
88
+ setHeader: (key, value) => {
89
+ res.headers[key] = value;
90
+ },
91
+ getHeader: key => {
92
+ return res.headers[key];
93
+ }
94
+ };
95
+ const nonASCIIheader = 'x-non-ascii-header';
96
+ const nonASCIIstr = 'テスト';
97
+ const expectedEncoding = '%E3%83%86%E3%82%B9%E3%83%88';
98
+ const regularHeaderKey = 'x-regular-header';
99
+ const regularHeaderValue = 'ascii-str';
100
+ _buildRemoteServer.RemoteServerFactory._setupCommonMiddleware(mockApp, mockOptions);
101
+ const encodeNonAsciiMiddleware = mockApp.use.mock.calls[3][0];
102
+ res.setHeader(nonASCIIheader, nonASCIIstr);
103
+ expect(res.getHeader(nonASCIIheader)).toEqual(nonASCIIstr);
104
+ encodeNonAsciiMiddleware({}, res, () => {});
105
+ res.setHeader(nonASCIIheader, nonASCIIstr);
106
+ expect(res.getHeader(nonASCIIheader)).toEqual(expectedEncoding);
107
+ expect(decodeURI(expectedEncoding)).toEqual(nonASCIIstr);
108
+ expect(res.getHeader(_constants.X_ENCODED_HEADERS)).toEqual(nonASCIIheader);
109
+
110
+ // confirm ASCII headers are not modified
111
+ res.setHeader(regularHeaderKey, regularHeaderValue);
112
+ expect(res.getHeader(regularHeaderKey)).toEqual(regularHeaderValue);
113
+ });
36
114
  });
@@ -3,7 +3,7 @@
3
3
  Object.defineProperty(exports, "__esModule", {
4
4
  value: true
5
5
  });
6
- exports.X_ORIGINAL_CONTENT_TYPE = exports.X_MOBIFY_QUERYSTRING = exports.X_MOBIFY_FROM_CACHE = exports.STRICT_TRANSPORT_SECURITY = exports.STATIC_ASSETS = exports.SLAS_CUSTOM_PROXY_PATH = exports.SET_COOKIE = exports.PROXY_PATH_PREFIX = exports.NO_CACHE = exports.CONTENT_TYPE = exports.CONTENT_SECURITY_POLICY = exports.CONTENT_ENCODING = exports.CACHE_CONTROL = exports.BUILD = exports.APPLICATION_OCTET_STREAM = void 0;
6
+ exports.X_ORIGINAL_CONTENT_TYPE = exports.X_MOBIFY_QUERYSTRING = exports.X_MOBIFY_FROM_CACHE = exports.X_ENCODED_HEADERS = exports.STRICT_TRANSPORT_SECURITY = exports.STATIC_ASSETS = exports.SLAS_CUSTOM_PROXY_PATH = exports.SET_COOKIE = exports.PROXY_PATH_PREFIX = exports.NO_CACHE = exports.CONTENT_TYPE = exports.CONTENT_SECURITY_POLICY = exports.CONTENT_ENCODING = exports.CACHE_CONTROL = exports.BUILD = exports.APPLICATION_OCTET_STREAM = void 0;
7
7
  /*
8
8
  * Copyright (c) 2021, salesforce.com, inc.
9
9
  * All rights reserved.
@@ -23,6 +23,7 @@ const CONTENT_ENCODING = exports.CONTENT_ENCODING = 'content-encoding';
23
23
  const X_ORIGINAL_CONTENT_TYPE = exports.X_ORIGINAL_CONTENT_TYPE = 'x-original-content-type';
24
24
  const X_MOBIFY_QUERYSTRING = exports.X_MOBIFY_QUERYSTRING = 'x-mobify-querystring';
25
25
  const X_MOBIFY_FROM_CACHE = exports.X_MOBIFY_FROM_CACHE = 'x-mobify-from-cache';
26
+ const X_ENCODED_HEADERS = exports.X_ENCODED_HEADERS = 'x-encoded-headers';
26
27
  const SET_COOKIE = exports.SET_COOKIE = 'set-cookie';
27
28
  const CACHE_CONTROL = exports.CACHE_CONTROL = 'cache-control';
28
29
  const NO_CACHE = exports.NO_CACHE = 'max-age=0, nocache, nostore, must-revalidate';