mirror of
https://github.com/opus-tango/http-server-in-c.git
synced 2026-03-20 03:55:25 +00:00
switched over to logging
This commit is contained in:
4
.vscode/settings.json
vendored
4
.vscode/settings.json
vendored
@@ -2,6 +2,8 @@
|
|||||||
"files.associations": {
|
"files.associations": {
|
||||||
"string.h": "c",
|
"string.h": "c",
|
||||||
"types.h": "c",
|
"types.h": "c",
|
||||||
"random": "c"
|
"random": "c",
|
||||||
|
"request_handler.h": "c",
|
||||||
|
"response_builder.h": "c"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
30
http_stuff.c
30
http_stuff.c
@@ -25,7 +25,7 @@ http_response* create_http_response() {
|
|||||||
|
|
||||||
void free_http_request(http_request* req) {
|
void free_http_request(http_request* req) {
|
||||||
if (req == NULL) {
|
if (req == NULL) {
|
||||||
printf("Attempting to free NULL request\n");
|
log_message(LOG_INFO, "Attempting to free NULL request");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (req->method_str != NULL) {
|
if (req->method_str != NULL) {
|
||||||
@@ -60,7 +60,7 @@ void free_http_request(http_request* req) {
|
|||||||
|
|
||||||
void free_http_response(http_response* res) {
|
void free_http_response(http_response* res) {
|
||||||
if (res == NULL) {
|
if (res == NULL) {
|
||||||
printf("Attempting to free NULL response\n");
|
log_message(LOG_INFO, "Attempting to free NULL response");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (res->status_line != NULL) {
|
if (res->status_line != NULL) {
|
||||||
@@ -84,10 +84,12 @@ void free_http_response(http_response* res) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void print_http_request(http_request* req) {
|
void print_http_request(http_request* req) {
|
||||||
|
log_message(LOG_WARN, "Deprecated function 'print_http_request' called");
|
||||||
if (req == NULL) {
|
if (req == NULL) {
|
||||||
printf("Attempting to print NULL request\n");
|
log_message(LOG_INFO, "Attempting to print NULL request");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
printf("Method: %s\n", (req->method_str == NULL) ? "" : req->method_str);
|
printf("Method: %s\n", (req->method_str == NULL) ? "" : req->method_str);
|
||||||
printf("URL: %s\n", (req->url == NULL) ? "" : req->url);
|
printf("URL: %s\n", (req->url == NULL) ? "" : req->url);
|
||||||
printf("Headers:\n");
|
printf("Headers:\n");
|
||||||
@@ -103,6 +105,7 @@ void print_http_request(http_request* req) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void print_http_response(http_response* res) {
|
void print_http_response(http_response* res) {
|
||||||
|
log_message(LOG_WARN, "Deprecated function 'print_http_response' called");
|
||||||
printf("Status Line: %s\n",
|
printf("Status Line: %s\n",
|
||||||
(res->status_line == NULL) ? "" : res->status_line);
|
(res->status_line == NULL) ? "" : res->status_line);
|
||||||
printf("Headers:\n");
|
printf("Headers:\n");
|
||||||
@@ -116,6 +119,7 @@ void print_http_response(http_response* res) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
char* response_to_string(http_response* res) {
|
char* response_to_string(http_response* res) {
|
||||||
|
log_message(LOG_DEBUG, "Converting entire response to string");
|
||||||
// Define lengths
|
// Define lengths
|
||||||
int total_length = 0;
|
int total_length = 0;
|
||||||
int len_newline = strlen("\r\n");
|
int len_newline = strlen("\r\n");
|
||||||
@@ -162,6 +166,7 @@ char* response_to_string(http_response* res) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
char* response_headers_to_string(http_response* res) {
|
char* response_headers_to_string(http_response* res) {
|
||||||
|
log_message(LOG_DEBUG, "Generating response headers string");
|
||||||
// Define lengths
|
// Define lengths
|
||||||
int total_length = 0;
|
int total_length = 0;
|
||||||
int len_newline = strlen("\r\n");
|
int len_newline = strlen("\r\n");
|
||||||
@@ -204,18 +209,19 @@ char* response_headers_to_string(http_response* res) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
char* get_header_value_request(http_request* req, char* key) {
|
char* get_header_value_request(http_request* req, char* key) {
|
||||||
// printf("Getting header value for key: %s\n", key);
|
log_message(LOG_DEBUG, "Getting header value for %s", key);
|
||||||
for (int i = 0; i < req->num_headers; i++) {
|
for (int i = 0; i < req->num_headers; i++) {
|
||||||
if (strcmp(req->headers[i].key, key) == 0) {
|
if (strcmp(req->headers[i].key, key) == 0) {
|
||||||
return req->headers[i].value;
|
return req->headers[i].value;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// printf("Header not found\n");
|
log_message(LOG_WARN, "Header %s not found\n", key);
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
void request_add_header_n(http_request* req, char* key, size_t key_length,
|
void request_add_header_n(http_request* req, char* key, size_t key_length,
|
||||||
char* value, size_t value_length) {
|
char* value, size_t value_length) {
|
||||||
|
log_message(LOG_DEBUG, "Adding header %s: %s", key, value);
|
||||||
req->num_headers++;
|
req->num_headers++;
|
||||||
req->headers = realloc(req->headers, req->num_headers * sizeof(header_kv));
|
req->headers = realloc(req->headers, req->num_headers * sizeof(header_kv));
|
||||||
req->headers[req->num_headers - 1].key = malloc(key_length + 1);
|
req->headers[req->num_headers - 1].key = malloc(key_length + 1);
|
||||||
@@ -230,20 +236,8 @@ void request_add_header(http_request* req, char* key, char* value) {
|
|||||||
request_add_header_n(req, key, strlen(key), value, strlen(value));
|
request_add_header_n(req, key, strlen(key), value, strlen(value));
|
||||||
}
|
}
|
||||||
|
|
||||||
void request_info_print(http_request* req) {
|
|
||||||
printf("Method: %p\n", (int*)req->method);
|
|
||||||
printf("URL: %p\n", (int*)req->url);
|
|
||||||
printf("Headers:\n");
|
|
||||||
for (int i = 0; i < req->num_headers; i++) {
|
|
||||||
printf("%p: %p\n", (int*)req->headers[i].key,
|
|
||||||
(int*)req->headers[i].value);
|
|
||||||
}
|
|
||||||
printf("Content-Type: %p\n", (int*)req->content_type);
|
|
||||||
printf("Content-Length: %d\n", (int)req->content_length);
|
|
||||||
printf("Body:\n%p\n", (int*)req->body);
|
|
||||||
}
|
|
||||||
|
|
||||||
void autofill_content_meta(http_request* req) {
|
void autofill_content_meta(http_request* req) {
|
||||||
|
log_message(LOG_DEBUG, "Autofilling content metadata");
|
||||||
char* cptr = get_header_value_request(req, "Content-Type");
|
char* cptr = get_header_value_request(req, "Content-Type");
|
||||||
if (cptr != NULL) {
|
if (cptr != NULL) {
|
||||||
req->content_type = malloc(strlen(cptr) + 1);
|
req->content_type = malloc(strlen(cptr) + 1);
|
||||||
|
|||||||
@@ -6,6 +6,8 @@
|
|||||||
#include <string.h>
|
#include <string.h>
|
||||||
#include <sys/types.h>
|
#include <sys/types.h>
|
||||||
|
|
||||||
|
#include "logging.h"
|
||||||
|
|
||||||
typedef enum {
|
typedef enum {
|
||||||
GET,
|
GET,
|
||||||
POST,
|
POST,
|
||||||
@@ -111,6 +113,7 @@ void free_http_request(http_request* req);
|
|||||||
void free_http_response(http_response* res);
|
void free_http_response(http_response* res);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* DEPRECATED
|
||||||
* Prints a formatted version of an http_request struct to stdout. This function
|
* Prints a formatted version of an http_request struct to stdout. This function
|
||||||
* is meant to be used for debugging purposes.
|
* is meant to be used for debugging purposes.
|
||||||
*
|
*
|
||||||
@@ -119,6 +122,7 @@ void free_http_response(http_response* res);
|
|||||||
void print_http_request(http_request* req);
|
void print_http_request(http_request* req);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* DEPRECATED
|
||||||
* Prints a formatted version of an http_response struct to stdout. This
|
* Prints a formatted version of an http_response struct to stdout. This
|
||||||
* function is meant to be used for debugging purposes.
|
* function is meant to be used for debugging purposes.
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -1,12 +1,6 @@
|
|||||||
#include "request_handler.h"
|
#include "request_handler.h"
|
||||||
|
|
||||||
void parse_http_request(char* request, int length, struct http_request* req);
|
|
||||||
void debug_print_request(char* request);
|
|
||||||
|
|
||||||
void handle_request(char* request, int length, http_response* response) {
|
void handle_request(char* request, int length, http_response* response) {
|
||||||
// Terminate request with EOF so strtok stops at end of string
|
|
||||||
request[length] = EOF;
|
|
||||||
|
|
||||||
// Parse request into struct
|
// Parse request into struct
|
||||||
http_request* req = create_http_request();
|
http_request* req = create_http_request();
|
||||||
parse_http_request(request, length, req);
|
parse_http_request(request, length, req);
|
||||||
@@ -14,7 +8,6 @@ void handle_request(char* request, int length, http_response* response) {
|
|||||||
// Switch statement to handle different request types
|
// Switch statement to handle different request types
|
||||||
switch (req->method) {
|
switch (req->method) {
|
||||||
case GET:
|
case GET:
|
||||||
// Build response
|
|
||||||
response_handle_get(req, response);
|
response_handle_get(req, response);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
@@ -31,20 +24,6 @@ void handle_request(char* request, int length, http_response* response) {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create response
|
|
||||||
|
|
||||||
// Create response string
|
|
||||||
// char* ptr_temp = response;
|
|
||||||
// char* temp =
|
|
||||||
// "HTTP/1.1 200 OK\r\nContent-Type: text/html \r\nContent-Length: "
|
|
||||||
// "0\r\n\r\n\0";
|
|
||||||
// strcpy(ptr_temp, temp);
|
|
||||||
|
|
||||||
// printf("Response --------\n");
|
|
||||||
// printf("%s\n--------\n", response);
|
|
||||||
|
|
||||||
// *response_length = strlen(response);
|
|
||||||
|
|
||||||
free_http_request(req);
|
free_http_request(req);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -52,7 +31,7 @@ void parse_http_request(char* request, int length, struct http_request* req) {
|
|||||||
// Get the end of the first line
|
// Get the end of the first line
|
||||||
char* request_line_end = strstr(request, "\r\n");
|
char* request_line_end = strstr(request, "\r\n");
|
||||||
if (request_line_end == NULL) {
|
if (request_line_end == NULL) {
|
||||||
printf("Invalid packet (end first line)\n");
|
log_message(LOG_ERROR, "Invalid packet (request line), cannot parse");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -60,7 +39,7 @@ void parse_http_request(char* request, int length, struct http_request* req) {
|
|||||||
char* type_start = request;
|
char* type_start = request;
|
||||||
char* type_end = strstr(type_start, " ");
|
char* type_end = strstr(type_start, " ");
|
||||||
if (type_end == NULL) {
|
if (type_end == NULL) {
|
||||||
printf("Invalid packet (method)\n");
|
log_message(LOG_ERROR, "Invalid packet (method), cannot parse");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
char* type = (char*)malloc(type_end - type_start + 1);
|
char* type = (char*)malloc(type_end - type_start + 1);
|
||||||
@@ -75,7 +54,7 @@ void parse_http_request(char* request, int length, struct http_request* req) {
|
|||||||
} else if (strcmp(type, "DELETE") == 0) {
|
} else if (strcmp(type, "DELETE") == 0) {
|
||||||
req->method = DELETE;
|
req->method = DELETE;
|
||||||
} else {
|
} else {
|
||||||
printf("Invalid packet (method)\n");
|
log_message(LOG_ERROR, "Invalid packet (method), cannot parse");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
req->method_str = (char*)malloc(type_end - type_start + 1);
|
req->method_str = (char*)malloc(type_end - type_start + 1);
|
||||||
@@ -86,7 +65,7 @@ void parse_http_request(char* request, int length, struct http_request* req) {
|
|||||||
char* url_start = type_end + 1;
|
char* url_start = type_end + 1;
|
||||||
char* url_end = strstr(url_start, " ");
|
char* url_end = strstr(url_start, " ");
|
||||||
if (url_end == NULL) {
|
if (url_end == NULL) {
|
||||||
printf("Invalid packet (url)\n");
|
log_message(LOG_ERROR, "Invalid packet (url), cannot parse");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
req->url = (char*)malloc(url_end - url_start + 1);
|
req->url = (char*)malloc(url_end - url_start + 1);
|
||||||
@@ -96,12 +75,12 @@ void parse_http_request(char* request, int length, struct http_request* req) {
|
|||||||
// Extract headers
|
// Extract headers
|
||||||
char* headers_end = strstr(request_line_end + 2, "\r\n\r\n");
|
char* headers_end = strstr(request_line_end + 2, "\r\n\r\n");
|
||||||
if (headers_end == NULL) {
|
if (headers_end == NULL) {
|
||||||
printf("Invalid packet (headers)\n");
|
log_message(LOG_ERROR, "Invalid packet (headers), cannot parse");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
char* headers_start = request_line_end + 2;
|
char* headers_start = request_line_end + 2;
|
||||||
if (headers_start == NULL) {
|
if (headers_start == NULL) {
|
||||||
printf("Invalid packet (headers)\n");
|
log_message(LOG_ERROR, "Invalid packet (headers), cannot parse");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
req->num_headers = 0;
|
req->num_headers = 0;
|
||||||
@@ -113,7 +92,7 @@ void parse_http_request(char* request, int length, struct http_request* req) {
|
|||||||
}
|
}
|
||||||
char* delim = strstr(header_start, ": ");
|
char* delim = strstr(header_start, ": ");
|
||||||
if (delim == NULL) {
|
if (delim == NULL) {
|
||||||
printf("Invalid packet (headers)\n");
|
log_message(LOG_ERROR, "Invalid packet (headers), cannot parse");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -132,29 +111,4 @@ void parse_http_request(char* request, int length, struct http_request* req) {
|
|||||||
req->body[length - (body_start - request)] = '\0';
|
req->body[length - (body_start - request)] = '\0';
|
||||||
|
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
|
||||||
void debug_print_request(char* request) {
|
|
||||||
// Copy request
|
|
||||||
char* request_copy = (char*)malloc(strlen(request) + 1);
|
|
||||||
strcpy(request_copy, request);
|
|
||||||
|
|
||||||
// Iterate through request and print "\\r\\n" before each \r\n
|
|
||||||
// and "\\r\\n\\r\\n" before each \r\n\r\n
|
|
||||||
char* ptr = request_copy;
|
|
||||||
while (*ptr != '\0') {
|
|
||||||
if (*ptr == '\r' && *(ptr + 1) == '\n') {
|
|
||||||
if (*(ptr + 2) == '\r' && *(ptr + 3) == '\n') {
|
|
||||||
printf("\\r\\n\\r\\n\n");
|
|
||||||
ptr += 4;
|
|
||||||
} else {
|
|
||||||
printf("\\r\\n\n");
|
|
||||||
ptr += 2;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
printf("%c", *ptr);
|
|
||||||
ptr++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
printf("\n");
|
|
||||||
}
|
|
||||||
@@ -6,8 +6,39 @@
|
|||||||
#include <sys/types.h>
|
#include <sys/types.h>
|
||||||
|
|
||||||
#include "http_stuff.h"
|
#include "http_stuff.h"
|
||||||
|
#include "logging.h"
|
||||||
#include "response_builder.h"
|
#include "response_builder.h"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles a given HTTP request and writes the appropriate response into the
|
||||||
|
* given http_response struct.
|
||||||
|
*
|
||||||
|
* @param request The request to handle
|
||||||
|
* @param length The length of the request
|
||||||
|
* @param response The response to write to
|
||||||
|
*
|
||||||
|
* @details
|
||||||
|
* This function will parse the given request into an http_request struct using
|
||||||
|
* parse_http_request, handle the request using one of the response_handle_*
|
||||||
|
* functions, and then write the response back into the given http_response
|
||||||
|
* struct. Finally, it will free the http_request struct.
|
||||||
|
*/
|
||||||
void handle_request(char* request, int length, http_response* response);
|
void handle_request(char* request, int length, http_response* response);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parse a given HTTP request into a struct http_request.
|
||||||
|
*
|
||||||
|
* @param request The HTTP request to parse
|
||||||
|
* @param length The length of the request
|
||||||
|
* @param req The struct to populate with the parsed request
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*
|
||||||
|
* @details
|
||||||
|
* This function will parse the given HTTP request into the req struct. This
|
||||||
|
* includes extracting the request type, URL, headers, and body. It will also
|
||||||
|
* autofill the content metadata based on the headers.
|
||||||
|
*/
|
||||||
|
void parse_http_request(char* request, int length, struct http_request* req);
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
@@ -1,12 +1,13 @@
|
|||||||
#include "response_builder.h"
|
#include "response_builder.h"
|
||||||
|
|
||||||
void response_handle_get(http_request* req, http_response* res) {
|
void response_handle_get(http_request* req, http_response* res) {
|
||||||
|
log_message(LOG_INFO, "GET request %s", req->url);
|
||||||
// Extract the path from the request URL
|
// Extract the path from the request URL
|
||||||
char* basepath = "./public";
|
char* basepath = "./public";
|
||||||
char* file_path = (char*)malloc(strlen(req->url) + strlen(basepath) + 1);
|
char* file_path = (char*)malloc(strlen(req->url) + strlen(basepath) + 1);
|
||||||
strcpy(file_path, basepath);
|
strcpy(file_path, basepath);
|
||||||
strcat(file_path, req->url);
|
strcat(file_path, req->url);
|
||||||
printf("%s\n", file_path);
|
log_message(LOG_INFO, "Path: %s", file_path);
|
||||||
|
|
||||||
// Determine the file type
|
// Determine the file type
|
||||||
char* ptr = file_path + strlen(file_path) - 1;
|
char* ptr = file_path + strlen(file_path) - 1;
|
||||||
@@ -31,22 +32,19 @@ void response_handle_get(http_request* req, http_response* res) {
|
|||||||
|
|
||||||
void response_handle_post(http_request* req, http_response* res) {
|
void response_handle_post(http_request* req, http_response* res) {
|
||||||
// TODO
|
// TODO
|
||||||
printf("POST request\n");
|
log_message(LOG_INFO, "POST request %s", req->url);
|
||||||
printf("%s\n", req->url);
|
|
||||||
serve_501(res);
|
serve_501(res);
|
||||||
}
|
}
|
||||||
|
|
||||||
void response_handle_delete(http_request* req, http_response* res) {
|
void response_handle_delete(http_request* req, http_response* res) {
|
||||||
// TODO
|
// TODO
|
||||||
printf("DELETE request\n");
|
log_message(LOG_INFO, "DELETE request %s", req->url);
|
||||||
printf("%s\n", req->url);
|
|
||||||
serve_501(res);
|
serve_501(res);
|
||||||
}
|
}
|
||||||
|
|
||||||
void response_handle_put(http_request* req, http_response* res) {
|
void response_handle_put(http_request* req, http_response* res) {
|
||||||
// TODO
|
// TODO
|
||||||
printf("PUT request\n");
|
log_message(LOG_INFO, "PUT request %s", req->url);
|
||||||
printf("%s\n", req->url);
|
|
||||||
serve_501(res);
|
serve_501(res);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -55,7 +53,7 @@ void response_build_static_file(char* file_path, content_type content_type,
|
|||||||
// Open the file and verify that the file exists
|
// Open the file and verify that the file exists
|
||||||
FILE* file = fopen(file_path, "rb");
|
FILE* file = fopen(file_path, "rb");
|
||||||
if (file == NULL) {
|
if (file == NULL) {
|
||||||
printf("fopen failed to find file %s\n", file_path);
|
log_message(LOG_WARN, "404:fopen failed to find file %s\n", file_path);
|
||||||
serve_404(res);
|
serve_404(res);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -68,13 +66,13 @@ void response_build_static_file(char* file_path, content_type content_type,
|
|||||||
// Read file into buffer
|
// Read file into buffer
|
||||||
char* file_buffer = (char*)malloc(file_size);
|
char* file_buffer = (char*)malloc(file_size);
|
||||||
if (file_buffer == NULL) {
|
if (file_buffer == NULL) {
|
||||||
printf("malloc failed\n");
|
log_message(LOG_WARN, "500: malloc for file failed");
|
||||||
serve_500(res);
|
serve_500(res);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
size_t bytes_read = fread(file_buffer, file_size, 1, file);
|
size_t bytes_read = fread(file_buffer, file_size, 1, file);
|
||||||
if (bytes_read != 1) {
|
if (bytes_read != 1) {
|
||||||
printf("fread failed\n");
|
log_message(LOG_WARN, "500: fread for file failed");
|
||||||
serve_500(res);
|
serve_500(res);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -154,14 +152,7 @@ void response_build_static_file(char* file_path, content_type content_type,
|
|||||||
// Set content type string
|
// Set content type string
|
||||||
res->content_type = content_type_str;
|
res->content_type = content_type_str;
|
||||||
|
|
||||||
// Add header for close connection
|
log_message(LOG_INFO, "Serving %s\n", file_path);
|
||||||
header_kv* close_connection = (header_kv*)malloc(sizeof(header_kv));
|
|
||||||
close_connection->key = (char*)malloc(strlen("Connection") + 1);
|
|
||||||
close_connection->value = (char*)malloc(strlen("close") + 1);
|
|
||||||
strcpy(close_connection->key, "Connection");
|
|
||||||
strcpy(close_connection->value, "close");
|
|
||||||
res->headers = close_connection;
|
|
||||||
res->num_headers = 1;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void serve_404(http_response* res) {
|
void serve_404(http_response* res) {
|
||||||
|
|||||||
@@ -6,6 +6,7 @@
|
|||||||
#include <sys/types.h>
|
#include <sys/types.h>
|
||||||
|
|
||||||
#include "http_stuff.h"
|
#include "http_stuff.h"
|
||||||
|
#include "logging.h"
|
||||||
|
|
||||||
typedef enum {
|
typedef enum {
|
||||||
HTML,
|
HTML,
|
||||||
|
|||||||
Reference in New Issue
Block a user