Request.php 47 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634
  1. <?php
  2. /*
  3. * This file is part of the Symfony package.
  4. *
  5. * (c) Fabien Potencier <[email protected]>
  6. *
  7. * For the full copyright and license information, please view the LICENSE
  8. * file that was distributed with this source code.
  9. */
  10. namespace Symfony\Component\HttpFoundation;
  11. use Symfony\Component\HttpFoundation\Session\SessionInterface;
  12. /**
  13. * Request represents an HTTP request.
  14. *
  15. * The methods dealing with URL accept / return a raw path (% encoded):
  16. * * getBasePath
  17. * * getBaseUrl
  18. * * getPathInfo
  19. * * getRequestUri
  20. * * getUri
  21. * * getUriForPath
  22. *
  23. * @author Fabien Potencier <[email protected]>
  24. *
  25. * @api
  26. */
  27. class Request
  28. {
  29. const HEADER_CLIENT_IP = 'client_ip';
  30. const HEADER_CLIENT_HOST = 'client_host';
  31. const HEADER_CLIENT_PROTO = 'client_proto';
  32. const HEADER_CLIENT_PORT = 'client_port';
  33. protected static $trustProxy = false;
  34. protected static $trustedProxies = array();
  35. /**
  36. * Names for headers that can be trusted when
  37. * using trusted proxies.
  38. *
  39. * The default names are non-standard, but widely used
  40. * by popular reverse proxies (like Apache mod_proxy or Amazon EC2).
  41. */
  42. protected static $trustedHeaders = array(
  43. self::HEADER_CLIENT_IP => 'X_FORWARDED_FOR',
  44. self::HEADER_CLIENT_HOST => 'X_FORWARDED_HOST',
  45. self::HEADER_CLIENT_PROTO => 'X_FORWARDED_PROTO',
  46. self::HEADER_CLIENT_PORT => 'X_FORWARDED_PORT',
  47. );
  48. /**
  49. * @var \Symfony\Component\HttpFoundation\ParameterBag
  50. *
  51. * @api
  52. */
  53. public $attributes;
  54. /**
  55. * @var \Symfony\Component\HttpFoundation\ParameterBag
  56. *
  57. * @api
  58. */
  59. public $request;
  60. /**
  61. * @var \Symfony\Component\HttpFoundation\ParameterBag
  62. *
  63. * @api
  64. */
  65. public $query;
  66. /**
  67. * @var \Symfony\Component\HttpFoundation\ServerBag
  68. *
  69. * @api
  70. */
  71. public $server;
  72. /**
  73. * @var \Symfony\Component\HttpFoundation\FileBag
  74. *
  75. * @api
  76. */
  77. public $files;
  78. /**
  79. * @var \Symfony\Component\HttpFoundation\ParameterBag
  80. *
  81. * @api
  82. */
  83. public $cookies;
  84. /**
  85. * @var \Symfony\Component\HttpFoundation\HeaderBag
  86. *
  87. * @api
  88. */
  89. public $headers;
  90. /**
  91. * @var string
  92. */
  93. protected $content;
  94. /**
  95. * @var array
  96. */
  97. protected $languages;
  98. /**
  99. * @var array
  100. */
  101. protected $charsets;
  102. /**
  103. * @var array
  104. */
  105. protected $acceptableContentTypes;
  106. /**
  107. * @var string
  108. */
  109. protected $pathInfo;
  110. /**
  111. * @var string
  112. */
  113. protected $requestUri;
  114. /**
  115. * @var string
  116. */
  117. protected $baseUrl;
  118. /**
  119. * @var string
  120. */
  121. protected $basePath;
  122. /**
  123. * @var string
  124. */
  125. protected $method;
  126. /**
  127. * @var string
  128. */
  129. protected $format;
  130. /**
  131. * @var \Symfony\Component\HttpFoundation\Session\SessionInterface
  132. */
  133. protected $session;
  134. /**
  135. * @var string
  136. */
  137. protected $locale;
  138. /**
  139. * @var string
  140. */
  141. protected $defaultLocale = 'en';
  142. /**
  143. * @var array
  144. */
  145. protected static $formats;
  146. /**
  147. * Constructor.
  148. *
  149. * @param array $query The GET parameters
  150. * @param array $request The POST parameters
  151. * @param array $attributes The request attributes (parameters parsed from the PATH_INFO, ...)
  152. * @param array $cookies The COOKIE parameters
  153. * @param array $files The FILES parameters
  154. * @param array $server The SERVER parameters
  155. * @param string $content The raw body data
  156. *
  157. * @api
  158. */
  159. public function __construct(array $query = array(), array $request = array(), array $attributes = array(), array $cookies = array(), array $files = array(), array $server = array(), $content = null)
  160. {
  161. $this->initialize($query, $request, $attributes, $cookies, $files, $server, $content);
  162. }
  163. /**
  164. * Sets the parameters for this request.
  165. *
  166. * This method also re-initializes all properties.
  167. *
  168. * @param array $query The GET parameters
  169. * @param array $request The POST parameters
  170. * @param array $attributes The request attributes (parameters parsed from the PATH_INFO, ...)
  171. * @param array $cookies The COOKIE parameters
  172. * @param array $files The FILES parameters
  173. * @param array $server The SERVER parameters
  174. * @param string $content The raw body data
  175. *
  176. * @api
  177. */
  178. public function initialize(array $query = array(), array $request = array(), array $attributes = array(), array $cookies = array(), array $files = array(), array $server = array(), $content = null)
  179. {
  180. $this->request = new ParameterBag($request);
  181. $this->query = new ParameterBag($query);
  182. $this->attributes = new ParameterBag($attributes);
  183. $this->cookies = new ParameterBag($cookies);
  184. $this->files = new FileBag($files);
  185. $this->server = new ServerBag($server);
  186. $this->headers = new HeaderBag($this->server->getHeaders());
  187. $this->content = $content;
  188. $this->languages = null;
  189. $this->charsets = null;
  190. $this->acceptableContentTypes = null;
  191. $this->pathInfo = null;
  192. $this->requestUri = null;
  193. $this->baseUrl = null;
  194. $this->basePath = null;
  195. $this->method = null;
  196. $this->format = null;
  197. }
  198. /**
  199. * Creates a new request with values from PHP's super globals.
  200. *
  201. * @return Request A new request
  202. *
  203. * @api
  204. */
  205. public static function createFromGlobals()
  206. {
  207. $request = new static($_GET, $_POST, array(), $_COOKIE, $_FILES, $_SERVER);
  208. if (0 === strpos($request->headers->get('CONTENT_TYPE'), 'application/x-www-form-urlencoded')
  209. && in_array(strtoupper($request->server->get('REQUEST_METHOD', 'GET')), array('PUT', 'DELETE', 'PATCH'))
  210. ) {
  211. parse_str($request->getContent(), $data);
  212. $request->request = new ParameterBag($data);
  213. }
  214. return $request;
  215. }
  216. /**
  217. * Creates a Request based on a given URI and configuration.
  218. *
  219. * @param string $uri The URI
  220. * @param string $method The HTTP method
  221. * @param array $parameters The query (GET) or request (POST) parameters
  222. * @param array $cookies The request cookies ($_COOKIE)
  223. * @param array $files The request files ($_FILES)
  224. * @param array $server The server parameters ($_SERVER)
  225. * @param string $content The raw body data
  226. *
  227. * @return Request A Request instance
  228. *
  229. * @api
  230. */
  231. public static function create($uri, $method = 'GET', $parameters = array(), $cookies = array(), $files = array(), $server = array(), $content = null)
  232. {
  233. $defaults = array(
  234. 'SERVER_NAME' => 'localhost',
  235. 'SERVER_PORT' => 80,
  236. 'HTTP_HOST' => 'localhost',
  237. 'HTTP_USER_AGENT' => 'Symfony/2.X',
  238. 'HTTP_ACCEPT' => 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
  239. 'HTTP_ACCEPT_LANGUAGE' => 'en-us,en;q=0.5',
  240. 'HTTP_ACCEPT_CHARSET' => 'ISO-8859-1,utf-8;q=0.7,*;q=0.7',
  241. 'REMOTE_ADDR' => '127.0.0.1',
  242. 'SCRIPT_NAME' => '',
  243. 'SCRIPT_FILENAME' => '',
  244. 'SERVER_PROTOCOL' => 'HTTP/1.1',
  245. 'REQUEST_TIME' => time(),
  246. );
  247. $components = parse_url($uri);
  248. if (isset($components['host'])) {
  249. $defaults['SERVER_NAME'] = $components['host'];
  250. $defaults['HTTP_HOST'] = $components['host'];
  251. }
  252. if (isset($components['scheme'])) {
  253. if ('https' === $components['scheme']) {
  254. $defaults['HTTPS'] = 'on';
  255. $defaults['SERVER_PORT'] = 443;
  256. }
  257. }
  258. if (isset($components['port'])) {
  259. $defaults['SERVER_PORT'] = $components['port'];
  260. $defaults['HTTP_HOST'] = $defaults['HTTP_HOST'].':'.$components['port'];
  261. }
  262. if (isset($components['user'])) {
  263. $defaults['PHP_AUTH_USER'] = $components['user'];
  264. }
  265. if (isset($components['pass'])) {
  266. $defaults['PHP_AUTH_PW'] = $components['pass'];
  267. }
  268. if (!isset($components['path'])) {
  269. $components['path'] = '/';
  270. }
  271. switch (strtoupper($method)) {
  272. case 'POST':
  273. case 'PUT':
  274. case 'DELETE':
  275. $defaults['CONTENT_TYPE'] = 'application/x-www-form-urlencoded';
  276. case 'PATCH':
  277. $request = $parameters;
  278. $query = array();
  279. break;
  280. default:
  281. $request = array();
  282. $query = $parameters;
  283. break;
  284. }
  285. if (isset($components['query'])) {
  286. parse_str(html_entity_decode($components['query']), $qs);
  287. $query = array_replace($qs, $query);
  288. }
  289. $queryString = http_build_query($query, '', '&');
  290. $uri = $components['path'].('' !== $queryString ? '?'.$queryString : '');
  291. $server = array_replace($defaults, $server, array(
  292. 'REQUEST_METHOD' => strtoupper($method),
  293. 'PATH_INFO' => '',
  294. 'REQUEST_URI' => $uri,
  295. 'QUERY_STRING' => $queryString,
  296. ));
  297. return new static($query, $request, array(), $cookies, $files, $server, $content);
  298. }
  299. /**
  300. * Clones a request and overrides some of its parameters.
  301. *
  302. * @param array $query The GET parameters
  303. * @param array $request The POST parameters
  304. * @param array $attributes The request attributes (parameters parsed from the PATH_INFO, ...)
  305. * @param array $cookies The COOKIE parameters
  306. * @param array $files The FILES parameters
  307. * @param array $server The SERVER parameters
  308. *
  309. * @return Request The duplicated request
  310. *
  311. * @api
  312. */
  313. public function duplicate(array $query = null, array $request = null, array $attributes = null, array $cookies = null, array $files = null, array $server = null)
  314. {
  315. $dup = clone $this;
  316. if ($query !== null) {
  317. $dup->query = new ParameterBag($query);
  318. }
  319. if ($request !== null) {
  320. $dup->request = new ParameterBag($request);
  321. }
  322. if ($attributes !== null) {
  323. $dup->attributes = new ParameterBag($attributes);
  324. }
  325. if ($cookies !== null) {
  326. $dup->cookies = new ParameterBag($cookies);
  327. }
  328. if ($files !== null) {
  329. $dup->files = new FileBag($files);
  330. }
  331. if ($server !== null) {
  332. $dup->server = new ServerBag($server);
  333. $dup->headers = new HeaderBag($dup->server->getHeaders());
  334. }
  335. $dup->languages = null;
  336. $dup->charsets = null;
  337. $dup->acceptableContentTypes = null;
  338. $dup->pathInfo = null;
  339. $dup->requestUri = null;
  340. $dup->baseUrl = null;
  341. $dup->basePath = null;
  342. $dup->method = null;
  343. $dup->format = null;
  344. return $dup;
  345. }
  346. /**
  347. * Clones the current request.
  348. *
  349. * Note that the session is not cloned as duplicated requests
  350. * are most of the time sub-requests of the main one.
  351. */
  352. public function __clone()
  353. {
  354. $this->query = clone $this->query;
  355. $this->request = clone $this->request;
  356. $this->attributes = clone $this->attributes;
  357. $this->cookies = clone $this->cookies;
  358. $this->files = clone $this->files;
  359. $this->server = clone $this->server;
  360. $this->headers = clone $this->headers;
  361. }
  362. /**
  363. * Returns the request as a string.
  364. *
  365. * @return string The request
  366. */
  367. public function __toString()
  368. {
  369. return
  370. sprintf('%s %s %s', $this->getMethod(), $this->getRequestUri(), $this->server->get('SERVER_PROTOCOL'))."\r\n".
  371. $this->headers."\r\n".
  372. $this->getContent();
  373. }
  374. /**
  375. * Overrides the PHP global variables according to this request instance.
  376. *
  377. * It overrides $_GET, $_POST, $_REQUEST, $_SERVER, $_COOKIE.
  378. * $_FILES is never override, see rfc1867
  379. *
  380. * @api
  381. */
  382. public function overrideGlobals()
  383. {
  384. $_GET = $this->query->all();
  385. $_POST = $this->request->all();
  386. $_SERVER = $this->server->all();
  387. $_COOKIE = $this->cookies->all();
  388. foreach ($this->headers->all() as $key => $value) {
  389. $key = strtoupper(str_replace('-', '_', $key));
  390. if (in_array($key, array('CONTENT_TYPE', 'CONTENT_LENGTH'))) {
  391. $_SERVER[$key] = implode(', ', $value);
  392. } else {
  393. $_SERVER['HTTP_'.$key] = implode(', ', $value);
  394. }
  395. }
  396. $request = array('g' => $_GET, 'p' => $_POST, 'c' => $_COOKIE);
  397. $requestOrder = ini_get('request_order') ?: ini_get('variable_order');
  398. $requestOrder = preg_replace('#[^cgp]#', '', strtolower($requestOrder)) ?: 'gp';
  399. $_REQUEST = array();
  400. foreach (str_split($requestOrder) as $order) {
  401. $_REQUEST = array_merge($_REQUEST, $request[$order]);
  402. }
  403. }
  404. /**
  405. * Trusts $_SERVER entries coming from proxies.
  406. *
  407. * @deprecated Deprecated since version 2.0, to be removed in 2.3. Use setTrustedProxies instead.
  408. */
  409. public static function trustProxyData()
  410. {
  411. self::$trustProxy = true;
  412. }
  413. /**
  414. * Sets a list of trusted proxies.
  415. *
  416. * You should only list the reverse proxies that you manage directly.
  417. *
  418. * @param array $proxies A list of trusted proxies
  419. *
  420. * @api
  421. */
  422. public static function setTrustedProxies(array $proxies)
  423. {
  424. self::$trustedProxies = $proxies;
  425. self::$trustProxy = $proxies ? true : false;
  426. }
  427. /**
  428. * Sets the name for trusted headers.
  429. *
  430. * The following header keys are supported:
  431. *
  432. * * Request::HEADER_CLIENT_IP: defaults to X-Forwarded-For (see getClientIp())
  433. * * Request::HEADER_CLIENT_HOST: defaults to X-Forwarded-Host (see getClientHost())
  434. * * Request::HEADER_CLIENT_PORT: defaults to X-Forwarded-Port (see getClientPort())
  435. * * Request::HEADER_CLIENT_PROTO: defaults to X-Forwarded-Proto (see getScheme() and isSecure())
  436. *
  437. * Setting an empty value allows to disable the trusted header for the given key.
  438. *
  439. * @param string $key The header key
  440. * @param string $value The header name
  441. */
  442. public static function setTrustedHeaderName($key, $value)
  443. {
  444. if (!array_key_exists($key, self::$trustedHeaders)) {
  445. throw new \InvalidArgumentException(sprintf('Unable to set the trusted header name for key "%s".', $key));
  446. }
  447. self::$trustedHeaders[$key] = $value;
  448. }
  449. /**
  450. * Returns true if $_SERVER entries coming from proxies are trusted,
  451. * false otherwise.
  452. *
  453. * @return boolean
  454. */
  455. public static function isProxyTrusted()
  456. {
  457. return self::$trustProxy;
  458. }
  459. /**
  460. * Normalizes a query string.
  461. *
  462. * It builds a normalized query string, where keys/value pairs are alphabetized,
  463. * have consistent escaping and unneeded delimiters are removed.
  464. *
  465. * @param string $qs Query string
  466. *
  467. * @return string A normalized query string for the Request
  468. */
  469. public static function normalizeQueryString($qs)
  470. {
  471. if ('' == $qs) {
  472. return '';
  473. }
  474. $parts = array();
  475. $order = array();
  476. foreach (explode('&', $qs) as $param) {
  477. if ('' === $param || '=' === $param[0]) {
  478. // Ignore useless delimiters, e.g. "x=y&".
  479. // Also ignore pairs with empty key, even if there was a value, e.g. "=value", as such nameless values cannot be retrieved anyway.
  480. // PHP also does not include them when building _GET.
  481. continue;
  482. }
  483. $keyValuePair = explode('=', $param, 2);
  484. // GET parameters, that are submitted from a HTML form, encode spaces as "+" by default (as defined in enctype application/x-www-form-urlencoded).
  485. // PHP also converts "+" to spaces when filling the global _GET or when using the function parse_str. This is why we use urldecode and then normalize to
  486. // RFC 3986 with rawurlencode.
  487. $parts[] = isset($keyValuePair[1]) ?
  488. rawurlencode(urldecode($keyValuePair[0])).'='.rawurlencode(urldecode($keyValuePair[1])) :
  489. rawurlencode(urldecode($keyValuePair[0]));
  490. $order[] = urldecode($keyValuePair[0]);
  491. }
  492. array_multisort($order, SORT_ASC, $parts);
  493. return implode('&', $parts);
  494. }
  495. /**
  496. * Gets a "parameter" value.
  497. *
  498. * This method is mainly useful for libraries that want to provide some flexibility.
  499. *
  500. * Order of precedence: GET, PATH, POST
  501. *
  502. * Avoid using this method in controllers:
  503. *
  504. * * slow
  505. * * prefer to get from a "named" source
  506. *
  507. * It is better to explicitly get request parameters from the appropriate
  508. * public property instead (query, attributes, request).
  509. *
  510. * @param string $key the key
  511. * @param mixed $default the default value
  512. * @param Boolean $deep is parameter deep in multidimensional array
  513. *
  514. * @return mixed
  515. */
  516. public function get($key, $default = null, $deep = false)
  517. {
  518. return $this->query->get($key, $this->attributes->get($key, $this->request->get($key, $default, $deep), $deep), $deep);
  519. }
  520. /**
  521. * Gets the Session.
  522. *
  523. * @return SessionInterface|null The session
  524. *
  525. * @api
  526. */
  527. public function getSession()
  528. {
  529. return $this->session;
  530. }
  531. /**
  532. * Whether the request contains a Session which was started in one of the
  533. * previous requests.
  534. *
  535. * @return Boolean
  536. *
  537. * @api
  538. */
  539. public function hasPreviousSession()
  540. {
  541. // the check for $this->session avoids malicious users trying to fake a session cookie with proper name
  542. return $this->hasSession() && $this->cookies->has($this->session->getName());
  543. }
  544. /**
  545. * Whether the request contains a Session object.
  546. *
  547. * This method does not give any information about the state of the session object,
  548. * like whether the session is started or not. It is just a way to check if this Request
  549. * is associated with a Session instance.
  550. *
  551. * @return Boolean true when the Request contains a Session object, false otherwise
  552. *
  553. * @api
  554. */
  555. public function hasSession()
  556. {
  557. return null !== $this->session;
  558. }
  559. /**
  560. * Sets the Session.
  561. *
  562. * @param SessionInterface $session The Session
  563. *
  564. * @api
  565. */
  566. public function setSession(SessionInterface $session)
  567. {
  568. $this->session = $session;
  569. }
  570. /**
  571. * Returns the client IP address.
  572. *
  573. * This method can read the client IP address from the "X-Forwarded-For" header
  574. * when trusted proxies were set via "setTrustedProxies()". The "X-Forwarded-For"
  575. * header value is a comma+space separated list of IP addresses, the left-most
  576. * being the original client, and each successive proxy that passed the request
  577. * adding the IP address where it received the request from.
  578. *
  579. * If your reverse proxy uses a different header name than "X-Forwarded-For",
  580. * ("Client-Ip" for instance), configure it via "setTrustedHeaderName()" with
  581. * the "client-ip" key.
  582. *
  583. * @return string The client IP address
  584. *
  585. * @see http://en.wikipedia.org/wiki/X-Forwarded-For
  586. *
  587. * @deprecated The proxy argument is deprecated since version 2.0 and will be removed in 2.3. Use setTrustedProxies instead.
  588. *
  589. * @api
  590. */
  591. public function getClientIp()
  592. {
  593. $ip = $this->server->get('REMOTE_ADDR');
  594. if (!self::$trustProxy) {
  595. return $ip;
  596. }
  597. if (!self::$trustedHeaders[self::HEADER_CLIENT_IP] || !$this->headers->has(self::$trustedHeaders[self::HEADER_CLIENT_IP])) {
  598. return $ip;
  599. }
  600. $clientIps = array_map('trim', explode(',', $this->headers->get(self::$trustedHeaders[self::HEADER_CLIENT_IP])));
  601. $clientIps[] = $ip;
  602. $trustedProxies = self::$trustProxy && !self::$trustedProxies ? array($ip) : self::$trustedProxies;
  603. $clientIps = array_diff($clientIps, $trustedProxies);
  604. return array_pop($clientIps);
  605. }
  606. /**
  607. * Returns current script name.
  608. *
  609. * @return string
  610. *
  611. * @api
  612. */
  613. public function getScriptName()
  614. {
  615. return $this->server->get('SCRIPT_NAME', $this->server->get('ORIG_SCRIPT_NAME', ''));
  616. }
  617. /**
  618. * Returns the path being requested relative to the executed script.
  619. *
  620. * The path info always starts with a /.
  621. *
  622. * Suppose this request is instantiated from /mysite on localhost:
  623. *
  624. * * http://localhost/mysite returns an empty string
  625. * * http://localhost/mysite/about returns '/about'
  626. * * htpp://localhost/mysite/enco%20ded returns '/enco%20ded'
  627. * * http://localhost/mysite/about?var=1 returns '/about'
  628. *
  629. * @return string The raw path (i.e. not urldecoded)
  630. *
  631. * @api
  632. */
  633. public function getPathInfo()
  634. {
  635. if (null === $this->pathInfo) {
  636. $this->pathInfo = $this->preparePathInfo();
  637. }
  638. return $this->pathInfo;
  639. }
  640. /**
  641. * Returns the root path from which this request is executed.
  642. *
  643. * Suppose that an index.php file instantiates this request object:
  644. *
  645. * * http://localhost/index.php returns an empty string
  646. * * http://localhost/index.php/page returns an empty string
  647. * * http://localhost/web/index.php returns '/web'
  648. * * http://localhost/we%20b/index.php returns '/we%20b'
  649. *
  650. * @return string The raw path (i.e. not urldecoded)
  651. *
  652. * @api
  653. */
  654. public function getBasePath()
  655. {
  656. if (null === $this->basePath) {
  657. $this->basePath = $this->prepareBasePath();
  658. }
  659. return $this->basePath;
  660. }
  661. /**
  662. * Returns the root url from which this request is executed.
  663. *
  664. * The base URL never ends with a /.
  665. *
  666. * This is similar to getBasePath(), except that it also includes the
  667. * script filename (e.g. index.php) if one exists.
  668. *
  669. * @return string The raw url (i.e. not urldecoded)
  670. *
  671. * @api
  672. */
  673. public function getBaseUrl()
  674. {
  675. if (null === $this->baseUrl) {
  676. $this->baseUrl = $this->prepareBaseUrl();
  677. }
  678. return $this->baseUrl;
  679. }
  680. /**
  681. * Gets the request's scheme.
  682. *
  683. * @return string
  684. *
  685. * @api
  686. */
  687. public function getScheme()
  688. {
  689. return $this->isSecure() ? 'https' : 'http';
  690. }
  691. /**
  692. * Returns the port on which the request is made.
  693. *
  694. * This method can read the client port from the "X-Forwarded-Port" header
  695. * when trusted proxies were set via "setTrustedProxies()".
  696. *
  697. * The "X-Forwarded-Port" header must contain the client port.
  698. *
  699. * If your reverse proxy uses a different header name than "X-Forwarded-Port",
  700. * configure it via "setTrustedHeaderName()" with the "client-port" key.
  701. *
  702. * @return string
  703. *
  704. * @api
  705. */
  706. public function getPort()
  707. {
  708. if (self::$trustProxy && self::$trustedHeaders[self::HEADER_CLIENT_PORT] && $port = $this->headers->get(self::$trustedHeaders[self::HEADER_CLIENT_PORT])) {
  709. return $port;
  710. }
  711. return $this->server->get('SERVER_PORT');
  712. }
  713. /**
  714. * Returns the user.
  715. *
  716. * @return string|null
  717. */
  718. public function getUser()
  719. {
  720. return $this->server->get('PHP_AUTH_USER');
  721. }
  722. /**
  723. * Returns the password.
  724. *
  725. * @return string|null
  726. */
  727. public function getPassword()
  728. {
  729. return $this->server->get('PHP_AUTH_PW');
  730. }
  731. /**
  732. * Gets the user info.
  733. *
  734. * @return string A user name and, optionally, scheme-specific information about how to gain authorization to access the server
  735. */
  736. public function getUserInfo()
  737. {
  738. $userinfo = $this->getUser();
  739. $pass = $this->getPassword();
  740. if ('' != $pass) {
  741. $userinfo .= ":$pass";
  742. }
  743. return $userinfo;
  744. }
  745. /**
  746. * Returns the HTTP host being requested.
  747. *
  748. * The port name will be appended to the host if it's non-standard.
  749. *
  750. * @return string
  751. *
  752. * @api
  753. */
  754. public function getHttpHost()
  755. {
  756. $scheme = $this->getScheme();
  757. $port = $this->getPort();
  758. if (('http' == $scheme && $port == 80) || ('https' == $scheme && $port == 443)) {
  759. return $this->getHost();
  760. }
  761. return $this->getHost().':'.$port;
  762. }
  763. /**
  764. * Returns the requested URI.
  765. *
  766. * @return string The raw URI (i.e. not urldecoded)
  767. *
  768. * @api
  769. */
  770. public function getRequestUri()
  771. {
  772. if (null === $this->requestUri) {
  773. $this->requestUri = $this->prepareRequestUri();
  774. }
  775. return $this->requestUri;
  776. }
  777. /**
  778. * Gets the scheme and HTTP host.
  779. *
  780. * If the URL was called with basic authentication, the user
  781. * and the password are not added to the generated string.
  782. *
  783. * @return string The scheme and HTTP host
  784. */
  785. public function getSchemeAndHttpHost()
  786. {
  787. return $this->getScheme().'://'.$this->getHttpHost();
  788. }
  789. /**
  790. * Generates a normalized URI for the Request.
  791. *
  792. * @return string A normalized URI for the Request
  793. *
  794. * @see getQueryString()
  795. *
  796. * @api
  797. */
  798. public function getUri()
  799. {
  800. $qs = $this->getQueryString();
  801. if (null !== $qs) {
  802. $qs = '?'.$qs;
  803. }
  804. return $this->getSchemeAndHttpHost().$this->getBaseUrl().$this->getPathInfo().$qs;
  805. }
  806. /**
  807. * Generates a normalized URI for the given path.
  808. *
  809. * @param string $path A path to use instead of the current one
  810. *
  811. * @return string The normalized URI for the path
  812. *
  813. * @api
  814. */
  815. public function getUriForPath($path)
  816. {
  817. return $this->getSchemeAndHttpHost().$this->getBaseUrl().$path;
  818. }
  819. /**
  820. * Generates the normalized query string for the Request.
  821. *
  822. * It builds a normalized query string, where keys/value pairs are alphabetized
  823. * and have consistent escaping.
  824. *
  825. * @return string|null A normalized query string for the Request
  826. *
  827. * @api
  828. */
  829. public function getQueryString()
  830. {
  831. $qs = static::normalizeQueryString($this->server->get('QUERY_STRING'));
  832. return '' === $qs ? null : $qs;
  833. }
  834. /**
  835. * Checks whether the request is secure or not.
  836. *
  837. * This method can read the client port from the "X-Forwarded-Proto" header
  838. * when trusted proxies were set via "setTrustedProxies()".
  839. *
  840. * The "X-Forwarded-Proto" header must contain the protocol: "https" or "http".
  841. *
  842. * If your reverse proxy uses a different header name than "X-Forwarded-Proto"
  843. * ("SSL_HTTPS" for instance), configure it via "setTrustedHeaderName()" with
  844. * the "client-proto" key.
  845. *
  846. * @return Boolean
  847. *
  848. * @api
  849. */
  850. public function isSecure()
  851. {
  852. if (self::$trustProxy && self::$trustedHeaders[self::HEADER_CLIENT_PROTO] && $proto = $this->headers->get(self::$trustedHeaders[self::HEADER_CLIENT_PROTO])) {
  853. return in_array(strtolower($proto), array('https', 'on', '1'));
  854. }
  855. return 'on' == strtolower($this->server->get('HTTPS')) || 1 == $this->server->get('HTTPS');
  856. }
  857. /**
  858. * Returns the host name.
  859. *
  860. * This method can read the client port from the "X-Forwarded-Host" header
  861. * when trusted proxies were set via "setTrustedProxies()".
  862. *
  863. * The "X-Forwarded-Host" header must contain the client host name.
  864. *
  865. * If your reverse proxy uses a different header name than "X-Forwarded-Host",
  866. * configure it via "setTrustedHeaderName()" with the "client-host" key.
  867. *
  868. * @return string
  869. *
  870. * @throws \UnexpectedValueException when the host name is invalid
  871. *
  872. * @api
  873. */
  874. public function getHost()
  875. {
  876. if (self::$trustProxy && self::$trustedHeaders[self::HEADER_CLIENT_HOST] && $host = $this->headers->get(self::$trustedHeaders[self::HEADER_CLIENT_HOST])) {
  877. $elements = explode(',', $host);
  878. $host = $elements[count($elements) - 1];
  879. } elseif (!$host = $this->headers->get('HOST')) {
  880. if (!$host = $this->server->get('SERVER_NAME')) {
  881. $host = $this->server->get('SERVER_ADDR', '');
  882. }
  883. }
  884. // trim and remove port number from host
  885. // host is lowercase as per RFC 952/2181
  886. $host = strtolower(preg_replace('/:\d+$/', '', trim($host)));
  887. // as the host can come from the user (HTTP_HOST and depending on the configuration, SERVER_NAME too can come from the user)
  888. // check that it does not contain forbidden characters (see RFC 952 and RFC 2181)
  889. if ($host && !preg_match('/^\[?(?:[a-zA-Z0-9-:\]_]+\.?)+$/', $host)) {
  890. throw new \UnexpectedValueException('Invalid Host');
  891. }
  892. return $host;
  893. }
  894. /**
  895. * Sets the request method.
  896. *
  897. * @param string $method
  898. *
  899. * @api
  900. */
  901. public function setMethod($method)
  902. {
  903. $this->method = null;
  904. $this->server->set('REQUEST_METHOD', $method);
  905. }
  906. /**
  907. * Gets the request method.
  908. *
  909. * The method is always an uppercased string.
  910. *
  911. * @return string The request method
  912. *
  913. * @api
  914. */
  915. public function getMethod()
  916. {
  917. if (null === $this->method) {
  918. $this->method = strtoupper($this->server->get('REQUEST_METHOD', 'GET'));
  919. if ('POST' === $this->method) {
  920. $this->method = strtoupper($this->headers->get('X-HTTP-METHOD-OVERRIDE', $this->request->get('_method', $this->query->get('_method', 'POST'))));
  921. }
  922. }
  923. return $this->method;
  924. }
  925. /**
  926. * Gets the mime type associated with the format.
  927. *
  928. * @param string $format The format
  929. *
  930. * @return string The associated mime type (null if not found)
  931. *
  932. * @api
  933. */
  934. public function getMimeType($format)
  935. {
  936. if (null === static::$formats) {
  937. static::initializeFormats();
  938. }
  939. return isset(static::$formats[$format]) ? static::$formats[$format][0] : null;
  940. }
  941. /**
  942. * Gets the format associated with the mime type.
  943. *
  944. * @param string $mimeType The associated mime type
  945. *
  946. * @return string|null The format (null if not found)
  947. *
  948. * @api
  949. */
  950. public function getFormat($mimeType)
  951. {
  952. if (false !== $pos = strpos($mimeType, ';')) {
  953. $mimeType = substr($mimeType, 0, $pos);
  954. }
  955. if (null === static::$formats) {
  956. static::initializeFormats();
  957. }
  958. foreach (static::$formats as $format => $mimeTypes) {
  959. if (in_array($mimeType, (array) $mimeTypes)) {
  960. return $format;
  961. }
  962. }
  963. return null;
  964. }
  965. /**
  966. * Associates a format with mime types.
  967. *
  968. * @param string $format The format
  969. * @param string|array $mimeTypes The associated mime types (the preferred one must be the first as it will be used as the content type)
  970. *
  971. * @api
  972. */
  973. public function setFormat($format, $mimeTypes)
  974. {
  975. if (null === static::$formats) {
  976. static::initializeFormats();
  977. }
  978. static::$formats[$format] = is_array($mimeTypes) ? $mimeTypes : array($mimeTypes);
  979. }
  980. /**
  981. * Gets the request format.
  982. *
  983. * Here is the process to determine the format:
  984. *
  985. * * format defined by the user (with setRequestFormat())
  986. * * _format request parameter
  987. * * $default
  988. *
  989. * @param string $default The default format
  990. *
  991. * @return string The request format
  992. *
  993. * @api
  994. */
  995. public function getRequestFormat($default = 'html')
  996. {
  997. if (null === $this->format) {
  998. $this->format = $this->get('_format', $default);
  999. }
  1000. return $this->format;
  1001. }
  1002. /**
  1003. * Sets the request format.
  1004. *
  1005. * @param string $format The request format.
  1006. *
  1007. * @api
  1008. */
  1009. public function setRequestFormat($format)
  1010. {
  1011. $this->format = $format;
  1012. }
  1013. /**
  1014. * Gets the format associated with the request.
  1015. *
  1016. * @return string|null The format (null if no content type is present)
  1017. *
  1018. * @api
  1019. */
  1020. public function getContentType()
  1021. {
  1022. return $this->getFormat($this->headers->get('CONTENT_TYPE'));
  1023. }
  1024. /**
  1025. * Sets the default locale.
  1026. *
  1027. * @param string $locale
  1028. *
  1029. * @api
  1030. */
  1031. public function setDefaultLocale($locale)
  1032. {
  1033. $this->defaultLocale = $locale;
  1034. if (null === $this->locale) {
  1035. $this->setPhpDefaultLocale($locale);
  1036. }
  1037. }
  1038. /**
  1039. * Sets the locale.
  1040. *
  1041. * @param string $locale
  1042. *
  1043. * @api
  1044. */
  1045. public function setLocale($locale)
  1046. {
  1047. $this->setPhpDefaultLocale($this->locale = $locale);
  1048. }
  1049. /**
  1050. * Get the locale.
  1051. *
  1052. * @return string
  1053. */
  1054. public function getLocale()
  1055. {
  1056. return null === $this->locale ? $this->defaultLocale : $this->locale;
  1057. }
  1058. /**
  1059. * Checks if the request method is of specified type.
  1060. *
  1061. * @param string $method Uppercase request method (GET, POST etc).
  1062. *
  1063. * @return Boolean
  1064. */
  1065. public function isMethod($method)
  1066. {
  1067. return $this->getMethod() === strtoupper($method);
  1068. }
  1069. /**
  1070. * Checks whether the method is safe or not.
  1071. *
  1072. * @return Boolean
  1073. *
  1074. * @api
  1075. */
  1076. public function isMethodSafe()
  1077. {
  1078. return in_array($this->getMethod(), array('GET', 'HEAD'));
  1079. }
  1080. /**
  1081. * Returns the request body content.
  1082. *
  1083. * @param Boolean $asResource If true, a resource will be returned
  1084. *
  1085. * @return string|resource The request body content or a resource to read the body stream.
  1086. */
  1087. public function getContent($asResource = false)
  1088. {
  1089. if (false === $this->content || (true === $asResource && null !== $this->content)) {
  1090. throw new \LogicException('getContent() can only be called once when using the resource return type.');
  1091. }
  1092. if (true === $asResource) {
  1093. $this->content = false;
  1094. return fopen('php://input', 'rb');
  1095. }
  1096. if (null === $this->content) {
  1097. $this->content = file_get_contents('php://input');
  1098. }
  1099. return $this->content;
  1100. }
  1101. /**
  1102. * Gets the Etags.
  1103. *
  1104. * @return array The entity tags
  1105. */
  1106. public function getETags()
  1107. {
  1108. return preg_split('/\s*,\s*/', $this->headers->get('if_none_match'), null, PREG_SPLIT_NO_EMPTY);
  1109. }
  1110. /**
  1111. * @return Boolean
  1112. */
  1113. public function isNoCache()
  1114. {
  1115. return $this->headers->hasCacheControlDirective('no-cache') || 'no-cache' == $this->headers->get('Pragma');
  1116. }
  1117. /**
  1118. * Returns the preferred language.
  1119. *
  1120. * @param array $locales An array of ordered available locales
  1121. *
  1122. * @return string|null The preferred locale
  1123. *
  1124. * @api
  1125. */
  1126. public function getPreferredLanguage(array $locales = null)
  1127. {
  1128. $preferredLanguages = $this->getLanguages();
  1129. if (empty($locales)) {
  1130. return isset($preferredLanguages[0]) ? $preferredLanguages[0] : null;
  1131. }
  1132. if (!$preferredLanguages) {
  1133. return $locales[0];
  1134. }
  1135. $preferredLanguages = array_values(array_intersect($preferredLanguages, $locales));
  1136. return isset($preferredLanguages[0]) ? $preferredLanguages[0] : $locales[0];
  1137. }
  1138. /**
  1139. * Gets a list of languages acceptable by the client browser.
  1140. *
  1141. * @return array Languages ordered in the user browser preferences
  1142. *
  1143. * @api
  1144. */
  1145. public function getLanguages()
  1146. {
  1147. if (null !== $this->languages) {
  1148. return $this->languages;
  1149. }
  1150. $languages = $this->splitHttpAcceptHeader($this->headers->get('Accept-Language'));
  1151. $this->languages = array();
  1152. foreach ($languages as $lang => $q) {
  1153. if (strstr($lang, '-')) {
  1154. $codes = explode('-', $lang);
  1155. if ($codes[0] == 'i') {
  1156. // Language not listed in ISO 639 that are not variants
  1157. // of any listed language, which can be registered with the
  1158. // i-prefix, such as i-cherokee
  1159. if (count($codes) > 1) {
  1160. $lang = $codes[1];
  1161. }
  1162. } else {
  1163. for ($i = 0, $max = count($codes); $i < $max; $i++) {
  1164. if ($i == 0) {
  1165. $lang = strtolower($codes[0]);
  1166. } else {
  1167. $lang .= '_'.strtoupper($codes[$i]);
  1168. }
  1169. }
  1170. }
  1171. }
  1172. $this->languages[] = $lang;
  1173. }
  1174. return $this->languages;
  1175. }
  1176. /**
  1177. * Gets a list of charsets acceptable by the client browser.
  1178. *
  1179. * @return array List of charsets in preferable order
  1180. *
  1181. * @api
  1182. */
  1183. public function getCharsets()
  1184. {
  1185. if (null !== $this->charsets) {
  1186. return $this->charsets;
  1187. }
  1188. return $this->charsets = array_keys($this->splitHttpAcceptHeader($this->headers->get('Accept-Charset')));
  1189. }
  1190. /**
  1191. * Gets a list of content types acceptable by the client browser
  1192. *
  1193. * @return array List of content types in preferable order
  1194. *
  1195. * @api
  1196. */
  1197. public function getAcceptableContentTypes()
  1198. {
  1199. if (null !== $this->acceptableContentTypes) {
  1200. return $this->acceptableContentTypes;
  1201. }
  1202. return $this->acceptableContentTypes = array_keys($this->splitHttpAcceptHeader($this->headers->get('Accept')));
  1203. }
  1204. /**
  1205. * Returns true if the request is a XMLHttpRequest.
  1206. *
  1207. * It works if your JavaScript library set an X-Requested-With HTTP header.
  1208. * It is known to work with Prototype, Mootools, jQuery.
  1209. *
  1210. * @return Boolean true if the request is an XMLHttpRequest, false otherwise
  1211. *
  1212. * @api
  1213. */
  1214. public function isXmlHttpRequest()
  1215. {
  1216. return 'XMLHttpRequest' == $this->headers->get('X-Requested-With');
  1217. }
  1218. /**
  1219. * Splits an Accept-* HTTP header.
  1220. *
  1221. * @param string $header Header to split
  1222. *
  1223. * @return array Array indexed by the values of the Accept-* header in preferred order
  1224. */
  1225. public function splitHttpAcceptHeader($header)
  1226. {
  1227. if (!$header) {
  1228. return array();
  1229. }
  1230. $values = array();
  1231. $groups = array();
  1232. foreach (array_filter(explode(',', $header)) as $value) {
  1233. // Cut off any q-value that might come after a semi-colon
  1234. if (preg_match('/;\s*(q=.*$)/', $value, $match)) {
  1235. $q = substr(trim($match[1]), 2);
  1236. $value = trim(substr($value, 0, -strlen($match[0])));
  1237. } else {
  1238. $q = 1;
  1239. }
  1240. $groups[$q][] = $value;
  1241. }
  1242. krsort($groups);
  1243. foreach ($groups as $q => $items) {
  1244. $q = (float) $q;
  1245. if (0 < $q) {
  1246. foreach ($items as $value) {
  1247. $values[trim($value)] = $q;
  1248. }
  1249. }
  1250. }
  1251. return $values;
  1252. }
  1253. /*
  1254. * The following methods are derived from code of the Zend Framework (1.10dev - 2010-01-24)
  1255. *
  1256. * Code subject to the new BSD license (http://framework.zend.com/license/new-bsd).
  1257. *
  1258. * Copyright (c) 2005-2010 Zend Technologies USA Inc. (http://www.zend.com)
  1259. */
  1260. protected function prepareRequestUri()
  1261. {
  1262. $requestUri = '';
  1263. if ($this->headers->has('X_ORIGINAL_URL') && false !== stripos(PHP_OS, 'WIN')) {
  1264. // IIS with Microsoft Rewrite Module
  1265. $requestUri = $this->headers->get('X_ORIGINAL_URL');
  1266. } elseif ($this->headers->has('X_REWRITE_URL') && false !== stripos(PHP_OS, 'WIN')) {
  1267. // IIS with ISAPI_Rewrite
  1268. $requestUri = $this->headers->get('X_REWRITE_URL');
  1269. } elseif ($this->server->get('IIS_WasUrlRewritten') == '1' && $this->server->get('UNENCODED_URL') != '') {
  1270. // IIS7 with URL Rewrite: make sure we get the unencoded url (double slash problem)
  1271. $requestUri = $this->server->get('UNENCODED_URL');
  1272. } elseif ($this->server->has('REQUEST_URI')) {
  1273. $requestUri = $this->server->get('REQUEST_URI');
  1274. // HTTP proxy reqs setup request uri with scheme and host [and port] + the url path, only use url path
  1275. $schemeAndHttpHost = $this->getSchemeAndHttpHost();
  1276. if (strpos($requestUri, $schemeAndHttpHost) === 0) {
  1277. $requestUri = substr($requestUri, strlen($schemeAndHttpHost));
  1278. }
  1279. } elseif ($this->server->has('ORIG_PATH_INFO')) {
  1280. // IIS 5.0, PHP as CGI
  1281. $requestUri = $this->server->get('ORIG_PATH_INFO');
  1282. if ('' != $this->server->get('QUERY_STRING')) {
  1283. $requestUri .= '?'.$this->server->get('QUERY_STRING');
  1284. }
  1285. }
  1286. return $requestUri;
  1287. }
  1288. /**
  1289. * Prepares the base URL.
  1290. *
  1291. * @return string
  1292. */
  1293. protected function prepareBaseUrl()
  1294. {
  1295. $filename = basename($this->server->get('SCRIPT_FILENAME'));
  1296. if (basename($this->server->get('SCRIPT_NAME')) === $filename) {
  1297. $baseUrl = $this->server->get('SCRIPT_NAME');
  1298. } elseif (basename($this->server->get('PHP_SELF')) === $filename) {
  1299. $baseUrl = $this->server->get('PHP_SELF');
  1300. } elseif (basename($this->server->get('ORIG_SCRIPT_NAME')) === $filename) {
  1301. $baseUrl = $this->server->get('ORIG_SCRIPT_NAME'); // 1and1 shared hosting compatibility
  1302. } else {
  1303. // Backtrack up the script_filename to find the portion matching
  1304. // php_self
  1305. $path = $this->server->get('PHP_SELF', '');
  1306. $file = $this->server->get('SCRIPT_FILENAME', '');
  1307. $segs = explode('/', trim($file, '/'));
  1308. $segs = array_reverse($segs);
  1309. $index = 0;
  1310. $last = count($segs);
  1311. $baseUrl = '';
  1312. do {
  1313. $seg = $segs[$index];
  1314. $baseUrl = '/'.$seg.$baseUrl;
  1315. ++$index;
  1316. } while (($last > $index) && (false !== ($pos = strpos($path, $baseUrl))) && (0 != $pos));
  1317. }
  1318. // Does the baseUrl have anything in common with the request_uri?
  1319. $requestUri = $this->getRequestUri();
  1320. if ($baseUrl && false !== $prefix = $this->getUrlencodedPrefix($requestUri, $baseUrl)) {
  1321. // full $baseUrl matches
  1322. return $prefix;
  1323. }
  1324. if ($baseUrl && false !== $prefix = $this->getUrlencodedPrefix($requestUri, dirname($baseUrl))) {
  1325. // directory portion of $baseUrl matches
  1326. return rtrim($prefix, '/');
  1327. }
  1328. $truncatedRequestUri = $requestUri;
  1329. if (($pos = strpos($requestUri, '?')) !== false) {
  1330. $truncatedRequestUri = substr($requestUri, 0, $pos);
  1331. }
  1332. $basename = basename($baseUrl);
  1333. if (empty($basename) || !strpos(rawurldecode($truncatedRequestUri), $basename)) {
  1334. // no match whatsoever; set it blank
  1335. return '';
  1336. }
  1337. // If using mod_rewrite or ISAPI_Rewrite strip the script filename
  1338. // out of baseUrl. $pos !== 0 makes sure it is not matching a value
  1339. // from PATH_INFO or QUERY_STRING
  1340. if ((strlen($requestUri) >= strlen($baseUrl)) && ((false !== ($pos = strpos($requestUri, $baseUrl))) && ($pos !== 0))) {
  1341. $baseUrl = substr($requestUri, 0, $pos + strlen($baseUrl));
  1342. }
  1343. return rtrim($baseUrl, '/');
  1344. }
  1345. /**
  1346. * Prepares the base path.
  1347. *
  1348. * @return string base path
  1349. */
  1350. protected function prepareBasePath()
  1351. {
  1352. $filename = basename($this->server->get('SCRIPT_FILENAME'));
  1353. $baseUrl = $this->getBaseUrl();
  1354. if (empty($baseUrl)) {
  1355. return '';
  1356. }
  1357. if (basename($baseUrl) === $filename) {
  1358. $basePath = dirname($baseUrl);
  1359. } else {
  1360. $basePath = $baseUrl;
  1361. }
  1362. if ('\\' === DIRECTORY_SEPARATOR) {
  1363. $basePath = str_replace('\\', '/', $basePath);
  1364. }
  1365. return rtrim($basePath, '/');
  1366. }
  1367. /**
  1368. * Prepares the path info.
  1369. *
  1370. * @return string path info
  1371. */
  1372. protected function preparePathInfo()
  1373. {
  1374. $baseUrl = $this->getBaseUrl();
  1375. if (null === ($requestUri = $this->getRequestUri())) {
  1376. return '/';
  1377. }
  1378. $pathInfo = '/';
  1379. // Remove the query string from REQUEST_URI
  1380. if ($pos = strpos($requestUri, '?')) {
  1381. $requestUri = substr($requestUri, 0, $pos);
  1382. }
  1383. if ((null !== $baseUrl) && (false === ($pathInfo = substr($requestUri, strlen($baseUrl))))) {
  1384. // If substr() returns false then PATH_INFO is set to an empty string
  1385. return '/';
  1386. } elseif (null === $baseUrl) {
  1387. return $requestUri;
  1388. }
  1389. return (string) $pathInfo;
  1390. }
  1391. /**
  1392. * Initializes HTTP request formats.
  1393. */
  1394. protected static function initializeFormats()
  1395. {
  1396. static::$formats = array(
  1397. 'html' => array('text/html', 'application/xhtml+xml'),
  1398. 'txt' => array('text/plain'),
  1399. 'js' => array('application/javascript', 'application/x-javascript', 'text/javascript'),
  1400. 'css' => array('text/css'),
  1401. 'json' => array('application/json', 'application/x-json'),
  1402. 'xml' => array('text/xml', 'application/xml', 'application/x-xml'),
  1403. 'rdf' => array('application/rdf+xml'),
  1404. 'atom' => array('application/atom+xml'),
  1405. 'rss' => array('application/rss+xml'),
  1406. );
  1407. }
  1408. /**
  1409. * Sets the default PHP locale.
  1410. *
  1411. * @param string $locale
  1412. */
  1413. private function setPhpDefaultLocale($locale)
  1414. {
  1415. // if either the class Locale doesn't exist, or an exception is thrown when
  1416. // setting the default locale, the intl module is not installed, and
  1417. // the call can be ignored:
  1418. try {
  1419. if (class_exists('Locale', false)) {
  1420. \Locale::setDefault($locale);
  1421. }
  1422. } catch (\Exception $e) {
  1423. }
  1424. }
  1425. /*
  1426. * Returns the prefix as encoded in the string when the string starts with
  1427. * the given prefix, false otherwise.
  1428. *
  1429. * @param string $string The urlencoded string
  1430. * @param string $prefix The prefix not encoded
  1431. *
  1432. * @return string|false The prefix as it is encoded in $string, or false
  1433. */
  1434. private function getUrlencodedPrefix($string, $prefix)
  1435. {
  1436. if (0 !== strpos(rawurldecode($string), $prefix)) {
  1437. return false;
  1438. }
  1439. $len = strlen($prefix);
  1440. if (preg_match("#^(%[[:xdigit:]]{2}|.){{$len}}#", $string, $match)) {
  1441. return $match[0];
  1442. }
  1443. return false;
  1444. }
  1445. }