mirror of
https://github.com/frappe/crm.git
synced 2025-12-03 13:34:40 +00:00
fix: enhance website handling with URL validation
This commit is contained in:
@@ -163,7 +163,12 @@ import { globalStore } from '@/stores/global'
|
||||
import { usersStore } from '@/stores/users'
|
||||
import { statusesStore } from '@/stores/statuses'
|
||||
import { getView } from '@/utils/view'
|
||||
import { formatDate, timeAgo, validateIsImageFile } from '@/utils'
|
||||
import {
|
||||
formatDate,
|
||||
timeAgo,
|
||||
validateIsImageFile,
|
||||
openWebsite as openExternalWebsite,
|
||||
} from '@/utils'
|
||||
import {
|
||||
Breadcrumbs,
|
||||
Avatar,
|
||||
@@ -281,8 +286,12 @@ async function deleteOrganization() {
|
||||
}
|
||||
|
||||
function openWebsite() {
|
||||
if (!organization.doc.website) toast.error(__('No website found'))
|
||||
else window.open(organization.doc.website, '_blank')
|
||||
if (!organization.doc.website) {
|
||||
toast.error(__('No website found'))
|
||||
return
|
||||
}
|
||||
|
||||
openExternalWebsite(organization.doc.website)
|
||||
}
|
||||
|
||||
const sections = createResource({
|
||||
|
||||
@@ -198,7 +198,13 @@ import { getMeta } from '@/stores/meta'
|
||||
import { usersStore } from '@/stores/users'
|
||||
import { statusesStore } from '@/stores/statuses'
|
||||
import { getView } from '@/utils/view'
|
||||
import { formatDate, timeAgo, validateIsImageFile, setupCustomizations } from '@/utils'
|
||||
import {
|
||||
formatDate,
|
||||
timeAgo,
|
||||
validateIsImageFile,
|
||||
setupCustomizations,
|
||||
openWebsite as openExternalWebsite,
|
||||
} from '@/utils'
|
||||
import {
|
||||
Breadcrumbs,
|
||||
Avatar,
|
||||
@@ -235,10 +241,11 @@ const errorMessage = ref('')
|
||||
|
||||
const showDeleteLinkedDocModal = ref(false)
|
||||
|
||||
const { document: organization, permissions, scripts } = useDocument(
|
||||
'CRM Organization',
|
||||
props.organizationId,
|
||||
)
|
||||
const {
|
||||
document: organization,
|
||||
permissions,
|
||||
scripts,
|
||||
} = useDocument('CRM Organization', props.organizationId)
|
||||
|
||||
const canDelete = computed(() => permissions.data?.permissions?.delete || false)
|
||||
|
||||
@@ -318,8 +325,12 @@ function website(url) {
|
||||
}
|
||||
|
||||
function openWebsite() {
|
||||
if (!organization.doc.website) toast.error(__('No website found'))
|
||||
else window.open(organization.doc.website, '_blank')
|
||||
if (!organization.doc.website) {
|
||||
toast.error(__('No website found'))
|
||||
return
|
||||
}
|
||||
|
||||
openExternalWebsite(organization.doc.website)
|
||||
}
|
||||
|
||||
const sections = createResource({
|
||||
|
||||
@@ -235,11 +235,46 @@ export function taskPriorityOptions(action, data) {
|
||||
})
|
||||
}
|
||||
|
||||
export function openWebsite(url) {
|
||||
if (!url.startsWith('http://') && !url.startsWith('https://')) {
|
||||
url = 'https://' + url
|
||||
export function getSafeWebsiteUrl(rawUrl) {
|
||||
const allowedProtocols = new Set(['http:', 'https:'])
|
||||
|
||||
if (!rawUrl) {
|
||||
return null
|
||||
}
|
||||
window.open(url, '_blank')
|
||||
|
||||
const trimmedUrl = rawUrl.trim()
|
||||
|
||||
if (!trimmedUrl) {
|
||||
return null
|
||||
}
|
||||
|
||||
const urlToParse = /^[a-zA-Z][a-zA-Z\d+\-.]*:/.test(trimmedUrl)
|
||||
? trimmedUrl
|
||||
: `https://${trimmedUrl}`
|
||||
|
||||
try {
|
||||
const parsedUrl = new URL(urlToParse)
|
||||
|
||||
if (!allowedProtocols.has(parsedUrl.protocol)) {
|
||||
return null
|
||||
}
|
||||
|
||||
return parsedUrl.href
|
||||
} catch (_error) {
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
export function openWebsite(url) {
|
||||
const safeUrl = getSafeWebsiteUrl(url)
|
||||
|
||||
if (!safeUrl) {
|
||||
toast.error(__('Invalid website URL'))
|
||||
return false
|
||||
}
|
||||
|
||||
window.open(safeUrl, '_blank', 'noopener')
|
||||
return true
|
||||
}
|
||||
|
||||
export function website(url) {
|
||||
|
||||
Reference in New Issue
Block a user