Skip to content

File Upload Handling

Handle file uploads with multipart/form-data parsing and validation.

Overview

Features: - Multipart form data parsing - File size limits - File type validation - Multiple file uploads - Save to disk or process in memory

Basic Usage

#include "crest/crest.hpp"
#include "crest/upload.hpp"

int main() {
    crest::App app;

    app.post("/upload", [](crest::Request& req, crest::Response& res) {
        std::string content_type = req.header("Content-Type");

        if (content_type.find("multipart/form-data") == std::string::npos) {
            res.json(400, R"({"error":"Expected multipart/form-data"})");
            return;
        }

        // Extract boundary
        size_t pos = content_type.find("boundary=");
        std::string boundary = content_type.substr(pos + 9);

        // Parse multipart data
        crest::MultipartParser parser;
        if (!parser.parse(req.body(), boundary)) {
            res.json(400, R"({"error":")" + parser.last_error() + R"("})");
            return;
        }

        // Get uploaded file
        auto file = parser.get_file("file");

        if (file.filename.empty()) {
            res.json(400, R"({"error":"No file uploaded"})");
            return;
        }

        // Save file
        if (file.save_to("./uploads/" + file.filename)) {
            res.json(200, R"({"message":"File uploaded successfully"})");
        } else {
            res.json(500, R"({"error":"Failed to save file"})");
        }
    });

    app.run("0.0.0.0", 8000);
    return 0;
}

Configuration

crest::MultipartParser::Config config;
config.max_file_size = 10 * 1024 * 1024; // 10MB
config.max_files = 5;
config.allowed_extensions = {".jpg", ".png", ".pdf"};
config.allowed_mime_types = {"image/jpeg", "image/png", "application/pdf"};

crest::MultipartParser parser(config);

Multiple Files

app.post("/upload-multiple", [](crest::Request& req, crest::Response& res) {
    std::string content_type = req.header("Content-Type");
    size_t pos = content_type.find("boundary=");
    std::string boundary = content_type.substr(pos + 9);

    crest::MultipartParser parser;
    parser.parse(req.body(), boundary);

    auto files = parser.get_files();

    int saved = 0;
    for (const auto& file : files) {
        if (file.save_to("./uploads/" + file.filename)) {
            saved++;
        }
    }

    res.json(200, R"({"uploaded":)" + std::to_string(saved) + R"(})");
});

File Information

auto file = parser.get_file("avatar");

std::cout << "Field name: " << file.field_name << std::endl;
std::cout << "Filename: " << file.filename << std::endl;
std::cout << "Content-Type: " << file.content_type << std::endl;
std::cout << "Size: " << file.size << " bytes" << std::endl;

Process in Memory

auto file = parser.get_file("document");

// Get as string
std::string content = file.to_string();

// Get as binary data
std::vector<uint8_t> data = file.data;

// Process data
// ... your processing logic

Form Fields

// Get regular form fields
std::string username = parser.get_field("username");
std::string description = parser.get_field("description");

// Get all fields
auto fields = parser.get_fields();
for (const auto& [key, value] : fields) {
    std::cout << key << ": " << value << std::endl;
}

Validation

app.post("/upload", [](crest::Request& req, crest::Response& res) {
    std::string content_type = req.header("Content-Type");
    size_t pos = content_type.find("boundary=");
    std::string boundary = content_type.substr(pos + 9);

    crest::MultipartParser::Config config;
    config.max_file_size = 5 * 1024 * 1024; // 5MB
    config.allowed_extensions = {".jpg", ".png", ".gif"};
    config.allowed_mime_types = {"image/jpeg", "image/png", "image/gif"};

    crest::MultipartParser parser(config);

    if (!parser.parse(req.body(), boundary)) {
        res.json(400, R"({"error":")" + parser.last_error() + R"("})");
        return;
    }

    auto file = parser.get_file("image");

    // Additional validation
    if (file.size < 1024) {
        res.json(400, R"({"error":"File too small"})");
        return;
    }

    if (file.filename.length() > 255) {
        res.json(400, R"({"error":"Filename too long"})");
        return;
    }

    file.save_to("./uploads/" + file.filename);
    res.json(200, R"({"message":"Upload successful"})");
});

Image Upload Example

#include "crest/crest.hpp"
#include "crest/upload.hpp"
#include <filesystem>

int main() {
    crest::App app;

    // Create uploads directory
    std::filesystem::create_directories("./uploads");

    app.post("/upload/image", [](crest::Request& req, crest::Response& res) {
        std::string content_type = req.header("Content-Type");
        size_t pos = content_type.find("boundary=");
        std::string boundary = content_type.substr(pos + 9);

        crest::MultipartParser::Config config;
        config.max_file_size = 5 * 1024 * 1024;
        config.allowed_extensions = {".jpg", ".jpeg", ".png", ".gif"};
        config.allowed_mime_types = {
            "image/jpeg", 
            "image/png", 
            "image/gif"
        };

        crest::MultipartParser parser(config);

        if (!parser.parse(req.body(), boundary)) {
            res.json(400, R"({"error":")" + parser.last_error() + R"("})");
            return;
        }

        auto file = parser.get_file("image");

        if (file.filename.empty()) {
            res.json(400, R"({"error":"No image provided"})");
            return;
        }

        // Generate unique filename
        std::string unique_name = std::to_string(std::time(nullptr)) + "_" + file.filename;
        std::string path = "./uploads/" + unique_name;

        if (file.save_to(path)) {
            res.json(200, R"({"message":"Image uploaded","path":")" + path + R"("})");
        } else {
            res.json(500, R"({"error":"Failed to save image"})");
        }
    });

    app.run("0.0.0.0", 8000);
    return 0;
}

HTML Form Example

<!DOCTYPE html>
<html>
<head>
    <title>File Upload</title>
</head>
<body>
    <h1>Upload File</h1>
    <form action="/upload" method="POST" enctype="multipart/form-data">
        <input type="text" name="username" placeholder="Username" required>
        <input type="file" name="file" required>
        <button type="submit">Upload</button>
    </form>
</body>
</html>

Best Practices

  • Always validate file size and type
  • Use unique filenames to prevent overwrites
  • Store files outside web root for security
  • Scan uploaded files for malware
  • Set appropriate file permissions
  • Clean up old uploads periodically
  • Use streaming for large files
  • Implement rate limiting for uploads
  • Provide progress feedback to users
  • Handle upload errors gracefully