I've got call recordings stored on a CDN (Rackspace CloudFiles) with names like:
KPOWIEJFIE2034020SVN10ASKZALBMRI.mp3
(They're Twilio Call SIDs).
My application (CakePHP) displays a list of these recordings, and uses an <audio> tag to allow them to be played on the page right from the CDN. I need to add the option to download the file directly as well, but I want to download the file named something more like this:
Call from Alex to Firm, Inc, 8/19/2011 4:00pm.mp3
I know how to do this with PHP and readfile() to set the name to whatever I'd like, but that requires the file data be streamed through my VPS. Besides being billed twice for the bandwidth (once through the CDN and once through my VPS), this would defeat the purpose of having my files on a CDN (speed and availability). I have the files named only with the call SID on the CDN for security.
Can I do this with browser-side somehow? Can JavaScript change the name of a downloaded file on the fly?
I appreciate any help!
The filename is based on the URL being requested and the headers attached to the response when the URL is fetched. Unless you proxy it, or can arrange for a "friendly" url to point at the resource, you have no control over how the browser will download the file, since the request will be handled by the rackspace servers, not your code.
JS cannot control the fetch/download process, other than possibly mangling the url that goes into the <audio> tag. but then you're limited to whatever mangling still allows the URL to be relevant to the cloudfiles servers.
You should be able to set an object's Content-Disposition when creating the file. For example,
PUT /<api version>/<account>/<container>/<object> HTTP/1.1
Host: storage.clouddrive.com
X-Auth-Token: 01234567-89ab-cdef-0123-456789abcdef
Content-Type: audio/mpeg
Content-Disposition: attachment; filename=Whatever_Filename_I_Want.mp3
The file will still be stored as whatever your <object> name is, but when retrieved it will appear as Whatever_Filename_I_Want.mp3.
Edit: According to the documentation, you can also update a file's metadata using this method.
From what I understand of the documentation, you're able to build a directory structure on the CDN for your purposes. So instead of relying on the security with the filename, you could put the security into the directory name. That way, http://cdn.example.com/KPOWIEJFIE2034020SVN10ASKZALBMRI.mp3 becomes http://cdn.example.com/KPOWIEJFIE2034020SVN10ASKZALBMRI/Call_from_Alex_to_Firm,_Inc,_8-19-2011_4:00pm.mp3, which will download exactly as you want it to be, without sacrificing the security.
Related
I'm using the PHP Azure library to create a SAS token for delivering files (File Storage, not blob, and not public) to an authenticated user of a site after they click a link. The link leads to a PHP page where I create the SAS and then issue a Location: header to invoke the downloads. There are no known issues with anything leading up to the final completion of the download.
In the attached image the steps 1-4 are repeated.. one for each file selected.
The issue is that as the URLs are external to the site (CDN) the Location: $url + sas is used. This alters the browser location but is not reflected in the browser... the headers push the file as an attachment.
As mentioned, he user can continue to click other download links on the originating page and they work fine (this is desirable) however the first time they select any link on that same page to go elsewhere on the originating page/site (shown as clicking "home" in the example, the browser true location is revealed (accountname.file.core.windows.net/etc ) and the user gets a Resource not found error originating from the Azure File Storage server. (shown under the chart)
7.10.2017 added this image to illustrate the process used:
Link to SAS process chart
I'm slightly at a loss on how to push a URL download from a CDN without the browser location getting "reset" in this manner. I've done this from local servers/files many times but the Location: $url/SAS aspect is a different dynamic. In my mind, this should behave no differently than pointing the browser to a publicly accessible URL with an EXE that pushes the download, and still allows the user to say on the originating site. I'm starting to think this should be a redirect instead of Location but I haven't found very many Azure/SAS/File Storage examples to back this up. I have to be missing something simple here but it's escaping me.
I am not sure if I get it correctly, you seem to wanna download the file from Azure storage without changing address in the browser by using PHP script.
Basically, you can use file_get_contents() function as below to achieve this.
header('Content-Type: application/octet-stream');
header("Content-Disposition: attachment; filename=$filename");
$response = file_get_contents($SASUri);
echo $response;
I have a CDN that is storing media files. Each file I want to store as just the ID number of the file in the data so, file.zip = 4423342 on the server. The problem is, the CDN does not allow php or any form of programming. How can I make it so I have a local file that is used to set the headers and let me change it so when they download a file from the CDN it downloads as file.zip instead of 4423342. Now I know this is simple if the file is stored on the same server as the file that sets the headers.
Thanks!
If your CDN doesn't allow you to set the name/headers of the stored file, you cannot do it on your server. The user gets directed to the CDN server, and you can't "inject" anything into that connection.
I would look into looking up the options your CDN does offer.
For example, you might be able to append something like /file.zip to the end of the CDN url, and still get the correct file.
That is – if the CDN url is http://cdn.example.com/113312, maybe http://cdn.example.com/113312/file.zip works as well.
I am looking for some input on something I have been thinking about for a long time. It is a very general problem, maybe there are solutions out there I haven't thought of yet.
I have a PHP-based CMS.
For each page created in the CMS, the user can upload assets (Files to download, Images, etc.)
Those assets are stored in a directory, let's call it "/myproject/assets", on a per-page basis (1 subdirectory = 1 page, e.g. "/myproject/assets/page19283")
The user can "un-publish" (hide) pages in the CMS. When a page is hidden, and somebody tries to access it because they have memorized the URL or they come from Google or something, they get a "Not found" message.
However, the assets are still available. I want to protect those as well, so that when the user un-publishes a page, they can trust it is completely gone. (Very important on judicial troubles like court orders to take content down ... Things like that can happen).
The most obvious way is to store all assets in a secure directory (= not accessible by the web server), and use a PHP "front gate" that passes the files through after checking. When a project needs to be watertight this is the way I currently go, but I don't like it because the PHP interpreter runs for every tiny image, script, and stylesheet on the site. I would like have a faster way.
.htaccess protection (Deny from all or similar) is not perfect because the CMS is supposed to be portable and able to run in a shared environment. I would like it to even run on IIS and other web servers.
The best way I can think of right now is moving the particular page's asset directory to a secure location when it is un-published, and move it back when it's published. However, the admin user needs to be able to see the page even when it's un-published, so I would have to work around the fact that I have to serve those assets from the secure directory.
Can anybody think of a way that allows direct Apache access to the files (=no passing through a PHP script) but still controlling access using PHP? I can't.
I would also consider a simple .htaccess solution that is likely to run on most shared environments.
Anything sensitive should be stored in a secure area like you suggested.
if your website is located at /var/www/public_html
You put the assets outside the web accessible area in /var/www/assets
PHP can call for a download or you can feed the files through PHP depending on your need.
If you kept the HTML in the CMS DB, that would leave only non-sensitive images & CSS.
If you absolutely have to turn on and off all access to all materials, I think your best bet might be symlinks. Keep -everything- in a non-web-accessible area, and sym link each folder of assets into the web area. This way, if you need to lock people out completely, just remove the symlink rather than removing all files.
I don't like it, but it is the only thing I can think of that fits your crtieria.
I'd just prevent hotlinking of any non-HTML file, so all the "assets" stuff is accessible only from the HTML page. Removing (or protecting) the page just removes everything without having to mess up the whole file system.
Use X-Sendfile
The best and most efficient way is using X-Sendfile. However, before using X-Sendfile you will need to install and configure it on your webserver.
The method on how to do this will depend on the web server you are using, so look up instructions for your specific server. It should only be a few steps to implement. Once implemented don't forget to restart your web server.
Once X-Sendfile has been installed, your PHP script will simply need to check for a logged in user and then supply the file. A very simple example using Sessions can be seen below:
session_start();
if (empty($_SESSION['user_id'])){
exit;
}
$file = "/path/to/secret/file.zip";
$download_name = basename($file);
header("X-Sendfile: $file");
header("Content-type: application/octet-stream");
header('Content-Disposition: attachment; filename="' . $download_name . '"');
Important note:
If you are wanting to serve the file from another webpage such as an image src value you will need to make sure you sanitize your filename. You do not want anyone overriding your script and using ".." etc. to access any file on your system.
Therefore, if you have code that looks like this:
<img src="myscript.php?file=myfile.jpg">
Then you will want to do something like this:
session_start();
if (empty($_SESSION['user_id'])){
exit;
}
$file = preg_replace('/[^-a-zA-Z0-9_\.]/', '', $_GET['file']);
$download_name = basename($file);
header("X-Sendfile: $file");
header("Content-type: application/octet-stream");
header('Content-Disposition: attachment; filename="' . $download_name . '"');
EDIT: How about a hybrid for the administrative interface? In the ACP you could access via the PHP method to, basically, send all file requests to the PHP authing file, but for public, you can use HTTP AUTH/htaccess to determine the availability of the result. this gives you the performance on the public side, but the protection on the ACP side.
OLD MESSAGE:
.htaccess is compatible with most Apache and IIS<7 environments (using various ISAPI modules) when using mod_rewrite type operations. The only exception is IIS7 + the new Rewrite module which uses the web.config file. HOWEVER, I'd be willing to be that you could efficiently generate/alter the web.config file for this instance instead of using .htaccess.
Given that, you could set up redirects using the rewrite method and redirect to your custom 404 Page (that hopefully sends the proper 404 header). It is not 100% appropriate because the actual asset should be the one giving a 403 header, but... it works.
This is the route I would go unless you want to properly create HTTP AUTH setups for every server platform. Plus, if you do it right, you could make your system extendable to allow other types in the future by you or your users (including a php based option if they wanted to do it).
I'm assuming the 'page' is being generated by PHP and the 'assets' should not require PHP. (Let me know if I got that wrong.)
You can rename the assets folder. For example, rename '/myproject/assets/page19283' to '/myproject/assets/page19283-hidden'. This will break all old, memorized links. When you generate the page for admin users that can see it, you just write the urls using the new folder name. After all, you know whether the page is 'hidden' or not. The assets can be accessed directly if you know the 'secret' url.
For additional security, rename the folder with a bunch of random text and store that in your page table (wherever you store the hidden flag): '/myproject/assets/page19283-78dbf76B&76daz1920bfisd6g&dsag'. This will make it much harder to guess at the hidden url.
Just prepend or append a GUID to the page name in the database and the resource directory in the filesystem. The admin will still be able to view it from the admin interface because the link will be updated but the GUID effectively makes the page undiscoverable by an outside user or search engine.
Store your information in a directory outside the web root (i.e.: one directory outside of public_html or htdocs). Then, use the readfile operator in a php script to proxy the files out when requested. readfile(...) basically takes a single parameter--the path to a file--and prints the contents of that file.
This way, you can create a barrier where if a visitor requests information that's hidden behind the proxy, even if they "memorized" the URL, you can turn them down with a 404 or a 403.
I would go with implementing a gateway. You set a .htaccess file to the /assets/ URL pointing to a gateway.php script that will deny if both the credentials are not valid and this particular file is not published or show it.
I'm a little confused. Do you need to protect also the stylesheet files and images? Perhaps moving this folder is the best alternative.
Im building a "premium" section of my site and Im in a need to give download access to files in a remote directly (on a different server), to users with special privileges (accounts stored in mysql db). My site is coded in php/mysql so a php solution would be great.
direct all download links to a php file that'll do all the credential checking.
you can call the file download.php
pass along parameters via cookies, get, post, session, or whichever manner you verify privileges.
once credentials are verified, you can send an appropriate header.
if it's an image, the header would be header("Content-type: image/jpeg");
i'm assuming that you also own this remote server.
some useful links:
MIME types
PHP Header Function
As #pxl said, you need to check for authorization and then output the correct mime type as an HTML header (like he said: header("Content-type: image/jpeg");)
Also, once you are done with that, you will need to output the actual contents of the file and it's size (in bytes) as such:
header("Content-Length: ".filesize("FILENAME")*1.001);
/* The *1.001 puts a nice buffer on the filesize, I read about it online.
Browsers will stop downloading exactly at the Content-Length, but if they go
over, it's not a big deal at all. */
readfile("FILENAME");
die();
Just make sure to store the file in a directory that is not accessible from the web.
I'm used to doing this in ASP.NET where it's built in, but this article seems to chronicle your exact situation.
Here's what I would do:
Built a PHP-SOAP-Sever on the remote server B that holds the files.
Whenever a user triggers a download on your main server A connect to the SOAP-Server on B and reserve a ticket for the user specifying an IP-address and the id/path of the file to download.
Server B will now create a ticketId(which should only be valid for a limited time) for this download and return it to A.
Server A redirects the user to Server B supplying the ticketId as a GET parameter
Server B now checks if the ticket was already used, is expired or if the user comes from the wrong IP. If none of them apply serve the file and mark the ticket as used.
Note: On server B don't keep PHP running while serving the file but use the X-Sendfile header instead. Otherwise the download might stop after the PHP max execution time.
I am trying to secure my PHP Image upload script and the last hurdle I have to jump is making it so that users cannot directly excecute the images, but the server can still serve them in web pages. I tried changing ownership and permissions of the folders to no avail, so I am trying to store the images above public_html and display them in pages that are stored in public_html.
My File Structure:
- userimages
image.jpg
image2.jpg
- public_html
filetoserveimage.html
I tried linking to an image in the userimages folder like this:
<img src="../userimages/image.jpg">
But it does not work. Is there something I am missing here? If you have any better suggestions please let me know. I am trying to keep public users from executing potentially dangerous files they may have uploaded. Just as an extra security measure. Thanks!
You want something that's basically impossible.
The way a browser loads a page (in a very basic sense) is this:
Step 1: Download the page.
Step 2: Parse the page.
Step 3: Download anything referenced in the content of the page (images, stylesheets, javascripts, etc)
Each "Download" event is atomic.
It seems like you want to only serve images to people who have just downloaded a page that references those images.
As PHP Jedi illustrated, you can pass the files through PHP. You could expand on his code, and check the HTTP_REFERER on the request to ensure that people aren't grabbing "just" the image.
Now, serving every image through a PHP passthru script is not efficient, but it could work.
The most common reason people want to do this is to avoid "hotlinking" -- when people create image tags on other sites that reference the image on your server. When they do that, you expend resources handling requests that get presented on someone else's page.
If that's what you're really trying to avoid, you can use mod_rewrite to check the referer.
A decent-looking discussion of hotlinking/anti-hotlinking can be found here
Use an image relay script!
To serve a imagefile that is outside the public_html folder you would have to do it by a php script. E.g make a image-relay.php that reads the image that is outside the public html...
<?php
header('Content-Type: image/jpeg');
$_file = 'myimage.jpg'; // or $_GET['img']
echo file_get_contents('/myimages/'.$_file);
?>
Now, $_file could be a $_GET parameter, but its absolutley important to validate the input parameter...
now you can make an <img src="image-relay.php?img=flower.jpg"> to access a flower.jpg image that is located in /myimage/flower.jpg ...
Well, a web browser will only be able to access files and folders inside public_html.
If the public_html directory is the root of the server for your users, Apache cannot serve anything that is not inside/below that dorectory.
If you want a file to be served by Apache directly, you'll have to put it in/below public_html.
I think your misunderstanding is in the fact that if you include an image in an <img> tag, your browser will send the exact same request to the webserver to fetch it, that will be sent to the webserver if you try to open the src url of the image in your browser directly.
Therefore, either both things work, or neither.
There are hacks around, involving a (php or other) script to make sure that an IP that has requested the image has also requested the html page within the last few seconds (which will not work if the user is behind a proxy that rotates outgoing IPs) or by checking the referer (which does not work with HTTPs and also not if the user has referer disabled).
If you want to make sure that only some users can see the image (both via <img> tag and directly), you can put the image outside public_html and have a (php or other) script that verifies the user's credentials before serving the image.
If you are using Apache or lighttpd you can use the X-Sendfile header to send files that are not in the web root(provided you haven't changed the configuration of mod_xsendfile).
To learn more about X-sendfile see this site.
This solution is giving you the best possible performance as PHP doesn't send the file but the server does and therefore PHP can be exited while the files are being served.
Hope that helps.