Opened 5 years ago

Closed 5 years ago

Last modified 4 years ago

#793 closed New feature (fixed)

New Code for sendHttpRequest for allow_url_fopen = 0

Reported by: Uli Owned by: vipsoft
Priority: normal Milestone: Piwik 0.4.3
Component: Core Keywords:
Cc: Sensitive: no

Description

I've changed the sendHttpRequest to allow checking for new versions on servers where allow_url_fopen equals zero. My code is tested on my server, and works as expected.

Open core/Piwik.php and replace sendHttpRequest by the following code:

        static public function sendHttpRequest($url, $timeout)
        {
                // Modified by Uli <m [AT] il [DOT] wolf-u [DOT] li>
                $response = false;
                if (ini_get('allow_url_fopen') == 0) {
                        if(function_exists(curl_init)) {
                                $ch = @curl_init();
                                @curl_setopt($ch, CURLOPT_URL, $url);
                                @curl_setopt($ch, CURLOPT_RETURNTRANSFER,1);
                                @curl_setopt($ch, CURLOPT_TIMEOUT, $timeout);
                                $response = @curl_exec ($ch);
                                @curl_close ($ch);
                                unset($ch);
                        } else {
                                $fsockurl = @parse_url($url);
                                if(empty($fsockurl['port'])) { $fsockurl['port'] = 80; }
                                $fsock = @fsockopen($fsockurl['host'],  $fsockurl['port'], $errno, $errstr, $timeout);
                                @fputs($fsock, "GET " . $fsockurl['path'] . " HTTP/1.0\r\n");
                                @fputs($fsock, "HOST: " . $fsockurl['host'] . "\r\n");
                                @fputs($fsock, "Connection: close\r\n\r\n");
                                while (!@feof($fsock)) {
                                        if ($get_info) {
                                                $response = @fread($fsock, 1024);
                                        } else {
                                                if (@fgets($fsock, 1024) == "\r\n") {
                                                        $get_info = true;
                                                }
                                        }
                                }
                                @fclose($fsock);
                                unset($fsockurl, $fsock, $get_info);
                        }
                } else {
                        // we make sure the request takes less than a few seconds to fail
                        // we also set the socket_timeout (for php < 5.2.1)
                        $default_socket_timeout = @ini_get('default_socket_timeout');
                        @ini_set('default_socket_timeout', $timeout);

                        // we create a stream_context (works in php >= 5.2.1)
                        $ctx = null;
                        if(function_exists('stream_context_create')) {
                                        $ctx = stream_context_create(array('http' => array( 'timeout' => $timeout)));
                        }
                        $response = trim(@file_get_contents($url, 0, $ctx));

                        // restore the socket_timeout value
                        if(!empty($default_socket_timeout))
                        {
                                        @ini_set('default_socket_timeout', $default_socket_timeout);
                        }
                }
                return $response;
        }

Checks by curl if allow_url_fopen = 0. If curl is not available, it gets checked by a socket.

I hope this (or something similar) gets included into one of the next releases ;)

Attachments (1)

patch-sendHttpRequest-0.4.1-1.patch (5.8 KB) - added by Uli 5 years ago.
Patch of sendHttpRequest against 0.4.1

Download all attachments as: .zip

Change History (17)

comment:1 follow-up: Changed 5 years ago by vipsoft (robocoder)

Why 3 methods? Why not just use sockets if that's the lowest common denominator?

Code comments re: socket version:

  • should call stream_set_timeout()
  • need to concatenate response, otherwise it doesn't work on files > 1K
  • should check the response header for HTTP error code

comment:2 in reply to: ↑ 1 Changed 5 years ago by Uli

Replying to vipsoft:

Why 3 methods? Why not just use sockets if that's the lowest common denominator?

Hmmm good thought. Don't think sockets get disabled by hosters, so yes, this would be the lowest common denominator. You are probably right.

Code comments re: socket version:

  • should call stream_set_timeout()

I didn't really dig into the socket-thing, i only checked if it works.

  • need to concatenate response, otherwise it doesn't work on files > 1K

Yeah right, i only saw the updater for this

  • should check the response header for HTTP error code

Also right, this code is probably too rudimentary

Will update the code, but need to check a few things first.

comment:3 Changed 5 years ago by vipsoft (robocoder)

  • Milestone changed from 1 - Piwik 0.4.1 to 2- DigitalVibes

comment:4 Changed 5 years ago by Uli

I've recoded the sendHttpRequest for sockets with your hints. Furthermore it should now meet the CodingStandard:

static public function sendHttpRequest($url, $timeout)
{
	// Modified by Uli <m [AT] il [DOT] wolf-u [DOT] li>
	$response = false;
	// Parse the url for fsockopen
	$fsockurl = @parse_url($url);
	if(empty($fsockurl['port']))
	{
		$fsockurl['port'] = 80;
	}
	if(!empty($fsockurl['query']))
	{
		$fsockurl['path'].="?" . $fsockurl['query'];
	}
	// Make the request
	$fsock = @fsockopen($fsockurl['host'],  $fsockurl['port'], $errno, $errstr, $timeout);
	if($fsock||!is_resource($fsock))
	{
		@fputs($fsock, "GET " . $fsockurl['path'] . " HTTP/1.0\r\n");
		@fputs($fsock, "HOST: " . $fsockurl['host'] . "\r\n");
		@fputs($fsock, "Connection: close\r\n\r\n");
		@stream_set_blocking($fsock, TRUE);
		@stream_set_timeout($fsock,$timeout);

		while ((!@feof($fsock)) && (!$streamMetaData['timed_out']))
		{
			// Load data as long as there is data and the connection didn't time out
			$fgetsData .= @fgets($fsock, 1024);
			$streamMetaData = @stream_get_meta_data($fsock);
			@ob_flush;
			@flush();
		}

		@fclose($fsock);
		unset($fsockurl, $fsock);
	}
	
	if (!$streamMetaData['timed_out'] && $fgetsData != "")
	{
		// The connection didn't time out and there is data available
		// Split into headers & body
		$hunks = explode("\r\n\r\n",trim($fgetsData));
		if (is_array($hunks) && count($hunks) >= 2)
		{
			$headers = explode("\n",$hunks[count($hunks) - 2]);
			$body = $hunks[count($hunks) - 1];
			unset($hunks);
			if (is_array($headers) && count($headers) >= 1)
			{
				// Check if the server also told us that everything went well
				switch(trim(strtolower($headers[0])))
				{
					case 'http/1.0 100 ok':
					case 'http/1.0 200 ok':
					case 'http/1.1 100 ok':
					case 'http/1.1 200 ok':
						$response = $body;
					break;
				}
			}
		}
	}
	return $response;
}

Works on my server ;)

comment:5 Changed 5 years ago by vipsoft (robocoder)

Thanks, it looks good (cursory inspection). I'll do a more complete review before commiting.

I'm also going to throw in a couple of additional requirements. (You're welcome to tackle these as well.)

  • Add a check for Content-length (if present in the header).
  • Refactor fetchRemoteFile

comment:6 Changed 5 years ago by Uli

Alright, i've added the check for the parameter "content-length". If it is present, the data gets checked and if the two values are not equal, the output will be false again.

Also removed two possible errors by initializing the variables $fgetsData and $streamMetaData.

/**
 * Sends http request ensuring the request will fail before $timeout seconds
 * Returns the response content (no header, trimmed)
 * @author Uli <m [AT] il [DOT] wolf-u [DOT] li>
 * @param string $url
 * @param int $timeout
 * @return string|false false if request failed
 */
static public function sendHttpRequest($url, $timeout)
{
	$response = false;
	// Parse the url for fsockopen
	$fsockurl = @parse_url($url);
	$fgetsData = "";
	$streamMetaData = array();
	if(empty($fsockurl['port']))
	{
		$fsockurl['port'] = 80;
	}
	if(!empty($fsockurl['query']))
	{
		$fsockurl['path'].="?" . $fsockurl['query'];
	}
	// Make the request
	$fsock = @fsockopen($fsockurl['host'],  $fsockurl['port'], $errno, $errstr, $timeout);
	if($fsock||!is_resource($fsock))
	{
		@fputs($fsock, "GET " . $fsockurl['path'] . " HTTP/1.0\r\n");
		@fputs($fsock, "HOST: " . $fsockurl['host'] . "\r\n");
		@fputs($fsock, "Connection: close\r\n\r\n");
		@stream_set_blocking($fsock, TRUE);
		@stream_set_timeout($fsock,$timeout);

		while ((!@feof($fsock)) && (!$streamMetaData['timed_out']))
		{
			// Load data as long as there is data and the connection didn't time out
			$fgetsData .= @fgets($fsock, 1024);
			$streamMetaData = @stream_get_meta_data($fsock);
			@ob_flush;
			@flush();
		}

		@fclose($fsock);
		unset($fsockurl, $fsock);
	}
	
	if (!$streamMetaData['timed_out'] && $fgetsData != "")
	{
		// The connection didn't time out and there is data available
		// Split into headers & body
		$hunks = @explode("\r\n\r\n",$fgetsData);
		if (is_array($hunks) && count($hunks) >= 2)
		{
			$headers = @explode("\n",$hunks[count($hunks) - 2]);
			$body = $hunks[count($hunks) - 1];
			unset($hunks);
			if (is_array($headers) && count($headers) >= 1)
			{
				// Check if the server also told us that everything went well
				switch(trim(strtolower($headers[0])))
				{
					case 'http/1.0 100 ok':
					case 'http/1.0 200 ok':
					case 'http/1.1 100 ok':
					case 'http/1.1 200 ok':
						// Generally the answer will be ok
						$response = true;
						// Now check the content-length if available
						foreach($headers as $header) {
							if(substr(trim(strtolower($header)),0,15) == "content-length:") {
								// Check the length against the content
								if(substr(trim($header),16) != strlen($body)) {
									//Reset the response if this is the wrong length
									$response = false;
								}
							}
						}
					break;
				}
			}
		}
	}

	if($response === true) {
		return trim($body);
	} else {
		return false;
	}
}

If i have time in the next few days i will try to refactor fetchRemoteFile, but this will be at the weekend at the earliest.

comment:7 Changed 5 years ago by vipsoft (robocoder)

If you can, please attach your code as a diff. Thanks.

comment:8 Changed 5 years ago by matt (mattab)

did you check that it was working when doing the one click upgrade?

comment:9 Changed 5 years ago by Uli

I didn't test sendHttpRequest against the one-click upgrade as the code was tested against various types of content and servers. It does what it should do (as far as i have tested this function).

Perhaps you mean fetchRemoteFile? I didn't have a look into it yet.

Changed 5 years ago by Uli

Patch of sendHttpRequest against 0.4.1

comment:10 Changed 5 years ago by Uli

Added a patch against 0.4.1 with one addition, the array $streamMetaData needed to be initialized.

comment:11 Changed 5 years ago by vipsoft (robocoder)

  • Milestone changed from 2- DigitalVibes to 1 - Piwik 0.4.2

comment:12 Changed 5 years ago by alivenk

comment:13 Changed 5 years ago by vipsoft (robocoder)

  • Owner set to vipsoft
  • Status changed from new to assigned

comment:14 Changed 5 years ago by vipsoft (robocoder)

  • Resolution set to fixed
  • Status changed from assigned to closed

(In [1369]) fixes #793 - rewrite sendHttpRequest() to work when allow_url_fopen=0; also refactor fetchRemoteFile

comment:15 Changed 5 years ago by vipsoft (robocoder)

  • Sensitive unset

In [1391], fixes #925, refs #793 - provide curl and stream methods (as originally proposed by Uli)

Order of preference: curl (6s), stream (10s), socket (13s) to d/l latest.zip

Add Piwik::getTransportMethod() and add to Installation system check

comment:16 Changed 4 years ago by vipsoft (robocoder)

  • Keywords CoreUpdater removed
Note: See TracTickets for help on using tickets.