More work on scan. The search.egw script takes care of cached
[egate.git] / www / wproto.c
1 /*
2  * Copyright (c) 1995, the EUROPAGATE consortium (see below).
3  *
4  * The EUROPAGATE consortium members are:
5  *
6  *    University College Dublin
7  *    Danmarks Teknologiske Videnscenter
8  *    An Chomhairle Leabharlanna
9  *    Consejo Superior de Investigaciones Cientificas
10  *
11  * Permission to use, copy, modify, distribute, and sell this software and
12  * its documentation, in whole or in part, for any purpose, is hereby granted,
13  * provided that:
14  *
15  * 1. This copyright and permission notice appear in all copies of the
16  * software and its documentation. Notices of copyright or attribution
17  * which appear at the beginning of any file must remain unchanged.
18  *
19  * 2. The names of EUROPAGATE or the project partners may not be used to
20  * endorse or promote products derived from this software without specific
21  * prior written permission.
22  *
23  * 3. Users of this software (implementors and gateway operators) agree to
24  * inform the EUROPAGATE consortium of their use of the software. This
25  * information will be used to evaluate the EUROPAGATE project and the
26  * software, and to plan further developments. The consortium may use
27  * the information in later publications.
28  * 
29  * 4. Users of this software agree to make their best efforts, when
30  * documenting their use of the software, to acknowledge the EUROPAGATE
31  * consortium, and the role played by the software in their work.
32  *
33  * THIS SOFTWARE IS PROVIDED "AS IS" AND WITHOUT WARRANTY OF ANY KIND,
34  * EXPRESS, IMPLIED, OR OTHERWISE, INCLUDING WITHOUT LIMITATION, ANY
35  * WARRANTY OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE.
36  * IN NO EVENT SHALL THE EUROPAGATE CONSORTIUM OR ITS MEMBERS BE LIABLE
37  * FOR ANY SPECIAL, INCIDENTAL, INDIRECT OR CONSEQUENTIAL DAMAGES OF
38  * ANY KIND, OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA
39  * OR PROFITS, WHETHER OR NOT ADVISED OF THE POSSIBILITY OF DAMAGE, AND
40  * ON ANY THEORY OF LIABILITY, ARISING OUT OF OR IN CONNECTION WITH THE
41  * USE OR PERFORMANCE OF THIS SOFTWARE.
42  *
43  * $Log: wproto.c,v $
44  * Revision 1.10  1995/12/22 14:21:16  adam
45  * More work on scan. The search.egw script takes care of cached
46  * query page (doesn't always increment nextSetNo). To make new search set
47  * either 'New query' must be selected or the query page must be reloaded.
48  * The msearch script doesn't do this yet, however.
49  *
50  * Revision 1.9  1995/11/14  16:31:36  adam
51  * Temporary remove of ccl entry.
52  *
53  * Revision 1.8  1995/11/13  15:41:45  adam
54  * Arrow gifs.
55  * Gateway uses record element set names B(rief) and F(ull).
56  * Bug fix. Didn't save idAuthentication correctly.
57  *
58  * Revision 1.7  1995/11/10  14:47:32  adam
59  * Plus (+) characters automatically converted to space in forms.
60  * Work on search in multiple targets. Doesn't work well - yet.
61  * Presentation formats enhanced.
62  *
63  * Revision 1.6  1995/11/06  10:51:17  adam
64  * End of response marker in response from wsh/wproto to wcgi.
65  * Shells are respawned when necessary.
66  *
67  * Revision 1.5  1995/11/02  16:35:37  adam
68  * Bug fixes and select on FIFOs in wcgi - doesn't really work!
69  *
70  * Revision 1.4  1995/10/31  16:56:25  adam
71  * Record presentation.
72  *
73  * Revision 1.3  1995/10/27  15:12:10  adam
74  * IrTcl incorporated in the gateway.
75  * Better separation of script types.
76  * Z39.50 gateway scripts entered.
77  *
78  * Revision 1.2  1995/10/23  16:55:39  adam
79  * A lot of changes - really.
80  *
81  * Revision 1.1  1995/10/20  11:49:26  adam
82  * First version of www gateway.
83  *
84  */
85
86 #include <stdio.h>
87 #include <string.h>
88 #include <stdlib.h>
89 #include <sys/time.h>
90 #include <sys/types.h>
91 #include <sys/stat.h>
92 #include <fcntl.h>
93 #include <unistd.h>
94 #include <stdarg.h>
95 #include <ctype.h>
96 #include <errno.h>
97
98 #include "wproto.h"
99
100 static int wproto_dumpcache(WCLIENT wc, int level);
101 static int wproto_findcache(WCLIENT wc, char *name);
102 static void wproto_uncache(WCLIENT wc, int level);
103
104 static char *mod = "wproto";
105
106 void wo_write (WCLIENT wc, const char *s, size_t len)
107 {
108     if (wc->outbuffer_offset + len >= wc->outbuffer_size)
109         wc->outbuffer = realloc(wc->outbuffer, wc->outbuffer_size +=
110         OUTBUFFER_CHUNK);
111     memcpy(wc->outbuffer + wc->outbuffer_offset, s, len);
112     wc->outbuffer_offset += len;
113 }
114
115 void wo_puts (WCLIENT wc, const char *s)
116 {
117     wo_write (wc, s, strlen(s));
118 }
119
120 void wo_printf (WCLIENT wc, const char *fmt, ...)
121 {
122     va_list ap;
123     char tmpbuf[4048];
124
125     va_start(ap, fmt);
126     vsprintf(tmpbuf, fmt, ap);
127     wo_puts(wc, tmpbuf);
128     va_end(ap);
129 }
130
131 void wo_clear (WCLIENT wc, char *type)
132 {
133     if (!wc->outbuffer)
134         wc->outbuffer = malloc(wc->outbuffer_size = OUTBUFFER_CHUNK);
135     wc->outbuffer_offset = 0;
136 #if 0
137     wo_printf(wc, "Expires: 0\nContent-type: %s\n\n", type);
138 #else
139     wo_printf(wc, "Content-type: %s\n\n", type);
140 #endif
141 }
142
143 int wo_puthtml (WCLIENT wc, char *name)
144 {
145     FILE *f; 
146     char ch;
147
148     wo_clear(wc, "text/html");
149     if (!(f = fopen(name, "r")))
150     {
151         wo_printf(wc, "<BR>Failed to open file: %s<BR>", name);
152         return 0;
153     }
154     while (ch = getc(f), !feof(f))
155     {
156         if (wo_putc(wc, ch) < 0)
157         {
158             fclose(f);
159             return -1;
160         }
161     }
162     fclose(f);
163     return 0;
164 }
165
166 int wo_flush(WCLIENT wc)
167 {
168     int wrote, towrite;
169
170     if (!(wc->outbuffer_offset))
171         return 0;
172     towrite = wc->outbuffer_offset;
173     wc->outbuffer_offset = 0;
174     for (;;)
175     {
176         int w_chunk;
177
178         w_chunk = towrite;
179         wrote = write(wc->lineout, wc->outbuffer + wc->outbuffer_offset,
180             w_chunk);
181         if (wrote <= 0)
182         {
183             gw_log (GW_LOG_FATAL|GW_LOG_ERRNO, mod, "write response");
184             return -1;
185         }
186         gw_log (GW_LOG_DEBUG, mod, "wrote %d bytes", wrote);
187         if (wc->cache_fd >= 0)
188             if (write(wc->cache_fd, wc->outbuffer + wc->outbuffer_offset,
189                 towrite) < 0)
190             {   
191                 gw_log (GW_LOG_FATAL|GW_LOG_ERRNO, mod, "write cache");
192                 return -1;
193             }
194         towrite -= wrote;
195         if (!towrite)
196             break;
197         wc->outbuffer_offset += wrote;
198     }
199     wc->outbuffer_offset = 0;
200     return 0;
201 }
202
203 int wo_overflow(WCLIENT wc, char ch)
204 {
205     gw_log (GW_LOG_DEBUG, mod, "wo_overflow");
206     if (wo_flush(wc) < 0)
207         return -1;
208     return wo_putc(wc, ch);
209 }
210
211 int wo_finish(WCLIENT wc)
212 {
213     gw_log (GW_LOG_DEBUG, mod, "wo_finish");
214   
215     wo_putc (wc, 0);
216     if (wo_flush(wc) < 0)
217         return -1;
218     close(wc->lineout);
219     wc->lineout = -1;
220     if (wc->cache_fd >= 0)
221     {
222         close(wc->cache_fd);
223         wc->cache_fd = -1;
224     }
225     return 0;
226 }
227
228 static void descramble(char *t, const char *o)
229 {
230     unsigned int v;
231
232     while (*o)
233     {
234         if (*o == '%' && isxdigit(*(o + 1)) && isxdigit(*(o + 2)))
235         {
236             sscanf(o + 1, "%2x", &v);
237             o += 3;
238             if (v == '+')
239                 *t = ' ';
240             else
241                 *t = (char) v;
242             t++;
243         }
244         else
245         {
246             if (*o == '+')
247                 *t = ' ';
248             else
249                 *t = *o;
250             t++;
251             o++;
252         }
253     }
254     *t = '\0';
255 }
256
257 static void decode_form(wform_data *form, char *buf)
258 {
259     int i = 0;
260     char *p;
261     char tmp[512];
262
263     while (*buf)
264     {
265         for (p = form[i].name; *buf && *buf != '='; buf++)
266             *(p++) = *buf;
267         *p = '\0';
268         if (*buf)
269             buf++;
270         for (p = tmp; *buf && *buf != '&'; buf++)
271             *(p++) = *buf;
272         *p = '\0';
273         descramble(form[i].value, tmp);
274         if (*buf)
275             buf++;
276         i++;
277     }
278     *form[i].name = '\0';
279 }
280
281 char *wgetval(WCLIENT wc, char *name)
282 {
283     int i;
284
285     for (i = 0; *wc->wf_data[i].name; i++)
286         if (!strcmp(name, wc->wf_data[i].name))
287             return wc->wf_data[i].value;
288     return 0;
289 }
290
291 int wproto_process(WCLIENT wc, int timeout)
292 {
293     int toread, rs, level;
294     char combuf[COMBUF], *p,*t;
295     fd_set input;
296     struct timeval to, *top;
297
298     for (;;)
299     {
300         gw_log (GW_LOG_DEBUG, mod, "process waiting for input.");
301         if (timeout > 0)
302         {
303             to.tv_usec = 0;
304             to.tv_sec = timeout;
305             top = &to;
306         }
307         else
308             top = 0;
309         FD_ZERO(&input);
310         FD_SET(wc->linein, &input);
311         /* go through select handle list */
312         while ((rs = select(wc->linein + 1, &input, 0, 0, top)) < 0 &&
313             errno == EINTR)
314             ;
315         if (rs < 0)
316         {
317             gw_log (GW_LOG_FATAL|GW_LOG_ERRNO, mod, "select");
318             return -1;
319         }
320         if (rs == 0)
321         {
322             gw_log (GW_LOG_STAT, mod, 
323                     "wproto_process returning 0 after %d second timeout.",
324                     timeout);
325             unlink (wc->wf_serverp);
326             return 0;
327         }
328         /* determine handle (fifo or user) */
329         if (read(wc->linein, &toread, sizeof(toread)) < sizeof(toread))
330         {
331             gw_log (GW_LOG_FATAL|GW_LOG_ERRNO, mod, "wp_proc:len read failed");
332             exit(1);
333         }
334         toread -= sizeof(toread);
335         if (read(wc->linein, combuf, toread) < toread)
336         {
337             gw_log (GW_LOG_FATAL|GW_LOG_ERRNO, mod, "wp_proc: data read failed");
338             exit(1);
339         }
340         p = combuf;
341         for (t = wc->wf_serverp; (*t = *p); t++, p++);
342         p++;
343         for (t = wc->wf_parms; (*t = *p); t++, p++);
344         p++;
345         p++;         /* we don't deal with envvars yet */
346         decode_form(wc->wf_data, p);
347         if (wc->lineout < 0 && (wc->lineout = open(wc->wf_serverp, O_WRONLY))
348             < 0)
349         {
350             gw_log (GW_LOG_FATAL|GW_LOG_ERRNO, mod, "open %s", wc->wf_serverp);
351             exit(1);
352         }
353         /* look in cache only if request carries no forms data. */
354         if (!*wc->wf_data[0].name && (level = wproto_findcache(wc,
355             wc->wf_parms)) >= 0)
356         {
357             wproto_dumpcache(wc, level);
358             wo_finish(wc);
359         }
360         else
361         {
362             return 1;
363         }
364     }
365 }
366
367 WCLIENT wproto_init(void)
368 {
369     char *val, path2[256];
370     wclient_data *new;
371
372     gw_log (GW_LOG_DEBUG, mod, "wproto_init");
373     close(1);    /* release us from the wserver */
374     if (!(new = malloc(sizeof(*new))))
375     {
376         gw_log (GW_LOG_FATAL|GW_LOG_ERRNO, mod, "malloc");
377         exit (1);
378     }
379     if (!(val = getenv ("GWID")))
380     {
381         gw_log (GW_LOG_FATAL, mod, "GWID not set");
382         exit (1);
383     }
384     new->id = atoi (val);
385     sprintf(new->path, "%s/%s/clt%d", FIFOROOT, FIFODIR, new->id);
386     if (mkfifo(new->path, 0666 | S_IFIFO) < 0)
387         gw_log (GW_LOG_WARN|GW_LOG_ERRNO, mod, "mkfifo(%s)", new->path);
388     gw_log (GW_LOG_DEBUG, mod, "Synchronizing with server.");
389     sprintf(path2, "%s/%s/srv%d", FIFOROOT, FIFODIR, getppid());
390     if ((new->lineout = open(path2, O_WRONLY)) < 0)
391     {
392         gw_log (GW_LOG_FATAL|GW_LOG_ERRNO, mod, "open out %s", path2);
393         exit(1);
394     }
395     if (write(new->lineout, "OK", 2) < 2)
396     {
397         gw_log (GW_LOG_FATAL|GW_LOG_ERRNO, mod, "write");
398         exit(1);
399     }
400     gw_log (GW_LOG_DEBUG, mod, "Synchronized.");
401     if ((new->linein = open(new->path, O_RDONLY)) < 0)
402     {
403         gw_log (GW_LOG_FATAL|GW_LOG_ERRNO, mod, "open input %s", new->path);
404         exit(1);
405     }
406     gw_log (GW_LOG_DEBUG, mod, "init. linein=%d lineout=%d",
407             new->linein, new->lineout);
408     /* we put a handle on this so we get a blocking read when no peer */
409     if (open(new->path, O_WRONLY | O_NDELAY) < 0)
410     {
411         gw_log (GW_LOG_FATAL|GW_LOG_ERRNO, mod, "open dummy %s", new->path);
412         exit(1);
413     }
414     new->outbuffer = 0;
415     new->cache_level = -1;
416     new->cache_fd = -1;
417     return new;
418 }
419
420 static void wproto_uncache(WCLIENT wc, int level)
421 {
422     for (;wc->cache_level >= level; wc->cache_level--)
423         unlink(wc->cache[wc->cache_level].path);
424 }
425
426 void wproto_terminate(WCLIENT wc)
427 {
428     close(wc->linein);
429     unlink(wc->path);
430     wproto_uncache(wc, 0);
431     free(wc);
432 }
433
434 int wproto_cache(WCLIENT wc, int level)
435 {
436     cache_data *p;
437
438     if (level > wc->cache_level + 1)
439     {
440         gw_log (GW_LOG_FATAL, mod, "Illegal cache level increment.");
441         exit(1);
442     }
443     wproto_uncache(wc, level);
444     p = &wc->cache[++wc->cache_level];
445     sprintf(p->path, "%s/%s/csh%d.%d", FIFOROOT, FIFODIR, wc->id, level);
446     if ((wc->cache_fd = open(p->path, O_WRONLY|O_CREAT|O_TRUNC, 0600)) < 0)
447     {
448         gw_log (GW_LOG_FATAL|GW_LOG_ERRNO, mod, "open %s", p->path);
449         return -1;
450     }
451     strcpy(p->name, wc->wf_parms);
452     return 0;
453 }
454
455 static int wproto_findcache(WCLIENT wc, char *name)
456 {
457     int i;
458
459     for (i = 0; i <= wc->cache_level; i++)
460         if (!strcmp(wc->cache[i].name, name))
461             return i;
462     return -1;
463 }
464
465 static int wproto_dumpcache(WCLIENT wc, int level)
466 {
467     int fd, rd;
468
469     gw_log (GW_LOG_STAT, mod, "Using Cache: %s", wc->cache[level].name);
470     if ((fd = open(wc->cache[level].path, O_RDONLY)) < 0)
471     {
472         gw_log (GW_LOG_FATAL, mod, "open (R) %s", wc->cache[level].path);
473         return -1;
474     }
475     while ((rd = read(fd, wc->outbuffer, OUTBUFFER_CHUNK)) > 0)
476         if (write(wc->lineout, wc->outbuffer, rd) < rd)
477         {
478             gw_log (GW_LOG_FATAL|GW_LOG_ERRNO, mod, "write toline");
479             return -1;
480         }
481     if (rd < 0)
482     {
483         gw_log (GW_LOG_FATAL|GW_LOG_ERRNO, mod, "read");
484         return -1;
485     }
486     wproto_uncache(wc, level + 1);
487     return 0;
488 }