Extend init resp to include server ID, bug #3231
[pazpar2-moved-to-github.git] / src / http_command.c
1 /* This file is part of Pazpar2.
2    Copyright (C) 2006-2010 Index Data
3
4 Pazpar2 is free software; you can redistribute it and/or modify it under
5 the terms of the GNU General Public License as published by the Free
6 Software Foundation; either version 2, or (at your option) any later
7 version.
8
9 Pazpar2 is distributed in the hope that it will be useful, but WITHOUT ANY
10 WARRANTY; without even the implied warranty of MERCHANTABILITY or
11 FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
12 for more details.
13
14 You should have received a copy of the GNU General Public License
15 along with this program; if not, write to the Free Software
16 Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
17
18 */
19
20 #if HAVE_CONFIG_H
21 #include <config.h>
22 #endif
23 #include <stdio.h>
24 #include <sys/types.h>
25 #if HAVE_UNISTD_H
26 #include <unistd.h>
27 #endif
28 #include <stdlib.h>
29 #include <string.h>
30 #if HAVE_SYS_TIME_H
31 #include <sys/time.h>
32 #endif
33 #include <yaz/snprintf.h>
34 #include <yaz/yaz-util.h>
35
36 #include "eventl.h"
37 #include "parameters.h"
38 #include "pazpar2.h"
39 #include "http.h"
40 #include "settings.h"
41 #include "client.h"
42
43 // Update this when the protocol changes
44 #define PAZPAR2_PROTOCOL_VERSION "1"
45
46 #define HTTP_COMMAND_RESPONSE_PREFIX "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
47
48 struct http_session {
49     IOCHAN timeout_iochan;     // NOTE: This is NOT associated with a socket
50     struct session *psession;
51     unsigned int session_id;
52     int timestamp;
53     NMEM nmem;
54     struct http_session *next;
55 };
56
57 static struct http_session *session_list = 0; /* thread pr */
58
59 void http_session_destroy(struct http_session *s);
60
61 static void session_timeout(IOCHAN i, int event)
62 {
63     struct http_session *s = iochan_getdata(i);
64     http_session_destroy(s);
65 }
66
67 struct http_session *http_session_create(struct conf_service *service)
68 {
69     NMEM nmem = nmem_create();
70     struct http_session *r = nmem_malloc(nmem, sizeof(*r));
71
72     r->psession = new_session(nmem, service);
73     r->session_id = 0;
74     r->timestamp = 0;
75     r->nmem = nmem;
76     r->next = session_list;
77     session_list = r;
78     r->timeout_iochan = iochan_create(-1, session_timeout, 0);
79     iochan_setdata(r->timeout_iochan, r);
80     iochan_settimeout(r->timeout_iochan, service->session_timeout);
81
82     pazpar2_add_channel(r->timeout_iochan);
83     return r;
84 }
85
86 void http_session_destroy(struct http_session *s)
87 {
88     struct http_session **p;
89
90     for (p = &session_list; *p; p = &(*p)->next)
91         if (*p == s)
92         {
93             *p = (*p)->next;
94             break;
95         }
96     yaz_log(YLOG_LOG, "Destroying session %u", s->session_id);
97     iochan_destroy(s->timeout_iochan);
98     destroy_session(s->psession);
99     nmem_destroy(s->nmem);
100 }
101
102 static const char *get_msg(enum pazpar2_error_code code)
103 {
104     struct pazpar2_error_msg {
105         enum pazpar2_error_code code;
106         const char *msg;
107     };
108     static const struct pazpar2_error_msg ar[] = {
109         { PAZPAR2_NO_SESSION, "Session does not exist or it has expired"},
110         { PAZPAR2_MISSING_PARAMETER, "Missing parameter"},
111         { PAZPAR2_MALFORMED_PARAMETER_VALUE, "Malformed parameter value"},
112         { PAZPAR2_MALFORMED_PARAMETER_ENCODING, "Malformed parameter encoding"},
113         { PAZPAR2_MALFORMED_SETTING, "Malformed setting argument"},
114         { PAZPAR2_HITCOUNTS_FAILED, "Failed to retrieve hitcounts"},
115         { PAZPAR2_RECORD_MISSING, "Record missing"},
116         { PAZPAR2_NO_TARGETS, "No targets"},
117         { PAZPAR2_CONFIG_TARGET, "Target cannot be configured"},
118         { PAZPAR2_RECORD_FAIL, "Record command failed"},
119         { PAZPAR2_NOT_IMPLEMENTED, "Not implemented"},
120         { PAZPAR2_NO_SERVICE, "No service"},
121         { PAZPAR2_LAST_ERROR, "Last error"},
122         { 0, 0 }
123     };
124     int i = 0;
125     while (ar[i].msg)
126     {
127         if (code == ar[i].code)
128             return ar[i].msg;
129         i++;
130     }
131     return "No error";
132 }
133
134 static void error(struct http_response *rs, 
135                   enum pazpar2_error_code code,
136                   const char *addinfo)
137 {
138     struct http_channel *c = rs->channel;
139     WRBUF text = wrbuf_alloc();
140     const char *http_status = "417";
141     const char *msg = get_msg(code);
142     
143     rs->msg = nmem_strdup(c->nmem, msg);
144     strcpy(rs->code, http_status);
145
146     wrbuf_printf(text, HTTP_COMMAND_RESPONSE_PREFIX "<error code=\"%d\" msg=\"%s\">", (int) code,
147                msg);
148     if (addinfo)
149         wrbuf_xmlputs(text, addinfo);
150     wrbuf_puts(text, "</error>");
151
152     yaz_log(YLOG_WARN, "HTTP %s %s%s%s", http_status,
153             msg, addinfo ? ": " : "" , addinfo ? addinfo : "");
154     rs->payload = nmem_strdup(c->nmem, wrbuf_cstr(text));
155     wrbuf_destroy(text);
156     http_send_response(c);
157 }
158
159 unsigned int make_sessionid(void)
160 {
161     static int seq = 0; /* thread pr */
162     unsigned int res;
163
164     seq++;
165     if (global_parameters.debug_mode)
166         res = seq;
167     else
168     {
169 #ifdef WIN32
170         res = seq;
171 #else
172         struct timeval t;
173
174         if (gettimeofday(&t, 0) < 0)
175         {
176             yaz_log(YLOG_WARN|YLOG_ERRNO, "gettimeofday");
177             exit(1);
178         }
179         /* at most 256 sessions per second .. 
180            (long long would be more appropriate)*/
181         res = t.tv_sec;
182         res = ((res << 8) | (seq & 0xff)) & ((1U << 31) - 1);
183 #endif
184     }
185     return res;
186 }
187
188 static struct http_session *locate_session(struct http_request *rq, struct http_response *rs)
189 {
190     struct http_session *p;
191     const char *session = http_argbyname(rq, "session");
192     unsigned int id;
193
194     if (!session)
195     {
196         error(rs, PAZPAR2_MISSING_PARAMETER, "session");
197         return 0;
198     }
199     id = atoi(session);
200     for (p = session_list; p; p = p->next)
201         if (id == p->session_id)
202         {
203             iochan_activity(p->timeout_iochan);
204             return p;
205         }
206     error(rs, PAZPAR2_NO_SESSION, session);
207     return 0;
208 }
209
210 // Decode settings parameters and apply to session
211 // Syntax: setting[target]=value
212 static int process_settings(struct session *se, struct http_request *rq,
213                             struct http_response *rs)
214 {
215     struct http_argument *a;
216
217     for (a = rq->arguments; a; a = a->next)
218         if (strchr(a->name, '['))
219         {
220             char **res;
221             int num;
222             char *dbname;
223             char *setting;
224
225             // Nmem_strsplit *rules*!!!
226             nmem_strsplit(se->session_nmem, "[]", a->name, &res, &num);
227             if (num != 2)
228             {
229                 error(rs, PAZPAR2_MALFORMED_SETTING, a->name);
230                 return -1;
231             }
232             setting = res[0];
233             dbname = res[1];
234             session_apply_setting(se, dbname, setting,
235                     nmem_strdup(se->session_nmem, a->value));
236         }
237     return 0;
238 }
239
240 static void cmd_exit(struct http_channel *c)
241 {
242     yaz_log(YLOG_WARN, "exit");
243     http_close_server(c->server);
244 }
245
246 static void cmd_init(struct http_channel *c)
247 {
248     char buf[1024];
249     struct http_request *r = c->request;
250     const char *clear = http_argbyname(r, "clear");
251     const char *content_type = http_lookup_header(r->headers, "Content-Type");
252     unsigned int sesid;
253     struct http_session *s;
254     struct http_response *rs = c->response;
255     struct conf_service *service = 0; /* no service (yet) */
256     
257     if (r->content_len && content_type && 
258         !yaz_strcmp_del("text/xml", content_type, "; "))
259     {
260         xmlDoc *doc = xmlParseMemory(r->content_buf, r->content_len);
261         xmlNode *root_n;
262         if (!doc)
263         {
264             error(rs, PAZPAR2_MALFORMED_SETTING, 0);
265             return;
266         }
267         root_n = xmlDocGetRootElement(doc);
268         service = service_create(c->server, root_n);
269         xmlFreeDoc(doc);
270         if (!service)
271         {
272             error(rs, PAZPAR2_MALFORMED_SETTING, 0);
273             return;
274         }
275     }
276     
277     if (!service)
278     {
279         const char *service_name = http_argbyname(c->request, "service");
280         service = locate_service(c->server, service_name);
281         if (!service)
282         {
283             error(rs, PAZPAR2_NO_SERVICE, service_name ? service_name : "unnamed");
284             return;
285         }
286         service_incref(service);
287     }
288     s = http_session_create(service);
289     
290     yaz_log(YLOG_DEBUG, "HTTP Session init");
291     if (!clear || *clear == '0')
292         session_init_databases(s->psession);
293     else
294         yaz_log(YLOG_LOG, "No databases preloaded");
295     
296     sesid = make_sessionid();
297     s->session_id = sesid;
298     if (process_settings(s->psession, c->request, c->response) < 0)
299         return;
300     
301     sprintf(buf, HTTP_COMMAND_RESPONSE_PREFIX 
302             "<init><status>OK</status><session>%u", sesid);
303     if (c->server->server_id)
304     {
305         strcat(buf, ".");
306         strcat(buf, c->server->server_id);
307     }
308     strcat(buf, "</session>"
309            "<protocol>" PAZPAR2_PROTOCOL_VERSION "</protocol></init>");
310     rs->payload = nmem_strdup(c->nmem, buf);
311     http_send_response(c);
312 }
313
314 static void apply_local_setting(void *client_data,
315                                 struct setting *set)
316 {
317     struct session *se =  (struct session *) client_data;
318
319     session_apply_setting(se, nmem_strdup(se->session_nmem, set->target),
320                           nmem_strdup(se->session_nmem, set->name),
321                           nmem_strdup(se->session_nmem, set->value));
322 }
323
324 static void cmd_settings(struct http_channel *c)
325 {
326     struct http_response *rs = c->response;
327     struct http_request *rq = c->request;
328     struct http_session *s = locate_session(rq, rs);
329     const char *content_type = http_lookup_header(rq->headers, "Content-Type");
330
331     if (!s)
332         return;
333
334     if (rq->content_len && content_type && 
335         !yaz_strcmp_del("text/xml", content_type, "; "))
336     {
337         xmlDoc *doc = xmlParseMemory(rq->content_buf, rq->content_len);
338         xmlNode *root_n;
339         if (!doc)
340         {
341             error(rs, PAZPAR2_MALFORMED_SETTING, 0);
342             return;
343         }
344         root_n = xmlDocGetRootElement(doc);
345
346         settings_read_node_x(root_n, s->psession, apply_local_setting);
347
348         xmlFreeDoc(doc);
349     }
350     if (process_settings(s->psession, rq, rs) < 0)
351         return;
352     rs->payload = HTTP_COMMAND_RESPONSE_PREFIX "<settings><status>OK</status></settings>";
353     http_send_response(c);
354 }
355
356 // Compares two hitsbytarget nodes by hitcount
357 static int cmp_ht(const void *p1, const void *p2)
358 {
359     const struct hitsbytarget *h1 = p1;
360     const struct hitsbytarget *h2 = p2;
361     return h2->hits - h1->hits;
362 }
363
364 // This implements functionality somewhat similar to 'bytarget', but in a termlist form
365 static void targets_termlist(WRBUF wrbuf, struct session *se, int num,
366                              NMEM nmem)
367 {
368     struct hitsbytarget *ht;
369     int count, i;
370
371     ht = hitsbytarget(se, &count, nmem);
372     qsort(ht, count, sizeof(struct hitsbytarget), cmp_ht);
373     for (i = 0; i < count && i < num && ht[i].hits > 0; i++)
374     {
375
376         // do only print terms which have display names
377     
378         wrbuf_puts(wrbuf, "<term>\n");
379
380         wrbuf_puts(wrbuf, "<id>");
381         wrbuf_xmlputs(wrbuf, ht[i].id);
382         wrbuf_puts(wrbuf, "</id>\n");
383         
384         wrbuf_puts(wrbuf, "<name>");
385         if (!ht[i].name || !ht[i].name[0])
386             wrbuf_xmlputs(wrbuf, "NO TARGET NAME");
387         else
388             wrbuf_xmlputs(wrbuf, ht[i].name);
389         wrbuf_puts(wrbuf, "</name>\n");
390         
391         wrbuf_printf(wrbuf, "<frequency>" ODR_INT_PRINTF "</frequency>\n",
392                      ht[i].hits);
393         
394         wrbuf_puts(wrbuf, "<state>");
395         wrbuf_xmlputs(wrbuf, ht[i].state);
396         wrbuf_puts(wrbuf, "</state>\n");
397         
398         wrbuf_printf(wrbuf, "<diagnostic>%d</diagnostic>\n", 
399                      ht[i].diagnostic);
400         wrbuf_puts(wrbuf, "</term>\n");
401     }
402 }
403
404 static void cmd_termlist(struct http_channel *c)
405 {
406     struct http_response *rs = c->response;
407     struct http_request *rq = c->request;
408     struct http_session *s = locate_session(rq, rs);
409     struct termlist_score **p;
410     int len;
411     int i;
412     const char *name = http_argbyname(rq, "name");
413     const char *nums = http_argbyname(rq, "num");
414     int num = 15;
415     int status;
416
417     if (!s)
418         return;
419
420     status = session_active_clients(s->psession);
421
422     if (!name)
423         name = "subject";
424     if (strlen(name) > 255)
425         return;
426     if (nums)
427         num = atoi(nums);
428
429     wrbuf_rewind(c->wrbuf);
430
431     wrbuf_puts(c->wrbuf, "<termlist>\n");
432     wrbuf_printf(c->wrbuf, "<activeclients>%d</activeclients>\n", status);
433     while (*name)
434     {
435         char tname[256];
436         const char *tp;
437
438         if (!(tp = strchr(name, ',')))
439             tp = name + strlen(name);
440         strncpy(tname, name, tp - name);
441         tname[tp - name] = '\0';
442
443         wrbuf_puts(c->wrbuf, "<list name=\"");
444         wrbuf_xmlputs(c->wrbuf, tname);
445         wrbuf_puts(c->wrbuf, "\">\n");
446         if (!strcmp(tname, "xtargets"))
447             targets_termlist(c->wrbuf, s->psession, num, c->nmem);
448         else
449         {
450             p = termlist(s->psession, tname, &len);
451             if (p)
452                 for (i = 0; i < len && i < num; i++){
453                     // prevnt sending empty term elements
454                     if (!p[i]->term || !p[i]->term[0])
455                         continue;
456
457                     wrbuf_puts(c->wrbuf, "<term>");
458                     wrbuf_puts(c->wrbuf, "<name>");
459                     wrbuf_xmlputs(c->wrbuf, p[i]->term);
460                     wrbuf_puts(c->wrbuf, "</name>");
461                         
462                     wrbuf_printf(c->wrbuf, 
463                                  "<frequency>%d</frequency>", 
464                                  p[i]->frequency);
465                     wrbuf_puts(c->wrbuf, "</term>\n");
466                }
467         }
468         wrbuf_puts(c->wrbuf, "</list>\n");
469         name = tp;
470         if (*name == ',')
471             name++;
472     }
473     wrbuf_puts(c->wrbuf, "</termlist>\n");
474     rs->payload = nmem_strdup(rq->channel->nmem, wrbuf_cstr(c->wrbuf));
475     http_send_response(c);
476 }
477
478
479 static void cmd_bytarget(struct http_channel *c)
480 {
481     struct http_response *rs = c->response;
482     struct http_request *rq = c->request;
483     struct http_session *s = locate_session(rq, rs);
484     struct hitsbytarget *ht;
485     const char *settings = http_argbyname(rq, "settings");
486     int count, i;
487
488     if (!s)
489         return;
490     ht = hitsbytarget(s->psession, &count, c->nmem);
491     wrbuf_rewind(c->wrbuf);
492     wrbuf_puts(c->wrbuf, HTTP_COMMAND_RESPONSE_PREFIX "<bytarget><status>OK</status>");
493
494     for (i = 0; i < count; i++)
495     {
496         wrbuf_puts(c->wrbuf, "\n<target>");
497
498         wrbuf_puts(c->wrbuf, "<id>");
499         wrbuf_xmlputs(c->wrbuf, ht[i].id);
500         wrbuf_puts(c->wrbuf, "</id>\n");
501
502         if (ht[i].name && ht[i].name[0]) 
503         {
504             wrbuf_puts(c->wrbuf, "<name>");
505             wrbuf_xmlputs(c->wrbuf, ht[i].name);
506             wrbuf_puts(c->wrbuf, "</name>\n");
507         }
508
509         wrbuf_printf(c->wrbuf, "<hits>" ODR_INT_PRINTF "</hits>\n", ht[i].hits);
510         wrbuf_printf(c->wrbuf, "<diagnostic>%d</diagnostic>\n", ht[i].diagnostic);
511         wrbuf_printf(c->wrbuf, "<records>%d</records>\n", ht[i].records);
512
513         wrbuf_puts(c->wrbuf, "<state>");
514         wrbuf_xmlputs(c->wrbuf, ht[i].state);
515         wrbuf_puts(c->wrbuf, "</state>\n");
516         if (settings && *settings == '1')
517         {
518             wrbuf_puts(c->wrbuf, "<settings>\n");
519             wrbuf_puts(c->wrbuf, wrbuf_cstr(ht[i].settings_xml));
520             wrbuf_puts(c->wrbuf, "</settings>\n");
521         }
522         wrbuf_puts(c->wrbuf, "</target>");
523         wrbuf_destroy(ht[i].settings_xml);
524     }
525
526     wrbuf_puts(c->wrbuf, "</bytarget>");
527     rs->payload = nmem_strdup(c->nmem, wrbuf_cstr(c->wrbuf));
528     http_send_response(c);
529 }
530
531 static void write_metadata(WRBUF w, struct conf_service *service,
532         struct record_metadata **ml, int full)
533 {
534     int imeta;
535
536     for (imeta = 0; imeta < service->num_metadata; imeta++)
537     {
538         struct conf_metadata *cmd = &service->metadata[imeta];
539         struct record_metadata *md;
540         if (!cmd->brief && !full)
541             continue;
542         for (md = ml[imeta]; md; md = md->next)
543         {
544             struct record_metadata_attr *attr = md->attributes;
545             wrbuf_printf(w, "\n<md-%s", cmd->name);
546
547             for (; attr; attr = attr->next)
548             {
549                 wrbuf_printf(w, " %s=\"", attr->name);
550                 wrbuf_xmlputs(w, attr->value);
551                 wrbuf_puts(w, "\"");
552             }
553             wrbuf_puts(w, ">");
554             switch (cmd->type)
555             {
556                 case Metadata_type_generic:
557                     wrbuf_xmlputs(w, md->data.text.disp);
558                     break;
559                 case Metadata_type_year:
560                     wrbuf_printf(w, "%d", md->data.number.min);
561                     if (md->data.number.min != md->data.number.max)
562                         wrbuf_printf(w, "-%d", md->data.number.max);
563                     break;
564                 default:
565                     wrbuf_puts(w, "[can't represent]");
566             }
567             wrbuf_printf(w, "</md-%s>", cmd->name);
568         }
569     }
570 }
571
572 static void write_subrecord(struct record *r, WRBUF w,
573         struct conf_service *service, int show_details)
574 {
575     const char *name = session_setting_oneval(
576         client_get_database(r->client), PZ_NAME);
577
578     wrbuf_puts(w, "<location id=\"");
579     wrbuf_xmlputs(w, client_get_database(r->client)->database->url);
580     wrbuf_puts(w, "\" ");
581
582     wrbuf_puts(w, "name=\"");
583     wrbuf_xmlputs(w,  *name ? name : "Unknown");
584     wrbuf_puts(w, "\">");
585
586     write_metadata(w, service, r->metadata, show_details);
587     wrbuf_puts(w, "</location>\n");
588 }
589
590 static void show_raw_record_error(void *data, const char *addinfo)
591 {
592     http_channel_observer_t obs = data;
593     struct http_channel *c = http_channel_observer_chan(obs);
594     struct http_response *rs = c->response;
595
596     http_remove_observer(obs);
597
598     error(rs, PAZPAR2_RECORD_FAIL, addinfo);
599 }
600
601 static void show_raw_record_ok(void *data, const char *buf, size_t sz)
602 {
603     http_channel_observer_t obs = data;
604     struct http_channel *c = http_channel_observer_chan(obs);
605     struct http_response *rs = c->response;
606
607     http_remove_observer(obs);
608
609     wrbuf_write(c->wrbuf, buf, sz);
610     rs->payload = nmem_strdup(c->nmem, wrbuf_cstr(c->wrbuf));
611     http_send_response(c);
612 }
613
614
615 static void show_raw_record_ok_binary(void *data, const char *buf, size_t sz)
616 {
617     http_channel_observer_t obs = data;
618     struct http_channel *c = http_channel_observer_chan(obs);
619     struct http_response *rs = c->response;
620
621     http_remove_observer(obs);
622
623     wrbuf_write(c->wrbuf, buf, sz);
624     rs->payload = nmem_strdup(c->nmem, wrbuf_cstr(c->wrbuf));
625
626     rs->content_type = "application/octet-stream";
627     http_send_response(c);
628 }
629
630
631 void show_raw_reset(void *data, struct http_channel *c, void *data2)
632 {
633     //struct client *client = data;
634     //client_show_raw_remove(client, data2);
635 }
636
637 static void cmd_record_ready(void *data);
638
639 static void cmd_record(struct http_channel *c)
640 {
641     struct http_response *rs = c->response;
642     struct http_request *rq = c->request;
643     struct http_session *s = locate_session(rq, rs);
644     struct record_cluster *rec, *prev_r, *next_r;
645     struct record *r;
646     struct conf_service *service;
647     const char *idstr = http_argbyname(rq, "id");
648     const char *offsetstr = http_argbyname(rq, "offset");
649     const char *binarystr = http_argbyname(rq, "binary");
650     
651     if (!s)
652         return;
653     service = s->psession->service;
654     if (!idstr)
655     {
656         error(rs, PAZPAR2_MISSING_PARAMETER, "id");
657         return;
658     }
659     wrbuf_rewind(c->wrbuf);
660     if (!(rec = show_single(s->psession, idstr, &prev_r, &next_r)))
661     {
662         if (session_active_clients(s->psession) == 0)
663         {
664             error(rs, PAZPAR2_RECORD_MISSING, idstr);
665         }
666         else if (session_set_watch(s->psession, SESSION_WATCH_RECORD,
667                               cmd_record_ready, c, c) != 0)
668         {
669             error(rs, PAZPAR2_RECORD_MISSING, idstr);
670         }
671         return;
672     }
673     if (offsetstr)
674     {
675         int offset = atoi(offsetstr);
676         const char *syntax = http_argbyname(rq, "syntax");
677         const char *esn = http_argbyname(rq, "esn");
678         int i;
679         struct record*r = rec->records;
680         int binary = 0;
681
682         if (binarystr && *binarystr != '0')
683             binary = 1;
684
685         for (i = 0; i < offset && r; r = r->next, i++)
686             ;
687         if (!r)
688         {
689             error(rs, PAZPAR2_RECORD_FAIL, "no record at offset given");
690             return;
691         }
692         else
693         {
694             http_channel_observer_t obs =
695                 http_add_observer(c, r->client, show_raw_reset);
696             int ret = client_show_raw_begin(r->client, r->position,
697                                         syntax, esn, 
698                                         obs /* data */,
699                                         show_raw_record_error,
700                                         (binary ? 
701                                          show_raw_record_ok_binary : 
702                                          show_raw_record_ok),
703                                         (binary ? 1 : 0));
704             if (ret == -1)
705             {
706                 http_remove_observer(obs);
707                 error(rs, PAZPAR2_NO_SESSION, 0);
708             }
709         }
710     }
711     else
712     {
713         wrbuf_puts(c->wrbuf, HTTP_COMMAND_RESPONSE_PREFIX "<record>\n");
714         wrbuf_puts(c->wrbuf, "<recid>");
715         wrbuf_xmlputs(c->wrbuf, rec->recid);
716         wrbuf_puts(c->wrbuf, "</recid>\n");
717         if (prev_r)
718         {
719             wrbuf_puts(c->wrbuf, "<prevrecid>");
720             wrbuf_xmlputs(c->wrbuf, prev_r->recid);
721             wrbuf_puts(c->wrbuf, "</prevrecid>\n");
722         }
723         if (next_r)
724         {
725             wrbuf_puts(c->wrbuf, "<nextrecid>");
726             wrbuf_xmlputs(c->wrbuf, next_r->recid);
727             wrbuf_puts(c->wrbuf, "</nextrecid>\n");
728         }
729         wrbuf_printf(c->wrbuf, "<activeclients>%d</activeclients>\n", 
730                      session_active_clients(s->psession));
731         write_metadata(c->wrbuf, service, rec->metadata, 1);
732         for (r = rec->records; r; r = r->next)
733             write_subrecord(r, c->wrbuf, service, 1);
734         wrbuf_puts(c->wrbuf, "</record>\n");
735         rs->payload = nmem_strdup(c->nmem, wrbuf_cstr(c->wrbuf));
736         http_send_response(c);
737     }
738 }
739
740 static void cmd_record_ready(void *data)
741 {
742     struct http_channel *c = (struct http_channel *) data;
743
744     cmd_record(c);
745 }
746
747 static void show_records(struct http_channel *c, int active)
748 {
749     struct http_request *rq = c->request;
750     struct http_response *rs = c->response;
751     struct http_session *s = locate_session(rq, rs);
752     struct record_cluster **rl;
753     struct reclist_sortparms *sp;
754     const char *start = http_argbyname(rq, "start");
755     const char *num = http_argbyname(rq, "num");
756     const char *sort = http_argbyname(rq, "sort");
757     int startn = 0;
758     int numn = 20;
759     int total;
760     Odr_int total_hits;
761     int i;
762
763     if (!s)
764         return;
765
766     // We haven't counted clients yet if we're called on a block release
767     if (active < 0)
768         active = session_active_clients(s->psession);
769
770     if (start)
771         startn = atoi(start);
772     if (num)
773         numn = atoi(num);
774     if (!sort)
775         sort = "relevance";
776     if (!(sp = reclist_parse_sortparms(c->nmem, sort, s->psession->service)))
777     {
778         error(rs, PAZPAR2_MALFORMED_PARAMETER_VALUE, "sort");
779         return;
780     }
781
782     rl = show(s->psession, sp, startn, &numn, &total, &total_hits, c->nmem);
783
784     wrbuf_rewind(c->wrbuf);
785     wrbuf_puts(c->wrbuf, HTTP_COMMAND_RESPONSE_PREFIX "<show>\n<status>OK</status>\n");
786     wrbuf_printf(c->wrbuf, "<activeclients>%d</activeclients>\n", active);
787     wrbuf_printf(c->wrbuf, "<merged>%d</merged>\n", total);
788     wrbuf_printf(c->wrbuf, "<total>" ODR_INT_PRINTF "</total>\n", total_hits);
789     wrbuf_printf(c->wrbuf, "<start>%d</start>\n", startn);
790     wrbuf_printf(c->wrbuf, "<num>%d</num>\n", numn);
791
792     for (i = 0; i < numn; i++)
793     {
794         int ccount;
795         struct record *p;
796         struct record_cluster *rec = rl[i];
797         struct conf_service *service = s->psession->service;
798
799         wrbuf_puts(c->wrbuf, "<hit>\n");
800         write_metadata(c->wrbuf, service, rec->metadata, 0);
801         for (ccount = 0, p = rl[i]->records; p;  p = p->next, ccount++)
802             write_subrecord(p, c->wrbuf, service, 0); // subrecs w/o details
803         if (ccount > 1)
804             wrbuf_printf(c->wrbuf, "<count>%d</count>\n", ccount);
805         if (strstr(sort, "relevance"))
806             wrbuf_printf(c->wrbuf, "<relevance>%d</relevance>\n", rec->relevance);
807         wrbuf_puts(c->wrbuf, "<recid>");
808         wrbuf_xmlputs(c->wrbuf, rec->recid);
809         wrbuf_puts(c->wrbuf, "</recid>\n");
810         wrbuf_puts(c->wrbuf, "</hit>\n");
811     }
812
813     wrbuf_puts(c->wrbuf, "</show>\n");
814     rs->payload = nmem_strdup(c->nmem, wrbuf_cstr(c->wrbuf));
815     http_send_response(c);
816 }
817
818 static void show_records_ready(void *data)
819 {
820     struct http_channel *c = (struct http_channel *) data;
821
822     show_records(c, -1);
823 }
824
825 static void cmd_show(struct http_channel *c)
826 {
827     struct http_request *rq = c->request;
828     struct http_response *rs = c->response;
829     struct http_session *s = locate_session(rq, rs);
830     const char *block = http_argbyname(rq, "block");
831     int status;
832
833     if (!s)
834         return;
835
836     status = session_active_clients(s->psession);
837
838     if (block)
839     {
840         if (status && reclist_get_num_records(s->psession->reclist) == 0)
841         {
842             // if there is already a watch/block. we do not block this one
843             if (session_set_watch(s->psession, SESSION_WATCH_SHOW,
844                                   show_records_ready, c, c) != 0)
845             {
846                 yaz_log(YLOG_DEBUG, "Blocking on cmd_show");
847             }
848             return;
849         }
850     }
851
852     show_records(c, status);
853 }
854
855 static void cmd_ping(struct http_channel *c)
856 {
857     struct http_request *rq = c->request;
858     struct http_response *rs = c->response;
859     struct http_session *s = locate_session(rq, rs);
860     if (!s)
861         return;
862     rs->payload = HTTP_COMMAND_RESPONSE_PREFIX "<ping><status>OK</status></ping>";
863     http_send_response(c);
864 }
865
866 static int utf_8_valid(const char *str)
867 {
868     yaz_iconv_t cd = yaz_iconv_open("utf-8", "utf-8");
869     if (cd)
870     {
871         /* check that query is UTF-8 encoded */
872         char *inbuf = (char *) str; /* we know iconv does not alter this */
873         size_t inbytesleft = strlen(inbuf);
874
875         size_t outbytesleft = strlen(inbuf) + 10;
876         char *out = xmalloc(outbytesleft);
877         char *outbuf = out;
878         size_t r = yaz_iconv(cd, &inbuf, &inbytesleft, &outbuf, &outbytesleft);
879
880         /* if OK, try flushing the rest  */
881         if (r != (size_t) (-1))
882             r = yaz_iconv(cd, 0, 0, &outbuf, &outbytesleft);
883         yaz_iconv_close(cd);
884         xfree(out);
885         if (r == (size_t) (-1))
886             return 0;
887     }
888     return 1;
889 }
890
891 static void cmd_search(struct http_channel *c)
892 {
893     struct http_request *rq = c->request;
894     struct http_response *rs = c->response;
895     struct http_session *s = locate_session(rq, rs);
896     const char *query = http_argbyname(rq, "query");
897     const char *filter = http_argbyname(rq, "filter");
898     const char *maxrecs = http_argbyname(rq, "maxrecs");
899     const char *startrecs = http_argbyname(rq, "startrecs");
900     enum pazpar2_error_code code;
901     const char *addinfo = 0;
902
903     if (!s)
904         return;
905     if (!query)
906     {
907         error(rs, PAZPAR2_MISSING_PARAMETER, "query");
908         return;
909     }
910     if (!utf_8_valid(query))
911     {
912         error(rs, PAZPAR2_MALFORMED_PARAMETER_ENCODING, "query");
913         return;
914     }
915     code = search(s->psession, query, startrecs, maxrecs, filter, &addinfo);
916     if (code)
917     {
918         error(rs, code, addinfo);
919         return;
920     }
921     rs->payload = HTTP_COMMAND_RESPONSE_PREFIX "<search><status>OK</status></search>";
922     http_send_response(c);
923 }
924
925
926 static void cmd_stat(struct http_channel *c)
927 {
928     struct http_request *rq = c->request;
929     struct http_response *rs = c->response;
930     struct http_session *s = locate_session(rq, rs);
931     struct statistics stat;
932     int clients;
933
934     float progress = 0;
935
936     if (!s)
937         return;
938
939     clients = session_active_clients(s->psession);
940     statistics(s->psession, &stat);
941
942     if (stat.num_clients > 0) {
943         progress = (stat.num_clients  - clients) / (float)stat.num_clients;
944     }
945
946     wrbuf_rewind(c->wrbuf);
947     wrbuf_puts(c->wrbuf, HTTP_COMMAND_RESPONSE_PREFIX "<stat>");
948     wrbuf_printf(c->wrbuf, "<activeclients>%d</activeclients>\n", clients);
949     wrbuf_printf(c->wrbuf, "<hits>" ODR_INT_PRINTF "</hits>\n", stat.num_hits);
950     wrbuf_printf(c->wrbuf, "<records>%d</records>\n", stat.num_records);
951     wrbuf_printf(c->wrbuf, "<clients>%d</clients>\n", stat.num_clients);
952     wrbuf_printf(c->wrbuf, "<unconnected>%d</unconnected>\n", stat.num_no_connection);
953     wrbuf_printf(c->wrbuf, "<connecting>%d</connecting>\n", stat.num_connecting);
954     wrbuf_printf(c->wrbuf, "<working>%d</working>\n", stat.num_working);
955     wrbuf_printf(c->wrbuf, "<idle>%d</idle>\n", stat.num_idle);
956     wrbuf_printf(c->wrbuf, "<failed>%d</failed>\n", stat.num_failed);
957     wrbuf_printf(c->wrbuf, "<error>%d</error>\n", stat.num_error);
958     wrbuf_printf(c->wrbuf, "<progress>%.2f</progress>\n", progress);
959     wrbuf_puts(c->wrbuf, "</stat>");
960     rs->payload = nmem_strdup(c->nmem, wrbuf_cstr(c->wrbuf));
961     http_send_response(c);
962 }
963
964 static void cmd_info(struct http_channel *c)
965 {
966     char yaz_version_str[20];
967     struct http_response *rs = c->response;
968
969     wrbuf_rewind(c->wrbuf);
970     wrbuf_puts(c->wrbuf, HTTP_COMMAND_RESPONSE_PREFIX "<info>\n");
971     wrbuf_puts(c->wrbuf, " <version>\n");
972     wrbuf_puts(c->wrbuf, "<pazpar2");
973 #ifdef PAZPAR2_VERSION_SHA1
974     wrbuf_printf(c->wrbuf, " sha1=\"%s\"", PAZPAR2_VERSION_SHA1);
975 #endif
976     wrbuf_puts(c->wrbuf, ">");
977     wrbuf_xmlputs(c->wrbuf, VERSION);
978     wrbuf_puts(c->wrbuf, "</pazpar2>");
979
980
981     yaz_version(yaz_version_str, 0);
982     wrbuf_puts(c->wrbuf, "  <yaz compiled=\"");
983     wrbuf_xmlputs(c->wrbuf, YAZ_VERSION);
984     wrbuf_puts(c->wrbuf, "\">");
985     wrbuf_xmlputs(c->wrbuf, yaz_version_str);
986     wrbuf_puts(c->wrbuf, "</yaz>\n");
987
988     wrbuf_puts(c->wrbuf, " </version>\n");
989     
990     wrbuf_puts(c->wrbuf, "</info>");
991     rs->payload = nmem_strdup(c->nmem, wrbuf_cstr(c->wrbuf));
992     http_send_response(c);
993 }
994
995 struct {
996     char *name;
997     void (*fun)(struct http_channel *c);
998 } commands[] = {
999     { "init", cmd_init },
1000     { "settings", cmd_settings },
1001     { "stat", cmd_stat },
1002     { "bytarget", cmd_bytarget },
1003     { "show", cmd_show },
1004     { "search", cmd_search },
1005     { "termlist", cmd_termlist },
1006     { "exit", cmd_exit },
1007     { "ping", cmd_ping },
1008     { "record", cmd_record },
1009     { "info", cmd_info },
1010     {0,0}
1011 };
1012
1013 void http_command(struct http_channel *c)
1014 {
1015     const char *command = http_argbyname(c->request, "command");
1016     struct http_response *rs = http_create_response(c);
1017     int i;
1018
1019     c->response = rs;
1020
1021     http_addheader(rs, "Expires", "Thu, 19 Nov 1981 08:52:00 GMT");
1022     http_addheader(rs, "Cache-Control", "no-store, no-cache, must-revalidate, post-check=0, pre-check=0");
1023
1024     if (!command)
1025     {
1026         error(rs, PAZPAR2_MISSING_PARAMETER, "command");
1027         return;
1028     }
1029     for (i = 0; commands[i].name; i++)
1030         if (!strcmp(commands[i].name, command))
1031         {
1032             (*commands[i].fun)(c);
1033             break;
1034         }
1035     if (!commands[i].name)
1036         error(rs, PAZPAR2_MALFORMED_PARAMETER_VALUE, "command");
1037
1038     return;
1039 }
1040
1041 /*
1042  * Local variables:
1043  * c-basic-offset: 4
1044  * c-file-style: "Stroustrup"
1045  * indent-tabs-mode: nil
1046  * End:
1047  * vim: shiftwidth=4 tabstop=8 expandtab
1048  */
1049