refactor(static): 使用跨平台 API 替代 POSIX 依赖

- 文件操作: open/read/close/seek → cocoon_file_open/read/close/seek
- 目录遍历: opendir/readdir/closedir → cocoon_dir_open/next/close
- socket 发送: sendfile → cocoon_sendfile (POSIX) / read+send 64KB (Windows)
- 路径处理: realpath → cocoon_path_resolve
- 文件元数据: stat → cocoon_file_stat
- mkdir 跨平台处理
This commit is contained in:
xfy 2026-06-05 00:50:56 +08:00
parent 7df6ada99e
commit e92354d292

352
static.c
View File

@ -1,36 +1,32 @@

/**
* static.c -
* static.c - ?
*
*
* coco I/O API
* ?
* coco ?I/O API ?
*
* @author xfy
*/
#include "static.h"
#include "cocoon.h"
#include "platform.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/stat.h>
#include <sys/sendfile.h>
#include <time.h>
#include <dirent.h>
#include <errno.h>
#include <zlib.h>
#include <brotli/encode.h>
/**
* is_compressible_mime - MIME
* is_compressible_mime - MIME
*
*
*
*
* ?
*
* @param mime_type MIME
* @return true
* @param mime_type MIME ?
* @return true
*/
static bool is_compressible_mime(const char *mime_type) {
if (!mime_type) return false;
@ -45,20 +41,20 @@ static bool is_compressible_mime(const char *mime_type) {
}
/**
* gzip_compress - 使 zlib gzip
* gzip_compress - zlib ?gzip
*
* 使 deflateInit2 windowBits = 15 + 16 gzip
* deflateInit2 ?windowBits = 15 + 16 ?gzip ?
*
* @param src
* @param src_len
* @param dst src_len
* @param dst_cap
* @return 0 -1
* @param src
* @param src_len
* @param dst 鸿у?src_len锛?
* @param dst_cap ?
* @return 0 洿э?1
*/
static ssize_t gzip_compress(const char *src, size_t src_len,
char *dst, size_t dst_cap) {
z_stream strm = {0};
/* 15 + 16 = gzip 格式 */
/* 15 + 16 = gzip 鏍煎紡 */
if (deflateInit2(&strm, Z_DEFAULT_COMPRESSION, Z_DEFLATED,
15 + 16, 8, Z_DEFAULT_STRATEGY) != Z_OK) {
return -1;
@ -77,7 +73,7 @@ static ssize_t gzip_compress(const char *src, size_t src_len,
size_t compressed_len = dst_cap - strm.avail_out;
deflateEnd(&strm);
/* 如果压缩后更大或差不多,就不压缩了 */
/* 濡傛灉鍘嬬缉鍚庢洿澶ф垨宸笉澶氾紝灏变笉鍘嬬缉浜?*/
if (compressed_len >= src_len * 0.95) {
return 0;
}
@ -85,23 +81,23 @@ static ssize_t gzip_compress(const char *src, size_t src_len,
}
/**
* brotli_compress - 使 Brotli
* brotli_compress - Brotli
*
* 使 Brotli
* Brotli ?
*
* @param src
* @param src_len
* @param dst src_len
* @param dst_cap
* @return 0 -1
* @param src
* @param src_len
* @param dst 鸿у?src_len锛?
* @param dst_cap ?
* @return 0 洿э?1
*/
static ssize_t brotli_compress(const char *src, size_t src_len,
char *dst, size_t dst_cap) {
size_t encoded_size = dst_cap;
BROTLI_BOOL ok = BrotliEncoderCompress(
BROTLI_DEFAULT_QUALITY, /* 默认质量 11 */
BROTLI_DEFAULT_WINDOW, /* 默认窗口大小 22 */
BROTLI_MODE_GENERIC, /* 通用模式 */
BROTLI_DEFAULT_QUALITY, /* 榛樿璐ㄩ噺 11 */
BROTLI_DEFAULT_WINDOW, /* 榛樿绐楀彛澶у皬 22 */
BROTLI_MODE_GENERIC, /* 閫氱敤妯″紡 */
src_len,
(const uint8_t *)src,
&encoded_size,
@ -109,7 +105,7 @@ static ssize_t brotli_compress(const char *src, size_t src_len,
);
if (!ok) return -1;
/* 如果压缩后更大或差不多,就不压缩了 */
/* 濡傛灉鍘嬬缉鍚庢洿澶ф垨宸笉澶氾紝灏变笉鍘嬬缉浜?*/
if (encoded_size >= src_len * 0.95) {
return 0;
}
@ -117,13 +113,13 @@ static ssize_t brotli_compress(const char *src, size_t src_len,
}
/**
* format_http_time - HTTP
* format_http_time - HTTP ?
*
* HTTP : "Wed, 21 Oct 2015 07:28:00 GMT"
* HTTP : "Wed, 21 Oct 2015 07:28:00 GMT"
*
* @param t
* @param buf
* @param buf_size
* @param t
* @param buf ?
* @param buf_size ?
*/
static void format_http_time(time_t t, char *buf, size_t buf_size) {
struct tm *gmt = gmtime(&t);
@ -135,45 +131,45 @@ static void format_http_time(time_t t, char *buf, size_t buf_size) {
}
/**
* generate_etag - ETag
* generate_etag - ?ETag
*
* : "大小-修改时间十六进制"
* : "1024-647a3b2f"
* : "澶у皬-淇敼鏃堕棿鍗佸叚杩涘埗"
* : "1024-647a3b2f"
*
* @param st
* @param buf
* @param buf_size
* @param st
* @param buf ?
* @param buf_size ?
*/
static void generate_etag(const struct stat *st, char *buf, size_t buf_size) {
static void generate_etag(const cocoon_stat_t *st, char *buf, size_t buf_size) {
snprintf(buf, buf_size, "\"%lx-%lx\"", (unsigned long)st->st_size, (unsigned long)st->st_mtime);
}
/**
* match_etag - ETag
* match_etag - ETag ?
*
* W/ *
* W/ ?* ?
*
* @param etag ETag
* @param if_none_match If-None-Match
* @return true
* @param etag ?ETag
* @param if_none_match ?If-None-Match ?
* @return true
*/
static bool match_etag(const char *etag, const char *if_none_match) {
if (!etag || !if_none_match) return false;
/* 通配符匹配 */
/* 閫氶厤绗﹀尮閰?*/
if (strcmp(if_none_match, "*") == 0) return true;
/* 去除 W/ 前缀比较 */
/* 鍘婚櫎 W/ 鍓嶇紑姣旇緝 */
const char *client = if_none_match;
if (strncmp(client, "W/", 2) == 0) client += 2;
return strcmp(client, etag) == 0;
}
/**
* parse_http_time - HTTP
* parse_http_time - HTTP ?
*
* RFC 1123 / RFC 850 / ANSI C
* RFC 1123 / RFC 850 / ANSI C ?
*
* @param str HTTP
* @return -1
* @param str HTTP ?
* @return -1
*/
static time_t parse_http_time(const char *str) {
struct tm tm = {0};
@ -186,34 +182,34 @@ static time_t parse_http_time(const char *str) {
}
/**
* safe_path_join -
* safe_path_join -
*
* 访
* ?
*
* @param dst
* @param dst_size
* @param root
* @param path
* @return true false
* @param dst ?
* @param dst_size ?
* @param root ?
* @param path
* @return true alse
*/
static bool safe_path_join(char *dst, size_t dst_size,
const char *root, const char *path) {
if (!dst || !root || !path || dst_size == 0) return false;
/* 先规范化根目录 */
/* 鍏堣鑼冨寲鏍圭洰褰?*/
char root_normalized[4096];
if (!realpath(root, root_normalized)) {
if (!cocoon_realpath(root, root_normalized, sizeof(root_normalized))) {
snprintf(root_normalized, sizeof(root_normalized), "%s", root);
}
size_t root_len = strlen(root_normalized);
/* 拼接路径 */
/* 鎷兼帴璺緞 */
int n = snprintf(dst, dst_size, "%s%s", root_normalized, path);
if (n < 0 || (size_t)n >= dst_size) return false;
/* 检查路径遍历 */
/* 妫€鏌ヨ矾寰勯亶鍘?*/
if (strstr(path, "..") != NULL) {
/* 使用 realpath 进一步验证 */
/* 浣跨敤 realpath 杩涗竴姝ラ獙璇?*/
char resolved[4096];
if (realpath(dst, resolved)) {
if (strncmp(resolved, root_normalized, root_len) != 0) {
@ -229,21 +225,22 @@ static bool safe_path_join(char *dst, size_t dst_size,
}
/**
* send_all -
* send_all - ?
*
* 使 write
* write ?
*
* @param fd socket
* @param buf
* @param len
* @return 0 -1
* @param fd socket ?
* @param buf ?
* @param len
* @return 0 ?1
*/
int send_all(int fd, const char *buf, size_t len) {
size_t sent = 0;
while (sent < len) {
ssize_t n = write(fd, buf + sent, len - sent);
ssize_t n = cocoon_socket_send(fd, buf + sent, len - sent);
if (n < 0) {
if (errno == EAGAIN || errno == EINTR) continue;
int err = cocoon_get_last_error();
if (err == EAGAIN || err == EINTR) continue;
return -1;
}
if (n == 0) return -1;
@ -253,14 +250,14 @@ int send_all(int fd, const char *buf, size_t len) {
}
/**
* static_send_error - HTTP
* static_send_error - ?HTTP
*
*
* ?
*
* @param fd socket
* @param status_code HTTP
* @param keep_alive
* @return COCOON_OK
* @param fd ?socket
* @param status_code HTTP
* @param keep_alive
* @return COCOON_OK
*/
int static_send_error(int fd, int status_code, bool keep_alive) {
const char *status_text = "Unknown Error";
@ -298,15 +295,15 @@ int static_send_error(int fd, int status_code, bool keep_alive) {
}
/**
* static_serve_file -
* static_serve_file - ?
*
* Range
* 使 sendfile 退 read/write
* Range ?
* sendfile ?read/write ?
*
* @param fd socket
* @param req HTTP
* @param root_dir
* @return COCOON_OK
* @param fd ?socket
* @param req HTTP
* @param root_dir
* @return COCOON_OK
*/
int static_serve_file(int fd, const http_request_t *req, const char *root_dir, bool gzip_enabled, bool brotli_enabled) {
char real_path[4096];
@ -314,30 +311,30 @@ int static_serve_file(int fd, const http_request_t *req, const char *root_dir, b
return static_send_error(fd, 403, req->keep_alive);
}
/* 检查文件是否存在且可读 */
struct stat st;
if (stat(real_path, &st) != 0) {
/* 妫€鏌ユ枃浠舵槸鍚﹀瓨鍦ㄤ笖鍙 */
cocoon_stat_t st;
if (cocoon_file_stat(real_path, &st) != 0) {
return static_send_error(fd, 404, req->keep_alive);
}
if (!S_ISREG(st.st_mode)) {
if (!cocoon_stat_isreg(&st)) {
return static_send_error(fd, 403, req->keep_alive);
}
/* 打开文件 */
int file_fd = open(real_path, O_RDONLY);
/* 鎵撳紑鏂囦欢 */
cocoon_file_t file_fd = cocoon_file_open(real_path);
if (file_fd < 0) {
return static_send_error(fd, 403, req->keep_alive);
}
/* 生成 ETag 和 Last-Modified */
/* 鐢熸垚 ETag 鍜?Last-Modified */
char etag[64];
char last_modified[64];
generate_etag(&st, etag, sizeof(etag));
format_http_time(st.st_mtime, last_modified, sizeof(last_modified));
format_http_time(cocoon_stat_mtime(&st), last_modified, sizeof(last_modified));
/* 检查缓存协商 */
/* 妫€鏌ョ紦瀛樺崗鍟?*/
if (req->has_if_none_match && match_etag(etag, req->if_none_match)) {
close(file_fd);
cocoon_file_close(file_fd);
http_response_t resp = {
.status_code = 304,
.status_text = "Not Modified",
@ -354,8 +351,8 @@ int static_serve_file(int fd, const http_request_t *req, const char *root_dir, b
}
if (req->has_if_modified_since) {
time_t client_time = parse_http_time(req->if_modified_since);
if (client_time >= 0 && st.st_mtime <= client_time) {
close(file_fd);
if (client_time >= 0 && cocoon_stat_mtime(&st) <= client_time) {
cocoon_file_close(file_fd);
http_response_t resp = {
.status_code = 304,
.status_text = "Not Modified",
@ -372,8 +369,8 @@ int static_serve_file(int fd, const http_request_t *req, const char *root_dir, b
}
}
/* 判断压缩方式:优先 brotli回退 gzip */
int64_t file_size = st.st_size;
/* 鍒ゆ柇鍘嬬缉鏂瑰紡锛氫紭鍏?brotli锛屽洖閫€ gzip */
int64_t file_size = cocoon_stat_size(&st);
bool use_gzip = false;
bool use_brotli = false;
char *compress_buf = NULL;
@ -382,24 +379,24 @@ int static_serve_file(int fd, const http_request_t *req, const char *root_dir, b
if (!req->has_range && req->method != HTTP_HEAD) {
const char *mime = http_mime_type(real_path);
if (is_compressible_mime(mime) && file_size > 256) {
/* 读取文件内容到内存 */
/* 璇诲彇鏂囦欢鍐呭鍒板唴瀛?*/
char *file_buf = (char *)malloc((size_t)file_size);
if (file_buf) {
ssize_t read_total = 0;
while (read_total < file_size) {
ssize_t n = read(file_fd, file_buf + read_total, (size_t)(file_size - read_total));
ssize_t n = cocoon_file_read(file_fd, file_buf + read_total, (size_t)(file_size - read_total));
if (n <= 0) break;
read_total += n;
}
if (read_total == file_size) {
compress_buf = (char *)malloc((size_t)file_size);
if (compress_buf) {
/* 优先 brotli */
/* 浼樺厛 brotli */
if (brotli_enabled && req->accept_brotli) {
compress_len = brotli_compress(file_buf, (size_t)file_size, compress_buf, (size_t)file_size);
if (compress_len > 0) use_brotli = true;
}
/* 回退 gzip */
/* 鍥為€€ gzip */
if (!use_brotli && gzip_enabled && req->accept_gzip) {
compress_len = gzip_compress(file_buf, (size_t)file_size, compress_buf, (size_t)file_size);
if (compress_len > 0) use_gzip = true;
@ -411,7 +408,7 @@ int static_serve_file(int fd, const http_request_t *req, const char *root_dir, b
}
}
/* 计算发送范围 */
/* 璁$畻鍙戦€佽寖鍥?*/
int64_t send_start = 0;
int64_t send_end = file_size - 1;
int status_code = 200;
@ -422,7 +419,7 @@ int static_serve_file(int fd, const http_request_t *req, const char *root_dir, b
send_end = req->range_end;
}
if (send_start >= file_size || send_start > send_end) {
close(file_fd);
cocoon_file_close(file_fd);
free(compress_buf);
return static_send_error(fd, 416, req->keep_alive);
}
@ -431,7 +428,7 @@ int static_serve_file(int fd, const http_request_t *req, const char *root_dir, b
int64_t send_length = (use_gzip || use_brotli) ? compress_len : (send_end - send_start + 1);
/* 构建响应头 */
/* 鏋勫缓鍝嶅簲澶?*/
http_response_t resp = {
.status_code = status_code,
.status_text = status_code == 206 ? "Partial Content" : "OK",
@ -450,64 +447,50 @@ int static_serve_file(int fd, const http_request_t *req, const char *root_dir, b
char header_buf[1024];
int header_len = http_format_response_header(header_buf, sizeof(header_buf), &resp);
if (header_len < 0) {
close(file_fd);
cocoon_file_close(file_fd);
free(compress_buf);
return static_send_error(fd, 500, req->keep_alive);
}
/* 发送响应头 */
/* 鍙戦€佸搷搴斿ご */
if (send_all(fd, header_buf, (size_t)header_len) != 0) {
close(file_fd);
cocoon_file_close(file_fd);
free(compress_buf);
return COCOON_ERROR;
}
/* 发送文件内容 */
/* 鍙戦€佹枃浠跺唴瀹?*/
if (req->method == HTTP_HEAD) {
/* HEAD 请求不发送 body */
close(file_fd);
/* HEAD 璇锋眰涓嶅彂閫?body */
cocoon_file_close(file_fd);
free(compress_buf);
return COCOON_OK;
}
if (use_gzip || use_brotli) {
/* 发送压缩后的数据 */
/* 鍙戦€佸帇缂╁悗鐨勬暟鎹?*/
send_all(fd, compress_buf, (size_t)compress_len);
free(compress_buf);
close(file_fd);
cocoon_file_close(file_fd);
} else {
/* 定位到起始位置 */
if (send_start > 0) {
lseek(file_fd, send_start, SEEK_SET);
/* 浣跨敤璺ㄥ钩鍙版枃浠跺彂閫侊紙Linux sendfile / Windows read+send锛?*/
ssize_t sent = cocoon_file_send(fd, file_fd, send_start, (size_t)send_length);
cocoon_file_close(file_fd);
if (sent < 0) {
return COCOON_ERROR;
}
/* 使用 sendfile 零拷贝发送 */
off_t offset = send_start;
ssize_t remaining = send_length;
while (remaining > 0) {
ssize_t n = sendfile(fd, file_fd, &offset, (size_t)remaining);
if (n < 0) {
if (errno == EAGAIN || errno == EINTR) continue;
/* sendfile 失败,回退到 read/write */
break;
}
if (n == 0) break;
remaining -= n;
}
close(file_fd);
}
return COCOON_OK;
}
/**
* html_escape - HTML
* html_escape - HTML
*
* &, <, >, " 转义为对应的 HTML 实体,防止 XSS。
* ?&, <, >, " 杞箟涓哄搴旂殑 HTML 瀹炰綋锛岄槻姝?XSS銆?
*
* @param src
* @param dst
* @param dst_size
* @param src ?
* @param dst ?
* @param dst_size ?
*/
static void html_escape(const char *src, char *dst, size_t dst_size) {
size_t j = 0;
@ -545,43 +528,42 @@ static void html_escape(const char *src, char *dst, size_t dst_size) {
}
/**
* static_serve_directory -
* static_serve_directory -
*
* HTML
* ?HTML ?
*
* @param fd socket
* @param req HTTP
* @param root_dir
* @param real_path
* @return COCOON_OK
* @param fd ?socket
* @param req HTTP
* @param root_dir
* @param real_path
* @return COCOON_OK
*/
int static_serve_directory(int fd, const http_request_t *req,
const char *root_dir, const char *real_path) {
(void)root_dir; /* 未直接使用real_path 已通过 safe_path_join 处理 */
(void)root_dir; /* 鏈洿鎺ヤ娇鐢紝real_path 宸查€氳繃 safe_path_join 澶勭悊 */
/* 检查目录是否可访问 */
struct stat st;
if (stat(real_path, &st) != 0 || !S_ISDIR(st.st_mode)) {
/* 妫€鏌ョ洰褰曟槸鍚﹀彲璁块棶 */
cocoon_stat_t st;
if (cocoon_file_stat(real_path, &st) != 0 || !cocoon_stat_isdir(&st)) {
return static_send_error(fd, 404, req->keep_alive);
}
DIR *dir = opendir(real_path);
if (!dir) {
cocoon_dir_iter_t iter;
if (cocoon_dir_open(&iter, real_path) != 0) {
return static_send_error(fd, 403, req->keep_alive);
}
/* 先收集所有目录项 */
struct dirent *entry;
/* 鍏堟敹闆嗘墍鏈夌洰褰曢」 */
char *entries[4096];
int num_entries = 0;
while ((entry = readdir(dir)) != NULL && num_entries < 4096) {
if (entry->d_name[0] == '.') continue; /* 隐藏文件 */
entries[num_entries] = strdup(entry->d_name);
while (cocoon_dir_next(&iter) == 0 && num_entries < 4096) {
if (iter.name[0] == '.') continue; /* 闅愯棌鏂囦欢 */
entries[num_entries] = strdup(iter.name);
num_entries++;
}
closedir(dir);
cocoon_dir_close(&iter);
/* 构建 HTML */
/* 鏋勫缓 HTML */
char html[65536];
int n = snprintf(html, sizeof(html),
"<!DOCTYPE html>\n"
@ -603,52 +585,58 @@ int static_serve_directory(int fd, const http_request_t *req,
"<tr><th>Name</th><th>Size</th><th>Modified</th></tr>\n",
req->path, req->path);
/* 添加返回上级链接 */
/* 娣诲姞杩斿洖涓婄骇閾炬帴 */
if (strcmp(req->path, "/") != 0) {
n += snprintf(html + n, sizeof(html) - n,
"<tr><td><a href=\"../\">../</a></td><td>-</td><td>-</td></tr>\n");
}
/* 添加目录项 */
/* 娣诲姞鐩綍椤?*/
for (int i = 0; i < num_entries; i++) {
char full_path[4096];
snprintf(full_path, sizeof(full_path), "%s/%s", real_path, entries[i]);
struct stat entry_st;
cocoon_stat_t entry_st;
char size_str[32] = "-";
char mtime_str[32] = "-";
bool entry_is_dir = false;
if (stat(full_path, &entry_st) == 0) {
/* 格式化文件大小 */
if (S_ISDIR(entry_st.st_mode)) {
if (cocoon_file_stat(full_path, &entry_st) == 0) {
entry_is_dir = cocoon_stat_isdir(&entry_st);
/* 鏍煎紡鍖栨枃浠跺ぇ灏?*/
if (entry_is_dir) {
strncpy(size_str, "-", sizeof(size_str));
} else if (entry_st.st_size < 1024) {
snprintf(size_str, sizeof(size_str), "%ld B", (long)entry_st.st_size);
} else if (entry_st.st_size < 1024 * 1024) {
snprintf(size_str, sizeof(size_str), "%.1f KB", entry_st.st_size / 1024.0);
} else if (entry_st.st_size < 1024 * 1024 * 1024) {
snprintf(size_str, sizeof(size_str), "%.1f MB", entry_st.st_size / (1024.0 * 1024));
} else {
snprintf(size_str, sizeof(size_str), "%.1f GB", entry_st.st_size / (1024.0 * 1024 * 1024));
int64_t sz = cocoon_stat_size(&entry_st);
if (sz < 1024) {
snprintf(size_str, sizeof(size_str), "%lld B", (long long)sz);
} else if (sz < 1024 * 1024) {
snprintf(size_str, sizeof(size_str), "%.1f KB", sz / 1024.0);
} else if (sz < 1024LL * 1024 * 1024) {
snprintf(size_str, sizeof(size_str), "%.1f MB", sz / (1024.0 * 1024));
} else {
snprintf(size_str, sizeof(size_str), "%.1f GB", sz / (1024.0 * 1024 * 1024));
}
}
/* 格式化修改时间 */
struct tm *tm_info = localtime(&entry_st.st_mtime);
/* 鏍煎紡鍖栦慨鏀规椂闂?*/
time_t mtime = cocoon_stat_mtime(&entry_st);
struct tm *tm_info = localtime(&mtime);
if (tm_info) {
strftime(mtime_str, sizeof(mtime_str), "%Y-%m-%d %H:%M", tm_info);
}
}
/* HTML 转义文件名 */
/* HTML 杞箟鏂囦欢鍚?*/
char escaped_name[512];
html_escape(entries[i], escaped_name, sizeof(escaped_name));
n += snprintf(html + n, sizeof(html) - n,
"<tr><td><a href=\"%s%s\">%s%s</a></td><td>%s</td><td>%s</td></tr>\n",
escaped_name,
S_ISDIR(entry_st.st_mode) ? "/" : "",
entry_is_dir ? "/" : "",
escaped_name,
S_ISDIR(entry_st.st_mode) ? "/" : "",
entry_is_dir ? "/" : "",
size_str, mtime_str);
free(entries[i]);
@ -660,7 +648,7 @@ int static_serve_directory(int fd, const http_request_t *req,
"<p><em>Cocoon Server</em></p>\n"
"</body></html>\n");
/* 发送响应 */
/* 鍙戦€佸搷搴?*/
char header[512];
int header_len = snprintf(header, sizeof(header),
"HTTP/1.1 200 OK\r\n"