/* Copyright 2013 Akira Ohta (akohta001@gmail.com)
    This file is part of ntch.

    The ntch is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    The ntch is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with ntch.  If not, see <http://www.gnu.org/licenses/>.
    
*/
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <openssl/sha.h>
#include <fcntl.h>
#include <arpa/inet.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <assert.h>

#include "utils/nt_std_t.h"
#include "utils/text.h"
#include "utils/zip.h"
#include "utils/db.h"
#include "utils/nt_mutex.h"
#include "net/nt_socket.h"
#include "net/nt_http.h"
#include "net/nt_cookie.h"


static nt_http_header_tp nt_http_init_header(const char *url);
static void nt_http_free_header(nt_http_header_tp headerp);
static BOOL nt_http_init_header2(
		nt_http_header_tp headerp, const char *out_path);

BOOL nt_http_post(const char *url, const char *post_data,
		char *out_buf, size_t out_buf_len, 
		const char *referer, const char *user_agent, 
		nt_link_tp extend_headers, 
		nt_cookie_tp cookiep)
{
	nt_socket_tp socketp = NULL;
	int fd = -1, sockfd = 0;
	char data[1024*8];
	char ssl_connect[512];
	size_t data_len;
	int nread;
	BOOL result = FALSE;
	nt_http_response_header_tp responsep;
	nt_key_value_tp kv;
	nt_link_tp linkp, cookies;
	char itoa_buf[33];
	char cookie_data[1024];
	char *cptr;

	nt_http_header_tp headerp = nt_http_init_header(url);
	if(headerp == NULL)
		return FALSE;

	strcpy(data, "POST ");
	strcat(data, headerp->param);
	strcat(data, " HTTP/1.1\r\nHost: ");
	strcat(data, headerp->host);
	strcat(data, "\r\nAccept: */*");
	strcat(data, "\r\nAccept-Language: ja");
	strcat(data, "\r\nUser-Agent: ");
	if(user_agent)
		strcat(data, user_agent);
	else
		strcat(data, USER_AGENT);
	if(referer){
		strcat(data, "\r\nReferer: ");
		strcat(data, referer);
	}

	if(cookiep){
		cookies = nt_get_cookies(cookiep, headerp->host);
		if(cookies){
			cookie_data[0] = '\0';
			linkp = cookies;
			do{
				if(cookie_data[0] != '\0')
					strcat(cookie_data, "; ");
				strcat(cookie_data, (char*)linkp->data);
				 linkp = linkp->next;
			}while(linkp != cookies);
			if(strlen(cookie_data) > 0){
				strcat(data, "\r\nCookie: ");
				strcat(data, cookie_data);
			}
			nt_all_link_free(cookies, NULL);
		}
	}

	if(extend_headers){
		linkp = extend_headers;
		do{
			kv = (nt_key_value_tp)linkp->data;
			strcat(data, "\r\n");
			strcat(data, kv->key);
			strcat(data,": ");
			strcat(data, kv->value);
			linkp = linkp->next;
		}while(linkp != extend_headers);
	}
	strcat(data, "\r\nContent-Length: ");
	sprintf(itoa_buf, "%u", (unsigned int)strlen(post_data));
	strcat(data, itoa_buf);
	strcat(data, "\r\nConnection: close\r\n\r\n");

	strcat(data, post_data);


	if(IS_SET_FLAG(headerp,SSL_FLAG)){
		data_len = strlen(data);
		sprintf(ssl_connect, "%s:443", headerp->host);
		if(!nt_ssl_connect(ssl_connect, "cacert.pem", FALSE,
					data, &data_len, sizeof(data))){
			nt_http_free_header(headerp);
			return FALSE;
		}
		responsep = nt_http_alloc_response_header();
		if(responsep == NULL){
			nt_http_free_header(headerp);
			return FALSE;
		}
		nread = nt_http_parse_response_header2(data, data_len,
			responsep);
		if(nread <= 0){
			nt_http_free_response_header(responsep);
			nt_http_free_header(headerp);
			return FALSE;
		}
		if(out_buf_len <= data_len - nread + 1){
			nt_http_free_response_header(responsep);
			nt_http_free_header(headerp);
			return FALSE;
		}
		memcpy(out_buf, data+nread, data_len-nread);
		out_buf[data_len - nread] = '\0';
	}else{
		if(headerp->port == -1)
			headerp->port = 80;

		socketp = nt_socket_init(headerp->port, headerp->host);
		if(socketp == NULL){
			nt_http_free_header(headerp);
			return FALSE;
		}
	
		sockfd = nt_socket_connect(socketp, data, strlen(data));
		if(-1 == sockfd){
			nt_http_free_header(headerp);
			nt_socket_free(socketp);
			return FALSE;
		}

		responsep = nt_http_alloc_response_header();
		if(responsep == NULL){
			close(sockfd);
			nt_http_free_header(headerp);
			nt_socket_free(socketp);
			return FALSE;
		}

		nread = nt_http_parse_response_header(sockfd, 
					responsep, cookiep, headerp->host);
		if(nread == -1){
			close(sockfd);
			nt_http_free_header(headerp);
			nt_socket_free(socketp);
			nt_http_free_response_header(responsep);
			return FALSE;
		}
	}

	switch(responsep->status_code){
	case 200:
		if(IS_SET_FLAG(headerp,SSL_FLAG)){
		}else{
			cptr = out_buf;
			do{
				nread = read(sockfd, data, sizeof(data));
				switch(nread){
				case -1:
					break;
				case 0:
					break;
				default:
					if(out_buf_len <= (cptr - out_buf) + nread){
						goto ERROR_TRAP;
					}
					memcpy(cptr, data, nread);
					cptr[nread] = '\0';
					cptr += nread;
					break;
				}
			}while(nread > 0);
			result = (nread == 0);
		}
		result = TRUE;
		break;
	default:
		break;
	}/* switch */
ERROR_TRAP:	
	if(fd > 0)
		close(fd);
	if(sockfd)
		close(sockfd);
	if(socketp)
		nt_socket_free(socketp);
	nt_http_free_response_header(responsep);
	nt_http_free_header(headerp);
	return result;
}

BOOL nt_http_get(const char *url, const char *out_path,
		const char *referer, const char *user_agent, 
		nt_link_tp extend_headers, BOOL range, BOOL ignore_cache)
{
	nt_socket_tp socketp;
	int fd = -1, sockfd;
	char data[1024*2];
	char itoa_buf[32];
	char ssl_connect[512];
	size_t data_len;
	int nread, nwrite;
	BOOL result, retry;
	nt_http_response_header_tp responsep;
	nt_key_value_tp kv;
	nt_link_tp linkp;

	retry = FALSE;

	nt_http_header_tp headerp = nt_http_init_header(url);
	if(headerp == NULL)
		return FALSE;
	if(!ignore_cache){
		if(!nt_http_init_header2(headerp, out_path))
			return FALSE;
	}

	strcpy(data, "GET ");
	strcat(data, headerp->param);
	strcat(data," HTTP/1.1\r\nHost: ");
	strcat(data, headerp->host);
	strcat(data, "\r\nUser-Agent: ");
	if(user_agent)
		strcat(data, user_agent);
	else
		strcat(data, USER_AGENT);
	if(referer){
		strcat(data, "\r\nReferer: ");
		strcat(data, referer);
	}
	if(IS_SET_FLAG(headerp, RANGE_FLAG) && range){
		if(0 <= sprintf(itoa_buf, "%zu-", headerp->fsize)){
			strcat(data, "\r\nRange: bytes=");
			strcat(data, itoa_buf);
		}
	}
	if(headerp->last_modified){
		strcat(data, "\r\nIf-Modified-Since: ");
		strcat(data, headerp->last_modified);
	}
	if(!IS_SET_FLAG(headerp,SSL_FLAG) && 
			!IS_SET_FLAG(headerp, RANGE_FLAG))
		strcat(data, "\r\nAccept-Encoding: gzip");
	if(extend_headers){
		linkp = extend_headers;
		do{
			kv = (nt_key_value_tp)linkp->data;
			strcat(data, "\r\n");
			strcat(data, kv->key);
			strcat(data,": ");
			strcat(data, kv->value);
			linkp = linkp->next;
		}while(linkp != extend_headers);
	}
	strcat(data, "\r\nConnection: close\r\n\r\n");

	if(IS_SET_FLAG(headerp,SSL_FLAG)){
		data_len = strlen(data);
		sprintf(ssl_connect, "%s:443", headerp->host);
		if(!nt_ssl_connect(ssl_connect, "cacert.pem", FALSE,
					data, &data_len, sizeof(data))){
			nt_http_free_header(headerp);
			return FALSE;
		}
		/* Not implemented yet. */
		assert(1);
		return FALSE;
	}else{
		if(headerp->port == -1)
			headerp->port = 80;

		socketp = nt_socket_init(headerp->port, headerp->host);
		if(socketp == NULL){
			nt_http_free_header(headerp);
			return FALSE;
		}
	
		sockfd = nt_socket_connect(socketp, data, strlen(data));
		if(-1 == sockfd){
			nt_socket_free(socketp);
			nt_http_free_header(headerp);
			return FALSE;
		}

		responsep = nt_http_alloc_response_header();
		if(responsep == NULL){
			close(sockfd);
			nt_socket_free(socketp);
			nt_http_free_header(headerp);
			return FALSE;
		}

		nread = nt_http_parse_response_header(sockfd, responsep,
							NULL, NULL);
		if(nread == -1){
			close(sockfd);
			nt_socket_free(socketp);
			nt_http_free_header(headerp);
			nt_http_free_response_header(responsep);
			return FALSE;
		}
	}

	result = FALSE;
	switch(responsep->status_code){
	case 200:
		fd = open(out_path,
			O_CREAT | O_WRONLY,
			S_IRUSR | S_IWUSR | S_IROTH);
		if(fd == -1){
			result = FALSE;
			fd = -1;
			break;
		}
		if(IS_SET_FLAG(responsep, GZIP_FLAG)){
			if(IS_SET_FLAG(responsep, CHUNKED_FLAG)){	
				if(!nt_zip_inflate2(sockfd, fd))
					goto ERROR_TRAP;
			}else{
				if(!nt_zip_inflate(sockfd, fd))
					goto ERROR_TRAP;
			}
		}else{
			do{
				nread = read(sockfd, data, sizeof(data));
				switch(nread){
				case -1:
					break;
				case 0:
					break;
				default:
					nwrite = write(fd, data, nread);
					if(nwrite == -1){
						goto ERROR_TRAP;
					}
					break;
				}
			}while(nread > 0);
		}
		if(!ignore_cache)
			nt_http_save_response_header(out_path, responsep);
		result = TRUE;
		break;
	case 206: /* Partial content */
		fd = open(out_path,
			O_APPEND | O_WRONLY,
			S_IRUSR | S_IWUSR | S_IROTH);
		if(fd == -1){
			result = FALSE;
			fd = -1;
			break;
		}
		do{
			nread = read(sockfd, data, sizeof(data));
			switch(nread){
			case -1:
				break;
			case 0:
				break;
			default:
				nwrite = write(fd, data, nread);
				if(nwrite == -1){
					goto ERROR_TRAP;
				}
				break;
			}
		}while(nread > 0);
		if(!ignore_cache)
			nt_http_save_response_header(out_path, responsep);
		result = TRUE;
		break;
	case 304: /* Not Modified */
		result = TRUE;
		break;
	case 416:
		retry = TRUE;
		break;	
	default:
		break;
	}/* switch */
ERROR_TRAP:	
	if(fd > 0)
		close(fd);
	close(sockfd);
	nt_socket_free(socketp);
	nt_http_free_header(headerp);
	nt_http_free_response_header(responsep);
	if(retry && range)
		return nt_http_get(url, out_path, referer, user_agent, 
				extend_headers, FALSE, ignore_cache);

	return result;
}


static BOOL nt_http_init_header2(
		nt_http_header_tp headerp, const char *out_path)
{
    char key[DB_KEY_SIZE_MAX];
    nt_db_log_rec_tp recp;
    datum d_key, d_data;
    const char *log_path;
	DBM *dbm;
	struct stat st;
	nt_mutex_handle h_mutex;

	assert(headerp);
	assert(out_path);

	if(!nt_db_cpy_key(out_path, key)){
		return FALSE;
	}
	
	h_mutex = nt_mutex_get_one_time_handle(NT_MUTEX_ROOT_NAME_FILE);
	if(!h_mutex){
		return FALSE;
	}
	if(!nt_mutex_add_moniker(h_mutex, LOG_DB_W_NAME)){
		return FALSE;
	}
	if(!nt_mutex_lock(h_mutex)){
		return FALSE;
	}
	log_path	=	nt_db_get_log_path();
	dbm	=	dbm_open((char*)log_path, O_RDWR | O_CREAT, 0600);
	if(dbm == NULL){
		nt_mutex_unlock(h_mutex);
		return FALSE;
	}

	d_key.dsize = strlen(key);
	d_key.dptr = (void*)key;

	d_data = dbm_fetch(dbm, d_key);
	if(d_data.dptr && d_data.dsize == sizeof(nt_db_log_rec_t)){
		recp = (nt_db_log_rec_tp)d_data.dptr;
		headerp->last_modified = nt_trim(recp->last_modified);	
	}else{
		headerp->last_modified = NULL;
	}
	dbm_close(dbm);
	nt_mutex_unlock(h_mutex);

	if(0 == stat(out_path, &st)){
		headerp->fsize = st.st_size;
		SET_FLAG(headerp, RANGE_FLAG);
	}else{
		headerp->fsize = -1;
	}
	
	return TRUE;

}

static nt_http_header_tp nt_http_init_header(const char *url)
{
	nt_http_header_tp headerp;
	char *cptr, *host, *param;
	int port = -1;
	int len;
	BOOL ssl;

	if(0 == strncmp(url, "http://", 7)){
		url += 7;
		ssl = FALSE;
	}else if(0 == strncmp(url, "https://", 8)){
		url += 8;
		port = 443;
		ssl = TRUE;
	}else{
		return NULL;
	}

	cptr = strchr(url, '/');
	if(NULL == cptr){
		param = malloc(2);
		if(param == NULL){
			return NULL;
		}
		param[0] = '/';
		param[1] = '\0';
		host = malloc(strlen(url)+1);
		if(host == NULL){;
			return NULL;
		}
		strcpy(host, url);
	}else{
		param = malloc(strlen(cptr)+1);
		if(param == NULL){
			return NULL;
		}
		strcpy(param, cptr);
		len = cptr - url;
		host = malloc(len+1);
		if(host == NULL){
			return NULL;
		}
		memcpy(host, url, len);
		host[len] = '\0';
	}

	cptr = strrchr(host, ':');
	if(cptr != NULL && strlen(cptr) > 0){
		port = atoi(cptr);
		port = (port == 0) ? -1 : port;
		*cptr = '\0';
	}

	headerp = calloc(1, sizeof(nt_http_header_t));
	if(headerp == NULL){
		free(host);
		free(param);
		return NULL;
	}

	headerp->port = port;
	headerp->host = host;
	headerp->param = param;
	if(ssl){
		SET_FLAG(headerp, SSL_FLAG);
	}else{
		CLR_FLAG(headerp, SSL_FLAG);
	}
	return headerp;
}

static void nt_http_free_header(nt_http_header_tp headerp)
{
	if(headerp->host != NULL)
		free(headerp->host);
	if(headerp->param != NULL)
		free(headerp->param);
	if(headerp->last_modified != NULL)
		free(headerp->last_modified);
	free(headerp);
}
