slimphp / Slim

Slim is a PHP micro framework that helps you quickly write simple yet powerful web applications and APIs.
http://slimframework.com
MIT License
11.98k stars 1.95k forks source link

Router::dispatch() directs to '/' when $_SERVER['SCRIPT_NAME'] == $_SERVER['REQUEST_URI'] #2107

Closed DanKottke closed 5 years ago

DanKottke commented 7 years ago

I'm trying to incorporate Slim v 3.x and encountered a problem when $_SERVER['SCRIPT_NAME'] and $_SERVER['REQUEST_URI'] are identical (in our case, say both equal '/documentation/test/foo'). In this case, Slims Uri::createFromEnvironment() function sets the basePath property of the $uri object to '/documentation/test/foo' and the path property to '/'. When Router::dispatch() fires, it ignores the basePath and attempts to route to '/'.

I may have a naive understanding of this virtualPath and basePath distinction that Uri::createFromEnvironment() is making, but one would imagine that in a clean apache httpd.conf case, without virtual hosts or re-write rules configured, that you would encounter this situation where SCRIPT_NAME == REQUEST_URI commonly. Shouldn't Router::dispatch() incorporate the "basePath" in the uri used to dispatch the request? Something like:

    $basePath = rtrim($request->getUri()->getBasePath(), '/');
    $path = ltrim($request->getUri()->getPath(), '/');

    if (!empty($path) || (empty($basePath) && empty($path))) {
        $path = '/' . $path;
    }

    $uri = $basePath . $path;

    return $this->createDispatcher()->dispatch(
        $request->getMethod(),
        $uri
    );
geggleto commented 7 years ago

Can you give us an example of the situation that leads to both $_SERVER['SCRIPT_NAME'] equaling $_SERVER['REQUEST_URI'] ?

DanKottke commented 7 years ago

Hey, sure, though I'm not sure in what way you mean for an example. I'll give you all I know. When I halt execution right before the return of Uri::createFromEnvironment(), I can verify that both of those server variables equal (these are dummied, but the same sort of thing I'm seeing) "/documentation/api/view", which is the route entered into, let's just say for clarity, a normal GET route in a browser to "/documentation/api/view". At this point, basePath is "/documentation/api/view" and path is "/".

Apache-wise, we're using virtual hosts with mod rewrite in place to route to appropriate routing scripts. So, for instance:

RewriteCond %{REQUEST_URI} ^/documentation
RewriteRule .* /www/<stuff here>/documentation/routes/main.php [NS,L,QSA]

This file, main.php, is where the Slim\App is instantiated, configured with paths to respond to, and where run() is called on it.

Another thing I've noticed is that in version 4.x, the references to basePath have been reduced. I'm hoping this means that the concept is being phased out? One of the critical places where basePath still factors in on 4.x is in URI::createFromEnvironment() at the part relevant to this ticket:


if (stripos($requestUri, $requestScriptName) === 0) {
            $basePath = $requestScriptName;
        } elseif ($requestScriptDir !== '/' && stripos($requestUri, $requestScriptDir) === 0) {
            $basePath = $requestScriptDir;
        }
        if ($basePath) {
            $virtualPath = ltrim(substr($requestUri, strlen($basePath)), '/');
        }

I'd like to mention that the solution that I've employed locally is actually commenting out this entire block and therefore completely gutting this basePath/virtualPath distinction.

starlight36 commented 7 years ago

Hi, this problem occurred when I was using php-cli server in my develop environment. When I access to /aabb.txt, the SCRIPT_NAME is /aabb.txt, others are /index.php. To fix this problem, I have to manually write a line such as $_SERVER['SCRIPT_NAME'] = '/index.php' in my index.php file.

I was referred to the implementation in Symfony HttpFoundation, usingSCRIPT_FILENAME and SCRIPT_NAME in the same time is a good idea to avoid this problem.

geggleto commented 7 years ago

Yes there is an open problem with the php-cli web server which they have indicated that they will not be fixing.

@DanKottke Yes we are deprecating it :)

jackwilsdon commented 6 years ago

I am still having this problem with apache2.4.27-2ubuntu3 and Slim 3.9.0.

I am using this rewrite;

RewriteEngine On
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^ index.php [QSA,L]

And all URLs are resolving to / and not being routed where they should be.

Here is my $_SERVER;

Array
(
    [SCRIPT_URL] => /basket
    [SCRIPT_URI] => http://127.0.0.1/basket
    [HTTP_HOST] => 127.0.0.1
    [HTTP_USER_AGENT] => Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:57.0) Gecko/20100101 Firefox/57.0
    [HTTP_ACCEPT] => text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
    [HTTP_ACCEPT_LANGUAGE] => en-GB,en;q=0.5
    [HTTP_ACCEPT_ENCODING] => gzip, deflate
    [HTTP_COOKIE] => XDEBUG_SESSION=XDEBUG_ECLIPSE
    [HTTP_CONNECTION] => keep-alive
    [HTTP_UPGRADE_INSECURE_REQUESTS] => 1
    [HTTP_CACHE_CONTROL] => max-age=0
    [PATH] => /usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
    [SERVER_SIGNATURE] => <address>Apache/2.4.27 (Ubuntu) Server at 127.0.0.1 Port 80</address>
    [SERVER_SOFTWARE] => Apache/2.4.27 (Ubuntu)
    [SERVER_NAME] => 127.0.0.1
    [SERVER_ADDR] => 127.0.0.1
    [SERVER_PORT] => 80
    [REMOTE_ADDR] => 127.0.0.1
    [DOCUMENT_ROOT] => /var/www
    [REQUEST_SCHEME] => http
    [CONTEXT_PREFIX] => 
    [CONTEXT_DOCUMENT_ROOT] => /var/www
    [SERVER_ADMIN] => webmaster@localhost
    [SCRIPT_FILENAME] => /var/www/index.php
    [REMOTE_PORT] => 38632
    [GATEWAY_INTERFACE] => CGI/1.1
    [SERVER_PROTOCOL] => HTTP/1.1
    [REQUEST_METHOD] => GET
    [QUERY_STRING] => 
    [REQUEST_URI] => /basket
    [SCRIPT_NAME] => /basket
    [PHP_SELF] => /basket
    [REQUEST_TIME_FLOAT] => 1513643368.615
    [REQUEST_TIME] => 1513643368
)

As you can see SCRIPT_NAME is not correct, which leads the path to be incorrect during routing. Is there a solution to this which doesn't involve manually setting SCRIPT_NAME in my PHP? (feels like a hack)

mmelvin0 commented 6 years ago

This is still an issue out of the box with the latest Slim skeleton. This issue should really not be closed IMHO since all the instructions say to use the PHP built-in development web server and it is apparently also happening to @jackwilsdon w/Apache.

An easy but ugly workaround is to add this in index.php:

if (strpos($_SERVER['SCRIPT_FILENAME'], $_SERVER['DOCUMENT_ROOT']) === 0) {
    $_SERVER['SCRIPT_NAME'] = substr($_SERVER['SCRIPT_FILENAME'], strlen(rtrim($_SERVER['DOCUMENT_ROOT'], DIRECTORY_SEPARATOR)));
}
paolomainardi commented 6 years ago

I confirm the same bug with the latest slim version.

akrabat commented 6 years ago

If this can be fixed in a B/C way I'm interested.

Also, can someone test if this is a problem for Slim 4?

l0gicgate commented 5 years ago

I'm removing the Slim 4 label from this as it is not an issue there. URI comes from ServerRequestInterface which is determined by the PSR-7 implementation that you choose since #2529 was merged.

akrabat commented 5 years ago

We could do with a reproducible project with .htaccess file and php -S command line.

IMSoP commented 5 years ago

Hunting around for the cause of this and possible workarounds, I found this long-standing bug report in Apache: https://bz.apache.org/bugzilla/show_bug.cgi?id=40102 Although dating to Apache 2.0, I believe this is the same issue I'm seeing on Apache 2.4

Based on that discussion, the key here seems to be the "context" within the Apache configuration:

This explains why not everyone sees this bug, because they're using a .htaccess rather than editing <VirtualHost> block in the main server config. It also gives a workaround, since surrounding the redirect with <Location /> ... </Location> is enough to change the context.

An additional workaround mentioned in the Apache bug is to use the [PT] (pass-through) flag on the RewriteRule. This forces it to rewrite URL-to-URL rather than URL-to-filename, which seems to cause SCRIPT_NAME to populate correctly.


All that being said, it would be nice if this could be mitigated in Slim as well.

If the detection code cannot be fixed, perhaps a setting could be added in some way to disable it, for people running into this issue. Unfortunately, \Slim\Http\Uri::createFromEnvironment is static, and only has access to the environment collection, so I'm not sure how to do that other than putting something into the environment data.

Alternatively, we might be able to detect the broken case by comparing SCRIPT_FILENAME and SCRIPT_NAME: in the expected case, SCRIPT_FILENAME is the full path on disk for the same file as SCRIPT_NAME; in the broken case, SCRIPT_NAME is an unrelated string from the requested URL.

if ( substr($env->get('SCRIPT_FILENAME'), -strlen($requestScriptName)) != $requestScriptName ) {
    $requestScriptName = '';
    $requestScriptDir = '';
}

Adding that just after the definition of $requestScriptName in Uri::createFromEnvironment seems to leave the feature working in the PHP built-in server, and when Apache sets SCRIPT_NAME correctly, but disable it (and so let the router work) when SCRIPT_NAME is bogus.

I don't have environments on hand to test it under other setups, though, like CGI, Apache+FPM, or Nginx.

IMSoP commented 5 years ago

Actually, combining that detection with mmelvin0's fix above might make it work for everyone:

$requestScriptFullPath = $env->get('SCRIPT_FILENAME');
if ( substr($requestScriptFullPath, -strlen($requestScriptName)) != $requestScriptName ) {
    // SCRIPT_NAME appears to have been set wrong by the server
    // Attempt to recover the correct value by taking everything in SCRIPT_FILENAME not also in DOCUMENT_ROOT
    $documentRootNoSlash = rtrim($env->get('DOCUMENT_ROOT'), DIRECTORY_SEPARATOR);
    if (strpos($requestScriptFullPath, $documentRootNoSlash) === 0) {
        $requestScriptName = substr($requestScriptFullPath, strlen($documentRootNoSlash));
        $requestScriptDir = dirname($requestScriptName);
    }
    else {
        // If SCRIPT_FILENAME and DOCUMENT_ROOT don't match either, just disable the base path detection
        $requestScriptName = '';
        $requestScriptDir = '';
    }
}

Again, though, I'm not confident how these vars will behave under other setups.

l0gicgate commented 5 years ago

I'm closing this as the Slim 3 branch is now in maintenance mode only for security fixes.