Newer
Older
2001
2002
2003
2004
2005
2006
2007
2008
2009
2010
2011
2012
2013
2014
2015
2016
2017
2018
2019
2020
2021
2022
2023
list = server->config_options[EXTRA_MIME_TYPES];
while ((list = next_option(list, &ext_vec, &mime_vec)) != NULL) {
// ext now points to the path suffix
ext = path + path_len - ext_vec.len;
if (mg_strncasecmp(ext, ext_vec.ptr, ext_vec.len) == 0) {
*vec = mime_vec;
return;
}
}
vec->ptr = mg_get_mime_type(path);
vec->len = strlen(vec->ptr);
}
static const char *suggest_connection_header(const struct mg_connection *conn) {
return should_keep_alive(conn) ? "keep-alive" : "close";
}
static void construct_etag(char *buf, size_t buf_len, const file_stat_t *st) {
mg_snprintf(buf, buf_len, "\"%lx.%" INT64_FMT "\"",
(unsigned long) st->st_mtime, (int64_t) st->st_size);
}
// Return True if we should reply 304 Not Modified.
static int is_not_modified(const struct connection *conn,
const file_stat_t *stp) {
char etag[64];
const char *ims = mg_get_header(&conn->mg_conn, "If-Modified-Since");
const char *inm = mg_get_header(&conn->mg_conn, "If-None-Match");
construct_etag(etag, sizeof(etag), stp);
return (inm != NULL && !mg_strcasecmp(etag, inm)) ||
(ims != NULL && stp->st_mtime <= parse_date_string(ims));
}
// For given directory path, substitute it to valid index file.
// Return 0 if index file has been found, -1 if not found.
// If the file is found, it's stats is returned in stp.
static int find_index_file(struct connection *conn, char *path,
size_t path_len, file_stat_t *stp) {
const char *list = conn->server->config_options[INDEX_FILES];
file_stat_t st;
struct vec filename_vec;
size_t n = strlen(path), found = 0;
// The 'path' given to us points to the directory. Remove all trailing
// directory separator characters from the end of the path, and
// then append single directory separator character.
while (n > 0 && path[n - 1] == '/') {
n--;
// Traverse index files list. For each entry, append it to the given
// path and see if the file exists. If it exists, break the loop
while ((list = next_option(list, &filename_vec, NULL)) != NULL) {
// Ignore too long entries that may overflow path buffer
if (filename_vec.len > (int) (path_len - (n + 2)))
continue;
// Prepare full path to the index file
strncpy(path + n + 1, filename_vec.ptr, filename_vec.len);
path[n + 1 + filename_vec.len] = '\0';
//DBG(("[%s]", path));
// Does it exist?
if (!stat(path, &st)) {
// Yes it does, break the loop
*stp = st;
found = 1;
break;
}
// If no index file exists, restore directory path
if (!found) {
path[n] = '\0';
}
return found;
2084
2085
2086
2087
2088
2089
2090
2091
2092
2093
2094
2095
2096
2097
2098
2099
2100
2101
2102
2103
2104
2105
2106
2107
2108
2109
2110
2111
2112
2113
2114
2115
2116
2117
2118
2119
2120
2121
2122
2123
2124
2125
2126
2127
2128
2129
2130
2131
2132
2133
2134
2135
2136
2137
2138
2139
2140
2141
2142
2143
2144
2145
2146
2147
2148
2149
2150
2151
2152
2153
static int parse_range_header(const char *header, int64_t *a, int64_t *b) {
return sscanf(header, "bytes=%" INT64_FMT "-%" INT64_FMT, a, b);
}
static void gmt_time_string(char *buf, size_t buf_len, time_t *t) {
strftime(buf, buf_len, "%a, %d %b %Y %H:%M:%S GMT", gmtime(t));
}
static void open_file_endpoint(struct connection *conn, const char *path,
file_stat_t *st) {
char date[64], lm[64], etag[64], range[64], headers[500];
const char *msg = "OK", *hdr;
time_t curtime = time(NULL);
int64_t r1, r2;
struct vec mime_vec;
int n;
conn->endpoint_type = EP_FILE;
set_close_on_exec(conn->endpoint.fd);
conn->mg_conn.status_code = 200;
get_mime_type(conn->server, path, &mime_vec);
conn->cl = st->st_size;
range[0] = '\0';
// If Range: header specified, act accordingly
r1 = r2 = 0;
hdr = mg_get_header(&conn->mg_conn, "Range");
if (hdr != NULL && (n = parse_range_header(hdr, &r1, &r2)) > 0 &&
r1 >= 0 && r2 >= 0) {
conn->mg_conn.status_code = 206;
conn->cl = n == 2 ? (r2 > conn->cl ? conn->cl : r2) - r1 + 1: conn->cl - r1;
mg_snprintf(range, sizeof(range), "Content-Range: bytes "
"%" INT64_FMT "-%" INT64_FMT "/%" INT64_FMT "\r\n",
r1, r1 + conn->cl - 1, (int64_t) st->st_size);
msg = "Partial Content";
lseek(conn->endpoint.fd, r1, SEEK_SET);
}
// Prepare Etag, Date, Last-Modified headers. Must be in UTC, according to
// http://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.3
gmt_time_string(date, sizeof(date), &curtime);
gmt_time_string(lm, sizeof(lm), &st->st_mtime);
construct_etag(etag, sizeof(etag), st);
n = mg_snprintf(headers, sizeof(headers),
"HTTP/1.1 %d %s\r\n"
"Date: %s\r\n"
"Last-Modified: %s\r\n"
"Etag: %s\r\n"
"Content-Type: %.*s\r\n"
"Content-Length: %" INT64_FMT "\r\n"
"Connection: %s\r\n"
"Accept-Ranges: bytes\r\n"
"%s%s\r\n",
conn->mg_conn.status_code, msg, date, lm, etag,
(int) mime_vec.len, mime_vec.ptr, conn->cl,
suggest_connection_header(&conn->mg_conn),
range, USE_EXTRA_HTTP_HEADERS);
spool(&conn->remote_iobuf, headers, n);
if (!strcmp(conn->mg_conn.request_method, "HEAD")) {
conn->flags |= CONN_SPOOL_DONE;
close(conn->endpoint.fd);
conn->endpoint_type = EP_NONE;
}
}
#endif // NO_FILESYSTEM
static void write_terminating_chunk(struct connection *conn) {
mg_write(&conn->mg_conn, "0\r\n\r\n", 5);
}
static void call_uri_handler_if_data_is_buffered(struct connection *conn) {
struct iobuf *loc = &conn->local_iobuf;
struct mg_connection *c = &conn->mg_conn;
c->content = loc->buf;
#ifndef NO_WEBSOCKET
if (conn->mg_conn.is_websocket) {
do { } while (deliver_websocket_frame(conn));
} else
if (loc->len >= c->content_len) {
conn->endpoint.uh->handler(c);
if (conn->flags & CONN_HEADERS_SENT) {
write_terminating_chunk(conn);
close_local_endpoint(conn);
}
#if !defined(NO_DIRECTORY_LISTING) || !defined(NO_DAV)
#ifdef _WIN32
struct dirent {
char d_name[MAX_PATH_SIZE];
};
typedef struct DIR {
HANDLE handle;
WIN32_FIND_DATAW info;
struct dirent result;
} DIR;
// Implementation of POSIX opendir/closedir/readdir for Windows.
static DIR *opendir(const char *name) {
DIR *dir = NULL;
wchar_t wpath[MAX_PATH_SIZE];
DWORD attrs;
if (name == NULL) {
SetLastError(ERROR_BAD_ARGUMENTS);
} else if ((dir = (DIR *) malloc(sizeof(*dir))) == NULL) {
SetLastError(ERROR_NOT_ENOUGH_MEMORY);
} else {
to_wchar(name, wpath, ARRAY_SIZE(wpath));
attrs = GetFileAttributesW(wpath);
if (attrs != 0xFFFFFFFF &&
((attrs & FILE_ATTRIBUTE_DIRECTORY) == FILE_ATTRIBUTE_DIRECTORY)) {
(void) wcscat(wpath, L"\\*");
dir->handle = FindFirstFileW(wpath, &dir->info);
dir->result.d_name[0] = '\0';
} else {
free(dir);
dir = NULL;
}
static int closedir(DIR *dir) {
int result = 0;
if (dir != NULL) {
if (dir->handle != INVALID_HANDLE_VALUE)
result = FindClose(dir->handle) ? 0 : -1;
free(dir);
} else {
result = -1;
SetLastError(ERROR_BAD_ARGUMENTS);
static struct dirent *readdir(DIR *dir) {
struct dirent *result = 0;
if (dir) {
if (dir->handle != INVALID_HANDLE_VALUE) {
result = &dir->result;
(void) WideCharToMultiByte(CP_UTF8, 0,
dir->info.cFileName, -1, result->d_name,
sizeof(result->d_name), NULL, NULL);
if (!FindNextFileW(dir->handle, &dir->info)) {
(void) FindClose(dir->handle);
dir->handle = INVALID_HANDLE_VALUE;
}
} else {
SetLastError(ERROR_FILE_NOT_FOUND);
} else {
SetLastError(ERROR_BAD_ARGUMENTS);
return result;
}
#endif // _WIN32 POSIX opendir/closedir/readdir implementation
static int must_hide_file(struct connection *conn, const char *path) {
const char *pw_pattern = "**" PASSWORDS_FILE_NAME "$";
const char *pattern = conn->server->config_options[HIDE_FILES_PATTERN];
return match_prefix(pw_pattern, strlen(pw_pattern), path) > 0 ||
(pattern != NULL && match_prefix(pattern, strlen(pattern), path) > 0);
}
static int scan_directory(struct connection *conn, const char *dir,
struct dir_entry **arr) {
char path[MAX_PATH_SIZE];
struct dir_entry *p;
struct dirent *dp;
int arr_size = 0, arr_ind = 0, inc = 100;
if ((dirp = (opendir(dir))) == NULL) return 0;
while ((dp = readdir(dirp)) != NULL) {
// Do not show current dir and hidden files
if (!strcmp(dp->d_name, ".") ||
!strcmp(dp->d_name, "..") ||
must_hide_file(conn, dp->d_name)) {
continue;
mg_snprintf(path, sizeof(path), "%s%c%s", dir, '/', dp->d_name);
realloc(*arr, (inc + arr_size) * sizeof(**arr))) != NULL) {
// Memset new chunk to zero, otherwize st_mtime will have garbage which
// can make strftime() segfault, see
// http://code.google.com/p/mongoose/issues/detail?id=79
memset(p + arr_size, 0, sizeof(**arr) * inc);
if (arr_ind < arr_size) {
(*arr)[arr_ind].conn = conn;
(*arr)[arr_ind].file_name = strdup(dp->d_name);
stat(path, &(*arr)[arr_ind].st);
arr_ind++;
}
static void mg_url_encode(const char *src, char *dst, size_t dst_len) {
static const char *dont_escape = "._-$,;~()";
static const char *hex = "0123456789abcdef";
const char *end = dst + dst_len - 1;
for (; *src != '\0' && dst < end; src++, dst++) {
if (isalnum(*(const unsigned char *) src) ||
strchr(dont_escape, * (const unsigned char *) src) != NULL) {
*dst = *src;
} else if (dst + 2 < end) {
dst[0] = '%';
dst[1] = hex[(* (const unsigned char *) src) >> 4];
dst[2] = hex[(* (const unsigned char *) src) & 0xf];
dst += 2;
}
#endif // !NO_DIRECTORY_LISTING || !NO_DAV
static void print_dir_entry(const struct dir_entry *de) {
char size[64], mod[64], href[MAX_PATH_SIZE * 3], chunk[MAX_PATH_SIZE * 4];
int64_t fsize = de->st.st_size;
int is_dir = S_ISDIR(de->st.st_mode), n;
const char *slash = is_dir ? "/" : "";
if (is_dir) {
mg_snprintf(size, sizeof(size), "%s", "[DIRECTORY]");
} else {
// We use (signed) cast below because MSVC 6 compiler cannot
// convert unsigned __int64 to double.
if (fsize < 1024) {
mg_snprintf(size, sizeof(size), "%d", (int) fsize);
} else if (fsize < 0x100000) {
mg_snprintf(size, sizeof(size), "%.1fk", (double) fsize / 1024.0);
} else if (fsize < 0x40000000) {
mg_snprintf(size, sizeof(size), "%.1fM", (double) fsize / 1048576);
} else {
mg_snprintf(size, sizeof(size), "%.1fG", (double) fsize / 1073741824);
strftime(mod, sizeof(mod), "%d-%b-%Y %H:%M", localtime(&de->st.st_mtime));
mg_url_encode(de->file_name, href, sizeof(href));
n = mg_snprintf(chunk, sizeof(chunk),
"<tr><td><a href=\"%s%s%s\">%s%s</a></td>"
"<td> %s</td><td> %s</td></tr>\n",
de->conn->mg_conn.uri, href, slash, de->file_name, slash,
mod, size);
write_chunk((struct connection *) de->conn, chunk, n);
2366
2367
2368
2369
2370
2371
2372
2373
2374
2375
2376
2377
2378
2379
2380
2381
2382
2383
2384
2385
2386
2387
// Sort directory entries by size, or name, or modification time.
// On windows, __cdecl specification is needed in case if project is built
// with __stdcall convention. qsort always requires __cdels callback.
static int __cdecl compare_dir_entries(const void *p1, const void *p2) {
const struct dir_entry *a = (const struct dir_entry *) p1,
*b = (const struct dir_entry *) p2;
const char *qs = a->conn->mg_conn.query_string ?
a->conn->mg_conn.query_string : "na";
int cmp_result = 0;
if (S_ISDIR(a->st.st_mode) && !S_ISDIR(b->st.st_mode)) {
return -1; // Always put directories on top
} else if (!S_ISDIR(a->st.st_mode) && S_ISDIR(b->st.st_mode)) {
return 1; // Always put directories on top
} else if (*qs == 'n') {
cmp_result = strcmp(a->file_name, b->file_name);
} else if (*qs == 's') {
cmp_result = a->st.st_size == b->st.st_size ? 0 :
a->st.st_size > b->st.st_size ? 1 : -1;
} else if (*qs == 'd') {
cmp_result = a->st.st_mtime == b->st.st_mtime ? 0 :
a->st.st_mtime > b->st.st_mtime ? 1 : -1;
return qs[1] == 'd' ? -cmp_result : cmp_result;
2393
2394
2395
2396
2397
2398
2399
2400
2401
2402
2403
2404
2405
2406
2407
2408
2409
2410
2411
2412
2413
2414
2415
static void send_directory_listing(struct connection *conn, const char *dir) {
char buf[2000];
struct dir_entry *arr = NULL;
int i, num_entries, sort_direction = conn->mg_conn.query_string != NULL &&
conn->mg_conn.query_string[1] == 'd' ? 'a' : 'd';
conn->mg_conn.status_code = 200;
mg_snprintf(buf, sizeof(buf), "%s",
"HTTP/1.1 200 OK\r\n"
"Transfer-Encoding: Chunked\r\n"
"Content-Type: text/html; charset=utf-8\r\n\r\n");
spool(&conn->remote_iobuf, buf, strlen(buf));
mg_snprintf(buf, sizeof(buf),
"<html><head><title>Index of %s</title>"
"<style>th {text-align: left;}</style></head>"
"<body><h1>Index of %s</h1><pre><table cellpadding=\"0\">"
"<tr><th><a href=\"?n%c\">Name</a></th>"
"<th><a href=\"?d%c\">Modified</a></th>"
"<th><a href=\"?s%c\">Size</a></th></tr>"
"<tr><td colspan=\"3\"><hr></td></tr>",
conn->mg_conn.uri, conn->mg_conn.uri,
sort_direction, sort_direction, sort_direction);
write_chunk(conn, buf, strlen(buf));
num_entries = scan_directory(conn, dir, &arr);
qsort(arr, num_entries, sizeof(arr[0]), compare_dir_entries);
for (i = 0; i < num_entries; i++) {
print_dir_entry(&arr[i]);
free(arr[i].file_name);
}
free(arr);
write_terminating_chunk(conn);
2427
2428
2429
2430
2431
2432
2433
2434
2435
2436
2437
2438
2439
2440
2441
2442
2443
2444
2445
2446
2447
2448
2449
2450
2451
close_local_endpoint(conn);
}
#endif // NO_DIRECTORY_LISTING
#ifndef NO_DAV
static void print_props(struct connection *conn, const char *uri,
file_stat_t *stp) {
char mtime[64], buf[MAX_PATH_SIZE + 200];
gmt_time_string(mtime, sizeof(mtime), &stp->st_mtime);
mg_snprintf(buf, sizeof(buf),
"<d:response>"
"<d:href>%s</d:href>"
"<d:propstat>"
"<d:prop>"
"<d:resourcetype>%s</d:resourcetype>"
"<d:getcontentlength>%" INT64_FMT "</d:getcontentlength>"
"<d:getlastmodified>%s</d:getlastmodified>"
"</d:prop>"
"<d:status>HTTP/1.1 200 OK</d:status>"
"</d:propstat>"
"</d:response>\n",
uri, S_ISDIR(stp->st_mode) ? "<d:collection/>" : "",
(int64_t) stp->st_size, mtime);
spool(&conn->remote_iobuf, buf, strlen(buf));
static void handle_propfind(struct connection *conn, const char *path,
file_stat_t *stp) {
static const char header[] = "HTTP/1.1 207 Multi-Status\r\n"
"Connection: close\r\n"
"Content-Type: text/xml; charset=utf-8\r\n\r\n"
"<?xml version=\"1.0\" encoding=\"utf-8\"?>"
"<d:multistatus xmlns:d='DAV:'>\n";
static const char footer[] = "</d:multistatus>";
const char *depth = mg_get_header(&conn->mg_conn, "Depth"),
*list_dir = conn->server->config_options[ENABLE_DIRECTORY_LISTING];
conn->mg_conn.status_code = 207;
spool(&conn->remote_iobuf, header, sizeof(header) - 1);
// Print properties for the requested resource itself
print_props(conn, conn->mg_conn.uri, stp);
// If it is a directory, print directory entries too if Depth is not 0
if (S_ISDIR(stp->st_mode) && !mg_strcasecmp(list_dir, "yes") &&
(depth == NULL || strcmp(depth, "0") != 0)) {
struct dir_entry *arr = NULL;
int i, num_entries = scan_directory(conn, path, &arr);
for (i = 0; i < num_entries; i++) {
char buf[MAX_PATH_SIZE], buf2[sizeof(buf) * 3];
struct dir_entry *de = &arr[i];
mg_snprintf(buf, sizeof(buf), "%s%s", de->conn->mg_conn.uri,
de->file_name);
mg_url_encode(buf, buf2, sizeof(buf2) - 1);
print_props(conn, buf, &de->st);
spool(&conn->remote_iobuf, footer, sizeof(footer) - 1);
close_local_endpoint(conn);
static void handle_mkcol(struct connection *conn, const char *path) {
int status_code = 500;
if (conn->mg_conn.content_len > 0) {
status_code = 415;
} else if (!mkdir(path, 0755)) {
status_code = 201;
} else if (errno == EEXIST) {
status_code = 405;
} else if (errno == EACCES) {
status_code = 403;
} else if (errno == ENOENT) {
status_code = 409;
}
send_http_error(conn, status_code, NULL);
static int remove_directory(const char *dir) {
char path[MAX_PATH_SIZE];
struct dirent *dp;
file_stat_t st;
DIR *dirp;
if ((dirp = opendir(dir)) == NULL) return 0;
while ((dp = readdir(dirp)) != NULL) {
if (!strcmp(dp->d_name, ".") || !strcmp(dp->d_name, "..")) continue;
mg_snprintf(path, sizeof(path), "%s%c%s", dir, '/', dp->d_name);
stat(path, &st);
if (S_ISDIR(st.st_mode)) {
remove_directory(path);
} else {
remove(path);
}
return 1;
}
static void handle_delete(struct connection *conn, const char *path) {
file_stat_t st;
if (!stat(path, &st)) {
send_http_error(conn, 404, NULL);
} else if (S_ISDIR(st.st_mode)) {
remove_directory(path);
send_http_error(conn, 204, NULL);
send_http_error(conn, 204, NULL);
send_http_error(conn, 423, NULL);
// For a given PUT path, create all intermediate subdirectories
// for given path. Return 0 if the path itself is a directory,
// or -1 on error, 1 if OK.
static int put_dir(const char *path) {
char buf[MAX_PATH_SIZE];
const char *s, *p;
file_stat_t st;
// Create intermediate directories if they do not exist
for (s = p = path + 1; (p = strchr(s, '/')) != NULL; s = ++p) {
if (p - path >= (int) sizeof(buf)) return -1; // Buffer overflow
memcpy(buf, path, p - path);
buf[p - path] = '\0';
if (stat(buf, &st) != 0 && mkdir(buf, 0755) != 0) return -1;
if (p[1] == '\0') return 0; // Path is a directory itself
static void handle_put(struct connection *conn, const char *path) {
file_stat_t st;
const char *range, *cl_hdr = mg_get_header(&conn->mg_conn, "Content-Length");
conn->mg_conn.status_code = !stat(path, &st) ? 200 : 201;
mg_printf(&conn->mg_conn, "HTTP/1.1 %d OK\r\n\r\n",
conn->mg_conn.status_code);
close_local_endpoint(conn);
send_http_error(conn, 500, "put_dir: %s", strerror(errno));
send_http_error(conn, 411, NULL);
#ifdef _WIN32
//On Windows, open() is a macro with 2 params
} else if ((conn->endpoint.fd =
open(path, O_RDWR | O_CREAT | O_TRUNC)) < 0) {
#else
} else if ((conn->endpoint.fd =
open(path, O_RDWR | O_CREAT | O_TRUNC, 0644)) < 0) {
send_http_error(conn, 500, "open(%s): %s", path, strerror(errno));
DBG(("PUT [%s] %d", path, conn->local_iobuf.len));
conn->endpoint_type = EP_PUT;
set_close_on_exec(conn->endpoint.fd);
range = mg_get_header(&conn->mg_conn, "Content-Range");
conn->cl = to64(cl_hdr);
r1 = r2 = 0;
if (range != NULL && parse_range_header(range, &r1, &r2) > 0) {
conn->mg_conn.status_code = 206;
lseek(conn->endpoint.fd, r1, SEEK_SET);
conn->cl = r2 > r1 ? r2 - r1 + 1: conn->cl - r1;
mg_printf(&conn->mg_conn, "HTTP/1.1 %d OK\r\nContent-Length: 0\r\n\r\n",
conn->mg_conn.status_code);
static void forward_put_data(struct connection *conn) {
struct iobuf *io = &conn->local_iobuf;
int n = write(conn->endpoint.fd, io->buf, io->len);
if (n > 0) {
memmove(io->buf, io->buf + n, io->len - n);
io->len -= n;
conn->cl -= n;
if (conn->cl <= 0) {
close_local_endpoint(conn);
static void send_options(struct connection *conn) {
static const char reply[] = "HTTP/1.1 200 OK\r\nAllow: GET, POST, HEAD, "
"CONNECT, PUT, DELETE, OPTIONS, PROPFIND, MKCOL\r\nDAV: 1\r\n\r\n";
spool(&conn->remote_iobuf, reply, sizeof(reply) - 1);
conn->flags |= CONN_SPOOL_DONE;
#ifndef NO_AUTH
static void send_authorization_request(struct connection *conn) {
conn->mg_conn.status_code = 401;
mg_printf(&conn->mg_conn,
"HTTP/1.1 401 Unauthorized\r\n"
"WWW-Authenticate: Digest qop=\"auth\", "
"realm=\"%s\", nonce=\"%lu\"\r\n\r\n",
conn->server->config_options[AUTH_DOMAIN],
(unsigned long) time(NULL));
close_local_endpoint(conn);
}
// Use the global passwords file, if specified by auth_gpass option,
// or search for .htpasswd in the requested directory.
static FILE *open_auth_file(struct connection *conn, const char *path) {
char name[MAX_PATH_SIZE];
const char *p, *gpass = conn->server->config_options[GLOBAL_AUTH_FILE];
file_stat_t st;
FILE *fp = NULL;
if (gpass != NULL) {
// Use global passwords file
fp = fopen(gpass, "r");
} else if (!stat(path, &st) && S_ISDIR(st.st_mode)) {
mg_snprintf(name, sizeof(name), "%s%c%s", path, '/', PASSWORDS_FILE_NAME);
fp = fopen(name, "r");
// Try to find .htpasswd in requested directory.
if ((p = strrchr(path, '/')) == NULL) p = path;
mg_snprintf(name, sizeof(name), "%.*s%c%s",
(int) (p - path), path, '/', PASSWORDS_FILE_NAME);
fp = fopen(name, "r");
typedef struct MD5Context {
uint32_t buf[4];
uint32_t bits[2];
unsigned char in[64];
} MD5_CTX;
static void byteReverse(unsigned char *buf, unsigned longs) {
uint32_t t;
// Forrest: MD5 expect LITTLE_ENDIAN, swap if BIG_ENDIAN
if (is_big_endian()) {
do {
t = (uint32_t) ((unsigned) buf[3] << 8 | buf[2]) << 16 |
((unsigned) buf[1] << 8 | buf[0]);
* (uint32_t *) buf = t;
buf += 4;
} while (--longs);
#define F1(x, y, z) (z ^ (x & (y ^ z)))
#define F2(x, y, z) F1(z, x, y)
#define F3(x, y, z) (x ^ y ^ z)
#define F4(x, y, z) (y ^ (x | ~z))
#define MD5STEP(f, w, x, y, z, data, s) \
( w += f(x, y, z) + data, w = w<<s | w>>(32-s), w += x )
// Start MD5 accumulation. Set bit count to 0 and buffer to mysterious
// initialization constants.
static void MD5Init(MD5_CTX *ctx) {
ctx->buf[0] = 0x67452301;
ctx->buf[1] = 0xefcdab89;
ctx->buf[2] = 0x98badcfe;
ctx->buf[3] = 0x10325476;
ctx->bits[0] = 0;
ctx->bits[1] = 0;
static void MD5Transform(uint32_t buf[4], uint32_t const in[16]) {
register uint32_t a, b, c, d;
a = buf[0];
b = buf[1];
c = buf[2];
d = buf[3];
MD5STEP(F1, a, b, c, d, in[0] + 0xd76aa478, 7);
MD5STEP(F1, d, a, b, c, in[1] + 0xe8c7b756, 12);
MD5STEP(F1, c, d, a, b, in[2] + 0x242070db, 17);
MD5STEP(F1, b, c, d, a, in[3] + 0xc1bdceee, 22);
MD5STEP(F1, a, b, c, d, in[4] + 0xf57c0faf, 7);
MD5STEP(F1, d, a, b, c, in[5] + 0x4787c62a, 12);
MD5STEP(F1, c, d, a, b, in[6] + 0xa8304613, 17);
MD5STEP(F1, b, c, d, a, in[7] + 0xfd469501, 22);
MD5STEP(F1, a, b, c, d, in[8] + 0x698098d8, 7);
MD5STEP(F1, d, a, b, c, in[9] + 0x8b44f7af, 12);
MD5STEP(F1, c, d, a, b, in[10] + 0xffff5bb1, 17);
MD5STEP(F1, b, c, d, a, in[11] + 0x895cd7be, 22);
MD5STEP(F1, a, b, c, d, in[12] + 0x6b901122, 7);
MD5STEP(F1, d, a, b, c, in[13] + 0xfd987193, 12);
MD5STEP(F1, c, d, a, b, in[14] + 0xa679438e, 17);
MD5STEP(F1, b, c, d, a, in[15] + 0x49b40821, 22);
MD5STEP(F2, a, b, c, d, in[1] + 0xf61e2562, 5);
MD5STEP(F2, d, a, b, c, in[6] + 0xc040b340, 9);
MD5STEP(F2, c, d, a, b, in[11] + 0x265e5a51, 14);
MD5STEP(F2, b, c, d, a, in[0] + 0xe9b6c7aa, 20);
MD5STEP(F2, a, b, c, d, in[5] + 0xd62f105d, 5);
MD5STEP(F2, d, a, b, c, in[10] + 0x02441453, 9);
MD5STEP(F2, c, d, a, b, in[15] + 0xd8a1e681, 14);
MD5STEP(F2, b, c, d, a, in[4] + 0xe7d3fbc8, 20);
MD5STEP(F2, a, b, c, d, in[9] + 0x21e1cde6, 5);
MD5STEP(F2, d, a, b, c, in[14] + 0xc33707d6, 9);
MD5STEP(F2, c, d, a, b, in[3] + 0xf4d50d87, 14);
MD5STEP(F2, b, c, d, a, in[8] + 0x455a14ed, 20);
MD5STEP(F2, a, b, c, d, in[13] + 0xa9e3e905, 5);
MD5STEP(F2, d, a, b, c, in[2] + 0xfcefa3f8, 9);
MD5STEP(F2, c, d, a, b, in[7] + 0x676f02d9, 14);
MD5STEP(F2, b, c, d, a, in[12] + 0x8d2a4c8a, 20);
MD5STEP(F3, a, b, c, d, in[5] + 0xfffa3942, 4);
MD5STEP(F3, d, a, b, c, in[8] + 0x8771f681, 11);
MD5STEP(F3, c, d, a, b, in[11] + 0x6d9d6122, 16);
MD5STEP(F3, b, c, d, a, in[14] + 0xfde5380c, 23);
MD5STEP(F3, a, b, c, d, in[1] + 0xa4beea44, 4);
MD5STEP(F3, d, a, b, c, in[4] + 0x4bdecfa9, 11);
MD5STEP(F3, c, d, a, b, in[7] + 0xf6bb4b60, 16);
MD5STEP(F3, b, c, d, a, in[10] + 0xbebfbc70, 23);
MD5STEP(F3, a, b, c, d, in[13] + 0x289b7ec6, 4);
MD5STEP(F3, d, a, b, c, in[0] + 0xeaa127fa, 11);
MD5STEP(F3, c, d, a, b, in[3] + 0xd4ef3085, 16);
MD5STEP(F3, b, c, d, a, in[6] + 0x04881d05, 23);
MD5STEP(F3, a, b, c, d, in[9] + 0xd9d4d039, 4);
MD5STEP(F3, d, a, b, c, in[12] + 0xe6db99e5, 11);
MD5STEP(F3, c, d, a, b, in[15] + 0x1fa27cf8, 16);
MD5STEP(F3, b, c, d, a, in[2] + 0xc4ac5665, 23);
2767
2768
2769
2770
2771
2772
2773
2774
2775
2776
2777
2778
2779
2780
2781
2782
2783
2784
2785
2786
2787
MD5STEP(F4, a, b, c, d, in[0] + 0xf4292244, 6);
MD5STEP(F4, d, a, b, c, in[7] + 0x432aff97, 10);
MD5STEP(F4, c, d, a, b, in[14] + 0xab9423a7, 15);
MD5STEP(F4, b, c, d, a, in[5] + 0xfc93a039, 21);
MD5STEP(F4, a, b, c, d, in[12] + 0x655b59c3, 6);
MD5STEP(F4, d, a, b, c, in[3] + 0x8f0ccc92, 10);
MD5STEP(F4, c, d, a, b, in[10] + 0xffeff47d, 15);
MD5STEP(F4, b, c, d, a, in[1] + 0x85845dd1, 21);
MD5STEP(F4, a, b, c, d, in[8] + 0x6fa87e4f, 6);
MD5STEP(F4, d, a, b, c, in[15] + 0xfe2ce6e0, 10);
MD5STEP(F4, c, d, a, b, in[6] + 0xa3014314, 15);
MD5STEP(F4, b, c, d, a, in[13] + 0x4e0811a1, 21);
MD5STEP(F4, a, b, c, d, in[4] + 0xf7537e82, 6);
MD5STEP(F4, d, a, b, c, in[11] + 0xbd3af235, 10);
MD5STEP(F4, c, d, a, b, in[2] + 0x2ad7d2bb, 15);
MD5STEP(F4, b, c, d, a, in[9] + 0xeb86d391, 21);
buf[0] += a;
buf[1] += b;
buf[2] += c;
buf[3] += d;
static void MD5Update(MD5_CTX *ctx, unsigned char const *buf, unsigned len) {
uint32_t t;
t = ctx->bits[0];
if ((ctx->bits[0] = t + ((uint32_t) len << 3)) < t)
ctx->bits[1]++;
ctx->bits[1] += len >> 29;
if (t) {
unsigned char *p = (unsigned char *) ctx->in + t;
t = 64 - t;
if (len < t) {
memcpy(p, buf, len);
return;
memcpy(p, buf, t);
byteReverse(ctx->in, 16);
MD5Transform(ctx->buf, (uint32_t *) ctx->in);
buf += t;
len -= t;
while (len >= 64) {
memcpy(ctx->in, buf, 64);
byteReverse(ctx->in, 16);
MD5Transform(ctx->buf, (uint32_t *) ctx->in);
buf += 64;
len -= 64;
static void MD5Final(unsigned char digest[16], MD5_CTX *ctx) {
unsigned count;
unsigned char *p;
uint32_t *a;
count = (ctx->bits[0] >> 3) & 0x3F;
p = ctx->in + count;
*p++ = 0x80;
count = 64 - 1 - count;
if (count < 8) {
memset(p, 0, count);
byteReverse(ctx->in, 16);
MD5Transform(ctx->buf, (uint32_t *) ctx->in);
memset(ctx->in, 0, 56);
a = (uint32_t *)ctx->in;
a[14] = ctx->bits[0];
a[15] = ctx->bits[1];
MD5Transform(ctx->buf, (uint32_t *) ctx->in);
byteReverse((unsigned char *) ctx->buf, 4);
memcpy(digest, ctx->buf, 16);
memset((char *) ctx, 0, sizeof(*ctx));
// Stringify binary data. Output buffer must be twice as big as input,
// because each byte takes 2 bytes in string representation
static void bin2str(char *to, const unsigned char *p, size_t len) {
static const char *hex = "0123456789abcdef";
for (; len--; p++) {
*to++ = hex[p[0] >> 4];
*to++ = hex[p[0] & 0x0f];
// Return stringified MD5 hash for list of strings. Buffer must be 33 bytes.
char *mg_md5(char buf[33], ...) {
unsigned char hash[16];
const char *p;
va_list ap;
MD5_CTX ctx;
va_start(ap, buf);
while ((p = va_arg(ap, const char *)) != NULL) {
MD5Update(&ctx, (const unsigned char *) p, (unsigned) strlen(p));
MD5Final(hash, &ctx);
bin2str(buf, hash, sizeof(hash));
return buf;
// Check the user's password, return 1 if OK
static int check_password(const char *method, const char *ha1, const char *uri,
const char *nonce, const char *nc, const char *cnonce,
const char *qop, const char *response) {
char ha2[32 + 1], expected_response[32 + 1];
#if 0
// Check for authentication timeout
if ((unsigned long) time(NULL) - (unsigned long) to64(nonce) > 3600) {
return 0;
}
mg_md5(ha2, method, ":", uri, NULL);
mg_md5(expected_response, ha1, ":", nonce, ":", nc,
":", cnonce, ":", qop, ":", ha2, NULL);
return mg_strcasecmp(response, expected_response) == 0;
// Authorize against the opened passwords file. Return 1 if authorized.
int mg_authorize_digest(struct mg_connection *c, FILE *fp) {
struct connection *conn = (struct connection *) c;
const char *hdr = mg_get_header(c, "Authorization");
char line[256], f_user[256], ha1[256], f_domain[256], user[100], nonce[100],
uri[MAX_REQUEST_SIZE], cnonce[100], resp[100], qop[100], nc[100];
if (hdr == NULL || mg_strncasecmp(hdr, "Digest ", 7) != 0) return 0;
if (!mg_parse_header(hdr, "username", user, sizeof(user))) return 0;
if (!mg_parse_header(hdr, "cnonce", cnonce, sizeof(cnonce))) return 0;
if (!mg_parse_header(hdr, "response", resp, sizeof(resp))) return 0;
if (!mg_parse_header(hdr, "uri", uri, sizeof(uri))) return 0;
if (!mg_parse_header(hdr, "qop", qop, sizeof(qop))) return 0;
if (!mg_parse_header(hdr, "nc", nc, sizeof(nc))) return 0;
if (!mg_parse_header(hdr, "nonce", nonce, sizeof(nonce))) return 0;
while (fgets(line, sizeof(line), fp) != NULL) {
if (sscanf(line, "%[^:]:%[^:]:%s", f_user, f_domain, ha1) == 3 &&
!strcmp(user, f_user) &&
// NOTE(lsm): due to a bug in MSIE, we do not compare URIs
!strcmp(conn->server->config_options[AUTH_DOMAIN], f_domain))
return check_password(c->request_method, ha1, uri,
// Return 1 if request is authorised, 0 otherwise.
static int is_authorized(struct connection *conn, const char *path) {
FILE *fp;
int authorized = 1;
if ((fp = open_auth_file(conn, path)) != NULL) {
authorized = mg_authorize_digest(&conn->mg_conn, fp);
static int is_authorized_for_dav(struct connection *conn) {
const char *auth_file = conn->server->config_options[DAV_AUTH_FILE];
FILE *fp;
int authorized = 0;
if (auth_file != NULL && (fp = fopen(auth_file, "r")) != NULL) {
authorized = mg_authorize_digest(&conn->mg_conn, fp);
static int is_dangerous_dav_request(const struct connection *conn) {
const char *s = conn->mg_conn.request_method;
return s && (!strcmp(s, "PUT") || !strcmp(s, "DELETE") ||
!strcmp(s, "MKCOL"));
int mg_parse_header(const char *str, const char *var_name, char *buf,
size_t buf_size) {
int ch = ' ', len = 0, n = strlen(var_name);
const char *p, *s = NULL;
// Find where variable starts
while (str != NULL && (s = strstr(str, var_name)) != NULL &&
((s > str && s[-1] != ' ') || s[n] != '=')) {
str = s + n;
if (s != NULL && s[n + 1] != '\0') {
s += n + 1;
if (*s == '"' || *s == '\'') ch = *s++;
p = s;
while (p[0] != '\0' && p[0] != ch && len < (int) buf_size) {
if (p[0] == '\\' && p[1] == ch) p++;
buf[len++] = *p++;
}
if (len >= (int) buf_size || (ch != ' ' && *p != ch)) {
len = 0;
} else {
if (len > 0 && s[len - 1] == ',') len--;
buf[len] = '\0';
}
}