2877a16554fda14b17a8c4701afafe7a3e66c1e8
[yaz-moved-to-github.git] / src / zoom-c.c
1 /* This file is part of the YAZ toolkit.
2  * Copyright (C) 1995-2013 Index Data
3  * See the file LICENSE for details.
4  */
5 /**
6  * \file zoom-c.c
7  * \brief Implements ZOOM C interface.
8  */
9 #if HAVE_CONFIG_H
10 #include <config.h>
11 #endif
12
13 #include <assert.h>
14 #include <string.h>
15 #include <errno.h>
16 #include "zoom-p.h"
17
18 #include <yaz/yaz-util.h>
19 #include <yaz/xmalloc.h>
20 #include <yaz/otherinfo.h>
21 #include <yaz/log.h>
22 #include <yaz/diagbib1.h>
23 #include <yaz/charneg.h>
24 #include <yaz/query-charset.h>
25 #include <yaz/snprintf.h>
26 #include <yaz/facet.h>
27
28 #include <yaz/shptr.h>
29
30 #if SHPTR
31 YAZ_SHPTR_TYPE(WRBUF)
32 #endif
33
34 static int log_api0 = 0;
35 static int log_details0 = 0;
36
37 static void resultset_destroy(ZOOM_resultset r);
38 static zoom_ret do_write_ex(ZOOM_connection c, char *buf_out, int len_out);
39
40 static void initlog(void)
41 {
42     static int log_level_initialized = 0;
43
44     if (!log_level_initialized)
45     {
46         log_api0 = yaz_log_module_level("zoom");
47         log_details0 = yaz_log_module_level("zoomdetails");
48         log_level_initialized = 1;
49     }
50 }
51
52 void ZOOM_connection_remove_tasks(ZOOM_connection c);
53
54 void ZOOM_set_dset_error(ZOOM_connection c, int error,
55                          const char *dset,
56                          const char *addinfo, const char *addinfo2)
57 {
58     char *cp;
59
60     xfree(c->addinfo);
61     c->addinfo = 0;
62     c->error = error;
63     if (!c->diagset || strcmp(dset, c->diagset))
64     {
65         xfree(c->diagset);
66         c->diagset = xstrdup(dset);
67         /* remove integer part from SRW diagset .. */
68         if ((cp = strrchr(c->diagset, '/')))
69             *cp = '\0';
70     }
71     if (addinfo && addinfo2)
72     {
73         c->addinfo = (char*) xmalloc(strlen(addinfo) + strlen(addinfo2) + 3);
74         strcpy(c->addinfo, addinfo);
75         strcat(c->addinfo, ": ");
76         strcat(c->addinfo, addinfo2);
77     }
78     else if (addinfo)
79         c->addinfo = xstrdup(addinfo);
80     if (error != ZOOM_ERROR_NONE)
81     {
82         yaz_log(c->log_api, "%p set_dset_error %s %s:%d %s %s",
83                 c, c->host_port ? c->host_port : "<>", dset, error,
84                 addinfo ? addinfo : "",
85                 addinfo2 ? addinfo2 : "");
86         ZOOM_connection_remove_tasks(c);
87     }
88 }
89
90 int ZOOM_uri_to_code(const char *uri)
91 {
92     int code = 0;
93     const char *cp;
94     if ((cp = strrchr(uri, '/')))
95         code = atoi(cp+1);
96     return code;
97 }
98
99
100
101 void ZOOM_set_error(ZOOM_connection c, int error, const char *addinfo)
102 {
103     ZOOM_set_dset_error(c, error, "ZOOM", addinfo, 0);
104 }
105
106 static void clear_error(ZOOM_connection c)
107 {
108     /*
109      * If an error is tied to an operation then it's ok to clear: for
110      * example, a diagnostic returned from a search is cleared by a
111      * subsequent search.  However, problems such as Connection Lost
112      * or Init Refused are not cleared, because they are not
113      * recoverable: doing another search doesn't help.
114      */
115
116     ZOOM_connection_remove_events(c);
117     switch (c->error)
118     {
119     case ZOOM_ERROR_CONNECT:
120     case ZOOM_ERROR_MEMORY:
121     case ZOOM_ERROR_DECODE:
122     case ZOOM_ERROR_CONNECTION_LOST:
123     case ZOOM_ERROR_INIT:
124     case ZOOM_ERROR_INTERNAL:
125     case ZOOM_ERROR_UNSUPPORTED_PROTOCOL:
126         break;
127     default:
128         ZOOM_set_error(c, ZOOM_ERROR_NONE, 0);
129     }
130 }
131
132 void ZOOM_connection_show_task(ZOOM_task task)
133 {
134     switch(task->which)
135     {
136     case ZOOM_TASK_SEARCH:
137         yaz_log(YLOG_LOG, "search p=%p", task);
138         break;
139     case ZOOM_TASK_RETRIEVE:
140         yaz_log(YLOG_LOG, "retrieve p=%p", task);
141         break;
142     case ZOOM_TASK_CONNECT:
143         yaz_log(YLOG_LOG, "connect p=%p", task);
144         break;
145     case ZOOM_TASK_SCAN:
146         yaz_log(YLOG_LOG, "scan p=%p", task);
147         break;
148     }
149 }
150
151 void ZOOM_connection_show_tasks(ZOOM_connection c)
152 {
153     ZOOM_task task;
154     yaz_log(YLOG_LOG, "connection p=%p tasks", c);
155     for (task = c->tasks; task; task = task->next)
156         ZOOM_connection_show_task(task);
157 }
158
159 ZOOM_task ZOOM_connection_add_task(ZOOM_connection c, int which)
160 {
161     ZOOM_task *taskp = &c->tasks;
162     while (*taskp)
163         taskp = &(*taskp)->next;
164     *taskp = (ZOOM_task) xmalloc(sizeof(**taskp));
165     (*taskp)->running = 0;
166     (*taskp)->which = which;
167     (*taskp)->next = 0;
168     clear_error(c);
169     return *taskp;
170 }
171
172 ZOOM_API(int) ZOOM_connection_is_idle(ZOOM_connection c)
173 {
174     return c->tasks ? 0 : 1;
175 }
176
177 ZOOM_task ZOOM_connection_insert_task(ZOOM_connection c, int which)
178 {
179     ZOOM_task task = (ZOOM_task) xmalloc(sizeof(*task));
180
181     task->next = c->tasks;
182     c->tasks = task;
183
184     task->running = 0;
185     task->which = which;
186     return task;
187 }
188
189 void ZOOM_connection_remove_task(ZOOM_connection c)
190 {
191     ZOOM_task task = c->tasks;
192
193     if (task)
194     {
195         c->tasks = task->next;
196         switch (task->which)
197         {
198         case ZOOM_TASK_SEARCH:
199             resultset_destroy(task->u.search.resultset);
200             xfree(task->u.search.syntax);
201             xfree(task->u.search.elementSetName);
202             xfree(task->u.search.schema);
203             break;
204         case ZOOM_TASK_RETRIEVE:
205             resultset_destroy(task->u.retrieve.resultset);
206             xfree(task->u.retrieve.syntax);
207             xfree(task->u.retrieve.elementSetName);
208             xfree(task->u.retrieve.schema);
209             break;
210         case ZOOM_TASK_CONNECT:
211             break;
212         case ZOOM_TASK_SCAN:
213             ZOOM_scanset_destroy(task->u.scan.scan);
214             break;
215         case ZOOM_TASK_PACKAGE:
216             ZOOM_package_destroy(task->u.package);
217             break;
218         case ZOOM_TASK_SORT:
219             resultset_destroy(task->u.sort.resultset);
220             ZOOM_query_destroy(task->u.sort.q);
221             break;
222         default:
223             assert(0);
224         }
225         xfree(task);
226
227         if (!c->tasks)
228         {
229             ZOOM_Event event = ZOOM_Event_create(ZOOM_EVENT_END);
230             ZOOM_connection_put_event(c, event);
231         }
232     }
233 }
234
235 void ZOOM_connection_remove_tasks(ZOOM_connection c)
236 {
237     while (c->tasks)
238         ZOOM_connection_remove_task(c);
239 }
240
241 static void odr_wrbuf_write(ODR o, void *handle, int type,
242                             const char *buf, int len)
243 {
244     WRBUF w = (WRBUF) handle;
245     wrbuf_write(w, buf, len);
246 }
247
248 ZOOM_API(ZOOM_connection)
249     ZOOM_connection_create(ZOOM_options options)
250 {
251     ZOOM_connection c = (ZOOM_connection) xmalloc(sizeof(*c));
252
253     initlog();
254
255     c->log_api = log_api0;
256     c->log_details = log_details0;
257
258     yaz_log(c->log_api, "%p ZOOM_connection_create", c);
259
260     c->proto = PROTO_Z3950;
261     c->cs = 0;
262     ZOOM_connection_set_mask(c, 0);
263     c->reconnect_ok = 0;
264     c->state = STATE_IDLE;
265     c->addinfo = 0;
266     c->diagset = 0;
267     ZOOM_set_error(c, ZOOM_ERROR_NONE, 0);
268     c->buf_in = 0;
269     c->len_in = 0;
270     c->buf_out = 0;
271     c->len_out = 0;
272     c->resultsets = 0;
273
274     c->options = ZOOM_options_create_with_parent(options);
275
276     c->host_port = 0;
277     c->proxy = 0;
278     c->tproxy = 0;
279
280     c->charset = c->lang = 0;
281
282     c->cookie_out = 0;
283     c->cookie_in = 0;
284     c->client_IP = 0;
285     c->tasks = 0;
286
287     c->user = 0;
288     c->group = 0;
289     c->password = 0;
290     c->url_authentication = 0;
291
292     c->maximum_record_size = 0;
293     c->preferred_message_size = 0;
294
295     c->odr_in = odr_createmem(ODR_DECODE);
296     c->odr_out = odr_createmem(ODR_ENCODE);
297     c->odr_print = 0;
298     c->odr_save = 0;
299
300     c->async = 0;
301     c->support_named_resultsets = 0;
302     c->last_event = ZOOM_EVENT_NONE;
303
304     c->m_queue_front = 0;
305     c->m_queue_back = 0;
306
307     c->sru_version = 0;
308     c->no_redirects = 0;
309     c->saveAPDU_wrbuf = 0;
310     return c;
311 }
312
313 ZOOM_API(void) ZOOM_connection_save_apdu_wrbuf(ZOOM_connection c, WRBUF w)
314 {
315     if (c->odr_save)
316     {
317         odr_destroy(c->odr_save);
318         c->odr_save = 0;
319     }
320     if (w)
321     {
322         c->odr_save = odr_createmem(ODR_PRINT);
323         odr_set_stream(c->odr_save, w, odr_wrbuf_write, 0);
324     }
325 }
326
327 /* set database names. Take local databases (if set); otherwise
328    take databases given in ZURL (if set); otherwise use Default */
329 char **ZOOM_connection_get_databases(ZOOM_connection con, ZOOM_options options,
330                                      int *num, ODR odr)
331 {
332     char **databaseNames;
333     const char *cp = ZOOM_options_get(options, "databaseName");
334
335     if ((!cp || !*cp) && con->host_port)
336         cs_get_host_args(con->host_port, &cp);
337     if (!cp || !*cp)
338         cp = "Default";
339     nmem_strsplit(odr_getmem(odr), "+", cp,  &databaseNames, num);
340     return databaseNames;
341 }
342
343 ZOOM_API(ZOOM_connection)
344     ZOOM_connection_new(const char *host, int portnum)
345 {
346     ZOOM_connection c = ZOOM_connection_create(0);
347
348     ZOOM_connection_connect(c, host, portnum);
349     return c;
350 }
351
352 static zoom_sru_mode get_sru_mode_from_string(const char *s)
353 {
354     if (!s || !*s)
355         return zoom_sru_soap;
356     if (!yaz_matchstr(s, "soap"))
357         return zoom_sru_soap;
358     else if (!yaz_matchstr(s, "get"))
359         return zoom_sru_get;
360     else if (!yaz_matchstr(s, "post"))
361         return zoom_sru_post;
362     else if (!yaz_matchstr(s, "solr"))
363         return zoom_sru_solr;
364     return zoom_sru_error;
365 }
366
367 ZOOM_API(void)
368     ZOOM_connection_connect(ZOOM_connection c,
369                             const char *host, int portnum)
370 {
371     const char *val;
372
373     initlog();
374
375     yaz_log(c->log_api, "%p ZOOM_connection_connect host=%s portnum=%d",
376             c, host ? host : "null", portnum);
377
378     ZOOM_set_error(c, ZOOM_ERROR_NONE, 0);
379     ZOOM_connection_remove_tasks(c);
380
381     if (c->odr_print)
382     {
383         odr_setprint(c->odr_print, 0); /* prevent destroy from fclose'ing */
384         odr_destroy(c->odr_print);
385     }
386     if (ZOOM_options_get_bool(c->options, "apdulog", 0))
387     {
388         c->odr_print = odr_createmem(ODR_PRINT);
389         odr_setprint(c->odr_print, yaz_log_file());
390     }
391     else
392         c->odr_print = 0;
393
394     if (c->cs)
395     {
396         yaz_log(c->log_details, "%p ZOOM_connection_connect reconnect ok", c);
397         c->reconnect_ok = 1;
398         return;
399     }
400     yaz_log(c->log_details, "%p ZOOM_connection_connect connect", c);
401     xfree(c->proxy);
402     c->proxy = 0;
403     val = ZOOM_options_get(c->options, "proxy");
404     if (val && *val)
405     {
406         yaz_log(c->log_details, "%p ZOOM_connection_connect proxy=%s", c, val);
407         c->proxy = xstrdup(val);
408     }
409
410     xfree(c->tproxy);
411     c->tproxy = 0;
412     val = ZOOM_options_get(c->options, "tproxy");
413     if (val && *val)
414     {
415         yaz_log(c->log_details, "%p ZOOM_connection_connect tproxy=%s", c, val);
416         c->tproxy = xstrdup(val);
417     }
418
419     xfree(c->charset);
420     c->charset = 0;
421     val = ZOOM_options_get(c->options, "charset");
422     if (val && *val)
423     {
424         yaz_log(c->log_details, "%p ZOOM_connection_connect charset=%s", c, val);
425         c->charset = xstrdup(val);
426     }
427
428     xfree(c->lang);
429     val = ZOOM_options_get(c->options, "lang");
430     if (val && *val)
431     {
432         yaz_log(c->log_details, "%p ZOOM_connection_connect lang=%s", c, val);
433         c->lang = xstrdup(val);
434     }
435     else
436         c->lang = 0;
437
438     if (host)
439     {
440         xfree(c->host_port);
441         if (portnum)
442         {
443             char hostn[128];
444             sprintf(hostn, "%.80s:%d", host, portnum);
445             c->host_port = xstrdup(hostn);
446         }
447         else
448             c->host_port = xstrdup(host);
449     }
450
451     {
452         /*
453          * If the "<scheme>:" part of the host string is preceded by one
454          * or more comma-separated <name>=<value> pairs, these are taken
455          * to be options to be set on the connection object.  Among other
456          * applications, this facility can be used to embed authentication
457          * in a host string:
458          *          user=admin,password=secret,tcp:localhost:9999
459          */
460         char *remainder = c->host_port;
461         char *pcolon = strchr(remainder, ':');
462         char *pcomma;
463         char *pequals;
464         while ((pcomma = strchr(remainder, ',')) != 0 &&
465                (pcolon == 0 || pcomma < pcolon))
466         {
467             *pcomma = '\0';
468             if ((pequals = strchr(remainder, '=')) != 0)
469             {
470                 *pequals = '\0';
471                 ZOOM_connection_option_set(c, remainder, pequals+1);
472             }
473             remainder = pcomma+1;
474         }
475
476         if (remainder != c->host_port)
477         {
478             remainder = xstrdup(remainder);
479             xfree(c->host_port);
480             c->host_port = remainder;
481         }
482     }
483
484     val = ZOOM_options_get(c->options, "sru");
485     c->sru_mode = get_sru_mode_from_string(val);
486
487     xfree(c->sru_version);
488     val = ZOOM_options_get(c->options, "sru_version");
489     c->sru_version = xstrdup(val ? val : "1.2");
490
491     ZOOM_options_set(c->options, "host", c->host_port);
492
493     xfree(c->cookie_out);
494     c->cookie_out = 0;
495     val = ZOOM_options_get(c->options, "cookie");
496     if (val && *val)
497     {
498         yaz_log(c->log_details, "%p ZOOM_connection_connect cookie=%s", c, val);
499         c->cookie_out = xstrdup(val);
500     }
501
502     xfree(c->client_IP);
503     c->client_IP = 0;
504     val = ZOOM_options_get(c->options, "clientIP");
505     if (val && *val)
506     {
507         yaz_log(c->log_details, "%p ZOOM_connection_connect clientIP=%s",
508                 c, val);
509         c->client_IP = xstrdup(val);
510     }
511
512     xfree(c->group);
513     c->group = 0;
514     val = ZOOM_options_get(c->options, "group");
515     if (val && *val)
516         c->group = xstrdup(val);
517
518     xfree(c->user);
519     c->user = 0;
520     val = ZOOM_options_get(c->options, "user");
521     if (val && *val)
522         c->user = xstrdup(val);
523
524     xfree(c->password);
525     c->password = 0;
526     val = ZOOM_options_get(c->options, "password");
527     if (!val)
528         val = ZOOM_options_get(c->options, "pass");
529     if (val && *val)
530         c->password = xstrdup(val);
531
532     val = ZOOM_options_get(c->options, "authenticationMode");
533     if (val && !strcmp(val, "url"))
534         c->url_authentication = 1;
535     else
536         c->url_authentication = 0;
537
538     c->maximum_record_size =
539         ZOOM_options_get_int(c->options, "maximumRecordSize", 64*1024*1024);
540     c->preferred_message_size =
541         ZOOM_options_get_int(c->options, "preferredMessageSize", 64*1024*1024);
542
543     c->async = ZOOM_options_get_bool(c->options, "async", 0);
544
545     if (c->sru_mode == zoom_sru_error)
546     {
547         ZOOM_set_error(c, ZOOM_ERROR_UNSUPPORTED_PROTOCOL, val);
548         ZOOM_connection_remove_tasks(c);
549         return;
550     }
551
552     yaz_log(c->log_details, "%p ZOOM_connection_connect async=%d", c, c->async);
553     ZOOM_connection_add_task(c, ZOOM_TASK_CONNECT);
554
555     if (!c->async)
556     {
557         while (ZOOM_event(1, &c))
558             ;
559     }
560 }
561
562 ZOOM_API(void) ZOOM_resultset_release(ZOOM_resultset r)
563 {
564     if (r->connection)
565     {
566         /* remove ourselves from the resultsets in connection */
567         ZOOM_resultset *rp = &r->connection->resultsets;
568         while (1)
569         {
570             assert(*rp);   /* we must be in this list!! */
571             if (*rp == r)
572             {   /* OK, we're here - take us out of it */
573                 *rp = (*rp)->next;
574                 break;
575             }
576             rp = &(*rp)->next;
577         }
578         r->connection = 0;
579     }
580 }
581
582 ZOOM_API(void)
583     ZOOM_connection_destroy(ZOOM_connection c)
584 {
585     ZOOM_resultset r;
586     if (!c)
587         return;
588     yaz_log(c->log_api, "%p ZOOM_connection_destroy", c);
589     if (c->cs)
590         cs_close(c->cs);
591
592     for (r = c->resultsets; r; r = r->next)
593         r->connection = 0;
594
595     xfree(c->buf_in);
596     xfree(c->addinfo);
597     xfree(c->diagset);
598     odr_destroy(c->odr_in);
599     odr_destroy(c->odr_out);
600     if (c->odr_save)
601         odr_destroy(c->odr_save);
602     if (c->odr_print)
603     {
604         odr_setprint(c->odr_print, 0); /* prevent destroy from fclose'ing */
605         odr_destroy(c->odr_print);
606     }
607     ZOOM_options_destroy(c->options);
608     ZOOM_connection_remove_tasks(c);
609     ZOOM_connection_remove_events(c);
610     xfree(c->host_port);
611     xfree(c->proxy);
612     xfree(c->tproxy);
613     xfree(c->charset);
614     xfree(c->lang);
615     xfree(c->cookie_out);
616     xfree(c->cookie_in);
617     xfree(c->client_IP);
618     xfree(c->user);
619     xfree(c->group);
620     xfree(c->password);
621     xfree(c->sru_version);
622     wrbuf_destroy(c->saveAPDU_wrbuf);
623     xfree(c);
624 }
625
626 void ZOOM_resultset_addref(ZOOM_resultset r)
627 {
628     if (r)
629     {
630         yaz_mutex_enter(r->mutex);
631         (r->refcount)++;
632         yaz_log(log_details0, "%p ZOOM_resultset_addref count=%d",
633                 r, r->refcount);
634         yaz_mutex_leave(r->mutex);
635     }
636 }
637
638 static int g_resultsets = 0;
639 static YAZ_MUTEX g_resultset_mutex = 0;
640
641 /* TODO We need to initialize this before running threaded:
642  * call resultset_use(0)  */
643
644 static int resultset_use(int delta) {
645     int resultset_count;
646     if (g_resultset_mutex == 0)
647         yaz_mutex_create(&g_resultset_mutex);
648     yaz_mutex_enter(g_resultset_mutex);
649     g_resultsets += delta;
650     resultset_count = g_resultsets;
651     yaz_mutex_leave(g_resultset_mutex);
652     return resultset_count;
653 }
654
655 int resultsets_count(void) {
656     return resultset_use(0);
657 }
658
659 ZOOM_resultset ZOOM_resultset_create(void)
660 {
661     int i;
662     ZOOM_resultset r = (ZOOM_resultset) xmalloc(sizeof(*r));
663
664     initlog();
665
666     yaz_log(log_details0, "%p ZOOM_resultset_create", r);
667     r->refcount = 1;
668     r->size = 0;
669     r->odr = odr_createmem(ODR_ENCODE);
670     r->piggyback = 1;
671     r->setname = 0;
672     r->step = 0;
673     for (i = 0; i<RECORD_HASH_SIZE; i++)
674         r->record_hash[i] = 0;
675     r->r_sort_spec = 0;
676     r->query = 0;
677     r->connection = 0;
678     r->databaseNames = 0;
679     r->num_databaseNames = 0;
680     r->facets = 0;
681     r->num_facets = 0;
682     r->facets_names = 0;
683     r->mutex = 0;
684     yaz_mutex_create(&r->mutex);
685 #if SHPTR
686     {
687         WRBUF w = wrbuf_alloc();
688         YAZ_SHPTR_INIT(r->record_wrbuf, w);
689     }
690 #endif
691     resultset_use(1);
692     return r;
693 }
694
695 ZOOM_API(ZOOM_resultset)
696     ZOOM_connection_search_pqf(ZOOM_connection c, const char *q)
697 {
698     ZOOM_resultset r;
699     ZOOM_query s = ZOOM_query_create();
700
701     ZOOM_query_prefix(s, q);
702
703     r = ZOOM_connection_search(c, s);
704     ZOOM_query_destroy(s);
705     return r;
706 }
707
708 ZOOM_API(ZOOM_resultset)
709     ZOOM_connection_search(ZOOM_connection c, ZOOM_query q)
710 {
711     ZOOM_resultset r = ZOOM_resultset_create();
712     ZOOM_task task;
713     const char *cp;
714     int start, count;
715     const char *syntax, *elementSetName, *schema;
716
717     yaz_log(c->log_api, "%p ZOOM_connection_search set %p query %p", c, r, q);
718     r->r_sort_spec = ZOOM_query_get_sortspec(q);
719     r->query = q;
720
721     r->options = ZOOM_options_create_with_parent(c->options);
722
723     start = ZOOM_options_get_int(r->options, "start", 0);
724     count = ZOOM_options_get_int(r->options, "count", 0);
725     {
726         /* If "presentChunk" is defined use that; otherwise "step" */
727         const char *cp = ZOOM_options_get(r->options, "presentChunk");
728         r->step = ZOOM_options_get_int(r->options,
729                                        (cp != 0 ? "presentChunk": "step"), 0);
730     }
731     r->piggyback = ZOOM_options_get_bool(r->options, "piggyback", 1);
732     cp = ZOOM_options_get(r->options, "setname");
733     if (cp)
734         r->setname = xstrdup(cp);
735
736     r->databaseNames = ZOOM_connection_get_databases(c, c->options, &r->num_databaseNames,
737                                          r->odr);
738
739     r->connection = c;
740     r->next = c->resultsets;
741     c->resultsets = r;
742     if (c->host_port && c->proto == PROTO_HTTP)
743     {
744         if (!c->cs)
745         {
746             yaz_log(c->log_details, "ZOOM_connection_search: no comstack");
747             ZOOM_connection_add_task(c, ZOOM_TASK_CONNECT);
748         }
749         else
750         {
751             yaz_log(c->log_details, "ZOOM_connection_search: reconnect");
752             c->reconnect_ok = 1;
753         }
754     }
755
756     task = ZOOM_connection_add_task(c, ZOOM_TASK_SEARCH);
757     task->u.search.resultset = r;
758     task->u.search.start = start;
759     task->u.search.count = count;
760     task->u.search.recv_search_fired = 0;
761
762     syntax = ZOOM_options_get(r->options, "preferredRecordSyntax");
763     task->u.search.syntax = syntax ? xstrdup(syntax) : 0;
764     elementSetName = ZOOM_options_get(r->options, "elementSetName");
765     task->u.search.elementSetName = elementSetName ?
766         xstrdup(elementSetName) : 0;
767     schema = ZOOM_options_get(r->options, "schema");
768     task->u.search.schema = schema ? xstrdup(schema) : 0;
769
770     ZOOM_resultset_addref(r);
771
772     ZOOM_query_addref(q);
773
774     if (!c->async)
775     {
776         while (ZOOM_event(1, &c))
777             ;
778     }
779     return r;
780 }
781
782 ZOOM_API(void)
783     ZOOM_resultset_sort(ZOOM_resultset r,
784                          const char *sort_type, const char *sort_spec)
785 {
786     (void) ZOOM_resultset_sort1(r, sort_type, sort_spec);
787 }
788
789 ZOOM_API(int)
790     ZOOM_resultset_sort1(ZOOM_resultset r,
791                          const char *sort_type, const char *sort_spec)
792 {
793     ZOOM_connection c = r->connection;
794     ZOOM_task task;
795     ZOOM_query newq;
796
797     newq = ZOOM_query_create();
798     if (ZOOM_query_sortby(newq, sort_spec) < 0)
799         return -1;
800
801     yaz_log(c->log_api, "%p ZOOM_resultset_sort r=%p sort_type=%s sort_spec=%s",
802             r, r, sort_type, sort_spec);
803     if (!c)
804         return 0;
805
806     if (c->host_port && c->proto == PROTO_HTTP)
807     {
808         if (!c->cs)
809         {
810             yaz_log(c->log_details, "%p ZOOM_resultset_sort: no comstack", r);
811             ZOOM_connection_add_task(c, ZOOM_TASK_CONNECT);
812         }
813         else
814         {
815             yaz_log(c->log_details, "%p ZOOM_resultset_sort: prepare reconnect",
816                     r);
817             c->reconnect_ok = 1;
818         }
819     }
820
821     ZOOM_resultset_cache_reset(r);
822     task = ZOOM_connection_add_task(c, ZOOM_TASK_SORT);
823     task->u.sort.resultset = r;
824     task->u.sort.q = newq;
825
826     ZOOM_resultset_addref(r);
827
828     if (!c->async)
829     {
830         while (ZOOM_event(1, &c))
831             ;
832     }
833
834     return 0;
835 }
836
837 ZOOM_API(void)
838     ZOOM_resultset_destroy(ZOOM_resultset r)
839 {
840     resultset_destroy(r);
841 }
842
843 static void resultset_destroy(ZOOM_resultset r)
844 {
845     if (!r)
846         return;
847     yaz_mutex_enter(r->mutex);
848     (r->refcount)--;
849     yaz_log(log_details0, "%p ZOOM_resultset_destroy r=%p count=%d",
850             r, r, r->refcount);
851     if (r->refcount == 0)
852     {
853         yaz_mutex_leave(r->mutex);
854
855         yaz_log(log_details0, "%p ZOOM_connection resultset_destroy: Deleting resultset (%p) ", r->connection, r);
856         ZOOM_resultset_cache_reset(r);
857         ZOOM_resultset_release(r);
858         ZOOM_query_destroy(r->query);
859         ZOOM_options_destroy(r->options);
860         odr_destroy(r->odr);
861         xfree(r->setname);
862         yaz_mutex_destroy(&r->mutex);
863 #if SHPTR
864         YAZ_SHPTR_DEC(r->record_wrbuf, wrbuf_destroy);
865 #endif
866         resultset_use(-1);
867         xfree(r);
868     }
869     else
870         yaz_mutex_leave(r->mutex);
871 }
872
873 ZOOM_API(size_t)
874     ZOOM_resultset_size(ZOOM_resultset r)
875 {
876     return r->size;
877 }
878
879 int ZOOM_test_reconnect(ZOOM_connection c)
880 {
881     ZOOM_Event event;
882
883     if (!c->reconnect_ok)
884         return 0;
885     ZOOM_connection_close(c);
886     c->reconnect_ok = 0;
887     c->tasks->running = 0;
888     ZOOM_connection_insert_task(c, ZOOM_TASK_CONNECT);
889
890     event = ZOOM_Event_create(ZOOM_EVENT_CONNECT);
891     ZOOM_connection_put_event(c, event);
892
893     return 1;
894 }
895
896 static void ZOOM_resultset_retrieve(ZOOM_resultset r,
897                                     int force_sync, int start, int count)
898 {
899     ZOOM_task task;
900     ZOOM_connection c;
901     const char *cp;
902     const char *syntax, *elementSetName;
903
904     if (!r)
905         return;
906     yaz_log(log_details0, "%p ZOOM_resultset_retrieve force_sync=%d start=%d"
907             " count=%d", r, force_sync, start, count);
908     c = r->connection;
909     if (!c)
910         return;
911
912     if (c->host_port && c->proto == PROTO_HTTP)
913     {
914         if (!c->cs)
915         {
916             yaz_log(log_details0, "%p ZOOM_resultset_retrieve: no comstack", r);
917             ZOOM_connection_add_task(c, ZOOM_TASK_CONNECT);
918         }
919         else
920         {
921             yaz_log(log_details0, "%p ZOOM_resultset_retrieve: prepare "
922                     "reconnect", r);
923             c->reconnect_ok = 1;
924         }
925     }
926     task = ZOOM_connection_add_task(c, ZOOM_TASK_RETRIEVE);
927     task->u.retrieve.resultset = r;
928     task->u.retrieve.start = start;
929     task->u.retrieve.count = count;
930
931     syntax = ZOOM_options_get(r->options, "preferredRecordSyntax");
932     task->u.retrieve.syntax = syntax ? xstrdup(syntax) : 0;
933     elementSetName = ZOOM_options_get(r->options, "elementSetName");
934     task->u.retrieve.elementSetName = elementSetName
935         ? xstrdup(elementSetName) : 0;
936
937     cp = ZOOM_options_get(r->options, "schema");
938     task->u.retrieve.schema = cp ? xstrdup(cp) : 0;
939
940     ZOOM_resultset_addref(r);
941
942     if (!r->connection->async || force_sync)
943         while (r->connection && ZOOM_event(1, &r->connection))
944             ;
945 }
946
947 ZOOM_API(void)
948     ZOOM_resultset_records(ZOOM_resultset r, ZOOM_record *recs,
949                            size_t start, size_t count)
950 {
951     int force_present = 0;
952
953     if (!r)
954         return ;
955     yaz_log(log_api0, "%p ZOOM_resultset_records r=%p start=%ld count=%ld",
956             r, r, (long) start, (long) count);
957     if (count && recs)
958         force_present = 1;
959     ZOOM_resultset_retrieve(r, force_present, start, count);
960     if (force_present)
961     {
962         size_t i;
963         for (i = 0; i< count; i++)
964             recs[i] = ZOOM_resultset_record_immediate(r, i+start);
965     }
966 }
967
968 ZOOM_API(size_t)
969     ZOOM_resultset_facets_size(ZOOM_resultset r) {
970     return r->num_facets;
971 }
972
973 ZOOM_API(ZOOM_facet_field)
974     ZOOM_resultset_get_facet_field(ZOOM_resultset r, const char *name) {
975     int num = r->num_facets;
976     ZOOM_facet_field *facets = r->facets;
977     int index;
978     for (index = 0; index < num; index++) {
979         if (!strcmp(facets[index]->facet_name, name)) {
980             return facets[index];
981         }
982     }
983     return 0;
984 }
985
986 ZOOM_API(ZOOM_facet_field)
987     ZOOM_resultset_get_facet_field_by_index(ZOOM_resultset r, int index) {
988     int num = r->num_facets;
989     ZOOM_facet_field *facets = r->facets;
990     if (index >= 0 && index < num) {
991         return facets[index];
992     }
993     return 0;
994 }
995
996 ZOOM_API(ZOOM_facet_field *)
997     ZOOM_resultset_facets(ZOOM_resultset r)
998 {
999     return r->facets;
1000 }
1001
1002 ZOOM_API(const char**)
1003     ZOOM_resultset_facets_names(ZOOM_resultset r)
1004 {
1005     return (const char **) r->facets_names;
1006 }
1007
1008 ZOOM_API(const char*)
1009     ZOOM_facet_field_name(ZOOM_facet_field field)
1010 {
1011     return field->facet_name;
1012 }
1013
1014 ZOOM_API(size_t)
1015     ZOOM_facet_field_term_count(ZOOM_facet_field field)
1016 {
1017     return field->num_terms;
1018 }
1019
1020 ZOOM_API(const char*)
1021     ZOOM_facet_field_get_term(ZOOM_facet_field field, size_t idx, int *freq) {
1022     *freq = field->facet_terms[idx].frequency;
1023     return field->facet_terms[idx].term;
1024 }
1025
1026
1027 static void get_cert(ZOOM_connection c)
1028 {
1029     char *cert_buf;
1030     int cert_len;
1031
1032     if (cs_get_peer_certificate_x509(c->cs, &cert_buf, &cert_len))
1033     {
1034         ZOOM_connection_option_setl(c, "sslPeerCert",
1035                                     cert_buf, cert_len);
1036         xfree(cert_buf);
1037     }
1038 }
1039
1040 static zoom_ret do_connect_host(ZOOM_connection c,
1041                                 const char *logical_url);
1042
1043 static zoom_ret do_connect(ZOOM_connection c)
1044 {
1045     return do_connect_host(c, c->host_port);
1046 }
1047
1048 static zoom_ret do_connect_host(ZOOM_connection c, const char *logical_url)
1049 {
1050     void *add;
1051
1052     if (c->cs)
1053         cs_close(c->cs);
1054     c->cs = cs_create_host_proxy(logical_url, 0, &add,
1055                                  c->tproxy ? c->tproxy : c->proxy);
1056
1057     if (c->cs && c->cs->protocol == PROTO_HTTP)
1058     {
1059 #if YAZ_HAVE_XML2
1060         c->proto = PROTO_HTTP;
1061 #else
1062         ZOOM_set_error(c, ZOOM_ERROR_UNSUPPORTED_PROTOCOL, "SRW");
1063         ZOOM_connection_close(c);
1064         return zoom_complete;
1065 #endif
1066     }
1067     if (c->cs)
1068     {
1069         int ret = cs_connect(c->cs, add);
1070         if (ret == 0)
1071         {
1072             ZOOM_Event event = ZOOM_Event_create(ZOOM_EVENT_CONNECT);
1073             ZOOM_connection_put_event(c, event);
1074             get_cert(c);
1075             if (c->proto == PROTO_Z3950)
1076                 ZOOM_connection_Z3950_send_init(c);
1077             else
1078             {
1079                 /* no init request for SRW .. */
1080                 assert(c->tasks->which == ZOOM_TASK_CONNECT);
1081                 ZOOM_connection_remove_task(c);
1082                 ZOOM_connection_set_mask(c, 0);
1083                 ZOOM_connection_exec_task(c);
1084             }
1085             c->state = STATE_ESTABLISHED;
1086             return zoom_pending;
1087         }
1088         else if (ret > 0)
1089         {
1090             int mask = ZOOM_SELECT_EXCEPT;
1091             if (c->cs->io_pending & CS_WANT_WRITE)
1092                 mask += ZOOM_SELECT_WRITE;
1093             if (c->cs->io_pending & CS_WANT_READ)
1094                 mask += ZOOM_SELECT_READ;
1095             ZOOM_connection_set_mask(c, mask);
1096             c->state = STATE_CONNECTING;
1097             return zoom_pending;
1098         }
1099     }
1100     c->state = STATE_IDLE;
1101     ZOOM_set_error(c, ZOOM_ERROR_CONNECT, logical_url);
1102     return zoom_complete;
1103 }
1104
1105 /* returns 1 if PDU was sent OK (still pending )
1106    0 if PDU was not sent OK (nothing to wait for)
1107 */
1108
1109 ZOOM_API(ZOOM_record)
1110     ZOOM_resultset_record_immediate(ZOOM_resultset s,size_t pos)
1111 {
1112     const char *syntax =
1113         ZOOM_options_get(s->options, "preferredRecordSyntax");
1114     const char *elementSetName =
1115         ZOOM_options_get(s->options, "elementSetName");
1116     const char *schema =
1117         ZOOM_options_get(s->options, "schema");
1118
1119     return ZOOM_record_cache_lookup(s, pos, syntax, elementSetName, schema);
1120 }
1121
1122 ZOOM_API(ZOOM_record)
1123     ZOOM_resultset_record(ZOOM_resultset r, size_t pos)
1124 {
1125     ZOOM_record rec = ZOOM_resultset_record_immediate(r, pos);
1126
1127     if (!rec)
1128     {
1129         /*
1130          * MIKE: I think force_sync should always be zero, but I don't
1131          * want to make this change until I get the go-ahead from
1132          * Adam, in case something depends on the old synchronous
1133          * behaviour.
1134          */
1135         int force_sync = 1;
1136         if (getenv("ZOOM_RECORD_NO_FORCE_SYNC")) force_sync = 0;
1137         ZOOM_resultset_retrieve(r, force_sync, pos, 1);
1138         rec = ZOOM_resultset_record_immediate(r, pos);
1139     }
1140     return rec;
1141 }
1142
1143 ZOOM_API(ZOOM_scanset)
1144     ZOOM_connection_scan(ZOOM_connection c, const char *start)
1145 {
1146     ZOOM_scanset s;
1147     ZOOM_query q = ZOOM_query_create();
1148
1149     ZOOM_query_prefix(q, start);
1150
1151     s = ZOOM_connection_scan1(c, q);
1152     ZOOM_query_destroy(q);
1153     return s;
1154
1155 }
1156
1157 ZOOM_API(ZOOM_scanset)
1158     ZOOM_connection_scan1(ZOOM_connection c, ZOOM_query q)
1159 {
1160     ZOOM_scanset scan = 0;
1161     Z_Query *z_query = ZOOM_query_get_Z_Query(q);
1162
1163     if (!z_query)
1164         return 0;
1165     scan = (ZOOM_scanset) xmalloc(sizeof(*scan));
1166     scan->connection = c;
1167     scan->odr = odr_createmem(ODR_DECODE);
1168     scan->options = ZOOM_options_create_with_parent(c->options);
1169     scan->refcount = 1;
1170     scan->scan_response = 0;
1171     scan->srw_scan_response = 0;
1172
1173     scan->query = q;
1174     ZOOM_query_addref(q);
1175     scan->databaseNames = ZOOM_connection_get_databases(c, c->options,
1176                                             &scan->num_databaseNames,
1177                                             scan->odr);
1178
1179     if (1)
1180     {
1181         ZOOM_task task = ZOOM_connection_add_task(c, ZOOM_TASK_SCAN);
1182         task->u.scan.scan = scan;
1183
1184         (scan->refcount)++;
1185         if (!c->async)
1186         {
1187             while (ZOOM_event(1, &c))
1188                 ;
1189         }
1190     }
1191     return scan;
1192 }
1193
1194 ZOOM_API(void)
1195     ZOOM_scanset_destroy(ZOOM_scanset scan)
1196 {
1197     if (!scan)
1198         return;
1199     (scan->refcount)--;
1200     if (scan->refcount == 0)
1201     {
1202         ZOOM_query_destroy(scan->query);
1203
1204         odr_destroy(scan->odr);
1205
1206         ZOOM_options_destroy(scan->options);
1207         xfree(scan);
1208     }
1209 }
1210
1211 static zoom_ret send_package(ZOOM_connection c)
1212 {
1213     ZOOM_Event event;
1214
1215     yaz_log(c->log_details, "%p send_package", c);
1216     if (!c->tasks)
1217         return zoom_complete;
1218     assert (c->tasks->which == ZOOM_TASK_PACKAGE);
1219
1220     event = ZOOM_Event_create(ZOOM_EVENT_SEND_APDU);
1221     ZOOM_connection_put_event(c, event);
1222
1223     c->buf_out = c->tasks->u.package->buf_out;
1224     c->len_out = c->tasks->u.package->len_out;
1225
1226     return ZOOM_send_buf(c);
1227 }
1228
1229 ZOOM_API(size_t)
1230     ZOOM_scanset_size(ZOOM_scanset scan)
1231 {
1232     if (!scan)
1233         return 0;
1234
1235     if (scan->scan_response && scan->scan_response->entries)
1236         return scan->scan_response->entries->num_entries;
1237     else if (scan->srw_scan_response)
1238         return scan->srw_scan_response->num_terms;
1239     return 0;
1240 }
1241
1242 static void ZOOM_scanset_term_x(ZOOM_scanset scan, size_t pos,
1243                                 size_t *occ,
1244                                 const char **value_term, size_t *value_len,
1245                                 const char **disp_term, size_t *disp_len)
1246 {
1247     size_t noent = ZOOM_scanset_size(scan);
1248
1249     *value_term = 0;
1250     *value_len = 0;
1251
1252     *disp_term = 0;
1253     *disp_len = 0;
1254
1255     *occ = 0;
1256     if (pos >= noent)
1257         return;
1258     if (scan->scan_response)
1259     {
1260         Z_ScanResponse *res = scan->scan_response;
1261         if (res->entries->entries[pos]->which == Z_Entry_termInfo)
1262         {
1263             Z_TermInfo *t = res->entries->entries[pos]->u.termInfo;
1264
1265             *value_term = (const char *) t->term->u.general->buf;
1266             *value_len = t->term->u.general->len;
1267             if (t->displayTerm)
1268             {
1269                 *disp_term = t->displayTerm;
1270                 *disp_len = strlen(*disp_term);
1271             }
1272             else if (t->term->which == Z_Term_general)
1273             {
1274                 *disp_term = (const char *) t->term->u.general->buf;
1275                 *disp_len = t->term->u.general->len;
1276             }
1277             *occ = t->globalOccurrences ? *t->globalOccurrences : 0;
1278         }
1279     }
1280     if (scan->srw_scan_response)
1281     {
1282         Z_SRW_scanResponse *res = scan->srw_scan_response;
1283         Z_SRW_scanTerm *t = res->terms + pos;
1284         if (t)
1285         {
1286             *value_term = t->value;
1287             *value_len = strlen(*value_term);
1288
1289             if (t->displayTerm)
1290                 *disp_term = t->displayTerm;
1291             else
1292                 *disp_term = t->value;
1293             *disp_len = strlen(*disp_term);
1294             *occ = t->numberOfRecords ? *t->numberOfRecords : 0;
1295         }
1296     }
1297 }
1298
1299 ZOOM_API(const char *)
1300     ZOOM_scanset_term(ZOOM_scanset scan, size_t pos,
1301                       size_t *occ, size_t *len)
1302 {
1303     const char *value_term = 0;
1304     size_t value_len = 0;
1305     const char *disp_term = 0;
1306     size_t disp_len = 0;
1307
1308     ZOOM_scanset_term_x(scan, pos, occ, &value_term, &value_len,
1309                         &disp_term, &disp_len);
1310
1311     *len = value_len;
1312     return value_term;
1313 }
1314
1315 ZOOM_API(const char *)
1316     ZOOM_scanset_display_term(ZOOM_scanset scan, size_t pos,
1317                               size_t *occ, size_t *len)
1318 {
1319     const char *value_term = 0;
1320     size_t value_len = 0;
1321     const char *disp_term = 0;
1322     size_t disp_len = 0;
1323
1324     ZOOM_scanset_term_x(scan, pos, occ, &value_term, &value_len,
1325                         &disp_term, &disp_len);
1326
1327     *len = disp_len;
1328     return disp_term;
1329 }
1330
1331 ZOOM_API(const char *)
1332     ZOOM_scanset_option_get(ZOOM_scanset scan, const char *key)
1333 {
1334     return ZOOM_options_get(scan->options, key);
1335 }
1336
1337 ZOOM_API(void)
1338     ZOOM_scanset_option_set(ZOOM_scanset scan, const char *key,
1339                             const char *val)
1340 {
1341     ZOOM_options_set(scan->options, key, val);
1342 }
1343
1344
1345 ZOOM_API(ZOOM_package)
1346     ZOOM_connection_package(ZOOM_connection c, ZOOM_options options)
1347 {
1348     ZOOM_package p = (ZOOM_package) xmalloc(sizeof(*p));
1349
1350     p->connection = c;
1351     p->odr_out = odr_createmem(ODR_ENCODE);
1352     p->options = ZOOM_options_create_with_parent2(options, c->options);
1353     p->refcount = 1;
1354     p->buf_out = 0;
1355     p->len_out = 0;
1356     return p;
1357 }
1358
1359 ZOOM_API(void)
1360     ZOOM_package_destroy(ZOOM_package p)
1361 {
1362     if (!p)
1363         return;
1364     (p->refcount)--;
1365     if (p->refcount == 0)
1366     {
1367         odr_destroy(p->odr_out);
1368         xfree(p->buf_out);
1369
1370         ZOOM_options_destroy(p->options);
1371         xfree(p);
1372     }
1373 }
1374
1375 ZOOM_API(const char *)
1376     ZOOM_package_option_get(ZOOM_package p, const char *key)
1377 {
1378     return ZOOM_options_get(p->options, key);
1379 }
1380
1381 ZOOM_API(const char *)
1382     ZOOM_package_option_getl(ZOOM_package p, const char *key, int *lenp)
1383 {
1384     return ZOOM_options_getl(p->options, key, lenp);
1385 }
1386
1387 ZOOM_API(void)
1388     ZOOM_package_option_set(ZOOM_package p, const char *key,
1389                             const char *val)
1390 {
1391     ZOOM_options_set(p->options, key, val);
1392 }
1393
1394 ZOOM_API(void)
1395     ZOOM_package_option_setl(ZOOM_package p, const char *key,
1396                              const char *val, int len)
1397 {
1398     ZOOM_options_setl(p->options, key, val, len);
1399 }
1400
1401 ZOOM_API(int)
1402     ZOOM_connection_exec_task(ZOOM_connection c)
1403 {
1404     ZOOM_task task = c->tasks;
1405     zoom_ret ret = zoom_complete;
1406
1407     if (!task)
1408         return 0;
1409     yaz_log(c->log_details, "%p ZOOM_connection_exec_task type=%d run=%d",
1410             c, task->which, task->running);
1411     if (c->error != ZOOM_ERROR_NONE)
1412     {
1413         yaz_log(c->log_details, "%p ZOOM_connection_exec_task "
1414                 "removing tasks because of error = %d", c, c->error);
1415         ZOOM_connection_remove_tasks(c);
1416         return 0;
1417     }
1418     if (task->running)
1419     {
1420         yaz_log(c->log_details, "%p ZOOM_connection_exec_task "
1421                 "task already running", c);
1422         return 0;
1423     }
1424     task->running = 1;
1425     ret = zoom_complete;
1426     if (c->cs || task->which == ZOOM_TASK_CONNECT)
1427     {
1428         switch (task->which)
1429         {
1430         case ZOOM_TASK_SEARCH:
1431             if (c->proto == PROTO_HTTP)
1432                 ret = ZOOM_connection_srw_send_search(c);
1433             else
1434                 ret = ZOOM_connection_Z3950_send_search(c);
1435             break;
1436         case ZOOM_TASK_RETRIEVE:
1437             if (c->proto == PROTO_HTTP)
1438                 ret = ZOOM_connection_srw_send_search(c);
1439             else
1440                 ret = send_Z3950_present(c);
1441             break;
1442         case ZOOM_TASK_CONNECT:
1443             ret = do_connect(c);
1444             break;
1445         case ZOOM_TASK_SCAN:
1446             if (c->proto == PROTO_HTTP)
1447                 ret = ZOOM_connection_srw_send_scan(c);
1448             else
1449                 ret = ZOOM_connection_Z3950_send_scan(c);
1450             break;
1451         case ZOOM_TASK_PACKAGE:
1452             ret = send_package(c);
1453             break;
1454         case ZOOM_TASK_SORT:
1455             c->tasks->u.sort.resultset->r_sort_spec =
1456                 ZOOM_query_get_sortspec(c->tasks->u.sort.q);
1457             ret = send_Z3950_sort(c, c->tasks->u.sort.resultset);
1458             break;
1459         }
1460     }
1461     else
1462     {
1463         yaz_log(c->log_details, "%p ZOOM_connection_exec_task "
1464                 "remove tasks because no connection exist", c);
1465         ZOOM_connection_remove_tasks(c);
1466     }
1467     if (ret == zoom_complete)
1468     {
1469         yaz_log(c->log_details, "%p ZOOM_connection_exec_task "
1470                 "task removed (complete)", c);
1471         ZOOM_connection_remove_task(c);
1472         return 0;
1473     }
1474     yaz_log(c->log_details, "%p ZOOM_connection_exec_task "
1475             "task pending", c);
1476     return 1;
1477 }
1478
1479 #if YAZ_HAVE_XML2
1480
1481 static zoom_ret send_HTTP_redirect(ZOOM_connection c, const char *uri,
1482                                   Z_HTTP_Response *cookie_hres)
1483 {
1484     struct Z_HTTP_Header *h;
1485     char *combined_cookies = 0;
1486     int combined_cookies_len = 0;
1487     Z_GDU *gdu = z_get_HTTP_Request_uri(c->odr_out, uri, 0, c->proxy ? 1 : 0);
1488
1489     gdu->u.HTTP_Request->method = odr_strdup(c->odr_out, "GET");
1490     z_HTTP_header_add(c->odr_out, &gdu->u.HTTP_Request->headers, "Accept",
1491                       "text/xml");
1492
1493     for (h = cookie_hres->headers; h; h = h->next)
1494     {
1495         if (!strcmp(h->name, "Set-Cookie"))
1496         {
1497             char *cp;
1498
1499             if (!(cp = strchr(h->value, ';')))
1500                 cp = h->value + strlen(h->value);
1501             if (cp - h->value >= 1) {
1502                 combined_cookies = xrealloc(combined_cookies, combined_cookies_len + cp - h->value + 3);
1503                 memcpy(combined_cookies+combined_cookies_len, h->value, cp - h->value);
1504                 combined_cookies[combined_cookies_len + cp - h->value] = '\0';
1505                 strcat(combined_cookies,"; ");
1506                 combined_cookies_len = strlen(combined_cookies);
1507             }
1508         }
1509     }
1510
1511     if (combined_cookies_len)
1512     {
1513         z_HTTP_header_add(c->odr_out, &gdu->u.HTTP_Request->headers,
1514                           "Cookie", combined_cookies);
1515         xfree(combined_cookies);
1516     }
1517
1518     if (c->user && c->password)
1519     {
1520         z_HTTP_header_add_basic_auth(c->odr_out, &gdu->u.HTTP_Request->headers,
1521                                      c->user, c->password);
1522     }
1523     return ZOOM_send_GDU(c, gdu);
1524 }
1525
1526 zoom_ret ZOOM_send_GDU(ZOOM_connection c, Z_GDU *gdu)
1527 {
1528     ZOOM_Event event;
1529
1530     int r = z_GDU(c->odr_out, &gdu, 0, 0);
1531     if (!r)
1532         return zoom_complete;
1533     if (c->odr_print)
1534         z_GDU(c->odr_print, &gdu, 0, 0);
1535     if (c->odr_save)
1536         z_GDU(c->odr_save, &gdu, 0, 0);
1537     c->buf_out = odr_getbuf(c->odr_out, &c->len_out, 0);
1538     odr_reset(c->odr_out);
1539
1540     event = ZOOM_Event_create(ZOOM_EVENT_SEND_APDU);
1541     ZOOM_connection_put_event(c, event);
1542
1543     return ZOOM_send_buf(c);
1544 }
1545
1546 #if YAZ_HAVE_XML2
1547 void ZOOM_set_HTTP_error(ZOOM_connection c, int error,
1548                          const char *addinfo, const char *addinfo2)
1549 {
1550     ZOOM_set_dset_error(c, error, "HTTP", addinfo, addinfo2);
1551 }
1552 #endif
1553
1554
1555 static void handle_http(ZOOM_connection c, Z_HTTP_Response *hres)
1556 {
1557     zoom_ret cret = zoom_complete;
1558     int ret = -1;
1559     char *addinfo = 0;
1560     const char *connection_head = z_HTTP_header_lookup(hres->headers,
1561                                                        "Connection");
1562     const char *location;
1563
1564     ZOOM_connection_set_mask(c, 0);
1565     yaz_log(c->log_details, "%p handle_http", c);
1566
1567     if ((hres->code == 301 || hres->code == 302) && c->sru_mode == zoom_sru_get
1568         && (location = z_HTTP_header_lookup(hres->headers, "Location")))
1569     {
1570         c->no_redirects++;
1571         if (c->no_redirects > 10)
1572         {
1573             ZOOM_set_HTTP_error(c, hres->code, 0, 0);
1574             c->no_redirects = 0;
1575             ZOOM_connection_close(c);
1576         }
1577         else
1578         {
1579             /* since redirect may change host we just reconnect. A smarter
1580                implementation might check whether it's the same server */
1581             do_connect_host(c, location);
1582             send_HTTP_redirect(c, location, hres);
1583             /* we're OK for now. Operation is not really complete */
1584             return;
1585         }
1586     }
1587     else
1588     {
1589         ret = ZOOM_handle_sru(c, hres, &cret, &addinfo);
1590         if (ret == 0)
1591         {
1592             if (c->no_redirects) /* end of redirect. change hosts again */
1593                 ZOOM_connection_close(c);
1594         }
1595         c->no_redirects = 0;
1596     }
1597     if (ret)
1598     {
1599         if (hres->code != 200)
1600             ZOOM_set_HTTP_error(c, hres->code, 0, 0);
1601         else
1602         {
1603             yaz_log(YLOG_LOG, "set error... addinfo=%s", addinfo ?
1604                     addinfo : "NULL");
1605             ZOOM_set_error(c, ZOOM_ERROR_DECODE, addinfo);
1606         }
1607         ZOOM_connection_close(c);
1608     }
1609     if (cret == zoom_complete)
1610     {
1611         yaz_log(c->log_details, "removing tasks in handle_http");
1612         ZOOM_connection_remove_task(c);
1613     }
1614     {
1615         int must_close = 0;
1616         if (!strcmp(hres->version, "1.0"))
1617         {
1618             /* HTTP 1.0: only if Keep-Alive we stay alive.. */
1619             if (!connection_head || strcmp(connection_head, "Keep-Alive"))
1620                 must_close = 1;
1621         }
1622         else
1623         {
1624             /* HTTP 1.1: only if no close we stay alive.. */
1625             if (connection_head && !strcmp(connection_head, "close"))
1626                 must_close = 1;
1627         }
1628         if (must_close)
1629         {
1630             ZOOM_connection_close(c);
1631             if (c->tasks)
1632             {
1633                 c->tasks->running = 0;
1634                 ZOOM_connection_insert_task(c, ZOOM_TASK_CONNECT);
1635                 c->reconnect_ok = 0;
1636             }
1637         }
1638         else
1639             c->reconnect_ok = 1; /* if the server closes anyway */
1640     }
1641 }
1642 #endif
1643
1644 static int do_read(ZOOM_connection c)
1645 {
1646     int r, more;
1647     ZOOM_Event event;
1648
1649     event = ZOOM_Event_create(ZOOM_EVENT_RECV_DATA);
1650     ZOOM_connection_put_event(c, event);
1651
1652     r = cs_get(c->cs, &c->buf_in, &c->len_in);
1653     more = cs_more(c->cs);
1654     yaz_log(c->log_details, "%p do_read len=%d more=%d", c, r, more);
1655     if (r == 1)
1656         return 0;
1657     if (r <= 0)
1658     {
1659         if (!ZOOM_test_reconnect(c))
1660         {
1661             ZOOM_set_error(c, ZOOM_ERROR_CONNECTION_LOST, c->host_port);
1662             ZOOM_connection_close(c);
1663         }
1664     }
1665     else
1666     {
1667         Z_GDU *gdu;
1668         ZOOM_Event event;
1669
1670         odr_reset(c->odr_in);
1671         odr_setbuf(c->odr_in, c->buf_in, r, 0);
1672         event = ZOOM_Event_create(ZOOM_EVENT_RECV_APDU);
1673         ZOOM_connection_put_event(c, event);
1674
1675         if (!z_GDU(c->odr_in, &gdu, 0, 0))
1676         {
1677             int x;
1678             int err = odr_geterrorx(c->odr_in, &x);
1679             char msg[100];
1680             const char *element = odr_getelement(c->odr_in);
1681             yaz_snprintf(msg, sizeof(msg),
1682                     "ODR code %d:%d element=%s offset=%d",
1683                     err, x, element ? element : "<unknown>",
1684                     odr_offset(c->odr_in));
1685             ZOOM_set_error(c, ZOOM_ERROR_DECODE, msg);
1686             if (c->log_api)
1687             {
1688                 FILE *ber_file = yaz_log_file();
1689                 if (ber_file)
1690                     odr_dumpBER(ber_file, c->buf_in, r);
1691             }
1692             ZOOM_connection_close(c);
1693         }
1694         else
1695         {
1696             if (c->odr_print)
1697                 z_GDU(c->odr_print, &gdu, 0, 0);
1698             if (c->odr_save)
1699                 z_GDU(c->odr_save, &gdu, 0, 0);
1700             if (gdu->which == Z_GDU_Z3950)
1701                 ZOOM_handle_Z3950_apdu(c, gdu->u.z3950);
1702             else if (gdu->which == Z_GDU_HTTP_Response)
1703             {
1704 #if YAZ_HAVE_XML2
1705                 handle_http(c, gdu->u.HTTP_Response);
1706 #else
1707                 ZOOM_set_error(c, ZOOM_ERROR_DECODE, 0);
1708                 ZOOM_connection_close(c);
1709 #endif
1710             }
1711         }
1712     }
1713     return 1;
1714 }
1715
1716 static zoom_ret do_write_ex(ZOOM_connection c, char *buf_out, int len_out)
1717 {
1718     int r;
1719     ZOOM_Event event;
1720
1721     event = ZOOM_Event_create(ZOOM_EVENT_SEND_DATA);
1722     ZOOM_connection_put_event(c, event);
1723
1724     yaz_log(c->log_details, "%p do_write_ex len=%d", c, len_out);
1725     if ((r = cs_put(c->cs, buf_out, len_out)) < 0)
1726     {
1727         yaz_log(c->log_details, "%p do_write_ex write failed", c);
1728         if (ZOOM_test_reconnect(c))
1729         {
1730             return zoom_pending;
1731         }
1732         if (c->state == STATE_CONNECTING)
1733             ZOOM_set_error(c, ZOOM_ERROR_CONNECT, c->host_port);
1734         else
1735             ZOOM_set_error(c, ZOOM_ERROR_CONNECTION_LOST, c->host_port);
1736         ZOOM_connection_close(c);
1737         return zoom_complete;
1738     }
1739     else if (r == 1)
1740     {
1741         int mask = ZOOM_SELECT_EXCEPT;
1742         if (c->cs->io_pending & CS_WANT_WRITE)
1743             mask += ZOOM_SELECT_WRITE;
1744         if (c->cs->io_pending & CS_WANT_READ)
1745             mask += ZOOM_SELECT_READ;
1746         ZOOM_connection_set_mask(c, mask);
1747         yaz_log(c->log_details, "%p do_write_ex write incomplete mask=%d",
1748                 c, c->mask);
1749     }
1750     else
1751     {
1752         ZOOM_connection_set_mask(c, ZOOM_SELECT_READ|ZOOM_SELECT_EXCEPT);
1753         yaz_log(c->log_details, "%p do_write_ex write complete mask=%d",
1754                 c, c->mask);
1755     }
1756     return zoom_pending;
1757 }
1758
1759 zoom_ret ZOOM_send_buf(ZOOM_connection c)
1760 {
1761     return do_write_ex(c, c->buf_out, c->len_out);
1762 }
1763
1764
1765 ZOOM_API(const char *)
1766     ZOOM_connection_option_get(ZOOM_connection c, const char *key)
1767 {
1768     if (!strcmp(key, "APDU"))
1769     {
1770         return c->saveAPDU_wrbuf ? wrbuf_cstr(c->saveAPDU_wrbuf) : "";
1771     }
1772     else
1773         return ZOOM_options_get(c->options, key);
1774 }
1775
1776 ZOOM_API(const char *)
1777     ZOOM_connection_option_getl(ZOOM_connection c, const char *key, int *lenp)
1778 {
1779     if (!strcmp(key, "APDU"))
1780     {
1781         if (c->saveAPDU_wrbuf)
1782         {
1783             *lenp = wrbuf_len(c->saveAPDU_wrbuf);
1784             return wrbuf_cstr(c->saveAPDU_wrbuf);
1785         }
1786         else
1787         {
1788             *lenp = 0;
1789             return "";
1790         }
1791     }
1792     else
1793         return ZOOM_options_getl(c->options, key, lenp);
1794 }
1795
1796 ZOOM_API(void)
1797     ZOOM_connection_option_set(ZOOM_connection c, const char *key,
1798                                const char *val)
1799 {
1800     if (!strcmp(key, "saveAPDU"))
1801     {
1802         if (val && strcmp(val, "0"))
1803         {
1804             if (!c->saveAPDU_wrbuf)
1805                 c->saveAPDU_wrbuf = wrbuf_alloc();
1806             else
1807                 wrbuf_rewind(c->saveAPDU_wrbuf);
1808         }
1809         else
1810         {
1811             wrbuf_destroy(c->saveAPDU_wrbuf);
1812             c->saveAPDU_wrbuf = 0;
1813         }
1814         ZOOM_connection_save_apdu_wrbuf(c, c->saveAPDU_wrbuf);
1815     }
1816     else
1817         ZOOM_options_set(c->options, key, val);
1818 }
1819
1820 ZOOM_API(void)
1821     ZOOM_connection_option_setl(ZOOM_connection c, const char *key,
1822                                 const char *val, int len)
1823 {
1824     ZOOM_options_setl(c->options, key, val, len);
1825 }
1826
1827 ZOOM_API(const char *)
1828     ZOOM_resultset_option_get(ZOOM_resultset r, const char *key)
1829 {
1830     return ZOOM_options_get(r->options, key);
1831 }
1832
1833 ZOOM_API(void)
1834     ZOOM_resultset_option_set(ZOOM_resultset r, const char *key,
1835                               const char *val)
1836 {
1837     ZOOM_options_set(r->options, key, val);
1838 }
1839
1840
1841 ZOOM_API(int)
1842     ZOOM_connection_errcode(ZOOM_connection c)
1843 {
1844     return ZOOM_connection_error(c, 0, 0);
1845 }
1846
1847 ZOOM_API(const char *)
1848     ZOOM_connection_errmsg(ZOOM_connection c)
1849 {
1850     const char *msg;
1851     ZOOM_connection_error(c, &msg, 0);
1852     return msg;
1853 }
1854
1855 ZOOM_API(const char *)
1856     ZOOM_connection_addinfo(ZOOM_connection c)
1857 {
1858     const char *addinfo;
1859     ZOOM_connection_error(c, 0, &addinfo);
1860     return addinfo;
1861 }
1862
1863 ZOOM_API(const char *)
1864     ZOOM_connection_diagset(ZOOM_connection c)
1865 {
1866     const char *diagset;
1867     ZOOM_connection_error_x(c, 0, 0, &diagset);
1868     return diagset;
1869 }
1870
1871 ZOOM_API(const char *)
1872     ZOOM_diag_str(int error)
1873 {
1874     switch (error)
1875     {
1876     case ZOOM_ERROR_NONE:
1877         return "No error";
1878     case ZOOM_ERROR_CONNECT:
1879         return "Connect failed";
1880     case ZOOM_ERROR_MEMORY:
1881         return "Out of memory";
1882     case ZOOM_ERROR_ENCODE:
1883         return "Encoding failed";
1884     case ZOOM_ERROR_DECODE:
1885         return "Decoding failed";
1886     case ZOOM_ERROR_CONNECTION_LOST:
1887         return "Connection lost";
1888     case ZOOM_ERROR_INIT:
1889         return "Init rejected";
1890     case ZOOM_ERROR_INTERNAL:
1891         return "Internal failure";
1892     case ZOOM_ERROR_TIMEOUT:
1893         return "Timeout";
1894     case ZOOM_ERROR_UNSUPPORTED_PROTOCOL:
1895         return "Unsupported protocol";
1896     case ZOOM_ERROR_UNSUPPORTED_QUERY:
1897         return "Unsupported query type";
1898     case ZOOM_ERROR_INVALID_QUERY:
1899         return "Invalid query";
1900     case ZOOM_ERROR_CQL_PARSE:
1901         return "CQL parsing error";
1902     case ZOOM_ERROR_CQL_TRANSFORM:
1903         return "CQL transformation error";
1904     case ZOOM_ERROR_CCL_CONFIG:
1905         return "CCL configuration error";
1906     case ZOOM_ERROR_CCL_PARSE:
1907         return "CCL parsing error";
1908     case ZOOM_ERROR_ES_INVALID_ACTION:
1909         return "Extended Service. invalid action";
1910     case ZOOM_ERROR_ES_INVALID_VERSION:
1911         return "Extended Service. invalid version";
1912     case ZOOM_ERROR_ES_INVALID_SYNTAX:
1913         return "Extended Service. invalid syntax";
1914     default:
1915         return diagbib1_str(error);
1916     }
1917 }
1918
1919 ZOOM_API(int)
1920     ZOOM_connection_error_x(ZOOM_connection c, const char **cp,
1921                             const char **addinfo, const char **diagset)
1922 {
1923     int error = c->error;
1924     if (cp)
1925     {
1926         if (!c->diagset || !strcmp(c->diagset, "ZOOM"))
1927             *cp = ZOOM_diag_str(error);
1928         else if (!strcmp(c->diagset, "HTTP"))
1929             *cp = z_HTTP_errmsg(c->error);
1930         else if (!strcmp(c->diagset, "Bib-1"))
1931             *cp = ZOOM_diag_str(error);
1932         else if (!strcmp(c->diagset, "info:srw/diagnostic/1"))
1933             *cp = yaz_diag_srw_str(c->error);
1934         else
1935             *cp = "Unknown error and diagnostic set";
1936     }
1937     if (addinfo)
1938         *addinfo = c->addinfo ? c->addinfo : "";
1939     if (diagset)
1940         *diagset = c->diagset ? c->diagset : "";
1941     return c->error;
1942 }
1943
1944 ZOOM_API(int)
1945     ZOOM_connection_error(ZOOM_connection c, const char **cp,
1946                           const char **addinfo)
1947 {
1948     return ZOOM_connection_error_x(c, cp, addinfo, 0);
1949 }
1950
1951 static void ZOOM_connection_do_io(ZOOM_connection c, int mask)
1952 {
1953     ZOOM_Event event = 0;
1954     int r = cs_look(c->cs);
1955     yaz_log(c->log_details, "%p ZOOM_connection_do_io mask=%d cs_look=%d",
1956             c, mask, r);
1957
1958     if (r == CS_NONE)
1959     {
1960         event = ZOOM_Event_create(ZOOM_EVENT_CONNECT);
1961         ZOOM_set_error(c, ZOOM_ERROR_CONNECT, c->host_port);
1962         ZOOM_connection_close(c);
1963         ZOOM_connection_put_event(c, event);
1964     }
1965     else if (r == CS_CONNECT)
1966     {
1967         int ret = ret = cs_rcvconnect(c->cs);
1968         yaz_log(c->log_details, "%p ZOOM_connection_do_io "
1969                 "cs_rcvconnect returned %d", c, ret);
1970         if (ret == 1)
1971         {
1972             int mask = ZOOM_SELECT_EXCEPT;
1973             if (c->cs->io_pending & CS_WANT_WRITE)
1974                 mask += ZOOM_SELECT_WRITE;
1975             if (c->cs->io_pending & CS_WANT_READ)
1976                 mask += ZOOM_SELECT_READ;
1977             ZOOM_connection_set_mask(c, mask);
1978             event = ZOOM_Event_create(ZOOM_EVENT_NONE);
1979             ZOOM_connection_put_event(c, event);
1980         }
1981         else if (ret == 0)
1982         {
1983             event = ZOOM_Event_create(ZOOM_EVENT_CONNECT);
1984             ZOOM_connection_put_event(c, event);
1985             get_cert(c);
1986             if (c->proto == PROTO_Z3950)
1987                 ZOOM_connection_Z3950_send_init(c);
1988             else
1989             {
1990                 /* no init request for SRW .. */
1991                 assert(c->tasks->which == ZOOM_TASK_CONNECT);
1992                 ZOOM_connection_remove_task(c);
1993                 ZOOM_connection_set_mask(c, 0);
1994                 ZOOM_connection_exec_task(c);
1995             }
1996             c->state = STATE_ESTABLISHED;
1997         }
1998         else
1999         {
2000             ZOOM_set_error(c, ZOOM_ERROR_CONNECT, c->host_port);
2001             ZOOM_connection_close(c);
2002         }
2003     }
2004     else
2005     {
2006         if (mask & ZOOM_SELECT_EXCEPT)
2007         {
2008             if (!ZOOM_test_reconnect(c))
2009             {
2010                 ZOOM_set_error(c, ZOOM_ERROR_CONNECTION_LOST, c->host_port);
2011                 ZOOM_connection_close(c);
2012             }
2013             return;
2014         }
2015         if (mask & ZOOM_SELECT_READ)
2016             do_read(c);
2017         if (c->cs && (mask & ZOOM_SELECT_WRITE))
2018             ZOOM_send_buf(c);
2019     }
2020 }
2021
2022 ZOOM_API(int)
2023     ZOOM_connection_last_event(ZOOM_connection cs)
2024 {
2025     if (!cs)
2026         return ZOOM_EVENT_NONE;
2027     return cs->last_event;
2028 }
2029
2030
2031 ZOOM_API(int) ZOOM_connection_fire_event_timeout(ZOOM_connection c)
2032 {
2033     if (c->mask)
2034     {
2035         ZOOM_Event event = ZOOM_Event_create(ZOOM_EVENT_TIMEOUT);
2036         /* timeout and this connection was waiting */
2037         ZOOM_set_error(c, ZOOM_ERROR_TIMEOUT, 0);
2038         ZOOM_connection_close(c);
2039         ZOOM_connection_put_event(c, event);
2040     }
2041     return 0;
2042 }
2043
2044 ZOOM_API(int)
2045     ZOOM_connection_process(ZOOM_connection c)
2046 {
2047     ZOOM_Event event;
2048     if (!c)
2049         return 0;
2050
2051     event = ZOOM_connection_get_event(c);
2052     if (event)
2053     {
2054         ZOOM_Event_destroy(event);
2055         return 1;
2056     }
2057     ZOOM_connection_exec_task(c);
2058     event = ZOOM_connection_get_event(c);
2059     if (event)
2060     {
2061         ZOOM_Event_destroy(event);
2062         return 1;
2063     }
2064     return 0;
2065 }
2066
2067 ZOOM_API(int)
2068     ZOOM_event_nonblock(int no, ZOOM_connection *cs)
2069 {
2070     int i;
2071
2072     yaz_log(log_details0, "ZOOM_process_event(no=%d,cs=%p)", no, cs);
2073
2074     for (i = 0; i<no; i++)
2075     {
2076         ZOOM_connection c = cs[i];
2077
2078         if (c && ZOOM_connection_process(c))
2079             return i+1;
2080     }
2081     return 0;
2082 }
2083
2084 ZOOM_API(int) ZOOM_connection_fire_event_socket(ZOOM_connection c, int mask)
2085 {
2086     if (c->mask && mask)
2087         ZOOM_connection_do_io(c, mask);
2088     return 0;
2089 }
2090
2091 ZOOM_API(int) ZOOM_connection_get_socket(ZOOM_connection c)
2092 {
2093     if (c->cs)
2094         return cs_fileno(c->cs);
2095     return -1;
2096 }
2097
2098 ZOOM_API(int) ZOOM_connection_set_mask(ZOOM_connection c, int mask)
2099 {
2100     c->mask = mask;
2101     if (!c->cs)
2102         return -1;
2103     return 0;
2104 }
2105
2106 ZOOM_API(int) ZOOM_connection_get_mask(ZOOM_connection c)
2107 {
2108     if (c->cs)
2109         return c->mask;
2110     return 0;
2111 }
2112
2113 ZOOM_API(int) ZOOM_connection_get_timeout(ZOOM_connection c)
2114 {
2115     return ZOOM_options_get_int(c->options, "timeout", 30);
2116 }
2117
2118 ZOOM_API(void) ZOOM_connection_close(ZOOM_connection c)
2119 {
2120     if (c->cs)
2121         cs_close(c->cs);
2122     c->cs = 0;
2123     ZOOM_connection_set_mask(c, 0);
2124     c->state = STATE_IDLE;
2125 }
2126
2127 /*
2128  * Local variables:
2129  * c-basic-offset: 4
2130  * c-file-style: "Stroustrup"
2131  * indent-tabs-mode: nil
2132  * End:
2133  * vim: shiftwidth=4 tabstop=8 expandtab
2134  */
2135