OSDN Git Service

BugTrack/2442 showrss plugin - Support HTTPS RSS feeds
[pukiwiki/pukiwiki.git] / lib / proxy.php
1 <?php
2 // PukiWiki - Yet another WikiWikiWeb clone
3 // proxy.php
4 // Copyright: 2003-2017 PukiWiki Development Team
5 // License: GPL v2 or (at your option) any later version
6 //
7 // HTTP-Proxy related functions
8
9 // Max number of 'track' redirection message with 301 or 302 response
10 define('PKWK_HTTP_REQUEST_URL_REDIRECT_MAX', 2);
11
12 // We also define deprecated function 'http_request' for backward compatibility
13 if (!function_exists('http_request')) {
14         // pecl_http extension also have the function named 'http_request'
15         function http_request($url, $method = 'GET', $headers = '',
16                 $post = array(), $redirect_max = PKWK_HTTP_REQUEST_URL_REDIRECT_MAX,
17                 $content_charset = '') {
18                 return pkwk_http_request($url, $method, $headers, $post,
19                         $redirect_max, $content_charset);
20         }
21 }
22
23 /*
24  * pkwk_http_request($url)
25  *     Get / Send data via HTTP request
26  * $url     : URI started with http:// (http://user:pass@host:port/path?query)
27  * $method  : GET, POST, or HEAD
28  * $headers : Additional HTTP headers, ended with "\r\n"
29  * $post    : An array of data to send via POST method ('key'=>'value')
30  * $redirect_max : Max number of HTTP redirect
31  * $content_charset : Content charset. Use '' or CONTENT_CHARSET
32 */
33 function pkwk_http_request($url, $method = 'GET', $headers = '', $post = array(),
34         $redirect_max = PKWK_HTTP_REQUEST_URL_REDIRECT_MAX, $content_charset = '')
35 {
36         global $use_proxy, $no_proxy, $proxy_host, $proxy_port;
37         global $need_proxy_auth, $proxy_auth_user, $proxy_auth_pass;
38         $rc  = array();
39         $arr = parse_url($url);
40
41         $via_proxy = $use_proxy ? ! in_the_net($no_proxy, $arr['host']) : FALSE;
42
43         // query
44         $arr['query'] = isset($arr['query']) ? '?' . $arr['query'] : '';
45         // port
46         if (!isset($arr['port'])) {
47                 if ($arr['scheme'] === 'https') {
48                         $arr['port'] = 443;
49                 } else {
50                         $arr['port'] = 80;
51                 }
52         }
53
54         $url_base = $arr['scheme'] . '://' . $arr['host'] . ':' . $arr['port'];
55         $url_path = isset($arr['path']) ? $arr['path'] : '/';
56         $url = ($via_proxy ? $url_base : '') . $url_path . $arr['query'];
57
58         $query = $method . ' ' . $url . ' HTTP/1.0' . "\r\n";
59         $query .= 'Host: ' . $arr['host'] . "\r\n";
60         $query .= 'User-Agent: PukiWiki/' . S_VERSION . "\r\n";
61
62         // Basic-auth for HTTP proxy server
63         if ($need_proxy_auth && isset($proxy_auth_user) && isset($proxy_auth_pass))
64                 $query .= 'Proxy-Authorization: Basic '.
65                         base64_encode($proxy_auth_user . ':' . $proxy_auth_pass) . "\r\n";
66
67         // (Normal) Basic-auth for remote host
68         if (isset($arr['user']) && isset($arr['pass']))
69                 $query .= 'Authorization: Basic '.
70                         base64_encode($arr['user'] . ':' . $arr['pass']) . "\r\n";
71
72         $query .= $headers;
73
74         if (strtoupper($method) == 'POST') {
75                 // 'application/x-www-form-urlencoded', especially for TrackBack ping
76                 $POST = array();
77                 foreach ($post as $name=>$val) $POST[] = $name . '=' . urlencode($val);
78                 $data = join('&', $POST);
79
80                 if (preg_match('/^[a-zA-Z0-9_-]+$/', $content_charset)) {
81                         // Legacy but simple
82                         $query .= 'Content-Type: application/x-www-form-urlencoded' . "\r\n";
83                 } else {
84                         // With charset (NOTE: Some implementation may hate this)
85                         $query .= 'Content-Type: application/x-www-form-urlencoded' .
86                                 '; charset=' . strtolower($content_charset) . "\r\n";
87                 }
88
89                 $query .= 'Content-Length: ' . strlen($data) . "\r\n";
90                 $query .= "\r\n";
91                 $query .= $data;
92         } else {
93                 $query .= "\r\n";
94         }
95
96         $errno  = 0;
97         $errstr = '';
98         $ssl_prefix = '';
99         if ($arr['scheme'] === 'https') {
100                 $ssl_prefix = 'ssl://';
101         }
102         $fp = fsockopen(
103                 $ssl_prefix . ($via_proxy ? $proxy_host : $arr['host']),
104                 $via_proxy ? $proxy_port : $arr['port'],
105                 $errno, $errstr, 30);
106         if ($fp === FALSE) {
107                 return array(
108                         'query'  => $query, // Query string
109                         'rc'     => $errno, // Error number
110                         'header' => '',     // Header
111                         'data'   => $errstr // Error message
112                 );
113         }
114         fputs($fp, $query);
115         $response = '';
116         while (! feof($fp)) $response .= fread($fp, 4096);
117         fclose($fp);
118
119         $resp = explode("\r\n\r\n", $response, 2);
120         $rccd = explode(' ', $resp[0], 3); // array('HTTP/1.1', '200', 'OK\r\n...')
121         $rc   = (integer)$rccd[1];
122
123         switch ($rc) {
124         case 301: // Moved Permanently
125         case 302: // Moved Temporarily
126                 $matches = array();
127                 if (preg_match('/^Location: (.+)$/m', $resp[0], $matches)
128                         && --$redirect_max > 0)
129                 {
130                         $url = trim($matches[1]);
131                         if (! preg_match('/^https?:\//', $url)) {
132                                 // Relative path to Absolute
133                                 if ($url{0} != '/')
134                                         $url = substr($url_path, 0, strrpos($url_path, '/')) . '/' . $url;
135                                 $url = $url_base . $url; // Add sheme, host
136                         }
137                         // Redirect
138                         return pkwk_http_request($url, $method, $headers, $post, $redirect_max);
139                 }
140         }
141         return array(
142                 'query'  => $query,   // Query String
143                 'rc'     => $rc,      // Response Code
144                 'header' => $resp[0], // Header
145                 'data'   => $resp[1]  // Data
146         );
147 }
148
149 // Separate IPv4 network-address and its netmask
150 define('PKWK_CIDR_NETWORK_REGEX', '/^(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})(?:\/([0-9.]+))?$/');
151
152 // Check if the $host is in the specified network(s)
153 function in_the_net($networks = array(), $host = '')
154 {
155         if (empty($networks) || $host == '') return FALSE;
156         if (! is_array($networks)) $networks = array($networks);
157
158         $matches = array();
159
160         if (preg_match(PKWK_CIDR_NETWORK_REGEX, $host, $matches)) {
161                 $ip = $matches[1];
162         } else {
163                 $ip = gethostbyname($host); // May heavy
164         }
165         $l_ip = ip2long($ip);
166
167         foreach ($networks as $network) {
168                 if (preg_match(PKWK_CIDR_NETWORK_REGEX, $network, $matches) &&
169                     is_long($l_ip) && long2ip($l_ip) == $ip) {
170                         // $host seems valid IPv4 address
171                         // Sample: '10.0.0.0/8' or '10.0.0.0/255.0.0.0'
172                         $l_net = ip2long($matches[1]); // '10.0.0.0'
173                         $mask  = isset($matches[2]) ? $matches[2] : 32; // '8' or '255.0.0.0'
174                         $mask  = is_numeric($mask) ?
175                                 pow(2, 32) - pow(2, 32 - $mask) : // '8' means '8-bit mask'
176                                 ip2long($mask);                   // '255.0.0.0' (the same)
177
178                         if (($l_ip & $mask) == $l_net) return TRUE;
179                 } else {
180                         // $host seems not IPv4 address. May be a DNS name like 'foobar.example.com'?
181                         foreach ($networks as $network)
182                                 if (preg_match('/\.?\b' . preg_quote($network, '/') . '$/', $host))
183                                         return TRUE;
184                 }
185         }
186
187         return FALSE; // Not found
188 }