summaryrefslogtreecommitdiff
path: root/imap/src/c-client/http.c
diff options
context:
space:
mode:
Diffstat (limited to 'imap/src/c-client/http.c')
-rw-r--r--imap/src/c-client/http.c1215
1 files changed, 1215 insertions, 0 deletions
diff --git a/imap/src/c-client/http.c b/imap/src/c-client/http.c
new file mode 100644
index 00000000..1e580c6a
--- /dev/null
+++ b/imap/src/c-client/http.c
@@ -0,0 +1,1215 @@
+/*
+ * Copyright 2018 Eduardo Chappa
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ */
+#include <ctype.h>
+#include <stdio.h>
+#include <time.h>
+#include "c-client.h"
+#include "flstring.h"
+#include "netmsg.h"
+#include "http.h"
+
+//char t[] = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!#$%&'*+-.^_`|~";
+static char http_notok[] = "\1\2\3\4\5\6\7\10\11\12\13\14\15\16\17\20\21\22\23\24\25\26\27\30\31\32\33\34\35\36\37\40\42\50\51\54\57\72\73\74\75\76\77\100\133\134\135\173\175\177\200\201\202\203\204\205\206\207\210\211\212\213\214\215\216\217\220\221\222\223\224\225\226\227\230\231\232\233\234\235\236\237\240\241\242\243\244\245\246\247\250\251\252\253\254\255\256\257\260\261\262\263\264\265\266\267\270\271\272\273\274\275\276\277\300\301\302\303\304\305\306\307\310\311\312\313\314\315\316\317\320\321\322\323\324\325\326\327\330\331\332\333\334\335\336\337\340\341\342\343\344\345\346\347\350\351\352\353\354\355\356\357\360\361\362\363\364\365\366\367\370\371\372\373\374\375\376\377";
+static char http_noparam_val[] = "\1\2\3\4\5\6\7\10\12\13\14\15\16\17\20\21\22\23\24\25\26\27\30\31\32\33\34\35\36\37\42\134\177";
+
+#define HTTPTCPPORT (long) 80 /* assigned TCP contact port */
+#define HTTPSSLPORT (long) 443 /* assigned SSL TCP contact port */
+
+typedef struct http_request_s {
+ char *request;
+ char *header;
+ char *body;
+} HTTP_REQUEST_S;
+
+#define HTP_NOVAL 0x001 /* the header accepts parameters without value */
+
+#define HTP_UNLIMITED (-1) /* parse and infinite list */
+
+#if 0
+typedef struct http_val_param_s {
+ char *value;
+ PARAMETER *plist;
+} HTTP_VAL_PARAM_S;
+
+typedef struct http_param_list_s {
+ HTTP_VAL_PARAM_S *vp;
+ struct http_param_list_s *next;
+} HTTP_PARAM_LIST_S;
+
+typedef struct http_header_value_s {
+ char *data;
+ HTTP_PARAM_LIST_S *p;
+} HTTP_HEADER_S;
+
+typedef struct http_header_data_s {
+ HTTP_HEADER_S *accept, /* RFC 7231, Section 5.3.2 */
+ *accept_charset, /* RFC 7231, Section 5.3.3 */
+ *accept_encoding, /* RFC 7231, Section 5.3.4 */
+ *accept_language, /* RFC 7231, Section 5.3.5 */
+ *accept_ranges, /* RFC 7233, Section 2.3 */
+ *age, /* RFC 7234, Section 5.1 */
+ *allow, /* RFC 7231, Section 7.4.1 */
+ *cache_control, /* RFC 7234, Section 5.2 */
+ *connection, /* RFC 7230, Section 6.1 */
+ *content_encoding, /* RFC 7231, Section 3.1.2.2 */
+ *content_disposition, /* RFC 6266 */
+ *content_language, /* RFC 7231, Section 3.1.3.2 */
+ *content_length, /* RFC 7230, Section 3.3.2 */
+ *content_location, /* RFC 7231, Section 3.1.4.2 */
+ *content_type, /* RFC 7231, Section 3.1.1.5 */
+ *date, /* RFC 7231, Section 7.1.1.2 */
+ *etag, /* RFC 7232, Section 2.3 */
+ *expect, /* RFC 7231, Section 5.1.1 */
+ *expires, /* RFC 7234, Section 5.3 */
+ *from, /* RFC 7231, Section 5.5.1 */
+ *host, /* RFC 7230, Section 5.4 */
+ *last_modified, /* RFC 7232, Section 2.2 */
+ *location, /* RFC 7231, Section 7.1.2 */
+ *max_forwards, /* RFC 7231, Section 5.1.2 */
+ *mime_version, /* RFC 7231, Appendix A.1 */
+ *pragma, /* RFC 7234, Section 5.4 */
+ *proxy_authenticate, /* RFC 7235, Section 4.3 */
+ *referer, /* RFC 7231, Section 5.5.2 */
+ *retry_after, /* RFC 7231, Section 7.1.3 */
+ *server, /* RFC 7231, Section 7.4.2 */
+ *te, /* RFC 7230, Section 4.3 */
+ *trailer, /* RFC 7230, Section 4.4 */
+ *transfer_encoding, /* RFC 7230, Section 3.3.1 */
+ *upgrade, /* RFC 7230, Section 6.7 */
+ *user_agent, /* RFC 7231, Section 5.5.3 */
+ *via, /* RFC 7230, Section 5.7.1 */
+ *vary, /* RFC 7231, Section 7.1.4 */
+ *warning, /* RFC 7234, Section 5.5 */
+ *www_authenticate; /* RFC 7235, Section 4.1 */
+} HTTP_HEADER_DATA_S;
+#endif
+
+/* helper functions */
+HTTP_STATUS_S *http_status_line_get(char *);
+void http_status_line_free(HTTP_STATUS_S **);
+HTTP_REQUEST_S *http_request_get(void);
+void http_request_free(HTTP_REQUEST_S **);
+char *http_request_line(char *, char *, char *);
+void http_add_header(HTTP_REQUEST_S **, char *, char *);
+void http_add_body(HTTP_REQUEST_S **, char *);
+void buffer_add(char **, char *);
+char *hex_escape_url_part(char *, char *);
+char *encode_url_body_part(char *, char *);
+
+
+/* HTTP function prototypes */
+int http_valid_net_parse (char *, NETMBX *);
+void *http_parameters (long function,void *value);
+
+long http_send (HTTPSTREAM *stream, HTTP_REQUEST_S *);
+long http_reply (HTTPSTREAM *stream);
+long http_fake (HTTPSTREAM *stream, char *text);
+
+void http_skipows(char **);
+void http_remove_trailing_ows(char *);
+
+int valid_dquote_text(char *);
+#define valid_token_name(X) (strpbrk((X), http_notok) ? 0 : 1)
+#define valid_parameter_value(X) \
+ ((valid_token_name((X)) || valid_dquote_text((X))) ? 1 : 0)
+
+/* HTTP HEADER FUNCTIONS */
+void http_add_header_data(HTTPSTREAM *, char *);
+void http_add_data_to_header(HTTP_HEADER_S **, char *);
+
+HTTP_PARAM_LIST_S *http_parse_token_parameter(char *, int);
+HTTP_PARAM_LIST_S *http_parse_token_list(char *, int);
+PARAMETER *http_parse_parameter(char *, int);
+
+void http_parse_headers(HTTPSTREAM *);
+
+void
+http_parse_headers(HTTPSTREAM *stream)
+{
+ HTTP_HEADER_DATA_S *hd;
+ HTTP_HEADER_S *h;
+
+ if(!stream || !stream->header) return;
+
+ hd = stream->header;
+
+ if(((h = hd->accept)) && h->data){ /* RFC 7231, Section 5.3.2 */
+ h->p = http_parse_token_parameter(h->data, HTP_NOVAL);
+ fs_give((void **) &h->data);
+ }
+
+ if(((h = hd->accept_charset)) && h->data){ /* RFC 7231, Section 5.3.3 */
+ h->p = http_parse_token_parameter(h->data, 0);
+ fs_give((void **) &h->data);
+ }
+
+ if(((h = hd->accept_encoding)) && h->data){ /* RFC 7231, Section 5.3.4 */
+ h->p = http_parse_token_parameter(h->data, 0);
+ fs_give((void **) &h->data);
+ }
+
+ if(((h = hd->accept_language)) && h->data){ /* RFC 7231, Section 5.3.5 */
+ h->p = http_parse_token_parameter(h->data, 0);
+ fs_give((void **) &h->data);
+ }
+
+ if(((h = hd->accept_ranges)) && h->data){ /* RFC 7233, Section 2.3 */
+ h->p = http_parse_token_parameter(h->data, 0);
+ fs_give((void **) &h->data);
+ }
+
+ if(((h = hd->age)) && h->data){ /* RFC 7234, Section 5.1 */
+ h->p = http_parse_token_list(h->data, 1);
+ fs_give((void **) &h->data);
+ }
+
+ if(((h = hd->allow)) && h->data){ /* RFC 7231, Section 7.4.1 */
+ h->p = http_parse_token_list(h->data, HTP_UNLIMITED);
+ fs_give((void **) &h->data);
+ }
+
+ if(((h = hd->cache_control)) && h->data){ /* RFC 7234, Section 5.2 */
+ h->p = http_parse_token_parameter(h->data, HTP_NOVAL);
+ fs_give((void **) &h->data);
+ }
+
+ if(((h = hd->connection)) && h->data){ /* RFC 7230, Section 6.1 */
+ h->p = http_parse_token_list(h->data, HTP_UNLIMITED);
+ fs_give((void **) &h->data);
+ }
+
+ if(((h = hd->content_encoding)) && h->data){ /* RFC 7231, Section 3.1.2.2 */
+ h->p = http_parse_token_list(h->data, HTP_UNLIMITED);
+ fs_give((void **) &h->data);
+ }
+
+ if(((h = hd->content_disposition)) && h->data){ /* RFC 6266 */
+ h->p = http_parse_token_parameter(h->data, HTP_NOVAL);
+ fs_give((void **) &h->data);
+ }
+
+ if(((h = hd->content_language)) && h->data){ /* RFC 7231, Section 3.1.3.2 */
+ h->p = http_parse_token_list(h->data, HTP_UNLIMITED);
+ fs_give((void **) &h->data);
+ }
+
+ if(((h = hd->content_length)) && h->data){ /* RFC 7230, Section 3.3.2 */
+ h->p = http_parse_token_list(h->data, HTP_UNLIMITED);
+ fs_give((void **) &h->data);
+ }
+
+ if(((h = hd->content_location)) && h->data){ /* RFC 7231, Section 3.1.4.2 */
+ h->p = http_parse_token_list(h->data, 1);
+ fs_give((void **) &h->data);
+ }
+
+ if(((h = hd->content_type)) && h->data){ /* RFC 7231, Section 3.1.1.5 */
+ h->p = http_parse_token_parameter(h->data, 0);
+ fs_give((void **) &h->data);
+ }
+
+ if(((h = hd->date)) && h->data){ /* RFC 7231, Section 7.1.1.2 */
+ h->p = http_parse_token_list(h->data, 1);
+ fs_give((void **) &h->data);
+ }
+
+ if(((h = hd->etag)) && h->data){ /* Rewrite this. RFC 7232, Section 2.3 */
+ h->p = http_parse_token_list(h->data, 1);
+ fs_give((void **) &h->data);
+ }
+
+ if(((h = hd->expect)) && h->data){ /* Rewrite this. RFC 7231, Section 5.1.1 */
+ h->p = http_parse_token_list(h->data, 1);
+ fs_give((void **) &h->data);
+ }
+
+ if(((h = hd->expires)) && h->data){ /* Rewrite this. RFC 7234, Section 5.3 */
+ h->p = http_parse_token_list(h->data, 1);
+ fs_give((void **) &h->data);
+ }
+
+ if(((h = hd->from)) && h->data){ /* Rewrite this. RFC 7231, Section 5.5.1 */
+ h->p = http_parse_token_list(h->data, 1);
+ fs_give((void **) &h->data);
+ }
+
+ if(((h = hd->host)) && h->data){ /* Rewrite this. RFC 7230, Section 5.4 */
+ h->p = http_parse_token_list(h->data, 1);
+ fs_give((void **) &h->data);
+ }
+
+ if(((h = hd->last_modified)) && h->data){ /* Rewrite this. RFC 7232, Section 2.2 */
+ h->p = http_parse_token_list(h->data, 1);
+ fs_give((void **) &h->data);
+ }
+
+ if(((h = hd->location)) && h->data){ /* Rewrite this. RFC 7231, Section 7.1.2 */
+ h->p = http_parse_token_list(h->data, 1);
+ fs_give((void **) &h->data);
+ }
+
+ if(((h = hd->max_forwards)) && h->data){ /* RFC 7231, Section 5.1.2 */
+ h->p = http_parse_token_list(h->data, 1);
+ fs_give((void **) &h->data);
+ }
+
+ if(((h = hd->mime_version)) && h->data){ /* Rewrite this. RFC 7231, Appendix A.1 */
+ h->p = http_parse_token_list(h->data, 1);
+ fs_give((void **) &h->data);
+ }
+
+ if(((h = hd->pragma)) && h->data){ /* RFC 7234, Section 5.4 */
+ h->p = http_parse_token_parameter(h->data, HTP_NOVAL);
+ fs_give((void **) &h->data);
+ }
+
+ if(((h = hd->proxy_authenticate)) && h->data){ /* Rewrite this. RFC 7235, Section 4.3 */
+ h->p = http_parse_token_parameter(h->data, 0);
+ fs_give((void **) &h->data);
+ }
+
+ if(((h = hd->referer)) && h->data){ /* Rewrite this. RFC 7231, Section 5.5.2 */
+ h->p = http_parse_token_list(h->data, 1);
+ fs_give((void **) &h->data);
+ }
+
+ if(((h = hd->retry_after)) && h->data){ /* Rewrite this. RFC 7231, Section 7.1.3 */
+ h->p = http_parse_token_list(h->data, 1);
+ fs_give((void **) &h->data);
+ }
+
+ if(((h = hd->server)) && h->data){ /* Rewrite this. RFC 7231, Section 7.4.2 */
+ h->p = http_parse_token_list(h->data, 1);
+ fs_give((void **) &h->data);
+ }
+
+ if(((h = hd->te)) && h->data){ /* Rewrite this. RFC 7230, Section 4.3 */
+ h->p = http_parse_token_parameter(h->data, 0);
+ fs_give((void **) &h->data);
+ }
+
+ if(((h = hd->trailer)) && h->data){ /* RFC 7230, Section 4.4 */
+ h->p = http_parse_token_list(h->data, HTP_UNLIMITED);
+ fs_give((void **) &h->data);
+ }
+
+ if(((h = hd->transfer_encoding)) && h->data){ /* Rewrite this. RFC 7230, Section 3.3.1 */
+ h->p = http_parse_token_parameter(h->data, 0);
+ fs_give((void **) &h->data);
+ }
+
+ if(((h = hd->upgrade)) && h->data){ /* Rewrite this. RFC 7230, Section 6.7 */
+ h->p = http_parse_token_list(h->data, 1);
+ fs_give((void **) &h->data);
+ }
+
+ if(((h = hd->user_agent)) && h->data){ /* Rewrite this. RFC 7231, Section 5.5.3 */
+ h->p = http_parse_token_list(h->data, HTP_UNLIMITED);
+ fs_give((void **) &h->data);
+ }
+
+ if(((h = hd->via)) && h->data){ /* Rewrite this. RFC 7230, Section 5.7.1 */
+ h->p = http_parse_token_list(h->data, HTP_UNLIMITED);
+ fs_give((void **) &h->data);
+ }
+
+ if(((h = hd->vary)) && h->data){ /* Rewrite this. RFC 7231, Section 7.1.4 */
+ h->p = http_parse_token_list(h->data, HTP_UNLIMITED);
+ fs_give((void **) &h->data);
+ }
+
+ if(((h = hd->warning)) && h->data){ /* Rewrite this. RFC 7234, Section 5.5 */
+ h->p = http_parse_token_list(h->data, HTP_UNLIMITED);
+ fs_give((void **) &h->data);
+ }
+
+ if(((h = hd->www_authenticate)) && h->data){ /* Rewrite this. RFC 7235, Section 4.1 */
+ h->p = http_parse_token_parameter(h->data, 0);
+ fs_give((void **) &h->data);
+ }
+}
+
+
+void
+http_add_data_to_header(HTTP_HEADER_S **headerp, char *data)
+{
+ HTTP_HEADER_S *h = *headerp;
+
+ if(!h){
+ h = fs_get(sizeof(HTTP_HEADER_S));
+ memset((void *)h, 0, sizeof(HTTP_HEADER_S));
+ }
+
+ if(h->data) buffer_add(&h->data, ", ");
+ buffer_add(&h->data, data);
+ *headerp = h;
+}
+
+void
+http_add_header_data(HTTPSTREAM *stream, char *hdata)
+{
+ char *hname, *h;
+ size_t hlen;
+ int found = 1;
+
+ if(!stream || !hdata || !*hdata) return;
+
+ if(!stream->header){
+ stream->header = fs_get(sizeof(HTTP_HEADER_DATA_S));
+ memset((void *) stream->header, 0, sizeof(HTTP_HEADER_DATA_S));
+ }
+
+
+ /* extract header name first */
+ if((h = strchr(hdata, ':'))){
+ *h = '\0';
+ hname = fs_get(h-hdata+1);
+ strncpy(hname, hdata, h-hdata);
+ hname[h-hdata] = '\0';
+ if(!valid_token_name(hname))
+ return;
+ hname[h-hdata] = ':';
+ hname[h-hdata+1] = '\0';
+ *h++ = ':';
+ }
+ else return;
+
+ switch(*hname){
+ case 'A':
+ case 'a': if(!compare_cstring(hname+1, "ccept:")) /* RFC 7231, Section 5.3.2 */
+ http_add_data_to_header(&stream->header->accept, h);
+ else if(!compare_cstring(hname+1, "ccept-charset:")) /* RFC 7231, Section 5.3.3 */
+ http_add_data_to_header(&stream->header->accept_charset, h);
+ else if(!compare_cstring(hname+1, "ccept-encoding:")) /* RFC 7231, Section 5.3.4 */
+ http_add_data_to_header(&stream->header->accept_encoding, h);
+ else if(!compare_cstring(hname+1, "ccept-language:")) /* RFC 7231, Section 5.3.5 */
+ http_add_data_to_header(&stream->header->accept_language, h);
+ else if(!compare_cstring(hname+1, "ccept-ranges:")) /* RFC 7233, Section 2.3 */
+ http_add_data_to_header(&stream->header->accept_ranges, h);
+ else if(!compare_cstring(hname+1, "ge:")) /* RFC 7234, Section 5.1 */
+ http_add_data_to_header(&stream->header->age, h);
+ else if(!compare_cstring(hname+1, "llow:")) /* RFC 7231, Section 7.4.1 */
+ http_add_data_to_header(&stream->header->allow, h);
+ else found = 0;
+ break;
+
+ case 'C':
+ case 'c': if(!compare_cstring(hname+1, "ache-control:")) /* RFC 7234, Section 5.2 */
+ http_add_data_to_header(&stream->header->cache_control, h);
+ else if(!compare_cstring(hname+1, "onnection:")) /* RFC 7230, Section 6.1 */
+ http_add_data_to_header(&stream->header->connection, h);
+ else if(!compare_cstring(hname+1, "ontent-disposition:")) /* RFC 6266 */
+ http_add_data_to_header(&stream->header->content_disposition, h);
+ else if(!compare_cstring(hname+1, "ontent-encoding:")) /* RFC 7231, Section 3.1.2.2 */
+ http_add_data_to_header(&stream->header->content_encoding, h);
+ else if(!compare_cstring(hname+1, "ontent-language:")) /* RFC 7231, Section 3.1.3.2 */
+/* rewrite this */ http_add_data_to_header(&stream->header->content_language, h);
+ else if(!compare_cstring(hname+1, "ontent-length:")) /* RFC 7230, Section 3.3.2 */
+ http_add_data_to_header(&stream->header->content_length, h);
+ else if(!compare_cstring(hname+1, "ontent-location:")) /* RFC 7231, Section 3.1.4.2 */
+/* rewrite this */ http_add_data_to_header(&stream->header->content_location, h);
+ else if(!compare_cstring(hname+1, "ontent-type:")) /* RFC 7231, Section 3.1.1.5 */
+ http_add_data_to_header(&stream->header->content_type, h);
+ else found = 0;
+ break;
+
+ case 'D':
+ case 'd': if(!compare_cstring(hname+1, "ate:")) /* RFC 7231, Section 7.1.1.2 */
+/* revise this */ http_add_data_to_header(&stream->header->date, h);
+ else found = 0;
+ break;
+
+ case 'E':
+ case 'e': if(!compare_cstring(hname+1, "tag:")) /* RFC 7232, Section 2.3 */
+/* rewrite this */ http_add_data_to_header(&stream->header->etag, h);
+ else if(!compare_cstring(hname+1, "xpect:")) /* RFC 7231, Section 5.1.1 */
+/* rewrite this */ http_add_data_to_header(&stream->header->expect, h);
+ else if(!compare_cstring(hname+1, "xpires:")) /* RFC 7234, Section 5.3 */
+/* rewrite this */ http_add_data_to_header(&stream->header->expires, h);
+ else found = 0;
+ break;
+
+ case 'F':
+ case 'f': if(!compare_cstring(hname+1, "rom:")) /* RFC 7231, Section 5.5.1 */
+/* rewrite this */ http_add_data_to_header(&stream->header->from, h);
+ else found = 0;
+ break;
+
+ case 'H':
+ case 'h': if(!compare_cstring(hname+1, "ost:")) /* RFC 7230, Section 5.4 */
+ http_add_data_to_header(&stream->header->host, h);
+ else found = 0;
+ break;
+
+ case 'L':
+ case 'l': if(!compare_cstring(hname+1, "ast-modified:")) /* RFC 7232, Section 2.2 */
+ http_add_data_to_header(&stream->header->last_modified, h);
+ else if(!compare_cstring(hname+1, "ocation:")) /* RFC 7231, Section 7.1.2 */
+ http_add_data_to_header(&stream->header->location, h);
+ else found = 0;
+ break;
+
+ case 'M':
+ case 'm': if(!compare_cstring(hname+1, "ax-forwards:")) /* RFC 7231, Section 5.1.2 */
+ http_add_data_to_header(&stream->header->max_forwards, h);
+ else if(!compare_cstring(hname+1, "ime-version:")) /* RFC 7231, Appendix A.1 */
+ http_add_data_to_header(&stream->header->mime_version, h);
+ else found = 0;
+ break;
+
+ case 'P':
+ case 'p': if(!compare_cstring(hname+1, "ragma:")) /* RFC 7234, Section 5.4 */
+ http_add_data_to_header(&stream->header->pragma, h);
+ else if(!compare_cstring(hname+1, "roxy-authenticate:")) /* RFC 7235, Section 4.3 */
+ http_add_data_to_header(&stream->header->proxy_authenticate, h);
+ else found = 0;
+ break;
+
+ case 'R':
+ case 'r': if(!compare_cstring(hname+1, "eferer:")) /* RFC 7231, Section 5.5.2 */
+ http_add_data_to_header(&stream->header->referer, h);
+ else if(!compare_cstring(hname+1, "etry-after:")) /* RFC 7231, Section 7.1.3 */
+ http_add_data_to_header(&stream->header->retry_after, h);
+ else found = 0;
+ break;
+
+ case 'S':
+ case 's': if(!compare_cstring(hname+1, "erver:")) /* RFC 7231, Section 7.4.2 */
+ http_add_data_to_header(&stream->header->server, h);
+ else found = 0;
+ break;
+
+ case 'T':
+ case 't': if(!compare_cstring(hname+1, "e:")) /* RFC 7230, Section 4.3 */
+ http_add_data_to_header(&stream->header->te, h);
+ else if(!compare_cstring(hname+1, "railer:")) /* RFC 7230, Section 4.4 */
+ http_add_data_to_header(&stream->header->trailer, h);
+ else if(!compare_cstring(hname+1, "ransfer-encoding:")) /* RFC 7230, Section 3.3.1 */
+ http_add_data_to_header(&stream->header->transfer_encoding, h);
+ else found = 0;
+ break;
+ break;
+
+ case 'U':
+ case 'u': if(!compare_cstring(hname+1, "pgrade:")) /* RFC 7230, Section 6.7 */
+ http_add_data_to_header(&stream->header->upgrade, h);
+ else if(!compare_cstring(hname+1, "ser-agent:")) /* RFC 7231, Section 5.5.3 */
+ http_add_data_to_header(&stream->header->user_agent, h);
+ else found = 0;
+ break;
+
+ case 'V':
+ case 'v': if(!compare_cstring(hname+1, "ia:")) /* RFC 7230, Section 5.7.1 */
+ http_add_data_to_header(&stream->header->via, h);
+ else if(!compare_cstring(hname+1, "ary:")) /* RFC 7231, Section 7.1.4 */
+ http_add_data_to_header(&stream->header->vary, h);
+ else found = 0;
+ break;
+
+ case 'W':
+ case 'w': if(!compare_cstring(hname+1, "arning:")) /* RFC 7234, Section 5.5 */
+ http_add_data_to_header(&stream->header->warning, h);
+ else if(!compare_cstring(hname+1, "ww-authenticate:")) /* RFC 7235, Section 4.1 */
+ http_add_data_to_header(&stream->header->www_authenticate, h);
+ else found = 0;
+ break;
+
+ default: break;
+ }
+}
+
+
+/* parse a list of tokens. If num is positive, parse at most
+ * num members in the list. Set num to HTP_UNLIMITED for a list
+ * without bounds
+ */
+HTTP_PARAM_LIST_S *
+http_parse_token_list(char *h, int num)
+{
+ char *s = h, *t, *u, c, d;
+ HTTP_PARAM_LIST_S *rv = NIL;
+
+ if(!s || !*s || num == 0) return NIL;
+ http_skipows(&s);
+ if(!*s) return NIL;
+ for(t = s; *t != '\0' && *t != ','; t++);
+ c = *t; *t = '\0';
+ http_remove_trailing_ows(s);
+
+ if(!valid_token_name(s))
+ return c == ',' ? http_parse_token_list(t+1, num) : NIL;
+
+ if(num > 0) num--; /* this one counts! */
+ rv = fs_get(sizeof(HTTP_PARAM_LIST_S));
+ memset((void *) rv, 0, sizeof(HTTP_PARAM_LIST_S));
+ rv->vp = fs_get(sizeof(HTTP_VAL_PARAM_S));
+ memset((void *) rv->vp, 0, sizeof(HTTP_VAL_PARAM_S));
+ rv->vp->value = cpystr(s);
+ *t = c;
+ if(c == ',')
+ rv->next = http_parse_token_list(t+1, num);
+
+ return rv;
+}
+
+
+/*
+ * parse a list of tokens with optional parameters
+ * into a HEADER_DATA structure. Do not parse into
+ * it anything invalid.
+ */
+HTTP_PARAM_LIST_S *
+http_parse_token_parameter(char *h, int flag)
+{
+ char *s = h, *t, *u, c, d;
+ HTTP_PARAM_LIST_S *rv = NIL;
+
+ /*
+ * Step 1:
+ * isolate first list element from list and remove
+ * leading and trailing white space.
+ */
+ if(!s) return NIL;
+ http_skipows(&s);
+ if(!*s) return NIL;
+ for(t = s; *t != '\0' && *t != ','; t++);
+ c = *t; *t = '\0';
+ http_remove_trailing_ows(s);
+
+ /*
+ * Step 2:
+ * isolate token name from its parameters. Remove
+ * any trailing spaces. If not valid token, move
+ * to the next entry in the list.
+ */
+ for(u = s; *u != '\0' && *u != ';'; u++);
+ d = *u; *u = '\0';
+ http_remove_trailing_ows(s);
+ if(!valid_token_name(s))
+ return c == ',' ? http_parse_token_parameter(t+1, flag) : NIL;
+
+ /*
+ * Step 3:
+ * If we make it this far, create a non-null reply
+ * and parse the token and parameters into a
+ * HTTP_HEADER_DATA_S structure
+ */
+ rv = fs_get(sizeof(HTTP_PARAM_LIST_S));
+ memset((void *) rv, 0, sizeof(HTTP_PARAM_LIST_S));
+ rv->vp = fs_get(sizeof(HTTP_VAL_PARAM_S));
+ memset((void *) rv->vp, 0, sizeof(HTTP_VAL_PARAM_S));
+ rv->vp->value = cpystr(s);
+ if(d == ';')
+ rv->vp->plist = http_parse_parameter(u+1, flag);
+ *u = d;
+ *t = c;
+ if(c == ',')
+ rv->next = http_parse_token_parameter(t+1, flag);
+
+ return rv;
+}
+
+int
+valid_dquote_text(char *s)
+{
+ char *t;
+
+ if(!s || *s != '\"') return 0;
+
+ t = strchr(s+1, '\"');
+ return (t && !t[1]) ? 1 : 0;
+}
+
+
+void
+http_skipows(char **sp)
+{
+ char *s = *sp;
+ for(; *s == ' ' || *s == '\t'; s++);
+ *sp = s;
+}
+
+void
+http_remove_trailing_ows(char *s)
+{
+ char *t;
+ for(t = s; strlen(t) > 0 ;)
+ if(t[strlen(t)-1] == ' ' || t[strlen(t)-1] == '\t')
+ t[strlen(t)-1] = '\0';
+ else
+ break;
+}
+
+PARAMETER *
+http_parse_parameter(char *s, int flag)
+{
+ PARAMETER *p;
+ char *t, *u, c, d;
+
+ /* Step 1:
+ * separate the parameters into a list separated by ";"
+ */
+ if(!s || !*s) return NIL;
+ http_skipows(&s);
+ if(!*s) return NIL;
+ for(t = s; *t != '\0' && *t != ';'; t++);
+ c = *t; *t = '\0';
+
+ /* Now we look for separation of attribute and value */
+ u = strchr(s, '=');
+
+ if(u){
+ *u = '\0';
+ http_remove_trailing_ows(s); http_remove_trailing_ows(u+1);
+ if(!valid_token_name(s) || !valid_parameter_value(u+1))
+ return c == ';' ? http_parse_parameter(t+1, flag) : NIL;
+ p = mail_newbody_parameter();
+ p->attribute = cpystr(s);
+ p->value = cpystr(u+1);
+ p->next = c == ';' ? http_parse_parameter(t+1, flag) : NIL;
+ *u = '=';
+ }
+ else if(flag & HTP_NOVAL){
+ /* this is a parameter with attribute but no value. RFC 7231
+ * section 5.3.2 allows this.
+ */
+ http_remove_trailing_ows(s);
+ if(!valid_token_name(s))
+ return c == ';' ? http_parse_parameter(t+1, flag) : NIL;
+ p = mail_newbody_parameter();
+ p->attribute = cpystr(s);
+ p->next = c == ';' ? http_parse_parameter(t+1, flag) : NIL;
+ } else
+ p = c == ';' ? http_parse_parameter(t+1, flag) : NIL;
+
+ return p;
+}
+
+char *
+http_get_param_url(char *url, HTTP_PARAM_S *param)
+{
+ int i;
+ char *rv = NULL;
+ HTTP_PARAM_S enc_param;
+
+ buffer_add(&rv, url);
+ for(i = 0; param[i].name != NULL; i++){
+ enc_param.name = hex_escape_url_part(param[i].name, NULL);
+ enc_param.value = hex_escape_url_part(param[i].value, NULL);
+ buffer_add(&rv, i == 0 ? "?" : "&");
+ buffer_add(&rv, enc_param.name);
+ buffer_add(&rv, "=");
+ buffer_add(&rv, enc_param.value);
+ fs_give((void **) &enc_param.name);
+ fs_give((void **) &enc_param.value);
+ }
+
+ return rv;
+}
+
+HTTP_REQUEST_S *
+http_request_get(void)
+{
+ HTTP_REQUEST_S *rv = fs_get(sizeof(HTTP_REQUEST_S));
+ memset((void *) rv, 0, sizeof(HTTP_REQUEST_S));
+
+ return rv;
+}
+
+void
+http_request_free(HTTP_REQUEST_S **hr)
+{
+ if(!hr) return;
+
+ if((*hr)->request) fs_give((void **) &(*hr)->request);
+ if((*hr)->header) fs_give((void **) &(*hr)->header);
+ if((*hr)->body) fs_give((void **) &(*hr)->body);
+ fs_give((void **) hr);
+}
+
+char *
+http_request_line(char *method, char *target, char *version)
+{
+ int len = strlen(method) + strlen(target) + strlen(version) + 2 + 1;
+ char *line = fs_get(len);
+
+ sprintf(line, "%s %s %s", method, target, version);
+ return line;
+}
+
+void
+http_add_header(HTTP_REQUEST_S **reqp, char *name, char *value)
+{
+ int len, hlen;
+
+ if(!reqp) return;
+
+ if(!*reqp) *reqp = http_request_get();
+
+ len = strlen(name) + 2 + strlen(value) + 2 + 1;
+ hlen = (*reqp)->header ? strlen((*reqp)->header) : 0;
+ len += hlen;
+ fs_resize((void **) &(*reqp)->header, len);
+ sprintf((*reqp)->header + hlen, "%s: %s\015\012", name, value);
+}
+
+void
+buffer_add(char **bufp, char *text)
+{
+ int len;
+
+ if(!bufp || !text || !*text) return;
+
+ len = *bufp ? strlen(*bufp) : 0;
+ fs_resize((void **) bufp, len + strlen(text) + 1);
+ (*bufp)[len] = '\0';
+ strcat(*bufp, text);
+}
+
+void
+http_add_body(HTTP_REQUEST_S **reqp, char *text)
+{
+ if(!reqp) return;
+
+ if(!*reqp) *reqp = http_request_get();
+
+ buffer_add(&(*reqp)->body, text);
+}
+
+
+/* NULL terminated list of HTTP_PARAM_S objects.
+ * If caller needs "x" parameters, call this function
+ * with argument "x+1".
+ */
+HTTP_PARAM_S *
+http_param_get(int len)
+{
+ HTTP_PARAM_S *http_params;
+
+ http_params = fs_get(len*sizeof(HTTP_PARAM_S));
+ memset((void *) http_params, 0, len*sizeof(HTTP_PARAM_S));
+ return http_params;
+}
+
+void
+http_param_free(HTTP_PARAM_S **param)
+{
+ int i;
+
+ if(param == NULL) return;
+
+ for(i = 0; (*param)[i].name != NULL; i++)
+ fs_give((void **) &(*param)[i].name);
+
+ for(i = 0; (*param)[i].value != NULL; i++)
+ fs_give((void **) &(*param)[i].value);
+
+ fs_give((void **) param);
+}
+
+
+/* This encodes for a GET request */
+char *
+hex_escape_url_part(char *text, char *addsafe)
+{
+ char *safechars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_.-";
+ char *s = fs_get(3*strlen(text) + 1), *t;
+
+ *s = '\0';
+ for(t = text; *t != '\0'; t++)
+ if(strchr(safechars, *t) != NULL
+ || (addsafe != NULL && strchr(addsafe, *t) != NULL))
+ sprintf(s + strlen(s), "%c", *t);
+ else
+ sprintf(s + strlen(s), "%%%X", *t);
+ fs_resize((void **) &s, strlen(s)+1);
+ return s;
+}
+
+/* this encodes for a POST request */
+char *
+encode_url_body_part(char *text, char *addsafe)
+{
+ char *safechars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_.-";
+ char *s = fs_get(3*strlen(text) + 1), *t;
+
+ *s = '\0';
+ for(t = text; *t != '\0'; t++)
+ if(*t == ' ') /* ASCII 32 is never safe, must always be encoded */
+ sprintf(s + strlen(s), "%c", '+');
+ else if(strchr(safechars, *t) != NULL
+ || (addsafe != NULL && strchr(addsafe, *t) != NULL))
+ sprintf(s + strlen(s), "%c", *t);
+ else
+ sprintf(s + strlen(s), "%%%X", *t);
+ fs_resize((void **) &s, strlen(s)+1);
+ return s;
+}
+
+int
+http_valid_net_parse (char *url, NETMBX *mb)
+{
+ int i, len;
+ char *s, *p;
+
+ if((url == NIL)
+ || (url[0] != 'h' && url[0] != 'H')
+ || (url[1] == 't' && url[1] == 'T')
+ || (url[2] == 't' && url[2] == 'T')
+ || (url[3] == 'p' && url[3] == 'P'))
+ return 0;
+
+ if(url[i = 4] == 's' || url[i] == 'S')
+ mb->sslflag = mb->notlsflag = T;
+ else i = 3;
+
+ if(url[++i] != ':' || url[++i] != '/' || url[++i] != '/')
+ return 0;
+
+ strcpy(mb->service, "http");
+ s = strchr(url+i+1, '/');
+ len = s ? s - url - i - 1 : strlen(url+i+1);
+ strncpy(mb->orighost, url+i+1, len);
+ mb->orighost[len] = '\0';
+ if((p = strchr(mb->orighost, ':')) != NULL){
+ *p++ = '\0';
+ mb->port = strtoul(p, &p, 10);
+ if(mb->port == 0L || *p != '\0')
+ return NIL;
+ }
+ strcpy(mb->host, mb->orighost);
+ return T;
+}
+
+HTTPSTREAM *
+http_open (char *url)
+{
+ HTTPSTREAM *stream;
+ NETMBX mb;
+ char *s;
+
+ memset((void *) &mb, 0, sizeof(NETMBX));
+ if(http_valid_net_parse (url,&mb) == 0)
+ return NIL;
+
+ stream = fs_get(sizeof(HTTPSTREAM));
+ memset((void *) stream, 0, sizeof(HTTPSTREAM));
+
+ s = strchr(url + 7 + (mb.trysslflag ? 1 : 0) + 1, '/'); /* 7 = strlen("http://") + 1 */
+ stream->url = cpystr(url);
+ stream->urlhost = cpystr(mb.orighost);
+ stream->urltail = cpystr(s ? s : "/");
+ stream->netstream = net_open (&mb, NIL, mb.port ? mb.port : HTTPTCPPORT,
+ (NETDRIVER *) mail_parameters (NIL,GET_SSLDRIVER,NIL),
+ "*https", mb.port ? mb.port : HTTPSSLPORT);
+ if(!stream->netstream) http_close(stream);
+ return stream;
+}
+
+char *
+http_post_param(char *url, HTTP_PARAM_S *param)
+{
+ HTTPSTREAM *stream;
+ HTTP_PARAM_S enc_param;
+ HTTP_REQUEST_S *http_request;
+ char *reply;
+ int i;
+
+ if(url == NULL || param == NULL || (stream = http_open(url)) == NULL)
+ return NULL;
+
+ http_request = http_request_get();
+ http_request->request = http_request_line("POST", stream->urltail, HTTP_1_1_VERSION);
+ http_add_header(&http_request, "Host", stream->urlhost);
+ http_add_header(&http_request, "Content-Type", HTTP_MIME_URLENCODED);
+
+ for(i = 0; param[i].name != NULL; i++){
+ enc_param.name = encode_url_body_part(param[i].name, NULL);
+ enc_param.value = encode_url_body_part(param[i].value, NULL);
+ if(i > 0)
+ http_add_body(&http_request, "&");
+ http_add_body(&http_request, enc_param.name);
+ http_add_body(&http_request, "=");
+ http_add_body(&http_request, enc_param.value);
+ fs_give((void **) &enc_param.name);
+ fs_give((void **) &enc_param.value);
+ }
+
+ if(http_send(stream, http_request)){
+ reply = cpystr(stream->reply ? stream->reply : "");
+ http_close(stream);
+ }
+
+ http_request_free(&http_request);
+
+ return reply;
+}
+
+char *
+http_post_param2(char *url, HTTP_PARAM_S *param)
+{
+ HTTPSTREAM *stream;
+ HTTP_PARAM_S enc_param;
+ HTTP_REQUEST_S *http_request;
+ char *reply;
+ int i;
+
+ if(url == NULL || param == NULL || (stream = http_open(url)) == NULL)
+ return NULL;
+
+ http_request = http_request_get();
+ http_request->request = http_request_line("POST", stream->urltail, HTTP_1_1_VERSION);
+ http_add_header(&http_request, "Host", stream->urlhost);
+ http_add_header(&http_request, "User-Agent", "Alpine");
+ http_add_header(&http_request, "Content-Type", HTTP_MIME_URLENCODED);
+
+ for(i = 0; param[i].name != NULL; i++){
+ enc_param.name = encode_url_body_part(param[i].name, NULL);
+ enc_param.value = encode_url_body_part(param[i].value, NULL);
+ if(i > 0)
+ http_add_body(&http_request, "&");
+ http_add_body(&http_request, enc_param.name);
+ http_add_body(&http_request, "=");
+ http_add_body(&http_request, enc_param.value);
+ fs_give((void **) &enc_param.name);
+ fs_give((void **) &enc_param.value);
+ }
+
+ if(http_send(stream, http_request)){
+ reply = cpystr(stream->reply ? stream->reply : "");
+ http_close(stream);
+ }
+
+ http_request_free(&http_request);
+
+ return reply;
+}
+
+char *
+http_get_param(char *base_url, HTTP_PARAM_S *param)
+{
+ HTTPSTREAM *stream;
+ char *url, *reply;
+
+ url = http_get_param_url(base_url, param);
+ if(url){
+ reply = http_get(url);
+ fs_give((void **) &url);
+ }
+ return reply;
+}
+
+char *
+http_get(char *url)
+{
+ HTTP_REQUEST_S *http_request;
+ char *reply;
+ HTTPSTREAM *stream;
+
+ if(!url) return NIL;
+ stream = http_open(url);
+ if(!stream){
+ fs_give((void **) &url);
+ return NIL;
+ }
+
+ http_request = http_request_get();
+ http_request->request = http_request_line("GET", stream->urltail, HTTP_1_1_VERSION);
+ http_add_header(&http_request, "Host", stream->urlhost);
+
+ if(http_send(stream, http_request)){
+ reply = cpystr(stream->reply ? stream->reply : "");
+ http_close(stream);
+ }
+
+ http_request_free(&http_request);
+
+ return reply;
+}
+
+void
+http_close (HTTPSTREAM *stream)
+{
+ if(stream){
+ if (stream->netstream) net_close (stream->netstream);
+ stream->netstream = NIL;
+ if (stream->url) fs_give ((void **) &stream->url);
+ if (stream->urlhost) fs_give ((void **) &stream->urlhost);
+ if (stream->urltail) fs_give ((void **) &stream->urltail);
+ if (stream->response) fs_give ((void **) &stream->response);
+ if (stream->reply) fs_give ((void **) &stream->reply);
+ fs_give((void **) &stream);
+ }
+}
+
+long
+http_send (HTTPSTREAM *stream, HTTP_REQUEST_S *req)
+{
+ long ret;
+ int len;
+ char *s = NULL;
+
+ if (!stream->netstream)
+ ret = http_fake (stream,"http connection lost");
+ else {
+ if(req->body){
+ char length[20];
+
+ sprintf(length, "%lu", strlen(req->body));
+ http_add_header(&req, "Content-Length", length);
+ }
+
+ buffer_add(&s, req->request); buffer_add(&s, "\015\012");
+ buffer_add(&s, req->header); buffer_add(&s, "\015\012");
+ buffer_add(&s, req->body); buffer_add(&s, "\015\012");
+ mm_log(s, TCPDEBUG);
+ ret = net_soutr (stream->netstream,s)
+ ? http_reply (stream)
+ : http_fake (stream,"http connection broken in command");
+ fs_give ((void **) &s);
+ }
+ return ret;
+}
+
+HTTP_STATUS_S *
+http_status_line_get(char *status_line)
+{
+ HTTP_STATUS_S *rv = NULL;
+ char *version, *s;
+ int status;
+ int len;
+
+ if(!status_line) return NIL;
+
+ if((s = strchr(status_line, ' ')) != NIL){
+ *s = '\0';
+ version = cpystr(status_line);
+ *s++ = ' ';
+ status = strtoul(s, &s, 10);
+ if(s && *s == ' ' && status >= 100 && status < 600){
+ rv = fs_get(sizeof(HTTP_STATUS_S));
+ rv->version = version;
+ rv->status = status;
+ rv->text = cpystr(++s);
+ }
+ else
+ fs_give((void **) &version);
+ }
+ return rv;
+}
+
+void
+http_status_line_free(HTTP_STATUS_S **status)
+{
+ if(status == NULL) return;
+
+ if((*status)->version) fs_give((void **) &(*status)->version);
+ if((*status)->text) fs_give((void **) &(*status)->text);
+ fs_give((void **) status);
+}
+
+
+long
+http_reply (HTTPSTREAM *stream)
+{
+ int in_header = 1;
+ HTTP_STATUS_S reply_status;
+ unsigned long size;
+
+ if (stream->response) fs_give ((void **) &stream->response);
+ stream->response = net_getline(stream->netstream);
+
+ if(stream->response){
+ buffer_add(&stream->reply, stream->response);
+ buffer_add(&stream->reply, "\015\012");
+ }
+
+ if(stream->status) http_status_line_free(&stream->status);
+ stream->status = http_status_line_get(stream->response);
+
+ if(!stream->status){
+ http_fake(stream, "Invalid status line received. Closing connection");
+ return NIL;
+ }
+
+ while (in_header > 0){
+ if (stream->response) fs_give ((void **) &stream->response);
+ stream->response = net_getline (stream->netstream);
+ if(stream->response){
+ buffer_add(&stream->reply, stream->response);
+ http_add_header_data(stream, stream->response);
+ }
+ buffer_add(&stream->reply, "\015\012");
+// save_header(stream->headers, stream->response);
+ if(!stream->response || *stream->response == '\0')
+ in_header--;
+ }
+
+ http_parse_headers(stream);
+ if(stream->header->content_length){
+ size = atol(stream->header->content_length->p->vp->value);
+ if (stream->response) fs_give ((void **) &stream->response);
+ stream->response = net_getsize (stream->netstream, size);
+ if(stream->response) buffer_add(&stream->reply, stream->response);
+ }
+ else if (stream->header->transfer_encoding){
+ HTTP_PARAM_LIST_S *p = stream->header->transfer_encoding->p;
+ for(; p ; p = p->next){
+ if(!compare_cstring(p->vp->value, "chunked"))
+ break;
+ }
+ if(p && p->vp->value){ /* chunked transfer */
+ int done = 0;
+ size = 0L;
+ while(!done){
+ if (stream->response) fs_give ((void **) &stream->response);
+ stream->response = net_getline (stream->netstream);
+ if(stream->response){
+ buffer_add(&stream->reply, stream->response);
+ buffer_add(&stream->reply, "\015\012");
+ size = strtol(stream->response, NIL, 16);
+ fs_give ((void **) &stream->response);
+ stream->response = net_getsize (stream->netstream, size);
+ buffer_add(&stream->reply, stream->response);
+ }
+ if(size == 0L) done++;
+ }
+ }
+ }
+
+#if 0
+ while(in_header == 0){
+ if (stream->response) fs_give ((void **) &stream->response);
+ stream->response = net_getline (stream->netstream);
+ if(stream->response){
+ buffer_add(&stream->reply, stream->response);
+ buffer_add(&stream->reply, stream->response);
+ }
+ buffer_add(&stream->reply, "\015\012");
+ if(!stream->response || *stream->response == '\0')
+ in_header--;
+ }
+#endif
+
+ if(!stream->netstream)
+ http_fake(stream, "Connection to HTTP server closed");
+ return stream->netstream ? T : NIL;
+}
+
+long
+http_fake (HTTPSTREAM *stream, char *text)
+{
+ if (stream->netstream) net_close (stream->netstream);
+ stream->netstream = NIL;
+ if (stream->response) fs_give ((void **) &stream->response);
+ /* add *text to the log, to pass this to the client */
+ return NIL;
+}