PHP: How to resolve a relative url
Asked Answered
E

7

29

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.

Extracurricular answered 7/8, 2009 at 7:25 Comment(4)
This code did the trick for me : sourceforge.net/projects/absoluteurlExtracurricular
Here's a comparison of some of the solutions mentioned plus one of my own.Geier
dublicates: #4444975 #11654177 #19619254 #26424404Ratiocinate
almost duplicate: #14884001Ratiocinate
P
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;
    }
?>
Puissance answered 7/8, 2009 at 7:29 Comment(2)
This implementation doesn't work if the base URL is: foobar.com With no trailing /. It also doesn't respect port numbers in base URLs.Shoran
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.Summerlin
F
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

Fribble answered 18/3, 2019 at 9:57 Comment(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; } `Cascade
S
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
Saimon answered 18/5, 2012 at 6:11 Comment(2)
FYI, the http_build_url method is part of a PECL extension that is not bundled with PHP.Augustus
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.6Beyrouth
R
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'); 
Ratiocinate answered 4/1, 2016 at 8:12 Comment(0)
A
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";
Ali answered 3/12, 2017 at 13:11 Comment(0)
S
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.
Sinkage answered 9/9, 2011 at 13:24 Comment(1)
On linux boxes but not windows? This seems to be the only solution so far that is not even worth considering.Geier
S
-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,

Shout answered 18/4, 2016 at 19:42 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.