Skip to content

Commit 5a6ce5d

Browse files
committed
Allow regex and url component matching in whitelist 👍
1 parent e4c0206 commit 5a6ce5d

File tree

3 files changed

+89
-27
lines changed

3 files changed

+89
-27
lines changed

README.md

Lines changed: 37 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -15,12 +15,13 @@ Installation
1515
---
1616

1717
Since `proxy.php` is completely self-contained, you can just
18-
1. Copy `proxy.php` into your web application
19-
2. Edit the $whitelist array
20-
3. And that's pretty much it
18+
19+
1. Copy `proxy.php` into your web application,
20+
2. Edit the $whitelist array,
21+
3. And that's pretty much it...
2122

2223
If using [Composer](http://getcomposer.org), you can also add
23-
the following dependency to your `composer.json`:
24+
the [geekality/php-cross-domain-proxy](https://packagist.org/packages/geekality/php-cross-domain-proxy) to your `composer.json` like this:
2425

2526
``` JSON
2627
"require":
@@ -29,26 +30,53 @@ the following dependency to your `composer.json`:
2930
},
3031
```
3132

32-
And then add a `proxy.php` like this to your web application:
33+
And then for example add a `proxy.php` like this to your web application:
3334

3435
``` PHP
3536
<?php
3637
require 'vendor/autoload.php';
3738

3839
CrossOriginProxy::proxy([
39-
'www.example.com',
40-
'api.example.com',
40+
['host' => 'example.com'],
4141
]);
4242

4343
```
4444

4545
Security
4646
---
4747

48-
As a simple sanity check the proxy will that the `Referer` header is set and that the hostname is the same as for the proxy. This can be easily spoofed of course, so you should also add entries to the whitelist array.
48+
The whitelist array can contain any number of these criterias:
49+
50+
- Exact paths
51+
`['http://example.com/api/specific-method']`
52+
- Array with single regex key
53+
`['regex' => '%^http://example.com/api/%']`
54+
- Array with any [parse_url](http://php.net/manual/en/function.parse-url.php) components to match
55+
`['host' => 'example.com']`
56+
`['host' => 'example.com', 'scheme' => 'https']`
57+
58+
The requested URL must match at least one of the whitelisted criterias to be accepted, otherwise a 403 will be returned. An empty whitelist will accept any requests.
59+
60+
**An example using all types**
61+
62+
``` PHP
63+
<?php
64+
65+
require 'vendor/autoload.php';
66+
67+
CrossOriginProxy::proxy([
68+
// Exact matching
69+
['http://www.yr.no/place/Sweden/Stockholm/Stockholm/forecast.xml'],
4970

50-
If the whitelist array is empty, any requests will be accepted.
71+
// URL component matching
72+
['host' => 'localhost'],
73+
['host' => 'example.com', 'scheme' => 'http'],
5174

75+
// Regex matching
76+
['regex' => '%^http://www.yr.no/place/Norway/%'],
77+
]);
78+
79+
```
5280

5381
Usage
5482
---

proxy.php

Lines changed: 38 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,11 @@
44
if( ! isset($whitelist))
55
$whitelist = [];
66

7-
if( ! isset($maxredirs))
8-
$maxredirs = 10;
7+
if( ! isset($curl_maxredirs))
8+
$curl_maxredirs = 10;
99

10-
if( ! isset($timeout))
11-
$timeout = 60;
10+
if( ! isset($curl_timeout))
11+
$curl_timeout = 30;
1212

1313

1414

@@ -22,19 +22,19 @@
2222

2323
// Check that we have a URL
2424
if( ! $url)
25-
http_response_code(400) and exit('X-Proxy-URL header missing');
25+
http_response_code(400) and exit("X-Proxy-URL header missing");
2626

2727
// Check that the URL looks like an absolute URL
2828
if( ! parse_url($url, PHP_URL_SCHEME))
29-
http_response_code(403) and exit('X-Proxy-URL must be an absolute URL');
29+
http_response_code(403) and exit("Not an absolute URL: $url");
3030

31-
// Check that target hostname is in whitelist
32-
if( ! empty($whitelist) && ! in_array(parse_url($url, PHP_URL_HOST), $whitelist))
33-
http_response_code(403) and exit('Hostname not in whitelist');
34-
35-
// Check that current and referer hostnames are equal
31+
// Check referer hostname
3632
if( ! parse_url(__('Referer', $headers), PHP_URL_HOST) == $_SERVER['HTTP_HOST'])
37-
http_response_code(403) and exit('Referer mismatch');
33+
http_response_code(403) and exit("Referer mismatch");
34+
35+
// Check whitelist, if not empty
36+
if( ! empty($whitelist) and ! array_reduce($whitelist, 'whitelist', [$url, false]))
37+
http_response_code(403) and exit("Not whitelisted: $url");
3838

3939

4040
// Remove ignored headers and prepare the rest for resending
@@ -53,9 +53,9 @@
5353
CURLOPT_URL => $url,
5454
CURLOPT_HTTPHEADER => $headers,
5555
CURLOPT_HEADER => TRUE,
56-
CURLOPT_TIMEOUT => $timeout,
56+
CURLOPT_TIMEOUT => $curl_timeout,
5757
CURLOPT_FOLLOWLOCATION => TRUE,
58-
CURLOPT_MAXREDIRS => $maxredirs,
58+
CURLOPT_MAXREDIRS => $curl_maxredirs,
5959
]);
6060

6161
// Method specific options
@@ -104,14 +104,36 @@
104104
array_map('header', explode("\r\n", $header));
105105

106106
// And finally the body
107-
exit(substr($out, $info['header_size']));
107+
echo substr($out, $info['header_size']);
108108

109109

110110

111111

112112

113-
// Clean, safe array get
113+
// Helper functions
114114
function __($key, array $array, $default = null)
115115
{
116116
return array_key_exists($key, $array) ? $array[$key] : $default;
117117
}
118+
119+
function whitelist($carry, $item)
120+
{
121+
static $url;
122+
if(is_array($carry))
123+
{
124+
$url = parse_url($carry[0]);
125+
$url['raw'] = $carry[0];
126+
$carry = $carry[1];
127+
}
128+
129+
// Equals the full URL
130+
if(isset($item[0]))
131+
return $carry or $url['raw'] == $item[0];
132+
133+
// Regex matches the full URL
134+
if(isset($item['regex']))
135+
return $carry or preg_match($item['regex'], $url['raw']);
136+
137+
// Select components matches same components in the URL
138+
return $carry or $item == array_intersect_key($url, $item);
139+
}

src/CrossOriginProxy.php

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,21 @@
11
<?php
22

3+
/**
4+
* Cross origin request proxy for client-side scripts.
5+
*
6+
* @see https://github.com/Svish/php-cross-domain-proxy
7+
*/
38
class CrossOriginProxy
49
{
5-
public static function proxy($whitelist = [], $timeout = 30, $maxredirs = 10)
10+
/**
11+
* Proxies the incoming request and outputs the response, including headers.
12+
*
13+
* @param whitelist Array of acceptable request URLs.
14+
* @param curl_timeout Timeout for the request.
15+
* @param curl_maxredirs Maximum number of allowed redirects.
16+
*/
17+
public static function proxy($whitelist = [], $curl_timeout = 30, $curl_maxredirs = 10)
618
{
719
require dirname(__FILE__).DIRECTORY_SEPARATOR.'..'.DIRECTORY_SEPARATOR.'proxy.php';
820
}
9-
}
21+
}

0 commit comments

Comments
 (0)