PageRenderTime 58ms CodeModel.GetById 24ms RepoModel.GetById 1ms app.codeStats 0ms

/browse.php

https://github.com/francinebo/glype
PHP | 1587 lines | 594 code | 492 blank | 501 comment | 194 complexity | 9f238b1f09c978684f6e368ca74bb53a MD5 | raw file
  1. <?php
  2. /*******************************************************************
  3. * Glype is copyright and trademark 2007-2012 UpsideOut, Inc. d/b/a Glype
  4. * and/or its licensors, successors and assigners. All rights reserved.
  5. *
  6. * Use of Glype is subject to the terms of the Software License Agreement.
  7. * http://www.glype.com/license.php
  8. *******************************************************************
  9. * This file is the main component of the glype proxy application.
  10. * It decodes values contained within the current URI to determine a
  11. * resource to download and pass onto the user.
  12. ******************************************************************/
  13. /*****************************************************************
  14. * Initialise
  15. ******************************************************************/
  16. require 'includes/init.php';
  17. if (count($adminDetails)===0) {
  18. header("HTTP/1.1 302 Found"); header("Location: admin.php"); exit;
  19. }
  20. # Debug mode - stores extra information in the cURL wrapper object and prints it
  21. # out. It produces an ugly mess but still a quick tool for debugging.
  22. define('DEBUG_MODE', 0);
  23. define('CURL_LOG', 0);
  24. # Log cURLs activity to file
  25. # Change filename below if desired. Ensure file exists and is writable.
  26. if ( CURL_LOG && ( $fh = @fopen('curl.txt', 'w')) ) {
  27. $toSet[CURLOPT_STDERR] = $fh;
  28. $toSet[CURLOPT_VERBOSE] = true;
  29. }
  30. /*****************************************************************
  31. * PHP sends some headers by default. Stop them.
  32. ******************************************************************/
  33. # Clear the default mime-type
  34. header('Content-Type:');
  35. # And remove the caching headers
  36. header('Cache-Control:');
  37. header('Last-Modified:');
  38. /*****************************************************************
  39. * Find URI of resource to load
  40. * NB: flag and bitfield already extracted in /includes/init.php
  41. ******************************************************************/
  42. switch ( true ) {
  43. # Try query string for URL
  44. case ! empty($_GET['u']) && ( $toLoad = deproxyURL($_GET['u'], true) ):
  45. break;
  46. # Try path info
  47. case ! empty($_SERVER['PATH_INFO']) && ( $toLoad = deproxyURL($_SERVER['PATH_INFO'], true) ):
  48. break;
  49. # Found no valid URL, return to index
  50. default:
  51. redirect();
  52. }
  53. # Validate the URL
  54. if ( ! preg_match('#^((https?)://(?:([a-z0-9-.]+:[a-z0-9-.]+)@)?([a-z0-9-.]+)(?::([0-9]+))?)(?:/|$)((?:[^?/]*/)*)([^?]*)(?:\?([^\#]*))?(?:\#.*)?$#i', $toLoad, $tmp) ) {
  55. # Invalid, show error
  56. error('invalid_url', htmlentities($toLoad));
  57. }
  58. # Rename parts to more useful names
  59. $URL = array(
  60. 'scheme_host' => $tmp[1],
  61. 'scheme' => $tmp[2],
  62. 'auth' => $tmp[3],
  63. 'host' => strtolower($tmp[4]),
  64. 'domain' => preg_match('#(?:^|\.)([a-z0-9-]+\.(?:[a-z.]{5,6}|[a-z]{2,}))$#', $tmp[4], $domain) ? $domain[1] : $tmp[4], # Attempt to split off the subdomain (if any)
  65. 'port' => $tmp[5],
  66. 'path' => '/' . $tmp[6],
  67. 'filename' => $tmp[7],
  68. 'extension' => pathinfo($tmp[7], PATHINFO_EXTENSION),
  69. 'query' => isset($tmp[8]) ? $tmp[8] : ''
  70. );
  71. # Apply encoding on full URL. In theory all parts of the URL need various special
  72. # characters encoding but this needs to be done by the author of the webpage.
  73. # We can make a guess at what needs encoding but some servers will complain when
  74. # receiving the encoded character instead of unencoded and vice versa. We want
  75. # to edit the URL as little as possible so we're only encoding spaces, as this
  76. # seems to 'fix' the majority of cases.
  77. $URL['href'] = str_replace(' ', '%20', $toLoad);
  78. # Protect LAN from access through proxy (protected addresses copied from PHProxy)
  79. if ( preg_match('#^(?:127\.|192\.168\.|10\.|172\.(?:1[6-9]|2[0-9]|3[01])\.|localhost)#i', $URL['host']) ) {
  80. error('banned_site', $URL['host']);
  81. }
  82. # Add any supplied authentication information to our auth array
  83. if ( $URL['auth'] ) {
  84. $_SESSION['authenticate'][$URL['scheme_host']] = $URL['auth'];
  85. }
  86. /*****************************************************************
  87. * Protect us from hotlinking
  88. ******************************************************************/
  89. # Protect only if option is enabled and we don't have a verified session
  90. if ( $CONFIG['stop_hotlinking'] && empty($_SESSION['no_hotlink']) ) {
  91. # Assume hotlinking to start with, then check against allowed domains
  92. $tmp = true;
  93. # Ensure we have valid referrer information to check
  94. if ( ! empty($_SERVER['HTTP_REFERER']) && strpos($_SERVER['HTTP_REFERER'], 'http') === 0 ) {
  95. # Examine all the allowed domains (including our current domain)
  96. foreach ( array_merge( (array) GLYPE_URL, $CONFIG['hotlink_domains'] ) as $domain ) {
  97. # Do a case-insensitive comparison
  98. if ( stripos($_SERVER['HTTP_REFERER'], $domain) !== false ) {
  99. # This referrer is OK
  100. $tmp = false;
  101. break;
  102. }
  103. }
  104. }
  105. # Redirect to index if this is still identified as hotlinking
  106. if ( $tmp ) {
  107. error('no_hotlink');
  108. }
  109. }
  110. # If we're still here, the referrer must be OK so set the session for next time
  111. $_SESSION['no_hotlink'] = true;
  112. /*****************************************************************
  113. * Are we allowed to visit this site? Check whitelist/blacklist
  114. ******************************************************************/
  115. # Whitelist - deny IF NOT on list
  116. if ( ! empty($CONFIG['whitelist']) ) {
  117. $tmp = false;
  118. # Loop through
  119. foreach ( $CONFIG['whitelist'] as $domain ) {
  120. # Check for match
  121. if ( strpos($URL['host'], $domain) !== false ) {
  122. # Must be a permitted site
  123. $tmp = true;
  124. }
  125. }
  126. # Unless $tmp is flagged true, this is an illegal site
  127. if ( ! $tmp ) {
  128. error('banned_site', $URL['host']);
  129. }
  130. }
  131. # Blacklist
  132. if ( ! empty($CONFIG['blacklist']) ) {
  133. # Loop through
  134. foreach ( $CONFIG['blacklist'] as $domain ) {
  135. # Check for match
  136. if ( strpos($URL['host'], $domain) !== false ) {
  137. # If matched, site is banned
  138. error('banned_site', $URL['host']);
  139. }
  140. }
  141. }
  142. /*****************************************************************
  143. * Show SSL warning
  144. * This warns users if they access a secure site when the proxy is NOT
  145. * on a secure connection and the $CONFIG['ssl_warning'] option is on.
  146. ******************************************************************/
  147. if ( $URL['scheme'] == 'https' && $CONFIG['ssl_warning'] && empty($_SESSION['ssl_warned']) && ! HTTPS ) {
  148. # Remember this page so we can return after agreeing to the warning
  149. $_SESSION['return'] = currentURL();
  150. # Don't cache the warning page
  151. sendNoCache();
  152. # Show the page
  153. echo loadTemplate('sslwarning.page');
  154. # All done!
  155. exit;
  156. }
  157. /*****************************************************************
  158. * Plugins
  159. * Load any site-specific plugin.
  160. ******************************************************************/
  161. $plugins = explode(',', $CONFIG['plugins']);
  162. if ($foundPlugin = in_array($URL['domain'], $plugins)) {
  163. include(GLYPE_ROOT.'/plugins/'.$URL['domain'].'.php');
  164. }
  165. /*****************************************************************
  166. * Close session to allow simultaneous transfers
  167. * PHP automatically prevents multiple instances of the script running
  168. * simultaneously to avoid concurrency issues with the session.
  169. * This may be beneficial on high traffic servers but we have the option
  170. * to close the session and thus allow simultaneous transfers.
  171. ******************************************************************/
  172. if ( ! $CONFIG['queue_transfers'] ) {
  173. session_write_close();
  174. }
  175. /*****************************************************************
  176. * * * * * * * * * * Prepare the REQUEST * * * * * * * * * * * *
  177. ******************************************************************/
  178. /*****************************************************************
  179. * Set cURL transfer options
  180. * These options are merely passed to cURL and our script has no further
  181. * impact or dependence of them. See the libcurl documentation and
  182. * http://php.net/curl_setopt for more details.
  183. *
  184. * The following options are required for the proxy to function or
  185. * inherit values from our config. In short: they shouldn't need changing.
  186. ******************************************************************/
  187. # Time to wait for connection
  188. $toSet[CURLOPT_CONNECTTIMEOUT] = $CONFIG['connection_timeout'];
  189. # Time to allow for entire transfer
  190. $toSet[CURLOPT_TIMEOUT] = $CONFIG['transfer_timeout'];
  191. # Show SSL without verifying - we almost definitely don't have an up to date CA cert
  192. # bundle so we can't verify the certificate. See http://curl.haxx.se/docs/sslcerts.html
  193. $toSet[CURLOPT_SSL_VERIFYPEER] = false;
  194. $toSet[CURLOPT_SSL_VERIFYHOST] = false;
  195. # Send an empty Expect header (avoids 100 responses)
  196. $toSet[CURLOPT_HTTPHEADER][] = 'Expect:';
  197. # Can we use "If-Modified-Since" to save a transfer? Server can return 304 Not Modified
  198. if ( isset($_SERVER['HTTP_IF_MODIFIED_SINCE']) ) {
  199. # How to treat the time condition : if un/modified since
  200. $toSet[CURLOPT_TIMECONDITION] = CURL_TIMECOND_IFMODSINCE;
  201. # The time value. Requires a timestamp so we can't just forward it raw
  202. $toSet[CURLOPT_TIMEVALUE] = strtotime($_SERVER['HTTP_IF_MODIFIED_SINCE']);
  203. }
  204. # Resume a transfer?
  205. if ( $CONFIG['resume_transfers'] && isset($_SERVER['HTTP_RANGE']) ) {
  206. # And give cURL the right part
  207. $toSet[CURLOPT_RANGE] = substr($_SERVER['HTTP_RANGE'], 6);
  208. }
  209. # cURL has a max filesize option but it's not listed in the PHP manual so check it's available
  210. if ( $CONFIG['max_filesize'] && defined('CURLOPT_MAXFILESIZE') ) {
  211. # Use the cURL option - should be faster than our implementation
  212. $toSet[CURLOPT_MAXFILESIZE] = $CONFIG['max_filesize'];
  213. }
  214. /*****************************************************************
  215. * Performance options
  216. * The values below are NOT the result of benchmarking tests. For
  217. * optimum performance, you may want to try adjusting these values.
  218. ******************************************************************/
  219. # DNS cache expiry time (seconds)
  220. $toSet[CURLOPT_DNS_CACHE_TIMEOUT] = 600;
  221. # Speed limits - aborts transfer if we're going too slowly
  222. #$toSet[CURLOPT_LOW_SPEED_LIMIT] = 5; # speed limit in bytes per second
  223. #$toSet[CURLOPT_LOW_SPEED_TIME] = 20; # seconds spent under the speed limit before aborting
  224. # Number of max connections (no idea what this should be)
  225. # $toSet[CURLOPT_MAXCONNECTS] = 100;
  226. # Accept encoding in any format (allows compressed pages to be downloaded)
  227. # Any bandwidth savings are likely to be minimal so better to save on load by
  228. # downloading pages uncompressed. Use blank string for any compression or
  229. # 'identity' to explicitly ask for uncompressed.
  230. # $toSet[CURLOPT_ENCODING] = '';
  231. # Undocumented in PHP manual (added 5.2.1) but allows uploads to some sites
  232. # (e.g. imageshack) when without this option, an error occurs. Less efficient
  233. # so probably best not to set this unless you need it.
  234. # $toSet[CURLOPT_TCP_NODELAY] = true;
  235. /*****************************************************************
  236. * "Accept" headers
  237. * No point sending back a file that the browser won't understand.
  238. * Forward all the "Accept" headers. For each, check if it exists
  239. * and if yes, add to the custom headers array.
  240. * NB: These may cause problems if the target server provides different
  241. * content for the same URI based on these headers and we cache the response.
  242. ******************************************************************/
  243. # Language (geotargeting will find the location of the server -
  244. # forwarding this header can help avoid incorrect localisation)
  245. if ( isset($_SERVER['HTTP_ACCEPT_LANGUAGE']) ) {
  246. $toSet[CURLOPT_HTTPHEADER][] = 'Accept-Language: ' . $_SERVER['HTTP_ACCEPT_LANGUAGE'];
  247. }
  248. # Accepted filetypes
  249. if ( isset($_SERVER['HTTP_ACCEPT']) ) {
  250. $toSet[CURLOPT_HTTPHEADER][] = 'Accept: ' . $_SERVER['HTTP_ACCEPT'];
  251. }
  252. # Accepted charsets
  253. if ( isset($_SERVER['HTTP_ACCEPT_CHARSET']) ) {
  254. $toSet[CURLOPT_HTTPHEADER][] = 'Accept-Charset: ' . $_SERVER['HTTP_ACCEPT_CHARSET'];
  255. }
  256. /*****************************************************************
  257. * Browser options
  258. * Allows customization of a "virtual" browser via /extras/edit-browser.php
  259. ******************************************************************/
  260. # Send user agent
  261. if ( $_SESSION['custom_browser']['user_agent'] ) {
  262. $toSet[CURLOPT_USERAGENT] = $_SESSION['custom_browser']['user_agent'];
  263. }
  264. # Set referrer
  265. if ( $_SESSION['custom_browser']['referrer'] == 'real' ) {
  266. # Automatically determine referrer
  267. if ( isset($_SERVER['HTTP_REFERER']) && $flag != 'norefer' && strpos($tmp = deproxyURL($_SERVER['HTTP_REFERER']), GLYPE_URL) === false ) {
  268. $toSet[CURLOPT_REFERER] = $tmp;
  269. }
  270. } else if ( $_SESSION['custom_browser']['referrer'] ) {
  271. # Send custom referrer
  272. $toSet[CURLOPT_REFERER] = $_SESSION['custom_browser']['referrer'];
  273. }
  274. # Clear the norefer flag
  275. if ( $flag == 'norefer' ) {
  276. $flag = '';
  277. }
  278. /*****************************************************************
  279. * Authentication
  280. ******************************************************************/
  281. # Check for stored credentials for this site
  282. if ( isset($_SESSION['authenticate'][$URL['scheme_host']]) ) {
  283. # Found credentials so use them!
  284. $toSet[CURLOPT_HTTPAUTH] = CURLAUTH_BASIC;
  285. $toSet[CURLOPT_USERPWD] = $_SESSION['authenticate'][$URL['scheme_host']];
  286. }
  287. /*****************************************************************
  288. * Cookies
  289. * Find the relevant cookies for this request. All cookies get sent
  290. * to the proxy, but we only want to forward the ones that were set
  291. * for the current domain.
  292. *
  293. * Cookie storage methods:
  294. * (1) Server-side - cookies stored server-side and handled
  295. * (mostly) internally by cURL
  296. * (2) Encoded - cookies forwarded to client but encoded
  297. * (3) Normal - cookies forwarded without encoding
  298. ******************************************************************/
  299. # Are cookies allowed?
  300. if ( $options['allowCookies'] ) {
  301. # Option (1): cookies stored server-side
  302. if ( $CONFIG['cookies_on_server'] ) {
  303. # Check cookie folder exists or try to create it
  304. if ( $s = checkTmpDir($CONFIG['cookies_folder'], 'Deny from all') ) {
  305. # Set cURL to use this as the cookie jar
  306. $toSet[CURLOPT_COOKIEFILE] = $toSet[CURLOPT_COOKIEJAR] = $CONFIG['cookies_folder'] . session_id();
  307. }
  308. } else if ( isset($_COOKIE[COOKIE_PREFIX]) ) {
  309. # Encoded or unencoded?
  310. if ( $CONFIG['encode_cookies'] ) {
  311. # Option (2): encoded cookies stored client-side
  312. foreach ( $_COOKIE[COOKIE_PREFIX] as $attributes => $value ) {
  313. # Decode cookie to [domain,path,name]
  314. $attributes = explode(' ', base64_decode($attributes));
  315. # Check successful decoding and skip if failed
  316. if ( ! isset($attributes[2]) ) {
  317. continue;
  318. }
  319. # Extract parts
  320. list($domain, $path, $name) = $attributes;
  321. # Check for a domain match and skip if no match
  322. if ( stripos($URL['host'], $domain) === false ) {
  323. continue;
  324. }
  325. # Check for match and skip to next path if fail
  326. if ( stripos($URL['path'], $path) !== 0 ) {
  327. continue;
  328. }
  329. # Multiple cookies of the same name are permitted if different paths
  330. # so use path AND name as the key in the temp array
  331. $key = $path . $name;
  332. # Check for existing cookie with same domain, same path and same name
  333. if ( isset($toSend[$key]) && $toSend[$key]['path'] == $path && $toSend[$key]['domain'] > strlen($domain) ) {
  334. # Conflicting cookies so ignore the one with the less complete tail match
  335. # (i.e. the current one)
  336. continue;
  337. }
  338. # Domain and path OK, decode cookie value
  339. $value = base64_decode($value);
  340. # Only send secure cookies on https connection - secure cookies marked by !SEC suffix
  341. # so remove the suffix
  342. $value = str_replace('!SEC', '', $value, $tmp);
  343. # And if secure cookie but not https site, do not send
  344. if ( $tmp && $URL['scheme'] != 'https' ) {
  345. continue;
  346. }
  347. # Everything checked and verified, add to $toSend for further processing later
  348. $toSend[$key] = array('path_size' => strlen($path), 'path' => $path, 'domain' => strlen($domain), 'send' => $name . '=' . $value);
  349. }
  350. } else {
  351. # Option (3): unencoded cookies stored client-side
  352. foreach ( $_COOKIE[COOKIE_PREFIX] as $domain => $paths ) {
  353. # $domain holds the domain (surprisingly) and $path is an array
  354. # of keys (paths) and more arrays (each child array of $path = one cookie)
  355. # e.g. Array('domain.com' => Array('/' => Array('cookie_name' => 'value')))
  356. # First check for domain match and skip to next domain if no match
  357. if ( stripos($URL['host'], $domain) === false ) {
  358. continue;
  359. }
  360. # If conflicting cookies with same name and same path,
  361. # send the one with the more complete tail match. To do this we
  362. # need to know how long each match is/was so record domain length.
  363. $domainSize = strlen($domain);
  364. # Now look at all the available paths
  365. foreach ( $paths as $path => $cookies ) {
  366. # Check for match and skip to next path if fail
  367. if ( stripos($URL['path'], $path) !== 0 ) {
  368. continue;
  369. }
  370. # In final header, cookies are ordered with most specific path
  371. # matches first so include the length of match in temp array
  372. $pathSize = strlen($path);
  373. # All cookies in $cookies array should be sent
  374. foreach ( $cookies as $name => $value ) {
  375. # Multiple cookies of the same name are permitted if different paths
  376. # so use path AND name as the key in the temp array
  377. $key = $path . $name;
  378. # Check for existing cookie with same domain, same path and same name
  379. if ( isset($toSend[$key]) && $toSend[$key]['path'] == $path && $toSend[$key]['domain'] > $domainSize ) {
  380. # Conflicting cookies so ignore the one with the less complete tail match
  381. # (i.e. the current one)
  382. continue;
  383. }
  384. # Only send secure cookies on https connection - secure cookies marked by !SEC suffix
  385. # so remove the suffix
  386. $value = str_replace('!SEC', '', $value, $tmp);
  387. # And if secure cookie but not https site, do not send
  388. if ( $tmp && $URL['scheme'] != 'https' ) {
  389. continue;
  390. }
  391. # Add to $toSend for further processing later
  392. $toSend[$key] = array('path_size' => $pathSize, 'path' => $path, 'domain' => $domainSize, 'send' => $name . '=' . $value);
  393. }
  394. }
  395. }
  396. }
  397. # Ensure we have found cookies
  398. if ( ! empty($toSend) ) {
  399. # Order by path specificity (as per Netscape spec)
  400. function compareArrays($a, $b) {
  401. return ( $a['path_size'] > $b['path_size'] ) ? -1 : 1;
  402. }
  403. # Apply the sort to order by path_size descending
  404. uasort($toSend, 'compareArrays');
  405. # Go through the ordered array and generate the Cookie: header
  406. $tmp = '';
  407. foreach ( $toSend as $cookie ) {
  408. $tmp .= $cookie['send'] . '; ';
  409. }
  410. # Give the string to cURL
  411. $toSet[CURLOPT_COOKIE] = $tmp;
  412. }
  413. # And clear the toSend array
  414. unset($toSend);
  415. }
  416. }
  417. /*****************************************************************
  418. * Post
  419. * Forward the post data. Usually very simple but complicated by
  420. * multipart forms because in those cases, the raw post is not available.
  421. ******************************************************************/
  422. if ( ! empty($_POST) ) {
  423. # Attempt to get raw POST from the input wrapper
  424. if ( ! ($tmp = file_get_contents('php://input')) ) {
  425. # Raw data not available (probably multipart/form-data).
  426. # cURL will do a multipart post if we pass an array as the
  427. # POSTFIELDS value but this array can only be one deep.
  428. # Recursively flatten array to one level deep and rename keys
  429. # as firstLayer[second][etc]. Also apply the input decode to all
  430. # array keys.
  431. function flattenArray($array, $prefix='') {
  432. # Start with empty array
  433. $stack = array();
  434. # Loop through the array to flatten
  435. foreach ( $array as $key => $value ) {
  436. # Decode the input name
  437. $key = inputDecode($key);
  438. # Determine what the new key should be - add the current key to
  439. # the prefix and surround in []
  440. $newKey = $prefix ? $prefix . '[' . $key . ']' : $key;
  441. if ( is_array($value) ) {
  442. # If it's an array, recurse and merge the returned array
  443. $stack = array_merge($stack, flattenArray($value, $newKey));
  444. } else {
  445. # Otherwise just add it to the current stack
  446. $stack[$newKey] = clean($value);
  447. }
  448. }
  449. # Return flattened
  450. return $stack;
  451. }
  452. $tmp = flattenArray($_POST);
  453. # Add any file uploads?
  454. if ( ! empty($_FILES) ) {
  455. # Loop through and add the files
  456. foreach ( $_FILES as $name => $file ) {
  457. # Is this an array?
  458. if ( is_array($file['tmp_name']) ) {
  459. # Flatten it - file arrays are in the slightly odd format of
  460. # $_FILES['layer1']['tmp_name']['layer2']['layer3,etc.'] so add
  461. # layer1 onto the start.
  462. $flattened = flattenArray(array($name => $file['tmp_name']));
  463. # And add all files to the post
  464. foreach ( $flattened as $key => $value ) {
  465. $tmp[$key] = '@' . $value;
  466. }
  467. } else {
  468. # Not another array. Check if the file uploaded successfully?
  469. if ( ! empty($file['error']) || empty($file['tmp_name']) ) {
  470. continue;
  471. }
  472. # Add to array with @ - tells cURL to upload this file
  473. $tmp[$name] = '@' . $file['tmp_name'];
  474. }
  475. # To do: rename the temp file to it's real name before
  476. # uploading it to the target? Otherwise, the target receives
  477. # the temp name instead of the original desired name
  478. # but doing this may be a security risk.
  479. }
  480. }
  481. }
  482. # Convert back to GET if required
  483. if ( isset($_POST['convertGET']) ) {
  484. # Remove convertGET from POST array and update our location
  485. $URL['href'] .= ( empty($URL['query']) ? '?' : '&' ) . str_replace('convertGET=1', '', $tmp);
  486. } else {
  487. # Genuine POST so set the cURL post value
  488. $toSet[CURLOPT_POST] = 1;
  489. $toSet[CURLOPT_POSTFIELDS] = $tmp;
  490. }
  491. }
  492. /*****************************************************************
  493. * Apply pre-request code from plugins
  494. ******************************************************************/
  495. if ( $foundPlugin && function_exists('preRequest') ) {
  496. preRequest();
  497. }
  498. /*****************************************************************
  499. * Make the request
  500. * This request object uses custom header/body reading functions
  501. * so we can start processing responses on the fly - e.g. we don't
  502. * need to wait till the whole file has downloaded before deciding
  503. * if it needs parsing or can be sent out unchanged.
  504. ******************************************************************/
  505. class Request {
  506. # Response status code
  507. public $status = 0;
  508. # Headers received and read by our callback
  509. public $headers = array();
  510. # Returned data (if saved)
  511. public $return;
  512. # Reason for aborting transfer (or empty to continue downloading)
  513. public $abort;
  514. # The error (if any) returned by curl_error()
  515. public $error;
  516. # Type of resource downloaded [html, js, css] or empty if no parsing needed
  517. public $parseType;
  518. # Automatically detect(ed) content type?
  519. public $sniff = false;
  520. # Forward cookies or not
  521. private $forwardCookies = false;
  522. # Limit filesize?
  523. private $limitFilesize = 0;
  524. # Speed limit (bytes per second)
  525. private $speedLimit = 0;
  526. # URL array split into pieces
  527. private $URL;
  528. # = $options from the global scope
  529. private $browsingOptions;
  530. # Options to pass to cURL
  531. private $curlOptions;
  532. # Constructor - takes the parameters and saves them
  533. public function __construct($curlOptions) {
  534. global $options, $CONFIG;
  535. # Set our reading callbacks
  536. $curlOptions[CURLOPT_HEADERFUNCTION] = array(&$this, 'readHeader');
  537. $curlOptions[CURLOPT_WRITEFUNCTION] = array(&$this, 'readBody');
  538. # Determine whether or not to forward cookies
  539. if ( $options['allowCookies'] && ! $CONFIG['cookies_on_server'] ) {
  540. $this->forwardCookies = $CONFIG['encode_cookies'] ? 'encode' : 'normal';
  541. }
  542. # Determine a filesize limit
  543. if ( $CONFIG['max_filesize'] ) {
  544. $this->limitFilesize = $CONFIG['max_filesize'];
  545. }
  546. # Determine speed limit
  547. if ( $CONFIG['download_speed_limit'] ) {
  548. $this->speedLimit = $CONFIG['download_speed_limit'];
  549. }
  550. # Set options
  551. $this->browsingOptions = $options;
  552. $this->curlOptions = $curlOptions;
  553. # Extend the PHP timeout
  554. if ( ! SAFE_MODE ) {
  555. set_time_limit($CONFIG['transfer_timeout']);
  556. }
  557. # Record debug information
  558. if ( DEBUG_MODE ) {
  559. $this->cookiesSent = isset($curlOptions[CURLOPT_COOKIE]) ? $curlOptions[CURLOPT_COOKIE] : ( isset($curlOptions[CURLOPT_COOKIEFILE]) ? 'using cookie jar' : 'none');
  560. $this->postSent = isset($curlOptions[CURLOPT_POSTFIELDS]) ? $curlOptions[CURLOPT_POSTFIELDS] : '';
  561. }
  562. }
  563. # Make the request and return the downloaded file if parsing is needed
  564. public function go($URL) {
  565. # Save options
  566. $this->URL = $URL;
  567. # Get a cURL handle
  568. $ch = curl_init($this->URL['href']);
  569. # Set the options
  570. curl_setopt_array($ch, $this->curlOptions);
  571. # Make the request
  572. curl_exec($ch);
  573. # Save any errors (but not if we caused the error by aborting!)
  574. if ( ! $this->abort ) {
  575. $this->error = curl_error($ch);
  576. }
  577. # And close the curl handle
  578. curl_close($ch);
  579. # And return the document (will be empty if no parsing needed,
  580. # because everything else is outputted immediately)
  581. return $this->return;
  582. }
  583. /*****************************************************************
  584. * * * * * * * * * * Manage the RESPONSE * * * * * * * * * * * *
  585. ******************************************************************/
  586. /*****************************************************************
  587. * Read headers - receives headers line by line (cURL callback)
  588. ******************************************************************/
  589. public function readHeader($handle, $header) {
  590. # Extract the status code (can occur more than once if 100 continue)
  591. if ( $this->status == 0 || ( $this->status == 100 && ! strpos($header, ':') ) ) {
  592. $this->status = substr($header, 9, 3);
  593. }
  594. # Attempt to extract header name and value
  595. $parts = explode(':', $header, 2);
  596. # Did it split successfully? (i.e. was there a ":" in the header?)
  597. if ( isset($parts[1]) ) {
  598. # Header names are case insensitive
  599. $headerType = strtolower($parts[0]);
  600. # And header values will have trailing newlines and prevailing spaces
  601. $headerValue = trim($parts[1]);
  602. # Set any cookies
  603. if ( $headerType == 'set-cookie' && $this->forwardCookies ) {
  604. $this->setCookie($headerValue);
  605. }
  606. # Everything else, store as associative array
  607. $this->headers[$headerType] = $headerValue;
  608. # Do we want to forward this header? First list the headers we want:
  609. $toForward = array('last-modified',
  610. 'content-disposition',
  611. 'content-type',
  612. 'content-range',
  613. 'content-language',
  614. 'expires',
  615. 'cache-control',
  616. 'pragma');
  617. # And check for a match before forwarding the header.
  618. if ( in_array($headerType, $toForward) ) {
  619. header($header);
  620. }
  621. } else {
  622. # Either first header or last 'header' (more precisely, the 2 newlines
  623. # that indicate end of headers)
  624. # No ":", so save whole header. Also check for end of headers.
  625. if ( ( $this->headers[] = trim($header) ) == false ) {
  626. # Must be end of headers so process them before reading body
  627. $this->processHeaders();
  628. # And has that processing given us any reason to abort?
  629. if ( $this->abort ) {
  630. return -1;
  631. }
  632. }
  633. }
  634. # cURL needs us to return length of data read
  635. return strlen($header);
  636. }
  637. /*****************************************************************
  638. * Process headers after all received and before body is read
  639. ******************************************************************/
  640. private function processHeaders() {
  641. # Ensure we only run this function once
  642. static $runOnce;
  643. # Check for flag and if found, stop running function
  644. if ( isset($runOnce) ) {
  645. return;
  646. }
  647. # Set flag for next time
  648. $runOnce = true;
  649. # Send the appropriate status code
  650. header(' ', true, $this->status);
  651. # Find out if we want to abort the transfer
  652. switch ( true ) {
  653. # Redirection
  654. case isset($this->headers['location']):
  655. $this->abort = 'redirect';
  656. return;
  657. # 304 Not Modified
  658. case $this->status == 304:
  659. $this->abort = 'not_modified';
  660. return;
  661. # 401 Auth required
  662. case $this->status == 401:
  663. $this->abort = 'auth_required';
  664. return;
  665. # Error code (>=400)
  666. case $this->status >= 400:
  667. $this->abort = 'http_status_error';
  668. return;
  669. # Check for a content-length above the filesize limit
  670. case isset($this->headers['content-length']) && $this->limitFilesize && $this->headers['content-length'] > $this->limitFilesize:
  671. $this->abort = 'filesize_limit';
  672. return;
  673. }
  674. # Still here? No need to abort so next we determine parsing mechanism to use (if any)
  675. if ( isset($this->headers['content-type']) ) {
  676. # Define content-type to parser type relations
  677. $types = array('text/javascript' => 'javascript',
  678. 'application/javascript' => 'javascript',
  679. 'application/x-javascript' => 'javascript',
  680. 'application/xhtml+xml' => 'html',
  681. 'text/html' => 'html',
  682. 'text/css' => 'css');
  683. # Extract mimetype from charset (if exists)
  684. list($mime) = explode(';', $this->headers['content-type'], 2);
  685. # Remove whitespace
  686. $mime = trim($mime);
  687. # Look for that mimetype in our array to find the parsing mechanism needed
  688. if ( isset($types[$mime]) ) {
  689. $this->parseType = $types[$mime];
  690. }
  691. } else {
  692. # Tell our read body function to 'sniff' the data to determine type
  693. $this->sniff = true;
  694. }
  695. # If no content-disposition sent, send one with the correct filename
  696. if ( ! isset($this->headers['content-disposition']) && $this->URL['filename'] ) {
  697. header('Content-Disposition: filename="' . $this->URL['filename'] . '"');
  698. }
  699. # If filesize limit exists, content-length received and we're still here, the
  700. # content-length is OK. If we assume the content-length is accurate (and since
  701. # clients [and possibly libcurl too] stop downloading after reaching the limit,
  702. # it's probably safe to assume that),we can save on load by not checking the
  703. # limit with each chunk received.
  704. if ( $this->limitFilesize && isset($this->headers['content-length']) ) {
  705. $this->limitFilesize = 0;
  706. }
  707. }
  708. /*****************************************************************
  709. * Read body - takes chunks of data (cURL callback)
  710. ******************************************************************/
  711. public function readBody($handle, $data) {
  712. # Static var to tell us if this function has been run before
  713. static $first;
  714. # Check for set variable
  715. if ( ! isset($first) ) {
  716. # Run the pre-body code
  717. $this->firstBody($data);
  718. # Set the variable so we don't run this code again
  719. $first = false;
  720. }
  721. # Find length of data
  722. $length = strlen($data);
  723. # Limit speed to X bytes/second
  724. if ( $this->speedLimit ) {
  725. # Limit download speed
  726. # Speed = Amount of data / Time
  727. # [bytes/s] = [bytes] / [s]
  728. # We know the desired speed (defined earlier in bytes per second)
  729. # and we know the number of bytes we've received. Now we need to find
  730. # the time that it should take to receive those bytes.
  731. $time = $length / $this->speedLimit; # [s]
  732. # Convert time to microseconds and sleep for that value
  733. usleep(round($time * 1000000));
  734. }
  735. # Monitor length if desired
  736. if ( $this->limitFilesize ) {
  737. # Set up a static downloaded-bytes value
  738. static $downloadedBytes;
  739. if ( ! isset($downloadedBytes) ) {
  740. $downloadedBytes = 0;
  741. }
  742. # Add length to downloadedBytes
  743. $downloadedBytes += $length;
  744. # Is downloadedBytes over the limit?
  745. if ( $downloadedBytes > $this->limitFilesize ) {
  746. # Set the abort variable and return -1 (so cURL aborts)
  747. $this->abort = 'filesize_limit';
  748. return -1;
  749. }
  750. }
  751. # If parsing is required, save as $return
  752. if ( $this->parseType ) {
  753. $this->return .= $data;
  754. } else {
  755. echo $data; # No parsing so print immediately
  756. }
  757. # cURL needs us to return length of data read
  758. return $length;
  759. }
  760. /*****************************************************************
  761. * Process first chunk of data in body
  762. * Sniff the content if no content-type was sent and create the file
  763. * handle if caching this.
  764. ******************************************************************/
  765. private function firstBody($data) {
  766. # Do we want to sniff the data? Determines if ascii or binary.
  767. if ( $this->sniff ) {
  768. # Take a sample of 100 chars chosen at random
  769. $length = strlen($data);
  770. $sample = $length < 150 ? $data : substr($data, rand(0, $length-100), 100);
  771. # Assume ASCII if more than 95% of bytes are "normal" text characters
  772. if ( strlen(preg_replace('#[^A-Z0-9\!"$%\^&*\(\)=\+\\\\|\[\]\{\};:\\\'\@\#~,\.<>/\?\-]#i', '', $sample)) > 95 ) {
  773. # To do: expand this to detect if html/js/css
  774. $this->parseType = 'html';
  775. }
  776. }
  777. # Now we know if parsing is required, we can forward content-length
  778. if ( ! $this->parseType && isset($this->headers['content-length']) ) {
  779. header('Content-Length: ' . $this->headers['content-length']);
  780. }
  781. }
  782. /*****************************************************************
  783. * Accept cookies - takes the value from Set-Cookie: [COOKIE STRING]
  784. * and forwards cookies to the client
  785. ******************************************************************/
  786. private function setCookie($cookieString) {
  787. # The script can handle cookies following the Netscape specification
  788. # (or close enough!) and supports "Max-Age" from RFC2109
  789. # Split parts by ;
  790. $cookieParts = explode(';', $cookieString);
  791. # Process each line
  792. foreach ( $cookieParts as $part ) {
  793. # Split attribute/value pairs by =
  794. $pair = explode('=', $part, 2);
  795. # Ensure we have a second part
  796. $pair[1] = isset($pair[1]) ? $pair[1] : '';
  797. # First pair must be name/cookie value
  798. if ( ! isset($cookieName) ) {
  799. # Name is first pair item, value is second
  800. $cookieName = $pair[0];
  801. $cookieValue = $pair[1];
  802. # Skip rest of loop and start processing attributes
  803. continue;
  804. }
  805. # If still here, must be an attribute (case-insensitive so lower it)
  806. $pair[0] = strtolower($pair[0]);
  807. # And save in array
  808. if ( $pair[1] ) {
  809. # We have a attribute/value pair so save as associative
  810. $attr[ltrim($pair[0])] = $pair[1];
  811. } else {
  812. # Not a pair, just a value
  813. $attr[] = $pair[0];
  814. }
  815. }
  816. # All cookies need to be sent to this script (and then we choose
  817. # the correct cookies to forward to the client) so the extra attributes
  818. # (path, domain, etc.) must be stored in the cookie itself
  819. # Cookies stored as c[domain.com][path][cookie_name] with values of
  820. # cookie_value;secure;
  821. # If encoded, cookie name becomes c[base64_encode(domain.com path cookie_name)]
  822. # Find the EXPIRES date
  823. if ( isset($attr['expires']) ) {
  824. # From the "Expires" attribute (original Netscape spec)
  825. $expires = strtotime($attr['expires']);
  826. } else if ( isset($attr['max-age']) ) {
  827. # From the "Max-Age" attribute (RFC2109)
  828. $expires = $_SERVER['REQUEST_TIME']+$attr['max-age'];
  829. } else {
  830. # Default to temp cookies
  831. $expires = 0;
  832. }
  833. # If temp cookies, override expiry date to end of session unless time
  834. # is in the past since that means the cookie should be deleted
  835. if ( $this->browsingOptions['tempCookies'] && $expires > $_SERVER['REQUEST_TIME'] ) {
  836. $expires = 0;
  837. }
  838. # Find the PATH. The spec says if none found, default to the current path.
  839. # Certain browsers default to the the root path so we'll do the same.
  840. if ( ! isset($attr['path']) ) {
  841. $attr['path'] = '/';
  842. }
  843. # Were we sent a DOMAIN?
  844. if ( isset($attr['domain']) ) {
  845. # Ensure it's valid and we can accept this cookie
  846. if ( stripos($attr['domain'], $this->URL['domain']) === false ) {
  847. # Our current domain does not match the specified domain
  848. # so we reject the cookie
  849. return;
  850. }
  851. # Some cookies will be sent with the domain starting with . as per RFC2109
  852. # The . then has to be stripped off by us when doing the tail match to determine
  853. # which cookies to send since ".glype.com" should match "glype.com". It's more
  854. # efficient to do any manipulations while forwarding cookies than on every request
  855. if ( $attr['domain'][0] == '.' ) {
  856. $attr['domain'] = substr($attr['domain'], 1);
  857. }
  858. } else {
  859. # No domain sent so use current domain
  860. $attr['domain'] = $this->URL['domain'];
  861. }
  862. # Check for SECURE cookie
  863. $sentSecure = in_array('secure', $attr);
  864. # Append "[SEC]" to cookie value if we should only forward to secure connections
  865. if ( $sentSecure ) {
  866. $cookieValue .= '!SEC';
  867. }
  868. # If we're on HTTPS, we can also send this cookie back as secure
  869. $secure = HTTPS && $sentSecure;
  870. # If the PHP version is recent enough, we can also forward the httponly flag
  871. $httponly = in_array('httponly', $attr) && version_compare(PHP_VERSION,'5.2.0','>=') ? true : false;
  872. # Prepare cookie name/value to save as
  873. $name = COOKIE_PREFIX . '[' . $attr['domain'] . '][' . $attr['path'] . '][' . inputEncode($cookieName) . ']';
  874. $value = $cookieValue;
  875. # Add encodings
  876. if ( $this->forwardCookies == 'encode' ) {
  877. $name = COOKIE_PREFIX . '[' . urlencode(base64_encode($attr['domain'] . ' ' . $attr['path'] . ' ' . urlencode($cookieName))) . ']';
  878. $value = base64_encode($value);
  879. }
  880. # Send cookie ...
  881. if ( $httponly ) {
  882. # ... with httponly flag
  883. setcookie($name, $value, $expires, '/', '', $secure, true);
  884. } else {
  885. # ... without httponly flag
  886. setcookie($name, $value, $expires, '/', '', $secure);
  887. }
  888. # And log if in debug mode
  889. if ( DEBUG_MODE ) {
  890. $this->cookiesReceived[] = array('name' => $cookieName,
  891. 'value' => $cookieValue,
  892. 'attributes' => $attr);
  893. }
  894. }
  895. }
  896. /*****************************************************************
  897. * Execute the request
  898. ******************************************************************/
  899. # Initiate cURL wrapper request object with our cURL options
  900. $fetch = new Request($toSet);
  901. # And make the request
  902. $document = $fetch->go($URL);
  903. /*****************************************************************
  904. * Handle aborted transfers
  905. ******************************************************************/
  906. if ( $fetch->abort ) {
  907. switch ( $fetch->abort ) {
  908. # Do a redirection
  909. case 'redirect':
  910. # Proxy the location
  911. $location = proxyURL($fetch->headers['location'], $flag);
  912. # Do not redirect in debug mode
  913. if ( DEBUG_MODE ) {
  914. $fetch->redirected = '<a href="' . $location . '">' . $fetch->headers['location'] . '</a>';
  915. break;
  916. }
  917. # Go there
  918. header('Location: ' . $location, true, $fetch->status);
  919. exit;
  920. # Send back a 304 Not modified and stop running the script
  921. case 'not_modified':
  922. header("HTTP/1.1 304 Not Modified", true, 304);
  923. exit;
  924. # 401 Authentication (HTTP authentication hooks not available in all PHP versions
  925. # so we have to use our method)
  926. case 'auth_required':
  927. # Ensure we have some means of authenticating and extract details about the type of authentication
  928. if ( ! isset($fetch->headers['www-authenticate']) ) {
  929. break;
  930. }
  931. # Realm to display to the user
  932. $realm = preg_match('#\brealm="([^"]*)"#i', $fetch->headers['www-authenticate'], $tmp) ? $tmp[1] : '';
  933. # Prevent caching
  934. sendNoCache();
  935. # Prepare template variables (session may be closed at this point so send via form)
  936. $tmp = array('site' => $URL['scheme_host'],
  937. 'realm' => $realm,
  938. 'return' => currentURL());
  939. # Show our form and quit
  940. echo loadTemplate('authenticate.page', $tmp);
  941. exit;
  942. # File request above filesize limit
  943. case 'filesize_limit':
  944. # If already sent some of the file, we can't display an error
  945. # so just stop running
  946. if ( ! $fetch->parseType ) {
  947. exit;
  948. }
  949. # Send to error page with filesize limit expressed in MB
  950. error('file_too_large', round($CONFIG['max_filesize']/1024/1024, 3));
  951. exit;
  952. # >=400 response code (some sort of HTTP error)
  953. case 'http_status_error':
  954. # Provide a friendly message
  955. $explain = isset($httpErrors[$fetch->status]) ? $httpErrors[$fetch->status] : '';
  956. # Simply forward the error with details
  957. error('http_error', $fetch->status, trim(substr($fetch->headers[0], 12)), $explain);
  958. exit;
  959. # Unknown (shouldn't happen)
  960. default:
  961. error('cURL::$abort (' . $fetch->abort .')');
  962. }
  963. }
  964. # Any cURL errors?
  965. if ( $fetch->error ) {
  966. error('curl_error', $fetch->error);
  967. }
  968. /*****************************************************************
  969. * Transfer finished and errors handle. Process the file.
  970. ******************************************************************/
  971. # Is this AJAX? If so, don't cache, log or parse.
  972. # Also, assume ajax if return is VERY short.
  973. if ( $flag == 'ajax' || ( $fetch->parseType && strlen($document) < 10 ) ) {
  974. # Print if not already printed
  975. if ( $fetch->parseType ) {
  976. echo $document;
  977. }
  978. # And exit
  979. exit;
  980. }
  981. # Do we want to parse the file?
  982. if ( $fetch->parseType ) {
  983. /*****************************************************************
  984. * Apply the relevant parsing methods to the document
  985. ******************************************************************/
  986. # Apply preparsing from plugins
  987. if ( $foundPlugin && function_exists('preParse') ) {
  988. $document = preParse($document, $fetch->parseType);
  989. }
  990. # Load the main parser
  991. require GLYPE_ROOT . '/includes/parser.php';
  992. # Create new instance, passing in the options that affect parsing
  993. $parser = new parser($options, $jsFlags);
  994. # Method of parsing depends on $parseType
  995. switch ( $fetch->parseType ) {
  996. # HTML document
  997. case 'html':
  998. # Do we want to insert our own code into the document?
  999. $inject =
  1000. $footer =
  1001. $insert = false;
  1002. # Mini-form only if NOT frame or sniffed
  1003. if ( $flag != 'frame' && $fetch->sniff == false ) {
  1004. # Showing the mini-form?
  1005. if ( $options['showForm'] ) {
  1006. $toShow = array();
  1007. # Prepare the options
  1008. foreach ( $CONFIG['options'] as $name => $details ) {
  1009. # Ignore if forced
  1010. if ( ! empty($details['force']) ) {
  1011. continue;
  1012. }
  1013. # Add to array
  1014. $toShow[] = array('name' => $name,
  1015. 'title' => $details['title'],
  1016. 'checked' => $options[$name] ? ' checked="checked" ' : '');
  1017. }
  1018. # Prepare variables to pass to template
  1019. $vars['toShow'] = $toShow; # Options
  1020. $vars['url'] = $URL['href']; # Currently visited URL
  1021. $vars['return'] = rawurlencode(currentURL()); # Return URL (for clearcookies) (i.e. current URL proxied)
  1022. $vars['proxy'] = GLYPE_URL; # Base URL for proxy directory
  1023. # Load the template
  1024. $insert = loadTemplate('framedForm.inc', $vars);
  1025. # Wrap in enable/disble override to prevent the overriden functions
  1026. # affecting anything in the mini-form (like ad codes)
  1027. if ( $CONFIG['override_javascript'] ) {
  1028. $insert = '<script type="text/javascript">disableOverride();</script>'
  1029. . $insert
  1030. . '<script type="text/javascript">enableOverride();</script>';
  1031. }
  1032. }
  1033. # And load the footer
  1034. $footer = $CONFIG['footer_include'];
  1035. }
  1036. # Inject javascript unless sniffed
  1037. if ( $fetch->sniff == false ) {
  1038. $inject = true;
  1039. }
  1040. # Run through HTML parser
  1041. $document = $parser->HTMLDocument($document, $insert, $inject, $footer);
  1042. break;
  1043. # CSS file
  1044. case 'css':
  1045. # Run through CSS parser
  1046. $document = $parser->CSS($document);
  1047. break;
  1048. # Javascript file
  1049. case 'javascript':
  1050. # Run through javascript parser
  1051. $document = $parser->JS($document);
  1052. break;
  1053. }
  1054. # Apply postparsing from plugins
  1055. if ( $foundPlugin && function_exists('postParse') ) {
  1056. $document = postParse($document, $fetch->parseType);
  1057. }
  1058. # Send output
  1059. if ( ! DEBUG_MODE ) {
  1060. # Do we want to gzip this? Yes, if all of the following are true:
  1061. # - gzip option enabled
  1062. # - client supports gzip
  1063. # - zlib extension loaded
  1064. # - output compression not automated
  1065. if ( $CONFIG['gzip_return'] && isset($_SERVER['HTTP_ACCEPT_ENCODING']) && strpos($_SERVER['HTTP_ACCEPT_ENCODING'],'gzip') !== false && extension_loaded('zlib') && ! ini_get('zlib.output_compression') ) {
  1066. # Send compressed (using level 3 compression - can be adjusted
  1067. # to give smaller/larger files but will take longer/shorter time!)
  1068. header('Content-Encoding: gzip');
  1069. echo gzencode($document, 3);
  1070. } else {
  1071. # Send uncompressed
  1072. echo $document;
  1073. }
  1074. }
  1075. }
  1076. if ( DEBUG_MODE ) {
  1077. # Just dump the $fetch object in DEBUG_MODE
  1078. $fetch->return = $document;
  1079. echo '<pre>', print_r($fetch, 1), '</pre>';
  1080. }
  1081. /*****************************************************************
  1082. * Log the request
  1083. ******************************************************************/
  1084. # Do we want to log? Check we want to log this type of request.
  1085. if ( $CONFIG['enable_logging'] && ( $CONFIG['log_all'] || $fetch->parseType == 'html' ) ) {
  1086. # Is the log directory writable?
  1087. if ( checkTmpDir($CONFIG['logging_destination'], 'Deny from all') ) {
  1088. # Filename to save as
  1089. $file = $CONFIG['logging_destination'] . '/' . date('Y-m-d') . '.log';
  1090. # Line to write
  1091. $write = str_pad($_SERVER['REMOTE_ADDR'] . ', ' , 17) . date('d/M/Y:H:i:s O') . ', ' . $URL['href'] . "\r\n";
  1092. # Do it
  1093. file_put_contents($file, $write, FILE_APPEND);
  1094. }
  1095. }