a7df9715638bfc81577016ae485a9233ccc45941
[citadel.git] / webcit-ng / http.c
1 /*
2  * This module handles HTTP transactions.
3  *
4  * Copyright (c) 1996-2018 by the citadel.org team
5  *
6  * This program is open source software; you can redistribute it and/or
7  * modify it under the terms of the GNU General Public License version 3.
8  *
9  * This program is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12  * GNU General Public License for more details.
13  */
14
15 #include "webcit.h"
16
17 /*
18  * Write data to the HTTP client.  Encrypt if necessary.
19  */
20 int client_write(struct client_handle *ch, char *buf, int nbytes)
21 {
22         if (is_https) {
23                 return client_write_ssl(ch, buf, nbytes);
24         }
25         else {
26                 return write(ch->sock, buf, nbytes);
27         }
28 }
29
30
31 /*
32  * Read data from the HTTP client.  Decrypt if necessary.
33  * Returns number of bytes read, or -1 to indicate an error.
34  */
35 int client_read(struct client_handle *ch, char *buf, int nbytes)
36 {
37         if (is_https) {
38                 return client_read_ssl(ch, buf, nbytes);
39         }
40         else {
41                 int bytes_received = 0;
42                 int bytes_this_block = 0;
43                 while (bytes_received < nbytes) {
44                         bytes_this_block = read(ch->sock, &buf[bytes_received], nbytes-bytes_received);
45                         if (bytes_this_block < 1) {
46                                 return(-1);
47                         }
48                         bytes_received += bytes_this_block;
49                 }
50                 return(nbytes);
51         }
52 }
53
54
55 /*
56  * Read a newline-terminated line of text from the client.
57  * Implemented in terms of client_read() and is therefore transparent...
58  * Returns the string length or -1 for error.
59  */
60 int client_readline(struct client_handle *ch, char *buf, int maxbytes)
61 {
62         int len = 0;
63         int c = 0;
64
65         if (buf == NULL) return(-1);
66
67         while (len < maxbytes) {
68                 c = client_read(ch, &buf[len], 1);
69                 if (c <= 0) {
70                         syslog(LOG_DEBUG, "Socket error or zero-length read");
71                         return(-1);
72                 }
73                 if (buf[len] == '\n') {
74                         if ( (len >0) && (buf[len-1] == '\r') ) {
75                                 --len;
76                         }
77                         buf[len] = 0;
78                         return(len);
79                 }
80                 ++len;
81         }
82         return(len);
83 }
84
85
86 /*
87  * printf() type function to send data to the web client.
88  */
89 void client_printf(struct client_handle *ch, const char *format,...)
90 {
91         va_list arg_ptr;
92         StrBuf *Buf = NewStrBuf();
93
94         va_start(arg_ptr, format);
95         StrBufVAppendPrintf(Buf, format, arg_ptr);
96         va_end(arg_ptr);
97
98         client_write(ch, (char *)ChrPtr(Buf), StrLength(Buf));
99         FreeStrBuf(&Buf);
100 }
101
102
103 /*
104  * Push one new header into the response of an HTTP request.
105  * When completed, the HTTP transaction now owns the memory allocated for key and val.
106  */
107 void add_response_header(struct http_transaction *h, char *key, char *val)
108 {
109         struct http_header *new_response_header = malloc(sizeof(struct http_header));
110         new_response_header->key = key;
111         new_response_header->val = val;
112         new_response_header->next = h->response_headers;
113         h->response_headers = new_response_header;
114 }
115
116
117 /*
118  * Entry point for this layer.  Socket is connected.  If running as an HTTPS
119  * server, SSL has already been negotiated.  Now perform one transaction.
120  */
121 void perform_one_http_transaction(struct client_handle *ch)
122 {
123         char buf[1024];
124         int len;
125         int lines_read = 0;
126         struct http_transaction h;
127         char *c, *d;
128         struct sockaddr_in sin;
129         struct http_header *clh;                                                // general purpose iterator variable
130
131         memset(&h, 0, sizeof h);
132
133         while (len = client_readline(ch, buf, sizeof buf), (len > 0) ) {
134                 ++lines_read;
135
136                 if (lines_read == 1) {                                          // First line is method and URI.
137                         c = strchr(buf, ' ');                                   // IGnore the HTTP-version.
138                         if (c == NULL) {
139                                 h.method = strdup("NULL");
140                                 h.uri = strdup("/");
141                         }
142                         else {
143                                 *c = 0;
144                                 h.method = strdup(buf);
145                                 ++c;
146                                 d = c;
147                                 c = strchr(d, ' ');
148                                 if (c != NULL) {
149                                         *c = 0;
150                                 }
151                                 ++c;
152                                 h.uri = strdup(d);
153                         }
154                 }
155                 else {                                                          // Subsequent lines are headers.
156                         c = strchr(buf, ':');                                   // Header line folding is obsolete so we don't support it.
157                         if (c != NULL) {
158                                 struct http_header *new_request_header = malloc(sizeof(struct http_header));
159                                 *c = 0;
160                                 new_request_header->key = strdup(buf);
161                                 ++c;
162                                 new_request_header->val = strdup(c);
163                                 striplt(new_request_header->key);
164                                 striplt(new_request_header->val);
165                                 new_request_header->next = h.request_headers;
166                                 h.request_headers = new_request_header;
167 #ifdef DEBUG_HTTP
168                                 syslog(LOG_DEBUG, "{ %s: %s", new_request_header->key, new_request_header->val);
169 #endif
170                         }
171                 }
172
173         }
174
175         // build up the site prefix, such as https://foo.bar.com:4343
176
177         h.site_prefix = malloc(256);
178         strcpy(h.site_prefix, (is_https ? "https://" : "http://"));
179         char *hostheader = header_val(&h, "Host");
180         if (hostheader) {
181                 strcat(h.site_prefix, hostheader);
182         }
183         else {
184                 strcat(h.site_prefix, "127.0.0.1");
185         }
186         socklen_t llen = sizeof(sin);
187         if (getsockname(ch->sock, (struct sockaddr *)&sin, &llen) >= 0) {
188                 sprintf(&h.site_prefix[strlen(h.site_prefix)], ":%d", ntohs(sin.sin_port));
189         }
190
191         // Now try to read in the request body (if one is present)
192
193         if (len < 0) {
194                 syslog(LOG_DEBUG, "Client disconnected");
195         }
196         else {
197                 syslog(LOG_DEBUG, "< %s %s", h.method, h.uri);
198
199                 // If there is a request body, read it now.
200                 char *ccl = header_val(&h, "Content-Length");
201                 if (ccl) {
202                         h.request_body_length = atol(ccl);
203                 }
204                 if (h.request_body_length > 0) {
205                         syslog(LOG_DEBUG, "Reading request body (%ld bytes)", h.request_body_length);
206                         h.request_body = malloc(h.request_body_length);
207                         client_read(ch, h.request_body, h.request_body_length);
208
209                         //write(2, HKEY("\033[31m"));
210                         //write(2, h.request_body, h.request_body_length);
211                         //write(2, HKEY("\033[0m\n"));
212
213                 }
214
215                 // Now pass control up to the next layer to perform the request.
216                 perform_request(&h);
217
218                 // Output the results back to the client.
219                 //write(2, HKEY("\033[32m"));
220                 //write(2, h.response_body, h.response_body_length);
221                 //write(2, HKEY("\033[0m\n"));
222
223                 syslog(LOG_DEBUG, "> %03d %s", h.response_code, h.response_string);
224                 client_printf(ch, "HTTP/1.1 %03d %s\r\n", h.response_code, h.response_string);
225                 client_printf(ch, "Connection: close\r\n");
226                 client_printf(ch, "Content-Length: %ld\r\n", h.response_body_length);
227                 char *datestring = http_datestring(time(NULL));
228                 if (datestring) {
229                         client_printf(ch, "Date: %s\r\n", datestring);
230                         free(datestring);
231                 }
232
233                 client_printf(ch, "Content-encoding: identity\r\n");                    // change if we gzip/deflate
234                 for (clh = h.response_headers; clh != NULL; clh = clh->next) {
235 #ifdef DEBUG_HTTP
236                         syslog(LOG_DEBUG, "} %s: %s", clh->key, clh->val);
237 #endif
238                         client_printf(ch, "%s: %s\r\n", clh->key, clh->val);
239                 }
240                 client_printf(ch, "\r\n");
241                 if ((h.response_body_length > 0) && (h.response_body != NULL)) {
242                         client_write(ch, h.response_body, h.response_body_length);
243                 }
244         }
245
246         // free the transaction memory
247         while (h.request_headers) {
248                 if (h.request_headers->key) free(h.request_headers->key);
249                 if (h.request_headers->val) free(h.request_headers->val);
250                 clh = h.request_headers->next;
251                 free(h.request_headers);
252                 h.request_headers = clh;
253         }
254         while (h.response_headers) {
255                 if (h.response_headers->key) free(h.response_headers->key);
256                 if (h.response_headers->val) free(h.response_headers->val);
257                 clh = h.response_headers->next;
258                 free(h.response_headers);
259                 h.response_headers = clh;
260         }
261         if (h.method) free(h.method);
262         if (h.uri) free(h.uri);
263         if (h.request_body) free(h.request_body);
264         if (h.response_string) free(h.response_string);
265         if (h.site_prefix) free(h.site_prefix);
266 }
267
268
269 /*
270  * Utility function to fetch a specific header from a completely read-in request.
271  * Returns the value of the requested header, or NULL if the specified header was not sent.
272  * The caller does NOT own the memory of the returned pointer, but can count on the pointer
273  * to still be valid through the end of the transaction.
274  */
275 char *header_val(struct http_transaction *h, char *requested_header)
276 {
277         struct http_header *clh;                                                // general purpose iterator variable
278         for (clh = h->request_headers; clh != NULL; clh = clh->next) {
279                 if (!strcasecmp(clh->key, requested_header)) {
280                         return(clh->val);
281                 }
282         }
283         return(NULL);
284 }