@cloudflare/pages-shared 0.8.0 → 0.8.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -7,10 +7,11 @@ const escapeRegex = (str: string) => {
7
7
  return str.replace(ESCAPE_REGEX_CHARACTERS, "\\$&");
8
8
  };
9
9
 
10
- // Placeholder names must begin with a colon, be alphanumeric and optionally contain underscores.
10
+ // Placeholder names must begin with a colon then a letter, be alphanumeric and optionally contain underscores.
11
11
  // e.g. :place_123_holder
12
- const HOST_PLACEHOLDER_REGEX = /(?<=^https:\\\/\\\/[^/]*?):([^\\]+)(?=\\)/g;
13
- const PLACEHOLDER_REGEX = /:(\w+)/g;
12
+ const HOST_PLACEHOLDER_REGEX =
13
+ /(?<=^https:\\\/\\\/[^/]*?):([A-Za-z]\w*)(?=\\)/g;
14
+ const PLACEHOLDER_REGEX = /:([A-Za-z]\w*)/g;
14
15
 
15
16
  export type Replacements = Record<string, string>;
16
17
 
@@ -67,11 +68,22 @@ export const generateRulesMatcher = <T>(
67
68
  ][];
68
69
 
69
70
  return ({ request }: { request: Request }) => {
70
- const { pathname, host } = new URL(request.url);
71
+ const { pathname, hostname } = new URL(request.url);
71
72
 
72
73
  return compiledRules
73
74
  .map(([{ crossHost, regExp }, match]) => {
74
- const test = crossHost ? `https://${host}${pathname}` : pathname;
75
+ // This, rather confusingly, means that although we enforce `https://` protocols in
76
+ // the rules of `_headers`/`_redirects`, we don't actually respect that at all at runtime.
77
+ // When processing a request against an absolute URL rule, we rewrite the protocol to `https://`.
78
+ // This has the benefit of ensuring attackers can't specify a different protocol
79
+ // to circumvent a developer's security rules (e.g. CORS), but it isn't obvious behavior.
80
+ // We should consider different syntax in the future for developers when they specify rules.
81
+ // For example, `*://example.com/path`, `://example.com/path` or `//example.com/`.
82
+ // Though we'd need to be careful with that last one
83
+ // as that would currently be read as a relative URL.
84
+ // Perhaps, if we ever move the `_headers`/`_redirects` files to acting ahead of Functions,
85
+ // this might be a good time for this change.
86
+ const test = crossHost ? `https://${hostname}${pathname}` : pathname;
75
87
  const result = regExp.exec(test);
76
88
  if (result) {
77
89
  return replacerFn(match, result.groups || {});
@@ -12,4 +12,4 @@ export const MAX_STATIC_REDIRECT_RULES = 2000;
12
12
  export const UNSET_OPERATOR = "! ";
13
13
 
14
14
  export const SPLAT_REGEX = /\*/g;
15
- export const PLACEHOLDER_REGEX = /:\w+/g;
15
+ export const PLACEHOLDER_REGEX = /:[A-Za-z]\w*/g;
@@ -58,7 +58,7 @@ export function parseHeaders(input: string): ParsedHeaders {
58
58
  }
59
59
  }
60
60
 
61
- const [path, pathError] = validateUrl(line);
61
+ const [path, pathError] = validateUrl(line, false, true);
62
62
  if (pathError) {
63
63
  invalid.push({
64
64
  line,
@@ -50,7 +50,7 @@ export function parseRedirects(input: string): ParsedRedirects {
50
50
 
51
51
  const [str_from, str_to, str_status = "302"] = tokens as RedirectLine;
52
52
 
53
- const fromResult = validateUrl(str_from, true, false, false);
53
+ const fromResult = validateUrl(str_from, true, true, false, false);
54
54
  if (fromResult[0] === undefined) {
55
55
  invalid.push({
56
56
  line,
@@ -88,7 +88,7 @@ export function parseRedirects(input: string): ParsedRedirects {
88
88
  }
89
89
  }
90
90
 
91
- const toResult = validateUrl(str_to, false, true, true);
91
+ const toResult = validateUrl(str_to, false, false, true, true);
92
92
  if (toResult[0] === undefined) {
93
93
  invalid.push({
94
94
  line,
@@ -11,11 +11,13 @@ export const extractPathname = (
11
11
  };
12
12
 
13
13
  const URL_REGEX = /^https:\/\/+(?<host>[^/]+)\/?(?<path>.*)/;
14
+ const HOST_WITH_PORT_REGEX = /.*:\d+$/;
14
15
  const PATH_REGEX = /^\//;
15
16
 
16
17
  export const validateUrl = (
17
18
  token: string,
18
19
  onlyRelative = false,
20
+ disallowPorts = false,
19
21
  includeSearch = false,
20
22
  includeHash = false
21
23
  ): [undefined, string] | [string, undefined] => {
@@ -27,6 +29,13 @@ export const validateUrl = (
27
29
  `Only relative URLs are allowed. Skipping absolute URL ${token}.`,
28
30
  ];
29
31
 
32
+ if (disallowPorts && host.groups.host.match(HOST_WITH_PORT_REGEX)) {
33
+ return [
34
+ undefined,
35
+ `Specifying ports is not supported. Skipping absolute URL ${token}.`,
36
+ ];
37
+ }
38
+
30
39
  return [
31
40
  `https://${host.groups.host}${extractPathname(
32
41
  host.groups.path,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cloudflare/pages-shared",
3
- "version": "0.8.0",
3
+ "version": "0.8.2",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "https://github.com/cloudflare/workers-sdk.git",
@@ -18,7 +18,7 @@
18
18
  "test:ci": "vitest run"
19
19
  },
20
20
  "dependencies": {
21
- "miniflare": "3.20230724.0"
21
+ "miniflare": "3.20230814.1"
22
22
  },
23
23
  "devDependencies": {
24
24
  "@cloudflare/workers-tsconfig": "*",