Skip to content

Commit

Permalink
feat: sanitize file names (#767)
Browse files Browse the repository at this point in the history
  • Loading branch information
BlankParticle authored Sep 2, 2024
1 parent cdf0d5e commit abe875b
Show file tree
Hide file tree
Showing 8 changed files with 32 additions and 188 deletions.
13 changes: 10 additions & 3 deletions apps/mail-bridge/queue/mail-processor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import { createExtensionSet } from '@u22n/tiptap/extensions';
import { sendRealtimeNotification } from '../utils/realtime';
import { simpleParser, type EmailAddress } from 'mailparser';
import { parseAddressIds } from '../utils/contactParsing';
import { sanitizeFilename } from '@u22n/utils/sanitizers';
import { addConvoToSpace } from '../utils/spaceUtils';
import { eq, and, inArray } from '@u22n/database/orm';
import { tiptapCore, tiptapHtml } from '@u22n/tiptap';
Expand Down Expand Up @@ -244,9 +245,11 @@ type GenerationContext = {
function generateFileName({ identifier, fileType }: GenerationContext) {
switch (fileType) {
case 'text/calendar':
return `${identifier} Calender Invite.ics`;
return sanitizeFilename(`${identifier} Calender Invite.ics`);
default:
return `Attachment (${identifier}) ${new Date().toDateString()}.${mime.getExtension(fileType) ?? 'bin'}`;
return sanitizeFilename(
`Attachment ${identifier} ${new Date().toDateString()}.${mime.getExtension(fileType) ?? 'bin'}`
);
}
}

Expand Down Expand Up @@ -374,7 +377,11 @@ export const worker = createWorker<MailProcessorJobData>(
: parsedEmail.textAsHtml
? parsedEmail.textAsHtml.replace(/\n/g, '')
: '';
const attachments = parsedEmail.attachments || [];
const attachments =
parsedEmail.attachments.map((f) => ({
...f,
filename: f.filename ? sanitizeFilename(f.filename) : undefined
})) || [];

if (forwardedEmailAddress) {
const forwardingAddressObject: EmailAddress = {
Expand Down
10 changes: 6 additions & 4 deletions apps/storage/proxy/attachment.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { convoAttachments, orgMembers } from '@u22n/database/schema';
import { getSignedUrl } from '@aws-sdk/s3-request-presigner';
import { sanitizeFilename } from '@u22n/utils/sanitizers';
import { GetObjectCommand } from '@aws-sdk/client-s3';
import { typeIdValidator } from '@u22n/utils/typeid';
import { zValidator } from '@u22n/hono/helpers';
Expand All @@ -16,7 +17,7 @@ export const attachmentProxy = createHonoApp<Ctx>().get(
zValidator(
'param',
z.object({
filename: z.string().transform((f) => decodeURIComponent(f)),
filename: z.string().transform(decodeURIComponent),
attachmentId: typeIdValidator('convoAttachments'),
orgShortcode: z.string()
})
Expand Down Expand Up @@ -44,7 +45,8 @@ export const attachmentProxy = createHonoApp<Ctx>().get(

if (
!attachmentQueryResponse ||
decodeURIComponent(attachmentQueryResponse.fileName) !== filename ||
sanitizeFilename(attachmentQueryResponse.fileName) !==
sanitizeFilename(filename) ||
attachmentQueryResponse.org.shortcode !== orgShortcode
) {
return c.json(
Expand Down Expand Up @@ -72,7 +74,7 @@ export const attachmentProxy = createHonoApp<Ctx>().get(

const command = new GetObjectCommand({
Bucket: env.STORAGE_S3_BUCKET_ATTACHMENTS,
Key: `${attachmentQueryResponse.org.publicId}/${attachmentId}/${filename}`
Key: `${attachmentQueryResponse.org.publicId}/${attachmentId}/${attachmentQueryResponse.fileName}`
});
const url = await getSignedUrl(s3Client, command, { expiresIn: 3600 });
const res = await fetch(url);
Expand All @@ -84,6 +86,6 @@ export const attachmentProxy = createHonoApp<Ctx>().get(
}
// Cache for 1 hour
c.header('Cache-Control', 'private, max-age=3600');
return c.body(res.body);
return c.body(res.body, res);
}
);
8 changes: 5 additions & 3 deletions apps/storage/proxy/inline-proxy.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { getSignedUrl } from '@aws-sdk/s3-request-presigner';
import { pendingAttachments } from '@u22n/database/schema';
import { sanitizeFilename } from '@u22n/utils/sanitizers';
import { GetObjectCommand } from '@aws-sdk/client-s3';
import { typeIdValidator } from '@u22n/utils/typeid';
import { zValidator } from '@u22n/hono/helpers';
Expand All @@ -19,7 +20,7 @@ export const inlineProxy = createHonoApp<Ctx>()
zValidator(
'param',
z.object({
filename: z.string().transform((f) => decodeURIComponent(f)),
filename: z.string().transform(decodeURIComponent),
attachmentId: typeIdValidator('convoAttachments'),
orgShortcode: z.string()
})
Expand Down Expand Up @@ -52,7 +53,8 @@ export const inlineProxy = createHonoApp<Ctx>()

if (
!attachmentQueryResponse ||
attachmentQueryResponse.filename !== filename ||
sanitizeFilename(attachmentQueryResponse.filename) !==
sanitizeFilename(filename) ||
attachmentQueryResponse.org.shortcode !== orgShortcode ||
!attachmentQueryResponse.org.members.find(
(member) => member.accountId === c.get('account')?.id
Expand All @@ -66,7 +68,7 @@ export const inlineProxy = createHonoApp<Ctx>()

const command = new GetObjectCommand({
Bucket: env.STORAGE_S3_BUCKET_ATTACHMENTS,
Key: `${attachmentQueryResponse.org.publicId}/${attachmentId}/${filename}`
Key: `${attachmentQueryResponse.org.publicId}/${attachmentId}/${attachmentQueryResponse.filename}`
});
const url = await getSignedUrl(s3Client, command, { expiresIn: 3600 });
const res = await fetch(url);
Expand Down
173 changes: 0 additions & 173 deletions apps/web/src/components/shared/attachment-button.tsx

This file was deleted.

5 changes: 3 additions & 2 deletions apps/web/src/components/shared/attachments.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import {
} from '@phosphor-icons/react';
import { type VariantProps, cva } from 'class-variance-authority';
import { memo, useCallback, useMemo, useState } from 'react';
import { sanitizeFilename } from '@u22n/utils/sanitizers';
import { useOrgShortcode } from '@/src/hooks/use-params';
import { cn, prettyBytes } from '../../lib/utils';
import { type TypeId } from '@u22n/utils/typeid';
Expand Down Expand Up @@ -70,7 +71,7 @@ export function useAttachmentUploader(defaultList?: Attachment[]) {
async (file: File) => {
setPrefetching(true);
const preSignedData = (await fetch(
`${env.NEXT_PUBLIC_STORAGE_URL}/api/attachments/presign?orgShortcode=${orgShortcode}&filename=${file.name}`,
`${env.NEXT_PUBLIC_STORAGE_URL}/api/attachments/presign?orgShortcode=${orgShortcode}&filename=${sanitizeFilename(file.name)}`,
{
method: 'GET',
credentials: 'include'
Expand All @@ -83,7 +84,7 @@ export function useAttachmentUploader(defaultList?: Attachment[]) {

setAttachments((prev) =>
prev.concat({
filename: file.name,
filename: sanitizeFilename(file.name),
size: file.size,
type: file.type,
publicId: preSignedData.publicId,
Expand Down
5 changes: 3 additions & 2 deletions apps/web/src/hooks/use-inline-uploader.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { createImageUpload } from '@u22n/tiptap/extensions/image-uploader';
import { sanitizeFilename } from '@u22n/utils/sanitizers';
import { type TypeId } from '@u22n/utils/typeid';
import { useOrgShortcode } from './use-params';
import uploadTracker from '../lib/upload';
Expand Down Expand Up @@ -27,7 +28,7 @@ export function useInlineUploader(sizeValidate?: (file: File) => boolean) {
},
onUpload: async (file) => {
const presignedData = (await fetch(
`${env.NEXT_PUBLIC_STORAGE_URL}/api/attachments/presign?orgShortcode=${orgShortcode}&filename=${file.name}`,
`${env.NEXT_PUBLIC_STORAGE_URL}/api/attachments/presign?orgShortcode=${orgShortcode}&filename=${sanitizeFilename(file.name)}`,
{
method: 'GET',
credentials: 'include'
Expand All @@ -48,7 +49,7 @@ export function useInlineUploader(sizeValidate?: (file: File) => boolean) {
includeCredentials: false
}).then(() => {
const image = new Image();
image.src = `${env.NEXT_PUBLIC_STORAGE_URL}/inline-proxy/${orgShortcode}/${presignedData.publicId}/${file.name}?type=${encodeURIComponent(file.type)}&size=${file.size}`;
image.src = `${env.NEXT_PUBLIC_STORAGE_URL}/inline-proxy/${orgShortcode}/${presignedData.publicId}/${sanitizeFilename(file.name)}?type=${encodeURIComponent(file.type)}&size=${file.size}`;
image.onload = () => {
resolve(image.src);
};
Expand Down
3 changes: 2 additions & 1 deletion packages/utils/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@
"./colors": "./uiColors.ts",
"./zodSchemas": "./zodSchemas.ts",
"./discord": "./discord.ts",
"./spaces": "./spaces.ts"
"./spaces": "./spaces.ts",
"./sanitizers": "./sanitizers.ts"
},
"keywords": [],
"author": "",
Expand Down
3 changes: 3 additions & 0 deletions packages/utils/sanitizers.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export function sanitizeFilename(filename: string) {
return filename.replace(/[^a-zA-Z0-9-_.]/g, '_');
}

0 comments on commit abe875b

Please sign in to comment.