This commit is contained in:
kento2 2026-09-09 04:16:40 +02:00
commit 2ed9805b67
9 changed files with 459 additions and 0 deletions

211
minimal_multipart_parser.c Normal file
View file

@ -0,0 +1,211 @@
//
// minimal_multipart_parser.c
//
// Copyright (c) 2024 Brian Khuu https://briankhuu.com/
// MIT licensed
//
// https://github.com/mofosyne/minimal-multipart-form-data-parser-c
//
#include "minimal_multipart_parser.h"
#include <stdbool.h>
static inline unsigned int buffer_count(MinimalMultipartParserCharBuffer *context) { return context->count; }
static inline void buffer_reset(MinimalMultipartParserCharBuffer *context)
{
context->buffer[0] = '\0';
context->count = 0;
}
static inline bool buffer_add(MinimalMultipartParserCharBuffer *context, const char c)
{
if (context->count >= MINIMAL_MULTIPART_PARSER_FULL_BOUNDARY_BUFFER_MAX_CHAR)
{
return false;
}
context->buffer[context->count] = c;
context->buffer[context->count + 1] = '\0';
context->count++;
return true;
}
MultipartParserEvent minimal_multipart_parser_process(MinimalMultipartParserContext *context, const char c)
{
MinimalMultipartParserCharBuffer *boundaryBuffer = &(context->boundary);
MinimalMultipartParserCharBuffer *dataBuffer = &(context->data);
if (context->data_available)
{
buffer_reset(dataBuffer);
context->data_available = false;
}
if (context->phase == MultipartParserPhase_INIT)
{
switch (c)
{
case '\r':
context->phase = MultipartParserPhase_Preamble_CR;
return MultipartParserEvent_None;
case '-':
context->phase = MultipartParserPhase_Preamble_HYPHEN;
return MultipartParserEvent_None;
default:
context->phase = MultipartParserPhase_Preamble_SKIP_LINE;
return MultipartParserEvent_None;
}
}
if (context->phase == MultipartParserPhase_Preamble_SKIP_LINE)
{
switch (c)
{
case '\r':
context->phase = MultipartParserPhase_Preamble_CR;
return MultipartParserEvent_None;
case '\n':
context->phase = MultipartParserPhase_Preamble_LF;
return MultipartParserEvent_None;
default:
return MultipartParserEvent_None;
}
}
if (context->phase == MultipartParserPhase_Preamble_CR)
{
switch (c)
{
case '\n':
context->phase = MultipartParserPhase_Preamble_LF;
return MultipartParserEvent_None;
default:
context->phase = MultipartParserPhase_Preamble_SKIP_LINE;
return MultipartParserEvent_None;
}
}
if (context->phase == MultipartParserPhase_Preamble_LF)
{
switch (c)
{
case '-':
context->phase = MultipartParserPhase_Preamble_HYPHEN;
return MultipartParserEvent_None;
default:
context->phase = MultipartParserPhase_Preamble_SKIP_LINE;
return MultipartParserEvent_None;
}
}
if (context->phase == MultipartParserPhase_Preamble_HYPHEN)
{
// Previously got a dash in '\r\n--', seeking another dash
switch (c)
{
case '-':
context->phase = MultipartParserPhase_GetBoundary;
buffer_add(boundaryBuffer, '\r');
buffer_add(boundaryBuffer, '\n');
buffer_add(boundaryBuffer, '-');
buffer_add(boundaryBuffer, '-');
return MultipartParserEvent_None;
default:
context->phase = MultipartParserPhase_Preamble_SKIP_LINE;
return MultipartParserEvent_None;
}
}
if (context->phase == MultipartParserPhase_GetBoundary)
{
switch (c)
{
case '\r':
context->phase = MultipartParserPhase_GetBoundary_Done;
return MultipartParserEvent_None;
default:
if ((c < ' ') || ('~' < c))
{
context->phase = MultipartParserPhase_Preamble_SKIP_LINE;
buffer_reset(dataBuffer);
return MultipartParserEvent_None;
}
if (!buffer_add(boundaryBuffer, c))
{
context->phase = MultipartParserPhase_Preamble_SKIP_LINE;
buffer_reset(dataBuffer);
return MultipartParserEvent_None;
}
return MultipartParserEvent_None;
}
}
if (context->phase == MultipartParserPhase_GetBoundary_Done)
{
switch (c)
{
case '\n':
context->phase = MultipartParserPhase_SkipFileHeader;
return MultipartParserEvent_FileStreamFound;
default:
context->phase = MultipartParserPhase_Preamble_SKIP_LINE;
buffer_reset(dataBuffer);
return MultipartParserEvent_None;
}
}
if (context->phase == MultipartParserPhase_SkipFileHeader)
{
const unsigned expected_index = buffer_count(dataBuffer);
const char expected_char = (MINIMAL_MULTIPART_PARSER_FILE_START_MARKER)[expected_index];
if (c != expected_char)
{
buffer_reset(dataBuffer);
return MultipartParserEvent_None;
}
buffer_add(dataBuffer, c);
if (buffer_count(dataBuffer) >= MINIMAL_MULTIPART_PARSER_FILE_START_MARKER_COUNT)
{
context->phase = MultipartParserPhase_GetFileBytes;
buffer_reset(dataBuffer);
return MultipartParserEvent_FileStreamStarting;
}
return MultipartParserEvent_None;
}
if (context->phase == MultipartParserPhase_GetFileBytes)
{
const unsigned char full_boundary_size = buffer_count(boundaryBuffer);
const char *full_boundary_string = context->boundary.buffer;
const unsigned expected_index = buffer_count(dataBuffer);
const char expected_char = full_boundary_string[expected_index];
buffer_add(dataBuffer, c);
if (c != expected_char)
{
context->data_available = true;
return MultipartParserEvent_DataBufferAvailable;
}
if (buffer_count(dataBuffer) >= full_boundary_size)
{
context->phase = MultipartParserPhase_EndOfFile;
return MultipartParserEvent_DataStreamCompleted;
}
return MultipartParserEvent_None;
}
if (context->phase == MultipartParserPhase_EndOfFile)
{
// Do nothing... We already got the file now
return MultipartParserEvent_DataStreamCompleted;
}
return MultipartParserEvent_None;
}