1
1
/*
2
- Copyright 2015 - 2021 The Matrix.org Foundation C.I.C.
2
+ Copyright 2015 - 2024 The Matrix.org Foundation C.I.C.
3
3
4
4
Licensed under the Apache License, Version 2.0 (the "License");
5
5
you may not use this file except in compliance with the License.
@@ -14,7 +14,22 @@ See the License for the specific language governing permissions and
14
14
limitations under the License.
15
15
*/
16
16
17
- import { encodeParams } from "./utils.ts" ;
17
+ // Validation based on https://spec.matrix.org/v1.12/appendices/#server-name
18
+ // We do not use the validation described in https://spec.matrix.org/v1.12/client-server-api/#security-considerations-5
19
+ // as it'd wrongly make all MXCs invalid due to not allowing `[].:` in server names.
20
+ const serverNameRegex =
21
+ / ^ (?: (?: \d { 1 , 3 } \. \d { 1 , 3 } \. \d { 1 , 3 } \. \d { 1 , 3 } ) | (?: \[ [ \d A - F a - f : . ] { 2 , 45 } ] ) | (?: [ A - Z a - z \d \- . ] { 1 , 255 } ) ) (?: : \d { 1 , 5 } ) ? $ / ;
22
+ function validateServerName ( serverName : string ) : boolean {
23
+ const matches = serverNameRegex . exec ( serverName ) ;
24
+ return matches ?. [ 0 ] === serverName ;
25
+ }
26
+
27
+ // Validation based on https://spec.matrix.org/v1.12/client-server-api/#security-considerations-5
28
+ const mediaIdRegex = / ^ [ \w - ] + $ / ;
29
+ function validateMediaId ( mediaId : string ) : boolean {
30
+ const matches = mediaIdRegex . exec ( mediaId ) ;
31
+ return matches ?. [ 0 ] === mediaId ;
32
+ }
18
33
19
34
/**
20
35
* Get the HTTP URL for an MXC URI.
@@ -36,7 +51,7 @@ import { encodeParams } from "./utils.ts";
36
51
* for authenticated media will *not* be checked - it is the caller's responsibility
37
52
* to do so before calling this function. Note also that `useAuthentication`
38
53
* implies `allowRedirects`. Defaults to false (unauthenticated endpoints).
39
- * @returns The complete URL to the content.
54
+ * @returns The complete URL to the content, may be an empty string if the provided mxc is not valid .
40
55
*/
41
56
export function getHttpUriForMxc (
42
57
baseUrl : string ,
@@ -51,14 +66,19 @@ export function getHttpUriForMxc(
51
66
if ( typeof mxc !== "string" || ! mxc ) {
52
67
return "" ;
53
68
}
54
- if ( mxc . indexOf ( "mxc://" ) !== 0 ) {
69
+ if ( ! mxc . startsWith ( "mxc://" ) ) {
55
70
if ( allowDirectLinks ) {
56
71
return mxc ;
57
72
} else {
58
73
return "" ;
59
74
}
60
75
}
61
76
77
+ const [ serverName , mediaId , ...rest ] = mxc . slice ( 6 ) . split ( "/" ) ;
78
+ if ( rest . length > 0 || ! validateServerName ( serverName ) || ! validateMediaId ( mediaId ) ) {
79
+ return "" ;
80
+ }
81
+
62
82
if ( useAuthentication ) {
63
83
allowRedirects = true ; // per docs (MSC3916 always expects redirects)
64
84
@@ -67,46 +87,31 @@ export function getHttpUriForMxc(
67
87
// callers, hopefully.
68
88
}
69
89
70
- let serverAndMediaId = mxc . slice ( 6 ) ; // strips mxc://
71
90
let prefix : string ;
91
+ const isThumbnailRequest = ! ! width || ! ! height || ! ! resizeMethod ;
92
+ const verb = isThumbnailRequest ? "thumbnail" : "download" ;
72
93
if ( useAuthentication ) {
73
- prefix = " /_matrix/client/v1/media/download/" ;
94
+ prefix = ` /_matrix/client/v1/media/${ verb } ` ;
74
95
} else {
75
- prefix = " /_matrix/media/v3/download/" ;
96
+ prefix = ` /_matrix/media/v3/${ verb } ` ;
76
97
}
77
- const params : Record < string , string > = { } ;
98
+
99
+ const url = new URL ( `${ prefix } /${ serverName } /${ mediaId } ` , baseUrl ) ;
78
100
79
101
if ( width ) {
80
- params [ "width" ] = Math . round ( width ) . toString ( ) ;
102
+ url . searchParams . set ( "width" , Math . round ( width ) . toString ( ) ) ;
81
103
}
82
104
if ( height ) {
83
- params [ "height" ] = Math . round ( height ) . toString ( ) ;
105
+ url . searchParams . set ( "height" , Math . round ( height ) . toString ( ) ) ;
84
106
}
85
107
if ( resizeMethod ) {
86
- params [ "method" ] = resizeMethod ;
87
- }
88
- if ( Object . keys ( params ) . length > 0 ) {
89
- // these are thumbnailing params so they probably want the
90
- // thumbnailing API...
91
- if ( useAuthentication ) {
92
- prefix = "/_matrix/client/v1/media/thumbnail/" ;
93
- } else {
94
- prefix = "/_matrix/media/v3/thumbnail/" ;
95
- }
108
+ url . searchParams . set ( "method" , resizeMethod ) ;
96
109
}
97
110
98
111
if ( typeof allowRedirects === "boolean" ) {
99
112
// We add this after, so we don't convert everything to a thumbnail request.
100
- params [ "allow_redirect" ] = JSON . stringify ( allowRedirects ) ;
101
- }
102
-
103
- const fragmentOffset = serverAndMediaId . indexOf ( "#" ) ;
104
- let fragment = "" ;
105
- if ( fragmentOffset >= 0 ) {
106
- fragment = serverAndMediaId . slice ( fragmentOffset ) ;
107
- serverAndMediaId = serverAndMediaId . slice ( 0 , fragmentOffset ) ;
113
+ url . searchParams . set ( "allow_redirect" , JSON . stringify ( allowRedirects ) ) ;
108
114
}
109
115
110
- const urlParams = Object . keys ( params ) . length === 0 ? "" : "?" + encodeParams ( params ) ;
111
- return baseUrl + prefix + serverAndMediaId + urlParams + fragment ;
116
+ return url . href ;
112
117
}
0 commit comments