28

I need a function that given a relative URL and a base returns an absolute URL. I've searched and found many functions that do it different ways.

resolve("../abc.png", "http://example.com/path/thing?foo=bar")
# returns http://example.com/abc.png

Is there a canonical way?

On this site I see great examples for python and c#, lets get a PHP solution.

Paul Tarjan
  • 48,968
  • 59
  • 172
  • 213
  • 2
    Here's [a comparison](http://scraperblog.blogspot.com/2012/12/convert-relative-urls-to-absolute-in.html) of some of the solutions mentioned plus one of my own. – pguardiario Dec 10 '12 at 13:14
  • This code did the trick for me : http://sourceforge.net/projects/absoluteurl/ – Paul Tarjan Aug 13 '09 at 07:43
  • dublicates: http://stackoverflow.com/questions/4444475/transfrom-relative-path-into-absolute-url-using-php http://stackoverflow.com/questions/11653677/php-relative-urls-to-absolute-urls-conversion-with-eventually-base-href-html-tag http://stackoverflow.com/questions/19618754/convert-relative-url-to-absolute-url http://stackoverflow.com/questions/26423904/converting-relative-url-to-absolute – qdinar Jan 03 '16 at 16:04
  • almost duplicate: http://stackoverflow.com/questions/14883501/resolve-a-relative-path-in-a-url-with-php – qdinar Jan 03 '16 at 16:14

7 Answers7

8

Perhaps this article could help?

http:// nashruddin.com/PHP_Script_for_Converting_Relative_to_Absolute_URL

Edit: reproduced code below for convenience

<?php
    function rel2abs($rel, $base)
    {
        /* return if already absolute URL */
        if (parse_url($rel, PHP_URL_SCHEME) != '' || substr($rel, 0, 2) == '//') return $rel;

        /* queries and anchors */
        if ($rel[0]=='#' || $rel[0]=='?') return $base.$rel;

        /* parse base URL and convert to local variables:
         $scheme, $host, $path */
        extract(parse_url($base));

        /* remove non-directory element from path */
        $path = preg_replace('#/[^/]*$#', '', $path);

        /* destroy path if relative url points to root */
        if ($rel[0] == '/') $path = '';

        /* dirty absolute URL */
        $abs = "$host$path/$rel";

        /* replace '//' or '/./' or '/foo/../' with '/' */
        $re = array('#(/\.?/)#', '#/(?!\.\.)[^/]+/\.\./#');
        for($n=1; $n>0; $abs=preg_replace($re, '/', $abs, -1, $n)) {}

        /* absolute URL is ready! */
        return $scheme.'://'.$abs;
    }
?>
Taco de Wolff
  • 1,682
  • 3
  • 17
  • 34
Amber
  • 507,862
  • 82
  • 626
  • 550
  • This implementation doesn't work if the base URL is: http://foobar.com With no trailing /. It also doesn't respect port numbers in base URLs. – Tom Boutell Jun 02 '11 at 18:30
  • 2
    It's by design ignoring any RFC specs that are in use for that anyway. I would say it's ***guessing*** the absolute URL or a relative one. – hakre Sep 16 '11 at 15:40
7

Another solution in case you already use GuzzleHttp.

This solution is based on an internal method of GuzzleHttp\Client.

use GuzzleHttp\Psr7\UriResolver;
use GuzzleHttp\Psr7\Utils;

function resolve(string $uri, ?string $base_uri): string
{
    $uri = Utils::uriFor(trim($uri));

    if (isset($base_uri)) {
        $uri = UriResolver::resolve(Utils::uriFor(trim($base_uri)), $uri);
    }

    // optional: set default scheme if missing
    $uri = $uri->getScheme() === '' && $uri->getHost() !== '' ? $uri->withScheme('http') : $uri;

    return (string)$uri;
}

EDIT: the source code was updated as suggested by myriacl

Johannes Buchholz
  • 1,857
  • 19
  • 34
  • 1
    This code is a bit old, but the best solution in my opinion, here is the modern version ` use GuzzleHttp\Psr7\UriResolver; use GuzzleHttp\Psr7\Utils; function resolve($uri, $base_uri):string { $uri = Utils::uriFor($uri); if (isset($base_uri)) { $uri = UriResolver::resolve( Utils::uriFor($base_uri), $uri); } // optional: set default scheme if missing $uri = $uri->getScheme() === '' && $uri->getHost() !== '' ? $uri->withScheme('http') : $uri; return (string) $uri; } ` – myriacl Jan 29 '21 at 21:30
4

If your have pecl-http, you can use http://php.net/manual/en/function.http-build-url.php

<?php
$url_parts = parse_url($relative_url);
$absolute = http_build_url($source_url, $url_parts, HTTP_URL_JOIN_PATH);

Ex:

<?php
function getAbsoluteURL($source_url, $relative_url)
{
    $url_parts = parse_url($relative_url);
    return http_build_url($source_url, $url_parts, HTTP_URL_JOIN_PATH);
}
echo getAbsoluteURL('http://foo.tw/a/b/c', '../pic.jpg') . "\n";
// http://foo.tw/a/pic.jpg

echo getAbsoluteURL('http://foo.tw/a/b/c/', '../pic.jpg') . "\n";
// http://foo.tw/a/b/pic.jpg

echo getAbsoluteURL('http://foo.tw/a/b/c/', 'http://bar.tw/a.js') . "\n";
// http://bar.tw/a.js

echo getAbsoluteURL('http://foo.tw/a/b/c/', '/robots.txt') . "\n";
// http://foo.tw/robots.txt
Ronny Wang
  • 414
  • 4
  • 5
  • 2
    FYI, the `http_build_url` method is part of a PECL extension that is not bundled with PHP. – mpen Oct 18 '12 at 18:18
  • This works as described, but watch out when installing the pecl extension. The newly released 2.0 version now uses namespaces and does not provide this function directly. So I installed an older Version and it works perfectly for me: `pecl install pecl_http-1.7.6` – KTB Jan 10 '14 at 09:29
1

other tools that are already linked in page linked in pguardiario's comment: http://publicmind.in/blog/urltoabsolute/ , https://github.com/monkeysuffrage/phpuri .

and i have found other tool from comment in http://nadeausoftware.com/articles/2008/05/php_tip_how_convert_relative_url_absolute_url :

require_once 'Net/URL2.php';
$base = new Net_URL2('http://example.org/foo.html');
$absolute = (string)$base->resolve('relative.html#bar'); 
qdinar
  • 119
  • 7
1

Here is another function that can handle protocol relative urls

<?php
function getAbsoluteURL($to, $from = null) {
    $arTarget = parse_url($to);
    $arSource = parse_url($from);
    $targetPath = isset($arTarget['path']) ? $arTarget['path'] : '';

    if (isset($arTarget['host'])) {
        if (!isset($arTarget['scheme'])) {
            $proto = isset($arSource['scheme']) ? "{$arSource['scheme']}://" : '//';
        } else {
            $proto = "{$arTarget['scheme']}://";
        }
        $baseUrl = "{$proto}{$arTarget['host']}" . (isset($arTarget['port']) ? ":{$arTarget['port']}" : '');
    } else {
        if (isset($arSource['host'])) {
            $proto = isset($arSource['scheme']) ? "{$arSource['scheme']}://" : '//';
            $baseUrl = "{$proto}{$arSource['host']}" . (isset($arSource['port']) ? ":{$arSource['port']}" : '');
        } else {
            $baseUrl = '';
        }
        $arPath = [];

        if ((empty($targetPath) || $targetPath[0] !== '/') && !empty($arSource['path'])) {
            $arTargetPath = explode('/', $targetPath);
            if (empty($arSource['path'])) {
                $arPath = [];
            } else {
                $arPath = explode('/', $arSource['path']);
                array_pop($arPath);
            }
            $len = count($arPath);
            foreach ($arTargetPath as $idx => $component) {
                if ($component === '..') {
                    if ($len > 1) {
                        $len--;
                        array_pop($arPath);
                    }
                } elseif ($component !== '.') {
                    $len++;
                    array_push($arPath, $component);
                }
            }
            $targetPath = implode('/', $arPath);
        }
    }

    return $baseUrl . $targetPath;
}

// SAMPLES
// Absolute path => https://www.google.com/doubleclick/
echo getAbsoluteURL('/doubleclick/', 'https://www.google.com/doubleclick/insights/') . "\n";
// Relative path 1 => https://www.google.com/doubleclick/studio
echo getAbsoluteURL('../studio', 'https://www.google.com/doubleclick/insights/') . "\n";
// Relative path 2 => https://www.google.com/doubleclick/insights/case-studies.html
echo getAbsoluteURL('./case-studies.html', 'https://www.google.com/doubleclick/insights/') . "\n";
// Relative path 3 => https://www.google.com/doubleclick/insights/case-studies.html
echo getAbsoluteURL('case-studies.html', 'https://www.google.com/doubleclick/insights/') . "\n";
// Protocol relative url => https://www.google.com/doubleclick/
echo getAbsoluteURL('//www.google.com/doubleclick/', 'https://www.google.com/doubleclick/insights/') . "\n";
// Empty path => https://www.google.com/doubleclick/insights/
echo getAbsoluteURL('', 'https://www.google.com/doubleclick/insights/') . "\n";
// Different url => http://www.yahoo.com/
echo getAbsoluteURL('http://www.yahoo.com/', 'https://www.google.com') . "\n";
Joyce Babu
  • 19,602
  • 13
  • 62
  • 97
0
function absoluteUri($Path, $URI)
{   # Requires PHP4 or better.
    $URL = parse_url($URI);
    $Str = "{$URL['scheme']}://";

    if (isset($URL['user']) || isset($URL['pass']))
        $Str .= "{$URL['user']}:{$URL['pass']}@";

    $Str .= $URL['host'];

    if (isset($URL['port']))
        $Str .= ":{$URL['port']}";

    $Str .= realpath($URL['path'] . $Path); # This part might have an issue on windows boxes.

    if (isset($URL['query']))
        $Str .= "?{$URL['query']}";

    if (isset($URL['fragment']))
        $Str .= "#{$URL['fragment']}";

    return $Str;
}

absoluteUri("../abc.png", "http://example.com/path/thing?foo=bar");
# Should return "http://example.com/abc.png?foo=bar" on Linux boxes.
Mark Tomlin
  • 8,593
  • 11
  • 57
  • 72
  • 2
    On linux boxes but not windows? This seems to be the only solution so far that is not even worth considering. – pguardiario Dec 06 '12 at 15:05
-1

I noticed the upvoted answer above uses RegEx, which can be dangerous when dealing with URLs.

This function will resolve relative URLs to a given current page url in $pgurl without regex. It successfully resolves:

/home.php?example types,

same-dir nextpage.php types,

../...../.../parentdir types,

full http://example.net urls,

and shorthand //example.net urls

//Current base URL (you can dynamically retrieve from $_SERVER)
$pgurl = 'http://example.com/scripts/php/absurl.php';

function absurl($url) {
 global $pgurl;
 if(strpos($url,'://')) return $url; //already absolute
 if(substr($url,0,2)=='//') return 'http:'.$url; //shorthand scheme
 if($url[0]=='/') return parse_url($pgurl,PHP_URL_SCHEME).'://'.parse_url($pgurl,PHP_URL_HOST).$url; //just add domain
 if(strpos($pgurl,'/',9)===false) $pgurl .= '/'; //add slash to domain if needed
 return substr($pgurl,0,strrpos($pgurl,'/')+1).$url; //for relative links, gets current directory and appends new filename
}

function nodots($path) { //Resolve dot dot slashes, no regex!
 $arr1 = explode('/',$path);
 $arr2 = array();
 foreach($arr1 as $seg) {
  switch($seg) {
   case '.':
    break;
   case '..':
    array_pop($arr2);
    break;
   case '...':
    array_pop($arr2); array_pop($arr2);
    break;
   case '....':
    array_pop($arr2); array_pop($arr2); array_pop($arr2);
    break;
   case '.....':
    array_pop($arr2); array_pop($arr2); array_pop($arr2); array_pop($arr2);
    break;
   default:
    $arr2[] = $seg;
  }
 }
 return implode('/',$arr2);
}

Usage Example:

echo nodots(absurl('../index.html'));

nodots() must be called after the URL is converted to absolute.

The dots function is kind of redundant, but is readable, fast, doesn't use regex's, and will resolve 99% of typical urls (if you want to be 100% sure, just extend the switch block to support 6+ dots, although I've never seen that many dots in a URL).

Hope this helps,

Aaron Gillion
  • 2,227
  • 3
  • 19
  • 31