Skip to content

fix(files_external): validate FTP and SFTP ports as TCP port numbers - #63936

Open
bahman026 wants to merge 4 commits into
nextcloud:masterfrom
bahman026:fix/noid/files-external-validate-port
Open

bahman026 wants to merge 4 commits into
nextcloud:masterfrom
bahman026:fix/noid/files-external-validate-port

Conversation

@bahman026

@bahman026 bahman026 commented Sep 2, 2026 •

Copy link
Copy Markdown
Contributor

Follow-up to #63161, addressing the review suggestion there to validate that the port is an integer within the valid TCP range.

Summary

#63161 made FTP::__construct() fall back to the default port unless the configured value is numeric, mirroring the SFTP guard from #58350:

$parsedPort = $parameters['port'] ?? null;
$this->port = is_numeric($parsedPort) ? (int)$parsedPort : 21;

That fixes the empty-string crash, but is_numeric() still accepts values that are not usable TCP ports, and the cast then silently turns them into something else:

configured port resulting port comment
"21.5" 21 truncated, no longer the configured value
"1e3" 1000 exponential notation accepted
"0" 0 not a valid port
"-2121" -2121 not a valid port
"65536" 65536 above the maximum

The port field of an external storage is a free-text parameter holding whatever the administrator typed, so all of these are reachable through the UI. FTP and SFTP are the only backends that read ['port'].

The cases that merely fail to connect are not really the problem — the ones that matter are "21.5" and "1e3", where the storage silently connects to a different valid port than the one configured.

Fix

Both constructors validate the configured value inline with filter_var(), and log a warning when an invalid value is ignored rather than falling back unnoticed:

$configuredPort = trim((string)($parameters['port'] ?? ''));
$port = filter_var(ltrim($configuredPort, '0'), FILTER_VALIDATE_INT, ['options' => ['min_range' => 1, 'max_range' => 65535]]);
if ($port === false && $configuredPort !== '') {
	Server::get(LoggerInterface::class)->warning('Ignoring invalid port configured for FTP storage, falling back to the default port', ['port' => $configuredPort, 'default' => self::DEFAULT_PORT]);
}
$this->port = $port ?: self::DEFAULT_PORT;

The hardcoded 21 / 22 defaults are replaced with a DEFAULT_PORT class constant.

FILTER_VALIDATE_INT rejects leading zeros, so they are stripped first and "0022" keeps resolving to 22 exactly as it does on master.

For SFTP the port can also be part of the host field (somehost:2222). parse_url() already returns that as an int and fails outright for anything outside 1–65535, so it needs no validation of its own — it simply remains the fallback when the port field is empty or invalid, so somehost:2222 with an empty port still connects to 2222.

Valid configurations are unaffected: an integer, or a numeric string such as "2121", resolves exactly as before, as do "+2121", " 2121 " and 2121.0.

Checklist

Follow-up to nextcloud#63161. The port field of an external storage holds whatever the
admin typed, so `is_numeric()` still let through values that are not usable TCP
ports: "21.5" and "1e3" were silently truncated by the int cast, and "0",
"-2121" or "65536" were passed on to the connection as-is.

Add PortHelper::parsePort(), which only accepts an integer or a digit-only
string within the valid TCP port range of 1-65535 and otherwise returns the
given fallback. Use it for both FTP and SFTP, including the port that SFTP
parses out of the host field, and replace the hardcoded default ports with
class constants.

Signed-off-by: bahman026 <bahman026@gmail.com>
@bahman026
bahman026 requested a review from a team as a code owner September 2, 2026 07:43
@bahman026
bahman026 requested review from CarlSchwan, come-nc, icewind1991 and leftybournes and removed request for a team September 2, 2026 07:43

@come-nc come-nc left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have mixed feelings about this, especially the use of a static class for it, and the fact that it silently falls back to default port when invalid values are given.

All the callsite with parse_url do not need this complexity as parse_url will always return an int for port. Is it really needed to check the port is valid? What happens if it’s not? I expect connection will simply fail.

Comment thread apps/files_external/lib/Lib/PortHelper.php Outdated
Address review feedback on the previous commit:

- Remove the PortHelper static class. filter_var() with FILTER_VALIDATE_INT
  and a 1-65535 range covers the same cases in one stdlib call, so both
  constructors do it inline.
- Drop the validation from splitHost(). parse_url() already returns the port
  as an int and fails outright for anything outside 1-65535, so the check
  there could never fall back.
- Log a warning instead of silently ignoring a configured port, so a
  misconfiguration is visible rather than falling back unnoticed.

Leading zeros are stripped before validating, so "0022" keeps resolving to
22 as it did before. This also restores the master behaviour for "+2121",
" 2121 " and 2121.0, which the previous commit rejected.

Signed-off-by: bahman026 <bahman026@gmail.com>
@bahman026

bahman026 commented Sep 15, 2026 •

Copy link
Copy Markdown
Contributor Author

@come-nc
Thanks, that makes sense.

I removed the parse_url() port validation since parse_url() already rejects values outside the valid range. The static PortHelper is gone as well, and validation is now handled inline with filter_var().

Invalid configured ports now trigger a warning instead of silently falling back. I kept the 1–65535 check because values like "2121.5" or "1e3" can otherwise be coerced into a different valid port, which could result in connecting somewhere the admin didn't configure.

I also handled leading zeros so values like "0022" remain valid without allowing "02222" to be interpreted as 22.

@github-actions

Copy link
Copy Markdown
Contributor

Hello there,
Thank you so much for taking the time and effort to create a pull request to our Nextcloud project.

We hope that the review process is going smooth and is helpful for you. We want to ensure your pull request is reviewed to your satisfaction. If you have a moment, our community management team would very much appreciate your feedback on your experience with this PR review process.

Your feedback is valuable to us as we continuously strive to improve our community developer experience. Please take a moment to complete our short survey by clicking on the following link: https://cloud.nextcloud.com/apps/forms/s/i9Ago4EQRZ7TWxjfmeEpPkf6

Thank you for contributing to Nextcloud and we hope to hear from you soon!

(If you believe you should not receive this message, you can add yourself to the blocklist.)

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants