Add to git.
[monolith.git] / widgets / ml_msp.c
1 /* Monolith server-parsed pages (.msp's).
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: ml_msp.c,v 1.10 2003/02/22 15:34:33 rich Exp $
19  */
20
21 #include "config.h"
22
23 #include <stdio.h>
24 #include <stdlib.h>
25 #include <ctype.h>
26
27 #ifdef HAVE_STRING_H
28 #include <string.h>
29 #endif
30
31 #ifdef HAVE_FCNTL_H
32 #include <fcntl.h>
33 #endif
34
35 #ifdef HAVE_UNISTD_H
36 #include <unistd.h>
37 #endif
38
39 #ifdef HAVE_DLFCN_H
40 #include <dlfcn.h>
41 #endif
42
43 #include <assert.h>
44
45 #include <pcre.h>
46
47 #include <pool.h>
48 #include <pstring.h>
49 #include <pre.h>
50 #include <pthr_iolib.h>
51
52 #include "monolith.h"
53 #include "ml_widget.h"
54 #include "ml_flow_layout.h"
55 #include "ml_label.h"
56 #include "ml_msp.h"
57
58 static void repaint (void *, ml_session, const char *, io_handle);
59
60 struct ml_widget_operations msp_ops =
61   {
62     repaint: repaint
63   };
64
65 struct ml_msp
66 {
67   struct ml_widget_operations *ops;
68   pool pool;                    /* Pool for allocations. */
69   ml_session session;           /* Current session. */
70   const char *conninfo;         /* Database connection. */
71   const char *rootdir;          /* Document root. */
72   const char *filename;         /* MSP file, relative to rootdir. */
73   const char *pathname;         /* Full path to file. */
74   vector widgetpath;            /* Path when loading widgets (may be NULL). */
75   ml_flow_layout w;             /* MSP is really just a flow layout. */
76 };
77
78 static void init_msp (void) __attribute__((constructor));
79 static void free_msp (void) __attribute__((destructor));
80 static int  parse_file (ml_msp w, int fd);
81 static int  verify_relative_path (const char *s);
82 static int  verify_filename (const char *s);
83
84 /* Global variables. */
85 static pool msp_pool;
86 static const pcre *re_openclose, *re_ws;
87
88 /* Initialise the library. */
89 static void
90 init_msp ()
91 {
92   msp_pool = new_subpool (global_pool);
93   re_openclose = precomp (msp_pool, "<%|%>", 0);
94   re_ws = precomp (msp_pool, "[ \t]+", 0);
95 }
96
97 /* Free up global memory used by the library. */
98 static void
99 free_msp ()
100 {
101   delete_pool (msp_pool);
102 }
103
104 ml_msp
105 new_ml_msp (pool pool, ml_session session, const char *conninfo,
106             const char *rootdir, const char *filename)
107 {
108   ml_msp w = pmalloc (pool, sizeof *w);
109   int fd;
110
111   /* Security checks on the rootdir and filename. */
112   if (rootdir[0] != '/')
113     pth_die ("ml_msp.c: rootdir must start with '/'\n");
114
115   if (!verify_relative_path (filename))
116     return 0;
117
118   w->ops = &msp_ops;
119   w->pool = pool;
120   w->session = session;
121   w->conninfo = conninfo;
122   w->rootdir = rootdir;
123   w->filename = filename;
124   w->widgetpath = 0;
125   w->w = new_ml_flow_layout (pool);
126
127   /* Create the full path to the file. */
128   w->pathname = psprintf (pool, "%s/%s", rootdir, filename);
129
130   /* Open it. */
131   fd = open (w->pathname, O_RDONLY);
132   if (fd == -1)
133     {
134       perror (psprintf (pool, "ml_msp.c: %s", w->pathname));
135       return 0;
136     }
137
138   /* Read and parse the file. */
139   if (parse_file (w, fd) == -1)
140     {
141       close (fd);
142       return 0;                 /* parse_file prints an error. */
143     }
144
145   close (fd);
146
147   return w;
148 }
149
150 static int parse_directive (ml_msp w, const char *directive);
151 static const char *load_file (pool tmp, int fd);
152
153 static int
154 parse_file (ml_msp w, int fd)
155 {
156   pool pool = w->pool, tmp = new_subpool (pool);
157   ml_label label;
158   vector v;
159   const char *file;
160   int i;
161   char state = 'o';
162
163   /* Load the file into a temporary buffer. */
164   file = load_file (tmp, fd);
165   if (!file) return -1;
166
167   /* Using pstrresplit2 we can split up the file into units like
168    * this: [ "some HTML", "<%", "directive", "%>", "some more HTML", ... ]
169    * This makes parsing the file much simpler.
170    */
171   v = pstrresplit2 (pool, file, re_openclose);
172
173   for (i = 0; i < vector_size (v); ++i)
174     {
175       const char *s;
176       char type;
177
178       vector_get (v, i, s);
179
180 #if 0                           /* Debug. */
181       fprintf (stderr, "ml_msp.c: reading %s\n", pstrndup (tmp, s, 20));
182 #endif
183
184       if (strcmp (s, "<%") == 0) type = '(';
185       else if (strcmp (s, "%>") == 0) type = ')';
186       else type = '-';
187
188       switch (state)
189         {
190         case 'o':               /* Waiting for opening <% */
191           if (type == '(')
192             /* Found it. We are now inside a directive. */
193             state = 'i';
194           else
195             {
196               /* Must be HTML. Output it as a label. */
197               label = new_ml_label (pool, s);
198               ml_flow_layout_push_back (w->w, label);
199             }
200           break;
201         case 'i':               /* Inside a directive. */
202           if (type == '-')
203             {
204               /* Parse the directive. */
205               if (parse_directive (w, s) == -1)
206                 return -1;
207               state = 'c';
208             }
209           else if (type == ')') /* Allow <%%> - just ignore it. */
210             state = 'o';
211           else if (type != ')')
212             {
213               /* It's an error. */
214               fprintf (stderr,
215                        "ml_msp.c: %s: unexpected '%s' inside directive.\n",
216                        w->filename, s);
217               return -1;
218             }
219           break;
220         case 'c':               /* Expecting %>. */
221           if (type == ')')
222             /* Found it. We are now back in HTML. */
223             state = 'o';
224           else
225             {
226               fprintf (stderr,
227                        "ml_msp.c: %s: unexpected '%s' inside directive.\n",
228                        w->filename, s);
229               return -1;
230             }
231         } /* switch (state) */
232     } /* for */
233
234   /* Check our final state, which must be 'o'. */
235   if (state != 'o')
236     {
237       fprintf (stderr, "ml_msp.c: %s: unclosed '<%%' in file.\n", w->filename);
238       return -1;
239     }
240
241   delete_pool (tmp);
242   return 0;
243 }
244
245 static int
246 do_include (ml_msp w, const char *include_file)
247 {
248   pool pool = w->pool;
249   char *dir, *t, *try;
250   int fd;
251
252   /* Verify that this is a plain, ordinary filename. */
253   if (!verify_filename (include_file))
254     return -1;
255
256   /* Locate the included file, relative to the current filename. Never leave
257    * the current root, however.
258    */
259   dir = pstrdup (pool, w->filename);
260   while (strlen (dir) > 0)
261     {
262       t = strrchr (dir, '/');
263       if (t)
264         {
265           *t = '\0';
266           try = psprintf (pool, "%s/%s/%s", w->rootdir, dir, include_file);
267         }
268       else
269         {
270           *dir = '\0';
271           try = psprintf (pool, "%s/%s", w->rootdir, include_file);
272         }
273
274       fd = open (try, O_RDONLY);
275       if (fd >= 0)
276         goto found_it;
277     }
278
279   /* Not found. */
280   fprintf (stderr, "ml_msp.c: include: %s: file not found.\n", include_file);
281   return -1;
282
283  found_it:
284   /* Parse the included file. */
285   if (parse_file (w, fd) == -1)
286     {
287       close (fd);
288       return -1;                /* parse_file prints an error. */
289     }
290
291   close (fd);
292
293   return 0;
294 }
295
296 static int
297 do_widget (ml_msp w, const char *libfile, const char *new_fn,
298            const vector args)
299 {
300   void *lib, *new_sym;
301   const char *filename, *error, *arg[5];
302   ml_widget widget;
303   int i;
304
305   if (strcmp (libfile, "-") == 0)
306     filename = 0;               /* Search in the current executable. */
307   else if (libfile[0] != '/')   /* Relative to the widget path. */
308     {
309       filename = libfile;
310
311       if (w->widgetpath)
312         for (i = 0; i < vector_size (w->widgetpath); ++i)
313           {
314             const char *path;
315             const char *try;
316
317             vector_get (w->widgetpath, i, path);
318             try = psprintf (w->pool, "%s/%s", path, libfile);
319             if (access (try, X_OK) == 0)
320               {
321                 filename = try;
322                 break;
323               }
324           }
325     }
326   else                          /* Absolute path. */
327     filename = libfile;
328
329   lib = dlopen (filename,
330 #ifndef __OpenBSD__
331                 RTLD_NOW
332 #else
333                 O_RDWR
334 #endif
335                 );
336   if (lib == 0)
337     {
338       fprintf (stderr, "ml_msp.c: %s: %s\n", libfile, dlerror ());
339       return -1;
340     }
341
342   /* Does the new function exist? */
343   new_sym = dlsym (lib, new_fn);
344   if ((error = dlerror ()) != 0)
345     {
346       fprintf (stderr, "ml_msp.c: %s: %s: %s\n", libfile, new_fn, error);
347       dlclose (new_sym);
348       return -1;
349     }
350
351   /* Make sure we close this library when the widget is deleted. */
352   pool_register_cleanup_fn (w->pool, (void (*)(void *)) dlclose, lib);
353
354   /* Formulate our call.
355    * XXX There needs to be a generic method for doing this in c2lib XXX
356    */
357   arg[0] = arg[1] = arg[2] = arg[3] = arg[4] = 0;
358   if (vector_size (args) >= 1) vector_get (args, 0, arg[0]);
359   if (vector_size (args) >= 2) vector_get (args, 1, arg[1]);
360   if (vector_size (args) >= 3) vector_get (args, 2, arg[2]);
361   if (vector_size (args) >= 4) vector_get (args, 3, arg[3]);
362   if (vector_size (args) >= 5) vector_get (args, 4, arg[4]);
363
364   if (vector_size (args) == 1 && strcmp (arg[0], "pool") == 0)
365     {
366       ml_widget (*fn) (pool) = (ml_widget (*) (pool)) new_sym;
367
368       widget = fn (w->pool);
369       if (widget) ml_flow_layout_push_back (w->w, widget);
370     }
371   else if (vector_size (args) == 2 && strcmp (arg[0], "pool") == 0 &&
372            strcmp (arg[1], "session") == 0)
373     {
374       ml_widget (*fn) (pool, ml_session) =
375         (ml_widget (*) (pool, ml_session)) new_sym;
376
377       widget = fn (w->pool, w->session);
378       if (widget) ml_flow_layout_push_back (w->w, widget);
379     }
380   else if (vector_size (args) == 3 && strcmp (arg[0], "pool") == 0 &&
381            strcmp (arg[1], "session") == 0 &&
382            strcmp (arg[2], "conninfo") == 0)
383     {
384       ml_widget (*fn) (pool, ml_session, const char *) =
385         (ml_widget (*) (pool, ml_session, const char *)) new_sym;
386
387       widget = fn (w->pool, w->session, w->conninfo);
388       if (widget) ml_flow_layout_push_back (w->w, widget);
389     }
390   else if (vector_size (args) == 4 && strcmp (arg[0], "pool") == 0 &&
391            strcmp (arg[1], "session") == 0 &&
392            strcmp (arg[2], "conninfo") == 0)
393     {
394       ml_widget (*fn) (pool, ml_session, const char *, const char *) =
395       (ml_widget (*) (pool, ml_session, const char *, const char *)) new_sym;
396
397       widget = fn (w->pool, w->session, w->conninfo, arg[3]);
398       if (widget) ml_flow_layout_push_back (w->w, widget);
399     }
400   else if (vector_size (args) == 5 && strcmp (arg[0], "pool") == 0 &&
401            strcmp (arg[1], "session") == 0 &&
402            strcmp (arg[2], "conninfo") == 0)
403     {
404       ml_widget (*fn) (pool, ml_session, const char *, const char *, const char *) =
405       (ml_widget (*) (pool, ml_session, const char *, const char *, const char *)) new_sym;
406
407       widget = fn (w->pool, w->session, w->conninfo, arg[3], arg[4]);
408       if (widget) ml_flow_layout_push_back (w->w, widget);
409     }
410   else
411     abort ();                   /* XXX Not yet implemented */
412
413   return 0;
414 }
415
416 static int
417 do_widgetpath (ml_msp w, const vector dirs)
418 {
419   fprintf (stderr, "XXX not impl XXX\n");
420   return -1;
421 }
422
423 static int
424 parse_directive (ml_msp w, const char *directive)
425 {
426   vector tokens;
427   pool pool = w->pool;
428   const char *command;
429
430   /* Split the directive up into tokens.
431    * XXX This is presently not very intelligent. It will split
432    * string arguments. There is a proposed solution for this in
433    * the form of a 'pstrtok' function in c2lib. At the moment this
434    * will suffice.
435    */
436   tokens = pstrresplit (pool, directive, re_ws);
437   if (vector_size (tokens) < 1)
438     return 0;                   /* Just ignore it. */
439
440   /* Get the command. */
441   vector_pop_front (tokens, command);
442
443   if (strcasecmp (command, "include") == 0)
444     {
445       const char *file;
446
447       if (vector_size (tokens) != 1)
448         {
449           fprintf (stderr, "ml_msp.c: %s: include: needs one filename\n",
450                    w->filename);
451           return -1;
452         }
453       vector_pop_front (tokens, file);
454       return do_include (w, file);
455     }
456   else if (strcasecmp (command, "widget") == 0)
457     {
458       const char *file;
459       const char *new_fn;
460
461       if (vector_size (tokens) < 3)
462         {
463           fprintf (stderr, "ml_msp.c: %s: widget: bad parameters\n",
464                    w->filename);
465           return -1;
466         }
467       vector_pop_front (tokens, file);
468       vector_pop_front (tokens, new_fn);
469       return do_widget (w, file, new_fn, tokens);
470     }
471   else if (strcasecmp (command, "widgetpath") == 0)
472     {
473       return do_widgetpath (w, tokens);
474     }
475   else
476     {
477       fprintf (stderr, "ml_msp.c: %s: %s: unknown directive\n",
478                w->filename, command);
479       return -1;
480     }
481 }
482
483 /* Load a file into memory from fd, allocating space from the
484  * temporary pool tmp.
485  */
486 static const char *
487 load_file (pool tmp, int fd)
488 {
489   char *file = 0;
490   int r, sz = 0;
491   const int n = 16385;          /* [sic] - see comment at end */
492
493   for (;;)
494     {
495       file = prealloc (tmp, file, sz + n);
496       r = read (fd, file + sz, n-1);
497
498       if (r < 0)
499         {
500           perror ("read");
501           return 0;
502         }
503       if (r == 0) break;        /* end of file */
504
505       sz += r;
506     }
507
508   /* Since tmp is a temporary pool, don't bother rounding the size of the
509    * buffer down to match the file size. Just make sure it's null-terminated.
510    * Note that one extra byte we reserved above.
511    */
512   file[sz] = '\0';
513
514   return file;
515 }
516
517 static int
518 verify_relative_path (const char *s)
519 {
520   if (s[0] == '/' ||
521       strncmp (s, "..", 2) == 0 ||
522       strstr (s, "/..") ||
523       strlen (s) == 0)
524     {
525       fprintf (stderr,
526                "ml_msp.c: security error: string is not a relative path.\n");
527       return 0;
528     }
529   return 1;
530 }
531
532 static int
533 verify_filename (const char *s)
534 {
535   if (strchr (s, '/') != 0 ||
536       s[0] == '.' ||
537       strlen (s) == 0)
538     {
539       fprintf (stderr,
540                "ml_msp.c: security error: string is not a plain filename.\n");
541       return 0;
542     }
543   return 1;
544 }
545
546 static void
547 repaint (void *vw, ml_session session, const char *windowid, io_handle io)
548 {
549   ml_msp w = (ml_msp) vw;
550
551   /* Repaint the flow layout. */
552   if (w->w)
553     ml_widget_repaint (w->w, session, windowid, io);
554 }