Add to git.
[rws.git] / dir.c
1 /* Directory serving.
2  * - by Richard W.M. Jones <rich@annexia.org>
3  *
4  * This library is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU Library General Public
6  * License as published by the Free Software Foundation; either
7  * version 2 of the License, or (at your option) any later version.
8  *
9  * This library is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12  * Library General Public License for more details.
13  *
14  * You should have received a copy of the GNU Library General Public
15  * License along with this library; if not, write to the Free
16  * Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
17  *
18  * $Id: dir.c,v 1.13 2003/02/05 23:02:51 rich Exp $
19  */
20
21 #include "config.h"
22
23 #include <assert.h>
24
25 #ifdef HAVE_STRING_H
26 #include <string.h>
27 #endif
28
29 #ifdef HAVE_SYS_TYPES_H
30 #include <sys/types.h>
31 #endif
32
33 #ifdef HAVE_SYS_STAT_H
34 #include <sys/stat.h>
35 #endif
36
37 #ifdef HAVE_DIRENT_H
38 #include <dirent.h>
39 #endif
40
41 #ifdef HAVE_SYS_SYSLIMITS_H
42 #include <sys/syslimits.h>
43 #endif
44
45 #include <pool.h>
46 #include <pstring.h>
47 #include <vector.h>
48 #include <pre.h>
49
50 #include <pthr_pseudothread.h>
51 #include <pthr_http.h>
52 #include <pthr_iolib.h>
53
54 #include "process_rq.h"
55 #include "mime_types.h"
56 #include "file.h"
57 #include "errors.h"
58 #include "cfg.h"
59 #include "re.h"
60 #include "dir.h"
61
62 static void choose_icon (process_rq p,
63                          const char *filename, const struct stat *statbuf,
64                          const char **icon, const char **icon_alt,
65                          int *icon_width, int *icon_height);
66 static void standard_icon (process_rq p, const char *name,
67                            const char **icon, const char **icon_alt,
68                            int *icon_width, int *icon_height);
69 static void unknown_icon (process_rq p,
70                           const char **icon, const char **icon_alt,
71                           int *icon_width, int *icon_height);
72 static int parse_icon_str (process_rq p, const char *icon_str,
73                            const char **icon, const char **icon_alt,
74                            int *icon_width, int *icon_height);
75 static const char *get_printable_size (process_rq p,
76                                        const struct stat *statbuf);
77 static const char *get_link_field (process_rq p, const char *filename);
78
79 static int
80 my_strcmp (const char **p1, const char **p2)
81 {
82   return strcmp (*p1, *p2);
83 }
84
85 int
86 dir_serve (process_rq p)
87 {
88   http_response http_response;
89   int close, i;
90   char *index_file;
91   struct stat index_statbuf;
92   DIR *dir;
93   struct dirent *d;
94   vector files;
95
96   /* Is there an index file in this directory? If so, internally redirect
97    * the request to that file.
98    */
99   index_file = psprintf (p->pool, "%s/index.html", p->file_path);
100   if (stat (index_file, &index_statbuf) == 0 &&
101       S_ISREG (index_statbuf.st_mode))
102     {
103       /* Update the request structure appropriately. */
104       p->file_path = index_file;
105       p->remainder = psprintf (p->pool, "%s/index.html", p->remainder);
106       p->statbuf = index_statbuf;
107
108       /* Serve the file. */
109       return file_serve (p);
110     }
111
112   /* Are we allowed to generate a directory listing? */
113   if (!cfg_get_bool (p->host, p->alias, "list", 0))
114     return bad_request_error (p, "directory listing not allowed");
115
116   /* Yes: read the files into a local vector. */
117   dir = opendir (p->file_path);
118   if (dir == 0)
119     return bad_request_error (p, "error opening directory");
120
121   files = new_vector (p->pool, const char *);
122   while ((d = readdir (dir)) != 0)
123     {
124       if (d->d_name[0] != '.')  /* Ignore hidden files. */
125         {
126           char *name = pstrdup (p->pool, d->d_name);
127           vector_push_back (files, name);
128         }
129     }
130   closedir (dir);
131
132   /* Sort them into alphabetical order. */
133   psort (files, my_strcmp);
134
135   /* Not changed, so it's a real cache hit. */
136   http_response = new_http_response (p->pool, p->http_request, p->io,
137                                      200, "OK");
138   http_response_send_headers (http_response,
139                               /* Content type. */
140                               "Content-Type", "text/html",
141                               /* End of headers. */
142                               NULL);
143   close = http_response_end_headers (http_response);
144
145   if (http_request_is_HEAD (p->http_request)) return close;
146
147   io_fprintf (p->io,
148               "<html><head><title>Directory listing: %s</title></head>" CRLF
149               "<body bgcolor=\"#ffffff\">" CRLF
150               "<h1>Directory listing: %s</h1>" CRLF
151               "<a href=\"..\">Go up to parent directory</a>" CRLF
152               "<table border=\"0\">" CRLF,
153               p->canonical_path, p->canonical_path);
154
155   for (i = 0; i < vector_size (files); ++i)
156     {
157       const char *name, *pathname, *icon, *icon_alt;
158       const char *size = "", *link_field = "";
159       int icon_width, icon_height;
160       struct stat file_statbuf;
161
162       vector_get (files, i, name);
163
164       /* Generate the full pathname. */
165       pathname = psprintf (p->pool, "%s/%s", p->file_path, name);
166
167       /* Stat the file to get type and size information. */
168       if (lstat (pathname, &file_statbuf) == 0)
169         {
170           /* Choose an icon type. */
171           choose_icon (p, name, &file_statbuf, &icon, &icon_alt,
172                        &icon_width, &icon_height);
173
174           /* Get the size. */
175           if (S_ISREG (file_statbuf.st_mode))
176             size = get_printable_size (p, &file_statbuf);
177
178           /* If it's a link, get the link field. */
179           if (S_ISLNK (file_statbuf.st_mode))
180             link_field = get_link_field (p, pathname);
181
182           /* Print the pathname. */
183           io_fprintf (p->io,
184                       "<tr><td><img src=\"%s\" alt=\"%s\" width=\"%d\" height=\"%d\"></td><td><a href=\"%s%s\">%s</a> %s</td><td>%s</td></tr>" CRLF,
185                       icon, icon_alt, icon_width, icon_height,
186                       name,
187                       S_ISDIR (file_statbuf.st_mode) ? "/" : "",
188                       name,
189                       link_field,
190                       size);
191         }
192     }
193
194   io_fprintf (p->io,
195               "</table>" CRLF
196               "<hr>%s<br>" CRLF
197               "</body></html>" CRLF,
198               http_get_servername ());
199
200   return close;
201 }
202
203 static void
204 choose_icon (process_rq p,
205              const char *filename, const struct stat *statbuf,
206              const char **icon, const char **icon_alt,
207              int *icon_width, int *icon_height)
208 {
209   if (S_ISREG (statbuf->st_mode))
210     {
211       const char *mime_type = 0;
212       const char *icon_str;
213       vector v;
214
215       /* Get the file extension and map it to a MIME type. */
216       if ((v = prematch (p->pool, filename, re_ext, 0)) != 0)
217         {
218           char *ext;
219
220           vector_get (v, 1, ext);
221           mime_type = mime_types_get_type (ext);
222         }
223       if (!mime_type)
224         {
225           standard_icon (p, "no type",
226                          icon, icon_alt, icon_width, icon_height);
227           return;
228         }
229
230       /* If there a icon specified for this MIME type? */
231       icon_str = cfg_get_string (p->host, p->alias,
232                                  psprintf (p->pool, "icon for %s", mime_type),
233                                  0);
234       if (!icon_str)
235         {
236           /* Try looking for an icon for class / * instead. */
237           v = pstrcsplit (p->pool, mime_type, '/');
238           if (vector_size (v) >= 1)
239             {
240               const char *mime_class;
241
242               vector_get (v, 0, mime_class);
243               icon_str = cfg_get_string (p->host, p->alias,
244                                          psprintf (p->pool, "icon for %s/*",
245                                                    mime_class), 0);
246             }
247         }
248
249       if (!icon_str)
250         {
251           unknown_icon (p, icon, icon_alt, icon_width, icon_height);
252           return;
253         }
254
255       /* Split up the string. */
256       if (!parse_icon_str (p, icon_str, icon, icon_alt, icon_width, icon_height))
257         {
258           fprintf (stderr,
259                    "cannot parse icon description: %s (mime_type = %s)\n",
260                    icon_str, mime_type);
261           unknown_icon (p, icon, icon_alt, icon_width, icon_height);
262           return;
263         }
264     }
265   else if (S_ISDIR (statbuf->st_mode))
266     {
267       standard_icon (p, "directory", icon, icon_alt, icon_width, icon_height);
268     }
269   else if (S_ISLNK (statbuf->st_mode))
270     {
271       standard_icon (p, "link", icon, icon_alt, icon_width, icon_height);
272     }
273   else
274     {
275       standard_icon (p, "special", icon, icon_alt, icon_width, icon_height);
276     }
277 }
278
279 static void
280 standard_icon (process_rq p, const char *name,
281                const char **icon, const char **icon_alt,
282                int *icon_width, int *icon_height)
283 {
284   const char *icon_str;
285
286   icon_str = cfg_get_string (p->host, p->alias,
287                              psprintf (p->pool, "%s icon", name), 0);
288   if (!icon_str)
289     {
290       unknown_icon (p, icon, icon_alt, icon_width, icon_height);
291       return;
292     }
293
294   if (!parse_icon_str (p, icon_str, icon, icon_alt, icon_width, icon_height))
295     {
296       fprintf (stderr, "cannot parse icon description: %s\n", icon_str);
297       unknown_icon (p, icon, icon_alt, icon_width, icon_height);
298       return;
299     }
300 }
301
302 static void
303 unknown_icon (process_rq p,
304               const char **icon, const char **icon_alt,
305               int *icon_width, int *icon_height)
306 {
307   const char *icon_str;
308
309   icon_str = cfg_get_string (p->host, p->alias, "unknown icon", 0);
310   if (!icon_str)
311     {
312       fprintf (stderr,
313                "``unknown icon'' must be present in configuration file\n");
314       exit (1);
315     }
316
317   if (!parse_icon_str (p, icon_str, icon, icon_alt, icon_width, icon_height))
318     {
319       fprintf (stderr, "cannot parse icon description: %s\n", icon_str);
320       exit (1);
321     }
322 }
323
324 static int
325 parse_icon_str (process_rq p, const char *icon_str,
326                 const char **icon, const char **icon_alt,
327                 int *icon_width, int *icon_height)
328 {
329   vector v;
330   char *s;
331
332   /* Split up the string. */
333   if ((v = prematch (p->pool, icon_str, re_icon, 0)) == 0)
334     return 0;
335
336   if (vector_size (v) != 5) return 0;
337
338   vector_get (v, 1, *icon);
339   vector_get (v, 2, s);
340   sscanf (s, "%d", icon_width);
341   vector_get (v, 3, s);
342   sscanf (s, "%d", icon_height);
343   vector_get (v, 4, *icon_alt);
344
345   return 1;
346 }
347
348 static const char *
349 get_printable_size (process_rq p,
350                     const struct stat *statbuf)
351 {
352   unsigned long size = statbuf->st_size;
353
354   if (size < 1024)
355     return psprintf (p->pool, "%lu bytes", size);
356   else if (size < 1024 * 1024)
357     return psprintf (p->pool, "%.1f KB", size / 1024.0);
358   else
359     return psprintf (p->pool, "%.1f MB", size / (1024 * 1024.0));
360 }
361
362 static const char *
363 get_link_field (process_rq p, const char *filename)
364 {
365   const char prefix[] = "-&gt; ";
366   const int prefix_sz = sizeof prefix - 1;
367   char *buffer;
368   int n;
369
370 #ifndef NAME_MAX
371   /* Solaris defines NAME_MAX on a per-filesystem basis.
372    * See: http://lists.spine.cx/archives/everybuddy/2002-May/001419.html
373    */
374   long NAME_MAX = pathconf (filename, _PC_NAME_MAX);
375 #endif
376
377   buffer = pmalloc (p->pool, NAME_MAX + 1 + prefix_sz);
378
379   memcpy (buffer, prefix, prefix_sz);
380
381   n = readlink (filename, buffer + prefix_sz, NAME_MAX + 1);
382   if (n == -1) return "";
383
384   buffer[n + prefix_sz] = '\0';
385   return buffer;
386 }