diff options
| author | Hazelnoot <acomputerdog@gmail.com> | 2025-07-04 12:16:18 -0400 |
|---|---|---|
| committer | dakkar <dakkar@thenautilus.net> | 2025-07-27 17:57:26 +0100 |
| commit | 982223ad38e428ca4e2269fff56bccd332ca0222 (patch) | |
| tree | 681f25f5db37ca1177c3214a29956c8d1c2658fc /packages/backend/src/core/UtilityService.ts | |
| parent | merge: disable outgoing mastodon quotes *FOR STABLE* (!1169) (diff) | |
| download | sharkey-982223ad38e428ca4e2269fff56bccd332ca0222.tar.gz sharkey-982223ad38e428ca4e2269fff56bccd332ca0222.tar.bz2 sharkey-982223ad38e428ca4e2269fff56bccd332ca0222.zip | |
validate all URLs before fetch
Diffstat (limited to 'packages/backend/src/core/UtilityService.ts')
| -rw-r--r-- | packages/backend/src/core/UtilityService.ts | 59 |
1 files changed, 56 insertions, 3 deletions
diff --git a/packages/backend/src/core/UtilityService.ts b/packages/backend/src/core/UtilityService.ts index 3098367392..281edccca3 100644 --- a/packages/backend/src/core/UtilityService.ts +++ b/packages/backend/src/core/UtilityService.ts @@ -10,7 +10,10 @@ import psl from 'psl'; import { DI } from '@/di-symbols.js'; import type { Config } from '@/config.js'; import { bindThis } from '@/decorators.js'; -import { MiMeta } from '@/models/Meta.js'; +import { MiMeta, SoftwareSuspension } from '@/models/Meta.js'; +import { MiInstance } from '@/models/Instance.js'; +import { IdentifiableError } from '@/misc/identifiable-error.js'; +import { EnvService } from '@/core/EnvService.js'; @Injectable() export class UtilityService { @@ -20,6 +23,8 @@ export class UtilityService { @Inject(DI.meta) private meta: MiMeta, + + private readonly envService: EnvService, ) { } @@ -181,8 +186,8 @@ export class UtilityService { } @bindThis - public punyHostPSLDomain(url: string): string { - const urlObj = new URL(url); + public punyHostPSLDomain(url: string | URL): string { + const urlObj = typeof(url) === 'object' ? url : new URL(url); const hostname = urlObj.hostname; const domain = this.specialSuffix(hostname) ?? psl.get(hostname) ?? hostname; const host = `${this.toPuny(domain)}${urlObj.port.length > 0 ? ':' + urlObj.port : ''}`; @@ -213,4 +218,52 @@ export class UtilityService { return ''; } } + + /** + * Verifies that a provided URL is in a format acceptable for federation. + * @throws {IdentifiableError} If URL cannot be parsed + * @throws {IdentifiableError} If URL is not HTTPS + * @throws {IdentifiableError} If URL contains credentials + */ + @bindThis + public assertUrl(url: string | URL): URL | never { + // If string, parse and validate + if (typeof(url) === 'string') { + try { + url = new URL(url); + } catch { + throw new IdentifiableError('0bedd29b-e3bf-4604-af51-d3352e2518af', `invalid url ${url}: not a valid URL`); + } + } + + // Must be HTTPS + if (!this.checkHttps(url)) { + throw new IdentifiableError('0bedd29b-e3bf-4604-af51-d3352e2518af', `invalid url ${url}: unsupported protocol ${url.protocol}`); + } + + // Must not have credentials + if (url.username || url.password) { + throw new IdentifiableError('0bedd29b-e3bf-4604-af51-d3352e2518af', `invalid url ${url}: contains embedded credentials`); + } + + return url; + } + + /** + * Checks if the URL contains HTTPS. + * Additionally, allows HTTP in non-production environments. + * Based on check-https.ts. + */ + @bindThis + public checkHttps(url: string | URL): boolean { + const isNonProd = this.envService.env.NODE_ENV !== 'production'; + + try { + const proto = new URL(url).protocol; + return proto === 'https:' || (proto === 'http:' && isNonProd); + } catch { + // Invalid URLs don't "count" as HTTPS + return false; + } + } } |