nextcloud / nextcloud/server

[Bug]: Dispatcher swallows controller-invocation TypeError as an empty, unlogged 400 - admin cannot diagnose silently-failed saves (repro: changing the SMTP port)

Open
#62,650 0 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

0. Needs triage 34-feedback bug
Dominant language
PHP
Stars
36.9k
Forks
5.2k
Avg merge
2d 3h
Merged PRs (30d)
713

Description

⚠️ This issue respects the following points: ⚠️
Bug description

When a controller method is invoked with an argument whose type does not match the method signature, lib/private/AppFramework/Http/Dispatcher.php catches the resulting TypeError at the invocation boundary and returns an empty HTTP 400 - without logging anything. From an administrator's point of view the request simply fails with a generic client-side AxiosError: Request failed with status code 400, no server-side log entry exists at any log level, and there is no way to tell a genuinely-handled bad request apart from an internal type mismatch.

This turns any form/controller type-coercion mismatch into a silent, undiagnosable failure in production. The concrete, fully reproducible instance below is saving the mail settings after changing the SMTP port, but the underlying diagnostic gap is general and is the primary point of this report.

This is a recurring class of bug in Nextcloud (see "Prior art" below), which is why the systemic diagnostic issue matters more than any single field fix.

Steps to reproduce
  1. Go to Settings > Administration > Basic settings > Email server.
  2. Change the "Port" field to any value (e.g. 587 → 465).
  3. Trigger save (click outside the field).
  4. Observe: a generic "saving failed" notification.
  5. Verify persistence: occ config:system:get mail_smtpport is unchanged — the save genuinely did not persist (not merely a UI/notification glitch).
  6. Check nextcloud.log: no corresponding error/exception entry, at any log level.
  7. Check the webserver access log: the POST to /settings/admin/mailsettings returns 400 with Content-Length: 0.
Expected behavior

Primary (the point of this report):

  • When the Dispatcher catches a TypeError at the controller-invocation boundary, it should log it (even at debug/info level) with enough context (controller, method, offending parameter/type) so administrators can diagnose an otherwise-silent 400.

Secondary (fix the concrete case):

  • The port field should serialize as a string matching the controller signature (cast back to string before submit, or use a text input with numeric validation instead of type="number"), and/or
  • MailSettingsController::setMailSettings() should accept int|string $mail_smtpport and cast internally, since numeric form fields are prone to exactly this coercion mismatch.
Nextcloud Server version

34

Operating system

Debian/Ubuntu

PHP engine version

PHP 8.4

Web server

Nginx

Database engine version

MariaDB

Is this bug present after an update or on a fresh install?

Fresh Nextcloud Server install

Are you using the Nextcloud Server Encryption module?

None

What user-backends are you using?
  • Default user-backend (database)
  • LDAP/ Active Directory
  • SSO - SAML
  • Other
Configuration report

List of activated Apps

Nextcloud Signing status

Nextcloud Logs

Additional info
Root cause (traced through the code)
  • The port field is bound in the settings frontend as a numeric input:
    <NcTextField v-model="mailConfig.mail_smtpport" type="number" max="65535" min="1" .../>
    
    Because it is type="number", once edited the bound value becomes a JavaScript number.
  • The save handler POSTs the whole mailConfig object as JSON (axios.post(generateUrl('/settings/admin/mailsettings'), config)), so mail_smtpport is serialized as a JSON number (465) rather than a string ("465").
  • OCA\Settings\Controller\MailSettingsController::setMailSettings() declares the parameter as non-nullable string $mail_smtpport.
  • lib/private/AppFramework/Http/Dispatcher.php has declare(strict_types=1). PHP determines strict-typing from the calling file, so the controller invocation is strictly typed: passing a JSON number where a string is required throws a TypeError at the invocation line.
  • The Dispatcher catches exactly this and returns an empty 400, intentionally not logging it (to distinguish it from TypeErrors thrown inside controller logic):
    } catch (\TypeError $e) {
        // Only intercept TypeErrors occurring on the first line, meaning that the
        // invocation of the controller method failed.
        // Any other TypeError happens inside the controller method logic and should be logged as normal.
        ...
        return new Response(Http::STATUS_BAD_REQUEST);
    }
    
  • Net effect: setSystemValues() inside setMailSettings() never runs, the new port is silently discarded, and there is zero diagnostic trail — no server log entry, only a generic AxiosError in the browser.

Why the diagnostic gap is the real issue

The specific port mismatch could be fixed in one line on either side (see below). But the reason this took hours to diagnose — and the reason similar reports keep recurring under different symptoms — is that the Dispatcher gives operators nothing to go on. An empty, unlogged 400 at the controller-invocation boundary is indistinguishable, from the outside, from any other bad request, and it is invisible even at the lowest log level (loglevel 0).

Any current or future controller whose signature doesn't match what a form actually serializes will fail this same silent way. Fixing individual fields does not close the class of bug; making the boundary observable does.

Workaround (for other admins hitting this)

Set the value directly via occ, bypassing the web form and its JSON binding entirely:

occ config:system:set mail_smtpport --value=<port>

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start with lib/private/AppFramework/Http/Dispatcher.php and trace the TypeError handling at controller invocation, then inspect MailSettingsController::setMailSettings() and the SMTP port field binding. Reproduce the save from Administration > Basic settings > Email server and verify that the invocation failure produces a contextual server log while the request behavior remains distinguishable from a handled bad request.

Written by the indexing model from the issue text.

Assessment

Tech stack
javascript, php
Domain
backend, frontend
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Quiet
Clarity
Mostly clear
Newbie friendliness
52/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.