I have been developing a website for teachers using cakephp 2.0.3. One requirement is for the teachers to be able to upload files (images, docs, pdfs and videos) that are only viewable by a students in a given class. I had been using a php fpassthrough to read binary data to the http output stream.
This has worked fine for all the documents except for the video, which won't be played by the chrome built-in player, vlc firefox plugin or jwplayer, which will be used on the actual web site. If I save the file, however, Windows media player will play the file fine. Playing the file from the cakephp webroot directory also works fine. Here is the file view code:
The controller code, which loads and outputs the file:
//cakephp find used to pull file information from database
$fileName = "/redacted/" . $File['File']['unit_id'] . "/" . $File['File']['id'];
$fp = fopen($fileName, 'rb');
header("Content-Type: " . $File['File']['type']);
header("Content-Length: " . filesize($fileName));
fpassthru($fp);
The layout which is used:
<?php echo $this->fetch('content'); ?>
The jwplayer loading code:
<div style="width: 200px; height: 91px;" id="1"> </div>
<script type="text/javascript">
jwplayer("1").setup({
file: "/Files/view/1/blah.m4v",
});
</script>
The view file is empty, as all output is done in the controller. I've looked through this site and google but can't find a reason for the issue or better way to perform this function. I have verified that the headers are correct and that there are no errant line breaks or data before or after. I have also checked that the extension is not required (in the final code block, '1' specifies the file ID, and the file name at the end is a dummy).
My questions, therefor:
Is fpassthru(), readfile() or similar the correct method for php video passthrough?
Is this problem specific to my code, or inherent to cakephp's handling of MVC? Has anyone successfully implemented such a solution?
Are there any other 'thinking outside the box' solutions (using node.js or some other language, .htaccess trickery) that would work better.
I could use a non-cakephp script in the webroot folder, but how would I go about passing user session data? Is this just a dumb idea?
[Edit]
After testing fpassthrough on video files separate from cakephp, I found that the issue is with the video passthrough rather than cakephp. This code was used to test, in the app/webroot directory:
<?php
$fileName = "/var/www/redacted/files/2/1";
$fp = fopen($fileName, 'rb');
header("Content-Type: video/mp4");
header("Content-Length: 18485");
fpassthru($fp);
?>
The content type, path, and length were taken from the database entry for that file.
Cake has the functionality you want built in, check out Media Views in the documentation. Note that it's deprecated in Cake 2.3.0, but since you're using 2.0, it should work just fine. I tested the following code and had no problem with it playing a sample m4v in jwplayer or the VLC plugin:
$this->viewClass = 'Media';
$fileName = WWW_ROOT . DS . 'sample_iPod.m4v';
$parts = pathinfo($fileName);
$params = array(
'id' => $parts['basename'],
'name' => $parts['filename'], // requires PHP >= 5.2.0
'extension' => $parts['extension'],
'download' => false,
'path' => $parts['dirname'] . DS
);
$this->set($params);
Note that Cake will automatically output the correct content type/length for you based on the extension.
Related
My website contains a section that allows users to access restricted PDF files if they have access to them. Basically, they arrive at a page which has a nav bar at the top, allowing them to cycle between PDF files. Below this, there is an iframe which is linked to a PHP page that does the retrieval of the file, and displays it. This file is called falr_pdf.php, and depending on how it is accessed, the PDF file is either displayed inline, or downloaded as an attachment.
Here is the navbar page, along with the falr_pdf.php IFRAME embedded, to give you a clear idea.
As you can see, the PDF should be displayed inline, and then if the user clicks "Download File", the same page is opened in a new tab, but to download the file as an attachment instead of displaying it inline.
My problem is that this works flawlessly for small PDF files, but anything larger than about 1.6MB will take an incredibly long time to display inline. However, if they are downloaded using the direct link instead, they download at normal speed, very quickly. Here is the code I am using...
$path = $falr_filesbasedir . "/" . $folder . "_pdf/" . $file . ".pdf";
$public_name = basename($path);
header('Content-Length:'.filesize($path));
header('Content-type: application/pdf');
header('Content-Disposition: ' . $method . '; filename="' . $public_name . '"');
readfile($path);
exit;
Now I know that the issue is not in any of my variables or links, since it works fine for every type of small file, and the download works fine for large files. It's only that large files don't display inline correctly.
$method is only ever "inline" or "attachment"
Is this a bug with readfile()? Or is it something to do with my PHP settings?
I am at a complete loss here.
Have you tried looking at your performance waterfall? Have you tried accessing the file served directly from the webserver as a static document? Have you tried measuring timings in your php code and comparing that with what's happening in the browser? What is the latency and bandwidth between client and server? Whatt happens when you download the file using a client running on the same host as the server? Are there any signs of stress on the server host? These are the basic questions you should start by investigating.
At a guess, the most likely cause is buffering in php or on the webserver. If you unroll the readfile into read/write operations with flushes ever 200kb you can test this. But you need to start measuring properly.
I have a file with no extension on it, but I know it's a tiff. I want to be able to download this file via PHP.
I created a page with a link to another php page, which has the following content:
<?php
$imgPath = 'http://server/23700-b074137f-eb5c-45d6-87c2-13c96812345b';
header("Content-disposition: attachment; filename=invoice.tiff");
header("Content-type: image/tiff");
readfile($imgPath);
?>
When I click the link, I get a prompt to download invoice.tiff, but it's 0 bytes.
However, if I rename the file on the server to 23700-b074137f-eb5c-45d6-87c2-13c96812345b.tiff (and change the $imgPath), it works.
How do I accomplish this without renaming the file to include the extension?
It's possible the 'tiff' extension is registered as a known file type on the server, so when you rename and request the tiff it's permissions will allow you to open it. However, with no extension, the security is probably stopping you from reading it, as mentioned by 'Mike B' above. To check this try just entering the file name in your browser address bar and see if it opens, both with and without the 'tiff' extension. There is no workaround for getting past the security issue, short of changing the severs security which would be very bad.
You are retrieving the file from a URL, therefore activating the 'fopen wrappers' in readfile. In general, you should not do this, especially when working locally since it invokes a lot of unnecessary overhead and (in this case) unwanted 'magic' behaviour.
Just use readfile on the local path to the file, and it'll be fine, or use die(file_get_contents($imgPath)) instead of the last line to circumvent PHP's native behaviour.
It works for me:
$imgPath = 'http://server/23700-b074137f-eb5c-45d6-87c2-13c96812345b';
$f = fopen($imgPath, "r");
header("Content-disposition: attachment; filename=invoice.tiff");
header("Content-type: image/tiff");
fpassthru($f);
You should also add the content-length header like so:
// untested code
header('Content-Length: '.strlen(stream_get_contents($imgPath)));
I have a project that is composed of two separate parts. On one side, there is a Rails app and on the other side, there is an ExtJs client with CakePhp server.
What needs to happen is this: attach a file in Rails (this is done by using Paperclip) and be able to read them on the Cake side. It might be easy if both systems were on the same server, but this needs to be done remotely, so the Cake side will call a Rails route and Rails will provide the file.
Cake code to download file:
function download_file($path, $file_name, $content_type, $size) {
if ($fd = fopen ($path, "r")) {
header("Content-type: " . $content_type);
header("Content-Disposition: attachment; filename=\"".$file_name."\"");
header("Content-length: $size");
header("Cache-control: private"); //use this to open files directly
header('Pragma: public');
ob_clean();
flush();
echo readfile($path);
// $this->log(apache_response_headers(), 'debug');
}
fclose ($fd);
}
Ruby / Rails code to provide document:
send_file document.path, :type => document.document_content_type
Rails Document model:
class Document < ActiveRecord::Base
belongs_to :attachable, :polymorphic => true
has_attached_file :document,
:url => '/download/:id/:fingerprint/documents'
def path
document.path
end
end
Everything works as expected except one type of file, .docx. I can upload and after that download any other files except those of this type. I can read the file in rails, after it was uploaded, with no problem. There seems to be something wrong on the Cake side, as what happends there is this: I can download the file, but I can't open it with LibreOffice after that. I seems that the mime types are set up correctly as apache and the browser recognize the type of file and the application to open it. It seems I can open the file with TextEdit.
So, the question is: what is wrong? Why don't .docx files open on the php / Cakephp side?
Any ideas will be highly appreciated.
Found the answer for this. Actually it was some sort of Cake issue after all, and it was related to the way Cake is rendering views.
Cakes MediaView doesn't allow working with remote files because of the way it builds the path of the file, but I've added
$this->autoRender = false;
and everything went well after that.
This post was helpful: Corrupted .docx download using phpdocx
Thanks to burzum as well for the hint with MediaView.
My site uses bookmarklets to gather data from external sites, kinda like Pinterest. I'm concerned about security and want to move the images the bookmarklet gathers from the doc root up one level. My script has some hefty security checks in place, but I want to add this as a last line of defense.
How do I access my images within my script? Obviously using ../userimages/id/image.jpg wont work. I'm using Apache.
Thanks!
Proxy the image
You would use a proxy script to feed the images through like the following example:
// open the file in a binary mode
$name = '../userimages/id/image.jpg';
$fp = fopen($name, 'rb');
// send the right headers
header("Content-Type: image/png");
header("Content-Length: " . filesize($name));
// you may like to set some cache headers here
// dump the picture and stop the script
fpassthru($fp);
exit;
This example is from the PHP manuals fpassthru() page. You would save this script somewhere in your servers document root/httpdocs folder.
"Spoofing" the URL to the image
The easiest way to give the PHP file the appearance of being an image file to a user/browser is to use Apaches mod_rewrite. Usually I use a URL structure something like this:
http://www.example.org/image-id/image.png
Where image-id is the unique identifier for that particular image. This way the file has the correct extensions of an image instead of .php.
I have a PDF which has an image on it (a logo)
I want to open it with PHP and write some text over it and save it back to the file system.
I've done this with the Zend framework before but this project is using code igniter so I need either a standalone lib or a code igniter plugin.
thanks
Zend_Pdf is a standalone lib.
Zend Framework is deliberately designed with a Use-At-Will architecture, so you can use most components with no (or very little) dependencies on other components in the framework.
To use Zend_PDF in Code Igniter, place the Zend/Pdf folder into your CI project's include path, so it is accessible. Then include it with
// Load Zend_Pdf class
require_once('Zend/Pdf.php');
See this (general) tutorial:
http://devzone.zend.com/article/2525
In my esperience, the first question you should ask is: where the original pdf come from?
Do you want to create the pdf from php, as a template, and then insert the text on it in a second time?
Or you create the pdf in other ways, and then fulfill it via php?
In the first case, go with zend pdf, and write down your class to handle it.
In the second case, you may want to take a look to pdftk, that allow you to merge an fdf file with a PDF file.
Here an example with a forms (and the createFDF.php file), but the behavior can be applyed in many other ways...
Its quite simple:
<?php
require_once('createFDF.php');
$data = array(
'field_1' => 'Text 1',
'Field_2' => 'Text 2
);
$FDF_file = 'myfile.fdf';
$PDF_file = 'mypdf.pdf';
$PDF_source = 'your-pdf-original-file.pdf';
$fdf_data = createFDF($PDF_source, $data);
$fh = fopen($FDF_file, 'w');
fwrite($fh, $fdf_data);
fclose($fh);
?>
<h2>File FDF created.</h2>
<?php
passthru("pdftk $PDF_source fill_form $FDF_file output $PDF_file flatten");
?>
<h2>Pdf merged.</h2>
but, you will need to create the original pdf file with forms within by hand (or, as far as i know, there is no tools to create it via php)
You can try to set the following header:
<?php
// We'll be outputting a PDF
header('Content-type: application/pdf');
// The PDF source is in original.pdf
readfile(base_url($pdf->URL));
?>