From b22fc5b74aecd505d4e2df01258171fc65d312cf Mon Sep 17 00:00:00 2001
From: Brion Vibber <brion@pobox.com>
Date: Mon, 2 Nov 2009 06:56:31 -0800
Subject: Revert "Rebuilt HTTPClient class as an extension of PEAR
 HTTP_Request2 package, adding redirect handling and convenience functions."
 Going to restructure a little more before finalizing this...

This reverts commit fa37967858c3c29000797e510e5f98aca8ab558f.
---
 lib/Shorturl_api.php |  23 ++++---
 lib/curlclient.php   | 179 ++++++++++++++++++++++++++++++++++++++++++++++++++
 lib/default.php      |   2 +
 lib/httpclient.php   | 180 ++++++++++++---------------------------------------
 lib/oauthclient.php  |  65 ++++++++++---------
 lib/ping.php         |  14 ++--
 lib/snapshot.php     |  21 +++++-
 7 files changed, 297 insertions(+), 187 deletions(-)
 create mode 100644 lib/curlclient.php

(limited to 'lib')

diff --git a/lib/Shorturl_api.php b/lib/Shorturl_api.php
index ef0d8dda4..18ae7719b 100644
--- a/lib/Shorturl_api.php
+++ b/lib/Shorturl_api.php
@@ -41,17 +41,22 @@ abstract class ShortUrlApi
         return strlen($url) >= common_config('site', 'shorturllength');
     }
 
-    protected function http_post($data)
-    {
-        $request = new HTTPClient($this->service_url);
-        return $request->post($data);
+    protected function http_post($data) {
+        $ch = curl_init();
+        curl_setopt($ch, CURLOPT_URL, $this->service_url);
+        curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
+        curl_setopt($ch, CURLOPT_POST, 1);
+        curl_setopt($ch, CURLOPT_POSTFIELDS, $data);
+        $response = curl_exec($ch);
+        $code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
+        curl_close($ch);
+        if (($code < 200) || ($code >= 400)) return false;
+        return $response;
     }
 
-    protected function http_get($url)
-    {
-        $service = $this->service_url . urlencode($url);
-        $request = new HTTPClient($service);
-        return $request->get();
+    protected function http_get($url) {
+        $encoded_url = urlencode($url);
+        return file_get_contents("{$this->service_url}$encoded_url");
     }
 
     protected function tidy($response) {
diff --git a/lib/curlclient.php b/lib/curlclient.php
new file mode 100644
index 000000000..c307c2984
--- /dev/null
+++ b/lib/curlclient.php
@@ -0,0 +1,179 @@
+<?php
+/**
+ * StatusNet, the distributed open-source microblogging tool
+ *
+ * Utility class for wrapping Curl
+ *
+ * PHP version 5
+ *
+ * LICENCE: This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * @category  HTTP
+ * @package   StatusNet
+ * @author    Evan Prodromou <evan@status.net>
+ * @copyright 2009 StatusNet, Inc.
+ * @license   http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
+ * @link      http://status.net/
+ */
+
+if (!defined('STATUSNET')) {
+    exit(1);
+}
+
+define(CURLCLIENT_VERSION, "0.1");
+
+/**
+ * Wrapper for Curl
+ *
+ * Makes Curl HTTP client calls within our HTTPClient framework
+ *
+ * @category HTTP
+ * @package  StatusNet
+ * @author   Evan Prodromou <evan@status.net>
+ * @license  http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
+ * @link     http://status.net/
+ */
+
+class CurlClient extends HTTPClient
+{
+    function __construct()
+    {
+    }
+
+    function head($url, $headers=null)
+    {
+        $ch = curl_init($url);
+
+        $this->setup($ch);
+
+        curl_setopt_array($ch,
+                          array(CURLOPT_NOBODY => true));
+
+        if (!is_null($headers)) {
+            curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
+        }
+
+        $result = curl_exec($ch);
+
+        curl_close($ch);
+
+        return $this->parseResults($result);
+    }
+
+    function get($url, $headers=null)
+    {
+        $ch = curl_init($url);
+
+        $this->setup($ch);
+
+        if (!is_null($headers)) {
+            curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
+        }
+
+        $result = curl_exec($ch);
+
+        curl_close($ch);
+
+        return $this->parseResults($result);
+    }
+
+    function post($url, $headers=null, $body=null)
+    {
+        $ch = curl_init($url);
+
+        $this->setup($ch);
+
+        curl_setopt($ch, CURLOPT_POST, true);
+
+        if (!is_null($body)) {
+            curl_setopt($ch, CURLOPT_POSTFIELDS, $body);
+        }
+
+        if (!is_null($headers)) {
+            curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
+        }
+
+        $result = curl_exec($ch);
+
+        curl_close($ch);
+
+        return $this->parseResults($result);
+    }
+
+    function setup($ch)
+    {
+        curl_setopt_array($ch,
+                          array(CURLOPT_USERAGENT => $this->userAgent(),
+                                CURLOPT_HEADER => true,
+                                CURLOPT_RETURNTRANSFER => true));
+    }
+
+    function userAgent()
+    {
+        $version = curl_version();
+        return parent::userAgent() . " CurlClient/".CURLCLIENT_VERSION . " cURL/" . $version['version'];
+    }
+
+    function parseResults($results)
+    {
+        $resp = new HTTPResponse();
+
+        $lines = explode("\r\n", $results);
+
+        if (preg_match("#^HTTP/1.[01] (\d\d\d) .+$#", $lines[0], $match)) {
+            $resp->code = $match[1];
+        } else {
+            throw Exception("Bad format: initial line is not HTTP status line");
+        }
+
+        $lastk = null;
+
+        for ($i = 1; $i < count($lines); $i++) {
+            $l =& $lines[$i];
+            if (mb_strlen($l) == 0) {
+                $resp->body = implode("\r\n", array_slice($lines, $i + 1));
+                break;
+            }
+            if (preg_match("#^(\S+):\s+(.*)$#", $l, $match)) {
+                $k = $match[1];
+                $v = $match[2];
+
+                if (array_key_exists($k, $resp->headers)) {
+                    if (is_array($resp->headers[$k])) {
+                        $resp->headers[$k][] = $v;
+                    } else {
+                        $resp->headers[$k] = array($resp->headers[$k], $v);
+                    }
+                } else {
+                    $resp->headers[$k] = $v;
+                }
+                $lastk = $k;
+            } else if (preg_match("#^\s+(.*)$#", $l, $match)) {
+                // continuation line
+                if (is_null($lastk)) {
+                    throw Exception("Bad format: initial whitespace in headers");
+                }
+                $h =& $resp->headers[$lastk];
+                if (is_array($h)) {
+                    $n = count($h);
+                    $h[$n-1] .= $match[1];
+                } else {
+                    $h .= $match[1];
+                }
+            }
+        }
+
+        return $resp;
+    }
+}
diff --git a/lib/default.php b/lib/default.php
index f6cc4b725..7ec8558b0 100644
--- a/lib/default.php
+++ b/lib/default.php
@@ -228,6 +228,8 @@ $default =
         array('contentlimit' => null),
         'message' =>
         array('contentlimit' => null),
+        'http' =>
+        array('client' => 'curl'), // XXX: should this be the default?
         'location' =>
         array('namespace' => 1), // 1 = geonames, 2 = Yahoo Where on Earth
         );
diff --git a/lib/httpclient.php b/lib/httpclient.php
index ee894e983..f16e31e10 100644
--- a/lib/httpclient.php
+++ b/lib/httpclient.php
@@ -31,9 +31,6 @@ if (!defined('STATUSNET')) {
     exit(1);
 }
 
-require_once 'HTTP/Request2.php';
-require_once 'HTTP/Request2/Response.php';
-
 /**
  * Useful structure for HTTP responses
  *
@@ -41,42 +38,18 @@ require_once 'HTTP/Request2/Response.php';
  * ways of doing them. This class hides the specifics of what underlying
  * library (curl or PHP-HTTP or whatever) that's used.
  *
- * This extends the HTTP_Request2_Response class with methods to get info
- * about any followed redirects.
- *
  * @category HTTP
- * @package StatusNet
- * @author Evan Prodromou <evan@status.net>
- * @author Brion Vibber <brion@status.net>
- * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
- * @link http://status.net/
+ * @package  StatusNet
+ * @author   Evan Prodromou <evan@status.net>
+ * @license  http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
+ * @link     http://status.net/
  */
-class HTTPResponse extends HTTP_Request2_Response
-{
-    function __construct(HTTP_Request2_Response $response, $url, $redirects=0)
-    {
-        foreach (get_object_vars($response) as $key => $val) {
-            $this->$key = $val;
-        }
-        $this->url = strval($url);
-        $this->redirectCount = intval($redirects);
-    }
-
-    /**
-     * Get the count of redirects that have been followed, if any.
-     * @return int
-     */
-    function getRedirectCount() {
-        return $this->redirectCount;
-    }
 
-    /**
-     * Gets the final target URL, after any redirects have been followed.
-     * @return string URL
-     */
-    function getUrl() {
-        return $this->url;
-    }
+class HTTPResponse
+{
+    public $code = null;
+    public $headers = array();
+    public $body = null;
 }
 
 /**
@@ -86,133 +59,64 @@ class HTTPResponse extends HTTP_Request2_Response
  * ways of doing them. This class hides the specifics of what underlying
  * library (curl or PHP-HTTP or whatever) that's used.
  *
- * This extends the PEAR HTTP_Request2 package:
- * - sends StatusNet-specific User-Agent header
- * - 'follow_redirects' config option, defaulting off
- * - 'max_redirs' config option, defaulting to 10
- * - extended response class adds getRedirectCount() and getUrl() methods
- * - get() and post() convenience functions return body content directly
- *
  * @category HTTP
  * @package  StatusNet
  * @author   Evan Prodromou <evan@status.net>
- * @author   Brion Vibber <brion@status.net>
  * @license  http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
  * @link     http://status.net/
  */
 
-class HTTPClient extends HTTP_Request2
+class HTTPClient
 {
+    static $_client = null;
 
-    function __construct($url=null, $method=self::METHOD_GET, $config=array())
+    static function start()
     {
-        $this->config['max_redirs'] = 10;
-        $this->config['follow_redirects'] = false;
-        parent::__construct($url, $method, $config);
-        $this->setHeader('User-Agent', $this->userAgent());
+        if (!is_null(self::$_client)) {
+            return self::$_client;
+        }
+
+        $type = common_config('http', 'client');
+
+        switch ($type) {
+         case 'curl':
+            self::$_client = new CurlClient();
+            break;
+         default:
+            throw new Exception("Unknown HTTP client type '$type'");
+            break;
+        }
+
+        return self::$_client;
     }
 
-    /**
-     * Convenience function to run a get request and return the response body.
-     * Use when you don't need to get into details of the response.
-     *
-     * @return mixed string on success, false on failure
-     */
-    function get()
+    function head($url, $headers)
     {
-        $this->setMethod(self::METHOD_GET);
-        return $this->doRequest();
+        throw new Exception("HEAD method unimplemented");
     }
 
-    /**
-     * Convenience function to post form data and return the response body.
-     * Use when you don't need to get into details of the response.
-     *
-     * @param array associative array of form data to submit
-     * @return mixed string on success, false on failure
-     */
-    public function post($data=array())
+    function get($url, $headers)
     {
-        $this->setMethod(self::METHOD_POST);
-        if ($data) {
-            $this->addPostParameter($data);
-        }
-        return $this->doRequest();
+        throw new Exception("GET method unimplemented");
     }
 
-    /**
-     * @return mixed string on success, false on failure
-     */
-    protected function doRequest()
+    function post($url, $headers, $body)
     {
-        try {
-            $response = $this->send();
-            $code = $response->getStatus();
-            if (($code < 200) || ($code >= 400)) {
-                return false;
-            }
-            return $response->getBody();
-        } catch (HTTP_Request2_Exception $e) {
-            $this->log(LOG_ERR, $e->getMessage());
-            return false;
-        }
+        throw new Exception("POST method unimplemented");
     }
-    
-    protected function log($level, $detail) {
-        $method = $this->getMethod();
-        $url = $this->getUrl();
-        common_log($level, __CLASS__ . ": HTTP $method $url - $detail");
+
+    function put($url, $headers, $body)
+    {
+        throw new Exception("PUT method unimplemented");
     }
 
-    /**
-     * Pulls up StatusNet's customized user-agent string, so services
-     * we hit can track down the responsible software.
-     */
-    function userAgent()
+    function delete($url, $headers)
     {
-        return "StatusNet/".STATUSNET_VERSION." (".STATUSNET_CODENAME.")";
+        throw new Exception("DELETE method unimplemented");
     }
 
-    function send()
+    function userAgent()
     {
-        $maxRedirs = intval($this->config['max_redirs']);
-        if (empty($this->config['follow_redirects'])) {
-            $maxRedirs = 0;
-        }
-        $redirs = 0;
-        do {
-            try {
-                $response = parent::send();
-            } catch (HTTP_Request2_Exception $e) {
-                $this->log(LOG_ERR, $e->getMessage());
-                throw $e;
-            }
-            $code = $response->getStatus();
-            if ($code >= 200 && $code < 300) {
-                $reason = $response->getReasonPhrase();
-                $this->log(LOG_INFO, "$code $reason");
-            } elseif ($code >= 300 && $code < 400) {
-                $url = $this->getUrl();
-                $target = $response->getHeader('Location');
-                
-                if (++$redirs >= $maxRedirs) {
-                    common_log(LOG_ERR, __CLASS__ . ": Too many redirects: skipping $code redirect from $url to $target");
-                    break;
-                }
-                try {
-                    $this->setUrl($target);
-                    $this->setHeader('Referer', $url);
-                    common_log(LOG_INFO, __CLASS__ . ": Following $code redirect from $url to $target");
-                    continue;
-                } catch (HTTP_Request2_Exception $e) {
-                    common_log(LOG_ERR, __CLASS__ . ": Invalid $code redirect from $url to $target");
-                }
-            } else {
-                $reason = $response->getReasonPhrase();
-                $this->log(LOG_ERR, "$code $reason");
-            }
-            break;
-        } while ($maxRedirs);
-        return new HTTPResponse($response, $this->getUrl(), $redirs);
+        return "StatusNet/".STATUSNET_VERSION." (".STATUSNET_CODENAME.")";
     }
 }
diff --git a/lib/oauthclient.php b/lib/oauthclient.php
index 1a86e2460..f1827726e 100644
--- a/lib/oauthclient.php
+++ b/lib/oauthclient.php
@@ -43,7 +43,7 @@ require_once 'OAuth.php';
  * @link     http://status.net/
  *
  */
-class OAuthClientException extends Exception
+class OAuthClientCurlException extends Exception
 {
 }
 
@@ -97,14 +97,9 @@ class OAuthClient
     function getRequestToken($url)
     {
         $response = $this->oAuthGet($url);
-        $arr = array();
-        parse_str($response, $arr);
-        if (isset($arr['oauth_token']) && isset($arr['oauth_token_secret'])) {
-            $token = new OAuthToken($arr['oauth_token'], @$arr['oauth_token_secret']);
-            return $token;
-        } else {
-            throw new OAuthClientException();
-        }
+        parse_str($response);
+        $token = new OAuthToken($oauth_token, $oauth_token_secret);
+        return $token;
     }
 
     /**
@@ -182,7 +177,7 @@ class OAuthClient
     }
 
     /**
-     * Make a HTTP request.
+     * Make a HTTP request using cURL.
      *
      * @param string $url    Where to make the
      * @param array  $params post parameters
@@ -191,32 +186,40 @@ class OAuthClient
      */
     function httpRequest($url, $params = null)
     {
-        $request = new HTTPClient($url);
-        $request->setConfig(array(
-            'connect_timeout' => 120,
-            'timeout' => 120,
-            'follow_redirects' => true,
-            'ssl_verify_peer' => false,
-        ));
-
-        // Twitter is strict about accepting invalid "Expect" headers
-        $request->setHeader('Expect', '');
+        $options = array(
+            CURLOPT_RETURNTRANSFER => true,
+            CURLOPT_FAILONERROR    => true,
+            CURLOPT_HEADER         => false,
+            CURLOPT_FOLLOWLOCATION => true,
+            CURLOPT_USERAGENT      => 'StatusNet',
+            CURLOPT_CONNECTTIMEOUT => 120,
+            CURLOPT_TIMEOUT        => 120,
+            CURLOPT_HTTPAUTH       => CURLAUTH_ANY,
+            CURLOPT_SSL_VERIFYPEER => false,
+
+            // Twitter is strict about accepting invalid "Expect" headers
+
+            CURLOPT_HTTPHEADER => array('Expect:')
+        );
 
         if (isset($params)) {
-            $request->setMethod(HTTP_Request2::METHOD_POST);
-            $request->setBody($params);
+            $options[CURLOPT_POST]       = true;
+            $options[CURLOPT_POSTFIELDS] = $params;
         }
 
-        try {
-            $response = $request->send();
-            $code = $response->getStatus();
-            if ($code < 200 || $code >= 400) {
-                throw new OAuthClientException($response->getBody(), $code);
-            }
-            return $response->getBody();
-        } catch (Exception $e) {
-            throw new OAuthClientException($e->getMessage(), $e->getCode());
+        $ch = curl_init($url);
+        curl_setopt_array($ch, $options);
+        $response = curl_exec($ch);
+
+        if ($response === false) {
+            $msg  = curl_error($ch);
+            $code = curl_errno($ch);
+            throw new OAuthClientCurlException($msg, $code);
         }
+
+        curl_close($ch);
+
+        return $response;
     }
 
 }
diff --git a/lib/ping.php b/lib/ping.php
index 2797c1b2d..175bf8440 100644
--- a/lib/ping.php
+++ b/lib/ping.php
@@ -44,18 +44,20 @@ function ping_broadcast_notice($notice) {
 																array('nickname' => $profile->nickname)),
 											   $tags));
 
-            $request = new HTTPClient($notify_url, HTTP_Request2::METHOD_POST);
-            $request->setHeader('Content-Type', 'text/xml');
-            $request->setBody($req);
-            $httpResponse = $request->send();
+            $context = stream_context_create(array('http' => array('method' => "POST",
+                                                                   'header' =>
+                                                                   "Content-Type: text/xml\r\n".
+                                                                   "User-Agent: StatusNet/".STATUSNET_VERSION."\r\n",
+                                                                   'content' => $req)));
+            $file = file_get_contents($notify_url, false, $context);
 
-            if (!$httpResponse || mb_strlen($httpResponse->getBody()) == 0) {
+            if ($file === false || mb_strlen($file) == 0) {
                 common_log(LOG_WARNING,
                            "XML-RPC empty results for ping ($notify_url, $notice->id) ");
                 continue;
             }
 
-            $response = xmlrpc_decode($httpResponse->getBody());
+            $response = xmlrpc_decode($file);
 
             if (is_array($response) && xmlrpc_is_fault($response)) {
                 common_log(LOG_WARNING,
diff --git a/lib/snapshot.php b/lib/snapshot.php
index 6829e8a75..ede846e5b 100644
--- a/lib/snapshot.php
+++ b/lib/snapshot.php
@@ -172,11 +172,26 @@ class Snapshot
     {
         // XXX: Use OICU2 and OAuth to make authorized requests
 
+        $postdata = http_build_query($this->stats);
+
+        $opts =
+          array('http' =>
+                array(
+                      'method'  => 'POST',
+                      'header'  => 'Content-type: '.
+                                   'application/x-www-form-urlencoded',
+                      'content' => $postdata,
+                      'user_agent' => 'StatusNet/'.STATUSNET_VERSION
+                      )
+                );
+
+        $context = stream_context_create($opts);
+
         $reporturl = common_config('snapshot', 'reporturl');
 
-        $request = new HTTPClient($reporturl, HTTP_Request2::METHOD_POST);
-        $request->addPostParameter($this->stats);
-        $request->send();
+        $result = @file_get_contents($reporturl, false, $context);
+
+        return $result;
     }
 
     /**
-- 
cgit v1.2.3-54-g00ecf