Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 18 additions & 3 deletions apps/dav/lib/Files/Sharing/FilesDropPlugin.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

use OC\Files\View;
use OCP\Share\IShare;
use Sabre\DAV\Exception\BadRequest;
use Sabre\DAV\Exception\MethodNotAllowed;
use Sabre\DAV\ServerPlugin;
use Sabre\HTTP\RequestInterface;
Expand Down Expand Up @@ -64,14 +65,28 @@ public function beforeMethod(RequestInterface $request, ResponseInterface $respo
// Extract the attributes for the file request
$isFileRequest = false;
$attributes = $this->share->getAttributes();
$nickName = $request->hasHeader('X-NC-Nickname') ? urldecode($request->getHeader('X-NC-Nickname')) : null;
$nickName = $request->hasHeader('X-NC-Nickname') ? trim(urldecode($request->getHeader('X-NC-Nickname'))) : null;
if ($attributes !== null) {
$isFileRequest = $attributes->getAttribute('fileRequest', 'enabled') === true;
}

// We need a valid nickname for file requests
if ($isFileRequest && ($nickName == null || trim($nickName) === '')) {
throw new MethodNotAllowed('Nickname is required for file requests');
if ($isFileRequest && !$nickName) {
throw new BadRequest('Nickname is required for file requests');
}

if ($nickName !== null) {
try {
$this->view->verifyPath($path, $nickName);
} catch (\Exception $e) {
// If the path is not valid, we throw an exception
throw new BadRequest('Invalid nickname: ' . $nickName);
}

// Forbid nicknames starting with a dot
if (str_starts_with($nickName, '.')) {
throw new BadRequest('Invalid nickname: ' . $nickName);
}
}

// If this is a file request we need to create a folder for the user
Expand Down
45 changes: 45 additions & 0 deletions apps/files_sharing/src/services/GuestNameValidity.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
/*!
* SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
import { InvalidFilenameError, InvalidFilenameErrorReason, validateFilename } from '@nextcloud/files'
import { t } from '@nextcloud/l10n'

/**
* Get the validity of a filename (empty if valid).
* This can be used for `setCustomValidity` on input elements
* @param name The filename
* @param escape Escape the matched string in the error (only set when used in HTML)
*/
export function getGuestNameValidity(name: string, escape = false): string {
if (name.trim() === '') {
return t('files', 'Filename must not be empty.')
}

if (name.startsWith('.')) {
return t('files', 'Names must not start with a dot.')
}

try {
validateFilename(name)
return ''
} catch (error) {
if (!(error instanceof InvalidFilenameError)) {
throw error
}

switch (error.reason) {
case InvalidFilenameErrorReason.Character:
return t('files', '"{char}" is not allowed inside a name.', { char: error.segment }, undefined, { escape })
case InvalidFilenameErrorReason.ReservedName:
return t('files', '"{segment}" is a reserved name and not allowed.', { segment: error.segment }, undefined, { escape: false })
case InvalidFilenameErrorReason.Extension:
if (error.segment.match(/\.[a-z]/i)) {
return t('files', '"{extension}" is not an allowed name.', { extension: error.segment }, undefined, { escape: false })
}
return t('files', 'Names must not end with "{extension}".', { extension: error.segment }, undefined, { escape: false })
default:
return t('files', 'Invalid name.')
}
}
}
23 changes: 19 additions & 4 deletions apps/files_sharing/src/views/PublicAuthPrompt.vue
Original file line number Diff line number Diff line change
Expand Up @@ -35,12 +35,14 @@

<script lang="ts">
import { defineComponent } from 'vue'
import { loadState } from '@nextcloud/initial-state'
import { t } from '@nextcloud/l10n'

import NcDialog from '@nextcloud/vue/dist/Components/NcDialog.js'
import NcNoteCard from '@nextcloud/vue/dist/Components/NcNoteCard.js'
import NcTextField from '@nextcloud/vue/dist/Components/NcTextField.js'
import { loadState } from '@nextcloud/initial-state'
import NcDialog from '@nextcloud/vue/components/NcDialog'
import NcNoteCard from '@nextcloud/vue/components/NcNoteCard'
import NcTextField from '@nextcloud/vue/components/NcTextField'

import { getGuestNameValidity } from '../services/GuestNameValidity'

export default defineComponent({
name: 'PublicAuthPrompt',
Expand Down Expand Up @@ -101,6 +103,19 @@ export default defineComponent({
},
immediate: true,
},

name() {
// Check validity of the new name
const newName = this.name.trim?.() || ''
const input = (this.$refs.input as Vue|undefined)?.$el.querySelector('input')
if (!input) {
return
}

const validity = getGuestNameValidity(newName)
input.setCustomValidity(validity)
input.reportValidity()
},
},
})
</script>
Expand Down
41 changes: 40 additions & 1 deletion build/integration/filesdrop_features/filesdrop.feature
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ Feature: FilesDrop
And Downloading file "/drop/a.txt"
Then Downloaded content should be "abc"

Scenario: Files drop forbis MKCOL
Scenario: Files drop forbid MKCOL
Given user "user0" exists
And As an "user0"
And user "user0" created a folder "/drop"
Expand Down Expand Up @@ -90,3 +90,42 @@ Feature: FilesDrop
Then Downloaded content should be "abc"
And Downloading file "/drop/Mallory/a (2).txt"
Then Downloaded content should be "def"

Scenario: Files request drop with invalid nickname with slashes
Given user "user0" exists
And As an "user0"
And user "user0" created a folder "/drop"
And as "user0" creating a share with
| path | drop |
| shareType | 4 |
| permissions | 4 |
| attributes | [{"scope":"fileRequest","key":"enabled","value":true}] |
| shareWith | |
When Dropping file "/folder/a.txt" with "abc" as "Alice/Bob/Mallory"
Then the HTTP status code should be "400"

Scenario: Files request drop with invalid nickname with forbidden characters
Given user "user0" exists
And As an "user0"
And user "user0" created a folder "/drop"
And as "user0" creating a share with
| path | drop |
| shareType | 4 |
| permissions | 4 |
| attributes | [{"scope":"fileRequest","key":"enabled","value":true}] |
| shareWith | |
When Dropping file "/folder/a.txt" with "abc" as ".htaccess"
Then the HTTP status code should be "400"

Scenario: Files request drop with invalid nickname with forbidden characters
Given user "user0" exists
And As an "user0"
And user "user0" created a folder "/drop"
And as "user0" creating a share with
| path | drop |
| shareType | 4 |
| permissions | 4 |
| attributes | [{"scope":"fileRequest","key":"enabled","value":true}] |
| shareWith | |
When Dropping file "/folder/a.txt" with "abc" as ".Mallory"
Then the HTTP status code should be "400"
2 changes: 0 additions & 2 deletions dist/4926-4926.js

This file was deleted.

1 change: 0 additions & 1 deletion dist/4926-4926.js.map

This file was deleted.

1 change: 0 additions & 1 deletion dist/4926-4926.js.map.license

This file was deleted.

Loading
Loading