openrat-cms

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs | README

Http.class.php (13665B)


      1 <?php
      2 // OpenRat Content Management System
      3 // Copyright (C) 2002-2012 Jan Dankert, cms@jandankert.de
      4 //
      5 // This program is free software; you can redistribute it and/or
      6 // modify it under the terms of the GNU General Public License
      7 // as published by the Free Software Foundation; either version 2
      8 // of the License, or (at your option) any later version.
      9 //
     10 // This program is distributed in the hope that it will be useful,
     11 // but WITHOUT ANY WARRANTY; without even the implied warranty of
     12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
     13 // GNU General Public License for more details.
     14 //
     15 // You should have received a copy of the GNU General Public License
     16 // along with this program; if not, write to the Free Software
     17 // Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
     18 
     19 
     20 namespace util;
     21 use cms\base\DB;
     22 use logger\Logger;
     23 use withPraefixQuestionMark;
     24 
     25 /**
     26  * Kapselung einer HTTP-Anfrage.<br>
     27  * Unter Beruecksichtigung von RFC 1945.<br>
     28  *
     29  * @author Jan Dankert
     30  * @package openrat.services
     31  */
     32 class Http
     33 {
     34 	public $header = array();
     35 
     36 	public $url = array();
     37 	public $responseHeader = array();
     38 	public $requestParameter = array();
     39 	public $urlParameter = array();
     40 
     41 	/**
     42 	 * HTTP-Request-Typ.<br>
     43 	 * Muss entweder "GET" oder "POST" sein.<br>
     44 	 * Default: "GET".
     45 	 *
     46 	 * @var String Request-Typ
     47 	 */
     48 	public $method = 'GET';
     49 	public $error = '';
     50 	public $status = '';
     51 	public $body = '';
     52 
     53 	public $httpCmd = '';
     54 
     55 
     56 	/**
     57 	 * Erzeugt eine HTTP-Anfrage.
     58 	 *
     59 	 * @param String URL
     60 	 * @return Http
     61 	 */
     62 	public function __construct($url = '')
     63 	{
     64 		$this->setURL($url);
     65 		$this->header['User-Agent'] = 'Mozilla/5.0 (OpenRat CMS)';
     66 		$this->header['Connection'] = 'close';
     67 	}
     68 
     69 
     70 	/**
     71 	 * Setzt die URL.
     72 	 *
     73 	 * @param String URL
     74 	 */
     75 	function setURL($url)
     76 	{
     77 		$this->url = parse_url($url);
     78 
     79 		if (empty($this->url['host']) && !empty($this->url['path'])) {
     80 			$this->url['host'] = basename($this->url['path']);
     81 			$this->url['path'] = '/';
     82 		}
     83 
     84 		if (empty($this->url['path']))
     85 			$this->url['path'] = '/';
     86 
     87 		if (!isset($this->url['port']))
     88 			if (!isset($this->url['scheme'])) {
     89 				$this->url['scheme'] = 'http'; // Standard-Port.
     90 				$this->url['port'] = 80; // Standard-Port.
     91 			} elseif ($this->url['scheme'] == 'https')
     92 				$this->url['port'] = 443; // SSL-Port.
     93 			else
     94 				$this->url['port'] = 80; // Standard-Port.
     95 
     96 		if (!empty($this->url['query']))
     97 			parse_str($this->url['query'], $this->urlParameter);
     98 
     99 	}
    100 
    101 
    102 	/**
    103 	 * Setzt Authentisierungsinformationen in den HTTP-Request.<br>
    104 	 *
    105 	 * @param String Benutzername
    106 	 * @param String Kennwort
    107 	 */
    108 	public function setBasicAuthentication($user, $password)
    109 	{
    110 		$this->header['Authorization'] = 'Basic ' . base64_encode($user . ':' . $password);
    111 	}
    112 
    113 
    114 	/**
    115 	 * Erzeugt eine HTTP-Parameterstring mit allen Parametern.
    116 	 *
    117 	 * @param withPraefixQuestionMark Praefix mit Fragezeichen (fuer GET-Anfragen)
    118 	 * @return String URL-Parameter
    119 	 */
    120 	public function getParameterString($withPraefixQuestionMark = false)
    121 	{
    122 		$parameterString = '';
    123 		$parameter = $this->urlParameter + $this->requestParameter;
    124 
    125 		if (!empty($parameter)) {
    126 			foreach ($this->requestParameter as $paramName => $paramValue) {
    127 				if (strlen($parameterString) > 0)
    128 					$parameterString .= '&';
    129 				elseif ($withPraefixQuestionMark)
    130 					$parameterString .= '?';
    131 
    132 				$parameterString .= urlencode($paramName) . '=' . urlencode($paramValue);
    133 			}
    134 		}
    135 
    136 		return $parameterString;
    137 	}
    138 
    139 
    140 	/**
    141 	 * Liefert die URL des Requests.
    142 	 *
    143 	 * @return String URL
    144 	 */
    145 	public function getUrl()
    146 	{
    147 		$location = $this->url['scheme'];
    148 		$location .= '://';
    149 		$location .= $this->url['host'];
    150 		if ($this->url['scheme'] == 'http' && $this->url['port'] != 80 ||
    151 			$this->url['scheme'] == 'https' && $this->url['port'] != 443)
    152 			$location .= ':' . $this->url['port'];
    153 		$location .= $this->url['path'];
    154 
    155 		$location .= $this->getParameterString(true);
    156 
    157 		if (isset($this->url['fragment']))
    158 			$location .= '#' . $this->url['fragment'];
    159 
    160 		return $location;
    161 	}
    162 
    163 
    164 	/**
    165 	 * Sendet eine Redirect-Anweisung mit der aktuellen URL an den Browser.
    166 	 */
    167 	public function sendRedirect()
    168 	{
    169 		$location = $this->getUrl();
    170 
    171 		header('Location: ' . $location);
    172 		exit;
    173 	}
    174 
    175 
    176 	/**
    177 	 * Führt einen HTTP-Request durch.
    178 	 *
    179 	 * @return boolean Erfolg der Anfrage.
    180 	 */
    181 	public function request()
    182 	{
    183 		$this->error = '';
    184 		$this->status = '';
    185 
    186 		$errno = 0;
    187 		$errstr = '';
    188 
    189 		if (empty($this->url['host'])) {
    190 			$this->error = "No hostname specified";
    191 			return false;
    192 		}
    193 
    194 		foreach ($this->header as $header_key => $header_value) {
    195 			if (is_numeric($header_key)) {
    196 				$dp = strpos($header_value, ':');
    197 				if ($dp !== FALSE)
    198 					$this->header[substr($header_value, 0, $dp)] = substr($header_value, $dp + 1);
    199 				unset($this->header[$header_key]);
    200 			}
    201 		}
    202 
    203 		$parameterString = $this->getParameterString();
    204 
    205 		if ($this->method == 'POST') {
    206 			$this->header['Content-Type'] = 'application/x-www-form-urlencoded';
    207 			$this->header['Content-Length'] = strlen($parameterString);
    208 		}
    209 
    210 		// Accept-Header setzen, falls noch nicht vorhanden.
    211 		if (!array_key_exists('Accept', $this->header))
    212 			$this->header['Accept'] = '*/*';
    213 
    214 		$this->responseHeader = array();
    215 
    216 		// RFC 1945 (Section 9.3) says:
    217 		// A user agent should never automatically redirect a request
    218 		// more than 5 times, since such redirections usually indicate an infinite loop.
    219 		for ($r = 1; $r <= 5; $r++) {
    220 			$this->header['Host'] = $this->url['host'];
    221 
    222 			// Die Funktion fsockopen() erwartet eine Protokollangabe (bei TCP optional, bei SSL notwendig).
    223 			if ($this->url['scheme'] == 'https' || $this->url['port'] == '443')
    224 				$prx_proto = 'ssl://'; // SSL
    225 			else
    226 				$prx_proto = 'tcp://'; // Default
    227 
    228 			$fp = @fsockopen($prx_proto . $this->url['host'], $this->url['port'], $errno, $errstr, 30);
    229 
    230 			if (!$fp || !is_resource($fp)) {
    231 				// Keine Verbindung zum Host moeglich.
    232 				$this->error = "Connection refused: '" . $prx_proto . $this->url['host'] . ':' . $this->url['port'] . " - $errstr ($errno)";
    233 				return false;
    234 			} else {
    235 
    236 				$lb = "\r\n";
    237 				$http_get = $this->url['path'];
    238 
    239 				$request_header = array($this->method . ' ' . $http_get . ' HTTP/1.0');
    240 
    241 				foreach ($this->header as $header_key => $header_value)
    242 					$request_header[] = $header_key . ': ' . $header_value;
    243 
    244 				$http_request = implode($lb, $request_header) . $lb . $lb;
    245 
    246 				if ($this->method == 'GET')
    247 					if (!empty($parameterString))
    248 						$http_get .= '?' . $parameterString;
    249 
    250 				if ($this->method == 'POST')
    251 					$http_request .= $parameterString;
    252 
    253 				if (!is_resource($fp)) {
    254 					$this->error = 'Connection lost after connect: ' . $prx_proto . $this->url['host'] . ':' . $this->url['port'];
    255 					return false;
    256 				}
    257 				fputs($fp, $http_request); // Die HTTP-Anfrage zum Server senden.
    258 
    259 				// Jetzt erfolgt das Auslesen der HTTP-Antwort.
    260 				$isHeader = true;
    261 
    262 				// RFC 1945 (Section 6.1) schreibt als Statuszeile folgendes Format vor
    263 				// "HTTP/" 1*DIGIT "." 1*DIGIT SP 3DIGIT SP
    264 				if (!is_resource($fp)) {
    265 					$this->error = 'Connection lost during transfer: ' . $this->url['host'] . ':' . $this->url['port'];
    266 					return false;
    267 				} elseif (!feof($fp)) {
    268 					$line = fgets($fp, 1028);
    269 					$this->status = substr($line, 9, 3);
    270 				} else {
    271 					$this->error = 'Unexpected EOF while reading HTTP-Response';
    272 					return false;
    273 				}
    274 
    275 				$this->body = '';
    276 				while (!feof($fp)) {
    277 					$line = fgets($fp, 1028);
    278 					if ($isHeader && trim($line) == '') // Leerzeile nach Header.
    279 					{
    280 						$isHeader = false;
    281 					} elseif ($isHeader) {
    282 						list($headerName, $headerValue) = explode(': ', $line) + array(1 => '');
    283 						$this->responseHeader[$headerName] = trim($headerValue);
    284 					} else {
    285 						$this->body .= $line;
    286 					}
    287 				}
    288 				fclose($fp); // Verbindung brav schlie�en.
    289 
    290 
    291 				// RFC 1945 (Section 6.1.1) schreibt
    292 				// "[...] However, applications must understand the class of any status code, as
    293 				// indicated by the first digit"
    294 				// Daher interessiert uns nur die erste Stelle des 3-stelligen HTTP-Status.
    295 
    296 				// 301 Moved Permanently
    297 				// 302 Moved Temporarily
    298 				if ($this->status == '301' ||
    299 					$this->status == '302') {
    300 					$location = @$this->responseHeader['Location'];
    301 					if (empty($location)) {
    302 						$this->error = '301/302 Response without Location-header';
    303 						return false;
    304 					}
    305 
    306 					//Html::debug($this->url,"alte URL");
    307 					//Html::debug($location,"NEUES REDIRECT AUF");
    308 					$this->setURL($location);
    309 					continue; // Naechster Versuch mit umgeleiteter Adresse.
    310 				}
    311 
    312 				// RFC 1945 (Section 6.1.1) schreibt
    313 				// "2xx: Success - The action was successfully received, understood, and accepted."
    314 				elseif (substr($this->status, 0, 1) == '2') {
    315 					return true;
    316 				} elseif (substr($this->status, 0, 1) == '4') {
    317 					$this->error = 'Client Error: ' . $this->status;
    318 					return false;
    319 				} elseif (substr($this->status, 0, 1) == '5') {
    320 					$this->error = 'Server Error: ' . $this->status;
    321 					return false;
    322 				} else {
    323 					$this->error = 'Unexpected HTTP-Status: ' . $this->status . '; this is mostly a client error, sorry.';
    324 					return false;
    325 				}
    326 			}
    327 
    328 			$this->error = 'Too much redirects, infinite loop assumed. Exiting. Last URL: ' . $http_get;
    329 			return false;
    330 
    331 		}
    332 
    333 	}
    334 
    335 
    336 	/**
    337 	 * Aus dem HTTP-Header werden die vom Browser angeforderten Sprachen
    338 	 * gelesen.<br>
    339 	 * Es wird eine Liste von Sprachen erzeugt.<br>
    340 	 *
    341 	 * Beispiel:
    342 	 * 'de_DE','de','en_GB','en' ... usw.<br>
    343 	 * Wenn der Browser 'de_DE' anfordert, wird hier zusätzlich
    344 	 * auch 'de' (als Fallback) ermittelt.
    345 	 *
    346 	 * @static
    347 	 * @return array
    348 	 */
    349 	public static function getLanguages()
    350 	{
    351 		$languages = array();
    352 		$http_languages = @$_SERVER['HTTP_ACCEPT_LANGUAGE'];
    353 		foreach (explode(',', $http_languages) as $l) {
    354 			list($part) = explode(';', $l); // Priorit�ten ignorieren.
    355 			$languages[] = trim($part);
    356 
    357 			// Aus "de_DE" das "de" extrahieren.
    358 			$languages[] = current(explode('_', str_replace('-', '_', trim($part))));
    359 		}
    360 
    361 		return array_unique($languages);
    362 	}
    363 
    364 
    365 	/**
    366 	 * Ermittelt die aktuelle HTTP-Adresse des Requests (inkl. Pfad, jedoch ohne Datei).
    367 	 *
    368 	 * @return String URL
    369 	 */
    370 	public static function getServer()
    371 	{
    372 		$https = getenv('HTTPS');
    373 
    374 		if ($https)
    375 			$server = 'https://';
    376 		else
    377 			$server = 'http://';
    378 
    379 		$server .= getenv('SERVER_NAME') . dirname(getenv('REQUEST_URI'));
    380 
    381 		return $server;
    382 	}
    383 
    384 
    385 
    386 	public static function badRequest() {
    387 		self::sendStatus('400','Bad Request');
    388 	}
    389 
    390 	public static function methodNotAllowed() {
    391 		self::sendStatus('405','Bad Request');
    392 	}
    393 
    394 	public static function notAcceptable() {
    395 		self::sendStatus('406','Not Acceptable');
    396 	}
    397 
    398 	/**
    399 	 * Server-Fehlermeldung anzeigen.<br>
    400 	 *
    401 	 * Erzeugt einen "HTTP 501 Internal Server Error". Zusaetzlich
    402 	 * wird ein 'rollback' auf der Datenbank ausgefaehrt.
    403 	 *
    404 	 * @param String $message Eigener Hinweistext
    405 	 */
    406 	public static function serverError($message = '', $reason = '')
    407 	{
    408 		/*
    409 		try {
    410 
    411 		if (class_exists('util\Session')) {
    412 			$db = DB::get();
    413 			if (is_object($db))
    414 				$db->rollback();
    415 		}
    416 
    417 		if (class_exists('logger\Logger'))
    418 			Logger::warn($message . "\n" . $reason);
    419 		}
    420 		catch( \Exception $e ) {
    421 			//error_log( $e->__toString() );
    422 		}*/
    423 
    424 		self::sendStatus(500, 'Internal Server Error');
    425 	}
    426 
    427 
    428 	/**
    429 	 * Der Benutzer ist nicht autorisiert, eine Aktion auszufuehren.
    430 	 *
    431 	 * Diese Funktion erzeugt einen "HTTP 403 Not Authorized" und das
    432 	 * Skript wird beendet.
    433 	 *
    434 	 * @param String $text Text
    435 	 * @param String $message Eigener Hinweistext
    436 	 */
    437 	public static function forbidden()
    438 	{
    439 		Http::sendStatus(403, 'Forbidden');
    440 	}
    441 
    442 
    443 	/**
    444 	 * Nichts gefunden.
    445 	 *
    446 	 * Diese Funktion erzeugt einen "HTTP 404 Not found" und das
    447 	 * Skript wird beendet.
    448 	 *
    449 	 */
    450 	public static function notFound()
    451 	{
    452 		Http::sendStatus(404, 'Not found');
    453 	}
    454 
    455 
    456 	/**
    457 	 * Kein Inhalt.
    458 	 *
    459 	 * Die HTTP-Antwort stellt gegenüber dem Client klar, dass es keinen Inhalt gibt.
    460 	 */
    461 	public static function noContent()
    462 	{
    463 		self::sendStatus(204,'No Content');
    464 	}
    465 
    466 
    467 	/**
    468 	 * Schickt einen HTTP-Status zum Client und beendet das Skript.
    469 	 *
    470 	 * @param Integer $status HTTP-Status (ganzzahlig) (Default: 501)
    471 	 * @param String $text HTTP-Meldung (Default: 'Internal Server Error')
    472 	 */
    473 	private static function sendStatus($status = 500, $text = 'Internal Server Error')
    474 	{
    475 		if (headers_sent()) {
    476 			echo "$status $text";
    477 			exit;
    478 		}
    479 
    480 		header('HTTP/1.0 ' . intval($status) . ' ' . $text);
    481 	}
    482 
    483 
    484 	/**
    485 	 * Liefert den Mime-Type, den der Browser (oder besser: HTTP-Client) wünscht.
    486 	 *
    487 	 * @return array Mime-Typen, welche vom User-Agent akzeptiert werden.
    488 	 */
    489 	public static function getAccept()
    490 	{
    491 		$httpAccept = getenv('HTTP_ACCEPT');
    492 		return array_map( function($accept) {
    493 				return explode(';',$accept)[0];
    494 			}, explode(',', $httpAccept)
    495 		);
    496 	}
    497 
    498 
    499 	/**
    500 	 * Liefert die IPv4-Adresse des Clients. Falls der Request durch einen Proxy kam, wird
    501 	 * versucht, die echte IP-Adresse aus dem Anfrageheader zu ermitteln.
    502 	 *
    503 	 * @return Client-IPv4-Adresse
    504 	 */
    505 	public static function getClientIP()
    506 	{
    507 		$ip = '';
    508 
    509 		if (isset($_SERVER["HTTP_X_FORWARDED_FOR"])) {
    510 			$ip = $_SERVER["HTTP_X_FORWARDED_FOR"];
    511 		} elseif (isset($_SERVER["HTTP_CLIENT_IP"])) {
    512 			$ip = $_SERVER["HTTP_CLIENT_IP"];
    513 		} elseif (isset($_SERVER["REMOTE_ADDR"])) {
    514 			$ip = $_SERVER["REMOTE_ADDR"];
    515 		}
    516 
    517 		return $ip;
    518 	}
    519 
    520 
    521 	/**
    522 	 * Ermittelt den TCP/IP-Port des Clients.
    523 	 * Achtung, bei Proxy-Zugriffen kann dies der Port des Proxys sein.
    524 	 *
    525 	 * @return string TCP/IP-Port
    526 	 */
    527 	public static function getClientPort()
    528 	{
    529 		$ip = '';
    530 
    531 		if (isset($_SERVER["REMOTE_PORT"])) {
    532 			$ip = $_SERVER["REMOTE_PORT"];
    533 		}
    534 
    535 		return $ip;
    536 	}
    537 }
    538 
    539 ?>