diff options
-rw-r--r-- | composer.json | 2 | ||||
-rw-r--r-- | system/helpers/url_helper.php | 21 | ||||
-rw-r--r-- | system/libraries/Email.php | 158 | ||||
-rwxr-xr-x | system/libraries/Session/drivers/Session_cookie.php | 47 | ||||
-rwxr-xr-x | system/libraries/Session/drivers/Session_native.php | 20 | ||||
-rw-r--r-- | tests/codeigniter/core/Config_test.php | 197 | ||||
-rw-r--r-- | tests/codeigniter/helpers/captcha_helper_test.php | 10 | ||||
-rw-r--r-- | tests/codeigniter/helpers/cookie_helper_test.php | 59 | ||||
-rw-r--r-- | tests/codeigniter/helpers/download_helper_test.php | 10 | ||||
-rw-r--r-- | tests/codeigniter/helpers/language_helper_test.php | 14 | ||||
-rw-r--r-- | tests/codeigniter/helpers/security_helper_test.php | 64 | ||||
-rw-r--r-- | tests/codeigniter/helpers/url_helper_test.php | 2 | ||||
-rw-r--r-- | tests/codeigniter/libraries/Upload_test.php | 10 | ||||
-rw-r--r-- | tests/phpunit.xml | 7 | ||||
-rw-r--r-- | user_guide_src/source/changelog.rst | 26 | ||||
-rw-r--r-- | user_guide_src/source/libraries/javascript.rst | 2 | ||||
-rw-r--r-- | user_guide_src/source/libraries/output.rst | 2 |
17 files changed, 482 insertions, 169 deletions
diff --git a/composer.json b/composer.json index dc098acc3..7d60020c3 100644 --- a/composer.json +++ b/composer.json @@ -3,6 +3,6 @@ "mikey179/vfsStream": "*" }, "require-dev": { - "EHER/PHPUnit": "*" + "phpunit/phpunit": "*" } }
\ No newline at end of file diff --git a/system/helpers/url_helper.php b/system/helpers/url_helper.php index b1f5eccf1..de5bdec31 100644 --- a/system/helpers/url_helper.php +++ b/system/helpers/url_helper.php @@ -388,40 +388,43 @@ if ( ! function_exists('auto_link')) for ($i = 0, $c = count($matches[0]); $i < $c; $i++) { - if (preg_match('|\.$|', $matches[6][$i])) + if (preg_match('/(\.|\,)$/i', $matches[6][$i], $m)) { - $period = '.'; + $punct = $m[1]; $matches[6][$i] = substr($matches[6][$i], 0, -1); } else { - $period = ''; + $punct = ''; } $str = str_replace($matches[0][$i], $matches[1][$i].'<a href="http'.$matches[4][$i].'://' .$matches[5][$i].$matches[6][$i].'"'.$pop.'>http' .$matches[4][$i].'://'.$matches[5][$i] - .$matches[6][$i].'</a>'.$period, + .$matches[6][$i].'</a>'.$punct, $str); } } - if ($type !== 'url' && preg_match_all('/([a-zA-Z0-9_\.\-\+]+)@([a-zA-Z0-9\-]+)\.([a-zA-Z0-9\-\.]*)/i', $str, $matches)) + if ($type !== 'url' && preg_match_all('/([a-zA-Z0-9_\.\-\+]+)@([a-zA-Z0-9\-]+)\.([a-zA-Z0-9\-\.]+)/i', $str, $matches)) { for ($i = 0, $c = count($matches); $i < $c; $i++) { - if (preg_match('|\.$|', $matches[3][$i])) + if (preg_match('/(\.|\,)$/i', $matches[3][$i], $m)) { - $period = '.'; + $punct = $m[1]; $matches[3][$i] = substr($matches[3][$i], 0, -1); } else { - $period = ''; + $punct = ''; } - $str = str_replace($matches[0][$i], safe_mailto($matches[1][$i].'@'.$matches[2][$i].'.'.$matches[3][$i]).$period, $str); + if (filter_var(($m = $matches[1][$i].'@'.$matches[2][$i].'.'.$matches[3][$i]), FILTER_VALIDATE_EMAIL) !== FALSE) + { + $str = str_replace($matches[0][$i], safe_mailto($m).$punct, $str); + } } } diff --git a/system/libraries/Email.php b/system/libraries/Email.php index 08057f2f7..c1130e915 100644 --- a/system/libraries/Email.php +++ b/system/libraries/Email.php @@ -98,7 +98,7 @@ class CI_Email { */ public function __construct($config = array()) { - $this->charset = strtoupper(config_item('charset')); + $this->charset = config_item('charset'); if (count($config) > 0) { @@ -110,6 +110,8 @@ class CI_Email { $this->_safe_mode = (bool) @ini_get('safe_mode'); } + $this->charset = strtoupper($this->charset); + log_message('debug', 'Email Class Initialized'); } @@ -186,11 +188,11 @@ class CI_Email { /** * Set FROM * - * @param string - * @param string + * @param string From + * @param string Return-Path * @return object */ - public function from($from, $name = '', $return_path = '') + public function from($from, $name = '', $return_path = NULL) { if (preg_match('/\<(.*)\>/', $from, $match)) { @@ -217,16 +219,13 @@ class CI_Email { } else { - $name = $this->_prep_q_encoding($name, TRUE); + $name = $this->_prep_q_encoding($name); } } $this->set_header('From', $name.' <'.$from.'>'); - if( ! $return_path) - { - $return_path = $from; - } + isset($return_path) OR $return_path = $from; $this->set_header('Return-Path', '<'.$return_path.'>'); return $this; @@ -292,16 +291,7 @@ class CI_Email { $this->set_header('To', implode(', ', $to)); } - switch ($this->_get_protocol()) - { - case 'smtp': - $this->_recipients = $to; - break; - case 'sendmail': - case 'mail': - $this->_recipients = implode(', ', $to); - break; - } + $this->_recipients = $to; return $this; } @@ -752,8 +742,8 @@ class CI_Email { /** * Build alternative plain text message * - * This public function provides the raw message for use - * in plain-text headers of HTML-formatted emails. + * Provides the raw message for use in plain-text headers of + * HTML-formatted emails. * If the user hasn't specified his own alternative message * it creates one by stripping the HTML * @@ -761,9 +751,11 @@ class CI_Email { */ protected function _get_alt_message() { - if ($this->alt_message !== '') + if ( ! empty($this->alt_message)) { - return $this->word_wrap($this->alt_message, '76'); + return ($this->wordwrap) + ? $this->word_wrap($this->alt_message, 76) + : $this->alt_message; } $body = preg_match('/\<body.*?\>(.*)\<\/body\>/si', $this->_body, $match) ? $match[1] : $this->_body; @@ -774,7 +766,9 @@ class CI_Email { $body = str_replace(str_repeat("\n", $i), "\n\n", $body); } - return $this->word_wrap($body, 76); + return ($this->wordwrap) + ? $this->word_wrap($body, 76) + : $body; } // -------------------------------------------------------------------- @@ -783,15 +777,15 @@ class CI_Email { * Word Wrap * * @param string - * @param int + * @param int line-length limit * @return string */ - public function word_wrap($str, $charlim = '') + public function word_wrap($str, $charlim = NULL) { - // Se the character limit - if ($charlim === '') + // Set the character limit, if not already present + if (empty($charlim)) { - $charlim = ($this->wrapchars === '') ? 76 : $this->wrapchars; + $charlim = empty($this->wrapchars) ? 76 : $this->wrapchars; } // Reduce multiple spaces @@ -1105,6 +1099,10 @@ class CI_Email { */ protected function _prep_quoted_printable($str) { + // We are intentionally wrapping so mail servers will encode characters + // properly and MUAs will behave, so {unwrap} must go! + $str = str_replace(array('{unwrap}', '{/unwrap}'), '', $str); + // RFC 2045 specifies CRLF as "\r\n". // However, many developers choose to override that and violate // the RFC rules due to (apparently) a bug in MS Exchange, @@ -1130,10 +1128,6 @@ class CI_Email { $str = str_replace(array("\r\n", "\r"), "\n", $str); } - // We are intentionally wrapping so mail servers will encode characters - // properly and MUAs will behave, so {unwrap} must go! - $str = str_replace(array('{unwrap}', '{/unwrap}'), '', $str); - $escape = '='; $output = ''; @@ -1186,66 +1180,75 @@ class CI_Email { /** * Prep Q Encoding * - * Performs "Q Encoding" on a string for use in email headers. It's related - * but not identical to quoted-printable, so it has its own method + * Performs "Q Encoding" on a string for use in email headers. + * It's related but not identical to quoted-printable, so it has its + * own method. * * @param string - * @param bool set to TRUE for processing From: headers * @return string */ - protected function _prep_q_encoding($str, $from = FALSE) + protected function _prep_q_encoding($str) { - $str = str_replace(array("\r", "\n"), array('', ''), $str); - - // Line length must not exceed 76 characters, so we adjust for - // a space, 7 extra characters =??Q??=, and the charset that we will add to each line - $limit = 75 - 7 - strlen($this->charset); + $str = str_replace(array("\r", "\n"), '', $str); - // these special characters must be converted too - $convert = array('_', '=', '?'); - - if ($from === TRUE) + if ($this->charset === 'UTF-8') { - $convert[] = ','; - $convert[] = ';'; + if (MB_ENABLED === TRUE) + { + return mb_encode_mimeheader($str, $this->charset, 'Q', $this->crlf); + } + elseif (extension_loaded('iconv')) + { + $output = @iconv_mime_encode('', $str, + array( + 'scheme' => 'Q', + 'line-length' => 76, + 'input-charset' => $this->charset, + 'output-charset' => $this->charset, + 'line-break-chars' => $this->crlf + ) + ); + + // There are reports that iconv_mime_encode() might fail and return FALSE + if ($output !== FALSE) + { + // iconv_mime_encode() will always put a header field name. + // We've passed it an empty one, but it still prepends our + // encoded string with ': ', so we need to strip it. + return substr($output, 2); + } + + $chars = iconv_strlen($str, 'UTF-8'); + } } - $output = ''; - $temp = ''; + // We might already have this set for UTF-8 + isset($chars) OR $chars = strlen($str); - for ($i = 0, $length = strlen($str); $i < $length; $i++) + $output = '=?'.$this->charset.'?Q?'; + for ($i = 0, $length = strlen($output), $iconv = extension_loaded('iconv'); $i < $chars; $i++) { - // Grab the next character - $char = $str[$i]; - $ascii = ord($char); + $chr = ($this->charset === 'UTF-8' && $iconv === TRUE) + ? '='.implode('=', str_split(strtoupper(bin2hex(iconv_substr($str, $i, 1, $this->charset))), 2)) + : '='.strtoupper(bin2hex($str[$i])); - // convert ALL non-printable ASCII characters and our specials - if ($ascii < 32 OR $ascii > 126 OR in_array($char, $convert)) + // RFC 2045 sets a limit of 76 characters per line. + // We'll append ?= to the end of each line though. + if ($length + ($l = strlen($chr)) > 74) { - $char = '='.dechex($ascii); + $output .= '?='.$this->crlf // EOL + .' =?'.$this->charset.'?Q?'.$chr; // New line + $length = 6 + strlen($this->charset) + $l; // Reset the length for the new line } - - // handle regular spaces a bit more compactly than =20 - if ($ascii === 32) - { - $char = '_'; - } - - // If we're at the character limit, add the line to the output, - // reset our temp variable, and keep on chuggin' - if ((strlen($temp) + strlen($char)) >= $limit) + else { - $output .= $temp.$this->crlf; - $temp = ''; + $output .= $chr; + $length += $l; } - - // Add the character to our temporary line - $temp .= $char; } - // wrap each line with the shebang, charset, and transfer encoding - // the preceding space on successive lines is required for header "folding" - return trim(preg_replace('/^(.*?)(\r*)$/m', ' =?'.$this->charset.'?Q?$1?=$2', $output.$temp)); + // End the header + return $output.'?='; } // -------------------------------------------------------------------- @@ -1408,6 +1411,11 @@ class CI_Email { */ protected function _send_with_mail() { + if (is_array($this->_recipients)) + { + $this->_recipients = implode(', ', $this->_recipients); + } + if ($this->_safe_mode === TRUE) { return mail($this->_recipients, $this->_subject, $this->_finalbody, $this->_header_str); diff --git a/system/libraries/Session/drivers/Session_cookie.php b/system/libraries/Session/drivers/Session_cookie.php index 5bb1f7aa6..8617aec2d 100755 --- a/system/libraries/Session/drivers/Session_cookie.php +++ b/system/libraries/Session/drivers/Session_cookie.php @@ -308,7 +308,7 @@ class CI_Session_cookie extends CI_Session_driver { } // Kill the cookie - $this->_setcookie($this->sess_cookie_name, addslashes(serialize(array())), ($this->now - 31500000), + $this->_setcookie($this->sess_cookie_name, '', ($this->now - 31500000), $this->cookie_path, $this->cookie_domain, 0); // Kill session data @@ -372,27 +372,31 @@ class CI_Session_cookie extends CI_Session_driver { return FALSE; } + $len = strlen($session) - 40; + + if ($len < 0) + { + log_message('debug', 'The session cookie was not signed.'); + return FALSE; + } + + // Check cookie authentication + $hmac = substr($session, $len); + $session = substr($session, 0, $len); + + if ($hmac !== hash_hmac('sha1', $session, $this->encryption_key)) + { + log_message('error', 'The session cookie data did not match what was expected.'); + $this->sess_destroy(); + return FALSE; + } + // Check for encryption if ($this->sess_encrypt_cookie === TRUE) { // Decrypt the cookie data $session = $this->CI->encrypt->decode($session); } - else - { - // Encryption was not used, so we need to check the md5 hash in the last 32 chars - $len = strlen($session)-32; - $hash = substr($session, $len); - $session = substr($session, 0, $len); - - // Does the md5 hash match? This is to prevent manipulation of session data in userspace - if ($hash !== md5($session.$this->encryption_key)) - { - log_message('error', 'The session cookie data did not match what was expected. This could be a possible hacking attempt.'); - $this->sess_destroy(); - return FALSE; - } - } // Unserialize the session array $session = $this->_unserialize($session); @@ -658,10 +662,13 @@ class CI_Session_cookie extends CI_Session_driver { // Serialize the userdata for the cookie $cookie_data = $this->_serialize($cookie_data); - $cookie_data = ($this->sess_encrypt_cookie === TRUE) - ? $this->CI->encrypt->encode($cookie_data) - // if encryption is not used, we provide an md5 hash to prevent userside tampering - : $cookie_data.md5($cookie_data.$this->encryption_key); + if ($this->sess_encrypt_cookie === TRUE) + { + $cookie_data = $this->CI->encrypt->encode($cookie_data); + } + + // Require message authentication + $cookie_data .= hash_hmac('sha1', $cookie_data, $this->encryption_key); $expire = ($this->sess_expire_on_close === TRUE) ? 0 : $this->sess_expiration + time(); diff --git a/system/libraries/Session/drivers/Session_native.php b/system/libraries/Session/drivers/Session_native.php index 6529d4c36..da744f39b 100755 --- a/system/libraries/Session/drivers/Session_native.php +++ b/system/libraries/Session/drivers/Session_native.php @@ -55,7 +55,9 @@ class CI_Session_native extends CI_Session_driver { 'sess_time_to_update', 'cookie_prefix', 'cookie_path', - 'cookie_domain' + 'cookie_domain', + 'cookie_secure', + 'cookie_httponly' ); foreach ($prefs as $key) @@ -82,6 +84,9 @@ class CI_Session_native extends CI_Session_driver { $expire = 7200; $path = '/'; $domain = ''; + $secure = (bool) $config['cookie_secure']; + $http_only = (bool) $config['cookie_httponly']; + if ($config['sess_expiration'] !== FALSE) { // Default to 2 years if expiration is "0" @@ -99,7 +104,8 @@ class CI_Session_native extends CI_Session_driver { // Use specified domain $domain = $config['cookie_domain']; } - session_set_cookie_params($config['sess_expire_on_close'] ? 0 : $expire, $path, $domain); + + session_set_cookie_params($config['sess_expire_on_close'] ? 0 : $expire, $path, $domain, $secure, $http_only); // Start session session_start(); @@ -137,8 +143,12 @@ class CI_Session_native extends CI_Session_driver { if ($config['sess_time_to_update'] && isset($_SESSION['last_activity']) && ($_SESSION['last_activity'] + $config['sess_time_to_update']) < $now) { - // Regenerate ID, but don't destroy session - $this->sess_regenerate(FALSE); + // Changing the session ID amidst a series of AJAX calls causes problems + if( ! $this->CI->input->is_ajax_request()) + { + // Regenerate ID, but don't destroy session + $this->sess_regenerate(FALSE); + } } // Set activity time @@ -189,7 +199,7 @@ class CI_Session_native extends CI_Session_driver { { // Clear session cookie $params = session_get_cookie_params(); - setcookie($name, '', time() - 42000, $params['path'], $params['domain']); + setcookie($name, '', time() - 42000, $params['path'], $params['domain'], $params['secure'], $params['httponly']); unset($_COOKIE[$name]); } session_destroy(); diff --git a/tests/codeigniter/core/Config_test.php b/tests/codeigniter/core/Config_test.php index 80e0862ff..d652a625e 100644 --- a/tests/codeigniter/core/Config_test.php +++ b/tests/codeigniter/core/Config_test.php @@ -7,11 +7,12 @@ class Config_test extends CI_TestCase { $cls =& $this->ci_core_class('cfg'); // set predictable config values - $this->ci_set_config(array( + $this->cfg = array( 'index_page' => 'index.php', 'base_url' => 'http://example.com/', 'subclass_prefix' => 'MY_' - )); + ); + $this->ci_set_config($this->cfg); $this->config = new $cls; } @@ -20,7 +21,7 @@ class Config_test extends CI_TestCase { public function test_item() { - $this->assertEquals('http://example.com/', $this->config->item('base_url')); + $this->assertEquals($this->cfg['base_url'], $this->config->item('base_url')); // Bad Config value $this->assertFalse($this->config->item('no_good_item')); @@ -48,36 +49,103 @@ class Config_test extends CI_TestCase { // Bad Config value $this->assertFalse($this->config->slash_item('no_good_item')); - $this->assertEquals('http://example.com/', $this->config->slash_item('base_url')); + $this->assertEquals($this->cfg['base_url'], $this->config->slash_item('base_url')); - $this->assertEquals('MY_/', $this->config->slash_item('subclass_prefix')); + $this->assertEquals($this->cfg['subclass_prefix'].'/', $this->config->slash_item('subclass_prefix')); } // -------------------------------------------------------------------- - public function test_site_url() + public function test_base_url() { - $this->assertEquals('http://example.com/index.php', $this->config->site_url()); + // Test regular base URL + $base_url = $this->cfg['base_url']; + $this->assertEquals($base_url, $this->config->base_url()); + + // Test with URI + $uri = 'test'; + $this->assertEquals($base_url.$uri, $this->config->base_url($uri)); + + // Clear base_url + $this->ci_set_config('base_url', ''); + + // Rerun constructor + $cls =& $this->ci_core_class('cfg'); + $this->config = new $cls; - $base_url = $this->config->item('base_url'); + // Test default base + $this->assertEquals('http://localhost/', $this->config->base_url()); + // Capture server vars + $old_host = isset($_SERVER['HTTP_HOST']) ? $_SERVER['HTTP_HOST'] : NULL; + $old_script = isset($_SERVER['SCRIPT_NAME']) ? $_SERVER['SCRIPT_NAME'] : NULL; + $old_https = isset($_SERVER['HTTPS']) ? $_SERVER['HTTPS'] : NULL; + + // Setup server vars for detection + $host = 'test.com'; + $path = '/path/'; + $script = 'base_test.php'; + $_SERVER['HTTP_HOST'] = $host; + $_SERVER['SCRIPT_NAME'] = $path.$script; + + // Rerun constructor + $this->config = new $cls; + + // Test plain detected + $this->assertEquals('http://'.$host.$path, $this->config->base_url()); + + // Rerun constructor + $_SERVER['HTTPS'] = 'on'; + $this->config = new $cls; + + // Test secure detected + $this->assertEquals('https://'.$host.$path, $this->config->base_url()); + + // Restore server vars + if ($old_host === NULL) unset($_SERVER['HTTP_HOST']); + else $_SERVER['HTTP_HOST'] = $old_host; + if ($old_script === NULL) unset($_SERVER['SCRIPT_NAME']); + else $_SERVER['SCRIPT_NAME'] = $old_script; + if ($old_https === NULL) unset($_SERVER['HTTPS']); + else $_SERVER['HTTPS'] = $old_https; + } + + // -------------------------------------------------------------------- + + public function test_site_url() + { + $base_url = $this->cfg['base_url']; + $index_page = $this->cfg['index_page']; + $this->assertEquals($base_url.$index_page, $this->config->site_url()); + + $old_base = $this->config->item('base_url'); $this->config->set_item('base_url', ''); $q_string = $this->config->item('enable_query_strings'); - $this->config->set_item('enable_query_strings', FALSE); - $this->assertEquals('index.php/test', $this->config->site_url('test')); - $this->assertEquals('index.php/test/1', $this->config->site_url(array('test', '1'))); + $uri= 'test'; + $uri2 = '1'; + $this->assertEquals($index_page.'/'.$uri, $this->config->site_url($uri)); + $this->assertEquals($index_page.'/'.$uri.'/'.$uri2, $this->config->site_url(array($uri, $uri2))); + + $suffix = 'ing'; + $this->config->set_item('url_suffix', $suffix); + + $arg = 'pass'; + $this->assertEquals($index_page.'/'.$uri.$suffix, $this->config->site_url($uri)); + $this->assertEquals($index_page.'/'.$uri.$suffix.'?'.$arg, $this->config->site_url($uri.'?'.$arg)); + + $this->config->set_item('url_suffix', FALSE); $this->config->set_item('enable_query_strings', TRUE); - $this->assertEquals('index.php?test', $this->config->site_url('test')); - $this->assertEquals('index.php?0=test&1=1', $this->config->site_url(array('test', '1'))); + $this->assertEquals($index_page.'?'.$uri, $this->config->site_url($uri)); + $this->assertEquals($index_page.'?0='.$uri.'&1='.$uri2, $this->config->site_url(array($uri, $uri2))); - $this->config->set_item('base_url', $base_url); + $this->config->set_item('base_url', $old_base); - $this->assertEquals('http://example.com/index.php?test', $this->config->site_url('test')); + $this->assertEquals($base_url.$index_page.'?'.$uri, $this->config->site_url($uri)); // back to home base $this->config->set_item('enable_query_strings', $q_string); @@ -87,49 +155,100 @@ class Config_test extends CI_TestCase { public function test_system_url() { - $this->assertEquals('http://example.com/system/', $this->config->system_url()); + $this->assertEquals($this->cfg['base_url'].'system/', $this->config->system_url()); } // -------------------------------------------------------------------- public function test_load() { - // Create config files in VFS - $file1 = 'test.php'; - $file2 = 'secttest'; - $key1 = 'testconfig'; - $val1 = 'my_value'; - $cfg1 = array( - $key1 => $val1 - ); - $cfg2 = array( + // Test regular load + $file = 'test.php'; + $key = 'testconfig'; + $val = 'my_value'; + $cfg = array($key => $val); + $this->ci_vfs_create($file, '<?php $config = '.var_export($cfg, TRUE).';', $this->ci_app_root, 'config'); + $this->assertTrue($this->config->load($file)); + $this->assertEquals($val, $this->config->item($key)); + + // Test reload - value should not change + $val2 = 'new_value'; + $cfg = array($key => $val2); + $this->ci_vfs_create($file, '<?php $config = '.var_export($cfg, TRUE).';', $this->ci_app_root, 'config'); + $this->assertTrue($this->config->load($file)); + $this->assertEquals($val, $this->config->item($key)); + + // Test section load + $file = 'secttest'; + $cfg = array( 'one' => 'prime', 'two' => 2, 'three' => true ); - $this->ci_vfs_create(array( - $file1 => '<?php $config = '.var_export($cfg1, TRUE).';', - $file2.'.php' => '<?php $config = '.var_export($cfg2, TRUE).';' - ), '', $this->ci_app_root, 'config'); + $this->ci_vfs_create($file.'.php', '<?php $config = '.var_export($cfg, TRUE).';', $this->ci_app_root, 'config'); + $this->assertTrue($this->config->load($file, TRUE)); + $this->assertEquals($cfg, $this->config->item($file)); - // Test regular load - $this->assertTrue($this->config->load($file1)); - $this->assertEquals($val1, $this->config->item($key1)); + // Test section merge + $cfg2 = array( + 'three' => 'tres', + 'number' => 42, + 'letter' => 'Z' + ); + $pkg_dir = 'package'; + $this->ci_vfs_create($file.'.php', '<?php $config = '.var_export($cfg2, TRUE).';', $this->ci_app_root, + array($pkg_dir, 'config')); + array_push($this->config->_config_paths, $this->ci_vfs_path($pkg_dir.'/', APPPATH)); + $this->assertTrue($this->config->load($file, TRUE)); + $this->assertEquals(array_merge($cfg, $cfg2), $this->config->item($file)); + array_pop($this->config->_config_paths); + + // Test graceful fail of invalid file + $file = 'badfile'; + $this->ci_vfs_create($file, '', $this->ci_app_root, 'config'); + $this->assertFalse($this->config->load($file, FALSE, TRUE)); + + // Test regular fail of invalid file + $this->setExpectedException( + 'RuntimeException', + 'CI Error: Your '.$this->ci_vfs_path('config/'.$file.'.php', APPPATH). + ' file does not appear to contain a valid configuration array.' + ); + $this->assertNull($this->config->load($file)); + } - // Test section load - $this->assertTrue($this->config->load($file2, TRUE)); - $this->assertEquals($cfg2, $this->config->item($file2)); + // -------------------------------------------------------------------- - // Test graceful fail + public function test_load_nonexistent() + { + // Test graceful fail of nonexistent file $this->assertFalse($this->config->load('not_config_file', FALSE, TRUE)); // Test regular fail - $file3 = 'absentia'; + $file = 'absentia'; $this->setExpectedException( 'RuntimeException', - 'CI Error: The configuration file '.$file3.'.php does not exist.' + 'CI Error: The configuration file '.$file.'.php does not exist.' + ); + $this->assertNull($this->config->load($file)); + } + + // -------------------------------------------------------------------- + + public function test_assign_to_config() + { + $key1 = 'test'; + $key2 = '1'; + $val1 = 'foo'; + $val2 = 'bar'; + $cfg = array( + $key1 => $val1, + $key2 => $val2 ); - $this->assertNull($this->config->load($file3)); + + $this->assertNull($this->config->_assign_to_config($cfg)); + $this->assertEquals($val1, $this->config->item($key1)); + $this->assertEquals($val2, $this->config->item($key2)); } }
\ No newline at end of file diff --git a/tests/codeigniter/helpers/captcha_helper_test.php b/tests/codeigniter/helpers/captcha_helper_test.php new file mode 100644 index 000000000..fc86305e3 --- /dev/null +++ b/tests/codeigniter/helpers/captcha_helper_test.php @@ -0,0 +1,10 @@ +<?php + +class Captcha_helper_test extends CI_TestCase { + + public function test_create_captcha() + { + $this->markTestSkipped('Cant easily test'); + } + +}
\ No newline at end of file diff --git a/tests/codeigniter/helpers/cookie_helper_test.php b/tests/codeigniter/helpers/cookie_helper_test.php new file mode 100644 index 000000000..fba68f20f --- /dev/null +++ b/tests/codeigniter/helpers/cookie_helper_test.php @@ -0,0 +1,59 @@ +<?php + +class Cookie_helper_test extends CI_TestCase { + + public function set_up() + { + $this->helper('cookie'); + } + + // ------------------------------------------------------------------------ + + function test_set_cookie() + { + /*$input_cls = $this->ci_core_class('input'); + $this->ci_instance_var('input', new $input_cls); + + $this->assertTrue(set_cookie( + 'my_cookie', + 'foobar' + ));*/ + + $this->markTestSkipped('Need to find a way to overcome a headers already set exception'); + } + + // ------------------------------------------------------------------------ + + function test_get_cookie() + { + $_COOKIE['foo'] = 'bar'; + + $security = new Mock_Core_Security(); + $utf8 = new Mock_Core_Utf8(); + $input_cls = $this->ci_core_class('input'); + $this->ci_instance_var('input', new Mock_Core_Input($security, $utf8)); + + $this->assertEquals('bar', get_cookie('foo', FALSE)); + $this->assertEquals('bar', get_cookie('foo', TRUE)); + + $_COOKIE['bar'] = "Hello, i try to <script>alert('Hack');</script> your site"; + + $this->assertEquals("Hello, i try to [removed]alert('Hack');[removed] your site", get_cookie('bar', TRUE)); + $this->assertEquals("Hello, i try to <script>alert('Hack');</script> your site", get_cookie('bar', FALSE)); + } + + // ------------------------------------------------------------------------ + + function test_delete_cookie() + { + /*$input_cls = $this->ci_core_class('input'); + $this->ci_instance_var('input', new $input_cls); + + $this->assertTrue(delete_cookie( + 'my_cookie' + ));*/ + + $this->markTestSkipped('Need to find a way to overcome a headers already set exception'); + } + +}
\ No newline at end of file diff --git a/tests/codeigniter/helpers/download_helper_test.php b/tests/codeigniter/helpers/download_helper_test.php new file mode 100644 index 000000000..d2b42e46b --- /dev/null +++ b/tests/codeigniter/helpers/download_helper_test.php @@ -0,0 +1,10 @@ +<?php + +class Download_helper_test extends CI_TestCase { + + public function test_force_download() + { + $this->markTestSkipped('Cant easily test'); + } + +}
\ No newline at end of file diff --git a/tests/codeigniter/helpers/language_helper_test.php b/tests/codeigniter/helpers/language_helper_test.php new file mode 100644 index 000000000..06932b9fd --- /dev/null +++ b/tests/codeigniter/helpers/language_helper_test.php @@ -0,0 +1,14 @@ +<?php + +class Language_helper_test extends CI_TestCase { + + public function test_lang() + { + $this->helper('language'); + $this->ci_instance_var('lang', new Mock_Core_Lang()); + + $this->assertFalse(lang(1)); + $this->assertEquals('<label for="foo"></label>', lang(1, 'foo')); + } + +}
\ No newline at end of file diff --git a/tests/codeigniter/helpers/security_helper_test.php b/tests/codeigniter/helpers/security_helper_test.php new file mode 100644 index 000000000..effd3ec02 --- /dev/null +++ b/tests/codeigniter/helpers/security_helper_test.php @@ -0,0 +1,64 @@ +<?php + +class Security_helper_tests extends CI_TestCase { + + function setUp() + { + $this->helper('security'); + $obj = new stdClass; + $obj->security = new Mock_Core_Security(); + $this->ci_instance($obj); + } + + function test_xss_clean() + { + $this->assertEquals('foo', xss_clean('foo')); + + $this->assertEquals("Hello, i try to [removed]alert('Hack');[removed] your site", xss_clean("Hello, i try to <script>alert('Hack');</script> your site")); + } + + function test_sanitize_filename() + { + $this->assertEquals('hello.doc', sanitize_filename('hello.doc')); + + $filename = './<!--foo-->'; + $this->assertEquals('foo', sanitize_filename($filename)); + } + + function test_do_hash() + { + $md5 = md5('foo'); + $sha1 = sha1('foo'); + + $algos = hash_algos(); + $algo_results = array(); + foreach ($algos as $k => $v) + { + $algo_results[$v] = hash($v, 'foo'); + } + + $this->assertEquals($sha1, do_hash('foo')); + $this->assertEquals($sha1, do_hash('foo', 'sha1')); + $this->assertEquals($md5, do_hash('foo', 'md5')); + $this->assertEquals($md5, do_hash('foo', 'foobar')); + + // Test each algorithm available to PHP + foreach ($algo_results as $algo => $result) + { + $this->assertEquals($result, do_hash('foo', $algo)); + } + } + + function test_strip_image_tags() + { + $this->assertEquals('http://example.com/spacer.gif', strip_image_tags('http://example.com/spacer.gif')); + + $this->assertEquals('http://example.com/spacer.gif', strip_image_tags('<img src="http://example.com/spacer.gif" alt="Who needs CSS when you have a spacer.gif?" />')); + } + + function test_encode_php_tags() + { + $this->assertEquals('<? echo $foo; ?>', encode_php_tags('<? echo $foo; ?>')); + } + +}
\ No newline at end of file diff --git a/tests/codeigniter/helpers/url_helper_test.php b/tests/codeigniter/helpers/url_helper_test.php index c81c5f1b8..5fc364238 100644 --- a/tests/codeigniter/helpers/url_helper_test.php +++ b/tests/codeigniter/helpers/url_helper_test.php @@ -51,6 +51,8 @@ class Url_helper_test extends CI_TestCase { 'www.codeigniter.com test' => '<a href="http://www.codeigniter.com">http://www.codeigniter.com</a> test', 'This is my noreply@codeigniter.com test' => 'This is my noreply@codeigniter.com test', '<br />www.google.com' => '<br /><a href="http://www.google.com">http://www.google.com</a>', + 'Download CodeIgniter at www.codeigniter.com. Period test.' => 'Download CodeIgniter at <a href="http://www.codeigniter.com">http://www.codeigniter.com</a>. Period test.', + 'Download CodeIgniter at www.codeigniter.com, comma test' => 'Download CodeIgniter at <a href="http://www.codeigniter.com">http://www.codeigniter.com</a>, comma test' ); foreach ($strings as $in => $out) diff --git a/tests/codeigniter/libraries/Upload_test.php b/tests/codeigniter/libraries/Upload_test.php index 827942773..546cebc59 100644 --- a/tests/codeigniter/libraries/Upload_test.php +++ b/tests/codeigniter/libraries/Upload_test.php @@ -11,9 +11,9 @@ class Upload_test extends CI_TestCase { $this->upload = $ci->upload; } - function test_do_upload() + function test_do_upload() { - $this->markTestIncomplete('We can\'t really test this at the moment because of the call to `is_uploaded_file` in do_upload which isn\'t supported by vfsStream'); + $this->markTestSkipped('We can\'t really test this at the moment because of the call to `is_uploaded_file` in do_upload which isn\'t supported by vfsStream'); } function test_data() @@ -72,7 +72,7 @@ class Upload_test extends CI_TestCase { { $this->upload->set_max_filesize(100); $this->assertEquals(100, $this->upload->max_size); - } + } function test_set_max_filename() { @@ -84,7 +84,7 @@ class Upload_test extends CI_TestCase { { $this->upload->set_max_width(100); $this->assertEquals(100, $this->upload->max_width); - } + } function test_set_max_height() { @@ -178,7 +178,7 @@ class Upload_test extends CI_TestCase { $this->upload->file_type = 'image/gif'; $this->upload->file_temp = realpath(PROJECT_BASE.'tests/mocks/uploads/ci_logo.gif'); - $this->upload->max_width = 10; + $this->upload->max_width = 10; $this->assertFalse($this->upload->is_allowed_dimensions()); $this->upload->max_width = 170; diff --git a/tests/phpunit.xml b/tests/phpunit.xml index 56cb8841c..96c3af9bb 100644 --- a/tests/phpunit.xml +++ b/tests/phpunit.xml @@ -1,6 +1,6 @@ <?xml version="1.0" encoding="UTF-8"?> -<phpunit +<phpunit bootstrap="./Bootstrap.php" colors="true" convertNoticesToExceptions="true" @@ -16,10 +16,11 @@ <directory suffix="test.php">./codeigniter/libraries</directory> </testsuite> </testsuites> - <filters> + <filter> <blacklist> <directory suffix=".php">PEAR_INSTALL_DIR</directory> <directory suffix=".php">PHP_LIBDIR</directory> + <directory suffix=".php">../vendor</directory> </blacklist> - </filters> + </filter> </phpunit>
\ No newline at end of file diff --git a/user_guide_src/source/changelog.rst b/user_guide_src/source/changelog.rst index f31781d5c..4ee382fce 100644 --- a/user_guide_src/source/changelog.rst +++ b/user_guide_src/source/changelog.rst @@ -65,7 +65,7 @@ Release Date: Not Released - ``create_captcha()`` accepts additional colors parameter, allowing for color customization. - :doc:`URL Helper <helpers/url_helper>` changes include: - ``url_title()`` will now trim extra dashes from beginning and end. - - ``anchor_popup()`` will now fill the "href" attribute with the URL and its JS code will return false instead. + - ``anchor_popup()`` will now fill the *href* attribute with the URL and its JS code will return FALSE instead. - Added JS window name support to ``anchor_popup()`` function. - Added support (auto-detection) for HTTP/1.1 response code 303 in ``redirect()``. - "auto" method in ``redirect()`` now chooses the "refresh" method only on IIS servers, instead of all servers on Windows. @@ -153,12 +153,14 @@ Release Date: Not Released - :doc:`Session Library <libraries/sessions>` changes include: - Library changed to :doc:`Driver <general/drivers>` with classic Cookie driver as default. - - Added Native PHP Session driver to work with $_SESSION. - - Custom session drivers can be added anywhere in package paths and loaded with Session library. - - Session drivers interchangeable on the fly. - - New tempdata feature allows setting user data items with an expiration time. - - Added default $config['sess_driver'] and $config['sess_valid_drivers'] items to config.php file. - - Cookie driver now respects php.ini's session.gc_probability and session.gc_divisor + - Added Native PHP Session driver to work with ``$_SESSION``. + - Custom drivers can be added anywhere in package paths and be loaded with the library. + - Drivers interchangeable on the fly. + - New **tempdata** feature allows setting user data items with an expiration time. + - Added default ``$config['sess_driver']`` and ``$config['sess_valid_drivers']`` items to *config.php* file. + - Cookie driver now respects php.ini's *session.gc_probability* and *session.gc_divisor* settings. + - Cookie driver now uses HMAC authentication instead of the simple md5 checksum. + - The Cookie driver now also checks authentication on encrypted session data. - Changed the Cookie driver to select only one row when using database sessions. - Cookie driver now only writes to database at end of request when using database. - Cookie driver now uses PHP functions for faster array manipulation when using database. @@ -204,6 +206,7 @@ Release Date: Not Released - Internal method ``_prep_quoted_printable()`` will now utilize the native ``quoted_printable_encode()``, ``imap_8bit()`` functions (if available) when CRLF is set to "\r\n". - Default charset now relies on the global ``$config['charset']`` setting. - Removed unused protected method ``_get_ip()`` (:doc:`Input Library <libraries/input>`'s ``ip_address()`` should be used anyway). + - Internal method ``_prep_q_encoding()`` now utilizes PHP's *mbstring* and *iconv* extensions (when available) and no longer has a second (``$from``) argument. - :doc:`Pagination Library <libraries/pagination>` changes include: - Added support for the anchor "rel" attribute. - Added support for setting custom attributes. @@ -341,7 +344,7 @@ Bug fixes for 3.0 - Fixed a bug (#318) - :doc:`Profiling <general/profiling>` setting *query_toggle_count* was not settable as described in the manual. - Fixed a bug (#938) - :doc:`Config Library <libraries/config>` method ``site_url()`` added a question mark to the URL string when query strings are enabled even if it already existed. - Fixed a bug (#999) - :doc:`Config Library <libraries/config>` method ``site_url()`` always appended ``$config['url_suffix']`` to the end of the URL string, regardless of whether a query string exists in it. -- Fixed a bug where :doc:`URL Helper <helpers/url_helper>` function anchor_popup() ignored the attributes argument if it is not an array. +- Fixed a bug where :doc:`URL Helper <helpers/url_helper>` function ``anchor_popup()`` ignored the attributes argument if it is not an array. - Fixed a bug (#1328) - :doc:`Form Validation Library <libraries/form_validation>` didn't properly check the type of the form fields before processing them. - Fixed a bug (#79) - :doc:`Form Validation Library <libraries/form_validation>` didn't properly validate array fields that use associative keys or have custom indexes. - Fixed a bug (#427) - :doc:`Form Validation Library <libraries/form_validation>` method ``strip_image_tags()`` was an alias to a non-existent method. @@ -362,7 +365,10 @@ Bug fixes for 3.0 - Fixed a bug (#1257) - :doc:`Query Builder <database/query_builder>` used to (unnecessarily) group FROM clause contents, which breaks certain queries and is invalid for some databases. - Fixed a bug (#1709) - :doc:`Email <libraries/email>` headers were broken when using long email subjects and \r\n as CRLF. - Fixed a bug where ``MB_ENABLED`` was only declared if ``UTF8_ENABLED`` was set to TRUE. -- Fixed a bug where the :doc:`Session Library <libraries/session>` accepted cookies with *last_activity* values being in the future. +- Fixed a bug where the :doc:`Session Library <libraries/sessions>` accepted cookies with *last_activity* values being in the future. +- Fixed a bug (#1897) - :doc:`Email Library <libraries/email>` triggered PHP E_WARNING errors when *mail* protocol used and ``to()`` is never called. +- Fixed a bug (#1409) - :doc:`Email Library <libraries/email>` didn't properly handle multibyte characters when applying Q-encoding to headers. +- Fixed a bug where :doc:`Email Library <libraries/email>` didn't honor it's *wordwrap* setting while handling alternative messages. Version 2.1.3 ============= @@ -376,7 +382,7 @@ Bug fixes for 2.1.3 ------------------- - Fixed a bug (#1543) - File-based :doc:`Caching <libraries/caching>` method ``get_metadata()`` used a non-existent array key to look for the TTL value. -- Fixed a bug (#1314) - :doc:`Session Library <libraries/session>` method ``sess_destroy()`` didn't destroy the userdata array. +- Fixed a bug (#1314) - :doc:`Session Library <libraries/sessions>` method ``sess_destroy()`` didn't destroy the userdata array. - Fixed a bug (#804) - Profiler library was trying to handle objects as strings in some cases, resulting in *E_WARNING* messages being issued by ``htmlspecialchars()``. - Fixed a bug (#1699) - :doc:`Migration Library <libraries/migration>` ignored the ``$config['migration_path']`` setting. - Fixed a bug (#227) - :doc:`Input Library <libraries/input>` allowed unconditional spoofing of HTTP clients' IP addresses through the *HTTP_CLIENT_IP* header. diff --git a/user_guide_src/source/libraries/javascript.rst b/user_guide_src/source/libraries/javascript.rst index d5e09c314..393d4e321 100644 --- a/user_guide_src/source/libraries/javascript.rst +++ b/user_guide_src/source/libraries/javascript.rst @@ -192,7 +192,7 @@ and triggered by a click using the jQuery library's click() event. 'width' => '50%', 'marginLeft' => 125 ); - $this->jquery->click('#trigger', $this->jquery->animate('#note', $params, normal)); + $this->jquery->click('#trigger', $this->jquery->animate('#note', $params, 'normal')); fadeIn() / fadeOut() -------------------- diff --git a/user_guide_src/source/libraries/output.rst b/user_guide_src/source/libraries/output.rst index 3289a241f..2b72ba777 100644 --- a/user_guide_src/source/libraries/output.rst +++ b/user_guide_src/source/libraries/output.rst @@ -105,7 +105,7 @@ Permits you to manually set a server status header. Example:: `See here <http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html>`_ for a full list of headers. -.. note:: This method is an alias for :doc:`Common function <../general/common_funtions.rst>` +.. note:: This method is an alias for :doc:`Common function <../general/common_functions.rst>` ``set_status_header()``. $this->output->enable_profiler(); |