Skip to content
Merged
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
22 changes: 16 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,8 @@ Library supports all instructions that the Ledge parser supports. Also supports
- [contentTypes](#contenttypes)
- [disableThirdPartyIncludes](#disablethirdpartyincludes)
- [recursionLimit](#recursionlimit)
- [thirdPatyIncludesDomainWhitelist](#thirdpatyincludesdomainwhitelist)
- [surrogateControlHeader](#surrogatecontrolheader)
- [thirdPartyIncludesDomainWhitelist](#thirdpartyincludesdomainwhitelist)
- [varsCookieBlacklist](#varscookieblacklist)
- [Custom ESI Vars Function](#custom-esi-vars-function)
- [Custom Fetch Function](#custom-fetch-function)
Expand Down Expand Up @@ -73,7 +74,8 @@ export type ESIConfig = {
contentTypes?: string[];
disableThirdPartyIncludes?: boolean;
recursionLimit?: number;
thirdPatyIncludesDomainWhitelist?: string[];
surrogateControlHeader?: string;
thirdPartyIncludesDomainWhitelist?: string[];
varsCookieBlacklist?: string[];
};

Expand All @@ -83,7 +85,8 @@ const defaultConfig = {
contentTypes: ["text/html", "text/plain"],
disableThirdPartyIncludes: false,
recursionLimit: 10,
thirdPatyIncludesDomainWhitelist: [],
surrogateControlHeader: 'Surrogate-Control',
thirdPartyIncludesDomainWhitelist: [],
varsCookieBlacklist: []
}
```
Expand Down Expand Up @@ -117,7 +120,7 @@ Whether or not to enable third party includes (includes from other domains).

If set to false and an include points to another domain the include will be returned as a blank string

Also see thirdPatyIncludesDomainWhitelist for usage with this.
Also see thirdPartyIncludesDomainWhitelist for usage with this.



Expand All @@ -126,10 +129,17 @@ Also see thirdPatyIncludesDomainWhitelist for usage with this.
* *default*: `10`
* *type*: `number`

Levels of recusion the parser is allowed to go do. Think includes that include themselves causing recusion
Levels of recursion the parser is allowed to go do. Think includes that include themselves causing recusion

#### surrogateControlHeader

#### thirdPatyIncludesDomainWhitelist
* *default*: `Surrogate Control`
* *type*: `string`

Name of the header that the library will check for Surrogate-Control. We allow customisation as Cloudflare priorities Surrogate-Control over Cache-Control


#### thirdPartyIncludesDomainWhitelist

* *default*: `[]`
* *type*: `string[]`
Expand Down
31 changes: 21 additions & 10 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ import { getheaderToken } from "./headerUtils";
* @property {number} [recursionLimit] - Levels of recursion the parser is allowed to include before bailing out
* think includes that include themselves causing recursion
* @default 10
* @property {string} surrogateControlHeader - Name of the header that the library will check for Surrogate-Control
* We allow customisation as Cloudflare priorities Surrogate-Control over Cache-Control
* @property {string[]} thirdPartyIncludesDomainWhitelist - If third party includes are disabled you can white list them by including domains here
* @property {string[]} varsCookieBlacklist - Array of strings of cookies that will be blacklisted from being expanded in esi VARs.
*/
Expand All @@ -32,8 +34,9 @@ export type ESIConfig = {
contentTypes?: string[];
disableThirdPartyIncludes?: boolean;
recursionLimit?: number;
thirdPartyIncludesDomainWhitelist?: string[];
varsCookieBlacklist?: string[];
surrogateControlHeader?: string;
thirdPartyIncludesDomainWhitelist?: string[] | null;
varsCookieBlacklist?: string[] | null;
};

export type ESIVars = {
Expand All @@ -60,7 +63,7 @@ export type ESIEventData = {
/**
* {ESIConfig} for the current Request
*/
config: ESIConfig;
config: FullESIConfig;
/**
* All headers of the current Request in {Object}
* All headers are in uppercase and - is converted to _
Expand Down Expand Up @@ -107,12 +110,24 @@ export type fetchFunction = (
ctx: Request[],
) => Promise<Response>;
export type postBodyFunction = () => void | Promise<void>;
export type FullESIConfig = Required<ESIConfig>;

const processorToken = "ESI";
const processorVersion = 1.0;
const defaultSurrogateControlHeader = "Surrogate-Control";

const defaultConfig: FullESIConfig = {
allowSurrogateDelegation: false,
contentTypes: ["text/html", "text/plain"],
disableThirdPartyIncludes: false,
recursionLimit: 10,
surrogateControlHeader: defaultSurrogateControlHeader,
thirdPartyIncludesDomainWhitelist: null,
varsCookieBlacklist: null,
};

export class esi {
options: ESIConfig;
options: FullESIConfig;
esiFunction?: customESIVarsFunction;
fetcher: fetchFunction;
postBodyFunction?: postBodyFunction;
Expand All @@ -125,10 +140,6 @@ export class esi {
ctx: Request[] = [],
postBodyFunction?: postBodyFunction,
) {
const defaultConfig = {
recursionLimit: 10,
contentTypes: ["text/html", "text/plain"],
};
this.options = { ...defaultConfig, ...options };
this.fetcher = fetcher;
this.esiFunction = customESIFunction;
Expand Down Expand Up @@ -221,7 +232,7 @@ export class esi {
mutResponse.headers.delete("content-length");

// Remove surrogate-control
mutResponse.headers.delete("Surrogate-Control");
mutResponse.headers.delete(this.options.surrogateControlHeader);

// `streamBody` will free the request context when finished
this.streamBody(eventData, response.body, writable);
Expand Down Expand Up @@ -362,7 +373,7 @@ export class esi {
}

validSurrogateControl(response: Response): boolean {
const sControl = response.headers.get("Surrogate-Control");
const sControl = response.headers.get(this.options.surrogateControlHeader);
if (!sControl) {
return false;
}
Expand Down
4 changes: 2 additions & 2 deletions src/processIncludes.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { replace_vars } from "./processESIVars";
import esi, { customESIVarsFunction, ESIConfig, fetchFunction } from ".";
import esi, { customESIVarsFunction, FullESIConfig, fetchFunction } from ".";
import { ESIEventData } from ".";

const esi_include_pattern = /<esi:include\s*src="([^"]+)"\s*\/>/;
Expand Down Expand Up @@ -169,7 +169,7 @@ function isIncludeOnSameDomain(requestURL: URL, srcURL: URL): boolean {
* @param {string} host host to check against
* @returns {boolean} true if domain is whitelisted. false if not
*/
function thirdPartyWhitelisted(config: ESIConfig, host: string): boolean {
function thirdPartyWhitelisted(config: FullESIConfig, host: string): boolean {
if (config.disableThirdPartyIncludes) {
if (!config.thirdPartyIncludesDomainWhitelist) {
return false;
Expand Down
6 changes: 3 additions & 3 deletions src/surrogate.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import {
ESIConfig,
FullESIConfig,
getProcessorToken,
getProcessorVersion,
getProcessorVersionString,
Expand Down Expand Up @@ -35,10 +35,10 @@ export function advertiseSurrogateControl(request: Request): Request {
*/
export function canDelegateToSurrogate(
request: Request,
config: ESIConfig,
config: FullESIConfig,
): boolean {
const surrogates = config.allowSurrogateDelegation;
if (surrogates === undefined || surrogates === false) return false;
if (surrogates === false) return false;

const surrogateCapability = request.headers.get("Surrogate-Capability");
if (surrogateCapability) {
Expand Down
36 changes: 36 additions & 0 deletions test/esi.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1979,3 +1979,39 @@ test("TEST 52: Empty variables can be compared", async () => {
expect(checkSurrogate(res)).toBeTruthy();
expect(await res.text()).toEqual(`x empty;y empty;`);
});

test("TEST 53: When custom Surrogate-Control header is set ignore normal Surrogate-Control (dont remove default)", async () => {
const url = `/esi/test-53`;
config.surrogateControlHeader = "X-Custom-Surrogate-Control";
parser = new esi(config);
routeHandler.add(url, function (req, res) {
res.writeHead(200, esiHead);
res.end(`START <esi:include src="${url}/test" /> END`);
});
const res = await makeRequest(url);
expect(res.ok).toBeTruthy();
expect(checkSurrogate(res)).toBeFalsy();
expect(await res.text()).toEqual(
`START <esi:include src=\"/esi/test-53/test\" /> END`,
);
});

test("TEST 54: When custom Surrogate-Control header is set ESI Worker (remove default)", async () => {
const url = `/esi/test-54`;
config.surrogateControlHeader = "X-Custom-Surrogate-Control";
parser = new esi(config);
routeHandler.add(url, function (req, res) {
res.writeHead(200, {
"Content-Type": "text/html",
"X-Custom-Surrogate-Control": `content="ESI/1.0"`,
});
res.end(`START <esi:include src="${url}/test" /> END`);
});
routeHandler.add(`${url}/test`, function (req, res) {
res.end("Hello");
});
const res = await makeRequest(url);
expect(res.ok).toBeTruthy();
expect(checkSurrogate(res)).toBeTruthy();
expect(await res.text()).toEqual(`START Hello END`);
});