summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorAndrey Andreev <narf@devilix.net>2016-02-09 22:35:08 +0100
committerAndrey Andreev <narf@devilix.net>2016-02-09 22:35:08 +0100
commit477e08f1d6726809660e4841f389493541f7bc07 (patch)
tree785b12b53c5e46e317def1768558f33445ab2b1f
parent2342256c076502cbaca86fcff2a1dbbfc49a1900 (diff)
parent39967987ebcc79fc867c1f7fa8d69cc65801f594 (diff)
Merge branch '3.0-stable' into develop
Fixed conflicts: user_guide_src/source/overview/at_a_glance.rst
-rw-r--r--system/database/DB_driver.php8
-rw-r--r--system/database/DB_query_builder.php21
-rw-r--r--system/helpers/captcha_helper.php45
-rw-r--r--system/helpers/form_helper.php4
-rw-r--r--system/helpers/inflector_helper.php2
-rw-r--r--system/helpers/text_helper.php21
-rw-r--r--system/libraries/Form_validation.php8
-rw-r--r--system/libraries/Upload.php6
-rw-r--r--tests/codeigniter/core/Log_test.php64
-rw-r--r--tests/codeigniter/database/query_builder/join_test.php20
-rw-r--r--tests/codeigniter/libraries/Form_validation_test.php7
-rw-r--r--user_guide_src/source/changelog.rst14
-rw-r--r--user_guide_src/source/database/query_builder.rst18
-rw-r--r--user_guide_src/source/overview/at_a_glance.rst4
14 files changed, 193 insertions, 49 deletions
diff --git a/system/database/DB_driver.php b/system/database/DB_driver.php
index 1b52bf3b8..43e8eeac6 100644
--- a/system/database/DB_driver.php
+++ b/system/database/DB_driver.php
@@ -1504,11 +1504,11 @@ abstract class CI_DB_driver {
'\s*>\s*', // >
'\s+IS NULL', // IS NULL
'\s+IS NOT NULL', // IS NOT NULL
- '\s+EXISTS\s*\([^\)]+\)', // EXISTS(sql)
- '\s+NOT EXISTS\s*\([^\)]+\)', // NOT EXISTS(sql)
+ '\s+EXISTS\s*\(.*\)', // EXISTS(sql)
+ '\s+NOT EXISTS\s*\(.*\)', // NOT EXISTS(sql)
'\s+BETWEEN\s+', // BETWEEN value AND value
- '\s+IN\s*\([^\)]+\)', // IN(list)
- '\s+NOT IN\s*\([^\)]+\)', // NOT IN (list)
+ '\s+IN\s*\(.*\)', // IN(list)
+ '\s+NOT IN\s*\(.*\)', // NOT IN (list)
'\s+LIKE\s+\S.*('.$_les.')?', // LIKE 'expr'[ ESCAPE '%s']
'\s+NOT LIKE\s+\S.*('.$_les.')?' // NOT LIKE 'expr'[ ESCAPE '%s']
);
diff --git a/system/database/DB_query_builder.php b/system/database/DB_query_builder.php
index 00c5394e2..d6f35e0df 100644
--- a/system/database/DB_query_builder.php
+++ b/system/database/DB_query_builder.php
@@ -542,9 +542,8 @@ abstract class CI_DB_query_builder extends CI_DB_driver {
$s = $m[0][$i][1] + strlen($m[0][$i][0]), $i++)
{
$temp = substr($cond, $s, ($m[0][$i][1] - $s));
-
- $newcond .= preg_match("/([\[\]\w\.'-]+)(\s*[^\"\[`'\w]+\s*)(.+)/i", $temp, $match)
- ? $this->protect_identifiers($match[1]).$match[2].$this->protect_identifiers($match[3])
+ $newcond .= preg_match("/(\(*)?([\[\]\w\.'-]+)(\s*[^\"\[`'\w]+\s*)(.+)/i", $temp, $match)
+ ? $match[1].$this->protect_identifiers($match[2]).$match[3].$this->protect_identifiers($match[4])
: $temp;
$newcond .= $m[0][$i][0];
@@ -553,9 +552,9 @@ abstract class CI_DB_query_builder extends CI_DB_driver {
$cond = ' ON '.$newcond;
}
// Split apart the condition and protect the identifiers
- elseif ($escape === TRUE && preg_match("/([\[\]\w\.'-]+)(\s*[^\"\[`'\w]+\s*)(.+)/i", $cond, $match))
+ elseif ($escape === TRUE && preg_match("/(\(*)?([\[\]\w\.'-]+)(\s*[^\"\[`'\w]+\s*)(.+)/i", $cond, $match))
{
- $cond = ' ON '.$this->protect_identifiers($match[1]).$match[2].$this->protect_identifiers($match[3]);
+ $cond = ' ON '.$match[1].$this->protect_identifiers($match[2]).$match[3].$this->protect_identifiers($match[4]);
}
elseif ( ! $this->_has_operator($cond))
{
@@ -1458,7 +1457,7 @@ abstract class CI_DB_query_builder extends CI_DB_driver {
* @param bool $escape Whether to escape values and identifiers
* @return int Number of rows inserted or FALSE on failure
*/
- public function insert_batch($table, $set = NULL, $escape = NULL)
+ public function insert_batch($table, $set = NULL, $escape = NULL, $batch_size = 100)
{
if ($set === NULL)
{
@@ -1489,9 +1488,9 @@ abstract class CI_DB_query_builder extends CI_DB_driver {
// Batch this baby
$affected_rows = 0;
- for ($i = 0, $total = count($this->qb_set); $i < $total; $i += 100)
+ for ($i = 0, $total = count($this->qb_set); $i < $total; $i += $batch_size)
{
- $this->query($this->_insert_batch($this->protect_identifiers($table, TRUE, $escape, FALSE), $this->qb_keys, array_slice($this->qb_set, $i, 100)));
+ $this->query($this->_insert_batch($this->protect_identifiers($table, TRUE, $escape, FALSE), $this->qb_keys, array_slice($this->qb_set, $i, $batch_size)));
$affected_rows += $this->affected_rows();
}
@@ -1865,7 +1864,7 @@ abstract class CI_DB_query_builder extends CI_DB_driver {
* @param string the where key
* @return int number of rows affected or FALSE on failure
*/
- public function update_batch($table, $set = NULL, $index = NULL)
+ public function update_batch($table, $set = NULL, $index = NULL, $batch_size = 100)
{
// Combine any cached components with the current statements
$this->_merge_cache();
@@ -1904,9 +1903,9 @@ abstract class CI_DB_query_builder extends CI_DB_driver {
// Batch this baby
$affected_rows = 0;
- for ($i = 0, $total = count($this->qb_set); $i < $total; $i += 100)
+ for ($i = 0, $total = count($this->qb_set); $i < $total; $i += $batch_size)
{
- $this->query($this->_update_batch($this->protect_identifiers($table, TRUE, NULL, FALSE), array_slice($this->qb_set, $i, 100), $this->protect_identifiers($index)));
+ $this->query($this->_update_batch($this->protect_identifiers($table, TRUE, NULL, FALSE), array_slice($this->qb_set, $i, $batch_size), $this->protect_identifiers($index)));
$affected_rows += $this->affected_rows();
$this->qb_where = array();
}
diff --git a/system/helpers/captcha_helper.php b/system/helpers/captcha_helper.php
index fd1b8f1ed..3c1e006f8 100644
--- a/system/helpers/captcha_helper.php
+++ b/system/helpers/captcha_helper.php
@@ -171,35 +171,36 @@ if ( ! function_exists('create_captcha'))
$byte_index = $word_index = 0;
while ($word_index < $word_length)
{
- list(, $rand_index) = unpack('C', $bytes[$byte_index++]);
- if ($rand_index > $rand_max)
+ // Do we have more random data to use?
+ // It could be exhausted by previous iterations
+ // ignoring bytes higher than $rand_max.
+ if ($byte_index === $pool_length)
{
- // Was this the last byte we have?
- // If so, try to fetch more.
- if ($byte_index === $pool_length)
+ // No failures should be possible if the
+ // first get_random_bytes() call didn't
+ // return FALSE, but still ...
+ for ($i = 0; $i < 5; $i++)
{
- // No failures should be possible if
- // the first get_random_bytes() call
- // didn't return FALSE, but still ...
- for ($i = 0; $i < 5; $i++)
+ if (($bytes = $security->get_random_bytes($pool_length)) === FALSE)
{
- if (($bytes = $security->get_random_bytes($pool_length)) === FALSE)
- {
- continue;
- }
-
- $byte_index = 0;
- break;
+ continue;
}
- if ($bytes === FALSE)
- {
- // Sadly, this means fallback to mt_rand()
- $word = '';
- break;
- }
+ $byte_index = 0;
+ break;
+ }
+
+ if ($bytes === FALSE)
+ {
+ // Sadly, this means fallback to mt_rand()
+ $word = '';
+ break;
}
+ }
+ list(, $rand_index) = unpack('C', $bytes[$byte_index++]);
+ if ($rand_index > $rand_max)
+ {
continue;
}
diff --git a/system/helpers/form_helper.php b/system/helpers/form_helper.php
index 04778b084..3e1039525 100644
--- a/system/helpers/form_helper.php
+++ b/system/helpers/form_helper.php
@@ -791,7 +791,7 @@ if ( ! function_exists('set_checkbox'))
// Unchecked checkbox and radio inputs are not even submitted by browsers ...
if ($CI->input->method() === 'post')
{
- return ($input === 'value') ? ' checked="checked"' : '';
+ return ($input === $value) ? ' checked="checked"' : '';
}
return ($default === TRUE) ? ' checked="checked"' : '';
@@ -843,7 +843,7 @@ if ( ! function_exists('set_radio'))
// Unchecked checkbox and radio inputs are not even submitted by browsers ...
if ($CI->input->method() === 'post')
{
- return ($input === 'value') ? ' checked="checked"' : '';
+ return ($input === $value) ? ' checked="checked"' : '';
}
return ($default === TRUE) ? ' checked="checked"' : '';
diff --git a/system/helpers/inflector_helper.php b/system/helpers/inflector_helper.php
index 96b723c8d..c064d8de4 100644
--- a/system/helpers/inflector_helper.php
+++ b/system/helpers/inflector_helper.php
@@ -219,7 +219,7 @@ if ( ! function_exists('humanize'))
*/
function humanize($str, $separator = '_')
{
- return ucwords(preg_replace('/['.$separator.']+/', ' ', trim(MB_ENABLED ? mb_strtolower($str) : strtolower($str))));
+ return ucwords(preg_replace('/['.preg_quote($separator).']+/', ' ', trim(MB_ENABLED ? mb_strtolower($str) : strtolower($str))));
}
}
diff --git a/system/helpers/text_helper.php b/system/helpers/text_helper.php
index 1fdbedda5..4f9210f2d 100644
--- a/system/helpers/text_helper.php
+++ b/system/helpers/text_helper.php
@@ -275,13 +275,28 @@ if ( ! function_exists('word_censor'))
foreach ($censored as $badword)
{
+ $badword = str_replace('\*', '\w*?', preg_quote($badword, '/'));
if ($replacement !== '')
{
- $str = preg_replace("/({$delim})(".str_replace('\*', '\w*?', preg_quote($badword, '/')).")({$delim})/i", "\\1{$replacement}\\3", $str);
+ $str = preg_replace(
+ "/({$delim})(".$badword.")({$delim})/i",
+ "\\1{$replacement}\\3",
+ $str
+ );
}
- else
+ elseif (preg_match_all("/{$delim}(".$badword."){$delim}/i", $str, $matches, PREG_PATTERN_ORDER | PREG_OFFSET_CAPTURE))
{
- $str = preg_replace("/({$delim})(".str_replace('\*', '\w*?', preg_quote($badword, '/')).")({$delim})/ie", "'\\1'.str_repeat('#', strlen('\\2')).'\\3'", $str);
+ $matches = $matches[1];
+ for ($i = count($matches) - 1; $i >= 0; $i--)
+ {
+ $length = strlen($matches[$i][0]);
+ $str = substr_replace(
+ $str,
+ str_repeat('#', $length),
+ $matches[$i][1],
+ $length
+ );
+ }
}
}
diff --git a/system/libraries/Form_validation.php b/system/libraries/Form_validation.php
index 31632762d..ea3bc6de7 100644
--- a/system/libraries/Form_validation.php
+++ b/system/libraries/Form_validation.php
@@ -1214,6 +1214,14 @@ class CI_Form_validation {
$str = $matches[2];
}
+ // PHP 7 accepts IPv6 addresses within square brackets as hostnames,
+ // but it appears that the PR that came in with https://bugs.php.net/bug.php?id=68039
+ // was never merged into a PHP 5 branch ... https://3v4l.org/8PsSN
+ if (preg_match('/^\[([^\]]+)\]/', $str, $matches) && ! is_php('7') && filter_var($matches[1], FILTER_VALIDATE_IP, FILTER_FLAG_IPV6) !== FALSE)
+ {
+ $str = 'ipv6.host'.substr($str, strlen($matches[1]) + 2);
+ }
+
$str = 'http://'.$str;
// There's a bug affecting PHP 5.2.13, 5.3.2 that considers the
diff --git a/system/libraries/Upload.php b/system/libraries/Upload.php
index 15caebebe..f2418378b 100644
--- a/system/libraries/Upload.php
+++ b/system/libraries/Upload.php
@@ -526,6 +526,12 @@ class CI_Upload {
$this->file_name = preg_replace('/\s+/', '_', $this->file_name);
}
+ if ($this->file_ext_tolower && ($ext_length = strlen($this->file_ext)))
+ {
+ // file_ext was previously lower-cased by a get_extension() call
+ $this->file_name = substr($this->file_name, 0, -$ext_length).$this->file_ext;
+ }
+
/*
* Validate the file name
* This function appends an number onto the end of
diff --git a/tests/codeigniter/core/Log_test.php b/tests/codeigniter/core/Log_test.php
new file mode 100644
index 000000000..01e90f5fc
--- /dev/null
+++ b/tests/codeigniter/core/Log_test.php
@@ -0,0 +1,64 @@
+<?php
+
+class Log_test extends CI_TestCase {
+
+ public function test_configuration()
+ {
+ $path = new ReflectionProperty('CI_Log', '_log_path');
+ $path->setAccessible(TRUE);
+ $threshold = new ReflectionProperty('CI_Log', '_threshold');
+ $threshold->setAccessible(TRUE);
+ $date_fmt = new ReflectionProperty('CI_Log', '_date_fmt');
+ $date_fmt->setAccessible(TRUE);
+ $file_ext = new ReflectionProperty('CI_Log', '_file_ext');
+ $file_ext->setAccessible(TRUE);
+ $file_perms = new ReflectionProperty('CI_Log', '_file_permissions');
+ $file_perms->setAccessible(TRUE);
+ $enabled = new ReflectionProperty('CI_Log', '_enabled');
+ $enabled->setAccessible(TRUE);
+
+ $this->ci_set_config('log_path', '/root/');
+ $this->ci_set_config('log_threshold', 'z');
+ $this->ci_set_config('log_date_format', 'd.m.Y');
+ $this->ci_set_config('log_file_extension', '');
+ $this->ci_set_config('log_file_permissions', '');
+ $instance = new CI_Log();
+
+ $this->assertEquals($path->getValue($instance), '/root/');
+ $this->assertEquals($threshold->getValue($instance), 1);
+ $this->assertEquals($date_fmt->getValue($instance), 'd.m.Y');
+ $this->assertEquals($file_ext->getValue($instance), 'php');
+ $this->assertEquals($file_perms->getValue($instance), 0644);
+ $this->assertEquals($enabled->getValue($instance), FALSE);
+
+ $this->ci_set_config('log_path', '');
+ $this->ci_set_config('log_threshold', '0');
+ $this->ci_set_config('log_date_format', '');
+ $this->ci_set_config('log_file_extension', '.log');
+ $this->ci_set_config('log_file_permissions', 0600);
+ $instance = new CI_Log();
+
+ $this->assertEquals($path->getValue($instance), APPPATH.'logs/');
+ $this->assertEquals($threshold->getValue($instance), 0);
+ $this->assertEquals($date_fmt->getValue($instance), 'Y-m-d H:i:s');
+ $this->assertEquals($file_ext->getValue($instance), 'log');
+ $this->assertEquals($file_perms->getValue($instance), 0600);
+ $this->assertEquals($enabled->getValue($instance), TRUE);
+ }
+
+ // --------------------------------------------------------------------
+
+ public function test_format_line()
+ {
+ $this->ci_set_config('log_path', '');
+ $this->ci_set_config('log_threshold', 0);
+ $instance = new CI_Log();
+
+ $format_line = new ReflectionMethod($instance, '_format_line');
+ $format_line->setAccessible(TRUE);
+ $this->assertEquals(
+ $format_line->invoke($instance, 'LEVEL', 'Timestamp', 'Message'),
+ "LEVEL - Timestamp --> Message\n"
+ );
+ }
+} \ No newline at end of file
diff --git a/tests/codeigniter/database/query_builder/join_test.php b/tests/codeigniter/database/query_builder/join_test.php
index 25bd4accb..58cb21492 100644
--- a/tests/codeigniter/database/query_builder/join_test.php
+++ b/tests/codeigniter/database/query_builder/join_test.php
@@ -55,4 +55,24 @@ class Join_test extends CI_TestCase {
$this->assertEquals($expected, $result);
}
+ // ------------------------------------------------------------------------
+
+ public function test_join_escape_multiple_conditions_with_parentheses()
+ {
+ // We just need a valid query produced, not one that makes sense
+ $fields = array($this->db->protect_identifiers('table1.field1'), $this->db->protect_identifiers('table2.field2'));
+
+ $expected = 'SELECT '.implode(', ', $fields)
+ ."\nFROM ".$this->db->escape_identifiers('table1')
+ ."\nRIGHT JOIN ".$this->db->escape_identifiers('table2').' ON '.implode(' = ', $fields)
+ .' AND ('.$fields[0]." = 'foo' OR ".$fields[1].' = 0)';
+
+ $result = $this->db->select('table1.field1, table2.field2')
+ ->from('table1')
+ ->join('table2', "table1.field1 = table2.field2 AND (table1.field1 = 'foo' OR table2.field2 = 0)", 'RIGHT')
+ ->get_compiled_select();
+
+ $this->assertEquals($expected, $result);
+ }
+
} \ No newline at end of file
diff --git a/tests/codeigniter/libraries/Form_validation_test.php b/tests/codeigniter/libraries/Form_validation_test.php
index 65a3bbff7..f455b9146 100644
--- a/tests/codeigniter/libraries/Form_validation_test.php
+++ b/tests/codeigniter/libraries/Form_validation_test.php
@@ -231,6 +231,13 @@ class Form_validation_test extends CI_TestCase {
$this->assertTrue($this->form_validation->valid_url('www.codeigniter.com'));
$this->assertTrue($this->form_validation->valid_url('http://codeigniter.com'));
+ // https://bugs.php.net/bug.php?id=51192
+ $this->assertTrue($this->form_validation->valid_url('http://accept-dashes.tld'));
+ $this->assertFalse($this->form_validation->valid_url('http://reject_underscores.tld'));
+
+ // https://github.com/bcit-ci/CodeIgniter/issues/4415
+ $this->assertTrue($this->form_validation->valid_url('http://[::1]/ipv6'));
+
$this->assertFalse($this->form_validation->valid_url('htt://www.codeIgniter.com'));
$this->assertFalse($this->form_validation->valid_url(''));
$this->assertFalse($this->form_validation->valid_url('code igniter'));
diff --git a/user_guide_src/source/changelog.rst b/user_guide_src/source/changelog.rst
index b8d073931..d490f1504 100644
--- a/user_guide_src/source/changelog.rst
+++ b/user_guide_src/source/changelog.rst
@@ -27,6 +27,11 @@ Version 3.0.5
Release Date: Not Released
+- :doc:`Query Builder <database/query_builder>`
+
+ - Added a ``$batch_size`` parameter to the ``insert_batch()`` method (defaults to 100).
+ - Added a ``$batch_size`` parameter to the ``update_batch()`` method (defaults to 100).
+
Bug fixes for 3.0.5
-------------------
@@ -34,6 +39,15 @@ Bug fixes for 3.0.5
- Fixed a bug (#4384) - :doc:`Pagination Library <libraries/pagination>` ignored (possible) *cur_page* configuration value.
- Fixed a bug (#4395) - :doc:`Query Builder <database/query_builder>` method ``count_all_results()`` still fails if an ``ORDER BY`` condition is used.
- Fixed a bug (#4399) - :doc:`Query Builder <database/query_builder>` methods ``insert_batch()``, ``update_batch()`` produced confusing error messages when called with no data and *db_debug* is enabled.
+- Fixed a bug (#4401) - :doc:`Query Builder <database/query_builder>` breaks ``WHERE`` and ``HAVING`` conditions that use ``IN()`` with strings containing a closing parenthesis.
+- Fixed a regression in :doc:`Form Helper <helpers/form_helper>` functions :php:func:`set_checkbox()`, :php:func:`set_radio()` where "checked" inputs aren't recognized after a form submit.
+- Fixed a bug (#4407) - :doc:`Text Helper <helpers/text_helper>` function :php:func:`word_censor()` doesn't work under PHP 7 if there's no custom replacement provided.
+- Fixed a bug (#4415) - :doc:`Form Validation Library <libraries/form_validation>` rule **valid_url** didn't accept URLs with IPv6 addresses enclosed in square brackets under PHP 5 (upstream bug).
+- Fixed a bug (#4427) - :doc:`CAPTCHA Helper <helpers/capcha_helper>` triggers an error if the provided character pool is too small.
+- Fixed a bug (#4430) - :doc:`File Uploading Library <libraries/file_uploading>` option **file_ext_tolower** didn't work.
+- Fixed a bug (#4431) - :doc:`Query Builder <database/query_builder>` method ``join()`` discarded opening parentheses.
+- Fixed a bug (#4424) - :doc:`Session Library <libraries/sessions>` triggered a PHP warning when writing a newly created session with the 'redis' driver.
+- Fixed a bug (#4437) - :doc:`Inflector Helper <helpers/inflector_helper>` function :php:func:`humanize()` didn't escape its ``$separator`` parameter while using it in a regular expression.
Version 3.0.4
=============
diff --git a/user_guide_src/source/database/query_builder.rst b/user_guide_src/source/database/query_builder.rst
index 5d9ae4592..3135f76da 100644
--- a/user_guide_src/source/database/query_builder.rst
+++ b/user_guide_src/source/database/query_builder.rst
@@ -1433,15 +1433,20 @@ Class Reference
Compiles and executes an INSERT statement.
- .. php:method:: insert_batch([$table = ''[, $set = NULL[, $escape = NULL]]])
+ .. php:method:: insert_batch($table[, $set = NULL[, $escape = NULL[, $batch_size = 100]]])
:param string $table: Table name
:param array $set: Data to insert
:param bool $escape: Whether to escape values and identifiers
+ :param int $batch_size: Count of rows to insert at once
:returns: Number of rows inserted or FALSE on failure
:rtype: mixed
- Compiles and executes batch INSERT statements.
+ Compiles and executes batch ``INSERT`` statements.
+
+ .. note:: When more than ``$batch_size`` rows are provided, multiple
+ ``INSERT`` queries will be executed, each trying to insert
+ up to ``$batch_size`` rows.
.. php:method:: set_insert_batch($key[, $value = ''[, $escape = NULL]])
@@ -1464,15 +1469,20 @@ Class Reference
Compiles and executes an UPDATE statement.
- .. php:method:: update_batch([$table = ''[, $set = NULL[, $value = NULL]]])
+ .. php:method:: update_batch($table[, $set = NULL[, $value = NULL[, $batch_size = 100]]])
:param string $table: Table name
:param array $set: Field name, or an associative array of field/value pairs
:param string $value: Field value, if $set is a single field
+ :param int $batch_size: Count of conditions to group in a single query
:returns: Number of rows updated or FALSE on failure
:rtype: mixed
- Compiles and executes batch UPDATE statements.
+ Compiles and executes batch ``UPDATE`` statements.
+
+ .. note:: When more than ``$batch_size`` field/value pairs are provided,
+ multiple queries will be executed, each handling up to
+ ``$batch_size`` field/value pairs.
.. php:method:: set_update_batch($key[, $value = ''[, $escape = NULL]])
diff --git a/user_guide_src/source/overview/at_a_glance.rst b/user_guide_src/source/overview/at_a_glance.rst
index b4db6b18b..742d7bd0e 100644
--- a/user_guide_src/source/overview/at_a_glance.rst
+++ b/user_guide_src/source/overview/at_a_glance.rst
@@ -54,8 +54,8 @@ approach::
example.com/news/article/345
-Note: By default the *index.php* file is included in the URL but it can be
-removed using a simple *.htaccess* file.
+.. note:: By default the *index.php* file is included in the URL but it can
+ be removed using a simple *.htaccess* file.
CodeIgniter Packs a Punch
=========================