当在code文件夹中直接写路径时,系统会以当前代码文件所在文件夹为根目录

博客指出在code文件夹中直接写路径时,系统会以当前代码文件所在文件夹作为根目录,这是关于代码路径设定的关键信息。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

当在code文件夹中直接写路径时,系统会以当前代码文件所在文件夹为根目录

在这里插入图片描述

<think>我们之前设计的HTTP服务器中,文件路径直接使用当前目录(`.`)作为根目录。现在要求将文件放在当前工作目录的`web`文件夹内,我们需要修改文件路径的构建方式。 修改点: 1. 在构建实际文件路径,将基础目录改为`./web`,然后加上URI路径。 2. 需要确保路径安全,防止路径遍历攻击(例如使用`..`跳出web目录)。 具体修改步骤: 在`Handle_Request_Message`函数中,修改构建文件路径代码: 原代码:`snprintf(path, BUF_SIZE, ".%s", uri);` 修改为:`snprintf(path, BUF_SIZE, "./web%s", uri);` 但是,这样修改后,我们还需要确保: - 如果URI是`/`,则实际路径为`./web/index.html`(或指定的默认页面) - 防止路径遍历:通过检查最终路径是否在`./web`目录内。 安全增强:我们可以使用`realpath`函数来解析绝对路径,并检查是否在web目录内。 修改后的代码逻辑: 1. 构建基础目录:`base_dir = "./web"` 2. 将URI附加到基础目录:`sprintf(full_path, "%s%s", base_dir, uri)` 3. 使用`realpath`解析完整路径,并检查是否以基础目录的绝对路径开头。 注意:使用`realpath`需要确保路径存在,否则返回NULL。 修改后的`Handle_Request_Message`函数中关于路径处理的部分: 步骤: a. 定义基础目录 b. 构建完整路径(相对路径) c. 解析绝对路径并验证 代码示例: ```c // ... 之前的代码 // 3. 处理URI // 根路径重定向 if (strcmp(uri, "/") == 0) { strncpy(uri, "/index.html", BUF_SIZE - 1); } // 构建基础目录 const char *base_dir = "./web"; char full_path[BUF_SIZE]; snprintf(full_path, BUF_SIZE, "%s%s", base_dir, uri); // 防止路径遍历攻击:使用realpath检查 char resolved_path[BUF_SIZE]; char base_abs_path[BUF_SIZE]; // 获取基础目录的绝对路径 if (realpath(base_dir, base_abs_path) == NULL) { perror("realpath base_dir"); Send_Error_Response(socket, 500, "Internal Server Error"); return ERROR; } // 获取请求文件的绝对路径 if (realpath(full_path, resolved_path) == NULL) { // 文件不存在或路径无效 Send_Error_Response(socket, 404, "Not Found"); return ERROR; } // 检查解析后的路径是否在基础目录内 if (strncmp(resolved_path, base_abs_path, strlen(base_abs_path)) != 0) { // 不在基础目录内,可能是路径遍历攻击 Send_Error_Response(socket, 403, "Forbidden"); return ERROR; } // 现在使用resolved_path作为文件路径 // ... 后续的stat和Send_File都使用resolved_path ``` 注意:使用`realpath`函数需要包含头文件`<stdlib.h>`和`<limits.h>`。 完整修改后的`Handle_Request_Message`函数(只显示修改部分): ```c #include <stdlib.h> #include <limits.h> int Handle_Request_Message(char *request, int socket) { // ... 之前解析请求行、方法等 // 3. 处理URI if (strcmp(uri, "/") == 0) { strncpy(uri, "/index.html", BUF_SIZE - 1); } // 设置基础目录 const char *base_dir = "./web"; char full_path[BUF_SIZE]; snprintf(full_path, BUF_SIZE, "%s%s", base_dir, uri); // 安全验证:获取绝对路径并检查是否在基础目录下 char base_abs_path[BUF_SIZE]; if (realpath(base_dir, base_abs_path) == NULL) { Send_Error_Response(socket, 500, "Internal Server Error"); return ERROR; } char resolved_path[BUF_SIZE]; if (realpath(full_path, resolved_path) == NULL) { Send_Error_Response(socket, 404, "Not Found"); return ERROR; } // 检查解析后的路径是否在base_abs_path目录下 size_t base_len = strlen(base_abs_path); if (strncmp(resolved_path, base_abs_path, base_len) != 0) { Send_Error_Response(socket, 403, "Forbidden"); return ERROR; } // 检查文件是否存在且为普通文件 struct stat st; if (stat(resolved_path, &st) == -1 || !S_ISREG(st.st_mode)) { Send_Error_Response(socket, 404, "Not Found"); return ERROR; } // 5. 发送响应(使用resolved_path) if (Send_File(resolved_path, socket, method_type) != OK) { Send_Error_Response(socket, 500, "Internal Server Error"); return ERROR; } return OK; } ``` 注意:这里使用了`realpath`来解析绝对路径,并检查文件是否在基础目录内。这样可以有效防止路径遍历攻击。 另外,如果基础目录`./web`不存在,`realpath`会返回NULL,所以服务器启动应确保该目录存在。 关于`realpath`函数: - 功能:将相对路径转换为绝对路径,解析其中的`/./`、`/../`以及多余的`/`。 - 返回值:成功返回绝对路径字符串指针(存放在resolved_path中),失败返回NULL(错误原因在errno中)。 这种安全措施可以确保用户无法通过`../`跳出web目录。 修改后,所有的文件都将在`./web`目录下提供。 ### §§相关问题§§: 1. 在C语言中,`realpath`函数的工作原理是什么?它如何处理符号链接? 2. 除了使用`realpath`,还有哪些方法可以防止路径遍历攻击? 3. 在HTTP服务器中,如何处理对目录的请求(例如URI以`/`结尾)? 4. 如何实现一个目录列表(directory listing)功能,当请求目录显示文件列表?</think>### 文件存储在 `web` 文件夹的实现方案 当文件需要存储在 `web` 文件夹,我们需要修改文件路径处理逻辑,并确保所有文件访问都限制在该目录下。以下是完整的实现方案: #### 核心修改点 1. **设置根目录常量** 2. **路径规范化处理** 3. **安全防护增强** 4. **目录请求处理** ### 完整实现代码 ```c #include <string.h> #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <sys/stat.h> #include <fcntl.h> #include <dirent.h> #include <errno.h> #define BUF_SIZE 4096 #define WEB_ROOT "./web" // 定义web根目录 // 文件发送函数(基于之前实现的Send_File) int Send_File(const char *path, int socket, int method); // 错误响应函数 int Send_Error_Response(int socket, int status_code, const char *message) { char response[BUF_SIZE]; int length = snprintf(response, BUF_SIZE, "HTTP/1.1 %d %s\r\n" "Content-Type: text/plain\r\n" "Connection: close\r\n" "\r\n" "Error %d: %s", status_code, message, status_code, message); send(socket, response, length, 0); return 0; } // 检查路径是否在web目录内 int is_path_safe(const char *path) { char resolved_path[BUF_SIZE]; // 获取绝对路径 if (realpath(path, resolved_path) == NULL) { return 0; } // 检查是否在WEB_ROOT目录下 return strncmp(resolved_path, realpath(WEB_ROOT, NULL), strlen(realpath(WEB_ROOT, NULL))) == 0; } // 处理目录请求 int handle_directory_request(const char *path, int socket) { char index_path[BUF_SIZE]; snprintf(index_path, BUF_SIZE, "%s/index.html", path); // 检查是否存在index.html struct stat st; if (stat(index_path, &st) == 0 && S_ISREG(st.st_mode)) { return Send_File(index_path, socket, GET_METHOD); } // 没有index.html,返回目录列表 DIR *dir = opendir(path); if (!dir) { Send_Error_Response(socket, 403, "Forbidden"); return 0; } // 生成目录列表HTML char response[BUF_SIZE * 2]; int len = snprintf(response, BUF_SIZE * 2, "HTTP/1.1 200 OK\r\n" "Content-Type: text/html\r\n" "\r\n" "<html><head><title>Directory Listing</title></head><body>" "<h1>Directory Listing: %s</h1><ul>", path); struct dirent *entry; while ((entry = readdir(dir)) != NULL) { // 跳过隐藏文件当前目录 if (entry->d_name[0] == '.' || strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0) continue; char entry_path[BUF_SIZE]; snprintf(entry_path, BUF_SIZE, "%s/%s", path, entry->d_name); struct stat entry_st; if (stat(entry_path, &entry_st) != 0) continue; const char *type = S_ISDIR(entry_st.st_mode) ? "📁" : "📄"; len += snprintf(response + len, (BUF_SIZE * 2) - len, "<li>%s <a href=\"%s\">%s</a> (%ld bytes)</li>", type, entry->d_name, entry->d_name, entry_st.st_size); } len += snprintf(response + len, (BUF_SIZE * 2) - len, "</ul></body></html>"); closedir(dir); send(socket, response, len, 0); return 1; } // 修改后的请求处理函数 int Handle_Request_Message(char *request, int socket) { char method[16], uri[BUF_SIZE], version[16]; // 解析请求行 if (sscanf(request, "%15s %1023s %15s", method, uri, version) != 3) { Send_Error_Response(socket, 400, "Bad Request"); return 0; } // 构建完整文件路径 char path[BUF_SIZE]; snprintf(path, BUF_SIZE, "%s%s", WEB_ROOT, uri); // 处理目录请求(以/结尾) size_t len = strlen(path); if (len > 0 && path[len-1] == '/') { return handle_directory_request(path, socket); } // 检查路径安全性 if (!is_path_safe(path)) { Send_Error_Response(socket, 403, "Forbidden"); return 0; } // 检查文件是否存在 struct stat st; if (stat(path, &st) == -1) { // 尝试添加.html扩展名 if (strstr(path, ".") == NULL) { char html_path[BUF_SIZE]; snprintf(html_path, BUF_SIZE, "%s.html", path); if (stat(html_path, &st) == 0) { strcpy(path, html_path); } else { Send_Error_Response(socket, 404, "Not Found"); return 0; } } else { Send_Error_Response(socket, 404, "Not Found"); return 0; } } // 处理目录请求(没有结尾斜杠) if (S_ISDIR(st.st_mode)) { // 重定向到带斜杠的URL char redirect[BUF_SIZE]; snprintf(redirect, BUF_SIZE, "HTTP/1.1 301 Moved Permanently\r\n" "Location: %s/\r\n" "Content-Length: 0\r\n" "Connection: close\r\n\r\n", uri); send(socket, redirect, strlen(redirect), 0); return 1; } // 发送文件 return Send_File(path, socket, GET_METHOD); } // 修改后的Send_File函数 int Send_File(const char *path, int socket, int method) { int fd = open(path, O_RDONLY); if (fd == -1) { Send_Error_Response(socket, 500, "Internal Server Error"); return 0; } struct stat file_stat; if (fstat(fd, &file_stat) == -1) { close(fd); Send_Error_Response(socket, 500, "Internal Server Error"); return 0; } // 获取内容类型 const char *content_type = "text/plain"; const char *ext = strrchr(path, '.'); if (ext) { if (strcmp(ext, ".html") == 0) content_type = "text/html"; else if (strcmp(ext, ".css") == 0) content_type = "text/css"; else if (strcmp(ext, ".js") == 0) content_type = "application/javascript"; else if (strcmp(ext, ".jpg") == 0) content_type = "image/jpeg"; else if (strcmp(ext, ".png") == 0) content_type = "image/png"; } // 发送HTTP头 char headers[BUF_SIZE]; int headers_len = snprintf(headers, BUF_SIZE, "HTTP/1.1 200 OK\r\n" "Content-Type: %s\r\n" "Content-Length: %ld\r\n" "Connection: close\r\n\r\n", content_type, file_stat.st_size); send(socket, headers, headers_len, 0); // 发送文件内容 char buffer[BUF_SIZE]; ssize_t bytes_read; while ((bytes_read = read(fd, buffer, BUF_SIZE)) > 0) { if (send(socket, buffer, bytes_read, 0) != bytes_read) { break; } } close(fd); return 1; } ``` ### 关键安全增强措施 #### 1. 路径安全验证 ```c int is_path_safe(const char *path) { char resolved_path[BUF_SIZE]; if (realpath(path, resolved_path) == NULL) { return 0; } // 获取web目录的绝对路径 char web_abs_path[BUF_SIZE]; realpath(WEB_ROOT, web_abs_path); // 检查解析后的路径是否在web目录内 return strncmp(resolved_path, web_abs_path, strlen(web_abs_path)) == 0; } ``` - **原理**:使用`realpath()`解析绝对路径 - **作用**:防止`../`路径遍历攻击 - **验证**:确保文件路径以web根目录开头 #### 2. 目录请求处理 ```c // 处理以斜杠结尾的URL if (len > 0 && path[len-1] == '/') { return handle_directory_request(path, socket); } // 处理没有斜杠的目录请求 if (S_ISDIR(st.st_mode)) { // 301重定向到带斜杠的URL char redirect[BUF_SIZE]; snprintf(redirect, BUF_SIZE, "HTTP/1.1 301 Moved Permanently\r\n" "Location: %s/\r\n", uri); send(socket, redirect, strlen(redirect), 0); return 1; } ``` - **目录斜杠处理**:确保目录URL始终以`/`结尾 - **重定向**:301永久重定向保持SEO友好 - **目录列表**:当不存在index.html显示目录内容 #### 3. 文件扩展名处理 ```c // 检查文件是否存在 if (stat(path, &st) == -1) { // 尝试添加.html扩展名 if (strstr(path, ".") == NULL) { char html_path[BUF_SIZE]; snprintf(html_path, BUF_SIZE, "%s.html", path); if (stat(html_path, &st) == 0) { strcpy(path, html_path); } } } ``` - **无扩展名处理**:自动尝试添加`.html`扩展名 - **用户友好**:支持`/about`访问`/about.html` - **安全**:仅当原始路径无扩展名尝试 ### 目录列表功能实现 ```c int handle_directory_request(const char *path, int socket) { // 检查index.html是否存在 char index_path[BUF_SIZE]; snprintf(index_path, BUF_SIZE, "%s/index.html", path); if (access(index_path, F_OK) == 0) { return Send_File(index_path, socket, GET_METHOD); } // 生成目录列表 DIR *dir = opendir(path); if (!dir) return 0; char html[BUF_SIZE * 2]; int len = snprintf(html, BUF_SIZE * 2, "<html><head><title>Index of %s</title></head><body>" "<h1>Index of %s</h1><table>", path, path); struct dirent *entry; while ((entry = readdir(dir)) != NULL) { // 跳过特殊目录 if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0) continue; char full_path[BUF_SIZE]; snprintf(full_path, BUF_SIZE, "%s/%s", path, entry->d_name); struct stat entry_stat; stat(full_path, &entry_stat); const char *icon = S_ISDIR(entry_stat.st_mode) ? "📁" : "📄"; len += snprintf(html + len, (BUF_SIZE * 2) - len, "<tr><td>%s</td><td><a href=\"%s\">%s</a></td><td>%ld</td></tr>", icon, entry->d_name, entry->d_name, entry_stat.st_size); } len += snprintf(html + len, (BUF_SIZE * 2) - len, "</table></body></html>"); closedir(dir); // 发送HTML响应 char headers[BUF_SIZE]; int headers_len = snprintf(headers, BUF_SIZE, "HTTP/1.1 200 OK\r\n" "Content-Type: text/html\r\n" "Content-Length: %d\r\n\r\n", len); send(socket, headers, headers_len, 0); send(socket, html, len, 0); return 1; } ``` ### 部署建议 1. **目录结构示例** ``` 项目目录/ ├── server.c # 服务器源代码 ├── web/ # WEB根目录 │ ├── index.html # 默认首页 │ ├── about.html │ ├── styles/ │ │ └── main.css │ ├── images/ │ │ └── logo.png │ └── downloads/ │ └── manual.pdf ``` 2. **启动服务器前准备** ```bash # 创建web目录和内容 mkdir -p web/{styles,images,downloads} echo "<h1>Welcome</h1>" > web/index.html echo "body { color: blue; }" > web/styles/main.css # 编译并运行服务器 gcc server.c -o server ./server ``` 3. **访问示例** - `http://localhost:8080/` → 显示index.html - `http://localhost:8080/about` → 显示about.html - `http://localhost:8080/styles/` → 显示styles目录内容 - `http://localhost:8080/images/logo.png` → 下载图片 ###
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值