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