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