Skip to content

Commit 6fe7bc8

Browse files
authored
Merge pull request #652 from MaxGenash/STRF-8705_Bug_local_server_is_broken_after_updating_packages
fix: (strf-8705) fix broken headers and cookies in local server
2 parents 6e2aa7d + 1dc8afd commit 6fe7bc8

6 files changed

Lines changed: 299 additions & 105 deletions

File tree

server/lib/utils.js

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,15 @@ const uuidRegExp = '[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-([0-9a-f]{12
33
/**
44
* Strip domain from the cookies header string
55
*
6-
* @param {string} cookies
7-
* @returns {string}
6+
* @param {string[]} cookies
7+
* @returns {string[]}
88
*/
99
function stripDomainFromCookies(cookies) {
10-
return cookies
11-
.replace(/(?:;\s)?domain=(?:.+?)(;|$)/gi, '$1')
12-
.replace(new RegExp('; SameSite=none', 'gi'), '');
10+
return cookies.map((val) =>
11+
val
12+
.replace(/(?:;\s)?domain=(?:.+?)(;|$)/gi, '$1')
13+
.replace(new RegExp('; SameSite=none', 'gi'), ''),
14+
);
1315
}
1416

1517
/**
@@ -30,7 +32,7 @@ function normalizeRedirectUrl(request, redirectUrl) {
3032
}
3133

3234
if (stripHost) {
33-
return redirectUrlObj.path;
35+
return redirectUrlObj.pathname + redirectUrlObj.search + redirectUrlObj.hash;
3436
}
3537
return redirectUrl;
3638
}

server/plugins/renderer/renderer.module.js

Lines changed: 55 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -69,14 +69,16 @@ internals.getResponse = async (request) => {
6969
: new URL(request.app.storeUrl);
7070

7171
const httpOpts = {
72-
headers: {
73-
...internals.getHeaders(request, { get_template_file: true, get_data_only: true }),
74-
host: staplerUrlObject.host,
75-
},
72+
headers: internals.buildReqHeaders({
73+
request,
74+
stencilOptions: { get_template_file: true, get_data_only: true },
75+
extraHeaders: { host: staplerUrlObject.host },
76+
}),
7677
// Fetch will break if request body is Stream and server response is redirect,
7778
// so we need to read the data first and then send the request
7879
body: request.payload ? await readFromStream(request.payload) : request.payload,
79-
method: 'post',
80+
method: request.method,
81+
redirect: 'manual',
8082
};
8183

8284
const url = Object.assign(new URL(request.url.toString()), {
@@ -116,6 +118,8 @@ internals.getResponse = async (request) => {
116118

117119
const response = await fetch(url, httpOpts);
118120

121+
internals.processResHeaders(response.headers);
122+
119123
if (response.status === 401) {
120124
return response;
121125
}
@@ -124,13 +128,6 @@ internals.getResponse = async (request) => {
124128
throw new Error('The BigCommerce server responded with a 500 error');
125129
}
126130

127-
if (response.headers.get('set-cookie')) {
128-
response.headers.set(
129-
'set-cookie',
130-
utils.stripDomainFromCookies(response.headers.get('set-cookie')),
131-
);
132-
}
133-
134131
// Response is a redirect
135132
if (response.status >= 301 && response.status <= 303) {
136133
return internals.redirect(response, request);
@@ -163,32 +160,29 @@ internals.getResponse = async (request) => {
163160
* @returns {*}
164161
*/
165162
internals.parseResponse = async (bcAppData, request, response, responseArgs) => {
166-
const configuration = await request.app.themeConfig.getConfig();
167163
const { httpOpts, staplerUrlObject, url } = responseArgs;
168164

169165
if (typeof bcAppData !== 'object' || !('pencil_response' in bcAppData)) {
170166
response.headers.delete('x-frame-options');
171167

172168
// this is a raw response not emitted by TemplateEngine
173-
return new RawResponse(
174-
bcAppData,
175-
_.fromPairs(response.headers.entries()), // Transform fetch.Headers map to a plain object
176-
response.status,
177-
);
169+
return new RawResponse(bcAppData, response.headers.raw(), response.status);
178170
}
179171

172+
const configuration = await request.app.themeConfig.getConfig();
173+
180174
// If a remote call, no need to do a second call to get the data,
181175
// it has already come back
182176
if (bcAppData.remote) {
183177
return internals.getPencilResponse(bcAppData, request, response, configuration);
184178
}
185179

186-
const resourcesConfig = internals.getResourceConfig(bcAppData, request, configuration);
187-
188-
httpOpts.headers = {
189-
...internals.getHeaders(request, { get_data_only: true }, resourcesConfig),
190-
host: staplerUrlObject.host,
191-
};
180+
httpOpts.headers = internals.buildReqHeaders({
181+
request,
182+
stencilOptions: { get_data_only: true },
183+
stencilConfig: internals.getResourceConfig(bcAppData, request, configuration),
184+
extraHeaders: { host: staplerUrlObject.host },
185+
});
192186

193187
// create request signature for caching
194188
const httpOptsSignature = internals.sha1sum(_.omit(httpOpts.headers, ['cookie']));
@@ -204,31 +198,24 @@ internals.parseResponse = async (bcAppData, request, response, responseArgs) =>
204198
} else {
205199
response2 = await fetch(url, httpOpts);
206200

201+
internals.processResHeaders(response2.headers);
202+
207203
// Response is a redirect
208204
if (response2.status >= 301 && response2.status <= 303) {
209205
return internals.redirect(response2, request);
210206
}
211207

212-
// Response is bad
213208
if (response2.status === 500) {
214209
throw new Error('The BigCommerce server responded with a 500 error');
215210
}
216211

217212
data = await response2.json();
218213

219-
// Data response is bad
220214
if (data.status && data.status === 500) {
221215
throw new Error('The BigCommerce server responded with a 500 error');
222216
}
223217

224218
cache.put(dataRequestSignature, { data, response2 }, internals.cacheTTL);
225-
226-
if (response2.headers.get('set-cookie')) {
227-
response2.headers.set(
228-
'set-cookie',
229-
utils.stripDomainFromCookies(response2.headers.get('set-cookie')),
230-
);
231-
}
232219
}
233220

234221
return internals.getPencilResponse(data, request, response2, configuration);
@@ -298,22 +285,16 @@ internals.getResourceConfig = (data, request, configuration) => {
298285
* @returns {*}
299286
*/
300287
internals.redirect = async (response, request) => {
301-
if (!response.headers.get('location')) {
288+
const location = response.headers.get('location');
289+
290+
if (!location) {
302291
throw new Error('StatusCode is set to 30x but there is no location header to redirect to.');
303292
}
304293

305-
const normalizedRedirectUrl = utils.normalizeRedirectUrl(
306-
request,
307-
response.headers.get('location'),
308-
);
309-
response.headers.set('location', normalizedRedirectUrl);
294+
response.headers.set('location', utils.normalizeRedirectUrl(request, location));
310295

311296
// return a redirect response
312-
return new RedirectResponse(
313-
response.headers.get('location'),
314-
_.fromPairs(response.headers.entries()), // Transform fetch.Headers map to a plain object
315-
response.status,
316-
);
297+
return new RedirectResponse(location, response.headers.raw(), response.status);
317298
};
318299

319300
/**
@@ -390,21 +371,30 @@ internals.getPencilResponse = (data, request, response, configuration) => {
390371
translations: data.translations,
391372
method: request.method,
392373
acceptLanguage: request.headers['accept-language'],
393-
headers: _.fromPairs(response.headers.entries()), // Transform fetch.Headers map to a plain object
374+
headers: response.headers.raw(),
394375
statusCode: response.status,
395376
},
396377
internals.themeAssembler,
397378
);
398379
};
399380

400381
/**
401-
* Generate and return headers
382+
* Get headers from request and return a new object with processed headers
402383
*
403-
* @param {object} request
384+
* @param {object} request - Hapi request
385+
* @param {[string]: string} request.headers
386+
* @param {object} request.app
404387
* @param {object} [stencilOptions]
405388
* @param {object} [stencilConfig]
389+
* @param {object} [extraHeaders] - extra headers to add to the result
390+
* @returns {object}
406391
*/
407-
internals.getHeaders = (request, stencilOptions = {}, stencilConfig) => {
392+
internals.buildReqHeaders = ({
393+
request,
394+
stencilOptions = {},
395+
stencilConfig = null,
396+
extraHeaders = {},
397+
}) => {
408398
// Merge in current stencil-options with passed in options
409399
const currentOptions = request.headers['stencil-options']
410400
? JSON.parse(request.headers['stencil-options'])
@@ -429,7 +419,23 @@ internals.getHeaders = (request, stencilOptions = {}, stencilConfig) => {
429419
headers['stencil-store-url'] = request.app.storeUrl;
430420
}
431421

432-
return { ...request.headers, ...headers };
422+
return { ...request.headers, ...headers, ...extraHeaders };
423+
};
424+
425+
/**
426+
* Process headers from Fetch response
427+
*
428+
* @param {Headers} headers
429+
* @returns {object}
430+
*/
431+
internals.processResHeaders = (headers) => {
432+
if (headers.get('set-cookie')) {
433+
// When there are several values for the same header headers.get() returns
434+
// an array joined with comma which is wrong for cookies, so we use the initial raw Array
435+
// https://github.com/node-fetch/node-fetch/issues/251#issuecomment-428143940
436+
// eslint-disable-next-line no-param-reassign
437+
headers.raw()['set-cookie'] = utils.stripDomainFromCookies(headers.raw()['set-cookie']);
438+
}
433439
};
434440

435441
/**

0 commit comments

Comments
 (0)