Big update. Introduced session-specific settings. These can be supplied
[pazpar2-moved-to-github.git] / src / http_command.c
1 /* $Id: http_command.c,v 1.33 2007-04-11 02:14:15 quinn Exp $
2    Copyright (c) 2006-2007, Index Data.
3
4 This file is part of Pazpar2.
5
6 Pazpar2 is free software; you can redistribute it and/or modify it under
7 the terms of the GNU General Public License as published by the Free
8 Software Foundation; either version 2, or (at your option) any later
9 version.
10
11 Pazpar2 is distributed in the hope that it will be useful, but WITHOUT ANY
12 WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
14 for more details.
15
16 You should have received a copy of the GNU General Public License
17 along with Pazpar2; see the file LICENSE.  If not, write to the
18 Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA
19 02111-1307, USA.
20  */
21
22 /*
23  * $Id: http_command.c,v 1.33 2007-04-11 02:14:15 quinn Exp $
24  */
25
26 #include <stdio.h>
27 #include <sys/types.h>
28 #include <sys/uio.h>
29 #include <unistd.h>
30 #include <stdlib.h>
31 #include <strings.h>
32 #include <ctype.h>
33 #include <sys/time.h>
34
35 #if HAVE_CONFIG_H
36 #include <cconfig.h>
37 #endif
38
39 #include <yaz/yaz-util.h>
40
41 #include "config.h"
42 #include "util.h"
43 #include "eventl.h"
44 #include "pazpar2.h"
45 #include "http.h"
46 #include "http_command.h"
47
48 extern struct parameters global_parameters;
49 extern IOCHAN channel_list;
50
51 struct http_session {
52     IOCHAN timeout_iochan;     // NOTE: This is NOT associated with a socket
53     struct session *psession;
54     unsigned int session_id;
55     int timestamp;
56     NMEM nmem;
57     struct http_session *next;
58 };
59
60 static struct http_session *session_list = 0;
61
62 void http_session_destroy(struct http_session *s);
63
64 static void session_timeout(IOCHAN i, int event)
65 {
66     struct http_session *s = iochan_getdata(i);
67     http_session_destroy(s);
68 }
69
70 struct http_session *http_session_create()
71 {
72     NMEM nmem = nmem_create();
73     struct http_session *r = nmem_malloc(nmem, sizeof(*r));
74
75     r->psession = new_session(nmem);
76     r->session_id = 0;
77     r->timestamp = 0;
78     r->nmem = nmem;
79     r->next = session_list;
80     session_list = r;
81     r->timeout_iochan = iochan_create(-1, session_timeout, 0);
82     iochan_setdata(r->timeout_iochan, r);
83     iochan_settimeout(r->timeout_iochan, global_parameters.session_timeout);
84     r->timeout_iochan->next = channel_list;
85     channel_list = r->timeout_iochan;
86     return r;
87 }
88
89 void http_session_destroy(struct http_session *s)
90 {
91     struct http_session **p;
92
93     for (p = &session_list; *p; p = &(*p)->next)
94         if (*p == s)
95         {
96             *p = (*p)->next;
97             break;
98         }
99     iochan_destroy(s->timeout_iochan);
100     destroy_session(s->psession);
101     nmem_destroy(s->nmem);
102 }
103
104 static void error(struct http_response *rs, char *code, char *msg, char *txt)
105 {
106     struct http_channel *c = rs->channel;
107     char tmp[1024];
108
109     if (!txt)
110         txt = msg;
111     rs->msg = nmem_strdup(c->nmem, msg);
112     strcpy(rs->code, code);
113     sprintf(tmp, "<error code=\"general\">%s</error>", txt);
114     rs->payload = nmem_strdup(c->nmem, tmp);
115     http_send_response(c);
116 }
117
118 unsigned int make_sessionid()
119 {
120     struct timeval t;
121     unsigned int res;
122     static int seq = 0;
123
124     seq++;
125     if (gettimeofday(&t, 0) < 0)
126         abort();
127     res = t.tv_sec;
128     res = ((res << 8) | (seq & 0xff)) & ((1U << 31) - 1);
129     return res;
130 }
131
132 static struct http_session *locate_session(struct http_request *rq, struct http_response *rs)
133 {
134     struct http_session *p;
135     char *session = http_argbyname(rq, "session");
136     unsigned int id;
137
138     if (!session)
139     {
140         error(rs, "417", "Must supply session", 0);
141         return 0;
142     }
143     id = atoi(session);
144     for (p = session_list; p; p = p->next)
145         if (id == p->session_id)
146         {
147             iochan_activity(p->timeout_iochan);
148             return p;
149         }
150     error(rs, "417", "Session does not exist, or it has expired", 0);
151     return 0;
152 }
153
154 // Decode settings parameters and apply to session
155 // Syntax: setting[target]=value
156 static int process_settings(struct session *se, struct http_request *rq,
157         struct http_response *rs)
158 {
159     struct http_argument *a;
160
161     for (a = rq->arguments; a; a = a->next)
162         if (strchr(a->name, '['))
163         {
164             char **res;
165             int num;
166             char *dbname;
167             char *setting;
168
169             // Nmem_strsplit *rules*!!!
170             nmem_strsplit(se->session_nmem, "[]", a->name, &res, &num);
171             if (num != 2)
172             {
173                 error(rs, "417", "Malformed setting argument", 0);
174                 yaz_log(YLOG_WARN, "Malformed setting: %s", a->name);
175                 return -1;
176             }
177             setting = res[0];
178             dbname = res[1];
179             session_apply_setting(se, dbname, setting,
180                     nmem_strdup(se->session_nmem, a->value));
181         }
182     return 0;
183 }
184
185 static void cmd_exit(struct http_channel *c)
186 {
187     yaz_log(YLOG_WARN, "exit");
188     exit(0);
189 }
190
191 static void cmd_init(struct http_channel *c)
192 {
193     unsigned int sesid;
194     char buf[1024];
195     struct http_session *s = http_session_create();
196     struct http_response *rs = c->response;
197
198     yaz_log(YLOG_DEBUG, "HTTP Session init");
199     sesid = make_sessionid();
200     s->session_id = sesid;
201     if (process_settings(s->psession, c->request, c->response) < 0)
202         return;
203     sprintf(buf, "<init><status>OK</status><session>%u</session></init>", sesid);
204     rs->payload = nmem_strdup(c->nmem, buf);
205     http_send_response(c);
206 }
207
208 static void cmd_settings(struct http_channel *c)
209 {
210     struct http_response *rs = c->response;
211     struct http_request *rq = c->request;
212     struct http_session *s = locate_session(rq, rs);
213
214     if (!s)
215         return;
216
217     if (process_settings(s->psession, rq, rs) < 0)
218         return;
219     rs->payload = "<settings><status>OK</status></settings>";
220     http_send_response(c);
221 }
222
223 // Compares two hitsbytarget nodes by hitcount
224 static int cmp_ht(const void *p1, const void *p2)
225 {
226     const struct hitsbytarget *h1 = p1;
227     const struct hitsbytarget *h2 = p2;
228     return h2->hits - h1->hits;
229 }
230
231 // This implements functionality somewhat similar to 'bytarget', but in a termlist form
232 static void targets_termlist(WRBUF wrbuf, struct session *se, int num)
233 {
234     struct hitsbytarget *ht;
235     int count, i;
236
237     if (!(ht = hitsbytarget(se, &count)))
238         return;
239     qsort(ht, count, sizeof(struct hitsbytarget), cmp_ht);
240     for (i = 0; i < count && i < num && ht[i].hits > 0; i++)
241     {
242         wrbuf_puts(wrbuf, "\n<term>\n");
243         wrbuf_printf(wrbuf, "<id>%s</id>\n", ht[i].id);
244         wrbuf_printf(wrbuf, "<name>%s</name>\n", ht[i].name);
245         wrbuf_printf(wrbuf, "<frequency>%d</frequency>\n", ht[i].hits);
246         wrbuf_printf(wrbuf, "<state>%s</state>\n", ht[i].state);
247         wrbuf_printf(wrbuf, "<diagnostic>%d</diagnostic>\n", ht[i].diagnostic);
248         wrbuf_puts(wrbuf, "\n</term>\n");
249     }
250 }
251
252 static void cmd_termlist(struct http_channel *c)
253 {
254     struct http_response *rs = c->response;
255     struct http_request *rq = c->request;
256     struct http_session *s = locate_session(rq, rs);
257     struct termlist_score **p;
258     int len;
259     int i;
260     char *name = http_argbyname(rq, "name");
261     char *nums = http_argbyname(rq, "num");
262     int num = 15;
263     int status;
264
265     if (!s)
266         return;
267
268     status = session_active_clients(s->psession);
269
270     if (!name)
271         name = "subject";
272     if (strlen(name) > 255)
273         return;
274     if (nums)
275         num = atoi(nums);
276
277     wrbuf_rewind(c->wrbuf);
278
279     wrbuf_puts(c->wrbuf, "<termlist>");
280     wrbuf_printf(c->wrbuf, "\n<activeclients>%d</activeclients>", status);
281     while (*name)
282     {
283         char tname[256];
284         char *tp;
285
286         if (!(tp = strchr(name, ',')))
287             tp = name + strlen(name);
288         strncpy(tname, name, tp - name);
289         tname[tp - name] = '\0';
290
291         wrbuf_printf(c->wrbuf, "\n<list name=\"%s\">\n", tname);
292         if (!strcmp(tname, "xtargets"))
293             targets_termlist(c->wrbuf, s->psession, num);
294         else
295         {
296             p = termlist(s->psession, tname, &len);
297             if (p)
298                 for (i = 0; i < len && i < num; i++)
299                 {
300                     wrbuf_puts(c->wrbuf, "\n<term>");
301                     wrbuf_printf(c->wrbuf, "<name>%s</name>", p[i]->term);
302                     wrbuf_printf(c->wrbuf, "<frequency>%d</frequency>", p[i]->frequency);
303                     wrbuf_puts(c->wrbuf, "</term>");
304                 }
305         }
306         wrbuf_puts(c->wrbuf, "\n</list>");
307         name = tp;
308         if (*name == ',')
309             name++;
310     }
311     wrbuf_puts(c->wrbuf, "</termlist>");
312     rs->payload = nmem_strdup(rq->channel->nmem, wrbuf_cstr(c->wrbuf));
313     http_send_response(c);
314 }
315
316
317 static void cmd_bytarget(struct http_channel *c)
318 {
319     struct http_response *rs = c->response;
320     struct http_request *rq = c->request;
321     struct http_session *s = locate_session(rq, rs);
322     struct hitsbytarget *ht;
323     int count, i;
324
325     if (!s)
326         return;
327     if (!(ht = hitsbytarget(s->psession, &count)))
328     {
329         error(rs, "500", "Failed to retrieve hitcounts", 0);
330         return;
331     }
332     wrbuf_rewind(c->wrbuf);
333     wrbuf_puts(c->wrbuf, "<bytarget><status>OK</status>");
334
335     for (i = 0; i < count; i++)
336     {
337         wrbuf_puts(c->wrbuf, "\n<target>");
338         wrbuf_printf(c->wrbuf, "<id>%s</id>\n", ht[i].id);
339         wrbuf_printf(c->wrbuf, "<hits>%d</hits>\n", ht[i].hits);
340         wrbuf_printf(c->wrbuf, "<diagnostic>%d</diagnostic>\n", ht[i].diagnostic);
341         wrbuf_printf(c->wrbuf, "<records>%d</records>\n", ht[i].records);
342         wrbuf_printf(c->wrbuf, "<state>%s</state>\n", ht[i].state);
343         wrbuf_puts(c->wrbuf, "</target>");
344     }
345
346     wrbuf_puts(c->wrbuf, "</bytarget>");
347     rs->payload = nmem_strdup(c->nmem, wrbuf_cstr(c->wrbuf));
348     http_send_response(c);
349 }
350
351 static void write_metadata(WRBUF w, struct conf_service *service,
352         struct record_metadata **ml, int full)
353 {
354     int imeta;
355
356     for (imeta = 0; imeta < service->num_metadata; imeta++)
357     {
358         struct conf_metadata *cmd = &service->metadata[imeta];
359         struct record_metadata *md;
360         if (!cmd->brief && !full)
361             continue;
362         for (md = ml[imeta]; md; md = md->next)
363         {
364             wrbuf_printf(w, "<md-%s>", cmd->name);
365             switch (cmd->type)
366             {
367                 case Metadata_type_generic:
368                     wrbuf_puts(w, md->data.text);
369                     break;
370                 case Metadata_type_year:
371                     wrbuf_printf(w, "%d", md->data.number.min);
372                     if (md->data.number.min != md->data.number.max)
373                         wrbuf_printf(w, "-%d", md->data.number.max);
374                     break;
375                 default:
376                     wrbuf_puts(w, "[can't represent]");
377             }
378             wrbuf_printf(w, "</md-%s>", cmd->name);
379         }
380     }
381 }
382
383 static void write_subrecord(struct record *r, WRBUF w, struct conf_service *service)
384 {
385     wrbuf_printf(w, "<location id=\"%s\" name=\"%s\">\n",
386             r->client->database->database->url,
387             r->client->database->database->name ? r->client->database->database->name : "");
388     write_metadata(w, service, r->metadata, 1);
389     wrbuf_puts(w, "</location>\n");
390 }
391
392 static void cmd_record(struct http_channel *c)
393 {
394     struct http_response *rs = c->response;
395     struct http_request *rq = c->request;
396     struct http_session *s = locate_session(rq, rs);
397     struct record_cluster *rec;
398     struct record *r;
399     struct conf_service *service = global_parameters.server->service;
400     char *idstr = http_argbyname(rq, "id");
401     int id;
402
403     if (!s)
404         return;
405     if (!idstr)
406     {
407         error(rs, "417", "Must supply id", 0);
408         return;
409     }
410     wrbuf_rewind(c->wrbuf);
411     id = atoi(idstr);
412     if (!(rec = show_single(s->psession, id)))
413     {
414         error(rs, "500", "Record missing", 0);
415         return;
416     }
417     wrbuf_puts(c->wrbuf, "<record>\n");
418     wrbuf_printf(c->wrbuf, "<recid>%d</recid>", rec->recid);
419     write_metadata(c->wrbuf, service, rec->metadata, 1);
420     for (r = rec->records; r; r = r->next)
421         write_subrecord(r, c->wrbuf, service);
422     wrbuf_puts(c->wrbuf, "</record>\n");
423     rs->payload = nmem_strdup(c->nmem, wrbuf_cstr(c->wrbuf));
424     http_send_response(c);
425 }
426
427 static void show_records(struct http_channel *c, int active)
428 {
429     struct http_request *rq = c->request;
430     struct http_response *rs = c->response;
431     struct http_session *s = locate_session(rq, rs);
432     struct record_cluster **rl;
433     struct reclist_sortparms *sp;
434     char *start = http_argbyname(rq, "start");
435     char *num = http_argbyname(rq, "num");
436     char *sort = http_argbyname(rq, "sort");
437     int startn = 0;
438     int numn = 20;
439     int total;
440     int total_hits;
441     int i;
442
443     if (!s)
444         return;
445
446     // We haven't counted clients yet if we're called on a block release
447     if (active < 0)
448         active = session_active_clients(s->psession);
449
450     if (start)
451         startn = atoi(start);
452     if (num)
453         numn = atoi(num);
454     if (!sort)
455         sort = "relevance";
456     if (!(sp = reclist_parse_sortparms(c->nmem, sort)))
457     {
458         error(rs, "500", "Bad sort parameters", 0);
459         return;
460     }
461
462     rl = show(s->psession, sp, startn, &numn, &total, &total_hits, c->nmem);
463
464     wrbuf_rewind(c->wrbuf);
465     wrbuf_puts(c->wrbuf, "<show>\n<status>OK</status>\n");
466     wrbuf_printf(c->wrbuf, "<activeclients>%d</activeclients>\n", active);
467     wrbuf_printf(c->wrbuf, "<merged>%d</merged>\n", total);
468     wrbuf_printf(c->wrbuf, "<total>%d</total>\n", total_hits);
469     wrbuf_printf(c->wrbuf, "<start>%d</start>\n", startn);
470     wrbuf_printf(c->wrbuf, "<num>%d</num>\n", numn);
471
472     for (i = 0; i < numn; i++)
473     {
474         int ccount;
475         struct record *p;
476         struct record_cluster *rec = rl[i];
477         struct conf_service *service = global_parameters.server->service;
478
479         wrbuf_puts(c->wrbuf, "<hit>\n");
480         write_metadata(c->wrbuf, service, rec->metadata, 0);
481         for (ccount = 0, p = rl[i]->records; p;  p = p->next, ccount++)
482             ;
483         if (ccount > 1)
484             wrbuf_printf(c->wrbuf, "<count>%d</count>\n", ccount);
485         wrbuf_printf(c->wrbuf, "<recid>%d</recid>\n", rec->recid);
486         wrbuf_puts(c->wrbuf, "</hit>\n");
487     }
488
489     wrbuf_puts(c->wrbuf, "</show>\n");
490     rs->payload = nmem_strdup(c->nmem, wrbuf_cstr(c->wrbuf));
491     http_send_response(c);
492 }
493
494 static void show_records_ready(void *data)
495 {
496     struct http_channel *c = (struct http_channel *) data;
497
498     show_records(c, -1);
499 }
500
501 static void cmd_show(struct http_channel *c)
502 {
503     struct http_request *rq = c->request;
504     struct http_response *rs = c->response;
505     struct http_session *s = locate_session(rq, rs);
506     char *block = http_argbyname(rq, "block");
507     int status;
508
509     if (!s)
510         return;
511
512     status = session_active_clients(s->psession);
513
514     if (block)
515     {
516         if (status && (!s->psession->reclist || !s->psession->reclist->num_records))
517         {
518             session_set_watch(s->psession, SESSION_WATCH_RECORDS, show_records_ready, c);
519             yaz_log(YLOG_DEBUG, "Blocking on cmd_show");
520             return;
521         }
522     }
523
524     show_records(c, status);
525 }
526
527 static void cmd_ping(struct http_channel *c)
528 {
529     struct http_request *rq = c->request;
530     struct http_response *rs = c->response;
531     struct http_session *s = locate_session(rq, rs);
532     if (!s)
533         return;
534     rs->payload = "<ping><status>OK</status></ping>";
535     http_send_response(c);
536 }
537
538 static void cmd_search(struct http_channel *c)
539 {
540     struct http_request *rq = c->request;
541     struct http_response *rs = c->response;
542     struct http_session *s = locate_session(rq, rs);
543     char *query = http_argbyname(rq, "query");
544     char *filter = http_argbyname(rq, "filter");
545     char *res;
546
547     if (!s)
548         return;
549     if (!query)
550     {
551         error(rs, "417", "Must supply query", 0);
552         return;
553     }
554     res = search(s->psession, query, filter);
555     if (res)
556     {
557         error(rs, "417", res, res);
558         return;
559     }
560     rs->payload = "<search><status>OK</status></search>";
561     http_send_response(c);
562 }
563
564
565 static void cmd_stat(struct http_channel *c)
566 {
567     struct http_request *rq = c->request;
568     struct http_response *rs = c->response;
569     struct http_session *s = locate_session(rq, rs);
570     struct statistics stat;
571     int clients;
572
573     if (!s)
574         return;
575
576     clients = session_active_clients(s->psession);
577     statistics(s->psession, &stat);
578
579     wrbuf_rewind(c->wrbuf);
580     wrbuf_puts(c->wrbuf, "<stat>");
581     wrbuf_printf(c->wrbuf, "<activeclients>%d</activeclients>\n", clients);
582     wrbuf_printf(c->wrbuf, "<hits>%d</hits>\n", stat.num_hits);
583     wrbuf_printf(c->wrbuf, "<records>%d</records>\n", stat.num_records);
584     wrbuf_printf(c->wrbuf, "<clients>%d</clients>\n", stat.num_clients);
585     wrbuf_printf(c->wrbuf, "<unconnected>%d</unconnected>\n", stat.num_no_connection);
586     wrbuf_printf(c->wrbuf, "<connecting>%d</connecting>\n", stat.num_connecting);
587     wrbuf_printf(c->wrbuf, "<initializing>%d</initializing>\n", stat.num_initializing);
588     wrbuf_printf(c->wrbuf, "<searching>%d</searching>\n", stat.num_searching);
589     wrbuf_printf(c->wrbuf, "<presenting>%d</presenting>\n", stat.num_presenting);
590     wrbuf_printf(c->wrbuf, "<idle>%d</idle>\n", stat.num_idle);
591     wrbuf_printf(c->wrbuf, "<failed>%d</failed>\n", stat.num_failed);
592     wrbuf_printf(c->wrbuf, "<error>%d</error>\n", stat.num_error);
593     wrbuf_puts(c->wrbuf, "</stat>");
594     rs->payload = nmem_strdup(c->nmem, wrbuf_cstr(c->wrbuf));
595     http_send_response(c);
596 }
597
598 static void cmd_info(struct http_channel *c)
599 {
600     char yaz_version_str[20];
601     struct http_response *rs = c->response;
602
603     wrbuf_rewind(c->wrbuf);
604     wrbuf_puts(c->wrbuf, "<info>\n");
605     wrbuf_printf(c->wrbuf, " <version>\n");
606     wrbuf_printf(c->wrbuf, "  <pazpar2>%s</pazpar2>\n", VERSION);
607
608     yaz_version(yaz_version_str, 0);
609     wrbuf_printf(c->wrbuf, "  <yaz compiled=\"%s\">%s</yaz>\n",
610                  YAZ_VERSION, yaz_version_str);
611     wrbuf_printf(c->wrbuf, " </version>\n");
612     
613     wrbuf_puts(c->wrbuf, "</info>");
614     rs->payload = nmem_strdup(c->nmem, wrbuf_cstr(c->wrbuf));
615     http_send_response(c);
616 }
617
618 struct {
619     char *name;
620     void (*fun)(struct http_channel *c);
621 } commands[] = {
622     { "init", cmd_init },
623     { "settings", cmd_settings },
624     { "stat", cmd_stat },
625     { "bytarget", cmd_bytarget },
626     { "show", cmd_show },
627     { "search", cmd_search },
628     { "termlist", cmd_termlist },
629     { "exit", cmd_exit },
630     { "ping", cmd_ping },
631     { "record", cmd_record },
632     { "info", cmd_info },
633     {0,0}
634 };
635
636 void http_command(struct http_channel *c)
637 {
638     char *command = http_argbyname(c->request, "command");
639     struct http_response *rs = http_create_response(c);
640     int i;
641
642     c->response = rs;
643
644     http_addheader(rs, "Expires", "Thu, 19 Nov 1981 08:52:00 GMT");
645     http_addheader(rs, "Cache-Control", "no-store, no-cache, must-revalidate, post-check=0, pre-check=0");
646
647     if (!command)
648     {
649         error(rs, "417", "Must supply command", 0);
650         return;
651     }
652     for (i = 0; commands[i].name; i++)
653         if (!strcmp(commands[i].name, command))
654         {
655             (*commands[i].fun)(c);
656             break;
657         }
658     if (!commands[i].name)
659         error(rs, "417", "Unknown command", 0);
660
661     return;
662 }
663
664 /*
665  * Local variables:
666  * c-basic-offset: 4
667  * indent-tabs-mode: nil
668  * End:
669  * vim: shiftwidth=4 tabstop=8 expandtab
670  */