75b694a1e2f25732ebcc5d68865a3ffb1e74fa86
[yaz-moved-to-github.git] / src / zoom-c.c
1 /* This file is part of the YAZ toolkit.
2  * Copyright (C) 1995-2010 Index Data
3  * See the file LICENSE for details.
4  */
5 /**
6  * \file zoom-c.c
7  * \brief Implements ZOOM C interface.
8  */
9
10 #include <assert.h>
11 #include <string.h>
12 #include <errno.h>
13 #include "zoom-p.h"
14
15 #include <yaz/yaz-util.h>
16 #include <yaz/xmalloc.h>
17 #include <yaz/otherinfo.h>
18 #include <yaz/log.h>
19 #include <yaz/marcdisp.h>
20 #include <yaz/diagbib1.h>
21 #include <yaz/charneg.h>
22 #include <yaz/query-charset.h>
23 #include <yaz/copy_types.h>
24 #include <yaz/snprintf.h>
25 #include <yaz/facet.h>
26
27 #include <yaz/shptr.h>
28
29 #if SHPTR
30 YAZ_SHPTR_TYPE(WRBUF)
31 #endif
32
33 static int log_api0 = 0;
34 static int log_details0 = 0;
35
36 static void resultset_destroy(ZOOM_resultset r);
37 static zoom_ret do_write_ex(ZOOM_connection c, char *buf_out, int len_out);
38
39 ZOOM_API(const char *) ZOOM_get_event_str(int event)
40 {
41     static const char *ar[] = {
42         "NONE",
43         "CONNECT",
44         "SEND_DATA",
45         "RECV_DATA",
46         "TIMEOUT",
47         "UNKNOWN",
48         "SEND_APDU",
49         "RECV_APDU",
50         "RECV_RECORD",
51         "RECV_SEARCH",
52         "END"
53     };
54     return ar[event];
55 }
56
57 static void initlog(void)
58 {
59     static int log_level_initialized = 0;
60     if (!log_level_initialized)
61     {
62         log_api0 = yaz_log_module_level("zoom");
63         log_details0 = yaz_log_module_level("zoomdetails");
64         log_level_initialized = 1;
65     }
66 }
67
68 ZOOM_Event ZOOM_Event_create(int kind)
69 {
70     ZOOM_Event event = (ZOOM_Event) xmalloc(sizeof(*event));
71     event->kind = kind;
72     event->next = 0;
73     event->prev = 0;
74     return event;
75 }
76
77 static void ZOOM_Event_destroy(ZOOM_Event event)
78 {
79     xfree(event);
80 }
81
82 void ZOOM_connection_put_event(ZOOM_connection c, ZOOM_Event event)
83 {
84     if (c->m_queue_back)
85     {
86         c->m_queue_back->prev = event;
87         assert(c->m_queue_front);
88     }
89     else
90     {
91         assert(!c->m_queue_front);
92         c->m_queue_front = event;
93     }
94     event->next = c->m_queue_back;
95     event->prev = 0;
96     c->m_queue_back = event;
97 }
98
99 static ZOOM_Event ZOOM_connection_get_event(ZOOM_connection c)
100 {
101     ZOOM_Event event = c->m_queue_front;
102     if (!event)
103     {
104         c->last_event = ZOOM_EVENT_NONE;
105         return 0;
106     }
107     assert(c->m_queue_back);
108     c->m_queue_front = event->prev;
109     if (c->m_queue_front)
110     {
111         assert(c->m_queue_back);
112         c->m_queue_front->next = 0;
113     }
114     else
115         c->m_queue_back = 0;
116     c->last_event = event->kind;
117     return event;
118 }
119
120 static void ZOOM_connection_remove_events(ZOOM_connection c)
121 {
122     ZOOM_Event event;
123     while ((event = ZOOM_connection_get_event(c)))
124         ZOOM_Event_destroy(event);
125 }
126
127 ZOOM_API(int) ZOOM_connection_peek_event(ZOOM_connection c)
128 {
129     ZOOM_Event event = c->m_queue_front;
130
131     return event ? event->kind : ZOOM_EVENT_NONE;
132 }
133
134 void ZOOM_connection_remove_tasks(ZOOM_connection c);
135
136 void ZOOM_set_dset_error(ZOOM_connection c, int error,
137                          const char *dset,
138                          const char *addinfo, const char *addinfo2)
139 {
140     char *cp;
141
142     xfree(c->addinfo);
143     c->addinfo = 0;
144     c->error = error;
145     if (!c->diagset || strcmp(dset, c->diagset))
146     {
147         xfree(c->diagset);
148         c->diagset = xstrdup(dset);
149         /* remove integer part from SRW diagset .. */
150         if ((cp = strrchr(c->diagset, '/')))
151             *cp = '\0';
152     }
153     if (addinfo && addinfo2)
154     {
155         c->addinfo = (char*) xmalloc(strlen(addinfo) + strlen(addinfo2) + 2);
156         strcpy(c->addinfo, addinfo);
157         strcat(c->addinfo, addinfo2);
158     }
159     else if (addinfo)
160         c->addinfo = xstrdup(addinfo);
161     if (error != ZOOM_ERROR_NONE)
162     {
163         yaz_log(c->log_api, "%p set_dset_error %s %s:%d %s %s",
164                 c, c->host_port ? c->host_port : "<>", dset, error,
165                 addinfo ? addinfo : "",
166                 addinfo2 ? addinfo2 : "");
167         ZOOM_connection_remove_tasks(c);
168     }
169 }
170
171 static int uri_to_code(const char *uri)
172 {
173     int code = 0;       
174     const char *cp;
175     if ((cp = strrchr(uri, '/')))
176         code = atoi(cp+1);
177     return code;
178 }
179
180 #if YAZ_HAVE_XML2
181 static void set_HTTP_error(ZOOM_connection c, int error,
182                            const char *addinfo, const char *addinfo2)
183 {
184     ZOOM_set_dset_error(c, error, "HTTP", addinfo, addinfo2);
185 }
186
187 static void set_SRU_error(ZOOM_connection c, Z_SRW_diagnostic *d)
188 {
189     const char *uri = d->uri;
190     if (uri)
191         ZOOM_set_dset_error(c, uri_to_code(uri), uri, d->details, 0);
192 }
193
194 #endif
195
196
197 void ZOOM_set_error(ZOOM_connection c, int error, const char *addinfo)
198 {
199     ZOOM_set_dset_error(c, error, "ZOOM", addinfo, 0);
200 }
201
202 static void clear_error(ZOOM_connection c)
203 {
204     /*
205      * If an error is tied to an operation then it's ok to clear: for
206      * example, a diagnostic returned from a search is cleared by a
207      * subsequent search.  However, problems such as Connection Lost
208      * or Init Refused are not cleared, because they are not
209      * recoverable: doing another search doesn't help.
210      */
211
212     ZOOM_connection_remove_events(c);
213     switch (c->error)
214     {
215     case ZOOM_ERROR_CONNECT:
216     case ZOOM_ERROR_MEMORY:
217     case ZOOM_ERROR_DECODE:
218     case ZOOM_ERROR_CONNECTION_LOST:
219     case ZOOM_ERROR_INIT:
220     case ZOOM_ERROR_INTERNAL:
221     case ZOOM_ERROR_UNSUPPORTED_PROTOCOL:
222         break;
223     default:
224         ZOOM_set_error(c, ZOOM_ERROR_NONE, 0);
225     }
226 }
227
228 void ZOOM_connection_show_task(ZOOM_task task)
229 {
230     switch(task->which)
231     {
232     case ZOOM_TASK_SEARCH:
233         yaz_log(YLOG_LOG, "search p=%p", task);
234         break;
235     case ZOOM_TASK_RETRIEVE:
236         yaz_log(YLOG_LOG, "retrieve p=%p", task);
237         break;
238     case ZOOM_TASK_CONNECT:
239         yaz_log(YLOG_LOG, "connect p=%p", task);
240         break;
241     case ZOOM_TASK_SCAN:
242         yaz_log(YLOG_LOG, "scan p=%p", task);
243         break;
244     }
245 }
246
247 void ZOOM_connection_show_tasks(ZOOM_connection c)
248 {
249     ZOOM_task task;
250     yaz_log(YLOG_LOG, "connection p=%p tasks", c);
251     for (task = c->tasks; task; task = task->next)
252         ZOOM_connection_show_task(task);
253 }
254
255 ZOOM_task ZOOM_connection_add_task(ZOOM_connection c, int which)
256 {
257     ZOOM_task *taskp = &c->tasks;
258     while (*taskp)
259         taskp = &(*taskp)->next;
260     *taskp = (ZOOM_task) xmalloc(sizeof(**taskp));
261     (*taskp)->running = 0;
262     (*taskp)->which = which;
263     (*taskp)->next = 0;
264     clear_error(c);
265     return *taskp;
266 }
267
268 ZOOM_API(int) ZOOM_connection_is_idle(ZOOM_connection c)
269 {
270     return c->tasks ? 0 : 1;
271 }
272
273 ZOOM_task ZOOM_connection_insert_task(ZOOM_connection c, int which)
274 {
275     ZOOM_task task = (ZOOM_task) xmalloc(sizeof(*task));
276
277     task->next = c->tasks;
278     c->tasks = task;
279
280     task->running = 0;
281     task->which = which;
282     clear_error(c);
283     return task;
284 }
285
286 void ZOOM_connection_remove_task(ZOOM_connection c)
287 {
288     ZOOM_task task = c->tasks;
289
290     if (task)
291     {
292         c->tasks = task->next;
293         switch (task->which)
294         {
295         case ZOOM_TASK_SEARCH:
296             resultset_destroy(task->u.search.resultset);
297             xfree(task->u.search.syntax);
298             xfree(task->u.search.elementSetName);
299             break;
300         case ZOOM_TASK_RETRIEVE:
301             resultset_destroy(task->u.retrieve.resultset);
302             xfree(task->u.retrieve.syntax);
303             xfree(task->u.retrieve.elementSetName);
304             break;
305         case ZOOM_TASK_CONNECT:
306             break;
307         case ZOOM_TASK_SCAN:
308             ZOOM_scanset_destroy(task->u.scan.scan);
309             break;
310         case ZOOM_TASK_PACKAGE:
311             ZOOM_package_destroy(task->u.package);
312             break;
313         case ZOOM_TASK_SORT:
314             resultset_destroy(task->u.sort.resultset);
315             ZOOM_query_destroy(task->u.sort.q);
316             break;
317         default:
318             assert(0);
319         }
320         xfree(task);
321
322         if (!c->tasks)
323         {
324             ZOOM_Event event = ZOOM_Event_create(ZOOM_EVENT_END);
325             ZOOM_connection_put_event(c, event);
326         }
327     }
328 }
329
330 void ZOOM_connection_remove_tasks(ZOOM_connection c)
331 {
332     while (c->tasks)
333         ZOOM_connection_remove_task(c);
334 }
335
336
337 ZOOM_API(ZOOM_connection)
338     ZOOM_connection_create(ZOOM_options options)
339 {
340     ZOOM_connection c = (ZOOM_connection) xmalloc(sizeof(*c));
341
342     initlog();
343
344     c->log_api = log_api0;
345     c->log_details = log_details0;
346
347     yaz_log(c->log_api, "%p ZOOM_connection_create", c);
348
349     c->proto = PROTO_Z3950;
350     c->cs = 0;
351     ZOOM_connection_set_mask(c, 0);
352     c->reconnect_ok = 0;
353     c->state = STATE_IDLE;
354     c->addinfo = 0;
355     c->diagset = 0;
356     ZOOM_set_error(c, ZOOM_ERROR_NONE, 0);
357     c->buf_in = 0;
358     c->len_in = 0;
359     c->buf_out = 0;
360     c->len_out = 0;
361     c->resultsets = 0;
362
363     c->options = ZOOM_options_create_with_parent(options);
364
365     c->host_port = 0;
366     c->path = 0;
367     c->proxy = 0;
368     
369     c->charset = c->lang = 0;
370
371     c->cookie_out = 0;
372     c->cookie_in = 0;
373     c->client_IP = 0;
374     c->tasks = 0;
375
376     c->user = 0;
377     c->group = 0;
378     c->password = 0;
379
380     c->maximum_record_size = 0;
381     c->preferred_message_size = 0;
382
383     c->odr_in = odr_createmem(ODR_DECODE);
384     c->odr_out = odr_createmem(ODR_ENCODE);
385     c->odr_print = 0;
386
387     c->async = 0;
388     c->support_named_resultsets = 0;
389     c->last_event = ZOOM_EVENT_NONE;
390
391     c->m_queue_front = 0;
392     c->m_queue_back = 0;
393
394     c->sru_version = 0;
395     c->no_redirects = 0;
396     return c;
397 }
398
399
400 /* set database names. Take local databases (if set); otherwise
401    take databases given in ZURL (if set); otherwise use Default */
402 char **ZOOM_connection_get_databases(ZOOM_connection con, ZOOM_options options,
403                                      int *num, ODR odr)
404 {
405     char **databaseNames;
406     const char *cp = ZOOM_options_get(options, "databaseName");
407     
408     if ((!cp || !*cp) && con->host_port)
409     {
410         if (strncmp(con->host_port, "unix:", 5) == 0)
411             cp = strchr(con->host_port+5, ':');
412         else
413             cp = strchr(con->host_port, '/');
414         if (cp)
415             cp++;
416     }
417     if (!cp)
418         cp = "Default";
419     nmem_strsplit(odr_getmem(odr), "+", cp,  &databaseNames, num);
420     return databaseNames;
421 }
422
423 ZOOM_API(ZOOM_connection)
424     ZOOM_connection_new(const char *host, int portnum)
425 {
426     ZOOM_connection c = ZOOM_connection_create(0);
427
428     ZOOM_connection_connect(c, host, portnum);
429     return c;
430 }
431
432 static zoom_sru_mode get_sru_mode_from_string(const char *s)
433 {
434     if (!s || !*s)
435         return zoom_sru_soap;
436     if (!yaz_matchstr(s, "soap"))
437         return zoom_sru_soap;
438     else if (!yaz_matchstr(s, "get"))
439         return zoom_sru_get;
440     else if (!yaz_matchstr(s, "post"))
441         return zoom_sru_post;
442     return zoom_sru_error;
443 }
444
445 ZOOM_API(void)
446     ZOOM_connection_connect(ZOOM_connection c,
447                             const char *host, int portnum)
448 {
449     const char *val;
450     ZOOM_task task;
451
452     initlog();
453
454     yaz_log(c->log_api, "%p ZOOM_connection_connect host=%s portnum=%d",
455             c, host ? host : "null", portnum);
456
457     ZOOM_set_error(c, ZOOM_ERROR_NONE, 0);
458     ZOOM_connection_remove_tasks(c);
459
460     if (c->odr_print)
461     {
462         odr_setprint(c->odr_print, 0); /* prevent destroy from fclose'ing */
463         odr_destroy(c->odr_print);
464     }
465     if (ZOOM_options_get_bool(c->options, "apdulog", 0))
466     {
467         c->odr_print = odr_createmem(ODR_PRINT);
468         odr_setprint(c->odr_print, yaz_log_file());
469     }
470     else
471         c->odr_print = 0;
472
473     if (c->cs)
474     {
475         yaz_log(c->log_details, "%p ZOOM_connection_connect reconnect ok", c);
476         c->reconnect_ok = 1;
477         return;
478     }
479     yaz_log(c->log_details, "%p ZOOM_connection_connect connect", c);
480     xfree(c->proxy);
481     c->proxy = 0;
482     val = ZOOM_options_get(c->options, "proxy");
483     if (val && *val)
484     {
485         yaz_log(c->log_details, "%p ZOOM_connection_connect proxy=%s", c, val);
486         c->proxy = xstrdup(val);
487     }
488
489     xfree(c->charset);
490     c->charset = 0;
491     val = ZOOM_options_get(c->options, "charset");
492     if (val && *val)
493     {
494         yaz_log(c->log_details, "%p ZOOM_connection_connect charset=%s", c, val);
495         c->charset = xstrdup(val);
496     }
497
498     xfree(c->lang);
499     val = ZOOM_options_get(c->options, "lang");
500     if (val && *val)
501     {
502         yaz_log(c->log_details, "%p ZOOM_connection_connect lang=%s", c, val);
503         c->lang = xstrdup(val);
504     }
505     else
506         c->lang = 0;
507
508     if (host)
509     {
510         xfree(c->host_port);
511         if (portnum)
512         {
513             char hostn[128];
514             sprintf(hostn, "%.80s:%d", host, portnum);
515             c->host_port = xstrdup(hostn);
516         }
517         else
518             c->host_port = xstrdup(host);
519     }        
520
521     {
522         /*
523          * If the "<scheme>:" part of the host string is preceded by one
524          * or more comma-separated <name>=<value> pairs, these are taken
525          * to be options to be set on the connection object.  Among other
526          * applications, this facility can be used to embed authentication
527          * in a host string:
528          *          user=admin,password=secret,tcp:localhost:9999
529          */
530         char *remainder = c->host_port;
531         char *pcolon = strchr(remainder, ':');
532         char *pcomma;
533         char *pequals;
534         while ((pcomma = strchr(remainder, ',')) != 0 &&
535                (pcolon == 0 || pcomma < pcolon)) {
536             *pcomma = '\0';
537             if ((pequals = strchr(remainder, '=')) != 0) {
538                 *pequals = '\0';
539                 /*printf("# setting '%s'='%s'\n", remainder, pequals+1);*/
540                 ZOOM_connection_option_set(c, remainder, pequals+1);
541             }
542             remainder = pcomma+1;
543         }
544
545         if (remainder != c->host_port) {
546             xfree(c->host_port);
547             c->host_port = xstrdup(remainder);
548             /*printf("# reset hp='%s'\n", remainder);*/
549         }
550     }
551
552     val = ZOOM_options_get(c->options, "sru");
553     c->sru_mode = get_sru_mode_from_string(val);
554
555     xfree(c->sru_version);
556     val = ZOOM_options_get(c->options, "sru_version");
557     c->sru_version = xstrdup(val ? val : "1.2");
558
559     ZOOM_options_set(c->options, "host", c->host_port);
560
561     xfree(c->cookie_out);
562     c->cookie_out = 0;
563     val = ZOOM_options_get(c->options, "cookie");
564     if (val && *val)
565     { 
566         yaz_log(c->log_details, "%p ZOOM_connection_connect cookie=%s", c, val);
567         c->cookie_out = xstrdup(val);
568     }
569
570     xfree(c->client_IP);
571     c->client_IP = 0;
572     val = ZOOM_options_get(c->options, "clientIP");
573     if (val && *val)
574     {
575         yaz_log(c->log_details, "%p ZOOM_connection_connect clientIP=%s",
576                 c, val);
577         c->client_IP = xstrdup(val);
578     }
579
580     xfree(c->group);
581     c->group = 0;
582     val = ZOOM_options_get(c->options, "group");
583     if (val && *val)
584         c->group = xstrdup(val);
585
586     xfree(c->user);
587     c->user = 0;
588     val = ZOOM_options_get(c->options, "user");
589     if (val && *val)
590         c->user = xstrdup(val);
591
592     xfree(c->password);
593     c->password = 0;
594     val = ZOOM_options_get(c->options, "password");
595     if (!val)
596         val = ZOOM_options_get(c->options, "pass");
597
598     if (val && *val)
599         c->password = xstrdup(val);
600     
601     c->maximum_record_size =
602         ZOOM_options_get_int(c->options, "maximumRecordSize", 1024*1024);
603     c->preferred_message_size =
604         ZOOM_options_get_int(c->options, "preferredMessageSize", 1024*1024);
605
606     c->async = ZOOM_options_get_bool(c->options, "async", 0);
607     yaz_log(c->log_details, "%p ZOOM_connection_connect async=%d", c, c->async);
608  
609     task = ZOOM_connection_add_task(c, ZOOM_TASK_CONNECT);
610
611     if (!c->async)
612     {
613         while (ZOOM_event(1, &c))
614             ;
615     }
616 }
617
618 ZOOM_API(void) ZOOM_resultset_release(ZOOM_resultset r)
619 {
620 #if ZOOM_RESULT_LISTS
621 #else
622     if (r->connection)
623     {
624         /* remove ourselves from the resultsets in connection */
625         ZOOM_resultset *rp = &r->connection->resultsets;
626         while (1)
627         {
628             assert(*rp);   /* we must be in this list!! */
629             if (*rp == r)
630             {   /* OK, we're here - take us out of it */
631                 *rp = (*rp)->next;
632                 break;
633             }
634             rp = &(*rp)->next;
635         }
636         r->connection = 0;
637     }
638 #endif
639 }
640
641 ZOOM_API(void)
642     ZOOM_connection_destroy(ZOOM_connection c)
643 {
644 #if ZOOM_RESULT_LISTS
645     ZOOM_resultsets list;
646 #else
647     ZOOM_resultset r;
648 #endif
649     if (!c)
650         return;
651     yaz_log(c->log_api, "%p ZOOM_connection_destroy", c);
652     if (c->cs)
653         cs_close(c->cs);
654
655 #if ZOOM_RESULT_LISTS
656     // Remove the connection's usage of resultsets
657     list = c->resultsets;
658     while (list) {
659         ZOOM_resultsets removed = list;
660         ZOOM_resultset_destroy(list->resultset);
661         list = list->next;
662         xfree(removed);
663     }
664 #else
665     for (r = c->resultsets; r; r = r->next)
666         r->connection = 0;
667 #endif
668
669     xfree(c->buf_in);
670     xfree(c->addinfo);
671     xfree(c->diagset);
672     odr_destroy(c->odr_in);
673     odr_destroy(c->odr_out);
674     if (c->odr_print)
675     {
676         odr_setprint(c->odr_print, 0); /* prevent destroy from fclose'ing */
677         odr_destroy(c->odr_print);
678     }
679     ZOOM_options_destroy(c->options);
680     ZOOM_connection_remove_tasks(c);
681     ZOOM_connection_remove_events(c);
682     xfree(c->host_port);
683     xfree(c->path);
684     xfree(c->proxy);
685     xfree(c->charset);
686     xfree(c->lang);
687     xfree(c->cookie_out);
688     xfree(c->cookie_in);
689     xfree(c->client_IP);
690     xfree(c->user);
691     xfree(c->group);
692     xfree(c->password);
693     xfree(c->sru_version);
694     xfree(c);
695 }
696
697 void ZOOM_resultset_addref(ZOOM_resultset r)
698 {
699     if (r)
700     {
701         yaz_mutex_enter(r->mutex);
702         (r->refcount)++;
703         yaz_log(log_details0, "%p ZOOM_resultset_addref count=%d",
704                 r, r->refcount);
705         yaz_mutex_leave(r->mutex);
706     }
707 }
708
709 ZOOM_resultset ZOOM_resultset_create(void)
710 {
711     int i;
712     ZOOM_resultset r = (ZOOM_resultset) xmalloc(sizeof(*r));
713
714     initlog();
715
716     yaz_log(log_details0, "%p ZOOM_resultset_create", r);
717     r->refcount = 1;
718     r->size = 0;
719     r->odr = odr_createmem(ODR_ENCODE);
720     r->piggyback = 1;
721     r->setname = 0;
722     r->schema = 0;
723     r->step = 0;
724     for (i = 0; i<RECORD_HASH_SIZE; i++)
725         r->record_hash[i] = 0;
726     r->r_sort_spec = 0;
727     r->query = 0;
728     r->connection = 0;
729     r->databaseNames = 0;
730     r->num_databaseNames = 0;
731     r->facets = 0;
732     r->num_facets = 0;
733     r->facets_names = 0;
734     r->mutex = 0;
735     yaz_mutex_create(&r->mutex);
736 #if SHPTR
737     {
738         WRBUF w = wrbuf_alloc();
739         YAZ_SHPTR_INIT(r->record_wrbuf, w);
740     }
741 #endif
742     return r;
743 }
744
745 ZOOM_API(ZOOM_resultset)
746     ZOOM_connection_search_pqf(ZOOM_connection c, const char *q)
747 {
748     ZOOM_resultset r;
749     ZOOM_query s = ZOOM_query_create();
750
751     ZOOM_query_prefix(s, q);
752
753     r = ZOOM_connection_search(c, s);
754     ZOOM_query_destroy(s);
755     return r;
756 }
757
758 ZOOM_API(ZOOM_resultset)
759     ZOOM_connection_search(ZOOM_connection c, ZOOM_query q)
760 {
761     ZOOM_resultset r = ZOOM_resultset_create();
762     ZOOM_task task;
763     const char *cp;
764     int start, count;
765     const char *syntax, *elementSetName;
766 #if ZOOM_RESULT_LISTS
767     ZOOM_resultsets set;
768 #endif
769
770     yaz_log(c->log_api, "%p ZOOM_connection_search set %p query %p", c, r, q);
771     r->r_sort_spec = ZOOM_query_get_sortspec(q);
772     r->query = q;
773
774     r->options = ZOOM_options_create_with_parent(c->options);
775     
776     start = ZOOM_options_get_int(r->options, "start", 0);
777     count = ZOOM_options_get_int(r->options, "count", 0);
778     {
779         /* If "presentChunk" is defined use that; otherwise "step" */
780         const char *cp = ZOOM_options_get(r->options, "presentChunk");
781         r->step = ZOOM_options_get_int(r->options,
782                                        (cp != 0 ? "presentChunk": "step"), 0);
783     }
784     r->piggyback = ZOOM_options_get_bool(r->options, "piggyback", 1);
785     cp = ZOOM_options_get(r->options, "setname");
786     if (cp)
787         r->setname = xstrdup(cp);
788     cp = ZOOM_options_get(r->options, "schema");
789     if (cp)
790         r->schema = xstrdup(cp);
791
792     r->databaseNames = ZOOM_connection_get_databases(c, c->options, &r->num_databaseNames,
793                                          r->odr);
794     
795     r->connection = c;
796
797 #if ZOOM_RESULT_LISTS
798     yaz_log(log_details, "%p ZOOM_connection_search: Adding new resultset (%p) to resultsets (%p) ", c, r, c->resultsets);
799     set = xmalloc(sizeof(*set));
800     ZOOM_resultset_addref(r);
801     set->resultset = r;
802     set->next = c->resultsets;
803     c->resultsets = set;
804 #else
805     r->next = c->resultsets;
806     c->resultsets = r;
807 #endif
808     if (c->host_port && c->proto == PROTO_HTTP)
809     {
810         if (!c->cs)
811         {
812             yaz_log(c->log_details, "ZOOM_connection_search: no comstack");
813             ZOOM_connection_add_task(c, ZOOM_TASK_CONNECT);
814         }
815         else
816         {
817             yaz_log(c->log_details, "ZOOM_connection_search: reconnect");
818             c->reconnect_ok = 1;
819         }
820     }
821
822     task = ZOOM_connection_add_task(c, ZOOM_TASK_SEARCH);
823     task->u.search.resultset = r;
824     task->u.search.start = start;
825     task->u.search.count = count;
826     task->u.search.recv_search_fired = 0;
827
828     syntax = ZOOM_options_get(r->options, "preferredRecordSyntax"); 
829     task->u.search.syntax = syntax ? xstrdup(syntax) : 0;
830     elementSetName = ZOOM_options_get(r->options, "elementSetName");
831     task->u.search.elementSetName = elementSetName 
832         ? xstrdup(elementSetName) : 0;
833    
834     ZOOM_resultset_addref(r);
835
836     ZOOM_query_addref(q);
837
838     if (!c->async)
839     {
840         while (ZOOM_event(1, &c))
841             ;
842     }
843     return r;
844 }
845
846 ZOOM_API(void)
847     ZOOM_resultset_sort(ZOOM_resultset r,
848                          const char *sort_type, const char *sort_spec)
849 {
850     (void) ZOOM_resultset_sort1(r, sort_type, sort_spec);
851 }
852
853 ZOOM_API(int)
854     ZOOM_resultset_sort1(ZOOM_resultset r,
855                          const char *sort_type, const char *sort_spec)
856 {
857     ZOOM_connection c = r->connection;
858     ZOOM_task task;
859     ZOOM_query newq;
860
861     newq = ZOOM_query_create();
862     if (ZOOM_query_sortby(newq, sort_spec) < 0)
863         return -1;
864
865     yaz_log(c->log_api, "%p ZOOM_resultset_sort r=%p sort_type=%s sort_spec=%s",
866             r, r, sort_type, sort_spec);
867     if (!c)
868         return 0;
869
870     if (c->host_port && c->proto == PROTO_HTTP)
871     {
872         if (!c->cs)
873         {
874             yaz_log(c->log_details, "%p ZOOM_resultset_sort: no comstack", r);
875             ZOOM_connection_add_task(c, ZOOM_TASK_CONNECT);
876         }
877         else
878         {
879             yaz_log(c->log_details, "%p ZOOM_resultset_sort: prepare reconnect",
880                     r);
881             c->reconnect_ok = 1;
882         }
883     }
884     
885     ZOOM_resultset_cache_reset(r);
886     task = ZOOM_connection_add_task(c, ZOOM_TASK_SORT);
887     task->u.sort.resultset = r;
888     task->u.sort.q = newq;
889
890     ZOOM_resultset_addref(r);  
891
892     if (!c->async)
893     {
894         while (ZOOM_event(1, &c))
895             ;
896     }
897
898     return 0;
899 }
900
901 static void ZOOM_record_release(ZOOM_record rec)
902 {
903     if (!rec)
904         return;
905
906 #if SHPTR
907     if (rec->record_wrbuf)
908         YAZ_SHPTR_DEC(rec->record_wrbuf, wrbuf_destroy);
909 #else
910     if (rec->wrbuf)
911         wrbuf_destroy(rec->wrbuf);
912 #endif
913
914     if (rec->odr)
915         odr_destroy(rec->odr);
916 }
917
918 ZOOM_API(void)
919     ZOOM_resultset_cache_reset(ZOOM_resultset r)
920 {
921     int i;
922     for (i = 0; i<RECORD_HASH_SIZE; i++)
923     {
924         ZOOM_record_cache rc;
925         for (rc = r->record_hash[i]; rc; rc = rc->next)
926         {
927             ZOOM_record_release(&rc->rec);
928         }
929         r->record_hash[i] = 0;
930     }
931 }
932
933 ZOOM_API(void)
934     ZOOM_resultset_destroy(ZOOM_resultset r)
935 {
936     resultset_destroy(r);
937 }
938
939 static void resultset_destroy(ZOOM_resultset r)
940 {
941     if (!r)
942         return;
943     yaz_mutex_enter(r->mutex);
944     (r->refcount)--;
945     yaz_log(log_details0, "%p ZOOM_resultset_destroy r=%p count=%d",
946             r, r, r->refcount);
947     if (r->refcount == 0)
948     {
949         yaz_mutex_leave(r->mutex);
950
951         yaz_log(log_details0, "%p ZOOM_connection resultset_destroy: Deleting resultset (%p) ", r->connection, r);
952         ZOOM_resultset_cache_reset(r);
953         ZOOM_resultset_release(r);
954         ZOOM_query_destroy(r->query);
955         ZOOM_options_destroy(r->options);
956         odr_destroy(r->odr);
957         xfree(r->setname);
958         xfree(r->schema);
959         yaz_mutex_destroy(&r->mutex);
960 #if SHPTR
961         YAZ_SHPTR_DEC(r->record_wrbuf, wrbuf_destroy);
962 #endif
963         xfree(r);
964     }
965     else
966         yaz_mutex_leave(r->mutex);
967 }
968
969 ZOOM_API(size_t)
970     ZOOM_resultset_size(ZOOM_resultset r)
971 {
972     return r->size;
973 }
974
975 int ZOOM_test_reconnect(ZOOM_connection c)
976 {
977     ZOOM_Event event;
978
979     if (!c->reconnect_ok)
980         return 0;
981     ZOOM_connection_close(c);
982     c->reconnect_ok = 0;
983     c->tasks->running = 0;
984     ZOOM_connection_insert_task(c, ZOOM_TASK_CONNECT);
985
986     event = ZOOM_Event_create(ZOOM_EVENT_CONNECT);
987     ZOOM_connection_put_event(c, event);
988
989     return 1;
990 }
991
992 static void ZOOM_resultset_retrieve(ZOOM_resultset r,
993                                     int force_sync, int start, int count)
994 {
995     ZOOM_task task;
996     ZOOM_connection c;
997     const char *cp;
998     const char *syntax, *elementSetName;
999
1000     if (!r)
1001         return;
1002     yaz_log(log_details0, "%p ZOOM_resultset_retrieve force_sync=%d start=%d"
1003             " count=%d", r, force_sync, start, count);
1004     c = r->connection;
1005     if (!c)
1006         return;
1007
1008     if (c->host_port && c->proto == PROTO_HTTP)
1009     {
1010         if (!c->cs)
1011         {
1012             yaz_log(log_details0, "%p ZOOM_resultset_retrieve: no comstack", r);
1013             ZOOM_connection_add_task(c, ZOOM_TASK_CONNECT);
1014         }
1015         else
1016         {
1017             yaz_log(log_details0, "%p ZOOM_resultset_retrieve: prepare "
1018                     "reconnect", r);
1019             c->reconnect_ok = 1;
1020         }
1021     }
1022     task = ZOOM_connection_add_task(c, ZOOM_TASK_RETRIEVE);
1023     task->u.retrieve.resultset = r;
1024     task->u.retrieve.start = start;
1025     task->u.retrieve.count = count;
1026
1027     syntax = ZOOM_options_get(r->options, "preferredRecordSyntax"); 
1028     task->u.retrieve.syntax = syntax ? xstrdup(syntax) : 0;
1029     elementSetName = ZOOM_options_get(r->options, "elementSetName");
1030     task->u.retrieve.elementSetName = elementSetName 
1031         ? xstrdup(elementSetName) : 0;
1032
1033     cp = ZOOM_options_get(r->options, "schema");
1034     if (cp)
1035     {
1036         if (!r->schema || strcmp(r->schema, cp))
1037         {
1038             xfree(r->schema);
1039             r->schema = xstrdup(cp);
1040         }
1041     }
1042
1043     ZOOM_resultset_addref(r);
1044
1045     if (!r->connection->async || force_sync)
1046         while (r->connection && ZOOM_event(1, &r->connection))
1047             ;
1048 }
1049
1050 ZOOM_API(void)
1051     ZOOM_resultset_records(ZOOM_resultset r, ZOOM_record *recs,
1052                            size_t start, size_t count)
1053 {
1054     int force_present = 0;
1055
1056     if (!r)
1057         return ;
1058     yaz_log(log_api0, "%p ZOOM_resultset_records r=%p start=%ld count=%ld",
1059             r, r, (long) start, (long) count);
1060     if (count && recs)
1061         force_present = 1;
1062     ZOOM_resultset_retrieve(r, force_present, start, count);
1063     if (force_present)
1064     {
1065         size_t i;
1066         for (i = 0; i< count; i++)
1067             recs[i] = ZOOM_resultset_record_immediate(r, i+start);
1068     }
1069 }
1070
1071 ZOOM_API(size_t)
1072     ZOOM_resultset_facets_size(ZOOM_resultset r) {
1073     return r->num_facets;
1074 }
1075
1076 ZOOM_API(ZOOM_facet_field)
1077     ZOOM_resultset_get_facet_field(ZOOM_resultset r, const char *name) {
1078     int num = r->num_facets;
1079     ZOOM_facet_field *facets = r->facets;
1080     int index;
1081     for (index = 0; index < num; index++) {
1082         if (!strcmp(facets[index]->facet_name, name)) {
1083             return facets[index];
1084         }
1085     }
1086     return 0;
1087 }
1088
1089
1090 ZOOM_API(ZOOM_facet_field *)
1091     ZOOM_resultset_facets(ZOOM_resultset r)
1092 {
1093     return r->facets;
1094 }
1095
1096 ZOOM_API(const char**)
1097     ZOOM_resultset_facet_names(ZOOM_resultset r)
1098 {
1099     return (const char **) r->facets_names;
1100 }
1101
1102 ZOOM_API(const char*)
1103     ZOOM_facet_field_name(ZOOM_facet_field field)
1104 {
1105     return field->facet_name;
1106 }
1107
1108 ZOOM_API(size_t)
1109     ZOOM_facet_field_term_count(ZOOM_facet_field field)
1110 {
1111     return field->num_terms;
1112 }
1113
1114 ZOOM_API(const char*)
1115     ZOOM_facet_field_get_term(ZOOM_facet_field field, size_t idx, int *freq) {
1116     *freq = field->facet_terms[idx].frequency;
1117     return field->facet_terms[idx].term;
1118 }
1119
1120
1121 static void get_cert(ZOOM_connection c)
1122 {
1123     char *cert_buf;
1124     int cert_len;
1125     
1126     if (cs_get_peer_certificate_x509(c->cs, &cert_buf, &cert_len))
1127     {
1128         ZOOM_connection_option_setl(c, "sslPeerCert",
1129                                     cert_buf, cert_len);
1130         xfree(cert_buf);
1131     }
1132 }
1133
1134 static zoom_ret do_connect_host(ZOOM_connection c,
1135                                 const char *effective_host,
1136                                 const char *logical_url);
1137
1138 static zoom_ret do_connect(ZOOM_connection c)
1139 {
1140     const char *effective_host;
1141
1142     if (c->proxy)
1143         effective_host = c->proxy;
1144     else
1145         effective_host = c->host_port;
1146     return do_connect_host(c, effective_host, c->host_port);
1147 }
1148
1149 static zoom_ret do_connect_host(ZOOM_connection c, const char *effective_host,
1150     const char *logical_url)
1151 {
1152     void *add;
1153
1154     yaz_log(c->log_details, "%p do_connect effective_host=%s", c, effective_host);
1155
1156     if (c->cs)
1157         cs_close(c->cs);
1158     c->cs = cs_create_host(effective_host, 0, &add);
1159
1160     if (c->cs && c->cs->protocol == PROTO_HTTP)
1161     {
1162 #if YAZ_HAVE_XML2
1163         if (logical_url)
1164         {
1165             const char *db = 0;
1166             
1167             c->proto = PROTO_HTTP;
1168             cs_get_host_args(logical_url, &db);
1169             xfree(c->path);
1170             
1171             c->path = xmalloc(strlen(db) * 3 + 2);
1172             yaz_encode_sru_dbpath_buf(c->path, db);
1173         }
1174 #else
1175         ZOOM_set_error(c, ZOOM_ERROR_UNSUPPORTED_PROTOCOL, "SRW");
1176         ZOOM_connection_close(c);
1177         return zoom_complete;
1178 #endif
1179     }
1180     if (c->cs)
1181     {
1182         int ret = cs_connect(c->cs, add);
1183         if (ret == 0)
1184         {
1185             ZOOM_Event event = ZOOM_Event_create(ZOOM_EVENT_CONNECT);
1186             ZOOM_connection_put_event(c, event);
1187             get_cert(c);
1188             if (c->proto == PROTO_Z3950)
1189                 ZOOM_connection_Z3950_send_init(c);
1190             else
1191             {
1192                 /* no init request for SRW .. */
1193                 assert(c->tasks->which == ZOOM_TASK_CONNECT);
1194                 ZOOM_connection_remove_task(c);
1195                 ZOOM_connection_set_mask(c, 0);
1196                 ZOOM_connection_exec_task(c);
1197             }
1198             c->state = STATE_ESTABLISHED;
1199             return zoom_pending;
1200         }
1201         else if (ret > 0)
1202         {
1203             int mask = ZOOM_SELECT_EXCEPT;
1204             if (c->cs->io_pending & CS_WANT_WRITE)
1205                 mask += ZOOM_SELECT_WRITE;
1206             if (c->cs->io_pending & CS_WANT_READ)
1207                 mask += ZOOM_SELECT_READ;
1208             ZOOM_connection_set_mask(c, mask);
1209             c->state = STATE_CONNECTING; 
1210             return zoom_pending;
1211         }
1212     }
1213     c->state = STATE_IDLE;
1214     ZOOM_set_error(c, ZOOM_ERROR_CONNECT, logical_url);
1215     return zoom_complete;
1216 }
1217
1218 /* returns 1 if PDU was sent OK (still pending )
1219    0 if PDU was not sent OK (nothing to wait for) 
1220 */
1221
1222 #if YAZ_HAVE_XML2
1223 static zoom_ret send_srw(ZOOM_connection c, Z_SRW_PDU *sr)
1224 {
1225     Z_GDU *gdu;
1226     ZOOM_Event event;
1227     const char *database =  ZOOM_options_get(c->options, "databaseName");
1228     char *fdatabase = 0;
1229     
1230     if (database)
1231         fdatabase = yaz_encode_sru_dbpath_odr(c->odr_out, database);
1232     gdu = z_get_HTTP_Request_host_path(c->odr_out, c->host_port,
1233                                        fdatabase ? fdatabase : c->path);
1234
1235     if (c->sru_mode == zoom_sru_get)
1236     {
1237         yaz_sru_get_encode(gdu->u.HTTP_Request, sr, c->odr_out, c->charset);
1238     }
1239     else if (c->sru_mode == zoom_sru_post)
1240     {
1241         yaz_sru_post_encode(gdu->u.HTTP_Request, sr, c->odr_out, c->charset);
1242     }
1243     else if (c->sru_mode == zoom_sru_soap)
1244     {
1245         yaz_sru_soap_encode(gdu->u.HTTP_Request, sr, c->odr_out, c->charset);
1246     }
1247     if (!z_GDU(c->odr_out, &gdu, 0, 0))
1248         return zoom_complete;
1249     if (c->odr_print)
1250         z_GDU(c->odr_print, &gdu, 0, 0);
1251     c->buf_out = odr_getbuf(c->odr_out, &c->len_out, 0);
1252         
1253     event = ZOOM_Event_create(ZOOM_EVENT_SEND_APDU);
1254     ZOOM_connection_put_event(c, event);
1255     odr_reset(c->odr_out);
1256     return ZOOM_send_buf(c);
1257 }
1258 #endif
1259
1260 #if YAZ_HAVE_XML2
1261 static Z_SRW_PDU *ZOOM_srw_get_pdu(ZOOM_connection c, int type)
1262 {
1263     Z_SRW_PDU *sr = yaz_srw_get_pdu(c->odr_out, type, c->sru_version);
1264     sr->username = c->user;
1265     sr->password = c->password;
1266     return sr;
1267 }
1268 #endif
1269
1270 #if YAZ_HAVE_XML2
1271 static zoom_ret ZOOM_connection_srw_send_search(ZOOM_connection c)
1272 {
1273     int i;
1274     int *start, *count;
1275     ZOOM_resultset resultset = 0;
1276     Z_SRW_PDU *sr = 0;
1277     const char *option_val = 0;
1278     Z_Query *z_query;
1279
1280     if (c->error)                  /* don't continue on error */
1281         return zoom_complete;
1282     assert(c->tasks);
1283     switch(c->tasks->which)
1284     {
1285     case ZOOM_TASK_SEARCH:
1286         resultset = c->tasks->u.search.resultset;
1287         if (!resultset->setname)
1288             resultset->setname = xstrdup("default");
1289         ZOOM_options_set(resultset->options, "setname", resultset->setname);
1290         start = &c->tasks->u.search.start;
1291         count = &c->tasks->u.search.count;
1292         break;
1293     case ZOOM_TASK_RETRIEVE:
1294         resultset = c->tasks->u.retrieve.resultset;
1295
1296         start = &c->tasks->u.retrieve.start;
1297         count = &c->tasks->u.retrieve.count;
1298
1299         if (*start >= resultset->size)
1300             return zoom_complete;
1301         if (*start + *count > resultset->size)
1302             *count = resultset->size - *start;
1303
1304         for (i = 0; i < *count; i++)
1305         {
1306             ZOOM_record rec =
1307                 ZOOM_record_cache_lookup(resultset, i + *start,
1308                                          c->tasks->u.retrieve.syntax,
1309                                          c->tasks->u.retrieve.elementSetName);
1310             if (!rec)
1311                 break;
1312             else
1313             {
1314                 ZOOM_Event event = ZOOM_Event_create(ZOOM_EVENT_RECV_RECORD);
1315                 ZOOM_connection_put_event(c, event);
1316             }
1317         }
1318         *start += i;
1319         *count -= i;
1320
1321         if (*count == 0)
1322             return zoom_complete;
1323         break;
1324     default:
1325         return zoom_complete;
1326     }
1327     assert(resultset->query);
1328         
1329     sr = ZOOM_srw_get_pdu(c, Z_SRW_searchRetrieve_request);
1330     z_query = ZOOM_query_get_Z_Query(resultset->query);
1331
1332     if (z_query->which == Z_Query_type_104
1333         && z_query->u.type_104->which == Z_External_CQL)
1334     {
1335         sr->u.request->query_type = Z_SRW_query_type_cql;
1336         sr->u.request->query.cql = z_query->u.type_104->u.cql;
1337     }
1338     else if (z_query->which == Z_Query_type_1 && z_query->u.type_1)
1339     {
1340         sr->u.request->query_type = Z_SRW_query_type_pqf;
1341         sr->u.request->query.pqf =
1342             ZOOM_query_get_query_string(resultset->query);
1343     }
1344     else
1345     {
1346         ZOOM_set_error(c, ZOOM_ERROR_UNSUPPORTED_QUERY, 0);
1347         return zoom_complete;
1348     }
1349     sr->u.request->startRecord = odr_intdup(c->odr_out, *start + 1);
1350     sr->u.request->maximumRecords = odr_intdup(
1351         c->odr_out, (resultset->step > 0 && resultset->step < *count) ? 
1352         resultset->step : *count);
1353     sr->u.request->recordSchema = resultset->schema;
1354     
1355     option_val = ZOOM_resultset_option_get(resultset, "recordPacking");
1356     if (option_val)
1357         sr->u.request->recordPacking = odr_strdup(c->odr_out, option_val);
1358
1359     option_val = ZOOM_resultset_option_get(resultset, "extraArgs");
1360     yaz_encode_sru_extra(sr, c->odr_out, option_val);
1361     return send_srw(c, sr);
1362 }
1363 #else
1364 static zoom_ret ZOOM_connection_srw_send_search(ZOOM_connection c)
1365 {
1366     return zoom_complete;
1367 }
1368 #endif
1369
1370 ZOOM_API(ZOOM_record)
1371     ZOOM_record_clone(ZOOM_record srec)
1372 {
1373     char *buf;
1374     int size;
1375     ODR odr_enc;
1376     ZOOM_record nrec;
1377
1378     odr_enc = odr_createmem(ODR_ENCODE);
1379     if (!z_NamePlusRecord(odr_enc, &srec->npr, 0, 0))
1380         return 0;
1381     buf = odr_getbuf(odr_enc, &size, 0);
1382     
1383     nrec = (ZOOM_record) xmalloc(sizeof(*nrec));
1384     yaz_log(log_details0, "ZOOM_record create");
1385     nrec->odr = odr_createmem(ODR_DECODE);
1386 #if SHPTR
1387     nrec->record_wrbuf = 0;
1388 #else
1389     nrec->wrbuf = 0;
1390 #endif
1391     odr_setbuf(nrec->odr, buf, size, 0);
1392     z_NamePlusRecord(nrec->odr, &nrec->npr, 0, 0);
1393     
1394     nrec->schema = odr_strdup_null(nrec->odr, srec->schema);
1395     nrec->diag_uri = odr_strdup_null(nrec->odr, srec->diag_uri);
1396     nrec->diag_message = odr_strdup_null(nrec->odr, srec->diag_message);
1397     nrec->diag_details = odr_strdup_null(nrec->odr, srec->diag_details);
1398     nrec->diag_set = odr_strdup_null(nrec->odr, srec->diag_set);
1399     odr_destroy(odr_enc);
1400     return nrec;
1401 }
1402
1403 ZOOM_API(ZOOM_record)
1404     ZOOM_resultset_record_immediate(ZOOM_resultset s,size_t pos)
1405 {
1406     const char *syntax =
1407         ZOOM_options_get(s->options, "preferredRecordSyntax"); 
1408     const char *elementSetName =
1409         ZOOM_options_get(s->options, "elementSetName");
1410
1411     return ZOOM_record_cache_lookup(s, pos, syntax, elementSetName);
1412 }
1413
1414 ZOOM_API(ZOOM_record)
1415     ZOOM_resultset_record(ZOOM_resultset r, size_t pos)
1416 {
1417     ZOOM_record rec = ZOOM_resultset_record_immediate(r, pos);
1418
1419     if (!rec)
1420     {
1421         /*
1422          * MIKE: I think force_sync should always be zero, but I don't
1423          * want to make this change until I get the go-ahead from
1424          * Adam, in case something depends on the old synchronous
1425          * behaviour.
1426          */
1427         int force_sync = 1;
1428         if (getenv("ZOOM_RECORD_NO_FORCE_SYNC")) force_sync = 0;
1429         ZOOM_resultset_retrieve(r, force_sync, pos, 1);
1430         rec = ZOOM_resultset_record_immediate(r, pos);
1431     }
1432     return rec;
1433 }
1434
1435 ZOOM_API(void)
1436     ZOOM_record_destroy(ZOOM_record rec)
1437 {
1438     ZOOM_record_release(rec);
1439     yaz_log(log_details0, "ZOOM_record destroy");
1440     xfree(rec);
1441 }
1442
1443
1444 static yaz_iconv_t iconv_create_charset(const char *record_charset)
1445 {
1446     char to[40];
1447     char from[40];
1448     yaz_iconv_t cd = 0;
1449
1450     *from = '\0';
1451     strcpy(to, "UTF-8");
1452     if (record_charset && *record_charset)
1453     {
1454         /* Use "from,to" or just "from" */
1455         const char *cp = strchr(record_charset, ',');
1456         size_t clen = strlen(record_charset);
1457         if (cp && cp[1])
1458         {
1459             strncpy( to, cp+1, sizeof(to)-1);
1460             to[sizeof(to)-1] = '\0';
1461             clen = cp - record_charset;
1462         }
1463         if (clen > sizeof(from)-1)
1464             clen = sizeof(from)-1;
1465         
1466         if (clen)
1467             strncpy(from, record_charset, clen);
1468         from[clen] = '\0';
1469     }
1470     if (*from && *to)
1471         cd = yaz_iconv_open(to, from);
1472     return cd;
1473 }
1474
1475 static const char *return_marc_record(WRBUF wrbuf,
1476                                       int marc_type,
1477                                       int *len,
1478                                       const char *buf, int sz,
1479                                       const char *record_charset)
1480 {
1481     yaz_iconv_t cd = iconv_create_charset(record_charset);
1482     yaz_marc_t mt = yaz_marc_create();
1483     const char *ret_string = 0;
1484
1485     if (cd)
1486         yaz_marc_iconv(mt, cd);
1487     yaz_marc_xml(mt, marc_type);
1488     if (yaz_marc_decode_wrbuf(mt, buf, sz, wrbuf) > 0)
1489     {
1490         if (len)
1491             *len = wrbuf_len(wrbuf);
1492         ret_string = wrbuf_cstr(wrbuf);
1493     }
1494     yaz_marc_destroy(mt);
1495     if (cd)
1496         yaz_iconv_close(cd);
1497     return ret_string;
1498 }
1499
1500 static const char *return_opac_record(WRBUF wrbuf,
1501                                       int marc_type,
1502                                       int *len,
1503                                       Z_OPACRecord *opac_rec,
1504                                       const char *record_charset)
1505 {
1506     yaz_iconv_t cd = iconv_create_charset(record_charset);
1507     yaz_marc_t mt = yaz_marc_create();
1508
1509     if (cd)
1510         yaz_marc_iconv(mt, cd);
1511     yaz_marc_xml(mt, marc_type);
1512
1513     yaz_opac_decode_wrbuf(mt, opac_rec, wrbuf);
1514     yaz_marc_destroy(mt);
1515
1516     if (cd)
1517         yaz_iconv_close(cd);
1518     if (len)
1519         *len = wrbuf_len(wrbuf);
1520     return wrbuf_cstr(wrbuf);
1521 }
1522
1523 static const char *return_string_record(WRBUF wrbuf,
1524                                         int *len,
1525                                         const char *buf, int sz,
1526                                         const char *record_charset)
1527 {
1528     yaz_iconv_t cd = iconv_create_charset(record_charset);
1529
1530     if (cd)
1531     {
1532         wrbuf_iconv_write(wrbuf, cd, buf, sz);
1533         wrbuf_iconv_reset(wrbuf, cd);
1534
1535         buf = wrbuf_cstr(wrbuf);
1536         sz = wrbuf_len(wrbuf);
1537         yaz_iconv_close(cd);
1538     }
1539     if (len)
1540         *len = sz;
1541     return buf;
1542 }
1543
1544 static const char *return_record_wrbuf(WRBUF wrbuf, int *len,
1545                                        Z_NamePlusRecord *npr,
1546                                        int marctype, const char *charset)
1547 {
1548     Z_External *r = (Z_External *) npr->u.databaseRecord;
1549     const Odr_oid *oid = r->direct_reference;
1550
1551     wrbuf_rewind(wrbuf);
1552     /* render bibliographic record .. */
1553     if (r->which == Z_External_OPAC)
1554     {
1555         return return_opac_record(wrbuf, marctype, len,
1556                                   r->u.opac, charset);
1557     }
1558     if (r->which == Z_External_sutrs)
1559         return return_string_record(wrbuf, len,
1560                                     (char*) r->u.sutrs->buf,
1561                                     r->u.sutrs->len,
1562                                     charset);
1563     else if (r->which == Z_External_octet)
1564     {
1565         if (yaz_oid_is_iso2709(oid))
1566         {
1567             const char *ret_buf = return_marc_record(
1568                 wrbuf, marctype, len,
1569                 (const char *) r->u.octet_aligned->buf,
1570                 r->u.octet_aligned->len,
1571                 charset);
1572             if (ret_buf)
1573                 return ret_buf;
1574             /* bad ISO2709. Return fail unless raw (ISO2709) is wanted */
1575             if (marctype != YAZ_MARC_ISO2709)
1576                 return 0;
1577         }
1578         return return_string_record(wrbuf, len,
1579                                     (const char *) r->u.octet_aligned->buf,
1580                                     r->u.octet_aligned->len,
1581                                     charset);
1582     }
1583     else if (r->which == Z_External_grs1)
1584     {
1585         yaz_display_grs1(wrbuf, r->u.grs1, 0);
1586         return return_string_record(wrbuf, len,
1587                                     wrbuf_buf(wrbuf),
1588                                     wrbuf_len(wrbuf),
1589                                     charset);
1590     }
1591     return 0;
1592 }
1593     
1594 ZOOM_API(int)
1595     ZOOM_record_error(ZOOM_record rec, const char **cp,
1596                       const char **addinfo, const char **diagset)
1597 {
1598     Z_NamePlusRecord *npr;
1599     
1600     if (!rec)
1601         return 0;
1602
1603     npr = rec->npr;
1604     if (rec->diag_uri)
1605     {
1606         if (cp)
1607             *cp = rec->diag_message;
1608         if (addinfo)
1609             *addinfo = rec->diag_details;
1610         if (diagset)
1611             *diagset = rec->diag_set;
1612         return uri_to_code(rec->diag_uri);
1613     }
1614     if (npr && npr->which == Z_NamePlusRecord_surrogateDiagnostic)
1615     {
1616         Z_DiagRec *diag_rec = npr->u.surrogateDiagnostic;
1617         int error = YAZ_BIB1_UNSPECIFIED_ERROR;
1618         const char *add = 0;
1619
1620         if (diag_rec->which == Z_DiagRec_defaultFormat)
1621         {
1622             Z_DefaultDiagFormat *ddf = diag_rec->u.defaultFormat;
1623             oid_class oclass;
1624     
1625             error = *ddf->condition;
1626             switch (ddf->which)
1627             {
1628             case Z_DefaultDiagFormat_v2Addinfo:
1629                 add = ddf->u.v2Addinfo;
1630                 break;
1631             case Z_DefaultDiagFormat_v3Addinfo:
1632                 add = ddf->u.v3Addinfo;
1633                 break;
1634             }
1635             if (diagset)
1636                 *diagset =
1637                     yaz_oid_to_string(yaz_oid_std(),
1638                                       ddf->diagnosticSetId, &oclass);
1639         }
1640         else
1641         {
1642             if (diagset)
1643                 *diagset = "Bib-1";
1644         }
1645         if (addinfo)
1646             *addinfo = add ? add : "";
1647         if (cp)
1648             *cp = diagbib1_str(error);
1649         return error;
1650     }
1651     return 0;
1652 }
1653
1654 static const char *get_record_format(WRBUF wrbuf, int *len,
1655                                      Z_NamePlusRecord *npr,
1656                                      int marctype, const char *charset,
1657                                      const char *format)
1658 {
1659     const char *res = return_record_wrbuf(wrbuf, len, npr, marctype, charset);
1660 #if YAZ_HAVE_XML2
1661     if (*format == '1' && len)
1662     {
1663         /* try to XML format res */
1664         xmlDocPtr doc;
1665         xmlKeepBlanksDefault(0); /* get get xmlDocFormatMemory to work! */
1666         doc = xmlParseMemory(res, *len);
1667         if (doc)
1668         {
1669             xmlChar *xml_mem;
1670             int xml_size;
1671             xmlDocDumpFormatMemory(doc, &xml_mem, &xml_size, 1);
1672             wrbuf_rewind(wrbuf);
1673             wrbuf_write(wrbuf, (const char *) xml_mem, xml_size);
1674             xmlFree(xml_mem);
1675             xmlFreeDoc(doc);
1676             res = wrbuf_cstr(wrbuf);
1677             *len = wrbuf_len(wrbuf);
1678         } 
1679     }
1680 #endif
1681     return res;
1682 }
1683
1684
1685 static const char *npr_format(Z_NamePlusRecord *npr, const char *schema,
1686                               WRBUF wrbuf,
1687                               const char *type_spec, int *len)
1688 {
1689     size_t i;
1690     char type[40];
1691     char charset[40];
1692     char format[3];
1693     const char *cp = type_spec;
1694
1695     for (i = 0; cp[i] && cp[i] != ';' && cp[i] != ' ' && i < sizeof(type)-1;
1696          i++)
1697         type[i] = cp[i];
1698     type[i] = '\0';
1699     charset[0] = '\0';
1700     format[0] = '\0';
1701     while (1)
1702     {
1703         while (cp[i] == ' ')
1704             i++;
1705         if (cp[i] != ';')
1706             break;
1707         i++;
1708         while (cp[i] == ' ')
1709             i++;
1710         if (!strncmp(cp + i, "charset=", 8))
1711         {
1712             size_t j = 0;
1713             i = i + 8; /* skip charset= */
1714             for (j = 0; cp[i] && cp[i] != ';' && cp[i] != ' '; i++)
1715             {
1716                 if (j < sizeof(charset)-1)
1717                     charset[j++] = cp[i];
1718             }
1719             charset[j] = '\0';
1720         }
1721         else if (!strncmp(cp + i, "format=", 7))
1722         {
1723             size_t j = 0; 
1724             i = i + 7;
1725             for (j = 0; cp[i] && cp[i] != ';' && cp[i] != ' '; i++)
1726             {
1727                 if (j < sizeof(format)-1)
1728                     format[j++] = cp[i];
1729             }
1730             format[j] = '\0';
1731         } 
1732     }
1733     if (!strcmp(type, "database"))
1734     {
1735         if (len)
1736             *len = (npr->databaseName ? strlen(npr->databaseName) : 0);
1737         return npr->databaseName;
1738     }
1739     else if (!strcmp(type, "schema"))
1740     {
1741         if (len)
1742             *len = schema ? strlen(schema) : 0;
1743         return schema;
1744     }
1745     else if (!strcmp(type, "syntax"))
1746     {
1747         const char *desc = 0;   
1748         if (npr->which == Z_NamePlusRecord_databaseRecord)
1749         {
1750             Z_External *r = (Z_External *) npr->u.databaseRecord;
1751             desc = yaz_oid_to_string(yaz_oid_std(), r->direct_reference, 0);
1752         }
1753         if (!desc)
1754             desc = "none";
1755         if (len)
1756             *len = strlen(desc);
1757         return desc;
1758     }
1759     if (npr->which != Z_NamePlusRecord_databaseRecord)
1760         return 0;
1761
1762     /* from now on - we have a database record .. */
1763     if (!strcmp(type, "render"))
1764     {
1765         return get_record_format(wrbuf, len, npr, YAZ_MARC_LINE, charset, format);
1766     }
1767     else if (!strcmp(type, "xml"))
1768     {
1769         return get_record_format(wrbuf, len, npr, YAZ_MARC_MARCXML, charset,
1770                                  format);
1771     }
1772     else if (!strcmp(type, "txml"))
1773     {
1774         return get_record_format(wrbuf, len, npr, YAZ_MARC_TURBOMARC, charset,
1775                                  format);
1776     }
1777     else if (!strcmp(type, "raw"))
1778     {
1779         return get_record_format(wrbuf, len, npr, YAZ_MARC_ISO2709, charset,
1780             format);
1781     }
1782     else if (!strcmp(type, "ext"))
1783     {
1784         if (len) *len = -1;
1785         return (const char *) npr->u.databaseRecord;
1786     }
1787     else if (!strcmp(type, "opac"))
1788     {
1789         if (npr->u.databaseRecord->which == Z_External_OPAC)
1790             return get_record_format(wrbuf, len, npr, YAZ_MARC_MARCXML, charset,
1791                                      format);
1792     }
1793     return 0;
1794 }
1795
1796 ZOOM_API(const char *)
1797     ZOOM_record_get(ZOOM_record rec, const char *type_spec, int *len)
1798 {
1799     WRBUF wrbuf;
1800     
1801     if (len)
1802         *len = 0; /* default return */
1803         
1804     if (!rec || !rec->npr)
1805         return 0;
1806
1807 #if SHPTR
1808     if (!rec->record_wrbuf)
1809     {
1810         WRBUF w = wrbuf_alloc();
1811         YAZ_SHPTR_INIT(rec->record_wrbuf, w);
1812     }
1813     wrbuf = rec->record_wrbuf->ptr;
1814 #else
1815     if (!rec->wrbuf)
1816         rec->wrbuf = wrbuf_alloc();
1817     wrbuf = rec->wrbuf;
1818 #endif
1819     return npr_format(rec->npr, rec->schema, wrbuf, type_spec, len);
1820 }
1821
1822 static int strcmp_null(const char *v1, const char *v2)
1823 {
1824     if (!v1 && !v2)
1825         return 0;
1826     if (!v1 || !v2)
1827         return -1;
1828     return strcmp(v1, v2);
1829 }
1830
1831 static size_t record_hash(int pos)
1832 {
1833     if (pos < 0)
1834         pos = 0;
1835     return pos % RECORD_HASH_SIZE;
1836 }
1837
1838 void ZOOM_record_cache_add(ZOOM_resultset r, Z_NamePlusRecord *npr, 
1839                            int pos,
1840                            const char *syntax, const char *elementSetName,
1841                            const char *schema,
1842                            Z_SRW_diagnostic *diag)
1843 {
1844     ZOOM_record_cache rc = 0;
1845     
1846     ZOOM_Event event = ZOOM_Event_create(ZOOM_EVENT_RECV_RECORD);
1847     ZOOM_connection_put_event(r->connection, event);
1848
1849     for (rc = r->record_hash[record_hash(pos)]; rc; rc = rc->next)
1850     {
1851         if (pos == rc->pos 
1852             && strcmp_null(r->schema, rc->schema) == 0
1853             && strcmp_null(elementSetName,rc->elementSetName) == 0
1854             && strcmp_null(syntax, rc->syntax) == 0)
1855             break;
1856     }
1857     if (!rc)
1858     {
1859         rc = (ZOOM_record_cache) odr_malloc(r->odr, sizeof(*rc));
1860         rc->rec.odr = 0;
1861 #if SHPTR
1862         YAZ_SHPTR_INC(r->record_wrbuf);
1863         rc->rec.record_wrbuf = r->record_wrbuf;
1864 #else
1865         rc->rec.wrbuf = 0;
1866 #endif
1867         rc->elementSetName = odr_strdup_null(r->odr, elementSetName);
1868         
1869         rc->syntax = odr_strdup_null(r->odr, syntax);
1870         
1871         rc->schema = odr_strdup_null(r->odr, r->schema);
1872
1873         rc->pos = pos;
1874         rc->next = r->record_hash[record_hash(pos)];
1875         r->record_hash[record_hash(pos)] = rc;
1876     }
1877     rc->rec.npr = npr;
1878     rc->rec.schema = odr_strdup_null(r->odr, schema);
1879     rc->rec.diag_set = 0;
1880     rc->rec.diag_uri = 0;
1881     rc->rec.diag_message = 0;
1882     rc->rec.diag_details = 0;
1883     if (diag)
1884     {
1885         if (diag->uri)
1886         {
1887             char *cp;
1888             rc->rec.diag_set = odr_strdup(r->odr, diag->uri);
1889             if ((cp = strrchr(rc->rec.diag_set, '/')))
1890                 *cp = '\0';
1891             rc->rec.diag_uri = odr_strdup(r->odr, diag->uri);
1892         }
1893         rc->rec.diag_message = odr_strdup_null(r->odr, diag->message);            
1894         rc->rec.diag_details = odr_strdup_null(r->odr, diag->details);
1895     }
1896 }
1897
1898 ZOOM_record ZOOM_record_cache_lookup(ZOOM_resultset r, int pos,
1899                                      const char *syntax,
1900                                      const char *elementSetName)
1901 {
1902     ZOOM_record_cache rc;
1903     
1904     for (rc = r->record_hash[record_hash(pos)]; rc; rc = rc->next)
1905     {
1906         if (pos == rc->pos)
1907         {
1908             if (strcmp_null(r->schema, rc->schema))
1909                 continue;
1910             if (strcmp_null(elementSetName,rc->elementSetName))
1911                 continue;
1912             if (strcmp_null(syntax, rc->syntax))
1913                 continue;
1914             return &rc->rec;
1915         }
1916     }
1917     return 0;
1918 }
1919                                              
1920
1921 ZOOM_API(ZOOM_scanset)
1922     ZOOM_connection_scan(ZOOM_connection c, const char *start)
1923 {
1924     ZOOM_scanset s;
1925     ZOOM_query q = ZOOM_query_create();
1926
1927     ZOOM_query_prefix(q, start);
1928
1929     s = ZOOM_connection_scan1(c, q);
1930     ZOOM_query_destroy(q);
1931     return s;
1932
1933 }
1934
1935 ZOOM_API(ZOOM_scanset)
1936     ZOOM_connection_scan1(ZOOM_connection c, ZOOM_query q)
1937 {
1938     ZOOM_scanset scan = 0;
1939     Z_Query *z_query = ZOOM_query_get_Z_Query(q);
1940
1941     if (!z_query)
1942         return 0;
1943     scan = (ZOOM_scanset) xmalloc(sizeof(*scan));
1944     scan->connection = c;
1945     scan->odr = odr_createmem(ODR_DECODE);
1946     scan->options = ZOOM_options_create_with_parent(c->options);
1947     scan->refcount = 1;
1948     scan->scan_response = 0;
1949     scan->srw_scan_response = 0;
1950
1951     scan->query = q;
1952     ZOOM_query_addref(q);
1953     scan->databaseNames = ZOOM_connection_get_databases(c, c->options,
1954                                             &scan->num_databaseNames,
1955                                             scan->odr);
1956
1957     if (1)
1958     {
1959         ZOOM_task task = ZOOM_connection_add_task(c, ZOOM_TASK_SCAN);
1960         task->u.scan.scan = scan;
1961         
1962         (scan->refcount)++;
1963         if (!c->async)
1964         {
1965             while (ZOOM_event(1, &c))
1966                 ;
1967         }
1968     }
1969     return scan;
1970 }
1971
1972 ZOOM_API(void)
1973     ZOOM_scanset_destroy(ZOOM_scanset scan)
1974 {
1975     if (!scan)
1976         return;
1977     (scan->refcount)--;
1978     if (scan->refcount == 0)
1979     {
1980         ZOOM_query_destroy(scan->query);
1981
1982         odr_destroy(scan->odr);
1983         
1984         ZOOM_options_destroy(scan->options);
1985         xfree(scan);
1986     }
1987 }
1988
1989 static zoom_ret send_package(ZOOM_connection c)
1990 {
1991     ZOOM_Event event;
1992
1993     yaz_log(c->log_details, "%p send_package", c);
1994     if (!c->tasks)
1995         return zoom_complete;
1996     assert (c->tasks->which == ZOOM_TASK_PACKAGE);
1997     
1998     event = ZOOM_Event_create(ZOOM_EVENT_SEND_APDU);
1999     ZOOM_connection_put_event(c, event);
2000     
2001     c->buf_out = c->tasks->u.package->buf_out;
2002     c->len_out = c->tasks->u.package->len_out;
2003
2004     return ZOOM_send_buf(c);
2005 }
2006
2007 #if YAZ_HAVE_XML2
2008 static zoom_ret ZOOM_connection_srw_send_scan(ZOOM_connection c)
2009 {
2010     ZOOM_scanset scan;
2011     Z_SRW_PDU *sr = 0;
2012     const char *option_val = 0;
2013     Z_Query *z_query;
2014
2015     if (!c->tasks)
2016         return zoom_complete;
2017     assert (c->tasks->which == ZOOM_TASK_SCAN);
2018     scan = c->tasks->u.scan.scan;
2019         
2020     sr = ZOOM_srw_get_pdu(c, Z_SRW_scan_request);
2021
2022     z_query = ZOOM_query_get_Z_Query(scan->query);
2023     /* SRU scan can only carry CQL and PQF */
2024     if (z_query->which == Z_Query_type_104)
2025     {
2026         sr->u.scan_request->query_type = Z_SRW_query_type_cql;
2027         sr->u.scan_request->scanClause.cql =
2028             ZOOM_query_get_query_string(scan->query);
2029     }
2030     else if (z_query->which == Z_Query_type_1
2031              || z_query->which == Z_Query_type_101)
2032     {
2033         sr->u.scan_request->query_type = Z_SRW_query_type_pqf;
2034         sr->u.scan_request->scanClause.pqf =
2035             ZOOM_query_get_query_string(scan->query);
2036     }
2037     else
2038     {
2039         ZOOM_set_error(c, ZOOM_ERROR_UNSUPPORTED_QUERY, 0);
2040         return zoom_complete;
2041     }
2042
2043     sr->u.scan_request->maximumTerms = odr_intdup(
2044         c->odr_out, ZOOM_options_get_int(scan->options, "number", 10));
2045     
2046     sr->u.scan_request->responsePosition = odr_intdup(
2047         c->odr_out, ZOOM_options_get_int(scan->options, "position", 1));
2048     
2049     option_val = ZOOM_options_get(scan->options, "extraArgs");
2050     yaz_encode_sru_extra(sr, c->odr_out, option_val);
2051     return send_srw(c, sr);
2052 }
2053 #else
2054 static zoom_ret ZOOM_connection_srw_send_scan(ZOOM_connection c)
2055 {
2056     return zoom_complete;
2057 }
2058 #endif
2059
2060
2061 ZOOM_API(size_t)
2062     ZOOM_scanset_size(ZOOM_scanset scan)
2063 {
2064     if (!scan)
2065         return 0;
2066
2067     if (scan->scan_response && scan->scan_response->entries)
2068         return scan->scan_response->entries->num_entries;
2069     else if (scan->srw_scan_response)
2070         return scan->srw_scan_response->num_terms;
2071     return 0;
2072 }
2073
2074 static void ZOOM_scanset_term_x(ZOOM_scanset scan, size_t pos,
2075                                 size_t *occ,
2076                                 const char **value_term, size_t *value_len,
2077                                 const char **disp_term, size_t *disp_len)
2078 {
2079     size_t noent = ZOOM_scanset_size(scan);
2080     
2081     *value_term = 0;
2082     *value_len = 0;
2083
2084     *disp_term = 0;
2085     *disp_len = 0;
2086
2087     *occ = 0;
2088     if (pos >= noent)
2089         return;
2090     if (scan->scan_response)
2091     {
2092         Z_ScanResponse *res = scan->scan_response;
2093         if (res->entries->entries[pos]->which == Z_Entry_termInfo)
2094         {
2095             Z_TermInfo *t = res->entries->entries[pos]->u.termInfo;
2096             
2097             *value_term = (const char *) t->term->u.general->buf;
2098             *value_len = t->term->u.general->len;
2099             if (t->displayTerm)
2100             {
2101                 *disp_term = t->displayTerm;
2102                 *disp_len = strlen(*disp_term);
2103             }
2104             else if (t->term->which == Z_Term_general)
2105             {
2106                 *disp_term = (const char *) t->term->u.general->buf;
2107                 *disp_len = t->term->u.general->len;
2108             }
2109             *occ = t->globalOccurrences ? *t->globalOccurrences : 0;
2110         }
2111     }
2112     if (scan->srw_scan_response)
2113     {
2114         Z_SRW_scanResponse *res = scan->srw_scan_response;
2115         Z_SRW_scanTerm *t = res->terms + pos;
2116         if (t)
2117         {
2118             *value_term = t->value;
2119             *value_len = strlen(*value_term);
2120
2121             if (t->displayTerm)
2122                 *disp_term = t->displayTerm;
2123             else
2124                 *disp_term = t->value;
2125             *disp_len = strlen(*disp_term);
2126             *occ = t->numberOfRecords ? *t->numberOfRecords : 0;
2127         }
2128     }
2129 }
2130
2131 ZOOM_API(const char *)
2132     ZOOM_scanset_term(ZOOM_scanset scan, size_t pos,
2133                       size_t *occ, size_t *len)
2134 {
2135     const char *value_term = 0;
2136     size_t value_len = 0;
2137     const char *disp_term = 0;
2138     size_t disp_len = 0;
2139
2140     ZOOM_scanset_term_x(scan, pos, occ, &value_term, &value_len,
2141                         &disp_term, &disp_len);
2142     
2143     *len = value_len;
2144     return value_term;
2145 }
2146
2147 ZOOM_API(const char *)
2148     ZOOM_scanset_display_term(ZOOM_scanset scan, size_t pos,
2149                               size_t *occ, size_t *len)
2150 {
2151     const char *value_term = 0;
2152     size_t value_len = 0;
2153     const char *disp_term = 0;
2154     size_t disp_len = 0;
2155
2156     ZOOM_scanset_term_x(scan, pos, occ, &value_term, &value_len,
2157                         &disp_term, &disp_len);
2158     
2159     *len = disp_len;
2160     return disp_term;
2161 }
2162
2163 ZOOM_API(const char *)
2164     ZOOM_scanset_option_get(ZOOM_scanset scan, const char *key)
2165 {
2166     return ZOOM_options_get(scan->options, key);
2167 }
2168
2169 ZOOM_API(void)
2170     ZOOM_scanset_option_set(ZOOM_scanset scan, const char *key,
2171                             const char *val)
2172 {
2173     ZOOM_options_set(scan->options, key, val);
2174 }
2175
2176
2177 ZOOM_API(ZOOM_package)
2178     ZOOM_connection_package(ZOOM_connection c, ZOOM_options options)
2179 {
2180     ZOOM_package p = (ZOOM_package) xmalloc(sizeof(*p));
2181
2182     p->connection = c;
2183     p->odr_out = odr_createmem(ODR_ENCODE);
2184     p->options = ZOOM_options_create_with_parent2(options, c->options);
2185     p->refcount = 1;
2186     p->buf_out = 0;
2187     p->len_out = 0;
2188     return p;
2189 }
2190
2191 ZOOM_API(void)
2192     ZOOM_package_destroy(ZOOM_package p)
2193 {
2194     if (!p)
2195         return;
2196     (p->refcount)--;
2197     if (p->refcount == 0)
2198     {
2199         odr_destroy(p->odr_out);
2200         xfree(p->buf_out);
2201         
2202         ZOOM_options_destroy(p->options);
2203         xfree(p);
2204     }
2205 }
2206
2207 ZOOM_API(const char *)
2208     ZOOM_package_option_get(ZOOM_package p, const char *key)
2209 {
2210     return ZOOM_options_get(p->options, key);
2211 }
2212
2213 ZOOM_API(const char *)
2214     ZOOM_package_option_getl(ZOOM_package p, const char *key, int *lenp)
2215 {
2216     return ZOOM_options_getl(p->options, key, lenp);
2217 }
2218
2219 ZOOM_API(void)
2220     ZOOM_package_option_set(ZOOM_package p, const char *key,
2221                             const char *val)
2222 {
2223     ZOOM_options_set(p->options, key, val);
2224 }
2225
2226 ZOOM_API(void)
2227     ZOOM_package_option_setl(ZOOM_package p, const char *key,
2228                              const char *val, int len)
2229 {
2230     ZOOM_options_setl(p->options, key, val, len);
2231 }
2232
2233 ZOOM_API(int)
2234     ZOOM_connection_exec_task(ZOOM_connection c)
2235 {
2236     ZOOM_task task = c->tasks;
2237     zoom_ret ret = zoom_complete;
2238
2239     if (!task)
2240         return 0;
2241     yaz_log(c->log_details, "%p ZOOM_connection_exec_task type=%d run=%d",
2242             c, task->which, task->running);
2243     if (c->error != ZOOM_ERROR_NONE)
2244     {
2245         yaz_log(c->log_details, "%p ZOOM_connection_exec_task "
2246                 "removing tasks because of error = %d", c, c->error);
2247         ZOOM_connection_remove_tasks(c);
2248         return 0;
2249     }
2250     if (task->running)
2251     {
2252         yaz_log(c->log_details, "%p ZOOM_connection_exec_task "
2253                 "task already running", c);
2254         return 0;
2255     }
2256     task->running = 1;
2257     ret = zoom_complete;
2258     if (c->cs || task->which == ZOOM_TASK_CONNECT)
2259     {
2260         switch (task->which)
2261         {
2262         case ZOOM_TASK_SEARCH:
2263             if (c->proto == PROTO_HTTP)
2264                 ret = ZOOM_connection_srw_send_search(c);
2265             else
2266                 ret = ZOOM_connection_Z3950_send_search(c);
2267             break;
2268         case ZOOM_TASK_RETRIEVE:
2269             if (c->proto == PROTO_HTTP)
2270                 ret = ZOOM_connection_srw_send_search(c);
2271             else
2272                 ret = send_Z3950_present(c);
2273             break;
2274         case ZOOM_TASK_CONNECT:
2275             ret = do_connect(c);
2276             break;
2277         case ZOOM_TASK_SCAN:
2278             if (c->proto == PROTO_HTTP)
2279                 ret = ZOOM_connection_srw_send_scan(c);
2280             else
2281                 ret = ZOOM_connection_Z3950_send_scan(c);
2282             break;
2283         case ZOOM_TASK_PACKAGE:
2284             ret = send_package(c);
2285             break;
2286         case ZOOM_TASK_SORT:
2287             c->tasks->u.sort.resultset->r_sort_spec = 
2288                 ZOOM_query_get_sortspec(c->tasks->u.sort.q);
2289             ret = send_Z3950_sort(c, c->tasks->u.sort.resultset);
2290             break;
2291         }
2292     }
2293     else
2294     {
2295         yaz_log(c->log_details, "%p ZOOM_connection_exec_task "
2296                 "remove tasks because no connection exist", c);
2297         ZOOM_connection_remove_tasks(c);
2298     }
2299     if (ret == zoom_complete)
2300     {
2301         yaz_log(c->log_details, "%p ZOOM_connection_exec_task "
2302                 "task removed (complete)", c);
2303         ZOOM_connection_remove_task(c);
2304         return 0;
2305     }
2306     yaz_log(c->log_details, "%p ZOOM_connection_exec_task "
2307             "task pending", c);
2308     return 1;
2309 }
2310
2311 #if YAZ_HAVE_XML2
2312 static zoom_ret handle_srw_response(ZOOM_connection c,
2313                                     Z_SRW_searchRetrieveResponse *res)
2314 {
2315     ZOOM_resultset resultset = 0;
2316     int i;
2317     NMEM nmem;
2318     ZOOM_Event event;
2319     int *start, *count;
2320     const char *syntax, *elementSetName;
2321
2322     if (!c->tasks)
2323         return zoom_complete;
2324
2325     switch(c->tasks->which)
2326     {
2327     case ZOOM_TASK_SEARCH:
2328         resultset = c->tasks->u.search.resultset;
2329         start = &c->tasks->u.search.start;
2330         count = &c->tasks->u.search.count;
2331         syntax = c->tasks->u.search.syntax;
2332         elementSetName = c->tasks->u.search.elementSetName;        
2333
2334         if (!c->tasks->u.search.recv_search_fired)
2335         {
2336             event = ZOOM_Event_create(ZOOM_EVENT_RECV_SEARCH);
2337             ZOOM_connection_put_event(c, event);
2338             c->tasks->u.search.recv_search_fired = 1;
2339         }
2340         break;
2341     case ZOOM_TASK_RETRIEVE:
2342         resultset = c->tasks->u.retrieve.resultset;
2343         start = &c->tasks->u.retrieve.start;
2344         count = &c->tasks->u.retrieve.count;
2345         syntax = c->tasks->u.retrieve.syntax;
2346         elementSetName = c->tasks->u.retrieve.elementSetName;
2347         break;
2348     default:
2349         return zoom_complete;
2350     }
2351
2352     resultset->size = 0;
2353
2354     if (res->resultSetId)
2355         ZOOM_resultset_option_set(resultset, "resultSetId", res->resultSetId);
2356
2357     yaz_log(c->log_details, "%p handle_srw_response got SRW response OK", c);
2358
2359     if (res->num_diagnostics > 0)
2360     {
2361         set_SRU_error(c, &res->diagnostics[0]);
2362     }
2363     else
2364     {
2365         if (res->numberOfRecords)
2366             resultset->size = *res->numberOfRecords;
2367         for (i = 0; i<res->num_records; i++)
2368         {
2369             int pos;
2370             Z_SRW_record *sru_rec;
2371             Z_SRW_diagnostic *diag = 0;
2372             int num_diag;
2373             
2374             Z_NamePlusRecord *npr = (Z_NamePlusRecord *)
2375                 odr_malloc(c->odr_in, sizeof(Z_NamePlusRecord));
2376             
2377             if (res->records[i].recordPosition && 
2378                 *res->records[i].recordPosition > 0)
2379                 pos = *res->records[i].recordPosition - 1;
2380             else
2381                 pos = *start + i;
2382             
2383             sru_rec = &res->records[i];
2384             
2385             npr->databaseName = 0;
2386             npr->which = Z_NamePlusRecord_databaseRecord;
2387             npr->u.databaseRecord = (Z_External *)
2388                 odr_malloc(c->odr_in, sizeof(Z_External));
2389             npr->u.databaseRecord->descriptor = 0;
2390             npr->u.databaseRecord->direct_reference =
2391                 odr_oiddup(c->odr_in, yaz_oid_recsyn_xml);
2392             npr->u.databaseRecord->which = Z_External_octet;
2393             
2394             npr->u.databaseRecord->u.octet_aligned = (Odr_oct *)
2395                 odr_malloc(c->odr_in, sizeof(Odr_oct));
2396             npr->u.databaseRecord->u.octet_aligned->buf = (unsigned char*)
2397                 sru_rec->recordData_buf;
2398             npr->u.databaseRecord->u.octet_aligned->len = 
2399                 npr->u.databaseRecord->u.octet_aligned->size = 
2400                 sru_rec->recordData_len;
2401             
2402             if (sru_rec->recordSchema 
2403                 && !strcmp(sru_rec->recordSchema,
2404                            "info:srw/schema/1/diagnostics-v1.1"))
2405             {
2406                 sru_decode_surrogate_diagnostics(sru_rec->recordData_buf,
2407                                                  sru_rec->recordData_len,
2408                                                  &diag, &num_diag,
2409                                                  resultset->odr);
2410             }
2411             ZOOM_record_cache_add(resultset, npr, pos, syntax, elementSetName,
2412                                   sru_rec->recordSchema, diag);
2413         }
2414         *count -= i;
2415         *start += i;
2416         if (*count + *start > resultset->size)
2417             *count = resultset->size - *start;
2418         if (*count < 0)
2419             *count = 0;
2420         
2421         nmem = odr_extract_mem(c->odr_in);
2422         nmem_transfer(odr_getmem(resultset->odr), nmem);
2423         nmem_destroy(nmem);
2424
2425         if (*count > 0)
2426             return ZOOM_connection_srw_send_search(c);
2427     }
2428     return zoom_complete;
2429 }
2430 #endif
2431
2432 #if YAZ_HAVE_XML2
2433 static void handle_srw_scan_response(ZOOM_connection c,
2434                                      Z_SRW_scanResponse *res)
2435 {
2436     NMEM nmem = odr_extract_mem(c->odr_in);
2437     ZOOM_scanset scan;
2438
2439     if (!c->tasks || c->tasks->which != ZOOM_TASK_SCAN)
2440         return;
2441     scan = c->tasks->u.scan.scan;
2442
2443     if (res->num_diagnostics > 0)
2444         set_SRU_error(c, &res->diagnostics[0]);
2445
2446     scan->scan_response = 0;
2447     scan->srw_scan_response = res;
2448     nmem_transfer(odr_getmem(scan->odr), nmem);
2449
2450     ZOOM_options_set_int(scan->options, "number", res->num_terms);
2451     nmem_destroy(nmem);
2452 }
2453 #endif
2454
2455 #if YAZ_HAVE_XML2
2456 static Z_GDU *get_HTTP_Request_url(ODR odr, const char *url)
2457 {
2458     Z_GDU *p = z_get_HTTP_Request(odr);
2459     const char *host = url;
2460     const char *cp0 = strstr(host, "://");
2461     const char *cp1 = 0;
2462     if (cp0)
2463         cp0 = cp0+3;
2464     else
2465         cp0 = host;
2466     
2467     cp1 = strchr(cp0, '/');
2468     if (!cp1)
2469         cp1 = cp0 + strlen(cp0);
2470     
2471     if (cp0 && cp1)
2472     {
2473         char *h = (char*) odr_malloc(odr, cp1 - cp0 + 1);
2474         memcpy (h, cp0, cp1 - cp0);
2475         h[cp1-cp0] = '\0';
2476         z_HTTP_header_add(odr, &p->u.HTTP_Request->headers, "Host", h);
2477     }
2478     p->u.HTTP_Request->path = odr_strdup(odr, *cp1 ? cp1 : "/");
2479     return p;
2480 }
2481
2482 static zoom_ret send_SRW_redirect(ZOOM_connection c, const char *uri,
2483                                   Z_HTTP_Response *cookie_hres)
2484 {
2485     struct Z_HTTP_Header *h;
2486     Z_GDU *gdu = get_HTTP_Request_url(c->odr_out, uri);
2487     char *combined_cookies;
2488     int combined_cookies_len = 0;
2489
2490     gdu->u.HTTP_Request->method = odr_strdup(c->odr_out, "GET");
2491     z_HTTP_header_add(c->odr_out, &gdu->u.HTTP_Request->headers, "Accept",
2492                       "text/xml");
2493
2494     for (h = cookie_hres->headers; h; h = h->next)
2495     {
2496         if (!strcmp(h->name, "Set-Cookie"))
2497         {
2498             char *cp;
2499
2500             if (!(cp = strchr(h->value, ';')))
2501                 cp = h->value + strlen(h->value);
2502             if (cp - h->value >= 1) {
2503                 combined_cookies = xrealloc(combined_cookies, combined_cookies_len + cp - h->value + 3);
2504                 memcpy(combined_cookies+combined_cookies_len, h->value, cp - h->value);
2505                 combined_cookies[combined_cookies_len + cp - h->value] = '\0';
2506                 strcat(combined_cookies,"; ");
2507                 combined_cookies_len = strlen(combined_cookies);
2508             }
2509         }
2510     }
2511
2512     if (combined_cookies_len)
2513     {
2514         z_HTTP_header_add(c->odr_out, &gdu->u.HTTP_Request->headers,
2515                           "Cookie", combined_cookies);
2516         xfree(combined_cookies);
2517     }
2518
2519     if (c->user && c->password)
2520     {
2521         z_HTTP_header_add_basic_auth(c->odr_out, &gdu->u.HTTP_Request->headers,
2522                                      c->user, c->password);
2523     }
2524     if (!z_GDU(c->odr_out, &gdu, 0, 0))
2525         return zoom_complete;
2526     if (c->odr_print)
2527         z_GDU(c->odr_print, &gdu, 0, 0);
2528     c->buf_out = odr_getbuf(c->odr_out, &c->len_out, 0);
2529
2530     odr_reset(c->odr_out);
2531     return ZOOM_send_buf(c);
2532 }
2533
2534 static void handle_http(ZOOM_connection c, Z_HTTP_Response *hres)
2535 {
2536     zoom_ret cret = zoom_complete;
2537     int ret = -1;
2538     const char *addinfo = 0;
2539     const char *connection_head = z_HTTP_header_lookup(hres->headers,
2540                                                        "Connection");
2541     const char *location;
2542
2543     ZOOM_connection_set_mask(c, 0);
2544     yaz_log(c->log_details, "%p handle_http", c);
2545     
2546     if ((hres->code == 301 || hres->code == 302) && c->sru_mode == zoom_sru_get
2547         && (location = z_HTTP_header_lookup(hres->headers, "Location")))
2548     {
2549         c->no_redirects++;
2550         if (c->no_redirects > 10)
2551         {
2552             set_HTTP_error(c, hres->code, 0, 0);
2553             c->no_redirects = 0;
2554             ZOOM_connection_close(c);
2555         }
2556         else
2557         {
2558             /* since redirect may change host we just reconnect. A smarter
2559                implementation might check whether it's the same server */
2560             do_connect_host(c, location, 0);
2561             send_SRW_redirect(c, location, hres);
2562             /* we're OK for now. Operation is not really complete */
2563             ret = 0;
2564             cret = zoom_pending;
2565         }
2566     }
2567     else 
2568     {   /* not redirect (normal response) */
2569         if (!yaz_srw_check_content_type(hres))
2570             addinfo = "content-type";
2571         else
2572         {
2573             Z_SOAP *soap_package = 0;
2574             ODR o = c->odr_in;
2575             Z_SOAP_Handler soap_handlers[2] = {
2576                 {YAZ_XMLNS_SRU_v1_1, 0, (Z_SOAP_fun) yaz_srw_codec},
2577                 {0, 0, 0}
2578             };
2579             ret = z_soap_codec(o, &soap_package,
2580                                &hres->content_buf, &hres->content_len,
2581                                soap_handlers);
2582             if (!ret && soap_package->which == Z_SOAP_generic &&
2583                 soap_package->u.generic->no == 0)
2584             {
2585                 Z_SRW_PDU *sr = (Z_SRW_PDU*) soap_package->u.generic->p;
2586                 
2587                 ZOOM_options_set(c->options, "sru_version", sr->srw_version);
2588                 ZOOM_options_setl(c->options, "sru_extra_response_data",
2589                                   sr->extraResponseData_buf, sr->extraResponseData_len);
2590                 if (sr->which == Z_SRW_searchRetrieve_response)
2591                     cret = handle_srw_response(c, sr->u.response);
2592                 else if (sr->which == Z_SRW_scan_response)
2593                     handle_srw_scan_response(c, sr->u.scan_response);
2594                 else
2595                     ret = -1;
2596             }
2597             else if (!ret && (soap_package->which == Z_SOAP_fault
2598                               || soap_package->which == Z_SOAP_error))
2599             {
2600                 set_HTTP_error(c, hres->code,
2601                                soap_package->u.fault->fault_code,
2602                                soap_package->u.fault->fault_string);
2603             }
2604             else
2605                 ret = -1;
2606         }   
2607         if (ret == 0)
2608         {
2609             if (c->no_redirects) /* end of redirect. change hosts again */
2610                 ZOOM_connection_close(c);
2611         }
2612         c->no_redirects = 0;
2613     }
2614     if (ret)
2615     {
2616         if (hres->code != 200)
2617             set_HTTP_error(c, hres->code, 0, 0);
2618         else
2619             ZOOM_set_error(c, ZOOM_ERROR_DECODE, addinfo);
2620         ZOOM_connection_close(c);
2621     }
2622     if (cret == zoom_complete)
2623     {
2624         yaz_log(YLOG_LOG, "removing tasks in handle_http");
2625         ZOOM_connection_remove_task(c);
2626     }
2627     {
2628         int must_close = 0;
2629         if (!strcmp(hres->version, "1.0"))
2630         {
2631             /* HTTP 1.0: only if Keep-Alive we stay alive.. */
2632             if (!connection_head || strcmp(connection_head, "Keep-Alive"))
2633                 must_close = 1;
2634         }
2635         else
2636         {
2637             /* HTTP 1.1: only if no close we stay alive.. */
2638             if (connection_head && !strcmp(connection_head, "close"))
2639                 must_close = 1;
2640         }
2641         if (must_close)
2642         {
2643             ZOOM_connection_close(c);
2644             if (c->tasks)
2645             {
2646                 c->tasks->running = 0;
2647                 ZOOM_connection_insert_task(c, ZOOM_TASK_CONNECT);
2648                 c->reconnect_ok = 0;
2649             }
2650         }
2651     }
2652 }
2653 #endif
2654
2655 static int do_read(ZOOM_connection c)
2656 {
2657     int r, more;
2658     ZOOM_Event event;
2659     
2660     event = ZOOM_Event_create(ZOOM_EVENT_RECV_DATA);
2661     ZOOM_connection_put_event(c, event);
2662     
2663     r = cs_get(c->cs, &c->buf_in, &c->len_in);
2664     more = cs_more(c->cs);
2665     yaz_log(c->log_details, "%p do_read len=%d more=%d", c, r, more);
2666     if (r == 1)
2667         return 0;
2668     if (r <= 0)
2669     {
2670         if (!ZOOM_test_reconnect(c))
2671         {
2672             ZOOM_set_error(c, ZOOM_ERROR_CONNECTION_LOST, c->host_port);
2673             ZOOM_connection_close(c);
2674         }
2675     }
2676     else
2677     {
2678         Z_GDU *gdu;
2679         ZOOM_Event event;
2680
2681         odr_reset(c->odr_in);
2682         odr_setbuf(c->odr_in, c->buf_in, r, 0);
2683         event = ZOOM_Event_create(ZOOM_EVENT_RECV_APDU);
2684         ZOOM_connection_put_event(c, event);
2685
2686         if (!z_GDU(c->odr_in, &gdu, 0, 0))
2687         {
2688             int x;
2689             int err = odr_geterrorx(c->odr_in, &x);
2690             char msg[100];
2691             const char *element = odr_getelement(c->odr_in);
2692             yaz_snprintf(msg, sizeof(msg),
2693                     "ODR code %d:%d element=%s offset=%d",
2694                     err, x, element ? element : "<unknown>",
2695                     odr_offset(c->odr_in));
2696             ZOOM_set_error(c, ZOOM_ERROR_DECODE, msg);
2697             if (c->log_api)
2698             {
2699                 FILE *ber_file = yaz_log_file();
2700                 if (ber_file)
2701                     odr_dumpBER(ber_file, c->buf_in, r);
2702             }
2703             ZOOM_connection_close(c);
2704         }
2705         else
2706         {
2707             if (c->odr_print)
2708                 z_GDU(c->odr_print, &gdu, 0, 0);
2709             if (gdu->which == Z_GDU_Z3950)
2710                 ZOOM_handle_Z3950_apdu(c, gdu->u.z3950);
2711             else if (gdu->which == Z_GDU_HTTP_Response)
2712             {
2713 #if YAZ_HAVE_XML2
2714                 handle_http(c, gdu->u.HTTP_Response);
2715 #else
2716                 ZOOM_set_error(c, ZOOM_ERROR_DECODE, 0);
2717                 ZOOM_connection_close(c);
2718 #endif
2719             }
2720         }
2721         c->reconnect_ok = 0;
2722     }
2723     return 1;
2724 }
2725
2726 static zoom_ret do_write_ex(ZOOM_connection c, char *buf_out, int len_out)
2727 {
2728     int r;
2729     ZOOM_Event event;
2730     
2731     event = ZOOM_Event_create(ZOOM_EVENT_SEND_DATA);
2732     ZOOM_connection_put_event(c, event);
2733
2734     yaz_log(c->log_details, "%p do_write_ex len=%d", c, len_out);
2735     if ((r = cs_put(c->cs, buf_out, len_out)) < 0)
2736     {
2737         yaz_log(c->log_details, "%p do_write_ex write failed", c);
2738         if (ZOOM_test_reconnect(c))
2739         {
2740             return zoom_pending;
2741         }
2742         if (c->state == STATE_CONNECTING)
2743             ZOOM_set_error(c, ZOOM_ERROR_CONNECT, c->host_port);
2744         else
2745             ZOOM_set_error(c, ZOOM_ERROR_CONNECTION_LOST, c->host_port);
2746         ZOOM_connection_close(c);
2747         return zoom_complete;
2748     }
2749     else if (r == 1)
2750     {    
2751         int mask = ZOOM_SELECT_EXCEPT;
2752         if (c->cs->io_pending & CS_WANT_WRITE)
2753             mask += ZOOM_SELECT_WRITE;
2754         if (c->cs->io_pending & CS_WANT_READ)
2755             mask += ZOOM_SELECT_READ;
2756         ZOOM_connection_set_mask(c, mask);
2757         yaz_log(c->log_details, "%p do_write_ex write incomplete mask=%d",
2758                 c, c->mask);
2759     }
2760     else
2761     {
2762         ZOOM_connection_set_mask(c, ZOOM_SELECT_READ|ZOOM_SELECT_EXCEPT);
2763         yaz_log(c->log_details, "%p do_write_ex write complete mask=%d",
2764                 c, c->mask);
2765     }
2766     return zoom_pending;
2767 }
2768
2769 zoom_ret ZOOM_send_buf(ZOOM_connection c)
2770 {
2771     return do_write_ex(c, c->buf_out, c->len_out);
2772 }
2773
2774
2775 ZOOM_API(const char *)
2776     ZOOM_connection_option_get(ZOOM_connection c, const char *key)
2777 {
2778     return ZOOM_options_get(c->options, key);
2779 }
2780
2781 ZOOM_API(const char *)
2782     ZOOM_connection_option_getl(ZOOM_connection c, const char *key, int *lenp)
2783 {
2784     return ZOOM_options_getl(c->options, key, lenp);
2785 }
2786
2787 ZOOM_API(void)
2788     ZOOM_connection_option_set(ZOOM_connection c, const char *key,
2789                                const char *val)
2790 {
2791     ZOOM_options_set(c->options, key, val);
2792 }
2793
2794 ZOOM_API(void)
2795     ZOOM_connection_option_setl(ZOOM_connection c, const char *key,
2796                                 const char *val, int len)
2797 {
2798     ZOOM_options_setl(c->options, key, val, len);
2799 }
2800
2801 ZOOM_API(const char *)
2802     ZOOM_resultset_option_get(ZOOM_resultset r, const char *key)
2803 {
2804     return ZOOM_options_get(r->options, key);
2805 }
2806
2807 ZOOM_API(void)
2808     ZOOM_resultset_option_set(ZOOM_resultset r, const char *key,
2809                               const char *val)
2810 {
2811     ZOOM_options_set(r->options, key, val);
2812 }
2813
2814
2815 ZOOM_API(int)
2816     ZOOM_connection_errcode(ZOOM_connection c)
2817 {
2818     return ZOOM_connection_error(c, 0, 0);
2819 }
2820
2821 ZOOM_API(const char *)
2822     ZOOM_connection_errmsg(ZOOM_connection c)
2823 {
2824     const char *msg;
2825     ZOOM_connection_error(c, &msg, 0);
2826     return msg;
2827 }
2828
2829 ZOOM_API(const char *)
2830     ZOOM_connection_addinfo(ZOOM_connection c)
2831 {
2832     const char *addinfo;
2833     ZOOM_connection_error(c, 0, &addinfo);
2834     return addinfo;
2835 }
2836
2837 ZOOM_API(const char *)
2838     ZOOM_connection_diagset(ZOOM_connection c)
2839 {
2840     const char *diagset;
2841     ZOOM_connection_error_x(c, 0, 0, &diagset);
2842     return diagset;
2843 }
2844
2845 ZOOM_API(const char *)
2846     ZOOM_diag_str(int error)
2847 {
2848     switch (error)
2849     {
2850     case ZOOM_ERROR_NONE:
2851         return "No error";
2852     case ZOOM_ERROR_CONNECT:
2853         return "Connect failed";
2854     case ZOOM_ERROR_MEMORY:
2855         return "Out of memory";
2856     case ZOOM_ERROR_ENCODE:
2857         return "Encoding failed";
2858     case ZOOM_ERROR_DECODE:
2859         return "Decoding failed";
2860     case ZOOM_ERROR_CONNECTION_LOST:
2861         return "Connection lost";
2862     case ZOOM_ERROR_INIT:
2863         return "Init rejected";
2864     case ZOOM_ERROR_INTERNAL:
2865         return "Internal failure";
2866     case ZOOM_ERROR_TIMEOUT:
2867         return "Timeout";
2868     case ZOOM_ERROR_UNSUPPORTED_PROTOCOL:
2869         return "Unsupported protocol";
2870     case ZOOM_ERROR_UNSUPPORTED_QUERY:
2871         return "Unsupported query type";
2872     case ZOOM_ERROR_INVALID_QUERY:
2873         return "Invalid query";
2874     case ZOOM_ERROR_CQL_PARSE:
2875         return "CQL parsing error";
2876     case ZOOM_ERROR_CQL_TRANSFORM:
2877         return "CQL transformation error";
2878     case ZOOM_ERROR_CCL_CONFIG:
2879         return "CCL configuration error";
2880     case ZOOM_ERROR_CCL_PARSE:
2881         return "CCL parsing error";
2882     default:
2883         return diagbib1_str(error);
2884     }
2885 }
2886
2887 ZOOM_API(int)
2888     ZOOM_connection_error_x(ZOOM_connection c, const char **cp,
2889                             const char **addinfo, const char **diagset)
2890 {
2891     int error = c->error;
2892     if (cp)
2893     {
2894         if (!c->diagset || !strcmp(c->diagset, "ZOOM"))
2895             *cp = ZOOM_diag_str(error);
2896         else if (!strcmp(c->diagset, "HTTP"))
2897             *cp = z_HTTP_errmsg(c->error);
2898         else if (!strcmp(c->diagset, "Bib-1"))
2899             *cp = ZOOM_diag_str(error);
2900         else if (!strcmp(c->diagset, "info:srw/diagnostic/1"))
2901             *cp = yaz_diag_srw_str(c->error);
2902         else
2903             *cp = "Unknown error and diagnostic set";
2904     }
2905     if (addinfo)
2906         *addinfo = c->addinfo ? c->addinfo : "";
2907     if (diagset)
2908         *diagset = c->diagset ? c->diagset : "";
2909     return c->error;
2910 }
2911
2912 ZOOM_API(int)
2913     ZOOM_connection_error(ZOOM_connection c, const char **cp,
2914                           const char **addinfo)
2915 {
2916     return ZOOM_connection_error_x(c, cp, addinfo, 0);
2917 }
2918
2919 static void ZOOM_connection_do_io(ZOOM_connection c, int mask)
2920 {
2921     ZOOM_Event event = 0;
2922     int r = cs_look(c->cs);
2923     yaz_log(c->log_details, "%p ZOOM_connection_do_io mask=%d cs_look=%d",
2924             c, mask, r);
2925     
2926     if (r == CS_NONE)
2927     {
2928         event = ZOOM_Event_create(ZOOM_EVENT_CONNECT);
2929         ZOOM_set_error(c, ZOOM_ERROR_CONNECT, c->host_port);
2930         ZOOM_connection_close(c);
2931         ZOOM_connection_put_event(c, event);
2932     }
2933     else if (r == CS_CONNECT)
2934     {
2935         int ret = ret = cs_rcvconnect(c->cs);
2936         yaz_log(c->log_details, "%p ZOOM_connection_do_io "
2937                 "cs_rcvconnect returned %d", c, ret);
2938         if (ret == 1)
2939         {
2940             int mask = ZOOM_SELECT_EXCEPT;
2941             if (c->cs->io_pending & CS_WANT_WRITE)
2942                 mask += ZOOM_SELECT_WRITE;
2943             if (c->cs->io_pending & CS_WANT_READ)
2944                 mask += ZOOM_SELECT_READ;
2945             ZOOM_connection_set_mask(c, mask);
2946             event = ZOOM_Event_create(ZOOM_EVENT_NONE);
2947             ZOOM_connection_put_event(c, event);
2948         }
2949         else if (ret == 0)
2950         {
2951             event = ZOOM_Event_create(ZOOM_EVENT_CONNECT);
2952             ZOOM_connection_put_event(c, event);
2953             get_cert(c);
2954             if (c->proto == PROTO_Z3950)
2955                 ZOOM_connection_Z3950_send_init(c);
2956             else
2957             {
2958                 /* no init request for SRW .. */
2959                 assert(c->tasks->which == ZOOM_TASK_CONNECT);
2960                 ZOOM_connection_remove_task(c);
2961                 ZOOM_connection_set_mask(c, 0);
2962                 ZOOM_connection_exec_task(c);
2963             }
2964             c->state = STATE_ESTABLISHED;
2965         }
2966         else
2967         {
2968             ZOOM_set_error(c, ZOOM_ERROR_CONNECT, c->host_port);
2969             ZOOM_connection_close(c);
2970         }
2971     }
2972     else
2973     {
2974         if (mask & ZOOM_SELECT_EXCEPT)
2975         {
2976             if (!ZOOM_test_reconnect(c))
2977             {
2978                 ZOOM_set_error(c, ZOOM_ERROR_CONNECTION_LOST, c->host_port);
2979                 ZOOM_connection_close(c);
2980             }
2981             return;
2982         }
2983         if (mask & ZOOM_SELECT_READ)
2984             do_read(c);
2985         if (c->cs && (mask & ZOOM_SELECT_WRITE))
2986             ZOOM_send_buf(c);
2987     }
2988 }
2989
2990 ZOOM_API(int)
2991     ZOOM_connection_last_event(ZOOM_connection cs)
2992 {
2993     if (!cs)
2994         return ZOOM_EVENT_NONE;
2995     return cs->last_event;
2996 }
2997
2998
2999 ZOOM_API(int) ZOOM_connection_fire_event_timeout(ZOOM_connection c)
3000 {
3001     if (c->mask)
3002     {
3003         ZOOM_Event event = ZOOM_Event_create(ZOOM_EVENT_TIMEOUT);
3004         /* timeout and this connection was waiting */
3005         ZOOM_set_error(c, ZOOM_ERROR_TIMEOUT, 0);
3006         ZOOM_connection_close(c);
3007         ZOOM_connection_put_event(c, event);
3008     }
3009     return 0;
3010 }
3011
3012 ZOOM_API(int)
3013     ZOOM_connection_process(ZOOM_connection c)
3014 {
3015     ZOOM_Event event;
3016     if (!c)
3017         return 0;
3018
3019     event = ZOOM_connection_get_event(c);
3020     if (event)
3021     {
3022         ZOOM_Event_destroy(event);
3023         return 1;
3024     }
3025     ZOOM_connection_exec_task(c);
3026     event = ZOOM_connection_get_event(c);
3027     if (event)
3028     {
3029         ZOOM_Event_destroy(event);
3030         return 1;
3031     }
3032     return 0;
3033 }
3034
3035 ZOOM_API(int)
3036     ZOOM_event_nonblock(int no, ZOOM_connection *cs)
3037 {
3038     int i;
3039
3040     yaz_log(log_details0, "ZOOM_process_event(no=%d,cs=%p)", no, cs);
3041     
3042     for (i = 0; i<no; i++)
3043     {
3044         ZOOM_connection c = cs[i];
3045
3046         if (c && ZOOM_connection_process(c))
3047             return i+1;
3048     }
3049     return 0;
3050 }
3051
3052 ZOOM_API(int) ZOOM_connection_fire_event_socket(ZOOM_connection c, int mask)
3053 {
3054     if (c->mask && mask)
3055         ZOOM_connection_do_io(c, mask);
3056     return 0;
3057 }
3058
3059 ZOOM_API(int) ZOOM_connection_get_socket(ZOOM_connection c)
3060 {
3061     if (c->cs)
3062         return cs_fileno(c->cs);
3063     return -1;
3064 }
3065
3066 ZOOM_API(int) ZOOM_connection_set_mask(ZOOM_connection c, int mask)
3067 {
3068     c->mask = mask;
3069     if (!c->cs)
3070         return -1; 
3071     return 0;
3072 }
3073
3074 ZOOM_API(int) ZOOM_connection_get_mask(ZOOM_connection c)
3075 {
3076     if (c->cs)
3077         return c->mask;
3078     return 0;
3079 }
3080
3081 ZOOM_API(int) ZOOM_connection_get_timeout(ZOOM_connection c)
3082 {
3083     return ZOOM_options_get_int(c->options, "timeout", 30);
3084 }
3085
3086 ZOOM_API(void) ZOOM_connection_close(ZOOM_connection c)
3087 {
3088     if (c->cs)
3089         cs_close(c->cs);
3090     c->cs = 0;
3091     ZOOM_connection_set_mask(c, 0);
3092     c->state = STATE_IDLE;
3093 }
3094
3095 /*
3096  * Local variables:
3097  * c-basic-offset: 4
3098  * c-file-style: "Stroustrup"
3099  * indent-tabs-mode: nil
3100  * End:
3101  * vim: shiftwidth=4 tabstop=8 expandtab
3102  */
3103