follow-up r61655 fill out the rest of the missing messages
authorMark A. Hershberger <mah@users.mediawiki.org>
Wed, 3 Feb 2010 06:19:47 +0000 (06:19 +0000)
committerMark A. Hershberger <mah@users.mediawiki.org>
Wed, 3 Feb 2010 06:19:47 +0000 (06:19 +0000)
Add cookie handling to HttpFunctions.

includes/AutoLoader.php
includes/HttpFunctions.php
languages/messages/MessagesEn.php
maintenance/language/messages.inc
tests/HttpTest.php

index 2df2e8d..d6288f5 100644 (file)
@@ -36,6 +36,8 @@ $wgAutoloadLocalClasses = array(
        'ChangesFeed' => 'includes/ChangesFeed.php',
        'ChangeTags' => 'includes/ChangeTags.php',
        'ChannelFeed' => 'includes/Feed.php',
+       'Cookie' => 'includes/HttpFunctions.php',
+       'CookieJar' => 'includes/HttpFunctions.php',
        'ConcatenatedGzipHistoryBlob' => 'includes/HistoryBlob.php',
        'ConfEditor' => 'includes/ConfEditor.php',
        'ConfEditorParseError' => 'includes/ConfEditor.php',
index 8a8dc50..a0c1a90 100644 (file)
@@ -15,6 +15,15 @@ class Http {
         * @param $method string HTTP method. Usually GET/POST
         * @param $url string Full URL to act on
         * @param $options options to pass to HttpRequest object
+        *                               Possible keys for the array:
+        *                                      timeout                   Timeout length in seconds
+        *                                      postData                  An array of key-value pairs or a url-encoded form data
+        *                                      proxy                     The proxy to use.      Will use $wgHTTPProxy (if set) otherwise.
+        *                                      noProxy                   Override $wgHTTPProxy (if set) and don't use any proxy at all.
+        *                                      sslVerifyHost     (curl only) Verify the SSL certificate
+        *                                      caInfo                    (curl only) Provide CA information
+        *                                      maxRedirects      Maximum number of redirects to follow (defaults to 5)
+        *                                      followRedirects   Whether to follow redirects (defaults to true)
         * @returns mixed (bool)false on failure or a string on success
         */
        public static function request( $method, $url, $options = array() ) {
@@ -124,21 +133,21 @@ class HttpRequest {
        protected $url;
        protected $parsedUrl;
        protected $callback;
+       protected $maxRedirects = 5;
+       protected $followRedirects = true;
+
+       protected $cookieJar;
+
+       protected $headerList = array();
+       protected $respVersion = "0.9";
+       protected $respStatus = "0.1";
+       protected $respHeaders = array();
+
        public $status;
 
        /**
         * @param $url   string url to use
-        * @param $options array (optional) extra params to pass
-        *                               Possible keys for the array:
-        *                                      method
-        *                                      timeout
-        *                                      targetFilePath
-        *                                      requestKey
-        *                                      postData
-        *                                      proxy
-        *                                      noProxy
-        *                                      sslVerifyHost
-        *                                      caInfo
+        * @param $options array (optional) extra params to pass (see Http::request())
         */
        function __construct( $url, $options = array() ) {
                global $wgHTTPTimeout;
@@ -158,8 +167,8 @@ class HttpRequest {
                        $this->timeout = $wgHTTPTimeout;
                }
 
-               $members = array( "targetFilePath", "requestKey", "postData",
-                       "proxy", "noProxy", "sslVerifyHost", "caInfo", "method" );
+               $members = array( "postData", "proxy", "noProxy", "sslVerifyHost", "caInfo",
+                                                 "method", "followRedirects", "maxRedirects" );
                foreach ( $members as $o ) {
                        if ( isset($options[$o]) ) {
                                $this->$o = $options[$o];
@@ -175,7 +184,8 @@ class HttpRequest {
                if ( !Http::$httpEngine ) {
                        Http::$httpEngine = function_exists( 'curl_init' ) ? 'curl' : 'php';
                } elseif ( Http::$httpEngine == 'curl' && !function_exists( 'curl_init' ) ) {
-                       throw new MWException( __METHOD__.': curl (http://php.net/curl) is not installed, but Http::$httpEngine is set to "curl"' );
+                       throw new MWException( __METHOD__.': curl (http://php.net/curl) is not installed, but'.
+                                                                  ' Http::$httpEngine is set to "curl"' );
                }
 
                switch( Http::$httpEngine ) {
@@ -183,8 +193,8 @@ class HttpRequest {
                        return new CurlHttpRequest( $url, $options );
                case 'php':
                        if ( !wfIniGetBool( 'allow_url_fopen' ) ) {
-                               throw new MWException( __METHOD__.': allow_url_fopen needs to be enabled for pure PHP http requests to work. '.
-                                       'If possible, curl should be used instead.      See http://php.net/curl.' );
+                               throw new MWException( __METHOD__.': allow_url_fopen needs to be enabled for pure PHP'.
+                                       ' http requests to work. If possible, curl should be used instead. See http://php.net/curl.' );
                        }
                        return new PhpHttpRequest( $url, $options );
                default:
@@ -208,7 +218,6 @@ class HttpRequest {
        public function proxySetup() {
                global $wgHTTPProxy;
 
-
                if ( $this->proxy ) {
                        return;
                }
@@ -247,6 +256,9 @@ class HttpRequest {
        public function getHeaderList() {
                $list = array();
 
+               if( $this->cookieJar ) {
+                       $this->reqHeaders['Cookie'] = $this->cookieJar->serializeToHttpRequest();
+               }
                foreach($this->reqHeaders as $name => $value) {
                        $list[] = "$name: $value";
                }
@@ -262,7 +274,8 @@ class HttpRequest {
        }
 
        /**
-        * A generic callback to read in the response from a remote server
+        * A generic callback to read the body of the response from a remote
+        * server.
         * @param $fh handle
         * @param $content string
         */
@@ -302,13 +315,265 @@ class HttpRequest {
                        $this->setUserAgent(Http::userAgent());
                }
        }
+
+       protected function parseHeader() {
+               $lastname = "";
+               foreach( $this->headerList as $header ) {
+                       if( preg_match( "#^HTTP/([0-9.]+) (.*)#", $header, $match ) ) {
+                               $this->respVersion = $match[1];
+                               $this->respStatus = $match[2];
+                       } elseif( preg_match( "#^[ \t]#", $header ) ) {
+                               $last = count($this->respHeaders[$lastname]) - 1;
+                               $this->respHeaders[$lastname][$last] .= "\r\n$header";
+                       } elseif( preg_match( "#^([^:]*):[\t ]*(.*)#", $header, $match ) ) {
+                               $this->respHeaders[strtolower( $match[1] )][] = $match[2];
+                               $lastname = strtolower( $match[1] );
+                       }
+               }
+
+               $this->parseCookies();
+       }
+
+       /**
+        * Returns an associative array of response headers after the
+        * request has been executed.  Because some headers
+        * (e.g. Set-Cookie) can appear more than once the, each value of
+        * the associative array is an array of the values given.
+        * @return array
+        */
+       public function getResponseHeaders() {
+               if( !$this->respHeaders ) {
+                       $this->parseHeader();
+               }
+               return $this->respHeaders;
+       }
+
+       /**
+        * Tells the HttpRequest object to use this pre-loaded CookieJar.
+        * @param $jar CookieJar
+        */
+       public function setCookieJar( $jar ) {
+               $this->cookieJar = $jar;
+       }
+
+       /**
+        * Returns the cookie jar in use.
+        * @returns CookieJar
+        */
+       public function getCookieJar() {
+               if( !$this->respHeaders ) {
+                       $this->parseHeader();
+               }
+               return $this->cookieJar;
+       }
+
+       /**
+        * Sets a cookie.  Used before a request to set up any individual
+        * cookies.      Used internally after a request to parse the
+        * Set-Cookie headers.
+        * @see Cookie::set
+        */
+       public function setCookie( $name, $value = null, $attr = null) {
+               if( !$this->cookieJar ) {
+                       $this->cookieJar = new CookieJar;
+               }
+               $this->cookieJar->setCookie($name, $value, $attr);
+       }
+
+       /**
+        * Parse the cookies in the response headers and store them in the cookie jar.
+        */
+       protected function parseCookies() {
+               if( isset( $this->respHeaders['set-cookie'] ) ) {
+                       if( !$this->cookieJar ) {
+                               $this->cookieJar = new CookieJar;
+                       }
+                       $url = parse_url( $this->getFinalUrl() );
+                       foreach( $this->respHeaders['set-cookie'] as $cookie ) {
+                               $this->cookieJar->parseCookieResponseHeader( $cookie, $url['host'] );
+                       }
+               }
+       }
+
+       /**
+        * Returns the final URL after all redirections.
+        * @returns string
+        */
+       public function getFinalUrl() {
+               $finalUrl = $this->url;
+               if ( isset( $this->respHeaders['location'] ) ) {
+                       $redir = $this->respHeaders['location'];
+                       $finalUrl = $redir[count($redir) - 1];
+               }
+
+               return $finalUrl;
+       }
 }
 
+
+class Cookie {
+       protected $name;
+       protected $value;
+       protected $expires;
+       protected $path;
+       protected $domain;
+       protected $isSessionKey = true;
+       // TO IMPLEMENT  protected $secure
+       // TO IMPLEMENT? protected $maxAge (add onto expires)
+       // TO IMPLEMENT? protected $version
+       // TO IMPLEMENT? protected $comment
+
+       function __construct( $name, $value, $attr ) {
+               $this->name = $name;
+               $this->set( $value, $attr );
+       }
+
+       /**
+        * Sets a cookie.  Used before a request to set up any individual
+        * cookies.      Used internally after a request to parse the
+        * Set-Cookie headers.
+        * @param $name string the name of the cookie
+        * @param $value string the value of the cookie
+        * @param $attr array possible key/values:
+        *              expires  A date string
+        *              path     The path this cookie is used on
+        *              domain   Domain this cookie is used on
+        */
+       public function set( $value, $attr ) {
+               $this->value = $value;
+               if( isset( $attr['expires'] ) ) {
+                       $this->isSessionKey = false;
+                       $this->expires = strtotime( $attr['expires'] );
+               }
+               if( isset( $attr['path'] ) ) {
+                       $this->path = $attr['path'];
+               } else {
+                       $this->path = "/";
+               }
+               if( isset( $attr['domain'] ) ) {
+                       $this->domain = $attr['domain'];
+               } else {
+                       throw new MWException("You must specify a domain.");
+               }
+       }
+
+       /**
+        * Serialize the cookie jar into a format useful for HTTP Request headers.
+        * @param $path string the path that will be used. Required.
+        * @param $domain string the domain that will be used. Required.
+        * @return string
+        */
+       public function serializeToHttpRequest( $path, $domain ) {
+               $ret = "";
+
+               if( $this->canServeDomain( $domain )
+                               && $this->canServePath( $path )
+                               && $this->isUnExpired() ) {
+                       $ret = $this->name ."=". $this->value;
+               }
+
+               return $ret;
+       }
+
+       protected function canServeDomain( $domain ) {
+               if( $this->domain && substr_compare( $domain, $this->domain, -strlen( $this->domain ),
+                                                                                        strlen( $this->domain ), TRUE ) == 0 ) {
+                       return true;
+               }
+               return false;
+       }
+
+       protected function canServePath( $path ) {
+               if( $this->path && substr_compare( $this->path, $path, 0, strlen( $this->path ) ) == 0 ) {
+                       return true;
+               }
+               return false;
+       }
+
+       protected function isUnExpired() {
+               if( $this->isSessionKey || $this->expires > time() ) {
+                       return true;
+               }
+               return false;
+       }
+
+}
+
+class CookieJar {
+       private $cookie;
+
+       /**
+        * Set a cookie in the cookie jar.      Make sure only one cookie per-name exists.
+        * @see Cookie::set()
+        */
+       public function setCookie ($name, $value, $attr) {
+               /* cookies: case insensitive, so this should work.
+                * We'll still send the cookies back in the same case we got them, though.
+                */
+               $index = strtoupper($name);
+               if( isset( $this->cookie[$index] ) ) {
+                       $this->cookie[$index]->set( $value, $attr );
+               } else {
+                       $this->cookie[$index] = new Cookie( $name, $value, $attr );
+               }
+       }
+
+       /**
+        * @see Cookie::serializeToHttpRequest
+        */
+       public function serializeToHttpRequest( $path, $domain ) {
+               $cookies = array();
+
+               foreach( $this->cookie as $c ) {
+                       $serialized = $c->serializeToHttpRequest( $path, $domain );
+                       if ( $serialized ) $cookies[] = $serialized;
+               }
+
+               return implode("; ", $cookies);
+       }
+
+       /**
+        * Parse the content of an Set-Cookie HTTP Response header.
+        * @param $cookie string
+        */
+       public function parseCookieResponseHeader ( $cookie, $domain = null ) {
+               $len = strlen( "Set-Cookie:" );
+               if ( substr_compare( "Set-Cookie:", $cookie, 0, $len, TRUE ) === 0 ) {
+                       $cookie = substr( $cookie, $len );
+               }
+
+               $bit = array_map( 'trim', explode( ";", $cookie ) );
+               list($name, $value) = explode( "=", array_shift( $bit ), 2 );
+               $attr = array();
+               foreach( $bit as $piece ) {
+                       $parts = explode( "=", $piece );
+                       if( count( $parts ) > 1 ) {
+                               $attr[strtolower( $parts[0] )] = $parts[1];
+                       } else {
+                               $attr[strtolower( $parts[0] )] = true;
+                       }
+               }
+               $this->setCookie( $name, $value, $attr );
+       }
+}
+
+
 /**
  * HttpRequest implemented using internal curl compiled into PHP
  */
 class CurlHttpRequest extends HttpRequest {
+       static $curlMessageMap = array(
+               6 => 'http-host-unreachable',
+               28 => 'http-timed-out'
+       );
+
        protected $curlOptions = array();
+       protected $headerText = "";
+
+       protected function readHeader( $fh, $content ) {
+               $this->headerText .= $content;
+               return strlen( $content );
+       }
 
        public function execute() {
                parent::execute();
@@ -319,6 +584,9 @@ class CurlHttpRequest extends HttpRequest {
                $this->curlOptions[CURLOPT_TIMEOUT] = $this->timeout;
                $this->curlOptions[CURLOPT_HTTP_VERSION] = CURL_HTTP_VERSION_1_0;
                $this->curlOptions[CURLOPT_WRITEFUNCTION] = $this->callback;
+               $this->curlOptions[CURLOPT_HEADERFUNCTION] = array($this, "readHeader");
+               $this->curlOptions[CURLOPT_FOLLOWLOCATION] = $this->followRedirects;
+               $this->curlOptions[CURLOPT_MAXREDIRS] = $this->maxRedirects;
 
                /* not sure these two are actually necessary */
                if(isset($this->reqHeaders['Referer'])) {
@@ -354,8 +622,15 @@ class CurlHttpRequest extends HttpRequest {
                curl_setopt_array( $curlHandle, $this->curlOptions );
 
                if ( false === curl_exec( $curlHandle ) ) {
-                       // re-using already translated error messages
-                       $this->status->fatal( 'upload-curl-error'.curl_errno( $curlHandle ).'-text' );
+                       $code = curl_error( $curlHandle );
+
+                       if ( isset( self::$curlMessageMap[$code] ) ) {
+                               $this->status->fatal( self::$curlMessageMap[$code] );
+                       } else {
+                               $this->status->fatal( 'http-curl-error', curl_error( $curlHandle ) );
+                       }
+               } else {
+                       $this->headerList = explode("\r\n", $this->headerText);
                }
 
                curl_close( $curlHandle );
@@ -394,6 +669,12 @@ class PhpHttpRequest extends HttpRequest {
                        $options['request_fulluri'] = true;
                }
 
+               if ( !$this->followRedirects ) {
+                       $options['max_redirects'] = 0;
+               } else {
+                       $options['max_redirects'] = $this->maxRedirects;
+               }
+
                $options['method'] = $this->method;
                $options['timeout'] = $this->timeout;
                $options['header'] = implode("\r\n", $this->getHeaderList());
@@ -428,8 +709,7 @@ class PhpHttpRequest extends HttpRequest {
                        $this->status->fatal( 'http-timed-out', $this->url );
                        return $this->status;
                }
-
-               $this->headers = $result['wrapper_data'];
+               $this->headerList = $result['wrapper_data'];
 
                while ( !feof( $fh ) ) {
                        $buf = fread( $fh, 8192 );
index 5983135..7ae34e6 100644 (file)
@@ -2059,7 +2059,7 @@ To make your summary appear there, you will need to manually edit it.
 [[$1|thumb]]",
 'fileexists-extension'        => "A file with a similar name exists: [[$2|thumb]]
 * Name of the uploading file: '''<tt>[[:$1]]</tt>'''
-* Name of the existing file: '''<tt>[[:$2]]</tt>''' 
+* Name of the existing file: '''<tt>[[:$2]]</tt>'''
 Please choose a different name.",
 'fileexists-thumbnail-yes'    => "The file seems to be an image of reduced size ''(thumbnail)''.
 [[$1|thumb]]
@@ -2153,9 +2153,13 @@ For optimal security, img_auth.php is disabled.',
 'img-auth-noread'       => 'User does not have access to read "$1".',
 
 # HTTP errors
-'http-invalid-url'    => 'Invalid URL: $1',
+'http-invalid-url' => 'Invalid URL: $1',
 'http-invalid-scheme' => 'URLs with the "$1" scheme are not supported',
-'http-request-error'  => 'HTTP request failed due to unknown error.',
+'http-request-error' => 'HTTP request failed due to unknown error.',
+'http-read-error' => 'HTTP read error.',
+'http-timed-out' => 'HTTP request timed out.',
+'http-curl-error' => 'Error fetching URL: $1',
+'http-host-unreachable' => 'Could not reach URL',
 
 # Some likely curl errors. More could be added from <http://curl.haxx.se/libcurl/c/libcurl-errors.html>
 'upload-curl-error6'       => 'Could not reach URL',
@@ -2417,7 +2421,7 @@ It now redirects to [[$2]].',
 'ancientpages-summary'            => '', # do not translate or duplicate this message to other languages
 'move'                            => 'Move',
 'movethispage'                    => 'Move this page',
-'unusedimagestext'                => 'The following files exist but are not embedded in any page. 
+'unusedimagestext'                => 'The following files exist but are not embedded in any page.
 Please note that other web sites may link to a file with a direct URL, and so may still be listed here despite being in active use.',
 'unusedcategoriestext'            => 'The following category pages exist, although no other page or category makes use of them.',
 'notargettitle'                   => 'No target',
index cb87bcc..0e0d265 100644 (file)
@@ -842,7 +842,7 @@ $wgMessageStructure = array(
                'powersearch-redir',
                'powersearch-field',
                'powersearch-togglelabel',
-               'powersearch-toggleall', 
+               'powersearch-toggleall',
                'powersearch-togglenone',
                'search-external',
                'searchdisabled',
@@ -1275,6 +1275,10 @@ $wgMessageStructure = array(
                'http-invalid-url',
                'http-invalid-scheme',
                'http-request-error',
+               'http-read-error',
+               'http-timed-out',
+               'http-curl-error',
+               'http-host-unreachable',
        ),
 
        'upload-curl-errors' => array(
index e81c71d..79ce2c7 100644 (file)
@@ -1,7 +1,9 @@
 <?php
 
-if ( !defined( 'MW_PHPUNIT_TEST' ) ) {
-       require_once( 'bootstrap.php' );
+class MockCookie extends Cookie {
+       public function canServeDomain($arg) { return parent::canServeDomain($arg); }
+       public function canServePath($arg) { return parent::canServePath($arg); }
+       public function isUnExpired() { return parent::isUnExpired(); }
 }
 
 class HttpTest extends PhpUnit_Framework_TestCase {
@@ -131,6 +133,8 @@ class HttpTest extends PhpUnit_Framework_TestCase {
 
                if($proxy) {
                        $opt['proxy'] = $proxy;
+               } elseif( $proxy === false ) {
+                       $opt['noProxy'] = true;
                }
 
                /* no postData here because the only request I could find in code so far didn't have any */
@@ -208,6 +212,8 @@ class HttpTest extends PhpUnit_Framework_TestCase {
 
                if($proxy) {
                        $opt['proxy'] = $proxy;
+               } elseif( $proxy === false ) {
+                       $opt['noProxy'] = true;
                }
 
                foreach ( $this->test_geturl as $u ) {
@@ -241,6 +247,8 @@ class HttpTest extends PhpUnit_Framework_TestCase {
 
                if($proxy) {
                        $opt['proxy'] = $proxy;
+               } elseif( $proxy === false ) {
+                       $opt['noProxy'] = true;
                }
 
                foreach ( $this->test_posturl as $u => $postData ) {
@@ -277,6 +285,11 @@ class HttpTest extends PhpUnit_Framework_TestCase {
                self::runHTTPGets(self::$proxy);
                self::runHTTPPosts(self::$proxy);
                self::runHTTPRequests(self::$proxy);
+
+               // Set false here to do noProxy
+               self::runHTTPGets(false);
+               self::runHTTPPosts(false);
+               self::runHTTPRequests(false);
        }
 
        function testProxyDefault() {
@@ -308,4 +321,150 @@ class HttpTest extends PhpUnit_Framework_TestCase {
        function testIsValidUrl() {
        }
 
+       function testSetCookie() {
+               $c = new MockCookie( "name", "value",
+                                                        array(
+                                                                "domain" => ".example.com",
+                                                                "path" => "/path/",
+                                                        ) );
+
+               $this->assertFalse($c->canServeDomain("example.com"));
+               $this->assertFalse($c->canServeDomain("www.example.net"));
+               $this->assertTrue($c->canServeDomain("www.example.com"));
+
+               $this->assertFalse($c->canServePath("/"));
+               $this->assertFalse($c->canServePath("/bogus/path/"));
+               $this->assertFalse($c->canServePath("/path"));
+               $this->assertTrue($c->canServePath("/path/"));
+
+               $this->assertTrue($c->isUnExpired());
+
+               $this->assertEquals("", $c->serializeToHttpRequest("/path/", "www.example.net"));
+               $this->assertEquals("", $c->serializeToHttpRequest("/", "www.example.com"));
+               $this->assertEquals("name=value", $c->serializeToHttpRequest("/path/", "www.example.com"));
+
+               $c = new MockCookie( "name", "value",
+                                                array(
+                                                        "domain" => ".example.com",
+                                                        "path" => "/path/",
+                                                        "expires" => "January 1, 1990",
+                                                ) );
+               $this->assertFalse($c->isUnExpired());
+               $this->assertEquals("", $c->serializeToHttpRequest("/path/", "www.example.com"));
+
+               $c = new MockCookie( "name", "value",
+                                                array(
+                                                        "domain" => ".example.com",
+                                                        "path" => "/path/",
+                                                        "expires" => "January 1, 2999",
+                                                ) );
+               $this->assertTrue($c->isUnExpired());
+               $this->assertEquals("name=value", $c->serializeToHttpRequest("/path/", "www.example.com"));
+
+
+       }
+
+       function testCookieJarSetCookie() {
+               $cj = new CookieJar;
+               $cj->setCookie( "name", "value",
+                                                array(
+                                                        "domain" => ".example.com",
+                                                        "path" => "/path/",
+                                                ) );
+               $cj->setCookie( "name2", "value",
+                                                array(
+                                                        "domain" => ".example.com",
+                                                        "path" => "/path/sub",
+                                                ) );
+               $cj->setCookie( "name3", "value",
+                                                array(
+                                                        "domain" => ".example.com",
+                                                        "path" => "/",
+                                                ) );
+               $cj->setCookie( "name4", "value",
+                                                array(
+                                                        "domain" => ".example.net",
+                                                        "path" => "/path/",
+                                                ) );
+               $cj->setCookie( "name5", "value",
+                                                array(
+                                                        "domain" => ".example.net",
+                                                        "path" => "/path/",
+                                                        "expires" => "January 1, 1999",
+                                                ) );
+
+               $this->assertEquals("name4=value", $cj->serializeToHttpRequest("/path/", "www.example.net"));
+               $this->assertEquals("name3=value", $cj->serializeToHttpRequest("/", "www.example.com"));
+               $this->assertEquals("name=value; name3=value", $cj->serializeToHttpRequest("/path/", "www.example.com"));
+
+               $cj->setCookie( "name5", "value",
+                                                array(
+                                                        "domain" => ".example.net",
+                                                        "path" => "/path/",
+                                                        "expires" => "January 1, 2999",
+                                                ) );
+               $this->assertEquals("name4=value; name5=value", $cj->serializeToHttpRequest("/path/", "www.example.net"));
+
+               $cj->setCookie( "name4", "value",
+                                                array(
+                                                        "domain" => ".example.net",
+                                                        "path" => "/path/",
+                                                        "expires" => "January 1, 1999",
+                                                ) );
+               $this->assertEquals("name5=value", $cj->serializeToHttpRequest("/path/", "www.example.net"));
+       }
+
+       function testParseResponseHeader() {
+               $cj = new CookieJar;
+
+               $h[] = "Set-Cookie: name4=value; domain=.example.com; path=/; expires=Mon, 09-Dec-2999 13:46:00 GMT";
+               $cj->parseCookieResponseHeader( $h[0] );
+               $this->assertEquals("name4=value", $cj->serializeToHttpRequest("/", "www.example.com"));
+
+               $h[] = "name4=value2; domain=.example.com; path=/path/; expires=Mon, 09-Dec-2999 13:46:00 GMT";
+               $cj->parseCookieResponseHeader( $h[1] );
+               $this->assertEquals("", $cj->serializeToHttpRequest("/", "www.example.com"));
+               $this->assertEquals("name4=value2", $cj->serializeToHttpRequest("/path/", "www.example.com"));
+
+               $h[] = "name5=value3; domain=.example.com; path=/path/; expires=Mon, 09-Dec-2999 13:46:00 GMT";
+               $cj->parseCookieResponseHeader( $h[2] );
+               $this->assertEquals("name4=value2; name5=value3", $cj->serializeToHttpRequest("/path/", "www.example.com"));
+
+               $h[] = "name6=value3; domain=.example.net; path=/path/; expires=Mon, 09-Dec-1999 13:46:00 GMT";
+               $cj->parseCookieResponseHeader( $h[3] );
+               $this->assertEquals("", $cj->serializeToHttpRequest("/path/", "www.example.net"));
+
+               $h[] = "name6=value4; domain=.example.net; path=/path/; expires=Mon, 09-Dec-2999 13:46:00 GMT";
+               $cj->parseCookieResponseHeader( $h[4] );
+               $this->assertEquals("name6=value4", $cj->serializeToHttpRequest("/path/", "www.example.net"));
+       }
+
+       function runCookieRequests() {
+               $r = HttpRequest::factory( "http://www.php.net/manual" );
+               $r->execute();
+
+               $jar = $r->getCookieJar();
+
+               $this->assertThat( $jar, $this->isInstanceOf( 'CookieJar' ) );
+               $this->assertRegExp( '/^COUNTRY=.*; LAST_LANG=.*$/', $jar->serializeToHttpRequest( "/search?q=test", "www.php.net" ) );
+               $this->assertEquals( '', $jar->serializeToHttpRequest( "/search?q=test", "www.php.com" ) );
+       }
+
+       function testCookieRequestDefault() {
+               Http::$httpEngine = false;
+               self::runCookieRequests();
+       }
+       function testCookieRequestPhp() {
+               Http::$httpEngine = 'php';
+               self::runCookieRequests();
+       }
+       function testCookieRequestCurl() {
+               if (!self::$has_curl ) {
+                       $this->markTestIncomplete("This test requires curl.");
+               }
+
+               Http::$httpEngine = 'curl';
+               self::runCookieRequests();
+       }
+
 }
\ No newline at end of file