8363982082be08cf2a1ea6a8742e9526d200704a
[citadel.git] / webcit / downloads.c
1 /*
2  * Copyright (c) 1996-2012 by the citadel.org team
3  *
4  * This program is open source software.  You can redistribute it and/or
5  * modify it under the terms of the GNU General Public License, version 3.
6  *
7  * This program is distributed in the hope that it will be useful,
8  * but WITHOUT ANY WARRANTY; without even the implied warranty of
9  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
10  * GNU General Public License for more details.
11  */
12
13 #include "webcit.h"
14 #include "webserver.h"
15
16 CtxType CTX_FILELIST = CTX_NONE;
17
18 extern void output_static(const char* What);
19
20 extern char* static_dirs[];
21
22 typedef struct _FileListStruct {
23         StrBuf *Filename;
24         long FileSize;
25         StrBuf *MimeType;
26         StrBuf *Comment;
27         int IsPic;
28         int Sequence;
29 } FileListStruct;
30
31 void FreeFiles(void *vFile)
32 {
33         FileListStruct *F = (FileListStruct*) vFile;
34         FreeStrBuf(&F->Filename);
35         FreeStrBuf(&F->MimeType);
36         FreeStrBuf(&F->Comment);
37         free(F);
38 }
39
40 /* -------------------------------------------------------------------------------- */
41 void tmplput_FILE_NAME(StrBuf *Target, WCTemplputParams *TP)
42 {
43         FileListStruct *F = (FileListStruct*) CTX(CTX_FILELIST);
44         StrBufAppendTemplate(Target, TP, F->Filename, 0);
45 }
46 void tmplput_FILE_SIZE(StrBuf *Target, WCTemplputParams *TP)
47 {
48         FileListStruct *F = (FileListStruct*) CTX(CTX_FILELIST);
49         StrBufAppendPrintf(Target, "%ld", F->FileSize);
50 }
51 void tmplput_FILEMIMETYPE(StrBuf *Target, WCTemplputParams *TP)
52 {
53         FileListStruct *F = (FileListStruct*) CTX(CTX_FILELIST);
54         StrBufAppendTemplate(Target, TP, F->MimeType, 0);
55 }
56 void tmplput_FILE_COMMENT(StrBuf *Target, WCTemplputParams *TP)
57 {
58         FileListStruct *F = (FileListStruct*) CTX(CTX_FILELIST);
59         StrBufAppendTemplate(Target, TP, F->Comment, 0);
60 }
61
62 /* -------------------------------------------------------------------------------- */
63
64 int Conditional_FILE_ISPIC(StrBuf *Target, WCTemplputParams *TP)
65 {
66         FileListStruct *F = (FileListStruct*) CTX(CTX_FILELIST);
67         return F->IsPic;
68 }
69
70 /* -------------------------------------------------------------------------------- */
71 int CompareFilelistByMime(const void *vFile1, const void *vFile2)
72 {
73         FileListStruct *File1 = (FileListStruct*) GetSearchPayload(vFile1);
74         FileListStruct *File2 = (FileListStruct*) GetSearchPayload(vFile2);
75
76         if (File1->IsPic != File2->IsPic)
77                 return File1->IsPic > File2->IsPic;
78         return strcasecmp(ChrPtr(File1->MimeType), ChrPtr(File2->MimeType));
79 }
80 int CompareFilelistByMimeRev(const void *vFile1, const void *vFile2)
81 {
82         FileListStruct *File1 = (FileListStruct*) GetSearchPayload(vFile1);
83         FileListStruct *File2 = (FileListStruct*) GetSearchPayload(vFile2);
84         if (File1->IsPic != File2->IsPic)
85                 return File1->IsPic < File2->IsPic;
86         return strcasecmp(ChrPtr(File2->MimeType), ChrPtr(File1->MimeType));
87 }
88 int GroupchangeFilelistByMime(const void *vFile1, const void *vFile2)
89 {
90         FileListStruct *File1 = (FileListStruct*) vFile1;
91         FileListStruct *File2 = (FileListStruct*) vFile2;
92
93         if (File1->IsPic != File2->IsPic)
94                 return File1->IsPic > File2->IsPic;
95         return strcasecmp(ChrPtr(File1->MimeType), ChrPtr(File2->MimeType)) != 0;
96 }
97
98
99 int CompareFilelistByName(const void *vFile1, const void *vFile2)
100 {
101         FileListStruct *File1 = (FileListStruct*) GetSearchPayload(vFile1);
102         FileListStruct *File2 = (FileListStruct*) GetSearchPayload(vFile2);
103
104         if (File1->IsPic != File2->IsPic)
105                 return File1->IsPic > File2->IsPic;
106         return strcasecmp(ChrPtr(File1->Filename), ChrPtr(File2->Filename));
107 }
108 int CompareFilelistByNameRev(const void *vFile1, const void *vFile2)
109 {
110         FileListStruct *File1 = (FileListStruct*) GetSearchPayload(vFile1);
111         FileListStruct *File2 = (FileListStruct*) GetSearchPayload(vFile2);
112         if (File1->IsPic != File2->IsPic)
113                 return File1->IsPic < File2->IsPic;
114         return strcasecmp(ChrPtr(File2->Filename), ChrPtr(File1->Filename));
115 }
116 int GroupchangeFilelistByName(const void *vFile1, const void *vFile2)
117 {
118         FileListStruct *File1 = (FileListStruct*) vFile1;
119         FileListStruct *File2 = (FileListStruct*) vFile2;
120
121         return ChrPtr(File1->Filename)[0] != ChrPtr(File2->Filename)[0];
122 }
123
124
125 int CompareFilelistBySize(const void *vFile1, const void *vFile2)
126 {
127         FileListStruct *File1 = (FileListStruct*) GetSearchPayload(vFile1);
128         FileListStruct *File2 = (FileListStruct*) GetSearchPayload(vFile2);
129         if (File1->FileSize == File2->FileSize)
130                 return 0;
131         return (File1->FileSize > File2->FileSize);
132 }
133 int CompareFilelistBySizeRev(const void *vFile1, const void *vFile2)
134 {
135         FileListStruct *File1 = (FileListStruct*) GetSearchPayload(vFile1);
136         FileListStruct *File2 = (FileListStruct*) GetSearchPayload(vFile2);
137         if (File1->FileSize == File2->FileSize)
138                 return 0;
139         return (File1->FileSize < File2->FileSize);
140 }
141 int GroupchangeFilelistBySize(const void *vFile1, const void *vFile2)
142 {
143         return 0;
144 }
145
146
147 int CompareFilelistByComment(const void *vFile1, const void *vFile2)
148 {
149         FileListStruct *File1 = (FileListStruct*) GetSearchPayload(vFile1);
150         FileListStruct *File2 = (FileListStruct*) GetSearchPayload(vFile2);
151         return strcasecmp(ChrPtr(File1->Comment), ChrPtr(File2->Comment));
152 }
153 int CompareFilelistByCommentRev(const void *vFile1, const void *vFile2)
154 {
155         FileListStruct *File1 = (FileListStruct*) GetSearchPayload(vFile1);
156         FileListStruct *File2 = (FileListStruct*) GetSearchPayload(vFile2);
157         return strcasecmp(ChrPtr(File2->Comment), ChrPtr(File1->Comment));
158 }
159 int GroupchangeFilelistByComment(const void *vFile1, const void *vFile2)
160 {
161         FileListStruct *File1 = (FileListStruct*) vFile1;
162         FileListStruct *File2 = (FileListStruct*) vFile2;
163         return ChrPtr(File1->Comment)[9] != ChrPtr(File2->Comment)[0];
164 }
165
166
167 int CompareFilelistBySequence(const void *vFile1, const void *vFile2)
168 {
169         FileListStruct *File1 = (FileListStruct*) GetSearchPayload(vFile1);
170         FileListStruct *File2 = (FileListStruct*) GetSearchPayload(vFile2);
171         return (File2->Sequence >  File1->Sequence);
172 }
173 int GroupchangeFilelistBySequence(const void *vFile1, const void *vFile2)
174 {
175         return 0;
176 }
177
178 /* -------------------------------------------------------------------------------- */
179 HashList* LoadFileList(StrBuf *Target, WCTemplputParams *TP)
180 {
181         FileListStruct *Entry;
182         StrBuf *Buf;
183         HashList *Files;
184         int Done = 0;
185         int sequence = 0;
186         char buf[1024];
187         CompareFunc SortIt;
188         int HavePic = 0;
189         WCTemplputParams SubTP;
190
191         memset(&SubTP, 0, sizeof(WCTemplputParams));
192         serv_puts("RDIR");
193         serv_getln(buf, sizeof buf);
194         if (buf[0] != '1') return NULL;
195
196         Buf = NewStrBuf();             
197         Files = NewHash(1, NULL);
198         while (!Done && (StrBuf_ServGetln(Buf)>=0)) {
199                 if ( (StrLength(Buf)==3) && 
200                     !strcmp(ChrPtr(Buf), "000")) 
201                 {
202                         Done = 1;
203                         continue;
204                 }
205
206                 Entry = (FileListStruct*) malloc(sizeof (FileListStruct));
207                 Entry->Filename = NewStrBufPlain(NULL, StrLength(Buf));
208                 Entry->MimeType = NewStrBufPlain(NULL, StrLength(Buf));
209                 Entry->Comment = NewStrBufPlain(NULL, StrLength(Buf));
210
211                 Entry->Sequence = sequence++;
212
213                 StrBufExtract_token(Entry->Filename, Buf, 0, '|');
214                 Entry->FileSize = StrBufExtract_long(Buf, 1, '|');
215                 StrBufExtract_token(Entry->MimeType, Buf, 2, '|');
216                 StrBufExtract_token(Entry->Comment, Buf, 3, '|');
217
218
219
220                 Entry->IsPic = (strstr(ChrPtr(Entry->MimeType), "image") != NULL);
221                 if (Entry->IsPic) {
222                         HavePic = 1;
223                 }
224                 Put(Files, SKEY(Entry->Filename), Entry, FreeFiles);
225         }
226         if (HavePic)
227                 putbstr("__HAVE_PIC", NewStrBufPlain(HKEY("1")));
228         SubTP.Filter.ContextType = CTX_FILELIST;
229         SortIt = RetrieveSort(&SubTP, NULL, 0, HKEY("fileunsorted"), 0);
230         if (SortIt != NULL)
231                 SortByPayload(Files, SortIt);
232         else 
233                 SortByPayload(Files, CompareFilelistBySequence);
234         FreeStrBuf(&Buf);
235         return Files;
236 }
237
238 void display_mime_icon(void)
239 {
240         char FileBuf[SIZ];
241         const char *FileName;
242         char *MimeType;
243         size_t tlen;
244
245         MimeType = xbstr("type", &tlen);
246         FileName = GetIconFilename(MimeType, tlen);
247
248         if (FileName == NULL)
249                 snprintf (FileBuf, SIZ, "%s%s", static_dirs[0], "/webcit_icons/essen/16x16/file.png");
250         else
251                 snprintf (FileBuf, SIZ, "%s%s", static_dirs[3], FileName);
252         output_static(FileBuf);
253 }
254
255 void download_file(void)
256 {
257         wcsession *WCC = WC;
258         StrBuf *Buf;
259         off_t bytes;
260         StrBuf *ContentType = NewStrBufPlain(HKEY("application/octet-stream"));
261
262         /* Setting to nonzero forces a MIME type of application/octet-stream */
263         int force_download = 1;
264         
265         Buf = NewStrBuf();
266         StrBufExtract_token(Buf, WCC->Hdr->HR.ReqLine, 0, '/');
267         StrBufUnescape(Buf, 1);
268         serv_printf("OPEN %s", ChrPtr(Buf));
269         StrBuf_ServGetln(Buf);
270         if (GetServerStatus(Buf, NULL) == 2) {
271                 StrBufCutLeft(Buf, 4);
272                 bytes = StrBufExtract_long(Buf, 0, '|');
273                 StrBufExtract_token(ContentType, Buf, 3, '|');
274                 serv_read_binary(WCC->WBuf, bytes, Buf);
275                 serv_puts("CLOS");
276                 StrBuf_ServGetln(Buf);
277                 CheckGZipCompressionAllowed (SKEY(ContentType));
278                 if (force_download)
279                         FlushStrBuf(ContentType);
280                 http_transmit_thing(ChrPtr(ContentType), 0);
281         } else {
282                 StrBufCutLeft(Buf, 4);
283                 hprintf("HTTP/1.1 404 %s\n", ChrPtr(Buf));
284                 output_headers(0, 0, 0, 0, 0, 0);
285                 hprintf("Content-Type: text/plain\r\n");
286                 wc_printf(_("An error occurred while retrieving this file: %s\n"), 
287                         ChrPtr(Buf));
288                 end_burst();
289         }
290         FreeStrBuf(&ContentType);
291         FreeStrBuf(&Buf);
292 }
293
294
295
296 void delete_file(void)
297 {
298         const StrBuf *MimeType;
299         StrBuf *Line;
300         char buf[256];
301         
302         safestrncpy(buf, bstr("file"), sizeof buf);
303         unescape_input(buf);
304         serv_printf("DELF %s", buf);
305
306         StrBuf_ServGetln(Line);
307         GetServerStatusMsg(Line, NULL, 1, 0);
308
309         MimeType = DoTemplate(HKEY("files"), NULL, &NoCtx);
310         http_transmit_thing(ChrPtr(MimeType), 0);
311         FreeStrBuf(&Line);
312 }
313
314
315
316 void upload_file(void)
317 {
318         const StrBuf *RetMimeType;
319         const char *MimeType;
320         StrBuf *Line;
321         long bytes_transmitted = 0;
322         long blocksize;
323         const StrBuf *Desc;
324         wcsession *WCC = WC;     /* stack this for faster access (WC is a function) */
325
326         MimeType = GuessMimeType(ChrPtr(WCC->upload), WCC->upload_length); 
327
328                 Desc = sbstr("description");
329
330         serv_printf("UOPN %s|%s|%s", 
331                     ChrPtr(WCC->upload_filename), 
332                     MimeType, 
333                     ChrPtr(Desc));
334         Line = NewStrBuf();
335         StrBuf_ServGetln(Line);
336         if (GetServerStatusMsg(Line, NULL, 1, 2) != 2) {
337                 RetMimeType = DoTemplate(HKEY("files"), NULL, &NoCtx);
338                 http_transmit_thing(ChrPtr(RetMimeType), 0);
339                 FreeStrBuf(&Line);
340                 return;
341         }
342
343         while (bytes_transmitted < WCC->upload_length)
344         {
345                 blocksize = 4096;
346                 if (blocksize > (WCC->upload_length - bytes_transmitted))
347                 {
348                         blocksize = (WCC->upload_length - bytes_transmitted);
349                 }
350                 serv_printf("WRIT %ld", blocksize);
351                 StrBuf_ServGetln(Line);
352                 if (GetServerStatusMsg(Line, NULL, 0, 0) == 7) {
353                         blocksize = atoi(ChrPtr(Line) + 4);
354                         serv_write(&ChrPtr(WCC->upload)[bytes_transmitted], blocksize);
355                         bytes_transmitted += blocksize;
356                 }
357                 else
358                         break;
359         }
360
361         serv_puts("UCLS 1");
362         StrBuf_ServGetln(Line);
363         GetServerStatusMsg(Line, NULL, 1, 0);
364         RetMimeType = DoTemplate(HKEY("files"), NULL, &NoCtx);
365         http_transmit_thing(ChrPtr(RetMimeType), 0);
366         FreeStrBuf(&Line);
367 }
368
369
370
371 /*
372  * When the browser requests an image file from the Citadel server,
373  * this function is called to transmit it.
374  */
375 void output_image(void)
376 {
377         StrBuf *Buf;
378         wcsession *WCC = WC;
379         off_t bytes;
380         const char *MimeType;
381         
382         Buf = NewStrBuf();
383         serv_printf("OIMG %s|%s", bstr("name"), bstr("parm"));
384         StrBuf_ServGetln(Buf);
385         if (GetServerStatus(Buf, NULL) == 2) {
386                 int rc;
387                 StrBufCutLeft(Buf, 4);
388                 bytes = StrBufExtract_long(Buf, 0, '|');
389
390                 /** Read it from the server */
391                 
392                 rc = serv_read_binary(WCC->WBuf, bytes, Buf);
393                 serv_puts("CLOS");
394                 StrBuf_ServGetln(Buf);
395                 
396                 if (rc > 0) {
397                         MimeType = GuessMimeType (ChrPtr(WCC->WBuf), StrLength(WCC->WBuf));
398                         /** Write it to the browser */
399                         if (!IsEmptyStr(MimeType))
400                         {
401                                 CheckGZipCompressionAllowed (MimeType, strlen(MimeType));
402                                 http_transmit_thing(MimeType, 0);
403                                 FreeStrBuf(&Buf);
404                                 return;
405                         }
406                 }
407                 /* hm... unknown mimetype? fallback to blank gif */
408         }
409         else { 
410                 syslog(LOG_DEBUG, "OIMG failed: %s", ChrPtr(Buf));
411         }
412
413         
414         /*
415          * Instead of an ugly 404, send a 1x1 transparent GIF
416          * when there's no such image on the server.
417          */
418         StrBufPrintf (Buf, "%s%s", static_dirs[0], "/webcit_icons/blank.gif");
419         output_static(ChrPtr(Buf));
420         FreeStrBuf(&Buf);
421 }
422
423 void 
424 InitModule_DOWNLOAD
425 (void)
426 {
427         RegisterCTX(CTX_FILELIST);
428
429         RegisterIterator("ROOM:FILES", 0, NULL, LoadFileList,
430                          NULL, DeleteHash, CTX_FILELIST, CTX_NONE, 
431                          IT_FLAG_DETECT_GROUPCHANGE);
432
433         RegisterSortFunc(HKEY("filemime"),
434                          NULL, 0,
435                          CompareFilelistByMime,
436                          CompareFilelistByMimeRev,
437                          GroupchangeFilelistByMime,
438                          CTX_FILELIST);
439         RegisterSortFunc(HKEY("filename"),
440                          NULL, 0,
441                          CompareFilelistByName,
442                          CompareFilelistByNameRev,
443                          GroupchangeFilelistByName,
444                          CTX_FILELIST);
445         RegisterSortFunc(HKEY("filesize"),
446                          NULL, 0,
447                          CompareFilelistBySize,
448                          CompareFilelistBySizeRev,
449                          GroupchangeFilelistBySize,
450                          CTX_FILELIST);
451         RegisterSortFunc(HKEY("filesubject"),
452                          NULL, 0,
453                          CompareFilelistByComment,
454                          CompareFilelistByCommentRev,
455                          GroupchangeFilelistByComment,
456                          CTX_FILELIST);
457         RegisterSortFunc(HKEY("fileunsorted"),
458                          NULL, 0,
459                          CompareFilelistBySequence,
460                          CompareFilelistBySequence,
461                          GroupchangeFilelistBySequence,
462                          CTX_FILELIST);
463
464         RegisterNamespace("FILE:NAME", 0, 2, tmplput_FILE_NAME, NULL, CTX_FILELIST);
465         RegisterNamespace("FILE:SIZE", 0, 1, tmplput_FILE_SIZE, NULL, CTX_FILELIST);
466         RegisterNamespace("FILE:MIMETYPE", 0, 2, tmplput_FILEMIMETYPE, NULL, CTX_FILELIST);
467         RegisterNamespace("FILE:COMMENT", 0, 2, tmplput_FILE_COMMENT, NULL, CTX_FILELIST);
468
469         RegisterConditional("COND:FILE:ISPIC", 0, Conditional_FILE_ISPIC, CTX_FILELIST);
470
471         WebcitAddUrlHandler(HKEY("image"), "", 0, output_image, ANONYMOUS);
472         WebcitAddUrlHandler(HKEY("display_mime_icon"), "", 0, display_mime_icon , ANONYMOUS);
473         WebcitAddUrlHandler(HKEY("download_file"), "", 0, download_file, NEED_URL);
474         WebcitAddUrlHandler(HKEY("delete_file"), "", 0, delete_file, NEED_URL);
475         WebcitAddUrlHandler(HKEY("upload_file"), "", 0, upload_file, 0);
476 }