Merge pull request #2811 from dionysiosarvanitis/fix/ie11_user_agent

IE11 User Agent support added
diff --git a/.travis.yml b/.travis.yml
index fa9d5e5..27fe3c6 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -4,6 +4,7 @@
   - 5.3
   - 5.4
   - 5.5
+  - hhvm
   
 env:
   - DB=mysql
@@ -22,6 +23,10 @@
 
 script: phpunit --coverage-text --configuration tests/travis/$DB.phpunit.xml
 
+matrix:
+  allow_failures:
+    - php: hhvm
+
 branches:
   only:
     - develop
diff --git a/application/config/config.php b/application/config/config.php
index cd2ca47..5240f6c 100644
--- a/application/config/config.php
+++ b/application/config/config.php
@@ -141,15 +141,18 @@
 | Allowed URL Characters
 |--------------------------------------------------------------------------
 |
-| This lets you specify with a regular expression which characters are permitted
-| within your URLs.  When someone tries to submit a URL with disallowed
-| characters they will get a warning message.
+| This lets you specify which characters are permitted within your URLs.
+| When someone tries to submit a URL with disallowed characters they will
+| get a warning message.
 |
 | As a security measure you are STRONGLY encouraged to restrict URLs to
 | as few characters as possible.  By default only these are allowed: a-z 0-9~%.:_-
 |
 | Leave blank to allow all characters -- but only if you are insane.
 |
+| The configured value is actually a regular expression character group
+| and it will be executed as: ! preg_match('/^[<permitted_uri_chars>]+$/i
+|
 | DO NOT CHANGE THIS UNLESS YOU FULLY UNDERSTAND THE REPERCUSSIONS!!
 |
 */
diff --git a/system/core/Log.php b/system/core/Log.php
index b2327b8..ff3c635 100644
--- a/system/core/Log.php
+++ b/system/core/Log.php
@@ -175,7 +175,7 @@
 			return FALSE;
 		}
 
-		$message .= $level.' '.($level === 'INFO' ? ' -' : '-').' '.date($this->_date_fmt).' --> '.$msg."\n";
+		$message .= $level.' - '.date($this->_date_fmt).' --> '.$msg."\n";
 
 		flock($fp, LOCK_EX);
 		fwrite($fp, $message);
diff --git a/system/core/Router.php b/system/core/Router.php
index cb44a3c..71530ff 100644
--- a/system/core/Router.php
+++ b/system/core/Router.php
@@ -154,16 +154,16 @@
 		{
 			if (isset($_GET[$this->config->item('directory_trigger')]) && is_string($_GET[$this->config->item('directory_trigger')]))
 			{
-				$this->set_directory(trim($this->uri->_filter_uri($_GET[$this->config->item('directory_trigger')])));
+				$this->set_directory(trim($this->uri->filter_uri($_GET[$this->config->item('directory_trigger')])));
 				$segments[] = $this->directory;
 			}
 
-			$this->set_class(trim($this->uri->_filter_uri($_GET[$this->config->item('controller_trigger')])));
+			$this->set_class(trim($this->uri->filter_uri($_GET[$this->config->item('controller_trigger')])));
 			$segments[] = $this->class;
 
 			if ( ! empty($_GET[$this->config->item('function_trigger')]) && is_string($_GET[$this->config->item('function_trigger')]))
 			{
-				$this->set_method(trim($this->uri->_filter_uri($_GET[$this->config->item('function_trigger')])));
+				$this->set_method(trim($this->uri->filter_uri($_GET[$this->config->item('function_trigger')])));
 				$segments[] = $this->method;
 			}
 		}
diff --git a/system/core/URI.php b/system/core/URI.php
index 5e4c80a..3d6d202 100644
--- a/system/core/URI.php
+++ b/system/core/URI.php
@@ -70,6 +70,15 @@
 	public $rsegments =	array();
 
 	/**
+	 * Permitted URI chars
+	 *
+	 * PCRE character group allowed in URI segments
+	 *
+	 * @var	string
+	 */
+	protected $_permitted_uri_chars;
+
+	/**
 	 * Class constructor
 	 *
 	 * Simply globalizes the $RTR object. The front
@@ -81,6 +90,12 @@
 	public function __construct()
 	{
 		$this->config =& load_class('Config', 'core');
+
+		if ($this->config->item('enable_query_strings') !== TRUE OR is_cli())
+		{
+			$this->_permitted_uri_chars = $this->config->item('permitted_uri_chars');
+		}
+
 		log_message('debug', 'URI Class Initialized');
 	}
 
@@ -303,23 +318,19 @@
 	 * @param	string	$str
 	 * @return	string
 	 */
-	public function _filter_uri($str)
+	public function filter_uri($str)
 	{
-		if ($str !== '' && $this->config->item('permitted_uri_chars') != '' && $this->config->item('enable_query_strings') === FALSE)
+		if ( ! empty($str) && ! empty($this->_permitted_uri_chars) && ! preg_match('/^['.$this->_permitted_uri_chars.']+$/i', $str))
 		{
-			// preg_quote() in PHP 5.3 escapes -, so the str_replace() and addition of - to preg_quote() is to maintain backwards
-			// compatibility as many are unaware of how characters in the permitted_uri_chars will be parsed as a regex pattern
-			if ( ! preg_match('|^['.str_replace(array('\\-', '\-'), '-', preg_quote($this->config->item('permitted_uri_chars'), '-')).']+$|i', $str))
-			{
-				show_error('The URI you submitted has disallowed characters.', 400);
-			}
+			show_error('The URI you submitted has disallowed characters.', 400);
 		}
 
 		// Convert programatic characters to entities and return
 		return str_replace(
-					array('$',     '(',     ')',     '%28',   '%29'), // Bad
-					array('&#36;', '&#40;', '&#41;', '&#40;', '&#41;'), // Good
-					$str);
+			array('$',     '(',     ')',     '%28',   '%29'),	// Bad
+			array('&#36;', '&#40;', '&#41;', '&#40;', '&#41;'),	// Good
+			$str
+		);
 	}
 
 	// --------------------------------------------------------------------
@@ -365,7 +376,7 @@
 		foreach (explode('/', preg_replace('|/*(.+?)/*$|', '\\1', $this->uri_string)) as $val)
 		{
 			// Filter segments for security
-			$val = trim($this->_filter_uri($val));
+			$val = trim($this->filter_uri($val));
 
 			if ($val !== '')
 			{
diff --git a/system/libraries/Email.php b/system/libraries/Email.php
index 739b76c..9487ad4 100644
--- a/system/libraries/Email.php
+++ b/system/libraries/Email.php
@@ -710,39 +710,39 @@
 	/**
 	 * Assign file attachments
 	 *
-	 * @param	string	$filename
+	 * @param	string	$file	Can be local path, URL or buffered content
 	 * @param	string	$disposition = 'attachment'
 	 * @param	string	$newname = NULL
 	 * @param	string	$mime = ''
 	 * @return	CI_Email
 	 */
-	public function attach($filename, $disposition = '', $newname = NULL, $mime = '')
+	public function attach($file, $disposition = '', $newname = NULL, $mime = '')
 	{
 		if ($mime === '')
 		{
-			if ( ! file_exists($filename))
+			if (strpos($file, '://') === FALSE && ! file_exists($file))
 			{
-				$this->_set_error_message('lang:email_attachment_missing', $filename);
+				$this->_set_error_message('lang:email_attachment_missing', $file);
 				return FALSE;
 			}
 
-			if ( ! $fp = fopen($filename, FOPEN_READ))
+			if ( ! $fp = @fopen($file, FOPEN_READ))
 			{
-				$this->_set_error_message('lang:email_attachment_unreadable', $filename);
+				$this->_set_error_message('lang:email_attachment_unreadable', $file);
 				return FALSE;
 			}
 
 			$file_content = stream_get_contents($fp);
-			$mime = $this->_mime_types(pathinfo($filename, PATHINFO_EXTENSION));
+			$mime = $this->_mime_types(pathinfo($file, PATHINFO_EXTENSION));
 			fclose($fp);
 		}
 		else
 		{
-			$file_content =& $filename; // buffered file
+			$file_content =& $file; // buffered file
 		}
 
 		$this->_attachments[] = array(
-			'name'		=> array($filename, $newname),
+			'name'		=> array($file, $newname),
 			'disposition'	=> empty($disposition) ? 'attachment' : $disposition,  // Can also be 'inline'  Not sure if it matters
 			'type'		=> $mime,
 			'content'	=> chunk_split(base64_encode($file_content))
@@ -2097,7 +2097,7 @@
 	 */
 	protected function _send_data($data)
 	{
-		if ( ! fwrite($this->_smtp_connect, $data.$this->newline))
+		if (fwrite($this->_smtp_connect, $data.$this->newline) === FALSE)
 		{
 			$this->_set_error_message('lang:email_smtp_data_failure', $data);
 			return FALSE;
diff --git a/system/libraries/Xmlrpc.php b/system/libraries/Xmlrpc.php
index 2fd1259..1f93e69 100644
--- a/system/libraries/Xmlrpc.php
+++ b/system/libraries/Xmlrpc.php
@@ -724,7 +724,7 @@
 			.'Content-Length: '.strlen($msg->payload).$r.$r
 			.$msg->payload;
 
-		if ( ! fwrite($fp, $op, strlen($op)))
+		if (fwrite($fp, $op, strlen($op)) === FALSE)
 		{
 			error_log($this->xmlrpcstr['http_error']);
 			return new XML_RPC_Response(0, $this->xmlrpcerr['http_error'], $this->xmlrpcstr['http_error']);
diff --git a/tests/codeigniter/core/URI_test.php b/tests/codeigniter/core/URI_test.php
index 7fa0e62..99d79bb 100644
--- a/tests/codeigniter/core/URI_test.php
+++ b/tests/codeigniter/core/URI_test.php
@@ -112,11 +112,10 @@
 
 	public function test_filter_uri()
 	{
-		$this->uri->config->set_item('enable_query_strings', FALSE);
-		$this->uri->config->set_item('permitted_uri_chars', 'a-z 0-9~%.:_\-');
+		$this->uri->_set_permitted_uri_chars('a-z 0-9~%.:_\-');
 
 		$str_in = 'abc01239~%.:_-';
-		$str = $this->uri->_filter_uri($str_in);
+		$str = $this->uri->filter_uri($str_in);
 
 		$this->assertEquals($str, $str_in);
 	}
@@ -126,11 +125,9 @@
 	public function test_filter_uri_escaping()
 	{
 		// ensure escaping even if dodgey characters are permitted
+		$this->uri->_set_permitted_uri_chars('a-z 0-9~%.:_\-()$');
 
-		$this->uri->config->set_item('enable_query_strings', FALSE);
-		$this->uri->config->set_item('permitted_uri_chars', 'a-z 0-9~%.:_\-()$');
-
-		$str = $this->uri->_filter_uri('$destroy_app(foo)');
+		$str = $this->uri->filter_uri('$destroy_app(foo)');
 
 		$this->assertEquals($str, '&#36;destroy_app&#40;foo&#41;');
 	}
@@ -142,8 +139,8 @@
 		$this->setExpectedException('RuntimeException');
 
 		$this->uri->config->set_item('enable_query_strings', FALSE);
-		$this->uri->config->set_item('permitted_uri_chars', 'a-z 0-9~%.:_\-');
-		$this->uri->_filter_uri('$this()');
+		$this->uri->_set_permitted_uri_chars('a-z 0-9~%.:_\-');
+		$this->uri->filter_uri('$this()');
 	}
 
 	// --------------------------------------------------------------------
diff --git a/tests/mocks/autoloader.php b/tests/mocks/autoloader.php
index 3d216da..cc0a2e2 100644
--- a/tests/mocks/autoloader.php
+++ b/tests/mocks/autoloader.php
@@ -89,21 +89,7 @@
 
 	if ( ! file_exists($file))
 	{
-		$trace = debug_backtrace();
-
-		if ($trace[2]['function'] === 'class_exists' OR $trace[2]['function'] === 'file_exists')
-		{
-			// If the autoload call came from `class_exists` or `file_exists`,
-			// we skipped and return FALSE
-			return FALSE;
-		}
-		elseif (($autoloader = spl_autoload_functions()) && end($autoloader) !== __FUNCTION__)
-		{
-			// If there was other custom autoloader, passed away
-			return FALSE;
-		}
-
-		throw new InvalidArgumentException("Unable to load {$class}.");
+    return FALSE;
 	}
 
 	include_once($file);
diff --git a/tests/mocks/core/uri.php b/tests/mocks/core/uri.php
index 1107858..96ec5af 100644
--- a/tests/mocks/core/uri.php
+++ b/tests/mocks/core/uri.php
@@ -10,12 +10,23 @@
 		// set predictable config values
 		$test->ci_set_config(array(
 			'index_page'		=> 'index.php',
-			'base_url'			=> 'http://example.com/',
-			'subclass_prefix'	=> 'MY_'
+			'base_url'		=> 'http://example.com/',
+			'subclass_prefix'	=> 'MY_',
+			'enable_query_strings'	=> FALSE,
+			'permitted_uri_chars'	=> 'a-z 0-9~%.:_\-'
 		));
 
 		$this->config = new $cls;
 
+		if ($this->config->item('enable_query_strings') !== TRUE OR is_cli())
+		{
+			$this->_permitted_uri_chars = $this->config->item('permitted_uri_chars');
+		}
+	}
+
+	public function _set_permitted_uri_chars($value)
+	{
+		$this->_permitted_uri_chars = $value;
 	}
 
 }
\ No newline at end of file
diff --git a/user_guide_src/source/changelog.rst b/user_guide_src/source/changelog.rst
index 5da7070..7737852 100644
--- a/user_guide_src/source/changelog.rst
+++ b/user_guide_src/source/changelog.rst
@@ -334,6 +334,7 @@
 
       -  Added a custom filename parameter to ``attach()`` as ``$this->email->attach($filename, $disposition, $newname)``.
       -  Added possibility to send attachment as buffer string in ``attach()`` as ``$this->email->attach($buffer, $disposition, $newname, $mime)``.
+      -  Added possibility to attach remote files by passing a URL.
       -  Added method ``attachment_cid()`` to enable embedding inline attachments into HTML.
       -  Added dsn (delivery status notification) option.
       -  Renamed method ``_set_header()`` to ``set_header()`` and made it public to enable adding custom headers.
@@ -390,6 +391,7 @@
 
    -  :doc:`URI Library <libraries/uri>` changes include:
 
+      -  Renamed method ``_filter_uri()`` to ``filter_uri()`` and removed the ``preg_quote()`` call from it.
       -  Changed private methods to protected so that MY_URI can override them.
       -  Renamed internal method ``_parse_cli_args()`` to ``_parse_argv()``.
       -  Renamed internal method ``_detect_uri()`` to ``_parse_request_uri()``.
diff --git a/user_guide_src/source/libraries/email.rst b/user_guide_src/source/libraries/email.rst
index 8e38003..86f440a 100644
--- a/user_guide_src/source/libraries/email.rst
+++ b/user_guide_src/source/libraries/email.rst
@@ -259,6 +259,10 @@
 
 	$this->email->attach('image.jpg', 'inline');
 
+You can use URL::
+
+	$this->email->attach('http://example.com/filename.pdf');
+
 If you'd like to use a custom file name, you can use the third paramater::
 
 	$this->email->attach('filename.pdf', 'attachment', 'report.pdf');