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