Http parsing and responses are now mostly complete
This commit is contained in:
135
src/http/http.c
135
src/http/http.c
@@ -9,17 +9,16 @@
|
||||
#include "../ut/utstring.h"
|
||||
#include "http.h"
|
||||
|
||||
/*
|
||||
* METHOD_GET, METHOD_POST, METHOD_HEAD, METHOD_PUT,
|
||||
METHOD_DELETE, METHOD_OPTIONS, METHOD_TRACE,
|
||||
METHOD_CONNECT, METHOD_OTHER
|
||||
*/
|
||||
|
||||
void http_header_icd_init_f(void* elem) {
|
||||
memset(elem, 1, sizeof(http_header));
|
||||
}
|
||||
void http_header_icd_dtor_f(void* elem) {
|
||||
http_header *header = (http_header*)elem;
|
||||
if (header->name!=NULL) free(header->name);
|
||||
if (header->content!=NULL) free(header->content);
|
||||
}
|
||||
|
||||
UT_icd http_header_icd = {sizeof(http_header), http_header_icd_init_f, NULL, NULL};
|
||||
UT_icd http_header_icd = {sizeof(http_header), http_header_icd_init_f, NULL, http_header_icd_dtor_f};
|
||||
|
||||
char* http_method_getstring(http_request_method method, char* method_other) {
|
||||
switch(method) {
|
||||
@@ -207,17 +206,11 @@ void http_header_list_remove(http_header_list *list, const char* name) {
|
||||
headers = http_header_list_getall(list, name, &count);
|
||||
for(int i=0; i<count; i++) {
|
||||
int pos = utarray_eltidx(list,headers[i]);
|
||||
free(headers[i]->name);
|
||||
free(headers[i]->content);
|
||||
utarray_erase(list, pos, 1);
|
||||
}
|
||||
free(headers);
|
||||
}
|
||||
void http_header_list_delete(http_header_list *list) {
|
||||
HTTP_HEADER_FOREACH(list, elem) {
|
||||
free(elem->name);
|
||||
free(elem->content);
|
||||
}
|
||||
utarray_free(list);
|
||||
}
|
||||
|
||||
@@ -242,6 +235,30 @@ void http_request_append_body(http_request *req, const char* body) {
|
||||
}
|
||||
strcat(req->body, body);
|
||||
}
|
||||
char* http_request_write(http_request *req) {
|
||||
UT_string *output = calloc(1, sizeof(UT_string));
|
||||
utstring_init(output);
|
||||
|
||||
utstring_printf(output, "%s %s %s\r\n",
|
||||
http_method_getstring(req->req->method, req->req->method_other),
|
||||
req->req->uri,
|
||||
req->req->version == HTTP10 ? "HTTP/1.0" : "HTTP/1.1"
|
||||
);
|
||||
|
||||
HTTP_HEADER_FOREACH(req->headers, elem) {
|
||||
utstring_printf(output, "%s: %s\r\n",
|
||||
elem->name, elem->content);
|
||||
}
|
||||
|
||||
utstring_printf(output, "\r\n");
|
||||
|
||||
if (req->body != NULL) {
|
||||
utstring_printf(output, "%s\r\n", req->body);
|
||||
}
|
||||
char* result = utstring_body(output);
|
||||
free(output);
|
||||
return result;
|
||||
}
|
||||
void http_request_delete(http_request *req) {
|
||||
if (req->req != NULL) {
|
||||
http_request_line_delete(req->req);
|
||||
@@ -250,16 +267,17 @@ void http_request_delete(http_request *req) {
|
||||
free(req->body);
|
||||
free(req);
|
||||
}
|
||||
|
||||
|
||||
http_response* http_response_new(http_response_line *resp) {
|
||||
http_response *response = calloc(1, sizeof(http_response));
|
||||
response->resp = resp;
|
||||
response->headers = http_header_list_new();
|
||||
response->body_chunked = false;
|
||||
response->body = NULL;
|
||||
return response;
|
||||
}
|
||||
void http_response_append_body(http_response *resp, const char* body) {
|
||||
uint32_t bodylen = 0;
|
||||
size_t bodylen = 0;
|
||||
if (resp->body != NULL) {
|
||||
bodylen = strlen(resp->body);
|
||||
}
|
||||
@@ -290,14 +308,24 @@ char* http_response_write(http_response *resp) {
|
||||
utstring_printf(output, "%hu %s\r\n", resp->resp->code, http_response_line_get_message(resp->resp));
|
||||
|
||||
if (resp->resp->code != 100) { //No additional headers for Continue messages
|
||||
//Add content length header
|
||||
uint32_t messageLength = 0;
|
||||
if (resp->body != NULL) {
|
||||
messageLength = strlen(resp->body);
|
||||
if (resp->body_chunked == false) {
|
||||
//Add content length header
|
||||
uint32_t messageLength = 0;
|
||||
if (resp->body != NULL) {
|
||||
messageLength = strlen(resp->body);
|
||||
}
|
||||
char messageLengthStr[100];
|
||||
snprintf(messageLengthStr, 99, "%u", messageLength);
|
||||
http_header_list_add(resp->headers, http_header_new(HEADER_CONTENT_LENGTH, messageLengthStr), true);
|
||||
} else { //Chunked encoding
|
||||
http_header_list_add(resp->headers, http_header_new(HEADER_TRANSFER_ENCODING, "chunked"), true);
|
||||
}
|
||||
|
||||
//Add content type if not defined
|
||||
http_header* contenttype = http_header_list_get(resp->headers, HEADER_CONTENT_TYPE);
|
||||
if (contenttype == NULL) {
|
||||
http_header_list_add(resp->headers, http_header_new(HEADER_CONTENT_TYPE, DEFAULT_CONTENT_TYPE), false);
|
||||
}
|
||||
char messageLengthStr[100];
|
||||
snprintf(messageLengthStr, 99, "%u", messageLength);
|
||||
http_header_list_add(resp->headers, http_header_new(HEADER_CONTENT_LENGTH, messageLengthStr), true);
|
||||
|
||||
//Add date header
|
||||
time_t timenow = time(NULL);
|
||||
@@ -314,9 +342,13 @@ char* http_response_write(http_response *resp) {
|
||||
utstring_printf(output, "\r\n");
|
||||
|
||||
//Write the request
|
||||
//TODO: chunked support for output
|
||||
if (resp->body != NULL) {
|
||||
utstring_printf(output, "%s", resp->body);
|
||||
if (resp->body_chunked == false && resp->body != NULL) {
|
||||
utstring_printf(output, "%s\r\n", resp->body);
|
||||
}
|
||||
if (resp->body_chunked == true && resp->body != NULL) {
|
||||
char *chunks = http_chunks_write(resp->body);
|
||||
utstring_printf(output, "%s", chunks);
|
||||
free(chunks);
|
||||
}
|
||||
char* outputStr = utstring_body(output);
|
||||
free(output);
|
||||
@@ -328,10 +360,10 @@ http_response* http_response_create_builtin(uint16_t code, char* errmsg) {
|
||||
|
||||
http_header_list_add(resp->headers, http_header_new(HEADER_CONTENT_TYPE, "text/html"), false);
|
||||
|
||||
file_map* errorpage = map_file("content/error.html");
|
||||
file_map* errorpage = file_map_new("content/error.html");
|
||||
if (errorpage != NULL) {
|
||||
http_response_append_body(resp, errorpage->map);
|
||||
free_mapped_file(errorpage);
|
||||
file_map_delete(errorpage);
|
||||
} else {
|
||||
http_response_append_body(resp, "{{title}}\n\n{{message}}");
|
||||
http_header_list_add(resp->headers, http_header_new(HEADER_CONTENT_TYPE, "text/plain"), true);
|
||||
@@ -347,4 +379,53 @@ http_response* http_response_create_builtin(uint16_t code, char* errmsg) {
|
||||
resp->body = str_replace(resp->body, "{{message}}", errmsg);
|
||||
|
||||
return resp;
|
||||
}
|
||||
|
||||
|
||||
char* http_chunks_write(char* source) {
|
||||
size_t sourcelen = strlen(source);
|
||||
|
||||
UT_string *output = calloc(1, sizeof(UT_string));
|
||||
utstring_init(output);
|
||||
char buffer[HTTP_CHUNK_MAXSIZE+1] = {0};
|
||||
//determine max chars for length line
|
||||
sprintf(buffer, "%zx;\r\n", (size_t)HTTP_CHUNK_MAXSIZE);
|
||||
size_t overhead = strlen(buffer);
|
||||
overhead+=3;//account for terminating CRLF + \0
|
||||
buffer[0] = '\0';
|
||||
|
||||
size_t i = 0;
|
||||
while (i < sourcelen) {
|
||||
//how much can we write in this chunk?
|
||||
size_t sourcerem = sourcelen - i;
|
||||
size_t chunklen =
|
||||
sourcerem > HTTP_CHUNK_MAXSIZE-overhead
|
||||
? HTTP_CHUNK_MAXSIZE-overhead
|
||||
: sourcerem;
|
||||
utstring_printf(output, "%zx;\r\n", chunklen);
|
||||
memset(&buffer, 0, sizeof(buffer));
|
||||
strncpy(buffer, source+i, chunklen);
|
||||
utstring_printf(output, "%s\r\n", buffer);
|
||||
i += chunklen;
|
||||
}
|
||||
char* outputstr = utstring_body(output);
|
||||
free(output);
|
||||
return outputstr;
|
||||
}
|
||||
char* http_chunks_terminate(http_header_list *footers) {
|
||||
UT_string *output = calloc(1, sizeof(UT_string));
|
||||
utstring_init(output);
|
||||
|
||||
utstring_printf(output, "0\r\n");
|
||||
if (footers != NULL) {
|
||||
//write footers
|
||||
HTTP_HEADER_FOREACH(footers, elem) {
|
||||
utstring_printf(output, "%s: %s\r\n", elem->name, elem->content);
|
||||
}
|
||||
}
|
||||
utstring_printf(output, "\r\n");
|
||||
|
||||
char* outputstr = utstring_body(output);
|
||||
free(output);
|
||||
return outputstr;
|
||||
}
|
||||
@@ -30,6 +30,9 @@ extern "C" {
|
||||
#define HEADER_IF_UNMODIFIED_SINCE "If-Unmodified-Since"
|
||||
|
||||
#define FORMAT_HEADER_DATE "%a, %e %h %Y %T %Z"
|
||||
#define DEFAULT_CONTENT_TYPE "text/plain"
|
||||
|
||||
#define HTTP_CHUNK_MAXSIZE 1024*16
|
||||
|
||||
typedef enum http_request_method {
|
||||
METHOD_GET, METHOD_POST, METHOD_HEAD, METHOD_PUT,
|
||||
@@ -82,6 +85,7 @@ extern "C" {
|
||||
typedef struct http_response {
|
||||
http_response_line *resp;
|
||||
http_header_list *headers;
|
||||
bool body_chunked;
|
||||
char* body;
|
||||
} http_response;
|
||||
|
||||
@@ -109,12 +113,17 @@ extern "C" {
|
||||
|
||||
http_request* http_request_new();
|
||||
void http_request_append_body(http_request *req, const char* body);
|
||||
char* http_request_write(http_request *req);
|
||||
void http_request_delete(http_request *req);
|
||||
|
||||
http_response* http_response_new(http_response_line *resp);
|
||||
void http_response_append_body(http_response *resp, const char* body);
|
||||
void http_response_delete(http_response *resp);
|
||||
char* http_response_write(http_response *resp);
|
||||
http_response* http_response_create_builtin(uint16_t code, char* errmsg);
|
||||
|
||||
char* http_chunks_write(char* source);
|
||||
char* http_chunks_terminate(http_header_list *footers);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
|
||||
101
src/http/parse.c
101
src/http/parse.c
@@ -8,19 +8,14 @@
|
||||
#include "http.h"
|
||||
#include "parse.h"
|
||||
|
||||
#define GETSTR(str, at, length) do { \
|
||||
#define GET_CB_STR(str, at, length) do { \
|
||||
str = calloc(length+1, sizeof(char));\
|
||||
strncpy(str, at, length);\
|
||||
}while(0);
|
||||
#define SKT(parser) ((skt_elem*)parser->data)
|
||||
|
||||
http_parser_settings *parser_settings = NULL;
|
||||
|
||||
skt_elem *current_socket=NULL;
|
||||
|
||||
void parser_set_currentskt(skt_elem *elem) {
|
||||
current_socket = elem;
|
||||
}
|
||||
|
||||
http_parser_settings* parser_get_settings(skt_elem *elem) {
|
||||
if (parser_settings == NULL) {
|
||||
parser_settings = calloc(1, sizeof(http_parser_settings));
|
||||
@@ -30,111 +25,103 @@ http_parser_settings* parser_get_settings(skt_elem *elem) {
|
||||
parser_settings->on_headers_complete = parser_cb_on_headers_complete;
|
||||
parser_settings->on_message_begin = parser_cb_on_message_begin;
|
||||
parser_settings->on_message_complete = parser_cb_on_message_complete;
|
||||
parser_settings->on_status_complete = parser_cb_on_status;
|
||||
parser_settings->on_status = parser_cb_on_status;
|
||||
parser_settings->on_url = parser_cb_on_url;
|
||||
}
|
||||
parser_set_currentskt(elem);
|
||||
return parser_settings;
|
||||
}
|
||||
|
||||
int parser_cb_on_message_begin(http_parser* parser) {
|
||||
info("parser_cb_on_message_begin");
|
||||
if (current_socket->current_request != NULL) {
|
||||
http_request_delete(current_socket->current_request);
|
||||
if (SKT(parser)->current_request != NULL) {
|
||||
http_request_delete(SKT(parser)->current_request);
|
||||
}
|
||||
current_socket->current_request = http_request_new();
|
||||
SKT(parser)->current_request = http_request_new();
|
||||
|
||||
return 0;
|
||||
}
|
||||
int parser_cb_on_url(http_parser* parser, const char *at, size_t length) {
|
||||
char* str;GETSTR(str,at,length);
|
||||
info("parser_cb_on_url: %s",str);
|
||||
current_socket->current_request->req = http_request_line_new(http_method_fromstring(http_method_str(parser->method)), NULL);
|
||||
current_socket->current_request->req->uri = str;
|
||||
char* str;GET_CB_STR(str,at,length);
|
||||
SKT(parser)->current_request->req = http_request_line_new(http_method_fromstring(http_method_str(parser->method)), NULL);
|
||||
SKT(parser)->current_request->req->uri = str;
|
||||
return 0;
|
||||
}
|
||||
int parser_cb_on_status(http_parser* parser) {
|
||||
int parser_cb_on_status(http_parser* parser, const char *at, size_t length) {
|
||||
//Responses only, so ignored
|
||||
info("parser_cb_on_status");
|
||||
return 0;
|
||||
}
|
||||
int parser_cb_on_header_field(http_parser* parser, const char *at, size_t length) {
|
||||
char* str;GETSTR(str,at,length);
|
||||
info("parser_cb_on_header_field: %s",str);
|
||||
char* str;GET_CB_STR(str,at,length);
|
||||
|
||||
if (current_socket->parser_header_state == HSTATE_NONE) {
|
||||
if (SKT(parser)->parser_header_state == HSTATE_NONE) {
|
||||
//First call, new header
|
||||
if (current_socket->parser_current_header != NULL) {
|
||||
http_header_delete(current_socket->parser_current_header);
|
||||
if (SKT(parser)->parser_current_header != NULL) {
|
||||
http_header_delete(SKT(parser)->parser_current_header);
|
||||
}
|
||||
current_socket->parser_current_header = http_header_new(str, NULL);
|
||||
} else if (current_socket->parser_header_state == HSTATE_VALUE) {
|
||||
SKT(parser)->parser_current_header = http_header_new(str, NULL);
|
||||
|
||||
//the http version should also be set now
|
||||
if (parser->http_major == 1) {
|
||||
if (parser->http_minor == 0) {
|
||||
SKT(parser)->current_request->req->version = HTTP10;
|
||||
} else if (parser->http_minor == 1) {
|
||||
SKT(parser)->current_request->req->version = HTTP11;
|
||||
} else {
|
||||
return -1;
|
||||
}
|
||||
} else {
|
||||
return -1;
|
||||
}
|
||||
} else if (SKT(parser)->parser_header_state == HSTATE_VALUE) {
|
||||
//New header
|
||||
if (current_socket->parser_current_header != NULL) {
|
||||
http_header_list_add(current_socket->current_request->headers, current_socket->parser_current_header, false);
|
||||
if (SKT(parser)->parser_current_header != NULL) {
|
||||
http_header_list_add(SKT(parser)->current_request->headers, SKT(parser)->parser_current_header, false);
|
||||
}
|
||||
current_socket->parser_current_header = http_header_new(str, NULL);
|
||||
} else if (current_socket->parser_header_state == HSTATE_FIELD) {
|
||||
SKT(parser)->parser_current_header = http_header_new(str, NULL);
|
||||
} else if (SKT(parser)->parser_header_state == HSTATE_FIELD) {
|
||||
//continuation of current headers name
|
||||
http_header* header = current_socket->parser_current_header;
|
||||
http_header* header = SKT(parser)->parser_current_header;
|
||||
size_t newlen = strlen(header->name) + length +1;
|
||||
header->name = realloc(header->name, newlen * sizeof(char));
|
||||
} else {
|
||||
return 1;
|
||||
}
|
||||
|
||||
current_socket->parser_header_state = HSTATE_FIELD;
|
||||
SKT(parser)->parser_header_state = HSTATE_FIELD;
|
||||
free(str);
|
||||
return 0;
|
||||
|
||||
}
|
||||
int parser_cb_on_header_value(http_parser* parser, const char *at, size_t length) {
|
||||
char* str;GETSTR(str,at,length);
|
||||
info("parser_cb_on_header_value: %s",str);
|
||||
char* str;GET_CB_STR(str,at,length);
|
||||
|
||||
http_header_append_content(current_socket->parser_current_header, str);
|
||||
http_header_append_content(SKT(parser)->parser_current_header, str);
|
||||
|
||||
current_socket->parser_header_state = HSTATE_VALUE;
|
||||
SKT(parser)->parser_header_state = HSTATE_VALUE;
|
||||
free(str);
|
||||
return 0;
|
||||
}
|
||||
int parser_cb_on_headers_complete(http_parser* parser) {
|
||||
info("parser_cb_on_headers_complete");
|
||||
|
||||
//save current header
|
||||
if (current_socket->parser_current_header != NULL) {
|
||||
http_header_list_add(current_socket->current_request->headers, current_socket->parser_current_header, false);
|
||||
current_socket->parser_current_header = NULL;
|
||||
}
|
||||
|
||||
//the http version should also be set now
|
||||
if (parser->http_major == 1) {
|
||||
if (parser->http_minor == 0) {
|
||||
current_socket->current_request->req->version = HTTP10;
|
||||
} else if (parser->http_minor == 1) {
|
||||
current_socket->current_request->req->version = HTTP11;
|
||||
} else {
|
||||
return -1;
|
||||
}
|
||||
} else {
|
||||
return -1;
|
||||
if (SKT(parser)->parser_current_header != NULL) {
|
||||
http_header_list_add(SKT(parser)->current_request->headers, SKT(parser)->parser_current_header, false);
|
||||
SKT(parser)->parser_current_header = NULL;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
int parser_cb_on_body(http_parser* parser, const char *at, size_t length) {
|
||||
char* str;GETSTR(str,at,length);
|
||||
info("parser_cb_on_body: %s",str);
|
||||
char* str;GET_CB_STR(str,at,length);
|
||||
|
||||
http_request_append_body(current_socket->current_request, str);
|
||||
http_request_append_body(SKT(parser)->current_request, str);
|
||||
|
||||
free(str);
|
||||
return 0;
|
||||
}
|
||||
int parser_cb_on_message_complete(http_parser* parser) {
|
||||
info("parser_cb_on_message_complete");
|
||||
|
||||
current_socket->request_complete = true;
|
||||
SKT(parser)->request_complete = true;
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -12,7 +12,7 @@
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#include <http_parser.h>
|
||||
#include "http_parser.h"
|
||||
#include "http.h"
|
||||
#include "../main.h"
|
||||
|
||||
@@ -23,7 +23,7 @@ extern "C" {
|
||||
|
||||
int parser_cb_on_message_begin(http_parser* parser);
|
||||
int parser_cb_on_url(http_parser* parser, const char *at, size_t length);
|
||||
int parser_cb_on_status(http_parser* parser);
|
||||
int parser_cb_on_status(http_parser* parser, const char *at, size_t length);
|
||||
int parser_cb_on_header_field(http_parser* parser, const char *at, size_t length);
|
||||
int parser_cb_on_header_value(http_parser* parser, const char *at, size_t length);
|
||||
int parser_cb_on_headers_complete(http_parser* parser);
|
||||
|
||||
26
src/main.c
26
src/main.c
@@ -14,7 +14,8 @@
|
||||
#include <unistd.h>
|
||||
#include <fcntl.h>
|
||||
#include <sys/mman.h>
|
||||
#include <http_parser.h>
|
||||
|
||||
#include "http_parser.h"
|
||||
|
||||
#include "ut/utlist.h"
|
||||
#include "ut/utarray.h"
|
||||
@@ -60,11 +61,20 @@ int main(int argc, char** argv) {
|
||||
utstring_body(elem->info->read),
|
||||
utstring_len(elem->info->read));
|
||||
if (parsedcount != utstring_len(elem->info->read)) {
|
||||
warning("error parsing request. closing connection", false);
|
||||
char warningmsg[2048] = {0};
|
||||
snprintf(warningmsg, 2048,
|
||||
"error parsing request (%s: %s). closing connection",
|
||||
http_errno_name(elem->parser->http_errno),
|
||||
http_errno_description(elem->parser->http_errno));
|
||||
warning(warningmsg, false);
|
||||
elem->info->close = true;
|
||||
}
|
||||
utstring_clear(elem->info->read);
|
||||
if (elem->request_complete == true) {
|
||||
char* reqstr = http_request_write(elem->current_request);
|
||||
info("\n%s\n", reqstr);
|
||||
free(reqstr);
|
||||
|
||||
http_response* resp = http_response_create_builtin(200, elem->current_request->req->uri);
|
||||
utstring_printf(elem->info->write, "%s", http_response_write(resp));
|
||||
http_response_delete(resp);
|
||||
@@ -123,6 +133,7 @@ skt_elem* skt_elem_new(skt_info *info) {
|
||||
elem->info = info;
|
||||
elem->parser = calloc(1, sizeof(http_parser));
|
||||
http_parser_init(elem->parser, HTTP_REQUEST);
|
||||
elem->parser->data = (void*)elem;
|
||||
elem->parser_header_state = HSTATE_NONE;
|
||||
elem->request_complete = false;
|
||||
return elem;
|
||||
@@ -139,10 +150,9 @@ void fatal(char* msg) {
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
void warning(char* msg, bool showPError) {
|
||||
|
||||
char warning[512];
|
||||
bzero(&warning, sizeof warning);
|
||||
snprintf(warning, 511, "Warning: %s", msg);
|
||||
char warning[1024];
|
||||
memset(&warning, 0, 1024*sizeof(char));
|
||||
snprintf(warning, 1024, "Warning: %s", msg);
|
||||
|
||||
if (showPError == true) {
|
||||
perror(warning);
|
||||
@@ -200,7 +210,7 @@ char** str_splitlines(char *str, size_t *line_count) {
|
||||
return result;
|
||||
}
|
||||
|
||||
file_map* map_file(const char* filename) {
|
||||
file_map* file_map_new(const char* filename) {
|
||||
|
||||
int fd = open(filename, O_RDONLY);
|
||||
if (fd < 0) {
|
||||
@@ -221,7 +231,7 @@ file_map* map_file(const char* filename) {
|
||||
filemap->size = size;
|
||||
return filemap;
|
||||
}
|
||||
void free_mapped_file(file_map* file) {
|
||||
void file_map_delete(file_map* file) {
|
||||
if (munmap((void*)file->map, file->size) < 0) {
|
||||
warning("failed to unmap file", true);
|
||||
}
|
||||
|
||||
@@ -13,7 +13,7 @@ extern "C" {
|
||||
#endif
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <http_parser.h>
|
||||
#include "http_parser.h"
|
||||
#include "socket.h"
|
||||
#include "http/http.h"
|
||||
|
||||
@@ -45,8 +45,8 @@ extern "C" {
|
||||
char** str_splitlines(char *str, size_t *line_count);
|
||||
char* str_replace(char *str, const char *search, const char *replacement);
|
||||
|
||||
file_map* map_file(const char* filename);
|
||||
void free_mapped_file(file_map* map);
|
||||
file_map* file_map_new(const char* filename);
|
||||
void file_map_delete(file_map* map);
|
||||
|
||||
|
||||
#ifdef __cplusplus
|
||||
|
||||
Reference in New Issue
Block a user