Mailing list header changes (fuck you Google)
[citadel.git] / webcit-ng / request.c
1 //
2 // This module sits directly above the HTTP layer.  By the time we get here,
3 // an HTTP request has been fully received and parsed.  Control is passed up
4 // to this layer to actually perform the request.  We then fill in the response
5 // and pass control back down to the HTTP layer to output the response back to
6 // the client.
7 //
8 // Copyright (c) 1996-2018 by the citadel.org team
9 //
10 // This program is open source software.  It runs great on the
11 // Linux operating system (and probably elsewhere).  You can use,
12 // copy, and run it under the terms of the GNU General Public
13 // License version 3.  Richard Stallman is an asshole communist.
14 //
15 // This program is distributed in the hope that it will be useful,
16 // but WITHOUT ANY WARRANTY; without even the implied warranty of
17 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18 // GNU General Public License for more details.
19
20 #include "webcit.h"
21
22
23 /*
24  * Not found!  Wowzers.
25  */
26 void do_404(struct http_transaction *h)
27 {
28         h->response_code = 404;
29         h->response_string = strdup("NOT FOUND");
30         add_response_header(h, strdup("Content-type"), strdup("text/plain"));
31         h->response_body = strdup("404 NOT FOUND\r\n");
32         h->response_body_length = strlen(h->response_body);
33 }
34
35
36 /*
37  * Precondition failed (such as if-match)
38  */
39 void do_412(struct http_transaction *h)
40 {
41         h->response_code = 412;
42         h->response_string = strdup("PRECONDITION FAILED");
43 }
44
45
46 /*
47  * We throw an HTTP error "502 bad gateway" when we need to connect to Citadel, but can't.
48  */
49 void do_502(struct http_transaction *h)
50 {
51         h->response_code = 502;
52         h->response_string = strdup("bad gateway");
53         add_response_header(h, strdup("Content-type"), strdup("text/plain"));
54         h->response_body =
55             strdup(_
56                    ("This program was unable to connect or stay connected to the Citadel server.  Please report this problem to your system administrator."));
57         h->response_body_length = strlen(h->response_body);
58 }
59
60
61 /*
62  * Tell the client to authenticate using HTTP-AUTH (RFC 2617)
63  */
64 void request_http_authenticate(struct http_transaction *h)
65 {
66         h->response_code = 401;
67         h->response_string = strdup("Unauthorized");
68         add_response_header(h, strdup("WWW-Authenticate"), strdup("Basic realm=\"Citadel Server\""));
69 }
70
71
72 /*
73  * Easy and fun utility function to throw a redirect.
74  */
75 void http_redirect(struct http_transaction *h, char *to_where)
76 {
77         syslog(LOG_DEBUG, "Redirecting to: %s", to_where);
78         h->response_code = 302;
79         h->response_string = strdup("Moved Temporarily");
80         add_response_header(h, strdup("Location"), strdup(to_where));
81         add_response_header(h, strdup("Content-type"), strdup("text/plain"));
82         h->response_body = strdup(to_where);
83         h->response_body_length = strlen(h->response_body);
84 }
85
86
87 /*
88  * perform_request() is the entry point for *every* HTTP transaction.
89  */
90 void perform_request(struct http_transaction *h)
91 {
92         struct ctdlsession *c;
93
94         // Determine which code path to take based on the beginning of the URI.
95         // This is implemented as a series of strncasecmp() calls rather than a
96         // lookup table in order to make the code more readable.
97
98         if (IsEmptyStr(h->uri)) {       // Sanity check
99                 do_404(h);
100                 return;
101         }
102         // Right about here is where we should try to handle anything that doesn't start
103         // with the /ctdl/ prefix.
104         // Root (/) ...
105
106         if ((!strcmp(h->uri, "/")) && (!strcasecmp(h->method, "GET"))) {
107                 http_redirect(h, "/ctdl/s/index.html");
108                 return;
109         }
110         // Legacy URI patterns (/readnew?gotoroom=xxx&start_reading_at=yyy) ...
111         // Direct room name (/my%20blog) ...
112
113         // Everything below this line is strictly REST URI patterns.
114
115         if (strncasecmp(h->uri, HKEY("/ctdl/"))) {      // Reject non-REST
116                 do_404(h);
117                 return;
118         }
119
120         if (!strncasecmp(h->uri, HKEY("/ctdl/s/"))) {   // Static content
121                 output_static(h);
122                 return;
123         }
124
125         if (h->uri[7] != '/') {
126                 do_404(h);
127                 return;
128         }
129         // Anything below this line:
130         //      1. Is in the format of /ctdl/?/*
131         //      2. Requires a connection to a Citadel server.
132
133         c = connect_to_citadel(h);
134         if (c == NULL) {
135                 do_502(h);
136                 return;
137         }
138         // WebDAV methods like OPTIONS and PROPFIND *require* a logged-in session,
139         // even if the Citadel server allows anonymous access.
140         if (IsEmptyStr(c->auth)) {
141                 if ((!strcasecmp(h->method, "OPTIONS"))
142                     || (!strcasecmp(h->method, "PROPFIND"))
143                     || (!strcasecmp(h->method, "REPORT"))
144                     || (!strcasecmp(h->method, "DELETE"))
145                     ) {
146                         request_http_authenticate(h);
147                         disconnect_from_citadel(c);
148                         return;
149                 }
150         }
151         // Break down the URI by path and send the request to the appropriate part of the program.
152         //
153         switch (h->uri[6]) {
154         case 'a':               // /ctdl/a/ == RESTful path to admin functions
155                 ctdl_a(h, c);
156                 break;
157         case 'c':               // /ctdl/c/ == misc Citadel server commands
158                 ctdl_c(h, c);
159                 break;
160         case 'r':               // /ctdl/r/ == RESTful path to rooms
161                 ctdl_r(h, c);
162                 break;
163         case 'u':               // /ctdl/u/ == RESTful path to users
164                 ctdl_u(h, c);
165                 break;
166         default:
167                 do_404(h);
168         }
169
170         // Are we in an authenticated session?  If so, set a cookie so we stay logged in.
171         if (!IsEmptyStr(c->auth)) {
172                 char koekje[AUTH_MAX];
173                 char *exp = http_datestring(time(NULL) + (86400 * 30));
174                 snprintf(koekje, AUTH_MAX, "wcauth=%s; path=/ctdl/; Expires=%s", c->auth, exp);
175                 free(exp);
176                 add_response_header(h, strdup("Set-Cookie"), strdup(koekje));
177         }
178         // During development we are foiling the browser cache completely.  In production we'll be more selective.
179         add_response_header(h, strdup("Cache-Control"), strdup("no-store, must-revalidate"));
180         add_response_header(h, strdup("Pragma"), strdup("no-cache"));
181         add_response_header(h, strdup("Expires"), strdup("0"));
182
183         // Unbind from our Citadel server connection.
184         disconnect_from_citadel(c);
185 }