Steps to reproduce
- Run
owncloud/server:11.0.0-rc2 (or current master) with 'htaccess.RewriteBase' => '/' in config.php, so that .htaccess contains the front controller rewrite block.
- Log in and open Settings → Personal → General.
- Pick a different language in the language selector.
Expected behaviour
POST to the language endpoint returns 200 with {"data":{"message":"Language changed"},"status":"success"} and the UI reloads translated.
Actual behaviour
POST http://localhost:8080/settings/ajax/setlanguage.php 500 (Internal Server Error)
The language is never stored. Apache error log:
PHP Fatal error: Uncaught Error: Class "OC" not found in /var/www/owncloud/settings/ajax/setlanguage.php:27
Root cause
This is not specific to the language setting — it is a whole class of broken endpoints.
-
settings/js/panels/profile.js posts to the relative url 'ajax/setlanguage.php', which the browser resolves against /settings/personal → /settings/ajax/setlanguage.php.
-
That path is a real file on disk, and the front controller rewrite generated by lib/private/Setup.php::updateHtaccess() only forwards a request to index.php when the target does not exist:
RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule . index.php [PT,E=PATH_INFO:$1]
-
Apache therefore executes the script directly, bypassing the router that would have included it with a bootstrap (Route::actionInclude() → require_once from inside index.php). Run standalone, the script's very first statement (settings/ajax/setlanguage.php:27, \OC::$server->getL10N('settings')) dies with Class "OC" not found.
This is a regression introduced after 10.16.3
Ingredient 1 (js posting a real script path) already existed at v10.16.3. Ingredient 2 was introduced by 5bb332b — "fix: htaccess RewriteBase rules block API requests for files with common extensions (#41606)" (fixes #41418) — which replaced two extension-based RewriteConds with RewriteCond %{REQUEST_FILENAME} !-f.
5bb332b7aa is not an ancestor of v10.16.3 but is an ancestor of master, so 10.16.3 is unaffected.
A/B verified in one container by swapping only that single RewriteCond line:
.htaccess variant |
POST /settings/ajax/setlanguage.php (anonymous) |
RewriteCond %{REQUEST_FILENAME} !-f (current) |
500 — executed directly, no bootstrap |
| extension-based cond (pre-#41606) |
401 — routed through index.php |
It only bites when htaccess.RewriteBase is configured (the docker image sets /), since the whole <IfModule mod_rewrite.c> block is only emitted then. That is likely why CI did not catch it.
Blast radius
19 legacy routes declare a url that is also a real .php file on disk. Probed against the running instance — every one of these returns 500 when requested directly, and works when requested through /index.php/...:
| Endpoint |
User-visible impact |
Caller |
settings/ajax/setlanguage.php |
cannot change personal language |
settings/js/panels/profile.js |
core/ajax/share.php |
share-dialog email autocomplete |
core/js/sharedialogmailview.js:211 |
apps/files_sharing/ajax/publicpreview.php |
anonymous public-link thumbnails |
apps/files_sharing/js/public.js:231 |
apps/files_trashbin/ajax/preview.php |
trashbin previews |
Trashbin.php:1162, files_trashbin/js/filelist.js:278 |
apps/files_external/ajax/oauth2.php |
Google Drive OAuth flow |
apps/files_external/js/gdrive.js:3 |
core/ajax/appconfig.php |
legacy appconfig ajax |
— |
The remaining ~11 are latent: they only work because OC.filePath() unconditionally injects /index.php/ into the url, so the request never hits the file. Any caller switching to OC.generateUrl() would break them. core/ajax/update.php is unaffected — it has an explicit rewrite exemption and self-bootstraps.
#41720 "Cannot change language (11.0.0-rc2 docker image)" is closed as completed, closed by a9d5c97 / 99245f0 "fix(l10n): guard vsprintf against malformed translations (#41720)". That is a different bug:
- the rc2 image was built before that fix and still contains bare
\vsprintf in lib/private/legacy/l10n/string.php;
- more decisively, the fatal is
Class "OC" not found on the file's first statement — the request dies before any l10n code runs.
So #41720 was closed against the wrong root cause and the bug still reproduces.
Server configuration
Operating system: Debian (docker image owncloud/server:11.0.0-rc2)
Web server: Apache 2.4.58 with mod_rewrite
Database: sqlite3
PHP version: 8.3.32
ownCloud version: 11.0.0-rc2 (also reproduces on master)
Updated from an older ownCloud or fresh install: fresh install
Where did you install ownCloud from: docker hub, owncloud/server:11.0.0-rc2
Relevant config: 'htaccess.RewriteBase' => '/' (required to reproduce)
Client configuration
Browser: any
Logs
Web server error log
PHP Fatal error: Uncaught Error: Class "OC" not found in /var/www/owncloud/settings/ajax/setlanguage.php:27
Stack trace:
#0 {main}
thrown in /var/www/owncloud/settings/ajax/setlanguage.php on line 27
Browser log
POST http://localhost:8080/settings/ajax/setlanguage.php 500 (Internal Server Error)
Steps to reproduce
owncloud/server:11.0.0-rc2(or current master) with'htaccess.RewriteBase' => '/'inconfig.php, so that.htaccesscontains the front controller rewrite block.Expected behaviour
POSTto the language endpoint returns200with{"data":{"message":"Language changed"},"status":"success"}and the UI reloads translated.Actual behaviour
The language is never stored. Apache error log:
Root cause
This is not specific to the language setting — it is a whole class of broken endpoints.
settings/js/panels/profile.jsposts to the relative url'ajax/setlanguage.php', which the browser resolves against/settings/personal→/settings/ajax/setlanguage.php.That path is a real file on disk, and the front controller rewrite generated by
lib/private/Setup.php::updateHtaccess()only forwards a request toindex.phpwhen the target does not exist:Apache therefore executes the script directly, bypassing the router that would have
included it with a bootstrap (Route::actionInclude()→require_oncefrom insideindex.php). Run standalone, the script's very first statement (settings/ajax/setlanguage.php:27,\OC::$server->getL10N('settings')) dies withClass "OC" not found.This is a regression introduced after 10.16.3
Ingredient 1 (js posting a real script path) already existed at
v10.16.3. Ingredient 2 was introduced by 5bb332b — "fix: htaccess RewriteBase rules block API requests for files with common extensions (#41606)" (fixes #41418) — which replaced two extension-basedRewriteConds withRewriteCond %{REQUEST_FILENAME} !-f.5bb332b7aais not an ancestor ofv10.16.3but is an ancestor of master, so 10.16.3 is unaffected.A/B verified in one container by swapping only that single
RewriteCondline:.htaccessvariantPOST /settings/ajax/setlanguage.php(anonymous)RewriteCond %{REQUEST_FILENAME} !-f(current)index.phpIt only bites when
htaccess.RewriteBaseis configured (the docker image sets/), since the whole<IfModule mod_rewrite.c>block is only emitted then. That is likely why CI did not catch it.Blast radius
19 legacy routes declare a url that is also a real
.phpfile on disk. Probed against the running instance — every one of these returns 500 when requested directly, and works when requested through/index.php/...:settings/ajax/setlanguage.phpsettings/js/panels/profile.jscore/ajax/share.phpcore/js/sharedialogmailview.js:211apps/files_sharing/ajax/publicpreview.phpapps/files_sharing/js/public.js:231apps/files_trashbin/ajax/preview.phpTrashbin.php:1162,files_trashbin/js/filelist.js:278apps/files_external/ajax/oauth2.phpapps/files_external/js/gdrive.js:3core/ajax/appconfig.phpThe remaining ~11 are latent: they only work because
OC.filePath()unconditionally injects/index.php/into the url, so the request never hits the file. Any caller switching toOC.generateUrl()would break them.core/ajax/update.phpis unaffected — it has an explicit rewrite exemption and self-bootstraps.Note on #41720
#41720 "Cannot change language (11.0.0-rc2 docker image)" is closed as completed, closed by a9d5c97 / 99245f0 "fix(l10n): guard vsprintf against malformed translations (#41720)". That is a different bug:
\vsprintfinlib/private/legacy/l10n/string.php;Class "OC" not foundon the file's first statement — the request dies before any l10n code runs.So #41720 was closed against the wrong root cause and the bug still reproduces.
Server configuration
Operating system: Debian (docker image
owncloud/server:11.0.0-rc2)Web server: Apache 2.4.58 with
mod_rewriteDatabase: sqlite3
PHP version: 8.3.32
ownCloud version: 11.0.0-rc2 (also reproduces on master)
Updated from an older ownCloud or fresh install: fresh install
Where did you install ownCloud from: docker hub,
owncloud/server:11.0.0-rc2Relevant config:
'htaccess.RewriteBase' => '/'(required to reproduce)Client configuration
Browser: any
Logs
Web server error log
Browser log