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