How do I link to a file in the Drupal files directory when when it's located outside of the webroot path (ie, the Download Method is set to Private)? To clarify that question a bit, let me explain my current configuration.
My Drupal environment is installed in this location:
/path/to/drupal/
This folder contains two subdirectories:
htdocs/ - the webroot, containing the Drupal installation and modules
files/ - separate files directory outside of webroot for uploading/downloading files
Under the Administration Settings module, I have this configured for the two file system options:
File system path: /path/to/drupal/files
Download method: Private
So, if I copy a file to this files/ directory, I can't simply create a direct link to it because it's not visible under the webroot. I noticed that user avatars are stored in this directory, however, and are still displayed correctly. I did some digging through the code, and determined that it's being set with the file_create_url() function, which creates a sort of virtual path like "/system/files/avatars/picture-1.jpg". This works fine, so I duplicated this code in my own page, but it doesn't work. Specifically:
- I created a new directory named /path/to/drupal/files/software/ to hold the files I want to make available for download.
- I copied a test file to this directory, apeinfo_changelog.txt, and verified that the file permissions are correct.
- I used the following link hyperlink on my page to link to it:
<a href="<?php=file_create_url(variable_get('file_directory_path', 'files').'/software/apeinfo_changelog.txt')?>">ChangeLog</a>
This creates a link to http://www.example.com/system/files/software/apeinfo_changelog.txt, which is exactly that I'd expect. However, if I click the link, I get a "Page not found" error message displayed by Drupal (as opposed to my web server issuing a 404 or similar error).
Any suggestions? This seems like something that should be pretty easily doable, but I'm fresh out of ideas right now. To add further insult to injury, while testing the file permissions I tried copying the picture-1.jpg avatar I mentioned above to my software directory and renaming it apeinfo_changelog.txt (the exact name as the other file). If I try accessing this file, it loads, even though it's a JPEG with a .txt extension now! If I replace it with the original file, it fails again. WTF?
I'd definitely appreciate some assistance here. Thanks.
Comments
So the question is what is
So the question is what is so special about that avatar file... Could it be the owner? (apache vs you)
You're trying too hard...
You're trying too hard... Simply link like this;
<img src="/system/files/img_110.jpg" width="212" />This is taken straight from a page that works... I believe you're simply missing the first '/'!
Pobster
Actually he said that it
Actually he said that it worked for an uploaded file but not for a file which he put there himself.
Ah right, I did skim that
Ah right, I did skim that post - I thought the query was regarding the stupidly long href tag.
Don't forget you need to set permissions for the directory you've created as well as for the file inside it.
Pobster
Thanks for the replies
Thanks for the replies. I originally thought it was a permissions issue as well, but I've checked and rechecked multiple times. Here are the permissions:
As you can see, they're identical. However, this link works:
http://www.example.com/system/files/software/test.jpg
while this link does not:
http://www.example.com/system/files/software/test.txt
Any other ideas? Is there some reason it just doesn't seem to display or load anything other than image files?
Private downloads
Don't private downloads need an entry in the files table in your database?
Your working file that was uploaded through Drupal probably has an entry, while Drupal doesn't know about the one you put there manually.
--
Anton
New to Drupal? | Forum posting tips | Troubleshooting FAQ
Well, I tried that as well.
Well, I tried that as well. I can copy another image file to that same directory, give it any arbitrary name (new_image.jpg, for example), and then view it just fine using http://www.example.com/system/files/software/new_image.jpg. This image has not been uploaded through Drupal at all, but it will still display it. This issue only seems to affect non-image files.
Hmm... I was thinking that
Hmm... I was thinking that the files had to be registered in the 'files' table during uploading, but if the 'test.txt' file was "properly" uploaded by Drupal then this is not the case...
Partly verified in 4.7
Verified in a 4.7 installation, but only partly.
1. Uploading files into the private directory with FTP (owned by myself):
A txt file gives me a Page not Found.
A png file gives me a Page not Found.
A jpg file is displayed!
2. Uploading the files with Drupal as attachments, using the core upload module:
All three files are displayed!
----------------
I should also mention that the private files directory was owned by myself and set to 777 (I always create those myself to avoid owner permission annoyances). Also, it contained the small .htaccess with the Options +Followsymlinks directive.
Thanks for continuing to
Thanks for continuing to look into this, CogRusty. Unfortunately, attaching these files to a post using the Upload module isn't really feasible for me. Some of the files may be rather large (larger than the default 2MB upload limit, for example), I may link to them form multiple locations other than the associated post, etc.
I've done some more work on my test site, and have a page that perfectly illustrates the problem:
http://drupal.legroom.net/software/clihelp
Try clicking on the screenshot link to load a full version of it, then try clicking on the ChangeLog link. The screenshot will load, but the ChangeLog will not. Both files were copied to the web server in the exact same way, have the same permissions, and are linked in the source code exactly the same.
Why does it give me a "Page not found" error for the text file when it can load the image just fine?
By the way, I was wondering
By the way, I was wondering why you have been using the (slower and fussier) private method if you want to link to them anyway. If you are doing it to be able to control access through Drupal, doesn't bypassing Drupal defeat the purpose?
I'm not trying to bypass
I'm not trying to bypass Drupal. :-) I'd absolutely like Drupal to be able to control access to the files, and in particular track file hits. The file upload mechanism just doesn't meet my needs. Is there perhaps a separate way that I can inform Drupal about the files? Some manual input method?
Hmm.... something like a
Hmm.... something like a small php script which adds an entry to the files table with the file name and with the next available file ID taken from the sequences table (and incrementing that). Maybe something with the file revisions table as well... I have not checked how exactly that works.
This might make the file links work. Not sure if it would help with access control though, since most access control in Drupal is node oriented. That's why I wondered about the point of using private files.
I read a thread a while back about file access control using the filemanager+attachment modules instead of the core upload module (http://drupal.org/node/68411). Maybe that one also suffers from the 2MB limitation, I am not sure. Filemanager is used by the popular acidfree module for private storage, and they contribute patches for better file access control from time to time.
About increasing the 2MB
About increasing the 2MB upload limit for PHP:
http://drupal.org/node/97193
Are any devs reading this?
I looked into the files table that CogRusty mentioned in his last post, and in doing so I found how/where this is actually being called. The file_download() function in includes/file.inc is what's returning the "Page not found" error message, and it appears to be because this call fails:
$headers = module_invoke_all('file_download', $filepath);That's on line 592 of my install of Drupal 4.7.5 (I think it's .5 - how do I actually check that?). After doing some more searching, it appears that this 'file_download' function/hook referenced in that line is coming from modules/user.module:
As far as I can tell, this will ONLY allow images through, and will always fail otherwise. I don't understand this. Obviously there's a need to allow images (for user avatars, for example), but if I'm reading this correctly then NOTHING will ever be allowed for download, as it will fail the if test.
While searching I also found this defined in files/hooks/core.php:
It seems to me that this is the code that should be running instead, as it checks for a match in the fileupload table, and then checks to ensure that access is permitted. As far as I can tell, though, this code isn't executed at all. In fact, I don't even have a fileupload table in my Drupal database.
What's the deal? Do I perhaps have an error in my configuration? Is this a bug in Drupal? Or am I just way off in my interpretation of the code (always possible)?
You can find your exact
You can find your exact Drupal version in CHANGELOG.txt or, more reliably, at the beginning of system.module.
Here is how I understand what is happening.
module_invoke_all() checks for all modules which have their own modulename_file_download function. At least one module (any module) must respond with headers, which means "this file is mine, go ahead and let them download it". If no module responds, then function file_transfer() in file.inc won't download the file.
user_file_download() in user.module checks whether the requested file is in the 'pictures' directory and is properly named, and if so says "it's mine, here are the headers". In core, there are two modules which implement this hook: user (user_file_download) and upload (upload_file_download). None of them should be relevant in your case. There are others in contributed modules, for example image.module has an image_file_download() function. All of them are invoked.
Your last code snippet is probably from an example for creating a module of yours. There is no 'fileupload' table in the database in core.
So, what I think is happening is that when the file is an image then the hook implementation in some module responds. Maybe image.module, if it is installed. What it checks is:
No check for file name or extension, just for "image info" and for non-zero size.
Maybe this hook is a good place to put custom file access control code in a module of yours (for example by user role), since core provides mostly node access control.
----------
Edited to add: upload_file_download() in upload.module is the one checking the files table and providing some file access control. Perhaps this is the one to rely on if you update the files table.