Просмотр файла browse.php

Размер файла: 48.25Kb
  1. <?php
  2. /*******************************************************************
  3. * Glype is copyright and trademark 2007-2013 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. /*****************************************************************
  15. * Initialise
  16. ******************************************************************/
  17.  
  18. require 'includes/init.php';
  19.  
  20. if (count($adminDetails)===0) {
  21. header("HTTP/1.1 302 Found"); header("Location: admin.php"); exit;
  22. }
  23.  
  24. # Debug mode - stores extra information in the cURL wrapper object and prints it
  25. # out. It produces an ugly mess but still a quick tool for debugging.
  26. define('DEBUG_MODE', 0);
  27. define('CURL_LOG', 0);
  28.  
  29. # Log cURLs activity to file
  30. # Change filename below if desired. Ensure file exists and is writable.
  31. if ( CURL_LOG && ( $fh = @fopen('curl.txt', 'w')) ) {
  32. $toSet[CURLOPT_STDERR] = $fh;
  33. $toSet[CURLOPT_VERBOSE] = true;
  34. }
  35.  
  36.  
  37. /*****************************************************************
  38. * PHP sends some headers by default. Stop them.
  39. ******************************************************************/
  40.  
  41. # Clear the default mime-type
  42. header('Content-Type:');
  43.  
  44. # And remove the caching headers
  45. header('Cache-Control:');
  46. header('Last-Modified:');
  47.  
  48.  
  49. /*****************************************************************
  50. * Find URI of resource to load
  51. * NB: flag and bitfield already extracted in /includes/init.php
  52. ******************************************************************/
  53.  
  54. switch ( true ) {
  55.  
  56. # Try query string for URL
  57. case ! empty($_GET['u']) && ( $toLoad = deproxyURL($_GET['u'], true) ):
  58. break;
  59. # Try path info
  60. case ! empty($_SERVER['PATH_INFO']) && ( $toLoad = deproxyURL($_SERVER['PATH_INFO'], true) ):
  61. break;
  62. # Found no valid URL, return to index
  63. default:
  64. redirect();
  65. }
  66.  
  67. # Validate the URL
  68. if ( ! preg_match('#^((https?)://(?:([a-z0-9-.]+:[a-z0-9-.]+)@)?([a-z0-9-.]+)(?::([0-9]+))?)(?:/|$)((?:[^?/]*/)*)([^?]*)(?:\?([^\#]*))?(?:\#.*)?$#i', $toLoad, $tmp) ) {
  69.  
  70. # Invalid, show error
  71. error('invalid_url', htmlentities($toLoad));
  72.  
  73. }
  74.  
  75. # Rename parts to more useful names
  76. $URL = array(
  77. 'scheme_host' => $tmp[1],
  78. 'scheme' => $tmp[2],
  79. 'auth' => $tmp[3],
  80. 'host' => strtolower($tmp[4]),
  81. 'domain' => strtolower(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)
  82. 'port' => $tmp[5],
  83. 'path' => '/' . $tmp[6],
  84. 'filename' => $tmp[7],
  85. 'extension' => pathinfo($tmp[7], PATHINFO_EXTENSION),
  86. 'query' => isset($tmp[8]) ? $tmp[8] : ''
  87. );
  88.  
  89. # Apply encoding on full URL. In theory all parts of the URL need various special
  90. # characters encoding but this needs to be done by the author of the webpage.
  91. # We can make a guess at what needs encoding but some servers will complain when
  92. # receiving the encoded character instead of unencoded and vice versa. We want
  93. # to edit the URL as little as possible so we're only encoding spaces, as this
  94. # seems to 'fix' the majority of cases.
  95. $URL['href'] = str_replace(' ', '%20', $toLoad);
  96.  
  97. # Protect LAN from access through proxy (protected addresses copied from PHProxy)
  98. if ( preg_match('#^(?:127\.|192\.168\.|10\.|172\.(?:1[6-9]|2[0-9]|3[01])\.|localhost)#i', $URL['host']) ) {
  99. error('banned_site', $URL['host']);
  100. }
  101.  
  102. # Add any supplied authentication information to our auth array
  103. if ( $URL['auth'] ) {
  104. $_SESSION['authenticate'][$URL['scheme_host']] = $URL['auth'];
  105. }
  106.  
  107.  
  108. /*****************************************************************
  109. * Protect us from hotlinking
  110. ******************************************************************/
  111.  
  112. # Protect only if option is enabled and we don't have a verified session
  113. if ( $CONFIG['stop_hotlinking'] && empty($_SESSION['no_hotlink']) ) {
  114.  
  115. # Assume hotlinking to start with, then check against allowed domains
  116. $tmp = true;
  117.  
  118. # Ensure we have valid referrer information to check
  119. if ( ! empty($_SERVER['HTTP_REFERER']) && strpos($_SERVER['HTTP_REFERER'], 'http') === 0 ) {
  120. # Examine all the allowed domains (including our current domain)
  121. foreach ( array_merge( (array) GLYPE_URL, $CONFIG['hotlink_domains'] ) as $domain ) {
  122.  
  123. # Do a case-insensitive comparison
  124. if ( stripos($_SERVER['HTTP_REFERER'], $domain) !== false ) {
  125. # This referrer is OK
  126. $tmp = false;
  127. break;
  128. }
  129. }
  130.  
  131. }
  132. # Redirect to index if this is still identified as hotlinking
  133. if ( $tmp ) {
  134. error('no_hotlink');
  135. }
  136.  
  137. }
  138.  
  139. # If we're still here, the referrer must be OK so set the session for next time
  140. $_SESSION['no_hotlink'] = true;
  141.  
  142.  
  143. /*****************************************************************
  144. * Are we allowed to visit this site? Check whitelist/blacklist
  145. ******************************************************************/
  146.  
  147. # Whitelist - deny IF NOT on list
  148. if ( ! empty($CONFIG['whitelist']) ) {
  149.  
  150. $tmp = false;
  151.  
  152. # Loop through
  153. foreach ( $CONFIG['whitelist'] as $domain ) {
  154.  
  155. # Check for match
  156. if ( strpos($URL['host'], $domain) !== false ) {
  157.  
  158. # Must be a permitted site
  159. $tmp = true;
  160.  
  161. }
  162.  
  163. }
  164.  
  165. # Unless $tmp is flagged true, this is an illegal site
  166. if ( ! $tmp ) {
  167. error('banned_site', $URL['host']);
  168. }
  169.  
  170. }
  171.  
  172. # Blacklist
  173. if ( ! empty($CONFIG['blacklist']) ) {
  174.  
  175. # Loop through
  176. foreach ( $CONFIG['blacklist'] as $domain ) {
  177.  
  178. # Check for match
  179. if ( strpos($URL['host'], $domain) !== false ) {
  180.  
  181. # If matched, site is banned
  182. error('banned_site', $URL['host']);
  183.  
  184. }
  185.  
  186. }
  187.  
  188. }
  189.  
  190.  
  191. /*****************************************************************
  192. * Show SSL warning
  193. * This warns users if they access a secure site when the proxy is NOT
  194. * on a secure connection and the $CONFIG['ssl_warning'] option is on.
  195. ******************************************************************/
  196.  
  197. if ( $URL['scheme'] == 'https' && $CONFIG['ssl_warning'] && empty($_SESSION['ssl_warned']) && ! HTTPS ) {
  198.  
  199. # Remember this page so we can return after agreeing to the warning
  200. $_SESSION['return'] = currentURL();
  201.  
  202. # Don't cache the warning page
  203. sendNoCache();
  204.  
  205. # Show the page
  206. echo loadTemplate('sslwarning.page');
  207.  
  208. # All done!
  209. exit;
  210.  
  211. }
  212.  
  213.  
  214. /*****************************************************************
  215. * Plugins
  216. * Load any site-specific plugin.
  217. ******************************************************************/
  218. global $foundPlugin;
  219. $plugins = explode(',', $CONFIG['plugins']);
  220. if ($foundPlugin = in_array($URL['domain'], $plugins)) {
  221. include(GLYPE_ROOT.'/plugins/'.$URL['domain'].'.php');
  222. }
  223.  
  224.  
  225.  
  226. /*****************************************************************
  227. * Close session to allow simultaneous transfers
  228. * PHP automatically prevents multiple instances of the script running
  229. * simultaneously to avoid concurrency issues with the session.
  230. * This may be beneficial on high traffic servers but we have the option
  231. * to close the session and thus allow simultaneous transfers.
  232. ******************************************************************/
  233.  
  234. if ( ! $CONFIG['queue_transfers'] ) {
  235.  
  236. session_write_close();
  237.  
  238. }
  239.  
  240.  
  241. /*****************************************************************
  242. * Check load limit. This is done now rather than earlier so we
  243. * don't stop serving the (relatively) cheap cached files.
  244. ******************************************************************/
  245.  
  246. if (
  247. # Option enabled (and possible? safe_mode prevents shell_exec)
  248. ! SAFE_MODE && $CONFIG['load_limit']
  249.  
  250. # Ignore inline elements - when borderline on the server load, if the HTML
  251. # page downloads fine but the inline images, css and js are blocked, the user
  252. # may get very frustrated very quickly without knowing about the load issues.
  253. && ! in_array($URL['extension'], array('jpg','jpeg','png','gif','css','js','ico'))
  254. ) {
  255.  
  256. # Do we need to find the load and regenerate the temp cache file?
  257. # Try to fetch the load from the temp file (~30 times faster than
  258. # shell_exec()) and ensure the value is accurate and not outdated,
  259. if( ! file_exists($file = $CONFIG['tmp_dir'] . 'load.php') || ! (include $file) || ! isset($load, $lastChecked) || $lastChecked < $_SERVER['REQUEST_TIME']-60 ) {
  260.  
  261. $load = (float) 0;
  262.  
  263. # Attempt to fetch the load
  264. if ( ($uptime = @shell_exec('uptime')) && preg_match('#load average: ([0-9.]+),#', $uptime, $tmp) ) {
  265. $load = (float) $tmp[1];
  266.  
  267. # And regenerate the file
  268. file_put_contents($file, '<?php $load = ' . $load . '; $lastChecked = ' . $_SERVER['REQUEST_TIME'] . ';');
  269. }
  270.  
  271. }
  272.  
  273. # Load found, (or at least, should be), check against max permitted
  274. if ( $load > $CONFIG['load_limit'] ) {
  275. error('server_busy'); # Show error
  276. }
  277. }
  278.  
  279.  
  280. /*****************************************************************
  281. * * * * * * * * * * Prepare the REQUEST * * * * * * * * * * * *
  282. ******************************************************************/
  283.  
  284. /*****************************************************************
  285. * Set cURL transfer options
  286. * These options are merely passed to cURL and our script has no further
  287. * impact or dependence of them. See the libcurl documentation and
  288. * http://php.net/curl_setopt for more details.
  289. *
  290. * The following options are required for the proxy to function or
  291. * inherit values from our config. In short: they shouldn't need changing.
  292. ******************************************************************/
  293.  
  294. # Time to wait for connection
  295. $toSet[CURLOPT_CONNECTTIMEOUT] = $CONFIG['connection_timeout'];
  296.  
  297. # Time to allow for entire transfer
  298. $toSet[CURLOPT_TIMEOUT] = $CONFIG['transfer_timeout'];
  299.  
  300. # Show SSL without verifying - we almost definitely don't have an up to date CA cert
  301. # bundle so we can't verify the certificate. See http://curl.haxx.se/docs/sslcerts.html
  302. $toSet[CURLOPT_SSL_VERIFYPEER] = false;
  303. $toSet[CURLOPT_SSL_VERIFYHOST] = false;
  304.  
  305. # Send an empty Expect header (avoids 100 responses)
  306. $toSet[CURLOPT_HTTPHEADER][] = 'Expect:';
  307.  
  308. # Can we use "If-Modified-Since" to save a transfer? Server can return 304 Not Modified
  309. if ( isset($_SERVER['HTTP_IF_MODIFIED_SINCE']) ) {
  310.  
  311. # How to treat the time condition : if un/modified since
  312. $toSet[CURLOPT_TIMECONDITION] = CURL_TIMECOND_IFMODSINCE;
  313.  
  314. # The time value. Requires a timestamp so we can't just forward it raw
  315. $toSet[CURLOPT_TIMEVALUE] = strtotime($_SERVER['HTTP_IF_MODIFIED_SINCE']);
  316.  
  317. }
  318.  
  319. # Resume a transfer?
  320. if ( $CONFIG['resume_transfers'] && isset($_SERVER['HTTP_RANGE']) ) {
  321.  
  322. # And give cURL the right part
  323. $toSet[CURLOPT_RANGE] = substr($_SERVER['HTTP_RANGE'], 6);
  324.  
  325. }
  326.  
  327. # cURL has a max filesize option but it's not listed in the PHP manual so check it's available
  328. if ( $CONFIG['max_filesize'] && defined('CURLOPT_MAXFILESIZE') ) {
  329.  
  330. # Use the cURL option - should be faster than our implementation
  331. $toSet[CURLOPT_MAXFILESIZE] = $CONFIG['max_filesize'];
  332.  
  333. }
  334.  
  335.  
  336. /*****************************************************************
  337. * Performance options
  338. * The values below are NOT the result of benchmarking tests. For
  339. * optimum performance, you may want to try adjusting these values.
  340. ******************************************************************/
  341.  
  342. # DNS cache expiry time (seconds)
  343. $toSet[CURLOPT_DNS_CACHE_TIMEOUT] = 600;
  344.  
  345. # Speed limits - aborts transfer if we're going too slowly
  346. #$toSet[CURLOPT_LOW_SPEED_LIMIT] = 5; # speed limit in bytes per second
  347. #$toSet[CURLOPT_LOW_SPEED_TIME] = 20; # seconds spent under the speed limit before aborting
  348.  
  349. # Number of max connections (no idea what this should be)
  350. # $toSet[CURLOPT_MAXCONNECTS] = 100;
  351.  
  352. # Accept encoding in any format (allows compressed pages to be downloaded)
  353. # Any bandwidth savings are likely to be minimal so better to save on load by
  354. # downloading pages uncompressed. Use blank string for any compression or
  355. # 'identity' to explicitly ask for uncompressed.
  356. # $toSet[CURLOPT_ENCODING] = '';
  357.  
  358. # Undocumented in PHP manual (added 5.2.1) but allows uploads to some sites
  359. # (e.g. imageshack) when without this option, an error occurs. Less efficient
  360. # so probably best not to set this unless you need it.
  361. # $toSet[CURLOPT_TCP_NODELAY] = true;
  362.  
  363.  
  364. /*****************************************************************
  365. * "Accept" headers
  366. * No point sending back a file that the browser won't understand.
  367. * Forward all the "Accept" headers. For each, check if it exists
  368. * and if yes, add to the custom headers array.
  369. * NB: These may cause problems if the target server provides different
  370. * content for the same URI based on these headers and we cache the response.
  371. ******************************************************************/
  372.  
  373. # Language (geotargeting will find the location of the server -
  374. # forwarding this header can help avoid incorrect localisation)
  375. if ( isset($_SERVER['HTTP_ACCEPT_LANGUAGE']) ) {
  376. $toSet[CURLOPT_HTTPHEADER][] = 'Accept-Language: ' . $_SERVER['HTTP_ACCEPT_LANGUAGE'];
  377. }
  378.  
  379. # Accepted filetypes
  380. if ( isset($_SERVER['HTTP_ACCEPT']) ) {
  381. $toSet[CURLOPT_HTTPHEADER][] = 'Accept: ' . $_SERVER['HTTP_ACCEPT'];
  382. }
  383.  
  384. # Accepted charsets
  385. if ( isset($_SERVER['HTTP_ACCEPT_CHARSET']) ) {
  386. $toSet[CURLOPT_HTTPHEADER][] = 'Accept-Charset: ' . $_SERVER['HTTP_ACCEPT_CHARSET'];
  387. }
  388.  
  389.  
  390. /*****************************************************************
  391. * Browser options
  392. * Allows customization of a "virtual" browser via /extras/edit-browser.php
  393. ******************************************************************/
  394.  
  395. # Send user agent
  396. if ( $_SESSION['custom_browser']['user_agent'] ) {
  397. $toSet[CURLOPT_USERAGENT] = $_SESSION['custom_browser']['user_agent'];
  398. }
  399.  
  400. # Set referrer
  401. if ( $_SESSION['custom_browser']['referrer'] == 'real' ) {
  402.  
  403. # Automatically determine referrer
  404. if ( isset($_SERVER['HTTP_REFERER']) && $flag != 'norefer' && strpos($tmp = deproxyURL($_SERVER['HTTP_REFERER']), GLYPE_URL) === false ) {
  405. $toSet[CURLOPT_REFERER] = $tmp;
  406. }
  407.  
  408. } else if ( $_SESSION['custom_browser']['referrer'] ) {
  409.  
  410. # Send custom referrer
  411. $toSet[CURLOPT_REFERER] = $_SESSION['custom_browser']['referrer'];
  412.  
  413. }
  414.  
  415. # Clear the norefer flag
  416. if ( $flag == 'norefer' ) {
  417. $flag = '';
  418. }
  419.  
  420.  
  421. /*****************************************************************
  422. * Authentication
  423. ******************************************************************/
  424.  
  425. # Check for stored credentials for this site
  426. if ( isset($_SESSION['authenticate'][$URL['scheme_host']]) ) {
  427.  
  428. # Found credentials so use them!
  429. $toSet[CURLOPT_HTTPAUTH] = CURLAUTH_BASIC;
  430. $toSet[CURLOPT_USERPWD] = $_SESSION['authenticate'][$URL['scheme_host']];
  431.  
  432. }
  433.  
  434.  
  435. /*****************************************************************
  436. * Cookies
  437. * Find the relevant cookies for this request. All cookies get sent
  438. * to the proxy, but we only want to forward the ones that were set
  439. * for the current domain.
  440. *
  441. * Cookie storage methods:
  442. * (1) Server-side - cookies stored server-side and handled
  443. * (mostly) internally by cURL
  444. * (2) Encoded - cookies forwarded to client but encoded
  445. * (3) Normal - cookies forwarded without encoding
  446. ******************************************************************/
  447.  
  448. # Are cookies allowed?
  449. if ( $options['allowCookies'] ) {
  450.  
  451. # Option (1): cookies stored server-side
  452. if ( $CONFIG['cookies_on_server'] ) {
  453.  
  454. # Check cookie folder exists or try to create it
  455. if ( $s = checkTmpDir($CONFIG['cookies_folder'], 'Deny from all') ) {
  456.  
  457. # Set cURL to use this as the cookie jar
  458. $toSet[CURLOPT_COOKIEFILE] = $toSet[CURLOPT_COOKIEJAR] = $CONFIG['cookies_folder'] . session_id();
  459.  
  460. }
  461.  
  462. } else if ( isset($_COOKIE[COOKIE_PREFIX]) ) {
  463.  
  464. # Encoded or unencoded?
  465. if ( $CONFIG['encode_cookies'] ) {
  466.  
  467. # Option (2): encoded cookies stored client-side
  468. foreach ( $_COOKIE[COOKIE_PREFIX] as $attributes => $value ) {
  469.  
  470. # Decode cookie to [domain,path,name]
  471. $attributes = explode(' ', base64_decode($attributes));
  472.  
  473. # Check successful decoding and skip if failed
  474. if ( ! isset($attributes[2]) ) {
  475. continue;
  476. }
  477.  
  478. # Extract parts
  479. list($domain, $path, $name) = $attributes;
  480.  
  481. # Check for a domain match and skip if no match
  482. if ( stripos($URL['host'], $domain) === false ) {
  483. continue;
  484. }
  485.  
  486. # Check for match and skip to next path if fail
  487. if ( stripos($URL['path'], $path) !== 0 ) {
  488. continue;
  489. }
  490.  
  491. # Multiple cookies of the same name are permitted if different paths
  492. # so use path AND name as the key in the temp array
  493. $key = $path . $name;
  494.  
  495. # Check for existing cookie with same domain, same path and same name
  496. if ( isset($toSend[$key]) && $toSend[$key]['path'] == $path && $toSend[$key]['domain'] > strlen($domain) ) {
  497.  
  498. # Conflicting cookies so ignore the one with the less complete tail match
  499. # (i.e. the current one)
  500. continue;
  501.  
  502. }
  503.  
  504. # Domain and path OK, decode cookie value
  505. $value = base64_decode($value);
  506.  
  507. # Only send secure cookies on https connection - secure cookies marked by !SEC suffix
  508. # so remove the suffix
  509. $value = str_replace('!SEC', '', $value, $tmp);
  510.  
  511. # And if secure cookie but not https site, do not send
  512. if ( $tmp && $URL['scheme'] != 'https' ) {
  513. continue;
  514. }
  515.  
  516.  
  517. # Everything checked and verified, add to $toSend for further processing later
  518. $toSend[$key] = array('path_size' => strlen($path), 'path' => $path, 'domain' => strlen($domain), 'send' => $name . '=' . $value);
  519.  
  520. }
  521.  
  522. } else {
  523.  
  524. # Option (3): unencoded cookies stored client-side
  525. foreach ( $_COOKIE[COOKIE_PREFIX] as $domain => $paths ) {
  526.  
  527. # $domain holds the domain (surprisingly) and $path is an array
  528. # of keys (paths) and more arrays (each child array of $path = one cookie)
  529. # e.g. Array('domain.com' => Array('/' => Array('cookie_name' => 'value')))
  530.  
  531. # First check for domain match and skip to next domain if no match
  532. if ( stripos($URL['host'], $domain) === false ) {
  533. continue;
  534. }
  535.  
  536. # If conflicting cookies with same name and same path,
  537. # send the one with the more complete tail match. To do this we
  538. # need to know how long each match is/was so record domain length.
  539. $domainSize = strlen($domain);
  540.  
  541. # Now look at all the available paths
  542. foreach ( $paths as $path => $cookies ) {
  543.  
  544. # Check for match and skip to next path if fail
  545. if ( stripos($URL['path'], $path) !== 0 ) {
  546. continue;
  547. }
  548.  
  549. # In final header, cookies are ordered with most specific path
  550. # matches first so include the length of match in temp array
  551. $pathSize = strlen($path);
  552.  
  553. # All cookies in $cookies array should be sent
  554. foreach ( $cookies as $name => $value ) {
  555.  
  556. # Multiple cookies of the same name are permitted if different paths
  557. # so use path AND name as the key in the temp array
  558. $key = $path . $name;
  559.  
  560. # Check for existing cookie with same domain, same path and same name
  561. if ( isset($toSend[$key]) && $toSend[$key]['path'] == $path && $toSend[$key]['domain'] > $domainSize ) {
  562.  
  563. # Conflicting cookies so ignore the one with the less complete tail match
  564. # (i.e. the current one)
  565. continue;
  566.  
  567. }
  568.  
  569. # Only send secure cookies on https connection - secure cookies marked by !SEC suffix
  570. # so remove the suffix
  571. $value = str_replace('!SEC', '', $value, $tmp);
  572.  
  573. # And if secure cookie but not https site, do not send
  574. if ( $tmp && $URL['scheme'] != 'https' ) {
  575. continue;
  576. }
  577.  
  578. # Add to $toSend for further processing later
  579. $toSend[$key] = array('path_size' => $pathSize, 'path' => $path, 'domain' => $domainSize, 'send' => $name . '=' . $value);
  580.  
  581. }
  582.  
  583. }
  584.  
  585. }
  586.  
  587. }
  588.  
  589. # Ensure we have found cookies
  590. if ( ! empty($toSend) ) {
  591.  
  592. # Order by path specificity (as per Netscape spec)
  593. function compareArrays($a, $b) {
  594. return ( $a['path_size'] > $b['path_size'] ) ? -1 : 1;
  595. }
  596.  
  597. # Apply the sort to order by path_size descending
  598. uasort($toSend, 'compareArrays');
  599.  
  600. # Go through the ordered array and generate the Cookie: header
  601. $tmp = '';
  602.  
  603. foreach ( $toSend as $cookie ) {
  604. $tmp .= $cookie['send'] . '; ';
  605. }
  606.  
  607. # Give the string to cURL
  608. $toSet[CURLOPT_COOKIE] = $tmp;
  609.  
  610. }
  611.  
  612. # And clear the toSend array
  613. unset($toSend);
  614.  
  615. }
  616.  
  617. }
  618.  
  619.  
  620. /*****************************************************************
  621. * Post
  622. * Forward the post data. Usually very simple but complicated by
  623. * multipart forms because in those cases, the raw post is not available.
  624. ******************************************************************/
  625.  
  626. if ( ! empty($_POST) ) {
  627.  
  628. # Attempt to get raw POST from the input wrapper
  629. if ( ! ($tmp = file_get_contents('php://input')) ) {
  630.  
  631. # Raw data not available (probably multipart/form-data).
  632. # cURL will do a multipart post if we pass an array as the
  633. # POSTFIELDS value but this array can only be one deep.
  634.  
  635. # Recursively flatten array to one level deep and rename keys
  636. # as firstLayer[second][etc]. Also apply the input decode to all
  637. # array keys.
  638. function flattenArray($array, $prefix='') {
  639.  
  640. # Start with empty array
  641. $stack = array();
  642.  
  643. # Loop through the array to flatten
  644. foreach ( $array as $key => $value ) {
  645.  
  646. # Decode the input name
  647. $key = inputDecode($key);
  648.  
  649. # Determine what the new key should be - add the current key to
  650. # the prefix and surround in []
  651. $newKey = $prefix ? $prefix . '[' . $key . ']' : $key;
  652.  
  653. if ( is_array($value) ) {
  654.  
  655. # If it's an array, recurse and merge the returned array
  656. $stack = array_merge($stack, flattenArray($value, $newKey));
  657.  
  658. } else {
  659.  
  660. # Otherwise just add it to the current stack
  661. $stack[$newKey] = clean($value);
  662.  
  663. }
  664.  
  665. }
  666.  
  667. # Return flattened
  668. return $stack;
  669.  
  670. }
  671.  
  672. $tmp = flattenArray($_POST);
  673.  
  674. # Add any file uploads?
  675. if ( ! empty($_FILES) ) {
  676.  
  677. # Loop through and add the files
  678. foreach ( $_FILES as $name => $file ) {
  679.  
  680. # Is this an array?
  681. if ( is_array($file['tmp_name']) ) {
  682.  
  683. # Flatten it - file arrays are in the slightly odd format of
  684. # $_FILES['layer1']['tmp_name']['layer2']['layer3,etc.'] so add
  685. # layer1 onto the start.
  686. $flattened = flattenArray(array($name => $file['tmp_name']));
  687.  
  688. # And add all files to the post
  689. foreach ( $flattened as $key => $value ) {
  690. $tmp[$key] = '@' . $value;
  691. }
  692.  
  693. } else {
  694.  
  695. # Not another array. Check if the file uploaded successfully?
  696. if ( ! empty($file['error']) || empty($file['tmp_name']) ) {
  697. continue;
  698. }
  699.  
  700. # Add to array with @ - tells cURL to upload this file
  701. $tmp[$name] = '@' . $file['tmp_name'];
  702.  
  703. }
  704.  
  705. # To do: rename the temp file to it's real name before
  706. # uploading it to the target? Otherwise, the target receives
  707. # the temp name instead of the original desired name
  708. # but doing this may be a security risk.
  709.  
  710. }
  711.  
  712. }
  713.  
  714. }
  715.  
  716. # Convert back to GET if required
  717. if ( isset($_POST['convertGET']) ) {
  718.  
  719. # Remove convertGET from POST array and update our location
  720. $URL['href'] .= ( empty($URL['query']) ? '?' : '&' ) . str_replace('convertGET=1', '', $tmp);
  721.  
  722. } else {
  723.  
  724. # Genuine POST so set the cURL post value
  725. $toSet[CURLOPT_POST] = 1;
  726. $toSet[CURLOPT_POSTFIELDS] = $tmp;
  727.  
  728. }
  729.  
  730. }
  731.  
  732.  
  733. /*****************************************************************
  734. * Apply pre-request code from plugins
  735. ******************************************************************/
  736.  
  737. if ( $foundPlugin && function_exists('preRequest') ) {
  738. preRequest();
  739. }
  740.  
  741.  
  742. /*****************************************************************
  743. * Make the request
  744. * This request object uses custom header/body reading functions
  745. * so we can start processing responses on the fly - e.g. we don't
  746. * need to wait till the whole file has downloaded before deciding
  747. * if it needs parsing or can be sent out unchanged.
  748. ******************************************************************/
  749.  
  750. class Request {
  751.  
  752. # Response status code
  753. public $status = 0;
  754.  
  755. # Headers received and read by our callback
  756. public $headers = array();
  757.  
  758. # Returned data (if saved)
  759. public $return;
  760.  
  761. # Reason for aborting transfer (or empty to continue downloading)
  762. public $abort;
  763.  
  764. # The error (if any) returned by curl_error()
  765. public $error;
  766.  
  767. # Type of resource downloaded [html, js, css] or empty if no parsing needed
  768. public $parseType;
  769.  
  770. # Automatically detect(ed) content type?
  771. public $sniff = false;
  772.  
  773. # Forward cookies or not
  774. private $forwardCookies = false;
  775.  
  776. # Limit filesize?
  777. private $limitFilesize = 0;
  778.  
  779. # Speed limit (bytes per second)
  780. private $speedLimit = 0;
  781. # URL array split into pieces
  782. private $URL;
  783.  
  784. # = $options from the global scope
  785. private $browsingOptions;
  786.  
  787. # Options to pass to cURL
  788. private $curlOptions;
  789.  
  790.  
  791. # Constructor - takes the parameters and saves them
  792. public function __construct($curlOptions) {
  793.  
  794. global $options, $CONFIG;
  795.  
  796. # Set our reading callbacks
  797. $curlOptions[CURLOPT_HEADERFUNCTION] = array(&$this, 'readHeader');
  798. $curlOptions[CURLOPT_WRITEFUNCTION] = array(&$this, 'readBody');
  799.  
  800. # Determine whether or not to forward cookies
  801. if ( $options['allowCookies'] && ! $CONFIG['cookies_on_server'] ) {
  802. $this->forwardCookies = $CONFIG['encode_cookies'] ? 'encode' : 'normal';
  803. }
  804.  
  805. # Determine a filesize limit
  806. if ( $CONFIG['max_filesize'] ) {
  807. $this->limitFilesize = $CONFIG['max_filesize'];
  808. }
  809. # Determine speed limit
  810. if ( $CONFIG['download_speed_limit'] ) {
  811. $this->speedLimit = $CONFIG['download_speed_limit'];
  812. }
  813.  
  814. # Set options
  815. $this->browsingOptions = $options;
  816. $this->curlOptions = $curlOptions;
  817.  
  818. # Extend the PHP timeout
  819. if ( ! SAFE_MODE ) {
  820. set_time_limit($CONFIG['transfer_timeout']);
  821. }
  822.  
  823. # Record debug information
  824. if ( DEBUG_MODE ) {
  825. $this->cookiesSent = isset($curlOptions[CURLOPT_COOKIE]) ? $curlOptions[CURLOPT_COOKIE] : ( isset($curlOptions[CURLOPT_COOKIEFILE]) ? 'using cookie jar' : 'none');
  826. $this->postSent = isset($curlOptions[CURLOPT_POSTFIELDS]) ? $curlOptions[CURLOPT_POSTFIELDS] : '';
  827. }
  828.  
  829. }
  830.  
  831. # Make the request and return the downloaded file if parsing is needed
  832. public function go($URL) {
  833.  
  834. # Save options
  835. $this->URL = $URL;
  836.  
  837. # Get a cURL handle
  838. $ch = curl_init($this->URL['href']);
  839.  
  840. # Set the options
  841. curl_setopt_array($ch, $this->curlOptions);
  842.  
  843. # Make the request
  844. curl_exec($ch);
  845.  
  846. # Save any errors (but not if we caused the error by aborting!)
  847. if ( ! $this->abort ) {
  848. $this->error = curl_error($ch);
  849. }
  850.  
  851. # And close the curl handle
  852. curl_close($ch);
  853.  
  854. # And return the document (will be empty if no parsing needed,
  855. # because everything else is outputted immediately)
  856. return $this->return;
  857.  
  858. }
  859.  
  860.  
  861. /*****************************************************************
  862. * * * * * * * * * * Manage the RESPONSE * * * * * * * * * * * *
  863. ******************************************************************/
  864.  
  865.  
  866. /*****************************************************************
  867. * Read headers - receives headers line by line (cURL callback)
  868. ******************************************************************/
  869.  
  870. public function readHeader($handle, $header) {
  871.  
  872. # Extract the status code (can occur more than once if 100 continue)
  873. if ( $this->status == 0 || ( $this->status == 100 && ! strpos($header, ':') ) ) {
  874. $this->status = substr($header, 9, 3);
  875. }
  876.  
  877. # Attempt to extract header name and value
  878. $parts = explode(':', $header, 2);
  879.  
  880. # Did it split successfully? (i.e. was there a ":" in the header?)
  881. if ( isset($parts[1]) ) {
  882.  
  883. # Header names are case insensitive
  884. $headerType = strtolower($parts[0]);
  885.  
  886. # And header values will have trailing newlines and prevailing spaces
  887. $headerValue = trim($parts[1]);
  888.  
  889. # Set any cookies
  890. if ( $headerType == 'set-cookie' && $this->forwardCookies ) {
  891.  
  892. $this->setCookie($headerValue);
  893.  
  894. }
  895.  
  896. # Everything else, store as associative array
  897. $this->headers[$headerType] = $headerValue;
  898.  
  899. # Do we want to forward this header? First list the headers we want:
  900. $toForward = array('last-modified',
  901. 'content-disposition',
  902. 'content-type',
  903. 'content-range',
  904. 'content-language',
  905. 'expires',
  906. 'cache-control',
  907. 'pragma');
  908.  
  909. # And check for a match before forwarding the header.
  910. if ( in_array($headerType, $toForward) ) {
  911. header($header);
  912. }
  913.  
  914. } else {
  915.  
  916. # Either first header or last 'header' (more precisely, the 2 newlines
  917. # that indicate end of headers)
  918.  
  919. # No ":", so save whole header. Also check for end of headers.
  920. if ( ( $this->headers[] = trim($header) ) == false ) {
  921.  
  922. # Must be end of headers so process them before reading body
  923. $this->processHeaders();
  924.  
  925. # And has that processing given us any reason to abort?
  926. if ( $this->abort ) {
  927. return -1;
  928. }
  929.  
  930. }
  931.  
  932. }
  933.  
  934. # cURL needs us to return length of data read
  935. return strlen($header);
  936.  
  937. }
  938.  
  939.  
  940. /*****************************************************************
  941. * Process headers after all received and before body is read
  942. ******************************************************************/
  943.  
  944. private function processHeaders() {
  945.  
  946. # Ensure we only run this function once
  947. static $runOnce;
  948.  
  949. # Check for flag and if found, stop running function
  950. if ( isset($runOnce) ) {
  951. return;
  952. }
  953.  
  954. # Set flag for next time
  955. $runOnce = true;
  956.  
  957. # Send the appropriate status code
  958. header(' ', true, $this->status);
  959.  
  960. # Find out if we want to abort the transfer
  961. switch ( true ) {
  962.  
  963. # Redirection
  964. case isset($this->headers['location']):
  965.  
  966. $this->abort = 'redirect';
  967.  
  968. return;
  969.  
  970. # 304 Not Modified
  971. case $this->status == 304:
  972.  
  973. $this->abort = 'not_modified';
  974.  
  975. return;
  976.  
  977. # 401 Auth required
  978. case $this->status == 401:
  979.  
  980. $this->abort = 'auth_required';
  981.  
  982. return;
  983.  
  984. # Error code (>=400)
  985. case $this->status >= 400:
  986.  
  987. $this->abort = 'http_status_error';
  988.  
  989. return;
  990.  
  991. # Check for a content-length above the filesize limit
  992. case isset($this->headers['content-length']) && $this->limitFilesize && $this->headers['content-length'] > $this->limitFilesize:
  993.  
  994. $this->abort = 'filesize_limit';
  995.  
  996. return;
  997.  
  998. }
  999.  
  1000. # Still here? No need to abort so next we determine parsing mechanism to use (if any)
  1001. if ( isset($this->headers['content-type']) ) {
  1002.  
  1003. # Define content-type to parser type relations
  1004. $types = array(
  1005. 'text/javascript' => 'javascript',
  1006. 'text/ecmascript' => 'javascript',
  1007. 'application/javascript' => 'javascript',
  1008. 'application/x-javascript' => 'javascript',
  1009. 'application/ecmascript' => 'javascript',
  1010. 'application/x-ecmascript' => 'javascript',
  1011. 'text/livescript' => 'javascript',
  1012. 'text/jscript' => 'javascript',
  1013. 'application/xhtml+xml' => 'html',
  1014. 'text/html' => 'html',
  1015. 'text/css' => 'css',
  1016. # 'text/xml' => 'rss',
  1017. # 'application/rss+xml' => 'rss',
  1018. # 'application/rdf+xml' => 'rss',
  1019. # 'application/atom+xml' => 'rss',
  1020. # 'application/xml' => 'rss',
  1021. );
  1022.  
  1023. # Extract mimetype from charset (if exists)
  1024. global $charset;
  1025. $content_type = explode(';', $this->headers['content-type'], 2);
  1026. $mime = isset($content_type[0]) ? trim($content_type[0]) : '';
  1027. if (isset($content_type[1])) {
  1028. $charset = preg_match('#charset\s*=\s*([^"\'\s]*)#is', $content_type[1], $tmp, PREG_OFFSET_CAPTURE) ? $tmp[1][0] : null;
  1029. }
  1030.  
  1031. # Look for that mimetype in our array to find the parsing mechanism needed
  1032. if ( isset($types[$mime]) ) {
  1033. $this->parseType = $types[$mime];
  1034. }
  1035.  
  1036. } else {
  1037.  
  1038. # Tell our read body function to 'sniff' the data to determine type
  1039. $this->sniff = true;
  1040.  
  1041. }
  1042.  
  1043. # If no content-disposition sent, send one with the correct filename
  1044. if ( ! isset($this->headers['content-disposition']) && $this->URL['filename'] ) {
  1045. header('Content-Disposition: filename="' . $this->URL['filename'] . '"');
  1046. }
  1047.  
  1048. # If filesize limit exists, content-length received and we're still here, the
  1049. # content-length is OK. If we assume the content-length is accurate (and since
  1050. # clients [and possibly libcurl too] stop downloading after reaching the limit,
  1051. # it's probably safe to assume that),we can save on load by not checking the
  1052. # limit with each chunk received.
  1053. if ( $this->limitFilesize && isset($this->headers['content-length']) ) {
  1054. $this->limitFilesize = 0;
  1055. }
  1056.  
  1057. }
  1058.  
  1059.  
  1060. /*****************************************************************
  1061. * Read body - takes chunks of data (cURL callback)
  1062. ******************************************************************/
  1063.  
  1064. public function readBody($handle, $data) {
  1065.  
  1066. # Static var to tell us if this function has been run before
  1067. static $first;
  1068.  
  1069. # Check for set variable
  1070. if ( ! isset($first) ) {
  1071.  
  1072. # Run the pre-body code
  1073. $this->firstBody($data);
  1074.  
  1075. # Set the variable so we don't run this code again
  1076. $first = false;
  1077.  
  1078. }
  1079.  
  1080. # Find length of data
  1081. $length = strlen($data);
  1082. # Limit speed to X bytes/second
  1083. if ( $this->speedLimit ) {
  1084. # Limit download speed
  1085. # Speed = Amount of data / Time
  1086. # [bytes/s] = [bytes] / [s]
  1087. # We know the desired speed (defined earlier in bytes per second)
  1088. # and we know the number of bytes we've received. Now we need to find
  1089. # the time that it should take to receive those bytes.
  1090. $time = $length / $this->speedLimit; # [s]
  1091.  
  1092. # Convert time to microseconds and sleep for that value
  1093. usleep(round($time * 1000000));
  1094. }
  1095. # Monitor length if desired
  1096. if ( $this->limitFilesize ) {
  1097.  
  1098. # Set up a static downloaded-bytes value
  1099. static $downloadedBytes;
  1100.  
  1101. if ( ! isset($downloadedBytes) ) {
  1102. $downloadedBytes = 0;
  1103. }
  1104.  
  1105. # Add length to downloadedBytes
  1106. $downloadedBytes += $length;
  1107.  
  1108. # Is downloadedBytes over the limit?
  1109. if ( $downloadedBytes > $this->limitFilesize ) {
  1110.  
  1111. # Set the abort variable and return -1 (so cURL aborts)
  1112. $this->abort = 'filesize_limit';
  1113. return -1;
  1114.  
  1115. }
  1116.  
  1117. }
  1118.  
  1119. # If parsing is required, save as $return
  1120. if ( $this->parseType ) {
  1121.  
  1122. $this->return .= $data;
  1123.  
  1124. } else {
  1125. echo $data; # No parsing so print immediately
  1126. }
  1127.  
  1128. # cURL needs us to return length of data read
  1129. return $length;
  1130.  
  1131. }
  1132.  
  1133.  
  1134.  
  1135.  
  1136. /*****************************************************************
  1137. * Process first chunk of data in body
  1138. * Sniff the content if no content-type was sent and create the file
  1139. * handle if caching this.
  1140. ******************************************************************/
  1141.  
  1142. private function firstBody($data) {
  1143.  
  1144. # Do we want to sniff the data? Determines if ascii or binary.
  1145. if ( $this->sniff ) {
  1146.  
  1147. # Take a sample of 100 chars chosen at random
  1148. $length = strlen($data);
  1149. $sample = $length < 150 ? $data : substr($data, rand(0, $length-100), 100);
  1150.  
  1151. # Assume ASCII if more than 95% of bytes are "normal" text characters
  1152. if ( strlen(preg_replace('#[^A-Z0-9\!"$%\^&*\(\)=\+\\\\|\[\]\{\};:\\\'\@\#~,\.<>/\?\-]#i', '', $sample)) > 95 ) {
  1153.  
  1154. # To do: expand this to detect if html/js/css
  1155. $this->parseType = 'html';
  1156.  
  1157. }
  1158.  
  1159. }
  1160.  
  1161. # Now we know if parsing is required, we can forward content-length
  1162. if ( ! $this->parseType && isset($this->headers['content-length']) ) {
  1163. header('Content-Length: ' . $this->headers['content-length']);
  1164. }
  1165.  
  1166. }
  1167.  
  1168.  
  1169. /*****************************************************************
  1170. * Accept cookies - takes the value from Set-Cookie: [COOKIE STRING]
  1171. * and forwards cookies to the client
  1172. ******************************************************************/
  1173.  
  1174. private function setCookie($cookieString) {
  1175.  
  1176. # The script can handle cookies following the Netscape specification
  1177. # (or close enough!) and supports "Max-Age" from RFC2109
  1178.  
  1179. # Split parts by ;
  1180. $cookieParts = explode(';', $cookieString);
  1181.  
  1182. # Process each line
  1183. foreach ( $cookieParts as $part ) {
  1184.  
  1185. # Split attribute/value pairs by =
  1186. $pair = explode('=', $part, 2);
  1187.  
  1188. # Ensure we have a second part
  1189. $pair[1] = isset($pair[1]) ? $pair[1] : '';
  1190.  
  1191. # First pair must be name/cookie value
  1192. if ( ! isset($cookieName) ) {
  1193.  
  1194. # Name is first pair item, value is second
  1195. $cookieName = $pair[0];
  1196. $cookieValue = $pair[1];
  1197.  
  1198. # Skip rest of loop and start processing attributes
  1199. continue;
  1200.  
  1201. }
  1202.  
  1203. # If still here, must be an attribute (case-insensitive so lower it)
  1204. $pair[0] = strtolower($pair[0]);
  1205.  
  1206. # And save in array
  1207. if ( $pair[1] ) {
  1208.  
  1209. # We have a attribute/value pair so save as associative
  1210. $attr[ltrim($pair[0])] = $pair[1];
  1211.  
  1212. } else {
  1213.  
  1214. # Not a pair, just a value
  1215. $attr[] = $pair[0];
  1216.  
  1217. }
  1218.  
  1219. }
  1220.  
  1221. # All cookies need to be sent to this script (and then we choose
  1222. # the correct cookies to forward to the client) so the extra attributes
  1223. # (path, domain, etc.) must be stored in the cookie itself
  1224.  
  1225. # Cookies stored as c[domain.com][path][cookie_name] with values of
  1226. # cookie_value;secure;
  1227. # If encoded, cookie name becomes c[base64_encode(domain.com path cookie_name)]
  1228.  
  1229. # Find the EXPIRES date
  1230. if ( isset($attr['expires']) ) {
  1231.  
  1232. # From the "Expires" attribute (original Netscape spec)
  1233. $expires = strtotime($attr['expires']);
  1234.  
  1235. } else if ( isset($attr['max-age']) ) {
  1236.  
  1237. # From the "Max-Age" attribute (RFC2109)
  1238. $expires = $_SERVER['REQUEST_TIME']+$attr['max-age'];
  1239.  
  1240. } else {
  1241.  
  1242. # Default to temp cookies
  1243. $expires = 0;
  1244.  
  1245. }
  1246.  
  1247. # If temp cookies, override expiry date to end of session unless time
  1248. # is in the past since that means the cookie should be deleted
  1249. if ( $this->browsingOptions['tempCookies'] && $expires > $_SERVER['REQUEST_TIME'] ) {
  1250. $expires = 0;
  1251. }
  1252.  
  1253. # Find the PATH. The spec says if none found, default to the current path.
  1254. # Certain browsers default to the the root path so we'll do the same.
  1255. if ( ! isset($attr['path']) ) {
  1256. $attr['path'] = '/';
  1257. }
  1258.  
  1259. # Were we sent a DOMAIN?
  1260. if ( isset($attr['domain']) ) {
  1261.  
  1262. # Ensure it's valid and we can accept this cookie
  1263. if ( stripos($attr['domain'], $this->URL['domain']) === false ) {
  1264.  
  1265. # Our current domain does not match the specified domain
  1266. # so we reject the cookie
  1267. return;
  1268.  
  1269. }
  1270.  
  1271. # Some cookies will be sent with the domain starting with . as per RFC2109
  1272. # The . then has to be stripped off by us when doing the tail match to determine
  1273. # which cookies to send since ".glype.com" should match "glype.com". It's more
  1274. # efficient to do any manipulations while forwarding cookies than on every request
  1275. if ( $attr['domain'][0] == '.' ) {
  1276. $attr['domain'] = substr($attr['domain'], 1);
  1277. }
  1278.  
  1279. } else {
  1280.  
  1281. # No domain sent so use current domain
  1282. $attr['domain'] = $this->URL['domain'];
  1283.  
  1284. }
  1285.  
  1286. # Check for SECURE cookie
  1287. $sentSecure = in_array('secure', $attr);
  1288.  
  1289. # Append "[SEC]" to cookie value if we should only forward to secure connections
  1290. if ( $sentSecure ) {
  1291. $cookieValue .= '!SEC';
  1292. }
  1293.  
  1294. # If we're on HTTPS, we can also send this cookie back as secure
  1295. $secure = HTTPS && $sentSecure;
  1296.  
  1297. # If the PHP version is recent enough, we can also forward the httponly flag
  1298. $httponly = in_array('httponly', $attr) && version_compare(PHP_VERSION,'5.2.0','>=') ? true : false;
  1299.  
  1300. # Prepare cookie name/value to save as
  1301. $name = COOKIE_PREFIX . '[' . $attr['domain'] . '][' . $attr['path'] . '][' . inputEncode($cookieName) . ']';
  1302. $value = $cookieValue;
  1303.  
  1304. # Add encodings
  1305. if ( $this->forwardCookies == 'encode' ) {
  1306.  
  1307. $name = COOKIE_PREFIX . '[' . urlencode(base64_encode($attr['domain'] . ' ' . $attr['path'] . ' ' . urlencode($cookieName))) . ']';
  1308. $value = base64_encode($value);
  1309.  
  1310. }
  1311.  
  1312. # Send cookie ...
  1313. if ( $httponly ) {
  1314.  
  1315. # ... with httponly flag
  1316. setcookie($name, $value, $expires, '/', '', $secure, true);
  1317.  
  1318. } else {
  1319.  
  1320. # ... without httponly flag
  1321. setcookie($name, $value, $expires, '/', '', $secure);
  1322.  
  1323. }
  1324.  
  1325. # And log if in debug mode
  1326. if ( DEBUG_MODE ) {
  1327.  
  1328. $this->cookiesReceived[] = array('name' => $cookieName,
  1329. 'value' => $cookieValue,
  1330. 'attributes' => $attr);
  1331.  
  1332. }
  1333.  
  1334. }
  1335.  
  1336. }
  1337.  
  1338.  
  1339. /*****************************************************************
  1340. * Execute the request
  1341. ******************************************************************/
  1342.  
  1343. # Initiate cURL wrapper request object with our cURL options
  1344. $fetch = new Request($toSet);
  1345.  
  1346. # And make the request
  1347. $document = $fetch->go($URL);
  1348.  
  1349.  
  1350. /*****************************************************************
  1351. * Handle aborted transfers
  1352. ******************************************************************/
  1353.  
  1354. if ( $fetch->abort ) {
  1355.  
  1356. switch ( $fetch->abort ) {
  1357.  
  1358. # Do a redirection
  1359. case 'redirect':
  1360.  
  1361. # Proxy the location
  1362. $location = proxyURL($fetch->headers['location'], $flag);
  1363.  
  1364. # Do not redirect in debug mode
  1365. if ( DEBUG_MODE ) {
  1366. $fetch->redirected = '<a href="' . $location . '">' . $fetch->headers['location'] . '</a>';
  1367. break;
  1368. }
  1369.  
  1370. # Go there
  1371. header('Location: ' . $location, true, $fetch->status);
  1372. exit;
  1373.  
  1374.  
  1375. # Send back a 304 Not modified and stop running the script
  1376. case 'not_modified':
  1377. header("HTTP/1.1 304 Not Modified", true, 304);
  1378. exit;
  1379.  
  1380.  
  1381. # 401 Authentication (HTTP authentication hooks not available in all PHP versions
  1382. # so we have to use our method)
  1383. case 'auth_required':
  1384.  
  1385. # Ensure we have some means of authenticating and extract details about the type of authentication
  1386. if ( ! isset($fetch->headers['www-authenticate']) ) {
  1387. break;
  1388. }
  1389.  
  1390. # Realm to display to the user
  1391. $realm = preg_match('#\brealm="([^"]*)"#i', $fetch->headers['www-authenticate'], $tmp) ? $tmp[1] : '';
  1392.  
  1393. # Prevent caching
  1394. sendNoCache();
  1395.  
  1396. # Prepare template variables (session may be closed at this point so send via form)
  1397. $tmp = array('site' => $URL['scheme_host'],
  1398. 'realm' => $realm,
  1399. 'return' => currentURL());
  1400.  
  1401. # Show our form and quit
  1402. echo loadTemplate('authenticate.page', $tmp);
  1403. exit;
  1404.  
  1405.  
  1406. # File request above filesize limit
  1407. case 'filesize_limit':
  1408.  
  1409. # If already sent some of the file, we can't display an error
  1410. # so just stop running
  1411. if ( ! $fetch->parseType ) {
  1412. exit;
  1413. }
  1414. # Send to error page with filesize limit expressed in MB
  1415. error('file_too_large', round($CONFIG['max_filesize']/1024/1024, 3));
  1416. exit;
  1417.  
  1418.  
  1419. # >=400 response code (some sort of HTTP error)
  1420. case 'http_status_error':
  1421.  
  1422. # Provide a friendly message
  1423. $explain = isset($httpErrors[$fetch->status]) ? $httpErrors[$fetch->status] : '';
  1424.  
  1425. # Simply forward the error with details
  1426. error('http_error', $fetch->status, trim(substr($fetch->headers[0], 12)), $explain);
  1427. exit;
  1428.  
  1429.  
  1430. # Unknown (shouldn't happen)
  1431. default:
  1432. error('cURL::$abort (' . $fetch->abort .')');
  1433. }
  1434.  
  1435. }
  1436.  
  1437. # Any cURL errors?
  1438. if ( $fetch->error ) {
  1439.  
  1440. error('curl_error', $fetch->error);
  1441.  
  1442. }
  1443.  
  1444.  
  1445. /*****************************************************************
  1446. * Transfer finished and errors handle. Process the file.
  1447. ******************************************************************/
  1448.  
  1449. # Is this AJAX? If so, don't cache, log or parse.
  1450. # Also, assume ajax if return is VERY short.
  1451. if ( $flag == 'ajax' || ( $fetch->parseType && strlen($document) < 10 ) ) {
  1452.  
  1453. # Print if not already printed
  1454. if ( $fetch->parseType ) {
  1455. echo $document;
  1456. }
  1457.  
  1458. # And exit
  1459. exit;
  1460. }
  1461.  
  1462. # Do we want to parse the file?
  1463. if ( $fetch->parseType ) {
  1464.  
  1465. /*****************************************************************
  1466. * Apply the relevant parsing methods to the document
  1467. ******************************************************************/
  1468.  
  1469. # Decode gzip compressed content
  1470. if (isset($fetch->headers['content-encoding']) && $fetch->headers['content-encoding']=='gzip') {
  1471. if (function_exists('gzinflate')) {
  1472. unset($fetch->headers['content-encoding']);
  1473. $document=gzinflate(substr($document,10,-8));
  1474. }
  1475. }
  1476.  
  1477. # Apply preparsing from plugins
  1478. if ( $foundPlugin && function_exists('preParse') ) {
  1479. $document = preParse($document, $fetch->parseType);
  1480. }
  1481.  
  1482. # Load the main parser
  1483. require GLYPE_ROOT . '/includes/parser.php';
  1484. # Create new instance, passing in the options that affect parsing
  1485. $parser = new parser($options, $jsFlags);
  1486.  
  1487. # Method of parsing depends on $parseType
  1488. switch ( $fetch->parseType ) {
  1489.  
  1490. # HTML document
  1491. case 'html':
  1492.  
  1493. # Do we want to insert our own code into the document?
  1494. $inject =
  1495. $footer =
  1496. $insert = false;
  1497.  
  1498. # Mini-form only if NOT frame or sniffed
  1499. if ( $flag != 'frame' && $fetch->sniff == false ) {
  1500.  
  1501. # Showing the mini-form?
  1502. if ( $options['showForm'] ) {
  1503. $toShow = array();
  1504.  
  1505. # Prepare the options
  1506. foreach ( $CONFIG['options'] as $name => $details ) {
  1507.  
  1508. # Ignore if forced
  1509. if ( ! empty($details['force']) ) {
  1510. continue;
  1511. }
  1512.  
  1513. # Add to array
  1514. $toShow[] = array(
  1515. 'name' => $name,
  1516. 'title' => $details['title'],
  1517. 'checked' => $options[$name] ? ' checked="checked" ' : ''
  1518. );
  1519.  
  1520. }
  1521.  
  1522. # Prepare variables to pass to template
  1523. if ($options['encodePage']) {
  1524. $vars['url'] = ''; # Currently visited URL
  1525. } else {
  1526. $vars['url'] = $URL['href']; # Currently visited URL
  1527. }
  1528. $vars['toShow'] = $toShow; # Options
  1529. $vars['return'] = rawurlencode(currentURL()); # Return URL (for clearcookies) (i.e. current URL proxied)
  1530. $vars['proxy'] = GLYPE_URL; # Base URL for proxy directory
  1531.  
  1532. # Load the template
  1533. $insert = loadTemplate('framedForm.inc', $vars);
  1534. # Wrap in enable/disble override to prevent the overriden functions
  1535. # affecting anything in the mini-form (like ad codes)
  1536. if ( $CONFIG['override_javascript'] ) {
  1537. $insert = '<script type="text/javascript">disableOverride();</script>'
  1538. . $insert
  1539. . '<script type="text/javascript">enableOverride();</script>';
  1540. }
  1541. }
  1542.  
  1543. # And load the footer
  1544. $footer = $CONFIG['footer_include'];
  1545.  
  1546. }
  1547.  
  1548. # Inject javascript unless sniffed
  1549. if ( $fetch->sniff == false ) {
  1550. $inject = true;
  1551. }
  1552.  
  1553. # Run through HTML parser
  1554. $document = $parser->HTMLDocument($document, $insert, $inject, $footer);
  1555.  
  1556. break;
  1557.  
  1558.  
  1559. # CSS file
  1560. case 'css':
  1561.  
  1562. # Run through CSS parser
  1563. $document = $parser->CSS($document);
  1564.  
  1565. break;
  1566.  
  1567.  
  1568. # Javascript file
  1569. case 'javascript':
  1570.  
  1571. # Run through javascript parser
  1572. $document = $parser->JS($document);
  1573.  
  1574. break;
  1575.  
  1576. }
  1577.  
  1578. # Apply postparsing from plugins
  1579. if ( $foundPlugin && function_exists('postParse') ) {
  1580. $document = postParse($document, $fetch->parseType);
  1581. }
  1582.  
  1583. # Send output
  1584. if ( ! DEBUG_MODE ) {
  1585.  
  1586. # Do we want to gzip this? Yes, if all of the following are true:
  1587. # - gzip option enabled
  1588. # - client supports gzip
  1589. # - zlib extension loaded
  1590. # - output compression not automated
  1591. if ( $CONFIG['gzip_return'] && isset($_SERVER['HTTP_ACCEPT_ENCODING']) && strpos($_SERVER['HTTP_ACCEPT_ENCODING'],'gzip') !== false && extension_loaded('zlib') && ! ini_get('zlib.output_compression') ) {
  1592.  
  1593. # Send compressed (using level 3 compression - can be adjusted
  1594. # to give smaller/larger files but will take longer/shorter time!)
  1595. header('Content-Encoding: gzip');
  1596. echo gzencode($document, 3);
  1597.  
  1598. } else {
  1599.  
  1600. # Send uncompressed
  1601. echo $document;
  1602.  
  1603. }
  1604.  
  1605. }
  1606.  
  1607. }
  1608.  
  1609. if ( DEBUG_MODE ) {
  1610. # Just dump the $fetch object in DEBUG_MODE
  1611. $fetch->return = $document;
  1612. echo '<pre>', print_r($fetch, 1), '</pre>';
  1613. }
  1614.  
  1615.  
  1616. /*****************************************************************
  1617. * Log the request
  1618. ******************************************************************/
  1619.  
  1620. # Do we want to log? Check we want to log this type of request.
  1621. if ( $CONFIG['enable_logging'] && ( $CONFIG['log_all'] || $fetch->parseType == 'html' ) ) {
  1622.  
  1623. # Is the log directory writable?
  1624. if ( checkTmpDir($CONFIG['logging_destination'], 'Deny from all') ) {
  1625.  
  1626. # Filename to save as
  1627. $file = $CONFIG['logging_destination'] . '/' . date('Y-m-d') . '.log';
  1628.  
  1629. # Line to write
  1630. $write = str_pad($_SERVER['REMOTE_ADDR'] . ', ' , 17) . date('d/M/Y:H:i:s O') . ', ' . $URL['href'] . "\r\n";
  1631.  
  1632. # Do it
  1633. file_put_contents($file, $write, FILE_APPEND);
  1634.  
  1635. }
  1636.  
  1637. }