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
7 // Copyright (c) 1996-2023 by the citadel.org team
9 // This program is open source software. Use, duplication, or disclosure is subject to the GNU General Public License v3.
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);
25 void do_405(struct http_transaction *h) {
26 h->response_code = 412;
27 h->response_string = strdup("METHOD NOT ALLOWED");
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");
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");
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);
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\""));
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);
75 // perform_request() is the entry point for *every* HTTP transaction.
76 void perform_request(struct http_transaction *h) {
77 struct ctdlsession *c;
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.
83 if (IsEmptyStr(h->url)) { // Sanity check
88 // Right about here is where we should try to handle anything that doesn't start
89 // with the /ctdl/ prefix.
92 if ((!strcmp(h->url, "/")) && (!strcasecmp(h->method, "GET"))) {
93 http_redirect(h, "/ctdl/s/index.html");
98 if (!strncasecmp(h->url, HKEY("/.well-known/caldav"))) {
99 http_redirect(h, "/ctdl/r/calendar");
104 if (!strncasecmp(h->url, HKEY("/.well-known/carddav"))) {
105 http_redirect(h, "/ctdl/r/contacts");
109 // Legacy URL patterns (/readnew?gotoroom=xxx&start_reading_at=yyy) ...
110 // Direct room name (/my%20blog) ...
112 // HTTP-01 challenge [RFC5785 section 3, RFC8555 section 9.2]
113 if (!strncasecmp(h->url, HKEY("/.well-known"))) { // Static content
118 if (!strcasecmp(h->url, "/favicon.ico")) {
123 // Everything below this line is strictly REST URL patterns.
125 if (strncasecmp(h->url, HKEY("/ctdl/"))) { // Reject non-REST
130 if (!strncasecmp(h->url, HKEY("/ctdl/s/"))) { // Static content
135 if (h->url[7] != '/') {
140 // Anything below this line:
141 // 1. Is in the format of /ctdl/?/*
142 // 2. Requires a connection to a Citadel server.
144 c = connect_to_citadel(h);
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"))
160 request_http_authenticate(h);
161 disconnect_from_citadel(c);
166 // Break down the URL by path and send the request to the appropriate part of the program.
168 case 'a': // /ctdl/a/ == RESTful path to admin functions
171 case 'c': // /ctdl/c/ == misc Citadel server commands
174 case 'f': // /ctdl/f/ == RESTful path to floors
177 case 'r': // /ctdl/r/ == RESTful path to rooms
180 case 'u': // /ctdl/u/ == RESTful path to users
183 case 'p': // /ctdl/p/ == RESTful path to upload functions
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
196 add_response_header(h, strdup("Set-Cookie"), strdup(koekje));
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"));
204 // Unbind from our Citadel server connection.
205 disconnect_from_citadel(c);