static void handle_request(struct mg_connection *conn) {
struct mg_request_info *ri = &conn->request_info;
char path[PATH_MAX];
int uri_len;
struct file file = STRUCT_FILE_INITIALIZER;
if ((conn->request_info.query_string = strchr(ri->uri, '?')) != NULL) {
* ((char *) conn->request_info.query_string++) = '\0';
}
uri_len = (int) strlen(ri->uri);
url_decode(ri->uri, uri_len, (char *) ri->uri, uri_len + 1, 0);
remove_double_dots_and_double_slashes((char *) ri->uri);
convert_uri_to_file_name(conn, path, sizeof(path), &file);
conn->throttle = set_throttle(conn->ctx->config[THROTTLE],
get_remote_ip(conn), ri->uri);
DEBUG_TRACE(("%s", ri->uri));
if (!is_put_or_delete_request(conn) && !check_authorization(conn, path)) {
send_authorization_request(conn);
#if defined(USE_WEBSOCKET)
} else if (is_websocket_request(conn)) {
handle_websocket_request(conn);
#endif
} else if (call_user(conn, MG_NEW_REQUEST) != NULL) {
// Do nothing, callback has served the request
} else if (!strcmp(ri->request_method, "OPTIONS")) {
send_options(conn);
} else if (conn->ctx->config[DOCUMENT_ROOT] == NULL) {
send_http_error(conn, 404, "Not Found", "Not Found");
} else if (is_put_or_delete_request(conn) &&
(conn->ctx->config[PUT_DELETE_PASSWORDS_FILE] == NULL ||
is_authorized_for_put(conn) != 1)) {
send_authorization_request(conn);
} else if (!strcmp(ri->request_method, "PUT")) {
put_file(conn, path);
} else if (!strcmp(ri->request_method, "DELETE")) {
if (mg_remove(path) == 0) {
send_http_error(conn, 200, "OK", "%s", "");
} else {
send_http_error(conn, 500, http_500_error, "remove(%s): %s", path,
strerror(ERRNO));
}
} else if ((file.membuf == NULL && file.modification_time == (time_t) 0) ||
must_hide_file(conn, path)) {
send_http_error(conn, 404, "Not Found", "%s", "File not found");
} else if (file.is_directory && ri->uri[uri_len - 1] != '/') {
mg_printf(conn, "HTTP/1.1 301 Moved Permanently\r\n"
"Location: %s/\r\n\r\n", ri->uri);
} else if (!strcmp(ri->request_method, "PROPFIND")) {
handle_propfind(conn, path, &file);
} else if (file.is_directory &&
!substitute_index_file(conn, path, sizeof(path), &file)) {
if (!mg_strcasecmp(conn->ctx->config[ENABLE_DIRECTORY_LISTING], "yes")) {
handle_directory_request(conn, path);
} else {
send_http_error(conn, 403, "Directory Listing Denied",
"Directory listing denied");
}
#ifdef USE_LUA
} else if (match_prefix("**.lp$", 6, path) > 0) {
handle_lsp_request(conn, path, &file);
#endif
#if !defined(NO_CGI)
} else if (match_prefix(conn->ctx->config[CGI_EXTENSIONS],
strlen(conn->ctx->config[CGI_EXTENSIONS]),
path) > 0) {
if (strcmp(ri->request_method, "POST") &&
strcmp(ri->request_method, "HEAD") &&
strcmp(ri->request_method, "GET")) {
send_http_error(conn, 501, "Not Implemented",
"Method %s is not implemented", ri->request_method);
} else {
handle_cgi_request(conn, path);
}
#endif // !NO_CGI
} else if (match_prefix(conn->ctx->config[SSI_EXTENSIONS],
strlen(conn->ctx->config[SSI_EXTENSIONS]),
path) > 0) {
handle_ssi_file_request(conn, path);
} else if (is_not_modified(conn, &file)) {
send_http_error(conn, 304, "Not Modified", "%s", "");
} else {
handle_file_request(conn, path, &file);
}
}
其中:结构体file:
struct file {
int is_directory;
time_t modification_time;
int64_t size;
FILE *fp;
const char *membuf; // Non-NULL if file data is in memory
};#define STRUCT_FILE_INITIALIZER {0, 0, 0, NULL, NULL}
URL解码函数url_decode():有害字符的替换,即将“%”开始的字符进行替换
对 URL 字符串进行编码。
如果在 HTTP 流中传递空白和标点之类的字符,则它们在接收端可能会被错误地解释。
URL 编码将 URL 中不允许使用的字符转换为等效字符实体;URL 解码会反转此编码过程。
例如,当嵌入到要在 URL 中传输的文本块中时,字符 < 和 > 分别被编码为 %3c 和 %3e。url编码规则:
1.空格会转换成+.也可以转换成%2D.
2.0-9,a-z,A-Z之间不变.
3.其它字符表示为十六进制.并且在每个字符前加%.例如:&编码对应%26.
4.%20转换成空格
static int url_decode(const char *src, int src_len, char *dst,
int dst_len, int is_form_url_encoded) {
int i, j, a, b;
#define HEXTOI(x) (isdigit(x) ? x - '0' : x - 'W')
for (i = j = 0; i < src_len && j < dst_len - 1; i++, j++) {
if (src[i] == '%' &&
isxdigit(* (const unsigned char *) (src + i + 1)) &&
isxdigit(* (const unsigned char *) (src + i + 2))) {
a = tolower(* (const unsigned char *) (src + i + 1));
b = tolower(* (const unsigned char *) (src + i + 2));
dst[j] = (char) ((HEXTOI(a) << 4) | HEXTOI(b));
i += 2;
} else if (is_form_url_encoded && src[i] == '+') {
dst[j] = ' ';
} else {
dst[j] = src[i];
}
}
dst[j] = '\0'; // Null-terminate the destination
return i >= src_len ? j : -1;
}
remove_double_dots_and_double_slashes函数:对目录中的双点和双斜杠进行转换,".."即进入当前目录的父目录,测试发现函数将“//”替换成“/”。
static void remove_double_dots_and_double_slashes(char *s) {
char *p = s;
while (*s != '\0') {
*p++ = *s++;
if (s[-1] == '/' || s[-1] == '\\') {
// Skip all following slashes, backslashes and double-dots
while (s[0] != '\0') {
if (s[0] == '/' || s[0] == '\\') {
s++;
} else if (s[0] == '.' && s[1] == '.') {
s += 2;
} else {
break;
}
}
}
}
*p = '\0';
}
convert_uri_to_file_name函数:
static void convert_uri_to_file_name(struct mg_connection *conn, char *buf,
size_t buf_len, struct file *filep) {
struct vec a, b;
const char *rewrite, *uri = conn->request_info.uri;
char *p;
int match_len;
// Using buf_len - 1 because memmove() for PATH_INFO may shift part
// of the path one byte on the right.
mg_snprintf(conn, buf, buf_len - 1, "%s%s", conn->ctx->config[DOCUMENT_ROOT],
uri);
rewrite = conn->ctx->config[REWRITE];
while ((rewrite = next_option(rewrite, &a, &b)) != NULL) {
if ((match_len = match_prefix(a.ptr, a.len, uri)) > 0) {
mg_snprintf(conn, buf, buf_len - 1, "%.*s%s", (int) b.len, b.ptr,
uri + match_len);
break;
}
}
if (!mg_stat(conn, buf, filep)) {
// Support PATH_INFO for CGI scripts.
for (p = buf + strlen(buf); p > buf + 1; p--) {
if (*p == '/') {
*p = '\0';
if (match_prefix(conn->ctx->config[CGI_EXTENSIONS],
strlen(conn->ctx->config[CGI_EXTENSIONS]), buf) > 0 &&
mg_stat(conn, buf, filep)) {
// Shift PATH_INFO block one character right, e.g.
// "/x.cgi/foo/bar\x00" => "/x.cgi\x00/foo/bar\x00"
// conn->path_info is pointing to the local variable "path" declared
// in handle_request(), so PATH_INFO is not valid after
// handle_request returns.
conn->path_info = p + 1;
memmove(p + 2, p + 1, strlen(p + 1) + 1); // +1 is for trailing \0
p[1] = '/';
break;
} else {
*p = '/';
}
}
}
}
}
内容类型实现:内容类型标识服务器支持资源的格式,例如文本格式、超文本格式、流媒体的多种格式。
static const struct {
const char *extension;// 文件扩展名
size_t ext_len; //扩展名长度
const char *mime_type;//内容类型 RFC所定义的类型
} builtin_mime_types[] = {
{".html", 5, "text/html"},
{".htm", 4, "text/html"},
{".shtm", 5, "text/html"},
{".shtml", 6, "text/html"},
{".css", 4, "text/css"},
{".js", 3, "application/x-javascript"},
{".ico", 4, "image/x-icon"},
{".gif", 4, "image/gif"},
{".jpg", 4, "image/jpeg"},
{".jpeg", 5, "image/jpeg"},
{".png", 4, "image/png"},
{".svg", 4, "image/svg+xml"},
{".txt", 4, "text/plain"},
{".torrent", 8, "application/x-bittorrent"},
{".wav", 4, "audio/x-wav"},
{".mp3", 4, "audio/x-mp3"},
{".mid", 4, "audio/mid"},
{".m3u", 4, "audio/x-mpegurl"},
{".ogg", 4, "audio/ogg"},
{".ram", 4, "audio/x-pn-realaudio"},
{".xml", 4, "text/xml"},
{".json", 5, "text/json"},
{".xslt", 5, "application/xml"},
{".xsl", 4, "application/xml"},
{".ra", 3, "audio/x-pn-realaudio"},
{".doc", 4, "application/msword"},
{".exe", 4, "application/octet-stream"},
{".zip", 4, "application/x-zip-compressed"},
{".xls", 4, "application/excel"},
{".tgz", 4, "application/x-tar-gz"},
{".tar", 4, "application/x-tar"},
{".gz", 3, "application/x-gunzip"},
{".arj", 4, "application/x-arj-compressed"},
{".rar", 4, "application/x-arj-compressed"},
{".rtf", 4, "application/rtf"},
{".pdf", 4, "application/pdf"},
{".swf", 4, "application/x-shockwave-flash"},
{".mpg", 4, "video/mpeg"},
{".webm", 5, "video/webm"},
{".mpeg", 5, "video/mpeg"},
{".mp4", 4, "video/mp4"},
{".m4v", 4, "video/x-m4v"},
{".asf", 4, "video/x-ms-asf"},
{".avi", 4, "video/x-msvideo"},
{".bmp", 4, "image/bmp"},
{NULL, 0, NULL}
};
const char *mg_get_builtin_mime_type(const char *path) { //根据扩展名查找内容类型的匹配项
const char *ext;
size_t i, path_len;
path_len = strlen(path);
for (i = 0; builtin_mime_types[i].extension != NULL; i++) {
ext = path + (path_len - builtin_mime_types[i].ext_len);
if (path_len > builtin_mime_types[i].ext_len &&
mg_strcasecmp(ext, builtin_mime_types[i].extension) == 0) {
return builtin_mime_types[i].mime_type;
}
}
return "text/plain";
}