1 /**
2  * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3  * SPDX-License-Identifier: Apache-2.0.
4  */
5 #include <aws/crt/Api.h>
6 #include <aws/crt/crypto/Hash.h>
7 #include <aws/crt/http/HttpConnection.h>
8 #include <aws/crt/http/HttpRequestResponse.h>
9 #include <aws/crt/io/Uri.h>
10 
11 #include <aws/testing/aws_test_harness.h>
12 
13 #include <condition_variable>
14 #include <fstream>
15 #include <iostream>
16 #include <mutex>
17 
18 using namespace Aws::Crt;
19 
20 #if !BYO_CRYPTO
21 
s_VerifyFilesAreTheSame(Allocator * allocator,const char * fileName1,const char * fileName2)22 static int s_VerifyFilesAreTheSame(Allocator *allocator, const char *fileName1, const char *fileName2)
23 {
24     std::ifstream file1(fileName1, std::ios_base::binary);
25     std::ifstream file2(fileName2, std::ios_base::binary);
26 
27     ASSERT_TRUE(file1);
28     ASSERT_TRUE(file2);
29 
30     auto file1Hash = Crypto::Hash::CreateSHA256(allocator);
31     uint8_t buffer[1024];
32     AWS_ZERO_ARRAY(buffer);
33 
34     while (file1.read((char *)buffer, sizeof(buffer)))
35     {
36         auto read = file1.gcount();
37         ByteCursor toHash = ByteCursorFromArray(buffer, (size_t)read);
38         ASSERT_TRUE(file1Hash.Update(toHash));
39     }
40 
41     auto file2Hash = Crypto::Hash::CreateSHA256(allocator);
42 
43     while (file2.read((char *)buffer, sizeof(buffer)))
44     {
45         auto read = file2.gcount();
46         ByteCursor toHash = ByteCursorFromArray(buffer, (size_t)read);
47         ASSERT_TRUE(file2Hash.Update(toHash));
48     }
49 
50     uint8_t file1Digest[Crypto::SHA256_DIGEST_SIZE];
51     AWS_ZERO_ARRAY(file1Digest);
52     uint8_t file2Digest[Crypto::SHA256_DIGEST_SIZE];
53     AWS_ZERO_ARRAY(file2Digest);
54 
55     ByteBuf file1DigestBuf = ByteBufFromEmptyArray(file1Digest, sizeof(file1Digest));
56     ByteBuf file2DigestBuf = ByteBufFromEmptyArray(file2Digest, sizeof(file2Digest));
57 
58     ASSERT_TRUE(file1Hash.Digest(file1DigestBuf));
59     ASSERT_TRUE(file2Hash.Digest(file2DigestBuf));
60 
61     ASSERT_BIN_ARRAYS_EQUALS(file2DigestBuf.buffer, file2DigestBuf.len, file1DigestBuf.buffer, file1DigestBuf.len);
62     return AWS_OP_SUCCESS;
63 }
64 
s_TestHttpDownloadNoBackPressure(struct aws_allocator * allocator,ByteCursor urlCursor,bool h2Required)65 static int s_TestHttpDownloadNoBackPressure(struct aws_allocator *allocator, ByteCursor urlCursor, bool h2Required)
66 {
67     int result = AWS_OP_ERR;
68 
69     {
70         Aws::Crt::ApiHandle apiHandle(allocator);
71         Aws::Crt::Io::TlsContextOptions tlsCtxOptions = Aws::Crt::Io::TlsContextOptions::InitDefaultClient();
72         Aws::Crt::Io::TlsContext tlsContext(tlsCtxOptions, Aws::Crt::Io::TlsMode::CLIENT, allocator);
73         ASSERT_TRUE(tlsContext);
74 
75         Aws::Crt::Io::TlsConnectionOptions tlsConnectionOptions = tlsContext.NewConnectionOptions();
76 
77         Io::Uri uri(urlCursor, allocator);
78 
79         auto hostName = uri.GetHostName();
80         tlsConnectionOptions.SetServerName(hostName);
81         if (h2Required)
82         {
83             tlsConnectionOptions.SetAlpnList("h2");
84         }
85 
86         Aws::Crt::Io::SocketOptions socketOptions;
87         socketOptions.SetConnectTimeoutMs(5000);
88 
89         Aws::Crt::Io::EventLoopGroup eventLoopGroup(0, allocator);
90         ASSERT_TRUE(eventLoopGroup);
91 
92         Aws::Crt::Io::DefaultHostResolver defaultHostResolver(eventLoopGroup, 8, 30, allocator);
93         ASSERT_TRUE(defaultHostResolver);
94 
95         Aws::Crt::Io::ClientBootstrap clientBootstrap(eventLoopGroup, defaultHostResolver, allocator);
96         ASSERT_TRUE(clientBootstrap);
97         clientBootstrap.EnableBlockingShutdown();
98 
99         std::shared_ptr<Http::HttpClientConnection> connection(nullptr);
100         bool errorOccured = true;
101         bool connectionShutdown = false;
102 
103         std::condition_variable semaphore;
104         std::mutex semaphoreLock;
105 
106         auto onConnectionSetup = [&](const std::shared_ptr<Http::HttpClientConnection> &newConnection, int errorCode) {
107             std::lock_guard<std::mutex> lockGuard(semaphoreLock);
108 
109             if (!errorCode)
110             {
111                 connection = newConnection;
112                 errorOccured = false;
113             }
114             else
115             {
116                 connectionShutdown = true;
117             }
118 
119             semaphore.notify_one();
120         };
121 
122         auto onConnectionShutdown = [&](Http::HttpClientConnection &, int errorCode) {
123             std::lock_guard<std::mutex> lockGuard(semaphoreLock);
124 
125             connectionShutdown = true;
126             if (errorCode)
127             {
128                 errorOccured = true;
129             }
130 
131             semaphore.notify_one();
132         };
133 
134         Http::HttpClientConnectionOptions httpClientConnectionOptions;
135         httpClientConnectionOptions.Bootstrap = &clientBootstrap;
136         httpClientConnectionOptions.OnConnectionSetupCallback = onConnectionSetup;
137         httpClientConnectionOptions.OnConnectionShutdownCallback = onConnectionShutdown;
138         httpClientConnectionOptions.SocketOptions = socketOptions;
139         httpClientConnectionOptions.TlsOptions = tlsConnectionOptions;
140         httpClientConnectionOptions.HostName = String((const char *)hostName.ptr, hostName.len);
141         httpClientConnectionOptions.Port = 443;
142 
143         std::unique_lock<std::mutex> semaphoreULock(semaphoreLock);
144         ASSERT_TRUE(Http::HttpClientConnection::CreateConnection(httpClientConnectionOptions, allocator));
145         semaphore.wait(semaphoreULock, [&]() { return connection || connectionShutdown; });
146 
147         ASSERT_FALSE(errorOccured);
148         ASSERT_FALSE(connectionShutdown);
149         ASSERT_TRUE(connection);
150         String fileName = h2Required ? "http_download_test_file_h2.txt" : "http_download_test_file_h1_1.txt";
151         Http::HttpVersion excepted = h2Required ? Http::HttpVersion::Http2 : Http::HttpVersion::Http1_1;
152         ASSERT_TRUE(connection->GetVersion() == excepted);
153 
154         int responseCode = 0;
155         std::ofstream downloadedFile(fileName.c_str(), std::ios_base::binary);
156         ASSERT_TRUE(downloadedFile);
157 
158         Http::HttpRequest request;
159         Http::HttpRequestOptions requestOptions;
160         requestOptions.request = &request;
161 
162         bool streamCompleted = false;
163         requestOptions.onStreamComplete = [&](Http::HttpStream &, int errorCode) {
164             std::lock_guard<std::mutex> lockGuard(semaphoreLock);
165 
166             streamCompleted = true;
167             if (errorCode)
168             {
169                 errorOccured = true;
170             }
171 
172             semaphore.notify_one();
173         };
174         requestOptions.onIncomingHeadersBlockDone = nullptr;
175         requestOptions.onIncomingHeaders =
176             [&](Http::HttpStream &stream, enum aws_http_header_block, const Http::HttpHeader *, std::size_t) {
177                 responseCode = stream.GetResponseStatusCode();
178             };
179         requestOptions.onIncomingBody = [&](Http::HttpStream &, const ByteCursor &data) {
180             downloadedFile.write((const char *)data.ptr, data.len);
181         };
182 
183         request.SetMethod(ByteCursorFromCString("GET"));
184         request.SetPath(uri.GetPathAndQuery());
185 
186         Http::HttpHeader hostHeader;
187         hostHeader.name = ByteCursorFromCString("host");
188         hostHeader.value = uri.GetHostName();
189         request.AddHeader(hostHeader);
190 
191         auto stream = connection->NewClientStream(requestOptions);
192         ASSERT_TRUE(stream->Activate());
193 
194         semaphore.wait(semaphoreULock, [&]() { return streamCompleted; });
195         ASSERT_INT_EQUALS(200, responseCode);
196 
197         connection->Close();
198         semaphore.wait(semaphoreULock, [&]() { return connectionShutdown; });
199 
200         downloadedFile.flush();
201         downloadedFile.close();
202         result = s_VerifyFilesAreTheSame(allocator, fileName.c_str(), "http_test_doc.txt");
203     }
204 
205     return result;
206 }
207 
s_TestHttpDownloadNoBackPressureHTTP1_1(struct aws_allocator * allocator,void * ctx)208 static int s_TestHttpDownloadNoBackPressureHTTP1_1(struct aws_allocator *allocator, void *ctx)
209 {
210     (void)ctx;
211     ByteCursor cursor = ByteCursorFromCString("https://aws-crt-test-stuff.s3.amazonaws.com/http_test_doc.txt");
212     return s_TestHttpDownloadNoBackPressure(allocator, cursor, false /*h2Required*/);
213 }
214 
AWS_TEST_CASE(HttpDownloadNoBackPressureHTTP1_1,s_TestHttpDownloadNoBackPressureHTTP1_1)215 AWS_TEST_CASE(HttpDownloadNoBackPressureHTTP1_1, s_TestHttpDownloadNoBackPressureHTTP1_1)
216 
217 static int s_TestHttpDownloadNoBackPressureHTTP2(struct aws_allocator *allocator, void *ctx)
218 {
219     (void)ctx;
220     ByteCursor cursor = ByteCursorFromCString("https://d1cz66xoahf9cl.cloudfront.net/http_test_doc.txt");
221     return s_TestHttpDownloadNoBackPressure(allocator, cursor, true /*h2Required*/);
222 }
223 
AWS_TEST_CASE(HttpDownloadNoBackPressureHTTP2,s_TestHttpDownloadNoBackPressureHTTP2)224 AWS_TEST_CASE(HttpDownloadNoBackPressureHTTP2, s_TestHttpDownloadNoBackPressureHTTP2)
225 
226 static int s_TestHttpStreamUnActivated(struct aws_allocator *allocator, void *ctx)
227 {
228     (void)ctx;
229     {
230         Aws::Crt::ApiHandle apiHandle(allocator);
231         Aws::Crt::Io::TlsContextOptions tlsCtxOptions = Aws::Crt::Io::TlsContextOptions::InitDefaultClient();
232         Aws::Crt::Io::TlsContext tlsContext(tlsCtxOptions, Aws::Crt::Io::TlsMode::CLIENT, allocator);
233         ASSERT_TRUE(tlsContext);
234 
235         Aws::Crt::Io::TlsConnectionOptions tlsConnectionOptions = tlsContext.NewConnectionOptions();
236 
237         ByteCursor cursor = ByteCursorFromCString("https://aws-crt-test-stuff.s3.amazonaws.com/http_test_doc.txt");
238         Io::Uri uri(cursor, allocator);
239 
240         auto hostName = uri.GetHostName();
241         tlsConnectionOptions.SetServerName(hostName);
242 
243         Aws::Crt::Io::SocketOptions socketOptions;
244         socketOptions.SetConnectTimeoutMs(1000);
245 
246         Aws::Crt::Io::EventLoopGroup eventLoopGroup(0, allocator);
247         ASSERT_TRUE(eventLoopGroup);
248 
249         Aws::Crt::Io::DefaultHostResolver defaultHostResolver(eventLoopGroup, 8, 30, allocator);
250         ASSERT_TRUE(defaultHostResolver);
251 
252         Aws::Crt::Io::ClientBootstrap clientBootstrap(eventLoopGroup, defaultHostResolver, allocator);
253         ASSERT_TRUE(clientBootstrap);
254         clientBootstrap.EnableBlockingShutdown();
255 
256         std::shared_ptr<Http::HttpClientConnection> connection(nullptr);
257         bool errorOccured = true;
258         bool connectionShutdown = false;
259 
260         std::condition_variable semaphore;
261         std::mutex semaphoreLock;
262 
263         auto onConnectionSetup = [&](const std::shared_ptr<Http::HttpClientConnection> &newConnection, int errorCode) {
264             std::lock_guard<std::mutex> lockGuard(semaphoreLock);
265 
266             if (!errorCode)
267             {
268                 connection = newConnection;
269                 errorOccured = false;
270             }
271             else
272             {
273                 connectionShutdown = true;
274             }
275 
276             semaphore.notify_one();
277         };
278 
279         auto onConnectionShutdown = [&](Http::HttpClientConnection &, int errorCode) {
280             std::lock_guard<std::mutex> lockGuard(semaphoreLock);
281 
282             connectionShutdown = true;
283             if (errorCode)
284             {
285                 errorOccured = true;
286             }
287 
288             semaphore.notify_one();
289         };
290 
291         Http::HttpClientConnectionOptions httpClientConnectionOptions;
292         httpClientConnectionOptions.Bootstrap = &clientBootstrap;
293         httpClientConnectionOptions.OnConnectionSetupCallback = onConnectionSetup;
294         httpClientConnectionOptions.OnConnectionShutdownCallback = onConnectionShutdown;
295         httpClientConnectionOptions.SocketOptions = socketOptions;
296         httpClientConnectionOptions.TlsOptions = tlsConnectionOptions;
297         httpClientConnectionOptions.HostName = String((const char *)hostName.ptr, hostName.len);
298         httpClientConnectionOptions.Port = 443;
299 
300         std::unique_lock<std::mutex> semaphoreULock(semaphoreLock);
301         ASSERT_TRUE(Http::HttpClientConnection::CreateConnection(httpClientConnectionOptions, allocator));
302         semaphore.wait(semaphoreULock, [&]() { return connection || connectionShutdown; });
303 
304         ASSERT_FALSE(errorOccured);
305         ASSERT_FALSE(connectionShutdown);
306         ASSERT_TRUE(connection);
307 
308         Http::HttpRequest request;
309         Http::HttpRequestOptions requestOptions;
310         requestOptions.request = &request;
311 
312         requestOptions.onStreamComplete = [&](Http::HttpStream &, int) {
313             // do nothing.
314         };
315         requestOptions.onIncomingHeadersBlockDone = nullptr;
316         requestOptions.onIncomingHeaders =
317             [&](Http::HttpStream &, enum aws_http_header_block, const Http::HttpHeader *, std::size_t) {
318                 // do nothing
319             };
320         requestOptions.onIncomingBody = [&](Http::HttpStream &, const ByteCursor &) {
321             // do nothing
322         };
323 
324         request.SetMethod(ByteCursorFromCString("GET"));
325         request.SetPath(uri.GetPathAndQuery());
326 
327         Http::HttpHeader hostHeader;
328         hostHeader.name = ByteCursorFromCString("host");
329         hostHeader.value = uri.GetHostName();
330         request.AddHeader(hostHeader);
331 
332         // don't activate it and let it go out of scope.
333         auto stream = connection->NewClientStream(requestOptions);
334         stream = nullptr;
335         connection->Close();
336         semaphore.wait(semaphoreULock, [&]() { return connectionShutdown; });
337     }
338 
339     return AWS_OP_SUCCESS;
340 }
341 
AWS_TEST_CASE(HttpStreamUnActivated,s_TestHttpStreamUnActivated)342 AWS_TEST_CASE(HttpStreamUnActivated, s_TestHttpStreamUnActivated)
343 
344 static int s_TestHttpCreateConnectionInvalidTlsConnectionOptions(struct aws_allocator *allocator, void *ctx)
345 {
346     (void)ctx;
347     {
348         Aws::Crt::ApiHandle apiHandle(allocator);
349 
350         Aws::Crt::Io::TlsConnectionOptions invalidTlsConnectionOptions;
351         ASSERT_FALSE(invalidTlsConnectionOptions);
352 
353         ByteCursor cursor = ByteCursorFromCString("https://aws-crt-test-stuff.s3.amazonaws.com/http_test_doc.txt");
354         Io::Uri uri(cursor, allocator);
355 
356         auto hostName = uri.GetHostName();
357 
358         Aws::Crt::Io::SocketOptions socketOptions;
359 
360         Aws::Crt::Io::EventLoopGroup eventLoopGroup(0, allocator);
361         ASSERT_TRUE(eventLoopGroup);
362 
363         Aws::Crt::Io::DefaultHostResolver defaultHostResolver(eventLoopGroup, 8, 30, allocator);
364         ASSERT_TRUE(defaultHostResolver);
365 
366         Aws::Crt::Io::ClientBootstrap clientBootstrap(eventLoopGroup, defaultHostResolver, allocator);
367         ASSERT_TRUE(clientBootstrap);
368         clientBootstrap.EnableBlockingShutdown();
369 
370         Http::HttpClientConnectionOptions httpClientConnectionOptions;
371         httpClientConnectionOptions.Bootstrap = &clientBootstrap;
372         httpClientConnectionOptions.OnConnectionSetupCallback = [](const std::shared_ptr<Http::HttpClientConnection> &,
373                                                                    int) {};
374         httpClientConnectionOptions.OnConnectionShutdownCallback = [](Http::HttpClientConnection &, int) {};
375         httpClientConnectionOptions.SocketOptions = socketOptions;
376         httpClientConnectionOptions.TlsOptions = invalidTlsConnectionOptions;
377         httpClientConnectionOptions.HostName = String((const char *)hostName.ptr, hostName.len);
378         httpClientConnectionOptions.Port = 443;
379 
380         ASSERT_FALSE(Http::HttpClientConnection::CreateConnection(httpClientConnectionOptions, allocator));
381     }
382 
383     return AWS_OP_SUCCESS;
384 }
385 
386 AWS_TEST_CASE(HttpCreateConnectionInvalidTlsConnectionOptions, s_TestHttpCreateConnectionInvalidTlsConnectionOptions)
387 
388 #endif // !BYO_CRYPTO
389