Skip to content

Commit

Permalink
Fix API application routing (joomla#29303)
Browse files Browse the repository at this point in the history
API not routed correctly on semi-SEF URLs with index.php in them
such as /api/index.php/v1/content/article

The reason is that the ApiRouter code is incorrectly tied to the
URL rewriting option in Global Configuration and doesn't really
check correctly whether the index.php part is found at the
beginning of the URL.
  • Loading branch information
Nicholas K. Dionysopoulos authored May 31, 2020
1 parent 82eac13 commit 4563141
Show file tree
Hide file tree
Showing 3 changed files with 71 additions and 28 deletions.
42 changes: 28 additions & 14 deletions htaccess.txt
Original file line number Diff line number Diff line change
Expand Up @@ -77,20 +77,34 @@ Header always set X-Content-Type-Options "nosniff"
# RewriteBase /

## Begin - Joomla! core SEF Section.
#
RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}]
#
# If the requested path and file is not /index.php and the request
# has not already been internally rewritten to the index.php script
RewriteCond %{REQUEST_URI} !^/index\.php
# and the requested path and file doesn't directly match a physical file
RewriteCond %{REQUEST_FILENAME} !-f
# and the requested path and file doesn't directly match a physical folder
RewriteCond %{REQUEST_FILENAME} !-d
# internally rewrite the request to the index.php script
RewriteRule .* index.php [L]
#
## End - Joomla! core SEF Section.
#
# PHP FastCGI fix for HTTP Authorization, required for the API application
RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}]
# -- SEF URLs for the API application
# If the requested path starts with /api, the file is not /api/index.php
# and the request has not already been internally rewritten to the
# api/index.php script
RewriteCond %{REQUEST_URI} ^/api/
RewriteCond %{REQUEST_URI} !^/api/index\.php
# and the requested path and file doesn't directly match a physical file
RewriteCond %{REQUEST_FILENAME} !-f
# and the requested path and file doesn't directly match a physical folder
RewriteCond %{REQUEST_FILENAME} !-d
# internally rewrite the request to the /api/index.php script
RewriteRule .* api/index.php [L]
# -- SEF URLs for the public frontend application
# If the requested path and file is not /index.php and the request
# has not already been internally rewritten to the index.php script
RewriteCond %{REQUEST_URI} !^/index\.php
# and the requested path and file doesn't directly match a physical file
RewriteCond %{REQUEST_FILENAME} !-f
# and the requested path and file doesn't directly match a physical folder
RewriteCond %{REQUEST_FILENAME} !-d
# internally rewrite the request to the index.php script
RewriteRule .* index.php [L]
#
## End - Joomla! core SEF Section.

</IfModule>

## These directives are only enabled if the Apache mod_rewrite module is disabled
Expand Down
44 changes: 32 additions & 12 deletions libraries/src/Router/ApiRouter.php
Original file line number Diff line number Diff line change
Expand Up @@ -115,18 +115,8 @@ public function parseApiRoute($method = 'GET')
// Remove the base URI path.
$path = substr_replace($path, '', 0, \strlen($baseUri));

if (!$this->app->get('sef_rewrite'))
{
// Transform the route
if ($path === 'index.php')
{
$path = '';
}
else
{
$path = str_replace('index.php/', '', $path);
}
}
// Transform the route
$path = $this->removeIndexPhpFromPath($path);

$query = Uri::getInstance()->getQuery(true);

Expand Down Expand Up @@ -159,4 +149,34 @@ public function parseApiRoute($method = 'GET')

throw new RouteNotFoundException(sprintf('Unable to handle request for route `%s`.', $path));
}

/**
* Removes the index.php from the route's path.
*
* @param string $path
*
* @return string
*
* @since 4.0.0
*/
private function removeIndexPhpFromPath(string $path): string
{
// Normalize the path
$path = ltrim($path, '/');

// We can only remove index.php if it's present in the beginning of the route
if (strpos($path, 'index.php') !== 0)
{
return $path;
}

// Edge case: the route is index.php without a trailing slash. Bad idea but we can still map it to a null route.
if ($path === 'index.php')
{
return '';
}

// Remove the "index.php/" part of the route and return the result.
return substr($path, 10);
}
}
13 changes: 11 additions & 2 deletions web.config.txt
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
<directoryBrowse enabled="false" />
<rewrite>
<rules>
<rule name="Joomla! Rule 1" stopProcessing="true">
<rule name="Joomla! Common Exploits Prevention" stopProcessing="true">
<match url="^(.*)$" ignoreCase="false" />
<conditions logicalGrouping="MatchAny">
<add input="{QUERY_STRING}" pattern="base64_encode[^(]*\([^)]*\)" ignoreCase="false" />
Expand All @@ -15,7 +15,16 @@
</conditions>
<action type="CustomResponse" url="index.php" statusCode="403" statusReason="Forbidden" statusDescription="Forbidden" />
</rule>
<rule name="Joomla! Rule 2">
<rule name="Joomla! API Application SEF URLs">
<match url="^api/(.*)" ignoreCase="false" />
<conditions logicalGrouping="MatchAll">
<add input="{URL}" pattern="^/api/index.php" ignoreCase="true" negate="true" />
<add input="{REQUEST_FILENAME}" matchType="IsFile" ignoreCase="false" negate="true" />
<add input="{REQUEST_FILENAME}" matchType="IsDirectory" ignoreCase="false" negate="true" />
</conditions>
<action type="Rewrite" url="api/index.php" />
</rule>
<rule name="Joomla! Public Frontend SEF URLs">
<match url="(.*)" ignoreCase="false" />
<conditions logicalGrouping="MatchAll">
<add input="{URL}" pattern="^/index.php" ignoreCase="true" negate="true" />
Expand Down

0 comments on commit 4563141

Please sign in to comment.