86 * @copyright 2007 Dan Fuhry |
86 * @copyright 2007 Dan Fuhry |
87 */ |
87 */ |
88 |
88 |
89 class Request_HTTP |
89 class Request_HTTP |
90 { |
90 { |
91 |
91 |
92 /** |
92 /** |
93 * Switch to enable or disable debugging. You want this off on production sites. |
93 * Switch to enable or disable debugging. You want this off on production sites. |
94 * @var bool |
94 * @var bool |
95 */ |
95 */ |
96 |
96 |
97 var $debug = false; |
97 var $debug = false; |
98 |
98 |
99 /** |
99 /** |
100 * The host the request will be sent to. |
100 * The host the request will be sent to. |
101 * @var string |
101 * @var string |
102 */ |
102 */ |
103 |
103 |
104 var $host = ''; |
104 var $host = ''; |
105 |
105 |
106 /** |
106 /** |
107 * The TCP port our connection is (will be) on. |
107 * The TCP port our connection is (will be) on. |
108 * @var int |
108 * @var int |
109 */ |
109 */ |
110 |
110 |
111 var $port = 80; |
111 var $port = 80; |
112 |
112 |
113 /** |
113 /** |
114 * The request method. Can be GET or POST, defaults to GET. |
114 * The request method. Can be GET or POST, defaults to GET. |
115 * @var string |
115 * @var string |
116 */ |
116 */ |
117 |
117 |
118 var $method = 'GET'; |
118 var $method = 'GET'; |
119 |
119 |
120 /** |
120 /** |
121 * The URI to the remote script. |
121 * The URI to the remote script. |
122 * @var string |
122 * @var string |
123 */ |
123 */ |
124 |
124 |
125 var $uri = ''; |
125 var $uri = ''; |
126 |
126 |
127 /** |
127 /** |
128 * The parameters to be sent on GET. |
128 * The parameters to be sent on GET. |
129 * @var array (associative) |
129 * @var array (associative) |
130 */ |
130 */ |
131 |
131 |
132 var $parms_get = array(); |
132 var $parms_get = array(); |
133 |
133 |
134 /** |
134 /** |
135 * The parameters to be sent on POST. Ignored if $this->method == GET. |
135 * The parameters to be sent on POST. Ignored if $this->method == GET. |
136 * @var array (associative) |
136 * @var array (associative) |
137 */ |
137 */ |
138 |
138 |
139 var $parms_post = array(); |
139 var $parms_post = array(); |
140 |
140 |
141 /** |
141 /** |
142 * The list of cookies that will be sent. |
142 * The list of cookies that will be sent. |
143 * @var array (associative) |
143 * @var array (associative) |
144 */ |
144 */ |
145 |
145 |
146 var $cookies_out = array(); |
146 var $cookies_out = array(); |
147 |
147 |
148 /** |
148 /** |
149 * Additional request headers. |
149 * Additional request headers. |
150 * @var array (associative) |
150 * @var array (associative) |
151 */ |
151 */ |
152 |
152 |
153 var $headers = array(); |
153 var $headers = array(); |
154 |
154 |
155 /** |
155 /** |
156 * Follow server-side redirects; defaults to true. |
156 * Follow server-side redirects; defaults to true. |
157 * @var bool |
157 * @var bool |
158 */ |
158 */ |
159 |
159 |
160 var $follow_redirects = true; |
160 var $follow_redirects = true; |
161 |
161 |
162 /** |
162 /** |
163 * Cached response. |
163 * Cached response. |
164 * @var string, or bool:false if the request hasn't been sent yet |
164 * @var string, or bool:false if the request hasn't been sent yet |
165 */ |
165 */ |
166 |
166 |
167 var $response = false; |
167 var $response = false; |
168 |
168 |
169 /** |
169 /** |
170 * Cached response code |
170 * Cached response code |
171 * @var int set to -1 if request hasn't been sent yet |
171 * @var int set to -1 if request hasn't been sent yet |
172 */ |
172 */ |
173 |
173 |
174 var $response_code = -1; |
174 var $response_code = -1; |
175 |
175 |
176 /** |
176 /** |
177 * Cached response code string |
177 * Cached response code string |
178 * @var string or bool:false if the request hasn't been sent yet |
178 * @var string or bool:false if the request hasn't been sent yet |
179 */ |
179 */ |
180 |
180 |
181 var $response_string = false; |
181 var $response_string = false; |
182 |
182 |
183 /** |
183 /** |
184 * Resource for the socket. False if a connection currently isn't going. |
184 * Resource for the socket. False if a connection currently isn't going. |
185 * @var resource |
185 * @var resource |
186 */ |
186 */ |
187 |
187 |
188 var $socket = false; |
188 var $socket = false; |
189 |
189 |
190 /** |
190 /** |
191 * True if SSL is on, defaults to false |
191 * True if SSL is on, defaults to false |
192 * @var bool |
192 * @var bool |
193 */ |
193 */ |
194 |
194 |
195 var $ssl = false; |
195 var $ssl = false; |
196 |
196 |
197 /** |
197 /** |
198 * The state of our request. 0 means it hasn't been made yet. 1 means the socket is open, 2 means the socket is open and the request has been written, 3 means the headers have been fetched, and 4 means the request is completed. |
198 * The state of our request. 0 means it hasn't been made yet. 1 means the socket is open, 2 means the socket is open and the request has been written, 3 means the headers have been fetched, and 4 means the request is completed. |
199 * @var int |
199 * @var int |
200 */ |
200 */ |
201 |
201 |
202 var $state = 0; |
202 var $state = 0; |
203 |
203 |
204 /** |
204 /** |
205 * Constructor. |
205 * Constructor. |
206 * @param string Hostname to send to |
206 * @param string Hostname to send to |
207 * @param string URI (/index.php) |
207 * @param string URI (/index.php) |
208 * @param string Request method - GET or POST. |
208 * @param string Request method - GET or POST. |
209 * @param int Optional. The port to open the request on. Defaults to 80. |
209 * @param int Optional. The port to open the request on. Defaults to 80. |
210 * @param bool If true, uses SSL (and defaults the port to 443) |
210 * @param bool If true, uses SSL (and defaults the port to 443) |
211 */ |
211 */ |
212 |
212 |
213 function Request_HTTP($host, $uri, $method = 'GET', $port = 'default', $ssl = false) |
213 function Request_HTTP($host, $uri, $method = 'GET', $port = 'default', $ssl = false) |
214 { |
214 { |
215 if ( !preg_match('/^(?:(([a-z0-9-]+\.)*?)([a-z0-9-]+)|\[[a-f0-9:]+\])$/', $host) ) |
215 if ( !preg_match('/^(?:(([a-z0-9-]+\.)*?)([a-z0-9-]+)|\[[a-f0-9:]+\])$/', $host) ) |
216 throw new Exception(__CLASS__ . ': Invalid hostname'); |
216 throw new Exception(__CLASS__ . ': Invalid hostname'); |
217 if ( $ssl ) |
217 if ( $ssl ) |
218 { |
218 { |
219 $this->ssl = true; |
219 $this->ssl = true; |
220 $port = ( $port === 'default' ) ? 443 : $port; |
220 $port = ( $port === 'default' ) ? 443 : $port; |
221 } |
221 } |
222 else |
222 else |
223 { |
223 { |
224 $this->ssl = false; |
224 $this->ssl = false; |
225 $port = ( $port === 'default' ) ? 80 : $port; |
225 $port = ( $port === 'default' ) ? 80 : $port; |
226 } |
226 } |
227 // Yes - this really does support IPv6 URLs! |
227 // Yes - this really does support IPv6 URLs! |
228 $this->host = $host; |
228 $this->host = $host; |
229 $this->uri = $uri; |
229 $this->uri = $uri; |
230 if ( is_int($port) && $port >= 1 && $port <= 65535 ) |
230 if ( is_int($port) && $port >= 1 && $port <= 65535 ) |
231 $this->port = $port; |
231 $this->port = $port; |
232 else |
232 else |
233 throw new Exception(__CLASS__ . ': Invalid port'); |
233 throw new Exception(__CLASS__ . ': Invalid port'); |
234 $method = strtoupper($method); |
234 $method = strtoupper($method); |
235 if ( $method == 'GET' || $method == 'POST' ) |
235 if ( $method == 'GET' || $method == 'POST' ) |
236 $this->method = $method; |
236 $this->method = $method; |
237 else |
237 else |
238 throw new Exception(__CLASS__ . ': Invalid request method'); |
238 throw new Exception(__CLASS__ . ': Invalid request method'); |
239 |
239 |
240 $newline = "\r\n"; |
240 $newline = "\r\n"; |
241 $php_ver = PHP_VERSION; |
241 $php_ver = PHP_VERSION; |
242 $server = ( isset($_SERVER['SERVER_SOFTWARE']) ) ? "Server: {$_SERVER['SERVER_SOFTWARE']}" : "CLI"; |
242 $server = ( isset($_SERVER['SERVER_SOFTWARE']) ) ? "Server: {$_SERVER['SERVER_SOFTWARE']}" : "CLI"; |
243 $this->add_header('User-Agent', "PHP/$php_ver ({$server}; automated bot request)"); |
243 $this->add_header('User-Agent', "PHP/$php_ver ({$server}; automated bot request)"); |
244 } |
244 } |
245 |
245 |
246 /** |
246 /** |
247 * Sets one or more cookies to be sent to the server. |
247 * Sets one or more cookies to be sent to the server. |
248 * @param string or array If a string, the cookie name. If an array, associative array in the form of cookiename => cookievalue |
248 * @param string or array If a string, the cookie name. If an array, associative array in the form of cookiename => cookievalue |
249 * @param string or bool If a string, the cookie value. If boolean, defaults to false, param 1 should be an array, and this should not be passed. |
249 * @param string or bool If a string, the cookie value. If boolean, defaults to false, param 1 should be an array, and this should not be passed. |
250 */ |
250 */ |
251 |
251 |
252 function add_cookie($cookiename, $cookievalue = false) |
252 function add_cookie($cookiename, $cookievalue = false) |
253 { |
253 { |
254 if ( is_array($cookiename) && !$cookievalue ) |
254 if ( is_array($cookiename) && !$cookievalue ) |
255 { |
255 { |
256 foreach ( $cookiename as $name => $value ) |
256 foreach ( $cookiename as $name => $value ) |
257 { |
257 { |
258 $this->cookies_out[$name] = $value; |
258 $this->cookies_out[$name] = $value; |
259 } |
259 } |
260 } |
260 } |
261 else if ( is_string($cookiename) && is_string($cookievalue) ) |
261 else if ( is_string($cookiename) && is_string($cookievalue) ) |
262 { |
262 { |
263 $this->cookies_out[$cookiename] = $cookievalue; |
263 $this->cookies_out[$cookiename] = $cookievalue; |
264 } |
264 } |
265 else |
265 else |
266 { |
266 { |
267 throw new Exception(__METHOD__ . ': Invalid argument(s)'); |
267 throw new Exception(__METHOD__ . ': Invalid argument(s)'); |
268 } |
268 } |
269 } |
269 } |
270 |
270 |
271 /** |
271 /** |
272 * Sets one or more request header values. |
272 * Sets one or more request header values. |
273 * @param string or array If a string, the header name. If an array, associative array in the form of headername => headervalue |
273 * @param string or array If a string, the header name. If an array, associative array in the form of headername => headervalue |
274 * @param string or bool If a string, the header value. If boolean, defaults to false, param 1 should be an array, and this should not be passed. |
274 * @param string or bool If a string, the header value. If boolean, defaults to false, param 1 should be an array, and this should not be passed. |
275 */ |
275 */ |
276 |
276 |
277 function add_header($headername, $headervalue = false) |
277 function add_header($headername, $headervalue = false) |
278 { |
278 { |
279 if ( is_array($headername) && !$headervalue ) |
279 if ( is_array($headername) && !$headervalue ) |
280 { |
280 { |
281 foreach ( $headername as $name => $value ) |
281 foreach ( $headername as $name => $value ) |
282 { |
282 { |
283 $this->headers[$name] = $value; |
283 $this->headers[$name] = $value; |
284 } |
284 } |
285 } |
285 } |
286 else if ( is_string($headername) && is_string($headervalue) ) |
286 else if ( is_string($headername) && is_string($headervalue) ) |
287 { |
287 { |
288 $this->headers[$headername] = $headervalue; |
288 $this->headers[$headername] = $headervalue; |
289 } |
289 } |
290 else |
290 else |
291 { |
291 { |
292 throw new Exception(__METHOD__ . ': Invalid argument(s)'); |
292 throw new Exception(__METHOD__ . ': Invalid argument(s)'); |
293 } |
293 } |
294 } |
294 } |
295 |
295 |
296 /** |
296 /** |
297 * Adds one or more values to be passed on GET. |
297 * Adds one or more values to be passed on GET. |
298 * @param string or array If a string, the parameter name. If an array, associative array in the form of parametername => parametervalue |
298 * @param string or array If a string, the parameter name. If an array, associative array in the form of parametername => parametervalue |
299 * @param string or bool If a string, the parameter value. If boolean, defaults to false, param 1 should be an array, and this should not be passed. |
299 * @param string or bool If a string, the parameter value. If boolean, defaults to false, param 1 should be an array, and this should not be passed. |
300 */ |
300 */ |
301 |
301 |
302 function add_get($getname, $getvalue = false) |
302 function add_get($getname, $getvalue = false) |
303 { |
303 { |
304 if ( is_array($getname) && !$getvalue ) |
304 if ( is_array($getname) && !$getvalue ) |
305 { |
305 { |
306 foreach ( $getname as $name => $value ) |
306 foreach ( $getname as $name => $value ) |
307 { |
307 { |
308 $this->parms_get[$name] = $value; |
308 $this->parms_get[$name] = $value; |
309 } |
309 } |
310 } |
310 } |
311 else if ( is_string($getname) && is_string($getvalue) ) |
311 else if ( is_string($getname) && is_string($getvalue) ) |
312 { |
312 { |
313 $this->parms_get[$getname] = $getvalue; |
313 $this->parms_get[$getname] = $getvalue; |
314 } |
314 } |
315 else |
315 else |
316 { |
316 { |
317 throw new Exception(__METHOD__ . ': Invalid argument(s)'); |
317 throw new Exception(__METHOD__ . ': Invalid argument(s)'); |
318 } |
318 } |
319 } |
319 } |
320 |
320 |
321 /** |
321 /** |
322 * Adds one or more values to be passed on POST. |
322 * Adds one or more values to be passed on POST. |
323 * @param string or array If a string, the header name. If an array, associative array in the form of headername => headervalue |
323 * @param string or array If a string, the header name. If an array, associative array in the form of headername => headervalue |
324 * @param string or bool If a string, the header value. If boolean, defaults to false, param 1 should be an array, and this should not be passed. |
324 * @param string or bool If a string, the header value. If boolean, defaults to false, param 1 should be an array, and this should not be passed. |
325 */ |
325 */ |
326 |
326 |
327 function add_post($postname, $postvalue = false) |
327 function add_post($postname, $postvalue = false) |
328 { |
328 { |
329 if ( is_array($postname) && !$postvalue ) |
329 if ( is_array($postname) && !$postvalue ) |
330 { |
330 { |
331 foreach ( $postname as $name => $value ) |
331 foreach ( $postname as $name => $value ) |
332 { |
332 { |
333 $this->parms_post[$name] = $value; |
333 $this->parms_post[$name] = $value; |
334 } |
334 } |
335 } |
335 } |
336 else if ( is_string($postname) && is_string($postvalue) ) |
336 else if ( is_string($postname) && is_string($postvalue) ) |
337 { |
337 { |
338 $this->parms_post[$postname] = $postvalue; |
338 $this->parms_post[$postname] = $postvalue; |
339 } |
339 } |
340 else |
340 else |
341 { |
341 { |
342 throw new Exception(__METHOD__ . ': Invalid argument(s)'); |
342 throw new Exception(__METHOD__ . ': Invalid argument(s)'); |
343 } |
343 } |
344 } |
344 } |
345 |
345 |
346 /** |
346 /** |
347 * Internal function to open up the socket. |
347 * Internal function to open up the socket. |
348 * @access private |
348 * @access private |
349 */ |
349 */ |
350 |
350 |
351 function _sock_open(&$connection) |
351 function _sock_open(&$connection) |
352 { |
352 { |
353 // Open connection |
353 // Open connection |
354 $ssl_prepend = ( $this->ssl ) ? 'ssl://' : ''; |
354 $ssl_prepend = ( $this->ssl ) ? 'ssl://' : ''; |
355 $connection = fsockopen($ssl_prepend . $this->host, $this->port, $errno, $errstr); |
355 $connection = fsockopen($ssl_prepend . $this->host, $this->port, $errno, $errstr); |
356 if ( !$connection ) |
356 if ( !$connection ) |
357 throw new Exception(__METHOD__ . ": Could not make connection"); // to {$this->host}:{$this->port}: error $errno: $errstr"); |
357 throw new Exception(__METHOD__ . ": Could not make connection"); // to {$this->host}:{$this->port}: error $errno: $errstr"); |
358 |
358 |
359 // 1 = socket open |
359 // 1 = socket open |
360 $this->state = 1; |
360 $this->state = 1; |
361 } |
361 } |
362 |
362 |
363 /** |
363 /** |
364 * Internal function to actually write the request into the socket. |
364 * Internal function to actually write the request into the socket. |
365 * @access private |
365 * @access private |
366 */ |
366 */ |
367 |
367 |
368 function _write_request(&$connection, &$headers, &$cookies, &$get, &$post) |
368 function _write_request(&$connection, &$headers, &$cookies, &$get, &$post) |
369 { |
369 { |
370 $newline = "\r\n"; |
370 $newline = "\r\n"; |
371 |
371 |
372 if ( $this->debug ) |
372 if ( $this->debug ) |
373 echo '<p>Connection opened. Writing main request to socket. Raw socket data follows.</p><pre>'; |
373 echo '<p>Connection opened. Writing main request to socket. Raw socket data follows.</p><pre>'; |
374 |
374 |
375 if ( $this->debug ) |
375 if ( $this->debug ) |
376 { |
376 { |
377 echo '<hr /><div style="white-space: nowrap;">'; |
377 echo '<hr /><div style="white-space: nowrap;">'; |
378 echo '<p><b>' . __CLASS__ . ': Sending request</b></p><p>Request parameters:</p>'; |
378 echo '<p><b>' . __CLASS__ . ': Sending request</b></p><p>Request parameters:</p>'; |
379 echo "<p><b>Headers:</b></p><pre>$headers</pre>"; |
379 echo "<p><b>Headers:</b></p><pre>$headers</pre>"; |
380 echo "<p><b>Cookies:</b> $cookies</p>"; |
380 echo "<p><b>Cookies:</b> $cookies</p>"; |
381 echo "<p><b>GET URI:</b> " . htmlspecialchars($this->uri . $get) . "</p>"; |
381 echo "<p><b>GET URI:</b> " . htmlspecialchars($this->uri . $get) . "</p>"; |
382 echo "<p><b>POST DATA:</b> " . htmlspecialchars($post) . "</p>"; |
382 echo "<p><b>POST DATA:</b> " . htmlspecialchars($post) . "</p>"; |
383 echo "<pre>"; |
383 echo "<pre>"; |
384 } |
384 } |
385 |
385 |
386 $portline = ( $this->port == 80 ) ? '' : ":$this->port"; |
386 $portline = ( $this->port == 80 ) ? '' : ":$this->port"; |
387 |
387 |
388 $this->_fputs($connection, "{$this->method} {$this->uri}{$get} HTTP/1.1{$newline}"); |
388 $this->_fputs($connection, "{$this->method} {$this->uri}{$get} HTTP/1.1{$newline}"); |
389 $this->_fputs($connection, "Host: {$this->host}$portline{$newline}"); |
389 $this->_fputs($connection, "Host: {$this->host}$portline{$newline}"); |
390 $this->_fputs($connection, $headers); |
390 $this->_fputs($connection, $headers); |
391 $this->_fputs($connection, $cookies); |
391 $this->_fputs($connection, $cookies); |
392 |
392 |
393 if ( $this->method == 'POST' ) |
393 if ( $this->method == 'POST' ) |
394 { |
394 { |
395 // POST-specific parameters |
395 // POST-specific parameters |
396 $post_length = strlen($post); |
396 $post_length = strlen($post); |
397 $this->_fputs($connection, "Content-type: application/x-www-form-urlencoded{$newline}"); |
397 $this->_fputs($connection, "Content-type: application/x-www-form-urlencoded{$newline}"); |
398 $this->_fputs($connection, "Content-length: {$post_length}{$newline}"); |
398 $this->_fputs($connection, "Content-length: {$post_length}{$newline}"); |
399 } |
399 } |
400 |
400 |
401 $this->_fputs($connection, "Connection: close{$newline}"); |
401 $this->_fputs($connection, "Connection: close{$newline}"); |
402 $this->_fputs($connection, "{$newline}"); |
402 $this->_fputs($connection, "{$newline}"); |
403 |
403 |
404 if ( $this->method == 'POST' ) |
404 if ( $this->method == 'POST' ) |
405 { |
405 { |
406 $this->_fputs($connection, $post); |
406 $this->_fputs($connection, $post); |
407 } |
407 } |
408 |
408 |
409 if ( $this->debug ) |
409 if ( $this->debug ) |
410 echo '</pre><p>Request written. Fetching response.</p>'; |
410 echo '</pre><p>Request written. Fetching response.</p>'; |
411 |
411 |
412 // 2 = request written |
412 // 2 = request written |
413 $this->state = 2; |
413 $this->state = 2; |
414 } |
414 } |
415 |
415 |
416 /** |
416 /** |
417 * Wrap up and close the socket. Nothing more than a call to fsockclose() except in debug mode. |
417 * Wrap up and close the socket. Nothing more than a call to fsockclose() except in debug mode. |
418 * @access private |
418 * @access private |
419 */ |
419 */ |
420 |
420 |
421 function sock_close(&$connection) |
421 function sock_close(&$connection) |
422 { |
422 { |
423 if ( $this->debug ) |
423 if ( $this->debug ) |
424 { |
424 { |
425 echo '<p>Response fetched. Closing connection. Response text follows.</p><pre>'; |
425 echo '<p>Response fetched. Closing connection. Response text follows.</p><pre>'; |
426 echo htmlspecialchars($this->response); |
426 echo htmlspecialchars($this->response); |
427 echo '</pre></div><hr />'; |
427 echo '</pre></div><hr />'; |
428 } |
428 } |
429 |
429 |
430 fclose($connection); |
430 fclose($connection); |
431 $this->state = 0; |
431 $this->state = 0; |
432 } |
432 } |
433 |
433 |
434 /** |
434 /** |
435 * Internal function to grab the response code and status string |
435 * Internal function to grab the response code and status string |
436 * @access string |
436 * @access string |
437 */ |
437 */ |
438 |
438 |
439 function _parse_response_code($buffer) |
439 function _parse_response_code($buffer) |
440 { |
440 { |
441 // Retrieve response code and status |
441 // Retrieve response code and status |
442 $pos_newline = strpos($buffer, "\n"); |
442 $pos_newline = strpos($buffer, "\n"); |
443 $pos_carriage_return = strpos($buffer, "\r"); |
443 $pos_carriage_return = strpos($buffer, "\r"); |
444 $pos_end_first_line = ( $pos_carriage_return < $pos_newline && $pos_carriage_return > 0 ) ? $pos_carriage_return : $pos_newline; |
444 $pos_end_first_line = ( $pos_carriage_return < $pos_newline && $pos_carriage_return > 0 ) ? $pos_carriage_return : $pos_newline; |
445 |
445 |
446 // First line is in format of: |
446 // First line is in format of: |
447 // HTTP/1.1 ### Blah blah blah(\r?)\n |
447 // HTTP/1.1 ### Blah blah blah(\r?)\n |
448 $response_code = substr($buffer, 9, 3); |
448 $response_code = substr($buffer, 9, 3); |
449 $response_string = substr($buffer, 13, ( $pos_end_first_line - 13 ) ); |
449 $response_string = substr($buffer, 13, ( $pos_end_first_line - 13 ) ); |
450 $this->response_code = intval($response_code); |
450 $this->response_code = intval($response_code); |
451 $this->response_string = $response_string; |
451 $this->response_string = $response_string; |
452 } |
452 } |
453 |
453 |
454 /** |
454 /** |
455 * Internal function to send the request. |
455 * Internal function to send the request. |
456 * @access private |
456 * @access private |
457 */ |
457 */ |
458 |
458 |
459 function _send_request() |
459 function _send_request() |
460 { |
460 { |
461 $this->concat_headers($headers, $cookies, $get, $post); |
461 $this->concat_headers($headers, $cookies, $get, $post); |
462 |
462 |
463 if ( $this->state < 1 ) |
463 if ( $this->state < 1 ) |
464 { |
464 { |
465 $this->_sock_open($this->socket); |
465 $this->_sock_open($this->socket); |
466 } |
466 } |
467 if ( $this->state < 2 ) |
467 if ( $this->state < 2 ) |
468 { |
468 { |
469 $this->_write_request($this->socket, $headers, $cookies, $get, $post); |
469 $this->_write_request($this->socket, $headers, $cookies, $get, $post); |
470 } |
470 } |
471 if ( $this->state == 2 ) |
471 if ( $this->state == 2 ) |
472 { |
472 { |
473 $buffer = $this->_read_until_newlines($this->socket); |
473 $buffer = $this->_read_until_newlines($this->socket); |
474 $this->state = 3; |
474 $this->state = 3; |
475 $this->_parse_response_code($buffer); |
475 $this->_parse_response_code($buffer); |
476 $this->response = $buffer; |
476 $this->response = $buffer; |
477 } |
477 } |
478 // obey redirects |
478 // obey redirects |
479 $i = 0; |
479 $i = 0; |
480 while ( $i < 20 && $this->follow_redirects ) |
480 while ( $i < 20 && $this->follow_redirects ) |
481 { |
481 { |
482 $incoming_headers = $this->get_response_headers_array(); |
482 $incoming_headers = $this->get_response_headers_array(); |
483 if ( !$incoming_headers ) |
483 if ( !$incoming_headers ) |
484 break; |
484 break; |
485 if ( isset($incoming_headers['Location']) ) |
485 if ( isset($incoming_headers['Location']) ) |
486 { |
486 { |
487 // we've been redirected... |
487 // we've been redirected... |
488 $new_uri = $this->_resolve_uri($incoming_headers['Location']); |
488 $new_uri = $this->_resolve_uri($incoming_headers['Location']); |
489 if ( !$new_uri ) |
489 if ( !$new_uri ) |
490 { |
490 { |
491 // ... bad URI, ignore Location header. |
491 // ... bad URI, ignore Location header. |
492 break; |
492 break; |
493 } |
493 } |
494 // change location |
494 // change location |
495 $this->host = $new_uri['host']; |
495 $this->host = $new_uri['host']; |
496 $this->port = $new_uri['port']; |
496 $this->port = $new_uri['port']; |
497 $this->uri = $new_uri['uri']; |
497 $this->uri = $new_uri['uri']; |
498 $get = ''; |
498 $get = ''; |
499 |
499 |
500 // reset |
500 // reset |
501 $this->sock_close($this->socket); |
501 $this->sock_close($this->socket); |
502 $this->_sock_open($this->socket); |
502 $this->_sock_open($this->socket); |
503 $this->_write_request($this->socket, $headers, $cookies, $get, $post); |
503 $this->_write_request($this->socket, $headers, $cookies, $get, $post); |
504 $buffer = $this->_read_until_newlines($this->socket); |
504 $buffer = $this->_read_until_newlines($this->socket); |
505 $this->state = 3; |
505 $this->state = 3; |
506 $this->_parse_response_code($buffer); |
506 $this->_parse_response_code($buffer); |
507 $this->response = $buffer; |
507 $this->response = $buffer; |
508 $i++; |
508 $i++; |
509 } |
509 } |
510 else |
510 else |
511 { |
511 { |
512 break; |
512 break; |
513 } |
513 } |
514 } |
514 } |
515 if ( $i == 20 ) |
515 if ( $i == 20 ) |
516 { |
516 { |
517 throw new Exception(__METHOD__ . ": Redirect trap. Request_HTTP doesn't do cookies, btw."); |
517 throw new Exception(__METHOD__ . ": Redirect trap. Request_HTTP doesn't do cookies, btw."); |
518 } |
518 } |
519 |
519 |
520 if ( $this->state == 3 ) |
520 if ( $this->state == 3 ) |
521 { |
521 { |
522 // Determine transfer encoding |
522 // Determine transfer encoding |
523 $is_chunked = preg_match("/Transfer-Encoding: (chunked)\r?\n/", $this->response); |
523 $is_chunked = preg_match("/Transfer-Encoding: (chunked)\r?\n/", $this->response); |
524 if ( preg_match("/^Content-Length: ([0-9]+)[\s]*$/mi", $this->response, $match) && !$is_chunked ) |
524 if ( preg_match("/^Content-Length: ([0-9]+)[\s]*$/mi", $this->response, $match) && !$is_chunked ) |
525 { |
525 { |
526 $size = intval($match[1]); |
526 $size = intval($match[1]); |
527 if ( $this->debug ) |
527 if ( $this->debug ) |
528 { |
528 { |
529 echo "Pulling response using fread(), size $size\n"; |
529 echo "Pulling response using fread(), size $size\n"; |
530 } |
530 } |
531 $this->response .= fread($this->socket, $size); |
531 $this->response .= fread($this->socket, $size); |
532 } |
532 } |
533 else |
533 else |
534 { |
534 { |
535 if ( $this->debug ) |
535 if ( $this->debug ) |
536 echo "Pulling response using chunked handler\n"; |
536 echo "Pulling response using chunked handler\n"; |
537 |
537 |
538 $buffer = ''; |
538 $buffer = ''; |
539 while ( !feof($this->socket) ) |
539 while ( !feof($this->socket) ) |
540 { |
540 { |
541 $part = fgets($this->socket, 1024); |
541 $part = fgets($this->socket, 1024); |
542 if ( $is_chunked && preg_match("/^([a-f0-9]+)\x0D\x0A$/", $part, $match) ) |
542 if ( $is_chunked && preg_match("/^([a-f0-9]+)\x0D\x0A$/", $part, $match) ) |
543 { |
543 { |
544 $chunklen = hexdec($match[1]); |
544 $chunklen = hexdec($match[1]); |
545 $part = ( $chunklen > 0 ) ? fread($this->socket, $chunklen) : ''; |
545 $part = ( $chunklen > 0 ) ? fread($this->socket, $chunklen) : ''; |
546 // remove the last newline from $part |
546 // remove the last newline from $part |
547 $part = preg_replace("/\r?\n\$/", "", $part); |
547 $part = preg_replace("/\r?\n\$/", "", $part); |
548 } |
548 } |
549 $buffer .= $part; |
549 $buffer .= $part; |
550 } |
550 } |
551 $this->response .= $buffer; |
551 $this->response .= $buffer; |
552 } |
552 } |
553 } |
553 } |
554 $this->state = 4; |
554 $this->state = 4; |
555 |
555 |
556 $this->sock_close($this->socket); |
556 $this->sock_close($this->socket); |
557 $this->socket = false; |
557 $this->socket = false; |
558 } |
558 } |
559 |
559 |
560 /** |
560 /** |
561 * Internal function to send the request but only fetch the headers. Leaves a connection open for a finish-up function. |
561 * Internal function to send the request but only fetch the headers. Leaves a connection open for a finish-up function. |
562 * @access private |
562 * @access private |
563 */ |
563 */ |
564 |
564 |
565 function _send_request_headers_only() |
565 function _send_request_headers_only() |
566 { |
566 { |
567 $this->concat_headers($headers, $cookies, $get, $post); |
567 $this->concat_headers($headers, $cookies, $get, $post); |
568 |
568 |
569 if ( $this->state < 1 ) |
569 if ( $this->state < 1 ) |
570 { |
570 { |
571 $this->_sock_open($this->socket); |
571 $this->_sock_open($this->socket); |
572 } |
572 } |
573 if ( $this->state < 2 ) |
573 if ( $this->state < 2 ) |
574 { |
574 { |
575 $this->_write_request($this->socket, $headers, $cookies, $get, $post); |
575 $this->_write_request($this->socket, $headers, $cookies, $get, $post); |
576 } |
576 } |
577 if ( $this->state == 2 ) |
577 if ( $this->state == 2 ) |
578 { |
578 { |
579 $buffer = $this->_read_until_newlines($this->socket); |
579 $buffer = $this->_read_until_newlines($this->socket); |
580 $this->state = 3; |
580 $this->state = 3; |
581 $this->_parse_response_code($buffer); |
581 $this->_parse_response_code($buffer); |
582 $this->response = $buffer; |
582 $this->response = $buffer; |
583 } |
583 } |
584 } |
584 } |
585 |
585 |
586 /** |
586 /** |
587 * Internal function to read from a socket until two consecutive newlines are hit. |
587 * Internal function to read from a socket until two consecutive newlines are hit. |
588 * @access private |
588 * @access private |
589 */ |
589 */ |
590 |
590 |
591 function _read_until_newlines($sock) |
591 function _read_until_newlines($sock) |
592 { |
592 { |
593 $prev_char = ''; |
593 $prev_char = ''; |
594 $prev1_char = ''; |
594 $prev1_char = ''; |
595 $prev2_char = ''; |
595 $prev2_char = ''; |
596 $buf = ''; |
596 $buf = ''; |
597 while ( !feof($sock) ) |
597 while ( !feof($sock) ) |
598 { |
598 { |
599 $chr = fread($sock, 1); |
599 $chr = fread($sock, 1); |
600 $buf .= $chr; |
600 $buf .= $chr; |
601 if ( ( $chr == "\n" && $prev_char == "\n" ) || |
601 if ( ( $chr == "\n" && $prev_char == "\n" ) || |
602 ( $chr == "\n" && $prev_char == "\r" && $prev1_char == "\n" && $prev2_char == "\r" ) ) |
602 ( $chr == "\n" && $prev_char == "\r" && $prev1_char == "\n" && $prev2_char == "\r" ) ) |
603 { |
603 { |
604 return $buf; |
604 return $buf; |
605 } |
605 } |
606 $prev2_char = $prev1_char; |
606 $prev2_char = $prev1_char; |
607 $prev1_char = $prev_char; |
607 $prev1_char = $prev_char; |
608 $prev_char = $chr; |
608 $prev_char = $chr; |
609 } |
609 } |
610 return $buf; |
610 return $buf; |
611 } |
611 } |
612 |
612 |
613 /** |
613 /** |
614 * Returns the response text. If the request hasn't been sent, it will be sent here. |
614 * Returns the response text. If the request hasn't been sent, it will be sent here. |
615 * @return string |
615 * @return string |
616 */ |
616 */ |
617 |
617 |
618 function get_response() |
618 function get_response() |
619 { |
619 { |
620 if ( $this->state == 4 ) |
620 if ( $this->state == 4 ) |
621 return $this->response; |
621 return $this->response; |
622 $this->_send_request(); |
622 $this->_send_request(); |
623 return $this->response; |
623 return $this->response; |
624 } |
624 } |
625 |
625 |
626 /** |
626 /** |
627 * Writes the response body to a file. This is good for conserving memory when downloading large files. If the file already exists it will be overwritten. |
627 * Writes the response body to a file. This is good for conserving memory when downloading large files. If the file already exists it will be overwritten. |
628 * @param string File to write to |
628 * @param string File to write to |
629 * @param int Chunk size in KB to read from the socket. Optional and should only be needed in circumstances when extreme memory conservation is needed. Defaults to 768. |
629 * @param int Chunk size in KB to read from the socket. Optional and should only be needed in circumstances when extreme memory conservation is needed. Defaults to 768. |
630 * @param int Maximum file size. Defaults to 0, which means no limit. |
630 * @param int Maximum file size. Defaults to 0, which means no limit. |
631 * @return bool True on success, false on failure |
631 * @return bool True on success, false on failure |
632 */ |
632 */ |
633 |
633 |
634 function write_response_to_file($file, $chunklen = 768, $max_file_size = 0) |
634 function write_response_to_file($file, $chunklen = 768, $max_file_size = 0) |
635 { |
635 { |
636 if ( !is_writeable( dirname($file) ) || !file_exists( dirname($file) ) ) |
636 if ( !is_writeable( dirname($file) ) || !file_exists( dirname($file) ) ) |
637 { |
637 { |
638 return false; |
638 return false; |
639 } |
639 } |
640 $handle = @fopen($file, 'w'); |
640 $handle = @fopen($file, 'w'); |
641 if ( !$handle ) |
641 if ( !$handle ) |
642 return false; |
642 return false; |
643 $chunklen = intval($chunklen); |
643 $chunklen = intval($chunklen); |
644 if ( $chunklen < 1 ) |
644 if ( $chunklen < 1 ) |
645 return false; |
645 return false; |
646 if ( $this->state == 4 ) |
646 if ( $this->state == 4 ) |
647 { |
647 { |
648 // we already have the response, so cheat |
648 // we already have the response, so cheat |
649 $response = $this->get_response_body(); |
649 $response = $this->get_response_body(); |
650 fwrite($handle, $response); |
650 fwrite($handle, $response); |
651 } |
651 } |
652 else |
652 else |
653 { |
653 { |
654 // read data from the socket, write it immediately, and unset to free memory |
654 // read data from the socket, write it immediately, and unset to free memory |
655 $headers = $this->get_response_headers(); |
655 $headers = $this->get_response_headers(); |
656 $transferred_bytes = 0; |
656 $transferred_bytes = 0; |
657 $bandwidth_exceeded = false; |
657 $bandwidth_exceeded = false; |
658 // if transfer-encoding is chunked, read using chunk sizes the server specifies |
658 // if transfer-encoding is chunked, read using chunk sizes the server specifies |
659 $is_chunked = preg_match("/Transfer-Encoding: (chunked)\r?\n/", $this->response); |
659 $is_chunked = preg_match("/Transfer-Encoding: (chunked)\r?\n/", $this->response); |
660 if ( $is_chunked ) |
660 if ( $is_chunked ) |
661 { |
661 { |
662 $buffer = ''; |
662 $buffer = ''; |
663 while ( !feof($this->socket) ) |
663 while ( !feof($this->socket) ) |
664 { |
664 { |
665 $part = fgets($this->socket, ( 1024 * $chunklen )); |
665 $part = fgets($this->socket, ( 1024 * $chunklen )); |
666 // Theoretically if the encoding is really chunked then this should always match. |
666 // Theoretically if the encoding is really chunked then this should always match. |
667 if ( $is_chunked && preg_match("/^([a-f0-9]+)\x0D\x0A$/", $part, $match) ) |
667 if ( $is_chunked && preg_match("/^([a-f0-9]+)\x0D\x0A$/", $part, $match) ) |
668 { |
668 { |
669 $chunk_length = hexdec($match[1]); |
669 $chunk_length = hexdec($match[1]); |
670 $part = ( $chunk_length > 0 ) ? fread($this->socket, $chunk_length) : ''; |
670 $part = ( $chunk_length > 0 ) ? fread($this->socket, $chunk_length) : ''; |
671 // remove the last newline from $part |
671 // remove the last newline from $part |
672 $part = preg_replace("/\r?\n\$/m", "", $part); |
672 $part = preg_replace("/\r?\n\$/m", "", $part); |
673 } |
673 } |
674 |
674 |
675 $transferred_bytes += strlen($part); |
675 $transferred_bytes += strlen($part); |
676 if ( $max_file_size && $transferred_bytes > $max_file_size ) |
676 if ( $max_file_size && $transferred_bytes > $max_file_size ) |
677 { |
677 { |
678 // truncate output to $max_file_size bytes |
678 // truncate output to $max_file_size bytes |
679 $partlen = $max_file_size - ( $transferred_bytes - strlen($part) ); |
679 $partlen = $max_file_size - ( $transferred_bytes - strlen($part) ); |
680 $part = substr($part, 0, $partlen); |
680 $part = substr($part, 0, $partlen); |
681 $bandwidth_exceeded = true; |
681 $bandwidth_exceeded = true; |
682 } |
682 } |
683 fwrite($handle, $part); |
683 fwrite($handle, $part); |
684 if ( $bandwidth_exceeded ) |
684 if ( $bandwidth_exceeded ) |
685 { |
685 { |
686 break; |
686 break; |
687 } |
687 } |
688 } |
688 } |
689 } |
689 } |
690 else |
690 else |
691 { |
691 { |
692 $first_chunk = fread($this->socket, ( 1024 * $chunklen )); |
692 $first_chunk = fread($this->socket, ( 1024 * $chunklen )); |
693 fwrite($handle, $first_chunk); |
693 fwrite($handle, $first_chunk); |
694 while ( !feof($this->socket) ) |
694 while ( !feof($this->socket) ) |
695 { |
695 { |
696 $chunk = fread($this->socket, ( 1024 * $chunklen )); |
696 $chunk = fread($this->socket, ( 1024 * $chunklen )); |
697 |
697 |
698 $transferred_bytes += strlen($chunk); |
698 $transferred_bytes += strlen($chunk); |
699 if ( $max_file_size && $transferred_bytes > $max_file_size ) |
699 if ( $max_file_size && $transferred_bytes > $max_file_size ) |
700 { |
700 { |
701 // truncate output to $max_file_size bytes |
701 // truncate output to $max_file_size bytes |
702 $partlen = $max_file_size - ( $transferred_bytes - strlen($chunk) ); |
702 $partlen = $max_file_size - ( $transferred_bytes - strlen($chunk) ); |
703 $chunk = substr($chunk, 0, $partlen); |
703 $chunk = substr($chunk, 0, $partlen); |
704 $bandwidth_exceeded = true; |
704 $bandwidth_exceeded = true; |
705 } |
705 } |
706 |
706 |
707 fwrite($handle, $chunk); |
707 fwrite($handle, $chunk); |
708 unset($chunk); |
708 unset($chunk); |
709 |
709 |
710 if ( $bandwidth_exceeded ) |
710 if ( $bandwidth_exceeded ) |
711 { |
711 { |
712 break; |
712 break; |
713 } |
713 } |
714 } |
714 } |
715 } |
715 } |
716 } |
716 } |
717 fclose($handle); |
717 fclose($handle); |
718 // close socket and reset state, since we haven't cached the response |
718 // close socket and reset state, since we haven't cached the response |
719 $this->sock_close($this->socket); |
719 $this->sock_close($this->socket); |
720 $this->state = 0; |
720 $this->state = 0; |
721 return ($bandwidth_exceeded) ? false : true; |
721 return ($bandwidth_exceeded) ? false : true; |
722 } |
722 } |
723 |
723 |
724 /** |
724 /** |
725 * Resolves, based on current settings and URI, a URI string to an array consisting of a host, port, and new URI. Returns false on error. |
725 * Resolves, based on current settings and URI, a URI string to an array consisting of a host, port, and new URI. Returns false on error. |
726 * @param string |
726 * @param string |
727 * @return array |
727 * @return array |
728 */ |
728 */ |
729 |
729 |
730 function _resolve_uri($uri) |
730 function _resolve_uri($uri) |
731 { |
731 { |
732 // long ass regexp w00t |
732 // long ass regexp w00t |
733 if ( !preg_match('#^(?:https?://((?:(?:[a-z0-9-]+\.)*)(?:[a-z0-9-]+)|\[[a-f0-9:]+\])(?::([0-9]+))?)?(/)(.*)$#i', $uri, $match) ) |
733 if ( !preg_match('#^(?:https?://((?:(?:[a-z0-9-]+\.)*)(?:[a-z0-9-]+)|\[[a-f0-9:]+\])(?::([0-9]+))?)?(/)(.*)$#i', $uri, $match) ) |
734 { |
734 { |
735 // bad target URI |
735 // bad target URI |
736 return false; |
736 return false; |
737 } |
737 } |
738 $hostpart = $match[1]; |
738 $hostpart = $match[1]; |
739 if ( empty($hostpart) ) |
739 if ( empty($hostpart) ) |
740 { |
740 { |
741 // use existing host |
741 // use existing host |
742 $host = $this->host; |
742 $host = $this->host; |
743 $port = $this->port; |
743 $port = $this->port; |
744 } |
744 } |
745 else |
745 else |
746 { |
746 { |
747 $host = $match[1]; |
747 $host = $match[1]; |
748 $port = empty($match[2]) ? 80 : intval($match[2]); |
748 $port = empty($match[2]) ? 80 : intval($match[2]); |
749 } |
749 } |
750 // is this an absolute URI, or relative? |
750 // is this an absolute URI, or relative? |
751 if ( empty($match[3]) ) |
751 if ( empty($match[3]) ) |
752 { |
752 { |
753 // relative |
753 // relative |
754 $uri = dirname($this->uri) . $match[4]; |
754 $uri = dirname($this->uri) . $match[4]; |
755 } |
755 } |
756 else |
756 else |
757 { |
757 { |
758 // absolute |
758 // absolute |
759 $uri = '/' . $match[4]; |
759 $uri = '/' . $match[4]; |
760 } |
760 } |
761 return array( |
761 return array( |
762 'host' => $host, |
762 'host' => $host, |
763 'port' => $port, |
763 'port' => $port, |
764 'uri' => $uri |
764 'uri' => $uri |
765 ); |
765 ); |
766 } |
766 } |
767 |
767 |
768 /** |
768 /** |
769 * Returns only the response headers. |
769 * Returns only the response headers. |
770 * @return string |
770 * @return string |
771 */ |
771 */ |
772 |
772 |
773 function get_response_headers() |
773 function get_response_headers() |
774 { |
774 { |
775 if ( $this->state == 3 ) |
775 if ( $this->state == 3 ) |
776 { |
776 { |
777 return $this->response; |
777 return $this->response; |
778 } |
778 } |
779 else if ( $this->state == 4 ) |
779 else if ( $this->state == 4 ) |
780 { |
780 { |
781 $pos_end = strpos($this->response, "\r\n\r\n"); |
781 $pos_end = strpos($this->response, "\r\n\r\n"); |
782 if ( empty($pos_end) ) |
782 if ( empty($pos_end) ) |
783 { |
783 { |
784 $pos_end = strpos($this->response, "\n\n"); |
784 $pos_end = strpos($this->response, "\n\n"); |
785 } |
785 } |
786 $data = substr($this->response, 0, $pos_end); |
786 $data = substr($this->response, 0, $pos_end); |
787 return $data; |
787 return $data; |
788 } |
788 } |
789 else |
789 else |
790 { |
790 { |
791 $this->_send_request_headers_only(); |
791 $this->_send_request_headers_only(); |
792 return $this->response; |
792 return $this->response; |
793 } |
793 } |
794 } |
794 } |
795 |
795 |
796 /** |
796 /** |
797 * Returns only the response headers, as an associative array. |
797 * Returns only the response headers, as an associative array. |
798 * @return array |
798 * @return array |
799 */ |
799 */ |
800 |
800 |
801 function get_response_headers_array() |
801 function get_response_headers_array() |
802 { |
802 { |
803 $data = $this->get_response_headers(); |
803 $data = $this->get_response_headers(); |
804 preg_match_all("/(^|\n)([A-z0-9_-]+?): (.+?)(\r|\n|\$)/", $data, $matches); |
804 preg_match_all("/(^|\n)([A-z0-9_-]+?): (.+?)(\r|\n|\$)/", $data, $matches); |
805 $headers = array(); |
805 $headers = array(); |
806 for ( $i = 0; $i < count($matches[0]); $i++ ) |
806 for ( $i = 0; $i < count($matches[0]); $i++ ) |
807 { |
807 { |
808 $headers[ $matches[2][$i] ] = $matches[3][$i]; |
808 $headers[ $matches[2][$i] ] = $matches[3][$i]; |
809 } |
809 } |
810 return $headers; |
810 return $headers; |
811 } |
811 } |
812 |
812 |
813 /** |
813 /** |
814 * Returns only the body (not the headers) of the response. If the request hasn't been sent, it will be sent here. |
814 * Returns only the body (not the headers) of the response. If the request hasn't been sent, it will be sent here. |
815 * @return string |
815 * @return string |
816 */ |
816 */ |
817 |
817 |
818 function get_response_body() |
818 function get_response_body() |
819 { |
819 { |
820 $data = $this->get_response(); |
820 $data = $this->get_response(); |
821 $pos_start = strpos($data, "\r\n\r\n") + 4; |
821 $pos_start = strpos($data, "\r\n\r\n") + 4; |
822 if ( $pos_start == 4 ) |
822 if ( $pos_start == 4 ) |
823 { |
823 { |
824 $pos_start = strpos($data, "\n\n") + 4; |
824 $pos_start = strpos($data, "\n\n") + 4; |
825 } |
825 } |
826 $data = substr($data, $pos_start); |
826 $data = substr($data, $pos_start); |
827 return $data; |
827 return $data; |
828 } |
828 } |
829 |
829 |
830 /** |
830 /** |
831 * Returns all cookies requested to be set by the server as an associative array. If the request hasn't been sent, it will be sent here. |
831 * Returns all cookies requested to be set by the server as an associative array. If the request hasn't been sent, it will be sent here. |
832 * @return array |
832 * @return array |
833 */ |
833 */ |
834 |
834 |
835 function get_cookies() |
835 function get_cookies() |
836 { |
836 { |
837 $data = $this->get_response(); |
837 $data = $this->get_response(); |
838 $data = str_replace("\r\n", "\n", $data); |
838 $data = str_replace("\r\n", "\n", $data); |
839 $pos = strpos($data, "\n\n"); |
839 $pos = strpos($data, "\n\n"); |
840 $headers = substr($data, 0, $pos); |
840 $headers = substr($data, 0, $pos); |
841 preg_match_all("/Set-Cookie: ([a-z0-9_]+)=([^;]+);( expires=([^;]+);)?( path=(.*?))?\n/", $headers, $cookiematch); |
841 preg_match_all("/Set-Cookie: ([a-z0-9_]+)=([^;]+);( expires=([^;]+);)?( path=(.*?))?\n/", $headers, $cookiematch); |
842 if ( count($cookiematch[0]) < 1 ) |
842 if ( count($cookiematch[0]) < 1 ) |
843 return array(); |
843 return array(); |
844 $cookies = array(); |
844 $cookies = array(); |
845 foreach ( $cookiematch[0] as $i => $cookie ) |
845 foreach ( $cookiematch[0] as $i => $cookie ) |
846 { |
846 { |
847 $cookies[$cookiematch[1][$i]] = $cookiematch[2][$i]; |
847 $cookies[$cookiematch[1][$i]] = $cookiematch[2][$i]; |
848 } |
848 } |
849 return $cookies; |
849 return $cookies; |
850 } |
850 } |
851 |
851 |
852 /** |
852 /** |
853 * Internal method to write data to a socket with debugging possibility. |
853 * Internal method to write data to a socket with debugging possibility. |
854 * @access private |
854 * @access private |
855 */ |
855 */ |
856 |
856 |
857 function _fputs($socket, $data) |
857 function _fputs($socket, $data) |
858 { |
858 { |
859 if ( $this->debug ) |
859 if ( $this->debug ) |
860 echo htmlspecialchars($data); |
860 echo htmlspecialchars($data); |
861 |
861 |
862 return fputs($socket, $data); |
862 return fputs($socket, $data); |
863 } |
863 } |
864 |
864 |
865 /** |
865 /** |
866 * Internal function to stringify cookies, headers, get, and post. |
866 * Internal function to stringify cookies, headers, get, and post. |
867 * @access private |
867 * @access private |
868 */ |
868 */ |
869 |
869 |
870 function concat_headers(&$headers, &$cookies, &$get, &$post) |
870 function concat_headers(&$headers, &$cookies, &$get, &$post) |
871 { |
871 { |
872 $headers = ''; |
872 $headers = ''; |
873 $cookies = ''; |
873 $cookies = ''; |
874 foreach ( $this->headers as $name => $value ) |
874 foreach ( $this->headers as $name => $value ) |
875 { |
875 { |
876 $value = str_replace('\\n', '\\\\n', $value); |
876 $value = str_replace('\\n', '\\\\n', $value); |
877 $value = str_replace("\n", '\\n', $value); |
877 $value = str_replace("\n", '\\n', $value); |
878 $headers .= "$name: $value\r\n"; |
878 $headers .= "$name: $value\r\n"; |
879 } |
879 } |
880 unset($value); |
880 unset($value); |
881 if ( count($this->cookies_out) > 0 ) |
881 if ( count($this->cookies_out) > 0 ) |
882 { |
882 { |
883 $i = 0; |
883 $i = 0; |
884 $cookie_header = 'Cookie: '; |
884 $cookie_header = 'Cookie: '; |
885 foreach ( $this->cookies_out as $name => $value ) |
885 foreach ( $this->cookies_out as $name => $value ) |
886 { |
886 { |
887 $i++; |
887 $i++; |
888 if ( $i > 1 ) |
888 if ( $i > 1 ) |
889 $cookie_header .= '; '; |
889 $cookie_header .= '; '; |
890 $value = str_replace(';', rawurlencode(';'), $value); |
890 $value = str_replace(';', rawurlencode(';'), $value); |
891 $value = str_replace('\\n', '\\\\n', $value); |
891 $value = str_replace('\\n', '\\\\n', $value); |
892 $value = str_replace("\n", '\\n', $value); |
892 $value = str_replace("\n", '\\n', $value); |
893 $cookie_header .= "$name=$value"; |
893 $cookie_header .= "$name=$value"; |
894 } |
894 } |
895 $cookie_header .= "\r\n"; |
895 $cookie_header .= "\r\n"; |
896 $cookies = $cookie_header; |
896 $cookies = $cookie_header; |
897 unset($value, $cookie_header); |
897 unset($value, $cookie_header); |
898 } |
898 } |
899 if ( count($this->parms_get) > 0 ) |
899 if ( count($this->parms_get) > 0 ) |
900 { |
900 { |
901 $get = '?'; |
901 $get = '?'; |
902 $i = 0; |
902 $i = 0; |
903 foreach ( $this->parms_get as $name => $value ) |
903 foreach ( $this->parms_get as $name => $value ) |
904 { |
904 { |
905 $i++; |
905 $i++; |
906 if ( $i > 1 ) |
906 if ( $i > 1 ) |
907 $get .= '&'; |
907 $get .= '&'; |
908 $value = urlencode($value); |
908 $value = urlencode($value); |
909 if ( !empty($value) || is_string($value) ) |
909 if ( !empty($value) || is_string($value) ) |
910 $get .= "$name=$value"; |
910 $get .= "$name=$value"; |
911 else |
911 else |
912 $get .= "$name"; |
912 $get .= "$name"; |
913 } |
913 } |
914 } |
914 } |
915 if ( count($this->parms_post) > 0 ) |
915 if ( count($this->parms_post) > 0 ) |
916 { |
916 { |
917 $post = ''; |
917 $post = ''; |
918 $i = 0; |
918 $i = 0; |
919 foreach ( $this->parms_post as $name => $value ) |
919 foreach ( $this->parms_post as $name => $value ) |
920 { |
920 { |
921 $i++; |
921 $i++; |
922 if ( $i > 1 ) |
922 if ( $i > 1 ) |
923 $post .= '&'; |
923 $post .= '&'; |
924 $value = urlencode($value); |
924 $value = urlencode($value); |
925 $post .= "$name=$value"; |
925 $post .= "$name=$value"; |
926 } |
926 } |
927 } |
927 } |
928 } |
928 } |
929 |
929 |
930 } |
930 } |
931 |
931 |