Link v2.0
[link.git] / Source / Client.cpp
blob702eec91c1afa95b93bf86a213187fcfc30e6862
1 #include <Link.hpp>
2 #include <sys/socket.h>
3 #include <netinet/in.h>
4 #include <arpa/inet.h>
5 #include <netdb.h>
6 #include <openssl/ssl.h>
7 #include <unistd.h>
8 #include <iostream>
9 #include <fstream>
10 #include <fcntl.h>
11 #include <time.h>
12 #include <stdio.h>
13 #include <algorithm>
14 #include <cctype>
15 #include <string>
17 Link::Client::Client(Link::Request* request) {
18 Status = 0;
19 this->request = request;
20 if (this->request->GetProtocol() != "https" && this->request->GetProtocol() != "http") {
21 std::cout << "Invalid protocol: " << this->request->GetProtocol() << std::endl;
25 Link::Request* Link::Client::SetRequest(Link::Request* request) {
26 this->request = request;
27 return this->request;
30 Link::Request* Link::Client::GetRequest() {
31 return this->request;
34 Link::Response* Link::Client::GetResponse() {
35 return this->response;
38 int Link::Client::Write(const void* buf, size_t count) {
39 if (this->request->GetProtocol() == "https") return SSL_write((SSL*)ssl, buf, count);
40 else return send(sock, buf, count, 0);
43 int Link::Client::Read(void* buf, size_t count) {
44 if (this->request->GetProtocol() == "https") return SSL_read((SSL*)ssl, buf, count);
45 else return recv(sock, buf, count, 0);
48 bool Link::Client::getChunkSize(int& remaining, std::string& body) {
49 std::string chunkSizeStr = "";
50 char buffer[1];
51 while (chunkSizeStr.find("\r\n") == std::string::npos) {
52 int bytes = Read(buffer, 1);
53 switch(buffer[0]) {
54 case '\r':
55 case '\n':
56 if (chunkSizeStr == "" || chunkSizeStr == "\r" || chunkSizeStr == "\n") {
57 chunkSizeStr = "";
58 // body += buffer[0];
59 continue;
61 case 'a'...'f':
62 case 'A'...'F':
63 case '0'...'9':
64 chunkSizeStr += buffer[0];
65 break;
66 default:
67 body += buffer[0];
68 break;
71 remaining = std::stoi(chunkSizeStr, 0, 16);
72 if (remaining == 0) return true;
73 return false;
76 Link::Response* Link::Client::Send() {
77 Status = 0;
78 SSL_CTX* ctx = NULL;
79 if (this->request->GetProtocol() == "https") {
80 ctx = SSL_CTX_new(SSLv23_client_method());
82 struct sockaddr_in addr;
83 bzero(&addr, sizeof(addr));
84 addr.sin_family = AF_INET;
85 if (this->request->GetPort()==0) addr.sin_port = htons(this->request->GetProtocol()=="https"?443:80); // TODO: Parse port from URL
86 else addr.sin_port = htons(this->request->GetPort());
87 struct hostent* ipv4 = gethostbyname(this->request->GetDomain().c_str());
88 addr.sin_addr.s_addr = inet_addr(inet_ntoa(*(struct in_addr*)ipv4->h_addr_list[0]));
90 socklen_t socklen = sizeof(addr);
92 sock = socket(AF_INET, SOCK_STREAM, 0);
93 if (connect(sock, (struct sockaddr*)&addr, socklen) < 0) {
94 Status = 1;
95 return NULL;
98 if (this->request->GetProtocol() == "https") {
99 SSL_library_init();
100 SSLeay_add_ssl_algorithms();
101 SSL_load_error_strings();
102 // SSL_CTX_set_cipher_list(ctx, "TLS_AES_256_GCM_SHA384");
103 SSL_CTX_set_max_proto_version(ctx, TLS1_3_VERSION);
104 ssl = SSL_new(ctx);
105 int newsock = SSL_get_fd(ssl);
106 SSL_set_fd(ssl, sock);
107 SSL_set_tlsext_host_name(ssl, this->request->GetDomain().c_str());
108 int error = SSL_connect(ssl);
109 if (error < 0) {
110 Status = 2;
111 return NULL;
115 std::string r = this->request->GetRawHeaders();
116 r += "\r\n";
117 r += this->request->GetBody();
119 int status = Write(r.c_str(), strlen(r.c_str()));
120 if (status < 0) {
121 Status = 3;
122 return NULL;
125 int flags = fcntl(sock, F_GETFL, 0);
126 fcntl(sock, F_SETFL, flags | O_NONBLOCK);
128 int remaining = 0;
129 char buffer[1024];
130 std::string response;
132 while (response.find("\r\n\r\n") == std::string::npos) {
133 int bytes = Read(buffer, 1);
134 if (bytes > 0) response += std::string(buffer, bytes);
137 std::string body, headers;
139 std::string lower = response;
140 std::transform(lower.begin(), lower.end(), lower.begin(), ::tolower);
143 if (lower.find("transfer-encoding: chunked") != std::string::npos) {
145 * Finally figured out how to do this
146 * This should not be a problem anymore
147 * - FiRe
149 // Check for the first chunk size
150 headers = response.substr(0, response.find("\r\n\r\n"));
151 body = response.substr(response.find("\r\n\r\n") + 4, response.find("\r\n0\r\n\r\n") - response.find("\r\n\r\n") - 4);
152 if (body.substr(0, body.find("\r\n")).length() > 0) {
153 // We have a chunk size
155 * This check is no longer needed, but I'm leaving it here for now
156 * - FiRe
158 remaining = std::stoi(body.substr(0, body.find("\r\n")), 0, 16);
159 } else {
160 // Need to find the next chunk size
161 getChunkSize(remaining, body);
163 while (remaining > 0) {
164 int bytes = Read(buffer, remaining>1024?1024:remaining);
165 if (bytes > 0) {
166 body += std::string(buffer, bytes);
167 remaining -= bytes;
171 while (!getChunkSize(remaining, body)) {
172 while (remaining > 0) {
173 int bytes = Read(buffer, remaining>1024?1024:remaining);
174 if (bytes > 0) {
175 body += std::string(buffer, bytes);
176 remaining -= bytes;
180 } else if (lower.find("content-length: ") != std::string::npos) {
181 remaining = std::stoi(lower.substr(lower.find("content-length: ") + 16, lower.find("\r\n", lower.find("content-length: ") + 16) - lower.find("content-length: ") - 16));
182 remaining-=response.substr(response.find("\r\n\r\n") + 4).length();
183 if (remaining > 0) {
184 while (remaining > 0) {
185 int bytes = Read(buffer, 1024);
186 if (bytes > 0) {
187 response += std::string(buffer, bytes);
188 remaining -= bytes;
192 body = response.substr(response.find("\r\n\r\n") + 4);
193 headers = response.substr(0, response.find("\r\n\r\n"));
194 } else headers = response;
195 Response* res = new Response(headers, body);
197 if (this->request->GetProtocol() == "https") {
198 SSL_shutdown(ssl);
199 SSL_free(ssl);
200 SSL_CTX_free(ctx);
202 close(sock);
203 return res;