diff --git a/image_tagger.c b/image_tagger.c new file mode 100644 index 0000000000000000000000000000000000000000..735ba45a801115c57ff3e9e8ad2ad9d2bbcedc8c --- /dev/null +++ b/image_tagger.c @@ -0,0 +1,573 @@ +/* +** http-server.c +*/ + +#include <errno.h> +#include <stdbool.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include <arpa/inet.h> +#include <fcntl.h> +#include <netdb.h> +#include <netinet/in.h> +#include <strings.h> +#include <sys/select.h> +#include <sys/sendfile.h> +#include <sys/socket.h> +#include <sys/stat.h> +#include <sys/types.h> +#include <unistd.h> + + +// constants +static char const * const HTTP_200_FORMAT = "HTTP/1.1 200 OK\r\n\ +Content-Type: text/html\r\n\ +Content-Length: %ld\r\n\r\n"; +static char const * const HTTP_400 = "HTTP/1.1 400 Bad Request\r\nContent-Length: 0\r\n\r\n"; +static int const HTTP_400_LENGTH = 47; +static char const * const HTTP_404 = "HTTP/1.1 404 Not Found\r\nContent-Length: 0\r\n\r\n"; +static int const HTTP_404_LENGTH = 45; +static int GAMEOVER_STATE = 7; +static int READY_STATE = 3; +static int ENDGAME_STATE = 6; +static int MAX_LENGTH = 20; + +// represents the types of method +typedef enum +{ + GET, + POST, + UNKNOWN +} METHOD; + +// Structure for player +typedef struct Player { + char player_name[20]; + int player_socket; + int count; + char keyword_list[20][20]; + int player_status; +}Player; + +Player* current_player(int sockfd,Player *player1, Player *player2); + +static bool handle_http_request(int sockfd,Player *player1, Player *player2) +{ + // try to read the request + char buff[2049]; + char keyword[20]; + char* starting_keyword; + char* ending_keyword; + bool end = false; + int n = read(sockfd, buff, 2049); + if (n <= 0) + { + if (n < 0) + perror("read"); + else + printf("socket %d close the connection\n", sockfd); + return false; + } + + // terminate the string + buff[n] = 0; + + char * curr = buff; + + // parse the method + METHOD method = UNKNOWN; + if (strncmp(curr, "GET ", 4) == 0) + { + curr += 4; + method = GET; + } + else if (strncmp(curr, "POST ", 5) == 0) + { + curr += 5; + method = POST; + } + else if (write(sockfd, HTTP_400, HTTP_400_LENGTH) < 0) + { + perror("write"); + return false; + } + + // sanitise the URI + while (*curr == '.' || *curr == '/') + ++curr; + // if player quit or the other player quited + if((strstr(buff,"quit=Quit"))||player1->player_status == GAMEOVER_STATE ||player2->player_status == GAMEOVER_STATE){ + // send quit page + struct stat st; + stat("7_gameover.html", &st); + n = sprintf(buff, HTTP_200_FORMAT, st.st_size); + // send the header first + if (write(sockfd, buff, n) < 0) + { + perror("write"); + return false; + } + // send the file + int filefd = open("7_gameover.html", O_RDONLY); + do + { + n = sendfile(sockfd, filefd, NULL, 2048); + } + while (n > 0); + if (n < 0) + { + perror("sendfile"); + close(filefd); + return false; + } + close(filefd); + // change player status to 7(quit state) + if(player1->player_socket==sockfd){ + player1->player_status=GAMEOVER_STATE; + }else if(player2->player_socket == sockfd){ + player2->player_status=GAMEOVER_STATE; + } + + // reset player struct (reset game) + if(player1->player_status == GAMEOVER_STATE &&player2->player_status ==GAMEOVER_STATE){ + player1->player_status = 0; + strcpy(player1->player_name,"none"); + player1->player_socket = 0; + player1->count = 0; + player2->player_status = 0; + strcpy(player2->player_name,"none"); + player2->player_socket = 0; + player2->count = 0; + } + return false; + } + // if player guessing + else if(strstr(buff,"&guess=Guess")){ + // if both player are ready + if(player1->player_status == READY_STATE && player2->player_status == READY_STATE){ + // reset keyword + memset(keyword, 0, sizeof(char)*20); + // find length of keyword and copy keyword using strncpy + starting_keyword = strstr(buff,"keyword="); + ending_keyword = strstr(starting_keyword, "&guess=Guess"); + int keyword_length = ending_keyword-starting_keyword-8; + strncpy(keyword,strstr(buff,"keyword=")+8,keyword_length); + + // find current player + if(player1->player_socket==sockfd){ + // place keyword into player keyword_list + strcpy(player1->keyword_list[player1->count],keyword); + player1->count++; + // check if keyword appeared in other player keyword_list + for(int k=0; k<=player2->count;k++){ + // if keyword appeared in other player keyword_list change both player state to victory state(6) + if(strcmp(keyword,player2->keyword_list[k])==0){ + player1->player_status = ENDGAME_STATE; + player2->player_status = ENDGAME_STATE; + } + } + } + else if(player2->player_socket == sockfd){ + // place keyword into player keyword_list + strcpy(player2->keyword_list[player2->count],keyword); + player2->count++; + // check if keyword appeared in other player keyword_list + for(int k=0; k<=player1->count;k++){ + // if keyword appeared in other player keyword_list change both player state to endgame state(6) + if(strcmp(keyword,player1->keyword_list[k])==0){ + player1->player_status = ENDGAME_STATE; + player2->player_status = ENDGAME_STATE; + } + } + } + // if either player in endgame state + if (player2->player_status == ENDGAME_STATE||player1->player_status == ENDGAME_STATE) { + //reset players keyword_list + player1->count = 0; + player2->count = 0; + memset(player1->keyword_list,0,sizeof(player1->keyword_list [0][0])* 20* 20); + memset(player2->keyword_list,0,sizeof(player2->keyword_list [0][0])* 20 * 20); + + // send endgame page + struct stat st; + stat("6_endgame.html", &st); + n = sprintf(buff, HTTP_200_FORMAT, st.st_size); + // send the header first + if (write(sockfd, buff, n) < 0) + { + perror("write"); + return false; + } + // send the file + int filefd = open("6_endgame.html", O_RDONLY); + do + { + n = sendfile(sockfd, filefd, NULL, 2048); + } + while (n > 0); + if (n < 0) + { + perror("sendfile"); + close(filefd); + return false; + } + close(filefd); + // if player keyword not match(not victory state) + }else{ + + // send accepted file + struct stat st; + stat("4_accepted.html", &st); + char all_keyword[500]; + long current_length=0; + + // copy keywords in keyword_list to all_keyword string + // calculate the length of extra characters + for(int j=0;j<current_player(sockfd,player1,player2)->count;j++){ + strcpy(all_keyword+current_length,current_player(sockfd,player1,player2)->keyword_list[j]); + current_length += strlen(current_player(sockfd,player1,player2)->keyword_list[j]); + strcpy(all_keyword+current_length,","); + current_length += 1; + strcpy(all_keyword+current_length," "); + current_length += 1; + } + + // add size + long size = st.st_size + current_length; + n = sprintf(buff, HTTP_200_FORMAT, size); + // send the header first + if (write(sockfd, buff, n) < 0) + { + perror("write"); + return false; + } + // read the content of the HTML file + int filefd = open("4_accepted.html", O_RDONLY); + n = read(filefd, buff, 2048); + if (n < 0) + { + perror("read"); + close(filefd); + return false; + } + close(filefd); + // print all_keyword + // replace behind with body and html + strncpy(strstr(buff,"</body>"), all_keyword,current_length); + strncpy(strstr(buff,all_keyword)+current_length,"</body>\n</html>",13); + + if (write(sockfd, buff, size) < 0) + { + perror("write"); + return false; + } + } + // check if player in victory state (for player after the other player win) + }else if(current_player(sockfd,player1,player2)->player_status == ENDGAME_STATE){ + // send endgame page + struct stat st; + stat("6_endgame.html", &st); + n = sprintf(buff, HTTP_200_FORMAT, st.st_size); + // send the header first + if (write(sockfd, buff, n) < 0) + { + perror("write"); + return false; + } + // send the file + int filefd = open("6_endgame.html", O_RDONLY); + do + { + n = sendfile(sockfd, filefd, NULL, 2048); + } + while (n > 0); + if (n < 0) + { + perror("sendfile"); + close(filefd); + return false; + } + close(filefd); + // if other player not ready not in ready state(3) + }else{ + // send discarded page + struct stat st; + stat("5_discarded.html", &st); + n = sprintf(buff, HTTP_200_FORMAT, st.st_size); + // send the header first + if (write(sockfd, buff, n) < 0) + { + perror("write"); + return false; + } + // send the file + int filefd = open("5_discarded.html", O_RDONLY); + do + { + n = sendfile(sockfd, filefd, NULL, 2048); + } + while (n > 0); + if (n < 0) + { + perror("sendfile"); + close(filefd); + return false; + } + } + } + // request intro page + else if (*curr == ' '){ + if (method == GET) + { + //send intro page + struct stat st; + stat("1_intro.html", &st); + n = sprintf(buff, HTTP_200_FORMAT, st.st_size); + // send the header first + if (write(sockfd, buff, n) < 0) + { + perror("write"); + return false; + } + // send the file + int filefd = open("1_intro.html", O_RDONLY); + do + { + n = sendfile(sockfd, filefd, NULL, 2048); + } + while (n > 0); + if (n < 0) + { + perror("sendfile"); + close(filefd); + return false; + } + close(filefd); + } + else if (method == POST) + { + + // locate username + char * username = strstr(buff, "user=") + 5; + char username_copy[20]; + // ensure username is empty + memset(username_copy, 0, sizeof(char)*MAX_LENGTH); + // copy username + strcpy(username_copy,username); + int username_length = strlen(username); + long added_length = username_length; + + //set player name and socket + if(!strcmp(player1->player_name, "none")){ + strcpy(player1->player_name,username); + player1->player_socket= sockfd; + player1->player_status = 0; + }else if(!strcmp(player2->player_name, "none")){ + strcpy(player2->player_name,username); + player2->player_socket = sockfd; + player2->player_status = 0; + } + // get the size of the file + struct stat st; + stat("2_start.html", &st); + // increase file size to accommodate the username + long size = st.st_size + added_length; + n = sprintf(buff, HTTP_200_FORMAT, size); + // send the header first + if (write(sockfd, buff, n) < 0) + { + perror("write"); + return false; + } + // read the content of the HTML file + int filefd = open("2_start.html", O_RDONLY); + n = read(filefd, buff, 2048); + if (n < 0) + { + perror("read"); + close(filefd); + return false; + } + close(filefd); + // move the trailing part backward + // copy the username + strncpy(strstr(buff,"</body>"), username_copy, username_length); + strncpy(strstr(buff,username_copy)+username_length,"</body>\n</html>",13); + + + if (write(sockfd, buff, size) < 0) + { + perror("write"); + return false; + } + } + // request for start (start button pressed) + }else if(strstr(buff,"?start=Start")){ + if (method == GET) + { + // change player state to read state (3) + if(player1->player_socket==sockfd){ + player1->player_status= READY_STATE; + }else if(player2->player_socket == sockfd){ + player2->player_status= READY_STATE; + } + // get the size of the file + struct stat st; + stat("3_first_turn.html", &st); + n = sprintf(buff, HTTP_200_FORMAT, st.st_size); + // send the header first + if (write(sockfd, buff, n) < 0) + { + perror("write"); + return false; + } + // send the file + int filefd = open("3_first_turn.html", O_RDONLY); + do + { + n = sendfile(sockfd, filefd, NULL, 2048); + } + while (n > 0); + if (n < 0) + { + perror("sendfile"); + close(filefd); + return false; + } + close(filefd); + }else{ + // never used, just for completeness + fprintf(stderr, "no other methods supported"); + } + // send 404 + } + else if (write(sockfd, HTTP_404, HTTP_404_LENGTH) < 0) + { + perror("write"); + return false; + } + + return true; +} + +int main(int argc, char * argv[]) +{ + if (argc < 3) + { + fprintf(stderr, "usage: %s ip port\n", argv[0]); + return 0; + } + + // create TCP socket which only accept IPv4 + int sockfd = socket(AF_INET, SOCK_STREAM, 0); + if (sockfd < 0) + { + perror("socket"); + exit(EXIT_FAILURE); + } + + // reuse the socket if possible + int const reuse = 1; + if (setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(int)) < 0) + { + perror("setsockopt"); + exit(EXIT_FAILURE); + } + + // create and initialise address we will listen on + struct sockaddr_in serv_addr; + bzero(&serv_addr, sizeof(serv_addr)); + serv_addr.sin_family = AF_INET; + // if ip parameter is not specified + serv_addr.sin_addr.s_addr = inet_addr(argv[1]); + serv_addr.sin_port = htons(atoi(argv[2])); + + //create 2 players + Player player1; + strcpy(player1.player_name,"none"); + player1.count = 0; + Player player2; + strcpy(player2.player_name,"none"); + player2.count = 0; + + // bind address to socket + if (bind(sockfd, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) < 0) + { + perror("bind"); + exit(EXIT_FAILURE); + } + + // listen on the socket + listen(sockfd, 5); + + // initialise an active file descriptors set + fd_set masterfds; + FD_ZERO(&masterfds); + FD_SET(sockfd, &masterfds); + // record the maximum socket number + int maxfd = sockfd; + + while (1) + { + // monitor file descriptors + fd_set readfds = masterfds; + if (select(FD_SETSIZE, &readfds, NULL, NULL, NULL) < 0) + { + perror("select"); + exit(EXIT_FAILURE); + } + + // loop all possible descriptor + for (int i = 0; i <= maxfd; ++i) + // determine if the current file descriptor is active + if (FD_ISSET(i, &readfds)) + { + // create new socket if there is new incoming connection request + if (i == sockfd) + { + struct sockaddr_in cliaddr; + socklen_t clilen = sizeof(cliaddr); + int newsockfd = accept(sockfd, (struct sockaddr *)&cliaddr, &clilen); + if (newsockfd < 0) + perror("accept"); + else + { + // add the socket to the set + FD_SET(newsockfd, &masterfds); + // update the maximum tracker + if (newsockfd > maxfd) + maxfd = newsockfd; + // print out the IP and the socket number + char ip[INET_ADDRSTRLEN]; + printf( + "new connection from %s on socket %d\n", + // convert to human readable string + inet_ntop(cliaddr.sin_family, &cliaddr.sin_addr, ip, INET_ADDRSTRLEN), + newsockfd + ); + } + } + // a request is sent from the client + else if (!handle_http_request(i,&player1,&player2)) + { + close(i); + FD_CLR(i, &masterfds); + } + } + } + + return 0; +} + +// find current player +Player* current_player(int sockfd,Player *player1, Player *player2){ + Player* playerbot; + if(player1->player_socket==sockfd){ + return player1; + }else if(player2->player_socket == sockfd){ + return player2; + } + return playerbot; +}