Sie sind auf Seite 1von 20

Building a Complete CodeIgniter Application: Part 1

Over the coming weeks/months Im going to write a series of blog posts describing the construction of a complete AJAX application using the CodeIgniter framework. Ive chosen to build a multi-user Feed Reader, which Ill call Feedignition. Feed Readers seem to be the new hello world, and theres good feed parsing libraries available which allow us to concentrate on the application itself without having to worry about the myriad of details involved in actually parsing of a feed. That leaves us free to explore a number of topics which will be of interest to anyone building applications with CodeIgniter. Ill assume youre familiar with PHP programming, and have a PHP development environment already set up on your machine, including web server, MySQL database, and so forth. Ill post the series in parts as I write it, but I wont commit to a schedule for new parts. All code in this series (including the final product) is released under the terms of the GNU GPL. Lets get started, with part 1! Basic Configuration Before we can dive into the application itself we need to get a basic development environment up and running. Im developing on a Linux machine using Apache 2.2, PHP 5.2, and MySQL 5.0. If youre on Windows the easiest way to get up and running would be to download XAMPP from ApacheFriends.org. Most of the CI video tutorials skip over this bit and start off where this part finishes up, but preparation is very important so I figure we should start at the beginning. Create a working directory for your CodeIgniter project. If youre running Linux, the web server root directory will usually be /var/www/html. If youre not using apache for anything else, that can be used as your working directory, otherwise create a folder under there to use. Im develop this project in a directory called feedignition under my document root. Download the CodeIgniter source from the official site, and unzip it into your working directory, preserving subdirectories. Now we need to fix the permissions on the logs and cache directories to ensure the web server can create files there. On Linux, youll want to do the following:
cd /var/www/html/feedignition/system chown apache:apache logs chown apache:apache cache

When Im developing we applications I prefer to keep as much of the application as possible outside the web root. So I want to split the standard CI installation into two bits. I want to create a html folder which will be the document root when we deploy the finished application, and leave the existing system folder outside the document root. So Ill develop this app on my local machine in the URL http://localhost/feedignition/html/. The html folder will be web accessible, but virtually empty, while the bulk of the CI code is hidden. Lets create a html folder and put index.html into it:

cd /var/www/html/feedignition mkdir html mv index.html html

Weve rearranged CIs default directory structure, so we need to tweak a couple of settings in the html/index/php file. Open it up in your favorite text editor and change the $system_folder line to $system_folder = ../system;. Now we want to clean up the default CI install a bit. The system directory will be outside of the document root when the application is in production, so the index.html files which are in every CI directory are pointless, lets get rid of those. Lets also get rid of the CI license file, the license is available in the manual or online if we need it again.
cd /var/www/html/ find . -iname index.html -exec rm {} \; rm license.txt

You can also get rid of the CI manual at this point if you want. Ive got a number of projects Ive developed using CI, and I dont want the manual in the development folders for every one of them. If you want to get rid of the manual, use the following commands:
cd /var/www/html/feedignition rm -rf user_guide

Lets just make sure that we havent broken anything so far. Open up your web browser and go to http://localhost/feedignition/html (or whatever URL corresponds to your working directory). If you see a CI welcome page, so far, so good! Well need a database later on to hold things like users, subscriptions and feed items, so lets create one now and configure it in CI. I like to do mine from the MySQL command line, but you might want to do use phpMyAdmin instead. Create a database, and also create a new user granting all permissions to that user. From the MySQL command line I run the following statements:
create database `feedignition`; grant all on feedignition.* to feedignition@localhost identified by "YOUR_PASSWORD_HERE"; flush privileges;

We should now edit system/application/config/database.php and configure CI with the name of the user and database we just created:
$db['default']['hostname'] $db['default']['username'] $db['default']['password'] $db['default']['database'] $db['default']['dbdriver'] = = = = = "localhost"; "feedignition"; "YOUR_PASSWORD_HERE"; "feedignition"; "mysql";

Well use the database on pretty much every page load, so lets automaticcally load the database class so its available everywhere. Well also use the URL helper a lot, so lets go ahead and autoload that as well. Edit system/application/config/autoload.php, and look for each of the following autoload lines, and chenge the values as shown below.

$autoload['libraries'] = array('database'); $autoload['helper'] = array('url');

If you open up your web browser and go to http://localhost/feedignition/html again now, we can test our database connectivity. If you see the welcome page this time the database connection is ok. If you see a database connection error make sure the database configuration is correct. I want to use mod_rewrite to remove index.php from the CodeIgniter URLs, so lets now edit the system/application/config/config.php file to get that underway. Find each of these lines, and change them as appropriate:
$config['base_url'] = "http://localhost/feedignition/html/"; $config['index_page'] = "";

To make mod_rewrite work well need a .htaccess file in our html/ folder. Create a file called .htaccess and put the following rewrite rules:
RewriteEngine on RewriteRule ^$ /feedignition/html/index.php [L] RewriteCond $1 !^(index\.php|images|css|js|robots\.txt|favicon\.ico) RewriteRule ^(.*)$ /feedignition/html/index.php/$1 [L]

If everything works correctly, you should now be able to point your browser to http://localhost/feedignition/html/welcome/welcome and see the welcome page. If you get a 404 Not Found error your .htaccess file is being ignored, you may need to enable .htaccess by adding the following to your httpd.conf file, then restarting Apache:
<Directory //var/www/html/feedignition/html> AllowOverride All </Directory>

Once you can see the Welcome page at http://localhost/feedignition/html/welcome/welcome we know that the basic framework, database, and mod_rewrite are all configured and working. We no longer need the default welcome message controller and views, so lets delete those now:
cd /var/www/html/feedignition/ rm system/application/controllers/welcome.php rm system/application/views/welcome_message.php

So there we have it, CI is configured, weve got a database, and mod_rewrite all set up and ready to go, and were ready to build our application. The framework doesnt do anything at the moment, any URL we try and access will give us some sort of error message. I follow these basic steps every time I build a CodeIgniter application, once done it gives you a nice clean foundation on which we can build the remainder of our application. Next time well start our Feed reader.

Building a Complete CodeIgniter Application: Part 2


This is the second installment in a series called Building a Complete CodeIgniter application. In this series Ill walk readers through the construction of a complete AJAX application using the CodeIgniter framework. Ive chosen to build a multi-user Feed Reader, which Ill call Feedignition. Feed Readers seem to be the new hello world, and theres good feed parsing libraries available which allow us to concentrate on the application itself without having to worry about the myriad of details involved in actually parsing of a feed. That leaves us free to explore a number of topics which will be of interest to anyone building applications with CodeIgniter. In the last part of this series we created the foundations on which well build the FeedIgnition aggregator. We installed the basic CI framework, and set up our database connections. When we finished up we have an app that did absolutely nothing, every possible URL resulted in a 404 error. However, this was necessary to give us a base on which we can build our feed reader, now well get down to the nuts and bolts of actually building an app in CodeIgniter. Before we get started, youll need to make sure youve worked through part 1 and have CI + a database ready to go. In this part well build an extremely basic feed reader. Well show you how to integrate third party classes into the CodeIgniter framework, and construct Models, Views and Controllers to work with our feeds and their items. It may be helpful to review my earlier post describing How I use CodeIgniter MVC before you go too much further, as this will help to explain why some things are where they are. In a nutshell though, heres how I use each component:
y y y y y

Views - Generate everything the user might see, including HTML, email text, and PDFs. Models - Handle access to data sources, such as database tables and external APIs. Controlers - Glue models and views together, handling form input and view selection. Libraries - Generic classes which need to be shared between one or more models, views or controllers. Helpers - Generic functions (not class based) which are shared between models views and controllers.

Feed Parsing? Simple as Pie! The SimplePie class is a nice object oriented library which handles the download, and parsing of feeds. By using SimplePie we can get our feed reader off the ground without having to understand the nuts and bolts of parsing feeds, we dont need to learn the nuances involved in parsing different versions of RSS and Aton, we just throw a URL at SimplePie and let it do the heavy lifting for us. Much easier!

Well need to download SimplePie from http://simplepie.org/ and unpack it into a temporary directory.
cd /tmp wget http://simplepie.org/downloads/?download unzip simplepie_1.0.1.zip

External class libraries are quite easy to integrate into CodeIgniter, SimplePie especially. We need to ensure that:
y y

The main class file conforms to the naming conventions use by CodeIgniter. Any additional files required by the libraries are included from the main class file.

In the case of SimplePie, we only have to worry about one file (simplepie.inc). The main simplepie class is called SimplePie, so to conform to CI standards we should move simplepie.inc into the system/application/libraries directory and rename it to simplepie.php:
cp /tmp/SimplePie 1.0.1/simplepie.inc /var/www/system/application/libraries/simplepie.php

Thats all there is to it! SimplePie can now be loaded and accessed like any other CI library:
$this->load->library('simplepie'); $this->simplepie->do_stuff();

Simple Test Application Now that weve got a feed parsing library which can do the heavy lifting, lets take it for a spin. Well create a simple controller/view combination which can download the CodeIgniter news feed and display a simple bulleted list of news stories. Well then use this as a starting point, and evolve it into our application. First create a file called feed.php in the system/application/controllers directory. This will be our feed controller. It should look like this:
< ?php if (!defined('BASEPATH')) exit('No direct script access allowed'); class Feed extends Controller { function __construct() { parent::Controller(); } function view() { $this->load->library('simplepie'); $this->simplepie->cache_location = BASEPATH .'cache'; $this->simplepie>set_feed_url('http://codeigniter.com/feeds/atom/full/'); $this->simplepie->init(); $this->load->view('feed/view', array('items' => $this->simplepie->get_items())); } }

This controller class will respond to URLs beginning with http://localhost/feed/, and the view function will be called when the browser requests http://localhost/feed/view. The view method first loads the SimplePie class we created earlier, and sets its cache path. Doing this causes SimplePie to use the standard CI cache folder rather than trying to create its own cache. We set the URL to the CI feed, then call init() to get SimplePie to download and parse that feed. Finally, we load up a view and give the view access to all of our feeds items. We use a view here because we want to send some output to the browser. We could simply echo the output from the controller, but one of the goals of MVC frameworks is the separation of presentation from business logic. We use views to acheive that separation, and keep our html out of our controllers and models. To actually get some output, we need to create our view. First create a folder called feed under system/application/views, then create a file called view.php in the new folder. As a matter of personal preference I like to store my views in a folder with the same name as the controller which will utilise them. Wherever practical, I like to name the view file after the method from which it will be used. Views dont need to be tied directly to controller methods, it may be possible in sone instances to create generic views. However, when the views and the controller are linked, I like to use a consistent naming convention. The view file should contain the following:
<html> <head> <title>Feed Parser Test</title> </head> <body> <b>CodeIgniter Feed</b> <ul> <?php foreach($items as $item) { echo "<li><a href='" .$item>get_link() . "'>" . $item->get_title() . "</a></li>"; } ?> </li></ul> </body> </html>

If you open your browser and go to http://localhost/feed/view you should see a simple list of CI News items and a link to each. CI will use SimplePie to download and parse that feed everytime you request the page, so this wont scale to lots of users or feeds very well, but as a simple test its pretty good. Now lets dig into some of CIs database functionality and really start to get this thing rolling! Storing Feeds and Feed Items in the DB What we really want to do with our feed reader is store our feeds URLs and their items in a database. That way when a user logs into the feed reader weve already got their news ready for tem on our server and we dont need to continually fetch the users feeds. If we store the feed items in the database, then if a user doesnt read the their news for a while we can show them their unread items, even if they disappear from the feeds.

Well need to create two database tables initially. One to store the Feed information, and a second for the items (e.g. news articles, blog posts, etc) we get from those feeds. Use phpMyAdmin, or the mysql command line tools to create the two database tables we need using the following SQL statements:
CREATE TABLE `feeds` ( `id` INT NOT NULL AUTO_INCREMENT PRIMARY KEY , `feed_url` VARCHAR( 255 ) NOT NULL , `site_url` VARCHAR( 255 ) NOT NULL , `feed_name` VARCHAR( 100 ) NOT NULL ) ENGINE = MYISAM ; CREATE TABLE `items` ( `id` int(11) NOT NULL auto_increment, `feed_id` int(11) NOT NULL, `remote_id` varchar(32) NOT NULL, `title` varchar(255) NOT NULL, `link` varchar(255) NOT NULL, `text` text NOT NULL, `updated_time` datetime NOT NULL, `created_time` datetime NOT NULL, PRIMARY KEY (`id`) ) ENGINE=MyISAM;

The feeds table well use for basic feed information, and well expand that later with some additional data. The items table will be used for the news items, etc we get from those feeds. Again well add more fields to the items table later, but this will get us started. I dont want to build a subscription interface at the moment, so lets take a moment to insert a couple of feeds into our feeds table so weve got something to work with later:
INSERT INTO `feeds` (`id` ,`feed_url` ,`site_url` ,`feed_name`) VALUES (NULL , 'http://codeigniter.com/feeds/atom/full/', 'http://codeigniter.com/', 'CodeIgniter News Feed'), (NULL , 'http://www.jimohalloran.com/feed/atom/', 'http://www.jimohalloran.com/', 'Jim O''Halloran''s Blog');

What we want to do now is to set up a controller method which gets a list of feeds to update, fetches the feeds and inserts the items in the feed into our new items table. Weve got a database which well use for storing and retreiving information, so its time to introduce models. I like to use one model for each of my database tables, so well use two models here, a Feeds model which well use to get a list of feed URLs and a FeedItems model which well use to insert data into our items table. The FeedItems model will also be used to retreive our feed items later on and display them to the end user. Models live in the system/application/models directory. Their file name needs to be the same as the lowercase name of the class, and end in .php. To to create FeedModel, create a file called feedmodel.php in system/application/models with the following contents:
class FeedModel extends Model { function __construct() { parent::Model(); } function get_feed_update_urls() {

$rs = $this->db->query('SELECT id, feed_url FROM feeds'); $feeds = array(); if ($rs->num_rows() > 0) { foreach ($rs->result_array() as $row) { $feed[$row['id']] = $row['feed_url']; } } return $feed; } }

This class contains a single method which will return an array of feed URLs which well use in our update routine. Later on well flash this out and add some query function, subscription methods and so forth, but it will do for now. Next we need to create the FeedItemModel class, create a file in the system/application/models directory called feeditemmodel.php with the following contents.
< ?php if (!defined('BASEPATH')) exit('No direct script access allowed'); class FeedItemModel extends Model { private $_id = false; public $feed_id; public $remote_id; public $link; public $title; public $text; public $created_time; public $updated_time; function __construct() { parent::Model(); } public function reset() { $this->_id = false; $this->feed_id = 0; $this->remote_id = ''; $this->link = ''; $this->title = ''; $this->text = ''; $this->created_time = localtime(); $this->updated_time = localtime(); } public function load($feed_id, $remote_id) { $rs = $this->db->query('SELECT * FROM items WHERE feed_id=? AND remote_id=?', array($feed_id, $remote_id)); if ($rs->num_rows() > 0) { $row = $rs->row_array(); $this->_id = $row['id']; $this->feed_id = $feed_id; $this->remote_id = $remote_id; $this->link = $row['link']; $this->title = $row['title']; $this->text = $row['text']; $this->created_time = strtotime($row['created_time']);

$this->updated_time = strtotime($row['updated_time']); return true; } else { $this->reset(); $this->feed_id = $feed_id; $this->remote_id = $remote_id; return false; } } function save() { if ($this->_id !== false) { $this->db->query('UPDATE items SET link=?, title=?, text=?, updated_time=NOW() WHERE id=?', array($this->link, $this->title, $this->text, $this->_id)); } else { $this->db->query('INSERT INTO items(feed_id, remote_id, link, title, text, created_time, updated_time) VALUES(?, ?, ?, ?, ?, NOW(), NOW())', array($this->feed_id, $this->remote_id, $this->link, $this->title, $this->text)); $this->_id = $this->db->insert_id(); } } function get_items($offset, $num_per_page) { $rs = $this->db->query('SELECT count(*) as total FROM items', array($num_per_page, $offset)); if ($rs->num_rows() > 0) { $row = $rs->row_array(); $total = $row['total']; $rs = $this->db->query('SELECT * FROM items ORDER BY updated_time DESC LIMIT ? OFFSET ?', array($num_per_page, $offset)); return array('total' => $total, 'items' => $rs>result_array()); } else { return array('total' => 0, 'items' => array()); } } } ?>

This is the FeedItemModel. This one is more complex than the FeedModel. See my How I use CodeIgniter MVC piece for some background. We have a series of public member variables which will store the feed details were going to collect at this point. We have a load method which given a feed id and unique id will retrieve any existing data from the database. If the load method does not find an existing record, the model state is emptied in preparation for a new record to be inserted. As its name suggests, the save method saves that data back to the database. Save() will automatically handle inserts/updates. The reset() method can be used to reset the model ready to create another record. Its used internally by load(), but it can also be called directly. Finally the controller method which binds it all together:
function update_all() { $this->load->library('simplepie'); $this->simplepie->cache_location = BASEPATH .'cache';

$this->load->model('FeedModel'); $this->load->model('FeedItemModel'); $feeds = $this->FeedModel->get_feed_update_urls(); foreach ($feeds as $feed_id => $feed_url) { $this->simplepie->set_feed_url($feed_url); $this->simplepie->init(); $items = $this->simplepie->get_items(); foreach ($items as $item) { $this->FeedItemModel->load($feed_id, md5($item->get_id())); $this->FeedItemModel->link = $item>get_permalink(); $this->FeedItemModel->title = $item>get_title(); $this->FeedItemModel->text = $item>get_content(); $this->FeedItemModel->save(); } } }

This method loads the SimplePie library we created earlier, then used the FeedModel object to get a list of feeds. Next we retrieve each of these individually and use the FeedItemModel class to insert each item into the database. Because the load method resets the object for us if the feed item isnt found in the database we can safely ignore load()s return value and just assign our values and save(). This makes our controller code much simpler! Test out the update all method by going to http://localhost/feed/update_all. If you see a blank page it works fine! Well avoid sending any output for the update_all method because well call it later from a cron job rather than a web browser. If everything worked you should now have a series of records in the items table of your database! Now lets get those records out and display them to the end user, theyre not doing us much good just sitting there in the database! Lets return to our FeedItemModel class and add in a new get_items method. Eventually, there might be a large number of feeds with a larger number of items, so well want to use pagination. This means well need to tell our get_items method to retrieve a range of records, but well also want to know how many records there are in total so we can generate the appropriate pagination links. Our get_items() method returns an array containing a total and an array of items on success. If no records were found we return the total as 0 and an ampty array. This means that unless we specifically want to display a no items found method later on in our view, we can virtually ignore the empty database case.
function get_items($offset, $num_per_page) { $rs = $this->db->query('SELECT count(*) as total FROM items', array($num_per_page, $offset)); if ($rs->num_rows() > 0) { $row = $rs->row_array(); $total = $row['total']; $rs = $this->db->query('SELECT * FROM items ORDER BY updated_time DESC LIMIT ? OFFSET ?', array($num_per_page, $offset)); return array('total' => $total, 'items' => $rs>result_array()); } else { return array('total' => 0, 'items' => array()); }

Now that we can use our model to get feed items from the database, lets tweak our controller and view to turn this into a River of News style display. First the controller. We want to define the number of items per page, rather than hard code that wherever we need it, lets define it as a constant at the top of the controller:
class Feed extends Controller { const ITEMS_PER_PAGE = 20;

Now, lets go back and change the simple view method we created earlier:
function view($offset = 0) { if (is_numeric($offset)) { $offset = floor($offset); } else { $offset = 0; } $this->load->model('FeedItemModel'); $data = $this->FeedItemModel->get_items($offset, Feed::ITEMS_PER_PAGE); $data['per_page'] = Feed::ITEMS_PER_PAGE; $this->load->view('feed/view', $data); }

The first part of the controller method turns the $offset parameter into a proper integer value. This prevents it from being incorrectly quoted later in the get_items method, and prevents the injection of a string (via the URL) into our SQL where an integer was expected. The later parts should be fairly self explanetory. We load our FeedItemModel class and gell its get_items method, and feed the results into our view. Well need a new view to go with this, so lets take a look at that now:
< ?php // Initialise pagination first, then we can just call create_links() later wherever we want the pagination to appear. $CI = &get_instance(); $CI->load->library('pagination'); $config['base_url'] = site_url('feed/view'); $config['total_rows'] = $total; $config['per_page'] = $per_page; $CI->pagination->initialize($config); ?> < ? $this->load->view('header', array('title' => "Feed View")); ?> <div class="pagination"> < ?php echo $CI->pagination->create_links(); ?> </div> < ?php foreach ($items as $item) { ?> <div class="item"> <h1><a href="<?=$item['link'];?>">< ?=$item['title'];?></a></h1> <p>< ?=$item['text'];?></p>

</div> < ?php } ?> <div class="pagination"> < ?php echo $CI->pagination->create_links(); ?> </div> < ?php $this->load->view('footer'); ?>

Ive split our original view up into three parts. The feed/view.php fileis showen above. This handles the job of displaying the feed content, and creating the pagination. Notce how were loading the pagination library int he view and generating our pagination using the total records value and the number of records per page which were passed into the controller. Ivewrapped up the pagination and each feed item in divs and assigned classes to them. The divs are invisiable at the moment, but well use them later on in the tutorial series to add some style to our feed viewer. Were also loading two other views, header.php and footer.php. These will contain the generic header and footer html which well use on all of the pages. They look pretty empty now, but well flesh them out with external CSS and Javascript, navigation and footer details as this series progresses. The header view is very basic right now:
<html> <head> <title>FeedIgnition< ?php if (isset($title)) { echo " :: ".$title; } ?></title> </head> <body></body></html>

The only feature of note here is the use of the $title variable to allow our main view to alter the page title if it wishes to. The footer.php file is even simpler: Opening a browser now and going to http://localhost/feed/view should return the 20 most recent posts from the CI web site and my blog in a (pretty ugly) River of News style display. The pagination top and bottom of page should also work properly, passing an offset via the URL back to our controller. One thing youll find is that at the moment the URL http://localhost/ displays an error:
An Error Was Encountered Unable to load your default controller. Please make sure the controller specified in your Routes.php file is valid.

Lets fix that up now so that the Feed viewer is our sites default home page. Open up system/application/config/routes.php and look for the line:
$route['default_controller'] = "welcome";

and change it to
$route['default_controller'] = "feed";

This change will load the feed controller and execute the index function of controller/method is spacified on the URL, so now we need to get our index function to act the same as the view() method, the easiest way to do this is to siplay call $this->view() like so:
function index() { $this->view(); }

So now we have a simple feed reader. If we visit the http://localhost/feed/update_all URL periodically the app will update our feeds, then we can read them (with pagination) using the feed view weve built. Theres still a long way to go! Things we will address in coming parts:
y y

I want to make the feed updating process transperent to the end user by hooking it up to our systems scheduler (e.g. cron). Weve got a major security vulnerability at the moment, so we need to escape the data were displaying to end end user to prevent someone from using cross site scripting (XSS) to interfere with our feed reading experience. We should harvest more information from the feeds, including feed level metadata, and the item fields were not collecting at the moment.

The three items above will complete the basic feature set we already have, but theres a whole lot more I want to cover as part of this tutorial, including:
y y y y

Multi-user login/subscription facilities. Tagging of feeds. Reading feeds a tag at a time or individually as well as River of News style. Read/Unread tracking.

If you want to download all of the code created to date, click here to download a zip file containing a SQL dump of the database, the CI framework and all of the code from this part of the series. The version of the code in the zip file also includes phpDocumentor style comments for each of the functions which may help to explain the code. Ive cut the comments out of the code on this page to save space.

Building a Complete CodeIgniter Application: Part 3


I left you at the end of part 2 with the news that there was a large security hole in the work wed done so far. Readers whove done a bit of web development in the past should recognise the vulnerability as cross site scripting (XSS) and might understand the problems XSS can create. In this part I want to discuss some common security problems, and the steps we need to take to eliminate those. Understand that security is not a product but a process. We cant buy security, we cant develop our code and bolt on some security later. Effective security needs to be built into the product/project from the time its first written, and ongoing care and attention needs to be paid to making sure that every new line of code doesnt compromise our security in some

way. If you need evidence of that, theres any number of Open Source CMS or forum products out there which were put together and released, and have struggled for many many releases (often with little success) to properly secure themselves against attack. Security in the applications we write is the result of education, awareness, care and attention to detail in every piece of code we write, secure code should be the result of the process we use to write our code, not an afterthought. In the first two parts Ive done a couple of things already which were security related, so lets first loop back and explain what we did and why. Then settle in while I explain cross site scripting (XSS) and we look at the HTML Purifier tool then apply it to the problem at hand. Finally Ill talk about handling user logins and secure storage of passwords. Source Code Leakage / Direct Access to Included Files Anything thats placed in underneath the document root on your web server will be accessible to all users of that web server (unless you employ some sort of .htaccess style login controls). This means that all fo your PHP scripts, images, etc can be accessed by a user with a standard web browser, if they know (or can guess) the correct URL. If the file has a .php extenstion the server will treat it as a PHP script and run it through the PHP interpreter first then return the results to the browser. If the PHP script in question was an include file this might lead to unexpected results and possibly errors. If your include files had a .inc extension instead of .php (some developers prefer it that way), the source code itself would be returned to the browser on most PHP installations. CodeIgniters conventions prevent you from using the .inc extension on your controllers, models, views, libraries, and helpers but its worth understanding the risk because some 3rd party libraries you might want to hook into CodeIgniter might still use it. Either case (the direct execution of include files, or source code leakage) is undesirable. How do we avoid that? Theres two things we can do: 1. Most CI code modules include a test to ensure the script wasnt called directly, thats the if (!defined(BASEPATH)) exit(No direct script access allowed); line at the top of the file. Include this in each of the modules you create and youre mostly there. 2. The most effective remedy is to store your include files outside the web servers document root. With CI, the entire application is an include (directly or indirectly) from the index.php file. This emans the system directory can be kept out side of the web servers document root, leaving just the index.php file exposed to the users browser. Back in part 1, we moved the system directly and edited index.php to allow us to create a situation where the html directory represented our document root, and the system folder was outside it, and therefore not web accessible. You can also use the same idea for protecting images, store them outside the document root and use a PHP script to control access to the image data as required. I described this technique in an earlier blog post. The major advantage of the second approach is that if a bug is found in the PHP interpreter that causes it to return source code, or a server admin screws up the Apache configuration so that the PHP interpreter no longer works (very easy to do), the files outside the document root remain inaccessible, and an attacker cant access their content via specially crafted URLs. SQL Injection

Together I would describe SQL Injection and XSS as the big two. These are the things that can cause the most damage to your application and its users, but the also take a lot of work to properly eliminate. SQL Injection occurs when we take unvalidated user inout and use it in an SQL string without any form of escaping applied. If you have code like this, youre vulnerable to SQL Injection:
$sql = "SELECT * FROM users WHERE username = '".$_POST['username']."' AND password = '".$_POST['password']."'";

So whats wrong with that? Well, to begin with, lets say that my surname is used as the username, what do we get as SQL?
SELECT * FROM users WHERE username = 'O'Halloran' AND password = 'SuperSecret123';

Can you spot the problem? 10 points if you said thats invalid SQL! Exactly, the in my surname marks the end of the string, then our DB will try and interpret the Halloran part as an SQL keyword or identifier and fail. So now weve got a way to generate invalid SQL statements, but is that a security problem? Absolutely, what if our attacker entered admin; as their username? Lets see that in SQL:
SELECT * FROM users WHERE username = 'admin'; -- ' AND password = 'SuperSecret123';

Thats actually valid SQL because the indicates the start of a comment Lets trim off the comment text, the problem should be obvious now:
SELECT * FROM users WHERE username = 'admin';

Whered our password check go??? If we use the above fragment of PHP code to construct the SQL statement, it becomes trivially easy to bypass the login page. So what do we do about this? The problem occurs because our database uses characters to distinguish the start and end of strings. Given that these characters might also legitimately occur in the middle of a string it needs a way to tell the difference between the end of a string and a in the data. All databases do this by escaping the mid-string s in some way. In MySQL we can use the sequence \ and MySQL knows that whenever we see a backslash followed by a single quote () it shouldnt treat that single quote as the end of a string. The PHP magic quotes feature was intended to automagically fix this, but it created more problems than it solved. So theres two solutions to the problem, we can call an escaping function on the values before they are used in SQL statements, which in standard PHP would look like this:
$sql = "SELECT * FROM users WHERE username = '".mysql_real_escape_string($_POST['username'])."' AND password = '".mysql_real_escape_string($_POST['password'])."'";

CodeIgniter provides its own string escaping functions as well. Different databases often have slightly different rules regarding escaping special characters, so we shouldnt use mysql_real_escape_string with a Postgres database. Using the CI escaping functions gives us some portability between database engines. The CI version would look like this:
$rs = $this->db->query("SELECT * FROM users WHERE username = '".$this->db>escape($username)." AND password = ".$this->db->escape($password));

CI also provides a very convenient form of escaping often called prepared statements or query bindings. When using prepared statements you write your SQL statements and put question marks in the SQL string where a variable should go. You then supply an array of variables as the second parameter, and CI will take care of inserting those into the SQL string with proper escaping. Using prepared statements out example becomes:
$rs = $this->db->query("SELECT * FROM users WHERE username = ? AND password = ?", array($username, $password));

I actually find this form of query makes the SQL much easier to read, you can focus on the logic of the SQL without being distracted by the mechanics of constructing the string with escaped values. In part 2, I posted the FeedItemModel class, which had both load and save methods.for feed items. If you take another look at the save() method now you can clearly see the use of prepared statements, ensuring all data is escaped properly before its use in the database.
function save() { if ($this->_id !== false) { $this->db->query('UPDATE items SET link=?, title=?, text=?, updated_time=NOW() WHERE id=?', array($this->link, $this->title, $this>text, $this->_id)); } else { $this->db->query('INSERT INTO items(feed_id, remote_id, link, title, text, created_time, updated_time) VALUES(?, ?, ?, ?, ?, NOW(), NOW())', array($this->feed_id, $this->remote_id, $this->link, $this->title, $this->text)); $this->_id = $this->db->insert_id(); } }

Escaping data in this way doesnt stop an attacker fron trying to get rubbish data inserted into the database (we need to validate and filter for that) but it will prevent them from being able to generate invalid SQL in the process. Cross Site Scripting (XSS) SQL Injection vulnerabilities allow an attacker to manipulate the SQL thats used with the database in order to acheive unintended results. Cross Site Scripting on the other hand allows an attacker to manipulate the HTML thats sent to either their own, or another users browser. Usually the objective would be to insert Javascript code into the HTML and have that executed for their own purposes. The javascript might redirect the user to a different web site, read cookies, or anything else that we can accomplish with javascript. Cross Site Scripting is a real issue for any site which relies on user generated content, a user could creat malicious content which attacks other users or site admins. For example, if a forum package was suceptible to XSS, a user on that forum could make a post which automatically redirected anyone who viewed that post to a different web site (maybe a competing forum). As another example, lets say we had a blog package that was vulnerable. An attacker could leave a comment which captured subsequent users site cookies and submitted them to a remote web site the attacker controlled. Later the blogs owner might view that comment, the attacker captures his/her cookies and uses those to impersonate the site owner. As I said, pretty much any site which displays user generated content is a potential target for XSS attacks. Thats pretty much all of Web 2.0!

Like SQL Injection the solution here involves escaping. XSS is often introduced via a script tag in the content were trying to generate. The objective here is to remove HTML content which might allow the introduction of XSS. This might mean:
y y y y

Stripping out all HTML tags and attempting to reduce input to plain taxt. Limiting users to a small subset of HTML known to besafe from XSS attacks. Escaping angle brackets and other special HTML characters so they become visible instead of being parsed as HTML by the browser. Using an alternate markup language like bbCode or textile which doesnt give users the tools they need to create XSS attacks.

The code I posted in part 2 is extremely vulnerable to XSS, were displaying title, and item content without sanitizing it in any way. This data comes from an RSS feed from some unknown person/company elsewhere on the net and shouldnt be trusted. You can see just how vulnerable we are right now, but running the following SQL statement then going to your FeedIgnition home page.
INSERT INTO `feedignition`.`items` (`id` , `feed_id` , `remote_id` , `title` , `link` , `text`, `updated_time` , `created_time`) VALUES ( NULL , '1', '12345678901234567890123456789012', 'XSS Example', 'http://www.example.com/', '<script language="javascript">alert("XSS Vulnerable!");</script>If you see an alert box with the words "XSS Vulnerable" you''re vulnerable to XSS exploits.', NOW( ) , NOW( ));

This test displays a simple message box, but remember anything which can be done in javascript could be done at this point. Filter Input, Escape Output The basic security mantra for web development is Filter Input, Escape Output. This means check all of the input to your application to ensure it conforms with your expectations, and use escaping functions on all of your output (e.g. SQL Queries). Proper input filtering means validating all input to your system to make sure that the data you receive as input is the sort of data you expected to receive. If you expected a numeric value in a particular field, make sure you got one. If theres a select field with 3 possible values, make sure what you got on submission was one of these values. Its extremely easy for an attacker to bypass the HTML form itself and construct a HTTP request with their own data, so dont assume the only thing youll ever get on form submit is what a user might have been able to enter. Addressing XSS So lets take stock for a moment of the things were displaying in the feed view and asses its risk. 1. Pagination - Generated entirely by CI, no user entered data. No risk. 2. Item Title - From external RSS feed. XSS vulnerable. HTML formatting not needed. 3. Link URL - From RSS feed, XSS vulnerable if javascript: psuedo-protocol is used. Need to restrict to just http, https, and ftp URLs.

4. Item Content - From RSS feed, XSS vulnerable. Want to allow an item to contain HTML (e.g. this post) but need to prevent XSS. The pagination can remain as is, theres no risk there, but lets look at each of the others in turn and see what we might do about them. The items title should be plain text, and even if it isnt we want to treat it as such to preserve the formatting of our header. Ill use the htmlentities() function provided by PHP to convert all special HTML characters (e.g. angle brackets) into their HTML representation (e.g. < into <). Edit system/application/views/feed/view.php and change the following line:
<h1><a href="<?=$item['link'];?>"><?=$item['title'];?></a></h1>

to:
<h1><a href="<?=$item['link'];?>"><?=htmlentities($item['title']);?></a></h1>

We also want to change the update_all method in the controller to decode any entities which were supplied in the RSS feed, thus avoiding encoding them twice. Look for the following line in system/application/controllers/feed.php:
$this->FeedItemModel->title = $item->get_title();

and change it to
$this->FeedItemModel->title = htmlentities_decode($item->get_title());

I noted above that the link URLs shouldnt use the javascript Psuedo-protocol (allowing this allows feeds to introduce javascript directly). We could to a simple string comparison on the URL and see if it uses an invalid protocol, but what Id rather do is use a whitelist approach, and allow any valid http, https and ftp urls. Well decode the URL when we fetch it from the feed, and see if it matches our whitelist of protocols. In the controllers update_all() function, find the following line:
$this->FeedItemModel->link = $item->get_permalink();

Now lets make our changes. Replace the above line with:
$permalink = $item->get_permalink(); $scheme = @parse_url($permalink, PHP_URL_SCHEME); if ($scheme === FALSE || !in_array($scheme, array('http', 'https', 'ftp'))) { // parse_url returns false for seriously malformed URLs $permalink = ''; } $this->FeedItemModel->link = $permalink;

The final challenge is the items content. We want to remove the possibility of script (and therefore XSS), while allowing otherwise harmless HTML (e.g. bulleted lists, bold, underlines, headings, etc). CI is of assistance here, theres an xss_clean method available on the input library which strips out some of the tags and other html entities normally used to

introduce XSS. However when this was applied to the example we inserted into the database earlier, it left the javascript inside the script tag visible, which isnt ideal. Time to pull out the big guns! HTML Purifier HTML Purifier is a PHP library specifically designed to parse HTML, remove any element not on a whitelist, and put the HTML back together in a standards compliant form. This means that you can have a text area, allow the user to type anything they like into it, and HTML Purifier will remove XSS attacks and return W3C valid HTML. I like the idea of a whitelist based approach because it means that HTML Purifier wont need to be updated every time theres a new form of XSS attack found, the whitelist approach means that we remove everything except HTML tags that are known to be safe, rather than removing things which are known to be bad. Sounds like exactly what we want for this job! Andy Mathijs at Mindloop has done the hard work figuring out how to integrate HTML Purifier and CodeIgniter. As discussed in part 2, its quite easy to integrate 3rd party libraries into CodeIgniter provided they meet a few simple rules. 1. Theyre placed in the system/application/libraries directory. 2. They follow the CodeIgniter file naming convention (at least for the main class file). 3. The constructor doesnt expect any arguments, as CI wont allow you to supply parameters via the $this->load->library() method. All of these can usually be overcome with minor tweaks to the library code. Lets start by downloading HTML Purifier, then unzipping it to a temporary directory.. Im not aiming for PHP4 compatibility, so I got the strict version (Any uses the standard version in the link above, so either should work).
wget http://htmlpurifier.org/releases/htmlpurifier-2.1.2-strict.zip unzip htmlpurifier-2.1.2-strict.zip /tmp

Change into the temporary directory and pick out the bits that we need. We need both the HTMLPurifier directory and the HTMLPurifier.php file from the library directory of the bundle we just unpacked:
cd /tmp/htmlpurifier-2.1.2-strict/library cp HTMLPurifier.php /var/www/system/application/libraries/ cp -a HTMLPurifier /var/www/system/application/libraries/

We also need to add the following line to the top of the HTMLPurifier.php file to add the HTML Purifier modules to the PHP include path.
set_include_path(dirname(FILE) . PATH_SEPARATOR . get_include_path() );

Now we can load HTML Purifier like any other CI library and use it. Well purify the item content when we update the feeds rather than on output for efficiency reasons (a feed item may be displayed many times, but likely only written to the database once). So lets go back to the update_all function in our controller and add the following near the top, just underneath the block that loads the SimplePie library

$this->load->library('HTMLPurifier'); $purifier_config = HTMLPurifier_Config::createDefault(); $purifier_config->set('Cache', 'SerializerPath', BASEPATH .'cache');

Notice that, like SimplePie, were reconfiguring the HTML Purifier library to use the CI cache directory for its entify cache. This makes setting filesystem permissions much easier later when we need to install the app. Now we need to change the following line:
$this->FeedItemModel->text = $item->get_content();

to use HTML purifier:


$this->FeedItemModel->text = $this->htmlpurifier->purify($item>get_content(), $purifier_config);

So now we have a version of the HTML which we can be sure if free of XSS. We dont need to make any changes to the output in this case, just display it as is. Wrapup In part 1, we got CodeIgniter intalled and arranged into a usable form, including .htaccess configuration. Then in part 2 we got a basic River of News style aggregator off the ground, introducing basic CodeIgniter MVC concepts along the way. While there want a whole lot of new code in this part of the series, weve made some very important security enhancements. I hope the breif introduction to SQL Injection and XSS has been useful, and youll see the techniques introduced here used throughout the project in coming parts. Sorry this part has taken so long to materialise, it was written some weeks ago, but the code required additional testing before I was prepared to post it. Im aiming for a fortnightly posting schedule for the remainder of the series, well see how we go.

Das könnte Ihnen auch gefallen