feat(http): 添加缓存头部解析 + 修复编译警告

- http: 解析 If-None-Match 和 If-Modified-Since 请求头
- http: 响应结构体添加 etag 和 last_modified 字段
- server/static: 替换 strncpy 为 snprintf,消除 stringop-truncation 警告
- static: 修复 CSS 百分号转义(%%)
- static: 消除 unused parameter 警告
This commit is contained in:
xfy911 2026-06-03 17:30:37 +08:00
parent a30fcf6ee1
commit 413288d837
4 changed files with 39 additions and 10 deletions

15
http.c
View File

@ -12,6 +12,7 @@
#include <string.h>
#include <stdio.h>
#include <ctype.h>
#include <stdlib.h>
/**
* http_method_str - HTTP
@ -63,6 +64,8 @@ static void parse_headers(const char **p, const char *end, http_request_t *req)
req->has_range = false;
req->range_start = 0;
req->range_end = -1;
req->has_if_none_match = false;
req->has_if_modified_since = false;
while (*p < end && req->num_headers < HTTP_MAX_HEADERS) {
const char *line_start = *p;
@ -107,6 +110,18 @@ static void parse_headers(const char **p, const char *end, http_request_t *req)
req->content_length = atoll(req->headers[req->num_headers].value);
} else if (strcmp(req->headers[req->num_headers].name, "connection") == 0) {
req->keep_alive = (strcasecmp(req->headers[req->num_headers].value, "keep-alive") == 0);
} else if (strcmp(req->headers[req->num_headers].name, "if-none-match") == 0) {
req->has_if_none_match = true;
int copy_len = strlen(req->headers[req->num_headers].value);
if (copy_len >= 64) copy_len = 63;
memcpy(req->if_none_match, req->headers[req->num_headers].value, copy_len);
req->if_none_match[copy_len] = '\0';
} else if (strcmp(req->headers[req->num_headers].name, "if-modified-since") == 0) {
req->has_if_modified_since = true;
int copy_len = strlen(req->headers[req->num_headers].value);
if (copy_len >= 64) copy_len = 63;
memcpy(req->if_modified_since, req->headers[req->num_headers].value, copy_len);
req->if_modified_since[copy_len] = '\0';
} else if (strcmp(req->headers[req->num_headers].name, "range") == 0) {
/* 解析 Range: bytes=start-end */
const char *range_val = req->headers[req->num_headers].value;

10
http.h
View File

@ -48,6 +48,12 @@ typedef struct {
bool has_range;
int64_t range_start;
int64_t range_end;
/* 缓存相关头 */
char if_none_match[64];
char if_modified_since[64];
bool has_if_none_match;
bool has_if_modified_since;
} http_request_t;
/* === HTTP 响应 === */
@ -61,6 +67,10 @@ typedef struct {
int64_t range_start;
int64_t range_end;
int64_t total_length;
/* 缓存 */
const char *etag;
const char *last_modified;
} http_response_t;
/* === API === */

View File

@ -173,8 +173,7 @@ static bool handle_request(connection_t *conn, const char *root_dir) {
static_send_error(conn->fd, 403, req.keep_alive);
return req.keep_alive;
}
strncpy(real_path, resolved, sizeof(real_path) - 1);
real_path[sizeof(real_path) - 1] = '\0';
snprintf(real_path, sizeof(real_path), "%s", resolved);
}
/* 判断文件类型 */
@ -187,13 +186,18 @@ static bool handle_request(connection_t *conn, const char *root_dir) {
if (S_ISDIR(st.st_mode)) {
/* 目录:尝试 index.html */
char index_path[4096];
snprintf(index_path, sizeof(index_path), "%s/index.html", real_path);
if (snprintf(index_path, sizeof(index_path), "%s/index.html", real_path) >= (int)sizeof(index_path)) {
static_send_error(conn->fd, 400, req.keep_alive);
return req.keep_alive;
}
struct stat index_st;
if (stat(index_path, &index_st) == 0 && S_ISREG(index_st.st_mode)) {
/* 有 index.html作为文件服务 */
http_request_t index_req = req;
strncpy(index_req.path, req.path, sizeof(index_req.path) - 1);
strncat(index_req.path, "/index.html", sizeof(index_req.path) - strlen(index_req.path) - 1);
if (snprintf(index_req.path, sizeof(index_req.path), "%s/index.html", req.path) >= (int)sizeof(index_req.path)) {
static_send_error(conn->fd, 400, req.keep_alive);
return req.keep_alive;
}
static_serve_file(conn->fd, &index_req, root_dir);
} else {
/* 无 index.html生成目录列表 */

View File

@ -40,8 +40,7 @@ static bool safe_path_join(char *dst, size_t dst_size,
/* 先规范化根目录 */
char root_normalized[4096];
if (!realpath(root, root_normalized)) {
strncpy(root_normalized, root, sizeof(root_normalized) - 1);
root_normalized[sizeof(root_normalized) - 1] = '\0';
snprintf(root_normalized, sizeof(root_normalized), "%s", root);
}
size_t root_len = strlen(root_normalized);
@ -57,8 +56,7 @@ static bool safe_path_join(char *dst, size_t dst_size,
if (strncmp(resolved, root_normalized, root_len) != 0) {
return false;
}
strncpy(dst, resolved, dst_size - 1);
dst[dst_size - 1] = '\0';
snprintf(dst, dst_size, "%s", resolved);
return true;
}
return false;
@ -301,6 +299,8 @@ static void html_escape(const char *src, char *dst, size_t dst_size) {
*/
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 处理 */
/* 检查目录是否可访问 */
struct stat st;
if (stat(real_path, &st) != 0 || !S_ISDIR(st.st_mode)) {
@ -333,7 +333,7 @@ int static_serve_directory(int fd, const http_request_t *req,
"<style>"
"body{font-family:system-ui,-apple-system,sans-serif;max-width:800px;margin:40px auto;padding:0 20px}"
"h1{border-bottom:1px solid #ddd;padding-bottom:10px}"
"table{width:100%;border-collapse:collapse}"
"table{width:100%%;border-collapse:collapse}"
"th{text-align:left;padding:8px;border-bottom:2px solid #ddd}"
"td{padding:8px;border-bottom:1px solid #eee}"
"a{text-decoration:none;color:#0066cc}"