Skip to content

Commit f321e0d

Browse files
authored
Merge pull request #53280 from nextcloud/fix/dav-nickname-master
2 parents 0359b7c + d763b4a commit f321e0d

17 files changed

Lines changed: 208 additions & 20 deletions

File tree

apps/dav/lib/Files/Sharing/FilesDropPlugin.php

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
use OCP\Files\Folder;
99
use OCP\Files\NotFoundException;
1010
use OCP\Share\IShare;
11+
use Sabre\DAV\Exception\BadRequest;
1112
use Sabre\DAV\Exception\MethodNotAllowed;
1213
use Sabre\DAV\ServerPlugin;
1314
use Sabre\HTTP\RequestInterface;
@@ -71,13 +72,12 @@ public function beforeMethod(RequestInterface $request, ResponseInterface $respo
7172
? trim(urldecode($request->getHeader('X-NC-Nickname')))
7273
: null;
7374

74-
//
7575
if ($request->getMethod() !== 'PUT') {
7676
// If uploading subfolders we need to ensure they get created
7777
// within the nickname folder
7878
if ($request->getMethod() === 'MKCOL') {
7979
if (!$nickname) {
80-
throw new MethodNotAllowed('A nickname header is required when uploading subfolders');
80+
throw new BadRequest('A nickname header is required when uploading subfolders');
8181
}
8282
} else {
8383
throw new MethodNotAllowed('Only PUT is allowed on files drop');
@@ -113,20 +113,32 @@ public function beforeMethod(RequestInterface $request, ResponseInterface $respo
113113

114114
// We need a valid nickname for file requests
115115
if ($isFileRequest && !$nickname) {
116-
throw new MethodNotAllowed('A nickname header is required for file requests');
116+
throw new BadRequest('A nickname header is required for file requests');
117117
}
118118

119119
// We're only allowing the upload of
120120
// long path with subfolders if a nickname is set.
121121
// This prevents confusion when uploading files and help
122122
// classify them by uploaders.
123123
if (!$nickname && !$isRootUpload) {
124-
throw new MethodNotAllowed('A nickname header is required when uploading subfolders');
124+
throw new BadRequest('A nickname header is required when uploading subfolders');
125125
}
126126

127-
// If we have a nickname, let's put everything inside
128127
if ($nickname) {
129-
// Put all files in the subfolder
128+
try {
129+
$node->verifyPath($nickname);
130+
} catch (\Exception $e) {
131+
// If the path is not valid, we throw an exception
132+
throw new BadRequest('Invalid nickname: ' . $nickname);
133+
}
134+
135+
// Forbid nicknames starting with a dot
136+
if (str_starts_with($nickname, '.')) {
137+
throw new BadRequest('Invalid nickname: ' . $nickname);
138+
}
139+
140+
// If we have a nickname, let's put
141+
// all files in the subfolder
130142
$relativePath = '/' . $nickname . '/' . $relativePath;
131143
$relativePath = str_replace('//', '/', $relativePath);
132144
}

apps/dav/tests/unit/Files/Sharing/FilesDropPluginTest.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
use OCP\Share\IAttributes;
1414
use OCP\Share\IShare;
1515
use PHPUnit\Framework\MockObject\MockObject;
16-
use Sabre\DAV\Exception\MethodNotAllowed;
16+
use Sabre\DAV\Exception\BadRequest;
1717
use Sabre\DAV\Server;
1818
use Sabre\HTTP\RequestInterface;
1919
use Sabre\HTTP\ResponseInterface;
@@ -119,7 +119,7 @@ public function testNoMKCOLWithoutNickname(): void {
119119
$this->request->method('getMethod')
120120
->willReturn('MKCOL');
121121

122-
$this->expectException(MethodNotAllowed::class);
122+
$this->expectException(BadRequest::class);
123123

124124
$this->plugin->beforeMethod($this->request, $this->response);
125125
}
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
/*!
2+
* SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors
3+
* SPDX-License-Identifier: AGPL-3.0-or-later
4+
*/
5+
import { InvalidFilenameError, InvalidFilenameErrorReason, validateFilename } from '@nextcloud/files'
6+
import { t } from '@nextcloud/l10n'
7+
8+
/**
9+
* Get the validity of a filename (empty if valid).
10+
* This can be used for `setCustomValidity` on input elements
11+
* @param name The filename
12+
* @param escape Escape the matched string in the error (only set when used in HTML)
13+
*/
14+
export function getGuestNameValidity(name: string, escape = false): string {
15+
if (name.trim() === '') {
16+
return t('files', 'Filename must not be empty.')
17+
}
18+
19+
if (name.startsWith('.')) {
20+
return t('files', 'Names must not start with a dot.')
21+
}
22+
23+
try {
24+
validateFilename(name)
25+
return ''
26+
} catch (error) {
27+
if (!(error instanceof InvalidFilenameError)) {
28+
throw error
29+
}
30+
31+
switch (error.reason) {
32+
case InvalidFilenameErrorReason.Character:
33+
return t('files', '"{char}" is not allowed inside a name.', { char: error.segment }, undefined, { escape })
34+
case InvalidFilenameErrorReason.ReservedName:
35+
return t('files', '"{segment}" is a reserved name and not allowed.', { segment: error.segment }, undefined, { escape: false })
36+
case InvalidFilenameErrorReason.Extension:
37+
if (error.segment.match(/\.[a-z]/i)) {
38+
return t('files', '"{extension}" is not an allowed name.', { extension: error.segment }, undefined, { escape: false })
39+
}
40+
return t('files', 'Names must not end with "{extension}".', { extension: error.segment }, undefined, { escape: false })
41+
default:
42+
return t('files', 'Invalid name.')
43+
}
44+
}
45+
}

apps/files_sharing/src/views/PublicAuthPrompt.vue

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,12 +35,14 @@
3535

3636
<script lang="ts">
3737
import { defineComponent } from 'vue'
38+
import { loadState } from '@nextcloud/initial-state'
3839
import { t } from '@nextcloud/l10n'
3940
4041
import NcDialog from '@nextcloud/vue/components/NcDialog'
4142
import NcNoteCard from '@nextcloud/vue/components/NcNoteCard'
4243
import NcTextField from '@nextcloud/vue/components/NcTextField'
43-
import { loadState } from '@nextcloud/initial-state'
44+
45+
import { getGuestNameValidity } from '../services/GuestNameValidity'
4446
4547
export default defineComponent({
4648
name: 'PublicAuthPrompt',
@@ -101,6 +103,19 @@ export default defineComponent({
101103
},
102104
immediate: true,
103105
},
106+
107+
name() {
108+
// Check validity of the new name
109+
const newName = this.name.trim?.() || ''
110+
const input = (this.$refs.input as Vue|undefined)?.$el.querySelector('input')
111+
if (!input) {
112+
return
113+
}
114+
115+
const validity = getGuestNameValidity(newName)
116+
input.setCustomValidity(validity)
117+
input.reportValidity()
118+
},
104119
},
105120
})
106121
</script>

build/integration/filesdrop_features/filesdrop.feature

Lines changed: 43 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ Feature: FilesDrop
4444
And Updating last share with
4545
| permissions | 4 |
4646
When Dropping file "/folder/a.txt" with "abc"
47-
Then the HTTP status code should be "405"
47+
Then the HTTP status code should be "400"
4848

4949
Scenario: Files drop forbid MKCOL without a nickname
5050
Given user "user0" exists
@@ -57,7 +57,7 @@ Feature: FilesDrop
5757
And Updating last share with
5858
| permissions | 4 |
5959
When Creating folder "folder" in drop
60-
Then the HTTP status code should be "405"
60+
Then the HTTP status code should be "400"
6161

6262
Scenario: Files drop allows MKCOL with a nickname
6363
Given user "user0" exists
@@ -83,7 +83,7 @@ Feature: FilesDrop
8383
And Updating last share with
8484
| permissions | 4 |
8585
When dropping file "/folder/a.txt" with "abc"
86-
Then the HTTP status code should be "405"
86+
Then the HTTP status code should be "400"
8787

8888
Scenario: Files request drop
8989
Given user "user0" exists
@@ -195,4 +195,43 @@ Feature: FilesDrop
195195
| attributes | [{"scope":"fileRequest","key":"enabled","value":true}] |
196196
| shareWith | |
197197
When Dropping file "/folder/a.txt" with "abc"
198-
Then the HTTP status code should be "405"
198+
Then the HTTP status code should be "400"
199+
200+
Scenario: Files request drop with invalid nickname with slashes
201+
Given user "user0" exists
202+
And As an "user0"
203+
And user "user0" created a folder "/drop"
204+
And as "user0" creating a share with
205+
| path | drop |
206+
| shareType | 4 |
207+
| permissions | 4 |
208+
| attributes | [{"scope":"fileRequest","key":"enabled","value":true}] |
209+
| shareWith | |
210+
When Dropping file "/folder/a.txt" with "abc" as "Alice/Bob/Mallory"
211+
Then the HTTP status code should be "400"
212+
213+
Scenario: Files request drop with invalid nickname with forbidden characters
214+
Given user "user0" exists
215+
And As an "user0"
216+
And user "user0" created a folder "/drop"
217+
And as "user0" creating a share with
218+
| path | drop |
219+
| shareType | 4 |
220+
| permissions | 4 |
221+
| attributes | [{"scope":"fileRequest","key":"enabled","value":true}] |
222+
| shareWith | |
223+
When Dropping file "/folder/a.txt" with "abc" as ".htaccess"
224+
Then the HTTP status code should be "400"
225+
226+
Scenario: Files request drop with invalid nickname with forbidden characters
227+
Given user "user0" exists
228+
And As an "user0"
229+
And user "user0" created a folder "/drop"
230+
And as "user0" creating a share with
231+
| path | drop |
232+
| shareType | 4 |
233+
| permissions | 4 |
234+
| attributes | [{"scope":"fileRequest","key":"enabled","value":true}] |
235+
| shareWith | |
236+
When Dropping file "/folder/a.txt" with "abc" as ".Mallory"
237+
Then the HTTP status code should be "400"

dist/6127-6127.js

Lines changed: 2 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,65 @@
11
SPDX-License-Identifier: MIT
2+
SPDX-License-Identifier: ISC
23
SPDX-License-Identifier: GPL-3.0-or-later
34
SPDX-License-Identifier: AGPL-3.0-or-later
45
SPDX-License-Identifier: (MPL-2.0 OR Apache-2.0)
6+
SPDX-FileCopyrightText: inherits developers
57
SPDX-FileCopyrightText: escape-html developers
68
SPDX-FileCopyrightText: Tobias Koppers @sokra
9+
SPDX-FileCopyrightText: Roman Shtylman <shtylman@gmail.com>
10+
SPDX-FileCopyrightText: Roeland Jago Douma
711
SPDX-FileCopyrightText: Nextcloud GmbH and Nextcloud contributors
12+
SPDX-FileCopyrightText: Joyent
13+
SPDX-FileCopyrightText: Jonas Schade <derzade@gmail.com>
814
SPDX-FileCopyrightText: John Molakvoæ (skjnldsv) <skjnldsv@protonmail.com>
915
SPDX-FileCopyrightText: Guillaume Chau <guillaume.b.chau@gmail.com>
16+
SPDX-FileCopyrightText: GitHub Inc.
1017
SPDX-FileCopyrightText: Evan You
1118
SPDX-FileCopyrightText: Dr.-Ing. Mario Heiderich, Cure53 <mario@cure53.de> (https://cure53.de/)
1219
SPDX-FileCopyrightText: David Clark
20+
SPDX-FileCopyrightText: Christoph Wurst <christoph@winzerhof-wurst.at>
1321
SPDX-FileCopyrightText: Christoph Wurst
1422
SPDX-FileCopyrightText: Anthony Fu <https://github.com/antfu>
23+
SPDX-FileCopyrightText: Alkemics
1524

1625

1726
This file is generated from multiple sources. Included packages:
27+
- @nextcloud/auth
28+
- version: 2.5.1
29+
- license: GPL-3.0-or-later
30+
- @nextcloud/browser-storage
31+
- version: 0.4.0
32+
- license: GPL-3.0-or-later
33+
- @nextcloud/capabilities
34+
- version: 1.2.0
35+
- license: GPL-3.0-or-later
36+
- semver
37+
- version: 7.6.3
38+
- license: ISC
39+
- @nextcloud/event-bus
40+
- version: 3.3.2
41+
- license: GPL-3.0-or-later
42+
- @nextcloud/files
43+
- version: 3.10.2
44+
- license: AGPL-3.0-or-later
1845
- @nextcloud/initial-state
1946
- version: 2.2.0
2047
- license: GPL-3.0-or-later
2148
- @nextcloud/l10n
2249
- version: 3.2.0
2350
- license: GPL-3.0-or-later
51+
- @nextcloud/logger
52+
- version: 3.0.2
53+
- license: GPL-3.0-or-later
54+
- @nextcloud/paths
55+
- version: 2.2.1
56+
- license: GPL-3.0-or-later
2457
- @nextcloud/router
2558
- version: 3.0.1
2659
- license: GPL-3.0-or-later
60+
- @nextcloud/sharing
61+
- version: 0.2.4
62+
- license: GPL-3.0-or-later
2763
- @nextcloud/vue
2864
- version: 8.27.0
2965
- license: AGPL-3.0-or-later
@@ -33,6 +69,9 @@ This file is generated from multiple sources. Included packages:
3369
- @vueuse/shared
3470
- version: 11.3.0
3571
- license: MIT
72+
- cancelable-promise
73+
- version: 4.3.1
74+
- license: MIT
3675
- css-loader
3776
- version: 7.1.2
3877
- license: MIT
@@ -48,12 +87,27 @@ This file is generated from multiple sources. Included packages:
4887
- focus-trap
4988
- version: 7.6.5
5089
- license: MIT
90+
- inherits
91+
- version: 2.0.3
92+
- license: ISC
93+
- util
94+
- version: 0.10.4
95+
- license: MIT
96+
- path
97+
- version: 0.12.7
98+
- license: MIT
99+
- process
100+
- version: 0.11.10
101+
- license: MIT
51102
- style-loader
52103
- version: 4.0.0
53104
- license: MIT
54105
- tabbable
55106
- version: 6.2.0
56107
- license: MIT
108+
- typescript-event-target
109+
- version: 1.1.1
110+
- license: MIT
57111
- vue-loader
58112
- version: 15.11.1
59113
- license: MIT

0 commit comments

Comments
 (0)