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 * @author xfy
*/ */
#include "static.h" #include "static.h"
#include "cocoon.h" #include "cocoon.h"
#include "platform.h"
#include <stdio.h> #include <stdio.h>
#include <stdlib.h> #include <stdlib.h>
#include <string.h> #include <string.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/stat.h>
#include <sys/sendfile.h>
#include <time.h> #include <time.h>
#include <dirent.h>
#include <errno.h> #include <errno.h>
#include <zlib.h> #include <zlib.h>
#include <brotli/encode.h> #include <brotli/encode.h>
/** /**
* is_compressible_mime - MIME * is_compressible_mime - MIME
* *
* *
* * ?
* *
* @param mime_type MIME * @param mime_type MIME ?
* @return true * @return true
*/ */
static bool is_compressible_mime(const char *mime_type) { static bool is_compressible_mime(const char *mime_type) {
if (!mime_type) return false; 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
* @param src_len * @param src_len
* @param dst src_len * @param dst 鸿у?src_len锛?
* @param dst_cap * @param dst_cap ?
* @return 0 -1 * @return 0 洿э?1
*/ */
static ssize_t gzip_compress(const char *src, size_t src_len, static ssize_t gzip_compress(const char *src, size_t src_len,
char *dst, size_t dst_cap) { char *dst, size_t dst_cap) {
z_stream strm = {0}; z_stream strm = {0};
/* 15 + 16 = gzip 格式 */ /* 15 + 16 = gzip 鏍煎紡 */
if (deflateInit2(&strm, Z_DEFAULT_COMPRESSION, Z_DEFLATED, if (deflateInit2(&strm, Z_DEFAULT_COMPRESSION, Z_DEFLATED,
15 + 16, 8, Z_DEFAULT_STRATEGY) != Z_OK) { 15 + 16, 8, Z_DEFAULT_STRATEGY) != Z_OK) {
return -1; 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; size_t compressed_len = dst_cap - strm.avail_out;
deflateEnd(&strm); deflateEnd(&strm);
/* 如果压缩后更大或差不多,就不压缩了 */ /* 濡傛灉鍘嬬缉鍚庢洿澶ф垨宸笉澶氾紝灏变笉鍘嬬缉浜?*/
if (compressed_len >= src_len * 0.95) { if (compressed_len >= src_len * 0.95) {
return 0; 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
* @param src_len * @param src_len
* @param dst src_len * @param dst 鸿у?src_len锛?
* @param dst_cap * @param dst_cap ?
* @return 0 -1 * @return 0 洿э?1
*/ */
static ssize_t brotli_compress(const char *src, size_t src_len, static ssize_t brotli_compress(const char *src, size_t src_len,
char *dst, size_t dst_cap) { char *dst, size_t dst_cap) {
size_t encoded_size = dst_cap; size_t encoded_size = dst_cap;
BROTLI_BOOL ok = BrotliEncoderCompress( BROTLI_BOOL ok = BrotliEncoderCompress(
BROTLI_DEFAULT_QUALITY, /* 默认质量 11 */ BROTLI_DEFAULT_QUALITY, /* 榛樿璐ㄩ噺 11 */
BROTLI_DEFAULT_WINDOW, /* 默认窗口大小 22 */ BROTLI_DEFAULT_WINDOW, /* 榛樿绐楀彛澶у皬 22 */
BROTLI_MODE_GENERIC, /* 通用模式 */ BROTLI_MODE_GENERIC, /* 閫氱敤妯″紡 */
src_len, src_len,
(const uint8_t *)src, (const uint8_t *)src,
&encoded_size, &encoded_size,
@ -109,7 +105,7 @@ static ssize_t brotli_compress(const char *src, size_t src_len,
); );
if (!ok) return -1; if (!ok) return -1;
/* 如果压缩后更大或差不多,就不压缩了 */ /* 濡傛灉鍘嬬缉鍚庢洿澶ф垨宸笉澶氾紝灏变笉鍘嬬缉浜?*/
if (encoded_size >= src_len * 0.95) { if (encoded_size >= src_len * 0.95) {
return 0; 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 t
* @param buf * @param buf ?
* @param buf_size * @param buf_size ?
*/ */
static void format_http_time(time_t t, char *buf, size_t buf_size) { static void format_http_time(time_t t, char *buf, size_t buf_size) {
struct tm *gmt = gmtime(&t); 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 st
* @param buf * @param buf ?
* @param buf_size * @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); 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 etag ?ETag
* @param if_none_match If-None-Match * @param if_none_match ?If-None-Match ?
* @return true * @return true
*/ */
static bool match_etag(const char *etag, const char *if_none_match) { static bool match_etag(const char *etag, const char *if_none_match) {
if (!etag || !if_none_match) return false; if (!etag || !if_none_match) return false;
/* 通配符匹配 */ /* 閫氶厤绗﹀尮閰?*/
if (strcmp(if_none_match, "*") == 0) return true; if (strcmp(if_none_match, "*") == 0) return true;
/* 去除 W/ 前缀比较 */ /* 鍘婚櫎 W/ 鍓嶇紑姣旇緝 */
const char *client = if_none_match; const char *client = if_none_match;
if (strncmp(client, "W/", 2) == 0) client += 2; if (strncmp(client, "W/", 2) == 0) client += 2;
return strcmp(client, etag) == 0; 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 * @param str HTTP ?
* @return -1 * @return -1
*/ */
static time_t parse_http_time(const char *str) { static time_t parse_http_time(const char *str) {
struct tm tm = {0}; 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 ?
* @param dst_size * @param dst_size ?
* @param root * @param root ?
* @param path * @param path
* @return true false * @return true alse
*/ */
static bool safe_path_join(char *dst, size_t dst_size, static bool safe_path_join(char *dst, size_t dst_size,
const char *root, const char *path) { const char *root, const char *path) {
if (!dst || !root || !path || dst_size == 0) return false; if (!dst || !root || !path || dst_size == 0) return false;
/* 先规范化根目录 */ /* 鍏堣鑼冨寲鏍圭洰褰?*/
char root_normalized[4096]; 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); snprintf(root_normalized, sizeof(root_normalized), "%s", root);
} }
size_t root_len = strlen(root_normalized); size_t root_len = strlen(root_normalized);
/* 拼接路径 */ /* 鎷兼帴璺緞 */
int n = snprintf(dst, dst_size, "%s%s", root_normalized, path); int n = snprintf(dst, dst_size, "%s%s", root_normalized, path);
if (n < 0 || (size_t)n >= dst_size) return false; if (n < 0 || (size_t)n >= dst_size) return false;
/* 检查路径遍历 */ /* 妫€鏌ヨ矾寰勯亶鍘?*/
if (strstr(path, "..") != NULL) { if (strstr(path, "..") != NULL) {
/* 使用 realpath 进一步验证 */ /* 浣跨敤 realpath 杩涗竴姝ラ獙璇?*/
char resolved[4096]; char resolved[4096];
if (realpath(dst, resolved)) { if (realpath(dst, resolved)) {
if (strncmp(resolved, root_normalized, root_len) != 0) { 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 fd socket ?
* @param buf * @param buf ?
* @param len * @param len
* @return 0 -1 * @return 0 ?1
*/ */
int send_all(int fd, const char *buf, size_t len) { int send_all(int fd, const char *buf, size_t len) {
size_t sent = 0; size_t sent = 0;
while (sent < len) { 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 (n < 0) {
if (errno == EAGAIN || errno == EINTR) continue; int err = cocoon_get_last_error();
if (err == EAGAIN || err == EINTR) continue;
return -1; return -1;
} }
if (n == 0) 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 fd ?socket
* @param status_code HTTP * @param status_code HTTP
* @param keep_alive * @param keep_alive
* @return COCOON_OK * @return COCOON_OK
*/ */
int static_send_error(int fd, int status_code, bool keep_alive) { int static_send_error(int fd, int status_code, bool keep_alive) {
const char *status_text = "Unknown Error"; 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 * Range ?
* 使 sendfile 退 read/write * sendfile ?read/write ?
* *
* @param fd socket * @param fd ?socket
* @param req HTTP * @param req HTTP
* @param root_dir * @param root_dir
* @return COCOON_OK * @return COCOON_OK
*/ */
int static_serve_file(int fd, const http_request_t *req, const char *root_dir, bool gzip_enabled, bool brotli_enabled) { 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]; 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); return static_send_error(fd, 403, req->keep_alive);
} }
/* 检查文件是否存在且可读 */ /* 妫€鏌ユ枃浠舵槸鍚﹀瓨鍦ㄤ笖鍙 */
struct stat st; cocoon_stat_t st;
if (stat(real_path, &st) != 0) { if (cocoon_file_stat(real_path, &st) != 0) {
return static_send_error(fd, 404, req->keep_alive); 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); 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) { if (file_fd < 0) {
return static_send_error(fd, 403, req->keep_alive); return static_send_error(fd, 403, req->keep_alive);
} }
/* 生成 ETag 和 Last-Modified */ /* 鐢熸垚 ETag 鍜?Last-Modified */
char etag[64]; char etag[64];
char last_modified[64]; char last_modified[64];
generate_etag(&st, etag, sizeof(etag)); 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)) { 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 = { http_response_t resp = {
.status_code = 304, .status_code = 304,
.status_text = "Not Modified", .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) { if (req->has_if_modified_since) {
time_t client_time = parse_http_time(req->if_modified_since); time_t client_time = parse_http_time(req->if_modified_since);
if (client_time >= 0 && st.st_mtime <= client_time) { if (client_time >= 0 && cocoon_stat_mtime(&st) <= client_time) {
close(file_fd); cocoon_file_close(file_fd);
http_response_t resp = { http_response_t resp = {
.status_code = 304, .status_code = 304,
.status_text = "Not Modified", .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 */ /* 鍒ゆ柇鍘嬬缉鏂瑰紡锛氫紭鍏?brotli锛屽洖閫€ gzip */
int64_t file_size = st.st_size; int64_t file_size = cocoon_stat_size(&st);
bool use_gzip = false; bool use_gzip = false;
bool use_brotli = false; bool use_brotli = false;
char *compress_buf = NULL; 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) { if (!req->has_range && req->method != HTTP_HEAD) {
const char *mime = http_mime_type(real_path); const char *mime = http_mime_type(real_path);
if (is_compressible_mime(mime) && file_size > 256) { if (is_compressible_mime(mime) && file_size > 256) {
/* 读取文件内容到内存 */ /* 璇诲彇鏂囦欢鍐呭鍒板唴瀛?*/
char *file_buf = (char *)malloc((size_t)file_size); char *file_buf = (char *)malloc((size_t)file_size);
if (file_buf) { if (file_buf) {
ssize_t read_total = 0; ssize_t read_total = 0;
while (read_total < file_size) { 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; if (n <= 0) break;
read_total += n; read_total += n;
} }
if (read_total == file_size) { if (read_total == file_size) {
compress_buf = (char *)malloc((size_t)file_size); compress_buf = (char *)malloc((size_t)file_size);
if (compress_buf) { if (compress_buf) {
/* 优先 brotli */ /* 浼樺厛 brotli */
if (brotli_enabled && req->accept_brotli) { if (brotli_enabled && req->accept_brotli) {
compress_len = brotli_compress(file_buf, (size_t)file_size, compress_buf, (size_t)file_size); compress_len = brotli_compress(file_buf, (size_t)file_size, compress_buf, (size_t)file_size);
if (compress_len > 0) use_brotli = true; if (compress_len > 0) use_brotli = true;
} }
/* 回退 gzip */ /* 鍥為€€ gzip */
if (!use_brotli && gzip_enabled && req->accept_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); compress_len = gzip_compress(file_buf, (size_t)file_size, compress_buf, (size_t)file_size);
if (compress_len > 0) use_gzip = true; 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_start = 0;
int64_t send_end = file_size - 1; int64_t send_end = file_size - 1;
int status_code = 200; 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; send_end = req->range_end;
} }
if (send_start >= file_size || send_start > send_end) { if (send_start >= file_size || send_start > send_end) {
close(file_fd); cocoon_file_close(file_fd);
free(compress_buf); free(compress_buf);
return static_send_error(fd, 416, req->keep_alive); 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); int64_t send_length = (use_gzip || use_brotli) ? compress_len : (send_end - send_start + 1);
/* 构建响应头 */ /* 鏋勫缓鍝嶅簲澶?*/
http_response_t resp = { http_response_t resp = {
.status_code = status_code, .status_code = status_code,
.status_text = status_code == 206 ? "Partial Content" : "OK", .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]; char header_buf[1024];
int header_len = http_format_response_header(header_buf, sizeof(header_buf), &resp); int header_len = http_format_response_header(header_buf, sizeof(header_buf), &resp);
if (header_len < 0) { if (header_len < 0) {
close(file_fd); cocoon_file_close(file_fd);
free(compress_buf); free(compress_buf);
return static_send_error(fd, 500, req->keep_alive); return static_send_error(fd, 500, req->keep_alive);
} }
/* 发送响应头 */ /* 鍙戦€佸搷搴斿ご */
if (send_all(fd, header_buf, (size_t)header_len) != 0) { if (send_all(fd, header_buf, (size_t)header_len) != 0) {
close(file_fd); cocoon_file_close(file_fd);
free(compress_buf); free(compress_buf);
return COCOON_ERROR; return COCOON_ERROR;
} }
/* 发送文件内容 */ /* 鍙戦€佹枃浠跺唴瀹?*/
if (req->method == HTTP_HEAD) { if (req->method == HTTP_HEAD) {
/* HEAD 请求不发送 body */ /* HEAD 璇锋眰涓嶅彂閫?body */
close(file_fd); cocoon_file_close(file_fd);
free(compress_buf); free(compress_buf);
return COCOON_OK; return COCOON_OK;
} }
if (use_gzip || use_brotli) { if (use_gzip || use_brotli) {
/* 发送压缩后的数据 */ /* 鍙戦€佸帇缂╁悗鐨勬暟鎹?*/
send_all(fd, compress_buf, (size_t)compress_len); send_all(fd, compress_buf, (size_t)compress_len);
free(compress_buf); free(compress_buf);
close(file_fd); cocoon_file_close(file_fd);
} else { } else {
/* 定位到起始位置 */ /* 浣跨敤璺ㄥ钩鍙版枃浠跺彂閫侊紙Linux sendfile / Windows read+send锛?*/
if (send_start > 0) { ssize_t sent = cocoon_file_send(fd, file_fd, send_start, (size_t)send_length);
lseek(file_fd, send_start, SEEK_SET); 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; return COCOON_OK;
} }
/** /**
* html_escape - HTML * html_escape - HTML
* *
* &, <, >, " 转义为对应的 HTML 实体,防止 XSS。 * ?&, <, >, " 杞箟涓哄搴旂殑 HTML 瀹炰綋锛岄槻姝?XSS銆?
* *
* @param src * @param src ?
* @param dst * @param dst ?
* @param dst_size * @param dst_size ?
*/ */
static void html_escape(const char *src, char *dst, size_t dst_size) { static void html_escape(const char *src, char *dst, size_t dst_size) {
size_t j = 0; 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 fd ?socket
* @param req HTTP * @param req HTTP
* @param root_dir * @param root_dir
* @param real_path * @param real_path
* @return COCOON_OK * @return COCOON_OK
*/ */
int static_serve_directory(int fd, const http_request_t *req, int static_serve_directory(int fd, const http_request_t *req,
const char *root_dir, const char *real_path) { 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; cocoon_stat_t st;
if (stat(real_path, &st) != 0 || !S_ISDIR(st.st_mode)) { if (cocoon_file_stat(real_path, &st) != 0 || !cocoon_stat_isdir(&st)) {
return static_send_error(fd, 404, req->keep_alive); return static_send_error(fd, 404, req->keep_alive);
} }
DIR *dir = opendir(real_path); cocoon_dir_iter_t iter;
if (!dir) { if (cocoon_dir_open(&iter, real_path) != 0) {
return static_send_error(fd, 403, req->keep_alive); return static_send_error(fd, 403, req->keep_alive);
} }
/* 先收集所有目录项 */ /* 鍏堟敹闆嗘墍鏈夌洰褰曢」 */
struct dirent *entry;
char *entries[4096]; char *entries[4096];
int num_entries = 0; int num_entries = 0;
while ((entry = readdir(dir)) != NULL && num_entries < 4096) { while (cocoon_dir_next(&iter) == 0 && num_entries < 4096) {
if (entry->d_name[0] == '.') continue; /* 隐藏文件 */ if (iter.name[0] == '.') continue; /* 闅愯棌鏂囦欢 */
entries[num_entries] = strdup(entry->d_name); entries[num_entries] = strdup(iter.name);
num_entries++; num_entries++;
} }
closedir(dir); cocoon_dir_close(&iter);
/* 构建 HTML */ /* 鏋勫缓 HTML */
char html[65536]; char html[65536];
int n = snprintf(html, sizeof(html), int n = snprintf(html, sizeof(html),
"<!DOCTYPE html>\n" "<!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", "<tr><th>Name</th><th>Size</th><th>Modified</th></tr>\n",
req->path, req->path); req->path, req->path);
/* 添加返回上级链接 */ /* 娣诲姞杩斿洖涓婄骇閾炬帴 */
if (strcmp(req->path, "/") != 0) { if (strcmp(req->path, "/") != 0) {
n += snprintf(html + n, sizeof(html) - n, n += snprintf(html + n, sizeof(html) - n,
"<tr><td><a href=\"../\">../</a></td><td>-</td><td>-</td></tr>\n"); "<tr><td><a href=\"../\">../</a></td><td>-</td><td>-</td></tr>\n");
} }
/* 添加目录项 */ /* 娣诲姞鐩綍椤?*/
for (int i = 0; i < num_entries; i++) { for (int i = 0; i < num_entries; i++) {
char full_path[4096]; char full_path[4096];
snprintf(full_path, sizeof(full_path), "%s/%s", real_path, entries[i]); 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 size_str[32] = "-";
char mtime_str[32] = "-"; char mtime_str[32] = "-";
bool entry_is_dir = false;
if (stat(full_path, &entry_st) == 0) { if (cocoon_file_stat(full_path, &entry_st) == 0) {
/* 格式化文件大小 */ entry_is_dir = cocoon_stat_isdir(&entry_st);
if (S_ISDIR(entry_st.st_mode)) { /* 鏍煎紡鍖栨枃浠跺ぇ灏?*/
if (entry_is_dir) {
strncpy(size_str, "-", sizeof(size_str)); 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 { } 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) { if (tm_info) {
strftime(mtime_str, sizeof(mtime_str), "%Y-%m-%d %H:%M", tm_info); strftime(mtime_str, sizeof(mtime_str), "%Y-%m-%d %H:%M", tm_info);
} }
} }
/* HTML 转义文件名 */ /* HTML 杞箟鏂囦欢鍚?*/
char escaped_name[512]; char escaped_name[512];
html_escape(entries[i], escaped_name, sizeof(escaped_name)); html_escape(entries[i], escaped_name, sizeof(escaped_name));
n += snprintf(html + n, sizeof(html) - n, 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", "<tr><td><a href=\"%s%s\">%s%s</a></td><td>%s</td><td>%s</td></tr>\n",
escaped_name, escaped_name,
S_ISDIR(entry_st.st_mode) ? "/" : "", entry_is_dir ? "/" : "",
escaped_name, escaped_name,
S_ISDIR(entry_st.st_mode) ? "/" : "", entry_is_dir ? "/" : "",
size_str, mtime_str); size_str, mtime_str);
free(entries[i]); 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" "<p><em>Cocoon Server</em></p>\n"
"</body></html>\n"); "</body></html>\n");
/* 发送响应 */ /* 鍙戦€佸搷搴?*/
char header[512]; char header[512];
int header_len = snprintf(header, sizeof(header), int header_len = snprintf(header, sizeof(header),
"HTTP/1.1 200 OK\r\n" "HTTP/1.1 200 OK\r\n"