diff --git a/a.out b/a.out new file mode 100755 index 0000000..21a2ce9 Binary files /dev/null and b/a.out differ diff --git a/server.cc b/server.cc index c72e655..418fc63 100644 --- a/server.cc +++ b/server.cc @@ -36,12 +36,28 @@ static void sigint(int signo) { exit(0); } +enum orientation { + LEFT = 0, FLIP = 1, RIGHT = 2 +}; + +enum orientation alteration; + +bool parse_alteration(const char*, enum orientation*); + int main(int argc, char** argv) { + srand(time(NULL)); + signal(SIGPIPE, broken_pipe); Magick::InitializeMagick(*argv); if(argc < 3) { - fprintf(stderr, "Give port and image_rotation\n"); + cerr << "Give port as first arg and image_rotation as second arg" << endl; + exit(255); + } + + if (!parse_alteration(argv[2], &alteration)) { + cerr << "image_rotation can only be 'flip', 'clockwise', 'counterclockwise'" + "or 'random'" << endl; exit(255); } @@ -100,6 +116,32 @@ int main(int argc, char** argv) { } } +/* + * Parses argv[2] in order to set alteration using the constraints given in the + * assignment. 'random' sets a random alteration fixed for the entire execution + * of the program. Returns true when argv[2] is valid, false if not + */ +bool parse_alteration(const char* arg, enum orientation* p) { + if (!strcmp(arg, "counterclockwise")) { + *p = LEFT; + } else if (!strcmp(arg, "flip")) { + *p = FLIP; + } else if (!strcmp(arg, "clockwise")) { + *p = RIGHT; + } else if (!strcmp(arg, "random")) { + *p = (enum orientation) (rand() % 3); + } else { + return false; + } + + return true; +} + +/** + * Returns true if from the given file the first two chars that are read from + * the current seeking point are '\r' and '\n'. Removes those two chars from the + * stream if present, otherwise no character will apprar read + */ bool empty_line(FILE* in) { char a = fgetc(in), b = fgetc(in); if (a == '\r' && b == '\n') { @@ -111,6 +153,12 @@ bool empty_line(FILE* in) { } } +/** + * Reads from the given file HTTP header formats, and writes them to the given + * std::map. Halts when the header section is terminated (first occurrence of + * '\r\n\r\n'). Returns false at the first malformed headers found, true if no + * are found + */ bool parse_headers(FILE* in, map& headers) { while(true) { // probe first two chars to see if body will start @@ -144,18 +192,24 @@ bool parse_headers(FILE* in, map& headers) { fgetc(in); // \n headers[header_name] = value; - cout << header_name << ": " << value << endl; } return true; } -bool has_body(char* method) { +/** + * Returns true if it is customary for the given HTTP method to have a body + */ +bool has_body(const char* method) { return !strcmp(method, "POST") || !strcmp(method, "PUT"); } -bool is_chunk_start(char* buf) { - char* i; +/** + * Returns true if the given string contains a valid HTTP 1.1 chunk start + * delimiter, false otherwise. + */ +bool is_chunk_start(const char* buf) { + const char* i; for (i = buf; isdigit(*i); i++); if (*i != '\r') return false; i++; @@ -164,65 +218,84 @@ bool is_chunk_start(char* buf) { return *i == '\0'; } +/** + * Returns true if the given buffer contains a valid HTTP 1.1 chunk end + * delimiter at its very end, false otherwise. + */ +bool is_chunk_end(vector& body) { + const char b = body.back(); + body.pop_back(); + + const char a = body.back(); + body.push_back(b); + + return a == '\r' && b == '\n'; +} + +/** + * Fetch and parse HTTP body from given file, assuming the header has already + * been parsed. Parses bodies either delimited by Content-Length or endoded + * using HTTP 1.1 chunds. If the body appears to not meet this criteria (by + * inspecting the HTTP headers), false is returned, otherwise returns true. + */ bool fetch_body(FILE* in, vector& body, const map headers) { bool chunked; - size_t length, r = 0; + size_t length; - auto p = headers.find("Content-Length"); - chunked = p == headers.end(); + { + auto p = headers.find("Content-Length"); + chunked = p == headers.end(); - if (!chunked) { - length = atol(p->second.c_str()); - } else { - auto q = headers.find("Transfer-Encoding"); - if (q == headers.end() || q->second != "chunked") { - return false; + if (!chunked) { + length = atol(p->second.c_str()); + } else { + auto q = headers.find("Transfer-Encoding"); + if (q == headers.end() || q->second != "chunked") { + return false; + } } } if (!chunked) { // read Content-Length bytes - uint8_t buf[8192]; - const size_t n = atol(p->second.c_str()); - for(size_t w = 0; w < n;) { - ssize_t r = fread(buf, 1, (n - w) > 8192 ? 8192 : (n - w), in); + uint8_t buf[BUFFER]; + size_t r; + + for(size_t w = 0; w < length; w += r) { + r = fread(buf, 1, (length - w) > BUFFER ? BUFFER : (length - w), in); if (r == -1) return false; body.insert(end(body), begin(buf), end(buf)); - w += r; } } else { // This was implemented before Prof. Carzaniga said chunked encoding is not // required. I am leaving this just because it is already done. // Read chunks and search for final chunk 0\r\n\r\n - char buf[8193]; - bool chunk_start = true; + char buf[BUFFER + 1]; + buf[BUFFER] = '\0'; + bool will_chunk_start = true; while (true) { - fgets(buf, 8193, in); + size_t len; + for (len = 0; len < BUFFER; len++) { + if ((buf[len] = fgetc(in)) == '\n') { + buf[len + 1] = '\0'; + break; + } + } - if (!strcmp(buf, "0\r\n") && chunk_start) { + if (!strcmp(buf, "0\r\n") && will_chunk_start) { break; } - if (chunk_start && is_chunk_start(buf)) { + if (will_chunk_start && is_chunk_start(buf)) { continue; } body.insert(end(body), begin(buf), end(buf)); body.pop_back(); - char b = body.back(); - body.pop_back(); - char a = body.back(); - body.pop_back(); - if (a != '\r' || b != '\n') { - body.push_back(a); - body.push_back(b); - } - - size_t len = strlen(buf); - chunk_start = len > 2 && buf[len-2] == '\r' && buf[len-1] == '\n'; + will_chunk_start = is_chunk_end(body); } body.insert(end(body), begin(buf), end(buf)); @@ -233,6 +306,10 @@ bool fetch_body(FILE* in, vector& body, return true; } +/** + * Sends an HTTP response with status code and message given. Then proceeds to + * close the given input and output files and terminates the current thread. + */ void send_error(FILE* in, FILE* out, const char* protocol, const int status, const string message) { const char* msg = message.c_str(); @@ -265,6 +342,11 @@ void send_error(FILE* in, FILE* out, const char* protocol, const int status, pthread_exit(NULL); } +/** + * Parses the HTTP method name given as string and sets cURL options + * accordingly. Returns false if method is not supported on unknown, true + * otherwise + */ bool set_curl_method(CURL* c, const char* method) { if (!strcmp(method, "GET")) { curl_easy_setopt(c, CURLOPT_HTTPGET, 1); @@ -278,7 +360,6 @@ bool set_curl_method(CURL* c, const char* method) { } else if (!strcmp(method, "DELETE")) { curl_easy_setopt(c, CURLOPT_CUSTOMREQUEST, "DELETE"); } else { - //TODO: implement CONNECT method return false; } @@ -319,13 +400,13 @@ size_t write_buffer(void *ptr, size_t size, size_t nmemb, memcpy((rbody->data + index), ptr, n); rbody->data[rbody->size] = '\0'; - cout << "copy " << n << " size " << rbody->size << endl; - return size * nmemb; } -// Converts url in a zero-terminated string for the host name. Port is retured -// as integer, -1 on error +/** + * Converts url in a zero-terminated string for the host name. Port is retured + * as integer, -1 on error + */ int find_host_port(char* url) { char* i = url; size_t c = 0; @@ -388,10 +469,12 @@ void* forwarder_thread(void* data) { return NULL; } -void handle_connect(FILE* in, FILE* out, const char* protocol, char* url, - const int fd) { +/** + * Returns a socket file descriptof of a newly opened socket to the given host. + */ +int open_client_socket(FILE* in, FILE* out, const char* protocol, char* host) { int port; - if ((port = find_host_port(url)) == -1) { + if ((port = find_host_port(host)) == -1) { send_error(in, out, protocol, 500, "Hostname parse error"); } @@ -400,7 +483,7 @@ void handle_connect(FILE* in, FILE* out, const char* protocol, char* url, send_error(in, out, protocol, 500, "TCP socket connection error"); } - struct hostent *he = gethostbyname(url); + struct hostent *he = gethostbyname(host); if (!he) { send_error(in, out, protocol, 404, "Unknown host"); } @@ -409,7 +492,7 @@ void handle_connect(FILE* in, FILE* out, const char* protocol, char* url, memset (&locale, 0, sizeof(struct sockaddr_in)); locale.sin_family = AF_INET; - locale.sin_addr.s_addr = htonl(INADDR_ANY); + locale.sin_addr.s_addr = htonl(INADDR_ANY); locale.sin_port = htons(0); if (::bind(socketfd, (struct sockaddr*) &locale, sizeof(locale)) == -1) { @@ -427,10 +510,23 @@ void handle_connect(FILE* in, FILE* out, const char* protocol, char* url, send_error(in, out, protocol, 500, "TCP socket connection error"); } + return socketfd; +} + +/** + * Handle HTTP connect method. Opens a client socket to the host in *url (by + * doing a dns query first). Then forks in another threads and starts relaying + * data both from the HTTP client socket to the newly opened socket and + * videversa. + */ +void handle_connect(FILE* in, FILE* out, const char* protocol, char* url, + const int fd) { + + int socketfd = open_client_socket(in, out, protocol, url); + struct forward from = { .in = fd, .out = socketfd }, to = { .in = socketfd, .out = fd }; - fprintf(out, "%s %d %s\r\n\r\n", protocol, 200, "Connection established"); fflush(out); @@ -481,7 +577,9 @@ void* request_thread(void* data) { map headers; vector body; - if (fscanf(in, "%10s %8000s %10s\r\n", method, url, protocol) < 3) { + int i; + if ((i = fscanf(in, "%10s %8000s %10s\r\n", method, url, protocol)) < 3) { + if (i < 0) break; send_error(in, out, protocol, 400, "Bad request line"); } @@ -516,7 +614,30 @@ void* request_thread(void* data) { handle_connect(in, out, protocol, url, fd); break; } else { - struct buffer rhead; + + // Delete Proxy-Connection header + { + auto i = headers.find("Proxy-Connection"); + if (i != headers.end()) headers.erase(i); + } + + if (strncmp(url, "http://", 7) { + send_error(in. out, protocol, 400, "Protocol in URL not supported"); + } + + char* host = url + 7; + + int serverfd = open_client_socket(in, out, protocol, host); + FILE* in = fdopen(dup(serverfd), "r"); + FILE* out = fdopen(dup(serverfd), "w"); + + fprintf("%s %s %s\r\n", method, url, protocol); + for (auto i = headers.begin(); i != headers.end(); i++) { + fprintf("%s: %s\r\n", i->first.c_str(), i->second.c_str()); + } + + + /*struct buffer rhead; struct buffer rbody; buffer_init(&rhead); buffer_init(&rbody); @@ -538,14 +659,6 @@ void* request_thread(void* data) { curl_easy_setopt(curl, CURLOPT_URL, url); - // Delete Proxy-Connection header - { - auto i = headers.find("Proxy-Connection"); - if (i != headers.end()) { - headers.erase(i); - } - } - struct curl_slist *chunk = NULL; for (auto i = headers.begin(); i != headers.end(); i++) { string header = i->first + ": " + i->second; @@ -571,7 +684,7 @@ void* request_thread(void* data) { fprintf(stderr, "curl_easy_perform() failed: %s\n", curl_easy_strerror(res)); send_error(in, out, protocol, 502, "Request to server failed"); - } else { + } else*/ { char protocol_r[10]; char message[8000]; unsigned code; @@ -606,35 +719,44 @@ void* request_thread(void* data) { Magick::Blob my_blob(rbody.data, rbody.size); Magick::Blob output; - Magick::Image to_rotate(my_blob); - to_rotate.magick(image); - to_rotate.flip(); - to_rotate.write(&output); + Magick::Image to_rotate; - cout << "IN" << endl << my_blob.length() << endl; - cout << "OUT" << endl << output.length() << endl; + try { + to_rotate = Magick::Image(my_blob); + } catch(Magick::Warning& ignored) {} + + try { + to_rotate.magick(image); + } catch(Magick::Warning& ignored) {} + + try { + switch (alteration) { + case FLIP: to_rotate.flip(); break; + case LEFT: to_rotate.rotate(-90); break; + case RIGHT: to_rotate.rotate(90); break; + } + } catch(Magick::Warning& ignored) {} + + try { + to_rotate.write(&output); + } catch(Magick::Warning& ignored) {} fprintf(out, "%s %u %s\n", protocol_r, code, message); - cout << protocol_r << ' ' << code << ' ' << message << endl; headers["Content-Length"] = to_string(output.length()); + headers["Connection"] = "keep-alive"; for (auto i = headers.begin(); i != headers.end(); i++) { string header = i->first + ": " + i->second + "\r\n"; - fprintf(out, "%s\n", header.c_str()); - cout << header << endl; + fprintf(out, "%s", header.c_str()); } fprintf(out, "\r\n"); fflush(out); total_write((uint8_t*) output.data(), output.length(), fd); } catch (Magick::Exception &error) { + cout << "Magick++ image conversion failed: " << error.what() + << endl; send_error(in, out, protocol, 500, "Image conversion failed"); - fprintf(stderr, "I exit %s\n", error.what()); - break; - } catch (std::exception &error) { - send_error(in, out, protocol, 500, "Image conversion failed"); - fprintf(stderr, "I exit %s\n", error.what()); - break; } } else { total_write(rhead.data, rhead.size, fd); @@ -666,3 +788,5 @@ void* request_thread(void* data) { pthread_exit(NULL); } + +