feat(multipart): 新增 multipart/form-data 解析模块
- multipart.h: 定义 multipart_part_t 结构体 + parse/extract_boundary/free API - multipart.c: 轻量级解析器实现 - find_line_end: 查找行尾(\n 或文件尾) - parse_content_disposition: 解析 name/filename 字段 - multipart_parse: 两阶段解析(先计数再提取),跳过 preamble - multipart_extract_boundary: 从 Content-Type 提取 boundary 值 - multipart_parts_free: 释放所有分配的字段内存 - 零外部依赖,仅使用标准库
This commit is contained in:
parent
0d8c36ef25
commit
8ee487f64d
293
multipart.c
Normal file
293
multipart.c
Normal file
@ -0,0 +1,293 @@
|
||||
/**
|
||||
* multipart.c - multipart/form-data 解析实现
|
||||
*
|
||||
* 轻量级解析器,支持文件上传。
|
||||
*
|
||||
* @author xfy
|
||||
*/
|
||||
|
||||
#include "multipart.h"
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
/**
|
||||
* multipart_extract_boundary - 从 Content-Type 提取 boundary
|
||||
*/
|
||||
bool multipart_extract_boundary(const char *content_type, char *buf, size_t buf_size) {
|
||||
if (!content_type || !buf || buf_size == 0) return false;
|
||||
|
||||
const char *p = strstr(content_type, "boundary=");
|
||||
if (!p) return false;
|
||||
|
||||
p += 9; /* skip "boundary=" */
|
||||
|
||||
/* 跳过可能的引号 */
|
||||
size_t len = 0;
|
||||
if (*p == '"') {
|
||||
p++;
|
||||
while (p[len] && p[len] != '"') len++;
|
||||
} else {
|
||||
while (p[len] && p[len] != ';' && p[len] != ' ' && p[len] != '\r' && p[len] != '\n') len++;
|
||||
}
|
||||
|
||||
if (len == 0 || len >= buf_size) return false;
|
||||
|
||||
memcpy(buf, p, len);
|
||||
buf[len] = '\0';
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* find_line_end - 查找行尾(\r\n 或 \n)
|
||||
*
|
||||
* @param data 数据指针
|
||||
* @param len 剩余长度
|
||||
* @return 行尾后的位置偏移,未找到返回 len
|
||||
*/
|
||||
static size_t find_line_end(const char *data, size_t len) {
|
||||
for (size_t i = 0; i < len; i++) {
|
||||
if (data[i] == '\n') return i + 1;
|
||||
}
|
||||
return len;
|
||||
}
|
||||
|
||||
/**
|
||||
* parse_content_disposition - 解析 Content-Disposition 头
|
||||
*
|
||||
* @param line 头行内容
|
||||
* @param name 输出 name 缓冲区
|
||||
* @param name_size name 缓冲区大小
|
||||
* @param filename 输出 filename 缓冲区
|
||||
* @param filename_size filename 缓冲区大小
|
||||
*/
|
||||
static void parse_content_disposition(const char *line,
|
||||
char *name, size_t name_size,
|
||||
char *filename, size_t filename_size) {
|
||||
const char *p = strstr(line, "name=");
|
||||
if (p) {
|
||||
p += 5;
|
||||
if (*p == '"') {
|
||||
p++;
|
||||
size_t i = 0;
|
||||
while (*p && *p != '"' && i < name_size - 1) {
|
||||
name[i++] = *p++;
|
||||
}
|
||||
name[i] = '\0';
|
||||
} else {
|
||||
size_t i = 0;
|
||||
while (*p && *p != ';' && *p != ' ' && *p != '\r' && *p != '\n' && i < name_size - 1) {
|
||||
name[i++] = *p++;
|
||||
}
|
||||
name[i] = '\0';
|
||||
}
|
||||
}
|
||||
|
||||
p = strstr(line, "filename=");
|
||||
if (p) {
|
||||
p += 9;
|
||||
if (*p == '"') {
|
||||
p++;
|
||||
size_t i = 0;
|
||||
while (*p && *p != '"' && i < filename_size - 1) {
|
||||
filename[i++] = *p++;
|
||||
}
|
||||
filename[i] = '\0';
|
||||
} else {
|
||||
size_t i = 0;
|
||||
while (*p && *p != ';' && *p != ' ' && *p != '\r' && *p != '\n' && i < filename_size - 1) {
|
||||
filename[i++] = *p++;
|
||||
}
|
||||
filename[i] = '\0';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* multipart_parse - 解析 multipart/form-data 请求体
|
||||
*/
|
||||
int multipart_parse(const char *body, size_t body_len, const char *boundary,
|
||||
multipart_part_t **parts, int *num_parts) {
|
||||
if (!body || body_len == 0 || !boundary || !parts || !num_parts) return -1;
|
||||
|
||||
*parts = NULL;
|
||||
*num_parts = 0;
|
||||
|
||||
/* 构建边界分隔符:--boundary\r\n */
|
||||
char delim[256];
|
||||
int delim_len = snprintf(delim, sizeof(delim), "--%s", boundary);
|
||||
if (delim_len < 0 || (size_t)delim_len >= sizeof(delim)) return -1;
|
||||
|
||||
size_t b_len = (size_t)delim_len;
|
||||
|
||||
/* 第一遍:统计部分数量 */
|
||||
int count = 0;
|
||||
const char *p = body;
|
||||
size_t remain = body_len;
|
||||
|
||||
/* 跳过 preamble(边界前的内容) */
|
||||
while (remain >= b_len) {
|
||||
if (strncmp(p, delim, b_len) == 0) break;
|
||||
p++;
|
||||
remain--;
|
||||
}
|
||||
|
||||
while (remain >= b_len) {
|
||||
if (strncmp(p, delim, b_len) == 0) {
|
||||
count++;
|
||||
p += b_len;
|
||||
remain -= b_len;
|
||||
/* 跳过 -- 结束标记 */
|
||||
if (remain >= 2 && strncmp(p, "--", 2) == 0) break;
|
||||
/* 跳过 \r\n 或 \n */
|
||||
if (remain >= 2 && p[0] == '\r' && p[1] == '\n') {
|
||||
p += 2;
|
||||
remain -= 2;
|
||||
} else if (remain >= 1 && p[0] == '\n') {
|
||||
p += 1;
|
||||
remain -= 1;
|
||||
}
|
||||
} else {
|
||||
p++;
|
||||
remain--;
|
||||
}
|
||||
}
|
||||
|
||||
if (count == 0) return -1;
|
||||
|
||||
/* 分配数组 */
|
||||
*parts = (multipart_part_t *)calloc((size_t)count, sizeof(multipart_part_t));
|
||||
if (!*parts) return -1;
|
||||
|
||||
/* 第二遍:提取每个部分 */
|
||||
int idx = 0;
|
||||
p = body;
|
||||
remain = body_len;
|
||||
|
||||
while (remain >= b_len && idx < count) {
|
||||
/* 查找下一个边界 */
|
||||
if (strncmp(p, delim, b_len) != 0) {
|
||||
p++;
|
||||
remain--;
|
||||
continue;
|
||||
}
|
||||
|
||||
p += b_len;
|
||||
remain -= b_len;
|
||||
|
||||
/* 检查结束标记 */
|
||||
if (remain >= 2 && strncmp(p, "--", 2) == 0) break;
|
||||
|
||||
/* 跳过分隔线后的换行 */
|
||||
if (remain >= 2 && p[0] == '\r' && p[1] == '\n') {
|
||||
p += 2;
|
||||
remain -= 2;
|
||||
} else if (remain >= 1 && p[0] == '\n') {
|
||||
p += 1;
|
||||
remain -= 1;
|
||||
}
|
||||
|
||||
/* 解析头部 */
|
||||
char name[256] = {0};
|
||||
char filename[256] = {0};
|
||||
char content_type[128] = {0};
|
||||
|
||||
while (remain > 0) {
|
||||
size_t line_end = find_line_end(p, remain);
|
||||
size_t line_len = line_end;
|
||||
|
||||
/* 空行表示头部结束 */
|
||||
if (line_len == 2 && p[0] == '\r' && p[1] == '\n') {
|
||||
p += 2;
|
||||
remain -= 2;
|
||||
break;
|
||||
}
|
||||
if (line_len == 1 && p[0] == '\n') {
|
||||
p += 1;
|
||||
remain -= 1;
|
||||
break;
|
||||
}
|
||||
|
||||
/* 解析头部 */
|
||||
if (strncasecmp(p, "Content-Disposition:", 20) == 0) {
|
||||
parse_content_disposition(p, name, sizeof(name), filename, sizeof(filename));
|
||||
} else if (strncasecmp(p, "Content-Type:", 13) == 0) {
|
||||
const char *vp = p + 13;
|
||||
while (*vp == ' ' || *vp == '\t') vp++;
|
||||
size_t ct_len = line_len - (size_t)(vp - p);
|
||||
/* 去掉尾部 \r\n */
|
||||
if (ct_len >= 2 && vp[ct_len - 2] == '\r' && vp[ct_len - 1] == '\n') ct_len -= 2;
|
||||
else if (ct_len >= 1 && vp[ct_len - 1] == '\n') ct_len -= 1;
|
||||
if (ct_len > 0 && ct_len < sizeof(content_type)) {
|
||||
memcpy(content_type, vp, ct_len);
|
||||
content_type[ct_len] = '\0';
|
||||
}
|
||||
}
|
||||
|
||||
p += line_end;
|
||||
remain -= line_end;
|
||||
}
|
||||
|
||||
/* 查找数据结束位置(下一个边界) */
|
||||
const char *data_start = p;
|
||||
size_t data_remain = remain;
|
||||
size_t data_len = 0;
|
||||
|
||||
while (data_remain >= b_len) {
|
||||
if (strncmp(p, delim, b_len) == 0) break;
|
||||
p++;
|
||||
data_remain--;
|
||||
data_len++;
|
||||
}
|
||||
|
||||
/* 更新 remain 以反映已消费的数据 */
|
||||
remain = data_remain;
|
||||
|
||||
/* 去掉数据尾部的 \r\n(如果存在) */
|
||||
if (data_len >= 2 && data_start[data_len - 2] == '\r' && data_start[data_len - 1] == '\n') {
|
||||
data_len -= 2;
|
||||
} else if (data_len >= 1 && data_start[data_len - 1] == '\n') {
|
||||
data_len -= 1;
|
||||
}
|
||||
|
||||
/* 保存部分 */
|
||||
multipart_part_t *part = &(*parts)[idx];
|
||||
if (name[0]) {
|
||||
part->name = strdup(name);
|
||||
}
|
||||
if (filename[0]) {
|
||||
part->filename = strdup(filename);
|
||||
}
|
||||
if (content_type[0]) {
|
||||
part->content_type = strdup(content_type);
|
||||
}
|
||||
if (data_len > 0) {
|
||||
part->data = (char *)malloc(data_len + 1);
|
||||
if (part->data) {
|
||||
memcpy(part->data, data_start, data_len);
|
||||
part->data[data_len] = '\0';
|
||||
part->data_len = data_len;
|
||||
}
|
||||
}
|
||||
|
||||
idx++;
|
||||
}
|
||||
|
||||
*num_parts = idx;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* multipart_parts_free - 释放 multipart 解析结果
|
||||
*/
|
||||
void multipart_parts_free(multipart_part_t *parts, int num_parts) {
|
||||
if (!parts) return;
|
||||
for (int i = 0; i < num_parts; i++) {
|
||||
free(parts[i].name);
|
||||
free(parts[i].filename);
|
||||
free(parts[i].content_type);
|
||||
free(parts[i].data);
|
||||
}
|
||||
free(parts);
|
||||
}
|
||||
|
||||
58
multipart.h
Normal file
58
multipart.h
Normal file
@ -0,0 +1,58 @@
|
||||
/**
|
||||
* multipart.h - multipart/form-data 解析模块
|
||||
*
|
||||
* 轻量级 multipart 解析器,支持文件上传场景。
|
||||
* 零外部依赖,只处理 Cocoon 需要的字段。
|
||||
*
|
||||
* @author xfy
|
||||
*/
|
||||
|
||||
#ifndef COCOON_MULTIPART_H
|
||||
#define COCOON_MULTIPART_H
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
/**
|
||||
* multipart_part_t - 单个 multipart 部分
|
||||
*/
|
||||
typedef struct {
|
||||
char *name; /**< 字段名 */
|
||||
char *filename; /**< 文件名(文件上传时) */
|
||||
char *content_type; /**< 部分的 Content-Type */
|
||||
char *data; /**< 部分数据 */
|
||||
size_t data_len; /**< 数据长度 */
|
||||
} multipart_part_t;
|
||||
|
||||
/**
|
||||
* multipart_parse - 解析 multipart/form-data 请求体
|
||||
*
|
||||
* @param body 请求体数据
|
||||
* @param body_len 请求体长度
|
||||
* @param boundary 边界字符串(不含前导 --)
|
||||
* @param parts 输出部分数组(需调用者 free)
|
||||
* @param num_parts 输出部分数量
|
||||
* @return 0 成功,-1 错误
|
||||
*/
|
||||
int multipart_parse(const char *body, size_t body_len, const char *boundary,
|
||||
multipart_part_t **parts, int *num_parts);
|
||||
|
||||
/**
|
||||
* multipart_parts_free - 释放 multipart 解析结果
|
||||
*
|
||||
* @param parts 部分数组
|
||||
* @param num_parts 部分数量
|
||||
*/
|
||||
void multipart_parts_free(multipart_part_t *parts, int num_parts);
|
||||
|
||||
/**
|
||||
* multipart_extract_boundary - 从 Content-Type 提取 boundary
|
||||
*
|
||||
* @param content_type Content-Type 头值
|
||||
* @param buf 输出缓冲区
|
||||
* @param buf_size 缓冲区大小
|
||||
* @return true 成功提取
|
||||
*/
|
||||
bool multipart_extract_boundary(const char *content_type, char *buf, size_t buf_size);
|
||||
|
||||
#endif /* COCOON_MULTIPART_H */
|
||||
Loading…
x
Reference in New Issue
Block a user