? includes/common.inc.patched
? includes/common.inc.wrapped
? sites/localhost
Index: includes/common.inc
===================================================================
RCS file: /cvs/drupal/drupal/includes/common.inc,v
retrieving revision 1.1285
diff -u -p -r1.1285 common.inc
--- includes/common.inc	14 Jan 2011 08:37:20 -0000	1.1285
+++ includes/common.inc	14 Jan 2011 21:27:48 -0000
@@ -730,37 +730,51 @@ function drupal_access_denied() {
  *
  * @param $url
  *   A string containing a fully qualified URI.
- * @param array $options
- *   (optional) An array that can have one or more of the following elements:
- *   - headers: An array containing request headers to send as name/value pairs.
- *   - method: A string containing the request method. Defaults to 'GET'.
- *   - data: A string containing the request body, formatted as
- *     'param=value&param=value&...'. Defaults to NULL.
- *   - max_redirects: An integer representing how many times a redirect
- *     may be followed. Defaults to 3.
- *   - timeout: A float representing the maximum number of seconds the function
- *     call may take. The default is 30 seconds. If a timeout occurs, the error
- *     code is set to the HTTP_REQUEST_TIMEOUT constant.
- *   - context: A context resource created with stream_context_create().
- *
- * @return object
- *   An object that can have one or more of the following components:
- *   - request: A string containing the request body that was sent.
- *   - code: An integer containing the response status code, or the error code
- *     if an error occurred.
- *   - protocol: The response protocol (e.g. HTTP/1.1 or HTTP/1.0).
- *   - status_message: The status message from the response, if a response was
- *     received.
- *   - redirect_code: If redirected, an integer containing the initial response
- *     status code.
- *   - redirect_url: If redirected, a string containing the redirection location.
- *   - error: If an error occurred, the error message. Otherwise not set.
- *   - headers: An array containing the response headers as name/value pairs.
- *     HTTP header names are case-insensitive (RFC 2616, section 4.2), so for
- *     easy access the array keys are returned in lower case.
- *   - data: A string containing the response body that was received.
+ * @param $options
+ *   (optional) An array which can have one or more of following keys:
+ *   - headers
+ *       An array containing request headers to send as name/value pairs.
+ *   - method
+ *       A string containing the request method. Defaults to 'GET'.
+ *   - data
+ *       A string containing the request body. Defaults to NULL.
+ *   - max_redirects
+ *       An integer representing how many times a redirect may be followed.
+ *       Defaults to 3.
+ *   - timeout
+ *       A float representing the maximum number of seconds the function call
+ *       may take. The default is 30 seconds. If a timeout occurs, the error
+ *       code is set to the HTTP_REQUEST_TIMEOUT constant.
+ *   - context
+ *       A context resource created with stream_context_create().
+ * @param fp_in
+ *   An optional file pointer, if the socket is provided externally
+ * @return
+ *   An object which can have one or more of the following parameters:
+ *   - request
+ *       A string containing the request body that was sent.
+ *   - code
+ *       An integer containing the response status code, or the error code if
+ *       an error occurred.
+ *   - protocol
+ *       The response protocol (e.g. HTTP/1.1 or HTTP/1.0).
+ *   - status_message
+ *       The status message from the response, if a response was received.
+ *   - redirect_code
+ *       If redirected, an integer containing the initial response status code.
+ *   - redirect_url
+ *       If redirected, a string containing the redirection location.
+ *   - error
+ *       If an error occurred, the error message. Otherwise not set.
+ *   - headers
+ *       An array containing the response headers as name/value pairs. HTTP
+ *       header names are case-insensitive (RFC 2616, section 4.2), so for easy
+ *       access the array keys are returned in lower case.
+ *   - data
+ *       A string containing the response body that was received.
  */
-function drupal_http_request($url, array $options = array()) {
+
+function drupal_http_request($url, array $options = array(), $fp_in = NULL) {
   $result = new stdClass();
 
   // Parse the URL and make sure we can handle the schema.
@@ -768,45 +782,89 @@ function drupal_http_request($url, array
 
   if ($uri == FALSE) {
     $result->error = 'unable to parse URL';
-    $result->code = -1001;
+    $result->code  = -1001;
     return $result;
   }
 
   if (!isset($uri['scheme'])) {
     $result->error = 'missing schema';
-    $result->code = -1002;
+    $result->code  = -1002;
     return $result;
   }
 
-  timer_start(__FUNCTION__);
-
   // Merge the default options.
   $options += array(
-    'headers' => array(),
-    'method' => 'GET',
-    'data' => NULL,
+    'headers'       => array(),
+    'method'        => 'GET',
+    'protocol'      => 'HTTP/1.0',
+    'data'          => NULL,
     'max_redirects' => 3,
-    'timeout' => 30.0,
-    'context' => NULL,
+    'timeout'       => 30.0,
+    'context'       => NULL,
   );
-  // stream_socket_client() requires timeout to be a float.
-  $options['timeout'] = (float) $options['timeout'];
+  $timeout = (float) $options['timeout'];
+
+  // Merge the default headers.
+  //
+  // RFC 2616: "... non-standard ports MUST, default ports MAY be included ...".
+  // When port is implicit (standard port), allow Header Host without information
+  // for port, else some rewrite rules could be broken when checking the host
+  // that do not take into account the port number.
+  $options['headers'] += array(
+    'User-Agent' => 'Drupal (+http://drupal.org/)',
+    'Host'       => isset($uri['port']) ? (":" . $uri['port']) : $uri['host'],
+  );
+
+  // Proxy setup - normal sites don't need a proxy.
+  $proxy_server = ($options['method'] == 'CONNECT')
+                ? '': variable_get('proxy_server', '');
+  if ($proxy_server && __drupal_http_use_proxy($uri['host'])) {
+    // Since the url is passed as the path, we won't use the parsed query.
+    unset($uri['query']);
+    $uri['path'] = $url;
+
+    // Set the scheme so we open a socket to the proxy server.
+    $uri['scheme'] = ($uri['scheme'] == 'https') ? 'proxy_https' : 'proxy_http';
+
+    if ($proxy_username = variable_get('proxy_username', '')) {
+      $proxy_password = variable_get('proxy_password', '');
+      // As described by RFC-2617 for Basic Authentication Scheme.
+      // Digest Access Authentication Scheme is not supported (by now)
+      $options['headers']['Proxy-Authorization'] = 'Basic ' . base64_encode($proxy_username . (!empty($proxy_password) ? ":" . $proxy_password : ''));
+    }
+    // Some proxies reject requests with any User-Agent headers, while others
+    // require a specific one.
+    $proxy_user_agent = variable_get('proxy_user_agent', '');
+    // The default value matches neither condition.
+    if ($proxy_user_agent === NULL) {
+      unset($options['headers']['User-Agent']);
+    }
+    elseif ($proxy_user_agent) {
+      $options['headers']['User-Agent'] = $proxy_user_agent;
+    }
+  }
+
+  timer_start(__FUNCTION__);
 
   switch ($uri['scheme']) {
+    case 'proxy_http':
+    case 'proxy_https':
+      // Make the socket connection to a proxy server.
+      $socket = 'tcp://' . $proxy_server . ':' . variable_get('proxy_port', 8080);
+      break;
     case 'http':
     case 'feed':
-      $port = isset($uri['port']) ? $uri['port'] : 80;
-      $socket = 'tcp://' . $uri['host'] . ':' . $port;
-      // RFC 2616: "non-standard ports MUST, default ports MAY be included".
-      // We don't add the standard port to prevent from breaking rewrite rules
-      // checking the host that do not take into account the port number.
-      $options['headers']['Host'] = $uri['host'] . ($port != 80 ? ':' . $port : '');
-      break;
+        if (!isset($uri['port'])) {
+            $uri['port'] = 80;
+        }
+        $socket  = 'tcp://' . $uri['host'] . ':' . $uri['port'];
+        break;
     case 'https':
-      // Note: Only works when PHP is compiled with OpenSSL support.
-      $port = isset($uri['port']) ? $uri['port'] : 443;
-      $socket = 'ssl://' . $uri['host'] . ':' . $port;
-      $options['headers']['Host'] = $uri['host'] . ($port != 443 ? ':' . $port : '');
+        // Note: Only works when PHP is compiled with OpenSSL support.
+        if (!isset($uri['port'])) {
+            $uri['port'] = 443;
+        }
+        $socket  = 'ssl://' . $uri['host'] . ':' . $uri['port'];
       break;
     default:
       $result->error = 'invalid schema ' . $uri['scheme'];
@@ -814,13 +872,11 @@ function drupal_http_request($url, array
       return $result;
   }
 
-  if (empty($options['context'])) {
-    $fp = @stream_socket_client($socket, $errno, $errstr, $options['timeout']);
-  }
-  else {
-    // Create a stream with context. Allows verification of a SSL certificate.
-    $fp = @stream_socket_client($socket, $errno, $errstr, $options['timeout'], STREAM_CLIENT_CONNECT, $options['context']);
-  }
+  // Use of a context in the stream allows verification of a SSL certificate.
+  if (!($fp = $fp_in))
+      $fp = empty($options['context'])
+          ? @stream_socket_client ($socket, $errno, $errstr, $timeout)
+          : @stream_socket_client ($socket, $errno, $errstr, $timeout, STREAM_CLIENT_CONNECT, $options['context']);
 
   // Make sure the socket opened properly.
   if (!$fp) {
@@ -838,17 +894,35 @@ function drupal_http_request($url, array
     return $result;
   }
 
+  // As described by RFC-2817
+  switch ($uri['scheme']) {
+    case 'proxy_https':
+        $options_connect = array(
+            'headers'  => array(),
+            'method'   => 'CONNECT',
+            'protocol' => 'HTTP/1.0'
+        );
+        $options_connect['headers']['Host']
+              = $options['headers']['Host'];
+        $options_connect['headers']['User-Agent']
+              = $options['headers']['User-Agent'];
+        $options_connect['headers']['Proxy-Authorization']
+              = $options['headers']['Proxy-Authorization'];
+        if (!($result_connect = drupal_http_request($url, $options_connect, $fp))) {
+            ($fp_in) ? TRUE : fclose($fp);
+            return $result_connect;
+        }
+
+        stream_socket_enable_crypto($fp, TRUE, STREAM_CRYPTO_METHOD_SSLv23_CLIENT);
+      break;
+  }
+
   // Construct the path to act on.
   $path = isset($uri['path']) ? $uri['path'] : '/';
   if (isset($uri['query'])) {
     $path .= '?' . $uri['query'];
   }
 
-  // Merge the default headers.
-  $options['headers'] += array(
-    'User-Agent' => 'Drupal (+http://drupal.org/)',
-  );
-
   // Only add Content-Length if we actually have any content or if it is a POST
   // or PUT request. Some non-standard servers get confused by Content-Length in
   // at least HEAD/GET requests, and Squid always requires Content-Length in
@@ -874,70 +948,112 @@ function drupal_http_request($url, array
     $options['headers']['User-Agent'] = drupal_generate_test_ua($test_info['test_run_id']);
   }
 
-  $request = $options['method'] . ' ' . $path . " HTTP/1.0\r\n";
+  // Build request
+  switch ($options['method']) {
+    case 'CONNECT':
+        $request = $options['method'] . ' ' . $uri['host'] . ':' . $uri['port'];
+        break;
+    default:
+        $request = $options['method'] . ' ' . $path;
+  }
+  $request .= ' ' . $options['protocol'] . "\r\n";
+
+  // Add its headers to the request
   foreach ($options['headers'] as $name => $value) {
     $request .= $name . ': ' . trim($value) . "\r\n";
   }
   $request .= "\r\n" . $options['data'];
   $result->request = $request;
-  // Calculate how much time is left of the original timeout value.
-  $timeout = $options['timeout'] - timer_read(__FUNCTION__) / 1000;
-  if ($timeout > 0) {
-    stream_set_timeout($fp, floor($timeout), floor(1000000 * fmod($timeout, 1)));
-    fwrite($fp, $request);
-  }
 
-  // Fetch response. Due to PHP bugs like http://bugs.php.net/bug.php?id=43782
+
+  // Send request and then fetch response: status line, message headers and
+  // message body, if any.
+  // Due to PHP bugs like http://bugs.php.net/bug.php?id=43782
   // and http://bugs.php.net/bug.php?id=46049 we can't rely on feof(), but
   // instead must invoke stream_get_meta_data() each iteration.
-  $info = stream_get_meta_data($fp);
-  $alive = !$info['eof'] && !$info['timed_out'];
-  $response = '';
+  $request_sent = FALSE;
+  do { // do {} while(true);
+    $info = stream_get_meta_data($fp);
 
-  while ($alive) {
     // Calculate how much time is left of the original timeout value.
-    $timeout = $options['timeout'] - timer_read(__FUNCTION__) / 1000;
-    if ($timeout <= 0) {
-      $info['timed_out'] = TRUE;
-      break;
+    $timeout = ($info['timed_out']) ? 0.0 : ((float)$options['timeout']) - timer_read(__FUNCTION__) / 1000.0;
+    if ($timeout <= 0.0) {
+        $result->code  = HTTP_REQUEST_TIMEOUT;
+        $result->error = 'request timed out';
+    }
+    // RETURN !!
+    if (isset($result->error)) {
+        ($fp_in) ? TRUE : fclose($fp);
+        return $result;
     }
     stream_set_timeout($fp, floor($timeout), floor(1000000 * fmod($timeout, 1)));
-    $chunk = fread($fp, 1024);
-    $response .= $chunk;
-    $info = stream_get_meta_data($fp);
-    $alive = !$info['eof'] && !$info['timed_out'] && $chunk;
-  }
-  fclose($fp);
 
-  if ($info['timed_out']) {
-    $result->code = HTTP_REQUEST_TIMEOUT;
-    $result->error = 'request timed out';
-    return $result;
-  }
-  // Parse response headers from the response body.
-  list($response, $result->data) = explode("\r\n\r\n", $response, 2);
-  $response = preg_split("/\r\n|\n|\r/", $response);
-
-  // Parse the response status line.
-  list($protocol, $code, $status_message) = explode(' ', trim(array_shift($response)), 3);
-  $result->protocol = $protocol;
-  $result->status_message = $status_message;
-
-  $result->headers = array();
-
-  // Parse the response headers.
-  while ($line = trim(array_shift($response))) {
-    list($name, $value) = explode(':', $line, 2);
-    $name = strtolower($name);
-    if (isset($result->headers[$name]) && $name == 'set-cookie') {
-      // RFC 2109: the Set-Cookie response header comprises the token Set-
-      // Cookie:, followed by a comma-separated list of one or more cookies.
-      $result->headers[$name] .= ',' . trim($value);
+    if (!$request_sent) {
+        if (fwrite($fp, $request) === FALSE) {
+            $result->code = -$errno;
+            $result->error = trim($errstr) ? trim($errstr) : t('Error writing on socket');
+        }
+        $request_sent = TRUE;
+        continue;
     }
-    else {
-      $result->headers[$name] = trim($value);
+
+    // BREAK !!
+    if ($info['eof'] && !$info['unread_bytes']) break;
+
+    if (!isset($status)) {
+        $status = fscanf($fp, "%[^ ]%*[ ]%[^ ]%*[ ]%[^\r\n]\r\n");
+        if (!isset($status[0]) || !isset($status[1]) || !isset($status[2])) {
+            $result->code  = -$errno;
+            $result->error = trim($errstr) ? trim($errstr) : t('Error reading status on socket');
+            continue;
+        }
+        $result->protocol       = $status[0];
+        $result->code           = $status[1];
+        $result->status_message = $status[2];
+        $result->headers        = array();
+        $result->data           = '';
+
+        $blank_line = FALSE;
+    }
+    elseif (!$blank_line) {
+        $header = fscanf($fp, "%[^: ]%*[: ]%[^\r\n]\r\n");
+        if (!isset($header[0])) {
+            $result->code  = -$errno;
+            $result->error = trim($errstr) ? trim($errstr) : t('Error reading header on socket');
+            continue;
+        }
+        if (!isset($header[1])) {
+            $message_body_length = __drupal_http_message_body_length($options, $result);
+            $blank_line = TRUE;
+            continue;
+        }
+        $header[0] = strtolower($header[0]);
+
+        // Merging cookies
+        // RFC 2109, the Set-Cookie response header comprises the token Set-
+        // Cookie:, followed by a comma-separated list of one or more cookies.
+        // RFC 2965, equally for Set-Cookie2
+        if (($header[0] == 'set-cookie') || ($header[0] == 'set-cookie2'))
+            if (isset($result->headers[$header[0]])) {
+                $header[1] = $result->headers[$header[0]] . ',' . $header[1];
+            }
+
+        $result->headers += array($header[0] => $header[1]);
     }
-  }
+    else{
+        $ldata = $message_body_length - strlen($result->data);
+        // BREAK !!
+        if ($ldata <= 0) break;
+        if (($rdata = fread($fp, ($ldata<1024) ? $ldata : 1024)) === FALSE) {
+            $result->code  = -$errno;
+            $result->error = trim($errstr) ? trim($errstr) : t('Error reading data on socket');
+            continue;
+        }
+        $result->data .= $rdata;
+    }
+  } while (TRUE);
+
+  ($fp_in) ? TRUE : fclose($fp);
 
   $responses = array(
     100 => 'Continue',
@@ -983,12 +1099,11 @@ function drupal_http_request($url, array
   );
   // RFC 2616 states that all unknown HTTP codes must be treated the same as the
   // base code in their class.
-  if (!isset($responses[$code])) {
-    $code = floor($code / 100) * 100;
+  if (!isset($responses[$result->code])) {
+    $result->code = floor($result->code / 100) * 100;
   }
-  $result->code = $code;
 
-  switch ($code) {
+  switch ($result->code) {
     case 200: // OK
     case 304: // Not modified
       break;
@@ -996,25 +1111,65 @@ function drupal_http_request($url, array
     case 302: // Moved temporarily
     case 307: // Moved temporarily
       $location = $result->headers['location'];
-      $options['timeout'] -= timer_read(__FUNCTION__) / 1000;
-      if ($options['timeout'] <= 0) {
-        $result->code = HTTP_REQUEST_TIMEOUT;
-        $result->error = 'request timed out';
-      }
-      elseif ($options['max_redirects']) {
+      if ($options['max_redirects']) {
         // Redirect to the new location.
         $options['max_redirects']--;
+        $redirect_code = $result->code;
         $result = drupal_http_request($location, $options);
-        $result->redirect_code = $code;
+        $result->redirect_code = $redirect_code;
       }
       $result->redirect_url = $location;
       break;
     default:
-      $result->error = $status_message;
+      $result->error = $result->status_message;
   }
 
   return $result;
 }
+
+/**
+ * Helper function to determine message body length of the HTTP message
+ *
+ * @return
+ *  message body length of the HTTP message as described by RFC 2616
+ *  section 4.4. Return PHP_INT_MAX if length cannot be determined
+ */
+function __drupal_http_message_body_length($options, $result) {
+
+    // RFC 2817, " ... data to be tunneled may be sent inmediately after
+    // the blank line ..."
+    if ($options['method'] == 'CONNECT') return 0;
+
+    // Responses without message body
+    if ($options['method'] == 'HEAD') return 0;
+    if ($result->code == 100) return 0;
+    if ($result->code == 101) return 0;
+    if ($result->code == 204) return 0;
+    if ($result->code == 304) return 0;
+
+    // ToDo, length specified by Transfer-Encoding header
+
+    // Length specified by Content-Length header
+    if (!isset($result->headers['transfer-encoding'])
+        && isset($result->headers['content-length']))
+    return $result->headers['content-length'];
+
+    // ToDo, length specified by media type "multipart/byteranges"
+
+    return PHP_INT_MAX;
+}
+
+/**
+ * Helper function for determining hosts excluded from needing a proxy.
+ *
+ * @return
+ *   TRUE if a proxy should be used for this host.
+ */
+function __drupal_http_use_proxy($host) {
+  $proxy_exceptions = variable_get('proxy_exceptions', array('localhost', '127.0.0.1'));
+  return !in_array(strtolower($host), $proxy_exceptions, TRUE);
+}
+
 /**
  * @} End of "HTTP handling".
  */
Index: sites/default/default.settings.php
===================================================================
RCS file: /cvs/drupal/drupal/sites/default/default.settings.php,v
retrieving revision 1.51
diff -u -p -r1.51 default.settings.php
--- sites/default/default.settings.php	11 Oct 2010 23:49:48 -0000	1.51
+++ sites/default/default.settings.php	14 Jan 2011 21:28:06 -0000
@@ -444,3 +444,18 @@ ini_set('session.cookie_lifetime', 20000
  * Remove the leading hash signs to disable.
  */
 # $conf['allow_authorize_operations'] = FALSE;
+
+/**
+ * External access proxy settings:
+ *
+ * If your site must access the internet via a web proxy then you can enter
+ * the proxy settings here. Currently only basic authentication is supported
+ * by using the username and password variables. The proxy_exceptions variable
+ * is an array of host names to be accessed directly, not via proxy.
+ */
+# $conf['proxy_server'] = '';
+# $conf['proxy_port'] = 8080;
+# $conf['proxy_username'] = '';
+# $conf['proxy_password'] = '';
+# $conf['proxy_exceptions'] = array('127.0.0.1', 'localhost');
+# $conf['proxy_user_agent'] = '';
