1 /*
2 * Copyright 2008 The qDecoder Project. All rights reserved.
3 *
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions
6 * are met:
7 *
8 * 1. Redistributions of source code must retain the above copyright
9 * notice, this list of conditions and the following disclaimer.
10 * 2. Redistributions in binary form must reproduce the above copyright
11 * notice, this list of conditions and the following disclaimer in the
12 * documentation and/or other materials provided with the distribution.
13 *
14 * THIS SOFTWARE IS PROVIDED BY THE QDECODER PROJECT ``AS IS'' AND ANY
15 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
16 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
17 * DISCLAIMED. IN NO EVENT SHALL THE QDECODER PROJECT BE LIABLE FOR ANY
18 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
19 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
20 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
21 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
22 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
23 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
24 */
25
26 /**
27 * @file qHttpClient.c HTTP client API
28 */
29
30 #ifndef DISABLE_SOCKET
31
32 #include <stdio.h>
33 #include <stdlib.h>
34 #include <stdbool.h>
35 #include <string.h>
36 #include <unistd.h>
37 #include <errno.h>
38 #include <sys/types.h>
39 #include <sys/socket.h>
40 #include <netinet/in.h>
41 #include <arpa/inet.h>
42 #include "qDecoder.h"
43 #include "qInternal.h"
44
45 #ifndef _DOXYGEN_SKIP
46
47 static bool _open(Q_HTTPCLIENT *client);
48 static void _setTimeout(Q_HTTPCLIENT *client, int timeoutms);
49 static void _setKeepalive(Q_HTTPCLIENT *client, bool keepalive);
50 static void _setUseragent(Q_HTTPCLIENT *client, const char *agentname);
51 static bool _sendRequest(Q_HTTPCLIENT *client, const char *method, const char *uri, Q_ENTRY *reqheaders);
52 static int _readResponse(Q_HTTPCLIENT *client, Q_ENTRY *resheaders);
53 static bool _get(Q_HTTPCLIENT *client, const char *uri, int fd, off_t *savesize, int *rescode,
54 Q_ENTRY *reqheaders, Q_ENTRY *resheaders,
55 bool (*callback)(void *userdata, off_t recvbytes), void *userdata);
56 static bool _put(Q_HTTPCLIENT *client, const char *uri, int fd, off_t length, int *rescode,
57 Q_ENTRY *reqheaders, Q_ENTRY *resheaders,
58 bool (*callback)(void *userdata, off_t sentbytes), void *userdata);
59 static void *_cmd(Q_HTTPCLIENT *client, const char *method, const char *uri,
60 int *rescode, size_t *contentslength,
61 Q_ENTRY *reqheaders, Q_ENTRY *resheaders);
62 static bool _close(Q_HTTPCLIENT *client);
63 static void _free(Q_HTTPCLIENT *client);
64
65 // internal usages
66
67 #endif
68
69 #define HTTP_NO_RESPONSE (0)
70 #define HTTP_CODE_CONTINUE (100)
71 #define HTTP_CODE_OK (200)
72 #define HTTP_CODE_CREATED (201)
73 #define HTTP_CODE_NO_CONTENT (204)
74 #define HTTP_CODE_MULTI_STATUS (207)
75 #define HTTP_CODE_MOVED_TEMPORARILY (302)
76 #define HTTP_CODE_NOT_MODIFIED (304)
77 #define HTTP_CODE_BAD_REQUEST (400)
78 #define HTTP_CODE_FORBIDDEN (403)
79 #define HTTP_CODE_NOT_FOUND (404)
80 #define HTTP_CODE_METHOD_NOT_ALLOWED (405)
81 #define HTTP_CODE_REQUEST_TIME_OUT (408)
82 #define HTTP_CODE_REQUEST_URI_TOO_LONG (414)
83 #define HTTP_CODE_INTERNAL_SERVER_ERROR (500)
84 #define HTTP_CODE_NOT_IMPLEMENTED (501)
85 #define HTTP_CODE_SERVICE_UNAVAILABLE (503)
86
87 #define HTTP_PROTOCOL_10 "HTTP/1.0"
88 #define HTTP_PROTOCOL_11 "HTTP/1.1"
89
90 #define MAX_ATOMIC_DATA_SIZE (32 * 1024) /*< Maximum sending bytes, used in PUT method */
91
92 /**
93 * Initialize & create new HTTP client.
94 *
95 * @param hostname remote IP or FQDN domain name
96 * @param port remote port number
97 *
98 * @return HTTP client object if succcessful, otherwise returns NULL.
99 *
100 * @code
101 * // create new HTTP client
102 * Q_HTTPCLIENT *httpClient = qHttpClient("www.qdecoder.org", 80);
103 * if(httpClient == NULL) return;
104 *
105 * // set options
106 * httpClient->setKeepalive(httpClient, true);
107 *
108 * // make a connection
109 * if(httpClient->open(httpClient) == false) return;
110 *
111 * // upload files
112 * httpClient->put(httpClient, ...);
113 * httpClient->put(httpClient, ...); // this will be done within same connection using KEEP-ALIVE
114 *
115 * // close connection - not necessary, just for example
116 * httpClient->close(httpClient);
117 *
118 * // de-allocate HTTP client object
119 * httpClient->free(httpClient);
120 * @endcode
121 */
122 Q_HTTPCLIENT *qHttpClient(const char *hostname, int port) {
123 // get remote address
124 struct sockaddr_in addr;
125 if(qSocketGetAddr(&addr, hostname, port) == false) {
126 return NULL;
127 }
128
129 // allocate object
130 Q_HTTPCLIENT *client = (Q_HTTPCLIENT *)malloc(sizeof(Q_HTTPCLIENT));
131 if(client == NULL) return NULL;
132 memset((void*)client, 0, sizeof(Q_HTTPCLIENT));
133
134 // initialize object
135 client->socket = -1;
136
137 memcpy((void*)&client->addr, (void*)&addr, sizeof(client->addr));
138 client->hostname = strdup(hostname);
139 client->port = port;
140
141 // member methods
142 client->setTimeout = _setTimeout;
143 client->setKeepalive = _setKeepalive;
144 client->setUseragent = _setUseragent;
145
146 client->open = _open;
147 client->sendRequest = _sendRequest;
148 client->readResponse = _readResponse;
149 client->get = _get;
150 client->put = _put;
151 client->cmd = _cmd;
152 client->close = _close;
153 client->free = _free;
154
155 // init client
156 _setTimeout(client, 0);
157 _setKeepalive(client, false);
158 _setUseragent(client, _Q_PRGNAME "/" _Q_VERSION);
159
160 return client;
161 }
162
163 /**
164 * Q_HTTPCLIENT->setTimeout(): Set connection wait timeout.
165 *
166 * @param client Q_HTTPCLIENT object pointer
167 * @param timeoutms timeout mili-seconds. 0 for system defaults
168 *
169 * @code
170 * httpClient->setTimeout(httpClient, 0); // default
171 * httpClient->setTimeout(httpClient, 5000); // 5 seconds
172 * @endcode
173 */
174 static void _setTimeout(Q_HTTPCLIENT *client, int timeoutms) {
175 if(timeoutms <= 0) timeoutms = -1;
176 client->timeoutms = timeoutms;
177 }
178
179 /**
180 * Q_HTTPCLIENT->setKeepalive(): Set KEEP-ALIVE feature on/off.
181 *
182 * @param client Q_HTTPCLIENT object pointer
183 * @param keepalive true to set keep-alive on, false to set keep-alive off
184 *
185 * @code
186 * httpClient->setKeepalive(httpClient, true); // keep-alive on
187 * httpClient->setKeepalive(httpClient, false); // keep-alive off
188 * @endcode
189 */
190 static void _setKeepalive(Q_HTTPCLIENT *client, bool keepalive) {
191 client->keepalive = keepalive;
192 }
193
194 /**
195 * Q_HTTPCLIENT->setUseragent(): Set user-agent string.
196 *
197 * @param client Q_HTTPCLIENT object pointer
198 * @param useragent user-agent string
199 *
200 * @code
201 * httpClient->setUseragent(httpClient, "qDecoderAgent/1.0");
202 * @endcode
203 */
204 static void _setUseragent(Q_HTTPCLIENT *client, const char *useragent) {
205 if(client->useragent != NULL) free(client->useragent);
206 client->useragent = strdup(useragent);
207 }
208
209 /**
210 * Q_HTTPCLIENT->open(): Open(establish) connection to the remote host.
211 *
212 * @param client Q_HTTPCLIENT object pointer
213 *
214 * @return true if successful, otherwise returns false
215 *
216 * @note
217 * Don't need to open a connection unless you definitely need to do this, because qHttpClient open a connection automatically when it's needed.
218 * This function also can be used to veryfy a connection failure with remote host.
219 *
220 * @code
221 * if(httpClient->open(httpClient) == false) return;
222 * @endcode
223 */
224 static bool _open(Q_HTTPCLIENT *client) {
225 if(client->socket >= 0) {
226 if(qIoWaitWritable(client->socket, 0) > 0) return true;
227 _close(client);
228 }
229
230 // create new socket
231 int sockfd = socket(AF_INET, SOCK_STREAM, 0);
232 if (sockfd < 0) {
233 DEBUG("sockfd creation failed.");
234 return false;
235 }
236
237 // set to non-block socket
238 int sockflag = 0;
239 if(client->timeoutms > 0) {
240 sockflag = fcntl(sockfd, F_GETFL, 0);
241 fcntl(sockfd, F_SETFL, sockflag | O_NONBLOCK);
242 }
243
244 // try to connect
245 int status = connect(sockfd, (struct sockaddr *)&client->addr, sizeof(client->addr));
246 if(status < 0 && (errno != EINPROGRESS || qIoWaitWritable(sockfd, client->timeoutms) <= 0) ) {
247 DEBUG("connection failed.");
248 close(client->socket);
249 return false;
250 }
251
252 // restore to block socket
253 if(client->timeoutms > 0) {
254 fcntl(sockfd, F_SETFL, sockflag);
255 }
256
257 // store socket descriptor
258 client->socket = sockfd;
259
260 return true;
261 }
262
263 /**
264 * Q_HTTPCLIENT->sendRequest(): Send HTTP request to the remote host.
265 *
266 * @param client Q_HTTPCLIENT object pointer
267 * @param method HTTP method name
268 * @param uri URI string for the method. ("/path" or "http://.../path")
269 * @param reqheaders Q_ENTRY pointer which contains additional user request headers. (can be NULL)
270 *
271 * @return true if successful, otherwise returns false
272 *
273 * @note
274 * 3 default headers(Host, User-Agent, Connection) will be sent if reqheaders does not have those headers in it.
275 *
276 * @code
277 * Q_ENTRY *reqheaders = qEntry();
278 * reqheaders->putStr(reqheaders, "Date", qTimeGetGmtStaticStr(0), true);
279 *
280 * httpClient->sendRequest(client, "DELETE", "/img/qdecoder.png", reqheaders);
281 * @endcode
282 */
283 static bool _sendRequest(Q_HTTPCLIENT *client, const char *method, const char *uri, Q_ENTRY *reqheaders) {
284 if(_open(client) == false) {
285 return false;
286 }
287
288 // generate request headers if necessary
289 bool freeReqHeaders = false;
290 if(reqheaders == NULL) {
291 reqheaders = qEntry();
292 if(reqheaders == NULL) return false;
293 freeReqHeaders = true;
294 }
295
296 // append default headers
297 if(reqheaders->getCase(reqheaders, "Host", NULL, false) == NULL) {
298 reqheaders->putStrf(reqheaders, true, "Host", "%s:%d", client->hostname, client->port);
299 }
300 if(reqheaders->getCase(reqheaders, "User-Agent", NULL, false) == NULL) {
301 reqheaders->putStr(reqheaders, "User-Agent", client->useragent, true);
302 }
303 if(reqheaders->getCase(reqheaders, "Connection", NULL, false) == NULL) {
304 reqheaders->putStr(reqheaders, "Connection", (client->keepalive==true)?"Keep-Alive":"close", true);
305 }
306
307 // print out command
308 qIoPrintf(client->socket, client->timeoutms, "%s %s %s\r\n", method, uri, (client->keepalive==true)?HTTP_PROTOCOL_11:HTTP_PROTOCOL_10);
309
310 // print out headers
311 Q_NLOBJ_T obj;
312 memset((void*)&obj, 0, sizeof(obj)); // must be cleared before call
313 reqheaders->lock(reqheaders);
314 while(reqheaders->getNext(reqheaders, &obj, NULL, false) == true) {
315 qIoPrintf(client->socket, client->timeoutms, "%s: %s\r\n", obj.name, (char*)obj.data);
316 }
317 reqheaders->unlock(reqheaders);
318
319 qIoPrintf(client->socket, client->timeoutms, "\r\n");
320
321 // de-allocate
322 if(freeReqHeaders == true) reqheaders->free(reqheaders);
323
324 return true;
325 }
326
327 /**
328 * Q_HTTPCLIENT->readResponse(): Read and parse HTTP response from the remote host.
329 *
330 * @param client Q_HTTPCLIENT object pointer
331 * @param resheaders Q_ENTRY pointer for storing response headers. (can be NULL)
332 *
333 * @return numeric HTTP response code if successful, otherwise returns 0.
334 *
335 * @code
336 * // send request
337 * httpClient->sendRequest(client, "DELETE", "/img/qdecoder.png", NULL);
338 *
339 * // read response
340 * Q_ENTRY *resheaders = qEntry();
341 * int rescode = httpClient->readResponse(client, resheaders);
342 * @endcode
343 */
344 static int _readResponse(Q_HTTPCLIENT *client, Q_ENTRY *resheaders) {
345 // read response
346 char buf[1024];
347 if(qIoGets(buf, sizeof(buf), client->socket, client->timeoutms) <= 0) return HTTP_NO_RESPONSE;
348
349 // parse response code
350 if(strncmp(buf, "HTTP/", CONST_STRLEN("HTTP/"))) return HTTP_NO_RESPONSE;
351 char *tmp = strstr(buf, " ");
352 if(tmp == NULL) return HTTP_NO_RESPONSE;
353 int rescode = atoi(tmp+1);
354 if(rescode == 0) return HTTP_NO_RESPONSE;
355
356 // read headers
357 while(qIoGets(buf, sizeof(buf), client->socket, client->timeoutms) > 0) {
358 if(buf[0] == '\0') break;
359 if(resheaders != NULL) {
360 // parse header
361 char *value = strstr(buf, ":");
362 if(value != NULL) {
363 *value = '\0';
364 value += 1;
365 qStrTrim(value);
366 } else {
367 // missing colon
368 value = "";
369 }
370 resheaders->putStr(resheaders, buf, value, true);
371 }
372 }
373
374 return rescode;
375 }
376
377 /**
378 * Q_HTTPCLIENT->get(): Download file from remote host using GET method.
379 *
380 * @param client Q_HTTPCLIENT object pointer.
381 * @param uri remote URL for downloading file. ("/path" or "http://.../path")
382 * @param fd opened file descriptor for writing.
383 * @param savesize if not NULL, the length of stored bytes will be stored. (can be NULL)
384 * @param rescode if not NULL, remote response code will be stored. (can be NULL)
385 * @param reqheaders Q_ENTRY pointer which contains additional user request headers. (can be NULL)
386 * @param resheaders Q_ENTRY pointer for storing response headers. (can be NULL)
387 * @param callback set user call-back function. (can be NULL)
388 * @param userdata set user data for call-back. (can be NULL)
389 *
390 * @return true if successful, otherwise returns false
391 *
392 * @note
393 * The call-back function will be called peridically whenever it send data as much as MAX_ATOMIC_DATA_SIZE.
394 * To stop uploading, return false in the call-back function, then PUT process will be stopped immediately.
395 * If a connection was not opened, it will open a connection automatically.
396 *
397 * @code
398 * struct userdata {
399 * ...
400 * };
401 *
402 * static bool callback(void *userdata, off_t sentbytes) {
403 * struct userdata *pMydata = (struct userdata*)userdata;
404 * ...(codes)...
405 * if(need_to_cancel) return false; // stop file uploading immediately
406 * return true;
407 * }
408 *
409 * main() {
410 * // create new HTTP client
411 * Q_HTTPCLIENT *httpClient = qHttpClient("www.qdecoder.org", 80);
412 * if(httpClient == NULL) return;
413 *
414 * // open file
415 * int nFd = open("/tmp/test.data", O_WRONLY | O_CREAT, 0644);
416 *
417 * // set additional custom headers
418 * Q_ENTRY *pReqHeaders = qEntry();
419 * Q_ENTRY *pResHeaders = qEntry();
420 *
421 * // set userdata
422 * struct userdata mydata;
423 * ...(codes)...
424 *
425 * // send file
426 * int nRescode = 0;
427 * off_t nSavesize = 0;
428 * bool bRet = httpClient->get(httpClient, "/img/qdecoder.png", nFd, &nSavesize, &nRescode,
429 * pReqHeaders, pResHeaders,
430 * callback, (void*)&mydata);
431 * // to print out request, response headers
432 * pReqHeaders->print(pReqHeaders, stdout, true);
433 * pResHeaders->print(pResHeaders, stdout, true);
434 *
435 * // check results
436 * if(bRet == false) {
437 * ...(error occured)...
438 * }
439 *
440 * // free resources
441 * httpClient->free(httpClient);
442 * pReqHeaders->free(pReqHeaders);
443 * pResHeaders->free(pResHeaders);
444 * close(nFd);
445 * }
446 * @endcode
447 */
448 static bool _get(Q_HTTPCLIENT *client, const char *uri, int fd, off_t *savesize, int *rescode,
449 Q_ENTRY *reqheaders, Q_ENTRY *resheaders,
450 bool (*callback)(void *userdata, off_t recvbytes), void *userdata) {
451
452 // reset rescode
453 if(rescode != NULL) *rescode = 0;
454 if(savesize != NULL) *savesize = 0;
455
456 // generate request headers if necessary
457 bool freeReqHeaders = false;
458 if(reqheaders == NULL) {
459 reqheaders = qEntry();
460 freeReqHeaders = true;
461 }
462
463 // add additional headers
464 reqheaders->putStr(reqheaders, "Accept", "*/*", true);
465
466 // send request
467 bool sendRet = _sendRequest(client, "GET", uri, reqheaders);
468 if(freeReqHeaders == true) reqheaders->free(reqheaders);
469 if(sendRet == false) return false;
470
471 // generate response headers if necessary
472 bool freeResHeaders = false;
473 if(resheaders == NULL) {
474 resheaders = qEntry();
475 freeResHeaders = true;
476 }
477
478 // read response
479 int resno = _readResponse(client, resheaders);
480 if(rescode != NULL) *rescode = resno;
481
482 // parse contents-length
483 const char *clen_header = resheaders->getStrCase(resheaders, "Content-Length", false);
484 off_t length = 0;
485 if(clen_header != NULL) length = atoll(clen_header);
486
487 if(freeResHeaders == true) resheaders->free(resheaders);
488
489 // check response code
490 if(resno != HTTP_CODE_OK) {
491 _close(client);
492 if(freeResHeaders == true) resheaders->free(resheaders);
493 return false;
494 }
495
496 // retrieve data
497 off_t recv = 0;
498 if(callback != NULL) {
499 if(callback(userdata, recv) == false) {
500 _close(client);
501 return false;
502 }
503 }
504 if(length > 0) {
505 while(recv < length) {
506 size_t recvsize; // this time receive size
507 if(length - recv < MAX_ATOMIC_DATA_SIZE) recvsize = length - recv;
508 else recvsize = MAX_ATOMIC_DATA_SIZE;
509
510 ssize_t ret = qIoSend(fd, client->socket, recvsize, client->timeoutms);
511 if(ret <= 0) break; // Connection closed by peer
512 recv += ret;
513 if(savesize != NULL) *savesize = recv;
514
515 if(callback != NULL) {
516 if(callback(userdata, recv) == false) {
517 _close(client);
518 return false;
519 }
520 }
521 }
522
523 if(recv != length) {
524 _close(client);
525 return false;
526 }
527
528 if(callback != NULL) {
529 if(callback(userdata, recv) == false) {
530 _close(client);
531 return false;
532 }
533 }
534 }
535
536 // close connection
537 if(client->keepalive == false) {
538 _close(client);
539 }
540
541 return true;
542 }
543
544 /**
545 * Q_HTTPCLIENT->put(): Upload file to remote host using PUT method.
546 *
547 * @param client Q_HTTPCLIENT object pointer.
548 * @param uri remote URL for uploading file. ("/path" or "http://.../path")
549 * @param fd opened file descriptor for reading.
550 * @param length send size.
551 * @param rescode if not NULL, remote response code will be stored. (can be NULL)
552 * @param reqheaders Q_ENTRY pointer which contains additional user request headers. (can be NULL)
553 * @param resheaders Q_ENTRY pointer for storing response headers. (can be NULL)
554 * @param callback set user call-back function. (can be NULL)
555 * @param userdata set user data for call-back. (can be NULL)
556 *
557 * @return true if successful, otherwise returns false
558 *
559 * @note
560 * The call-back function will be called peridically whenever it send data as much as MAX_ATOMIC_DATA_SIZE.
561 * To stop uploading, return false in the call-back function, then PUT process will be stopped immediately.
562 * If a connection was not opened, it will open a connection automatically.
563 *
564 * @code
565 * struct userdata {
566 * ...
567 * };
568 *
569 * static bool callback(void *userdata, off_t sentbytes) {
570 * struct userdata *pMydata = (struct userdata*)userdata;
571 * ...(codes)...
572 * if(need_to_cancel) return false; // stop file uploading immediately
573 * return true;
574 * }
575 *
576 * main() {
577 * // create new HTTP client
578 * Q_HTTPCLIENT *httpClient = qHttpClient("www.qdecoder.org", 80);
579 * if(httpClient == NULL) return;
580 *
581 * // open file
582 * int nFd = open(...);
583 * off_t nFileSize = ...;
584 * char *pFileMd5sum = ...;
585 * time_t nFileDate = ...;
586 *
587 * // set additional custom headers
588 * Q_ENTRY *pReqHeaders = qEntry();
589 * pReqHeaders->putStr(pReqHeaders, "X-FILE-MD5SUM", pFileMd5sum, true);
590 * pReqHeaders->putInt(pReqHeaders, "X-FILE-DATE", nFileDate, true);
591 *
592 * // set userdata
593 * struct userdata mydata;
594 * ...(codes)...
595 *
596 * // send file
597 * int nRescode = 0;
598 * Q_ENTRY *pResHeaders = qEntry();
599 * bool bRet = httpClient->put(httpClient, "/img/qdecoder.png", nFd, nFileSize, &nRescode,
600 * pReqHeaders, pResHeaders,
601 * callback, (void*)&mydata);
602 * // to print out request, response headers
603 * pReqHeaders->print(pReqHeaders, stdout, true);
604 * pResHeaders->print(pResHeaders, stdout, true);
605 *
606 * // check results
607 * if(bRet == false) {
608 * ...(error occured)...
609 * }
610 *
611 * // free resources
612 * httpClient->free(httpClient);
613 * pReqHeaders->free(pReqHeaders);
614 * pResHeaders->free(pResHeaders);
615 * close(nFd);
616 * }
617 * @endcode
618 */
619 static bool _put(Q_HTTPCLIENT *client, const char *uri, int fd, off_t length, int *rescode,
620 Q_ENTRY *reqheaders, Q_ENTRY *resheaders,
621 bool (*callback)(void *userdata, off_t sentbytes), void *userdata) {
622
623 // reset rescode
624 if(rescode != NULL) *rescode = 0;
625
626 // generate request headers
627 bool freeReqHeaders = false;
628 if(reqheaders == NULL) {
629 reqheaders = qEntry();
630 freeReqHeaders = true;
631 }
632
633 // add additional headers
634 reqheaders->putStrf(reqheaders, true, "Content-Length", "%jd", length);
635 reqheaders->putStr(reqheaders, "Expect", "100-continue", true);
636
637 // send request
638 bool sendRet =_sendRequest(client, "PUT", uri, reqheaders);
639 if(freeReqHeaders == true) reqheaders->free(reqheaders);
640 if(sendRet == false) return false;
641
642 // wait 100-continue
643 if(qIoWaitReadable(client->socket, client->timeoutms) <= 0) {
644 DEBUG("timed out %d", client->timeoutms);
645 _close(client);
646 return false;
647 }
648
649 // read response
650 int resno = _readResponse(client, resheaders);
651 if(resno != HTTP_CODE_CONTINUE) {
652 if(rescode != NULL) *rescode = resno;
653 _close(client);
654 return false;
655 }
656
657 // send data
658 off_t sent = 0;
659 if(callback != NULL) {
660 if(callback(userdata, sent) == false) {
661 _close(client);
662 return false;
663 }
664 }
665 if(length > 0) {
666 while(sent < length) {
667 size_t sendsize; // this time sending size
668 if(length - sent < MAX_ATOMIC_DATA_SIZE) sendsize = length - sent;
669 else sendsize = MAX_ATOMIC_DATA_SIZE;
670
671 ssize_t ret = qIoSend(client->socket, fd, sendsize, client->timeoutms);
672 if(ret <= 0) break; // Connection closed by peer
673 sent += ret;
674
675 if(callback != NULL) {
676 if(callback(userdata, sent) == false) {
677 _close(client);
678 return false;
679 }
680 }
681 }
682
683 if(sent != length) {
684 _close(client);
685 return false;
686 }
687
688 if(callback != NULL) {
689 if(callback(userdata, sent) == false) {
690 _close(client);
691 return false;
692 }
693 }
694 }
695
696 // read response
697 resno = _readResponse(client, resheaders);
698 if(rescode != NULL) *rescode = resno;
699
700 if(resno == HTTP_NO_RESPONSE) {
701 _close(client);
702 return false;
703 }
704
705 if(resno != HTTP_CODE_CREATED) {
706 _close(client);
707 return false;
708 }
709
710 // close connection
711 if(client->keepalive == false) {
712 _close(client);
713 }
714
715 return true;
716 }
717
718 /**
719 * Q_HTTPCLIENT->cmd(): Send custom method to remote host.
720 *
721 * @param client Q_HTTPCLIENT object pointer.
722 * @param method method name.
723 * @param uri remote URL for uploading file. ("/path" or "http://.../path")
724 * @param rescode if not NULL, remote response code will be stored. (can be NULL)
725 * @param contentslength if not NULL, the contents length will be stored. (can be NULL)
726 * @param reqheaders Q_ENTRY pointer which contains additional user request headers. (can be NULL)
727 * @param resheaders Q_ENTRY pointer for storing response headers. (can be NULL)
728 *
729 * @return malloced contents data if successful, otherwise returns NULL
730 *
731 * @code
732 * int nResCode;
733 * size_t nContentsLength;
734 * void *contents = httpClient->cmd(httpClient, "DELETE" "/img/qdecoder.png",
735 * &nRescode, &nContentsLength
736 * NULL, NULL);
737 * if(contents == NULL) {
738 * ...(error occured)...
739 * } else {
740 * printf("Response code : %d\n", nResCode);
741 * printf("Contents length : %zu\n", nContentsLength);
742 * printf("Contents : %s\n", (char*)contents); // if contents is printable
743 * free(contents); // de-allocate
744 * }
745 * @endcode
746 */
747 static void *_cmd(Q_HTTPCLIENT *client, const char *method, const char *uri,
748 int *rescode, size_t *contentslength,
749 Q_ENTRY *reqheaders, Q_ENTRY *resheaders) {
750
751 // reset rescode
752 if(rescode != NULL) *rescode = 0;
753 if(contentslength != NULL) *contentslength = 0;
754
755 // send request
756 if(_sendRequest(client, method, uri, reqheaders) == false) {
757 return NULL;
758 }
759
760 // read response
761 bool freeResHeaders = false;
762 if(resheaders == NULL) {
763 resheaders = qEntry();
764 freeResHeaders = true;
765 }
766
767 int resno = _readResponse(client, resheaders);
768 if(rescode != NULL) *rescode = resno;
769
770 // parse contents-length
771 size_t length = resheaders->getInt(resheaders, "Content-Length");
772 if(freeResHeaders == true) resheaders->free(resheaders);
773
774 // malloc data
775 void *contents = NULL;
776 if(length > 0) {
777 contents = malloc(length + 1);
778 if(contents != NULL) {
779 if(qIoRead(contents, client->socket, length, client->timeoutms) != length) {
780 free(contents);
781 contents = NULL;
782 } else {
783 *(char*)(contents + length) = '\0';
784 }
785 }
786 }
787
788 // close connection
789 if(client->keepalive == false) {
790 _close(client);
791 }
792
793 if(contents == NULL) {
794 contents = strdup("");
795 length = strlen(contents);
796 }
797 if(contentslength != NULL) *contentslength = length;
798
799 return contents;
800 }
801
802 /**
803 * Q_HTTPCLIENT->close(): Close the connection.
804 *
805 * @param Q_HTTPCLIENT HTTP object pointer
806 *
807 * @return true if successful, otherwise returns false
808 *
809 * @code
810 * httpClient->close(httpClient);
811 * @endcode
812 */
813 static bool _close(Q_HTTPCLIENT *client) {
814 if(client->socket < 0) return true;
815
816 // shutdown connection
817 qSocketClose(client->socket);
818 client->socket = -1;
819
820 return true;
821 }
822
823 /**
824 * Q_HTTPCLIENT->free(): De-allocate object.
825 *
826 * @param Q_HTTPCLIENT HTTP object pointer
827 *
828 * @note
829 * If the connection was not closed, it will close the connection first prior to de-allocate object.
830 *
831 * @code
832 * httpClient->free(httpClient);
833 * @endcode
834 */
835 static void _free(Q_HTTPCLIENT *client) {
836 if(client->socket >= 0) {
837 client->close(client);
838 }
839
840 if(client->hostname != NULL) free(client->hostname);
841 if(client->useragent != NULL) free(client->useragent);
842
843 free(client);
844 }
845
846 #endif /* DISABLE_SOCKET */