fix(files_sharing): do not double escape the share title

The title is already escaped by vue so special characters would result
in incorrect strings.

Signed-off-by: Ferdinand Thiessen <opensource@fthiessen.de>
pull/53604/head
Ferdinand Thiessen 2025-06-18 19:40:54 +07:00 committed by Andy Scherzinger
parent 3cf84b7a0f
commit ae3027f852
No known key found for this signature in database
GPG Key ID: 6CADC7E3523C308B
2 changed files with 73 additions and 28 deletions

@ -217,22 +217,23 @@
</template>
<script>
import { showError, showSuccess } from '@nextcloud/dialogs'
import { emit } from '@nextcloud/event-bus'
import { t } from '@nextcloud/l10n'
import moment from '@nextcloud/moment'
import { generateUrl, getBaseUrl } from '@nextcloud/router'
import { showError, showSuccess } from '@nextcloud/dialogs'
import { ShareType } from '@nextcloud/sharing'
import VueQrcode from '@chenfengyuan/vue-qrcode'
import moment from '@nextcloud/moment'
import NcActionButton from '@nextcloud/vue/dist/Components/NcActionButton.js'
import NcActionCheckbox from '@nextcloud/vue/dist/Components/NcActionCheckbox.js'
import NcActionInput from '@nextcloud/vue/dist/Components/NcActionInput.js'
import NcActionLink from '@nextcloud/vue/dist/Components/NcActionLink.js'
import NcActionText from '@nextcloud/vue/dist/Components/NcActionText.js'
import NcActionSeparator from '@nextcloud/vue/dist/Components/NcActionSeparator.js'
import NcActions from '@nextcloud/vue/dist/Components/NcActions.js'
import NcAvatar from '@nextcloud/vue/dist/Components/NcAvatar.js'
import NcDialog from '@nextcloud/vue/dist/Components/NcDialog.js'
import VueQrcode from '@chenfengyuan/vue-qrcode'
import NcActionButton from '@nextcloud/vue/components/NcActionButton'
import NcActionCheckbox from '@nextcloud/vue/components/NcActionCheckbox'
import NcActionInput from '@nextcloud/vue/components/NcActionInput'
import NcActionLink from '@nextcloud/vue/components/NcActionLink'
import NcActionText from '@nextcloud/vue/components/NcActionText'
import NcActionSeparator from '@nextcloud/vue/components/NcActionSeparator'
import NcActions from '@nextcloud/vue/components/NcActions'
import NcAvatar from '@nextcloud/vue/components/NcAvatar'
import NcDialog from '@nextcloud/vue/components/NcDialog'
import Tune from 'vue-material-design-icons/Tune.vue'
import IconCalendarBlank from 'vue-material-design-icons/CalendarBlank.vue'
@ -251,7 +252,7 @@ import GeneratePassword from '../utils/GeneratePassword.ts'
import Share from '../models/Share.ts'
import SharesMixin from '../mixins/SharesMixin.js'
import ShareDetails from '../mixins/ShareDetails.js'
import { getLoggerBuilder } from '@nextcloud/logger'
import logger from '../services/logger.ts'
export default {
name: 'SharingEntryLink',
@ -305,10 +306,6 @@ export default {
ExternalLegacyLinkActions: OCA.Sharing.ExternalLinkActions.state,
ExternalShareActions: OCA.Sharing.ExternalShareActions.state,
logger: getLoggerBuilder()
.setApp('files_sharing')
.detectUser()
.build(),
// tracks whether modal should be opened or not
showQRCode: false,
@ -322,6 +319,8 @@ export default {
* @return {string}
*/
title() {
const l10nOptions = { escape: false /* no escape as this string is already escaped by Vue */ }
// if we have a valid existing share (not pending)
if (this.share && this.share.id) {
if (!this.isShareOwner && this.share.ownerDisplayName) {
@ -329,26 +328,26 @@ export default {
return t('files_sharing', '{shareWith} by {initiator}', {
shareWith: this.share.shareWith,
initiator: this.share.ownerDisplayName,
})
}, l10nOptions)
}
return t('files_sharing', 'Shared via link by {initiator}', {
initiator: this.share.ownerDisplayName,
})
}, l10nOptions)
}
if (this.share.label && this.share.label.trim() !== '') {
if (this.isEmailShareType) {
if (this.isFileRequest) {
return t('files_sharing', 'File request ({label})', {
label: this.share.label.trim(),
})
}, l10nOptions)
}
return t('files_sharing', 'Mail share ({label})', {
label: this.share.label.trim(),
})
}, l10nOptions)
}
return t('files_sharing', 'Share link ({label})', {
label: this.share.label.trim(),
})
}, l10nOptions)
}
if (this.isEmailShareType) {
if (!this.share.shareWith || this.share.shareWith.trim() === '') {
@ -383,6 +382,7 @@ export default {
}
return null
},
passwordExpirationTime() {
if (this.share.passwordExpirationTime === null) {
return null
@ -605,7 +605,7 @@ export default {
* @param {boolean} shareReviewComplete if the share was reviewed
*/
async onNewLinkShare(shareReviewComplete = false) {
this.logger.debug('onNewLinkShare called (with this.share)', this.share)
logger.debug('onNewLinkShare called (with this.share)', this.share)
// do not run again if already loading
if (this.loading) {
return
@ -620,7 +620,7 @@ export default {
shareDefaults.expiration = this.formatDateToString(this.config.defaultExpirationDate)
}
this.logger.debug('Missing required properties?', this.enforcedPropertiesMissing)
logger.debug('Missing required properties?', this.enforcedPropertiesMissing)
// Do not push yet if we need a password or an expiration date: show pending menu
// A share would require a review for example is default expiration date is set but not enforced, this allows
// the user to review the share and remove the expiration date if they don't want it
@ -628,7 +628,7 @@ export default {
this.pending = true
this.shareCreationComplete = false
this.logger.info('Share policy requires a review or has mandated properties (password, expirationDate)...')
logger.info('Share policy requires a review or has mandated properties (password, expirationDate)...')
// ELSE, show the pending popovermenu
// if password default or enforced, pre-fill with random one
@ -656,13 +656,13 @@ export default {
// if the share is valid, create it on the server
if (this.checkShare(this.share)) {
try {
this.logger.info('Sending existing share to server', this.share)
logger.info('Sending existing share to server', this.share)
await this.pushNewLinkShare(this.share, true)
this.shareCreationComplete = true
this.logger.info('Share created on server', this.share)
logger.info('Share created on server', this.share)
} catch (e) {
this.pending = false
this.logger.error('Error creating share', e)
logger.error('Error creating share', e)
return false
}
return true

@ -0,0 +1,45 @@
/*!
* SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
import type { User } from "@nextcloud/cypress"
import { createShare } from "./FilesSharingUtils"
import { createLinkShare, openLinkShareDetails } from "./PublicShareUtils"
describe('files_sharing: sidebar tab', () => {
let alice: User
beforeEach(() => {
cy.createRandomUser()
.then((user) => {
alice = user
cy.mkdir(user, '/test')
cy.login(user)
cy.visit('/apps/files')
})
})
/**
* Regression tests of https://github.com/nextcloud/server/issues/53566
* Where the ' char was shown as &#39;
*/
it('correctly lists shares by label with special characters', () => {
createLinkShare({ user: alice }, 'test')
openLinkShareDetails(0)
cy.findByRole('textbox', { name: /share label/i })
.should('be.visible')
.type('Alice\' share')
cy.intercept('PUT', '**/ocs/v2.php/apps/files_sharing/api/v1/shares/*').as('PUT')
cy.findByRole('button', { name: /update share/i }).click()
cy.wait('@PUT')
// see the label is shown correctly
cy.findByRole('list', { name: /link shares/i })
.findAllByRole('listitem')
.should('have.length', 1)
.first()
.should('contain.text', 'Share link (Alice\' share)')
})
})