Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -282,7 +282,13 @@ export default async function fetch(url, options_) {
// 3. no Content-Encoding header
// 4. no content response (204)
// 5. content not modified response (304)
if (!request.compress || request.method === 'HEAD' || codings === null || response_.statusCode === 204 || response_.statusCode === 304) {
if (response_.statusCode === 204 || response_.statusCode === 304) {
response = new Response(null, responseOptions);
resolve(response);
return;
}

if (!request.compress || request.method === 'HEAD' || codings === null) {
response = new Response(body, responseOptions);
resolve(response);
return;
Expand Down
40 changes: 34 additions & 6 deletions src/response.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import Body, {clone, extractContentType} from './body.js';
import {isRedirect} from './utils/is-redirect.js';

const INTERNALS = Symbol('Response internals');
const NULL_BODY_STATUSES = new Set([101, 204, 304]);

/**
* Response class
Expand All @@ -26,12 +27,31 @@ export default class Response extends Body {
// eslint-disable-next-line no-eq-null, eqeqeq, no-negated-condition
const status = options.status != null ? options.status : 200;

// Validate status per fetch spec
if (status < 200 || status > 599) {
throw new RangeError(`Failed to construct 'Response': The status provided (${status}) is outside the range [200, 599].`);
}

if (options.statusText !== undefined) {
// Validate statusText per fetch spec (reason-phrase token: HTAB, SP, VCHAR, obs-text)
// Disallow CR, LF, and NUL
if (/[\0\r\n]/.test(options.statusText)) {
throw new TypeError('Failed to construct \'Response\': The status text provided contains invalid characters.');
}
}

const headers = new Headers(options.headers);

if (body !== null && !headers.has('Content-Type')) {
const contentType = extractContentType(body, this);
if (contentType) {
headers.append('Content-Type', contentType);
if (body !== null && body !== undefined) {
if (NULL_BODY_STATUSES.has(status)) {
throw new TypeError('Failed to construct \'Response\': Response with null body status cannot have body.');
}

if (!headers.has('Content-Type')) {
const contentType = extractContentType(body, this);
if (contentType) {
headers.append('Content-Type', contentType);
}
}
}

Expand Down Expand Up @@ -119,8 +139,16 @@ export default class Response extends Body {
}

static error() {
const response = new Response(null, {status: 0, statusText: ''});
response[INTERNALS].type = 'error';
const response = Reflect.construct(Response, [], Response);
response[INTERNALS] = {
type: 'error',
url: '',
status: 0,
statusText: '',
headers: new Headers(),
counter: 0,
highWaterMark: 0
};
return response;
}

Expand Down
22 changes: 22 additions & 0 deletions test/response.js
Original file line number Diff line number Diff line change
Expand Up @@ -256,6 +256,28 @@ describe('Response', () => {
expect(res.statusText).to.equal('');
});

it('should throw RangeError when status is outside range 200-599', () => {
expect(() => new Response(null, {status: 199})).to.throw(RangeError);
expect(() => new Response(null, {status: 600})).to.throw(RangeError);
expect(() => new Response(null, {status: 0})).to.throw(RangeError);
});

it('should accept valid status codes', () => {
expect(() => new Response(null, {status: 200})).to.not.throw();
expect(() => new Response(null, {status: 599})).to.not.throw();
});

it('should throw TypeError when body is given with null body status', () => {
expect(() => new Response('body', {status: 204})).to.throw(TypeError);
expect(() => new Response('body', {status: 304})).to.throw(TypeError);
});

it('should allow null body with null body status', () => {
expect(() => new Response(null, {status: 204})).to.not.throw();
expect(() => new Response(null, {status: 304})).to.not.throw();
expect(() => new Response(undefined, {status: 204})).to.not.throw();
});

it('should warn once when using .data (response)', () => new Promise(resolve => {
process.once('warning', evt => {
expect(evt.message).to.equal('data doesn\'t exist, use json(), text(), arrayBuffer(), or body instead');
Expand Down