<?php
/*
 * Copyright (c) 2013, webvariants GbR, http://www.webvariants.de
 *
 * This file is released under the terms of the MIT license. You can find the
 * complete text in the attached LICENSE file or online at:
 *
 * http://www.opensource.org/licenses/mit-license.php
 */

class WV_HTTP_Request {
	protected $url;
	protected $method;
	protected $postValues;
	protected $timeout;
	protected $cookies;
	protected $headers;
	protected $username;
	protected $password;
	protected $maxRedirects;

	private $receivedHeaders;

	public function __construct($url, $method = 'GET') {
		$this->cookies    = array();
		$this->postValues = array();

		$this->setURL($url);
		$this->setMethod($method);
		$this->setTimeout(30);
		$this->setContentType('application/x-www-form-urlencoded');
		$this->setHeader('user-agent', 'SallyCMS/webvariants');
		$this->setAuth('', '');
		$this->setMaxRedirects(3);
	}

	public function setURL($url) {
		$components = parse_url($url);

		if ($components === false) {
			throw new WV_Exception_HTTP(t('devutils_http_malformed_url'));
		}

		if ($components['scheme'] == 'https' && !self::canSSL()) {
			throw new WV_Exception_HTTP(t('devutils_http_no_ssl_allowed'));
		}

		$this->url = $url;
	}

	public function setMethod($method) {
		if (in_array($method, array('GET', 'POST', 'HEAD'))) {
			$this->method = $method;
		}
		else {
			$this->method = 'GET';
		}

		return $this->method;
	}

	public function setTimeout($seconds) {
		$this->timeout = abs(intval($seconds));
	}

	public function setContentType($contentType) {
		$this->headers['content-type'] = $contentType;
	}

	public function setHeader($name, $value) {
		$name = strtolower($name);
		$this->headers[$name] = $value;
	}

	public function setAuth($username, $password) {
		$this->username = $username;
		$this->password = $password;
	}

	public function setMaxRedirects($max) {
		$this->maxRedirects = abs(intval($max));
	}

	public function addPOST($name, $value) {
		$this->postValues[$name] = $value;
		$this->setMethod('POST');
	}

	public function addCookie($name, $value) {
		$this->cookies[$name] = $value;
	}

	public static function canSSL() {
		return in_array('https', stream_get_wrappers()) || extension_loaded('curl');
	}

	public function getResponse() {
		// Ermitteln, welche Methode zum Abrufen der Daten verwendet werden soll
		$method = $this->getMethod();

		// Request vorbereiten
		$postData = http_build_query($this->postValues, '', '&');
		$cookies  = $this->buildCookies();

		switch ($method) {
			case 'file_get_contents':

				$context = $this->getContext($cookies, $postData);
				$content = file_get_contents($this->url, false, $context);

				if ($content === false) return null;
				return new WV_HTTP_Response($content, $http_response_header);

			case 'curl':

				$ch       = $this->initCurl($cookies, $postData);
				$response = curl_exec($ch);

				curl_close($ch);

				if ($response === false) return null;
				return new WV_HTTP_Response($response, $this->receivedHeaders);

			default:

				throw new WV_Exception_HTTP(t('devutils_http_no_method_found'));
		}
	}

	public function downloadTo($filename) {
		$target = fopen($filename, 'wb');
		if ($target === false) return null;

		// Ermitteln, welche Methode zum Abrufen der Daten verwendet werden soll
		$method = $this->getMethod();

		// Request vorbereiten
		$postData = http_build_query($this->postValues, '', '&');
		$cookies  = $this->buildCookies();

		switch ($method) {
			case 'file_get_contents':

				$context = $this->getContext($cookies, $postData);
				$handle  = fopen($this->url, 'rb', false, $context);

				if ($handle === false) {
					return null;
				}

				while (!feof($handle)) {
					fwrite($target, fread($handle, 8192));
				}

				fclose($target);
				fclose($handle);

				return true;

			case 'curl':

				$ch = $this->initCurl($cookies, $postData);

				curl_setopt($ch, CURLOPT_BINARYTRANSFER, true);
				curl_setopt($ch, CURLOPT_FILE, $target);

				$response = curl_exec($ch);

				curl_close($ch);
				fclose($target);

				if ($response === false) return null;
				return true;

			default:

				throw new WV_Exception_HTTP(t('devutils_http_no_method_found'));
		}
	}

	private function receiveHeader($curl, $header) {
		$this->receivedHeaders[] = $header;
		return strlen($header);
	}

	private function getMethod() {
		$needSSL    = parse_url($this->url, PHP_URL_SCHEME) == 'https';
		$canSSL     = in_array('https', stream_get_wrappers());
		$canURLOpen = in_array(ini_get('allow_url_fopen'), array('1', 'On', true));
		$hasCURL    = extension_loaded('curl');
		$method     = '';

		if (!$canURLOpen) {
			$method = $hasCURL ? 'curl' : '';
		}
		else { // allow_url_fopen = true
			if ($needSSL) {
				$method = $canSSL ? 'file_get_contents' : ($hasCURL ? 'curl' : '');
			}
			else {
				$method = $hasCURL ? 'curl' : 'file_get_contents';
			}
		}

		return $method;
	}

	private function getContext($cookies, $postData) {
		foreach ($this->headers as $name => $value) {
			$headers[] = ucwords($name).': '.$value;
		}

		if (!empty($this->username) && !empty($this->password)) {
			$headers[] = 'Authorization: Basic '.base64_encode($this->username.':'.$this->password);
		}

		if ($cookies) {
			$headers[] = 'Cookie: '.implode('; ', $cookies);
		}

		$options['http'] = array(
			'method'        => $this->method,
			'header'        => implode("\r\n", $headers),
			'content'       => $postData,
			'timeout'       => $this->timeout,
			'max_redirects' => $this->maxRedirects,
			'ignore_errors' => true
		);

		return stream_context_create($options);
	}

	private function buildCookies() {
		$cookies = array();

		foreach ($this->cookies as $name => $value) {
			$cookies[] = $name.'='.urlencode($value);
		}

		return $cookies;
	}

	private function initCurl($cookies, $postData) {
		$ch = curl_init();

		$this->receivedHeaders = array();

		curl_setopt($ch, CURLOPT_URL, $this->url);
		curl_setopt($ch, CURLOPT_CRLF, false);
		curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
		curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
		curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
		curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, $this->timeout);
		curl_setopt($ch, CURLOPT_HEADER, false);

		if (!ini_get('open_basedir') && !ini_get('safe_mode')) {
			curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
		}

		curl_setopt($ch, CURLOPT_MAXREDIRS, $this->maxRedirects);
		curl_setopt($ch, CURLOPT_HEADERFUNCTION, array($this, 'receiveHeader'));

		if (defined('CURLOPT_MUTE')) {
			// suppress error messages, because PHP complains even whe the constant is defined
			@curl_setopt($ch, CURLOPT_MUTE, true);
		}

		if ($this->method == 'POST') {
			curl_setopt($ch, CURLOPT_POST, true);
			curl_setopt($ch, CURLOPT_POSTFIELDS, $postData);
		}

		if ($cookies) {
			curl_setopt($ch, CURLOPT_COOKIE, implode('; ', $cookies));
		}

		if (!empty($this->username) && !empty($this->password)) {
			curl_setopt($ch, CURLOPT_USERPWD, $this->username.':'.$this->password);
		}

		return $ch;
	}
}
