Skip to content

Commit

Permalink
fix: validate all images on objects with markdown image links
Browse files Browse the repository at this point in the history
  • Loading branch information
1emu committed Dec 19, 2024
1 parent 528deeb commit 3072cbd
Show file tree
Hide file tree
Showing 2 changed files with 100 additions and 14 deletions.
27 changes: 13 additions & 14 deletions src/routes/proposal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,14 @@ import Time from '../utils/date/Time'
import { ErrorCategory } from '../utils/errorCategories'
import { isProdEnv } from '../utils/governanceEnvs'
import logger from '../utils/logger'
import { validateAddress, validateId, validateIsDaoCommittee, validateStatusUpdate } from '../utils/validations'
import {
isValidImage,
validateAddress,
validateId,
validateIsDaoCommittee,
validateObjectMarkdownImages,
validateStatusUpdate,
} from '../utils/validations'

const PITCH_PROPOSAL_SUBMIT_ENABLED = false
const GRANT_PROPOSAL_SUBMIT_ENABLED = false
Expand Down Expand Up @@ -481,6 +488,10 @@ export async function createProposalBid(req: WithAuth) {

/* eslint-disable @typescript-eslint/no-explicit-any */
export async function createProposal(proposalInCreation: ProposalInCreation) {
const { isValid, errors } = await validateObjectMarkdownImages(proposalInCreation)
if (!isValid) {
throw new RequestError(`One or more images are not valid: ${errors.join(', ')}`, RequestError.BadRequest)
}
try {
return await ProposalService.createProposal(proposalInCreation)
} catch (error: any) {
Expand Down Expand Up @@ -600,17 +611,5 @@ async function validateSubmissionThreshold(user: string, submissionThreshold?: s

async function checkImage(req: Request) {
const imageUrl = req.query.url as string
const allowedImageTypes = new Set(['image/bmp', 'image/jpeg', 'image/png', 'image/webp'])

return new Promise<boolean>((resolve) => {
fetch(imageUrl)
.then((response) => {
const mime = response.headers.get('content-type')
resolve(!!mime && allowedImageTypes.has(mime))
})
.catch((error) => {
logger.error('Fetching image error', error)
resolve(false)
})
})
return await isValidImage(imageUrl)
}
87 changes: 87 additions & 0 deletions src/utils/validations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import { validateUniqueAddresses } from '../entities/Transparency/utils'
import { ErrorService } from '../services/ErrorService'
import { ProjectService } from '../services/ProjectService'
import { EventFilterSchema } from '../shared/types/events'
import logger from '../utils/logger'

import { ErrorCategory } from './errorCategories'

Expand Down Expand Up @@ -237,3 +238,89 @@ export function stringToBoolean(str: string) {
throw new Error('Invalid boolean value')
}
}

export async function isValidImage(imageUrl: string) {
const allowedImageTypes = new Set(['image/bmp', 'image/jpeg', 'image/png', 'image/webp'])

return new Promise<boolean>((resolve) => {
fetch(imageUrl)
.then((response) => {
const mime = response.headers.get('content-type')
resolve(!!mime && allowedImageTypes.has(mime))
})
.catch((error) => {
logger.error('Fetching image error', error)
resolve(false)
})
})
}

export async function valdidateImagesUrls(proposalSection: string) {
const imageUrls = extractImageUrls(proposalSection)

const errors: string[] = []
for (const imageUrl of imageUrls) {
const isValid = await isValidImage(imageUrl)
if (!isValid) {
errors.push(imageUrl)
}
}

return {
isValid: errors.length === 0,
errors,
}
}

export function extractImageUrls(markdown: string): string[] {
const imageRegex = /!\[.*?\]\((.*?)\)|\[.*?\]:\s*(.*?)(?:\s|$)/g
const urls: string[] = []
let match

while ((match = imageRegex.exec(markdown)) !== null) {
const url = match[1] || match[2]
if (url) urls.push(url)
}

return urls
}

// eslint-disable-next-line @typescript-eslint/no-explicit-any
async function validateImagesOnValue(value: any, errors: string[]) {
if (typeof value === 'string') {
const imageUrls = extractImageUrls(value)
for (const imageUrl of imageUrls) {
const isValid = await isValidImage(imageUrl)
if (!isValid) {
errors.push(imageUrl)
}
}
} else if (value && typeof value === 'object') {
await validateImagesOnObject(value, errors)
}
}

// eslint-disable-next-line @typescript-eslint/no-explicit-any
async function validateImagesOnObject(obj: any, errors: string[]) {
if (Array.isArray(obj)) {
for (const item of obj) {
await validateImagesOnValue(item, errors)
}
} else {
for (const key in obj) {
await validateImagesOnValue(obj[key], errors)
}
}
}

// eslint-disable-next-line @typescript-eslint/no-explicit-any
export async function validateObjectMarkdownImages(obj: any): Promise<{ isValid: boolean; errors: string[] }> {
const errors: string[] = []

await validateImagesOnObject(obj, errors)

return {
isValid: errors.length === 0,
errors,
}
}

0 comments on commit 3072cbd

Please sign in to comment.