#include <arpa/inet.h>

#include <dirent.h>

#include <errno.h>

#include <fcntl.h>

#include <netdb.h>

#include <netinet/in.h>

#include <pthread.h>

#include <signal.h>

#include <stdio.h>

#include <stdlib.h>

#include <string.h>

#include <sys/socket.h>

#include <sys/stat.h>

#include <sys/types.h>

#include <unistd.h>

#include <unistd.h>


#include "libhttp.h"

#include "wq.h"


/*

 * Global configuration variables.

 * You need to use these in your implementation of handle_files_request and

 * handle_proxy_request. Their values are set up in main() using the

 * command line arguments (already implemented for you).

 */

wq_t work_queue; // Only used by poolserver

int num_threads; // Only used by poolserver

int server_port; // Default value: 8000

char* server_files_directory;

char* server_proxy_hostname;

int server_proxy_port;


/*

 * Serves the contents the file stored at `path` to the client socket `fd`.

 * It is the caller's reponsibility to ensure that the file stored at `path` exists.

 */


//将文件内容写入客户端套接字

int send_data(int fd,char* data, size_t size)

{

	ssize_t bytes_size;

	while(size>0)

	{

		bytes_size=write(fd,data,size);

		if(bytes_size>=0)

		{

			size-=bytes_size;

			data+=bytes_size;

		}

		else

			return -1;

	}

	return 0;

}

int send_file(int fd, int file)

{

	const int buf_size = 4096;

	char buf[buf_size];

	ssize_t bytes_read=read(file, buf, buf_size);

	int status=0;

	while (bytes_read>0)

       	{

		status=send_data(fd, buf, bytes_read);

		if (status<0) 

			return status;

		bytes_read=read(file, buf, buf_size);

	}

	return status;

}

int send_string(int fd, char* data)

{

	return send_data(fd,data,strlen(data));

}

char* join_string(char *str1, char *str2, size_t *size)

{

	size_t len1 = strlen(str1);

	size_t len2 = strlen(str2);

	char *buffer = (char *) malloc(len1 + len2 + 1);

	memcpy(buffer,str1,len1);

	memcpy(buffer+len1,str2,len2+1);

	if (size != NULL) 

		*size=(len1+len2)*sizeof(char);

	return buffer;

}

int get_file_size(int fd)

{

	struct stat st;

	if (fstat(fd,&st)==-1)

       	{

		return -1;

	}

	return st.st_size;

}

void serve_file(int fd, char* path) {


  /* TODO: PART 2 */

  /* PART 2 BEGIN */


  int file=open(path,O_RDONLY);

  char* content_length=(char*) malloc(sizeof(char)*100);

  snprintf(content_length, 100,"%d",get_file_size(file));

  http_start_response(fd, 200);

  http_send_header(fd, "Content-Type", http_get_mime_type(path));

  http_send_header(fd, "Content-Length", content_length); // TODO: change this line too

  http_end_headers(fd);

  send_file(fd,file);

  close(file);

  /* PART 2 END */


  /* PART 2 END */

}


void serve_directory(int fd, char* path) {

  http_start_response(fd, 200);

  http_send_header(fd, "Content-Type", http_get_mime_type(".html"));

  http_end_headers(fd);


  /* TODO: PART 3 */

  /* PART 3 BEGIN */


  // TODO: Open the directory (Hint: opendir() may be useful here)

  DIR *directory=opendir(path);

  struct dirent* entry;

  while ((entry = readdir(directory)) != NULL)

  {

 	if(strcmp(entry->d_name,"index.html")==0)

	{

		int length=strlen(path)+strlen("/index.html")+1;

		char* path1=(char*) malloc(sizeof(char)*length);

		http_format_index(path1,path);

		int file=open(path1,O_RDONLY);

		send_file(fd,file);

		close(file);

		closedir(directory);

		return;

	}

	else

	{

		char * file_name_buffer;

		if(strcmp(entry->d_name,"..")!=0)

		{

			int length = strlen("<a href=\"//\"></a><br/>") + strlen(path) + strlen(entry->d_name)*2 + 1;

     	 		file_name_buffer = (char* ) malloc(length * sizeof(char));

      			http_format_href(file_name_buffer, path, entry->d_name);

		}

		else

		{

			int length = strlen("<a href=\"//\"></a><br/>") + strlen("..") + strlen("")*2 + 1;

     			file_name_buffer = (char* ) malloc(length * sizeof(char));

      			http_format_href(file_name_buffer, "..", entry->d_name);

		}

		char* buffer1=(char*) malloc(1);

		buffer1[0]='\0';

		buffer1=join_string(buffer1,file_name_buffer,NULL);

		size_t content_length;

		buffer1=join_string(buffer1,"</center></body></html>",&content_length);

		free(file_name_buffer);

		send_string(fd,buffer1);

		closedir(directory);

		return;

	}

  }

  /**

   * TODO: For each entry in the directory (Hint: look at the usage of readdir() ),

   * send a string containing a properly formatted HTML. (Hint: the http_format_href()

   * function in libhttp.c may be useful here)

   */ 


  /* PART 3 END */

}


/*

 * Reads an HTTP request from client socket (fd), and writes an HTTP response

 * containing:

 *

 *   1) If user requested an existing file, respond with the file

 *   2) If user requested a directory and index.html exists in the directory,

 *      send the index.html file.

 *   3) If user requested a directory and index.html doesn't exist, send a list

 *      of files in the directory with links to each.

 *   4) Send a 404 Not Found response.

 *

 *   Closes the client socket (fd) when finished.

 */

void handle_files_request(int fd) {


  struct http_request* request = http_request_parse(fd);


  if (request == NULL || request->path[0] != '/') {

    http_start_response(fd, 400);

    http_send_header(fd, "Content-Type", "text/html");

    http_end_headers(fd);

    close(fd);

    return;

  }


  if (strstr(request->path, "..") != NULL) {

    http_start_response(fd, 403);

    http_send_header(fd, "Content-Type", "text/html");

    http_end_headers(fd);

    close(fd);

    return;

  }


  /* Remove beginning `./` */

  char* path = malloc(2 + strlen(request->path) + 1);

  path[0] = '.';

  path[1] = '/';

  memcpy(path + 2, request->path, strlen(request->path) + 1);


  /*

   * TODO: PART 2 is to serve files. If the file given by `path` exists,

   * call serve_file() on it. Else, serve a 404 Not Found error below.

   * The `stat()` syscall will be useful here.

   *

   * TODO: PART 3 is to serve both files and directories. You will need to

   * determine when to call serve_file() or serve_directory() depending

   * on `path`. Make your edits below here in this function.

   */


  /* PART 2 & 3 BEGIN */

  struct stat info;

  int result=stat(path,&info);

  if(result!=0)

  {

	http_start_response(fd, 404);

    	http_send_header(fd, "Content-Type", "text/html");

    	http_end_headers(fd);

  	

  }

  else

  {

  	if(S_ISDIR(info.st_mode))

	{

		serve_directory(fd,path);

	}

	if(S_ISREG(info.st_mode))

	{

		serve_file(fd,path);

	}

  close(fd);

  return;

  }

  /* PART 2 & 3 END */


  close(fd);

  return;

}


/*

 * Opens a connection to the proxy target (hostname=server_proxy_hostname and

 * port=server_proxy_port) and relays traffic to/from the stream fd and the

 * proxy target_fd. HTTP requests from the client (fd) should be sent to the

 * proxy target (target_fd), and HTTP responses from the proxy target (target_fd)

 * should be sent to the client (fd).

 *

 *   +--------+     +------------+     +--------------+

 *   | client | <-> | httpserver | <-> | proxy target |

 *   +--------+     +------------+     +--------------+

 *

 *   Closes client socket (fd) and proxy target fd (target_fd) when finished.

 */

void handle_proxy_request(int fd) {


  /*

  * The code below does a DNS lookup of server_proxy_hostname and

  * opens a connection to it. Please do not modify.

  */

  struct sockaddr_in target_address;

  memset(&target_address, 0, sizeof(target_address));

  target_address.sin_family = AF_INET;

  target_address.sin_port = htons(server_proxy_port);


  // Use DNS to resolve the proxy target's IP address

  struct hostent* target_dns_entry = gethostbyname2(server_proxy_hostname, AF_INET);


  // Create an IPv4 TCP socket to communicate with the proxy target.

  int target_fd = socket(PF_INET, SOCK_STREAM, 0);

  if (target_fd == -1) {

    fprintf(stderr, "Failed to create a new socket: error %d: %s\n", errno, strerror(errno));

    close(fd);

    exit(errno);

  }


  if (target_dns_entry == NULL) {

    fprintf(stderr, "Cannot find host: %s\n", server_proxy_hostname);

    close(target_fd);

    close(fd);

    exit(ENXIO);

  }


  char* dns_address = target_dns_entry->h_addr_list[0];


  // Connect to the proxy target.

  memcpy(&target_address.sin_addr, dns_address, sizeof(target_address.sin_addr));

  int connection_status =

      connect(target_fd, (struct sockaddr*)&target_address, sizeof(target_address));


  if (connection_status < 0) {

    /* Dummy request parsing, just to be compliant. */

    http_request_parse(fd);


    http_start_response(fd, 502);

    http_send_header(fd, "Content-Type", "text/html");

    http_end_headers(fd);

    close(target_fd);

    close(fd);

    return;

  }


  /* TODO: PART 4 */

  /* PART 4 BEGIN */


  /* PART 4 END */

}


#ifdef POOLSERVER

/*

 * All worker threads will run this function until the server shutsdown.

 * Each thread should block until a new request has been received.

 * When the server accepts a new connection, a thread should be dispatched

 * to send a response to the client.

 */

void* handle_clients(void* void_request_handler) {

  void (*request_handler)(int) = (void (*)(int))void_request_handler;

  /* (Valgrind) Detach so thread frees its memory on completion, since we won't

   * be joining on it. */

  pthread_detach(pthread_self());


  /* TODO: PART 7 */

  /* PART 7 BEGIN */


  /* PART 7 END */

}


/*

 * Creates `num_threads` amount of threads. Initializes the work queue.

 */

void init_thread_pool(int num_threads, void (*request_handler)(int)) {


  /* TODO: PART 7 */

  /* PART 7 BEGIN */


  /* PART 7 END */

}

#endif


/*

 * Opens a TCP stream socket on all interfaces with port number PORTNO. Saves

 * the fd number of the server socket in *socket_number. For each accepted

 * connection, calls request_handler with the accepted fd number.

 */

void serve_forever(int* socket_number, void (*request_handler)(int)) {


  struct sockaddr_in server_address, client_address;

  size_t client_address_length = sizeof(client_address);

  int client_socket_number;


  // Creates a socket for IPv4 and TCP.

  *socket_number = socket(PF_INET, SOCK_STREAM, 0);

  if (*socket_number == -1) {

    perror("Failed to create a new socket");

    exit(errno);

  }


  int socket_option = 1;

  if (setsockopt(*socket_number, SOL_SOCKET, SO_REUSEADDR, &socket_option, sizeof(socket_option)) ==

      -1) {

    perror("Failed to set socket options");

    exit(errno);

  }


  // Setup arguments for bind()

  memset(&server_address, 0, sizeof(server_address));

  server_address.sin_family = AF_INET;

  server_address.sin_addr.s_addr = INADDR_ANY;

  server_address.sin_port = htons(server_port);


  /*

   * TODO: PART 1

   *

   * Given the socket created above, call bind() to give it

   * an address and a port. Then, call listen() with the socket.

   * An appropriate size of the backlog is 1024, though you may

   * play around with this value during performance testing.

   */


  /* PART 1 BEGIN */


  if(bind(*socket_number,(struct sockaddr*)&server_address,sizeof(server_address))==-1)

  {

  	perror("Failed to bind the socket");

	exit(errno);

  }

  if(listen(*socket_number,1024)==-1)

  {

  	perror("Failed to listen to the socket");

	exit(errno);

  }

  /* PART 1 END */

  printf("Listening on port %d...\n", server_port);
修改代码，使得curl http://127.0.0.1/和curl http://127.0.0.1/index.html的结果相同