Implement ZOOM_scanset_display_term
[yaz-moved-to-github.git] / src / zoom-c.c
1 /*
2  * Copyright (c) 2000-2003, Index Data
3  * See the file LICENSE for details.
4  *
5  * $Id: zoom-c.c,v 1.4 2003-11-19 19:07:26 adam Exp $
6  *
7  * ZOOM layer for C, connections, result sets, queries.
8  */
9 #include <assert.h>
10 #include <string.h>
11 #include "zoom-p.h"
12
13 #include <yaz/yaz-util.h>
14 #include <yaz/xmalloc.h>
15 #include <yaz/otherinfo.h>
16 #include <yaz/log.h>
17 #include <yaz/pquery.h>
18 #include <yaz/marcdisp.h>
19 #include <yaz/diagbib1.h>
20 #include <yaz/charneg.h>
21 #include <yaz/ill.h>
22 #include <yaz/srw.h>
23
24 #if HAVE_SYS_POLL_H
25 #include <sys/poll.h>
26 #endif
27
28 typedef enum {
29     zoom_pending,
30     zoom_complete
31 } zoom_ret;
32
33 static zoom_ret ZOOM_connection_send_init (ZOOM_connection c);
34 static zoom_ret do_write_ex (ZOOM_connection c, char *buf_out, int len_out);
35
36 static ZOOM_Event ZOOM_Event_create (int kind)
37 {
38     ZOOM_Event event = (ZOOM_Event) xmalloc (sizeof(*event));
39     event->kind = kind;
40     event->next = 0;
41     event->prev = 0;
42     return event;
43 }
44
45 static void ZOOM_Event_destroy (ZOOM_Event event)
46 {
47     xfree (event);
48 }
49
50 static void ZOOM_connection_put_event (ZOOM_connection c, ZOOM_Event event)
51 {
52     if (c->m_queue_back)
53     {
54         c->m_queue_back->prev = event;
55         assert (c->m_queue_front);
56     }
57     else
58     {
59         assert (!c->m_queue_front);
60         c->m_queue_front = event;
61     }
62     event->next = c->m_queue_back;
63     event->prev = 0;
64     c->m_queue_back = event;
65 }
66
67 static ZOOM_Event ZOOM_connection_get_event(ZOOM_connection c)
68 {
69     ZOOM_Event event = c->m_queue_front;
70     if (!event)
71     {
72         c->last_event = ZOOM_EVENT_NONE;
73         return 0;
74     }
75     assert (c->m_queue_back);
76     c->m_queue_front = event->prev;
77     if (c->m_queue_front)
78     {
79         assert (c->m_queue_back);
80         c->m_queue_front->next = 0;
81     }
82     else
83         c->m_queue_back = 0;
84     c->last_event = event->kind;
85     return event;
86 }
87
88
89 static void set_dset_error (ZOOM_connection c, int error,
90                             const char *dset,
91                             const char *addinfo, const char *addinfo2)
92 {
93     xfree (c->addinfo);
94     c->addinfo = 0;
95     c->error = error;
96     c->diagset = dset;
97     if (addinfo && addinfo2)
98     {
99         c->addinfo = xmalloc(strlen(addinfo) + strlen(addinfo2) + 2);
100         strcpy(c->addinfo, addinfo);
101         strcat(c->addinfo, addinfo2);
102     }
103     else if (addinfo)
104         c->addinfo = xstrdup(addinfo);
105     if (error)
106         yaz_log(LOG_DEBUG, "Error %s %s:%d %s %s",
107                 c->host_port ? c->host_port : "<>", dset, error,
108                 addinfo ? addinfo : "",
109                 addinfo2 ? addinfo2 : "");
110 }
111
112 #if HAVE_XML2
113 static void set_HTTP_error (ZOOM_connection c, int error,
114                             const char *addinfo, const char *addinfo2)
115 {
116     set_dset_error(c, error, "HTTP", addinfo, addinfo2);
117 }
118 #endif
119
120 static void set_ZOOM_error (ZOOM_connection c, int error,
121                             const char *addinfo)
122 {
123     set_dset_error(c, error, "ZOOM", addinfo, 0);
124 }
125
126 static void clear_error (ZOOM_connection c)
127 {
128
129     switch (c->error)
130     {
131     case ZOOM_ERROR_CONNECT:
132     case ZOOM_ERROR_MEMORY:
133     case ZOOM_ERROR_DECODE:
134     case ZOOM_ERROR_CONNECTION_LOST:
135     case ZOOM_ERROR_INIT:
136     case ZOOM_ERROR_INTERNAL:
137     case ZOOM_ERROR_UNSUPPORTED_PROTOCOL:
138         break;
139     default:
140         set_ZOOM_error(c, ZOOM_ERROR_NONE, 0);
141     }
142 }
143
144 ZOOM_task ZOOM_connection_add_task (ZOOM_connection c, int which)
145 {
146     ZOOM_task *taskp = &c->tasks;
147     while (*taskp)
148         taskp = &(*taskp)->next;
149     *taskp = (ZOOM_task) xmalloc (sizeof(**taskp));
150     (*taskp)->running = 0;
151     (*taskp)->which = which;
152     (*taskp)->next = 0;
153     clear_error (c);
154     return *taskp;
155 }
156
157 ZOOM_task ZOOM_connection_insert_task (ZOOM_connection c, int which)
158 {
159     ZOOM_task task = (ZOOM_task) xmalloc (sizeof(*task));
160
161     task->next = c->tasks;
162     c->tasks = task;
163
164     task->running = 0;
165     task->which = which;
166     clear_error (c);
167     return task;
168 }
169
170 void ZOOM_connection_remove_task (ZOOM_connection c)
171 {
172     ZOOM_task task = c->tasks;
173
174     if (task)
175     {
176         c->tasks = task->next;
177         switch (task->which)
178         {
179         case ZOOM_TASK_SEARCH:
180
181             ZOOM_resultset_destroy (task->u.search.resultset);
182             break;
183         case ZOOM_TASK_RETRIEVE:
184             ZOOM_resultset_destroy (task->u.retrieve.resultset);
185             break;
186         case ZOOM_TASK_CONNECT:
187             break;
188         case ZOOM_TASK_SCAN:
189             ZOOM_scanset_destroy (task->u.scan.scan);
190             break;
191         case ZOOM_TASK_PACKAGE:
192             ZOOM_package_destroy (task->u.package);
193             break;
194         default:
195             assert (0);
196         }
197         xfree (task);
198     }
199 }
200
201 static int ZOOM_connection_exec_task (ZOOM_connection c);
202
203 void ZOOM_connection_remove_tasks (ZOOM_connection c)
204 {
205     while (c->tasks)
206         ZOOM_connection_remove_task(c);
207 }
208
209 static ZOOM_record record_cache_lookup (ZOOM_resultset r, int pos);
210
211 ZOOM_API(ZOOM_connection)
212 ZOOM_connection_create (ZOOM_options options)
213 {
214     ZOOM_connection c = (ZOOM_connection) xmalloc (sizeof(*c));
215
216     c->proto = PROTO_Z3950;
217     c->cs = 0;
218     c->mask = 0;
219     c->reconnect_ok = 0;
220     c->state = STATE_IDLE;
221     c->addinfo = 0;
222     set_ZOOM_error(c, ZOOM_ERROR_NONE, 0);
223     c->buf_in = 0;
224     c->len_in = 0;
225     c->buf_out = 0;
226     c->len_out = 0;
227     c->resultsets = 0;
228
229     c->options = ZOOM_options_create_with_parent(options);
230
231     c->host_port = 0;
232     c->path = 0;
233     c->proxy = 0;
234     
235     c->charset = c->lang = 0;
236
237     c->cookie_out = 0;
238     c->cookie_in = 0;
239     c->client_IP = 0;
240     c->tasks = 0;
241
242     c->odr_in = odr_createmem (ODR_DECODE);
243     c->odr_out = odr_createmem (ODR_ENCODE);
244
245     c->async = 0;
246     c->support_named_resultsets = 0;
247     c->last_event = ZOOM_EVENT_NONE;
248
249     c->m_queue_front = 0;
250     c->m_queue_back = 0;
251     return c;
252 }
253
254 /* set database names. Take local databases (if set); otherwise
255    take databases given in ZURL (if set); otherwise use Default */
256 static char **set_DatabaseNames (ZOOM_connection con, ZOOM_options options,
257                                  int *num)
258 {
259     char **databaseNames;
260     const char *c;
261     int no = 2;
262     const char *cp = ZOOM_options_get (options, "databaseName");
263     
264     if (!cp || !*cp)
265     {
266         if (strncmp (con->host_port, "unix:", 5) == 0)
267             cp = strchr (con->host_port+5, ':');
268         else
269             cp = strchr (con->host_port, '/');
270         if (cp)
271             cp++;
272     }
273     if (cp)
274     {
275         c = cp;
276         while ((c = strchr(c, '+')))
277         {
278             c++;
279             no++;
280         }
281     }
282     else
283         cp = "Default";
284     databaseNames = (char**)
285         odr_malloc (con->odr_out, no * sizeof(*databaseNames));
286     no = 0;
287     while (*cp)
288     {
289         c = strchr (cp, '+');
290         if (!c)
291             c = cp + strlen(cp);
292         else if (c == cp)
293         {
294             cp++;
295             continue;
296         }
297         /* cp ptr to first char of db name, c is char
298            following db name */
299         databaseNames[no] = (char*) odr_malloc (con->odr_out, 1+c-cp);
300         memcpy (databaseNames[no], cp, c-cp);
301         databaseNames[no++][c-cp] = '\0';
302         cp = c;
303         if (*cp)
304             cp++;
305     }
306     databaseNames[no] = NULL;
307     *num = no;
308     return databaseNames;
309 }
310
311 ZOOM_API(ZOOM_connection)
312 ZOOM_connection_new (const char *host, int portnum)
313 {
314     ZOOM_connection c = ZOOM_connection_create (0);
315
316     ZOOM_connection_connect (c, host, portnum);
317     return c;
318 }
319
320 ZOOM_API(void)
321 ZOOM_connection_connect(ZOOM_connection c,
322                         const char *host, int portnum)
323 {
324     const char *val;
325     ZOOM_task task;
326
327     if (c->cs)
328     {
329         yaz_log (LOG_DEBUG, "reconnect");
330         c->reconnect_ok = 1;
331         return;
332     }
333     yaz_log(LOG_DEBUG, "connect");
334     xfree (c->proxy);
335     val = ZOOM_options_get (c->options, "proxy");
336     if (val && *val)
337         c->proxy = xstrdup (val);
338     else
339         c->proxy = 0;
340
341     xfree (c->charset);
342     val = ZOOM_options_get (c->options, "charset");
343     if (val && *val)
344         c->charset = xstrdup (val);
345     else
346         c->charset = 0;
347
348     xfree (c->lang);
349     val = ZOOM_options_get (c->options, "lang");
350     if (val && *val)
351         c->lang = xstrdup (val);
352     else
353         c->lang = 0;
354
355     xfree (c->host_port);
356     if (portnum)
357     {
358         char hostn[128];
359         sprintf (hostn, "%.80s:%d", host, portnum);
360         c->host_port = xstrdup(hostn);
361     }
362     else
363         c->host_port = xstrdup(host);
364
365     ZOOM_options_set(c->options, "host", c->host_port);
366
367     val = ZOOM_options_get (c->options, "cookie");
368     if (val && *val)
369         c->cookie_out = xstrdup (val);
370
371     val = ZOOM_options_get (c->options, "clientIP");
372     if (val && *val)
373         c->client_IP = xstrdup (val);
374
375     c->async = ZOOM_options_get_bool (c->options, "async", 0);
376  
377     set_ZOOM_error(c, ZOOM_ERROR_NONE, 0);
378
379     task = ZOOM_connection_add_task (c, ZOOM_TASK_CONNECT);
380
381     if (!c->async)
382     {
383         while (ZOOM_event (1, &c))
384             ;
385     }
386 }
387
388 ZOOM_API(ZOOM_query)
389 ZOOM_query_create(void)
390 {
391     ZOOM_query s = (ZOOM_query) xmalloc (sizeof(*s));
392
393     s->refcount = 1;
394     s->z_query = 0;
395     s->sort_spec = 0;
396     s->odr = odr_createmem (ODR_ENCODE);
397     s->query_string = 0;
398
399     return s;
400 }
401
402 ZOOM_API(void)
403 ZOOM_query_destroy(ZOOM_query s)
404 {
405     if (!s)
406         return;
407
408     (s->refcount)--;
409     yaz_log (LOG_DEBUG, "ZOOM_query_destroy count=%d", s->refcount);
410     if (s->refcount == 0)
411     {
412         odr_destroy (s->odr);
413         xfree (s);
414     }
415 }
416
417 ZOOM_API(int)
418 ZOOM_query_prefix(ZOOM_query s, const char *str)
419 {
420     s->query_string = odr_strdup(s->odr, str);
421     s->z_query = (Z_Query *) odr_malloc (s->odr, sizeof(*s->z_query));
422     s->z_query->which = Z_Query_type_1;
423     s->z_query->u.type_1 =  p_query_rpn(s->odr, PROTO_Z3950, str);
424     if (!s->z_query->u.type_1)
425         return -1;
426     return 0;
427 }
428
429 ZOOM_API(int)
430 ZOOM_query_cql(ZOOM_query s, const char *str)
431 {
432     Z_External *ext;
433
434     s->query_string = odr_strdup(s->odr, str);
435
436     ext = (Z_External *) odr_malloc(s->odr, sizeof(*ext));
437     ext->direct_reference = odr_getoidbystr(s->odr, "1.2.840.10003.16.2");
438     ext->indirect_reference = 0;
439     ext->descriptor = 0;
440     ext->which = Z_External_CQL;
441     ext->u.cql = s->query_string;
442     
443     s->z_query = (Z_Query *) odr_malloc (s->odr, sizeof(*s->z_query));
444     s->z_query->which = Z_Query_type_104;
445     s->z_query->u.type_104 =  ext;
446
447     return 0;
448 }
449
450 ZOOM_API(int)
451 ZOOM_query_sortby(ZOOM_query s, const char *criteria)
452 {
453     s->sort_spec = yaz_sort_spec (s->odr, criteria);
454     if (!s->sort_spec)
455         return -1;
456     return 0;
457 }
458
459 static zoom_ret do_write(ZOOM_connection c);
460
461 ZOOM_API(void)
462 ZOOM_connection_destroy(ZOOM_connection c)
463 {
464     ZOOM_resultset r;
465     if (!c)
466         return;
467     if (c->cs)
468         cs_close (c->cs);
469     for (r = c->resultsets; r; r = r->next)
470         r->connection = 0;
471
472     xfree (c->buf_in);
473     xfree (c->addinfo);
474     odr_destroy (c->odr_in);
475     odr_destroy (c->odr_out);
476     ZOOM_options_destroy (c->options);
477     ZOOM_connection_remove_tasks (c);
478     xfree (c->host_port);
479     xfree (c->path);
480     xfree (c->proxy);
481     xfree (c->charset);
482     xfree (c->lang);
483     xfree (c->cookie_out);
484     xfree (c->cookie_in);
485     xfree (c->client_IP);
486     xfree (c);
487 }
488
489 void ZOOM_resultset_addref (ZOOM_resultset r)
490 {
491     if (r)
492     {
493         (r->refcount)++;
494         yaz_log (LOG_DEBUG, "ZOOM_resultset_addref r=%p count=%d",
495                  r, r->refcount);
496     }
497 }
498 ZOOM_resultset ZOOM_resultset_create ()
499 {
500     ZOOM_resultset r = (ZOOM_resultset) xmalloc (sizeof(*r));
501
502     yaz_log (LOG_DEBUG, "ZOOM_resultset_create r = %p", r);
503     r->refcount = 1;
504     r->size = 0;
505     r->odr = odr_createmem (ODR_ENCODE);
506     r->start = 0;
507     r->piggyback = 1;
508     r->setname = 0;
509     r->schema = 0;
510     r->count = 0;
511     r->step = 0;
512     r->record_cache = 0;
513     r->r_sort_spec = 0;
514     r->query = 0;
515     r->connection = 0;
516     r->next = 0;
517     return r;
518 }
519
520 ZOOM_API(ZOOM_resultset)
521 ZOOM_connection_search_pqf(ZOOM_connection c, const char *q)
522 {
523     ZOOM_resultset r;
524     ZOOM_query s = ZOOM_query_create();
525
526     ZOOM_query_prefix (s, q);
527
528     r = ZOOM_connection_search (c, s);
529     ZOOM_query_destroy (s);
530     return r;
531 }
532
533 ZOOM_API(ZOOM_resultset)
534 ZOOM_connection_search(ZOOM_connection c, ZOOM_query q)
535 {
536     ZOOM_resultset r = ZOOM_resultset_create ();
537     ZOOM_task task;
538     const char *cp;
539
540     r->r_sort_spec = q->sort_spec;
541     r->query = q;
542
543     r->options = ZOOM_options_create_with_parent(c->options);
544
545     r->start = ZOOM_options_get_int(r->options, "start", 0);
546     r->count = ZOOM_options_get_int(r->options, "count", 0);
547     r->step = ZOOM_options_get_int(r->options, "step", 0);
548     r->piggyback = ZOOM_options_get_bool (r->options, "piggyback", 1);
549     cp = ZOOM_options_get (r->options, "setname");
550     if (cp)
551         r->setname = xstrdup(cp);
552     cp = ZOOM_options_get (r->options, "schema");
553     if (cp)
554         r->schema = xstrdup(cp);
555     
556     r->connection = c;
557
558     r->next = c->resultsets;
559     c->resultsets = r;
560
561     if (c->host_port && c->proto == PROTO_HTTP)
562     {
563         if (!c->cs)
564         {
565             yaz_log(LOG_DEBUG, "NO COMSTACK");
566             ZOOM_connection_add_task(c, ZOOM_TASK_CONNECT);
567         }
568         else
569         {
570             yaz_log(LOG_DEBUG, "PREPARE FOR RECONNECT");
571             c->reconnect_ok = 1;
572         }
573     }
574
575     task = ZOOM_connection_add_task (c, ZOOM_TASK_SEARCH);
576     task->u.search.resultset = r;
577     ZOOM_resultset_addref (r);  
578
579     (q->refcount)++;
580
581     if (!c->async)
582     {
583         while (ZOOM_event (1, &c))
584             ;
585     }
586     return r;
587 }
588
589 ZOOM_API(void)
590 ZOOM_resultset_destroy(ZOOM_resultset r)
591 {
592     if (!r)
593         return;
594     (r->refcount)--;
595     yaz_log (LOG_DEBUG, "ZOOM_resultset_destroy r = %p count=%d",
596              r, r->refcount);
597     if (r->refcount == 0)
598     {
599         ZOOM_record_cache rc;
600
601         for (rc = r->record_cache; rc; rc = rc->next)
602         {
603             if (rc->rec.wrbuf_marc)
604                 wrbuf_free (rc->rec.wrbuf_marc, 1);
605             if (rc->rec.wrbuf_iconv)
606                 wrbuf_free (rc->rec.wrbuf_iconv, 1);
607             if (rc->rec.wrbuf_opac)
608                 wrbuf_free (rc->rec.wrbuf_opac, 1);
609         }
610         if (r->connection)
611         {
612             /* remove ourselves from the resultsets in connection */
613             ZOOM_resultset *rp = &r->connection->resultsets;
614             while (1)
615             {
616                 assert (*rp);   /* we must be in this list!! */
617                 if (*rp == r)
618                 {   /* OK, we're here - take us out of it */
619                     *rp = (*rp)->next;
620                     break;
621                 }
622                 rp = &(*rp)->next;
623             }
624         }
625         ZOOM_query_destroy (r->query);
626         ZOOM_options_destroy (r->options);
627         odr_destroy (r->odr);
628         xfree (r->setname);
629         xfree (r->schema);
630         xfree (r);
631     }
632 }
633
634 ZOOM_API(size_t)
635 ZOOM_resultset_size (ZOOM_resultset r)
636 {
637     return r->size;
638 }
639
640 static void do_close (ZOOM_connection c)
641 {
642     if (c->cs)
643         cs_close(c->cs);
644     c->cs = 0;
645     c->mask = 0;
646     c->state = STATE_IDLE;
647 }
648
649 static void ZOOM_resultset_retrieve (ZOOM_resultset r,
650                                      int force_sync, int start, int count)
651 {
652     ZOOM_task task;
653     ZOOM_connection c;
654     const char *cp;
655
656     if (!r)
657         return;
658     c = r->connection;
659     if (!c)
660         return;
661
662     if (c->host_port && c->proto == PROTO_HTTP)
663     {
664         if (!c->cs)
665         {
666             yaz_log(LOG_DEBUG, "NO COMSTACK");
667             ZOOM_connection_add_task(c, ZOOM_TASK_CONNECT);
668         }
669         else
670         {
671             yaz_log(LOG_DEBUG, "PREPARE FOR RECONNECT");
672             c->reconnect_ok = 1;
673         }
674     }
675     task = ZOOM_connection_add_task (c, ZOOM_TASK_RETRIEVE);
676     task->u.retrieve.resultset = r;
677     task->u.retrieve.start = start;
678     task->u.retrieve.count = count;
679
680     cp = ZOOM_options_get (r->options, "schema");
681     if (cp)
682     {
683         if (!r->schema || strcmp(r->schema, cp))
684         {
685             xfree(r->schema);
686             r->schema = xstrdup(cp);
687         }
688     }
689
690     ZOOM_resultset_addref (r);
691
692     if (!r->connection->async || force_sync)
693         while (r->connection && ZOOM_event (1, &r->connection))
694             ;
695 }
696
697 ZOOM_API(void)
698 ZOOM_resultset_records (ZOOM_resultset r, ZOOM_record *recs,
699                         size_t start, size_t count)
700 {
701     int force_present = 0;
702
703     if (!r)
704         return ;
705     if (count && recs)
706         force_present = 1;
707     ZOOM_resultset_retrieve (r, force_present, start, count);
708     if (force_present)
709     {
710         size_t i;
711         for (i = 0; i< count; i++)
712             recs[i] = ZOOM_resultset_record_immediate (r, i+start);
713     }
714 }
715
716 static zoom_ret do_connect (ZOOM_connection c)
717 {
718     void *add;
719     const char *effective_host;
720
721     if (c->proxy)
722         effective_host = c->proxy;
723     else
724         effective_host = c->host_port;
725
726     yaz_log (LOG_DEBUG, "do_connect host=%s", effective_host);
727
728     assert (!c->cs);
729     c->cs = cs_create_host (effective_host, 0, &add);
730
731     if (c->cs && c->cs->protocol == PROTO_HTTP)
732     {
733 #if HAVE_XML2
734         const char *path = 0;
735
736         c->proto = PROTO_HTTP;
737         cs_get_host_args(c->host_port, &path);
738         xfree(c->path);
739         c->path = xmalloc(strlen(path)+2);
740         c->path[0] = '/';
741         strcpy (c->path+1, path);
742 #else
743         set_ZOOM_error(c, ZOOM_ERROR_UNSUPPORTED_PROTOCOL, "SRW");
744         do_close(c);
745         return zoom_complete;
746 #endif
747     }
748     if (c->cs)
749     {
750         int ret = cs_connect (c->cs, add);
751         if (ret == 0)
752         {
753             ZOOM_Event event = ZOOM_Event_create(ZOOM_EVENT_CONNECT);
754             ZOOM_connection_put_event(c, event);
755             if (c->proto == PROTO_Z3950)
756                 ZOOM_connection_send_init(c);
757             else
758             {
759                 /* no init request for SRW .. */
760                 assert (c->tasks->which == ZOOM_TASK_CONNECT);
761                 ZOOM_connection_remove_task (c);
762                 c->mask = 0;
763                 ZOOM_connection_exec_task (c);
764             }
765             c->state = STATE_ESTABLISHED;
766             return zoom_pending;
767         }
768         else if (ret > 0)
769         {
770             c->state = STATE_CONNECTING; 
771             c->mask = ZOOM_SELECT_EXCEPT;
772             if (c->cs->io_pending & CS_WANT_WRITE)
773                 c->mask += ZOOM_SELECT_WRITE;
774             if (c->cs->io_pending & CS_WANT_READ)
775                 c->mask += ZOOM_SELECT_READ;
776             return zoom_pending;
777         }
778     }
779     c->state = STATE_IDLE;
780     set_ZOOM_error(c, ZOOM_ERROR_CONNECT, effective_host);
781     return zoom_complete;
782 }
783
784 int z3950_connection_socket(ZOOM_connection c)
785 {
786     if (c->cs)
787         return cs_fileno(c->cs);
788     return -1;
789 }
790
791 int z3950_connection_mask(ZOOM_connection c)
792 {
793     if (c->cs)
794         return c->mask;
795     return 0;
796 }
797
798 static void otherInfo_attach (ZOOM_connection c, Z_APDU *a, ODR out)
799 {
800     int i;
801     for (i = 0; i<200; i++)
802     {
803         size_t len;
804         Z_OtherInformation **oi;
805         char buf[80];
806         const char *val;
807         const char *cp;
808         int oidval;
809
810         sprintf (buf, "otherInfo%d", i);
811         val = ZOOM_options_get (c->options, buf);
812         if (!val)
813             break;
814         cp = strchr (val, ':');
815         if (!cp)
816             continue;
817         len = cp - val;
818         if (len >= sizeof(buf))
819             len = sizeof(buf)-1;
820         memcpy (buf, val, len);
821         buf[len] = '\0';
822         oidval = oid_getvalbyname (buf);
823         if (oidval == VAL_NONE)
824             continue;
825         
826         yaz_oi_APDU(a, &oi);
827         yaz_oi_set_string_oidval(oi, out, oidval, 1, cp+1);
828     }
829 }
830
831 static int encode_APDU(ZOOM_connection c, Z_APDU *a, ODR out)
832 {
833     assert (a);
834     if (c->cookie_out)
835     {
836         Z_OtherInformation **oi;
837         yaz_oi_APDU(a, &oi);
838         yaz_oi_set_string_oidval(oi, out, VAL_COOKIE, 1, c->cookie_out);
839     }
840     if (c->client_IP)
841     {
842         Z_OtherInformation **oi;
843         yaz_oi_APDU(a, &oi);
844         yaz_oi_set_string_oidval(oi, out, VAL_CLIENT_IP, 1, c->client_IP);
845     }
846     otherInfo_attach (c, a, out);
847     if (!z_APDU(out, &a, 0, 0))
848     {
849         FILE *outf = fopen("/tmp/apdu.txt", "a");
850         if (a && outf)
851         {
852             ODR odr_pr = odr_createmem(ODR_PRINT);
853             fprintf (outf, "a=%p\n", a);
854             odr_setprint(odr_pr, outf);
855             z_APDU(odr_pr, &a, 0, 0);
856             odr_destroy(odr_pr);
857         }
858         yaz_log (LOG_DEBUG, "encoding failed");
859         set_ZOOM_error(c, ZOOM_ERROR_ENCODE, 0);
860         odr_reset(out);
861         return -1;
862     }
863     
864     return 0;
865 }
866
867 static zoom_ret send_APDU (ZOOM_connection c, Z_APDU *a)
868 {
869     ZOOM_Event event;
870     assert (a);
871     if (encode_APDU(c, a, c->odr_out))
872         return zoom_complete;
873     yaz_log(LOG_DEBUG, "send APDU type=%d", a->which);
874     c->buf_out = odr_getbuf(c->odr_out, &c->len_out, 0);
875     event = ZOOM_Event_create (ZOOM_EVENT_SEND_APDU);
876     ZOOM_connection_put_event (c, event);
877     odr_reset(c->odr_out);
878     return do_write (c);
879 }
880
881 /* returns 1 if PDU was sent OK (still pending )
882            0 if PDU was not sent OK (nothing to wait for) 
883 */
884
885 static zoom_ret ZOOM_connection_send_init (ZOOM_connection c)
886 {
887     const char *impid, *impname, *impver;
888     Z_APDU *apdu = zget_APDU(c->odr_out, Z_APDU_initRequest);
889     Z_InitRequest *ireq = apdu->u.initRequest;
890     Z_IdAuthentication *auth = (Z_IdAuthentication *)
891         odr_malloc(c->odr_out, sizeof(*auth));
892     const char *auth_groupId = ZOOM_options_get (c->options, "group");
893     const char *auth_userId = ZOOM_options_get (c->options, "user");
894     const char *auth_password = ZOOM_options_get (c->options, "pass");
895     
896     ODR_MASK_SET(ireq->options, Z_Options_search);
897     ODR_MASK_SET(ireq->options, Z_Options_present);
898     ODR_MASK_SET(ireq->options, Z_Options_scan);
899     ODR_MASK_SET(ireq->options, Z_Options_sort);
900     ODR_MASK_SET(ireq->options, Z_Options_extendedServices);
901     ODR_MASK_SET(ireq->options, Z_Options_namedResultSets);
902     
903     ODR_MASK_SET(ireq->protocolVersion, Z_ProtocolVersion_1);
904     ODR_MASK_SET(ireq->protocolVersion, Z_ProtocolVersion_2);
905     ODR_MASK_SET(ireq->protocolVersion, Z_ProtocolVersion_3);
906     
907     impid = ZOOM_options_get (c->options, "implementationId");
908     ireq->implementationId =
909         (char *) odr_malloc (c->odr_out, 15 + (impid ? strlen(impid) : 0));
910     strcpy (ireq->implementationId, "");
911     if (impid)
912     {
913         strcat (ireq->implementationId, impid);
914         strcat (ireq->implementationId, "/");
915     }                                          
916     strcat (ireq->implementationId, "81"); /* Index's implementor ID */
917     
918     impname = ZOOM_options_get (c->options, "implementationName");
919     ireq->implementationName =
920         (char *) odr_malloc (c->odr_out, 15 + (impname ? strlen(impname) : 0));
921     strcpy (ireq->implementationName, "");
922     if (impname)
923     {
924         strcat (ireq->implementationName, impname);
925         strcat (ireq->implementationName, "/");
926     }                                          
927     strcat (ireq->implementationName, "ZOOM-C/YAZ");
928     
929     impver = ZOOM_options_get (c->options, "implementationVersion");
930     ireq->implementationVersion =
931         (char *) odr_malloc (c->odr_out, strlen("$Revision: 1.4 $") + 2 +
932                              (impver ? strlen(impver) : 0));
933     strcpy (ireq->implementationVersion, "");
934     if (impver)
935     {
936         strcat (ireq->implementationVersion, impver);
937         strcat (ireq->implementationVersion, "/");
938     }                                          
939     strcat (ireq->implementationVersion, "$Revision: 1.4 $");
940
941     *ireq->maximumRecordSize =
942         ZOOM_options_get_int (c->options, "maximumRecordSize", 1024*1024);
943     *ireq->preferredMessageSize =
944         ZOOM_options_get_int (c->options, "preferredMessageSize", 1024*1024);
945     
946     if (auth_groupId || auth_password)
947     {
948         Z_IdPass *pass = (Z_IdPass *) odr_malloc(c->odr_out, sizeof(*pass));
949         int i = 0;
950         pass->groupId = 0;
951         if (auth_groupId && *auth_groupId)
952         {
953             pass->groupId = (char *)
954                 odr_malloc(c->odr_out, strlen(auth_groupId)+1);
955             strcpy(pass->groupId, auth_groupId);
956             i++;
957         }
958         pass->userId = 0;
959         if (auth_userId && *auth_userId)
960         {
961             pass->userId = (char *)
962                 odr_malloc(c->odr_out, strlen(auth_userId)+1);
963             strcpy(pass->userId, auth_userId);
964             i++;
965         }
966         pass->password = 0;
967         if (auth_password && *auth_password)
968         {
969             pass->password = (char *)
970                 odr_malloc(c->odr_out, strlen(auth_password)+1);
971             strcpy(pass->password, auth_password);
972             i++;
973         }
974         if (i)
975         {
976             auth->which = Z_IdAuthentication_idPass;
977             auth->u.idPass = pass;
978             ireq->idAuthentication = auth;
979         }
980     }
981     else if (auth_userId)
982     {
983         auth->which = Z_IdAuthentication_open;
984         auth->u.open = (char *)
985             odr_malloc(c->odr_out, strlen(auth_userId)+1);
986         strcpy(auth->u.open, auth_userId);
987         ireq->idAuthentication = auth;
988     }
989     if (c->proxy)
990         yaz_oi_set_string_oidval(&ireq->otherInfo, c->odr_out,
991                                  VAL_PROXY, 1, c->host_port);
992     if (c->charset||c->lang)
993     {
994         Z_OtherInformation **oi;
995         Z_OtherInformationUnit *oi_unit;
996         
997         yaz_oi_APDU(apdu, &oi);
998         
999         if ((oi_unit = yaz_oi_update(oi, c->odr_out, NULL, 0, 0)))
1000         {
1001             ODR_MASK_SET(ireq->options, Z_Options_negotiationModel);
1002             
1003             oi_unit->which = Z_OtherInfo_externallyDefinedInfo;
1004             oi_unit->information.externallyDefinedInfo =
1005                 yaz_set_proposal_charneg
1006                 (c->odr_out,
1007                  (const char **)&c->charset, (c->charset) ? 1:0,
1008                  (const char **)&c->lang, (c->lang) ? 1:0, 1);
1009         }
1010     }
1011     assert (apdu);
1012     return send_APDU (c, apdu);
1013 }
1014
1015 #if HAVE_XML2
1016 static zoom_ret send_srw (ZOOM_connection c, Z_SRW_PDU *sr)
1017 {
1018     char ctype[50];
1019     Z_SOAP_Handler h[2] = {
1020         {"http://www.loc.gov/zing/srw/v1.0/", 0, (Z_SOAP_fun) yaz_srw_codec},
1021         {0, 0, 0}
1022     };
1023     ODR o = odr_createmem(ODR_ENCODE);
1024     int ret;
1025     Z_SOAP *p = odr_malloc(o, sizeof(*p));
1026     Z_GDU *gdu;
1027     ZOOM_Event event;
1028
1029     gdu = z_get_HTTP_Request(c->odr_out);
1030     gdu->u.HTTP_Request->path = c->path;
1031
1032     if (c->host_port)
1033     {
1034         const char *cp0 = strstr(c->host_port, "://");
1035         const char *cp1 = 0;
1036         if (cp0)
1037             cp0 = cp0+3;
1038         else
1039             cp0 = c->host_port;
1040
1041         cp1 = strchr(cp0, '/');
1042         if (!cp1)
1043             cp1 = cp0+strlen(cp0);
1044
1045         if (cp0 && cp1)
1046         {
1047             char *h = odr_malloc(c->odr_out, cp1 - cp0 + 1);
1048             memcpy (h, cp0, cp1 - cp0);
1049             h[cp1-cp0] = '\0';
1050             z_HTTP_header_add(c->odr_out, &gdu->u.HTTP_Request->headers,
1051                               "Host", h);
1052         }
1053     }
1054
1055     strcpy(ctype, "text/xml");
1056     if (c->charset && strlen(c->charset) < 20)
1057     {
1058         strcat(ctype, "; charset=");
1059         strcat(ctype, c->charset);
1060     }
1061     z_HTTP_header_add(c->odr_out, &gdu->u.HTTP_Request->headers,
1062                       "Content-Type", ctype);
1063     z_HTTP_header_add(c->odr_out, &gdu->u.HTTP_Request->headers,
1064                       "SOAPAction", "\"\"");
1065     p->which = Z_SOAP_generic;
1066     p->u.generic = odr_malloc(o, sizeof(*p->u.generic));
1067     p->u.generic->no = 0;
1068     p->u.generic->ns = 0;
1069     p->u.generic->p = sr;
1070     p->ns = "http://schemas.xmlsoap.org/soap/envelope/";
1071
1072     ret = z_soap_codec_enc(o, &p,
1073                            &gdu->u.HTTP_Request->content_buf,
1074                            &gdu->u.HTTP_Request->content_len, h,
1075                            c->charset);
1076
1077     if (!z_GDU(c->odr_out, &gdu, 0, 0))
1078         return zoom_complete;
1079     c->buf_out = odr_getbuf(c->odr_out, &c->len_out, 0);
1080
1081     odr_destroy(o);
1082
1083     event = ZOOM_Event_create (ZOOM_EVENT_SEND_APDU);
1084     ZOOM_connection_put_event (c, event);
1085     odr_reset(c->odr_out);
1086     return do_write (c);
1087 }
1088 #endif
1089
1090 #if HAVE_XML2
1091 static zoom_ret ZOOM_connection_srw_send_search(ZOOM_connection c)
1092 {
1093     int i;
1094     ZOOM_resultset resultset = 0;
1095     Z_SRW_PDU *sr = 0;
1096     const char *recordPacking = 0;
1097
1098     if (c->error)                  /* don't continue on error */
1099         return zoom_complete;
1100     assert (c->tasks);
1101     if (c->tasks->which == ZOOM_TASK_SEARCH)
1102     {
1103         resultset = c->tasks->u.search.resultset;
1104         resultset->setname = xstrdup ("default");
1105         ZOOM_options_set (resultset->options, "setname", resultset->setname);
1106     }
1107     else if(c->tasks->which == ZOOM_TASK_RETRIEVE)
1108     {
1109         resultset = c->tasks->u.retrieve.resultset;
1110
1111         resultset->start = c->tasks->u.retrieve.start;
1112         resultset->count = c->tasks->u.retrieve.count;
1113         
1114         if (resultset->start >= resultset->size)
1115             return zoom_complete;
1116         if (resultset->start + resultset->count > resultset->size)
1117             resultset->count = resultset->size - resultset->start;
1118
1119         for (i = 0; i<resultset->count; i++)
1120         {
1121             ZOOM_record rec =
1122                 record_cache_lookup (resultset, i + resultset->start);
1123             if (!rec)
1124                 break;
1125         }
1126         if (i == resultset->count)
1127             return zoom_complete;
1128     }
1129     assert(resultset->query);
1130         
1131     sr = yaz_srw_get(c->odr_out, Z_SRW_searchRetrieve_request);
1132
1133     if (resultset->query->z_query->which == Z_Query_type_104
1134         && resultset->query->z_query->u.type_104->which == Z_External_CQL)
1135     {
1136
1137         sr->u.request->query_type = Z_SRW_query_type_cql;
1138         sr->u.request->query.cql =resultset->query->z_query->u.type_104->u.cql;
1139     }
1140     else if (resultset->query->z_query->which == Z_Query_type_1 &&
1141              resultset->query->z_query->u.type_1)
1142     {
1143         sr->u.request->query_type = Z_SRW_query_type_pqf;
1144         sr->u.request->query.pqf = resultset->query->query_string;
1145     }
1146     else
1147     {
1148         set_ZOOM_error(c, ZOOM_ERROR_UNSUPPORTED_QUERY, 0);
1149         return zoom_complete;
1150     }
1151     sr->u.request->startRecord = odr_intdup (c->odr_out, resultset->start + 1);
1152     sr->u.request->maximumRecords = odr_intdup (
1153         c->odr_out, resultset->step>0 ? resultset->step : resultset->count);
1154     sr->u.request->recordSchema = resultset->schema;
1155
1156     recordPacking = ZOOM_resultset_option_get (resultset, "recordPacking");
1157
1158     if (recordPacking)
1159         sr->u.request->recordPacking = odr_strdup(c->odr_out, recordPacking);
1160     
1161     return send_srw(c, sr);
1162 }
1163 #else
1164 static zoom_ret ZOOM_connection_srw_send_search(ZOOM_connection c)
1165 {
1166     return zoom_complete;
1167 }
1168 #endif
1169
1170 static zoom_ret ZOOM_connection_send_search (ZOOM_connection c)
1171 {
1172     ZOOM_resultset r;
1173     int lslb, ssub, mspn;
1174     const char *syntax;
1175     Z_APDU *apdu = zget_APDU(c->odr_out, Z_APDU_searchRequest);
1176     Z_SearchRequest *search_req = apdu->u.searchRequest;
1177     const char *elementSetName;
1178     const char *smallSetElementSetName;
1179     const char *mediumSetElementSetName;
1180
1181     assert (c->tasks);
1182     assert (c->tasks->which == ZOOM_TASK_SEARCH);
1183
1184     r = c->tasks->u.search.resultset;
1185
1186     elementSetName =
1187         ZOOM_options_get (r->options, "elementSetName");
1188     smallSetElementSetName  =
1189         ZOOM_options_get (r->options, "smallSetElementSetName");
1190     mediumSetElementSetName =
1191         ZOOM_options_get (r->options, "mediumSetElementSetName");
1192
1193     if (!smallSetElementSetName)
1194         smallSetElementSetName = elementSetName;
1195
1196     if (!mediumSetElementSetName)
1197         mediumSetElementSetName = elementSetName;
1198
1199     assert (r);
1200     assert (r->query);
1201
1202     /* prepare query for the search request */
1203     search_req->query = r->query->z_query;
1204
1205     search_req->databaseNames =
1206         set_DatabaseNames (c, r->options, &search_req->num_databaseNames);
1207
1208     /* get syntax (no need to provide unless piggyback is in effect) */
1209     syntax = ZOOM_options_get (r->options, "preferredRecordSyntax");
1210
1211     lslb = ZOOM_options_get_int (r->options, "largeSetLowerBound", -1);
1212     ssub = ZOOM_options_get_int (r->options, "smallSetUpperBound", -1);
1213     mspn = ZOOM_options_get_int (r->options, "mediumSetPresentNumber", -1);
1214     if (lslb != -1 && ssub != -1 && mspn != -1)
1215     {
1216         /* So're a Z39.50 expert? Let's hope you don't do sort */
1217         *search_req->largeSetLowerBound = lslb;
1218         *search_req->smallSetUpperBound = ssub;
1219         *search_req->mediumSetPresentNumber = mspn;
1220     }
1221     else if (r->start == 0 && r->count > 0
1222              && r->piggyback && !r->r_sort_spec && !r->schema)
1223     {
1224         /* Regular piggyback - do it unless we're going to do sort */
1225         *search_req->largeSetLowerBound = 2000000000;
1226         *search_req->smallSetUpperBound = 1;
1227         *search_req->mediumSetPresentNumber = r->step>0 ? r->step : r->count;
1228     }
1229     else
1230     {
1231         /* non-piggyback. Need not provide elementsets or syntaxes .. */
1232         smallSetElementSetName = 0;
1233         mediumSetElementSetName = 0;
1234         syntax = 0;
1235     }
1236     if (smallSetElementSetName && *smallSetElementSetName)
1237     {
1238         Z_ElementSetNames *esn = (Z_ElementSetNames *)
1239             odr_malloc (c->odr_out, sizeof(*esn));
1240         
1241         esn->which = Z_ElementSetNames_generic;
1242         esn->u.generic = odr_strdup (c->odr_out, smallSetElementSetName);
1243         search_req->smallSetElementSetNames = esn;
1244     }
1245     if (mediumSetElementSetName && *mediumSetElementSetName)
1246     {
1247         Z_ElementSetNames *esn = (Z_ElementSetNames *)
1248             odr_malloc (c->odr_out, sizeof(*esn));
1249         
1250         esn->which = Z_ElementSetNames_generic;
1251         esn->u.generic = odr_strdup (c->odr_out, mediumSetElementSetName);
1252         search_req->mediumSetElementSetNames = esn;
1253     }
1254     if (syntax)
1255         search_req->preferredRecordSyntax =
1256             yaz_str_to_z3950oid (c->odr_out, CLASS_RECSYN, syntax);
1257     
1258     if (!r->setname)
1259     {
1260         if (c->support_named_resultsets)
1261         {
1262             char setname[14];
1263             int ord;
1264             /* find the lowest unused ordinal so that we re-use
1265                result sets on the server. */
1266             for (ord = 1; ; ord++)
1267             {
1268                 ZOOM_resultset rp;
1269                 sprintf (setname, "%d", ord);
1270                 for (rp = c->resultsets; rp; rp = rp->next)
1271                     if (rp->setname && !strcmp (rp->setname, setname))
1272                         break;
1273                 if (!rp)
1274                     break;
1275             }
1276             r->setname = xstrdup (setname);
1277             yaz_log (LOG_DEBUG, "allocating set %s", r->setname);
1278         }
1279         else
1280             r->setname = xstrdup ("default");
1281         ZOOM_options_set (r->options, "setname", r->setname);
1282     }
1283     search_req->resultSetName = odr_strdup(c->odr_out, r->setname);
1284     /* send search request */
1285     return send_APDU (c, apdu);
1286 }
1287
1288 static void response_diag (ZOOM_connection c, Z_DiagRec *p)
1289 {
1290     int oclass;
1291     Z_DefaultDiagFormat *r;
1292     char *addinfo = 0;
1293     
1294     xfree (c->addinfo);
1295     c->addinfo = 0;
1296     if (p->which != Z_DiagRec_defaultFormat)
1297     {
1298         set_ZOOM_error(c, ZOOM_ERROR_DECODE, 0);
1299         return;
1300     }
1301     r = p->u.defaultFormat;
1302     switch (r->which)
1303     {
1304     case Z_DefaultDiagFormat_v2Addinfo:
1305         addinfo = r->u.v2Addinfo;
1306         break;
1307     case Z_DefaultDiagFormat_v3Addinfo:
1308         addinfo = r->u.v3Addinfo;
1309         break;
1310     }
1311     set_dset_error(c, *r->condition,
1312                    yaz_z3950oid_to_str(r->diagnosticSetId, &oclass),
1313                    addinfo, 0);
1314 }
1315
1316 ZOOM_API(ZOOM_record)
1317 ZOOM_record_clone (ZOOM_record srec)
1318 {
1319     char *buf;
1320     int size;
1321     ODR odr_enc;
1322     ZOOM_record nrec;
1323
1324     odr_enc = odr_createmem(ODR_ENCODE);
1325     if (!z_NamePlusRecord (odr_enc, &srec->npr, 0, 0))
1326         return 0;
1327     buf = odr_getbuf (odr_enc, &size, 0);
1328     
1329     nrec = (ZOOM_record) xmalloc (sizeof(*nrec));
1330     nrec->odr = odr_createmem(ODR_DECODE);
1331     nrec->wrbuf_marc = 0;
1332     nrec->wrbuf_iconv = 0;
1333     nrec->wrbuf_opac = 0;
1334     odr_setbuf (nrec->odr, buf, size, 0);
1335     z_NamePlusRecord (nrec->odr, &nrec->npr, 0, 0);
1336     
1337     odr_destroy (odr_enc);
1338     return nrec;
1339 }
1340
1341 ZOOM_API(ZOOM_record)
1342 ZOOM_resultset_record_immediate (ZOOM_resultset s,size_t pos)
1343 {
1344     return record_cache_lookup (s, pos);
1345 }
1346
1347 ZOOM_API(ZOOM_record)
1348 ZOOM_resultset_record (ZOOM_resultset r, size_t pos)
1349 {
1350     ZOOM_record rec = ZOOM_resultset_record_immediate(r, pos);
1351
1352     if (!rec)
1353     {
1354         ZOOM_resultset_retrieve (r, 1, pos, 1);
1355         rec = ZOOM_resultset_record_immediate (r, pos);
1356     }
1357     return rec;
1358 }
1359
1360 ZOOM_API(void)
1361 ZOOM_record_destroy (ZOOM_record rec)
1362 {
1363     if (!rec)
1364         return;
1365     if (rec->wrbuf_marc)
1366         wrbuf_free (rec->wrbuf_marc, 1);
1367     if (rec->wrbuf_iconv)
1368         wrbuf_free (rec->wrbuf_iconv, 1);
1369     if (rec->wrbuf_opac)
1370         wrbuf_free (rec->wrbuf_opac, 1);
1371     odr_destroy (rec->odr);
1372     xfree (rec);
1373 }
1374
1375 static const char *record_iconv_return(ZOOM_record rec, int *len,
1376                                        const char *buf, int sz,
1377                                        const char *record_charset)
1378 {
1379     char to[40];
1380     char from[40];
1381     yaz_iconv_t cd = 0;
1382
1383     *from = '\0';
1384     strcpy(to, "UTF-8");
1385     if (record_charset && *record_charset)
1386     {
1387         /* Use "from,to" or just "from" */
1388         const char *cp =strchr(record_charset, ',');
1389         int clen = strlen(record_charset);
1390         if (cp && cp[1])
1391         {
1392             strncpy( to, cp+1, sizeof(to)-1);
1393             to[sizeof(to)-1] = '\0';
1394             clen = cp - record_charset;
1395         }
1396         if (clen > sizeof(from)-1)
1397             clen = sizeof(from)-1;
1398         
1399         if (clen)
1400             strncpy(from, record_charset, clen);
1401         from[clen] = '\0';
1402     }
1403
1404     if (*from && *to && (cd = yaz_iconv_open(to, from)))
1405     {
1406         char outbuf[12];
1407         size_t inbytesleft = sz;
1408         const char *inp = buf;
1409         
1410         if (!rec->wrbuf_iconv)
1411             rec->wrbuf_iconv = wrbuf_alloc();
1412
1413         wrbuf_rewind(rec->wrbuf_iconv);
1414
1415         while (inbytesleft)
1416         {
1417             size_t outbytesleft = sizeof(outbuf);
1418             char *outp = outbuf;
1419             size_t r = yaz_iconv (cd, (char**) &inp,
1420                                   &inbytesleft, 
1421                                   &outp, &outbytesleft);
1422             if (r == (size_t) (-1))
1423             {
1424                 int e = yaz_iconv_error(cd);
1425                 if (e != YAZ_ICONV_E2BIG)
1426                     break;
1427             }
1428             wrbuf_write(rec->wrbuf_iconv, outbuf, outp - outbuf);
1429         }
1430         wrbuf_puts(rec->wrbuf_iconv, "");
1431         buf = wrbuf_buf(rec->wrbuf_iconv);
1432         sz = wrbuf_len(rec->wrbuf_iconv);
1433         yaz_iconv_close(cd);
1434     }
1435     if (len)
1436         *len = sz;
1437     return buf;
1438 }
1439
1440 ZOOM_API(const char *)
1441 ZOOM_record_get (ZOOM_record rec, const char *type_spec, int *len)
1442 {
1443     char type[40];
1444     char charset[40];
1445     const char *cp;
1446     int i;
1447     Z_NamePlusRecord *npr;
1448     
1449     if (len)
1450         *len = 0; /* default return */
1451         
1452     if (!rec)
1453         return 0;
1454     npr = rec->npr;
1455     if (!npr)
1456         return 0;
1457
1458     cp = type_spec;
1459     for (i = 0; cp[i] && i < sizeof(type)-1; i++)
1460     {
1461         if (cp[i] == ';' || cp[i] == ' ')
1462             break;
1463         type[i] = cp[i];
1464     }
1465     type[i] = '\0';
1466     charset[0] = '\0';
1467     if (type_spec[i] == ';')
1468     {
1469         i++;
1470         while (type_spec[i] == ' ')
1471             i++;
1472         if (!strncmp(type_spec+i, "charset=", 8))
1473         {
1474             cp = type_spec+i+8;
1475             for (i = 0; cp[i] && i < sizeof(charset)-1; i++)
1476             {
1477                 if (cp[i] == ';' || cp[i] == ' ')
1478                     break;
1479                 charset[i] = cp[i];
1480             }
1481             charset[i] = '\0';
1482         }
1483     }   
1484
1485     if (!strcmp (type, "database"))
1486     {
1487         if (len)
1488             *len = (npr->databaseName ? strlen(npr->databaseName) : 0);
1489         return npr->databaseName;
1490     }
1491     else if (!strcmp (type, "syntax"))
1492     {
1493         const char *desc = 0;   
1494         if (npr->which == Z_NamePlusRecord_databaseRecord)
1495         {
1496             Z_External *r = (Z_External *) npr->u.databaseRecord;
1497             oident *ent = oid_getentbyoid(r->direct_reference);
1498             if (ent)
1499                 desc = ent->desc;
1500         }
1501         if (!desc)
1502             desc = "none";
1503         if (len)
1504             *len = strlen(desc);
1505         return desc;
1506     }
1507     else if (!strcmp (type, "render") && 
1508              npr->which == Z_NamePlusRecord_databaseRecord)
1509     {
1510         Z_External *r = (Z_External *) npr->u.databaseRecord;
1511         oident *ent = oid_getentbyoid(r->direct_reference);
1512
1513         /* render bibliographic record .. */
1514         if (r->which == Z_External_OPAC)
1515         {
1516             r = r->u.opac->bibliographicRecord;
1517             if (!r)
1518                 return 0;
1519             ent = oid_getentbyoid(r->direct_reference);
1520         }
1521         if (r->which == Z_External_sutrs)
1522             return record_iconv_return(rec, len,
1523                                        r->u.sutrs->buf, r->u.sutrs->len,
1524                                        charset);
1525         else if (r->which == Z_External_octet)
1526         {
1527             yaz_marc_t mt;
1528             switch (ent->value)
1529             {
1530             case VAL_SOIF:
1531             case VAL_HTML:
1532             case VAL_SUTRS:
1533                 break;
1534             case VAL_TEXT_XML:
1535             case VAL_APPLICATION_XML:
1536                 break;
1537             default:
1538                 if (!rec->wrbuf_marc)
1539                     rec->wrbuf_marc = wrbuf_alloc();
1540
1541                 mt = yaz_marc_create();
1542                 wrbuf_rewind (rec->wrbuf_marc);
1543                 if (yaz_marc_decode_wrbuf (
1544                         mt, (const char *) r->u.octet_aligned->buf,
1545                         r->u.octet_aligned->len,
1546                         rec->wrbuf_marc) > 0)
1547                 {
1548                     yaz_marc_destroy(mt);
1549                     return record_iconv_return(rec, len,
1550                                                wrbuf_buf(rec->wrbuf_marc),
1551                                                wrbuf_len(rec->wrbuf_marc),
1552                                                charset);
1553                 }
1554                 yaz_marc_destroy(mt);
1555             }
1556             return record_iconv_return(rec, len,
1557                                        (const char *) r->u.octet_aligned->buf,
1558                                        r->u.octet_aligned->len,
1559                                        charset);
1560         }
1561         else if (r->which == Z_External_grs1)
1562         {
1563             if (!rec->wrbuf_marc)
1564                 rec->wrbuf_marc = wrbuf_alloc();
1565             wrbuf_rewind (rec->wrbuf_marc);
1566             yaz_display_grs1(rec->wrbuf_marc, r->u.grs1, 0);
1567             return record_iconv_return(rec, len,
1568                                        wrbuf_buf(rec->wrbuf_marc),
1569                                        wrbuf_len(rec->wrbuf_marc),
1570                                        charset);
1571         }
1572         return 0;
1573     }
1574     else if (npr->which == Z_NamePlusRecord_databaseRecord &&
1575              (!strcmp (type, "xml") || !strcmp(type, "oai")))
1576     {
1577         Z_External *r = (Z_External *) npr->u.databaseRecord;
1578         oident *ent = oid_getentbyoid(r->direct_reference);
1579
1580         /* render bibliographic record .. */
1581         if (r->which == Z_External_OPAC)
1582         {
1583             r = r->u.opac->bibliographicRecord;
1584             if (!r)
1585                 return 0;
1586             ent = oid_getentbyoid(r->direct_reference);
1587         }
1588         
1589         if (r->which == Z_External_sutrs)
1590             return record_iconv_return(rec, len,
1591                                        (const char *) r->u.sutrs->buf,
1592                                        r->u.sutrs->len,
1593                                        charset);
1594         else if (r->which == Z_External_octet)
1595         {
1596             yaz_marc_t mt;
1597             int marc_decode_type = YAZ_MARC_MARCXML;
1598
1599             if (!strcmp(type, "oai"))
1600                 marc_decode_type = YAZ_MARC_OAIMARC;
1601             switch (ent->value)
1602             {
1603             case VAL_SOIF:
1604             case VAL_HTML:
1605             case VAL_SUTRS:
1606                 break;
1607             case VAL_TEXT_XML:
1608             case VAL_APPLICATION_XML:
1609                 break;
1610             default:
1611                 if (!rec->wrbuf_marc)
1612                     rec->wrbuf_marc = wrbuf_alloc();
1613                 wrbuf_rewind (rec->wrbuf_marc);
1614                 mt = yaz_marc_create();
1615
1616                 yaz_marc_xml(mt, YAZ_MARC_MARCXML);
1617                 if (yaz_marc_decode_wrbuf (
1618                         mt, (const char *) r->u.octet_aligned->buf,
1619                         r->u.octet_aligned->len,
1620                         rec->wrbuf_marc) > 0)
1621                 {
1622                     yaz_marc_destroy(mt);
1623                     return record_iconv_return(rec, len,
1624                                                wrbuf_buf(rec->wrbuf_marc),
1625                                                wrbuf_len(rec->wrbuf_marc),
1626                                                charset);
1627                 }
1628                 yaz_marc_destroy(mt);
1629             }
1630             return record_iconv_return(rec, len,
1631                                        (const char *) r->u.octet_aligned->buf,
1632                                        r->u.octet_aligned->len,
1633                                        charset);
1634         }
1635         else if (r->which == Z_External_grs1)
1636         {
1637             if (len) *len = 5;
1638             return "GRS-1";
1639         }
1640         return 0;
1641     }
1642     else if (!strcmp (type, "raw"))
1643     {
1644         if (npr->which == Z_NamePlusRecord_databaseRecord)
1645         {
1646             Z_External *r = (Z_External *) npr->u.databaseRecord;
1647             
1648             if (r->which == Z_External_sutrs)
1649             {
1650                 if (len) *len = r->u.sutrs->len;
1651                 return (const char *) r->u.sutrs->buf;
1652             }
1653             else if (r->which == Z_External_octet)
1654             {
1655                 if (len) *len = r->u.octet_aligned->len;
1656                 return (const char *) r->u.octet_aligned->buf;
1657             }
1658             else /* grs-1, explain, OPAC, ... */
1659             {
1660                 if (len) *len = -1;
1661                 return (const char *) npr->u.databaseRecord;
1662             }
1663         }
1664         return 0;
1665     }
1666     else if (!strcmp (type, "ext"))
1667     {
1668         if (npr->which == Z_NamePlusRecord_databaseRecord)
1669             return (const char *) npr->u.databaseRecord;
1670         return 0;
1671     }
1672     else if (npr->which == Z_NamePlusRecord_databaseRecord &&
1673              !strcmp (type, "opac"))
1674              
1675     {
1676         Z_External *r = (Z_External *) npr->u.databaseRecord;
1677         if (r->which == Z_External_OPAC)
1678         {
1679             if (!rec->wrbuf_opac)
1680                 rec->wrbuf_opac = wrbuf_alloc();
1681             wrbuf_rewind (rec->wrbuf_opac);
1682             yaz_display_OPAC(rec->wrbuf_opac, r->u.opac, 0);
1683             return record_iconv_return(rec, len,
1684                                        wrbuf_buf(rec->wrbuf_opac),
1685                                        wrbuf_len(rec->wrbuf_opac),
1686                                        charset);
1687         }
1688     }
1689     return 0;
1690 }
1691
1692 static int strcmp_null(const char *v1, const char *v2)
1693 {
1694     if (!v1 && !v2)
1695         return 0;
1696     if (!v1 || !v2)
1697         return -1;
1698     return strcmp(v1, v2);
1699 }
1700
1701 static void record_cache_add (ZOOM_resultset r, Z_NamePlusRecord *npr, 
1702                               int pos)
1703 {
1704     ZOOM_record_cache rc;
1705     const char *elementSetName =
1706         ZOOM_resultset_option_get (r, "elementSetName");
1707     const char *syntax = 
1708         ZOOM_resultset_option_get (r, "preferredRecordSyntax");
1709     
1710     ZOOM_Event event = ZOOM_Event_create(ZOOM_EVENT_RECV_RECORD);
1711     ZOOM_connection_put_event(r->connection, event);
1712
1713     for (rc = r->record_cache; rc; rc = rc->next)
1714     {
1715         if (pos == rc->pos)
1716         {
1717             if (strcmp_null(r->schema, rc->schema))
1718                 continue;
1719             if (strcmp_null(elementSetName,rc->elementSetName))
1720                 continue;
1721             if (strcmp_null(syntax, rc->syntax))
1722                 continue;
1723             /* not destroying rc->npr (it's handled by nmem )*/
1724             rc->rec.npr = npr;
1725             /* keeping wrbuf_marc too */
1726             return;
1727         }
1728     }
1729     rc = (ZOOM_record_cache) odr_malloc (r->odr, sizeof(*rc));
1730     rc->rec.npr = npr; 
1731     rc->rec.odr = 0;
1732     rc->rec.wrbuf_marc = 0;
1733     rc->rec.wrbuf_iconv = 0;
1734     rc->rec.wrbuf_opac = 0;
1735     if (elementSetName)
1736         rc->elementSetName = odr_strdup (r->odr, elementSetName);
1737     else
1738         rc->elementSetName = 0;
1739
1740     if (syntax)
1741         rc->syntax = odr_strdup (r->odr, syntax);
1742     else
1743         rc->syntax = 0;
1744
1745     if (r->schema)
1746         rc->schema = odr_strdup (r->odr, r->schema);
1747     else
1748         rc->schema = 0;
1749
1750     rc->pos = pos;
1751     rc->next = r->record_cache;
1752     r->record_cache = rc;
1753 }
1754
1755 static ZOOM_record record_cache_lookup (ZOOM_resultset r, int pos)
1756 {
1757     ZOOM_record_cache rc;
1758     const char *elementSetName =
1759         ZOOM_resultset_option_get (r, "elementSetName");
1760     const char *syntax = 
1761         ZOOM_resultset_option_get (r, "preferredRecordSyntax");
1762     
1763     for (rc = r->record_cache; rc; rc = rc->next)
1764     {
1765         if (pos == rc->pos)
1766         {
1767             if (strcmp_null(r->schema, rc->schema))
1768                 continue;
1769             if (strcmp_null(elementSetName,rc->elementSetName))
1770                 continue;
1771             if (strcmp_null(syntax, rc->syntax))
1772                 continue;
1773             return &rc->rec;
1774         }
1775     }
1776     return 0;
1777 }
1778                                              
1779 static void handle_records (ZOOM_connection c, Z_Records *sr,
1780                             int present_phase)
1781 {
1782     ZOOM_resultset resultset;
1783
1784     if (!c->tasks)
1785         return ;
1786     switch (c->tasks->which)
1787     {
1788     case ZOOM_TASK_SEARCH:
1789         resultset = c->tasks->u.search.resultset;
1790         break;
1791     case ZOOM_TASK_RETRIEVE:
1792         resultset = c->tasks->u.retrieve.resultset;        
1793         break;
1794     default:
1795         return;
1796     }
1797     if (sr && sr->which == Z_Records_NSD)
1798     {
1799         Z_DiagRec dr, *dr_p = &dr;
1800         dr.which = Z_DiagRec_defaultFormat;
1801         dr.u.defaultFormat = sr->u.nonSurrogateDiagnostic;
1802         
1803         response_diag (c, dr_p);
1804     }
1805     else if (sr && sr->which == Z_Records_multipleNSD)
1806     {
1807         if (sr->u.multipleNonSurDiagnostics->num_diagRecs >= 1)
1808             response_diag(c, sr->u.multipleNonSurDiagnostics->diagRecs[0]);
1809         else
1810             set_ZOOM_error(c, ZOOM_ERROR_DECODE, 0);
1811     }
1812     else 
1813     {
1814         if (resultset->count + resultset->start > resultset->size)
1815             resultset->count = resultset->size - resultset->start;
1816         if (resultset->count < 0)
1817             resultset->count = 0;
1818         if (sr && sr->which == Z_Records_DBOSD)
1819         {
1820             int i;
1821             NMEM nmem = odr_extract_mem (c->odr_in);
1822             Z_NamePlusRecordList *p =
1823                 sr->u.databaseOrSurDiagnostics;
1824             for (i = 0; i<p->num_records; i++)
1825             {
1826                 record_cache_add (resultset, p->records[i],
1827                                   i+ resultset->start);
1828             }
1829             /* transfer our response to search_nmem .. we need it later */
1830             nmem_transfer (resultset->odr->mem, nmem);
1831             nmem_destroy (nmem);
1832             if (present_phase && p->num_records == 0)
1833             {
1834                 /* present response and we didn't get any records! */
1835                 set_ZOOM_error(c, ZOOM_ERROR_DECODE, 0);
1836             }
1837         }
1838         else if (present_phase)
1839         {
1840             /* present response and we didn't get any records! */
1841             set_ZOOM_error(c, ZOOM_ERROR_DECODE, 0);
1842         }
1843     }
1844 }
1845
1846 static void handle_present_response (ZOOM_connection c, Z_PresentResponse *pr)
1847 {
1848     handle_records (c, pr->records, 1);
1849 }
1850
1851 static void handle_search_response (ZOOM_connection c, Z_SearchResponse *sr)
1852 {
1853     ZOOM_resultset resultset;
1854     ZOOM_Event event;
1855     
1856     yaz_log (LOG_DEBUG, "got search response");
1857     
1858     if (!c->tasks || c->tasks->which != ZOOM_TASK_SEARCH)
1859         return ;
1860     
1861     event = ZOOM_Event_create(ZOOM_EVENT_RECV_SEARCH);
1862     ZOOM_connection_put_event(c, event);
1863
1864     resultset = c->tasks->u.search.resultset;
1865
1866     resultset->size = *sr->resultCount;
1867     handle_records (c, sr->records, 0);
1868 }
1869
1870 static void sort_response (ZOOM_connection c, Z_SortResponse *res)
1871 {
1872     if (res->diagnostics && res->num_diagnostics > 0)
1873         response_diag (c, res->diagnostics[0]);
1874 }
1875
1876 static int scan_response (ZOOM_connection c, Z_ScanResponse *res)
1877 {
1878     NMEM nmem = odr_extract_mem (c->odr_in);
1879     ZOOM_scanset scan;
1880
1881     if (!c->tasks || c->tasks->which != ZOOM_TASK_SCAN)
1882         return 0;
1883     scan = c->tasks->u.scan.scan;
1884
1885     if (res->entries && res->entries->nonsurrogateDiagnostics)
1886         response_diag(c, res->entries->nonsurrogateDiagnostics[0]);
1887     scan->scan_response = res;
1888     nmem_transfer (scan->odr->mem, nmem);
1889     if (res->stepSize)
1890         ZOOM_options_set_int (scan->options, "stepSize", *res->stepSize);
1891     if (res->positionOfTerm)
1892         ZOOM_options_set_int (scan->options, "position", *res->positionOfTerm);
1893     if (res->scanStatus)
1894         ZOOM_options_set_int (scan->options, "scanStatus", *res->scanStatus);
1895     if (res->numberOfEntriesReturned)
1896         ZOOM_options_set_int (scan->options, "number",
1897                               *res->numberOfEntriesReturned);
1898     nmem_destroy (nmem);
1899     return 1;
1900 }
1901
1902 static zoom_ret send_sort (ZOOM_connection c)
1903 {
1904     ZOOM_resultset  resultset;
1905
1906     if (!c->tasks || c->tasks->which != ZOOM_TASK_SEARCH)
1907         return zoom_complete;
1908
1909     resultset = c->tasks->u.search.resultset;
1910
1911     if (c->error)
1912     {
1913         resultset->r_sort_spec = 0;
1914         return zoom_complete;
1915     }
1916     if (resultset->r_sort_spec)
1917     {
1918         Z_APDU *apdu = zget_APDU(c->odr_out, Z_APDU_sortRequest);
1919         Z_SortRequest *req = apdu->u.sortRequest;
1920         
1921         req->num_inputResultSetNames = 1;
1922         req->inputResultSetNames = (Z_InternationalString **)
1923             odr_malloc (c->odr_out, sizeof(*req->inputResultSetNames));
1924         req->inputResultSetNames[0] =
1925             odr_strdup (c->odr_out, resultset->setname);
1926         req->sortedResultSetName = odr_strdup (c->odr_out, resultset->setname);
1927         req->sortSequence = resultset->r_sort_spec;
1928         resultset->r_sort_spec = 0;
1929         return send_APDU (c, apdu);
1930     }
1931     return zoom_complete;
1932 }
1933
1934 static zoom_ret send_present (ZOOM_connection c)
1935 {
1936     Z_APDU *apdu = 0;
1937     Z_PresentRequest *req = 0;
1938     int i = 0;
1939     const char *syntax = 0;
1940     const char *elementSetName = 0;
1941     ZOOM_resultset  resultset;
1942
1943     if (!c->tasks)
1944         return zoom_complete;
1945
1946     switch (c->tasks->which)
1947     {
1948     case ZOOM_TASK_SEARCH:
1949         resultset = c->tasks->u.search.resultset;
1950         break;
1951     case ZOOM_TASK_RETRIEVE:
1952         resultset = c->tasks->u.retrieve.resultset;
1953         resultset->start = c->tasks->u.retrieve.start;
1954         resultset->count = c->tasks->u.retrieve.count;
1955
1956         if (resultset->start >= resultset->size)
1957             return zoom_complete;
1958         if (resultset->start + resultset->count > resultset->size)
1959             resultset->count = resultset->size - resultset->start;
1960         break;
1961     default:
1962         return zoom_complete;
1963     }
1964
1965     syntax = ZOOM_resultset_option_get (resultset, "preferredRecordSyntax");
1966     elementSetName = ZOOM_resultset_option_get (resultset, "elementSetName");
1967
1968     if (c->error)                  /* don't continue on error */
1969         return zoom_complete;
1970     if (resultset->start < 0)
1971         return zoom_complete;
1972     for (i = 0; i<resultset->count; i++)
1973     {
1974         ZOOM_record rec =
1975             record_cache_lookup (resultset, i + resultset->start);
1976         if (!rec)
1977             break;
1978     }
1979     if (i == resultset->count)
1980         return zoom_complete;
1981
1982     apdu = zget_APDU(c->odr_out, Z_APDU_presentRequest);
1983     req = apdu->u.presentRequest;
1984
1985     resultset->start += i;
1986     resultset->count -= i;
1987     *req->resultSetStartPoint = resultset->start + 1;
1988     *req->numberOfRecordsRequested = resultset->step>0 ?
1989         resultset->step : resultset->count;
1990     assert (*req->numberOfRecordsRequested > 0);
1991
1992     if (syntax && *syntax)
1993         req->preferredRecordSyntax =
1994             yaz_str_to_z3950oid (c->odr_out, CLASS_RECSYN, syntax);
1995
1996     if (resultset->schema && *resultset->schema)
1997     {
1998         Z_RecordComposition *compo = (Z_RecordComposition *)
1999             odr_malloc (c->odr_out, sizeof(*compo));
2000
2001         req->recordComposition = compo;
2002         compo->which = Z_RecordComp_complex;
2003         compo->u.complex = (Z_CompSpec *)
2004             odr_malloc(c->odr_out, sizeof(*compo->u.complex));
2005         compo->u.complex->selectAlternativeSyntax = (bool_t *) 
2006             odr_malloc(c->odr_out, sizeof(bool_t));
2007         *compo->u.complex->selectAlternativeSyntax = 0;
2008
2009         compo->u.complex->generic = (Z_Specification *)
2010             odr_malloc(c->odr_out, sizeof(*compo->u.complex->generic));
2011
2012         compo->u.complex->generic->which = Z_Schema_oid;
2013         compo->u.complex->generic->schema.oid = (Odr_oid *)
2014             yaz_str_to_z3950oid (c->odr_out, CLASS_SCHEMA, resultset->schema);
2015
2016         if (!compo->u.complex->generic->schema.oid)
2017         {
2018             /* OID wasn't a schema! Try record syntax instead. */
2019
2020             compo->u.complex->generic->schema.oid = (Odr_oid *)
2021                 yaz_str_to_z3950oid (c->odr_out, CLASS_RECSYN, resultset->schema);
2022         }
2023         if (elementSetName && *elementSetName)
2024         {
2025             compo->u.complex->generic->elementSpec = (Z_ElementSpec *)
2026                 odr_malloc(c->odr_out, sizeof(Z_ElementSpec));
2027             compo->u.complex->generic->elementSpec->which =
2028                 Z_ElementSpec_elementSetName;
2029             compo->u.complex->generic->elementSpec->u.elementSetName =
2030                 odr_strdup (c->odr_out, elementSetName);
2031         }
2032         else
2033             compo->u.complex->generic->elementSpec = 0;
2034         compo->u.complex->num_dbSpecific = 0;
2035         compo->u.complex->dbSpecific = 0;
2036         compo->u.complex->num_recordSyntax = 0;
2037         compo->u.complex->recordSyntax = 0;
2038     }
2039     else if (elementSetName && *elementSetName)
2040     {
2041         Z_ElementSetNames *esn = (Z_ElementSetNames *)
2042             odr_malloc (c->odr_out, sizeof(*esn));
2043         Z_RecordComposition *compo = (Z_RecordComposition *)
2044             odr_malloc (c->odr_out, sizeof(*compo));
2045         
2046         esn->which = Z_ElementSetNames_generic;
2047         esn->u.generic = odr_strdup (c->odr_out, elementSetName);
2048         compo->which = Z_RecordComp_simple;
2049         compo->u.simple = esn;
2050         req->recordComposition = compo;
2051     }
2052     req->resultSetId = odr_strdup(c->odr_out, resultset->setname);
2053     return send_APDU (c, apdu);
2054 }
2055
2056 ZOOM_API(ZOOM_scanset)
2057 ZOOM_connection_scan (ZOOM_connection c, const char *start)
2058 {
2059     ZOOM_scanset scan = (ZOOM_scanset) xmalloc (sizeof(*scan));
2060
2061     scan->connection = c;
2062     scan->odr = odr_createmem (ODR_DECODE);
2063     scan->options = ZOOM_options_create_with_parent (c->options);
2064     scan->refcount = 1;
2065     scan->scan_response = 0;
2066
2067     if ((scan->termListAndStartPoint =
2068          p_query_scan(scan->odr, PROTO_Z3950, &scan->attributeSet,
2069                       start)))
2070     {
2071         ZOOM_task task = ZOOM_connection_add_task (c, ZOOM_TASK_SCAN);
2072         task->u.scan.scan = scan;
2073         
2074         (scan->refcount)++;
2075         if (!c->async)
2076         {
2077             while (ZOOM_event (1, &c))
2078                 ;
2079         }
2080     }
2081     return scan;
2082 }
2083
2084 ZOOM_API(void)
2085 ZOOM_scanset_destroy (ZOOM_scanset scan)
2086 {
2087     if (!scan)
2088         return;
2089     (scan->refcount)--;
2090     if (scan->refcount == 0)
2091     {
2092         odr_destroy (scan->odr);
2093         
2094         ZOOM_options_destroy (scan->options);
2095         xfree (scan);
2096     }
2097 }
2098
2099 static zoom_ret send_package (ZOOM_connection c)
2100 {
2101     ZOOM_Event event;
2102     if (!c->tasks)
2103         return zoom_complete;
2104     assert (c->tasks->which == ZOOM_TASK_PACKAGE);
2105     
2106     event = ZOOM_Event_create (ZOOM_EVENT_SEND_APDU);
2107     ZOOM_connection_put_event (c, event);
2108     
2109     return do_write_ex (c, c->tasks->u.package->buf_out,
2110                         c->tasks->u.package->len_out);
2111 }
2112
2113 static zoom_ret send_scan (ZOOM_connection c)
2114 {
2115     ZOOM_scanset scan;
2116     Z_APDU *apdu = zget_APDU(c->odr_out, Z_APDU_scanRequest);
2117     Z_ScanRequest *req = apdu->u.scanRequest;
2118     if (!c->tasks)
2119         return zoom_complete;
2120     assert (c->tasks->which == ZOOM_TASK_SCAN);
2121     scan = c->tasks->u.scan.scan;
2122
2123     req->termListAndStartPoint = scan->termListAndStartPoint;
2124     req->attributeSet = scan->attributeSet;
2125
2126     *req->numberOfTermsRequested =
2127         ZOOM_options_get_int(scan->options, "number", 10);
2128
2129     req->preferredPositionInResponse =
2130         odr_intdup (c->odr_out,
2131                     ZOOM_options_get_int(scan->options, "position", 1));
2132
2133     req->stepSize =
2134         odr_intdup (c->odr_out,
2135                     ZOOM_options_get_int(scan->options, "stepSize", 0));
2136     
2137     req->databaseNames = set_DatabaseNames (c, scan->options, 
2138                                             &req->num_databaseNames);
2139
2140     return send_APDU (c, apdu);
2141 }
2142
2143 ZOOM_API(size_t)
2144 ZOOM_scanset_size (ZOOM_scanset scan)
2145 {
2146     if (!scan || !scan->scan_response || !scan->scan_response->entries)
2147         return 0;
2148     return scan->scan_response->entries->num_entries;
2149 }
2150
2151 ZOOM_API(const char *)
2152 ZOOM_scanset_term (ZOOM_scanset scan, size_t pos,
2153                    int *occ, int *len)
2154 {
2155     const char *term = 0;
2156     size_t noent = ZOOM_scanset_size (scan);
2157     Z_ScanResponse *res = scan->scan_response;
2158     
2159     *len = 0;
2160     *occ = 0;
2161     if (pos >= noent)
2162         return 0;
2163     if (res->entries->entries[pos]->which == Z_Entry_termInfo)
2164     {
2165         Z_TermInfo *t = res->entries->entries[pos]->u.termInfo;
2166         
2167         if (t->term->which == Z_Term_general)
2168         {
2169             term = (const char *) t->term->u.general->buf;
2170             *len = t->term->u.general->len;
2171         }
2172         *occ = t->globalOccurrences ? *t->globalOccurrences : 0;
2173     }
2174     return term;
2175 }
2176
2177 ZOOM_API(const char *)
2178 ZOOM_scanset_display_term (ZOOM_scanset scan, size_t pos,
2179                            int *occ, int *len)
2180 {
2181     const char *term = 0;
2182     size_t noent = ZOOM_scanset_size (scan);
2183     Z_ScanResponse *res = scan->scan_response;
2184     
2185     *len = 0;
2186     *occ = 0;
2187     if (pos >= noent)
2188         return 0;
2189     if (res->entries->entries[pos]->which == Z_Entry_termInfo)
2190     {
2191         Z_TermInfo *t = res->entries->entries[pos]->u.termInfo;
2192
2193         if (t->displayTerm)
2194         {
2195             term = (const char *) t->term->u.general->buf;
2196             *len = strlen(term);
2197         }
2198         else if (t->term->which == Z_Term_general)
2199         {
2200             term = (const char *) t->term->u.general->buf;
2201             *len = t->term->u.general->len;
2202         }
2203         *occ = t->globalOccurrences ? *t->globalOccurrences : 0;
2204     }
2205     return term;
2206 }
2207
2208 ZOOM_API(const char *)
2209 ZOOM_scanset_option_get (ZOOM_scanset scan, const char *key)
2210 {
2211     return ZOOM_options_get (scan->options, key);
2212 }
2213
2214 ZOOM_API(void)
2215 ZOOM_scanset_option_set (ZOOM_scanset scan, const char *key,
2216                               const char *val)
2217 {
2218     ZOOM_options_set (scan->options, key, val);
2219 }
2220
2221 static Z_APDU *create_es_package (ZOOM_package p, int type)
2222 {
2223     const char *str;
2224     Z_APDU *apdu = zget_APDU(p->odr_out, Z_APDU_extendedServicesRequest);
2225     Z_ExtendedServicesRequest *req = apdu->u.extendedServicesRequest;
2226     
2227     *req->function = Z_ExtendedServicesRequest_create;
2228     
2229     str = ZOOM_options_get(p->options, "package-name");
2230     if (str && *str)
2231         req->packageName = nmem_strdup (p->odr_out->mem, str);
2232     
2233     str = ZOOM_options_get(p->options, "user-id");
2234     if (str)
2235         req->userId = nmem_strdup (p->odr_out->mem, str);
2236     
2237     req->packageType = yaz_oidval_to_z3950oid(p->odr_out, CLASS_EXTSERV,
2238                                               type);
2239
2240     str = ZOOM_options_get(p->options, "function");
2241     if (str)
2242     {
2243         if (!strcmp (str, "create"))
2244             *req->function = 1;
2245         if (!strcmp (str, "delete"))
2246             *req->function = 2;
2247         if (!strcmp (str, "modify"))
2248             *req->function = 3;
2249     }
2250     return apdu;
2251 }
2252
2253 static const char *ill_array_lookup (void *clientData, const char *idx)
2254 {
2255     ZOOM_package p = (ZOOM_package) clientData;
2256     return ZOOM_options_get (p->options, idx+4);
2257 }
2258
2259 static Z_External *encode_ill_request (ZOOM_package p)
2260 {
2261     ODR out = p->odr_out;
2262     ILL_Request *req;
2263     Z_External *r = 0;
2264     struct ill_get_ctl ctl;
2265         
2266     ctl.odr = p->odr_out;
2267     ctl.clientData = p;
2268     ctl.f = ill_array_lookup;
2269         
2270     req = ill_get_ILLRequest(&ctl, "ill", 0);
2271         
2272     if (!ill_Request (out, &req, 0, 0))
2273     {
2274         int ill_request_size;
2275         char *ill_request_buf = odr_getbuf (out, &ill_request_size, 0);
2276         if (ill_request_buf)
2277             odr_setbuf (out, ill_request_buf, ill_request_size, 1);
2278         return 0;
2279     }
2280     else
2281     {
2282         oident oid;
2283         int illRequest_size = 0;
2284         char *illRequest_buf = odr_getbuf (out, &illRequest_size, 0);
2285                 
2286         oid.proto = PROTO_GENERAL;
2287         oid.oclass = CLASS_GENERAL;
2288         oid.value = VAL_ISO_ILL_1;
2289                 
2290         r = (Z_External *) odr_malloc (out, sizeof(*r));
2291         r->direct_reference = odr_oiddup(out,oid_getoidbyent(&oid)); 
2292         r->indirect_reference = 0;
2293         r->descriptor = 0;
2294         r->which = Z_External_single;
2295                 
2296         r->u.single_ASN1_type = (Odr_oct *)
2297             odr_malloc (out, sizeof(*r->u.single_ASN1_type));
2298         r->u.single_ASN1_type->buf = (unsigned char*)
2299             odr_malloc (out, illRequest_size);
2300         r->u.single_ASN1_type->len = illRequest_size;
2301         r->u.single_ASN1_type->size = illRequest_size;
2302         memcpy (r->u.single_ASN1_type->buf, illRequest_buf, illRequest_size);
2303     }
2304     return r;
2305 }
2306
2307 static Z_ItemOrder *encode_item_order(ZOOM_package p)
2308 {
2309     Z_ItemOrder *req = (Z_ItemOrder *) odr_malloc (p->odr_out, sizeof(*req));
2310     const char *str;
2311     
2312     req->which=Z_IOItemOrder_esRequest;
2313     req->u.esRequest = (Z_IORequest *) 
2314         odr_malloc(p->odr_out,sizeof(Z_IORequest));
2315
2316     /* to keep part ... */
2317     req->u.esRequest->toKeep = (Z_IOOriginPartToKeep *)
2318         odr_malloc(p->odr_out,sizeof(Z_IOOriginPartToKeep));
2319     req->u.esRequest->toKeep->supplDescription = 0;
2320     req->u.esRequest->toKeep->contact = (Z_IOContact *)
2321         odr_malloc (p->odr_out, sizeof(*req->u.esRequest->toKeep->contact));
2322         
2323     str = ZOOM_options_get(p->options, "contact-name");
2324     req->u.esRequest->toKeep->contact->name = str ?
2325         nmem_strdup (p->odr_out->mem, str) : 0;
2326         
2327     str = ZOOM_options_get(p->options, "contact-phone");
2328     req->u.esRequest->toKeep->contact->phone = str ?
2329         nmem_strdup (p->odr_out->mem, str) : 0;
2330         
2331     str = ZOOM_options_get(p->options, "contact-email");
2332     req->u.esRequest->toKeep->contact->email = str ?
2333         nmem_strdup (p->odr_out->mem, str) : 0;
2334         
2335     req->u.esRequest->toKeep->addlBilling = 0;
2336         
2337     /* not to keep part ... */
2338     req->u.esRequest->notToKeep = (Z_IOOriginPartNotToKeep *)
2339         odr_malloc(p->odr_out,sizeof(Z_IOOriginPartNotToKeep));
2340         
2341     str = ZOOM_options_get(p->options, "itemorder-setname");
2342     if (!str)
2343         str = "default";
2344
2345     if (!*str) 
2346         req->u.esRequest->notToKeep->resultSetItem = 0;
2347     else
2348     {
2349         req->u.esRequest->notToKeep->resultSetItem = (Z_IOResultSetItem *)
2350            odr_malloc(p->odr_out, sizeof(Z_IOResultSetItem));
2351
2352         req->u.esRequest->notToKeep->resultSetItem->resultSetId =
2353            nmem_strdup (p->odr_out->mem, str);
2354         req->u.esRequest->notToKeep->resultSetItem->item =
2355             (int *) odr_malloc(p->odr_out, sizeof(int));
2356         
2357         str = ZOOM_options_get(p->options, "itemorder-item");
2358         *req->u.esRequest->notToKeep->resultSetItem->item =
2359             (str ? atoi(str) : 1);
2360     }
2361     req->u.esRequest->notToKeep->itemRequest = encode_ill_request(p);
2362     
2363     return req;
2364 }
2365
2366 ZOOM_API(void)
2367     ZOOM_package_send (ZOOM_package p, const char *type)
2368 {
2369     Z_APDU *apdu = 0;
2370     ZOOM_connection c;
2371     if (!p)
2372         return;
2373     c = p->connection;
2374     odr_reset (p->odr_out);
2375     xfree (p->buf_out);
2376     p->buf_out = 0;
2377     if (!strcmp(type, "itemorder"))
2378     {
2379         Z_External *r;
2380         apdu = create_es_package (p, VAL_ITEMORDER);
2381         if (apdu)
2382         {
2383             r = (Z_External *) odr_malloc (p->odr_out, sizeof(*r));
2384             
2385             r->direct_reference =
2386                 yaz_oidval_to_z3950oid(p->odr_out, CLASS_EXTSERV,
2387                                        VAL_ITEMORDER);
2388             r->descriptor = 0;
2389             r->which = Z_External_itemOrder;
2390             r->indirect_reference = 0;
2391             r->u.itemOrder = encode_item_order (p);
2392
2393             apdu->u.extendedServicesRequest->taskSpecificParameters = r;
2394         }
2395     }
2396     if (apdu)
2397     {
2398         if (encode_APDU(p->connection, apdu, p->odr_out) == 0)
2399         {
2400             char *buf;
2401
2402             ZOOM_task task = ZOOM_connection_add_task (c, ZOOM_TASK_PACKAGE);
2403             task->u.package = p;
2404             buf = odr_getbuf(p->odr_out, &p->len_out, 0);
2405             p->buf_out = (char *) xmalloc (p->len_out);
2406             memcpy (p->buf_out, buf, p->len_out);
2407             
2408             (p->refcount)++;
2409             if (!c->async)
2410             {
2411                 while (ZOOM_event (1, &c))
2412                     ;
2413             }
2414         }
2415     }
2416 }
2417
2418 ZOOM_API(ZOOM_package)
2419     ZOOM_connection_package (ZOOM_connection c, ZOOM_options options)
2420 {
2421     ZOOM_package p = (ZOOM_package) xmalloc (sizeof(*p));
2422
2423     p->connection = c;
2424     p->odr_out = odr_createmem (ODR_ENCODE);
2425     p->options = ZOOM_options_create_with_parent2 (options, c->options);
2426     p->refcount = 1;
2427     p->buf_out = 0;
2428     p->len_out = 0;
2429     return p;
2430 }
2431
2432 ZOOM_API(void)
2433     ZOOM_package_destroy(ZOOM_package p)
2434 {
2435     if (!p)
2436         return;
2437     (p->refcount)--;
2438     if (p->refcount == 0)
2439     {
2440         odr_destroy (p->odr_out);
2441         xfree (p->buf_out);
2442         
2443         ZOOM_options_destroy (p->options);
2444         xfree (p);
2445     }
2446 }
2447
2448 ZOOM_API(const char *)
2449 ZOOM_package_option_get (ZOOM_package p, const char *key)
2450 {
2451     return ZOOM_options_get (p->options, key);
2452 }
2453
2454
2455 ZOOM_API(void)
2456 ZOOM_package_option_set (ZOOM_package p, const char *key,
2457                               const char *val)
2458 {
2459     ZOOM_options_set (p->options, key, val);
2460 }
2461
2462 static int ZOOM_connection_exec_task (ZOOM_connection c)
2463 {
2464     ZOOM_task task = c->tasks;
2465     zoom_ret ret = zoom_complete;
2466
2467     if (!task)
2468     {
2469         yaz_log (LOG_DEBUG, "ZOOM_connection_exec_task task=<null>");
2470         return 0;
2471     }
2472     yaz_log (LOG_DEBUG, "ZOOM_connection_exec_task type=%d run=%d",
2473              task->which, task->running);
2474     if (c->error != ZOOM_ERROR_NONE)
2475     {
2476         yaz_log (LOG_DEBUG, "remove tasks because of error = %d", c->error);
2477         ZOOM_connection_remove_tasks (c);
2478         return 0;
2479     }
2480     if (task->running)
2481     {
2482         yaz_log (LOG_DEBUG, "task already running");
2483         return 0;
2484     }
2485     task->running = 1;
2486     ret = zoom_complete;
2487     if (c->cs || task->which == ZOOM_TASK_CONNECT)
2488     {
2489         switch (task->which)
2490         {
2491         case ZOOM_TASK_SEARCH:
2492             if (c->proto == PROTO_HTTP)
2493                 ret = ZOOM_connection_srw_send_search(c);
2494             else
2495                 ret = ZOOM_connection_send_search(c);
2496             break;
2497         case ZOOM_TASK_RETRIEVE:
2498             if (c->proto == PROTO_HTTP)
2499                 ret = ZOOM_connection_srw_send_search(c);
2500             else
2501                 ret = send_present (c);
2502             break;
2503         case ZOOM_TASK_CONNECT:
2504             ret = do_connect(c);
2505             break;
2506         case ZOOM_TASK_SCAN:
2507             ret = send_scan(c);
2508             break;
2509         case ZOOM_TASK_PACKAGE:
2510             ret = send_package(c);
2511             break;
2512         }
2513     }
2514     else
2515     {
2516         yaz_log (LOG_DEBUG, "remove tasks because no connection exist");
2517         ZOOM_connection_remove_tasks (c);
2518     }
2519     if (ret == zoom_complete)
2520     {
2521         yaz_log (LOG_DEBUG, "task removed (complete)");
2522         ZOOM_connection_remove_task (c);
2523         return 0;
2524     }
2525     yaz_log (LOG_DEBUG, "task pending");
2526     return 1;
2527 }
2528
2529 static zoom_ret send_sort_present (ZOOM_connection c)
2530 {
2531     zoom_ret r = send_sort (c);
2532     if (r == zoom_complete)
2533         r = send_present (c);
2534     return r;
2535 }
2536
2537 static int es_response (ZOOM_connection c,
2538                         Z_ExtendedServicesResponse *res)
2539 {
2540     if (!c->tasks || c->tasks->which != ZOOM_TASK_PACKAGE)
2541         return 0;
2542     if (res->diagnostics && res->num_diagnostics > 0)
2543         response_diag(c, res->diagnostics[0]);
2544     if (res->taskPackage &&
2545         res->taskPackage->which == Z_External_extendedService)
2546     {
2547         Z_TaskPackage *taskPackage = res->taskPackage->u.extendedService;
2548         Odr_oct *id = taskPackage->targetReference;
2549         
2550         if (id)
2551             ZOOM_options_setl (c->tasks->u.package->options,
2552                                "targetReference", (char*) id->buf, id->len);
2553     }
2554     return 1;
2555 }
2556
2557
2558 static void handle_apdu (ZOOM_connection c, Z_APDU *apdu)
2559 {
2560     Z_InitResponse *initrs;
2561     
2562     c->mask = 0;
2563     yaz_log (LOG_DEBUG, "recv APDU type=%d", apdu->which);
2564     switch(apdu->which)
2565     {
2566     case Z_APDU_initResponse:
2567         initrs = apdu->u.initResponse;
2568         ZOOM_connection_option_set(c, "targetImplementationId",
2569                                    initrs->implementationId ?
2570                                    initrs->implementationId : "");
2571         ZOOM_connection_option_set(c, "targetImplementationName",
2572                                    initrs->implementationName ?
2573                                    initrs->implementationName : "");
2574         ZOOM_connection_option_set(c, "targetImplementationVersion",
2575                                    initrs->implementationVersion ?
2576                                    initrs->implementationVersion : "");
2577         if (!*initrs->result)
2578         {
2579             set_ZOOM_error(c, ZOOM_ERROR_INIT, 0);
2580         }
2581         else
2582         {
2583             char *cookie =
2584                 yaz_oi_get_string_oidval (&apdu->u.initResponse->otherInfo,
2585                                           VAL_COOKIE, 1, 0);
2586             xfree (c->cookie_in);
2587             c->cookie_in = 0;
2588             if (cookie)
2589                 c->cookie_in = xstrdup(cookie);
2590             if (ODR_MASK_GET(initrs->options, Z_Options_namedResultSets) &&
2591                 ODR_MASK_GET(initrs->protocolVersion, Z_ProtocolVersion_3))
2592                 c->support_named_resultsets = 1;
2593             if (c->tasks)
2594             {
2595                 assert (c->tasks->which == ZOOM_TASK_CONNECT);
2596                 ZOOM_connection_remove_task (c);
2597             }
2598             ZOOM_connection_exec_task (c);
2599         }
2600         if (ODR_MASK_GET(initrs->options, Z_Options_negotiationModel))
2601         {
2602             NMEM tmpmem = nmem_create();
2603             Z_CharSetandLanguageNegotiation *p =
2604                 yaz_get_charneg_record(initrs->otherInfo);
2605             
2606             if (p)
2607             {
2608                 char *charset=NULL, *lang=NULL;
2609                 int sel;
2610                 
2611                 yaz_get_response_charneg(tmpmem, p, &charset, &lang, &sel);
2612                 yaz_log(LOG_DEBUG, "Target accepted: charset %s, "
2613                         "language %s, select %d",
2614                         charset ? charset : "none", lang ? lang : "none", sel);
2615                 if (charset)
2616                     ZOOM_connection_option_set (c, "negotiation-charset",
2617                                                 charset);
2618                 if (lang)
2619                     ZOOM_connection_option_set (c, "negotiation-lang",
2620                                                 lang);
2621                 nmem_destroy(tmpmem);
2622             }
2623         }       
2624         break;
2625     case Z_APDU_searchResponse:
2626         handle_search_response (c, apdu->u.searchResponse);
2627         if (send_sort_present (c) == zoom_complete)
2628             ZOOM_connection_remove_task (c);
2629         break;
2630     case Z_APDU_presentResponse:
2631         handle_present_response (c, apdu->u.presentResponse);
2632         if (send_present (c) == zoom_complete)
2633             ZOOM_connection_remove_task (c);
2634         break;
2635     case Z_APDU_sortResponse:
2636         sort_response (c, apdu->u.sortResponse);
2637         if (send_present (c) == zoom_complete)
2638             ZOOM_connection_remove_task (c);
2639         break;
2640     case Z_APDU_scanResponse:
2641         scan_response (c, apdu->u.scanResponse);
2642         ZOOM_connection_remove_task (c);
2643         break;
2644     case Z_APDU_extendedServicesResponse:
2645         es_response (c, apdu->u.extendedServicesResponse);
2646         ZOOM_connection_remove_task (c);
2647         break;
2648     case Z_APDU_close:
2649         if (c->reconnect_ok)
2650         {
2651             do_close(c);
2652             c->tasks->running = 0;
2653             ZOOM_connection_insert_task (c, ZOOM_TASK_CONNECT);
2654         }
2655         else
2656         {
2657             set_ZOOM_error(c, ZOOM_ERROR_CONNECTION_LOST, 0);
2658             do_close(c);
2659         }
2660         break;
2661     default:
2662         set_ZOOM_error(c, ZOOM_ERROR_DECODE, 0);
2663         do_close(c);
2664     }
2665 }
2666
2667 #if HAVE_XML2
2668 static void handle_srw_response(ZOOM_connection c,
2669                                 Z_SRW_searchRetrieveResponse *res)
2670 {
2671     ZOOM_resultset resultset = 0;
2672     int i;
2673     NMEM nmem;
2674     ZOOM_Event event;
2675
2676     if (!c->tasks)
2677         return;
2678
2679     if (c->tasks->which == ZOOM_TASK_SEARCH)
2680         resultset = c->tasks->u.search.resultset;
2681     else if (c->tasks->which == ZOOM_TASK_RETRIEVE)
2682         resultset = c->tasks->u.retrieve.resultset;
2683     else
2684         return ;
2685
2686     event = ZOOM_Event_create(ZOOM_EVENT_RECV_SEARCH);
2687     ZOOM_connection_put_event(c, event);
2688
2689     resultset->size = 0;
2690
2691     yaz_log(LOG_DEBUG, "got SRW response OK");
2692     
2693     if (res->numberOfRecords)
2694         resultset->size = *res->numberOfRecords;
2695
2696     for (i = 0; i<res->num_records; i++)
2697     {
2698         int pos;
2699
2700         Z_NamePlusRecord *npr = (Z_NamePlusRecord *)
2701             odr_malloc(c->odr_in, sizeof(Z_NamePlusRecord));
2702
2703         if (res->records[i].recordPosition && 
2704             *res->records[i].recordPosition > 0)
2705             pos = *res->records[i].recordPosition - 1;
2706         else
2707             pos = resultset->start + i;
2708         
2709         npr->databaseName = 0;
2710         npr->which = Z_NamePlusRecord_databaseRecord;
2711         npr->u.databaseRecord = (Z_External *)
2712             odr_malloc(c->odr_in, sizeof(Z_External));
2713         npr->u.databaseRecord->descriptor = 0;
2714         npr->u.databaseRecord->direct_reference =
2715             yaz_oidval_to_z3950oid(c->odr_in, CLASS_RECSYN, VAL_TEXT_XML);
2716         npr->u.databaseRecord->which = Z_External_octet;
2717         npr->u.databaseRecord->u.octet_aligned = (Odr_oct *)
2718             odr_malloc(c->odr_in, sizeof(Odr_oct));
2719         npr->u.databaseRecord->u.octet_aligned->buf = 
2720             res->records[i].recordData_buf;
2721         npr->u.databaseRecord->u.octet_aligned->len = 
2722             npr->u.databaseRecord->u.octet_aligned->size = 
2723             res->records[i].recordData_len;
2724         record_cache_add (resultset, npr, pos);
2725     }
2726     if (res->num_diagnostics > 0)
2727     {
2728         set_dset_error(c, *res->diagnostics[0].code, "SRW",
2729                        res->diagnostics[0].details, 0);
2730     }
2731     nmem = odr_extract_mem(c->odr_in);
2732     nmem_transfer(resultset->odr->mem, nmem);
2733     nmem_destroy(nmem);
2734 }
2735 #endif
2736
2737 #if HAVE_XML2
2738 static void handle_http(ZOOM_connection c, Z_HTTP_Response *hres)
2739 {
2740     int ret = -1;
2741     const char *content_type = z_HTTP_header_lookup(hres->headers,
2742                                                     "Content-Type");
2743     const char *connection_head = z_HTTP_header_lookup(hres->headers,
2744                                                        "Connection");
2745     c->mask = 0;
2746     yaz_log (LOG_DEBUG, "handle_http");
2747
2748     if (content_type && !yaz_strcmp_del("text/xml", content_type, "; "))
2749     {
2750         Z_SOAP *soap_package = 0;
2751         ODR o = odr_createmem(ODR_DECODE);
2752         Z_SOAP_Handler soap_handlers[2] = {
2753             {"http://www.loc.gov/zing/srw/v1.0/", 0,
2754              (Z_SOAP_fun) yaz_srw_codec},
2755             {0, 0, 0}
2756         };
2757         ret = z_soap_codec(o, &soap_package,
2758                            &hres->content_buf, &hres->content_len,
2759                            soap_handlers);
2760         if (!ret && soap_package->which == Z_SOAP_generic &&
2761             soap_package->u.generic->no == 0)
2762         {
2763             Z_SRW_PDU *sr = soap_package->u.generic->p;
2764             if (sr->which == Z_SRW_searchRetrieve_response)
2765                 handle_srw_response(c, sr->u.response);
2766             else
2767                 ret = -1;
2768         }
2769         else if (!ret && (soap_package->which == Z_SOAP_fault
2770                           || soap_package->which == Z_SOAP_error))
2771         {
2772             set_HTTP_error(c, hres->code,
2773                            soap_package->u.fault->fault_code,
2774                            soap_package->u.fault->fault_string);
2775         }
2776         else
2777             ret = -1;
2778         odr_destroy(o);
2779     }
2780     if (ret)
2781     {
2782         if (hres->code != 200)
2783             set_HTTP_error(c, hres->code, 0, 0);
2784         else
2785             set_ZOOM_error(c, ZOOM_ERROR_DECODE, 0);
2786         do_close (c);
2787     }
2788     ZOOM_connection_remove_task(c);
2789     if (!strcmp(hres->version, "1.0"))
2790     {
2791         /* HTTP 1.0: only if Keep-Alive we stay alive.. */
2792         if (!connection_head || strcmp(connection_head, "Keep-Alive"))
2793             do_close(c);
2794     }
2795     else 
2796     {
2797         /* HTTP 1.1: only if no close we stay alive .. */
2798         if (connection_head && !strcmp(connection_head, "close"))
2799             do_close(c);
2800     }
2801 }
2802 #endif
2803
2804 static int do_read (ZOOM_connection c)
2805 {
2806     int r, more;
2807     ZOOM_Event event;
2808     
2809     event = ZOOM_Event_create (ZOOM_EVENT_RECV_DATA);
2810     ZOOM_connection_put_event (c, event);
2811     
2812
2813     r = cs_get (c->cs, &c->buf_in, &c->len_in);
2814     more = cs_more(c->cs);
2815     yaz_log (LOG_DEBUG, "do_read len=%d more=%d", r, more);
2816     if (r == 1)
2817         return 0;
2818     if (r <= 0)
2819     {
2820         if (c->reconnect_ok)
2821         {
2822             do_close (c);
2823             c->reconnect_ok = 0;
2824             yaz_log (LOG_DEBUG, "reconnect read");
2825             c->tasks->running = 0;
2826             ZOOM_connection_insert_task (c, ZOOM_TASK_CONNECT);
2827         }
2828         else
2829         {
2830             set_ZOOM_error(c, ZOOM_ERROR_CONNECTION_LOST, 0);
2831             do_close (c);
2832         }
2833     }
2834     else
2835     {
2836         Z_GDU *gdu;
2837         ZOOM_Event event;
2838
2839         odr_reset (c->odr_in);
2840         odr_setbuf (c->odr_in, c->buf_in, r, 0);
2841         event = ZOOM_Event_create (ZOOM_EVENT_RECV_APDU);
2842         ZOOM_connection_put_event (c, event);
2843
2844         if (!z_GDU (c->odr_in, &gdu, 0, 0))
2845         {
2846             set_ZOOM_error(c, ZOOM_ERROR_DECODE, 0);
2847             do_close (c);
2848         }
2849         else if (gdu->which == Z_GDU_Z3950)
2850             handle_apdu (c, gdu->u.z3950);
2851         else if (gdu->which == Z_GDU_HTTP_Response)
2852         {
2853 #if HAVE_XML2
2854             handle_http (c, gdu->u.HTTP_Response);
2855 #else
2856             set_ZOOM_error(c, ZOOM_ERROR_DECODE, 0);
2857             do_close (c);
2858 #endif
2859         }
2860         c->reconnect_ok = 0;
2861     }
2862     return 1;
2863 }
2864
2865 static zoom_ret do_write_ex (ZOOM_connection c, char *buf_out, int len_out)
2866 {
2867     int r;
2868     ZOOM_Event event;
2869     
2870     event = ZOOM_Event_create(ZOOM_EVENT_SEND_DATA);
2871     ZOOM_connection_put_event (c, event);
2872
2873     yaz_log (LOG_DEBUG, "do_write_ex len=%d", len_out);
2874     if ((r=cs_put (c->cs, buf_out, len_out)) < 0)
2875     {
2876         if (c->reconnect_ok)
2877         {
2878             do_close (c);
2879             c->reconnect_ok = 0;
2880             yaz_log (LOG_DEBUG, "reconnect write");
2881             c->tasks->running = 0;
2882             ZOOM_connection_insert_task (c, ZOOM_TASK_CONNECT);
2883             return zoom_pending;
2884         }
2885         if (c->state == STATE_CONNECTING)
2886             set_ZOOM_error(c, ZOOM_ERROR_CONNECT, 0);
2887         else
2888             set_ZOOM_error(c, ZOOM_ERROR_CONNECTION_LOST, 0);
2889         do_close (c);
2890         return zoom_complete;
2891     }
2892     else if (r == 1)
2893     {    
2894         c->mask = ZOOM_SELECT_EXCEPT;
2895         if (c->cs->io_pending & CS_WANT_WRITE)
2896             c->mask += ZOOM_SELECT_WRITE;
2897         if (c->cs->io_pending & CS_WANT_READ)
2898             c->mask += ZOOM_SELECT_READ;
2899         yaz_log (LOG_DEBUG, "do_write_ex 1 mask=%d", c->mask);
2900     }
2901     else
2902     {
2903         c->mask = ZOOM_SELECT_READ|ZOOM_SELECT_EXCEPT;
2904         yaz_log (LOG_DEBUG, "do_write_ex 2 mask=%d", c->mask);
2905     }
2906     return zoom_pending;
2907 }
2908
2909 static zoom_ret do_write(ZOOM_connection c)
2910 {
2911     return do_write_ex (c, c->buf_out, c->len_out);
2912 }
2913
2914
2915 ZOOM_API(const char *)
2916 ZOOM_connection_option_get (ZOOM_connection c, const char *key)
2917 {
2918     return ZOOM_options_get (c->options, key);
2919 }
2920
2921 ZOOM_API(void)
2922 ZOOM_connection_option_set (ZOOM_connection c, const char *key,
2923                                   const char *val)
2924 {
2925     ZOOM_options_set (c->options, key, val);
2926 }
2927
2928 ZOOM_API(const char *)
2929 ZOOM_resultset_option_get (ZOOM_resultset r, const char *key)
2930 {
2931     return ZOOM_options_get (r->options, key);
2932 }
2933
2934 ZOOM_API(void)
2935 ZOOM_resultset_option_set (ZOOM_resultset r, const char *key,
2936                                   const char *val)
2937 {
2938     ZOOM_options_set (r->options, key, val);
2939 }
2940
2941
2942 ZOOM_API(int)
2943 ZOOM_connection_errcode (ZOOM_connection c)
2944 {
2945     return ZOOM_connection_error (c, 0, 0);
2946 }
2947
2948 ZOOM_API(const char *)
2949 ZOOM_connection_errmsg (ZOOM_connection c)
2950 {
2951     const char *msg;
2952     ZOOM_connection_error (c, &msg, 0);
2953     return msg;
2954 }
2955
2956 ZOOM_API(const char *)
2957 ZOOM_connection_addinfo (ZOOM_connection c)
2958 {
2959     const char *addinfo;
2960     ZOOM_connection_error (c, 0, &addinfo);
2961     return addinfo;
2962 }
2963
2964 ZOOM_API(const char *)
2965 ZOOM_diag_str (int error)
2966 {
2967     switch (error)
2968     {
2969     case ZOOM_ERROR_NONE:
2970         return "No error";
2971     case ZOOM_ERROR_CONNECT:
2972         return "Connect failed";
2973     case ZOOM_ERROR_MEMORY:
2974         return "Out of memory";
2975     case ZOOM_ERROR_ENCODE:
2976         return "Encoding failed";
2977     case ZOOM_ERROR_DECODE:
2978         return "Decoding failed";
2979     case ZOOM_ERROR_CONNECTION_LOST:
2980         return "Connection lost";
2981     case ZOOM_ERROR_INIT:
2982         return "Init rejected";
2983     case ZOOM_ERROR_INTERNAL:
2984         return "Internal failure";
2985     case ZOOM_ERROR_TIMEOUT:
2986         return "Timeout";
2987     case ZOOM_ERROR_UNSUPPORTED_PROTOCOL:
2988         return "Unsupported protocol";
2989     case ZOOM_ERROR_UNSUPPORTED_QUERY:
2990         return "Unsupported query type";
2991     default:
2992         return diagbib1_str (error);
2993     }
2994 }
2995
2996 ZOOM_API(int)
2997 ZOOM_connection_error_x (ZOOM_connection c, const char **cp,
2998                          const char **addinfo, const char **diagset)
2999 {
3000     int error = c->error;
3001     if (cp)
3002     {
3003         if (!c->diagset || !strcmp(c->diagset, "ZOOM"))
3004             *cp = ZOOM_diag_str(error);
3005         else if (!strcmp(c->diagset, "HTTP"))
3006             *cp = z_HTTP_errmsg(c->error);
3007         else if (!strcmp(c->diagset, "Bib-1"))
3008             *cp = ZOOM_diag_str(error);
3009         else if (!strcmp(c->diagset, "SRW"))
3010             *cp = yaz_diag_srw_str(c->error);
3011         else
3012             *cp = "Unknown error and diagnostic set";
3013     }
3014     if (addinfo)
3015         *addinfo = c->addinfo ? c->addinfo : "";
3016     if (diagset)
3017         *diagset = c->diagset ? c->diagset : "";
3018     return c->error;
3019 }
3020
3021 ZOOM_API(int)
3022 ZOOM_connection_error (ZOOM_connection c, const char **cp,
3023                        const char **addinfo)
3024 {
3025     return ZOOM_connection_error_x(c, cp, addinfo, 0);
3026 }
3027
3028 static int ZOOM_connection_do_io(ZOOM_connection c, int mask)
3029 {
3030     ZOOM_Event event = 0;
3031     int r = cs_look(c->cs);
3032     yaz_log (LOG_DEBUG, "ZOOM_connection_do_io c=%p mask=%d cs_look=%d",
3033              c, mask, r);
3034     
3035     if (r == CS_NONE)
3036     {
3037         event = ZOOM_Event_create (ZOOM_EVENT_CONNECT);
3038         set_ZOOM_error(c, ZOOM_ERROR_CONNECT, 0);
3039         do_close (c);
3040         ZOOM_connection_put_event (c, event);
3041     }
3042     else if (r == CS_CONNECT)
3043     {
3044         int ret;
3045         event = ZOOM_Event_create (ZOOM_EVENT_CONNECT);
3046
3047         ret = cs_rcvconnect (c->cs);
3048         yaz_log (LOG_DEBUG, "cs_rcvconnect returned %d", ret);
3049         if (ret == 1)
3050         {
3051             c->mask = ZOOM_SELECT_EXCEPT;
3052             if (c->cs->io_pending & CS_WANT_WRITE)
3053                 c->mask += ZOOM_SELECT_WRITE;
3054             if (c->cs->io_pending & CS_WANT_READ)
3055                 c->mask += ZOOM_SELECT_READ;
3056             ZOOM_connection_put_event (c, event);
3057         }
3058         else if (ret == 0)
3059         {
3060             ZOOM_connection_put_event (c, event);
3061             if (c->proto == PROTO_Z3950)
3062                 ZOOM_connection_send_init(c);
3063             else
3064             {
3065                 /* no init request for SRW .. */
3066                 assert (c->tasks->which == ZOOM_TASK_CONNECT);
3067                 ZOOM_connection_remove_task (c);
3068                 c->mask = 0;
3069                 ZOOM_connection_exec_task (c);
3070             }
3071             c->state = STATE_ESTABLISHED;
3072         }
3073         else
3074         {
3075             set_ZOOM_error(c, ZOOM_ERROR_CONNECT, 0);
3076             do_close (c);
3077             ZOOM_connection_put_event (c, event);
3078         }
3079     }
3080     else
3081     {
3082         if (mask & ZOOM_SELECT_READ)
3083             do_read (c);
3084         if (c->cs && (mask & ZOOM_SELECT_WRITE))
3085             do_write (c);
3086     }
3087     return 1;
3088 }
3089
3090 ZOOM_API(int)
3091 ZOOM_connection_last_event(ZOOM_connection cs)
3092 {
3093     if (!cs)
3094         return ZOOM_EVENT_NONE;
3095     return cs->last_event;
3096 }
3097
3098 ZOOM_API(int)
3099 ZOOM_event (int no, ZOOM_connection *cs)
3100 {
3101     int timeout = 5000;
3102 #if HAVE_SYS_POLL_H
3103     struct pollfd pollfds[1024];
3104     ZOOM_connection poll_cs[1024];
3105 #else
3106     struct timeval tv;
3107     fd_set input, output, except;
3108 #endif
3109     int i, r, nfds;
3110     int max_fd = 0;
3111
3112     for (i = 0; i<no; i++)
3113     {
3114         ZOOM_connection c = cs[i];
3115         ZOOM_Event event;
3116         if (c && (event = ZOOM_connection_get_event(c)))
3117         {
3118             ZOOM_Event_destroy (event);
3119             return i+1;
3120         }
3121     }
3122     for (i = 0; i<no; i++)
3123     {
3124         ZOOM_connection c = cs[i];
3125         ZOOM_Event event;
3126         if (c && ZOOM_connection_exec_task (c))
3127         {
3128             if ((event = ZOOM_connection_get_event(c)))
3129             {
3130                 ZOOM_Event_destroy (event);
3131                 return i+1;
3132             }
3133         }
3134     }
3135 #if HAVE_SYS_POLL_H
3136
3137 #else
3138     FD_ZERO (&input);
3139     FD_ZERO (&output);
3140     FD_ZERO (&except);
3141 #endif
3142     nfds = 0;
3143     for (i = 0; i<no; i++)
3144     {
3145         ZOOM_connection c = cs[i];
3146         int fd, mask;
3147         int this_timeout;
3148         
3149         if (!c)
3150             continue;
3151         fd = z3950_connection_socket(c);
3152         mask = z3950_connection_mask(c);
3153
3154         if (fd == -1)
3155             continue;
3156         if (max_fd < fd)
3157             max_fd = fd;
3158
3159         this_timeout = ZOOM_options_get_int (c->options, "timeout", -1);
3160         if (this_timeout != -1 && this_timeout < timeout)
3161             timeout = this_timeout;
3162 #if HAVE_SYS_POLL_H
3163         if (mask)
3164         {
3165             short poll_events = 0;
3166
3167             if (mask & ZOOM_SELECT_READ)
3168                 poll_events += POLLIN;
3169             if (mask & ZOOM_SELECT_WRITE)
3170                 poll_events += POLLOUT;
3171             if (mask & ZOOM_SELECT_EXCEPT)
3172                 poll_events += POLLERR;
3173             pollfds[nfds].fd = fd;
3174             pollfds[nfds].events = poll_events;
3175             pollfds[nfds].revents = 0;
3176             poll_cs[nfds] = c;
3177             nfds++;
3178         }
3179 #else
3180         if (mask & ZOOM_SELECT_READ)
3181         {
3182             FD_SET (fd, &input);
3183             nfds++;
3184         }
3185         if (mask & ZOOM_SELECT_WRITE)
3186         {
3187             FD_SET (fd, &output);
3188             nfds++;
3189         }
3190         if (mask & ZOOM_SELECT_EXCEPT)
3191         {
3192             FD_SET (fd, &except);
3193             nfds++;
3194         }
3195 #endif
3196     }
3197     if (timeout >= 5000)
3198         timeout = 30;
3199
3200     if (!nfds)
3201         return 0;
3202
3203 #if HAVE_SYS_POLL_H
3204     r = poll (pollfds, nfds, timeout * 1000);
3205     for (i = 0; i<nfds; i++)
3206     {
3207         ZOOM_connection c = poll_cs[i];
3208         if (r && c->mask)
3209         {
3210             int mask = 0;
3211             if (pollfds[i].revents & POLLIN)
3212                 mask += ZOOM_SELECT_READ;
3213             if (pollfds[i].revents & POLLOUT)
3214                 mask += ZOOM_SELECT_WRITE;
3215             if (pollfds[i].revents & POLLERR)
3216                 mask += ZOOM_SELECT_EXCEPT;
3217             if (mask)
3218                 ZOOM_connection_do_io(c, mask);
3219         }
3220         else if (r == 0 && c->mask)
3221         {
3222             ZOOM_Event event = ZOOM_Event_create(ZOOM_EVENT_TIMEOUT);
3223             /* timeout and this connection was waiting */
3224             set_ZOOM_error(c, ZOOM_ERROR_TIMEOUT, 0);
3225             do_close (c);
3226             ZOOM_connection_put_event(c, event);
3227         }
3228     }
3229 #else
3230     tv.tv_sec = timeout;
3231     tv.tv_usec = 0;
3232     yaz_log (LOG_DEBUG, "select start");
3233     r = select (max_fd+1, &input, &output, &except, &tv);
3234     yaz_log (LOG_DEBUG, "select stop, returned r=%d", r);
3235     for (i = 0; i<no; i++)
3236     {
3237         ZOOM_connection c = cs[i];
3238         int fd, mask;
3239
3240         if (!c)
3241             continue;
3242         fd = z3950_connection_socket(c);
3243         mask = 0;
3244         if (r && c->mask)
3245         {
3246             /* no timeout and real socket */
3247             if (FD_ISSET(fd, &input))
3248                 mask += ZOOM_SELECT_READ;
3249             if (FD_ISSET(fd, &output))
3250                 mask += ZOOM_SELECT_WRITE;
3251             if (FD_ISSET(fd, &except))
3252                 mask += ZOOM_SELECT_EXCEPT;
3253             if (mask)
3254                 ZOOM_connection_do_io(c, mask);
3255         }
3256         if (r == 0 && c->mask)
3257         {
3258             ZOOM_Event event = ZOOM_Event_create(ZOOM_EVENT_TIMEOUT);
3259             /* timeout and this connection was waiting */
3260             set_ZOOM_error(c, ZOOM_ERROR_TIMEOUT, 0);
3261             do_close (c);
3262             yaz_log (LOG_DEBUG, "timeout");
3263             ZOOM_connection_put_event(c, event);
3264         }
3265     }
3266 #endif
3267     for (i = 0; i<no; i++)
3268     {
3269         ZOOM_connection c = cs[i];
3270         ZOOM_Event event;
3271         if (c && (event = ZOOM_connection_get_event(c)))
3272         {
3273             ZOOM_Event_destroy (event);
3274             return i+1;
3275         }
3276     }
3277     return 0;
3278 }