50 require_once
'PEAR.php';
54 require_once
'Net/Socket.php';
58 require_once
'Net/URL.php';
63 define(
'HTTP_REQUEST_METHOD_GET',
'GET',
true);
64 define(
'HTTP_REQUEST_METHOD_HEAD',
'HEAD',
true);
65 define(
'HTTP_REQUEST_METHOD_POST',
'POST',
true);
66 define(
'HTTP_REQUEST_METHOD_PUT',
'PUT',
true);
67 define(
'HTTP_REQUEST_METHOD_DELETE',
'DELETE',
true);
68 define(
'HTTP_REQUEST_METHOD_OPTIONS',
'OPTIONS',
true);
69 define(
'HTTP_REQUEST_METHOD_TRACE',
'TRACE',
true);
75 define(
'HTTP_REQUEST_ERROR_FILE', 1);
76 define(
'HTTP_REQUEST_ERROR_URL', 2);
77 define(
'HTTP_REQUEST_ERROR_PROXY', 4);
78 define(
'HTTP_REQUEST_ERROR_REDIRECTS', 8);
79 define(
'HTTP_REQUEST_ERROR_RESPONSE', 16);
80 define(
'HTTP_REQUEST_ERROR_GZIP_METHOD', 32);
81 define(
'HTTP_REQUEST_ERROR_GZIP_READ', 64);
82 define(
'HTTP_REQUEST_ERROR_GZIP_DATA', 128);
83 define(
'HTTP_REQUEST_ERROR_GZIP_CRC', 256);
89 define(
'HTTP_REQUEST_HTTP_VER_1_0',
'1.0',
true);
90 define(
'HTTP_REQUEST_HTTP_VER_1_1',
'1.1',
true);
93 if (extension_loaded(
'mbstring') && (2 & ini_get(
'mbstring.func_overload'))) {
97 define(
'HTTP_REQUEST_MBSTRING',
true);
102 define(
'HTTP_REQUEST_MBSTRING',
false);
148 var $_requestHeaders;
208 var $_bodyDisallowed = array(
'TRACE');
218 var $_bodyRequired = array(
'POST',
'PUT');
224 var $_postFiles = array();
242 var $_allowRedirects;
260 var $_useBrackets =
true;
266 var $_listeners = array();
272 var $_saveBody =
true;
278 var $_readTimeout = null;
284 var $_socketOptions = null;
312 function HTTP_Request($url =
'', $params = array())
316 $this->_requestHeaders = array();
317 $this->_postData = array();
323 $this->_proxy_host = null;
324 $this->_proxy_port = null;
325 $this->_proxy_user = null;
326 $this->_proxy_pass = null;
328 $this->_allowRedirects =
false;
329 $this->_maxRedirects = 3;
330 $this->_redirects = 0;
332 $this->_timeout = null;
333 $this->_response = null;
335 foreach ($params as $key => $value) {
336 $this->{
'_' . $key} = $value;
344 $this->addHeader(
'User-Agent',
'PEAR HTTP_Request class ( http://pear.php.net/ )');
347 $this->addHeader(
'Connection',
'close');
350 if (!empty($this->_user)) {
351 $this->addHeader(
'Authorization',
'Basic ' . base64_encode($this->_user .
':' . $this->_pass));
355 if (!empty($this->_proxy_user)) {
356 $this->addHeader(
'Proxy-Authorization',
'Basic ' . base64_encode($this->_proxy_user .
':' . $this->_proxy_pass));
361 $this->addHeader(
'Accept-Encoding',
'gzip');
371 function _generateHostHeader()
373 if ($this->_url->port != 80 AND strcasecmp($this->_url->protocol,
'http') == 0) {
374 $host = $this->_url->host .
':' . $this->_url->port;
376 } elseif ($this->_url->port != 443 AND strcasecmp($this->_url->protocol,
'https') == 0) {
377 $host = $this->_url->host .
':' . $this->_url->port;
379 } elseif ($this->_url->port == 443 AND strcasecmp($this->_url->protocol,
'https') == 0 AND strpos($this->_url->url,
':443') !==
false) {
380 $host = $this->_url->host .
':' . $this->_url->port;
383 $host = $this->_url->host;
399 function reset($url, $params = array())
401 $this->HTTP_Request($url, $params);
410 function setURL($url)
412 $this->_url = &
new Net_URL($url, $this->_useBrackets);
414 if (!empty($this->_url->user) || !empty($this->_url->pass)) {
415 $this->setBasicAuth($this->_url->user, $this->_url->pass);
419 $this->addHeader(
'Host', $this->_generateHostHeader());
423 if (empty($this->_url->path)) {
424 $this->_url->path =
'/';
436 return empty($this->_url)?
'': $this->_url->getUrl();
448 function setProxy($host, $port = 8080,
$user = null,
$pass = null)
450 $this->_proxy_host = $host;
451 $this->_proxy_port = $port;
452 $this->_proxy_user =
$user;
453 $this->_proxy_pass =
$pass;
456 $this->addHeader(
'Proxy-Authorization',
'Basic ' . base64_encode(
$user .
':' .
$pass));
468 $this->_user =
$user;
469 $this->_pass =
$pass;
471 $this->addHeader(
'Authorization',
'Basic ' . base64_encode(
$user .
':' .
$pass));
480 function setMethod($method)
482 $this->_method = $method;
491 function setHttpVer($http)
493 $this->_http = $http;
503 function addHeader($name, $value)
505 $this->_requestHeaders[strtolower($name)] = $value;
514 function removeHeader($name)
516 if (isset($this->_requestHeaders[strtolower($name)])) {
517 unset($this->_requestHeaders[strtolower($name)]);
529 function addQueryString($name, $value, $preencoded =
false)
531 $this->_url->addQueryString($name, $value, $preencoded);
541 function addRawQueryString($querystring, $preencoded =
true)
543 $this->_url->addRawQueryString($querystring, $preencoded);
554 function addPostData($name, $value, $preencoded =
false)
557 $this->_postData[$name] = $value;
559 $this->_postData[$name] = $this->_arrayMapRecursive(
'urlencode', $value);
571 function _arrayMapRecursive($callback, $value)
573 if (!is_array($value)) {
574 return call_user_func($callback, $value);
577 foreach ($value as $k => $v) {
578 $map[$k] = $this->_arrayMapRecursive($callback, $v);
600 function addFile($inputName, $fileName, $contentType =
'application/octet-stream')
602 if (!is_array($fileName) && !is_readable($fileName)) {
604 } elseif (is_array($fileName)) {
605 foreach ($fileName as $name) {
606 if (!is_readable($name)) {
611 $this->addHeader(
'Content-Type',
'multipart/form-data');
612 $this->_postFiles[$inputName] = array(
614 'type' => $contentType
627 function addRawPostData($postdata, $preencoded =
true)
629 $this->_body = $preencoded ? $postdata : urlencode($postdata);
638 function setBody($body)
640 $this->_body = $body;
651 function clearPostData()
653 $this->_postData = null;
663 function addCookie($name, $value)
665 $cookies = isset($this->_requestHeaders[
'cookie']) ? $this->_requestHeaders[
'cookie'].
'; ' :
'';
666 $this->addHeader(
'Cookie', $cookies . $name .
'=' . $value);
677 function clearCookies()
679 $this->removeHeader(
'Cookie');
690 function sendRequest($saveBody =
true)
692 if (!is_a($this->_url,
'Net_URL')) {
696 $host = isset($this->_proxy_host) ? $this->_proxy_host : $this->_url->host;
697 $port = isset($this->_proxy_port) ? $this->_proxy_port : $this->_url->port;
699 if (strcasecmp($this->_url->protocol,
'https') == 0) {
701 if (version_compare(PHP_VERSION,
'4.3.0',
'<') || !extension_loaded(
'openssl')) {
702 return PEAR::raiseError(
'Need PHP 4.3.0 or later with OpenSSL support for https:// requests',
704 } elseif (isset($this->_proxy_host)) {
707 $host =
'ssl://' . $host;
711 $magicQuotes = ini_get(
'magic_quotes_runtime');
712 ini_set(
'magic_quotes_runtime',
false);
716 if (isset($this->_proxy_host) && !empty($this->_requestHeaders[
'connection']) &&
717 'Keep-Alive' == $this->_requestHeaders[
'connection'])
719 $this->removeHeader(
'connection');
723 (!empty($this->_requestHeaders[
'connection']) &&
'Keep-Alive' == $this->_requestHeaders[
'connection']);
725 $sockKey = $host .
':' . $port;
729 if ($keepAlive && !empty($sockets[$sockKey]) &&
730 !empty($sockets[$sockKey]->fp))
732 $this->_sock =& $sockets[$sockKey];
735 $this->_notify(
'connect');
737 $err = $this->_sock->connect($host, $port, null, $this->_timeout, $this->_socketOptions);
739 PEAR::isError($err) or $err = $this->_sock->write($this->_buildRequest());
741 if (!
PEAR::isError($err)) {
742 if (!empty($this->_readTimeout)) {
743 $this->_sock->setTimeout($this->_readTimeout[0], $this->_readTimeout[1]);
746 $this->_notify(
'sentRequest');
749 $this->_response = &
new HTTP_Response($this->_sock, $this->_listeners);
750 $err = $this->_response->process(
751 $this->_saveBody && $saveBody,
756 $keepAlive = (isset($this->_response->_headers[
'content-length'])
757 || (isset($this->_response->_headers[
'transfer-encoding'])
758 && strtolower($this->_response->_headers[
'transfer-encoding']) ==
'chunked'));
760 if (isset($this->_response->_headers[
'connection'])) {
761 $keepAlive = strtolower($this->_response->_headers[
'connection']) ==
'keep-alive';
763 $keepAlive =
'HTTP/'.HTTP_REQUEST_HTTP_VER_1_1 == $this->_response->_protocol;
769 ini_set(
'magic_quotes_runtime', $magicQuotes);
778 } elseif (empty($sockets[$sockKey]) || empty($sockets[$sockKey]->fp)) {
779 $sockets[$sockKey] =& $this->_sock;
783 if ( $this->_allowRedirects
784 AND $this->_redirects <= $this->_maxRedirects
785 AND $this->getResponseCode() > 300
786 AND $this->getResponseCode() < 399
787 AND !empty($this->_response->_headers[
'location'])) {
790 $redirect = $this->_response->_headers[
'location'];
793 if (preg_match(
'/^https?:\/\//i',
$redirect)) {
795 $this->addHeader(
'Host', $this->_generateHostHeader());
802 if (substr($this->_url->path, -1) ==
'/') {
812 if (substr($this->_url->path, -1) ==
'/') {
821 return $this->sendRequest($saveBody);
824 } elseif ($this->_allowRedirects AND $this->_redirects > $this->_maxRedirects) {
836 function disconnect()
838 if (!empty($this->_sock) && !empty($this->_sock->fp)) {
839 $this->_notify(
'disconnect');
840 $this->_sock->disconnect();
850 function getResponseCode()
852 return isset($this->_response->_code) ? $this->_response->_code :
false;
861 function getResponseReason()
863 return isset($this->_response->_reason) ? $this->_response->_reason :
false;
874 function getResponseHeader($headername = null)
876 if (!isset($headername)) {
877 return isset($this->_response->_headers)? $this->_response->_headers: array();
879 $headername = strtolower($headername);
880 return isset($this->_response->_headers[$headername]) ? $this->_response->_headers[$headername] :
false;
890 function getResponseBody()
892 return isset($this->_response->_body) ? $this->_response->_body :
false;
901 function getResponseCookies()
903 return isset($this->_response->_cookies) ? $this->_response->_cookies :
false;
912 function _buildRequest()
915 ini_set(
'arg_separator.output',
'&');
916 $querystring = ($querystring = $this->_url->getQueryString()) ?
'?' . $querystring :
'';
919 $host = isset($this->_proxy_host) ? $this->_url->protocol .
'://' . $this->_url->host :
'';
920 $port = (isset($this->_proxy_host) AND $this->_url->port != 80) ? ':' . $this->_url->port : '';
921 $path = $this->_url->path . $querystring;
922 $url = $host . $port .
$path;
928 $request = $this->_method .
' ' . $url .
' HTTP/' . $this->_http .
"\r\n";
930 if (in_array($this->_method, $this->_bodyDisallowed) ||
932 (empty($this->_postData) && empty($this->_postFiles)))))
934 $this->removeHeader(
'Content-Type');
936 if (empty($this->_requestHeaders[
'content-type'])) {
938 $this->addHeader(
'Content-Type',
'application/x-www-form-urlencoded');
939 } elseif (
'multipart/form-data' == $this->_requestHeaders[
'content-type']) {
940 $boundary =
'HTTP_Request_' . md5(uniqid(
'request') . microtime());
941 $this->addHeader(
'Content-Type',
'multipart/form-data; boundary=' . $boundary);
946 if (!empty($this->_requestHeaders)) {
947 foreach ($this->_requestHeaders as $name => $value) {
948 $canonicalName = implode(
'-', array_map(
'ucfirst', explode(
'-', $name)));
949 $request .= $canonicalName .
': ' . $value .
"\r\n";
954 if (in_array($this->_method, $this->_bodyDisallowed)) {
960 (!empty($this->_postData) || !empty($this->_postFiles))) {
963 if (!isset($boundary)) {
964 $postdata = implode(
'&', array_map(
965 create_function(
'$a',
'return $a[0] . \'=\' . $a[1];'),
966 $this->_flattenArray(
'', $this->_postData)
972 if (!empty($this->_postData)) {
973 $flatData = $this->_flattenArray(
'', $this->_postData);
974 foreach ($flatData as $item) {
975 $postdata .=
'--' . $boundary .
"\r\n";
976 $postdata .=
'Content-Disposition: form-data; name="' . $item[0] .
'"';
977 $postdata .=
"\r\n\r\n" . urldecode($item[1]) .
"\r\n";
980 foreach ($this->_postFiles as $name => $value) {
981 if (is_array($value[
'name'])) {
982 $varname = $name . ($this->_useBrackets?
'[]':
'');
985 $value[
'name'] = array($value[
'name']);
987 foreach ($value[
'name'] as $key =>
$filename) {
990 $type = is_array($value[
'type'])? @$value[
'type'][$key]: $value[
'type'];
992 $postdata .=
'--' . $boundary .
"\r\n";
993 $postdata .=
'Content-Disposition: form-data; name="' . $varname .
'"; filename="' . $basename .
'"';
994 $postdata .=
"\r\nContent-Type: " . $type;
995 $postdata .=
"\r\n\r\n" . fread($fp, filesize(
$filename)) .
"\r\n";
999 $postdata .=
'--' . $boundary .
"--\r\n";
1001 $request .=
'Content-Length: ' .
1002 (HTTP_REQUEST_MBSTRING? mb_strlen($postdata,
'iso-8859-1'): strlen($postdata)) .
1004 $request .= $postdata;
1007 } elseif (0 < strlen($this->_body)) {
1009 $request .=
'Content-Length: ' .
1010 (HTTP_REQUEST_MBSTRING? mb_strlen($this->_body,
'iso-8859-1'): strlen($this->_body)) .
1012 $request .= $this->_body;
1018 if (in_array($this->_method, $this->_bodyRequired)) {
1019 $request .=
"Content-Length: 0\r\n";
1036 function _flattenArray($name, $values)
1038 if (!is_array($values)) {
1039 return array(array($name, $values));
1042 foreach ($values as $k => $v) {
1045 } elseif ($this->_useBrackets) {
1046 $newName = $name .
'[' . $k .
']';
1050 $ret = array_merge(
$ret, $this->_flattenArray($newName, $v));
1076 function attach(&$listener)
1078 if (!is_a($listener,
'HTTP_Request_Listener')) {
1081 $this->_listeners[$listener->getId()] =& $listener;
1093 function detach(&$listener)
1095 if (!is_a($listener,
'HTTP_Request_Listener') ||
1096 !isset($this->_listeners[$listener->getId()])) {
1099 unset($this->_listeners[$listener->getId()]);
1112 function _notify($event,
$data = null)
1114 foreach (array_keys($this->_listeners) as $id) {
1115 $this->_listeners[$id]->update($this, $event,
$data);
1178 var $_chunkLength = 0;
1184 var $_listeners = array();
1198 function HTTP_Response(&$sock, &$listeners)
1200 $this->_sock =& $sock;
1201 $this->_listeners =& $listeners;
1220 function process($saveBody =
true, $canHaveBody =
true)
1223 $line = $this->_sock->readLine();
1224 if (!preg_match(
'!^(HTTP/\d\.\d) (\d{3})(?: (.+))?!', $line, $s)) {
1227 $this->_protocol = $s[1];
1228 $this->_code = intval($s[2]);
1229 $this->_reason = empty($s[3])? null: $s[3];
1231 while (
'' !== ($header = $this->_sock->readLine())) {
1232 $this->_processHeader($header);
1234 }
while (100 == $this->_code);
1236 $this->_notify(
'gotHeaders', $this->_headers);
1244 $canHaveBody = $canHaveBody && $this->_code >= 200 &&
1245 $this->_code != 204 && $this->_code != 304;
1248 $chunked = isset($this->_headers[
'transfer-encoding']) && (
'chunked' == $this->_headers[
'transfer-encoding']);
1249 $gzipped = isset($this->_headers[
'content-encoding']) && (
'gzip' == $this->_headers[
'content-encoding']);
1251 if ($canHaveBody && ($chunked || !isset($this->_headers[
'content-length']) ||
1252 0 != $this->_headers[
'content-length']))
1254 if ($chunked || !isset($this->_headers[
'content-length'])) {
1255 $this->_toRead = null;
1257 $this->_toRead = $this->_headers[
'content-length'];
1259 while (!$this->_sock->eof() && (is_null($this->_toRead) || 0 < $this->_toRead)) {
1261 $data = $this->_readChunked();
1262 } elseif (is_null($this->_toRead)) {
1263 $data = $this->_sock->read(4096);
1265 $data = $this->_sock->read(min(4096, $this->_toRead));
1266 $this->_toRead -= HTTP_REQUEST_MBSTRING? mb_strlen(
$data,
'iso-8859-1'): strlen(
$data);
1268 if (
'' ==
$data && (!$this->_chunkLength || $this->_sock->eof())) {
1272 if ($saveBody || $gzipped) {
1273 $this->_body .=
$data;
1275 $this->_notify($gzipped?
'gzTick':
'tick',
$data);
1283 $body = $this->_decodeGzip($this->_body);
1287 $this->_body = $body;
1288 $this->_notify(
'gotBody', $this->_body);
1290 $this->_notify(
'gotBody');
1303 function _processHeader($header)
1305 if (
false === strpos($header,
':')) {
1308 list($headername, $headervalue) = explode(
':', $header, 2);
1309 $headername = strtolower($headername);
1310 $headervalue = ltrim($headervalue);
1312 if (
'set-cookie' != $headername) {
1313 if (isset($this->_headers[$headername])) {
1314 $this->_headers[$headername] .=
',' . $headervalue;
1316 $this->_headers[$headername] = $headervalue;
1319 $this->_parseCookie($headervalue);
1330 function _parseCookie($headervalue)
1340 if (!strpos($headervalue,
';')) {
1341 $pos = strpos($headervalue,
'=');
1342 $cookie[
'name'] = trim(substr($headervalue, 0, $pos));
1343 $cookie[
'value'] = trim(substr($headervalue, $pos + 1));
1347 $elements = explode(
';', $headervalue);
1348 $pos = strpos($elements[0],
'=');
1349 $cookie[
'name'] = trim(substr($elements[0], 0, $pos));
1350 $cookie[
'value'] = trim(substr($elements[0], $pos + 1));
1352 for ($i = 1; $i < count($elements); $i++) {
1353 if (
false === strpos($elements[$i],
'=')) {
1354 $elName = trim($elements[$i]);
1357 list ($elName, $elValue) = array_map(
'trim', explode(
'=', $elements[$i]));
1359 $elName = strtolower($elName);
1360 if (
'secure' == $elName) {
1361 $cookie[
'secure'] =
true;
1362 } elseif (
'expires' == $elName) {
1363 $cookie[
'expires'] = str_replace(
'"',
'', $elValue);
1364 } elseif (
'path' == $elName ||
'domain' == $elName) {
1365 $cookie[$elName] = urldecode($elValue);
1367 $cookie[$elName] = $elValue;
1371 $this->_cookies[] = $cookie;
1381 function _readChunked()
1384 if (0 == $this->_chunkLength) {
1385 $line = $this->_sock->readLine();
1386 if (preg_match(
'/^([0-9a-f]+)/i', $line, $matches)) {
1387 $this->_chunkLength = hexdec($matches[1]);
1389 if (0 == $this->_chunkLength) {
1390 $this->_sock->readLine();
1397 $data = $this->_sock->read($this->_chunkLength);
1398 $this->_chunkLength -= HTTP_REQUEST_MBSTRING? mb_strlen(
$data,
'iso-8859-1'): strlen(
$data);
1399 if (0 == $this->_chunkLength) {
1400 $this->_sock->readLine();
1414 function _notify($event,
$data = null)
1416 foreach (array_keys($this->_listeners) as $id) {
1417 $this->_listeners[$id]->update($this, $event,
$data);
1433 function _decodeGzip(
$data)
1435 if (HTTP_REQUEST_MBSTRING) {
1436 $oldEncoding = mb_internal_encoding();
1437 mb_internal_encoding(
'iso-8859-1');
1439 $length = strlen(
$data);
1441 if (18 > $length || strcmp(substr(
$data, 0, 2),
"\x1f\x8b")) {
1444 $method = ord(substr(
$data, 2, 1));
1448 $flags = ord(substr(
$data, 3, 1));
1457 if ($length - $headerLength - 2 < 8) {
1460 $extraLength = unpack(
'v', substr(
$data, 10, 2));
1461 if ($length - $headerLength - 2 - $extraLength[1] < 8) {
1464 $headerLength += $extraLength[1] + 2;
1468 if ($length - $headerLength - 1 < 8) {
1471 $filenameLength = strpos(substr(
$data, $headerLength), chr(0));
1472 if (
false === $filenameLength || $length - $headerLength - $filenameLength - 1 < 8) {
1475 $headerLength += $filenameLength + 1;
1479 if ($length - $headerLength - 1 < 8) {
1482 $commentLength = strpos(substr(
$data, $headerLength), chr(0));
1483 if (
false === $commentLength || $length - $headerLength - $commentLength - 1 < 8) {
1486 $headerLength += $commentLength + 1;
1490 if ($length - $headerLength - 2 < 8) {
1493 $crcReal = 0xffff & crc32(substr(
$data, 0, $headerLength));
1494 $crcStored = unpack(
'v', substr(
$data, $headerLength, 2));
1495 if ($crcReal != $crcStored[1]) {
1501 $tmp = unpack(
'V2', substr(
$data, -8));
1503 $dataSize = $tmp[2];
1507 $unpacked = gzinflate(substr(
$data, $headerLength, -8));
1508 if (
false === $unpacked) {
1510 } elseif ($dataSize != strlen($unpacked)) {
1512 } elseif ((0xffffffff & $dataCrc) != (0xffffffff & crc32($unpacked))) {
1515 if (HTTP_REQUEST_MBSTRING) {
1516 mb_internal_encoding($oldEncoding);