qLibc
qhttpclient.c
Go to the documentation of this file.
1/******************************************************************************
2 * qLibc
3 *
4 * Copyright (c) 2010-2026 Seungyoung Kim.
5 * All rights reserved.
6 *
7 * Redistribution and use in source and binary forms, with or without
8 * modification, are permitted provided that the following conditions are met:
9 *
10 * 1. Redistributions of source code must retain the above copyright notice,
11 * this list of conditions and the following disclaimer.
12 * 2. Redistributions in binary form must reproduce the above copyright notice,
13 * this list of conditions and the following disclaimer in the documentation
14 * and/or other materials provided with the distribution.
15 *
16 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
17 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
18 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
19 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
20 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
21 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
22 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
23 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
24 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
25 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
26 * POSSIBILITY OF SUCH DAMAGE.
27 *****************************************************************************/
28
29/**
30 * @file qhttpclient.c HTTP client object.
31 *
32 * qhttpclient implements HTTP client.
33 *
34 * Example code for simple HTTP GET operation.
35 *
36 * @code
37 * #define REMOTE_URL "/robots.txt"
38 * #define SAVEFILE "/tmp/robots.txt"
39 *
40 * int main(void) {
41 * // create new HTTP client
42 * qhttpclient_t *httpclient = qhttpclient("https://secure.qdecoder.org", 0);
43 * if(httpclient == NULL) return -1;
44 *
45 * // open file for writing
46 * int nFd = open(SAVEFILE, O_CREAT | O_TRUNC | O_WRONLY, 0644);
47 * if(nFd < 0) {
48 * httpclient->free(httpclient);
49 * return -1;
50 * }
51 *
52 * // container for storing response headers for debugging purposes
53 * qlisttbl_t *resheaders = qlisttbl(QLISTTBL_UNIQUE | QLISTTBL_CASEINSENSITIVE);
54 *
55 * // download
56 * off_t nSavesize = 0;
57 * int nRescode = 0;
58 * bool bRet = httpclient->get(httpclient, REMOTE_URL, nFd, &nSavesize,
59 * &nRescode, NULL, resheaders, NULL, NULL);
60 *
61 * // close file
62 * close(nFd);
63 *
64 * // print out debugging info
65 * printf("%s %d, %d bytes saved\n", (bRet?"Success":"Failed"), nRescode,
66 * (int)nSavesize);
67 * resheaders->debug(resheaders, stdout);
68 *
69 * // free HTTP client object
70 * httpclient->free(httpclient);
71 *
72 * return (bRet ? 0 : -1);
73 * }
74 *
75 * [Output]
76 * Success 200, 30 bytes saved
77 * Date=Fri, 11 Feb 2011 23:40:50 GMT? (30)
78 * Server=Apache? (7)
79 * Last-Modified=Sun, 15 Mar 2009 11:43:07 GMT? (30)
80 * ETag="2e5c9d-1e-46526d665c8c0"? (26)
81 * Accept-Ranges=bytes? (6)
82 * Content-Length=30? (3)
83 * Cache-Control=max-age=604800? (15)
84 * Expires=Fri, 18 Feb 2011 23:40:50 GMT? (30)
85 * Connection=close? (6)
86 * Content-Type=text/plain? (11)
87 * @endcode
88 *
89 * Example code for multiple PUT operation using same keep-alive connection.
90 *
91 * @code
92 * // create new HTTP client
93 * qhttpclient_t *httpclient = qhttpclient("www.qdecoder.org", 80);
94 * if(httpclient == NULL) return;
95 *
96 * // set options
97 * httpclient->setkeepalive(httpclient, true);
98 *
99 * // make a connection
100 * if(httpclient->open(httpclient) == false) return;
101 *
102 * // upload files
103 * httpclient->put(httpclient, ...);
104 * httpclient->put(httpclient, ...); // will be done within same connection.
105 *
106 * // close connection - not necessary if we call free() just after this.
107 * httpclient->close(httpclient);
108 *
109 * // free HTTP client object
110 * httpclient->free(httpclient);
111 * @endcode
112 */
113
114#ifndef DISABLE_QHTTPCLIENT
115
116#include <stdio.h>
117#include <stdlib.h>
118#include <stdbool.h>
119#include <string.h>
120#include <unistd.h>
121#include <fcntl.h>
122#include <errno.h>
123#include <sys/types.h>
124#include <sys/socket.h>
125#include <netinet/in.h>
126#include <netinet/tcp.h>
127#include <arpa/inet.h>
128
129#ifdef ENABLE_OPENSSL
130#include "openssl/ssl.h"
131#include "openssl/err.h"
132#endif
133
134#include "qinternal.h"
135#include "utilities/qio.h"
136#include "utilities/qstring.h"
137#include "utilities/qsocket.h"
138#include "containers/qlisttbl.h"
139#include "containers/qgrow.h"
140#include "extensions/qhttpclient.h"
141
142#ifndef _DOXYGEN_SKIP
143
144static bool open_(qhttpclient_t *client);
145static bool setssl(qhttpclient_t *client);
146static void settimeout(qhttpclient_t *client, int timeoutms);
147static void setkeepalive(qhttpclient_t *client, bool keepalive);
148static void setuseragent(qhttpclient_t *client, const char *agentname);
149
150static bool head(qhttpclient_t *client, const char *uri, int *rescode,
151 qlisttbl_t *reqheaders, qlisttbl_t *resheaders);
152static bool get(qhttpclient_t *client, const char *uri, int fd, off_t *savesize,
153 int *rescode, qlisttbl_t *reqheaders, qlisttbl_t *resheaders,
154 bool (*callback)(void *userdata, off_t recvbytes),
155 void *userdata);
156static bool put(qhttpclient_t *client, const char *uri, int fd, off_t length,
157 int *rescode, qlisttbl_t *reqheaders, qlisttbl_t *resheaders,
158 bool (*callback)(void *userdata, off_t sentbytes),
159 void *userdata);
160static void *cmd(qhttpclient_t *client, const char *method, const char *uri,
161 void *data, size_t size, int *rescode, size_t *contentslength,
162 qlisttbl_t *reqheaders, qlisttbl_t *resheaders);
163
164static bool sendrequest(qhttpclient_t *client, const char *method,
165 const char *uri, qlisttbl_t *reqheaders);
166static int readresponse(qhttpclient_t *client, qlisttbl_t *resheaders,
167 off_t *contentlength);
168
169static ssize_t gets_(qhttpclient_t *client, char *buf, size_t bufsize);
170static ssize_t read_(qhttpclient_t *client, void *buf, size_t nbytes);
171static ssize_t write_(qhttpclient_t *client, const void *buf, size_t nbytes);
172static off_t recvfile(qhttpclient_t *client, int fd, off_t nbytes);
173static off_t sendfile_(qhttpclient_t *client, int fd, off_t nbytes);
174
175static bool _close(qhttpclient_t *client);
176static void _free(qhttpclient_t *client);
177
178// internal usages
179static bool _set_socket_option(int socket);
180static bool _parse_uri(const char *uri, bool *protocol, char *hostname,
181 size_t namesize, int *port);
182
183#endif
184
185//
186// HTTP RESPONSE CODE
187//
188#define HTTP_NO_RESPONSE (0)
189#define HTTP_CODE_CONTINUE (100)
190#define HTTP_CODE_OK (200)
191#define HTTP_CODE_CREATED (201)
192#define HTTP_CODE_NO_CONTENT (204)
193#define HTTP_CODE_MULTI_STATUS (207)
194#define HTTP_CODE_MOVED_TEMPORARILY (302)
195#define HTTP_CODE_NOT_MODIFIED (304)
196#define HTTP_CODE_BAD_REQUEST (400)
197#define HTTP_CODE_FORBIDDEN (403)
198#define HTTP_CODE_NOT_FOUND (404)
199#define HTTP_CODE_METHOD_NOT_ALLOWED (405)
200#define HTTP_CODE_REQUEST_TIME_OUT (408)
201#define HTTP_CODE_REQUEST_URI_TOO_LONG (414)
202#define HTTP_CODE_INTERNAL_SERVER_ERROR (500)
203#define HTTP_CODE_NOT_IMPLEMENTED (501)
204#define HTTP_CODE_SERVICE_UNAVAILABLE (503)
205
206#define HTTP_PROTOCOL_11 "HTTP/1.1"
207
208//
209// TCP SOCKET DEFINITION
210//
211#define SET_TCP_LINGER_TIMEOUT (15) /*< linger seconds, 0 for disable */
212#define SET_TCP_NODELAY (1) /*< 0 for disable */
213#define MAX_SHUTDOWN_WAIT (100) /*< maximum shutdown wait, unit is ms */
214#define MAX_ATOMIC_DATA_SIZE (32 * 1024) /*< maximum sending bytes */
215
216#ifdef ENABLE_OPENSSL
217struct SslConn {
218 SSL *ssl;
219 SSL_CTX *ctx;
220};
221#endif
222
223/**
224 * Initialize and create a new HTTP client.
225 *
226 * @param destname remote address. This can be an IP address, FQDN, or URI.
227 * @param port remote port number. This can be 0 when `destname` is a URI.
228 *
229 * @return HTTP client object on success, or NULL on failure.
230 *
231 * @code
232 * qhttpclient_t *client = qhttpclient("1.2.3.4", 80);
233 * qhttpclient_t *client = qhttpclient("www.qdecoder.org", 80);
234 * qhttpclient_t *client = qhttpclient("http://www.qdecoder.org", 0);
235 * qhttpclient_t *client = qhttpclient("http://www.qdecoder.org:80", 0);
236 * qhttpclient_t *client = qhttpclient("https://www.qdecoder.org", 0);
237 * qhttpclient_t *client = qhttpclient("https://www.qdecoder.org:443", 0);
238 * @endcode
239 *
240 * @note
241 * Keep-alive feature is turned off by default. Turn it on by calling
242 * setkeepalive(). If destname is URI string starting with
243 * "https://", setssl() will be called internally.
244 */
245qhttpclient_t *qhttpclient(const char *destname, int port) {
246 bool ishttps = false;
247 char hostname[256];
248 if (port == 0 || strstr(destname, "://") != NULL) {
249 if (_parse_uri(destname, &ishttps, hostname, sizeof(hostname), &port)
250 == false) {
251 DEBUG("Can't parse URI %s", destname);
252 return NULL;
253 }
254
255 DEBUG("https: %d, hostname: %s, port:%d\n", ishttps, hostname, port);
256 } else {
257 qstrcpy(hostname, sizeof(hostname), destname);
258 }
259
260 // get remote address
261 struct sockaddr_in addr;
262 if (qsocket_get_addr(&addr, hostname, port) == false) {
263 return NULL;
264 }
265
266 // allocate object
267 qhttpclient_t *client = (qhttpclient_t *) malloc(sizeof(qhttpclient_t));
268 if (client == NULL)
269 return NULL;
270 memset((void *) client, 0, sizeof(qhttpclient_t));
271
272 // initialize object
273 client->socket = -1;
274
275 memcpy((void *) &client->addr, (void *) &addr, sizeof(client->addr));
276 client->hostname = strdup(hostname);
277 client->port = port;
278
279 // member methods
280 client->setssl = setssl;
281 client->settimeout = settimeout;
282 client->setkeepalive = setkeepalive;
283 client->setuseragent = setuseragent;
284
285 client->open = open_;
286
287 client->head = head;
288 client->get = get;
289 client->put = put;
290 client->cmd = cmd;
291
292 client->sendrequest = sendrequest;
293 client->readresponse = readresponse;
294
295 client->gets = gets_;
296 client->read = read_;
297 client->write = write_;
298 client->recvfile = recvfile;
299 client->sendfile = sendfile_;
300
301 client->close = _close;
302 client->free = _free;
303
304 // init client
305 settimeout(client, 0);
306 setkeepalive(client, false);
307 setuseragent(client, QHTTPCLIENT_NAME);
308 if (ishttps == true)
309 setssl(client);
310
311 return client;
312}
313
314/**
315 * qhttpclient->setssl(): Enable HTTPS for the connection.
316 *
317 * @param client qhttpclient object pointer
318 *
319 * @code
320 * httpclient->setssl(httpclient);
321 * @endcode
322 */
323static bool setssl(qhttpclient_t *client) {
324#ifdef ENABLE_OPENSSL
325 static bool initialized = false;
326
327 if (client->socket >= 0) {
328 // must be set before making a connection.
329 return false;
330 }
331
332 // init openssl
333 if (initialized == false) {
334 initialized = true;
335 SSL_load_error_strings();
336 SSL_library_init();
337 }
338
339 // allocate ssl structure
340 if (client->ssl == NULL) {
341 client->ssl = malloc(sizeof(struct SslConn));
342 if (client->ssl == NULL) return false;
343 memset(client->ssl, 0, sizeof(struct SslConn));
344 }
345
346 return true;
347#else
348 return false;
349#endif
350}
351
352/**
353 * qhttpclient->settimeout(): Sets connection wait timeout.
354 *
355 * @param client qhttpclient object pointer
356 * @param timeoutms timeout milliseconds. 0 for system defaults
357 *
358 * @code
359 * httpclient->settimeout(httpclient, 0); // default
360 * httpclient->settimeout(httpclient, 5000); // 5 seconds
361 * @endcode
362 */
363static void settimeout(qhttpclient_t *client, int timeoutms) {
364 if (timeoutms <= 0)
365 timeoutms = -1;
366 client->timeoutms = timeoutms;
367}
368
369/**
370 * qhttpclient->setkeepalive(): Sets KEEP-ALIVE feature on/off.
371 *
372 * @param client qhttpclient object pointer
373 * @param keepalive true to set keep-alive on, false to set keep-alive off
374 *
375 * @code
376 * httpclient->setkeepalive(httpclient, true); // keep-alive on
377 * httpclient->setkeepalive(httpclient, false); // keep-alive off
378 * @endcode
379 */
380static void setkeepalive(qhttpclient_t *client, bool keepalive) {
381 client->keepalive = keepalive;
382}
383
384/**
385 * qhttpclient->setuseragent(): Sets user-agent string.
386 *
387 * @param client qhttpclient object pointer
388 * @param useragent user-agent string
389 *
390 * @code
391 * httpclient->setuseragent(httpclient, "MyAgent/1.0");
392 * @endcode
393 */
394static void setuseragent(qhttpclient_t *client, const char *useragent) {
395 if (client->useragent != NULL)
396 free(client->useragent);
397 client->useragent = strdup(useragent);
398}
399
400/**
401 * qhttpclient->open(): Opens a connection to the remote host.
402 *
403 * @param client qhttpclient object pointer
404 *
405 * @return true on success, otherwise false
406 *
407 * @note
408 * You do not need to open a connection unless you definitely need to,
409 * because qhttpclient opens a connection automatically when needed.
410 * This function can also be used to verify a connection failure with the
411 * remote host.
412 *
413 * @code
414 * if(httpclient->open(httpclient) == false) return;
415 * @endcode
416 */
417static bool open_(qhttpclient_t *client) {
418 if (client->socket >= 0) {
419 // check if connection is still alive
420 if (qio_wait_writable(client->socket, 0) > 0)
421 return true;
422 _close(client);
423 }
424
425 // create new socket
426 int sockfd = socket(AF_INET, SOCK_STREAM, 0);
427 if (sockfd < 0) {
428 DEBUG("sockfd creation failed.");
429 return false;
430 }
431
432 // set to non-block socket if timeout is set
433 int sockflag = 0;
434 if (client->timeoutms > 0) {
435 sockflag = fcntl(sockfd, F_GETFL, 0);
436 fcntl(sockfd, F_SETFL, sockflag | O_NONBLOCK);
437 }
438
439 // try to connect
440 int status = connect(sockfd, (struct sockaddr *) &client->addr,
441 sizeof(client->addr));
442 if (status < 0
443 && (errno != EINPROGRESS
444 || qio_wait_writable(sockfd, client->timeoutms) <= 0)) {
445 DEBUG("connection failed. (%d)", errno);
446 close(sockfd);
447 return false;
448 }
449
450 // restore to block socket
451 if (client->timeoutms > 0) {
452 fcntl(sockfd, F_SETFL, sockflag);
453 }
454
455 // store socket descriptor
456 client->socket = sockfd;
457
458 // set socket option
459 _set_socket_option(sockfd);
460
461#ifdef ENABLE_OPENSSL
462 // set SSL option
463 if (client->ssl != NULL) {
464 // get ssl context using SSL 2 or 3
465 struct SslConn *ssl = client->ssl;
466 ssl->ctx = SSL_CTX_new(SSLv23_client_method());
467 if (ssl->ctx == NULL) {
468 DEBUG("OpenSSL: %s", ERR_reason_error_string(ERR_get_error()));
469 _close(client);
470 return false;
471 }
472
473 // get ssl handle
474 ssl->ssl = SSL_new(ssl->ctx);
475 if (ssl->ssl == NULL) {
476 DEBUG("OpenSSL: %s", ERR_reason_error_string(ERR_get_error()));
477 _close(client);
478 return false;
479 }
480
481 // map ssl handle with socket
482 if (SSL_set_fd(ssl->ssl, client->socket) != 1) {
483 DEBUG("OpenSSL: %s", ERR_reason_error_string(ERR_get_error()));
484 _close(client);
485 return false;
486 }
487
488 // set ssl to work in client mode
489 SSL_set_connect_state(ssl->ssl);
490
491#ifndef OPENSSL_NO_TLSEXT
492 // set server name indication extension for the handshake
493 ssl->ssl->tlsext_hostname = client->hostname;
494#endif
495
496 // do handshake
497 if (SSL_connect(ssl->ssl) != 1) {
498 DEBUG("OpenSSL: %s", ERR_reason_error_string(ERR_get_error()));
499 _close(client);
500 return false;
501 }
502
503 DEBUG("ssl initialized");
504 }
505#endif /* ENABLE_OPENSSL */
506
507 return true;
508}
509
510/**
511 * qhttpclient->head(): Sends a HEAD request.
512 *
513 * @param client qhttpclient object pointer.
514 * @param uri URL encoded remote URI for downloading file.
515 * ("/path" or "http://.../path")
516 * @param rescode if not NULL, remote response code will be stored.
517 * (can be NULL)
518 * @param reqheaders qlisttbl_t pointer which contains additional user
519 * request headers. (can be NULL)
520 * @param resheaders qlisttbl_t pointer for storing response headers.
521 * (can be NULL)
522 *
523 * @return true if successful(got 200 response), otherwise false
524 *
525 * @code
526 * main() {
527 * // create new HTTP client
528 * qhttpclient_t *httpclient = qhttpclient("http://www.qdecoder.org", 0);
529 * if(httpclient == NULL) return;
530 *
531 * // set additional custom headers
532 * qlisttbl_t *reqheaders = qlisttbl(QLISTTBL_UNIQUE | QLISTTBL_CASEINSENSITIVE);
533 * qlisttbl_t *resheaders = qlisttbl(QLISTTBL_UNIQUE | QLISTTBL_CASEINSENSITIVE);
534 *
535 * // send HEAD request
536 * int nRescode = 0;
537 * char *pszEncPath = qEncodeUrl("/img/qdecoder.png");
538 * bool bRet = httpclient->head(httpclient, pszEncPath, &nRescode,
539 * reqheaders, resheaders);
540 * free(pszEncPath);
541 *
542 * // to print out request, response headers
543 * reqheaders->debug(reqheaders, stdout);
544 * resheaders->debug(resheaders, stdout);
545 *
546 * // check results
547 * if(bRet == false) {
548 * ...(error occurred)...
549 * }
550 *
551 * // free resources
552 * httpclient->free(httpclient);
553 * reqheaders->free(reqheaders);
554 * resheaders->free(resheaders);
555 * }
556 * @endcode
557 */
558static bool head(qhttpclient_t *client, const char *uri, int *rescode,
559 qlisttbl_t *reqheaders, qlisttbl_t *resheaders) {
560
561 // reset rescode
562 if (rescode != NULL)
563 *rescode = 0;
564
565 // generate request headers if necessary
566 bool freeReqHeaders = false;
567 if (reqheaders == NULL) {
568 reqheaders = qlisttbl(
569 QLISTTBL_UNIQUE | QLISTTBL_CASEINSENSITIVE);
570 freeReqHeaders = true;
571 }
572
573 // add additional headers
574 reqheaders->putstr(reqheaders, "Accept", "*/*");
575
576 // send request
577 bool sendret = sendrequest(client, "HEAD", uri, reqheaders);
578 if (freeReqHeaders == true)
579 reqheaders->free(reqheaders);
580 if (sendret == false) {
581 _close(client);
582 return false;
583 }
584
585 // read response
586 off_t clength = 0;
587 int resno = readresponse(client, resheaders, &clength);
588 if (rescode != NULL)
589 *rescode = resno;
590
591 // throw out content
592 if (clength > 0) {
593 if (read_(client, NULL, clength) != clength) {
594 _close(client);
595 }
596 }
597
598 // close connection if required
599 if (client->keepalive == false || client->connclose == true) {
600 _close(client);
601 }
602
603 if (resno == HTTP_CODE_OK)
604 return true;
605 return false;
606}
607
608/**
609 * qhttpclient->get(): Downloads a file from the remote host using GET
610 * method.
611 *
612 * @param client qhttpclient object pointer.
613 * @param uri URL encoded remote URI for downloading file.
614 * ("/path" or "http://.../path")
615 * @param fd opened file descriptor for writing.
616 * @param savesize if not NULL, the length of stored bytes will be stored.
617 * (can be NULL)
618 * @param rescode if not NULL, remote response code will be stored.
619 * (can be NULL)
620 * @param reqheaders qlisttbl_t pointer which contains additional user
621 * request headers. (can be NULL)
622 * @param resheaders qlisttbl_t pointer for storing response headers.
623 * (can be NULL)
624 * @param callback set user call-back function. (can be NULL)
625 * @param userdata set user data for call-back. (can be NULL)
626 *
627 * @return true if successful(200 OK), otherwise false
628 *
629 * @code
630 * struct userdata {
631 * ...
632 * };
633 *
634 * static bool callback(void *userdata, off_t sentbytes) {
635 * struct userdata *pMydata = (struct userdata*)userdata;
636 * ...(codes)...
637 * if(need_to_cancel) return false; // stop file uploading immediately
638 * return true;
639 * }
640 *
641 * main() {
642 * // create new HTTP client
643 * qhttpclient_t *httpclient = qhttpclient("http://www.qdecoder.org", 0);
644 * if(httpclient == NULL) return;
645 *
646 * // open file
647 * int nFd = open("/tmp/test.data", O_WRONLY | O_CREAT, 0644);
648 *
649 * // set additional custom headers
650 * qlisttbl_t *reqheaders = qlisttbl(QLISTTBL_UNIQUE | QLISTTBL_CASEINSENSITIVE);
651 * qlisttbl_t *resheaders = qlisttbl(QLISTTBL_UNIQUE | QLISTTBL_CASEINSENSITIVE);
652 *
653 * // set userdata
654 * struct userdata mydata;
655 * ...(codes)...
656 *
657 * // send file
658 * int nRescode = 0;
659 * off_t nSavesize = 0;
660 * char *pszEncPath = qEncodeUrl("/img/qdecoder.png");
661 * bool bRet = httpclient->get(httpclient, pszEncPath, nFd, &nSavesize,
662 * &nRescode,
663 * reqheaders, resheaders,
664 * callback, (void*)&mydata);
665 * free(pszEncPath);
666 *
667 * // to print out request, response headers
668 * reqheaders->debug(reqheaders, stdout);
669 * resheaders->debug(resheaders, stdout);
670 *
671 * // check results
672 * if(bRet == false) {
673 * ...(error occurred)...
674 * }
675 *
676 * // free resources
677 * httpclient->free(httpclient);
678 * reqheaders->free(reqheaders);
679 * resheaders->free(resheaders);
680 * close(nFd);
681 * }
682 * @endcode
683 *
684 * @note
685 * The call-back function will be called periodically whenever it send data as
686 * much as MAX_ATOMIC_DATA_SIZE. To stop uploading, return false in the
687 * call-back function, then PUT process will be stopped immediately.
688 * If a connection was not opened, it will open a connection automatically.
689 *
690 * @note
691 * The "rescode" will be set if it received any response code from a remote
692 * server even though it returns false.
693 */
694static bool get(qhttpclient_t *client, const char *uri, int fd, off_t *savesize,
695 int *rescode, qlisttbl_t *reqheaders, qlisttbl_t *resheaders,
696 bool (*callback)(void *userdata, off_t recvbytes),
697 void *userdata) {
698
699 // reset rescode
700 if (rescode != NULL)
701 *rescode = 0;
702 if (savesize != NULL)
703 *savesize = 0;
704
705 // generate request headers if necessary
706 bool freeReqHeaders = false;
707 if (reqheaders == NULL) {
708 reqheaders = qlisttbl(
709 QLISTTBL_UNIQUE | QLISTTBL_CASEINSENSITIVE);
710 freeReqHeaders = true;
711 }
712
713 // add additional headers
714 reqheaders->putstr(reqheaders, "Accept", "*/*");
715
716 // send request
717 bool sendret = sendrequest(client, "GET", uri, reqheaders);
718 if (freeReqHeaders == true)
719 reqheaders->free(reqheaders);
720 if (sendret == false) {
721 _close(client);
722 return false;
723 }
724
725 // read response
726 off_t clength = 0;
727 int resno = readresponse(client, resheaders, &clength);
728 if (rescode != NULL)
729 *rescode = resno;
730
731 // check response code
732 if (resno != HTTP_CODE_OK) {
733 // throw out content
734 if (clength > 0) {
735 if (read_(client, NULL, clength) != clength) {
736 _close(client);
737 }
738 }
739
740 // close connection if required
741 if (client->keepalive == false || client->connclose == true) {
742 _close(client);
743 }
744 return false;
745 }
746
747 // start retrieving data
748 off_t recv = 0;
749 if (callback != NULL && callback(userdata, recv) == false) {
750 _close(client);
751 return false;
752 }
753
754 if (clength > 0) {
755 while (recv < clength) {
756 unsigned int recvsize; // this time receive size
757 if (clength - recv < MAX_ATOMIC_DATA_SIZE) {
758 recvsize = clength - recv;
759 } else {
760 recvsize = MAX_ATOMIC_DATA_SIZE;
761 }
762
763 ssize_t ret = recvfile(client, fd, recvsize);
764 if (ret <= 0)
765 break; // Connection closed by peer
766 recv += ret;
767 if (savesize != NULL)
768 *savesize = recv;
769
770 if (callback != NULL) {
771 if (callback(userdata, recv) == false) {
772 _close(client);
773 return false;
774 }
775 }
776 }
777
778 if (recv != clength) {
779 _close(client);
780 return false;
781 }
782
783 } else if (clength == -1) { // chunked
784 bool completed = false;
785 do {
786 // read chunk size
787 char buf[64];
788 if (gets_(client, buf, sizeof(buf)) <= 0)
789 break;
790
791 // parse chunk size
792 unsigned int recvsize; // this time chunk size
793 if (sscanf(buf, "%x", &recvsize) != 1) {
794 break;
795 }
796
797 if (recvsize == 0) {
798 // end of transfer
799 completed = true;
800 }
801
802 // save chunk
803 if (recvsize > 0) {
804 ssize_t ret = recvfile(client, fd, recvsize);
805 if (ret != recvsize)
806 break;
807 recv += ret;
808 DEBUG("%zd %zd", recv, ret);
809 if (savesize != NULL)
810 *savesize = recv;
811 }
812
813 // read trailing CRLF
814 if (gets_(client, buf, sizeof(buf)) <= 0)
815 break;
816
817 // call back
818 if (recvsize > 0 && callback != NULL
819 && callback(userdata, recv) == false) {
820 _close(client);
821 return false;
822 }
823 } while (completed == false);
824
825 if (completed == false) {
826 DEBUG("Broken pipe. %jd/chunked, errno=%d", recv, errno);
827 _close(client);
828 return false;
829 }
830 }
831
832 // close connection
833 if (client->keepalive == false || client->connclose == true) {
834 _close(client);
835 }
836
837 return true;
838}
839
840/**
841 * qhttpclient->put(): Uploads a file to the remote host using PUT method.
842 *
843 * @param client qhttpclient object pointer.
844 * @param uri remote URL for uploading file.
845 * ("/path" or "http://.../path")
846 * @param fd opened file descriptor for reading.
847 * @param length send size.
848 * @param rescode if not NULL, remote response code will be stored.
849 * (can be NULL)
850 * @param reqheaders qlisttbl_t pointer which contains additional user
851 * request headers. (can be NULL)
852 * @param resheaders qlisttbl_t pointer for storing response headers.
853 * (can be NULL)
854 * @param callback set user call-back function. (can be NULL)
855 * @param userdata set user data for call-back. (can be NULL)
856 *
857 * @return true if successful(201 Created), otherwise false
858 *
859 * @code
860 * struct userdata {
861 * ...
862 * };
863 *
864 * static bool callback(void *userdata, off_t sentbytes) {
865 * struct userdata *pMydata = (struct userdata*)userdata;
866 * ...(codes)...
867 * if(need_to_cancel) return false; // stop file uploading immediately
868 * return true;
869 * }
870 *
871 * main() {
872 * // create new HTTP client
873 * qhttpclient_t *httpclient = qhttpclient("http://www.qdecoder.org", 0);
874 * if(httpclient == NULL) return;
875 *
876 * // open file
877 * int nFd = open(...);
878 * off_t nFileSize = ...;
879 * char *pFileMd5sum = ...;
880 * time_t nFileDate = ...;
881 *
882 * // set additional custom headers
883 * qlisttbl_t *reqheaders = qlisttbl(QLISTTBL_UNIQUE | QLISTTBL_CASEINSENSITIVE);
884 * reqheaders->putstr(reqheaders, "X-FILE-MD5SUM", pFileMd5sum);
885 * reqheaders->putInt(reqheaders, "X-FILE-DATE", nFileDate);
886 *
887 * // set userdata
888 * struct userdata mydata;
889 * ...(codes)...
890 *
891 * // send file
892 * int nRescode = 0;
893 * qlisttbl_t *resheaders = qlisttbl(QLISTTBL_UNIQUE | QLISTTBL_CASEINSENSITIVE);
894 * bool bRet = httpclient->put(httpclient,
895 * "/img/qdecoder.png", nFd, nFileSize,
896 * &nRescode,
897 * reqheaders, resheaders,
898 * callback, (void*)&mydata);
899 * // to print out request, response headers
900 * reqheaders->debug(reqheaders, stdout);
901 * resheaders->debug(resheaders, stdout);
902 *
903 * // check results
904 * if(bRet == false) {
905 * ...(error occurred)...
906 * }
907 *
908 * // free resources
909 * httpclient->free(httpclient);
910 * reqheaders->free(reqheaders);
911 * resheaders->free(resheaders);
912 * close(nFd);
913 * }
914 * @endcode
915 *
916 * @note
917 * The call-back function will be called periodically whenever it send data as
918 * much as MAX_ATOMIC_DATA_SIZE. To stop uploading, return false in the
919 * call-back function, then PUT process will be stopped immediately.
920 * If a connection was not opened, it will open a connection automatically.
921 *
922 * @note
923 * The "rescode" will be set if it received any response code from a remote
924 * server even though it returns false.
925 */
926static bool put(qhttpclient_t *client, const char *uri, int fd, off_t length,
927 int *rescode, qlisttbl_t *reqheaders, qlisttbl_t *resheaders,
928 bool (*callback)(void *userdata, off_t sentbytes),
929 void *userdata) {
930
931 // reset rescode
932 if (rescode != NULL)
933 *rescode = 0;
934
935 // generate request headers
936 bool freeReqHeaders = false;
937 if (reqheaders == NULL) {
938 reqheaders = qlisttbl(
939 QLISTTBL_UNIQUE | QLISTTBL_CASEINSENSITIVE);
940 freeReqHeaders = true;
941 }
942
943 // add additional headers
944 reqheaders->putstrf(reqheaders, "Content-Length", "%jd", length);
945 reqheaders->putstr(reqheaders, "Expect", "100-continue");
946
947 // send request
948 bool sendret = sendrequest(client, "PUT", uri, reqheaders);
949 if (freeReqHeaders == true) {
950 reqheaders->free(reqheaders);
951 reqheaders = NULL;
952 }
953 if (sendret == false) {
954 _close(client);
955 return false;
956 }
957
958 // wait 100-continue
959 if (qio_wait_readable(client->socket, client->timeoutms) <= 0) {
960 DEBUG("timed out %d", client->timeoutms);
961 _close(client);
962 return false;
963 }
964
965 // read response
966 off_t clength = 0;
967 int resno = readresponse(client, resheaders, &clength);
968 if (resno != HTTP_CODE_CONTINUE) {
969 if (rescode != NULL)
970 *rescode = resno;
971
972 if (clength > 0) {
973 if (read_(client, NULL, clength) != clength) {
974 _close(client);
975 }
976 }
977
978 // close connection if required
979 if (client->keepalive == false || client->connclose == true) {
980 _close(client);
981 }
982 return false;
983 }
984
985 // send data
986 off_t sent = 0;
987 if (callback != NULL) {
988 if (callback(userdata, sent) == false) {
989 _close(client);
990 return false;
991 }
992 }
993 if (length > 0) {
994 while (sent < length) {
995 size_t sendsize; // this time sending size
996 if (length - sent < MAX_ATOMIC_DATA_SIZE)
997 sendsize = length - sent;
998 else
999 sendsize = MAX_ATOMIC_DATA_SIZE;
1000
1001 ssize_t ret = sendfile_(client, fd, sendsize);
1002 if (ret <= 0)
1003 break; // Connection closed by peer
1004 sent += ret;
1005
1006 if (callback != NULL) {
1007 if (callback(userdata, sent) == false) {
1008 _close(client);
1009 return false;
1010 }
1011 }
1012 }
1013
1014 if (sent != length) {
1015 _close(client);
1016 return false;
1017 }
1018
1019 if (callback != NULL) {
1020 if (callback(userdata, sent) == false) {
1021 _close(client);
1022 return false;
1023 }
1024 }
1025 }
1026
1027 // read response
1028 clength = 0;
1029 resno = readresponse(client, resheaders, &clength);
1030 if (rescode != NULL)
1031 *rescode = resno;
1032
1033 if (resno == HTTP_NO_RESPONSE) {
1034 _close(client);
1035 return false;
1036 }
1037
1038 if (clength > 0) {
1039 if (read_(client, NULL, clength) != clength) {
1040 _close(client);
1041 }
1042 }
1043
1044 // close connection
1045 if (client->keepalive == false || client->connclose == true) {
1046 _close(client);
1047 }
1048
1049 if (resno == HTTP_CODE_CREATED)
1050 return true;
1051 return false;
1052}
1053
1054/**
1055 * qhttpclient->cmd(): Send a custom request method to the remote host
1056 * and read the response.
1057 *
1058 * @param client qhttpclient object pointer.
1059 * @param method method name.
1060 * @param uri remote URI.
1061 * ("/path" or "http://.../path")
1062 * @param data data to send. (can be NULL)
1063 * @param size data size.
1064 * @param rescode if not NULL, remote response code will be stored.
1065 * (can be NULL)
1066 * @param contentslength if not NULL, the contents length will be stored.
1067 * (can be NULL)
1068 * @param reqheaders qlisttbl_t pointer which contains additional user
1069 * request headers. (can be NULL)
1070 * @param resheaders qlisttbl_t pointer for storing response headers.
1071 * (can be NULL)
1072 *
1073 * @return allocated response content on success, or NULL on failure.
1074 *
1075 * @code
1076 * int nResCode;
1077 * size_t nContentsLength;
1078 * void *contents = httpclient->cmd(httpclient, "DELETE" "/img/qdecoder.png",
1079 * NULL, 0
1080 * &nRescode, &nContentsLength
1081 * NULL, NULL);
1082 * if(contents == NULL) {
1083 * ...(error occurred)...
1084 * } else {
1085 * printf("Response code : %d\n", nResCode);
1086 * printf("Contents length : %zu\n", nContentsLength);
1087 * printf("Contents : %s\n", (char*)contents); // if contents is printable
1088 * free(contents); // free
1089 * }
1090 * @endcode
1091 *
1092 * @note
1093 * This function stores the server response in memory. If you expect a large
1094 * response body, consider using `sendrequest()` and `readresponse()` instead.
1095 * The returned buffer is allocated with one extra byte beyond
1096 * `contentslength` and is null-terminated.
1097 */
1098static void *cmd(qhttpclient_t *client, const char *method, const char *uri,
1099 void *data, size_t size, int *rescode, size_t *contentslength,
1100 qlisttbl_t *reqheaders, qlisttbl_t *resheaders) {
1101
1102 // reset rescode
1103 if (rescode != NULL)
1104 *rescode = 0;
1105 if (contentslength != NULL)
1106 *contentslength = 0;
1107
1108 // send request
1109 bool freeReqHeaders = false;
1110 if (reqheaders == NULL && data != NULL && size > 0) {
1111 reqheaders = qlisttbl(
1112 QLISTTBL_UNIQUE | QLISTTBL_CASEINSENSITIVE);
1113 reqheaders->putstrf(reqheaders, "Content-Length", "%jd", size);
1114 freeReqHeaders = true;
1115 }
1116
1117 bool sendret = sendrequest(client, method, uri, reqheaders);
1118 if (freeReqHeaders == true) {
1119 reqheaders->free(reqheaders);
1120 reqheaders = NULL;
1121 }
1122 if (sendret == false) {
1123 _close(client);
1124 return NULL;
1125 }
1126
1127 // send data
1128 if (data != NULL && size > 0) {
1129 ssize_t written = write_(client, data, size);
1130 if (written != size) {
1131 _close(client);
1132 return NULL;
1133 }
1134 }
1135
1136 // read response
1137 off_t clength = 0;
1138 int resno = readresponse(client, resheaders, &clength);
1139 if (rescode != NULL)
1140 *rescode = resno;
1141 if (contentslength != NULL)
1142 *contentslength = clength;
1143
1144 // malloc data
1145 void *content = NULL;
1146 if (clength > 0) {
1147 content = malloc(clength + 1);
1148 if (content != NULL) {
1149 if (read_(client, content, clength) == clength) {
1150 *(char *) (content + clength) = '\0';
1151 } else {
1152 free(content);
1153 content = NULL;
1154 _close(client);
1155 }
1156 }
1157 } else {
1158 // succeed. to distinguish between ok and error
1159 content = strdup("");
1160 }
1161
1162 // close connection
1163 if (client->keepalive == false || client->connclose == true) {
1164 _close(client);
1165 }
1166
1167 return content;
1168}
1169
1170/**
1171 * qhttpclient->sendrequest(): Sends an HTTP request to the remote host.
1172 *
1173 * @param client qhttpclient object pointer
1174 * @param method HTTP method name
1175 * @param uri URI string for the method. ("/path" or "http://.../path")
1176 * @param reqheaders qlisttbl_t pointer which contains additional user
1177 * request headers. (can be NULL)
1178 *
1179 * @return true on success, otherwise false
1180 *
1181 * @note
1182 * Default headers(Host, User-Agent, Connection) will be used if reqheaders
1183 * does not have those headers in it.
1184 *
1185 * @code
1186 * qlisttbl_t *reqheaders = qlisttbl(QLISTTBL_UNIQUE | QLISTTBL_CASEINSENSITIVE);
1187 * reqheaders->putstr(reqheaders, "Date", qTimeGetGmtStaticStr(0), true);
1188 *
1189 * httpclient->sendrequest(client,
1190 * "DELETE", "/img/qdecoder.png", reqheaders);
1191 * @endcode
1192 */
1193static bool sendrequest(qhttpclient_t *client, const char *method,
1194 const char *uri, qlisttbl_t *reqheaders) {
1195 if (open_(client) == false) {
1196 return false;
1197 }
1198
1199 // generate request headers if necessary
1200 bool freeReqHeaders = false;
1201 if (reqheaders == NULL) {
1202 reqheaders = qlisttbl(
1203 QLISTTBL_UNIQUE | QLISTTBL_CASEINSENSITIVE);
1204 if (reqheaders == NULL)
1205 return false;
1206 freeReqHeaders = true;
1207 }
1208
1209 // append default headers
1210 if (reqheaders->get(reqheaders, "Host", NULL, false) == NULL) {
1211 reqheaders->putstrf(reqheaders, "Host", "%s:%d", client->hostname,
1212 client->port);
1213 }
1214 if (reqheaders->get(reqheaders, "User-Agent", NULL, false) == NULL) {
1215 reqheaders->putstr(reqheaders, "User-Agent", client->useragent);
1216 }
1217 if (reqheaders->get(reqheaders, "Connection", NULL, false) == NULL) {
1218 reqheaders->putstr(
1219 reqheaders, "Connection",
1220 (client->keepalive == true) ? "Keep-Alive" : "close");
1221 }
1222
1223 // create stream buffer
1224 qgrow_t *outBuf = qgrow(0);
1225 if (outBuf == NULL)
1226 return false;
1227
1228 // Buffer the request line.
1229 outBuf->addstrf(outBuf, "%s %s %s\r\n", method, uri,
1230 HTTP_PROTOCOL_11);
1231
1232 // buffer out headers
1233 qlisttbl_obj_t obj;
1234 memset((void *) &obj, 0, sizeof(obj)); // must be cleared before call
1235 reqheaders->lock(reqheaders);
1236 while (reqheaders->getnext(reqheaders, &obj, NULL, false) == true) {
1237 outBuf->addstrf(outBuf, "%s: %s\r\n", obj.name, (char *) obj.data);
1238 }
1239 reqheaders->unlock(reqheaders);
1240
1241 outBuf->addstrf(outBuf, "\r\n");
1242
1243 // stream out
1244 size_t towrite = 0;
1245 char *final = outBuf->toarray(outBuf, &towrite);
1246 ssize_t written = 0;
1247 if (final != NULL) {
1248 written = write_(client, final, towrite);
1249 free(final);
1250 }
1251
1252 // Free temporary resources.
1253 outBuf->free(outBuf);
1254 if (freeReqHeaders == true)
1255 reqheaders->free(reqheaders);
1256
1257 if (written > 0 && written == towrite)
1258 return true;
1259 return false;
1260}
1261
1262/**
1263 * qhttpclient->readresponse(): Reads HTTP response header from the
1264 * remote host.
1265 *
1266 * @param client qhttpclient object pointer
1267 * @param resheaders qlisttbl_t pointer for storing response headers.
1268 * (can be NULL)
1269 * @param contentlength length of content body(or -1 for chunked transfer
1270 * encoding) will be stored. (can be NULL)
1271 *
1272 * @return numeric HTTP response code if successful, otherwise returns 0.
1273 *
1274 * @code
1275 * // send request
1276 * httpclient->sendrequest(client, "DELETE", "/img/qdecoder.png", NULL);
1277 *
1278 * // read response
1279 * qlisttbl_t *resheaders = qlisttbl(QLISTTBL_UNIQUE | QLISTTBL_CASEINSENSITIVE);
1280 * off_t clength;
1281 * int rescode = httpclient->readresponse(client, resheaders, &clength);
1282 * if(clength > 0) {
1283 * // read & throw out a content. don't need content
1284 * httpclient->read(client, NULL, clength);
1285 * }
1286 * @endcode
1287 *
1288 * @note
1289 * If you want to keep the connection alive, the application must read the
1290 * response body. Please refer to `qhttpclient->read()`.
1291 */
1292static int readresponse(qhttpclient_t *client, qlisttbl_t *resheaders,
1293 off_t *contentlength) {
1294 if (contentlength != NULL) {
1295 *contentlength = 0;
1296 }
1297
1298 // read response
1299 char buf[1024];
1300 if (gets_(client, buf, sizeof(buf)) <= 0)
1301 return HTTP_NO_RESPONSE;
1302
1303 // parse response code
1304 if (strncmp(buf, "HTTP/", CONST_STRLEN("HTTP/")))
1305 return HTTP_NO_RESPONSE;
1306 char *tmp = strstr(buf, " ");
1307 if (tmp == NULL)
1308 return HTTP_NO_RESPONSE;
1309 int rescode = atoi(tmp + 1);
1310 if (rescode == 0)
1311 return HTTP_NO_RESPONSE;
1312
1313 // read headers
1314 while (gets_(client, buf, sizeof(buf)) > 0) {
1315 if (buf[0] == '\0')
1316 break;
1317
1318 // parse header
1319 char *name = buf;
1320 char *value = strstr(buf, ":");
1321 if (value != NULL) {
1322 *value = '\0';
1323 value += 1;
1324 qstrtrim(value);
1325 } else {
1326 // missing colon
1327 value = "";
1328 }
1329
1330 if (resheaders != NULL) {
1331 resheaders->putstr(resheaders, name, value);
1332 }
1333
1334 // check Connection header
1335 if (!strcasecmp(name, "Connection")) {
1336 if (!strcasecmp(value, "close")) {
1337 client->connclose = true;
1338 }
1339 }
1340 // check Content-Length & Transfer-Encoding header
1341 else if (contentlength != NULL && *contentlength == 0) {
1342 if (!strcasecmp(name, "Content-Length")) {
1343 *contentlength = atoll(value);
1344 }
1345 // check transfer-encoding header
1346 else if (!strcasecmp(name, "Transfer-Encoding")
1347 && !strcasecmp(value, "chunked")) {
1348 *contentlength = -1;
1349 }
1350 }
1351 }
1352
1353 return rescode;
1354}
1355
1356/**
1357 * qhttpclient->gets(): Reads a text line from an HTTP/HTTPS stream.
1358 *
1359 * @param client qhttpclient object pointer
1360 * @param buf data buffer pointer
1361 * @param bufsize buffer size
1362 *
1363 * @return the number of bytes read from file descriptor if successful,
1364 * otherwise returns -1.
1365 *
1366 * @note
1367 * Be sure the return value does not mean the length of actual stored data.
1368 * It means how many bytes are read from the file descriptor, so the new-line
1369 * characters will be counted, but not stored.
1370 */
1371static ssize_t gets_(qhttpclient_t *client, char *buf, size_t bufsize) {
1372#ifdef ENABLE_OPENSSL
1373 if (client->ssl == NULL) {
1374 return qio_gets(client->socket, buf, bufsize, client->timeoutms);
1375 } else {
1376 if (bufsize <= 1) return -1;
1377
1378 struct SslConn *ssl = client->ssl;
1379 ssize_t readcnt = 0;
1380 char *ptr;
1381
1382 for (ptr = buf; readcnt < (bufsize - 1); ptr++) {
1383 // wait readable
1384 //if (qio_wait_readable(client->socket, client->timeoutms) <= 0) {
1385 // break;
1386 //}
1387
1388 int rsize = SSL_read(ssl->ssl, ptr, 1);
1389 if (rsize != 1) {
1390 unsigned long sslerr = ERR_get_error();
1391 if (sslerr == SSL_ERROR_WANT_READ) {
1392 continue;
1393 }
1394
1395 DEBUG("OpenSSL: %s (%d)",
1396 ERR_reason_error_string(sslerr), rsize);
1397 break;
1398 }
1399
1400 readcnt++;
1401 if (*ptr == '\r') ptr--;
1402 else if (*ptr == '\n') break;
1403 }
1404
1405 *ptr = '\0';
1406 DEBUG("SSL_read: %s (%zd)", buf, readcnt);
1407
1408 if (readcnt > 0) return readcnt;
1409 return -1;
1410 }
1411#else
1412 return qio_gets(client->socket, buf, bufsize, client->timeoutms);
1413#endif
1414}
1415
1416/**
1417 * qhttpclient->read(): Reads data from an HTTP/HTTPS stream.
1418 *
1419 * @param client qhttpclient object pointer.
1420 * @param buf a buffer pointer for storing content. (can be NULL, then
1421 * read & throw out content)
1422 * @param length content size to read.
1423 *
1424 * @return number of bytes read
1425 *
1426 * @code
1427 * off_t clength = 0;
1428 * int resno = client->readresponse(client, NULL, &clength);
1429 * if(clength > 0) {
1430 * void *buf = malloc(clength);
1431 * client->read(client, buf, clength);
1432 * }
1433 * @endcode
1434 */
1435static ssize_t read_(qhttpclient_t *client, void *buf, size_t nbytes) {
1436#ifdef ENABLE_OPENSSL
1437 if (client->ssl == NULL) {
1438 return qio_read(client->socket, buf, nbytes, client->timeoutms);
1439 } else {
1440 if (nbytes == 0) return 0;
1441
1442 struct SslConn *ssl = client->ssl;
1443 ssize_t total = 0;
1444 while (total < nbytes) {
1445 //if (qio_wait_readable(client->socket, client->timeoutms) <= 0) {
1446 // break;
1447 //}
1448
1449 int rsize = 0;
1450 if (buf != NULL) {
1451 rsize = SSL_read(ssl->ssl, buf + total, nbytes - total);
1452 } else {
1453 char trash[1024];
1454 int toread = nbytes - total;
1455 if (toread > sizeof(trash)) toread = sizeof(trash);
1456 rsize = SSL_read(ssl->ssl, trash, toread);
1457 }
1458 if (rsize <= 0) {
1459 DEBUG("OpenSSL: %s (%d)",
1460 ERR_reason_error_string(ERR_get_error()), rsize);
1461 unsigned long sslerr = ERR_get_error();
1462 if (sslerr == SSL_ERROR_WANT_READ) {
1463 usleep(1);
1464 continue;
1465 }
1466 break;
1467 }
1468 total += rsize;
1469 }
1470
1471 DEBUG("SSL_read: %zd", total);
1472 if (total > 0) return total;
1473 return -1;
1474 }
1475#else
1476 return qio_read(client->socket, buf, nbytes, client->timeoutms);
1477#endif
1478}
1479
1480/**
1481 * qhttpclient->write(): Writes data to an HTTP/HTTPS stream.
1482 *
1483 * @param client qhttpclient object pointer.
1484 * @param buf a data pointer.
1485 * @param length content size to write.
1486 *
1487 * @return number of bytes written.
1488 */
1489static ssize_t write_(qhttpclient_t *client, const void *buf, size_t nbytes) {
1490#ifdef ENABLE_OPENSSL
1491 if (client->ssl == NULL) {
1492 return qio_write(client->socket, buf, nbytes, -1);
1493 } else {
1494 if (nbytes == 0) return 0;
1495
1496 struct SslConn *ssl = client->ssl;
1497 ssize_t total = 0;
1498 while (total < nbytes) {
1499 errno = 0;
1500 int wsize = SSL_write(ssl->ssl, buf + total, nbytes - total);
1501 if (wsize <= 0) {
1502 DEBUG("OpenSSL: %s (%d)",
1503 ERR_reason_error_string(ERR_get_error()), wsize);
1504 unsigned long sslerr = ERR_get_error();
1505 if (sslerr == SSL_ERROR_WANT_WRITE) {
1506 usleep(1);
1507 continue;
1508 }
1509 break;
1510 }
1511 total += wsize;
1512 }
1513
1514 DEBUG("SSL_write: %zd/%zu", total, nbytes);
1515 if (total > 0) return total;
1516 return -1;
1517 }
1518#else
1519 return qio_write(client->socket, buf, nbytes, -1);
1520#endif
1521}
1522
1523/**
1524 * qhttpclient->recvfile(): Reads data from an HTTP/HTTPS stream and saves
1525 * it to a file descriptor.
1526 *
1527 * @param client qhttpclient object pointer.
1528 * @param fd output file descriptor
1529 * @param nbytes the number of bytes to read and save.
1530 *
1531 * @return the number of bytes written if successful, otherwise returns -1.
1532 */
1533static off_t recvfile(qhttpclient_t *client, int fd, off_t nbytes) {
1534 if (nbytes == 0)
1535 return 0;
1536
1537 unsigned char buf[MAX_ATOMIC_DATA_SIZE];
1538
1539 off_t total = 0; // total size sent
1540 while (total < nbytes) {
1541 size_t chunksize; // this time sending size
1542 if (nbytes - total <= sizeof(buf))
1543 chunksize = nbytes - total;
1544 else
1545 chunksize = sizeof(buf);
1546
1547 // read
1548 ssize_t rsize = read_(client, buf, chunksize);
1549 if (rsize <= 0)
1550 break;
1551
1552 // write
1553 ssize_t wsize = qio_write(fd, buf, rsize, -1);
1554 DEBUG("FILE write: %zd", wsize);
1555 if (wsize <= 0)
1556 break;
1557
1558 total += wsize;
1559 if (rsize != wsize) {
1560 DEBUG("size mismatch. read:%zd, write:%zd", rsize, wsize);
1561 break;
1562 }
1563 }
1564
1565 if (total > 0)
1566 return total;
1567 return -1;
1568}
1569
1570/**
1571 * qhttpclient->sendfile(): Sends file data to an HTTP/HTTPS stream.
1572 *
1573 * @param client qhttpclient object pointer.
1574 * @param fd input file descriptor
1575 * @param nbytes the number of bytes to read and send.
1576 *
1577 * @return the number of bytes sent if successful, otherwise returns -1.
1578 */
1579static off_t sendfile_(qhttpclient_t *client, int fd, off_t nbytes) {
1580 if (nbytes == 0)
1581 return 0;
1582
1583 unsigned char buf[MAX_ATOMIC_DATA_SIZE];
1584
1585 off_t total = 0; // total size sent
1586 while (total < nbytes) {
1587 size_t chunksize; // this time sending size
1588 if (nbytes - total <= sizeof(buf))
1589 chunksize = nbytes - total;
1590 else
1591 chunksize = sizeof(buf);
1592
1593 // read
1594 ssize_t rsize = qio_read(fd, buf, chunksize, -1);
1595 DEBUG("FILE read: %zd", rsize);
1596 if (rsize <= 0)
1597 break;
1598
1599 // write
1600 ssize_t wsize = write_(client, buf, rsize);
1601 if (wsize <= 0)
1602 break;
1603
1604 total += wsize;
1605 if (rsize != wsize) {
1606 DEBUG("size mismatch. read:%zd, write:%zd", rsize, wsize);
1607 break;
1608 }
1609 }
1610
1611 if (total > 0)
1612 return total;
1613 return -1;
1614}
1615
1616/**
1617 * qhttpclient->close(): Closes the connection.
1618 *
1619 * @param qhttpclient_t HTTP object pointer
1620 *
1621 * @return true on success, otherwise false
1622 *
1623 * @code
1624 * httpclient->close(httpclient);
1625 * @endcode
1626 */
1627static bool _close(qhttpclient_t *client) {
1628 if (client->socket < 0)
1629 return false;
1630
1631#ifdef ENABLE_OPENSSL
1632 // release ssl connection
1633 if (client->ssl != NULL) {
1634 struct SslConn *ssl = client->ssl;
1635
1636 if (ssl->ssl != NULL) {
1637 SSL_shutdown(ssl->ssl);
1638 SSL_free(ssl->ssl);
1639 ssl->ssl = NULL;
1640 }
1641
1642 if (ssl->ctx != NULL) {
1643 SSL_CTX_free(ssl->ctx);
1644 ssl->ctx = NULL;
1645 }
1646 }
1647#endif
1648
1649 // shutdown connection
1650 if (client->ssl == NULL && MAX_SHUTDOWN_WAIT >= 0
1651 && shutdown(client->socket, SHUT_WR) == 0) {
1652 char buf[1024];
1653 while (qio_read(client->socket, buf, sizeof(buf), MAX_SHUTDOWN_WAIT) > 0);
1654 }
1655
1656 // close connection
1657 close(client->socket);
1658 client->socket = -1;
1659 client->connclose = false;
1660
1661 return true;
1662}
1663
1664/**
1665 * qhttpclient->free(): Free object.
1666 *
1667 * @param qhttpclient_t HTTP object pointer
1668 *
1669 * @note
1670 * If the connection was not closed, it will close the connection first prior
1671 * to free object.
1672 *
1673 * @code
1674 * httpclient->free(httpclient);
1675 * @endcode
1676 */
1677static void _free(qhttpclient_t *client) {
1678 if (client->socket >= 0) {
1679 client->close(client);
1680 }
1681
1682 if (client->ssl != NULL)
1683 free(client->ssl);
1684 if (client->hostname != NULL)
1685 free(client->hostname);
1686 if (client->useragent != NULL)
1687 free(client->useragent);
1688
1689 free(client);
1690}
1691
1692#ifndef _DOXYGEN_SKIP
1693static bool _set_socket_option(int socket) {
1694 bool ret = true;
1695
1696 // linger option
1697 if (SET_TCP_LINGER_TIMEOUT > 0) {
1698 struct linger li;
1699 li.l_onoff = 1;
1700 li.l_linger = SET_TCP_LINGER_TIMEOUT;
1701 if (setsockopt(socket, SOL_SOCKET, SO_LINGER, &li,
1702 sizeof(struct linger)) < 0) {
1703 ret = false;
1704 }
1705 }
1706
1707 // nodelay option
1708 if (SET_TCP_NODELAY > 0) {
1709 int so_tcpnodelay = 1;
1710 if (setsockopt(socket, IPPROTO_TCP, TCP_NODELAY, &so_tcpnodelay,
1711 sizeof(so_tcpnodelay)) < 0) {
1712 ret = false;
1713 }
1714 }
1715
1716 return ret;
1717}
1718
1719static bool _parse_uri(const char *uri, bool *protocol, char *hostname,
1720 size_t namesize, int *port) {
1721
1722 if (!strncasecmp(uri, "http://", CONST_STRLEN("http://"))) {
1723 *protocol = false;
1724 *port = 80;
1725 } else if (!strncasecmp(uri, "https://", CONST_STRLEN("https://"))) {
1726 *protocol = true;
1727 *port = 443;
1728 } else {
1729 return false;
1730 }
1731
1732 char *t1 = strstr(uri, "://");
1733 t1 += 3;
1734 char *t2 = strstr(t1, "/");
1735 if (t2 == NULL)
1736 t2 = (char *) uri + strlen(uri);
1737
1738 if (t2 - t1 + 1 > namesize)
1739 return false;
1740 qstrncpy(hostname, namesize, t1, t2 - t1);
1741
1742 t1 = strstr(hostname, ":");
1743 if (t1 != NULL) {
1744 *t1 = '\0';
1745 *port = atoi(t1 + 1);
1746 }
1747
1748 return true;
1749}
1750#endif /* _DOXYGEN_SKIP */
1751
1752#endif /* DISABLE_QHTTPCLIENT */
qgrow_t * qgrow(int options)
Initialize grow.
Definition qgrow.c:134
static void settimeout(qhttpclient_t *client, int timeoutms)
qhttpclient->settimeout(): Sets connection wait timeout.
static bool sendrequest(qhttpclient_t *client, const char *method, const char *uri, qlisttbl_t *reqheaders)
qhttpclient->sendrequest(): Sends an HTTP request to the remote host.
static void * cmd(qhttpclient_t *client, const char *method, const char *uri, void *data, size_t size, int *rescode, size_t *contentslength, qlisttbl_t *reqheaders, qlisttbl_t *resheaders)
qhttpclient->cmd(): Send a custom request method to the remote host and read the response.
static off_t recvfile(qhttpclient_t *client, int fd, off_t nbytes)
qhttpclient->recvfile(): Reads data from an HTTP/HTTPS stream and saves it to a file descriptor.
static bool setssl(qhttpclient_t *client)
qhttpclient->setssl(): Enable HTTPS for the connection.
static ssize_t read_(qhttpclient_t *client, void *buf, size_t nbytes)
qhttpclient->read(): Reads data from an HTTP/HTTPS stream.
static bool _close(qhttpclient_t *client)
qhttpclient->close(): Closes the connection.
static ssize_t write_(qhttpclient_t *client, const void *buf, size_t nbytes)
qhttpclient->write(): Writes data to an HTTP/HTTPS stream.
static void _free(qhttpclient_t *client)
qhttpclient->free(): Free object.
static void setkeepalive(qhttpclient_t *client, bool keepalive)
qhttpclient->setkeepalive(): Sets KEEP-ALIVE feature on/off.
static int readresponse(qhttpclient_t *client, qlisttbl_t *resheaders, off_t *contentlength)
qhttpclient->readresponse(): Reads HTTP response header from the remote host.
qhttpclient_t * qhttpclient(const char *destname, int port)
Initialize and create a new HTTP client.
static bool open_(qhttpclient_t *client)
qhttpclient->open(): Opens a connection to the remote host.
static bool head(qhttpclient_t *client, const char *uri, int *rescode, qlisttbl_t *reqheaders, qlisttbl_t *resheaders)
qhttpclient->head(): Sends a HEAD request.
static off_t sendfile_(qhttpclient_t *client, int fd, off_t nbytes)
qhttpclient->sendfile(): Sends file data to an HTTP/HTTPS stream.
static ssize_t gets_(qhttpclient_t *client, char *buf, size_t bufsize)
qhttpclient->gets(): Reads a text line from an HTTP/HTTPS stream.
static bool put(qhttpclient_t *client, const char *uri, int fd, off_t length, int *rescode, qlisttbl_t *reqheaders, qlisttbl_t *resheaders, bool(*callback)(void *userdata, off_t sentbytes), void *userdata)
qhttpclient->put(): Uploads a file to the remote host using PUT method.
static bool get(qhttpclient_t *client, const char *uri, int fd, off_t *savesize, int *rescode, qlisttbl_t *reqheaders, qlisttbl_t *resheaders, bool(*callback)(void *userdata, off_t recvbytes), void *userdata)
qhttpclient->get(): Downloads a file from the remote host using GET method.
static void setuseragent(qhttpclient_t *client, const char *useragent)
qhttpclient->setuseragent(): Sets user-agent string.
ssize_t qio_write(int fd, const void *buf, size_t nbytes, int timeoutms)
Write to a file descriptor.
Definition qio.c:158
int qio_wait_readable(int fd, int timeoutms)
Wait until a file descriptor becomes readable.
Definition qio.c:59
ssize_t qio_read(int fd, void *buf, size_t nbytes, int timeoutms)
Read from a file descriptor.
Definition qio.c:118
ssize_t qio_gets(int fd, char *buf, size_t bufsize, int timeoutms)
Read a line from a file descriptor into the buffer until a terminating newline or EOF.
Definition qio.c:255
int qio_wait_writable(int fd, int timeoutms)
Wait until a file descriptor is ready for writing.
Definition qio.c:87
qlisttbl_t * qlisttbl(int options)
Create a new Q_LIST linked-list container.
Definition qlisttbl.c:149
static bool write_(qlog_t *log, const char *str)
qlog->write(): Log messages
Definition qlog.c:160
bool qsocket_get_addr(struct sockaddr_in *addr, const char *hostname, int port)
Convert a hostname to a sockaddr_in structure.
Definition qsocket.c:134
char * qstrcpy(char *dst, size_t size, const char *src)
Copy src string to dst.
Definition qstring.c:298
char * qstrncpy(char *dst, size_t size, const char *src, size_t nbytes)
Copy src string to dst no more than n bytes.
Definition qstring.c:317
char * qstrtrim(char *str)
Remove whitespace, including CR and LF, from both ends of a string.
Definition qstring.c:55