e598cf1f68dd7c8c41499a66070449fbbd47e68f
[citadel.git] / webcit-ng / server / 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-2023 by the citadel.org team
8 //
9 // This program is open source software.  Use, duplication, or
10 // disclosure are subject to the GNU General Public License v3.
11
12 #include "webcit.h"
13
14
15 // Not found!  Wowzers.
16 void do_404(struct http_transaction *h) {
17         h->response_code = 404;
18         h->response_string = strdup("NOT FOUND");
19         add_response_header(h, strdup("Content-type"), strdup("text/plain"));
20         h->response_body = strdup("404 NOT FOUND\r\n");
21         h->response_body_length = strlen(h->response_body);
22 }
23
24
25 // Method not allowed
26 void do_405(struct http_transaction *h) {
27         h->response_code = 412;
28         h->response_string = strdup("METHOD NOT ALLOWED");
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 // Succeed with no output
40 void do_204(struct http_transaction *h) {
41         h->response_code = 204;
42         h->response_string = strdup("No content");
43 }
44
45
46 // We throw an HTTP error "502 bad gateway" when we need to connect to Citadel, but can't.
47 void do_502(struct http_transaction *h) {
48         h->response_code = 502;
49         h->response_string = strdup("bad gateway");
50         add_response_header(h, strdup("Content-type"), strdup("text/plain"));
51         h->response_body = strdup(_("This program was unable to connect or stay connected to the Citadel server.  Please report this problem to your system administrator."));
52         h->response_body_length = strlen(h->response_body);
53 }
54
55
56 // Tell the client to authenticate using HTTP-AUTH (RFC 2617)
57 void request_http_authenticate(struct http_transaction *h) {
58         h->response_code = 401;
59         h->response_string = strdup("Unauthorized");
60         add_response_header(h, strdup("WWW-Authenticate"), strdup("Basic realm=\"Citadel Server\""));
61 }
62
63
64 // Easy and fun utility function to throw a redirect.
65 void http_redirect(struct http_transaction *h, char *to_where) {
66         syslog(LOG_DEBUG, "Redirecting to: %s", to_where);
67         h->response_code = 302;
68         h->response_string = strdup("Moved Temporarily");
69         add_response_header(h, strdup("Location"), strdup(to_where));
70         add_response_header(h, strdup("Content-type"), strdup("text/plain"));
71         h->response_body = strdup(to_where);
72         h->response_body_length = strlen(h->response_body);
73 }
74
75
76 // perform_request() is the entry point for *every* HTTP transaction.
77 void perform_request(struct http_transaction *h) {
78         struct ctdlsession *c;
79
80         // Determine which code path to take based on the beginning of the URL.
81         // This is implemented as a series of strncasecmp() calls rather than a
82         // lookup table in order to make the code more readable.
83
84         if (IsEmptyStr(h->url)) {       // Sanity check
85                 do_404(h);
86                 return;
87         }
88
89         // Right about here is where we should try to handle anything that doesn't start
90         // with the /ctdl/ prefix.
91         // Root (/) ...
92
93         if ((!strcmp(h->url, "/")) && (!strcasecmp(h->method, "GET"))) {
94                 http_redirect(h, "/ctdl/s/index.html");
95                 return;
96         }
97
98         // Legacy URL patterns (/readnew?gotoroom=xxx&start_reading_at=yyy) ...
99         // Direct room name (/my%20blog) ...
100
101         // HTTP-01 challenge [RFC5785 section 3, RFC8555 section 9.2]
102         if (!strncasecmp(h->url, HKEY("/.well-known"))) {       // Static content
103                 output_static(h);
104                 return;
105         }
106
107         if (!strcasecmp(h->url, "/favicon.ico")) {
108                 output_static(h);
109                 return;
110         }
111
112         // Everything below this line is strictly REST URL patterns.
113
114         if (strncasecmp(h->url, HKEY("/ctdl/"))) {              // Reject non-REST
115                 do_404(h);
116                 return;
117         }
118
119         if (!strncasecmp(h->url, HKEY("/ctdl/s/"))) {           // Static content
120                 output_static(h);
121                 return;
122         }
123
124         if (h->url[7] != '/') {
125                 do_404(h);
126                 return;
127         }
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
139         // WebDAV methods like OPTIONS and PROPFIND *require* a logged-in session,
140         // even if the Citadel server allows anonymous access.
141         if (IsEmptyStr(c->auth)) {
142                 if (       (!strcasecmp(h->method, "OPTIONS"))
143                         || (!strcasecmp(h->method, "PROPFIND"))
144                         || (!strcasecmp(h->method, "REPORT"))
145                         || (!strcasecmp(h->method, "DELETE"))
146                         || (!strcasecmp(h->method, "MOVE"))
147                         || (!strcasecmp(h->method, "COPY"))
148                 ) {
149                         request_http_authenticate(h);
150                         disconnect_from_citadel(c);
151                         return;
152                 }
153         }
154
155         // Break down the URL by path and send the request to the appropriate part of the program.
156         switch (h->url[6]) {
157         case 'a':               // /ctdl/a/ == RESTful path to admin functions
158                 ctdl_a(h, c);
159                 break;
160         case 'c':               // /ctdl/c/ == misc Citadel server commands
161                 ctdl_c(h, c);
162                 break;
163         case 'f':               // /ctdl/f/ == RESTful path to floors
164                 ctdl_f(h, c);
165                 break;
166         case 'r':               // /ctdl/r/ == RESTful path to rooms
167                 ctdl_r(h, c);
168                 break;
169         case 'u':               // /ctdl/u/ == RESTful path to users
170                 ctdl_u(h, c);
171                 break;
172         case 'p':               // /ctdl/p/ == RESTful path to upload functions
173                 ctdl_p(h, c);
174                 break;
175         default:
176                 do_404(h);
177         }
178
179         // Are we in an authenticated session?  If so, set a cookie so we stay logged in.
180         if (!IsEmptyStr(c->auth)) {
181                 char koekje[AUTH_MAX];
182                 char *exp = http_datestring(time(NULL) + (86400 * 30));
183                 snprintf(koekje, AUTH_MAX, "wcauth=%s; path=/ctdl/; Expires=%s", c->auth, exp); // warn
184                 free(exp);
185                 add_response_header(h, strdup("Set-Cookie"), strdup(koekje));
186         }
187
188         // Durlng development we are foiling the browser cache completely.  In production we'll be more selective.
189         add_response_header(h, strdup("Cache-Control"), strdup("no-store, must-revalidate"));
190         add_response_header(h, strdup("Pragma"), strdup("no-cache"));
191         add_response_header(h, strdup("Expires"), strdup("0"));
192
193         // Unbind from our Citadel server connection.
194         disconnect_from_citadel(c);
195 }