file-upload

v2026.09.24

Server-side file upload handling. Covers Multer (Node.js/Express), FastAPI UploadFile (Python), Spring MultipartFile (Java). File validation, streaming, size limits, and storage strategies. USE WHEN: user mentions "file upload", "multipart", "multer", "upload endpoint", "form data", "file validation", "image upload", "MultipartFile", "UploadFile" DO NOT USE FOR: cloud storage (S3/GCS/Azure) - use `cloud-storage`; file download/export - use `data-export`

GitHub
安装命令
npx skhub add claude-dev-suite/file-upload
Markdown
SKILL.md

File Upload Handling

Node.js (Multer — recommended)

import multer from 'multer';
import path from 'path';
import crypto from 'crypto';

// Storage configuration
const storage = multer.diskStorage({
  destination: './uploads',
  filename: (req, file, cb) => {
    const uniqueName = `${crypto.randomUUID()}${path.extname(file.originalname)}`;
    cb(null, uniqueName);
  },
});

// File filter
const fileFilter = (req: Express.Request, file: Express.Multer.File, cb: multer.FileFilterCallback) => {
  const allowedMimes = ['image/jpeg', 'image/png', 'image/webp', 'application/pdf'];
  if (allowedMimes.includes(file.mimetype)) {
    cb(null, true);
  } else {
    cb(new Error(`File type ${file.mimetype} not allowed`));
  }
};

const upload = multer({
  storage,
  fileFilter,
  limits: { fileSize: 10 * 1024 * 1024 }, // 10MB
});

// Single file
app.post('/upload', upload.single('file'), (req, res) => {
  res.json({ filename: req.file!.filename, size: req.file!.size });
});

// Multiple files
app.post('/upload/multiple', upload.array('files', 10), (req, res) => {
  res.json({ count: (req.files as Express.Multer.File[]).length });
});

// Error handling
app.use((err: Error, req: Request, res: Response, next: NextFunction) => {
  if (err instanceof multer.MulterError) {
    if (err.code === 'LIMIT_FILE_SIZE') return res.status(413).json({ error: 'File too large' });
    return res.status(400).json({ error: err.message });
  }
  if (err.message.includes('not allowed')) return res.status(415).json({ error: err.message });
  next(err);
});

Memory storage (for cloud forwarding)

const upload = multer({ storage: multer.memoryStorage(), limits: { fileSize: 10 * 1024 * 1024 } });

app.post('/upload', upload.single('file'), async (req, res) => {
  // req.file.buffer contains the file — forward to S3
  await s3.send(new PutObjectCommand({
    Bucket: bucket, Key: key, Body: req.file!.buffer, ContentType: req.file!.mimetype,
  }));
});

Python (FastAPI)

from fastapi import UploadFile, File, HTTPException
import aiofiles, uuid

ALLOWED_TYPES = {"image/jpeg", "image/png", "application/pdf"}
MAX_SIZE = 10 * 1024 * 1024  # 10MB

@app.post("/upload")
async def upload_file(file: UploadFile = File(...)):
    if file.content_type not in ALLOWED_TYPES:
        raise HTTPException(415, f"Type {file.content_type} not allowed")

    content = await file.read()
    if len(content) > MAX_SIZE:
        raise HTTPException(413, "File too large")

    filename = f"{uuid.uuid4()}{Path(file.filename).suffix}"
    async with aiofiles.open(f"uploads/{filename}", "wb") as f:
        await f.write(content)

    return {"filename": filename, "size": len(content)}

Java (Spring Boot)

@PostMapping("/upload")
public ResponseEntity<Map<String, String>> upload(
        @RequestParam("file") MultipartFile file) {

    if (file.isEmpty()) throw new ResponseStatusException(BAD_REQUEST, "Empty file");
    if (file.getSize() > 10_000_000) throw new ResponseStatusException(PAYLOAD_TOO_LARGE);

    String ext = StringUtils.getFilenameExtension(file.getOriginalFilename());
    String filename = UUID.randomUUID() + "." + ext;
    Path dest = Path.of("uploads", filename);
    file.transferTo(dest);

    return ResponseEntity.ok(Map.of("filename", filename));
}

Spring config:

spring:
  servlet:
    multipart:
      max-file-size: 10MB
      max-request-size: 10MB

File Validation Beyond MIME

// Validate actual file content (magic bytes), not just extension
import { fileTypeFromBuffer } from 'file-type';

const type = await fileTypeFromBuffer(req.file!.buffer);
if (!type || !['image/jpeg', 'image/png'].includes(type.mime)) {
  return res.status(415).json({ error: 'Invalid file content' });
}

Anti-Patterns

Anti-PatternFix
Trust client MIME type onlyValidate magic bytes with file-type
Original filename as storage keyUse UUID to prevent path traversal and collisions
No file size limitAlways set limits.fileSize
Sync disk writes on uploadUse streams or async writes
Storing uploads in app directoryUse separate /uploads or cloud storage
No cleanup of temp filesImplement lifecycle/cron cleanup

Production Checklist

  • File size limits configured
  • MIME type whitelist (validate magic bytes, not just extension)
  • UUID filenames (never use original filename for storage)
  • Multer error handler middleware
  • Virus scanning for user uploads (ClamAV)
  • Rate limiting on upload endpoints
  • Cleanup strategy for orphaned files
发现
标签

此技能尚未发布标签。

版本
最新版本元数据

版本

v2026.09.24

发布时间

2026年9月24日

分类

未分类

许可证

MIT

源路径

skills/file-storage/file-upload

默认分支

main

最新提交

9496306

Tree SHA

fe4e2f1