Skeleton code for filters.
[citadel.git] / webcit-ng / server / upload.c
1 // Upload handler
2 //
3 // Copyright (c) 1996-2023 by the citadel.org team
4 //
5 // This program is open source software.  Use, duplication, or disclosure is subject to the GNU General Public License v3.
6
7 #include "webcit.h"
8
9 Array *upload_list = NULL;                                      // all files uploaded to this webcit instance
10 pthread_mutex_t upload_list_mutex = PTHREAD_MUTEX_INITIALIZER;  // Lock it before modifying
11
12
13 // This function is called by the MIME parser to handle data uploaded by the browser.
14 void upload_handler(char *name, char *filename, char *partnum, char *disp,
15                     void *content, char *cbtype, char *cbcharset,
16                     size_t length, char *encoding, char *cbid, void *userdata)
17 {
18         struct uploaded_file u;
19
20         // create a random ID for the attachment
21         for (int i=0; i<sizeof(u.id); ++i) {
22                 u.id[i] = (rand() % 26) + 'a';
23         }
24         u.id[sizeof(u.id)-1] = 0;
25
26         syslog(LOG_DEBUG,
27                 "upload_handler: name=%s, filename=%s, partnum=%s, disp=%s, cbtype=%s, cbcharset=%s, length=%ld, encoding=%s, cbid=%s",
28                 name, filename, partnum, disp, cbtype, cbcharset, length, encoding, cbid
29         );
30
31         safestrncpy(u.filename, filename, sizeof(u.filename));
32         safestrncpy(u.content_type, cbtype, sizeof(u.content_type));
33         u.length = length;
34
35         // Write the upload to a file that we can access later when the user saves the message.
36         // tmpfile() creates a file with zero links in the directory, so it will be deleted when it is closed.
37         u.fp = tmpfile();
38         if (!u.fp) {
39                 syslog(LOG_ERR, "upload: %m");
40                 return;
41         }
42         if (fwrite(content, length, 1, u.fp) != 1) {
43                 syslog(LOG_ERR, "upload: %m");
44                 fclose(u.fp);
45                 return;
46         }
47
48         // Add it to the list of uploads the server is holding.
49         pthread_mutex_lock(&upload_list_mutex);
50         if (upload_list == NULL) {
51                 upload_list = array_new(sizeof(struct uploaded_file));
52         }
53         array_append(upload_list, &u);
54         pthread_mutex_unlock(&upload_list_mutex);
55
56         for (int i=0; i<array_len(upload_list); ++i) {
57                 memcpy(&u, array_get_element_at(upload_list, i), sizeof(struct uploaded_file));
58         }
59
60         // Create a JSON object describing this upload
61         JsonValue *j_one_upload = NewJsonObject(HKEY(""));
62         JsonObjectAppend(j_one_upload, NewJsonPlainString(HKEY("ref"), u.id, -1));
63         JsonObjectAppend(j_one_upload, NewJsonPlainString(HKEY("uploadfilename"), u.filename, -1));
64         JsonObjectAppend(j_one_upload, NewJsonPlainString(HKEY("contenttype"), u.content_type, -1));
65         JsonObjectAppend(j_one_upload, NewJsonNumber(HKEY("contentlength"), u.length));
66
67         // ...and attach it to the array of uploads
68         JsonValue *j_uploads = (JsonValue *) userdata;
69         JsonArrayAppend(j_uploads, j_one_upload);
70 }
71
72
73 // upload handler
74 void upload_files(struct http_transaction *h, struct ctdlsession *c) {
75         // FIXME reject uploads if we're not logged in
76
77         // This will be a JSON Array of all files that were uploaded during this HTTP transaction.
78         // Normally the browser will upload only one file per transaction, but that behavior is not guaranteed.
79         JsonValue *j_uploads = NewJsonArray(HKEY(""));
80
81         // h->request_body_with_synth_headers will contain the upload(s) in MIME format including headers
82         mime_parser(h->request_body_with_synth_headers, (h->request_body+h->request_body_length), *upload_handler, NULL, NULL, j_uploads, 0);
83
84         // probably do something more clever here
85         h->response_code = 200;
86         h->response_string = strdup("OK");
87
88         // send back a JSON array of all files uploaded
89         StrBuf *sj = NewStrBuf();
90         SerializeJson(sj, j_uploads, 1);        // '1' == free the source object
91         add_response_header(h, strdup("Content-type"), strdup("application/json"));
92         h->response_code = 200;
93         h->response_string = strdup("OK");
94         h->response_body_length = StrLength(sj);
95         h->response_body = SmashStrBuf(&sj);
96         syslog(LOG_DEBUG, "upload: %s", h->response_body);
97 }
98
99
100 // Caller has requested /ctdl/p or /ctdl/p/ but we still have to dispatch based on the method
101 void ctdl_p_base(struct http_transaction *h, struct ctdlsession *c) {
102         upload_files(h, c);             // we should only do this for POST requests
103 }
104
105
106 // delete an uploaded item
107 void delete_upload(struct uploaded_file this_one) {
108         int i;
109         struct uploaded_file *u;
110
111         pthread_mutex_lock(&upload_list_mutex);
112         for (i=0; i<array_len(upload_list); ++i) {
113                 u = (struct uploaded_file *) array_get_element_at(upload_list, i), sizeof(struct uploaded_file);
114                 if (!strcmp(u->id, this_one.id)) {
115                         fclose(u->fp);                          // this deletes the file because it has 0 links
116                         array_delete_element_at(upload_list, i);
117                         i = array_len(upload_list) + 1;         // Go out of scope; we're done here
118                 }
119         }
120         pthread_mutex_unlock(&upload_list_mutex);
121 }
122
123
124 // DAV delete an uploaded item
125 void dav_delete_upload(struct http_transaction *h, struct ctdlsession *c, struct uploaded_file this_one) {
126         delete_upload(this_one);
127         do_204(h);
128 }
129
130
131 // Remove an uploaded item from the upload_list.  Caller now owns the file handle and is responsible for closing it.
132 struct uploaded_file pop_upload(char *id) {
133         int i;
134         struct uploaded_file *u;
135         struct uploaded_file ret;
136
137         memset(&ret, 0, sizeof(struct uploaded_file));
138
139         pthread_mutex_lock(&upload_list_mutex);
140         for (i=0; i<array_len(upload_list); ++i) {
141                 u = (struct uploaded_file *) array_get_element_at(upload_list, i), sizeof(struct uploaded_file);
142                 if (!strcmp(u->id, id)) {
143                         ret = *u;
144                         array_delete_element_at(upload_list, i);
145                         i = array_len(upload_list) + 1;         // Go out of scope; we're done here
146                 }
147         }
148         pthread_mutex_unlock(&upload_list_mutex);
149
150         return(ret);                                            // ret will be all-zeroes if we didn't find it
151 }
152
153
154 // When reloading attachments already in an existing message, accept only parts that are tagged as attachments.
155 void attachment_filter(char *name, char *filename, char *partnum, char *disp,
156                     void *content, char *cbtype, char *cbcharset,
157                     size_t length, char *encoding, char *cbid, void *userdata)
158 {
159         struct uploaded_file u;
160
161         if (!strcasecmp(disp, "attachment")) {
162                 upload_handler(name, filename, partnum, disp, content, cbtype, cbcharset, length, encoding, cbid, userdata);
163         }
164 }
165
166
167 // Load the attachments from an existing message.  This is typically used when forwarding a message,
168 // so the attachments don't have to be sent out to the browser and back.
169 void load_attachments_from_message(struct http_transaction *h, struct ctdlsession *c, char *name) {
170         syslog(LOG_DEBUG, "\033[33mload_attachments_from_message: method is \033[35m%s\033[33m, name is \033[33m%s\033[0m", h->method, name);
171
172         char buf[1024];
173         StrBuf *Body = NULL;
174
175         ctdl_printf(c, "MSG2 %ld", atol(name));
176         ctdl_readline(c, buf, sizeof buf);
177         if (buf[0] != '1') {
178                 do_404(h);
179                 return;
180         }
181
182         JsonValue *j_uploads = NewJsonArray(HKEY(""));
183         Body = NewStrBuf();
184         while (ctdl_readline(c, buf, sizeof buf), strcmp(buf, "000")) {
185                 StrBufAppendPrintf(Body, "%s\n", buf);
186         }
187         char *raw_message = SmashStrBuf(&Body);
188         mime_parser(raw_message, NULL, *attachment_filter, NULL, NULL, j_uploads, 0);
189         free(raw_message);
190
191         // probably do something more clever here
192         h->response_code = 200;
193         h->response_string = strdup("OK");
194
195         // send back a JSON array of all files uploaded
196         StrBuf *sj = NewStrBuf();
197         SerializeJson(sj, j_uploads, 1);        // '1' == free the source object
198         add_response_header(h, strdup("Content-type"), strdup("application/json"));
199         h->response_code = 200;
200         h->response_string = strdup("OK");
201         h->response_body_length = StrLength(sj);
202         h->response_body = SmashStrBuf(&sj);
203         syslog(LOG_DEBUG, "upload: %s", h->response_body);
204 }
205
206
207 // Handle operations on a specific upload
208 void specific_upload(struct http_transaction *h, struct ctdlsession *c, char *name) {
209         int i;
210         struct uploaded_file *u;
211         struct uploaded_file this_one;
212
213         syslog(LOG_DEBUG, "\033[33mspecific_upload: method is \033[35m%s\033[33m, name is \033[33m%s\033[0m", h->method, name);
214
215         // GET of a msgnum is a request to load the attachments from an existing message.
216         if ( (!strcasecmp(h->method, "GET")) && (atol(name) > 0) ) {
217                 load_attachments_from_message(h, c, name);
218                 return;
219         }
220
221         if (upload_list == NULL) {
222                 do_404(h);
223                 return;
224         }
225
226         memset(&this_one, 0, sizeof(struct uploaded_file));
227         pthread_mutex_lock(&upload_list_mutex);
228         for (i=0; i<array_len(upload_list); ++i) {
229                 u = (struct uploaded_file *) array_get_element_at(upload_list, i), sizeof(struct uploaded_file);
230                 if (!strcmp(u->id, name)) {
231                         memcpy(&this_one, u, sizeof(struct uploaded_file));
232                         i = array_len(upload_list) + 1;         // Go out of scope; we're done here
233                 }
234         }
235         pthread_mutex_unlock(&upload_list_mutex);
236
237         // If we found a matching ID, now dispatch based on the HTTP method.
238
239         if (IsEmptyStr(this_one.id)) {                          // didn't find a match
240                 do_404(h);
241         }
242         else if (!strcasecmp(h->method, "GET")) {               // fetch the item
243                 do_405(h);
244         }
245         else if (!strcasecmp(h->method, "DELETE")) {            // delete the item
246                 dav_delete_upload(h, c, this_one);
247         }
248         else {                                                  // unsupported method
249                 do_405(h);
250         }
251 }
252
253
254 // Dispatcher for paths starting with /ctdl/p/
255 void ctdl_p(struct http_transaction *h, struct ctdlsession *c) {
256         char buf[SIZ];
257
258         if (num_tokens(h->url, '/') == 3) {     //      /ctdl/p
259                 ctdl_p_base(h, c);
260                 return;
261         }
262
263         extract_token(buf, h->url, 3, '/', sizeof buf);
264         if (num_tokens(h->url, '/') == 4) {
265                 if (IsEmptyStr(buf)) {
266                         ctdl_p_base(h, c);      //      /ctdl/p/
267                 }
268                 else {
269                         specific_upload(h, c, &h->url[8]);
270                 }
271                 return;
272         }
273
274         // If we get to this point, the client requested an action we don't know how to perform.
275         do_404(h);
276 }