#include <stdio.h>
#include <errno.h>
#include <signal.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/wait.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <vector>
#include <time.h>
#include <iostream>
#include <unistd.h>
#include <cstring>
#ifdef __cplusplus
extern "C"{
#endif
char* strstrstr(char* str, char* text, char* rear);//变态级的函数,看了可能会崩溃
char* httppost(char* hostname, char* url, char* cs);//POST方法
char* httpget(const char* hostname, char* url);//GET方法
char* getip(char* hostname);//域名转换IP
int hextoint(char* hex);//16进制字符串转整数

#ifdef __cplusplus
}
#endif


    
char* httppost(char* hostname, char* url, char* cs){
	// sock句柄
	int sockfd;
	struct sockaddr_in serveraddr;
	//两个是同一个类型，可混用，但是会警告
	//int addrlen = sizeof(serveraddr);
    socklen_t addrlen = sizeof(serveraddr);
	if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0){
		//printf("创建网络连接失败---socket error!\n");
		return NULL;
	}
	//C4droid的GCC没有这个函数，编译会报错
	//bzero(&serveraddr, addrlen);
	//可以使用这个函数
	memset(&serveraddr,0,addrlen);
	serveraddr.sin_family = AF_INET;
	//端口80
	serveraddr.sin_port = htons(80);
	struct hostent* host;
	host = gethostbyname(hostname);
	if (host == NULL){
		printf("无法解析域名\n");
		close(sockfd);
		return NULL;
	}
	struct in_addr ip = *((struct in_addr *)host->h_addr);
	//printf("ip:%s\n",inet_ntoa(ip));
	serveraddr.sin_addr = ip;
	if (connect(sockfd, (struct sockaddr*) & serveraddr, addrlen) < 0){
		//printf("连接到服务器失败,connect error!\n");
		close(sockfd);
		return NULL;
	}
	//因为懒，所以char[2048]
	//可以通过strlen获取参数长度来动态申请内存
	char postxyt[2048];
	char postsjlen[5];
	// 发送数据
	//32位要用 "%u" 经测试发现还是long最稳定
	//顺利通过 GCC，arm-linux-gcc，arm-linux-gnueabi-gcc，安卓jni 编译 无警告
#if __SIZEOF_LONG__ == 8
	sprintf(postsjlen, "%lu", strlen(cs));
#else
	sprintf(postsjlen, "%u", strlen(cs));
#endif
	memset(postxyt, 0, 2048);
	// 追加字符串（请求协议头）
	//个人感觉strcat函数比较低效
	//用char指针和strcpy函数会比较快
	sprintf(postxyt,"POST /%s HTTP/1.1\r\nHost: %s\r\nContent-Type: application/x-www-form-urlencoded\r\nContent-Length: %ld\r\n\r\n%s\r\n\r\n",url,hostname,strlen(cs),cs);
	//别问，问就是 “一切皆文件”
	/*
	if (write(sockfd, postxyt, strlen(postxyt)) == -1){
		//printf("发送失败！错误代码:%d，错误信息:%s\n", errno, strerror(errno));
		close(sockfd);
		return NULL;
	}*/
	if (send(sockfd, postxyt, strlen(postxyt),0) == -1){
		printf("%d，%s\n", errno, strerror(errno));
		close(sockfd);
		return NULL;
	}
	fd_set fds;
	struct timeval tv = { 3,0 }; //select等待3秒，3秒轮询，要非阻塞就置0
	//非阻塞需要循环
	FD_ZERO(&fds); //每次循环都要清空集合，否则不能检测描述符变化 
	FD_SET(sockfd, &fds); //添加描述符

	if (select(sockfd + 1, &fds, NULL, NULL, &tv) < 1){
		//非阻塞时使用continue;
		//-1错误
		//0无写入数据
		close(sockfd);
		return NULL;
	}
	
	if (FD_ISSET(sockfd, &fds)) { //测试sock是否可读，即是否网络上有数据
		char xyt[1024];
		char* xytzz = xyt;
		char* xytmaxlen = xyt + 1023;
		int readlen;
		while (readlen = read(sockfd, xytzz, 1)){
			if(*xytzz == '\n'){
				//printf("匹配到一个\\n\n");
				if(strncmp(xytzz - 3,"\r\n\r",3) == 0){
					*++xytzz = '\0';
					//printf("嗨呀，完全匹配，好开心\n");
					break;
				}
			}
			xytzz++;
			if(xytmaxlen == xytzz){
				//printf("什么吊毛协议头这么长\n");
				close(sockfd);
				return NULL;
			}
		}
		if(!readlen){
			//printf("服务器断开了连接，并未发送任何数据。\n");
			close(sockfd);
			return NULL;
		}
		//printf("%s\n--\n",xyt);
		char* xylen = strstrstr(xyt,"Content-Length: ","\n");
		char* xyzw;
		if(xylen == NULL){
			//如果读取正文长度失败则进入分段读取模式
			char hexlen[8];
			char* hex = hexlen;
			do{
				read(sockfd, hex, 1);
			}while(*hex++ != '\n');
			*hex = '\0';
			int rdlen = hextoint(hexlen);
			if(rdlen == 0){
				close(sockfd);
				return NULL;
			}
			//rdlen++;
			xyzw = (char*)malloc(rdlen + 2);//把\r\n读出来
			read(sockfd, xyzw, rdlen + 2);//方便下面读取区块信息
			//xyzw[rdlen] = '\0';
			//读取下一块内容
			while(1){
				hex = hexlen;
				do{
					read(sockfd, hex, 1);
				}while(*hex++ != '\n');
				*hex = '\0';
				int chlen = hextoint(hexlen);
				//判断当前区块是否为0
				if(chlen == 0)break;
				rdlen += chlen;
				xyzw = (char*)realloc(xyzw,rdlen + 2);
				char* xrzz = xyzw + rdlen - chlen;
				read(sockfd, xrzz, chlen + 2);
				//xyzw[rdlen] = '\0';
			}
			xyzw[rdlen] = '\0';
		}else{
			//得到正文长度，一次性读取 申请内存
			int xyzwlen = atoi(xylen);
			free(xylen);
			//printf("正文共%d字节\n",xyzwlen);
			xyzw = (char*)malloc(xyzwlen + 1);
			readlen = read(sockfd, xyzw, xyzwlen);//接受网络数据
			xyzw[readlen] = '\0';
			//printf("读取了%d字节\n",readlen);
		}
		close(sockfd);
		return xyzw;
	}
	close(sockfd);
	return NULL;
}

char* httpget(const char* hostname, char* url){
	// sock句柄
	int sockfd;
	struct sockaddr_in serveraddr;
	//两个是同一个类型，可混用，但是会警告
	//int addrlen = sizeof(serveraddr);
    socklen_t addrlen = sizeof(serveraddr);
	if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0){
		//printf("创建网络连接失败---socket error!\n");
		return NULL;
	}
	//C4droid的GCC没有这个函数，编译会报错
	//bzero(&serveraddr, addrlen);
	//可以使用这个函数
	memset(&serveraddr,0,addrlen);
	serveraddr.sin_family = AF_INET;
	//端口80
	serveraddr.sin_port = htons(80);
	struct hostent* host;
	host = gethostbyname(hostname);
	if (host == NULL){
		//printf("无法解析域名\n");
		close(sockfd);
		return NULL;
	}
	struct in_addr ip = *((struct in_addr *)host->h_addr);
	//printf("ip:%s\n",inet_ntoa(ip));
	serveraddr.sin_addr = ip;
	if (connect(sockfd, (struct sockaddr*) & serveraddr, addrlen) < 0){
		//printf("连接到服务器失败,connect error!\n");
		close(sockfd);
		return NULL;
	}
	//因为懒，所以char[2048]
	//可以通过strlen获取参数长度来动态申请内存
	char postxyt[2048];
	memset(postxyt, 0, 2048);
	// 追加字符串（请求协议头）
	//个人感觉strcat函数比较低效
	//用char指针和strcpy函数会比较快
	sprintf(postxyt,"GET /%s HTTP/1.1\r\nHost: %s\r\nContent-Type: application/x-www-form-urlencoded\r\n\r\n",url,hostname);
	//别问，问就是 “一切皆文件”
	/*
	if (write(sockfd, postxyt, strlen(postxyt)) == -1){
		//printf("发送失败！错误代码:%d，错误信息:%s\n", errno, strerror(errno));
		close(sockfd);
		return NULL;
	}*/
	if (send(sockfd, postxyt, strlen(postxyt),0) == -1){
		printf("%d%s\n", errno, strerror(errno));
		close(sockfd);
		return NULL;
	}
	fd_set fds;
	struct timeval tv = { 3,0 }; //select等待3秒，3秒轮询，要非阻塞就置0
	//非阻塞需要循环
	FD_ZERO(&fds); //每次循环都要清空集合，否则不能检测描述符变化 
	FD_SET(sockfd, &fds); //添加描述符

	if (select(sockfd + 1, &fds, NULL, NULL, &tv) < 1){
		//非阻塞时使用continue;
		//-1错误
		//0无写入数据
		close(sockfd);
		return NULL;
	}
	
	if (FD_ISSET(sockfd, &fds)) { //测试sock是否可读，即是否网络上有数据
		char xyt[1024];
		char* xytzz = xyt;
		char* xytmaxlen = xyt + 1023;
		int readlen;
		while (readlen = read(sockfd, xytzz, 1)){
			if(*xytzz == '\n'){
				//printf("匹配到一个\\n\n");
				if(strncmp(xytzz - 3,"\r\n\r",3) == 0){
					*++xytzz = '\0';
					//printf("嗨呀，完全匹配，好开心\n");
					break;
				}
			}
			xytzz++;
			if(xytmaxlen == xytzz){
				//printf("什么吊毛协议头这么长\n%s\n",xyt);
				close(sockfd);
				return NULL;
			}
		}

		if(!readlen){
			//printf("服务器断开了连接，并未发送任何数据。\n");
			close(sockfd);
			return NULL;
		}
		
		//printf("%s\n--\n",xyt);
		char* xylen = strstrstr(xyt,"Content-Length: ","\n");
		char* xyzw;
		if(xylen == NULL){
			//如果读取正文长度失败则进入分段读取模式
			char hexlen[8];
			char* hex = hexlen;
			do{
				read(sockfd, hex, 1);
			}while(*hex++ != '\n');
			*hex = '\0';
			int rdlen = hextoint(hexlen);
			if(rdlen == 0){
				close(sockfd);
				return NULL;
			}
			//rdlen++;
			xyzw = (char*)malloc(rdlen + 2);//把\r\n读出来
			read(sockfd, xyzw, rdlen + 2);//方便下面读取区块信息
			//xyzw[rdlen] = '\0';
			//读取下一块内容
			while(1){
				hex = hexlen;
				do{
					read(sockfd, hex, 1);
				}while(*hex++ != '\n');
				*hex = '\0';
				int chlen = hextoint(hexlen);
				//判断当前区块是否为0
				if(chlen == 0)break;
				rdlen += chlen;
				xyzw = (char*)realloc(xyzw,rdlen + 2);
				char* xrzz = xyzw + rdlen - chlen;
				read(sockfd, xrzz, chlen + 2);
				//xyzw[rdlen] = '\0';
			}
			xyzw[rdlen] = '\0';
		}else{
			//得到正文长度，一次性读取 申请内存
			int xyzwlen = atoi(xylen);
			free(xylen);
			//printf("正文共%d字节\n",xyzwlen);
			xyzw = (char*)malloc(xyzwlen + 1);
			readlen = read(sockfd, xyzw, xyzwlen);//接受网络数据
			xyzw[readlen] = '\0';
			//printf("读取了%d字节\n",readlen);
		}
		close(sockfd);
		return xyzw;
	}
	close(sockfd);
	return NULL;
}

char* strstrstr(char* str, char* front, char* rear){
	if(!str || !front || !rear)return NULL;//如果你不传NULL,我至于吗
	char* s;
	char* t;
	while(*str) {
		s = str;
		t = front;
		while (*s == *t) {
			s++;
			t++;
			if (!*t) {
				str = s;
				char* old = str;
				do{
					s = str;
					t = rear;
					while (*s == *t) {
						s++;
						t++;
						if (!*t) {
							int charlen = str - old;
							char* newstr = (char*)malloc(charlen + 1);
							strncpy(newstr, old, charlen);
							//使用Visual studio编程时会警告strncpy函数存在风险，使用strncpy_s替换之
							//strncpy_s(newstr, charlen + 1,old, charlen);
							newstr[charlen] = '\0';
							return newstr;
						}
					}
					str++;
				}while(*str);
				return NULL;
			}
		}
		str++;
	}
	return NULL;
}

char* getip(char* hostname) {
	//不多bb，面向百度编程
	struct hostent* host;
	host = gethostbyname(hostname);
	if (host == NULL){
		perror("cannot get host by hostname");
		return NULL;
	}
	return inet_ntoa(*((struct in_addr *)host->h_addr));
}

int hextoint(char * hex){
    int value = 0;
    while (*hex){
        if (*hex >= 'A' && *hex <= 'F')
            value = (*hex - 55) + 16 * value;
        else if (*hex >= 'a' && *hex <= 'f')
            value = (*hex - 87) + 16 * value;
        else if (*hex >= '0' && *hex <= '9')
            value = (*hex - 48) + 16 * value;
        else{
            return value;
        }
        hex++;
    }
    return value;
}

