Skip to content

Commit bc97f1d

Browse files
authored
Add tests for _matrix/client/v1/media/download (#727)
* add tests for _matrix/client/v1/media/download endpoint * requested changes * split content test into federation/local and test authentication
1 parent 104edcf commit bc97f1d

File tree

6 files changed

+289
-10
lines changed

6 files changed

+289
-10
lines changed

client/client.go

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,19 @@ func (c *CSAPI) DownloadContent(t ct.TestLike, mxcUri string) ([]byte, string) {
101101
return b, contentType
102102
}
103103

104+
// DownloadContentAuthenticated downloads media from _matrix/client/v1/media resource, returning the raw bytes and the Content-Type. Fails the test on error.
105+
func (c *CSAPI) DownloadContentAuthenticated(t ct.TestLike, mxcUri string) ([]byte, string) {
106+
t.Helper()
107+
origin, mediaId := SplitMxc(mxcUri)
108+
res := c.MustDo(t, "GET", []string{"_matrix", "client", "v1", "media", "download", origin, mediaId})
109+
contentType := res.Header.Get("Content-Type")
110+
b, err := io.ReadAll(res.Body)
111+
if err != nil {
112+
ct.Errorf(t, err.Error())
113+
}
114+
return b, contentType
115+
}
116+
104117
// MustCreateRoom creates a room with an optional HTTP request body. Fails the test on error. Returns the room ID.
105118
func (c *CSAPI) MustCreateRoom(t ct.TestLike, reqBody map[string]interface{}) string {
106119
t.Helper()

tests/csapi/apidoc_content_test.go

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package csapi_tests
22

33
import (
44
"bytes"
5+
"net/http"
56
"testing"
67

78
"github.com/matrix-org/complement"
@@ -27,3 +28,29 @@ func TestContent(t *testing.T) {
2728
t.Fatalf("expected contentType to be %s, got %s", wantContentType, contentType)
2829
}
2930
}
31+
32+
// same as above but testing _matrix/client/v1/media/download
33+
func TestContentCSAPIMediaV1(t *testing.T) {
34+
deployment := complement.Deploy(t, 1)
35+
defer deployment.Destroy(t)
36+
37+
alice := deployment.Register(t, "hs1", helpers.RegistrationOpts{})
38+
39+
wantContentType := "img/png"
40+
mxcUri := alice.UploadContent(t, data.MatrixPng, "test.png", wantContentType)
41+
42+
content, contentType := alice.DownloadContentAuthenticated(t, mxcUri)
43+
if !bytes.Equal(data.MatrixPng, content) {
44+
t.Fatalf("uploaded and downloaded content doesn't match: want %v\ngot\n%v", data.MatrixPng, content)
45+
}
46+
if contentType != wantContentType {
47+
t.Fatalf("expected contentType to be \n %s, got \n %s", wantContentType, contentType)
48+
}
49+
50+
// Remove the AccessToken and try again, this should now return a 401.
51+
alice.AccessToken = ""
52+
res := alice.Do(t, "GET", []string{"_matrix", "client", "v1", "media", "download", "hs1", mxcUri})
53+
if res.StatusCode != http.StatusUnauthorized {
54+
t.Fatalf("expected HTTP status: %d, got %d", http.StatusUnauthorized, res.StatusCode)
55+
}
56+
}

tests/csapi/media_async_uploads_test.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,4 +70,14 @@ func TestAsyncUpload(t *testing.T) {
7070
t.Fatalf("expected contentType to be %s, got %s", wantContentType, contentType)
7171
}
7272
})
73+
74+
t.Run("Download media over _matrix/client/v1/media/download", func(t *testing.T) {
75+
content, contentType := alice.DownloadContentAuthenticated(t, mxcURI)
76+
if !bytes.Equal(data.MatrixPng, content) {
77+
t.Fatalf("uploaded and downloaded content doesn't match: want %v\ngot\n%v", data.MatrixPng, content)
78+
}
79+
if contentType != wantContentType {
80+
t.Fatalf("expected contentType to be %s, got %s", wantContentType, contentType)
81+
}
82+
})
7383
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
package tests
2+
3+
import (
4+
"bytes"
5+
"github.com/matrix-org/complement"
6+
"github.com/matrix-org/complement/helpers"
7+
"github.com/matrix-org/complement/internal/data"
8+
"testing"
9+
)
10+
11+
func TestContentMediaV1(t *testing.T) {
12+
deployment := complement.Deploy(t, 2)
13+
defer deployment.Destroy(t)
14+
15+
hs1 := deployment.Register(t, "hs1", helpers.RegistrationOpts{})
16+
hs2 := deployment.Register(t, "hs2", helpers.RegistrationOpts{})
17+
18+
wantContentType := "img/png"
19+
mxcUri := hs1.UploadContent(t, data.MatrixPng, "test.png", wantContentType)
20+
21+
content, contentType := hs2.DownloadContentAuthenticated(t, mxcUri)
22+
if !bytes.Equal(data.MatrixPng, content) {
23+
t.Fatalf("uploaded and downloaded content doesn't match: want %v\ngot\n%v", data.MatrixPng, content)
24+
}
25+
if contentType != wantContentType {
26+
t.Fatalf("expected contentType to be \n %s, got \n %s", wantContentType, contentType)
27+
}
28+
}

tests/media_filename_test.go

Lines changed: 136 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,20 @@ func TestMediaFilenames(t *testing.T) {
4545

4646
mxcUri := alice.UploadContent(t, data.MatrixPng, filename, "image/png")
4747

48-
name, _ := downloadForFilename(t, alice, mxcUri, "")
48+
name, _ := downloadForFilename(t, alice, mxcUri, "", false)
49+
50+
// filename is not required, but if it's an attachment then check it matches
51+
if name != filename {
52+
t.Fatalf("Incorrect filename '%s', expected '%s'", name, filename)
53+
}
54+
})
55+
56+
t.Run(fmt.Sprintf("Can download file '%s' over /_matrix/client/v1/media/download", filename), func(t *testing.T) {
57+
t.Parallel()
58+
59+
mxcUri := alice.UploadContent(t, data.MatrixPng, filename, "image/png")
60+
61+
name, _ := downloadForFilename(t, alice, mxcUri, "", true)
4962

5063
// filename is not required, but if it's an attachment then check it matches
5164
if name != filename {
@@ -61,12 +74,25 @@ func TestMediaFilenames(t *testing.T) {
6174
mxcUri := alice.UploadContent(t, data.MatrixPng, asciiFileName, "image/png")
6275

6376
const altName = "file.png"
64-
filename, _ := downloadForFilename(t, alice, mxcUri, altName)
77+
filename, _ := downloadForFilename(t, alice, mxcUri, altName, false)
6578

6679
if filename != altName {
6780
t.Fatalf("filename did not match, expected '%s', got '%s'", altName, filename)
6881
}
6982
})
83+
t.Run("Can download specifying a different ASCII file name over _matrix/client/v1/media/download", func(t *testing.T) {
84+
t.Parallel()
85+
86+
mxcUri := alice.UploadContent(t, data.MatrixPng, asciiFileName, "image/png")
87+
88+
const altName = "file.png"
89+
filename, _ := downloadForFilename(t, alice, mxcUri, altName, true)
90+
91+
if filename != altName {
92+
t.Fatalf("filename did not match, expected '%s', got '%s'", altName, filename)
93+
}
94+
})
95+
7096
})
7197

7298
t.Run("Unicode", func(t *testing.T) {
@@ -87,7 +113,21 @@ func TestMediaFilenames(t *testing.T) {
87113

88114
const diffUnicodeFilename = "\u2615" // coffee emoji
89115

90-
filename, _ := downloadForFilename(t, alice, mxcUri, diffUnicodeFilename)
116+
filename, _ := downloadForFilename(t, alice, mxcUri, diffUnicodeFilename, false)
117+
118+
if filename != diffUnicodeFilename {
119+
t.Fatalf("filename did not match, expected '%s', got '%s'", diffUnicodeFilename, filename)
120+
}
121+
})
122+
123+
t.Run("Can download specifying a different Unicode file name over _matrix/client/v1/media/download", func(t *testing.T) {
124+
t.Parallel()
125+
126+
mxcUri := alice.UploadContent(t, data.MatrixPng, unicodeFileName, "image/png")
127+
128+
const diffUnicodeFilename = "\u2615" // coffee emoji
129+
130+
filename, _ := downloadForFilename(t, alice, mxcUri, diffUnicodeFilename, true)
91131

92132
if filename != diffUnicodeFilename {
93133
t.Fatalf("filename did not match, expected '%s', got '%s'", diffUnicodeFilename, filename)
@@ -100,7 +140,19 @@ func TestMediaFilenames(t *testing.T) {
100140

101141
mxcUri := alice.UploadContent(t, data.MatrixPng, unicodeFileName, "image/png")
102142

103-
filename, _ := downloadForFilename(t, alice, mxcUri, "")
143+
filename, _ := downloadForFilename(t, alice, mxcUri, "", false)
144+
145+
if filename != unicodeFileName {
146+
t.Fatalf("filename did not match, expected '%s', got '%s'", unicodeFileName, filename)
147+
}
148+
})
149+
150+
t.Run("Can download with Unicode file name locally over _matrix/client/v1/media/download", func(t *testing.T) {
151+
t.Parallel()
152+
153+
mxcUri := alice.UploadContent(t, data.MatrixPng, unicodeFileName, "image/png")
154+
155+
filename, _ := downloadForFilename(t, alice, mxcUri, "", true)
104156

105157
if filename != unicodeFileName {
106158
t.Fatalf("filename did not match, expected '%s', got '%s'", unicodeFileName, filename)
@@ -113,7 +165,19 @@ func TestMediaFilenames(t *testing.T) {
113165

114166
mxcUri := alice.UploadContent(t, data.MatrixPng, unicodeFileName, "image/png")
115167

116-
filename, _ := downloadForFilename(t, bob, mxcUri, "")
168+
filename, _ := downloadForFilename(t, bob, mxcUri, "", false)
169+
170+
if filename != unicodeFileName {
171+
t.Fatalf("filename did not match, expected '%s', got '%s'", unicodeFileName, filename)
172+
}
173+
})
174+
175+
t.Run("Can download with Unicode file name over federation via _matrix/client/v1/media/download", func(t *testing.T) {
176+
t.Parallel()
177+
178+
mxcUri := alice.UploadContent(t, data.MatrixPng, unicodeFileName, "image/png")
179+
180+
filename, _ := downloadForFilename(t, bob, mxcUri, "", true)
117181

118182
if filename != unicodeFileName {
119183
t.Fatalf("filename did not match, expected '%s', got '%s'", unicodeFileName, filename)
@@ -131,7 +195,25 @@ func TestMediaFilenames(t *testing.T) {
131195

132196
mxcUri := alice.UploadContent(t, data.MatrixPng, "", "image/png")
133197

134-
_, isAttachment := downloadForFilename(t, bob, mxcUri, "")
198+
_, isAttachment := downloadForFilename(t, bob, mxcUri, "", false)
199+
200+
if isAttachment {
201+
t.Fatal("Expected file to be served as inline")
202+
}
203+
})
204+
205+
t.Run("Will serve safe media types as inline via _matrix/client/v1/media/download", func(t *testing.T) {
206+
if runtime.Homeserver != runtime.Synapse && runtime.Homeserver != runtime.Conduwuit {
207+
// We need to check that this security behaviour is being correctly run in
208+
// Synapse or conduwuit, but since this is not part of the Matrix spec we do not assume
209+
// other homeservers are doing so.
210+
t.Skip("Skipping test of Content-Disposition header requirements on non-Synapse and non-conduwuit homeserver")
211+
}
212+
t.Parallel()
213+
214+
mxcUri := alice.UploadContent(t, data.MatrixPng, "", "image/png")
215+
216+
_, isAttachment := downloadForFilename(t, bob, mxcUri, "", true)
135217

136218
if isAttachment {
137219
t.Fatal("Expected file to be served as inline")
@@ -150,7 +232,26 @@ func TestMediaFilenames(t *testing.T) {
150232
// Add parameters and upper-case, which should be parsed as text/plain.
151233
mxcUri := alice.UploadContent(t, data.MatrixPng, "", "Text/Plain; charset=utf-8")
152234

153-
_, isAttachment := downloadForFilename(t, bob, mxcUri, "")
235+
_, isAttachment := downloadForFilename(t, bob, mxcUri, "", false)
236+
237+
if isAttachment {
238+
t.Fatal("Expected file to be served as inline")
239+
}
240+
})
241+
242+
t.Run("Will serve safe media types with parameters as inline via _matrix/client/v1/media/download", func(t *testing.T) {
243+
if runtime.Homeserver != runtime.Synapse && runtime.Homeserver != runtime.Conduwuit {
244+
// We need to check that this security behaviour is being correctly run in
245+
// Synapse or conduwuit, but since this is not part of the Matrix spec we do not assume
246+
// other homeservers are doing so.
247+
t.Skip("Skipping test of Content-Disposition header requirements on non-Synapse and non-conduwuit homeserver")
248+
}
249+
t.Parallel()
250+
251+
// Add parameters and upper-case, which should be parsed as text/plain.
252+
mxcUri := alice.UploadContent(t, data.MatrixPng, "", "Text/Plain; charset=utf-8")
253+
254+
_, isAttachment := downloadForFilename(t, bob, mxcUri, "", true)
154255

155256
if isAttachment {
156257
t.Fatal("Expected file to be served as inline")
@@ -168,7 +269,25 @@ func TestMediaFilenames(t *testing.T) {
168269

169270
mxcUri := alice.UploadContent(t, data.MatrixSvg, "", "image/svg")
170271

171-
_, isAttachment := downloadForFilename(t, bob, mxcUri, "")
272+
_, isAttachment := downloadForFilename(t, bob, mxcUri, "", false)
273+
274+
if !isAttachment {
275+
t.Fatal("Expected file to be served as an attachment")
276+
}
277+
})
278+
279+
t.Run("Will serve unsafe media types as attachments via _matrix/client/v1/media/download", func(t *testing.T) {
280+
if runtime.Homeserver != runtime.Synapse && runtime.Homeserver != runtime.Conduwuit {
281+
// We need to check that this security behaviour is being correctly run in
282+
// Synapse or conduwuit, but since this is not part of the Matrix spec we do not assume
283+
// other homeservers are doing so.
284+
t.Skip("Skipping test of Content-Disposition header requirements on non-Synapse and non-conduwuit homeserver")
285+
}
286+
t.Parallel()
287+
288+
mxcUri := alice.UploadContent(t, data.MatrixSvg, "", "image/svg")
289+
290+
_, isAttachment := downloadForFilename(t, bob, mxcUri, "", true)
172291

173292
if !isAttachment {
174293
t.Fatal("Expected file to be served as an attachment")
@@ -179,7 +298,7 @@ func TestMediaFilenames(t *testing.T) {
179298
}
180299

181300
// Returns content disposition information like (filename, isAttachment)
182-
func downloadForFilename(t *testing.T, c *client.CSAPI, mxcUri string, diffName string) (filename string, isAttachment bool) {
301+
func downloadForFilename(t *testing.T, c *client.CSAPI, mxcUri string, diffName string, authenticatedEndpoint bool) (filename string, isAttachment bool) {
183302
t.Helper()
184303

185304
origin, mediaId := client.SplitMxc(mxcUri)
@@ -188,8 +307,16 @@ func downloadForFilename(t *testing.T, c *client.CSAPI, mxcUri string, diffName
188307

189308
if diffName != "" {
190309
path = []string{"_matrix", "media", "v3", "download", origin, mediaId, diffName}
310+
311+
if authenticatedEndpoint {
312+
path = []string{"_matrix", "client", "v1", "media", "download", origin, mediaId, diffName}
313+
}
191314
} else {
192315
path = []string{"_matrix", "media", "v3", "download", origin, mediaId}
316+
317+
if authenticatedEndpoint {
318+
path = []string{"_matrix", "client", "v1", "media", "download", origin, mediaId}
319+
}
193320
}
194321

195322
res := c.MustDo(t, "GET", path)

0 commit comments

Comments
 (0)