docs: Update contributing to allow PR's.
[link.git] / Source / Client.cpp
blob06840d3c892d0853b3424cd81e5239be667c7146
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 this->request = request;
19 if (this->request->GetProtocol() != "https" && this->request->GetProtocol() != "http") {
20 std::cout << "Invalid protocol: " << this->request->GetProtocol() << std::endl;
24 Link::Request* Link::Client::SetRequest(Link::Request* request) {
25 this->request = request;
26 return this->request;
29 Link::Request* Link::Client::GetRequest() {
30 return this->request;
33 Link::Response* Link::Client::GetResponse() {
34 return this->response;
37 int Link::Client::Write(const void* buf, size_t count) {
38 if (this->request->GetProtocol() == "https") return SSL_write((SSL*)ssl, buf, count);
39 else return write(sock, buf, count);
42 int Link::Client::Read(void* buf, size_t count) {
43 if (this->request->GetProtocol() == "https") return SSL_read((SSL*)ssl, buf, count);
44 else return read(sock, buf, count);
47 bool Link::Client::getChunkSize(int& remaining, std::string& body) {
48 std::string chunkSizeStr = "";
49 char buffer[1];
50 while (chunkSizeStr.find("\r\n") == std::string::npos) {
51 int bytes = Read(buffer, 1);
52 switch(buffer[0]) {
53 case '\r':
54 case '\n':
55 if (chunkSizeStr == "" || chunkSizeStr == "\r" || chunkSizeStr == "\n") {
56 chunkSizeStr = "";
57 // body += buffer[0];
58 continue;
60 case 'a'...'f':
61 case 'A'...'F':
62 case '0'...'9':
63 chunkSizeStr += buffer[0];
64 break;
65 default:
66 body += buffer[0];
67 break;
70 remaining = std::stoi(chunkSizeStr, 0, 16);
71 if (remaining == 0) return true;
72 return false;
75 Link::Response* Link::Client::Send() {
76 SSL_CTX* ctx = NULL;
77 if (this->request->GetProtocol() == "https") {
78 ctx = SSL_CTX_new(SSLv23_client_method());
80 struct sockaddr_in addr;
81 bzero(&addr, sizeof(addr));
82 addr.sin_family = AF_INET;
83 if (this->request->GetPort()==0) addr.sin_port = htons(this->request->GetProtocol()=="https"?443:80); // TODO: Parse port from URL
84 else addr.sin_port = htons(this->request->GetPort());
85 struct hostent* ipv4 = gethostbyname(this->request->GetDomain().c_str());
86 addr.sin_addr.s_addr = inet_addr(inet_ntoa(*(struct in_addr*)ipv4->h_addr_list[0]));
88 socklen_t socklen = sizeof(addr);
90 sock = socket(AF_INET, SOCK_STREAM, 0);
91 if (connect(sock, (struct sockaddr*)&addr, socklen) < 0) std::cout << "Connection failed" << std::endl;
93 if (this->request->GetProtocol() == "https") {
94 SSL_library_init();
95 SSLeay_add_ssl_algorithms();
96 SSL_load_error_strings();
97 // SSL_CTX_set_cipher_list(ctx, "TLS_AES_256_GCM_SHA384");
98 SSL_CTX_set_max_proto_version(ctx, TLS1_3_VERSION);
99 ssl = SSL_new(ctx);
100 int newsock = SSL_get_fd(ssl);
101 SSL_set_fd(ssl, sock);
102 SSL_set_tlsext_host_name(ssl, this->request->GetDomain().c_str());
103 int error = SSL_connect(ssl);
104 if (error < 0) std::cout << "SSL connection failed" << std::endl;
107 std::string request = this->request->GetMethod() + " " + this->request->GetPath() + " HTTP/1.1\r\n"; // TODO: Add HTTP version variable
108 this->request->SetHeader("Connection", "close");
109 this->request->SetHeader("Accept", "*/*");
110 this->request->SetHeader("Accept-Encoding", "gzip, deflate");
111 this->request->SetHeader("Accept-Language", "en-US,en;q=0.9");
112 this->request->SetHeader("User-Agent", "Link/2.0.0");
113 request += this->request->GetRawHeaders();
114 request += "\r\n";
115 request += this->request->GetBody();
117 int status = Write(request.c_str(), strlen(request.c_str()));
118 if (status < 0) std::cout << "Write failed: " << status << std::endl;
120 int flags = fcntl(sock, F_GETFL, 0);
121 fcntl(sock, F_SETFL, flags | O_NONBLOCK);
123 int remaining = 0;
124 char buffer[1024];
125 std::string response;
127 while (response.find("\r\n\r\n") == std::string::npos) {
128 int bytes = Read(buffer, 1);
129 if (bytes > 0) response += std::string(buffer, bytes);
132 std::string body, headers;
134 std::string lower = response;
135 std::transform(lower.begin(), lower.end(), lower.begin(), ::tolower);
137 if (lower.find("transfer-encoding: chunked") != std::string::npos) {
139 * Finally figured out how to do this
140 * This should not be a problem anymore
141 * - FiRe
143 // Check for the first chunk size
144 headers = response.substr(0, response.find("\r\n\r\n"));
145 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);
146 if (body.substr(0, body.find("\r\n")).length() > 0) {
147 // We have a chunk size
149 * This check is no longer needed, but I'm leaving it here for now
150 * - FiRe
152 remaining = std::stoi(body.substr(0, body.find("\r\n")), 0, 16);
153 } else {
154 // Need to find the next chunk size
155 getChunkSize(remaining, body);
157 while (remaining > 0) {
158 int bytes = Read(buffer, remaining>1024?1024:remaining);
159 if (bytes > 0) {
160 body += std::string(buffer, bytes);
161 remaining -= bytes;
165 while (!getChunkSize(remaining, body)) {
166 while (remaining > 0) {
167 int bytes = Read(buffer, remaining>1024?1024:remaining);
168 if (bytes > 0) {
169 body += std::string(buffer, bytes);
170 remaining -= bytes;
174 } else {
175 remaining = std::stoi(lower.substr(lower.find("content-length: ") + 16, lower.find("\r\n", lower.find("content-length: ") + 16) - lower.find("content-length: ") - 16));
176 remaining-=response.substr(response.find("\r\n\r\n") + 4).length();
177 if (remaining > 0) {
178 while (remaining > 0) {
179 int bytes = Read(buffer, 1024);
180 if (bytes > 0) {
181 response += std::string(buffer, bytes);
182 remaining -= bytes;
186 body = response.substr(response.find("\r\n\r\n") + 4);
187 headers = response.substr(0, response.find("\r\n\r\n"));
189 Response* res = new Response(headers, body);
191 if (this->request->GetProtocol() == "https") {
192 SSL_shutdown(ssl);
193 SSL_free(ssl);
194 SSL_CTX_free(ctx);
196 close(sock);
197 return res;