1: <?php
2: 3: 4: 5: 6: 7: 8: 9: 10:
11:
12: 13: 14: 15: 16: 17: 18: 19: 20:
21: class Requests {
22: 23: 24: 25: 26:
27: const POST = 'POST';
28:
29: 30: 31: 32: 33:
34: const GET = 'GET';
35:
36: 37: 38: 39: 40:
41: const HEAD = 'HEAD';
42:
43: 44: 45: 46: 47:
48: const VERSION = '1.5';
49:
50: 51: 52: 53: 54:
55: protected static $transports = array();
56:
57: 58: 59: 60: 61: 62: 63:
64: public static $transport = null;
65:
66: 67: 68: 69: 70:
71: private function __construct() {}
72:
73: 74: 75: 76: 77:
78: public static function add_transport($transport) {
79: if (empty(self::$transports)) {
80: self::$transports = array(
81: 'Requests_Transport_cURL',
82: 'Requests_Transport_fsockopen',
83: );
84: }
85:
86: self::$transports = array_merge(self::$transports, array($transport));
87: }
88:
89: 90: 91: 92: 93: 94:
95: protected static function get_transport() {
96:
97:
98: if (!is_null(self::$transport)) {
99: return new self::$transport();
100: }
101:
102:
103: if (empty(self::$transports)) {
104: self::$transports = array(
105: 'Requests_Transport_cURL',
106: 'Requests_Transport_fsockopen',
107: );
108: }
109:
110:
111: foreach (self::$transports as $class) {
112: if (!class_exists($class))
113: continue;
114:
115: $result = call_user_func(array($class, 'test'));
116: if ($result) {
117: self::$transport = $class;
118: break;
119: }
120: }
121: if (self::$transport === null) {
122: throw new Requests_Exception('No working transports found', 'notransport', self::$transports);
123: }
124:
125: return new self::$transport();
126: }
127:
128: 129: 130: 131: 132: 133: 134:
135: 136: 137:
138: public static function get($url, $headers = array(), $options = array()) {
139: return self::request($url, $headers, null, self::GET, $options);
140: }
141:
142: 143: 144:
145: public static function head($url, $headers = array(), $options = array()) {
146: return self::request($url, $headers, null, self::HEAD, $options);
147: }
148:
149:
150: 151: 152: 153: 154: 155: 156: 157: 158: 159:
160: public static function post($url, $headers = array(), $data = array(), $options = array()) {
161: return self::request($url, $headers, $data, self::POST, $options);
162: }
163:
164: 165: 166: 167: 168: 169: 170: 171: 172: 173: 174: 175: 176: 177: 178: 179: 180: 181: 182: 183: 184: 185: 186: 187: 188: 189: 190: 191: 192: 193: 194: 195: 196: 197: 198: 199: 200: 201: 202: 203:
204: public static function request($url, $headers = array(), $data = array(), $type = self::GET, $options = array()) {
205: if (!preg_match('/^http(s)?:\/\//i', $url)) {
206: throw new Requests_Exception('Only HTTP requests are handled.', 'nonhttp', $url);
207: }
208: $defaults = array(
209: 'timeout' => 10,
210: 'useragent' => 'php-requests/' . self::VERSION,
211: 'redirected' => 0,
212: 'redirects' => 10,
213: 'follow_redirects' => true,
214: 'blocking' => true,
215: 'type' => $type,
216: 'filename' => false,
217: 'auth' => false,
218: 'idn' => true,
219: 'hooks' => null,
220: 'transport' => null,
221: );
222: $options = array_merge($defaults, $options);
223:
224: if (empty($options['hooks'])) {
225: $options['hooks'] = new Requests_Hooks();
226: }
227:
228:
229: if (is_array($options['auth'])) {
230: $options['auth'] = new Requests_Auth_Basic($options['auth']);
231: }
232: if ($options['auth'] !== false) {
233: $options['auth']->register($options['hooks']);
234: }
235:
236: $options['hooks']->dispatch('requests.before_request', array(&$url, &$headers, &$data, &$type, &$options));
237:
238: if ($options['idn'] !== false) {
239: $iri = new Requests_IRI($url);
240: $iri->host = Requests_IDNAEncoder::encode($iri->ihost);
241: $url = $iri->uri;
242: }
243:
244: if (!empty($options['transport'])) {
245: $transport = $options['transport'];
246:
247: if (is_string($options['transport'])) {
248: $transport = new $transport();
249: }
250: }
251: else {
252: $transport = self::get_transport();
253: }
254: $response = $transport->request($url, $headers, $data, $options);
255:
256: $options['hooks']->dispatch('requests.before_parse', array(&$response, $url, $headers, $data, $type, $options));
257:
258: return self::parse_response($response, $url, $headers, $data, $options);
259: }
260:
261: 262: 263: 264: 265: 266: 267: 268: 269: 270: 271: 272: 273: 274:
275: protected static function parse_response($headers, $url, $req_headers, $req_data, $options) {
276: $return = new Requests_Response();
277: if (!$options['blocking']) {
278: return $return;
279: }
280:
281: $return->url = $url;
282:
283: if (!$options['filename']) {
284: if (strpos($headers, "\r\n\r\n") === false) {
285:
286: throw new Requests_Exception('Missing header/body separator', 'requests.no_crlf_separator');
287: }
288:
289: $headers = explode("\r\n\r\n", $headers, 2);
290: $return->body = array_pop($headers);
291: $headers = $headers[0];
292: }
293: else {
294: $return->body = '';
295: }
296:
297: $headers = str_replace("\r\n", "\n", $headers);
298:
299: $headers = preg_replace('/\n[ \t]/', ' ', $headers);
300: $headers = explode("\n", $headers);
301: preg_match('#^HTTP/1\.\d[ \t]+(\d+)#i', array_shift($headers), $matches);
302: if (empty($matches)) {
303: throw new Requests_Exception('Response could not be parsed', 'noversion', $headers);
304: }
305: $return->status_code = (int) $matches[1];
306: if ($return->status_code >= 200 && $return->status_code < 300) {
307: $return->success = true;
308: }
309:
310: foreach ($headers as $header) {
311: list($key, $value) = explode(':', $header, 2);
312: $value = trim($value);
313: preg_replace('#(\s+)#i', ' ', $value);
314: $return->headers[$key] = $value;
315: }
316: if (isset($return->headers['transfer-encoding'])) {
317: $return->body = self::decode_chunked($return->body);
318: unset($return->headers['transfer-encoding']);
319: }
320: if (isset($return->headers['content-encoding'])) {
321: $return->body = self::decompress($return->body);
322: }
323:
324:
325: if (isset($return->headers['connection'])) {
326: unset($return->headers['connection']);
327: }
328:
329: if ((in_array($return->status_code, array(300, 301, 302, 303, 307)) || $return->status_code > 307 && $return->status_code < 400) && $options['follow_redirects'] === true) {
330: if (isset($return->headers['location']) && $options['redirected'] < $options['redirects']) {
331: $options['redirected']++;
332: $location = $return->headers['location'];
333: $redirected = self::request($location, $req_headers, $req_data, false, $options);
334: $redirected->history[] = $return;
335: return $redirected;
336: }
337: elseif ($options['redirected'] >= $options['redirects']) {
338: throw new Requests_Exception('Too many redirects', 'toomanyredirects', $return);
339: }
340: }
341:
342: $return->redirects = $options['redirected'];
343:
344: $options['hooks']->dispatch('requests.after_request', array(&$return, $req_headers, $req_data, $options));
345: return $return;
346: }
347:
348: 349: 350: 351: 352: 353: 354:
355: protected static function decode_chunked($data) {
356: if (!preg_match('/^([0-9a-f]+)[^\r\n]*\r\n/i', trim($data))) {
357: return $data;
358: }
359:
360: $decoded = '';
361: $encoded = $data;
362:
363: while (true) {
364: $is_chunked = (bool) preg_match( '/^([0-9a-f]+)[^\r\n]*\r\n/i', $encoded, $matches );
365: if (!$is_chunked) {
366:
367: return $data;
368: }
369:
370: $length = hexdec(trim($matches[1]));
371: if ($length === 0) {
372:
373: return $decoded;
374: }
375:
376: $chunk_length = strlen($matches[0]);
377: $decoded .= $part = substr($encoded, $chunk_length, $length);
378: $encoded = substr($encoded, $chunk_length + $length + 2);
379:
380: if (trim($encoded) === '0' || empty($encoded)) {
381: return $decoded;
382: }
383: }
384:
385:
386:
387: }
388:
389:
390: 391: 392: 393: 394: 395:
396: public static function flattern($array) {
397: $return = array();
398: foreach ($array as $key => $value) {
399: $return[] = "$key: $value";
400: }
401: return $return;
402: }
403:
404: 405: 406: 407: 408: 409: 410: 411: 412: 413:
414: protected static function decompress($data) {
415: if (substr($data, 0, 2) !== "\x1f\x8b") {
416:
417: return $data;
418: }
419:
420: if (function_exists('gzdecode') && ($decoded = gzdecode($data)) !== false) {
421: return $decoded;
422: }
423: elseif (function_exists('gzinflate') && ($decoded = @gzinflate($data)) !== false) {
424: return $decoded;
425: }
426: elseif (($decoded = self::compatible_gzinflate($data)) !== false) {
427: return $decoded;
428: }
429: elseif (function_exists('gzuncompress') && ($decoded = @gzuncompress($data)) !== false) {
430: return $decoded;
431: }
432:
433: return $data;
434: }
435:
436: 437: 438: 439: 440: 441: 442: 443: 444: 445: 446: 447: 448:
449: protected static function compatible_gzinflate($gzData) {
450: if ( substr($gzData, 0, 3) == "\x1f\x8b\x08" ) {
451: $i = 10;
452: $flg = ord( substr($gzData, 3, 1) );
453: if ( $flg > 0 ) {
454: if ( $flg & 4 ) {
455: list($xlen) = unpack('v', substr($gzData, $i, 2) );
456: $i = $i + 2 + $xlen;
457: }
458: if ( $flg & 8 )
459: $i = strpos($gzData, "\0", $i) + 1;
460: if ( $flg & 16 )
461: $i = strpos($gzData, "\0", $i) + 1;
462: if ( $flg & 2 )
463: $i = $i + 2;
464: }
465: return gzinflate( substr($gzData, $i, -8) );
466: } else {
467: return false;
468: }
469: }
470: }