1<?php 2 3/* 4 5A spiritual port of Python's urlparse.urljoin() function to PHP. Why this isn't in the standard library is anyone's guess. 6 7Author: fluffy, http://beesbuzz.biz/ 8Latest version at: https://github.com/plaidfluff/php-urljoin 9 10 */ 11 12function urljoin($base, $rel) { 13 if (!$base) { 14 return $rel; 15 } 16 17 if (!$rel) { 18 return $base; 19 } 20 21 $uses_relative = array('', 'ftp', 'http', 'gopher', 'nntp', 'imap', 22 'wais', 'file', 'https', 'shttp', 'mms', 23 'prospero', 'rtsp', 'rtspu', 'sftp', 24 'svn', 'svn+ssh', 'ws', 'wss'); 25 26 $pbase = parse_url($base); 27 $prel = parse_url($rel); 28 29 if ($prel === false || preg_match('/^[a-z0-9\-.]*[^a-z0-9\-.:][a-z0-9\-.]*:/i', $rel)) { 30 /* 31 Either parse_url couldn't parse this, or the original URL 32 fragment had an invalid scheme character before the first :, 33 which can confuse parse_url 34 */ 35 $prel = array('path' => $rel); 36 } 37 38 if (array_key_exists('path', $pbase) && $pbase['path'] === '/') { 39 unset($pbase['path']); 40 } 41 42 if (isset($prel['scheme'])) { 43 if ($prel['scheme'] != $pbase['scheme'] || in_array($prel['scheme'], $uses_relative) == false) { 44 return $rel; 45 } 46 } 47 48 $merged = array_merge($pbase, $prel); 49 50 // Handle relative paths: 51 // 'path/to/file.ext' 52 // './path/to/file.ext' 53 if (array_key_exists('path', $prel) && substr($prel['path'], 0, 1) != '/') { 54 55 // Normalize: './path/to/file.ext' => 'path/to/file.ext' 56 if (substr($prel['path'], 0, 2) === './') { 57 $prel['path'] = substr($prel['path'], 2); 58 } 59 60 if (array_key_exists('path', $pbase)) { 61 $dir = preg_replace('@/[^/]*$@', '', $pbase['path']); 62 $merged['path'] = $dir . '/' . $prel['path']; 63 } else { 64 $merged['path'] = '/' . $prel['path']; 65 } 66 67 } 68 69 if(array_key_exists('path', $merged)) { 70 // Get the path components, and remove the initial empty one 71 $pathParts = explode('/', $merged['path']); 72 array_shift($pathParts); 73 74 $path = []; 75 $prevPart = ''; 76 foreach ($pathParts as $part) { 77 if ($part == '..' && count($path) > 0) { 78 // Cancel out the parent directory (if there's a parent to cancel) 79 $parent = array_pop($path); 80 // But if it was also a parent directory, leave it in 81 if ($parent == '..') { 82 array_push($path, $parent); 83 array_push($path, $part); 84 } 85 } else if ($prevPart != '' || ($part != '.' && $part != '')) { 86 // Don't include empty or current-directory components 87 if ($part == '.') { 88 $part = ''; 89 } 90 array_push($path, $part); 91 } 92 $prevPart = $part; 93 } 94 $merged['path'] = '/' . implode('/', $path); 95 } 96 97 $ret = ''; 98 if (isset($merged['scheme'])) { 99 $ret .= $merged['scheme'] . ':'; 100 } 101 102 if (isset($merged['scheme']) || isset($merged['host'])) { 103 $ret .= '//'; 104 } 105 106 if (isset($prel['host'])) { 107 $hostSource = $prel; 108 } else { 109 $hostSource = $pbase; 110 } 111 112 // username, password, and port are associated with the hostname, not merged 113 if (isset($hostSource['host'])) { 114 if (isset($hostSource['user'])) { 115 $ret .= $hostSource['user']; 116 if (isset($hostSource['pass'])) { 117 $ret .= ':' . $hostSource['pass']; 118 } 119 $ret .= '@'; 120 } 121 $ret .= $hostSource['host']; 122 if (isset($hostSource['port'])) { 123 $ret .= ':' . $hostSource['port']; 124 } 125 } 126 127 if (isset($merged['path'])) { 128 $ret .= $merged['path']; 129 } 130 131 if (isset($prel['query'])) { 132 $ret .= '?' . $prel['query']; 133 } 134 135 if (isset($prel['fragment'])) { 136 $ret .= '#' . $prel['fragment']; 137 } 138 139 return $ret; 140} 141