├── Makefile ├── README ├── README.md ├── http_message.c ├── http_message.h ├── list.c ├── list.h ├── main.c ├── net.c ├── net.h ├── proxy.h └── tests └── http_message_test.c /Makefile: -------------------------------------------------------------------------------- 1 | SOURCES := $(shell find . -iname '*.c' -depth 1 ) 2 | OBJECTS := $(SOURCES:.c=.o) 3 | 4 | all: http_proxy tests 5 | 6 | http_proxy: $(OBJECTS) 7 | gcc $(OBJECTS) $(LFLAGS) -o http_proxy 8 | 9 | .PHONY: tests 10 | tests: 11 | gcc -I.. tests/http_message_test.c http_message.c -o http_message_test 12 | 13 | .PHONY: clean 14 | clean: 15 | rm $(OBJECTS) http_proxy 16 | -------------------------------------------------------------------------------- /README: -------------------------------------------------------------------------------- 1 | HTTPProxy 2 | 3 | Description: 4 | HTTPProxy is a basic proxy server for http. 5 | 6 | 7 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | User Manual - NetNinny 2 | ====================== 3 | 4 | ### Building 5 | 6 | To build the software, simply unpack the archive and issue “make” in the HTTPProxy directory. This will produce a NetNinny binary in the same catalogue. 7 | 8 | ### Configuration 9 | 10 | It is possible to configure yourself what port the proxy server should use. This can be done by giving the port number as a argument to the executable. For example: 11 | 12 | ./NetNinny 8081 13 | 14 | ### Features 15 | 16 | NetNinny has the following features: 17 | 18 | * Support for both HTTP version 1.0 and 1.1 19 | * Can handle any size of data received from the web server 20 | * Blocks URLs containing forbidden words 21 | * Blocks content containing forbidden words 22 | * Compatible with all major browsers, i.e follows the specification 23 | 24 | ### Lacks 25 | 26 | We have not implemented any other request than GET currently. It also lacks complete error checking, so currently it is not hard, probably, to get the server to crash with a bad request. 27 | 28 | ### Proof 29 | 30 | We have tested the proxy server with sites like Aftonbladet, Expressen, Wikipedia, Facebook and YouTube and it seems to work fine. 31 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /http_message.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include "proxy.h" 6 | 7 | 8 | int http_methods_len = 9; 9 | const char *http_methods[] = 10 | { 11 | "OPTIONS", 12 | "GET", 13 | "HEAD", 14 | "POST", 15 | "PUT", 16 | "DELETE", 17 | "TRACE", 18 | "CONNECT", 19 | "INVALID" 20 | }; 21 | 22 | void http_request_init(http_request **req) 23 | { 24 | *req = (http_request*)malloc(sizeof(http_request)); 25 | 26 | http_request *request = *req; 27 | request->method = 0; 28 | request->search_path = NULL; 29 | 30 | TAILQ_INIT(&request->metadata_head); 31 | } 32 | 33 | void http_request_destroy(http_request *req) 34 | { 35 | free((char*)req->search_path); 36 | 37 | struct http_metadata_item *item; 38 | TAILQ_FOREACH(item, &req->metadata_head, entries) { 39 | free((char*)item->key); 40 | free((char*)item->value); 41 | free(item); 42 | } 43 | } 44 | 45 | void http_request_print(http_request *req) 46 | { 47 | printf("[HTTP_REQUEST] \n"); 48 | 49 | switch (req->version) { 50 | case HTTP_VERSION_1_0: 51 | printf("version:\tHTTP/1.0\n"); 52 | break; 53 | case HTTP_VERSION_1_1: 54 | printf("version:\tHTTP/1.1\n"); 55 | break; 56 | case HTTP_VERSION_INVALID: 57 | printf("version:\tInvalid\n"); 58 | break; 59 | } 60 | 61 | printf("method:\t\t%s\n", 62 | http_methods[req->method]); 63 | printf("path:\t\t%s\n", 64 | req->search_path); 65 | 66 | printf("[Metadata] \n"); 67 | struct http_metadata_item *item; 68 | TAILQ_FOREACH(item, &req->metadata_head, entries) { 69 | printf("%s: %s\n", item->key, item->value); 70 | } 71 | 72 | printf("\n"); 73 | } 74 | 75 | void http_parse_method(http_request* result, const char* line) 76 | { 77 | enum parser_states { 78 | METHOD, 79 | URL, 80 | VERSION, 81 | DONE 82 | }; 83 | 84 | char* copy; 85 | char* p; 86 | copy = p = strdup(line); 87 | char* token = NULL; 88 | int s = METHOD; 89 | 90 | while ((token = strsep(&p, " \r\n")) != NULL) { 91 | switch (s) { 92 | case METHOD: { 93 | int found = 0; 94 | for (int i = 0; i < http_methods_len; i++) { 95 | if (strcmp(token, http_methods[i]) == 0) { 96 | found = 1; 97 | result->method = i; 98 | break; 99 | } 100 | } 101 | if (found == 0) { 102 | result->method = http_methods_len - 1; 103 | free(copy); 104 | return; 105 | } 106 | s++; 107 | break; 108 | } 109 | case URL: 110 | result->search_path = strdup(token); 111 | s++; 112 | break; 113 | case VERSION: 114 | { 115 | if(strcmp(token, "HTTP/1.0") == 0) { 116 | result->version = HTTP_VERSION_1_0; 117 | } else if(strcmp(token, "HTTP/1.1") == 0) { 118 | result->version = HTTP_VERSION_1_1; 119 | } else { 120 | result->version = HTTP_VERSION_INVALID; 121 | } 122 | s++; 123 | break; 124 | } 125 | case DONE: 126 | break; 127 | } 128 | } 129 | free(copy); 130 | return; 131 | } 132 | 133 | // Content-Byte: 101 134 | void http_parse_metadata(http_request *result, char *line) 135 | { 136 | char *line_copy = strdup(line); 137 | char *key = strdup(strtok(line_copy, ":")); 138 | 139 | char *value = strtok(NULL, "\r"); 140 | 141 | // remove whitespaces :) 142 | char *p = value; 143 | while(*p == ' ') p++; 144 | value = strdup(p); 145 | 146 | free(line_copy); 147 | 148 | // create the http_metadata_item object and 149 | // put the data in it 150 | http_metadata_item *item = malloc(sizeof(*item)); 151 | item->key = key; 152 | item->value = value; 153 | 154 | // add the new item to the list of metadatas 155 | TAILQ_INSERT_TAIL(&result->metadata_head, item, entries); 156 | } 157 | 158 | 159 | 160 | char *http_build_request(http_request *req) 161 | { 162 | const char *search_path = req->search_path; 163 | 164 | // construct the http request 165 | int size = strlen("GET ") + 1; 166 | //char *request_buffer = calloc(sizeof(char)*size); 167 | char *request_buffer = calloc(size, sizeof(char)); 168 | strncat(request_buffer, "GET ", 4); 169 | 170 | size += strlen(search_path) + 1; 171 | request_buffer = realloc(request_buffer, size); 172 | strncat(request_buffer, search_path, strlen(search_path)); 173 | 174 | // TODO: Check the actual HTTP version that is used, and if 175 | // 1.1 is used we should append: 176 | // Connection: close 177 | // to the header. 178 | switch(req->version) 179 | { 180 | case HTTP_VERSION_1_0: 181 | size += strlen(" HTTP/1.0\r\n\r\n"); 182 | request_buffer = realloc(request_buffer, size); 183 | strncat(request_buffer, " HTTP/1.0\r\n", strlen(" HTTP/1.0\r\n")); 184 | break; 185 | case HTTP_VERSION_1_1: 186 | size += strlen(" HTTP/1.1\r\n\r\n"); 187 | request_buffer = realloc(request_buffer, size); 188 | strncat(request_buffer, " HTTP/1.1\r\n", strlen(" HTTP/1.1\r\n")); 189 | break; 190 | default: 191 | LOG(LOG_ERROR, "Failed to retrieve the http version\n"); 192 | return NULL; 193 | } 194 | 195 | http_metadata_item *item; 196 | TAILQ_FOREACH(item, &req->metadata_head, entries) { 197 | // Remove Connection properties in header in case 198 | // there are any 199 | if(strcmp(item->key, "Connection") == 0 || 200 | strcmp(item->key, "Proxy-Connection") == 0) 201 | { 202 | continue; 203 | } 204 | 205 | size += strlen(item->key) + strlen(": ") + strlen(item->value) + strlen("\r\n"); 206 | request_buffer = realloc(request_buffer, size); 207 | strncat(request_buffer, item->key, strlen(item->key)); 208 | strncat(request_buffer, ": ", 2); 209 | strncat(request_buffer, item->value, strlen(item->value)); 210 | strncat(request_buffer, "\r\n", 2); 211 | } 212 | 213 | if(req->version == HTTP_VERSION_1_1) 214 | { 215 | size += strlen("Connection: close\r\n"); 216 | request_buffer = realloc(request_buffer, size); 217 | strncat(request_buffer, "Connection: close\r\n", strlen("Connection: close\r\n")); 218 | } 219 | 220 | 221 | size += strlen("\r\n"); 222 | request_buffer = realloc(request_buffer, size); 223 | strncat(request_buffer, "\r\n", 2); 224 | 225 | return request_buffer; 226 | } 227 | -------------------------------------------------------------------------------- /http_message.h: -------------------------------------------------------------------------------- 1 | #ifndef HTTP_MESSAGE_HH 2 | #define HTTP_MESSAGE_HH 3 | #include "proxy.h" 4 | 5 | void http_request_init(http_request**); 6 | void http_request_destroy(http_request*); 7 | void http_request_print(http_request*); 8 | void http_parse_method(http_request*, char*); 9 | void http_parse_metadata(http_request*, char*); 10 | char *http_build_request(http_request*); 11 | 12 | extern int http_methods_len; 13 | extern const char* http_methods[]; 14 | 15 | #endif 16 | -------------------------------------------------------------------------------- /list.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include "list.h" 6 | #include "proxy.h" 7 | 8 | const char *list_get_key(struct METADATA_HEAD *list, const char *key) 9 | { 10 | http_metadata_item *item; 11 | TAILQ_FOREACH(item, list, entries) { 12 | if(strcmp(item->key, key) == 0) 13 | { 14 | return item->value; 15 | } 16 | } 17 | 18 | return NULL; 19 | } 20 | 21 | void list_add_key(struct METADATA_HEAD *list, const char *key, const char *value) 22 | { 23 | http_metadata_item *item = (http_metadata_item*)malloc(sizeof(http_metadata_item)); 24 | item->key = key; 25 | item->value = value; 26 | 27 | TAILQ_INSERT_TAIL(list, item, entries); 28 | } 29 | -------------------------------------------------------------------------------- /list.h: -------------------------------------------------------------------------------- 1 | #ifndef LIST_HH 2 | #define LIST_HH 3 | 4 | #include "proxy.h" 5 | 6 | const char *list_get_key(struct METADATA_HEAD *list, const char *key); 7 | 8 | #endif 9 | -------------------------------------------------------------------------------- /main.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | 16 | #include "proxy.h" 17 | #include "net.h" 18 | #include "list.h" 19 | #include "http_message.h" 20 | 21 | char *read_line(int sockfd) 22 | { 23 | int buffer_size = 2; 24 | char *line = (char*)malloc(sizeof(char)*buffer_size+1); 25 | char c; 26 | int length = 0; 27 | int counter = 0; 28 | 29 | while(1) 30 | { 31 | length = recv(sockfd, &c, 1, 0); 32 | line[counter++] = c; 33 | 34 | if(c == '\n') 35 | { 36 | line[counter] = '\0'; 37 | return line; 38 | } 39 | 40 | // reallocate the buffer 41 | if(counter == buffer_size) 42 | { 43 | buffer_size *= 2; 44 | 45 | // TODO: should probably allocate +1 for the null terminator, 46 | // but not sure. 47 | line = (char*)realloc(line, sizeof(char)*buffer_size); 48 | } 49 | 50 | } 51 | 52 | return NULL; 53 | } 54 | 55 | int containing_forbidden_words(char str[]){ 56 | 57 | // Forbidden words 58 | char *words[] = {"SpongeBob", "Britney Spears", "Paris Hilton", "Norrkӧping", "Norrköping", "Norrk%C3%B6ping"}; 59 | int hits[] = {0, 0, 0, 0, 0, 0}; // Every forbidden word need to have a zero in this array to be able to count number of char hits. 60 | int numb_words = 6; // Number of forbidden words 61 | 62 | int str_length = strlen(str); 63 | int c, w; // Index for char in str, and index for word in words 64 | 65 | // Search for forbidden words 66 | for (c = 0; c < str_length; c++) 67 | { 68 | for (w = 0; w < numb_words; w++) 69 | { 70 | if (tolower(words[w][ hits[w] ]) == tolower(str[c])){ 71 | if(++hits[w] == strlen(words[w])) 72 | return 1; 73 | } 74 | else if (hits[w] != 0) 75 | hits[w--] = 0; 76 | } 77 | } 78 | 79 | return 0; 80 | } 81 | 82 | int send_to_client(int client_sockfd, char data[], int packages_size, ssize_t length) 83 | { 84 | // if packages_size is set to 0, then the function will try to send all data as one package. 85 | if(packages_size < 1) 86 | { 87 | if(send(client_sockfd, data, length, 0) == -1) 88 | { 89 | perror("Couldn't send data to the client."); 90 | return -1; 91 | } 92 | } 93 | else 94 | { 95 | int p; 96 | for(p = 0; p*packages_size + packages_size < length; p++){ 97 | if(send(client_sockfd, (data + p*packages_size), packages_size, 0) == -1) 98 | { 99 | perror("Couldn't send any or just some data to the client. (loop)\n"); 100 | return -1; 101 | } 102 | } 103 | 104 | if (p*packages_size < length) 105 | { 106 | if(send(client_sockfd, (data + p*packages_size), length - p*packages_size, 0) == -1) 107 | { 108 | perror("Couldn't send any or just some data to the client.\n"); 109 | return -1; 110 | } 111 | } 112 | } 113 | 114 | return 0; 115 | } 116 | 117 | int http_request_send(int sockfd, http_request *req) 118 | { 119 | LOG(LOG_TRACE, "Requesting: %s\n", req->search_path); 120 | 121 | char *request_buffer = http_build_request(req); 122 | 123 | // send the http request to the web server 124 | if(send(sockfd, request_buffer, strlen(request_buffer), 0) == -1) 125 | { 126 | free(request_buffer); 127 | perror("send"); 128 | return 1; 129 | } 130 | free(request_buffer); 131 | 132 | LOG(LOG_TRACE, "Sent HTTP header to web server\n"); 133 | 134 | return 0; 135 | } 136 | 137 | void handle_client(int client_sockfd) 138 | { 139 | char *line; 140 | int server_sockfd; 141 | http_request *req; 142 | 143 | req = http_read_header(client_sockfd); 144 | if(req == NULL) 145 | { 146 | LOG(LOG_ERROR, "Failed to parse the header\n"); 147 | return; 148 | } 149 | 150 | if (containing_forbidden_words((char*)req->search_path) || containing_forbidden_words((char*)list_get_key(&req->metadata_head, "Host"))){ 151 | char *error1 = "HTTP/1.1 200 OK\r\nServer: Net Ninny\r\nContent-Type: text/html\r\n\r\n\n\n\nNet Ninny Error Page 1 for CPSC 441 Assignment 1\n\n\n\n

\nSorry, but the Web page that you were trying to access\nis inappropriate for you, based on the URL.\nThe page has been blocked to avoid insulting your intelligence.\n

\n\n

\nNet Ninny\n

\n\n\n\n\n"; 152 | http_request_destroy(req); 153 | send_to_client(client_sockfd, error1, 0, strlen(error1)); 154 | return; 155 | } 156 | 157 | server_sockfd = http_connect(req); 158 | if(server_sockfd == -1) 159 | { 160 | LOG(LOG_ERROR, "Failed to connect to host\n"); 161 | http_request_destroy(req); 162 | return; 163 | } 164 | 165 | LOG(LOG_TRACE, "Connected to host\n"); 166 | 167 | http_request_send(server_sockfd, req); 168 | http_request_destroy(req); 169 | 170 | LOG(LOG_TRACE, "Beginning to retrieve the response header\n"); 171 | int is_bad_encoding = 0; 172 | int is_text_content = 0; 173 | int line_length; 174 | while(1) 175 | { 176 | line = read_line(server_sockfd); 177 | line_length = strlen(line); 178 | send_to_client(client_sockfd, line, 0, line_length); 179 | 180 | if(line[0] == '\r' && line[1] == '\n') 181 | { 182 | // We received the end of the HTTP header 183 | LOG(LOG_TRACE, "Received the end of the HTTP response header\n"); 184 | free(line); 185 | break; 186 | } 187 | else if(18 <= line_length) 188 | { 189 | line[18] = '\0'; // Destroys the data in the line, but is needed to check if in coming data will be text format. 190 | if (strcmp(line, "Content-Type: text") == 0) 191 | is_text_content = 1; 192 | else if (strcmp(line, "Content-Encoding: ") == 0) 193 | is_bad_encoding = 1; 194 | } 195 | 196 | free(line); 197 | } 198 | 199 | LOG(LOG_TRACE, "Beginning to retrieve content\n"); 200 | ssize_t chunk_length; 201 | char *temp = http_read_chunk(server_sockfd, &chunk_length); 202 | LOG(LOG_TRACE, "Received the content, %d bytes\n", (int)chunk_length); 203 | 204 | if (is_text_content && !is_bad_encoding && containing_forbidden_words(temp)) 205 | { 206 | LOG(LOG_TRACE, "Received data contains forbidden words!\n"); 207 | char *error2 = "\n\nNet Ninny Error Page 3 for CPSC 441 Assignment 1\n\n\n\n

\nSorry, but the Web page that you were trying to access\nis inappropriate for you, based on some of the words it contains.\nThe page has been blocked to avoid insulting your intelligence.\n

\n\n

\nNet Ninny\n

\n\n\n\n\n"; 208 | 209 | send_to_client(client_sockfd, error2, 0, strlen(error2)); 210 | } 211 | else 212 | send_to_client(client_sockfd, temp, 0, chunk_length); 213 | free(temp); 214 | close(server_sockfd); 215 | } 216 | 217 | void start_server(char *port) 218 | { 219 | printf("Starting server\n"); 220 | 221 | int sockfd, new_fd; 222 | struct addrinfo hints, *servinfo, *p; 223 | struct sockaddr_storage their_addr; 224 | socklen_t sin_size; 225 | int rv; 226 | int yes = 1; 227 | 228 | memset(&hints, 0, sizeof(hints)); 229 | hints.ai_family = AF_UNSPEC; 230 | hints.ai_socktype = SOCK_STREAM; 231 | hints.ai_flags = AI_PASSIVE; 232 | 233 | if((rv = getaddrinfo(NULL, port, &hints, &servinfo)) != 0) 234 | { 235 | fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(rv)); 236 | return; 237 | } 238 | 239 | for(p = servinfo; p != NULL; p = p->ai_next) 240 | { 241 | if((sockfd = socket(p->ai_family, p->ai_socktype, 242 | p->ai_protocol)) == -1) 243 | { 244 | perror("server: socket"); 245 | continue; 246 | } 247 | 248 | if(setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &yes, 249 | sizeof(int)) == -1) 250 | { 251 | perror("setsockopt"); 252 | exit(1); 253 | } 254 | 255 | if(bind(sockfd, p->ai_addr, p->ai_addrlen) == -1) 256 | { 257 | close(sockfd); 258 | perror("server: bind"); 259 | continue; 260 | } 261 | 262 | break; 263 | } 264 | 265 | if(p == NULL) 266 | { 267 | fprintf(stderr, "server: failed to bind\n"); 268 | return; 269 | } 270 | 271 | freeaddrinfo(servinfo); 272 | 273 | if(listen(sockfd, 10) == -1) 274 | { 275 | perror("listen"); 276 | exit(1); 277 | } 278 | 279 | printf("server: waiting for connections..\n"); 280 | while(1) 281 | { 282 | sin_size = sizeof(their_addr); 283 | new_fd = accept(sockfd, (struct sockaddr*)&their_addr, &sin_size); 284 | if(new_fd == -1) 285 | { 286 | perror("accept"); 287 | continue; 288 | } 289 | 290 | printf("Receieved connection\n"); 291 | 292 | signal(SIGCHLD, SIG_IGN); 293 | pid_t child_pid = fork(); 294 | if(!child_pid) 295 | { 296 | handle_client(new_fd); 297 | 298 | close(new_fd); 299 | exit(0); 300 | } 301 | close(new_fd); 302 | } 303 | } 304 | 305 | int main(int argc, char *argv[]) 306 | { 307 | char *port = "8080"; 308 | if (argc > 1) 309 | port = argv[1]; 310 | start_server(port); 311 | return 0; 312 | } 313 | 314 | -------------------------------------------------------------------------------- /net.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | 18 | #include "proxy.h" 19 | #include "list.h" 20 | #include "http_message.h" 21 | 22 | /* 23 | Creates a TCP connection to the given host 24 | and returns the socket. Returns -1 if something 25 | fails. 26 | */ 27 | int http_connect(http_request *req) 28 | { 29 | char *host = (char*)list_get_key(&req->metadata_head, "Host"); 30 | char *port = strstr(host, ":"); 31 | 32 | if(port == NULL) 33 | { 34 | // set port to default 35 | port = calloc(3, sizeof(char)); 36 | strncat(port, "80", 2); 37 | 38 | LOG(LOG_TRACE, "Using default port\n"); 39 | } 40 | else 41 | { 42 | // remove the port number from the host 43 | host = strtok(host, ":"); 44 | 45 | // jump over the ':' char 46 | port++; 47 | 48 | LOG(LOG_TRACE, "Using port: %s\n", port); 49 | } 50 | 51 | 52 | LOG(LOG_TRACE, "Connecting to HTTP server: %s\n", host); 53 | 54 | if(host == NULL) 55 | { 56 | LOG(LOG_ERROR, "Could not find the Host property in the metadata\n"); 57 | return -1; 58 | } 59 | 60 | struct addrinfo hints, *servinfo, *p; 61 | int sockfd, rv; 62 | 63 | memset(&hints, 0, sizeof hints); 64 | hints.ai_family = AF_UNSPEC; 65 | hints.ai_socktype = SOCK_STREAM; 66 | 67 | if((rv = getaddrinfo(host, port, &hints, &servinfo)) != 0) 68 | { 69 | LOG(LOG_ERROR, "Failed to lookup hostname\n"); 70 | return -1; 71 | } 72 | 73 | // loop through all the results and connect to the first we can 74 | for(p = servinfo; p != NULL; p = p->ai_next) { 75 | if ((sockfd = socket(p->ai_family, p->ai_socktype, 76 | p->ai_protocol)) == -1) { 77 | perror("client: socket"); 78 | continue; 79 | } 80 | 81 | if (connect(sockfd, p->ai_addr, p->ai_addrlen) == -1) { 82 | close(sockfd); 83 | perror("client: connect"); 84 | continue; 85 | } 86 | 87 | break; 88 | } 89 | 90 | if (p == NULL) { 91 | LOG(LOG_ERROR, "Failed to connect to HTTP server\n"); 92 | return -1; 93 | } 94 | 95 | return sockfd; 96 | } 97 | 98 | /* 99 | Read a HTTP header from the given socket and 100 | returns a http_request*. 101 | */ 102 | http_request *http_read_header(int sockfd) 103 | { 104 | LOG(LOG_TRACE, "Reading header\n"); 105 | http_request *req; 106 | http_request_init(&req); 107 | 108 | char *line; 109 | line = read_line(sockfd); 110 | http_parse_method(req, line); 111 | 112 | while(1) 113 | { 114 | line = read_line(sockfd); 115 | if(line[0] == '\r' && line[1] == '\n') 116 | { 117 | // We received the end of the HTTP header 118 | LOG(LOG_TRACE, "Received header\n"); 119 | 120 | break; 121 | 122 | } 123 | 124 | http_parse_metadata(req, line); 125 | 126 | free(line); 127 | } 128 | 129 | return req; 130 | } 131 | 132 | /* 133 | Read as much data as possible from the given socket 134 | and returns it as a null terminated char pointer. Data 135 | returned from this function must be freed somewhere else. 136 | */ 137 | char *http_read_chunk(int sockfd, ssize_t *length) 138 | { 139 | if(length == NULL) 140 | { 141 | LOG(LOG_ERROR, "The length pointer supplied to http_read_chunk is NULL\n"); 142 | return NULL; 143 | } 144 | 145 | if(sockfd == -1) 146 | { 147 | LOG(LOG_ERROR, "The socket given to http_read_chunk is invalid\n"); 148 | return NULL; 149 | } 150 | 151 | char *buf = malloc(sizeof(char)); 152 | memset(buf, '\0', sizeof(char)); 153 | char c; 154 | int current_size = 1; 155 | 156 | time_t timeout = 5; 157 | time_t start = time(NULL); 158 | 159 | ssize_t total_bytes = 0; 160 | ssize_t num_bytes = 0; 161 | 162 | while(1) 163 | { 164 | // check if we should timeout 165 | if(time(NULL) - start > timeout) 166 | { 167 | LOG(LOG_WARNING, "Request timed out\n"); 168 | break; 169 | } 170 | 171 | num_bytes = recv(sockfd, &c, 1, 0); 172 | 173 | if(num_bytes <= -1) 174 | { 175 | break; 176 | } 177 | else if(num_bytes == 0) 178 | { 179 | break; 180 | } 181 | 182 | // reallocate the buffer so the new data will fit 183 | buf = realloc(buf, sizeof(char)*++current_size); 184 | buf[total_bytes] = c; 185 | 186 | total_bytes += num_bytes; 187 | } 188 | 189 | LOG(LOG_TRACE, "Received: %d\n", (int)total_bytes); 190 | 191 | *length = total_bytes; 192 | 193 | return buf; 194 | } 195 | -------------------------------------------------------------------------------- /net.h: -------------------------------------------------------------------------------- 1 | #ifndef NET_HH 2 | #define NET_HH 3 | 4 | int http_connect(http_request *req); 5 | http_request *http_read_header(int sockfd); 6 | char *http_read_chunk(int sockfd, ssize_t *length); 7 | 8 | #endif 9 | -------------------------------------------------------------------------------- /proxy.h: -------------------------------------------------------------------------------- 1 | #ifndef PROXY_HH 2 | #define PROXY_HH 3 | 4 | #include 5 | 6 | #define LOG_ERROR 0 7 | #define LOG_WARNING 1 8 | #define LOG_NOTICE 2 9 | #define LOG_TRACE 3 10 | 11 | #define ACTIVE_LEVEL 3 12 | #define LOG(LEVEL, MSG, ...) \ 13 | if(LEVEL <= ACTIVE_LEVEL) { \ 14 | printf("LOG(%d): ", LEVEL); \ 15 | printf(MSG, ##__VA_ARGS__); \ 16 | } \ 17 | 18 | extern int http_methods_len; 19 | extern const char *http_methods[]; 20 | 21 | char *read_line(int sockfd); 22 | 23 | enum http_methods_enum { 24 | OPTIONS, 25 | GET, 26 | HEAD, 27 | POST, 28 | PUT, 29 | DELETE, 30 | TRACE, 31 | CONNECT, 32 | UNKNOWN 33 | }; 34 | 35 | enum http_versions_enum { 36 | HTTP_VERSION_1_0, 37 | HTTP_VERSION_1_1, 38 | HTTP_VERSION_INVALID 39 | }; 40 | 41 | typedef struct http_request 42 | { 43 | enum http_methods_enum method; 44 | enum http_versions_enum version; 45 | const char *search_path; 46 | 47 | TAILQ_HEAD(METADATA_HEAD, http_metadata_item) metadata_head; 48 | } http_request; 49 | 50 | typedef struct http_metadata_item 51 | { 52 | const char *key; 53 | const char *value; 54 | 55 | TAILQ_ENTRY(http_metadata_item) entries; 56 | } http_metadata_item; 57 | 58 | 59 | #endif 60 | -------------------------------------------------------------------------------- /tests/http_message_test.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include "../http_message.h" 5 | 6 | int test_parse_valid_method() 7 | { 8 | printf("test_parse_valid_method - running\n"); 9 | http_request* req; 10 | http_request_init(&req); 11 | char* data = "GET http://www.redd.it/hej/ HTTP/1.1\r\n"; 12 | http_parse_method(req, data); 13 | assert(strcmp(http_methods[req->method], "GET") == 0); 14 | assert(req->version == HTTP_VERSION_1_1); 15 | assert(strcmp(req->search_path, "http://www.redd.it/hej/") == 0); 16 | http_request_destroy(req); 17 | printf("test_parse_valid_method - ok\n"); 18 | return 0; 19 | } 20 | 21 | int test_parse_invalid_method() 22 | { 23 | printf("test_parse_invalid_method - running\n"); 24 | http_request* req; 25 | http_request_init(&req); 26 | char* data = "FAKE http://www.redd.it/hej/ HTTP/1.1\r\n"; 27 | http_parse_method(req, data); 28 | assert(strcmp(http_methods[req->method], "INVALID") == 0); 29 | printf("test_parse_invalid_method - ok\n"); 30 | return 0; 31 | } 32 | 33 | int test_parse_invalid_http_version() 34 | { 35 | printf("test_parse_invalid_http_version - running\n"); 36 | http_request* req; 37 | http_request_init(&req); 38 | char* data = "GET http://www.redd.it/hej/ HTTP/2.0\r\n"; 39 | http_parse_method(req, data); 40 | assert(req->version == HTTP_VERSION_INVALID); 41 | printf("test_parse_invalid_http_version - ok\n"); 42 | return 0; 43 | } 44 | 45 | int main() 46 | { 47 | test_parse_valid_method(); 48 | test_parse_invalid_method(); 49 | test_parse_invalid_http_version(); 50 | } 51 | --------------------------------------------------------------------------------