Skip to content

Commit 4caf7cf

Browse files
committed
Build: Fix bug where reroute response is served to reqs with valid keys
Ref https://github.com/jquery/infrastructure/issues/474.
1 parent dcc833f commit 4caf7cf

5 files changed

Lines changed: 217 additions & 109 deletions

File tree

.github/workflows/CI.yaml

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ on:
88
pull_request:
99

1010
jobs:
11-
docker-test:
11+
docker-test-dev:
1212
# Includes PHP 7.4, Python 3, Node 14
1313
# https://github.com/actions/virtual-environments/blob/ubuntu20/20210816.1/images/linux/Ubuntu2004-README.md
1414
runs-on: ubuntu-20.04
@@ -26,6 +26,22 @@ jobs:
2626
# once GitHub's images come with curl 7.71.0+, use --retry-all-errors
2727
# instead of hardcoded sleep. --krinkle 2021-08-21
2828
sleep 1
29-
curl -f --retry 5 --retry-delay 1 --retry-connrefused http://127.0.0.1:4000/jquery-3.0.0.js > /dev/null
30-
curl -I http://127.0.0.1:4000/jquery-3.0.0.js
31-
php test/cfg-test.php
29+
curl -f --retry 5 --retry-delay 1 --retry-connrefused -I http://127.0.0.1:4000/jquery-3.0.0.js
30+
php test/static-dev.php
31+
32+
docker-test-strict:
33+
runs-on: ubuntu-20.04
34+
env:
35+
CDN_ACCESS_KEY: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
36+
steps:
37+
- uses: actions/checkout@v2
38+
39+
- name: Build the image
40+
run: docker build -t codeorigin-strict:$GITHUB_SHA --build-arg "CDN_ACCESS_KEY=$CDN_ACCESS_KEY" ./
41+
42+
- name: Test the container
43+
run: |
44+
docker run --rm -p 4000:80/tcp --detach codeorigin-strict:$GITHUB_SHA
45+
sleep 1
46+
curl -f --retry 5 --retry-delay 1 --retry-connrefused -I http://127.0.0.1:4000/jquery-3.0.0.js
47+
php test/static-strict.php

cfg/default.conf

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,10 @@ server {
4747
# https://stackoverflow.com/a/6475493/319266
4848
gzip_types text/plain text/xml application/xml text/css text/javascript application/javascript application/x-javascript text/x-component application/json application/xhtml+xml application/rss+xml application/atom+xml application/vnd.ms-fontobject image/svg+xml application/x-font-ttf font/opentype;
4949

50-
add_header Cache-Control public;
50+
# Proxies must not mix up differently keyed responses
51+
add_header Vary "x-cdn-access";
52+
53+
add_header Cache-Control "public, no-transform";
5154
add_header Access-Control-Allow-Origin *;
5255
expires max;
5356
gzip on;
@@ -57,8 +60,10 @@ server {
5760
# Applies to charset_types (mainly for JS and CSS)
5861
charset utf-8;
5962

63+
# Let rerouting responses be cached for a shorter time for fast recovery
64+
#
6065
# Do not change the following line. The Dockerfile will remove the line if needed.
61-
if ($reroute_to_cdn) { return 301 https://code.jquery.com$uri; } #REQUIRE_XCDNACCESS
66+
if ($reroute_to_cdn) { expires 5m; return 301 https://code.jquery.com$uri; } #REQUIRE_XCDNACCESS
6267
}
6368

6469
# Legacy redirects for renamed files

test/Unit.php

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
<?php
2+
3+
function jq_req( $url, array $reqHeaders = [] ) {
4+
print "# $url\n";
5+
$ch = curl_init( $url );
6+
$resp = [ 'headers' => [], 'body' => null ];
7+
curl_setopt_array( $ch, [
8+
CURLOPT_HTTPHEADER => $reqHeaders,
9+
CURLOPT_RETURNTRANSFER => 1,
10+
CURLOPT_FOLLOWLOCATION => 0,
11+
CURLOPT_HEADERFUNCTION => function( $ch, $header ) use ( &$resp ) {
12+
$len = strlen( $header );
13+
if ( preg_match( "/^(HTTP\/(?:1\.[01]|2)) (\d{3} .*)/", $header, $m ) ) {
14+
$resp['headers'] = [];
15+
$resp['headers']['status'] = trim( $m[2] );
16+
} else {
17+
$parts = explode( ':', $header, 2 );
18+
if ( count( $parts ) === 2 ) {
19+
$name = strtolower( $parts[0] );
20+
$val = trim( $parts[1] );
21+
if ( isset( $resp['headers'][$name] ) ) {
22+
$resp['headers'][$name] .= ", $val";
23+
} else {
24+
$resp['headers'][$name] = $val;
25+
}
26+
}
27+
}
28+
return $len;
29+
},
30+
] );
31+
try {
32+
$ret = curl_exec( $ch );
33+
if ( $ret === false ) {
34+
throw new Exception( curl_error( $ch ) );
35+
}
36+
$resp['body'] = $ret;
37+
return $resp;
38+
} finally {
39+
curl_close( $ch );
40+
}
41+
}
42+
43+
class Unit {
44+
static $i = 0;
45+
static $pass = true;
46+
47+
public static function start() {
48+
error_reporting( E_ALL );
49+
print "TAP version 13\n";
50+
}
51+
52+
public static function test( $name, $actual, $expected ) {
53+
$num = ++self::$i;
54+
if ( $actual === $expected ) {
55+
print "ok $num $name\n";
56+
} else {
57+
self::$pass = false;
58+
print "not ok $num $name\n ---\n actual: "
59+
. json_encode( $actual, JSON_UNESCAPED_SLASHES ) . "\n expected: "
60+
. json_encode( $expected, JSON_UNESCAPED_SLASHES ) . "\n";
61+
}
62+
}
63+
64+
public static function testHttp( $server, $path, array $reqHeaders = [], array $expectHeaders, $expectBody = null ) {
65+
try {
66+
$resp = jq_req( "http://{$server}{$path}", $reqHeaders );
67+
foreach ( $expectHeaders as $key => $val ) {
68+
self::test( "GET $path > header $key", @$resp['headers'][$key], $val );
69+
}
70+
} catch ( Exception $e ) {
71+
self::test( "GET $path > error", $e->getMessage(), null );
72+
}
73+
}
74+
75+
public static function end() {
76+
print '1..' . self::$i . "\n";
77+
if ( !self::$pass ) {
78+
exit( 1 );
79+
}
80+
}
81+
}
Lines changed: 25 additions & 103 deletions
Original file line numberDiff line numberDiff line change
@@ -2,56 +2,57 @@
22
/**
33
* Usage:
44
*
5-
* $ php test/cfp-php.php
6-
* $ php test/cfp-php.php "localhost:4000"
7-
* $ TEST_SERVER="localhost:4000" php test/cfp-php.php
5+
* $ php test/static-dev.php
6+
*
7+
* $ php test/static-dev.php "localhost:4000"
88
*/
99

10-
$server = getenv( 'TEST_SERVER' ) ?: @$argv[1] ?: 'localhost:4000';
10+
require_once __DIR__ . '/Unit.php';
11+
$server = @$argv[1] ?: 'localhost:4000';
1112

1213
Unit::start();
1314

1415
// Domain root
1516

16-
Unit::testHttp( $server, '/', [
17+
Unit::testHttp( $server, '/', [], [
1718
'status' => '301 Moved Permanently',
1819
'server' => 'nginx',
1920
'location' => 'https://releases.jquery.com/',
2021
] );
2122

2223
// Static assets
2324

24-
Unit::testHttp( $server, '/jquery-3.0.0.js', [
25+
Unit::testHttp( $server, '/jquery-3.0.0.js', [], [
2526
'status' => '200 OK',
2627
'server' => 'nginx',
2728
'content-type' => 'application/javascript; charset=utf-8',
2829
'content-length' => '263268',
2930
'last-modified' => 'Fri, 18 Oct 1991 12:00:00 GMT',
3031
'connection' => 'keep-alive',
31-
'vary' => 'Accept-Encoding',
32+
'vary' => 'Accept-Encoding, x-cdn-access',
3233
'etag' => '"28feccc0-40464"',
3334
'expires' => 'Thu, 31 Dec 2037 23:55:55 GMT',
34-
'cache-control' => 'max-age=315360000, public',
35+
'cache-control' => 'max-age=315360000, public, no-transform',
3536
'access-control-allow-origin' => '*',
3637
'accept-ranges' => 'bytes',
3738
] );
3839

39-
Unit::testHttp( $server, '/qunit/qunit-2.0.0.css', [
40+
Unit::testHttp( $server, '/qunit/qunit-2.0.0.css', [], [
4041
'status' => '200 OK',
4142
'server' => 'nginx',
4243
'content-type' => 'text/css',
4344
'content-length' => '7456',
4445
'last-modified' => 'Fri, 18 Oct 1991 12:00:00 GMT',
4546
'connection' => 'keep-alive',
46-
'vary' => 'Accept-Encoding',
47+
'vary' => 'Accept-Encoding, x-cdn-access',
4748
'etag' => '"28feccc0-1d20"',
4849
'expires' => 'Thu, 31 Dec 2037 23:55:55 GMT',
49-
'cache-control' => 'max-age=315360000, public',
50+
'cache-control' => 'max-age=315360000, public, no-transform',
5051
'access-control-allow-origin' => '*',
5152
'accept-ranges' => 'bytes',
5253
] );
5354

54-
Unit::testHttp( $server, '/ui/1.10.0/themes/base/images/ui-icons_222222_256x240.png', [
55+
Unit::testHttp( $server, '/ui/1.10.0/themes/base/images/ui-icons_222222_256x240.png', [], [
5556
'status' => '200 OK',
5657
'server' => 'nginx',
5758
'content-type' => 'image/png',
@@ -60,12 +61,12 @@
6061
'connection' => 'keep-alive',
6162
'etag' => '"28feccc0-1111"',
6263
'expires' => 'Thu, 31 Dec 2037 23:55:55 GMT',
63-
'cache-control' => 'max-age=315360000, public',
64+
'cache-control' => 'max-age=315360000, public, no-transform',
6465
'access-control-allow-origin' => '*',
6566
'accept-ranges' => 'bytes',
6667
] );
6768

68-
Unit::testHttp( $server, '/jquery-2.0.0.min.map', [
69+
Unit::testHttp( $server, '/jquery-2.0.0.min.map', [], [
6970
'status' => '200 OK',
7071
'server' => 'nginx',
7172
'content-type' => 'application/octet-stream',
@@ -74,156 +75,77 @@
7475
'connection' => 'keep-alive',
7576
'etag' => '"28feccc0-1ec81"',
7677
'expires' => 'Thu, 31 Dec 2037 23:55:55 GMT',
77-
'cache-control' => 'max-age=315360000, public',
78+
'cache-control' => 'max-age=315360000, public, no-transform',
7879
'access-control-allow-origin' => '*',
7980
'accept-ranges' => 'bytes',
8081
] );
8182

8283
// Renamed files
8384

84-
Unit::testHttp( $server, '/jquery-git2.js', [
85+
Unit::testHttp( $server, '/jquery-git2.js', [], [
8586
'status' => '301 Moved Permanently',
8687
'server' => 'nginx',
8788
'location' => 'https://code.jquery.com/jquery-git.js',
8889
] );
8990

9091
// Moved to releases, WordPress page
9192

92-
Unit::testHttp( $server, '/jquery/', [
93+
Unit::testHttp( $server, '/jquery/', [], [
9394
'status' => '301 Moved Permanently',
9495
'server' => 'nginx',
9596
'location' => 'https://releases.jquery.com/jquery/',
9697
] );
9798

9899
// Moved to releases, WordPress page without trailing slash
99100

100-
Unit::testHttp( $server, '/jquery', [
101+
Unit::testHttp( $server, '/jquery', [], [
101102
'status' => '301 Moved Permanently',
102103
'server' => 'nginx',
103104
'location' => 'https://releases.jquery.com/jquery/',
104105
] );
105106

106107
// Moved to releases, root -git file
107108

108-
Unit::testHttp( $server, '/jquery-git.js', [
109+
Unit::testHttp( $server, '/jquery-git.js', [], [
109110
'status' => '301 Moved Permanently',
110111
'server' => 'nginx',
111112
'location' => 'https://releases.jquery.com/git/jquery-git.js',
112113
] );
113114

114115
// Moved to releases, nested -git file
115116

116-
Unit::testHttp( $server, '/color/jquery.color-git.min.js', [
117+
Unit::testHttp( $server, '/color/jquery.color-git.min.js', [], [
117118
'status' => '301 Moved Permanently',
118119
'server' => 'nginx',
119120
'location' => 'https://releases.jquery.com/git/color/jquery.color-git.min.js',
120121
] );
121122

122-
Unit::testHttp( $server, '/qunit/qunit-git.css', [
123+
Unit::testHttp( $server, '/qunit/qunit-git.css', [], [
123124
'status' => '301 Moved Permanently',
124125
'server' => 'nginx',
125126
'location' => 'https://releases.jquery.com/git/qunit/qunit-git.css',
126127
] );
127128

128-
Unit::testHttp( $server, '/mobile/git/jquery.mobile-git.min.map', [
129+
Unit::testHttp( $server, '/mobile/git/jquery.mobile-git.min.map', [], [
129130
'status' => '301 Moved Permanently',
130131
'server' => 'nginx',
131132
'location' => 'https://releases.jquery.com/git/mobile/git/jquery.mobile-git.min.map',
132133
] );
133134

134135
// Moved to releases, any file under /mobile/git
135136

136-
Unit::testHttp( $server, '/mobile/git/images/icons-png/power-black.png', [
137+
Unit::testHttp( $server, '/mobile/git/images/icons-png/power-black.png', [], [
137138
'status' => '301 Moved Permanently',
138139
'server' => 'nginx',
139140
'location' => 'https://releases.jquery.com/git/mobile/git/images/icons-png/power-black.png',
140141
] );
141142

142143
// Moved to releases, any file under /git (new-style URL)
143144

144-
Unit::testHttp( $server, '/git/qunit/qunit-git.css', [
145+
Unit::testHttp( $server, '/git/qunit/qunit-git.css', [], [
145146
'status' => '301 Moved Permanently',
146147
'server' => 'nginx',
147148
'location' => 'https://releases.jquery.com/git/qunit/qunit-git.css',
148149
] );
149150

150151
Unit::end();
151-
152-
function jq_req( $url ) {
153-
print "# $url\n";
154-
$ch = curl_init( $url );
155-
$resp = [ 'headers' => [], 'body' => null ];
156-
curl_setopt_array( $ch, [
157-
CURLOPT_RETURNTRANSFER => 1,
158-
CURLOPT_FOLLOWLOCATION => 0,
159-
CURLOPT_HEADERFUNCTION => function( $ch, $header ) use ( &$resp ) {
160-
$len = strlen( $header );
161-
if ( preg_match( "/^(HTTP\/(?:1\.[01]|2)) (\d{3} .*)/", $header, $m ) ) {
162-
$resp['headers'] = [];
163-
$resp['headers']['status'] = trim( $m[2] );
164-
} else {
165-
$parts = explode( ':', $header, 2 );
166-
if ( count( $parts ) === 2 ) {
167-
$name = strtolower( $parts[0] );
168-
$val = trim( $parts[1] );
169-
if ( isset( $resp['headers'][$name] ) ) {
170-
$resp['headers'][$name] .= ", $val";
171-
} else {
172-
$resp['headers'][$name] = $val;
173-
}
174-
}
175-
}
176-
return $len;
177-
},
178-
] );
179-
try {
180-
$ret = curl_exec( $ch );
181-
if ( $ret === false ) {
182-
throw new Exception( curl_error( $ch ) );
183-
}
184-
$resp['body'] = $ret;
185-
return $resp;
186-
} finally {
187-
curl_close( $ch );
188-
}
189-
}
190-
191-
class Unit {
192-
static $i = 0;
193-
static $pass = true;
194-
195-
public static function start() {
196-
error_reporting( E_ALL );
197-
print "TAP version 13\n";
198-
}
199-
200-
public static function test( $name, $actual, $expected ) {
201-
$num = ++self::$i;
202-
if ( $actual === $expected ) {
203-
print "ok $num $name\n";
204-
} else {
205-
self::$pass = false;
206-
print "not ok $num $name\n ---\n actual: "
207-
. json_encode( $actual, JSON_UNESCAPED_SLASHES ) . "\n expected: "
208-
. json_encode( $expected, JSON_UNESCAPED_SLASHES ) . "\n";
209-
}
210-
}
211-
212-
public static function testHttp( $server, $path, array $expectHeaders, $expectBody = null ) {
213-
try {
214-
$resp = jq_req( "http://{$server}{$path}" );
215-
foreach ( $expectHeaders as $key => $val ) {
216-
self::test( "GET $path > header $key", @$resp['headers'][$key], $val );
217-
}
218-
} catch ( Exception $e ) {
219-
self::test( "GET $path > error", $e->getMessage(), null );
220-
}
221-
}
222-
223-
public static function end() {
224-
print '1..' . self::$i . "\n";
225-
if ( !self::$pass ) {
226-
exit( 1 );
227-
}
228-
}
229-
}

0 commit comments

Comments
 (0)