source: azure_iot_hub_f767zi/trunk/azure_iot_sdk/c-utility/adapters/httpapi_compact.c@ 464

Last change on this file since 464 was 464, checked in by coas-nagasima, 3 years ago

WolfSSLとAzure IoT SDKを更新

  • Property svn:eol-style set to native
  • Property svn:mime-type set to text/x-csrc;charset=UTF-8
File size: 63.7 KB
Line 
1// Copyright (c) Microsoft. All rights reserved.
2// Licensed under the MIT license. See LICENSE file in the project root for full license information.
3
4#include <stdlib.h>
5#include <stdio.h>
6#include <ctype.h>
7#include <string.h>
8#include <limits.h>
9
10#include "azure_macro_utils/macro_utils.h"
11#include "azure_c_shared_utility/gballoc.h"
12#include "azure_c_shared_utility/httpheaders.h"
13#include "azure_c_shared_utility/crt_abstractions.h"
14#include "azure_c_shared_utility/xlogging.h"
15#include "azure_c_shared_utility/xio.h"
16#include "azure_c_shared_utility/platform.h"
17#include "azure_c_shared_utility/tlsio.h"
18#include "azure_c_shared_utility/threadapi.h"
19#include "azure_c_shared_utility/shared_util_options.h"
20#include "azure_c_shared_utility/http_proxy_io.h"
21
22#ifdef _MSC_VER
23#define snprintf _snprintf
24#endif
25
26/*Codes_SRS_HTTPAPI_COMPACT_21_001: [ The httpapi_compact shall implement the methods defined by the httpapi.h. ]*/
27/*Codes_SRS_HTTPAPI_COMPACT_21_002: [ The httpapi_compact shall support the http requests. ]*/
28/*Codes_SRS_HTTPAPI_COMPACT_21_003: [ The httpapi_compact shall return error codes defined by HTTPAPI_RESULT. ]*/
29#include "azure_c_shared_utility/httpapi.h"
30
31#define MAX_HOSTNAME 64
32#define TEMP_BUFFER_SIZE 1024
33
34/*Codes_SRS_HTTPAPI_COMPACT_21_077: [ The HTTPAPI_ExecuteRequest shall wait, at least, 10 seconds for the SSL open process. ]*/
35#define MAX_OPEN_RETRY 100
36/*Codes_SRS_HTTPAPI_COMPACT_21_084: [ The HTTPAPI_CloseConnection shall wait, at least, 10 seconds for the SSL close process. ]*/
37#define MAX_CLOSE_RETRY 100
38/*Codes_SRS_HTTPAPI_COMPACT_21_079: [ The HTTPAPI_ExecuteRequest shall wait, at least, 20 seconds to send a buffer using the SSL connection. ]*/
39#define MAX_SEND_RETRY 200
40/*Codes_SRS_HTTPAPI_COMPACT_21_081: [ The HTTPAPI_ExecuteRequest shall try to read the message with the response up to 20 seconds. ]*/
41#define MAX_RECEIVE_RETRY 200
42/*Codes_SRS_HTTPAPI_COMPACT_21_083: [ The HTTPAPI_ExecuteRequest shall wait, at least, 100 milliseconds between retries. ]*/
43#define RETRY_INTERVAL_IN_MICROSECONDS 100
44
45MU_DEFINE_ENUM_STRINGS(HTTPAPI_RESULT, HTTPAPI_RESULT_VALUES)
46
47typedef struct HTTP_HANDLE_DATA_TAG
48{
49 char* hostName;
50 char* certificate;
51 char* x509ClientCertificate;
52 char* x509ClientPrivateKey;
53 XIO_HANDLE xio_handle;
54 size_t received_bytes_count;
55 unsigned char* received_bytes;
56 unsigned int is_io_error : 1;
57 unsigned int is_connected : 1;
58 unsigned int send_completed : 1;
59} HTTP_HANDLE_DATA;
60
61/*the following function does the same as sscanf(pos2, "%d", &sec)*/
62/*this function only exists because some of platforms do not have sscanf. */
63static int ParseStringToDecimal(const char *src, int* dst)
64{
65 int result;
66 char* next;
67 long num = strtol(src, &next, 0);
68 if (src == next || num < INT_MIN || num > INT_MAX)
69 {
70 result = EOF;
71 }
72 else
73 {
74 result = 1;
75 }
76 if (num < INT_MIN) num = INT_MIN;
77 if (num > INT_MAX) num = INT_MAX;
78 *dst = (int)num;
79 return result;
80}
81
82/*the following function does the same as sscanf(pos2, "%x", &sec)*/
83/*this function only exists because some of platforms do not have sscanf. This is not a full implementation; it only works with well-defined x numbers. */
84#define HEXA_DIGIT_VAL(c) (((c>='0') && (c<='9')) ? (c-'0') : ((c>='a') && (c<='f')) ? (c-'a'+10) : ((c>='A') && (c<='F')) ? (c-'A'+10) : -1)
85static int ParseStringToHexadecimal(const char *src, size_t* dst)
86{
87 int result;
88 int digitVal;
89 if (src == NULL)
90 {
91 result = EOF;
92 }
93 else if (HEXA_DIGIT_VAL(*src) == -1)
94 {
95 result = EOF;
96 }
97 else
98 {
99 (*dst) = 0;
100 while ((digitVal = HEXA_DIGIT_VAL(*src)) != -1)
101 {
102 (*dst) *= 0x10;
103 (*dst) += (size_t)digitVal;
104 src++;
105 }
106 result = 1;
107 }
108 return result;
109}
110
111/*the following function does the same as sscanf(buf, "HTTP/%*d.%*d %d %*[^\r\n]", &ret) */
112/*this function only exists because some of platforms do not have sscanf. This is not a full implementation; it only works with well-defined HTTP response. */
113static int ParseHttpResponse(const char* src, int* dst)
114{
115 int result;
116 static const char HTTPPrefix[] = "HTTP/";
117 bool fail;
118 const char* runPrefix;
119
120 if ((src == NULL) || (dst == NULL))
121 {
122 result = EOF;
123 }
124 else
125 {
126 fail = false;
127 runPrefix = HTTPPrefix;
128
129 while((*runPrefix) != '\0')
130 {
131 if ((*runPrefix) != (*src))
132 {
133 fail = true;
134 break;
135 }
136 src++;
137 runPrefix++;
138 }
139
140 if (!fail)
141 {
142 while ((*src) != '.')
143 {
144 if ((*src) == '\0')
145 {
146 fail = true;
147 break;
148 }
149 src++;
150 }
151 }
152
153 if (!fail)
154 {
155 while ((*src) != ' ')
156 {
157 if ((*src) == '\0')
158 {
159 fail = true;
160 break;
161 }
162 src++;
163 }
164 }
165
166 if (fail)
167 {
168 result = EOF;
169 }
170 else
171 {
172 result = ParseStringToDecimal(src, dst);
173 }
174 }
175
176 return result;
177}
178
179HTTPAPI_RESULT HTTPAPI_Init(void)
180{
181/*Codes_SRS_HTTPAPI_COMPACT_21_004: [ The HTTPAPI_Init shall allocate all memory to control the http protocol. ]*/
182/*Codes_SRS_HTTPAPI_COMPACT_21_007: [ If there is not enough memory to control the http protocol, the HTTPAPI_Init shall return HTTPAPI_ALLOC_FAILED. ]*/
183/**
184 * No memory is necessary.
185 */
186
187 /*Codes_SRS_HTTPAPI_COMPACT_21_006: [ If HTTPAPI_Init succeed allocating all the needed memory, it shall return HTTPAPI_OK. ]*/
188return HTTPAPI_OK;
189}
190
191void HTTPAPI_Deinit(void)
192{
193 /*Codes_SRS_HTTPAPI_COMPACT_21_009: [ The HTTPAPI_Init shall release all memory allocated by the httpapi_compact. ]*/
194 /**
195 * No memory was necessary.
196 */
197}
198
199/*Codes_SRS_HTTPAPI_COMPACT_21_011: [ The HTTPAPI_CreateConnection shall create an http connection to the host specified by the hostName parameter. ]*/
200HTTP_HANDLE HTTPAPI_CreateConnection(const char* hostName)
201{
202 HTTP_HANDLE_DATA* http_instance;
203 TLSIO_CONFIG tlsio_config;
204
205 if (hostName == NULL)
206 {
207 /*Codes_SRS_HTTPAPI_COMPACT_21_014: [ If the hostName is NULL, the HTTPAPI_CreateConnection shall return NULL as the handle. ]*/
208 LogError("Invalid host name. Null hostName parameter.");
209 http_instance = NULL;
210 }
211 else if (*hostName == '\0')
212 {
213 /*Codes_SRS_HTTPAPI_COMPACT_21_015: [ If the hostName is empty, the HTTPAPI_CreateConnection shall return NULL as the handle. ]*/
214 LogError("Invalid host name. Empty string.");
215 http_instance = NULL;
216 }
217 else
218 {
219 http_instance = (HTTP_HANDLE_DATA*)malloc(sizeof(HTTP_HANDLE_DATA));
220 /*Codes_SRS_HTTPAPI_COMPACT_21_013: [ If there is not enough memory to control the http connection, the HTTPAPI_CreateConnection shall return NULL as the handle. ]*/
221 if (http_instance == NULL)
222 {
223 LogError("There is no memory to control the http connection");
224 }
225 else if (mallocAndStrcpy_s(&http_instance->hostName, hostName) != 0)
226 {
227 LogError("Failed copying hostname");
228 free(http_instance);
229 http_instance = NULL;
230 }
231 else
232 {
233 tlsio_config.hostname = http_instance->hostName;
234 tlsio_config.port = 443;
235 tlsio_config.underlying_io_interface = NULL;
236 tlsio_config.underlying_io_parameters = NULL;
237
238 http_instance->xio_handle = xio_create(platform_get_default_tlsio(), (void*)&tlsio_config);
239
240 /*Codes_SRS_HTTPAPI_COMPACT_21_016: [ If the HTTPAPI_CreateConnection failed to create the connection, it shall return NULL as the handle. ]*/
241 if (http_instance->xio_handle == NULL)
242 {
243 LogError("Create connection failed");
244 free(http_instance->hostName);
245 free(http_instance);
246 http_instance = NULL;
247 }
248 else
249 {
250 http_instance->is_connected = 0;
251 http_instance->is_io_error = 0;
252 http_instance->received_bytes_count = 0;
253 http_instance->received_bytes = NULL;
254 http_instance->certificate = NULL;
255 http_instance->x509ClientCertificate = NULL;
256 http_instance->x509ClientPrivateKey = NULL;
257 }
258 }
259 }
260
261 /*Codes_SRS_HTTPAPI_COMPACT_21_012: [ The HTTPAPI_CreateConnection shall return a non-NULL handle on success. ]*/
262 return (HTTP_HANDLE)http_instance;
263}
264
265static void on_io_close_complete(void* context)
266{
267 HTTP_HANDLE_DATA* http_instance = (HTTP_HANDLE_DATA*)context;
268
269 if (http_instance != NULL)
270 {
271 http_instance->is_connected = 0;
272 }
273}
274
275void HTTPAPI_CloseConnection(HTTP_HANDLE handle)
276{
277 HTTP_HANDLE_DATA* http_instance = (HTTP_HANDLE_DATA*)handle;
278
279 /*Codes_SRS_HTTPAPI_COMPACT_21_020: [ If the connection handle is NULL, the HTTPAPI_CloseConnection shall not do anything. ]*/
280 if (http_instance != NULL)
281 {
282 /*Codes_SRS_HTTPAPI_COMPACT_21_019: [ If there is no previous connection, the HTTPAPI_CloseConnection shall not do anything. ]*/
283 if (http_instance->xio_handle != NULL)
284 {
285 http_instance->is_io_error = 0;
286 /*Codes_SRS_HTTPAPI_COMPACT_21_017: [ The HTTPAPI_CloseConnection shall close the connection previously created in HTTPAPI_ExecuteRequest. ]*/
287 if (xio_close(http_instance->xio_handle, on_io_close_complete, http_instance) != 0)
288 {
289 LogError("The SSL got error closing the connection");
290 /*Codes_SRS_HTTPAPI_COMPACT_21_087: [ If the xio return anything different than 0, the HTTPAPI_CloseConnection shall destroy the connection anyway. ]*/
291 http_instance->is_connected = 0;
292 }
293 else
294 {
295 /*Codes_SRS_HTTPAPI_COMPACT_21_084: [ The HTTPAPI_CloseConnection shall wait, at least, 10 seconds for the SSL close process. ]*/
296 int countRetry = MAX_CLOSE_RETRY;
297 while (http_instance->is_connected == 1)
298 {
299 xio_dowork(http_instance->xio_handle);
300 if ((countRetry--) < 0)
301 {
302 /*Codes_SRS_HTTPAPI_COMPACT_21_085: [ If the HTTPAPI_CloseConnection retries 10 seconds to close the connection without success, it shall destroy the connection anyway. ]*/
303 LogError("Close timeout. The SSL didn't close the connection");
304 http_instance->is_connected = 0;
305 }
306 else if (http_instance->is_io_error == 1)
307 {
308 LogError("The SSL got error closing the connection");
309 http_instance->is_connected = 0;
310 }
311 else if (http_instance->is_connected == 1)
312 {
313 LogInfo("Waiting for TLS close connection");
314 /*Codes_SRS_HTTPAPI_COMPACT_21_086: [ The HTTPAPI_CloseConnection shall wait, at least, 100 milliseconds between retries. ]*/
315 ThreadAPI_Sleep(RETRY_INTERVAL_IN_MICROSECONDS);
316 }
317 }
318 }
319 /*Codes_SRS_HTTPAPI_COMPACT_21_076: [ After close the connection, The HTTPAPI_CloseConnection shall destroy the connection previously created in HTTPAPI_CreateConnection. ]*/
320 xio_destroy(http_instance->xio_handle);
321 }
322
323#ifndef DO_NOT_COPY_TRUSTED_CERTS_STRING
324 /*Codes_SRS_HTTPAPI_COMPACT_21_018: [ If there is a certificate associated to this connection, the HTTPAPI_CloseConnection shall free all allocated memory for the certificate. ]*/
325 if (http_instance->certificate)
326 {
327 free(http_instance->certificate);
328 }
329#endif
330
331 /*Codes_SRS_HTTPAPI_COMPACT_06_001: [ If there is a x509 client certificate associated to this connection, the HTTAPI_CloseConnection shall free all allocated memory for the certificate. ]*/
332 if (http_instance->x509ClientCertificate)
333 {
334 free(http_instance->x509ClientCertificate);
335 }
336
337 /*Codes_SRS_HTTPAPI_COMPACT_06_002: [ If there is a x509 client private key associated to this connection, then HTTP_CloseConnection shall free all the allocated memory for the private key. ]*/
338 if (http_instance->x509ClientPrivateKey)
339 {
340 free(http_instance->x509ClientPrivateKey);
341 }
342
343 if (http_instance->hostName)
344 {
345 free(http_instance->hostName);
346 }
347
348 free(http_instance);
349 }
350}
351
352static void on_io_open_complete(void* context, IO_OPEN_RESULT open_result)
353{
354 HTTP_HANDLE_DATA* http_instance = (HTTP_HANDLE_DATA*)context;
355
356 if (http_instance != NULL)
357 {
358 if (open_result == IO_OPEN_OK)
359 {
360 http_instance->is_connected = 1;
361 http_instance->is_io_error = 0;
362 }
363 else
364 {
365 http_instance->is_io_error = 1;
366 }
367 }
368}
369
370static void on_send_complete(void* context, IO_SEND_RESULT send_result)
371{
372 HTTP_HANDLE_DATA* http_instance = (HTTP_HANDLE_DATA*)context;
373
374 if (http_instance != NULL)
375 {
376 if (send_result == IO_SEND_OK)
377 {
378 http_instance->send_completed = 1;
379 http_instance->is_io_error = 0;
380 }
381 else
382 {
383 http_instance->is_io_error = 1;
384 }
385 }
386}
387
388#define TOLOWER(c) (((c>='A') && (c<='Z'))?c-'A'+'a':c)
389static int InternStrnicmp(const char* s1, const char* s2, size_t n)
390{
391 int result;
392
393 if (s1 == NULL) result = -1;
394 else if (s2 == NULL) result = 1;
395 else
396 {
397 result = 0;
398
399 while(n-- && result == 0)
400 {
401 if (*s1 == 0) result = -1;
402 else if (*s2 == 0) result = 1;
403 else
404 {
405
406 result = TOLOWER(*s1) - TOLOWER(*s2);
407 ++s1;
408 ++s2;
409 }
410 }
411 }
412
413 return result;
414}
415
416static void on_bytes_received(void* context, const unsigned char* buffer, size_t size)
417{
418 unsigned char* new_received_bytes;
419 HTTP_HANDLE_DATA* http_instance = (HTTP_HANDLE_DATA*)context;
420
421 if (http_instance != NULL)
422 {
423
424 if (buffer == NULL)
425 {
426 http_instance->is_io_error = 1;
427 LogError("NULL pointer error");
428 }
429 else
430 {
431 /* Here we got some bytes so we'll buffer them so the receive functions can consumer it */
432 new_received_bytes = (unsigned char*)realloc(http_instance->received_bytes, http_instance->received_bytes_count + size);
433 if (new_received_bytes == NULL)
434 {
435 http_instance->is_io_error = 1;
436 LogError("Error allocating memory for received data");
437 }
438 else
439 {
440 http_instance->received_bytes = new_received_bytes;
441 if (memcpy(http_instance->received_bytes + http_instance->received_bytes_count, buffer, size) == NULL)
442 {
443 http_instance->is_io_error = 1;
444 LogError("Error copping received data to the HTTP bufffer");
445 }
446 else
447 {
448 http_instance->received_bytes_count += size;
449 }
450 }
451 }
452 }
453}
454
455static void on_io_error(void* context)
456{
457 HTTP_HANDLE_DATA* http_instance = (HTTP_HANDLE_DATA*)context;
458 if (http_instance != NULL)
459 {
460 http_instance->is_io_error = 1;
461 LogError("Error signalled by underlying IO");
462 }
463}
464
465static int conn_receive(HTTP_HANDLE_DATA* http_instance, char* buffer, int count)
466{
467 int result;
468
469 if ((http_instance == NULL) || (buffer == NULL) || (count < 0))
470 {
471 LogError("conn_receive: %s", ((http_instance == NULL) ? "Invalid HTTP instance" : "Invalid HTTP buffer"));
472 result = -1;
473 }
474 else
475 {
476 result = 0;
477 while (result < count)
478 {
479 xio_dowork(http_instance->xio_handle);
480
481 /* if any error was detected while receiving then simply break and report it */
482 if (http_instance->is_io_error != 0)
483 {
484 LogError("xio reported error on dowork");
485 result = -1;
486 break;
487 }
488
489 if (http_instance->received_bytes_count >= (size_t)count)
490 {
491 /* Consuming bytes from the receive buffer */
492 (void)memcpy(buffer, http_instance->received_bytes, count);
493 (void)memmove(http_instance->received_bytes, http_instance->received_bytes + count, http_instance->received_bytes_count - count);
494 http_instance->received_bytes_count -= count;
495
496 /* we're not reallocating at each consumption so that we don't trash due to byte by byte consumption */
497 if (http_instance->received_bytes_count == 0)
498 {
499 free(http_instance->received_bytes);
500 http_instance->received_bytes = NULL;
501 }
502
503 result = count;
504 break;
505 }
506
507 /*Codes_SRS_HTTPAPI_COMPACT_21_083: [ The HTTPAPI_ExecuteRequest shall wait, at least, 100 milliseconds between retries. ]*/
508 ThreadAPI_Sleep(RETRY_INTERVAL_IN_MICROSECONDS);
509 }
510 }
511
512 return result;
513}
514
515static void conn_receive_discard_buffer(HTTP_HANDLE_DATA* http_instance)
516{
517 if (http_instance != NULL)
518 {
519 if (http_instance->received_bytes != NULL)
520 {
521 free(http_instance->received_bytes);
522 http_instance->received_bytes = NULL;
523 }
524 http_instance->received_bytes_count = 0;
525 }
526}
527
528static int readLine(HTTP_HANDLE_DATA* http_instance, char* buf, const size_t maxBufSize)
529{
530 int resultLineSize;
531
532 if ((http_instance == NULL) || (buf == NULL) || (maxBufSize == 0))
533 {
534 LogError("%s", ((http_instance == NULL) ? "Invalid HTTP instance" : "Invalid HTTP buffer"));
535 resultLineSize = -1;
536 }
537 else
538 {
539 char* destByte = buf;
540 /*Codes_SRS_HTTPAPI_COMPACT_21_081: [ The HTTPAPI_ExecuteRequest shall try to read the message with the response up to 20 seconds. ]*/
541 int countRetry = MAX_RECEIVE_RETRY;
542 bool endOfSearch = false;
543 resultLineSize = -1;
544 while (!endOfSearch)
545 {
546 xio_dowork(http_instance->xio_handle);
547
548 /* if any error was detected while receiving then simply break and report it */
549 if (http_instance->is_io_error != 0)
550 {
551 LogError("xio reported error on dowork");
552 endOfSearch = true;
553 }
554 else
555 {
556 unsigned char* receivedByte = http_instance->received_bytes;
557 while (receivedByte < (http_instance->received_bytes + http_instance->received_bytes_count))
558 {
559 if ((*receivedByte) != '\r')
560 {
561 (*destByte) = (*receivedByte);
562 destByte++;
563 receivedByte++;
564
565 if (destByte >= (buf + maxBufSize - 1))
566 {
567 LogError("Received message is bigger than the http buffer");
568 receivedByte = http_instance->received_bytes + http_instance->received_bytes_count;
569 endOfSearch = true;
570 break;
571 }
572 }
573 else
574 {
575 receivedByte++;
576 if ((receivedByte < (http_instance->received_bytes + http_instance->received_bytes_count)) && ((*receivedByte) == '\n'))
577 {
578 receivedByte++;
579 }
580 (*destByte) = '\0';
581 resultLineSize = (int)(destByte - buf);
582 endOfSearch = true;
583 break;
584 }
585 }
586
587 http_instance->received_bytes_count -= (receivedByte - http_instance->received_bytes);
588 if (http_instance->received_bytes_count != 0)
589 {
590 (void)memmove(http_instance->received_bytes, receivedByte, http_instance->received_bytes_count);
591 }
592 else
593 {
594 conn_receive_discard_buffer(http_instance);
595 }
596 }
597
598 if (!endOfSearch)
599 {
600 if ((countRetry--) > 0)
601 {
602 /*Codes_SRS_HTTPAPI_COMPACT_21_083: [ The HTTPAPI_ExecuteRequest shall wait, at least, 100 milliseconds between retries. ]*/
603 ThreadAPI_Sleep(RETRY_INTERVAL_IN_MICROSECONDS);
604 }
605 else
606 {
607 /*Codes_SRS_HTTPAPI_COMPACT_21_082: [ If the HTTPAPI_ExecuteRequest retries 20 seconds to receive the message without success, it shall fail and return HTTPAPI_READ_DATA_FAILED. ]*/
608 LogError("Receive timeout. The HTTP request is incomplete");
609 endOfSearch = true;
610 }
611 }
612 }
613 }
614
615 return resultLineSize;
616}
617
618static int readChunk(HTTP_HANDLE_DATA* http_instance, char* buf, size_t size)
619{
620 int cur, offset;
621
622 // read content with specified length, even if it is received
623 // only in chunks due to fragmentation in the networking layer.
624 // returns -1 in case of error.
625 offset = 0;
626 while (size > (size_t)0)
627 {
628 cur = conn_receive(http_instance, buf + offset, (int)size);
629
630 // end of stream reached
631 if (cur == 0)
632 {
633 break;
634 }
635
636 // read cur bytes (might be less than requested)
637 size -= (size_t)cur;
638 offset += cur;
639 }
640
641 return offset;
642}
643
644static int skipN(HTTP_HANDLE_DATA* http_instance, size_t n)
645{
646 // read and abandon response content with specified length
647 // returns -1 in case of error.
648
649 int result;
650
651 if (http_instance == NULL)
652 {
653 LogError("Invalid HTTP instance");
654 result = -1;
655 }
656 else
657 {
658 /*Codes_SRS_HTTPAPI_COMPACT_21_081: [ The HTTPAPI_ExecuteRequest shall try to read the message with the response up to 20 seconds. ]*/
659 int countRetry = MAX_RECEIVE_RETRY;
660 result = (int)n;
661 while (n > 0)
662 {
663 xio_dowork(http_instance->xio_handle);
664
665 /* if any error was detected while receiving then simply break and report it */
666 if (http_instance->is_io_error != 0)
667 {
668 LogError("xio reported error on dowork");
669 result = -1;
670 n = 0;
671 }
672 else
673 {
674 if (http_instance->received_bytes_count <= n)
675 {
676 n -= http_instance->received_bytes_count;
677 http_instance->received_bytes_count = 0;
678 }
679 else
680 {
681 http_instance->received_bytes_count -= n;
682 (void)memmove(http_instance->received_bytes, http_instance->received_bytes + n, http_instance->received_bytes_count);
683 n = 0;
684 }
685
686 if (n > 0)
687 {
688 if ((countRetry--) > 0)
689 {
690 /*Codes_SRS_HTTPAPI_COMPACT_21_083: [ The HTTPAPI_ExecuteRequest shall wait, at least, 100 milliseconds between retries. ]*/
691 ThreadAPI_Sleep(RETRY_INTERVAL_IN_MICROSECONDS);
692 }
693 else
694 {
695 /*Codes_SRS_HTTPAPI_COMPACT_21_082: [ If the HTTPAPI_ExecuteRequest retries 20 seconds to receive the message without success, it shall fail and return HTTPAPI_READ_DATA_FAILED. ]*/
696 LogError("Receive timeout. The HTTP request is incomplete");
697 n = 0;
698 result = -1;
699 }
700 }
701 }
702 }
703 }
704
705 return result;
706}
707
708
709/*Codes_SRS_HTTPAPI_COMPACT_21_021: [ The HTTPAPI_ExecuteRequest shall execute the http communtication with the provided host, sending a request and reciving the response. ]*/
710static HTTPAPI_RESULT OpenXIOConnection(HTTP_HANDLE_DATA* http_instance)
711{
712 HTTPAPI_RESULT result;
713
714 if (http_instance->is_connected != 0)
715 {
716 /*Codes_SRS_HTTPAPI_COMPACT_21_033: [ If the whole process succeed, the HTTPAPI_ExecuteRequest shall retur HTTPAPI_OK. ]*/
717 result = HTTPAPI_OK;
718 }
719 else
720 {
721 http_instance->is_io_error = 0;
722
723 /*Codes_SRS_HTTPAPI_COMPACT_21_022: [ If a Certificate was provided, the HTTPAPI_ExecuteRequest shall set this option on the transport layer. ]*/
724 if ((http_instance->certificate != NULL) &&
725 (xio_setoption(http_instance->xio_handle, OPTION_TRUSTED_CERT, http_instance->certificate) != 0))
726 {
727 /*Codes_SRS_HTTPAPI_COMPACT_21_023: [ If the transport failed setting the Certificate, the HTTPAPI_ExecuteRequest shall not send any request and return HTTPAPI_SET_OPTION_FAILED. ]*/
728 result = HTTPAPI_SET_OPTION_FAILED;
729 LogInfo("Could not load certificate");
730 }
731 /*Codes_SRS_HTTPAPI_COMPACT_06_003: [ If the x509 client certificate is provided, the HTTPAPI_ExecuteRequest shall set this option on the transport layer. ]*/
732 else if ((http_instance->x509ClientCertificate != NULL) &&
733 (xio_setoption(http_instance->xio_handle, SU_OPTION_X509_CERT, http_instance->x509ClientCertificate) != 0))
734 {
735 /*Codes_SRS_HTTPAPI_COMPACT_06_005: [ If the transport failed setting the client certificate, the HTTPAPI_ExecuteRequest shall not send any request and return HTTPAPI_SET_OPTION_FAILED. ]*/
736 result = HTTPAPI_SET_OPTION_FAILED;
737 LogInfo("Could not load the client certificate");
738 }
739 else if ((http_instance->x509ClientPrivateKey != NULL) &&
740 (xio_setoption(http_instance->xio_handle, SU_OPTION_X509_PRIVATE_KEY, http_instance->x509ClientPrivateKey) != 0))
741 {
742
743 /*Codes_SRS_HTTPAPI_COMPACT_06_006: [ If the transport failed setting the client certificate private key, the HTTPAPI_ExecuteRequest shall not send any request and return HTTPAPI_SET_OPTION_FAILED. ] */
744 result = HTTPAPI_SET_OPTION_FAILED;
745 LogInfo("Could not load the client certificate private key");
746 }
747 else
748 {
749 /*Codes_SRS_HTTPAPI_COMPACT_21_024: [ The HTTPAPI_ExecuteRequest shall open the transport connection with the host to send the request. ]*/
750 if (xio_open(http_instance->xio_handle, on_io_open_complete, http_instance, on_bytes_received, http_instance, on_io_error, http_instance) != 0)
751 {
752 /*Codes_SRS_HTTPAPI_COMPACT_21_025: [ If the open process failed, the HTTPAPI_ExecuteRequest shall not send any request and return HTTPAPI_OPEN_REQUEST_FAILED. ]*/
753 result = HTTPAPI_OPEN_REQUEST_FAILED;
754 }
755 else
756 {
757 int countRetry;
758 /*Codes_SRS_HTTPAPI_COMPACT_21_033: [ If the whole process succeed, the HTTPAPI_ExecuteRequest shall retur HTTPAPI_OK. ]*/
759 result = HTTPAPI_OK;
760 /*Codes_SRS_HTTPAPI_COMPACT_21_077: [ The HTTPAPI_ExecuteRequest shall wait, at least, 10 seconds for the SSL open process. ]*/
761 countRetry = MAX_OPEN_RETRY;
762 while ((http_instance->is_connected == 0) &&
763 (http_instance->is_io_error == 0))
764 {
765 xio_dowork(http_instance->xio_handle);
766 LogInfo("Waiting for TLS connection");
767 if ((countRetry--) < 0)
768 {
769 /*Codes_SRS_HTTPAPI_COMPACT_21_078: [ If the HTTPAPI_ExecuteRequest cannot open the connection in 10 seconds, it shall fail and return HTTPAPI_OPEN_REQUEST_FAILED. ]*/
770 LogError("Open timeout. The HTTP request is incomplete");
771 result = HTTPAPI_OPEN_REQUEST_FAILED;
772 break;
773 }
774 else
775 {
776 /*Codes_SRS_HTTPAPI_COMPACT_21_083: [ The HTTPAPI_ExecuteRequest shall wait, at least, 100 milliseconds between retries. ]*/
777 ThreadAPI_Sleep(RETRY_INTERVAL_IN_MICROSECONDS);
778 }
779 }
780 }
781 }
782 }
783
784 if ((http_instance->is_io_error != 0) && (result == HTTPAPI_OK))
785 {
786 /*Codes_SRS_HTTPAPI_COMPACT_21_025: [ If the open process failed, the HTTPAPI_ExecuteRequest shall not send any request and return HTTPAPI_OPEN_REQUEST_FAILED. ]*/
787 result = HTTPAPI_OPEN_REQUEST_FAILED;
788 }
789
790 return result;
791}
792
793static HTTPAPI_RESULT conn_send_all(HTTP_HANDLE_DATA* http_instance, const unsigned char* buf, size_t bufLen)
794{
795 HTTPAPI_RESULT result;
796
797 http_instance->send_completed = 0;
798 http_instance->is_io_error = 0;
799 if (xio_send(http_instance->xio_handle, buf, bufLen, on_send_complete, http_instance) != 0)
800 {
801 /*Codes_SRS_HTTPAPI_COMPACT_21_028: [ If the HTTPAPI_ExecuteRequest cannot send the request header, it shall return HTTPAPI_HTTP_HEADERS_FAILED. ]*/
802 result = HTTPAPI_SEND_REQUEST_FAILED;
803 }
804 else
805 {
806 /*Codes_SRS_HTTPAPI_COMPACT_21_079: [ The HTTPAPI_ExecuteRequest shall wait, at least, 20 seconds to send a buffer using the SSL connection. ]*/
807 int countRetry = MAX_SEND_RETRY;
808 /*Codes_SRS_HTTPAPI_COMPACT_21_033: [ If the whole process succeed, the HTTPAPI_ExecuteRequest shall retur HTTPAPI_OK. ]*/
809 result = HTTPAPI_OK;
810 while ((http_instance->send_completed == 0) && (result == HTTPAPI_OK))
811 {
812 xio_dowork(http_instance->xio_handle);
813 if (http_instance->is_io_error != 0)
814 {
815 /*Codes_SRS_HTTPAPI_COMPACT_21_028: [ If the HTTPAPI_ExecuteRequest cannot send the request header, it shall return HTTPAPI_HTTP_HEADERS_FAILED. ]*/
816 result = HTTPAPI_SEND_REQUEST_FAILED;
817 }
818 else if ((countRetry--) <= 0)
819 {
820 /*Codes_SRS_HTTPAPI_COMPACT_21_080: [ If the HTTPAPI_ExecuteRequest retries to send the message for 20 seconds without success, it shall fail and return HTTPAPI_SEND_REQUEST_FAILED. ]*/
821 LogError("Send timeout. The HTTP request is incomplete");
822 /*Codes_SRS_HTTPAPI_COMPACT_21_028: [ If the HTTPAPI_ExecuteRequest cannot send the request header, it shall return HTTPAPI_HTTP_HEADERS_FAILED. ]*/
823 result = HTTPAPI_SEND_REQUEST_FAILED;
824 }
825 else
826 {
827 /*Codes_SRS_HTTPAPI_COMPACT_21_083: [ The HTTPAPI_ExecuteRequest shall wait, at least, 100 milliseconds between retries. ]*/
828 ThreadAPI_Sleep(RETRY_INTERVAL_IN_MICROSECONDS);
829 }
830 }
831 }
832
833 return result;
834}
835
836/*Codes_SRS_HTTPAPI_COMPACT_21_035: [ The HTTPAPI_ExecuteRequest shall execute resquest for types GET, POST, PUT, DELETE, PATCH, HEAD. ]*/
837const char httpapiRequestString[6][7] = { "GET", "POST", "PUT", "DELETE", "PATCH", "HEAD" };
838const char* get_request_type(HTTPAPI_REQUEST_TYPE requestType)
839{
840 return (const char*)httpapiRequestString[requestType - HTTPAPI_REQUEST_GET];
841}
842
843/*Codes_SRS_HTTPAPI_COMPACT_21_026: [ If the open process succeed, the HTTPAPI_ExecuteRequest shall send the request message to the host. ]*/
844static HTTPAPI_RESULT SendHeadsToXIO(HTTP_HANDLE_DATA* http_instance, HTTPAPI_REQUEST_TYPE requestType, const char* relativePath, HTTP_HEADERS_HANDLE httpHeadersHandle, size_t headersCount)
845{
846 HTTPAPI_RESULT result;
847 char buf[TEMP_BUFFER_SIZE];
848 int ret;
849
850 //Send request
851 /*Codes_SRS_HTTPAPI_COMPACT_21_038: [ The HTTPAPI_ExecuteRequest shall execute the resquest for the path in relativePath parameter. ]*/
852 /*Codes_SRS_HTTPAPI_COMPACT_21_036: [ The request type shall be provided in the parameter requestType. ]*/
853 if (((ret = snprintf(buf, sizeof(buf), "%s %s HTTP/1.1\r\n", get_request_type(requestType), relativePath)) < 0) ||
854 ((size_t)ret >= sizeof(buf)))
855 {
856 /*Codes_SRS_HTTPAPI_COMPACT_21_027: [ If the HTTPAPI_ExecuteRequest cannot create a buffer to send the request, it shall not send any request and return HTTPAPI_STRING_PROCESSING_ERROR. ]*/
857 result = HTTPAPI_STRING_PROCESSING_ERROR;
858 }
859 /*Codes_SRS_HTTPAPI_COMPACT_21_028: [ If the HTTPAPI_ExecuteRequest cannot send the request header, it shall return HTTPAPI_HTTP_HEADERS_FAILED. ]*/
860 else if ((result = conn_send_all(http_instance, (const unsigned char*)buf, strlen(buf))) == HTTPAPI_OK)
861 {
862 size_t i;
863 //Send default headers
864 /*Codes_SRS_HTTPAPI_COMPACT_21_033: [ If the whole process succeed, the HTTPAPI_ExecuteRequest shall retur HTTPAPI_OK. ]*/
865 for (i = 0; ((i < headersCount) && (result == HTTPAPI_OK)); i++)
866 {
867 char* header;
868 if (HTTPHeaders_GetHeader(httpHeadersHandle, i, &header) != HTTP_HEADERS_OK)
869 {
870 /*Codes_SRS_HTTPAPI_COMPACT_21_027: [ If the HTTPAPI_ExecuteRequest cannot create a buffer to send the request, it shall not send any request and return HTTPAPI_STRING_PROCESSING_ERROR. ]*/
871 result = HTTPAPI_STRING_PROCESSING_ERROR;
872 }
873 else
874 {
875 if ((result = conn_send_all(http_instance, (const unsigned char*)header, strlen(header))) == HTTPAPI_OK)
876 {
877 result = conn_send_all(http_instance, (const unsigned char*)"\r\n", (size_t)2);
878 }
879 free(header);
880 }
881 }
882
883 //Close headers
884 if (result == HTTPAPI_OK)
885 {
886 result = conn_send_all(http_instance, (const unsigned char*)"\r\n", (size_t)2);
887 }
888 }
889 return result;
890}
891
892/*Codes_SRS_HTTPAPI_COMPACT_21_042: [ The request can contain the a content message, provided in content parameter. ]*/
893static HTTPAPI_RESULT SendContentToXIO(HTTP_HANDLE_DATA* http_instance, const unsigned char* content, size_t contentLength)
894{
895 HTTPAPI_RESULT result;
896
897 //Send data (if available)
898 /*Codes_SRS_HTTPAPI_COMPACT_21_045: [ If the contentLength is lower than one, the HTTPAPI_ExecuteRequest shall send the request without content. ]*/
899 if (content && contentLength > 0)
900 {
901 /*Codes_SRS_HTTPAPI_COMPACT_21_044: [ If the content is not NULL, the number of bytes in the content shall be provided in contentLength parameter. ]*/
902 result = conn_send_all(http_instance, content, contentLength);
903 }
904 else
905 {
906 /*Codes_SRS_HTTPAPI_COMPACT_21_043: [ If the content is NULL, the HTTPAPI_ExecuteRequest shall send the request without content. ]*/
907 /*Codes_SRS_HTTPAPI_COMPACT_21_033: [ If the whole process succeed, the HTTPAPI_ExecuteRequest shall retur HTTPAPI_OK. ]*/
908 result = HTTPAPI_OK;
909 }
910 return result;
911}
912
913/*Codes_SRS_HTTPAPI_COMPACT_21_030: [ At the end of the transmission, the HTTPAPI_ExecuteRequest shall receive the response from the host. ]*/
914static HTTPAPI_RESULT ReceiveHeaderFromXIO(HTTP_HANDLE_DATA* http_instance, unsigned int* statusCode)
915{
916 HTTPAPI_RESULT result;
917 char buf[TEMP_BUFFER_SIZE];
918 int ret;
919
920 http_instance->is_io_error = 0;
921
922 //Receive response
923 if (readLine(http_instance, buf, TEMP_BUFFER_SIZE) < 0)
924 {
925 /*Codes_SRS_HTTPAPI_COMPACT_21_032: [ If the HTTPAPI_ExecuteRequest cannot read the message with the request result, it shall return HTTPAPI_READ_DATA_FAILED. ]*/
926 /*Codes_SRS_HTTPAPI_COMPACT_21_082: [ If the HTTPAPI_ExecuteRequest retries 20 seconds to receive the message without success, it shall fail and return HTTPAPI_READ_DATA_FAILED. ]*/
927 result = HTTPAPI_READ_DATA_FAILED;
928 }
929 //Parse HTTP response
930 else if (ParseHttpResponse(buf, &ret) != 1)
931 {
932 //Cannot match string, error
933 /*Codes_SRS_HTTPAPI_COMPACT_21_055: [ If the HTTPAPI_ExecuteRequest cannot parser the received message, it shall return HTTPAPI_RECEIVE_RESPONSE_FAILED. ]*/
934 LogInfo("Not a correct HTTP answer");
935 result = HTTPAPI_RECEIVE_RESPONSE_FAILED;
936 }
937 else
938 {
939 /*Codes_SRS_HTTPAPI_COMPACT_21_046: [ The HTTPAPI_ExecuteRequest shall return the http status reported by the host in the received response. ]*/
940 /*Codes_SRS_HTTPAPI_COMPACT_21_048: [ If the statusCode is NULL, the HTTPAPI_ExecuteRequest shall report not report any status. ]*/
941 if (statusCode)
942 {
943 /*Codes_SRS_HTTPAPI_COMPACT_21_047: [ The HTTPAPI_ExecuteRequest shall report the status in the statusCode parameter. ]*/
944 *statusCode = ret;
945 }
946 /*Codes_SRS_HTTPAPI_COMPACT_21_033: [ If the whole process succeed, the HTTPAPI_ExecuteRequest shall retur HTTPAPI_OK. ]*/
947 result = HTTPAPI_OK;
948 }
949
950 return result;
951}
952
953static HTTPAPI_RESULT ReceiveContentInfoFromXIO(HTTP_HANDLE_DATA* http_instance, HTTP_HEADERS_HANDLE responseHeadersHandle, size_t* bodyLength, bool* chunked)
954{
955 HTTPAPI_RESULT result;
956 char buf[TEMP_BUFFER_SIZE];
957 const char* substr;
958 char* whereIsColon;
959 int lengthInMsg;
960 const char ContentLength[] = "content-length:";
961 const size_t ContentLengthSize = sizeof(ContentLength) - 1;
962 const char TransferEncoding[] = "transfer-encoding:";
963 const size_t TransferEncodingSize = sizeof(TransferEncoding) - 1;
964 const char Chunked[] = "chunked";
965 const size_t ChunkedSize = sizeof(Chunked) - 1;
966
967 http_instance->is_io_error = 0;
968
969 //Read HTTP response headers
970 if (readLine(http_instance, buf, sizeof(buf)) < 0)
971 {
972 /*Codes_SRS_HTTPAPI_COMPACT_21_032: [ If the HTTPAPI_ExecuteRequest cannot read the message with the request result, it shall return HTTPAPI_READ_DATA_FAILED. ]*/
973 /*Codes_SRS_HTTPAPI_COMPACT_21_082: [ If the HTTPAPI_ExecuteRequest retries 20 seconds to receive the message without success, it shall fail and return HTTPAPI_READ_DATA_FAILED. ]*/
974 result = HTTPAPI_READ_DATA_FAILED;
975 }
976 else
977 {
978 /*Codes_SRS_HTTPAPI_COMPACT_21_033: [ If the whole process succeed, the HTTPAPI_ExecuteRequest shall retur HTTPAPI_OK. ]*/
979 result = HTTPAPI_OK;
980
981 while (*buf && (result == HTTPAPI_OK))
982 {
983 if (InternStrnicmp(buf, ContentLength, ContentLengthSize) == 0)
984 {
985 substr = buf + ContentLengthSize;
986 if (ParseStringToDecimal(substr, &lengthInMsg) != 1)
987 {
988 /*Codes_SRS_HTTPAPI_COMPACT_21_032: [ If the HTTPAPI_ExecuteRequest cannot read the message with the request result, it shall return HTTPAPI_READ_DATA_FAILED. ]*/
989 result = HTTPAPI_READ_DATA_FAILED;
990 }
991 else
992 {
993 (*bodyLength) = (size_t)lengthInMsg;
994 }
995 }
996 else if (InternStrnicmp(buf, TransferEncoding, TransferEncodingSize) == 0)
997 {
998 substr = buf + TransferEncodingSize;
999
1000 while (isspace(*substr)) substr++;
1001
1002 if (InternStrnicmp(substr, Chunked, ChunkedSize) == 0)
1003 {
1004 (*chunked) = true;
1005 }
1006 }
1007
1008 if (result == HTTPAPI_OK)
1009 {
1010 whereIsColon = strchr((char*)buf, ':');
1011 /*Codes_SRS_HTTPAPI_COMPACT_21_049: [ If responseHeadersHandle is provide, the HTTPAPI_ExecuteRequest shall prepare a Response Header usign the HTTPHeaders_AddHeaderNameValuePair. ]*/
1012 if (whereIsColon && (responseHeadersHandle != NULL))
1013 {
1014 *whereIsColon = '\0';
1015 HTTPHeaders_AddHeaderNameValuePair(responseHeadersHandle, buf, whereIsColon + 1);
1016 }
1017
1018 if (readLine(http_instance, buf, sizeof(buf)) < 0)
1019 {
1020 /*Codes_SRS_HTTPAPI_COMPACT_21_032: [ If the HTTPAPI_ExecuteRequest cannot read the message with the request result, it shall return HTTPAPI_READ_DATA_FAILED. ]*/
1021 /*Codes_SRS_HTTPAPI_COMPACT_21_082: [ If the HTTPAPI_ExecuteRequest retries 20 seconds to receive the message without success, it shall fail and return HTTPAPI_READ_DATA_FAILED. ]*/
1022 result = HTTPAPI_READ_DATA_FAILED;
1023 }
1024 }
1025 }
1026 }
1027
1028 return result;
1029}
1030
1031static HTTPAPI_RESULT ReadHTTPResponseBodyFromXIO(HTTP_HANDLE_DATA* http_instance, size_t bodyLength, bool chunked, BUFFER_HANDLE responseContent)
1032{
1033 HTTPAPI_RESULT result;
1034 char buf[TEMP_BUFFER_SIZE];
1035 const unsigned char* receivedContent;
1036
1037 http_instance->is_io_error = 0;
1038
1039 //Read HTTP response body
1040 if (!chunked)
1041 {
1042 if (bodyLength)
1043 {
1044 if (responseContent != NULL)
1045 {
1046 if (BUFFER_pre_build(responseContent, bodyLength) != 0)
1047 {
1048 /*Codes_SRS_HTTPAPI_COMPACT_21_052: [ If any memory allocation get fail, the HTTPAPI_ExecuteRequest shall return HTTPAPI_ALLOC_FAILED. ]*/
1049 result = HTTPAPI_ALLOC_FAILED;
1050 }
1051 else if (BUFFER_content(responseContent, &receivedContent) != 0)
1052 {
1053 (void)BUFFER_unbuild(responseContent);
1054
1055 /*Codes_SRS_HTTPAPI_COMPACT_21_052: [ If any memory allocation get fail, the HTTPAPI_ExecuteRequest shall return HTTPAPI_ALLOC_FAILED. ]*/
1056 result = HTTPAPI_ALLOC_FAILED;
1057 }
1058 else if (readChunk(http_instance, (char*)receivedContent, bodyLength) < 0)
1059 {
1060 /*Codes_SRS_HTTPAPI_COMPACT_21_032: [ If the HTTPAPI_ExecuteRequest cannot read the message with the request result, it shall return HTTPAPI_READ_DATA_FAILED. ]*/
1061 result = HTTPAPI_READ_DATA_FAILED;
1062 }
1063 else
1064 {
1065 /*Codes_SRS_HTTPAPI_COMPACT_21_033: [ If the whole process succeed, the HTTPAPI_ExecuteRequest shall retur HTTPAPI_OK. ]*/
1066 result = HTTPAPI_OK;
1067 }
1068 }
1069 else
1070 {
1071 /*Codes_SRS_HTTPAPI_COMPACT_21_051: [ If the responseContent is NULL, the HTTPAPI_ExecuteRequest shall ignore any content in the response. ]*/
1072 if (skipN(http_instance, bodyLength) < 0)
1073 {
1074 /*Codes_SRS_HTTPAPI_COMPACT_21_082: [ If the HTTPAPI_ExecuteRequest retries 20 seconds to receive the message without success, it shall fail and return HTTPAPI_READ_DATA_FAILED. ]*/
1075 result = HTTPAPI_READ_DATA_FAILED;
1076 }
1077 else
1078 {
1079 result = HTTPAPI_OK;
1080 }
1081 }
1082 }
1083 else
1084 {
1085 /*Codes_SRS_HTTPAPI_COMPACT_21_033: [ If the whole process succeed, the HTTPAPI_ExecuteRequest shall retur HTTPAPI_OK. ]*/
1086 result = HTTPAPI_OK;
1087 }
1088 }
1089 else
1090 {
1091 size_t size = 0;
1092 /*Codes_SRS_HTTPAPI_COMPACT_21_033: [ If the whole process succeed, the HTTPAPI_ExecuteRequest shall retur HTTPAPI_OK. ]*/
1093 result = HTTPAPI_OK;
1094 while (result == HTTPAPI_OK)
1095 {
1096 size_t chunkSize;
1097 if (readLine(http_instance, buf, sizeof(buf)) < 0) // read [length in hex]/r/n
1098 {
1099 /*Codes_SRS_HTTPAPI_COMPACT_21_032: [ If the HTTPAPI_ExecuteRequest cannot read the message with the request result, it shall return HTTPAPI_READ_DATA_FAILED. ]*/
1100 /*Codes_SRS_HTTPAPI_COMPACT_21_082: [ If the HTTPAPI_ExecuteRequest retries 20 seconds to receive the message without success, it shall fail and return HTTPAPI_READ_DATA_FAILED. ]*/
1101 result = HTTPAPI_READ_DATA_FAILED;
1102 }
1103 else if (ParseStringToHexadecimal(buf, &chunkSize) != 1) // chunkSize is length of next line (/r/n is not counted)
1104 {
1105 //Cannot match string, error
1106 /*Codes_SRS_HTTPAPI_COMPACT_21_055: [ If the HTTPAPI_ExecuteRequest cannot parser the received message, it shall return HTTPAPI_RECEIVE_RESPONSE_FAILED. ]*/
1107 result = HTTPAPI_RECEIVE_RESPONSE_FAILED;
1108 }
1109 else if (chunkSize == 0)
1110 {
1111 // 0 length means next line is just '\r\n' and end of chunks
1112 if (readChunk(http_instance, (char*)buf, (size_t)2) < 0
1113 || buf[0] != '\r' || buf[1] != '\n') // skip /r/n
1114 {
1115 (void)BUFFER_unbuild(responseContent);
1116
1117 result = HTTPAPI_READ_DATA_FAILED;
1118 }
1119 break;
1120 }
1121 else
1122 {
1123 if (responseContent != NULL)
1124 {
1125 if (BUFFER_enlarge(responseContent, chunkSize) != 0)
1126 {
1127 (void)BUFFER_unbuild(responseContent);
1128
1129 /*Codes_SRS_HTTPAPI_COMPACT_21_052: [ If any memory allocation get fail, the HTTPAPI_ExecuteRequest shall return HTTPAPI_ALLOC_FAILED. ]*/
1130 result = HTTPAPI_ALLOC_FAILED;
1131 }
1132 else if (BUFFER_content(responseContent, &receivedContent) != 0)
1133 {
1134 (void)BUFFER_unbuild(responseContent);
1135
1136 /*Codes_SRS_HTTPAPI_COMPACT_21_052: [ If any memory allocation get fail, the HTTPAPI_ExecuteRequest shall return HTTPAPI_ALLOC_FAILED. ]*/
1137 result = HTTPAPI_ALLOC_FAILED;
1138 }
1139 else if (readChunk(http_instance, (char*)receivedContent + size, chunkSize) < 0)
1140 {
1141 result = HTTPAPI_READ_DATA_FAILED;
1142 }
1143 }
1144 else
1145 {
1146 /*Codes_SRS_HTTPAPI_COMPACT_21_051: [ If the responseContent is NULL, the HTTPAPI_ExecuteRequest shall ignore any content in the response. ]*/
1147 if (skipN(http_instance, chunkSize) < 0)
1148 {
1149 /*Codes_SRS_HTTPAPI_COMPACT_21_082: [ If the HTTPAPI_ExecuteRequest retries 20 seconds to receive the message without success, it shall fail and return HTTPAPI_READ_DATA_FAILED. ]*/
1150 result = HTTPAPI_READ_DATA_FAILED;
1151 }
1152 }
1153
1154 if (result == HTTPAPI_OK)
1155 {
1156 if (readChunk(http_instance, (char*)buf, (size_t)2) < 0
1157 || buf[0] != '\r' || buf[1] != '\n') // skip /r/n
1158 {
1159 result = HTTPAPI_READ_DATA_FAILED;
1160 }
1161 size += chunkSize;
1162 }
1163 }
1164 }
1165
1166 }
1167 return result;
1168}
1169
1170
1171/*Codes_SRS_HTTPAPI_COMPACT_21_037: [ If the request type is unknown, the HTTPAPI_ExecuteRequest shall return HTTPAPI_INVALID_ARG. ]*/
1172static bool validRequestType(HTTPAPI_REQUEST_TYPE requestType)
1173{
1174 bool result;
1175
1176 if ((requestType == HTTPAPI_REQUEST_GET) ||
1177 (requestType == HTTPAPI_REQUEST_POST) ||
1178 (requestType == HTTPAPI_REQUEST_PUT) ||
1179 (requestType == HTTPAPI_REQUEST_DELETE) ||
1180 (requestType == HTTPAPI_REQUEST_PATCH) ||
1181 (requestType == HTTPAPI_REQUEST_HEAD))
1182 {
1183 result = true;
1184 }
1185 else
1186 {
1187 result = false;
1188 }
1189
1190 return result;
1191}
1192
1193/*Codes_SRS_HTTPAPI_COMPACT_21_021: [ The HTTPAPI_ExecuteRequest shall execute the http communtication with the provided host, sending a request and reciving the response. ]*/
1194/*Codes_SRS_HTTPAPI_COMPACT_21_050: [ If there is a content in the response, the HTTPAPI_ExecuteRequest shall copy it in the responseContent buffer. ]*/
1195//Note: This function assumes that "Host:" and "Content-Length:" headers are setup
1196// by the caller of HTTPAPI_ExecuteRequest() (which is true for httptransport.c).
1197HTTPAPI_RESULT HTTPAPI_ExecuteRequest(HTTP_HANDLE handle, HTTPAPI_REQUEST_TYPE requestType, const char* relativePath,
1198 HTTP_HEADERS_HANDLE httpHeadersHandle, const unsigned char* content,
1199 size_t contentLength, unsigned int* statusCode,
1200 HTTP_HEADERS_HANDLE responseHeadersHandle, BUFFER_HANDLE responseContent)
1201{
1202 HTTPAPI_RESULT result = HTTPAPI_ERROR;
1203 size_t headersCount;
1204 size_t bodyLength = 0;
1205 bool chunked = false;
1206 HTTP_HANDLE_DATA* http_instance = (HTTP_HANDLE_DATA*)handle;
1207
1208 /*Codes_SRS_HTTPAPI_COMPACT_21_034: [ If there is no previous connection, the HTTPAPI_ExecuteRequest shall return HTTPAPI_INVALID_ARG. ]*/
1209 /*Codes_SRS_HTTPAPI_COMPACT_21_037: [ If the request type is unknown, the HTTPAPI_ExecuteRequest shall return HTTPAPI_INVALID_ARG. ]*/
1210 /*Codes_SRS_HTTPAPI_COMPACT_21_039: [ If the relativePath is NULL or invalid, the HTTPAPI_ExecuteRequest shall return HTTPAPI_INVALID_ARG. ]*/
1211 /*Codes_SRS_HTTPAPI_COMPACT_21_041: [ If the httpHeadersHandle is NULL or invalid, the HTTPAPI_ExecuteRequest shall return HTTPAPI_INVALID_ARG. ]*/
1212 /*Codes_SRS_HTTPAPI_COMPACT_21_053: [ The HTTPAPI_ExecuteRequest shall produce a set of http header to send to the host. ]*/
1213 /*Codes_SRS_HTTPAPI_COMPACT_21_040: [ The request shall contain the http header provided in httpHeadersHandle parameter. ]*/
1214 /*Codes_SRS_HTTPAPI_COMPACT_21_054: [ If Http header maker cannot provide the number of headers, the HTTPAPI_ExecuteRequest shall return HTTPAPI_INVALID_ARG. ]*/
1215 if (http_instance == NULL ||
1216 relativePath == NULL ||
1217 httpHeadersHandle == NULL ||
1218 !validRequestType(requestType) ||
1219 HTTPHeaders_GetHeaderCount(httpHeadersHandle, &headersCount) != HTTP_HEADERS_OK)
1220 {
1221 result = HTTPAPI_INVALID_ARG;
1222 LogError("(result = %" PRI_MU_ENUM ")", MU_ENUM_VALUE(HTTPAPI_RESULT, result));
1223 }
1224 /*Codes_SRS_HTTPAPI_COMPACT_21_024: [ The HTTPAPI_ExecuteRequest shall open the transport connection with the host to send the request. ]*/
1225 else if ((result = OpenXIOConnection(http_instance)) != HTTPAPI_OK)
1226 {
1227 LogError("Open HTTP connection failed (result = %" PRI_MU_ENUM ")", MU_ENUM_VALUE(HTTPAPI_RESULT, result));
1228 }
1229 /*Codes_SRS_HTTPAPI_COMPACT_21_026: [ If the open process succeed, the HTTPAPI_ExecuteRequest shall send the request message to the host. ]*/
1230 else if ((result = SendHeadsToXIO(http_instance, requestType, relativePath, httpHeadersHandle, headersCount)) != HTTPAPI_OK)
1231 {
1232 LogError("Send heads to HTTP failed (result = %" PRI_MU_ENUM ")", MU_ENUM_VALUE(HTTPAPI_RESULT, result));
1233 }
1234 /*Codes_SRS_HTTPAPI_COMPACT_21_042: [ The request can contain the a content message, provided in content parameter. ]*/
1235 else if ((result = SendContentToXIO(http_instance, content, contentLength)) != HTTPAPI_OK)
1236 {
1237 LogError("Send content to HTTP failed (result = %" PRI_MU_ENUM ")", MU_ENUM_VALUE(HTTPAPI_RESULT, result));
1238 }
1239 /*Codes_SRS_HTTPAPI_COMPACT_21_030: [ At the end of the transmission, the HTTPAPI_ExecuteRequest shall receive the response from the host. ]*/
1240 /*Codes_SRS_HTTPAPI_COMPACT_21_073: [ The message received by the HTTPAPI_ExecuteRequest shall starts with a valid header. ]*/
1241 else if ((result = ReceiveHeaderFromXIO(http_instance, statusCode)) != HTTPAPI_OK)
1242 {
1243 LogError("Receive header from HTTP failed (result = %" PRI_MU_ENUM ")", MU_ENUM_VALUE(HTTPAPI_RESULT, result));
1244 }
1245 /*Codes_SRS_HTTPAPI_COMPACT_21_074: [ After the header, the message received by the HTTPAPI_ExecuteRequest can contain addition information about the content. ]*/
1246 else if ((result = ReceiveContentInfoFromXIO(http_instance, responseHeadersHandle, &bodyLength, &chunked)) != HTTPAPI_OK)
1247 {
1248 LogError("Receive content information from HTTP failed (result = %" PRI_MU_ENUM ")", MU_ENUM_VALUE(HTTPAPI_RESULT, result));
1249 }
1250 /*Codes_SRS_HTTPAPI_COMPACT_42_084: [ The message received by the HTTPAPI_ExecuteRequest should not contain http body. ]*/
1251 else if (requestType != HTTPAPI_REQUEST_HEAD)
1252 {
1253 /*Codes_SRS_HTTPAPI_COMPACT_21_075: [ The message received by the HTTPAPI_ExecuteRequest can contain a body with the message content. ]*/
1254 if ((result = ReadHTTPResponseBodyFromXIO(http_instance, bodyLength, chunked, responseContent)) != HTTPAPI_OK)
1255 {
1256 LogError("Read HTTP response body from HTTP failed (result = %" PRI_MU_ENUM ")", MU_ENUM_VALUE(HTTPAPI_RESULT, result));
1257 }
1258 }
1259
1260 conn_receive_discard_buffer(http_instance);
1261
1262 return result;
1263}
1264
1265/*Codes_SRS_HTTPAPI_COMPACT_21_056: [ The HTTPAPI_SetOption shall change the HTTP options. ]*/
1266/*Codes_SRS_HTTPAPI_COMPACT_21_057: [ The HTTPAPI_SetOption shall receive a handle that identiry the HTTP connection. ]*/
1267/*Codes_SRS_HTTPAPI_COMPACT_21_058: [ The HTTPAPI_SetOption shall receive the option as a pair optionName/value. ]*/
1268HTTPAPI_RESULT HTTPAPI_SetOption(HTTP_HANDLE handle, const char* optionName, const void* value)
1269{
1270 HTTPAPI_RESULT result;
1271 HTTP_HANDLE_DATA* http_instance = (HTTP_HANDLE_DATA*)handle;
1272
1273 if (
1274 (http_instance == NULL) ||
1275 (optionName == NULL) ||
1276 (value == NULL)
1277 )
1278 {
1279 /*Codes_SRS_HTTPAPI_COMPACT_21_059: [ If the handle is NULL, the HTTPAPI_SetOption shall return HTTPAPI_INVALID_ARG. ]*/
1280 /*Codes_SRS_HTTPAPI_COMPACT_21_060: [ If the optionName is NULL, the HTTPAPI_SetOption shall return HTTPAPI_INVALID_ARG. ]*/
1281 /*Codes_SRS_HTTPAPI_COMPACT_21_061: [ If the value is NULL, the HTTPAPI_SetOption shall return HTTPAPI_INVALID_ARG. ]*/
1282 result = HTTPAPI_INVALID_ARG;
1283 }
1284 else if (strcmp(OPTION_TRUSTED_CERT, optionName) == 0)
1285 {
1286#ifdef DO_NOT_COPY_TRUSTED_CERTS_STRING
1287 result = HTTPAPI_OK;
1288 http_instance->certificate = (char*)value;
1289#else
1290 int len;
1291
1292 if (http_instance->certificate)
1293 {
1294 free(http_instance->certificate);
1295 }
1296
1297 len = (int)strlen((char*)value);
1298 http_instance->certificate = (char*)malloc((len + 1) * sizeof(char));
1299 if (http_instance->certificate == NULL)
1300 {
1301 /*Codes_SRS_HTTPAPI_COMPACT_21_062: [ If any memory allocation get fail, the HTTPAPI_SetOption shall return HTTPAPI_ALLOC_FAILED. ]*/
1302 result = HTTPAPI_ALLOC_FAILED;
1303 LogInfo("unable to allocate memory for the certificate in HTTPAPI_SetOption");
1304 }
1305 else
1306 {
1307 /*Codes_SRS_HTTPAPI_COMPACT_21_064: [ If the HTTPAPI_SetOption get success setting the option, it shall return HTTPAPI_OK. ]*/
1308 (void)strcpy(http_instance->certificate, (const char*)value);
1309 result = HTTPAPI_OK;
1310 }
1311#endif // DO_NOT_COPY_TRUSTED_CERTS_STRING
1312 }
1313 else if (strcmp(SU_OPTION_X509_CERT, optionName) == 0 || strcmp(OPTION_X509_ECC_CERT, optionName) == 0)
1314 {
1315 int len;
1316 if (http_instance->x509ClientCertificate)
1317 {
1318 free(http_instance->x509ClientCertificate);
1319 }
1320
1321 len = (int)strlen((char*)value);
1322 http_instance->x509ClientCertificate = (char*)malloc((len + 1) * sizeof(char));
1323 if (http_instance->x509ClientCertificate == NULL)
1324 {
1325 /*Codes_SRS_HTTPAPI_COMPACT_21_062: [ If any memory allocation get fail, the HTTPAPI_SetOption shall return HTTPAPI_ALLOC_FAILED. ]*/
1326 result = HTTPAPI_ALLOC_FAILED;
1327 LogInfo("unable to allocate memory for the client certificate in HTTPAPI_SetOption");
1328 }
1329 else
1330 {
1331 /*Codes_SRS_HTTPAPI_COMPACT_21_064: [ If the HTTPAPI_SetOption get success setting the option, it shall return HTTPAPI_OK. ]*/
1332 (void)strcpy(http_instance->x509ClientCertificate, (const char*)value);
1333 result = HTTPAPI_OK;
1334 }
1335 }
1336 else if (strcmp(SU_OPTION_X509_PRIVATE_KEY, optionName) == 0 || strcmp(OPTION_X509_ECC_KEY, optionName) == 0)
1337 {
1338 int len;
1339 if (http_instance->x509ClientPrivateKey)
1340 {
1341 free(http_instance->x509ClientPrivateKey);
1342 }
1343
1344 len = (int)strlen((char*)value);
1345 http_instance->x509ClientPrivateKey = (char*)malloc((len + 1) * sizeof(char));
1346 if (http_instance->x509ClientPrivateKey == NULL)
1347 {
1348 /*Codes_SRS_HTTPAPI_COMPACT_21_062: [ If any memory allocation get fail, the HTTPAPI_SetOption shall return HTTPAPI_ALLOC_FAILED. ]*/
1349 result = HTTPAPI_ALLOC_FAILED;
1350 LogInfo("unable to allocate memory for the client private key in HTTPAPI_SetOption");
1351 }
1352 else
1353 {
1354 /*Codes_SRS_HTTPAPI_COMPACT_21_064: [ If the HTTPAPI_SetOption get success setting the option, it shall return HTTPAPI_OK. ]*/
1355 (void)strcpy(http_instance->x509ClientPrivateKey, (const char*)value);
1356 result = HTTPAPI_OK;
1357 }
1358 }
1359 else if (strcmp(OPTION_HTTP_PROXY, optionName) == 0)
1360 {
1361 TLSIO_CONFIG tlsio_config;
1362 HTTP_PROXY_IO_CONFIG proxy_config;
1363 HTTP_PROXY_OPTIONS* proxy_options = (HTTP_PROXY_OPTIONS*)value;
1364
1365 if (proxy_options->host_address == NULL)
1366 {
1367 LogError("NULL host_address in proxy options");
1368 result = HTTPAPI_ERROR;
1369 }
1370 else if (((proxy_options->username == NULL) || (proxy_options->password == NULL)) &&
1371 (proxy_options->username != proxy_options->password))
1372 {
1373 LogError("Only one of username and password for proxy settings was NULL");
1374 result = HTTPAPI_ERROR;
1375 }
1376 else
1377 {
1378
1379 /* Workaround: xio interface is already created when HTTPAPI_CreateConnection is call without proxy support
1380 * need to destroy the interface and create a new one with proxy information
1381 */
1382 OPTIONHANDLER_HANDLE xio_options;
1383 if ((xio_options = xio_retrieveoptions(http_instance->xio_handle)) == NULL)
1384 {
1385 LogError("failed saving underlying I/O transport options");
1386 result = HTTPAPI_ERROR;
1387 }
1388 else
1389 {
1390 xio_destroy(http_instance->xio_handle);
1391
1392 proxy_config.hostname = http_instance->hostName;
1393 proxy_config.proxy_hostname = proxy_options->host_address;
1394 proxy_config.password = proxy_options->password;
1395 proxy_config.username = proxy_options->username;
1396 proxy_config.proxy_port = proxy_options->port;
1397 proxy_config.port = 443;
1398
1399 tlsio_config.hostname = http_instance->hostName;
1400 tlsio_config.port = 443;
1401 tlsio_config.underlying_io_interface = http_proxy_io_get_interface_description();
1402 tlsio_config.underlying_io_parameters = &proxy_config;
1403
1404 http_instance->xio_handle = xio_create(platform_get_default_tlsio(), (void*)&tlsio_config);
1405
1406 if (http_instance->xio_handle == NULL)
1407 {
1408 LogError("Failed to create xio handle with proxy configuration");
1409 result = HTTPAPI_ERROR;
1410 }
1411 else
1412 {
1413 if (OptionHandler_FeedOptions(xio_options, http_instance->xio_handle) != OPTIONHANDLER_OK)
1414 {
1415 LogError("Failed feeding existing options to new xio instance.");
1416 result = HTTPAPI_ERROR;
1417 }
1418 else
1419 {
1420 result = HTTPAPI_OK;
1421 }
1422 }
1423
1424 OptionHandler_Destroy(xio_options);
1425 }
1426 }
1427 }
1428 else
1429 {
1430 /*Codes_SRS_HTTPAPI_COMPACT_21_063: [ If the HTTP do not support the optionName, the HTTPAPI_SetOption shall return HTTPAPI_INVALID_ARG. ]*/
1431 result = HTTPAPI_INVALID_ARG;
1432 LogInfo("unknown option %s", optionName);
1433 }
1434
1435 return result;
1436}
1437
1438/*Codes_SRS_HTTPAPI_COMPACT_21_065: [ The HTTPAPI_CloneOption shall provide the means to clone the HTTP option. ]*/
1439/*Codes_SRS_HTTPAPI_COMPACT_21_066: [ The HTTPAPI_CloneOption shall return a clone of the value identified by the optionName. ]*/
1440HTTPAPI_RESULT HTTPAPI_CloneOption(const char* optionName, const void* value, const void** savedValue)
1441{
1442 HTTPAPI_RESULT result;
1443 size_t certLen;
1444 char* tempCert;
1445
1446 if (
1447 (optionName == NULL) ||
1448 (value == NULL) ||
1449 (savedValue == NULL)
1450 )
1451 {
1452 /*Codes_SRS_HTTPAPI_COMPACT_21_067: [ If the optionName is NULL, the HTTPAPI_CloneOption shall return HTTPAPI_INVALID_ARG. ]*/
1453 /*Codes_SRS_HTTPAPI_COMPACT_21_068: [ If the value is NULL, the HTTPAPI_CloneOption shall return HTTPAPI_INVALID_ARG. ]*/
1454 /*Codes_SRS_HTTPAPI_COMPACT_21_069: [ If the savedValue is NULL, the HTTPAPI_CloneOption shall return HTTPAPI_INVALID_ARG. ]*/
1455 result = HTTPAPI_INVALID_ARG;
1456 }
1457 else if (strcmp(OPTION_TRUSTED_CERT, optionName) == 0)
1458 {
1459#ifdef DO_NOT_COPY_TRUSTED_CERTS_STRING
1460 *savedValue = (const void*)value;
1461 result = HTTPAPI_OK;
1462#else
1463 certLen = strlen((const char*)value);
1464 tempCert = (char*)malloc((certLen + 1) * sizeof(char));
1465 if (tempCert == NULL)
1466 {
1467 /*Codes_SRS_HTTPAPI_COMPACT_21_070: [ If any memory allocation get fail, the HTTPAPI_CloneOption shall return HTTPAPI_ALLOC_FAILED. ]*/
1468 result = HTTPAPI_ALLOC_FAILED;
1469 }
1470 else
1471 {
1472 /*Codes_SRS_HTTPAPI_COMPACT_21_072: [ If the HTTPAPI_CloneOption get success setting the option, it shall return HTTPAPI_OK. ]*/
1473 (void)strcpy(tempCert, (const char*)value);
1474 *savedValue = tempCert;
1475 result = HTTPAPI_OK;
1476 }
1477#endif // DO_NOT_COPY_TRUSTED_CERTS_STRING
1478 }
1479 else if (strcmp(SU_OPTION_X509_CERT, optionName) == 0 || strcmp(OPTION_X509_ECC_CERT, optionName) == 0)
1480 {
1481 certLen = strlen((const char*)value);
1482 tempCert = (char*)malloc((certLen + 1) * sizeof(char));
1483 if (tempCert == NULL)
1484 {
1485 /*Codes_SRS_HTTPAPI_COMPACT_21_070: [ If any memory allocation get fail, the HTTPAPI_CloneOption shall return HTTPAPI_ALLOC_FAILED. ]*/
1486 result = HTTPAPI_ALLOC_FAILED;
1487 }
1488 else
1489 {
1490 /*Codes_SRS_HTTPAPI_COMPACT_21_072: [ If the HTTPAPI_CloneOption get success setting the option, it shall return HTTPAPI_OK. ]*/
1491 (void)strcpy(tempCert, (const char*)value);
1492 *savedValue = tempCert;
1493 result = HTTPAPI_OK;
1494 }
1495 }
1496 else if (strcmp(SU_OPTION_X509_PRIVATE_KEY, optionName) == 0 || strcmp(OPTION_X509_ECC_KEY, optionName) == 0)
1497 {
1498 certLen = strlen((const char*)value);
1499 tempCert = (char*)malloc((certLen + 1) * sizeof(char));
1500 if (tempCert == NULL)
1501 {
1502 /*Codes_SRS_HTTPAPI_COMPACT_21_070: [ If any memory allocation get fail, the HTTPAPI_CloneOption shall return HTTPAPI_ALLOC_FAILED. ]*/
1503 result = HTTPAPI_ALLOC_FAILED;
1504 }
1505 else
1506 {
1507 /*Codes_SRS_HTTPAPI_COMPACT_21_072: [ If the HTTPAPI_CloneOption get success setting the option, it shall return HTTPAPI_OK. ]*/
1508 (void)strcpy(tempCert, (const char*)value);
1509 *savedValue = tempCert;
1510 result = HTTPAPI_OK;
1511 }
1512 }
1513 else if (strcmp(OPTION_HTTP_PROXY, optionName) == 0)
1514 {
1515 HTTP_PROXY_OPTIONS* proxy_data = (HTTP_PROXY_OPTIONS*)value;
1516
1517 HTTP_PROXY_OPTIONS* new_proxy_info = malloc(sizeof(HTTP_PROXY_OPTIONS));
1518 if (new_proxy_info == NULL)
1519 {
1520 LogError("unable to allocate proxy option information");
1521 result = HTTPAPI_ERROR;
1522 }
1523 else
1524 {
1525 new_proxy_info->host_address = proxy_data->host_address;
1526 new_proxy_info->port = proxy_data->port;
1527 new_proxy_info->password = proxy_data->password;
1528 new_proxy_info->username = proxy_data->username;
1529 *savedValue = new_proxy_info;
1530 result = HTTPAPI_OK;
1531 }
1532 }
1533 else
1534 {
1535 /*Codes_SRS_HTTPAPI_COMPACT_21_071: [ If the HTTP do not support the optionName, the HTTPAPI_CloneOption shall return HTTPAPI_INVALID_ARG. ]*/
1536 result = HTTPAPI_INVALID_ARG;
1537 LogInfo("unknown option %s", optionName);
1538 }
1539 return result;
1540}
Note: See TracBrowser for help on using the repository browser.