Add mutex for service (ref counting)
[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     }
287     s = http_session_create(service);
288     
289     yaz_log(YLOG_DEBUG, "HTTP Session init");
290     if (!clear || *clear == '0')
291         session_init_databases(s->psession);
292     else
293         yaz_log(YLOG_LOG, "No databases preloaded");
294     
295     sesid = make_sessionid();
296     s->session_id = sesid;
297     if (process_settings(s->psession, c->request, c->response) < 0)
298         return;
299     
300     sprintf(buf, HTTP_COMMAND_RESPONSE_PREFIX 
301             "<init><status>OK</status><session>%u", sesid);
302     if (c->server->server_id)
303     {
304         strcat(buf, ".");
305         strcat(buf, c->server->server_id);
306     }
307     strcat(buf, "</session>"
308            "<protocol>" PAZPAR2_PROTOCOL_VERSION "</protocol></init>");
309     rs->payload = nmem_strdup(c->nmem, buf);
310     http_send_response(c);
311 }
312
313 static void apply_local_setting(void *client_data,
314                                 struct setting *set)
315 {
316     struct session *se =  (struct session *) client_data;
317
318     session_apply_setting(se, nmem_strdup(se->session_nmem, set->target),
319                           nmem_strdup(se->session_nmem, set->name),
320                           nmem_strdup(se->session_nmem, set->value));
321 }
322
323 static void cmd_settings(struct http_channel *c)
324 {
325     struct http_response *rs = c->response;
326     struct http_request *rq = c->request;
327     struct http_session *s = locate_session(rq, rs);
328     const char *content_type = http_lookup_header(rq->headers, "Content-Type");
329
330     if (!s)
331         return;
332
333     if (rq->content_len && content_type && 
334         !yaz_strcmp_del("text/xml", content_type, "; "))
335     {
336         xmlDoc *doc = xmlParseMemory(rq->content_buf, rq->content_len);
337         xmlNode *root_n;
338         if (!doc)
339         {
340             error(rs, PAZPAR2_MALFORMED_SETTING, 0);
341             return;
342         }
343         root_n = xmlDocGetRootElement(doc);
344
345         settings_read_node_x(root_n, s->psession, apply_local_setting);
346
347         xmlFreeDoc(doc);
348     }
349     if (process_settings(s->psession, rq, rs) < 0)
350         return;
351     rs->payload = HTTP_COMMAND_RESPONSE_PREFIX "<settings><status>OK</status></settings>";
352     http_send_response(c);
353 }
354
355 // Compares two hitsbytarget nodes by hitcount
356 static int cmp_ht(const void *p1, const void *p2)
357 {
358     const struct hitsbytarget *h1 = p1;
359     const struct hitsbytarget *h2 = p2;
360     return h2->hits - h1->hits;
361 }
362
363 // This implements functionality somewhat similar to 'bytarget', but in a termlist form
364 static void targets_termlist(WRBUF wrbuf, struct session *se, int num,
365                              NMEM nmem)
366 {
367     struct hitsbytarget *ht;
368     int count, i;
369
370     ht = hitsbytarget(se, &count, nmem);
371     qsort(ht, count, sizeof(struct hitsbytarget), cmp_ht);
372     for (i = 0; i < count && i < num && ht[i].hits > 0; i++)
373     {
374
375         // do only print terms which have display names
376     
377         wrbuf_puts(wrbuf, "<term>\n");
378
379         wrbuf_puts(wrbuf, "<id>");
380         wrbuf_xmlputs(wrbuf, ht[i].id);
381         wrbuf_puts(wrbuf, "</id>\n");
382         
383         wrbuf_puts(wrbuf, "<name>");
384         if (!ht[i].name || !ht[i].name[0])
385             wrbuf_xmlputs(wrbuf, "NO TARGET NAME");
386         else
387             wrbuf_xmlputs(wrbuf, ht[i].name);
388         wrbuf_puts(wrbuf, "</name>\n");
389         
390         wrbuf_printf(wrbuf, "<frequency>" ODR_INT_PRINTF "</frequency>\n",
391                      ht[i].hits);
392         
393         wrbuf_puts(wrbuf, "<state>");
394         wrbuf_xmlputs(wrbuf, ht[i].state);
395         wrbuf_puts(wrbuf, "</state>\n");
396         
397         wrbuf_printf(wrbuf, "<diagnostic>%d</diagnostic>\n", 
398                      ht[i].diagnostic);
399         wrbuf_puts(wrbuf, "</term>\n");
400     }
401 }
402
403 static void cmd_termlist(struct http_channel *c)
404 {
405     struct http_response *rs = c->response;
406     struct http_request *rq = c->request;
407     struct http_session *s = locate_session(rq, rs);
408     struct termlist_score **p;
409     int len;
410     int i;
411     const char *name = http_argbyname(rq, "name");
412     const char *nums = http_argbyname(rq, "num");
413     int num = 15;
414     int status;
415
416     if (!s)
417         return;
418
419     status = session_active_clients(s->psession);
420
421     if (!name)
422         name = "subject";
423     if (strlen(name) > 255)
424         return;
425     if (nums)
426         num = atoi(nums);
427
428     wrbuf_rewind(c->wrbuf);
429
430     wrbuf_puts(c->wrbuf, "<termlist>\n");
431     wrbuf_printf(c->wrbuf, "<activeclients>%d</activeclients>\n", status);
432     while (*name)
433     {
434         char tname[256];
435         const char *tp;
436
437         if (!(tp = strchr(name, ',')))
438             tp = name + strlen(name);
439         strncpy(tname, name, tp - name);
440         tname[tp - name] = '\0';
441
442         wrbuf_puts(c->wrbuf, "<list name=\"");
443         wrbuf_xmlputs(c->wrbuf, tname);
444         wrbuf_puts(c->wrbuf, "\">\n");
445         if (!strcmp(tname, "xtargets"))
446             targets_termlist(c->wrbuf, s->psession, num, c->nmem);
447         else
448         {
449             p = termlist(s->psession, tname, &len);
450             if (p)
451                 for (i = 0; i < len && i < num; i++){
452                     // prevnt sending empty term elements
453                     if (!p[i]->term || !p[i]->term[0])
454                         continue;
455
456                     wrbuf_puts(c->wrbuf, "<term>");
457                     wrbuf_puts(c->wrbuf, "<name>");
458                     wrbuf_xmlputs(c->wrbuf, p[i]->term);
459                     wrbuf_puts(c->wrbuf, "</name>");
460                         
461                     wrbuf_printf(c->wrbuf, 
462                                  "<frequency>%d</frequency>", 
463                                  p[i]->frequency);
464                     wrbuf_puts(c->wrbuf, "</term>\n");
465                }
466         }
467         wrbuf_puts(c->wrbuf, "</list>\n");
468         name = tp;
469         if (*name == ',')
470             name++;
471     }
472     wrbuf_puts(c->wrbuf, "</termlist>\n");
473     rs->payload = nmem_strdup(rq->channel->nmem, wrbuf_cstr(c->wrbuf));
474     http_send_response(c);
475 }
476
477
478 static void cmd_bytarget(struct http_channel *c)
479 {
480     struct http_response *rs = c->response;
481     struct http_request *rq = c->request;
482     struct http_session *s = locate_session(rq, rs);
483     struct hitsbytarget *ht;
484     const char *settings = http_argbyname(rq, "settings");
485     int count, i;
486
487     if (!s)
488         return;
489     ht = hitsbytarget(s->psession, &count, c->nmem);
490     wrbuf_rewind(c->wrbuf);
491     wrbuf_puts(c->wrbuf, HTTP_COMMAND_RESPONSE_PREFIX "<bytarget><status>OK</status>");
492
493     for (i = 0; i < count; i++)
494     {
495         wrbuf_puts(c->wrbuf, "\n<target>");
496
497         wrbuf_puts(c->wrbuf, "<id>");
498         wrbuf_xmlputs(c->wrbuf, ht[i].id);
499         wrbuf_puts(c->wrbuf, "</id>\n");
500
501         if (ht[i].name && ht[i].name[0]) 
502         {
503             wrbuf_puts(c->wrbuf, "<name>");
504             wrbuf_xmlputs(c->wrbuf, ht[i].name);
505             wrbuf_puts(c->wrbuf, "</name>\n");
506         }
507
508         wrbuf_printf(c->wrbuf, "<hits>" ODR_INT_PRINTF "</hits>\n", ht[i].hits);
509         wrbuf_printf(c->wrbuf, "<diagnostic>%d</diagnostic>\n", ht[i].diagnostic);
510         wrbuf_printf(c->wrbuf, "<records>%d</records>\n", ht[i].records);
511
512         wrbuf_puts(c->wrbuf, "<state>");
513         wrbuf_xmlputs(c->wrbuf, ht[i].state);
514         wrbuf_puts(c->wrbuf, "</state>\n");
515         if (settings && *settings == '1')
516         {
517             wrbuf_puts(c->wrbuf, "<settings>\n");
518             wrbuf_puts(c->wrbuf, wrbuf_cstr(ht[i].settings_xml));
519             wrbuf_puts(c->wrbuf, "</settings>\n");
520         }
521         wrbuf_puts(c->wrbuf, "</target>");
522         wrbuf_destroy(ht[i].settings_xml);
523     }
524
525     wrbuf_puts(c->wrbuf, "</bytarget>");
526     rs->payload = nmem_strdup(c->nmem, wrbuf_cstr(c->wrbuf));
527     http_send_response(c);
528 }
529
530 static void write_metadata(WRBUF w, struct conf_service *service,
531         struct record_metadata **ml, int full)
532 {
533     int imeta;
534
535     for (imeta = 0; imeta < service->num_metadata; imeta++)
536     {
537         struct conf_metadata *cmd = &service->metadata[imeta];
538         struct record_metadata *md;
539         if (!cmd->brief && !full)
540             continue;
541         for (md = ml[imeta]; md; md = md->next)
542         {
543             struct record_metadata_attr *attr = md->attributes;
544             wrbuf_printf(w, "\n<md-%s", cmd->name);
545
546             for (; attr; attr = attr->next)
547             {
548                 wrbuf_printf(w, " %s=\"", attr->name);
549                 wrbuf_xmlputs(w, attr->value);
550                 wrbuf_puts(w, "\"");
551             }
552             wrbuf_puts(w, ">");
553             switch (cmd->type)
554             {
555                 case Metadata_type_generic:
556                     wrbuf_xmlputs(w, md->data.text.disp);
557                     break;
558                 case Metadata_type_year:
559                     wrbuf_printf(w, "%d", md->data.number.min);
560                     if (md->data.number.min != md->data.number.max)
561                         wrbuf_printf(w, "-%d", md->data.number.max);
562                     break;
563                 default:
564                     wrbuf_puts(w, "[can't represent]");
565             }
566             wrbuf_printf(w, "</md-%s>", cmd->name);
567         }
568     }
569 }
570
571 static void write_subrecord(struct record *r, WRBUF w,
572         struct conf_service *service, int show_details)
573 {
574     const char *name = session_setting_oneval(
575         client_get_database(r->client), PZ_NAME);
576
577     wrbuf_puts(w, "<location id=\"");
578     wrbuf_xmlputs(w, client_get_database(r->client)->database->url);
579     wrbuf_puts(w, "\" ");
580
581     wrbuf_puts(w, "name=\"");
582     wrbuf_xmlputs(w,  *name ? name : "Unknown");
583     wrbuf_puts(w, "\">");
584
585     write_metadata(w, service, r->metadata, show_details);
586     wrbuf_puts(w, "</location>\n");
587 }
588
589 static void show_raw_record_error(void *data, const char *addinfo)
590 {
591     http_channel_observer_t obs = data;
592     struct http_channel *c = http_channel_observer_chan(obs);
593     struct http_response *rs = c->response;
594
595     http_remove_observer(obs);
596
597     error(rs, PAZPAR2_RECORD_FAIL, addinfo);
598 }
599
600 static void show_raw_record_ok(void *data, const char *buf, size_t sz)
601 {
602     http_channel_observer_t obs = data;
603     struct http_channel *c = http_channel_observer_chan(obs);
604     struct http_response *rs = c->response;
605
606     http_remove_observer(obs);
607
608     wrbuf_write(c->wrbuf, buf, sz);
609     rs->payload = nmem_strdup(c->nmem, wrbuf_cstr(c->wrbuf));
610     http_send_response(c);
611 }
612
613
614 static void show_raw_record_ok_binary(void *data, const char *buf, size_t sz)
615 {
616     http_channel_observer_t obs = data;
617     struct http_channel *c = http_channel_observer_chan(obs);
618     struct http_response *rs = c->response;
619
620     http_remove_observer(obs);
621
622     wrbuf_write(c->wrbuf, buf, sz);
623     rs->payload = nmem_strdup(c->nmem, wrbuf_cstr(c->wrbuf));
624
625     rs->content_type = "application/octet-stream";
626     http_send_response(c);
627 }
628
629
630 void show_raw_reset(void *data, struct http_channel *c, void *data2)
631 {
632     //struct client *client = data;
633     //client_show_raw_remove(client, data2);
634 }
635
636 static void cmd_record_ready(void *data);
637
638 static void cmd_record(struct http_channel *c)
639 {
640     struct http_response *rs = c->response;
641     struct http_request *rq = c->request;
642     struct http_session *s = locate_session(rq, rs);
643     struct record_cluster *rec, *prev_r, *next_r;
644     struct record *r;
645     struct conf_service *service;
646     const char *idstr = http_argbyname(rq, "id");
647     const char *offsetstr = http_argbyname(rq, "offset");
648     const char *binarystr = http_argbyname(rq, "binary");
649     
650     if (!s)
651         return;
652     service = s->psession->service;
653     if (!idstr)
654     {
655         error(rs, PAZPAR2_MISSING_PARAMETER, "id");
656         return;
657     }
658     wrbuf_rewind(c->wrbuf);
659     if (!(rec = show_single(s->psession, idstr, &prev_r, &next_r)))
660     {
661         if (session_active_clients(s->psession) == 0)
662         {
663             error(rs, PAZPAR2_RECORD_MISSING, idstr);
664         }
665         else if (session_set_watch(s->psession, SESSION_WATCH_RECORD,
666                               cmd_record_ready, c, c) != 0)
667         {
668             error(rs, PAZPAR2_RECORD_MISSING, idstr);
669         }
670         return;
671     }
672     if (offsetstr)
673     {
674         int offset = atoi(offsetstr);
675         const char *syntax = http_argbyname(rq, "syntax");
676         const char *esn = http_argbyname(rq, "esn");
677         int i;
678         struct record*r = rec->records;
679         int binary = 0;
680
681         if (binarystr && *binarystr != '0')
682             binary = 1;
683
684         for (i = 0; i < offset && r; r = r->next, i++)
685             ;
686         if (!r)
687         {
688             error(rs, PAZPAR2_RECORD_FAIL, "no record at offset given");
689             return;
690         }
691         else
692         {
693             http_channel_observer_t obs =
694                 http_add_observer(c, r->client, show_raw_reset);
695             int ret = client_show_raw_begin(r->client, r->position,
696                                         syntax, esn, 
697                                         obs /* data */,
698                                         show_raw_record_error,
699                                         (binary ? 
700                                          show_raw_record_ok_binary : 
701                                          show_raw_record_ok),
702                                         (binary ? 1 : 0));
703             if (ret == -1)
704             {
705                 http_remove_observer(obs);
706                 error(rs, PAZPAR2_NO_SESSION, 0);
707             }
708         }
709     }
710     else
711     {
712         wrbuf_puts(c->wrbuf, HTTP_COMMAND_RESPONSE_PREFIX "<record>\n");
713         wrbuf_puts(c->wrbuf, "<recid>");
714         wrbuf_xmlputs(c->wrbuf, rec->recid);
715         wrbuf_puts(c->wrbuf, "</recid>\n");
716         if (prev_r)
717         {
718             wrbuf_puts(c->wrbuf, "<prevrecid>");
719             wrbuf_xmlputs(c->wrbuf, prev_r->recid);
720             wrbuf_puts(c->wrbuf, "</prevrecid>\n");
721         }
722         if (next_r)
723         {
724             wrbuf_puts(c->wrbuf, "<nextrecid>");
725             wrbuf_xmlputs(c->wrbuf, next_r->recid);
726             wrbuf_puts(c->wrbuf, "</nextrecid>\n");
727         }
728         wrbuf_printf(c->wrbuf, "<activeclients>%d</activeclients>\n", 
729                      session_active_clients(s->psession));
730         write_metadata(c->wrbuf, service, rec->metadata, 1);
731         for (r = rec->records; r; r = r->next)
732             write_subrecord(r, c->wrbuf, service, 1);
733         wrbuf_puts(c->wrbuf, "</record>\n");
734         rs->payload = nmem_strdup(c->nmem, wrbuf_cstr(c->wrbuf));
735         http_send_response(c);
736     }
737 }
738
739 static void cmd_record_ready(void *data)
740 {
741     struct http_channel *c = (struct http_channel *) data;
742
743     cmd_record(c);
744 }
745
746 static void show_records(struct http_channel *c, int active)
747 {
748     struct http_request *rq = c->request;
749     struct http_response *rs = c->response;
750     struct http_session *s = locate_session(rq, rs);
751     struct record_cluster **rl;
752     struct reclist_sortparms *sp;
753     const char *start = http_argbyname(rq, "start");
754     const char *num = http_argbyname(rq, "num");
755     const char *sort = http_argbyname(rq, "sort");
756     int startn = 0;
757     int numn = 20;
758     int total;
759     Odr_int total_hits;
760     int i;
761
762     if (!s)
763         return;
764
765     // We haven't counted clients yet if we're called on a block release
766     if (active < 0)
767         active = session_active_clients(s->psession);
768
769     if (start)
770         startn = atoi(start);
771     if (num)
772         numn = atoi(num);
773     if (!sort)
774         sort = "relevance";
775     if (!(sp = reclist_parse_sortparms(c->nmem, sort, s->psession->service)))
776     {
777         error(rs, PAZPAR2_MALFORMED_PARAMETER_VALUE, "sort");
778         return;
779     }
780
781     rl = show(s->psession, sp, startn, &numn, &total, &total_hits, c->nmem);
782
783     wrbuf_rewind(c->wrbuf);
784     wrbuf_puts(c->wrbuf, HTTP_COMMAND_RESPONSE_PREFIX "<show>\n<status>OK</status>\n");
785     wrbuf_printf(c->wrbuf, "<activeclients>%d</activeclients>\n", active);
786     wrbuf_printf(c->wrbuf, "<merged>%d</merged>\n", total);
787     wrbuf_printf(c->wrbuf, "<total>" ODR_INT_PRINTF "</total>\n", total_hits);
788     wrbuf_printf(c->wrbuf, "<start>%d</start>\n", startn);
789     wrbuf_printf(c->wrbuf, "<num>%d</num>\n", numn);
790
791     for (i = 0; i < numn; i++)
792     {
793         int ccount;
794         struct record *p;
795         struct record_cluster *rec = rl[i];
796         struct conf_service *service = s->psession->service;
797
798         wrbuf_puts(c->wrbuf, "<hit>\n");
799         write_metadata(c->wrbuf, service, rec->metadata, 0);
800         for (ccount = 0, p = rl[i]->records; p;  p = p->next, ccount++)
801             write_subrecord(p, c->wrbuf, service, 0); // subrecs w/o details
802         if (ccount > 1)
803             wrbuf_printf(c->wrbuf, "<count>%d</count>\n", ccount);
804         if (strstr(sort, "relevance"))
805             wrbuf_printf(c->wrbuf, "<relevance>%d</relevance>\n", rec->relevance);
806         wrbuf_puts(c->wrbuf, "<recid>");
807         wrbuf_xmlputs(c->wrbuf, rec->recid);
808         wrbuf_puts(c->wrbuf, "</recid>\n");
809         wrbuf_puts(c->wrbuf, "</hit>\n");
810     }
811
812     wrbuf_puts(c->wrbuf, "</show>\n");
813     rs->payload = nmem_strdup(c->nmem, wrbuf_cstr(c->wrbuf));
814     http_send_response(c);
815 }
816
817 static void show_records_ready(void *data)
818 {
819     struct http_channel *c = (struct http_channel *) data;
820
821     show_records(c, -1);
822 }
823
824 static void cmd_show(struct http_channel *c)
825 {
826     struct http_request *rq = c->request;
827     struct http_response *rs = c->response;
828     struct http_session *s = locate_session(rq, rs);
829     const char *block = http_argbyname(rq, "block");
830     int status;
831
832     if (!s)
833         return;
834
835     status = session_active_clients(s->psession);
836
837     if (block)
838     {
839         if (status && reclist_get_num_records(s->psession->reclist) == 0)
840         {
841             // if there is already a watch/block. we do not block this one
842             if (session_set_watch(s->psession, SESSION_WATCH_SHOW,
843                                   show_records_ready, c, c) != 0)
844             {
845                 yaz_log(YLOG_DEBUG, "Blocking on cmd_show");
846             }
847             return;
848         }
849     }
850
851     show_records(c, status);
852 }
853
854 static void cmd_ping(struct http_channel *c)
855 {
856     struct http_request *rq = c->request;
857     struct http_response *rs = c->response;
858     struct http_session *s = locate_session(rq, rs);
859     if (!s)
860         return;
861     rs->payload = HTTP_COMMAND_RESPONSE_PREFIX "<ping><status>OK</status></ping>";
862     http_send_response(c);
863 }
864
865 static int utf_8_valid(const char *str)
866 {
867     yaz_iconv_t cd = yaz_iconv_open("utf-8", "utf-8");
868     if (cd)
869     {
870         /* check that query is UTF-8 encoded */
871         char *inbuf = (char *) str; /* we know iconv does not alter this */
872         size_t inbytesleft = strlen(inbuf);
873
874         size_t outbytesleft = strlen(inbuf) + 10;
875         char *out = xmalloc(outbytesleft);
876         char *outbuf = out;
877         size_t r = yaz_iconv(cd, &inbuf, &inbytesleft, &outbuf, &outbytesleft);
878
879         /* if OK, try flushing the rest  */
880         if (r != (size_t) (-1))
881             r = yaz_iconv(cd, 0, 0, &outbuf, &outbytesleft);
882         yaz_iconv_close(cd);
883         xfree(out);
884         if (r == (size_t) (-1))
885             return 0;
886     }
887     return 1;
888 }
889
890 static void cmd_search(struct http_channel *c)
891 {
892     struct http_request *rq = c->request;
893     struct http_response *rs = c->response;
894     struct http_session *s = locate_session(rq, rs);
895     const char *query = http_argbyname(rq, "query");
896     const char *filter = http_argbyname(rq, "filter");
897     const char *maxrecs = http_argbyname(rq, "maxrecs");
898     const char *startrecs = http_argbyname(rq, "startrecs");
899     enum pazpar2_error_code code;
900     const char *addinfo = 0;
901
902     if (!s)
903         return;
904     if (!query)
905     {
906         error(rs, PAZPAR2_MISSING_PARAMETER, "query");
907         return;
908     }
909     if (!utf_8_valid(query))
910     {
911         error(rs, PAZPAR2_MALFORMED_PARAMETER_ENCODING, "query");
912         return;
913     }
914     code = search(s->psession, query, startrecs, maxrecs, filter, &addinfo);
915     if (code)
916     {
917         error(rs, code, addinfo);
918         return;
919     }
920     rs->payload = HTTP_COMMAND_RESPONSE_PREFIX "<search><status>OK</status></search>";
921     http_send_response(c);
922 }
923
924
925 static void cmd_stat(struct http_channel *c)
926 {
927     struct http_request *rq = c->request;
928     struct http_response *rs = c->response;
929     struct http_session *s = locate_session(rq, rs);
930     struct statistics stat;
931     int clients;
932
933     float progress = 0;
934
935     if (!s)
936         return;
937
938     clients = session_active_clients(s->psession);
939     statistics(s->psession, &stat);
940
941     if (stat.num_clients > 0) {
942         progress = (stat.num_clients  - clients) / (float)stat.num_clients;
943     }
944
945     wrbuf_rewind(c->wrbuf);
946     wrbuf_puts(c->wrbuf, HTTP_COMMAND_RESPONSE_PREFIX "<stat>");
947     wrbuf_printf(c->wrbuf, "<activeclients>%d</activeclients>\n", clients);
948     wrbuf_printf(c->wrbuf, "<hits>" ODR_INT_PRINTF "</hits>\n", stat.num_hits);
949     wrbuf_printf(c->wrbuf, "<records>%d</records>\n", stat.num_records);
950     wrbuf_printf(c->wrbuf, "<clients>%d</clients>\n", stat.num_clients);
951     wrbuf_printf(c->wrbuf, "<unconnected>%d</unconnected>\n", stat.num_no_connection);
952     wrbuf_printf(c->wrbuf, "<connecting>%d</connecting>\n", stat.num_connecting);
953     wrbuf_printf(c->wrbuf, "<working>%d</working>\n", stat.num_working);
954     wrbuf_printf(c->wrbuf, "<idle>%d</idle>\n", stat.num_idle);
955     wrbuf_printf(c->wrbuf, "<failed>%d</failed>\n", stat.num_failed);
956     wrbuf_printf(c->wrbuf, "<error>%d</error>\n", stat.num_error);
957     wrbuf_printf(c->wrbuf, "<progress>%.2f</progress>\n", progress);
958     wrbuf_puts(c->wrbuf, "</stat>");
959     rs->payload = nmem_strdup(c->nmem, wrbuf_cstr(c->wrbuf));
960     http_send_response(c);
961 }
962
963 static void cmd_info(struct http_channel *c)
964 {
965     char yaz_version_str[20];
966     struct http_response *rs = c->response;
967
968     wrbuf_rewind(c->wrbuf);
969     wrbuf_puts(c->wrbuf, HTTP_COMMAND_RESPONSE_PREFIX "<info>\n");
970     wrbuf_puts(c->wrbuf, " <version>\n");
971     wrbuf_puts(c->wrbuf, "<pazpar2");
972 #ifdef PAZPAR2_VERSION_SHA1
973     wrbuf_printf(c->wrbuf, " sha1=\"%s\"", PAZPAR2_VERSION_SHA1);
974 #endif
975     wrbuf_puts(c->wrbuf, ">");
976     wrbuf_xmlputs(c->wrbuf, VERSION);
977     wrbuf_puts(c->wrbuf, "</pazpar2>");
978
979
980     yaz_version(yaz_version_str, 0);
981     wrbuf_puts(c->wrbuf, "  <yaz compiled=\"");
982     wrbuf_xmlputs(c->wrbuf, YAZ_VERSION);
983     wrbuf_puts(c->wrbuf, "\">");
984     wrbuf_xmlputs(c->wrbuf, yaz_version_str);
985     wrbuf_puts(c->wrbuf, "</yaz>\n");
986
987     wrbuf_puts(c->wrbuf, " </version>\n");
988     
989     wrbuf_puts(c->wrbuf, "</info>");
990     rs->payload = nmem_strdup(c->nmem, wrbuf_cstr(c->wrbuf));
991     http_send_response(c);
992 }
993
994 struct {
995     char *name;
996     void (*fun)(struct http_channel *c);
997 } commands[] = {
998     { "init", cmd_init },
999     { "settings", cmd_settings },
1000     { "stat", cmd_stat },
1001     { "bytarget", cmd_bytarget },
1002     { "show", cmd_show },
1003     { "search", cmd_search },
1004     { "termlist", cmd_termlist },
1005     { "exit", cmd_exit },
1006     { "ping", cmd_ping },
1007     { "record", cmd_record },
1008     { "info", cmd_info },
1009     {0,0}
1010 };
1011
1012 void http_command(struct http_channel *c)
1013 {
1014     const char *command = http_argbyname(c->request, "command");
1015     struct http_response *rs = http_create_response(c);
1016     int i;
1017
1018     c->response = rs;
1019
1020     http_addheader(rs, "Expires", "Thu, 19 Nov 1981 08:52:00 GMT");
1021     http_addheader(rs, "Cache-Control", "no-store, no-cache, must-revalidate, post-check=0, pre-check=0");
1022
1023     if (!command)
1024     {
1025         error(rs, PAZPAR2_MISSING_PARAMETER, "command");
1026         return;
1027     }
1028     for (i = 0; commands[i].name; i++)
1029         if (!strcmp(commands[i].name, command))
1030         {
1031             (*commands[i].fun)(c);
1032             break;
1033         }
1034     if (!commands[i].name)
1035         error(rs, PAZPAR2_MALFORMED_PARAMETER_VALUE, "command");
1036
1037     return;
1038 }
1039
1040 /*
1041  * Local variables:
1042  * c-basic-offset: 4
1043  * c-file-style: "Stroustrup"
1044  * indent-tabs-mode: nil
1045  * End:
1046  * vim: shiftwidth=4 tabstop=8 expandtab
1047  */
1048