Skip to content

# Implementation Plan: File Upload (Multipart/Form-Data)  #43

Description

@nglmercer

To implement file upload handling in Kito, we need to add multipart/form-data parsing support to the Rust core, expose the API in TypeScript, and maintain compatibility with the existing system.

Files to Modify

1. Core Rust - Request Parsing

packages/core/src/http/request.rs

  • Add UploadedFile structure to represent uploaded files
  • Modify RequestCore to include files field
  • Implement multipart/form-data parser
// New structure for files
pub struct UploadedFile {
    pub filename: String,
    pub content_type: String,
    pub size: usize,
    pub data: Bytes,
}

// Modify RequestCore
pub struct RequestCore {
    // ... existing fields ...
    pub files: HashMap<String, UploadedFile>,
    pub multipart_boundary: Option<String>,
}

2. Core Rust - Multipart Parser

New file: packages/core/src/http/multipart.rs

pub struct MultipartParser {
    boundary: String,
    data: Bytes,
}

impl MultipartParser {
    pub fn new(boundary: String, data: Bytes) -> Self { ... }
    pub fn parse(&mut self) -> Result<(HashMap<String, String>, HashMap<String, UploadedFile>), MultipartError> { ... }
}

pub enum MultipartError {
    InvalidBoundary,
    InvalidFormat,
    IoError(std::io::Error),
}

3. TypeScript Types

packages/types/src/http/request.d.ts

export interface UploadedFile {
  filename: string;
  contentType: string;
  size: number;
  data: Buffer;
}

export interface RequestFiles {
  [fieldName: string]: UploadedFile | UploadedFile[];
}

// Modify KitoRequest
export interface KitoRequest {
  // ... existing properties ...
  files?: RequestFiles;
}

4. Request Builder TypeScript

packages/kitojs/src/server/request.ts

  • Add cache for files
  • Implement getter for files
  • Import new N-API functions
export class RequestBuilder implements KitoRequest {
  // ... existing fields ...
  private _files?: Record<string, UploadedFile | UploadedFile[]>;

  get files(): Record<string, UploadedFile | UploadedFile[]> | undefined {
    if (!this._files) {
      this._files = getAllFiles(this.core);
    }
    return this._files;
  }
}

5. N-API Bindings

packages/core/src/lib.rs

  • Expose new functions for file access
#[napi]
pub fn get_all_files(req: &RequestCore) -> HashMap<String, UploadedFile> { ... }

#[napi]
pub fn get_file(req: &RequestCore, name: String) -> Option<UploadedFile> { ... }

Implementation Details

Multipart Parser in Rust

The parser must:

  1. Identify boundary from Content-Type header
  2. Split body into parts using boundary
  3. Parse headers for each part (Content-Disposition, Content-Type)
  4. Extract binary data for files
  5. Handle multiple files per field

Integration with RequestCore

In RequestCore::new() 1 :

// After parsing body
if let Some(content_type) = headers_raw.get("content-type") {
    if content_type.starts_with("multipart/form-data") {
        if let Some(boundary) = extract_boundary(content_type) {
            let mut parser = MultipartParser::new(boundary, body.clone());
            match parser.parse() {
                Ok((fields, files)) => {
                    // Merge fields with query_raw if needed
                    self.files = files;
                }
                Err(e) => return Err(RequestError::MultipartError(e)),
            }
        }
    }
}

Validation Middleware

Optionally, add middleware for file validation:

New file: packages/kitojs/src/helpers/fileValidator.ts

export interface FileValidationOptions {
  maxSize?: number;
  allowedTypes?: string[];
  required?: boolean;
}

export function validateFiles(options: FileValidationOptions) {
  return middleware((ctx, next) => {
    // Validation logic
  });
}

Usage Example

import { server } from 'kitojs';

const app = server();

app.post('/upload', async (ctx) => {
  const files = ctx.req.files;
  
  if (files?.avatar) {
    const avatar = Array.isArray(files.avatar) ? files.avatar[0] : files.avatar;
    console.log(`File received: ${avatar.filename} (${avatar.size} bytes)`);
    
    // Save file, process, etc.
    await saveFile(avatar.data, avatar.filename);
  }
  
  ctx.res.json({ success: true });
});

app.listen(3000);

Considerations

Security

  • Validate maximum file size
  • Verify allowed content types
  • Sanitize filenames

Performance

  • Streaming for large files
  • Memory limit for parsing
  • Option to write files directly to disk

Compatibility

  • Maintain backward compatibility
  • Don't affect parsing of other content-types
  • Integrate with existing validation system

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions