xsendfile only works from index - codeigniter

I'm trying to send a file to the user using xsendfile within the code igniter framework.
It is all installed correctly, my problem is that it only seems to work from the route, even though every page comes from index.php anyway.
This is my function:
function _output_file($name, $root_location, $public_location = FALSE)
{
if (function_exists('apache_get_modules') && in_array('mod_xsendfile', apache_get_modules())) {
header ('Content-Description: File Transfer');
header ('Content-Type: application/octet-stream');
if (strstr($_SERVER["HTTP_USER_AGENT"], "MSIE") != FALSE) {
header ('Content-Disposition: attachment; filename='.urlencode($name));
} else {
header ('Content-Disposition: attachment; filename="'.$name.'"');
}
//86400 is one day
header ('Expires: '.gmdate('D, d M Y H:i:s', (TIME_NOW + 86400)));
header ('Cache-Control: must-revalidate, post-check=0, pre-check=0');
header ('Pragma: public');
header ('X-Sendfile: '.$root_location);
exit;
} else {
redirect(site_url($public_location));
}
}
If I place this at the top of my index.php and load the root it works fine, but if I try to access it from domain.com/controller/function it returns a 404 error.
It is definitely using the index.php file since if I replace the function call with die("test"); this displays to the screen.
I believe it's something to do with what permissions xsendfile has to access the file, but since it's working from the root index.php I would have thought it would have complete permissions, presumably it's based on what the request url is, which I find strange.
So.... does anyone have any suggestions as to how I can get xsendfile to work through codeigniter, from a url such as "domain.com/files/get/12"?

XSendFile has some specific security-related path quirks... and depending on your server configuration, these issues can sometimes occur over HTTPS even when HTTP seems to be working correctly.
If you run into mysterious 404s while using mod_xsendfile and you can confirm that the files being served really do exist, then you probably need to configure XSendFilePath in your Apache confs.
Add the following to the appropriate conf (httpd.conf, ssl.conf, httpd-ssl.conf, etc) and/or inside the appropriate VirtualHost declaration (if using vhosts)...
XSendFilePath /Absolute/Path/To/Your/Working/Directory/
Note: You cannot add this to an .htaccess file. It must be placed into an Apache conf.
Generally, xsendfile will try to figure out the working directory automatically, but sometimes it can't. This directive tells it explicitly what directory (or directories) should be accessible through xsendfile. Chances are that a mysterious 404 means that your directory isn't passing the whitelist check for some reason. This will fix that.
And don't forget to reboot apache after you change the config.

Prefixing a method name with an underscore makes it inaccessible through the URL.
From the documentation:
Private Functions
In some cases you may want certain functions hidden from public access. To make a function private, simply add an underscore as the name prefix and it will not be served via a URL request. For example, if you were to have a function like this:
private function _utility()
{
// some code
}
Trying to access it via the URL, like this, will not work: example.com/index.php/blog/_utility/

It seems this answer never got a reponse, in the end I just created a file in my root called "getfile.php", it's not perfect but it gets the job done for now, here it is for anyone that may find it useful.
<?php
define('BASEPATH', 'just done to stop direct access being disallowed');
function show_getfile_error()
{
echo 'You do not have permission to download this file, if you think this is a mistake please get in contact.';
exit;
}
include('applications/config/database.php');
$mysqli = new mysqli($db['default']['hostname'], $db['default']['username'], $db['default']['password'], $db['default']['database']);
if(!preg_match('%^[0-9]+$%', $_GET['key']))
{
show_getfile_error();
}
else
{
$query = mysqli_query($mysqli, 'SELECT * FROM getfiles WHERE getfile_key = '.(int)$_GET['key']);
$result = mysqli_fetch_array($query, MYSQLI_ASSOC);
if(!$result || $result['getfile_ip'] != $_SERVER['REMOTE_ADDR'])
{
show_getfile_error();
}
header ('Content-Description: File Transfer');
header ('Content-Type: application/octet-stream');
if (strstr($_SERVER["HTTP_USER_AGENT"], "MSIE") != FALSE) {
header ('Content-Disposition: attachment; filename='.urlencode($result['getfile_name']));
} else {
header ('Content-Disposition: attachment; filename="'.$result['getfile_name'].'"');
}
//86400 is one day
header ('Expires: '.gmdate('D, d M Y H:i:s', (TIME_NOW + 86400)));
header ('Cache-Control: must-revalidate, post-check=0, pre-check=0');
header ('Pragma: public');
header ('X-Sendfile: '.$result['getfile_location']);
}
?>

I have come across the 404 error today. If the path you pass to the header contains any components that lie outside of the root folder that index.php is in, then you get a 404. Make sure the path is relative to index.php, not an absolute path.

Related

how to process dynamic urls as static pages using nginx?

I want my Nginx serve dynamical urls as static pages, e.g.
given a url "/book?name=ruby_lang&published_at=2014" ,
the nginx will serve a static file (which is generated automatically ) named as:
"book?name=ruby_lang&published_at=2014.html" or:
"book-name-eq-ruby_lang-pblished_at-eq-2014.html"
is this possible?
NOTE:
1.there's no static file named:
"book?name=ruby_lang&published_at=2014.html" nor
"book-name-eq-ruby_lang-pblished_at-eq-2014.html"
however, I can generate them if needed.
2.I can't change the url that give to the consumer. e.g. my consumer could only send request to me via
"/book?name=ruby_lang&published_at=2014"
but not with any other urls.
If you are OK with generating the HTML files yourself, you could simply use nginx's rewrite module. Example:
rewrite ^/book book-name-eq-$arg_name-published_at-eq-$arg_published_at.html last;
If you need to make sure that name and published_at are valid, you can instead do something like this:
location = /book {
if ($arg_name !~ "^[A-Za-z\d_-]+$") { return 404; }
if ($arg_published_at !~ "^\d{4}$") { return 404; }
rewrite ^/book book-name-eq-$arg_name-published_at-eq-$arg_published_at.html last;
}
This will make sure that published_at is a valid 4-digits integer, and name is a valid identifier (English alphabets, numbers, underscore, and hyphen).
To make sure a book is only accessible from one URL, you should throw 404 if the URL is the HTML file. Add this before the previous rule:
location ~ /book-(.*).html {
return 404;
}
OK, thanks to #Alon Gubkin's help, finally I solved this problem, (see : http://siwei.me/blog/posts/nginx-try-files-and-rewrite-tips) .here are some tips:
use 'try_files' instead of 'rewrite'
use '-' instead of underscore '_' in your static file names, otherwise nginx would get confused when setting $arg_parameters to your file name. e.g. use "platform-$arg_platform.json' instead of "platform_$arg_platform.json"
take a look at nginx built-in variables.
and this is my nginx config snippet:
server {
listen 100;
charset utf-8;
root /workspace/test_static_files;
index index.html index.htm;
# nginx will first search '/platform-$arg_platform...' file,
# if not found return /defautl.json
location /popup_pages {
try_files /platform-$arg_platform-product-$arg_product.json /default.json;
}
}
also I put my code on github so that someone interested in this issue could take a look:
https://github.com/sg552/server_dynamic_urls_as_static_files

codeigniter hmvc routes not working properly

I installed HMVC by wiredesignz but the routes from application/modules/xxx/config/routes.php didn't get recognized at all.
Here is an example:
In application/modules/pages/config/routes.php I have:
$route['pages/admin/(:any)'] = 'admin/$1';
$route['pages/admin'] = 'admin';
If I type the URL, domain.com/admin/pages/create it is not working, the CI 404 Page not found appears.
If I move the routes to application/config/routes.php it works just fine.
How do I make it work without putting all the admin routes in main routes.php?
I searched the web for over 4 hours but found no answers regarding this problem. I already checked if routes.php from modules is loading and is working just fine, but any routes I put inside won't work.
I found a way of making the routes from modules working just fine, I don't know if is the ideal solution but works fine so far:
open your application/config/routes.php and underneath $route['404_override'] = ''; add the following code:
$modules_path = APPPATH.'modules/';
$modules = scandir($modules_path);
foreach($modules as $module)
{
if($module === '.' || $module === '..') continue;
if(is_dir($modules_path) . '/' . $module)
{
$routes_path = $modules_path . $module . '/config/routes.php';
if(file_exists($routes_path))
{
require($routes_path);
}
else
{
continue;
}
}
}
the following solution works fine even if config folder or routes.php is missing from your module folder
Here's the thing: the module's routes.php only gets loaded when that module is "invoked", otherwise CI would have to load all route configurations from all modules in order to process each request (which does not happen).
You'll have to use your main application's routes.php to get this to work. You aren't using the pages segment in your URL, therefore the routing for that module never gets loaded.
I know that's what you wanted to avoid, but unfortunately it's not possible unless you want to get "hacky".
Here's the routing I use to map requests for admin/module to module/admin, maybe you can use it:
// application/config/routes.php
$route['admin'] = "dashboard/admin"; // dashboard is a module
$route['admin/([a-zA-Z_-]+)/(:any)'] = "$1/admin/$2";
$route['admin/([a-zA-Z_-]+)'] = "$1/admin/index";
$route['(:any)/admin'] = "admin/$1";
You just need this https://gist.github.com/Kristories/5227732.
Copy MY_Router.php into application/core/

codeigniter output cache doesn't work?

I'm using $this->output->cache(n) to cache webpage, but i cannot figure out how does it work.. I didn't find any cache files under system/cache folder...and also after I edit the page and show it again, the content changes, so it seems that the page is not really cached. Can anyone give a help? (i'm using phil's template lib)
my code:
function show(){
$this->output->cache(5);
$this->load->model('page_model');
$var = $this->uri->segment(3, 0); //get About page
$row = $this->page_model->getPage($var);
$this->template->title('about')
->set_layout('default')
->set_partial('styles', 'css')
->set('data', $row->body)
->build('about');
}
THANKS!
Two things, as outlined in the documentation:
Warning: Because of the way CodeIgniter stores content for output, caching will only work if you are generating display for your controller with a view.
Perhaps not using the "native" views is an issue?
Additionally:
Note: Before the cache files can be written you must set the file permissions on your application/cache folder such that it is writable.
Are you sure your application/cache directory has the correct permissions?
Debug this file and check it's actually writing the cache:
system/core/Output.php
// Do we need to write a cache file? Only if the controller does not have its
// own _output() method and we are not dealing with a cache file, which we
// can determine by the existence of the $CI object above
if ($this->cache_expiration > 0 && isset($CI) && ! method_exists($CI, '_output'))
{
$this->_write_cache($output);
}
This works well for me:
function _output($content) {
// Load the base template with output content available as $content
$data['content'] = &$content;
$this->output->cache(600);
$output = $this->load->view('base', $data, true);
$this->output->_write_cache($output);
echo $output;
}
Are you sure your application/cache directory has the correct permissions?
you you to directories application/cache in cpanel , permissions become 777 is OK

PHPWord class in CodeIgniter is not generating correct, throwing errors in word file

Ok, so I'm having difficulty with the PHPWord class which can be found: http://phpword.codeplex.com
The weird thing about it is when I use this same code in the "example" file it works fine in generating. When I don't force a download using headers the file will open up just fine. Using this code though with the headers is causing it when it downloads to throw errors in the word file saying the file is corrupt and can't be opened, but then it opens up just fine.
public function export()
{
// Load PHPWORD Library
$this->load->library('PHPWord');
$sec = $this->phpword->createSection($secStyle);
$header = $sec->createHeader();
$header->addWatermark('images/CC_watermark.png', array('marginTop'=>1015, 'marginLeft'=>-80));
$resultSelected = $this->input->post('cbox');
foreach($resultSelected as $row)
{
$sec->addText($row);
echo $row."<br>";
}
$fileName = "Plan_Generate_".date('Ymd').".docx";
// Force Download
$filePath = $fileName;
$fileName = basename($filePath);
// $fileSize = filesize($filePath);
// Output headers.
header("Cache-Control: private");
header("Content-Type: application/stream");
// header("Content-Length: ".$fileSize);
header("Content-Disposition: attachment; filename=".$fileName);
// Output file.
// readfile ($filePath);
// exit();
// Save File
// $fileName = "files/SomethingNew_".date("Ymd").".docx";
$objWriter = PHPWord_IOFactory::createWriter($this->phpword, 'Word2007');
$objWriter->save('php://output');
}
This is the code i'm using when trying to generate the file. The trouble I'm having is it's throwing an error when it tries to force download. Thanks for any help! Ask questions if you don't fully understand the question.
Update:
Here's the image of the error's I'm receiving. Thanks for the quick responses and I'm actually going to try the Codeigniters way of doing it Tomm. morning.
You should be setting headers via CodeIgniters functions, that is what could be causing the issue for you:
CI Output Class
$this->output->set_header();
Permits you to manually set server headers, which the output class will send for you when outputting the final rendered display. Example:
$this->output->set_header("HTTP/1.0 200 OK");
$this->output->set_header("HTTP/1.1 200 OK");
$this->output->set_header('Last-Modified: '.gmdate('D, d M Y H:i:s', $last_update).' GMT');
$this->output->set_header("Cache-Control: no-store, no-cache, must-revalidate");
$this->output->set_header("Cache-Control: post-check=0, pre-check=0");
$this->output->set_header("Pragma: no-cache");
My assumption is you are trying to do update headers the PHP way, but you are playing by CI rules for output.
This was fixed by using CI's $this->download->force_download().
And by passing the data through ob_start(), ob_get_contents(), and ob_end_clean() for people needing help with it.
Are you sure you don't want
header("Content-Type: application/octet-stream");
instead of just stream? What is the actual error and is it word or the browser or php that is throwing the error?
Also, CodeIgniter has a download helper to send files... You may want to try that.
http://codeigniter.com/user_guide/helpers/download_helper.html

Is there a way to have a Codeigniter controller return an image?

I was wondering if there was a way for a controller to, instead of returning a string, or a view, return an image (be it JPG, PNG etc). For example, instead of ending with a $this->load->view('folder/special_view.php), I'd like to do something like $this->load->image('images/gorilla.png'), and have it so if my user were to go to that controller they would see an image as if they'd gone to a normal .png or jpeg. Can I set the headers so it expects a different MIME? Example code of this would be fantastic.
It would take forever for me to explain why I need this, but it involves bringing a premade CMS into codeigniter, and having it need certian things to be true. Thank you so much!
sure you can, use this instead of $this->load->view()
$filename="/path/to/file.jpg"; //<-- specify the image file
if(file_exists($filename)){
$mime = mime_content_type($filename); //<-- detect file type
header('Content-Length: '.filesize($filename)); //<-- sends filesize header
header("Content-Type: $mime"); //<-- send mime-type header
header('Content-Disposition: inline; filename="'.$filename.'";'); //<-- sends filename header
readfile($filename); //<--reads and outputs the file onto the output buffer
exit(); // or die()
}
This is not intended as One-upmanship, but pǝlɐɥʞ's suggestion is a pure PHP implementation that is not all that re-usable. You wanted to use the syntax $this->load->image('images/gorilla.png') so here is how you can.
Create /application/libraries/MY_Loader.php
<?php if ( ! defined('BASEPATH')) exit('No direct script access allowed');
/**
* Loader Class
*
* Loads views and files
*
* #package CodeIgniter
* #subpackage Libraries
* #author Phil Sturgeon
* #category Loader
* #link http://codeigniter.com/user_guide/libraries/loader.html
*/
class MY_Loader extends CI_Loader {
function image($file_path, $mime_type_or_return = 'image/png')
{
$this->helper('file');
$image_content = read_file($file_path);
// Image was not found
if($image_content === FALSE)
{
show_error('Image "'.$file_path.'" could not be found.');
return FALSE;
}
// Return the image or output it?
if($mime_type_or_return === TRUE)
{
return $image_content;
}
header('Content-Length: '.strlen($image_content)); // sends filesize header
header('Content-Type: '.$mime_type_or_return); // send mime-type header
header('Content-Disposition: inline; filename="'.basename($file_path).'";'); // sends filename header
exit($image_content); // reads and outputs the file onto the output buffer
}
There are a few ways you can use this:
Basic output (default is jpeg)
$this->load->image('/path/to/images/gorilla.png');
Send mime-type to use other image types
$this->load->image('/path/to/images/gorilla.jpg', 'image/jpeg');
Return the image
$image = $this->load->image('/path/to/images/gorilla.php', TRUE);
Just like $this->load->view, the 3rd parameter being set to TRUE means it will return instead of directly outputting.
Hope this helps :-)
An easier way with automatic mime-type.
$this->load->helper('file');
$image_path = '/path/to/image/file';
$this->output->set_content_type(get_mime_by_extension($image_path));
$this->output->set_output(file_get_contents($image_path));
About the Phil's code:
In CodeIgniter 2.0, today, there are a one change that have to be made in order to make it work:
The library has to be in /application/core/MY_Loader.php
I like to remark a small typo about the library's explanation:
There is a mistake in the header "Basic output (default is jpeg)" because in fact the default is .png
Another solutions to the problem are:
I've made a small code to make it work with the core codeIgniter libraries:
$this->output->set_header("Content-Type: image/png");
$this->load->file('../images/example.png');
Or using the Image Manipulation Library
$config['image_library'] = "GD2";
$config['source_image'] = "../images/example.png";
$config['maintain_ratio'] = TRUE;
$config['dynamic_output'] = TRUE;
$this->load->library('image_lib', $config);
$image = $this->image_lib->resize();
In both cases you get the same image you get from the source but in the output.
But for me, I liked more the extension to the core library :-)
Thank you very much Phil.
This method works even if you have $config['compress_output'] set to TRUE
$filename="/path/to/file.jpg"; //<-- specify the image file
if(file_exists($filename)){
header('Content-Length: '.filesize($filename])); //<-- sends filesize header
header('Content-Type: image/jpg'); //<-- send mime-type header
header('Content-Disposition: inline; filename="'.$filename.'";'); //<-- sends filename header
$jpg = file_get_contents($filename);
$this->output->set_output($jpg);
}
If it fits your use case, simply redirecting to it is just fine. For example, tracking using images would be like:
// Do your logic here
redirect($image_path); // Or PHP's header location function
No need to change headers. Your use case may not fit this, but someone might find this useful ^_^
I would do a
$computedImage = 'path/to/the/image.ext';
$this->load->helper('file');
$this->output->set_content_type(get_mime_by_extension($computedImage))->set_output(file_get_contents($computedImage));
If this helps. Cheers!

Resources