~ubuntu-branches/ubuntu/wily/pianobar/wily-proposed

« back to all changes in this revision

Viewing changes to src/libwaitress/waitress.c

  • Committer: Package Import Robot
  • Author(s): Luke Faraone
  • Date: 2011-11-16 14:26:36 UTC
  • mfrom: (1.3.6)
  • Revision ID: package-import@ubuntu.com-20111116142636-x2v716fs6wrybq46
Tags: 2011.11.11-1
* New upstream version 
  - SSL support and XMLRPC api version compatibility (v33) 
    (closes: #648209, LP: #887886)

Show diffs side-by-side

added added

removed removed

Lines of Context:
40
40
#include <errno.h>
41
41
#include <assert.h>
42
42
 
 
43
#include <gnutls/x509.h>
 
44
 
43
45
#include "config.h"
44
46
#include "waitress.h"
45
47
 
 
48
#define strcaseeq(a,b) (strcasecmp(a,b) == 0)
 
49
#define WAITRESS_HTTP_VERSION "1.1"
 
50
 
46
51
typedef struct {
47
 
        char *buf;
 
52
        char *data;
48
53
        size_t pos;
49
54
} WaitressFetchBufCbBuffer_t;
50
55
 
51
 
void WaitressInit (WaitressHandle_t *waith) {
 
56
WaitressReturn_t WaitressInit (WaitressHandle_t *waith, const char *caPath) {
 
57
        assert (waith != NULL);
 
58
 
52
59
        memset (waith, 0, sizeof (*waith));
53
 
        waith->socktimeout = 30000;
 
60
        waith->timeout = 30000;
 
61
        if (caPath != NULL) {
 
62
                gnutls_certificate_allocate_credentials (&waith->tlsCred);
 
63
                if (gnutls_certificate_set_x509_trust_file (waith->tlsCred, caPath,
 
64
                                GNUTLS_X509_FMT_PEM) <= 0) {
 
65
                        return WAITRESS_RET_TLS_TRUSTFILE_ERR;
 
66
                }
 
67
                waith->tlsInitialized = true;
 
68
        }
 
69
 
 
70
        return WAITRESS_RET_OK;
54
71
}
55
72
 
56
73
void WaitressFree (WaitressHandle_t *waith) {
 
74
        assert (waith != NULL);
 
75
 
57
76
        free (waith->url.url);
58
77
        free (waith->proxy.url);
 
78
        if (waith->tlsInitialized) {
 
79
                gnutls_certificate_free_credentials (waith->tlsCred);
 
80
        }
59
81
        memset (waith, 0, sizeof (*waith));
60
82
}
61
83
 
64
86
 *      @return true|false
65
87
 */
66
88
bool WaitressProxyEnabled (const WaitressHandle_t *waith) {
 
89
        assert (waith != NULL);
 
90
 
67
91
        return waith->proxy.host != NULL;
68
92
}
69
93
 
72
96
 *      @return malloc'ed encoded string, don't forget to free it
73
97
 */
74
98
char *WaitressUrlEncode (const char *in) {
 
99
        assert (in != NULL);
 
100
 
75
101
        size_t inLen = strlen (in);
76
102
        /* worst case: encode all characters */
77
103
        char *out = calloc (inLen * 3 + 1, sizeof (*in));
339
365
        return WaitressSplitUrl (url, &waith->proxy);
340
366
}
341
367
 
342
 
/*      Callback for WaitressFetchBuf, appends received data to NULL-terminated buffer
 
368
/*      Callback for WaitressFetchBuf, appends received data to \0-terminated
 
369
 *      buffer
343
370
 *      @param received data
344
371
 *      @param data size
345
372
 *      @param buffer structure
349
376
        char *recvBytes = recvData;
350
377
        WaitressFetchBufCbBuffer_t *buffer = extraData;
351
378
 
352
 
        if (buffer->buf == NULL) {
353
 
                if ((buffer->buf = malloc (sizeof (*buffer->buf) *
 
379
        if (buffer->data == NULL) {
 
380
                if ((buffer->data = malloc (sizeof (*buffer->data) *
354
381
                                (recvDataSize + 1))) == NULL) {
355
382
                        return WAITRESS_CB_RET_ERR;
356
383
                }
357
384
        } else {
358
385
                char *newbuf;
359
 
                if ((newbuf = realloc (buffer->buf,
360
 
                                sizeof (*buffer->buf) *
 
386
                if ((newbuf = realloc (buffer->data,
 
387
                                sizeof (*buffer->data) *
361
388
                                (buffer->pos + recvDataSize + 1))) == NULL) {
362
 
                        free (buffer->buf);
 
389
                        free (buffer->data);
363
390
                        return WAITRESS_CB_RET_ERR;
364
391
                }
365
 
                buffer->buf = newbuf;
 
392
                buffer->data = newbuf;
366
393
        }
367
 
        memcpy (buffer->buf + buffer->pos, recvBytes, recvDataSize);
 
394
        memcpy (buffer->data + buffer->pos, recvBytes, recvDataSize);
368
395
        buffer->pos += recvDataSize;
369
 
        *(buffer->buf+buffer->pos) = '\0';
 
396
        buffer->data[buffer->pos] = '\0';
370
397
 
371
398
        return WAITRESS_CB_RET_OK;
372
399
}
373
400
 
374
401
/*      Fetch string. Beware! This overwrites your waith->data pointer
375
402
 *      @param waitress handle
376
 
 *      @param result buffer, malloced (don't forget to free it yourself)
 
403
 *      @param \0-terminated result buffer, malloced (don't forget to free it
 
404
 *                      yourself)
377
405
 */
378
 
WaitressReturn_t WaitressFetchBuf (WaitressHandle_t *waith, char **buf) {
 
406
WaitressReturn_t WaitressFetchBuf (WaitressHandle_t *waith, char **retBuffer) {
379
407
        WaitressFetchBufCbBuffer_t buffer;
380
408
        WaitressReturn_t wRet;
381
409
 
 
410
        assert (waith != NULL);
 
411
        assert (retBuffer != NULL);
 
412
 
382
413
        memset (&buffer, 0, sizeof (buffer));
383
414
 
384
415
        waith->data = &buffer;
385
416
        waith->callback = WaitressFetchBufCb;
386
417
 
387
418
        wRet = WaitressFetchCall (waith);
388
 
        *buf = buffer.buf;
 
419
        *retBuffer = buffer.data;
389
420
        return wRet;
390
421
}
391
422
 
392
423
/*      poll wrapper that retries after signal interrupts, required for socksify
393
424
 *      wrapper
394
425
 */
395
 
static int WaitressPollLoop (struct pollfd *fds, nfds_t nfds, int timeout) {
 
426
static int WaitressPollLoop (int fd, short events, int timeout) {
396
427
        int pollres = -1;
397
 
        int pollerr = 0;
 
428
        struct pollfd sockpoll = {fd, events, 0};
 
429
 
 
430
        assert (fd != -1);
398
431
 
399
432
        do {
400
 
                pollres = poll (fds, nfds, timeout);
401
 
                pollerr = errno;
402
433
                errno = 0;
403
 
        } while (pollerr == EINTR || pollerr == EINPROGRESS || pollerr == EAGAIN);
 
434
                pollres = poll (&sockpoll, 1, timeout);
 
435
        } while (errno == EINTR || errno == EINPROGRESS || errno == EAGAIN);
404
436
 
405
437
        return pollres;
406
438
}
407
439
 
408
440
/*      write () wrapper with poll () timeout
409
 
 *      @param socket fd
 
441
 *      @param waitress handle
410
442
 *      @param write buffer
411
443
 *      @param write count bytes
412
 
 *      @param reuse existing pollfd structure
413
 
 *      @param timeout (microseconds)
414
 
 *      @return WAITRESS_RET_OK, WAITRESS_RET_TIMEOUT or WAITRESS_RET_ERR
 
444
 *      @return number of written bytes or -1 on error
415
445
 */
416
 
static WaitressReturn_t WaitressPollWrite (int sockfd, const char *buf, size_t count,
417
 
                struct pollfd *sockpoll, int timeout) {
 
446
static ssize_t WaitressPollWrite (WaitressHandle_t *waith,
 
447
                const char *buf, size_t count) {
418
448
        int pollres = -1;
419
 
 
420
 
        sockpoll->events = POLLOUT;
421
 
        pollres = WaitressPollLoop (sockpoll, 1, timeout);
 
449
        ssize_t retSize;
 
450
 
 
451
        assert (waith != NULL);
 
452
        assert (buf != NULL);
 
453
 
 
454
        /* FIXME: simplify logic */
 
455
        pollres = WaitressPollLoop (waith->request.sockfd, POLLOUT,
 
456
                        waith->timeout);
422
457
        if (pollres == 0) {
423
 
                return WAITRESS_RET_TIMEOUT;
 
458
                waith->request.readWriteRet = WAITRESS_RET_TIMEOUT;
 
459
                return -1;
424
460
        } else if (pollres == -1) {
425
 
                return WAITRESS_RET_ERR;
426
 
        }
427
 
        if (write (sockfd, buf, count) == -1) {
428
 
                return WAITRESS_RET_ERR;
429
 
        }
430
 
        return WAITRESS_RET_OK;
 
461
                waith->request.readWriteRet = WAITRESS_RET_ERR;
 
462
                return -1;
 
463
        }
 
464
        if ((retSize = write (waith->request.sockfd, buf, count)) == -1) {
 
465
                waith->request.readWriteRet = WAITRESS_RET_ERR;
 
466
                return -1;
 
467
        }
 
468
        waith->request.readWriteRet = WAITRESS_RET_OK;
 
469
        return retSize;
 
470
}
 
471
 
 
472
static WaitressReturn_t WaitressOrdinaryWrite (WaitressHandle_t *waith,
 
473
                const char *buf, const size_t size) {
 
474
        WaitressPollWrite (waith, buf, size);
 
475
        return waith->request.readWriteRet;
 
476
}
 
477
 
 
478
static WaitressReturn_t WaitressGnutlsWrite (WaitressHandle_t *waith,
 
479
                const char *buf, const size_t size) {
 
480
        if (gnutls_record_send (waith->request.tlsSession, buf, size) < 0) {
 
481
                return WAITRESS_RET_TLS_WRITE_ERR;
 
482
        }
 
483
        return waith->request.readWriteRet;
431
484
}
432
485
 
433
486
/*      read () wrapper with poll () timeout
434
 
 *      @param socket fd
 
487
 *      @param waitress handle
435
488
 *      @param write to this buf, not NULL terminated
436
489
 *      @param buffer size
437
 
 *      @param reuse existing pollfd struct
438
 
 *      @param timeout (in microseconds)
439
 
 *      @param read () return value/written bytes
440
 
 *      @return WAITRESS_RET_OK, WAITRESS_RET_TIMEOUT, WAITRESS_RET_ERR
 
490
 *      @return number of read bytes or -1 on error
441
491
 */
442
 
static WaitressReturn_t WaitressPollRead (int sockfd, char *buf, size_t count,
443
 
                struct pollfd *sockpoll, int timeout, ssize_t *retSize) {
 
492
static ssize_t WaitressPollRead (WaitressHandle_t *waith, char *buf,
 
493
                size_t count) {
444
494
        int pollres = -1;
445
 
 
446
 
        sockpoll->events = POLLIN;
447
 
        pollres = WaitressPollLoop (sockpoll, 1, timeout);
 
495
        ssize_t retSize;
 
496
 
 
497
        assert (waith != NULL);
 
498
        assert (buf != NULL);
 
499
 
 
500
        /* FIXME: simplify logic */
 
501
        pollres = WaitressPollLoop (waith->request.sockfd, POLLIN, waith->timeout);
448
502
        if (pollres == 0) {
449
 
                return WAITRESS_RET_TIMEOUT;
 
503
                waith->request.readWriteRet = WAITRESS_RET_TIMEOUT;
 
504
                return -1;
450
505
        } else if (pollres == -1) {
451
 
                return WAITRESS_RET_ERR;
452
 
        }
453
 
        if ((*retSize = read (sockfd, buf, count)) == -1) {
454
 
                return WAITRESS_RET_READ_ERR;
455
 
        }
456
 
        return WAITRESS_RET_OK;
457
 
}
458
 
 
459
 
/* FIXME: compiler macros are ugly... */
460
 
#define CLOSE_RET(ret) close (sockfd); return ret;
461
 
#define WRITE_RET(buf, count) \
462
 
                if ((wRet = WaitressPollWrite (sockfd, buf, count, \
463
 
                                &sockpoll, waith->socktimeout)) != WAITRESS_RET_OK) { \
464
 
                        CLOSE_RET (wRet); \
465
 
                }
466
 
#define READ_RET(buf, count, size) \
467
 
                if ((wRet = WaitressPollRead (sockfd, buf, count, \
468
 
                                &sockpoll, waith->socktimeout, size)) != WAITRESS_RET_OK) { \
469
 
                        CLOSE_RET (wRet); \
470
 
                }
 
506
                waith->request.readWriteRet = WAITRESS_RET_ERR;
 
507
                return -1;
 
508
        }
 
509
        if ((retSize = read (waith->request.sockfd, buf, count)) == -1) {
 
510
                waith->request.readWriteRet = WAITRESS_RET_READ_ERR;
 
511
                return -1;
 
512
        }
 
513
        waith->request.readWriteRet = WAITRESS_RET_OK;
 
514
        return retSize;
 
515
}
 
516
 
 
517
static WaitressReturn_t WaitressOrdinaryRead (WaitressHandle_t *waith,
 
518
                char *buf, const size_t size, size_t *retSize) {
 
519
        const ssize_t ret = WaitressPollRead (waith, buf, size);
 
520
        if (ret != -1) {
 
521
                *retSize = ret;
 
522
        }
 
523
        return waith->request.readWriteRet;
 
524
}
 
525
 
 
526
static WaitressReturn_t WaitressGnutlsRead (WaitressHandle_t *waith,
 
527
                char *buf, const size_t size, size_t *retSize) {
 
528
        ssize_t ret = gnutls_record_recv (waith->request.tlsSession, buf, size);
 
529
        if (ret < 0) {
 
530
                return WAITRESS_RET_TLS_READ_ERR;
 
531
        } else {
 
532
                *retSize = ret;
 
533
        }
 
534
        return waith->request.readWriteRet;
 
535
}
471
536
 
472
537
/*      send basic http authorization
473
538
 *      @param waitress handle
477
542
static bool WaitressFormatAuthorization (WaitressHandle_t *waith,
478
543
                WaitressUrl_t *url, const char *prefix, char *writeBuf,
479
544
                const size_t writeBufSize) {
 
545
        assert (waith != NULL);
 
546
        assert (url != NULL);
 
547
        assert (prefix != NULL);
 
548
        assert (writeBuf != NULL);
 
549
        assert (writeBufSize > 0);
 
550
 
480
551
        if (url->user != NULL) {
481
552
                char userPass[1024], *encodedUserPass;
482
553
                snprintf (userPass, sizeof (userPass), "%s:%s", url->user,
483
554
                                (url->password != NULL) ? url->password : "");
484
555
                encodedUserPass = WaitressBase64Encode (userPass);
 
556
                assert (encodedUserPass != NULL);
485
557
                snprintf (writeBuf, writeBufSize, "%sAuthorization: Basic %s\r\n",
486
558
                                prefix, encodedUserPass);
487
559
                free (encodedUserPass);
492
564
 
493
565
/*      get default http port if none was given
494
566
 */
495
 
static const char *WaitressDefaultPort (WaitressUrl_t *url) {
496
 
        return url->port == NULL ? "80" : url->port;
497
 
}
498
 
 
499
 
/*      Receive data from host and call *callback ()
500
 
 *      @param waitress handle
501
 
 *      @return WaitressReturn_t
502
 
 */
503
 
WaitressReturn_t WaitressFetchCall (WaitressHandle_t *waith) {
 
567
static const char *WaitressDefaultPort (const WaitressUrl_t * const url) {
 
568
        assert (url != NULL);
 
569
 
 
570
        return url->port == NULL ? (url->tls ? "443" : "80") : url->port;
 
571
}
 
572
 
 
573
/*      get line from string
 
574
 *      @param string beginning/return value of last call
 
575
 *      @return start of _next_ line or NULL if there is no next line
 
576
 */
 
577
static char *WaitressGetline (char * const str) {
 
578
        char *eol;
 
579
 
 
580
        assert (str != NULL);
 
581
 
 
582
        eol = strchr (str, '\n');
 
583
        if (eol == NULL) {
 
584
                return NULL;
 
585
        }
 
586
 
 
587
        /* make lines parseable by string routines */
 
588
        *eol = '\0';
 
589
        if (eol-1 >= str && *(eol-1) == '\r') {
 
590
                *(eol-1) = '\0';
 
591
        }
 
592
        /* skip \0 */
 
593
        ++eol;
 
594
 
 
595
        assert (eol >= str);
 
596
 
 
597
        return eol;
 
598
}
 
599
 
 
600
/*      identity encoding handler
 
601
 */
 
602
static WaitressHandlerReturn_t WaitressHandleIdentity (WaitressHandle_t *waith,
 
603
                char *buf, const size_t size) {
 
604
        assert (waith != NULL);
 
605
        assert (buf != NULL);
 
606
 
 
607
        waith->request.contentReceived += size;
 
608
        if (waith->callback (buf, size, waith->data) == WAITRESS_CB_RET_ERR) {
 
609
                return WAITRESS_HANDLER_ABORTED;
 
610
        } else {
 
611
                return WAITRESS_HANDLER_CONTINUE;
 
612
        }
 
613
}
 
614
 
 
615
/*      chunked encoding handler. buf must be \0-terminated, size does not include
 
616
 *      trailing \0.
 
617
 */
 
618
static WaitressHandlerReturn_t WaitressHandleChunked (WaitressHandle_t *waith,
 
619
                char *buf, const size_t size) {
 
620
        char *content = buf, *nextContent;
 
621
 
 
622
        assert (waith != NULL);
 
623
        assert (buf != NULL);
 
624
 
 
625
        while (1) {
 
626
                if (waith->request.chunkSize > 0) {
 
627
                        const size_t remaining = size-(content-buf);
 
628
 
 
629
                        if (remaining >= waith->request.chunkSize) {
 
630
                                if (WaitressHandleIdentity (waith, content,
 
631
                                                waith->request.chunkSize) == WAITRESS_HANDLER_ABORTED) {
 
632
                                        return WAITRESS_HANDLER_ABORTED;
 
633
                                }
 
634
 
 
635
                                content += waith->request.chunkSize;
 
636
                                if (content[0] == '\r' && content[1] == '\n') {
 
637
                                        content += 2;
 
638
                                } else {
 
639
                                        return WAITRESS_HANDLER_ERR;
 
640
                                }
 
641
                                waith->request.chunkSize = 0;
 
642
                        } else {
 
643
                                if (WaitressHandleIdentity (waith, content, remaining) ==
 
644
                                                WAITRESS_HANDLER_ABORTED) {
 
645
                                        return WAITRESS_HANDLER_ABORTED;
 
646
                                }
 
647
                                waith->request.chunkSize -= remaining;
 
648
                                return WAITRESS_HANDLER_CONTINUE;
 
649
                        }
 
650
                }
 
651
 
 
652
                if ((nextContent = WaitressGetline (content)) != NULL) {
 
653
                        const long int chunkSize = strtol (content, NULL, 16);
 
654
                        if (chunkSize == 0) {
 
655
                                return WAITRESS_HANDLER_DONE;
 
656
                        } else if (chunkSize < 0) {
 
657
                                return WAITRESS_HANDLER_ERR;
 
658
                        } else {
 
659
                                waith->request.chunkSize = chunkSize;
 
660
                                content = nextContent;
 
661
                        }
 
662
                } else {
 
663
                        return WAITRESS_HANDLER_ERR;
 
664
                }
 
665
        }
 
666
 
 
667
        assert (0);
 
668
        return WAITRESS_HANDLER_ERR;
 
669
}
 
670
 
 
671
/*      handle http header
 
672
 */
 
673
static void WaitressHandleHeader (WaitressHandle_t *waith, const char * const key,
 
674
                const char * const value) {
 
675
        assert (waith != NULL);
 
676
        assert (key != NULL);
 
677
        assert (value != NULL);
 
678
 
 
679
        if (strcaseeq (key, "Content-Length")) {
 
680
                waith->request.contentLength = atol (value);
 
681
        } else if (strcaseeq (key, "Transfer-Encoding")) {
 
682
                if (strcaseeq (value, "chunked")) {
 
683
                        waith->request.dataHandler = WaitressHandleChunked;
 
684
                }
 
685
        }
 
686
}
 
687
 
 
688
/*      parse http status line and return status code
 
689
 */
 
690
static int WaitressParseStatusline (const char * const line) {
 
691
        char status[4] = "000";
 
692
 
 
693
        assert (line != NULL);
 
694
 
 
695
        if (sscanf (line, "HTTP/1.%*1[0-9] %3[0-9] ", status) == 1) {
 
696
                return atoi (status);
 
697
        }
 
698
        return -1;
 
699
}
 
700
 
 
701
/*      verify server certificate
 
702
 */
 
703
static int WaitressTlsVerify (gnutls_session_t session) {
 
704
        unsigned int status, certListSize;
 
705
        const gnutls_datum_t *certList;
 
706
        gnutls_x509_crt_t cert;
 
707
        const WaitressHandle_t *waith;
 
708
 
 
709
        waith = gnutls_session_get_ptr (session);
 
710
        assert (waith != NULL);
 
711
 
 
712
        if (gnutls_certificate_verify_peers2 (session, &status) != GNUTLS_E_SUCCESS) {
 
713
                return GNUTLS_E_CERTIFICATE_ERROR;
 
714
        }
 
715
 
 
716
        /* don't accept invalid certs */
 
717
        if (status & (GNUTLS_CERT_INVALID | GNUTLS_CERT_SIGNER_NOT_FOUND |
 
718
                        GNUTLS_CERT_REVOKED | GNUTLS_CERT_EXPIRED |
 
719
                        GNUTLS_CERT_NOT_ACTIVATED)) {
 
720
                return GNUTLS_E_CERTIFICATE_ERROR;
 
721
        }
 
722
 
 
723
        if (gnutls_certificate_type_get (session) != GNUTLS_CRT_X509) {
 
724
                return GNUTLS_E_CERTIFICATE_ERROR;
 
725
        }
 
726
 
 
727
        /* check hostname */
 
728
        if ((certList = gnutls_certificate_get_peers (session,
 
729
                        &certListSize)) == NULL) {
 
730
                return GNUTLS_E_CERTIFICATE_ERROR;
 
731
        }
 
732
 
 
733
        if (gnutls_x509_crt_init (&cert) != GNUTLS_E_SUCCESS) {
 
734
                return GNUTLS_E_CERTIFICATE_ERROR;
 
735
        }
 
736
 
 
737
        if (gnutls_x509_crt_import (cert, &certList[0],
 
738
                        GNUTLS_X509_FMT_DER) != GNUTLS_E_SUCCESS) {
 
739
                return GNUTLS_E_CERTIFICATE_ERROR;
 
740
        }
 
741
 
 
742
        if (gnutls_x509_crt_check_hostname (cert, waith->url.host) == 0) {
 
743
                return GNUTLS_E_CERTIFICATE_ERROR;
 
744
        }
 
745
 
 
746
        gnutls_x509_crt_deinit (cert);
 
747
 
 
748
        return 0;
 
749
}
 
750
 
 
751
/*      Connect to server
 
752
 */
 
753
static WaitressReturn_t WaitressConnect (WaitressHandle_t *waith) {
504
754
        struct addrinfo hints, *res;
505
 
        int sockfd;
506
 
        char recvBuf[WAITRESS_RECV_BUFFER];
507
 
        char writeBuf[2*1024];
508
 
        ssize_t recvSize = 0;
509
 
        WaitressReturn_t wRet = WAITRESS_RET_OK;
510
 
        struct pollfd sockpoll;
511
755
        int pollres;
512
 
        /* header parser vars */
513
 
        char *nextLine = NULL, *thisLine = NULL;
514
 
        enum {HDRM_HEAD, HDRM_LINES, HDRM_FINISHED} hdrParseMode = HDRM_HEAD;
515
 
        char statusCode[3], val[256];
516
 
        unsigned int bufFilled = 0;
517
756
 
518
 
        /* initialize */
519
 
        waith->contentLength = 0;
520
 
        waith->contentReceived = 0;
521
757
        memset (&hints, 0, sizeof hints);
522
758
 
523
759
        hints.ai_family = AF_UNSPEC;
536
772
                }
537
773
        }
538
774
 
539
 
        if ((sockfd = socket (res->ai_family, res->ai_socktype, res->ai_protocol)) == -1) {
 
775
        if ((waith->request.sockfd = socket (res->ai_family, res->ai_socktype,
 
776
                        res->ai_protocol)) == -1) {
540
777
                freeaddrinfo (res);
541
778
                return WAITRESS_RET_SOCK_ERR;
542
779
        }
543
 
        sockpoll.fd = sockfd;
544
780
 
545
781
        /* we need shorter timeouts for connect() */
546
 
        fcntl (sockfd, F_SETFL, O_NONBLOCK);
 
782
        fcntl (waith->request.sockfd, F_SETFL, O_NONBLOCK);
547
783
 
548
784
        /* increase socket receive buffer */
549
785
        const int sockopt = 256*1024;
550
 
        setsockopt (sockfd, SOL_SOCKET, SO_RCVBUF, &sockopt, sizeof (sockopt));
 
786
        setsockopt (waith->request.sockfd, SOL_SOCKET, SO_RCVBUF, &sockopt,
 
787
                        sizeof (sockopt));
551
788
 
552
789
        /* non-blocking connect will return immediately */
553
 
        connect (sockfd, res->ai_addr, res->ai_addrlen);
 
790
        connect (waith->request.sockfd, res->ai_addr, res->ai_addrlen);
554
791
 
555
 
        sockpoll.events = POLLOUT;
556
 
        pollres = WaitressPollLoop (&sockpoll, 1, waith->socktimeout);
 
792
        pollres = WaitressPollLoop (waith->request.sockfd, POLLOUT,
 
793
                        waith->timeout);
557
794
        freeaddrinfo (res);
558
795
        if (pollres == 0) {
559
796
                return WAITRESS_RET_TIMEOUT;
562
799
        }
563
800
        /* check connect () return value */
564
801
        socklen_t pollresSize = sizeof (pollres);
565
 
        getsockopt (sockfd, SOL_SOCKET, SO_ERROR, &pollres, &pollresSize);
 
802
        getsockopt (waith->request.sockfd, SOL_SOCKET, SO_ERROR, &pollres,
 
803
                        &pollresSize);
566
804
        if (pollres != 0) {
567
805
                return WAITRESS_RET_CONNECT_REFUSED;
568
806
        }
569
807
 
 
808
        if (waith->url.tls) {
 
809
                /* set up proxy tunnel */
 
810
                if (WaitressProxyEnabled (waith)) {
 
811
                        char buf[256];
 
812
                        size_t size;
 
813
                        snprintf (buf, sizeof (buf), "CONNECT %s:%s HTTP/"
 
814
                                        WAITRESS_HTTP_VERSION "\r\n\r\n",
 
815
                                        waith->url.host, WaitressDefaultPort (&waith->url));
 
816
                        WaitressOrdinaryWrite (waith, buf, strlen (buf));
 
817
 
 
818
                        WaitressOrdinaryRead (waith, buf, sizeof (buf)-1, &size);
 
819
                        buf[size] = 0;
 
820
                        if (WaitressParseStatusline (buf) != 200) {
 
821
                                return WAITRESS_RET_CONNECT_REFUSED;
 
822
                        }
 
823
                }
 
824
 
 
825
                if (gnutls_handshake (waith->request.tlsSession) != GNUTLS_E_SUCCESS) {
 
826
                        return WAITRESS_RET_TLS_HANDSHAKE_ERR;
 
827
                }
 
828
        }
 
829
 
 
830
        return WAITRESS_RET_OK;
 
831
}
 
832
 
 
833
/*      Write http header/post data to socket
 
834
 */
 
835
static WaitressReturn_t WaitressSendRequest (WaitressHandle_t *waith) {
 
836
#define WRITE_RET(buf, count) \
 
837
                if ((wRet = waith->request.write (waith, buf, count)) != WAITRESS_RET_OK) { \
 
838
                        return wRet; \
 
839
                }
 
840
 
 
841
        assert (waith != NULL);
 
842
        assert (waith->request.buf != NULL);
 
843
 
570
844
        const char *path = waith->url.path;
 
845
        char * const buf = waith->request.buf;
 
846
        WaitressReturn_t wRet = WAITRESS_RET_OK;
 
847
 
571
848
        if (waith->url.path == NULL) {
572
849
                /* avoid NULL pointer deref */
573
850
                path = "";
577
854
        }
578
855
 
579
856
        /* send request */
580
 
        if (WaitressProxyEnabled (waith)) {
581
 
                snprintf (writeBuf, sizeof (writeBuf),
582
 
                        "%s http://%s:%s/%s HTTP/1.0\r\n",
 
857
        if (WaitressProxyEnabled (waith) && !waith->url.tls) {
 
858
                snprintf (buf, WAITRESS_BUFFER_SIZE,
 
859
                        "%s http://%s:%s/%s HTTP/" WAITRESS_HTTP_VERSION "\r\n",
583
860
                        (waith->method == WAITRESS_METHOD_GET ? "GET" : "POST"),
584
861
                        waith->url.host,
585
862
                        WaitressDefaultPort (&waith->url), path);
586
863
        } else {
587
 
                snprintf (writeBuf, sizeof (writeBuf),
588
 
                        "%s /%s HTTP/1.0\r\n",
 
864
                snprintf (buf, WAITRESS_BUFFER_SIZE,
 
865
                        "%s /%s HTTP/" WAITRESS_HTTP_VERSION "\r\n",
589
866
                        (waith->method == WAITRESS_METHOD_GET ? "GET" : "POST"),
590
867
                        path);
591
868
        }
592
 
        WRITE_RET (writeBuf, strlen (writeBuf));
 
869
        WRITE_RET (buf, strlen (buf));
593
870
 
594
 
        snprintf (writeBuf, sizeof (writeBuf),
595
 
                        "Host: %s\r\nUser-Agent: " PACKAGE "\r\n", waith->url.host);
596
 
        WRITE_RET (writeBuf, strlen (writeBuf));
 
871
        snprintf (buf, WAITRESS_BUFFER_SIZE,
 
872
                        "Host: %s\r\nUser-Agent: " PACKAGE "\r\nConnection: Close\r\n",
 
873
                        waith->url.host);
 
874
        WRITE_RET (buf, strlen (buf));
597
875
 
598
876
        if (waith->method == WAITRESS_METHOD_POST && waith->postData != NULL) {
599
 
                snprintf (writeBuf, sizeof (writeBuf), "Content-Length: %zu\r\n",
 
877
                snprintf (buf, WAITRESS_BUFFER_SIZE, "Content-Length: %zu\r\n",
600
878
                                strlen (waith->postData));
601
 
                WRITE_RET (writeBuf, strlen (writeBuf));
 
879
                WRITE_RET (buf, strlen (buf));
602
880
        }
603
881
 
604
882
        /* write authorization headers */
605
 
        if (WaitressFormatAuthorization (waith, &waith->url, "", writeBuf,
606
 
                        sizeof (writeBuf))) {
607
 
                WRITE_RET (writeBuf, strlen (writeBuf));
 
883
        if (WaitressFormatAuthorization (waith, &waith->url, "", buf,
 
884
                        WAITRESS_BUFFER_SIZE)) {
 
885
                WRITE_RET (buf, strlen (buf));
608
886
        }
609
887
        if (WaitressFormatAuthorization (waith, &waith->proxy, "Proxy-",
610
 
                        writeBuf, sizeof (writeBuf))) {
611
 
                WRITE_RET (writeBuf, strlen (writeBuf));
 
888
                        buf, WAITRESS_BUFFER_SIZE)) {
 
889
                WRITE_RET (buf, strlen (buf));
612
890
        }
613
891
        
614
892
        if (waith->extraHeaders != NULL) {
621
899
                WRITE_RET (waith->postData, strlen (waith->postData));
622
900
        }
623
901
 
 
902
        return WAITRESS_RET_OK;
 
903
#undef WRITE_RET
 
904
}
 
905
 
 
906
/*      read response header and data
 
907
 */
 
908
static WaitressReturn_t WaitressReceiveResponse (WaitressHandle_t *waith) {
 
909
#define READ_RET(buf, count, size) \
 
910
                if ((wRet = waith->request.read (waith, buf, count, size)) != \
 
911
                                WAITRESS_RET_OK) { \
 
912
                        return wRet; \
 
913
                }
 
914
 
 
915
        assert (waith != NULL);
 
916
        assert (waith->request.buf != NULL);
 
917
 
 
918
        char * const buf = waith->request.buf;
 
919
        char *nextLine = NULL, *thisLine = NULL;
 
920
        enum {HDRM_HEAD, HDRM_LINES, HDRM_FINISHED} hdrParseMode = HDRM_HEAD;
 
921
        ssize_t recvSize = 0;
 
922
        size_t bufFilled = 0;
 
923
        WaitressReturn_t wRet = WAITRESS_RET_OK;
 
924
 
624
925
        /* receive answer */
625
 
        nextLine = recvBuf;
 
926
        nextLine = buf;
626
927
        while (hdrParseMode != HDRM_FINISHED) {
627
 
                READ_RET (recvBuf+bufFilled, sizeof (recvBuf)-1 - bufFilled, &recvSize);
 
928
                READ_RET (buf+bufFilled, WAITRESS_BUFFER_SIZE-1 - bufFilled, &recvSize);
628
929
                if (recvSize == 0) {
629
930
                        /* connection closed too early */
630
 
                        CLOSE_RET (WAITRESS_RET_CONNECTION_CLOSED);
 
931
                        return WAITRESS_RET_CONNECTION_CLOSED;
631
932
                }
632
933
                bufFilled += recvSize;
633
 
                memset (recvBuf+bufFilled, 0, sizeof (recvBuf) - bufFilled);
634
 
                thisLine = recvBuf;
 
934
                buf[bufFilled] = '\0';
 
935
                thisLine = buf;
635
936
 
636
937
                /* split */
637
 
                while ((nextLine = strchr (thisLine, '\n')) != NULL &&
638
 
                                hdrParseMode != HDRM_FINISHED) {
639
 
                        /* make lines parseable by string routines */
640
 
                        *nextLine = '\0';
641
 
                        if (*(nextLine-1) == '\r') {
642
 
                                *(nextLine-1) = '\0';
643
 
                        }
644
 
                        /* skip \0 */
645
 
                        ++nextLine;
646
 
 
 
938
                while (hdrParseMode != HDRM_FINISHED &&
 
939
                                (nextLine = WaitressGetline (thisLine)) != NULL) {
647
940
                        switch (hdrParseMode) {
648
941
                                /* Status code */
649
942
                                case HDRM_HEAD:
650
 
                                        if (sscanf (thisLine, "HTTP/1.%*1[0-9] %3[0-9] ",
651
 
                                                        statusCode) == 1) {
652
 
                                                if (memcmp (statusCode, "200", 3) == 0 ||
653
 
                                                                memcmp (statusCode, "206", 3) == 0) {
654
 
                                                        /* everything's fine... */
655
 
                                                } else if (memcmp (statusCode, "403", 3) == 0) {
656
 
                                                        CLOSE_RET (WAITRESS_RET_FORBIDDEN);
657
 
                                                } else if (memcmp (statusCode, "404", 3) == 0) {
658
 
                                                        CLOSE_RET (WAITRESS_RET_NOTFOUND);
659
 
                                                } else {
660
 
                                                        CLOSE_RET (WAITRESS_RET_STATUS_UNKNOWN);
661
 
                                                }
662
 
                                                hdrParseMode = HDRM_LINES;
663
 
                                        } /* endif */
 
943
                                        switch (WaitressParseStatusline (thisLine)) {
 
944
                                                case 200:
 
945
                                                case 206:
 
946
                                                        hdrParseMode = HDRM_LINES;
 
947
                                                        break;
 
948
 
 
949
                                                case 403:
 
950
                                                        return WAITRESS_RET_FORBIDDEN;
 
951
                                                        break;
 
952
 
 
953
                                                case 404:
 
954
                                                        return WAITRESS_RET_NOTFOUND;
 
955
                                                        break;
 
956
 
 
957
                                                case -1:
 
958
                                                        /* ignore invalid line */
 
959
                                                        break;
 
960
 
 
961
                                                default:
 
962
                                                        return WAITRESS_RET_STATUS_UNKNOWN;
 
963
                                                        break;
 
964
                                        }
664
965
                                        break;
665
966
 
666
967
                                /* Everything else, except status code */
669
970
                                        if (*thisLine == '\0') {
670
971
                                                hdrParseMode = HDRM_FINISHED;
671
972
                                        } else {
672
 
                                                memset (val, 0, sizeof (val));
673
 
                                                if (sscanf (thisLine, "Content-Length: %255c", val) == 1) {
674
 
                                                        waith->contentLength = atol (val);
 
973
                                                /* parse header: "key: value", ignore invalid lines */
 
974
                                                char *key = thisLine, *val;
 
975
 
 
976
                                                val = strchr (thisLine, ':');
 
977
                                                if (val != NULL) {
 
978
                                                        *val++ = '\0';
 
979
                                                        while (*val != '\0' && isspace ((unsigned char) *val)) {
 
980
                                                                ++val;
 
981
                                                        }
 
982
                                                        WaitressHandleHeader (waith, key, val);
675
983
                                                }
676
984
                                        }
677
985
                                        break;
681
989
                        } /* end switch */
682
990
                        thisLine = nextLine;
683
991
                } /* end while strchr */
684
 
                memmove (recvBuf, thisLine, thisLine-recvBuf);
685
 
                bufFilled -= (thisLine-recvBuf);
 
992
                memmove (buf, thisLine, bufFilled-(thisLine-buf));
 
993
                bufFilled -= (thisLine-buf);
686
994
        } /* end while hdrParseMode */
687
995
 
688
 
        /* push remaining bytes */
689
 
        if (bufFilled > 0) {
690
 
                waith->contentReceived += bufFilled;
691
 
                if (waith->callback (thisLine, bufFilled, waith->data) ==
692
 
                                WAITRESS_CB_RET_ERR) {
693
 
                        CLOSE_RET (WAITRESS_RET_CB_ABORT);
694
 
                }
695
 
        }
696
 
 
697
 
        /* receive content */
 
996
        recvSize = bufFilled;
698
997
        do {
699
 
                READ_RET (recvBuf, sizeof (recvBuf), &recvSize);
700
 
                if (recvSize > 0) {
701
 
                        waith->contentReceived += recvSize;
702
 
                        if (waith->callback (recvBuf, recvSize, waith->data) ==
703
 
                                        WAITRESS_CB_RET_ERR) {
704
 
                                wRet = WAITRESS_RET_CB_ABORT;
705
 
                                break;
706
 
                        }
 
998
                /* data must be \0-terminated for chunked handler */
 
999
                buf[recvSize] = '\0';
 
1000
                switch (waith->request.dataHandler (waith, buf, recvSize)) {
 
1001
                        case WAITRESS_HANDLER_DONE:
 
1002
                                return WAITRESS_RET_OK;
 
1003
                                break;
 
1004
 
 
1005
                        case WAITRESS_HANDLER_ERR:
 
1006
                                return WAITRESS_RET_DECODING_ERR;
 
1007
                                break;
 
1008
 
 
1009
                        case WAITRESS_HANDLER_ABORTED:
 
1010
                                return WAITRESS_RET_CB_ABORT;
 
1011
                                break;
 
1012
 
 
1013
                        case WAITRESS_HANDLER_CONTINUE:
 
1014
                                /* go on */
 
1015
                                break;
707
1016
                }
 
1017
                READ_RET (buf, WAITRESS_BUFFER_SIZE-1, &recvSize);
708
1018
        } while (recvSize > 0);
709
1019
 
710
 
        close (sockfd);
711
 
 
712
 
        if (wRet == WAITRESS_RET_OK && waith->contentReceived < waith->contentLength) {
 
1020
        return WAITRESS_RET_OK;
 
1021
 
 
1022
#undef READ_RET
 
1023
}
 
1024
 
 
1025
/*      Receive data from host and call *callback ()
 
1026
 *      @param waitress handle
 
1027
 *      @return WaitressReturn_t
 
1028
 */
 
1029
WaitressReturn_t WaitressFetchCall (WaitressHandle_t *waith) {
 
1030
        WaitressReturn_t wRet = WAITRESS_RET_OK;
 
1031
 
 
1032
        /* initialize */
 
1033
        memset (&waith->request, 0, sizeof (waith->request));
 
1034
        waith->request.dataHandler = WaitressHandleIdentity;
 
1035
        waith->request.read = WaitressOrdinaryRead;
 
1036
        waith->request.write = WaitressOrdinaryWrite;
 
1037
 
 
1038
        if (waith->url.tls) {
 
1039
                assert (waith->tlsInitialized);
 
1040
 
 
1041
                waith->request.read = WaitressGnutlsRead;
 
1042
                waith->request.write = WaitressGnutlsWrite;
 
1043
                gnutls_init (&waith->request.tlsSession, GNUTLS_CLIENT);
 
1044
                const char *err;
 
1045
                if (gnutls_priority_set_direct (waith->request.tlsSession,
 
1046
                                "PERFORMANCE", &err) != GNUTLS_E_SUCCESS) {
 
1047
                        return WAITRESS_RET_ERR;
 
1048
                }
 
1049
                if (gnutls_credentials_set (waith->request.tlsSession,
 
1050
                                GNUTLS_CRD_CERTIFICATE,
 
1051
                                waith->tlsCred) != GNUTLS_E_SUCCESS) {
 
1052
                        return WAITRESS_RET_ERR;
 
1053
                }
 
1054
 
 
1055
                /* set up custom read/write functions */
 
1056
                gnutls_transport_set_ptr (waith->request.tlsSession,
 
1057
                                (gnutls_transport_ptr_t) waith);
 
1058
                gnutls_transport_set_pull_function (waith->request.tlsSession,
 
1059
                                WaitressPollRead);
 
1060
                gnutls_transport_set_push_function (waith->request.tlsSession,
 
1061
                                WaitressPollWrite);
 
1062
 
 
1063
                /* certificate verification function */
 
1064
                gnutls_session_set_ptr (waith->request.tlsSession,
 
1065
                                (gnutls_transport_ptr_t) waith);
 
1066
                gnutls_certificate_set_verify_function (waith->tlsCred,
 
1067
                                WaitressTlsVerify);
 
1068
        }
 
1069
 
 
1070
        /* request */
 
1071
        if ((wRet = WaitressConnect (waith)) == WAITRESS_RET_OK) {
 
1072
                waith->request.buf = malloc (WAITRESS_BUFFER_SIZE *
 
1073
                                sizeof (*waith->request.buf));
 
1074
 
 
1075
                if ((wRet = WaitressSendRequest (waith)) == WAITRESS_RET_OK) {
 
1076
                        wRet = WaitressReceiveResponse (waith);
 
1077
                }
 
1078
 
 
1079
                free (waith->request.buf);
 
1080
        }
 
1081
 
 
1082
        /* cleanup */
 
1083
        if (waith->url.tls) {
 
1084
                gnutls_bye (waith->request.tlsSession, GNUTLS_SHUT_RDWR);
 
1085
                gnutls_deinit (waith->request.tlsSession);
 
1086
        }
 
1087
        close (waith->request.sockfd);
 
1088
 
 
1089
        if (wRet == WAITRESS_RET_OK &&
 
1090
                        waith->request.contentReceived < waith->request.contentLength) {
713
1091
                return WAITRESS_RET_PARTIAL_FILE;
714
1092
        }
715
1093
        return wRet;
716
1094
}
717
1095
 
718
 
#undef CLOSE_RET
719
 
#undef WRITE_RET
720
 
#undef READ_RET
721
 
 
722
1096
const char *WaitressErrorToStr (WaitressReturn_t wRet) {
723
1097
        switch (wRet) {
724
1098
                case WAITRESS_RET_OK:
757
1131
                        return "Callback aborted request.";
758
1132
                        break;
759
1133
 
760
 
                case WAITRESS_RET_HDR_OVERFLOW:
761
 
                        return "HTTP header overflow.";
762
 
                        break;
763
 
 
764
1134
                case WAITRESS_RET_PARTIAL_FILE:
765
1135
                        return "Partial file.";
766
1136
                        break;
777
1147
                        return "Connection closed by remote host.";
778
1148
                        break;
779
1149
 
 
1150
                case WAITRESS_RET_DECODING_ERR:
 
1151
                        return "Invalid encoded data.";
 
1152
                        break;
 
1153
 
 
1154
                case WAITRESS_RET_TLS_WRITE_ERR:
 
1155
                        return "TLS write failed.";
 
1156
                        break;
 
1157
 
 
1158
                case WAITRESS_RET_TLS_READ_ERR:
 
1159
                        return "TLS read failed.";
 
1160
                        break;
 
1161
 
 
1162
                case WAITRESS_RET_TLS_HANDSHAKE_ERR:
 
1163
                        return "TLS handshake failed.";
 
1164
                        break;
 
1165
 
 
1166
                case WAITRESS_RET_TLS_TRUSTFILE_ERR:
 
1167
                        return "Loading root certificates failed.";
 
1168
                        break;
 
1169
 
780
1170
                default:
781
1171
                        return "No error message available.";
782
1172
                        break;
791
1181
#include <string.h>
792
1182
#include "waitress.h"
793
1183
 
794
 
#define streq(x,y) (strcmp(x,y) == 0)
 
1184
#define streq(a,b) (strcmp(a,b) == 0)
795
1185
 
796
1186
/*      string equality test (memory location or content)
797
1187
 */
899
1289
        compareStr (WaitressBase64Encode ("The quick brown fox jumped over the lazy do"),
900
1290
                        "VGhlIHF1aWNrIGJyb3duIGZveCBqdW1wZWQgb3ZlciB0aGUgbGF6eSBkbw==");
901
1291
 
 
1292
        gnutls_global_init ();
 
1293
        WaitressHandle_t waith;
 
1294
        char *buf;
 
1295
        WaitressInit (&waith);
 
1296
        WaitressSetUrl (&waith, "http://6xq.net:80/");
 
1297
        printf ("ret: %s\n", WaitressErrorToStr (WaitressFetchBuf (&waith, &buf)));
 
1298
        printf ("%s\n", buf);
 
1299
        free (buf);
 
1300
        WaitressFree (&waith);
 
1301
        gnutls_global_deinit ();
 
1302
 
902
1303
        return EXIT_SUCCESS;
903
1304
}
904
1305
#endif /* TEST */