Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

getAbsoluteUrl not including the host #708

Open
michaelthedev opened this issue Feb 27, 2024 · 3 comments
Open

getAbsoluteUrl not including the host #708

michaelthedev opened this issue Feb 27, 2024 · 3 comments

Comments

@michaelthedev
Copy link

I use laragon for local development and running
I first noticed something like url('home.test')->getAbsoluteUrl() only returned the path (no scheme, no host)

After trying with request()->getUrl()->getAbsoluteUrl() the scheme showed here, but the host was empty
The above code produced this "https:///_manage/test/"

On inspection, i made this change in Pecee\Http\Request

$this->setHost($this->getHeader('http-host'));

// Check if special IIS header exist, otherwise use default.
$url = $this->getHeader('unencoded-url');
if ($url !== null) {
    $this->setUrl(new Url($url));
} else {
    $this->setUrl(new Url(urldecode((string)$this->getHeader('request-uri'))));
}

$this->url->setHost($this->host); // change: since host has already been set

$this->setContentType((string)$this->getHeader('content-type'));

and everything worked fine for:

var_dump(request()->getUrl()->getAbsoluteUrl());
var_dump(\url()->getAbsoluteUrl());

But url('home.page')->getAbsoluteUrl() still returned only the path (without the host & scheme)

If you could look into this. I can provide more information if you need

@N247S
Copy link

N247S commented Mar 11, 2024

Yup figured out the same, but I am afraid it is going to be a bit more complicated to fix than a simple setHost call.

The Url class has the following url-specific attributes: schema, host, port, username, password, path, params, fragment.
From all the mentioned attributes only the last 3 can be retrieved from the request_uri. The rest is more complicated as it may be found in several places (for example forewarding gives different headers than a direct request). Furthermore, when parsing an url malformed/illformed data should be considered as clients can put anything into any of the header fields of a request.

A quick googlesearch would reveal several attempts to 'reconstruct' an url-string from scratch, but I think it is better to make the Request class build an Url instance from the $_SERVER global with appropriate sanatization instead. It is simply faster and easier to manage the different components, as most of the parsing is already done.

@N247S
Copy link

N247S commented Mar 18, 2024

Well, I took a quick stab at a more proper way of building the Url iinstance. It is not tested against all environments and php-engines, but as far as I could test it, it should do a decent job. It would be wonderfull if those with more experiance and test capabilities would perfect this for a PR.

/**
     * Create url from global $_SERVER data.
     * Note, this does not include validation so be carefull when using the returned result! 
     * @param bool checkForwarded `true` to include forwarded headers when checking/obtaining fields. 
     */
    static function createUrl(bool $checkForwarded = true)
    {
        function s($name)
        {
            if (isset($_SERVER[$name]))
                return $_SERVER[$name];
            return null;
        }
        $url = new Url(s('REQUEST_URI'));

        // Checking if http(s) protocol is used, in that case try proper handling, otherwise hope for the best and take server protocol literally
        if (stripos((s('SERVER_PROTOCOL') != null ? s('SERVER_PROTOCOL') : (s('REQUEST_SCHEME') != null ? s('REQUEST_SCHEME') : '')), 'http') !== false) {
            if ((s('HTTPS') == 'on' || s('HTTPS') == 1) ||
                $checkForwarded ? (s('HTTP_X_FORWARDED_PROTO') == 'https') : false
            )
                $url->setScheme('https');
            else
                $url->setScheme('http');
        } else
            $url->setScheme((s('SERVER_PROTOCOL') != null) ? s('SERVER_PROTOCOL') : s('REQUEST_SCHEME'));

        // Setting the host from a request is messy, proper validstion or else substitution of internal/fixed variable is advised. 
        $host = null;
        if ($checkForwarded) {
            if (s('HTTP_X_FORWARDED_HOST') != null)
                $host = s('HTTP_X_FORWARDED_HOST');
        }
        if ($host == null && s('SERVER_NAME') != null)
            $host = s('SERVER_NAME');
        if ($host == null && s('HTTP_HOST') != null)
            $host = s('HTTP_HOST');
        // This is a default/fallback value as setting the hostname reliably requires environment/engine-dependent configuration. Handy quick-fix if all else fails.
        // if ($host == null)
        //    $host = Config::$_HOST_NAME;

        // some systems and/or header-variants include the port, this would strip out the host in case it is present
        if(stripos($host, ':') != false)
            $host = parse_url($host, PHP_URL_HOST);
        $url->setHost($host);

        if (s('SERVER_PORT') != null)
            $url->setPort(s('SERVER_PORT'));
        // Reset query-string as soms system removes parameters when multiple are present
        if (s('QUERY_STRING') != null)
            $url->setQueryString(s('QUERY_STRING'));
        // Following two properties may not be available with some php-engines, so don't rely on it being present with incomming requests. 
        if (s('PHP_AUTH_USER') != null)
            $url->setPassword(s('PHP_AUTH_USER'));
        if (s('PHP_AUTH_PW') != null)
            $url->setUsername(s('PHP_AUTH_PW'));

        return $url;
    }

The above can be used to generate an Url instance from the php-globals (i.e. $_SERVER). For a quick-fix the way you could use this is as follows:

// either one of following for obtaining the router instance
$router = new Router();
$router = SimpleRouter::router();
$router->getRequest()->setUrl(createUrl())

This will 'reset' the Url which I would advise to do close to / at the start of the routing/index script.

One side-note with obtaining the host property. Since http-host (which is used in the Request constructor) may include the port if non-standard, It may mess the workings of the Request class? I haven't done a deep-dive into where that host property is used. But I think it is safe to say it will mess with the workings of the Url class since everything is treating the host variable as if the port part is only set separately in its own designated variable. Hence the workaround prior to setting the host.

@michaelthedev
Copy link
Author

Usually, I make fixes for things like this myself

But looks like it's going to cause one problem or the other that's why I created the issue for the author to look into

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants