Skip to content

Commit

Permalink
Support referrer and referrerPolicy
Browse files Browse the repository at this point in the history
  • Loading branch information
tekwiz committed Jan 3, 2021
1 parent 4abbfd2 commit 6578a76
Show file tree
Hide file tree
Showing 6 changed files with 978 additions and 5 deletions.
2 changes: 0 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -550,8 +550,6 @@ Due to the nature of Node.js, the following properties are not implemented at th

- `type`
- `destination`
- `referrer`
- `referrerPolicy`
- `mode`
- `credentials`
- `cache`
Expand Down
11 changes: 10 additions & 1 deletion src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import Request, {getNodeRequestOptions} from './request.js';
import {FetchError} from './errors/fetch-error.js';
import {AbortError} from './errors/abort-error.js';
import {isRedirect} from './utils/is-redirect.js';
import {parseReferrerPolicyFromHeader} from './utils/referrer.js';

export {Headers, Request, Response, FetchError, AbortError, isRedirect};

Expand Down Expand Up @@ -144,7 +145,9 @@ export default async function fetch(url, options_) {
method: request.method,
body: request.body,
signal: request.signal,
size: request.size
size: request.size,
referrer: request.referrer,
referrerPolicy: request.referrerPolicy
};

// HTTP-redirect fetch step 9
Expand All @@ -161,6 +164,12 @@ export default async function fetch(url, options_) {
requestOptions.headers.delete('content-length');
}

// HTTP-redirect fetch step 14
const responseReferrerPolicy = parseReferrerPolicyFromHeader(headers);
if (responseReferrerPolicy) {
requestOptions.referrerPolicy = responseReferrerPolicy;
}

// HTTP-redirect fetch step 15
resolve(fetch(new Request(locationURL, requestOptions)));
finalize();
Expand Down
82 changes: 80 additions & 2 deletions src/request.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@ import Headers from './headers.js';
import Body, {clone, extractContentType, getTotalBytes} from './body.js';
import {isAbortSignal} from './utils/is.js';
import {getSearch} from './utils/get-search.js';
import {
validateReferrerPolicy, determineRequestsReferrer, DEFAULT_REFERRER_POLICY
} from './utils/referrer.js';

const INTERNALS = Symbol('Request internals');

Expand Down Expand Up @@ -86,12 +89,33 @@ export default class Request extends Body {
throw new TypeError('Expected signal to be an instanceof AbortSignal or EventTarget');
}

// §5.4, Request constructor steps, step 15.1
// eslint-disable-next-line no-eq-null, eqeqeq
let referrer = init.referrer == null ? input.referrer : init.referrer;
if (referrer === '') {
// §5.4, Request constructor steps, step 15.2
referrer = 'no-referrer';
} else if (referrer) {
// §5.4, Request constructor steps, step 15.3.1, 15.3.2
const parsedReferrer = new URL(referrer);
// §5.4, Request constructor steps, step 15.3.3
if (/^about:(\/\/)?client$/.test(parsedReferrer)) {
referrer = 'client';
} else {
// §5.4, Request constructor steps, step 15.3.4
referrer = parsedReferrer;
}
} else {
referrer = undefined;
}

this[INTERNALS] = {
method,
redirect: init.redirect || input.redirect || 'follow',
headers,
parsedURL,
signal
signal,
referrer
};

// Node-fetch-only options
Expand All @@ -101,6 +125,10 @@ export default class Request extends Body {
this.agent = init.agent || input.agent;
this.highWaterMark = init.highWaterMark || input.highWaterMark || 16384;
this.insecureHTTPParser = init.insecureHTTPParser || input.insecureHTTPParser || false;

// §5.4, Request constructor steps, step 16.
// Default is empty string per https://fetch.spec.whatwg.org/#concept-request-referrer-policy
this.referrerPolicy = init.referrerPolicy || input.referrerPolicy || '';
}

get method() {
Expand All @@ -123,6 +151,31 @@ export default class Request extends Body {
return this[INTERNALS].signal;
}

// https://fetch.spec.whatwg.org/#dom-request-referrer
get referrer() {
if (this[INTERNALS].referrer === 'no-referrer') {
return '';
}

if (this[INTERNALS].referrer === 'client') {
return 'about:client';
}

if (this[INTERNALS].referrer) {
return this[INTERNALS].referrer.toString();
}

return undefined;
}

get referrerPolicy() {
return this[INTERNALS].referrerPolicy;
}

set referrerPolicy(referrerPolicy) {
this[INTERNALS].referrerPolicy = validateReferrerPolicy(referrerPolicy);
}

/**
* Clone this request
*
Expand All @@ -143,7 +196,9 @@ Object.defineProperties(Request.prototype, {
headers: {enumerable: true},
redirect: {enumerable: true},
clone: {enumerable: true},
signal: {enumerable: true}
signal: {enumerable: true},
referrer: {enumerable: true},
referrerPolicy: {enumerable: true}
});

/**
Expand Down Expand Up @@ -179,6 +234,29 @@ export const getNodeRequestOptions = request => {
headers.set('Content-Length', contentLengthValue);
}

// 4.1. Main fetch, step 2.6
// > If request's referrer policy is the empty string, then set request's referrer policy to the
// > default referrer policy.
if (request.referrerPolicy === '') {
request.referrerPolicy = DEFAULT_REFERRER_POLICY;
}

// 4.1. Main fetch, step 2.7
// > If request's referrer is not "no-referrer", set request's referrer to the result of invoking
// > determine request's referrer.
if (request.referrer && request.referrer !== 'no-referrer') {
request[INTERNALS].referrer = determineRequestsReferrer(request);
} else {
request[INTERNALS].referrer = 'no-referrer';
}

// 4.5. HTTP-network-or-cache fetch, step 6.9
// > If httpRequest's referrer is a URL, then append `Referer`/httpRequest's referrer, serialized
// > and isomorphic encoded, to httpRequest's header list.
if (request[INTERNALS].referrer instanceof URL) {
headers.set('Referer', request.referrer);
}

// HTTP-network-or-cache fetch step 2.11
if (!headers.has('User-Agent')) {
headers.set('User-Agent', 'node-fetch');
Expand Down

0 comments on commit 6578a76

Please sign in to comment.