I noticed using Drupal in an internal network that file_create_url() function always returns absolute URL. This is annoying in some case, using an Apache proxy in front of a Drupal, to access it from the outside web, the URL won't be correct.

function file_create_url($path) {
  // Strip file_directory_path from $path. We only include relative paths in urls.
  if (strpos($path, file_directory_path() . '/') === 0) {
    $path = trim(substr($path, strlen(file_directory_path())), '\\/');
  }
  switch (variable_get('file_downloads', FILE_DOWNLOADS_PUBLIC)) {
    case FILE_DOWNLOADS_PUBLIC:
      return $GLOBALS['base_url'] .'/'. file_directory_path() .'/'. str_replace('\\', '/', $path);
    case FILE_DOWNLOADS_PRIVATE:
      return url('system/files/'. $path, NULL, NULL, TRUE);
  }
}

If we look at this, we see the hard-coded $GLOBALS['base_url']

When looking at the url() function, we see the optional $absolute parameter, which default is FALSE. url() function always make relative to pathes to the server's document root if we don't set this parameter when we call it.

Would it be better to have the same behavior as the url() function ? In some cases, this would be better to let the module developer the choice of using relative URL.

I join to this post how could be a patch for this.

If always return an absolute URL for files is really a feature, I'd like to know why this seems to be necessary for you.

Support from Acquia helps fund testing for Drupal Acquia logo

Comments

pounard’s picture

I forgot to say what is really annoying about this for my use. Image and ImageField modules uses this method a lot, and I don't need absolute URL for images.

In the majority of cases, where files are somewhere in the docroot, I don't see the need of absolute URL at all. May be the files dir can be outside of the base_path(), case in which my patch seems to be broken, in this case another way to construct the URL is needed.

But the fact is, I need relative URL for images.

Errata:

When looking to the File System administration page, we can see: A file system path where the files will be stored. This directory has to exist and be writable by Drupal. If the download method is set to public this directory has to be relative to the Drupal installation directory, and be accessible over the web..

This makes absolutely obsolete to force absolute URL for public files.

Second errata:

I saw that $base_url is written from the HTTP_HOST user imput, which permits any proxy to rewrite this input in one way (from the user to Apache), but in some case with weird DNS handling (In my company we don't have admin rights on DNS entries, so we can't tell Apache to have an external DNS virtual host, for security reasons), absolute URL break all the proxies pass-through process.

c960657’s picture

See also #207310: Allow rewriting of file URL's using custom_url_rewrite_outbound(). This will allow you to make the generated URLs relative by setting $options['absolute'] to FALSE in your custom custom_url_rewrite_outbound().

pounard’s picture

Thanks.

Xen’s picture

While the custom_url_rewrite_outbound() solution works for getting things done, it doesn't fix the real problem.

That this function returns a relative or absolute path depends on whether you're using public or private downloads doesn't make any sense, and either breaks code or forces developers to take it into account in their modules.

I suggest that the function gets changed to return relative paths for private downloads:

return url('system/files/'. $path);

Damien Tournoud’s picture

Status: Active » Closed (won't fix)

Well, just set the correct $base_url if Drupal can't guess the correct one.

Xen’s picture

Version: 5.7 » 6.4
Status: Closed (won't fix) » Active
FileSize
1.04 KB

Bumping up to current version.

Xen’s picture

Damien:
The problem isn't the $base_url, the URLs are quite all right, the problem is that they're absolute (starts with 'http://', not relative (starts with '/system/files').

This not only breaks locale.module (#250451), possibly color.module (I suspect), proxys but also makes the life difficult for those using private downloads in general.

Using public downloads isn't an option in our case, as we're using private downloads because the file directory is set to a PHP stream handler that stores the files on Amazon S3. Apart from putting storage outside, it also means that we're able to run a cluster of Drupal installations without having to NFS mount file directories everywhere (which is a blessing when the servers is hosted different places).

It seems that the 'private is absolute' problem goes back to at least 4.7, but I haven't been able to find a reason for it. And it seems to break existing modules (even in core!) because they've only been tested with public downloads. So fixing file_create_url would actually *fix* multiple modules.

naxoc’s picture

subscribing

pounard’s picture

+1

Xen’s picture

After some fiddling about I've found that the proper way to get a 'local' URL is:

$path = file_create_url(file_create_path("something"));
$path = substr($dir, strlen($GLOBALS['base_url']));

While this is pretty strait forward, it might be wise to make this part of file_create_url, or creating a file_create_relative_url helper function, or documenting it better.

Murz’s picture

subscribe. Xen.dk, thanks for solution!

neochief’s picture

subscribing

grendzy’s picture

Title: file_create_url only returns absolute URL (containing the domain name) » Make file_create_url() absolute/relative URL behavior consistent for private and public files
Version: 6.4 » 7.x-dev
Category: support » feature
Status: Active » Needs review
FileSize
1.33 KB

Moving to 7.x. A new patch is attached. I also changed the default value to $absolute = FALSE, which is more logical and consistent with previous behavior, IMO (since relative is the default in l() and url(), and relative URLs generally seem to be favored in Drupal).

BTW there's some other work on file_create_url over here: http://drupal.org/node/238299

drewish’s picture

subscribing

webchick’s picture

Priority: Minor » Normal
Status: Needs review » Needs work

Yay! I like this patch very much. :) I ran into this headache with theme_image() (see #238681: theme_image() not evaluating properly?.

+      $base = ($absolute ? $GLOBALS['base_url'] . '/' : base_path());

Is there a reason that's in parentheses?

Also... we need some tests. :)

webchick’s picture

Hm. Further, this is not giving me the output I expect. What I want out of file_create_url() is system/files/cat.jpg. What I'm getting instead is /drupal/system/files/cat.jpg. (Drupal is installed in a subdirectory, "drupal.") Mind you, this is much better than http://localhost/drupal/system/files/cat.jpg I was getting before that caused theme_image() to choke.

I confirm that #238681: theme_image() not evaluating properly? solves this issue, but I'd love to fix this in file_create_url() instead, if we can.

Some rough steps to reproduce the behaviour I'm experiencing without this patch:

1. Go to Administer >> Site configuration >> File management
2. Set your File download type to Private, and move your files directory outside of webroot.
3. Stick an image in there, such as cat.jpg
4. In your theme somewhere, put:

<div id="image-goes-here">
<?php
 theme('image', file_create_url('cat.jpg')); // need to wrap this in file_create_url() because it is a private file.
?>
</div>

5. Notice that there is nothing printed between the div tags.
6. Bang face into desk for 25 minutes.

drewish’s picture

webchick, i'd be great if we could get #329226: Store file_test.module's values in variables rather than globals committed so that we could then get #238299: file_create_url should return valid URLs which already has some file_create_url() tests written.

grendzy’s picture

Hmm. Well this is sticky -- I see why you would want system/files/cat.jpg -- so that you have something that can be passed into url() and l(). This won't really work either though -- because if you are using public files, and non-clean URLs, what you will get is this:

/index.php?q=sites/default/files/cat.jpg. Which is no good.

It might not be possible to fix this without some bigger changes.

  • Make url() smarter to understand files?
  • Banish non-clean URLs?

Other ideas?

Xen’s picture

webchick:
I'd suggest that what you want should be obtainable with another new function. As I see it, file_create_url returns a path that's directly usable in html output, while what you want is something akin to Drupal paths (like "node/3").

Really, I'd expect (from the name) that file_create_path to do something like that, but it has a different purpose.

grendzy’s picture

Title: Make file_create_url() absolute/relative URL behavior consistent for private and public files » file_create_url only returns absolute URL (containing the domain name)
Status: Needs work » Needs review
FileSize
7.39 KB

OK, here's a new patch, including a unit test. I believe the test covers all the possible permutations of public / private files, and well as files inside and outside the docroot.

Here's the basic idea:

  • A new API function is introduced, file_check_private(). (I'm not convinced this is the most optimal function name, so suggestions are welcome). This gives you the internal Drupal path to a file, that's suitable for being passed to l(), url(), or theme functions.
  • A tiny change is made to url() to allow it to handle filepaths:
    -  if ($clean_url) {
    +  if ($clean_url || file_exists($path)) {
    

    This will probably have a small performance impact for those sites with clean urls disabled. My intuition is that the performance hit will be very small, and also the number of sites running without clean URLs is small.

  • file_create_url() is marked as deprecated, with the idea that it would be phased out by the time D7 is released.
  • I also changed the issue title back, the original was more accurate (I initially misunderstood why it was returning absolute URLs)
  • This approach would also solve #207310: Allow rewriting of file URL's using custom_url_rewrite_outbound()
  • But probably not help with #238681: theme_image() not evaluating properly?. Even with a relative path, by the time it gets to the theme layer it's too late, because private or non-clean paths could still interfere.

Status: Needs review » Needs work

The last submitted patch failed testing.

grendzy’s picture

Status: Needs work » Needs review
FileSize
7.39 KB

Status: Needs review » Needs work

The last submitted patch failed testing.

pounard’s picture

I saw the system message that says the patch don't patch anymore.

What is the status of this bug? Is there someone that will one day or another, make the choice of using relative url as default behavior for this function ?

I have have a lot of problems with D5 and D6 behind proxies or load balancers, and patching the core each new release which comes out is really anoying, and I think it's the same for a lot of people.

In all cases, Drupal 7 has no stable release today (or no public one has I saw), may be it's the right time to apply it upstream, else it will probably wait for Drupal 8, then 9, then 10... Plus the behavior of url() function is per default to always make relative urls, this is an inconsistency in Drupal API that the file_create_url() has a different behavior.

grendzy’s picture

pounard, thanks for your feedback. I'm still working on this. Regarding the system message, I'm a little stuck on that. The patch passes all the tests on my system, but not on Drupal's test system. After pondering this a bit more, I plan to try another patch that gets rid the proposed file_check_private and lets url() handle everything, which should also help #238681.

grendzy’s picture

Status: Needs work » Needs review
FileSize
7.39 KB
grendzy’s picture

FileSize
8.59 KB

New approach:

This patch enhances url() to understand files. This means file_create_url() is no longer needed.

I believe this will fix #207310: Allow rewriting of file URL's using custom_url_rewrite_outbound() and #238681: theme_image() not evaluating properly?.

Status: Needs review » Needs work

The last submitted patch failed testing.

grendzy’s picture

Status: Needs work » Needs review
FileSize
5.62 KB
2.74 KB

Keeping up with head. Also splitting the test into a separate patch, because of a glitch with the test bot.

Status: Needs review » Needs work

The last submitted patch failed testing.

Wim Leers’s picture

Subscribing.

grendzy’s picture

Status: Needs work » Closed (duplicate)

file_create_url has been reworked with the streamwrapper patch: #227232: Support PHP stream wrappers

_grizly’s picture

Surely simplest is best:

//relativise: 
$image_url = parse_url ($image_url, PHP_URL_PATH);

if needed in a function:

function relative_url($url){
    return parse_url($url, PHP_URL_PATH);
}

This way, if the drupal installation happens to exist outside the root dir, relative URI's will still work.