qLibc
qhttpclient.c
Go to the documentation of this file.
1/******************************************************************************
2 * qLibc
3 *
4 * Copyright (c) 2010-2015 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 purpose
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 * // de-allocate 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 * // de-allocate 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 & create new HTTP client.
225 *
226 * @param destname remote address, one of IP address, FQDN domain name and URI.
227 * @param port remote port number. (can be 0 when destname is URI)
228 *
229 * @return HTTP client object if succcessful, otherwise returns NULL.
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(): Sets connection to HTTPS 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 mili-seconds. 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 if successful, otherwise returns false
406 *
407 * @note
408 * Don't need to open a connection unless you definitely need to do this,
409 * because qhttpclient open a connection automatically when it's needed.
410 * This function also can be used to veryfy a connection failure with remote
411 * 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 returns 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 occured)...
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 returns 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 occured)...
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 peridically 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 tailing 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 returns 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 occured)...
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 peridically 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(): Sends a custom request(method) to the remote host
1056 * and reads it's response.
1057 *
1058 * @param client qhttpclient object pointer.
1059 * @param method method name.
1060 * @param uri remote URL for uploading file.
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 malloced content data if successful, otherwise returns NULL
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 occured)...
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); // de-allocate
1089 * }
1090 * @endcode
1091 *
1092 * @note
1093 * This store server's response into memory so if you expect server responses
1094 * large amount of data, consider to use sendrequest() and readresponse()
1095 * instead of using this. The returning malloced content will be allocated
1096 * +1 byte than actual content size 'contentslength' and will be null
1097 * terminated.
1098 */
1099static void *cmd(qhttpclient_t *client, const char *method, const char *uri,
1100 void *data, size_t size, int *rescode, size_t *contentslength,
1101 qlisttbl_t *reqheaders, qlisttbl_t *resheaders) {
1102
1103 // reset rescode
1104 if (rescode != NULL)
1105 *rescode = 0;
1106 if (contentslength != NULL)
1107 *contentslength = 0;
1108
1109 // send request
1110 bool freeReqHeaders = false;
1111 if (reqheaders == NULL && data != NULL && size > 0) {
1112 reqheaders = qlisttbl(
1113 QLISTTBL_UNIQUE | QLISTTBL_CASEINSENSITIVE);
1114 reqheaders->putstrf(reqheaders, "Content-Length", "%jd", size);
1115 freeReqHeaders = true;
1116 }
1117
1118 bool sendret = sendrequest(client, method, uri, reqheaders);
1119 if (freeReqHeaders == true) {
1120 reqheaders->free(reqheaders);
1121 reqheaders = NULL;
1122 }
1123 if (sendret == false) {
1124 _close(client);
1125 return NULL;
1126 }
1127
1128 // send data
1129 if (data != NULL && size > 0) {
1130 ssize_t written = write_(client, data, size);
1131 if (written != size) {
1132 _close(client);
1133 return NULL;
1134 }
1135 }
1136
1137 // read response
1138 off_t clength = 0;
1139 int resno = readresponse(client, resheaders, &clength);
1140 if (rescode != NULL)
1141 *rescode = resno;
1142 if (contentslength != NULL)
1143 *contentslength = clength;
1144
1145 // malloc data
1146 void *content = NULL;
1147 if (clength > 0) {
1148 content = malloc(clength + 1);
1149 if (content != NULL) {
1150 if (read_(client, content, clength) == clength) {
1151 *(char *) (content + clength) = '\0';
1152 } else {
1153 free(content);
1154 content = NULL;
1155 _close(client);
1156 }
1157 }
1158 } else {
1159 // succeed. to distinguish between ok and error
1160 content = strdup("");
1161 }
1162
1163 // close connection
1164 if (client->keepalive == false || client->connclose == true) {
1165 _close(client);
1166 }
1167
1168 return content;
1169}
1170
1171/**
1172 * qhttpclient->sendrequest(): Sends a HTTP request to the remote host.
1173 *
1174 * @param client qhttpclient object pointer
1175 * @param method HTTP method name
1176 * @param uri URI string for the method. ("/path" or "http://.../path")
1177 * @param reqheaders qlisttbl_t pointer which contains additional user
1178 * request headers. (can be NULL)
1179 *
1180 * @return true if successful, otherwise returns false
1181 *
1182 * @note
1183 * Default headers(Host, User-Agent, Connection) will be used if reqheaders
1184 * does not have those headers in it.
1185 *
1186 * @code
1187 * qlisttbl_t *reqheaders = qlisttbl(QLISTTBL_UNIQUE | QLISTTBL_CASEINSENSITIVE);
1188 * reqheaders->putstr(reqheaders, "Date", qTimeGetGmtStaticStr(0), true);
1189 *
1190 * httpclient->sendrequest(client,
1191 * "DELETE", "/img/qdecoder.png", reqheaders);
1192 * @endcode
1193 */
1194static bool sendrequest(qhttpclient_t *client, const char *method,
1195 const char *uri, qlisttbl_t *reqheaders) {
1196 if (open_(client) == false) {
1197 return false;
1198 }
1199
1200 // generate request headers if necessary
1201 bool freeReqHeaders = false;
1202 if (reqheaders == NULL) {
1203 reqheaders = qlisttbl(
1204 QLISTTBL_UNIQUE | QLISTTBL_CASEINSENSITIVE);
1205 if (reqheaders == NULL)
1206 return false;
1207 freeReqHeaders = true;
1208 }
1209
1210 // append default headers
1211 if (reqheaders->get(reqheaders, "Host", NULL, false) == NULL) {
1212 reqheaders->putstrf(reqheaders, "Host", "%s:%d", client->hostname,
1213 client->port);
1214 }
1215 if (reqheaders->get(reqheaders, "User-Agent", NULL, false) == NULL) {
1216 reqheaders->putstr(reqheaders, "User-Agent", client->useragent);
1217 }
1218 if (reqheaders->get(reqheaders, "Connection", NULL, false) == NULL) {
1219 reqheaders->putstr(
1220 reqheaders, "Connection",
1221 (client->keepalive == true) ? "Keep-Alive" : "close");
1222 }
1223
1224 // create stream buffer
1225 qgrow_t *outBuf = qgrow(0);
1226 if (outBuf == NULL)
1227 return false;
1228
1229 // buffer out command
1230 outBuf->addstrf(outBuf, "%s %s %s\r\n", method, uri,
1231 HTTP_PROTOCOL_11);
1232
1233 // buffer out headers
1234 qlisttbl_obj_t obj;
1235 memset((void *) &obj, 0, sizeof(obj)); // must be cleared before call
1236 reqheaders->lock(reqheaders);
1237 while (reqheaders->getnext(reqheaders, &obj, NULL, false) == true) {
1238 outBuf->addstrf(outBuf, "%s: %s\r\n", obj.name, (char *) obj.data);
1239 }
1240 reqheaders->unlock(reqheaders);
1241
1242 outBuf->addstrf(outBuf, "\r\n");
1243
1244 // stream out
1245 size_t towrite = 0;
1246 char *final = outBuf->toarray(outBuf, &towrite);
1247 ssize_t written = 0;
1248 if (final != NULL) {
1249 written = write_(client, final, towrite);
1250 free(final);
1251 }
1252
1253 // de-allocate
1254 outBuf->free(outBuf);
1255 if (freeReqHeaders == true)
1256 reqheaders->free(reqheaders);
1257
1258 if (written > 0 && written == towrite)
1259 return true;
1260 return false;
1261}
1262
1263/**
1264 * qhttpclient->readresponse(): Reads HTTP response header from the
1265 * remote host.
1266 *
1267 * @param client qhttpclient object pointer
1268 * @param resheaders qlisttbl_t pointer for storing response headers.
1269 * (can be NULL)
1270 * @param contentlength length of content body(or -1 for chunked transfer
1271 * encoding) will be stored. (can be NULL)
1272 *
1273 * @return numeric HTTP response code if successful, otherwise returns 0.
1274 *
1275 * @code
1276 * // send request
1277 * httpclient->sendrequest(client, "DELETE", "/img/qdecoder.png", NULL);
1278 *
1279 * // read response
1280 * qlisttbl_t *resheaders = qlisttbl(QLISTTBL_UNIQUE | QLISTTBL_CASEINSENSITIVE);
1281 * off_t clength;
1282 * int rescode = httpclient->readresponse(client, resheaders, &clength);
1283 * if(clength > 0) {
1284 * // read & throw out a content. don't need content
1285 * httpclient->read(client, NULL, clength);
1286 * }
1287 * @endcode
1288 *
1289 * @note
1290 * Data of content body must be read by a application side, if you want to use
1291 * Keep-Alive session. Please refer qhttpclient->read().
1292 */
1293static int readresponse(qhttpclient_t *client, qlisttbl_t *resheaders,
1294 off_t *contentlength) {
1295 if (contentlength != NULL) {
1296 *contentlength = 0;
1297 }
1298
1299 // read response
1300 char buf[1024];
1301 if (gets_(client, buf, sizeof(buf)) <= 0)
1302 return HTTP_NO_RESPONSE;
1303
1304 // parse response code
1305 if (strncmp(buf, "HTTP/", CONST_STRLEN("HTTP/")))
1306 return HTTP_NO_RESPONSE;
1307 char *tmp = strstr(buf, " ");
1308 if (tmp == NULL)
1309 return HTTP_NO_RESPONSE;
1310 int rescode = atoi(tmp + 1);
1311 if (rescode == 0)
1312 return HTTP_NO_RESPONSE;
1313
1314 // read headers
1315 while (gets_(client, buf, sizeof(buf)) > 0) {
1316 if (buf[0] == '\0')
1317 break;
1318
1319 // parse header
1320 char *name = buf;
1321 char *value = strstr(buf, ":");
1322 if (value != NULL) {
1323 *value = '\0';
1324 value += 1;
1325 qstrtrim(value);
1326 } else {
1327 // missing colon
1328 value = "";
1329 }
1330
1331 if (resheaders != NULL) {
1332 resheaders->putstr(resheaders, name, value);
1333 }
1334
1335 // check Connection header
1336 if (!strcasecmp(name, "Connection")) {
1337 if (!strcasecmp(value, "close")) {
1338 client->connclose = true;
1339 }
1340 }
1341 // check Content-Length & Transfer-Encoding header
1342 else if (contentlength != NULL && *contentlength == 0) {
1343 if (!strcasecmp(name, "Content-Length")) {
1344 *contentlength = atoll(value);
1345 }
1346 // check transfer-encoding header
1347 else if (!strcasecmp(name, "Transfer-Encoding")
1348 && !strcasecmp(value, "chunked")) {
1349 *contentlength = -1;
1350 }
1351 }
1352 }
1353
1354 return rescode;
1355}
1356
1357/**
1358 * qhttpclient->gets(): Reads a text line from a HTTP/HTTPS stream.
1359 *
1360 * @param client qhttpclient object pointer
1361 * @param buf data buffer pointer
1362 * @param bufsize buffer size
1363 *
1364 * @return the number of bytes read from file descriptor if successful,
1365 * otherwise returns -1.
1366 *
1367 * @note
1368 * Be sure the return value does not mean the length of actual stored data.
1369 * It means how many bytes are read from the file descriptor, so the new-line
1370 * characters will be counted, but not stored.
1371 */
1372static ssize_t gets_(qhttpclient_t *client, char *buf, size_t bufsize) {
1373#ifdef ENABLE_OPENSSL
1374 if (client->ssl == NULL) {
1375 return qio_gets(client->socket, buf, bufsize, client->timeoutms);
1376 } else {
1377 if (bufsize <= 1) return -1;
1378
1379 struct SslConn *ssl = client->ssl;
1380 ssize_t readcnt = 0;
1381 char *ptr;
1382
1383 for (ptr = buf; readcnt < (bufsize - 1); ptr++) {
1384 // wait readable
1385 //if (qio_wait_readable(client->socket, client->timeoutms) <= 0) {
1386 // break;
1387 //}
1388
1389 int rsize = SSL_read(ssl->ssl, ptr, 1);
1390 if (rsize != 1) {
1391 unsigned long sslerr = ERR_get_error();
1392 if (sslerr == SSL_ERROR_WANT_READ) {
1393 continue;
1394 }
1395
1396 DEBUG("OpenSSL: %s (%d)",
1397 ERR_reason_error_string(sslerr), rsize);
1398 break;
1399 }
1400
1401 readcnt++;
1402 if (*ptr == '\r') ptr--;
1403 else if (*ptr == '\n') break;
1404 }
1405
1406 *ptr = '\0';
1407 DEBUG("SSL_read: %s (%zd)", buf, readcnt);
1408
1409 if (readcnt > 0) return readcnt;
1410 return -1;
1411 }
1412#else
1413 return qio_gets(client->socket, buf, bufsize, client->timeoutms);
1414#endif
1415}
1416
1417/**
1418 * qhttpclient->read(): Reads data from a HTTP/HTTPS stream.
1419 *
1420 * @param client qhttpclient object pointer.
1421 * @param buf a buffer pointer for storing content. (can be NULL, then
1422 * read & throw out content)
1423 * @param length content size to read.
1424 *
1425 * @return number of bytes readed
1426 *
1427 * @code
1428 * off_t clength = 0;
1429 * int resno = client->readresponse(client, NULL, &clength);
1430 * if(clength > 0) {
1431 * void *buf = malloc(clength);
1432 * client->read(client, buf, clength);
1433 * }
1434 * @endcode
1435 */
1436static ssize_t read_(qhttpclient_t *client, void *buf, size_t nbytes) {
1437#ifdef ENABLE_OPENSSL
1438 if (client->ssl == NULL) {
1439 return qio_read(client->socket, buf, nbytes, client->timeoutms);
1440 } else {
1441 if (nbytes == 0) return 0;
1442
1443 struct SslConn *ssl = client->ssl;
1444 ssize_t total = 0;
1445 while (total < nbytes) {
1446 //if (qio_wait_readable(client->socket, client->timeoutms) <= 0) {
1447 // break;
1448 //}
1449
1450 int rsize = 0;
1451 if (buf != NULL) {
1452 rsize = SSL_read(ssl->ssl, buf + total, nbytes - total);
1453 } else {
1454 char trash[1024];
1455 int toread = nbytes - total;
1456 if (toread > sizeof(trash)) toread = sizeof(trash);
1457 rsize = SSL_read(ssl->ssl, trash, toread);
1458 }
1459 if (rsize <= 0) {
1460 DEBUG("OpenSSL: %s (%d)",
1461 ERR_reason_error_string(ERR_get_error()), rsize);
1462 unsigned long sslerr = ERR_get_error();
1463 if (sslerr == SSL_ERROR_WANT_READ) {
1464 usleep(1);
1465 continue;
1466 }
1467 break;
1468 }
1469 total += rsize;
1470 }
1471
1472 DEBUG("SSL_read: %zd", total);
1473 if (total > 0) return total;
1474 return -1;
1475 }
1476#else
1477 return qio_read(client->socket, buf, nbytes, client->timeoutms);
1478#endif
1479}
1480
1481/**
1482 * qhttpclient->write(): Writes data to a HTTP/HTTPS stream.
1483 *
1484 * @param client qhttpclient object pointer.
1485 * @param buf a data pointer.
1486 * @param length content size to write.
1487 *
1488 * @return number of bytes written.
1489 */
1490static ssize_t write_(qhttpclient_t *client, const void *buf, size_t nbytes) {
1491#ifdef ENABLE_OPENSSL
1492 if (client->ssl == NULL) {
1493 return qio_write(client->socket, buf, nbytes, -1);
1494 } else {
1495 if (nbytes == 0) return 0;
1496
1497 struct SslConn *ssl = client->ssl;
1498 ssize_t total = 0;
1499 while (total < nbytes) {
1500 errno = 0;
1501 int wsize = SSL_write(ssl->ssl, buf + total, nbytes - total);
1502 if (wsize <= 0) {
1503 DEBUG("OpenSSL: %s (%d)",
1504 ERR_reason_error_string(ERR_get_error()), wsize);
1505 unsigned long sslerr = ERR_get_error();
1506 if (sslerr == SSL_ERROR_WANT_WRITE) {
1507 usleep(1);
1508 continue;
1509 }
1510 break;
1511 }
1512 total += wsize;
1513 }
1514
1515 DEBUG("SSL_write: %zd/%zu", total, nbytes);
1516 if (total > 0) return total;
1517 return -1;
1518 }
1519#else
1520 return qio_write(client->socket, buf, nbytes, -1);
1521#endif
1522}
1523
1524/**
1525 * qhttpclient->recvfile(): Reads data from a HTTP/HTTPS stream and save
1526 * into a file descriptor.
1527 *
1528 * @param client qhttpclient object pointer.
1529 * @param fd output file descriptor
1530 * @param nbytes the number of bytes to read and save.
1531 *
1532 * @return the number of bytes written if successful, otherwise returns -1.
1533 */
1534static off_t recvfile(qhttpclient_t *client, int fd, off_t nbytes) {
1535 if (nbytes == 0)
1536 return 0;
1537
1538 unsigned char buf[MAX_ATOMIC_DATA_SIZE];
1539
1540 off_t total = 0; // total size sent
1541 while (total < nbytes) {
1542 size_t chunksize; // this time sending size
1543 if (nbytes - total <= sizeof(buf))
1544 chunksize = nbytes - total;
1545 else
1546 chunksize = sizeof(buf);
1547
1548 // read
1549 ssize_t rsize = read_(client, buf, chunksize);
1550 if (rsize <= 0)
1551 break;
1552
1553 // write
1554 ssize_t wsize = qio_write(fd, buf, rsize, -1);
1555 DEBUG("FILE write: %zd", wsize);
1556 if (wsize <= 0)
1557 break;
1558
1559 total += wsize;
1560 if (rsize != wsize) {
1561 DEBUG("size mismatch. read:%zd, write:%zd", rsize, wsize);
1562 break;
1563 }
1564 }
1565
1566 if (total > 0)
1567 return total;
1568 return -1;
1569}
1570
1571/**
1572 * qhttpclient->sendfile(): Send file data to a HTTP/HTTPS stream.
1573 *
1574 * @param client qhttpclient object pointer.
1575 * @param fd input file descriptor
1576 * @param nbytes the number of bytes to read and send.
1577 *
1578 * @return the number of bytes sent if successful, otherwise returns -1.
1579 */
1580static off_t sendfile_(qhttpclient_t *client, int fd, off_t nbytes) {
1581 if (nbytes == 0)
1582 return 0;
1583
1584 unsigned char buf[MAX_ATOMIC_DATA_SIZE];
1585
1586 off_t total = 0; // total size sent
1587 while (total < nbytes) {
1588 size_t chunksize; // this time sending size
1589 if (nbytes - total <= sizeof(buf))
1590 chunksize = nbytes - total;
1591 else
1592 chunksize = sizeof(buf);
1593
1594 // read
1595 ssize_t rsize = qio_read(fd, buf, chunksize, -1);
1596 DEBUG("FILE read: %zd", rsize);
1597 if (rsize <= 0)
1598 break;
1599
1600 // write
1601 ssize_t wsize = write_(client, buf, rsize);
1602 if (wsize <= 0)
1603 break;
1604
1605 total += wsize;
1606 if (rsize != wsize) {
1607 DEBUG("size mismatch. read:%zd, write:%zd", rsize, wsize);
1608 break;
1609 }
1610 }
1611
1612 if (total > 0)
1613 return total;
1614 return -1;
1615}
1616
1617/**
1618 * qhttpclient->close(): Closes the connection.
1619 *
1620 * @param qhttpclient_t HTTP object pointer
1621 *
1622 * @return true if successful, otherwise returns false
1623 *
1624 * @code
1625 * httpclient->close(httpclient);
1626 * @endcode
1627 */
1628static bool _close(qhttpclient_t *client) {
1629 if (client->socket < 0)
1630 return false;
1631
1632#ifdef ENABLE_OPENSSL
1633 // release ssl connection
1634 if (client->ssl != NULL) {
1635 struct SslConn *ssl = client->ssl;
1636
1637 if (ssl->ssl != NULL) {
1638 SSL_shutdown(ssl->ssl);
1639 SSL_free(ssl->ssl);
1640 ssl->ssl = NULL;
1641 }
1642
1643 if (ssl->ctx != NULL) {
1644 SSL_CTX_free(ssl->ctx);
1645 ssl->ctx = NULL;
1646 }
1647 }
1648#endif
1649
1650 // shutdown connection
1651 if (client->ssl == NULL && MAX_SHUTDOWN_WAIT >= 0
1652 && shutdown(client->socket, SHUT_WR) == 0) {
1653 char buf[1024];
1654 while (qio_read(client->socket, buf, sizeof(buf), MAX_SHUTDOWN_WAIT) > 0);
1655 }
1656
1657 // close connection
1658 close(client->socket);
1659 client->socket = -1;
1660 client->connclose = false;
1661
1662 return true;
1663}
1664
1665/**
1666 * qhttpclient->free(): Free object.
1667 *
1668 * @param qhttpclient_t HTTP object pointer
1669 *
1670 * @note
1671 * If the connection was not closed, it will close the connection first prior
1672 * to de-allocate object.
1673 *
1674 * @code
1675 * httpclient->free(httpclient);
1676 * @endcode
1677 */
1678static void _free(qhttpclient_t *client) {
1679 if (client->socket >= 0) {
1680 client->close(client);
1681 }
1682
1683 if (client->ssl != NULL)
1684 free(client->ssl);
1685 if (client->hostname != NULL)
1686 free(client->hostname);
1687 if (client->useragent != NULL)
1688 free(client->useragent);
1689
1690 free(client);
1691}
1692
1693#ifndef _DOXYGEN_SKIP
1694static bool _set_socket_option(int socket) {
1695 bool ret = true;
1696
1697 // linger option
1698 if (SET_TCP_LINGER_TIMEOUT > 0) {
1699 struct linger li;
1700 li.l_onoff = 1;
1701 li.l_linger = SET_TCP_LINGER_TIMEOUT;
1702 if (setsockopt(socket, SOL_SOCKET, SO_LINGER, &li,
1703 sizeof(struct linger)) < 0) {
1704 ret = false;
1705 }
1706 }
1707
1708 // nodelay option
1709 if (SET_TCP_NODELAY > 0) {
1710 int so_tcpnodelay = 1;
1711 if (setsockopt(socket, IPPROTO_TCP, TCP_NODELAY, &so_tcpnodelay,
1712 sizeof(so_tcpnodelay)) < 0) {
1713 ret = false;
1714 }
1715 }
1716
1717 return ret;
1718}
1719
1720static bool _parse_uri(const char *uri, bool *protocol, char *hostname,
1721 size_t namesize, int *port) {
1722
1723 if (!strncasecmp(uri, "http://", CONST_STRLEN("http://"))) {
1724 *protocol = false;
1725 *port = 80;
1726 } else if (!strncasecmp(uri, "https://", CONST_STRLEN("https://"))) {
1727 *protocol = true;
1728 *port = 443;
1729 } else {
1730 return false;
1731 }
1732
1733 char *t1 = strstr(uri, "://");
1734 t1 += 3;
1735 char *t2 = strstr(t1, "/");
1736 if (t2 == NULL)
1737 t2 = (char *) uri + strlen(uri);
1738
1739 if (t2 - t1 + 1 > namesize)
1740 return false;
1741 qstrncpy(hostname, namesize, t1, t2 - t1);
1742
1743 t1 = strstr(hostname, ":");
1744 if (t1 != NULL) {
1745 *t1 = '\0';
1746 *port = atoi(t1 + 1);
1747 }
1748
1749 return true;
1750}
1751#endif /* _DOXYGEN_SKIP */
1752
1753#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 a 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(): Sends a custom request(method) to the remote host and reads it's response.
static off_t recvfile(qhttpclient_t *client, int fd, off_t nbytes)
qhttpclient->recvfile(): Reads data from a HTTP/HTTPS stream and save into a file descriptor.
static bool setssl(qhttpclient_t *client)
qhttpclient->setssl(): Sets connection to HTTPS connection
static ssize_t read_(qhttpclient_t *client, void *buf, size_t nbytes)
qhttpclient->read(): Reads data from a 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 a 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 & create 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(): Send file data to a HTTP/HTTPS stream.
static ssize_t gets_(qhttpclient_t *client, char *buf, size_t bufsize)
qhttpclient->gets(): Reads a text line from a 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:159
int qio_wait_readable(int fd, int timeoutms)
Test & wait until the file descriptor has readable data.
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 pointed to until either a terminating newline or E...
Definition qio.c:257
int qio_wait_writable(int fd, int timeoutms)
Test & wait until the 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:150
bool qsocket_get_addr(struct sockaddr_in *addr, const char *hostname, int port)
Convert hostname to sockaddr_in structure.
Definition qsocket.c:135
char * qstrcpy(char *dst, size_t size, const char *src)
Copy src string to dst.
Definition qstring.c:325
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:344
char * qstrtrim(char *str)
Remove white spaces(including CR, LF) from head and tail of the string.
Definition qstring.c:55