Skip to content

Commit

Permalink
Implement raw headers, add tests
Browse files Browse the repository at this point in the history
  • Loading branch information
MRayermannMSFT committed Nov 29, 2021
1 parent a8acbc1 commit 4a2795c
Show file tree
Hide file tree
Showing 3 changed files with 157 additions and 11 deletions.
28 changes: 19 additions & 9 deletions lib/browser/api/net.ts
Expand Up @@ -61,31 +61,41 @@ class IncomingMessage extends Readable {
const filteredHeaders: Record<string, string | string[]> = {};
const { rawHeaders } = this._responseHead;
rawHeaders.forEach(header => {
if (Object.prototype.hasOwnProperty.call(filteredHeaders, header.key) &&
discardableDuplicateHeaders.has(header.key)) {
const keyLowerCase = header.key.toLowerCase();
if (Object.prototype.hasOwnProperty.call(filteredHeaders, keyLowerCase) &&
discardableDuplicateHeaders.has(keyLowerCase)) {
// do nothing with discardable duplicate headers
} else {
if (header.key === 'set-cookie') {
if (keyLowerCase === 'set-cookie') {
// keep set-cookie as an array per Node.js rules
// see https://nodejs.org/api/http.html#http_message_headers
if (Object.prototype.hasOwnProperty.call(filteredHeaders, header.key)) {
(filteredHeaders[header.key] as string[]).push(header.value);
if (Object.prototype.hasOwnProperty.call(filteredHeaders, keyLowerCase)) {
(filteredHeaders[keyLowerCase] as string[]).push(header.value);
} else {
filteredHeaders[header.key] = [header.value];
filteredHeaders[keyLowerCase] = [header.value];
}
} else {
// for non-cookie headers, the values are joined together with ', '
if (Object.prototype.hasOwnProperty.call(filteredHeaders, header.key)) {
filteredHeaders[header.key] += `, ${header.value}`;
if (Object.prototype.hasOwnProperty.call(filteredHeaders, keyLowerCase)) {
filteredHeaders[keyLowerCase] += `, ${header.value}`;
} else {
filteredHeaders[header.key] = header.value;
filteredHeaders[keyLowerCase] = header.value;
}
}
}
});
return filteredHeaders;
}

get rawHeaders () {
const rawHeadersArr: string[] = [];
const { rawHeaders } = this._responseHead;
rawHeaders.forEach(header => {
rawHeadersArr.push(header.key, header.value);
});
return rawHeadersArr;
}

get httpVersion () {
return `${this.httpVersionMajor}.${this.httpVersionMinor}`;
}
Expand Down
2 changes: 1 addition & 1 deletion shell/browser/api/electron_api_url_loader.cc
Expand Up @@ -40,7 +40,7 @@ struct Converter<network::mojom::HttpRawHeaderPairPtr> {
v8::Isolate* isolate,
const network::mojom::HttpRawHeaderPairPtr& pair) {
gin_helper::Dictionary dict = gin::Dictionary::CreateEmpty(isolate);
dict.Set("key", base::ToLowerASCII(pair->key));
dict.Set("key", pair->key);
dict.Set("value", pair->value);
return dict.GetHandle();
}
Expand Down
138 changes: 137 additions & 1 deletion spec-main/api-net-spec.ts
Expand Up @@ -1561,6 +1561,11 @@ describe('net module', () => {
const headerValue = headers[customHeaderName.toLowerCase()];
expect(headerValue).to.equal(customHeaderValue);

const rawHeaders = response.rawHeaders;
expect(rawHeaders).to.be.an('array');
expect(rawHeaders[0]).to.equal(customHeaderName);
expect(rawHeaders[1]).to.equal(customHeaderValue);

const httpVersion = response.httpVersion;
expect(httpVersion).to.be.a('string').and.to.have.lengthOf.at.least(1);

Expand Down Expand Up @@ -1602,7 +1607,7 @@ describe('net module', () => {
await collectStreamBody(response);
});

it('should join repeated non-discardable value with ,', async () => {
it('should join repeated non-discardable header values with ,', async () => {
const serverUrl = await respondOnce.toSingleURL((request, response) => {
response.statusCode = 200;
response.statusMessage = 'OK';
Expand All @@ -1622,6 +1627,137 @@ describe('net module', () => {
await collectStreamBody(response);
});

it('should not join repeated discardable header values with ,', async () => {
const serverUrl = await respondOnce.toSingleURL((request, response) => {
response.statusCode = 200;
response.statusMessage = 'OK';
response.setHeader('last-modified', ['yesterday', 'today']);
response.end();
});
const urlRequest = net.request(serverUrl);
const response = await getResponse(urlRequest);
expect(response.statusCode).to.equal(200);
expect(response.statusMessage).to.equal('OK');

const headers = response.headers;
expect(headers).to.be.an('object');
expect(headers).to.have.property('last-modified');
expect(headers['last-modified']).to.equal('yesterday');

await collectStreamBody(response);
});

it('should make set-cookie header an array even if single value', async () => {
const serverUrl = await respondOnce.toSingleURL((request, response) => {
response.statusCode = 200;
response.statusMessage = 'OK';
response.setHeader('set-cookie', 'chocolate-chip')
response.end();
});
const urlRequest = net.request(serverUrl);
const response = await getResponse(urlRequest);
expect(response.statusCode).to.equal(200);
expect(response.statusMessage).to.equal('OK');

const headers = response.headers;
expect(headers).to.be.an('object');
expect(headers).to.have.property('set-cookie');
expect(headers['set-cookie']).to.be.an('array');
expect(headers['set-cookie'][0]).to.equal('chocolate-chip');

await collectStreamBody(response);
});

it('should keep set-cookie header an array when an array', async () => {
const serverUrl = await respondOnce.toSingleURL((request, response) => {
response.statusCode = 200;
response.statusMessage = 'OK';
response.setHeader('set-cookie', ['chocolate-chip', 'oatmeal'])
response.end();
});
const urlRequest = net.request(serverUrl);
const response = await getResponse(urlRequest);
expect(response.statusCode).to.equal(200);
expect(response.statusMessage).to.equal('OK');

const headers = response.headers;
expect(headers).to.be.an('object');
expect(headers).to.have.property('set-cookie');
expect(headers['set-cookie']).to.be.an('array');
expect(headers['set-cookie'][0]).to.equal('chocolate-chip');
expect(headers['set-cookie'][1]).to.equal('oatmeal');

await collectStreamBody(response);
});

it('should lowercase header keys', async () => {
const serverUrl = await respondOnce.toSingleURL((request, response) => {
response.statusCode = 200;
response.statusMessage = 'OK';
response.setHeader('HEADER-KEY', ['header-value']);
response.setHeader('SeT-CookiE', ['chocolate-chip', 'oatmeal'])
response.setHeader('rEFERREr-pOLICy', ['first-text', 'second-text']);
response.setHeader('LAST-modified', 'yesterday');

response.end();
});
const urlRequest = net.request(serverUrl);
const response = await getResponse(urlRequest);
expect(response.statusCode).to.equal(200);
expect(response.statusMessage).to.equal('OK');

const headers = response.headers;
expect(headers).to.be.an('object');

expect(headers).to.have.property('header-key');
expect(headers).to.have.property('set-cookie');
expect(headers).to.have.property('referrer-policy');
expect(headers).to.have.property('last-modified');

await collectStreamBody(response);
});

it('should return correct raw headers', async () => {
const customHeaders: [string, string|string[]][] = [
['HEADER-KEY-ONE','header-value-one'],
['set-cookie', 'chocolate-chip'],
['header-key-two','header-value-two'],
['referrer-policy', ['first-text', 'second-text']],
['HEADER-KEY-THREE','header-value-three'],
['last-modified', ['first-text', 'second-text']],
['header-key-four','header-value-four']
];

const serverUrl = await respondOnce.toSingleURL((request, response) => {
response.statusCode = 200;
response.statusMessage = 'OK';
customHeaders.forEach((headerTuple) => {
response.setHeader(headerTuple[0], headerTuple[1])
});
response.end();
});
const urlRequest = net.request(serverUrl);
const response = await getResponse(urlRequest);
expect(response.statusCode).to.equal(200);
expect(response.statusMessage).to.equal('OK');

const rawHeaders = response.rawHeaders;
expect(rawHeaders).to.be.an('array');

let rawHeadersIdx = 0;
customHeaders.forEach((headerTuple) => {
const headerKey = headerTuple[0];
const headerValues = Array.isArray(headerTuple[1]) ? headerTuple[1] : [headerTuple[1]];
headerValues.forEach((headerValue) => {
expect(rawHeaders[rawHeadersIdx]).to.equal(headerKey);
expect(rawHeaders[rawHeadersIdx+1]).to.equal(headerValue);
rawHeadersIdx += 2
});
});

await collectStreamBody(response);
});

it('should be able to pipe a net response into a writable stream', async () => {
const bodyData = randomString(kOneKiloByte);
let nodeRequestProcessed = false;
Expand Down

0 comments on commit 4a2795c

Please sign in to comment.