Use 'Host' rather than 'host' for HTTP header
[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.2 2003-11-02 17:58:16 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 *impname;
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     impname = ZOOM_options_get (c->options, "implementationName");
908     ireq->implementationName =
909         (char *) odr_malloc (c->odr_out, 15 + (impname ? strlen(impname) : 0));
910     strcpy (ireq->implementationName, "");
911     if (impname)
912     {
913         strcat (ireq->implementationName, impname);
914         strcat (ireq->implementationName, "/");
915     }                                          
916     strcat (ireq->implementationName, "ZOOM-C/YAZ");
917     
918     *ireq->maximumRecordSize =
919         ZOOM_options_get_int (c->options, "maximumRecordSize", 1024*1024);
920     *ireq->preferredMessageSize =
921         ZOOM_options_get_int (c->options, "preferredMessageSize", 1024*1024);
922     
923     if (auth_groupId || auth_password)
924     {
925         Z_IdPass *pass = (Z_IdPass *) odr_malloc(c->odr_out, sizeof(*pass));
926         int i = 0;
927         pass->groupId = 0;
928         if (auth_groupId && *auth_groupId)
929         {
930             pass->groupId = (char *)
931                 odr_malloc(c->odr_out, strlen(auth_groupId)+1);
932             strcpy(pass->groupId, auth_groupId);
933             i++;
934         }
935         pass->userId = 0;
936         if (auth_userId && *auth_userId)
937         {
938             pass->userId = (char *)
939                 odr_malloc(c->odr_out, strlen(auth_userId)+1);
940             strcpy(pass->userId, auth_userId);
941             i++;
942         }
943         pass->password = 0;
944         if (auth_password && *auth_password)
945         {
946             pass->password = (char *)
947                 odr_malloc(c->odr_out, strlen(auth_password)+1);
948             strcpy(pass->password, auth_password);
949             i++;
950         }
951         if (i)
952         {
953             auth->which = Z_IdAuthentication_idPass;
954             auth->u.idPass = pass;
955             ireq->idAuthentication = auth;
956         }
957     }
958     else if (auth_userId)
959     {
960         auth->which = Z_IdAuthentication_open;
961         auth->u.open = (char *)
962             odr_malloc(c->odr_out, strlen(auth_userId)+1);
963         strcpy(auth->u.open, auth_userId);
964         ireq->idAuthentication = auth;
965     }
966     if (c->proxy)
967         yaz_oi_set_string_oidval(&ireq->otherInfo, c->odr_out,
968                                  VAL_PROXY, 1, c->host_port);
969     if (c->charset||c->lang)
970     {
971         Z_OtherInformation **oi;
972         Z_OtherInformationUnit *oi_unit;
973         
974         yaz_oi_APDU(apdu, &oi);
975         
976         if ((oi_unit = yaz_oi_update(oi, c->odr_out, NULL, 0, 0)))
977         {
978             ODR_MASK_SET(ireq->options, Z_Options_negotiationModel);
979             
980             oi_unit->which = Z_OtherInfo_externallyDefinedInfo;
981             oi_unit->information.externallyDefinedInfo =
982                 yaz_set_proposal_charneg
983                 (c->odr_out,
984                  (const char **)&c->charset, (c->charset) ? 1:0,
985                  (const char **)&c->lang, (c->lang) ? 1:0, 1);
986         }
987     }
988     assert (apdu);
989     return send_APDU (c, apdu);
990 }
991
992 #if HAVE_XML2
993 static zoom_ret send_srw (ZOOM_connection c, Z_SRW_PDU *sr)
994 {
995     char ctype[50];
996     Z_SOAP_Handler h[2] = {
997         {"http://www.loc.gov/zing/srw/v1.0/", 0, (Z_SOAP_fun) yaz_srw_codec},
998         {0, 0, 0}
999     };
1000     ODR o = odr_createmem(ODR_ENCODE);
1001     int ret;
1002     Z_SOAP *p = odr_malloc(o, sizeof(*p));
1003     Z_GDU *gdu;
1004     ZOOM_Event event;
1005
1006     gdu = z_get_HTTP_Request(c->odr_out);
1007     gdu->u.HTTP_Request->path = c->path;
1008
1009     if (c->host_port)
1010     {
1011         const char *cp0 = strstr(c->host_port, "://");
1012         const char *cp1 = 0;
1013         if (cp0)
1014             cp0 = cp0+3;
1015         else
1016             cp0 = c->host_port;
1017
1018         cp1 = strchr(cp0, '/');
1019         if (!cp1)
1020             cp1 = cp0+strlen(cp0);
1021
1022         if (cp0 && cp1)
1023         {
1024             char *h = odr_malloc(c->odr_out, cp1 - cp0 + 1);
1025             memcpy (h, cp0, cp1 - cp0);
1026             h[cp1-cp0] = '\0';
1027             z_HTTP_header_add(c->odr_out, &gdu->u.HTTP_Request->headers,
1028                               "Host", h);
1029         }
1030     }
1031
1032     strcpy(ctype, "text/xml");
1033     if (c->charset && strlen(c->charset) < 20)
1034     {
1035         strcat(ctype, "; charset=");
1036         strcat(ctype, c->charset);
1037     }
1038     z_HTTP_header_add(c->odr_out, &gdu->u.HTTP_Request->headers,
1039                       "Content-Type", ctype);
1040     z_HTTP_header_add(c->odr_out, &gdu->u.HTTP_Request->headers,
1041                       "SOAPAction", "\"\"");
1042     p->which = Z_SOAP_generic;
1043     p->u.generic = odr_malloc(o, sizeof(*p->u.generic));
1044     p->u.generic->no = 0;
1045     p->u.generic->ns = 0;
1046     p->u.generic->p = sr;
1047     p->ns = "http://schemas.xmlsoap.org/soap/envelope/";
1048
1049     ret = z_soap_codec_enc(o, &p,
1050                            &gdu->u.HTTP_Request->content_buf,
1051                            &gdu->u.HTTP_Request->content_len, h,
1052                            c->charset);
1053
1054     if (!z_GDU(c->odr_out, &gdu, 0, 0))
1055         return zoom_complete;
1056     c->buf_out = odr_getbuf(c->odr_out, &c->len_out, 0);
1057
1058     odr_destroy(o);
1059
1060     event = ZOOM_Event_create (ZOOM_EVENT_SEND_APDU);
1061     ZOOM_connection_put_event (c, event);
1062     odr_reset(c->odr_out);
1063     return do_write (c);
1064 }
1065 #endif
1066
1067 #if HAVE_XML2
1068 static zoom_ret ZOOM_connection_srw_send_search(ZOOM_connection c)
1069 {
1070     int i;
1071     ZOOM_resultset resultset = 0;
1072     Z_SRW_PDU *sr = 0;
1073     const char *recordPacking = 0;
1074
1075     if (c->error)                  /* don't continue on error */
1076         return zoom_complete;
1077     assert (c->tasks);
1078     if (c->tasks->which == ZOOM_TASK_SEARCH)
1079     {
1080         resultset = c->tasks->u.search.resultset;
1081         resultset->setname = xstrdup ("default");
1082         ZOOM_options_set (resultset->options, "setname", resultset->setname);
1083     }
1084     else if(c->tasks->which == ZOOM_TASK_RETRIEVE)
1085     {
1086         resultset = c->tasks->u.retrieve.resultset;
1087
1088         resultset->start = c->tasks->u.retrieve.start;
1089         resultset->count = c->tasks->u.retrieve.count;
1090         
1091         if (resultset->start >= resultset->size)
1092             return zoom_complete;
1093         if (resultset->start + resultset->count > resultset->size)
1094             resultset->count = resultset->size - resultset->start;
1095
1096         for (i = 0; i<resultset->count; i++)
1097         {
1098             ZOOM_record rec =
1099                 record_cache_lookup (resultset, i + resultset->start);
1100             if (!rec)
1101                 break;
1102         }
1103         if (i == resultset->count)
1104             return zoom_complete;
1105     }
1106     assert(resultset->query);
1107         
1108     sr = yaz_srw_get(c->odr_out, Z_SRW_searchRetrieve_request);
1109
1110     if (resultset->query->z_query->which == Z_Query_type_104
1111         && resultset->query->z_query->u.type_104->which == Z_External_CQL)
1112     {
1113
1114         sr->u.request->query_type = Z_SRW_query_type_cql;
1115         sr->u.request->query.cql =resultset->query->z_query->u.type_104->u.cql;
1116     }
1117     else if (resultset->query->z_query->which == Z_Query_type_1 &&
1118              resultset->query->z_query->u.type_1)
1119     {
1120         sr->u.request->query_type = Z_SRW_query_type_pqf;
1121         sr->u.request->query.pqf = resultset->query->query_string;
1122     }
1123     else
1124     {
1125         set_ZOOM_error(c, ZOOM_ERROR_UNSUPPORTED_QUERY, 0);
1126         return zoom_complete;
1127     }
1128     sr->u.request->startRecord = odr_intdup (c->odr_out, resultset->start + 1);
1129     sr->u.request->maximumRecords = odr_intdup (
1130         c->odr_out, resultset->step>0 ? resultset->step : resultset->count);
1131     sr->u.request->recordSchema = resultset->schema;
1132
1133     recordPacking = ZOOM_resultset_option_get (resultset, "recordPacking");
1134
1135     if (recordPacking)
1136         sr->u.request->recordPacking = odr_strdup(c->odr_out, recordPacking);
1137     
1138     return send_srw(c, sr);
1139 }
1140 #else
1141 static zoom_ret ZOOM_connection_srw_send_search(ZOOM_connection c)
1142 {
1143     return zoom_complete;
1144 }
1145 #endif
1146
1147 static zoom_ret ZOOM_connection_send_search (ZOOM_connection c)
1148 {
1149     ZOOM_resultset r;
1150     int lslb, ssub, mspn;
1151     const char *syntax;
1152     Z_APDU *apdu = zget_APDU(c->odr_out, Z_APDU_searchRequest);
1153     Z_SearchRequest *search_req = apdu->u.searchRequest;
1154     const char *elementSetName;
1155     const char *smallSetElementSetName;
1156     const char *mediumSetElementSetName;
1157
1158     assert (c->tasks);
1159     assert (c->tasks->which == ZOOM_TASK_SEARCH);
1160
1161     r = c->tasks->u.search.resultset;
1162
1163     elementSetName =
1164         ZOOM_options_get (r->options, "elementSetName");
1165     smallSetElementSetName  =
1166         ZOOM_options_get (r->options, "smallSetElementSetName");
1167     mediumSetElementSetName =
1168         ZOOM_options_get (r->options, "mediumSetElementSetName");
1169
1170     if (!smallSetElementSetName)
1171         smallSetElementSetName = elementSetName;
1172
1173     if (!mediumSetElementSetName)
1174         mediumSetElementSetName = elementSetName;
1175
1176     assert (r);
1177     assert (r->query);
1178
1179     /* prepare query for the search request */
1180     search_req->query = r->query->z_query;
1181
1182     search_req->databaseNames =
1183         set_DatabaseNames (c, r->options, &search_req->num_databaseNames);
1184
1185     /* get syntax (no need to provide unless piggyback is in effect) */
1186     syntax = ZOOM_options_get (r->options, "preferredRecordSyntax");
1187
1188     lslb = ZOOM_options_get_int (r->options, "largeSetLowerBound", -1);
1189     ssub = ZOOM_options_get_int (r->options, "smallSetUpperBound", -1);
1190     mspn = ZOOM_options_get_int (r->options, "mediumSetPresentNumber", -1);
1191     if (lslb != -1 && ssub != -1 && mspn != -1)
1192     {
1193         /* So're a Z39.50 expert? Let's hope you don't do sort */
1194         *search_req->largeSetLowerBound = lslb;
1195         *search_req->smallSetUpperBound = ssub;
1196         *search_req->mediumSetPresentNumber = mspn;
1197     }
1198     else if (r->start == 0 && r->count > 0
1199              && r->piggyback && !r->r_sort_spec && !r->schema)
1200     {
1201         /* Regular piggyback - do it unless we're going to do sort */
1202         *search_req->largeSetLowerBound = 2000000000;
1203         *search_req->smallSetUpperBound = 1;
1204         *search_req->mediumSetPresentNumber = r->step>0 ? r->step : r->count;
1205     }
1206     else
1207     {
1208         /* non-piggyback. Need not provide elementsets or syntaxes .. */
1209         smallSetElementSetName = 0;
1210         mediumSetElementSetName = 0;
1211         syntax = 0;
1212     }
1213     if (smallSetElementSetName && *smallSetElementSetName)
1214     {
1215         Z_ElementSetNames *esn = (Z_ElementSetNames *)
1216             odr_malloc (c->odr_out, sizeof(*esn));
1217         
1218         esn->which = Z_ElementSetNames_generic;
1219         esn->u.generic = odr_strdup (c->odr_out, smallSetElementSetName);
1220         search_req->smallSetElementSetNames = esn;
1221     }
1222     if (mediumSetElementSetName && *mediumSetElementSetName)
1223     {
1224         Z_ElementSetNames *esn = (Z_ElementSetNames *)
1225             odr_malloc (c->odr_out, sizeof(*esn));
1226         
1227         esn->which = Z_ElementSetNames_generic;
1228         esn->u.generic = odr_strdup (c->odr_out, mediumSetElementSetName);
1229         search_req->mediumSetElementSetNames = esn;
1230     }
1231     if (syntax)
1232         search_req->preferredRecordSyntax =
1233             yaz_str_to_z3950oid (c->odr_out, CLASS_RECSYN, syntax);
1234     
1235     if (!r->setname)
1236     {
1237         if (c->support_named_resultsets)
1238         {
1239             char setname[14];
1240             int ord;
1241             /* find the lowest unused ordinal so that we re-use
1242                result sets on the server. */
1243             for (ord = 1; ; ord++)
1244             {
1245                 ZOOM_resultset rp;
1246                 sprintf (setname, "%d", ord);
1247                 for (rp = c->resultsets; rp; rp = rp->next)
1248                     if (rp->setname && !strcmp (rp->setname, setname))
1249                         break;
1250                 if (!rp)
1251                     break;
1252             }
1253             r->setname = xstrdup (setname);
1254             yaz_log (LOG_DEBUG, "allocating set %s", r->setname);
1255         }
1256         else
1257             r->setname = xstrdup ("default");
1258         ZOOM_options_set (r->options, "setname", r->setname);
1259     }
1260     search_req->resultSetName = odr_strdup(c->odr_out, r->setname);
1261     /* send search request */
1262     return send_APDU (c, apdu);
1263 }
1264
1265 static void response_diag (ZOOM_connection c, Z_DiagRec *p)
1266 {
1267     int oclass;
1268     Z_DefaultDiagFormat *r;
1269     char *addinfo = 0;
1270     
1271     xfree (c->addinfo);
1272     c->addinfo = 0;
1273     if (p->which != Z_DiagRec_defaultFormat)
1274     {
1275         set_ZOOM_error(c, ZOOM_ERROR_DECODE, 0);
1276         return;
1277     }
1278     r = p->u.defaultFormat;
1279     switch (r->which)
1280     {
1281     case Z_DefaultDiagFormat_v2Addinfo:
1282         addinfo = r->u.v2Addinfo;
1283         break;
1284     case Z_DefaultDiagFormat_v3Addinfo:
1285         addinfo = r->u.v3Addinfo;
1286         break;
1287     }
1288     set_dset_error(c, *r->condition,
1289                    yaz_z3950oid_to_str(r->diagnosticSetId, &oclass),
1290                    addinfo, 0);
1291 }
1292
1293 ZOOM_API(ZOOM_record)
1294 ZOOM_record_clone (ZOOM_record srec)
1295 {
1296     char *buf;
1297     int size;
1298     ODR odr_enc;
1299     ZOOM_record nrec;
1300
1301     odr_enc = odr_createmem(ODR_ENCODE);
1302     if (!z_NamePlusRecord (odr_enc, &srec->npr, 0, 0))
1303         return 0;
1304     buf = odr_getbuf (odr_enc, &size, 0);
1305     
1306     nrec = (ZOOM_record) xmalloc (sizeof(*nrec));
1307     nrec->odr = odr_createmem(ODR_DECODE);
1308     nrec->wrbuf_marc = 0;
1309     nrec->wrbuf_iconv = 0;
1310     nrec->wrbuf_opac = 0;
1311     odr_setbuf (nrec->odr, buf, size, 0);
1312     z_NamePlusRecord (nrec->odr, &nrec->npr, 0, 0);
1313     
1314     odr_destroy (odr_enc);
1315     return nrec;
1316 }
1317
1318 ZOOM_API(ZOOM_record)
1319 ZOOM_resultset_record_immediate (ZOOM_resultset s,size_t pos)
1320 {
1321     return record_cache_lookup (s, pos);
1322 }
1323
1324 ZOOM_API(ZOOM_record)
1325 ZOOM_resultset_record (ZOOM_resultset r, size_t pos)
1326 {
1327     ZOOM_record rec = ZOOM_resultset_record_immediate(r, pos);
1328
1329     if (!rec)
1330     {
1331         ZOOM_resultset_retrieve (r, 1, pos, 1);
1332         rec = ZOOM_resultset_record_immediate (r, pos);
1333     }
1334     return rec;
1335 }
1336
1337 ZOOM_API(void)
1338 ZOOM_record_destroy (ZOOM_record rec)
1339 {
1340     if (!rec)
1341         return;
1342     if (rec->wrbuf_marc)
1343         wrbuf_free (rec->wrbuf_marc, 1);
1344     if (rec->wrbuf_iconv)
1345         wrbuf_free (rec->wrbuf_iconv, 1);
1346     if (rec->wrbuf_opac)
1347         wrbuf_free (rec->wrbuf_opac, 1);
1348     odr_destroy (rec->odr);
1349     xfree (rec);
1350 }
1351
1352 static const char *record_iconv_return(ZOOM_record rec, int *len,
1353                                        const char *buf, int sz,
1354                                        const char *record_charset)
1355 {
1356     char to[40];
1357     char from[40];
1358     yaz_iconv_t cd = 0;
1359
1360     *from = '\0';
1361     strcpy(to, "UTF-8");
1362     if (record_charset && *record_charset)
1363     {
1364         /* Use "from,to" or just "from" */
1365         const char *cp =strchr(record_charset, ',');
1366         int clen = strlen(record_charset);
1367         if (cp && cp[1])
1368         {
1369             strncpy( to, cp+1, sizeof(to)-1);
1370             to[sizeof(to)-1] = '\0';
1371             clen = cp - record_charset;
1372         }
1373         if (clen > sizeof(from)-1)
1374             clen = sizeof(from)-1;
1375         
1376         if (clen)
1377             strncpy(from, record_charset, clen);
1378         from[clen] = '\0';
1379     }
1380
1381     if (*from && *to && (cd = yaz_iconv_open(to, from)))
1382     {
1383         char outbuf[12];
1384         size_t inbytesleft = sz;
1385         const char *inp = buf;
1386         
1387         if (!rec->wrbuf_iconv)
1388             rec->wrbuf_iconv = wrbuf_alloc();
1389
1390         wrbuf_rewind(rec->wrbuf_iconv);
1391
1392         while (inbytesleft)
1393         {
1394             size_t outbytesleft = sizeof(outbuf);
1395             char *outp = outbuf;
1396             size_t r = yaz_iconv (cd, (char**) &inp,
1397                                   &inbytesleft, 
1398                                   &outp, &outbytesleft);
1399             if (r == (size_t) (-1))
1400             {
1401                 int e = yaz_iconv_error(cd);
1402                 if (e != YAZ_ICONV_E2BIG)
1403                     break;
1404             }
1405             wrbuf_write(rec->wrbuf_iconv, outbuf, outp - outbuf);
1406         }
1407         wrbuf_puts(rec->wrbuf_iconv, "");
1408         buf = wrbuf_buf(rec->wrbuf_iconv);
1409         sz = wrbuf_len(rec->wrbuf_iconv);
1410         yaz_iconv_close(cd);
1411     }
1412     if (len)
1413         *len = sz;
1414     return buf;
1415 }
1416
1417 ZOOM_API(const char *)
1418 ZOOM_record_get (ZOOM_record rec, const char *type_spec, int *len)
1419 {
1420     char type[40];
1421     char charset[40];
1422     const char *cp;
1423     int i;
1424     Z_NamePlusRecord *npr;
1425     
1426     if (len)
1427         *len = 0; /* default return */
1428         
1429     if (!rec)
1430         return 0;
1431     npr = rec->npr;
1432     if (!npr)
1433         return 0;
1434
1435     cp = type_spec;
1436     for (i = 0; cp[i] && i < sizeof(type)-1; i++)
1437     {
1438         if (cp[i] == ';' || cp[i] == ' ')
1439             break;
1440         type[i] = cp[i];
1441     }
1442     type[i] = '\0';
1443     charset[0] = '\0';
1444     if (type_spec[i] == ';')
1445     {
1446         i++;
1447         while (type_spec[i] == ' ')
1448             i++;
1449         if (!strncmp(type_spec+i, "charset=", 8))
1450         {
1451             cp = type_spec+i+8;
1452             for (i = 0; cp[i] && i < sizeof(charset)-1; i++)
1453             {
1454                 if (cp[i] == ';' || cp[i] == ' ')
1455                     break;
1456                 charset[i] = cp[i];
1457             }
1458             charset[i] = '\0';
1459         }
1460     }   
1461
1462     if (!strcmp (type, "database"))
1463     {
1464         if (len)
1465             *len = (npr->databaseName ? strlen(npr->databaseName) : 0);
1466         return npr->databaseName;
1467     }
1468     else if (!strcmp (type, "syntax"))
1469     {
1470         const char *desc = 0;   
1471         if (npr->which == Z_NamePlusRecord_databaseRecord)
1472         {
1473             Z_External *r = (Z_External *) npr->u.databaseRecord;
1474             oident *ent = oid_getentbyoid(r->direct_reference);
1475             if (ent)
1476                 desc = ent->desc;
1477         }
1478         if (!desc)
1479             desc = "none";
1480         if (len)
1481             *len = strlen(desc);
1482         return desc;
1483     }
1484     else if (!strcmp (type, "render") && 
1485              npr->which == Z_NamePlusRecord_databaseRecord)
1486     {
1487         Z_External *r = (Z_External *) npr->u.databaseRecord;
1488         oident *ent = oid_getentbyoid(r->direct_reference);
1489
1490         /* render bibliographic record .. */
1491         if (r->which == Z_External_OPAC)
1492         {
1493             r = r->u.opac->bibliographicRecord;
1494             if (!r)
1495                 return 0;
1496             ent = oid_getentbyoid(r->direct_reference);
1497         }
1498         if (r->which == Z_External_sutrs)
1499             return record_iconv_return(rec, len,
1500                                        r->u.sutrs->buf, r->u.sutrs->len,
1501                                        charset);
1502         else if (r->which == Z_External_octet)
1503         {
1504             yaz_marc_t mt;
1505             switch (ent->value)
1506             {
1507             case VAL_SOIF:
1508             case VAL_HTML:
1509             case VAL_SUTRS:
1510                 break;
1511             case VAL_TEXT_XML:
1512             case VAL_APPLICATION_XML:
1513                 break;
1514             default:
1515                 if (!rec->wrbuf_marc)
1516                     rec->wrbuf_marc = wrbuf_alloc();
1517
1518                 mt = yaz_marc_create();
1519                 wrbuf_rewind (rec->wrbuf_marc);
1520                 if (yaz_marc_decode_wrbuf (
1521                         mt, (const char *) r->u.octet_aligned->buf,
1522                         r->u.octet_aligned->len,
1523                         rec->wrbuf_marc) > 0)
1524                 {
1525                     yaz_marc_destroy(mt);
1526                     return record_iconv_return(rec, len,
1527                                                wrbuf_buf(rec->wrbuf_marc),
1528                                                wrbuf_len(rec->wrbuf_marc),
1529                                                charset);
1530                 }
1531                 yaz_marc_destroy(mt);
1532             }
1533             return record_iconv_return(rec, len,
1534                                        (const char *) r->u.octet_aligned->buf,
1535                                        r->u.octet_aligned->len,
1536                                        charset);
1537         }
1538         else if (r->which == Z_External_grs1)
1539         {
1540             if (!rec->wrbuf_marc)
1541                 rec->wrbuf_marc = wrbuf_alloc();
1542             wrbuf_rewind (rec->wrbuf_marc);
1543             yaz_display_grs1(rec->wrbuf_marc, r->u.grs1, 0);
1544             return record_iconv_return(rec, len,
1545                                        wrbuf_buf(rec->wrbuf_marc),
1546                                        wrbuf_len(rec->wrbuf_marc),
1547                                        charset);
1548         }
1549         return 0;
1550     }
1551     else if (npr->which == Z_NamePlusRecord_databaseRecord &&
1552              (!strcmp (type, "xml") || !strcmp(type, "oai")))
1553     {
1554         Z_External *r = (Z_External *) npr->u.databaseRecord;
1555         oident *ent = oid_getentbyoid(r->direct_reference);
1556
1557         /* render bibliographic record .. */
1558         if (r->which == Z_External_OPAC)
1559         {
1560             r = r->u.opac->bibliographicRecord;
1561             if (!r)
1562                 return 0;
1563             ent = oid_getentbyoid(r->direct_reference);
1564         }
1565         
1566         if (r->which == Z_External_sutrs)
1567             return record_iconv_return(rec, len,
1568                                        (const char *) r->u.sutrs->buf,
1569                                        r->u.sutrs->len,
1570                                        charset);
1571         else if (r->which == Z_External_octet)
1572         {
1573             yaz_marc_t mt;
1574             int marc_decode_type = YAZ_MARC_MARCXML;
1575
1576             if (!strcmp(type, "oai"))
1577                 marc_decode_type = YAZ_MARC_OAIMARC;
1578             switch (ent->value)
1579             {
1580             case VAL_SOIF:
1581             case VAL_HTML:
1582             case VAL_SUTRS:
1583                 break;
1584             case VAL_TEXT_XML:
1585             case VAL_APPLICATION_XML:
1586                 break;
1587             default:
1588                 if (!rec->wrbuf_marc)
1589                     rec->wrbuf_marc = wrbuf_alloc();
1590                 wrbuf_rewind (rec->wrbuf_marc);
1591                 mt = yaz_marc_create();
1592
1593                 yaz_marc_xml(mt, YAZ_MARC_MARCXML);
1594                 if (yaz_marc_decode_wrbuf (
1595                         mt, (const char *) r->u.octet_aligned->buf,
1596                         r->u.octet_aligned->len,
1597                         rec->wrbuf_marc) > 0)
1598                 {
1599                     yaz_marc_destroy(mt);
1600                     return record_iconv_return(rec, len,
1601                                                wrbuf_buf(rec->wrbuf_marc),
1602                                                wrbuf_len(rec->wrbuf_marc),
1603                                                charset);
1604                 }
1605                 yaz_marc_destroy(mt);
1606             }
1607             return record_iconv_return(rec, len,
1608                                        (const char *) r->u.octet_aligned->buf,
1609                                        r->u.octet_aligned->len,
1610                                        charset);
1611         }
1612         else if (r->which == Z_External_grs1)
1613         {
1614             if (len) *len = 5;
1615             return "GRS-1";
1616         }
1617         return 0;
1618     }
1619     else if (!strcmp (type, "raw"))
1620     {
1621         if (npr->which == Z_NamePlusRecord_databaseRecord)
1622         {
1623             Z_External *r = (Z_External *) npr->u.databaseRecord;
1624             
1625             if (r->which == Z_External_sutrs)
1626             {
1627                 if (len) *len = r->u.sutrs->len;
1628                 return (const char *) r->u.sutrs->buf;
1629             }
1630             else if (r->which == Z_External_octet)
1631             {
1632                 if (len) *len = r->u.octet_aligned->len;
1633                 return (const char *) r->u.octet_aligned->buf;
1634             }
1635             else /* grs-1, explain, OPAC, ... */
1636             {
1637                 if (len) *len = -1;
1638                 return (const char *) npr->u.databaseRecord;
1639             }
1640         }
1641         return 0;
1642     }
1643     else if (!strcmp (type, "ext"))
1644     {
1645         if (npr->which == Z_NamePlusRecord_databaseRecord)
1646             return (const char *) npr->u.databaseRecord;
1647         return 0;
1648     }
1649     else if (npr->which == Z_NamePlusRecord_databaseRecord &&
1650              !strcmp (type, "opac"))
1651              
1652     {
1653         Z_External *r = (Z_External *) npr->u.databaseRecord;
1654         if (r->which == Z_External_OPAC)
1655         {
1656             if (!rec->wrbuf_opac)
1657                 rec->wrbuf_opac = wrbuf_alloc();
1658             wrbuf_rewind (rec->wrbuf_opac);
1659             yaz_display_OPAC(rec->wrbuf_opac, r->u.opac, 0);
1660             return record_iconv_return(rec, len,
1661                                        wrbuf_buf(rec->wrbuf_opac),
1662                                        wrbuf_len(rec->wrbuf_opac),
1663                                        charset);
1664         }
1665     }
1666     return 0;
1667 }
1668
1669 static int strcmp_null(const char *v1, const char *v2)
1670 {
1671     if (!v1 && !v2)
1672         return 0;
1673     if (!v1 || !v2)
1674         return -1;
1675     return strcmp(v1, v2);
1676 }
1677
1678 static void record_cache_add (ZOOM_resultset r, Z_NamePlusRecord *npr, 
1679                               int pos)
1680 {
1681     ZOOM_record_cache rc;
1682     const char *elementSetName =
1683         ZOOM_resultset_option_get (r, "elementSetName");
1684     const char *syntax = 
1685         ZOOM_resultset_option_get (r, "preferredRecordSyntax");
1686     
1687     ZOOM_Event event = ZOOM_Event_create(ZOOM_EVENT_RECV_RECORD);
1688     ZOOM_connection_put_event(r->connection, event);
1689
1690     for (rc = r->record_cache; rc; rc = rc->next)
1691     {
1692         if (pos == rc->pos)
1693         {
1694             if (strcmp_null(r->schema, rc->schema))
1695                 continue;
1696             if (strcmp_null(elementSetName,rc->elementSetName))
1697                 continue;
1698             if (strcmp_null(syntax, rc->syntax))
1699                 continue;
1700             /* not destroying rc->npr (it's handled by nmem )*/
1701             rc->rec.npr = npr;
1702             /* keeping wrbuf_marc too */
1703             return;
1704         }
1705     }
1706     rc = (ZOOM_record_cache) odr_malloc (r->odr, sizeof(*rc));
1707     rc->rec.npr = npr; 
1708     rc->rec.odr = 0;
1709     rc->rec.wrbuf_marc = 0;
1710     rc->rec.wrbuf_iconv = 0;
1711     rc->rec.wrbuf_opac = 0;
1712     if (elementSetName)
1713         rc->elementSetName = odr_strdup (r->odr, elementSetName);
1714     else
1715         rc->elementSetName = 0;
1716
1717     if (syntax)
1718         rc->syntax = odr_strdup (r->odr, syntax);
1719     else
1720         rc->syntax = 0;
1721
1722     if (r->schema)
1723         rc->schema = odr_strdup (r->odr, r->schema);
1724     else
1725         rc->schema = 0;
1726
1727     rc->pos = pos;
1728     rc->next = r->record_cache;
1729     r->record_cache = rc;
1730 }
1731
1732 static ZOOM_record record_cache_lookup (ZOOM_resultset r, int pos)
1733 {
1734     ZOOM_record_cache rc;
1735     const char *elementSetName =
1736         ZOOM_resultset_option_get (r, "elementSetName");
1737     const char *syntax = 
1738         ZOOM_resultset_option_get (r, "preferredRecordSyntax");
1739     
1740     for (rc = r->record_cache; rc; rc = rc->next)
1741     {
1742         if (pos == rc->pos)
1743         {
1744             if (strcmp_null(r->schema, rc->schema))
1745                 continue;
1746             if (strcmp_null(elementSetName,rc->elementSetName))
1747                 continue;
1748             if (strcmp_null(syntax, rc->syntax))
1749                 continue;
1750             return &rc->rec;
1751         }
1752     }
1753     return 0;
1754 }
1755                                              
1756 static void handle_records (ZOOM_connection c, Z_Records *sr,
1757                             int present_phase)
1758 {
1759     ZOOM_resultset resultset;
1760
1761     if (!c->tasks)
1762         return ;
1763     switch (c->tasks->which)
1764     {
1765     case ZOOM_TASK_SEARCH:
1766         resultset = c->tasks->u.search.resultset;
1767         break;
1768     case ZOOM_TASK_RETRIEVE:
1769         resultset = c->tasks->u.retrieve.resultset;        
1770         break;
1771     default:
1772         return;
1773     }
1774     if (sr && sr->which == Z_Records_NSD)
1775     {
1776         Z_DiagRec dr, *dr_p = &dr;
1777         dr.which = Z_DiagRec_defaultFormat;
1778         dr.u.defaultFormat = sr->u.nonSurrogateDiagnostic;
1779         
1780         response_diag (c, dr_p);
1781     }
1782     else if (sr && sr->which == Z_Records_multipleNSD)
1783     {
1784         if (sr->u.multipleNonSurDiagnostics->num_diagRecs >= 1)
1785             response_diag(c, sr->u.multipleNonSurDiagnostics->diagRecs[0]);
1786         else
1787             set_ZOOM_error(c, ZOOM_ERROR_DECODE, 0);
1788     }
1789     else 
1790     {
1791         if (resultset->count + resultset->start > resultset->size)
1792             resultset->count = resultset->size - resultset->start;
1793         if (resultset->count < 0)
1794             resultset->count = 0;
1795         if (sr && sr->which == Z_Records_DBOSD)
1796         {
1797             int i;
1798             NMEM nmem = odr_extract_mem (c->odr_in);
1799             Z_NamePlusRecordList *p =
1800                 sr->u.databaseOrSurDiagnostics;
1801             for (i = 0; i<p->num_records; i++)
1802             {
1803                 record_cache_add (resultset, p->records[i],
1804                                   i+ resultset->start);
1805             }
1806             /* transfer our response to search_nmem .. we need it later */
1807             nmem_transfer (resultset->odr->mem, nmem);
1808             nmem_destroy (nmem);
1809             if (present_phase && p->num_records == 0)
1810             {
1811                 /* present response and we didn't get any records! */
1812                 set_ZOOM_error(c, ZOOM_ERROR_DECODE, 0);
1813             }
1814         }
1815         else if (present_phase)
1816         {
1817             /* present response and we didn't get any records! */
1818             set_ZOOM_error(c, ZOOM_ERROR_DECODE, 0);
1819         }
1820     }
1821 }
1822
1823 static void handle_present_response (ZOOM_connection c, Z_PresentResponse *pr)
1824 {
1825     handle_records (c, pr->records, 1);
1826 }
1827
1828 static void handle_search_response (ZOOM_connection c, Z_SearchResponse *sr)
1829 {
1830     ZOOM_resultset resultset;
1831     ZOOM_Event event;
1832     
1833     yaz_log (LOG_DEBUG, "got search response");
1834     
1835     if (!c->tasks || c->tasks->which != ZOOM_TASK_SEARCH)
1836         return ;
1837     
1838     event = ZOOM_Event_create(ZOOM_EVENT_RECV_SEARCH);
1839     ZOOM_connection_put_event(c, event);
1840
1841     resultset = c->tasks->u.search.resultset;
1842
1843     resultset->size = *sr->resultCount;
1844     handle_records (c, sr->records, 0);
1845 }
1846
1847 static void sort_response (ZOOM_connection c, Z_SortResponse *res)
1848 {
1849     if (res->diagnostics && res->num_diagnostics > 0)
1850         response_diag (c, res->diagnostics[0]);
1851 }
1852
1853 static int scan_response (ZOOM_connection c, Z_ScanResponse *res)
1854 {
1855     NMEM nmem = odr_extract_mem (c->odr_in);
1856     ZOOM_scanset scan;
1857
1858     if (!c->tasks || c->tasks->which != ZOOM_TASK_SCAN)
1859         return 0;
1860     scan = c->tasks->u.scan.scan;
1861
1862     if (res->entries && res->entries->nonsurrogateDiagnostics)
1863         response_diag(c, res->entries->nonsurrogateDiagnostics[0]);
1864     scan->scan_response = res;
1865     nmem_transfer (scan->odr->mem, nmem);
1866     if (res->stepSize)
1867         ZOOM_options_set_int (scan->options, "stepSize", *res->stepSize);
1868     if (res->positionOfTerm)
1869         ZOOM_options_set_int (scan->options, "position", *res->positionOfTerm);
1870     if (res->scanStatus)
1871         ZOOM_options_set_int (scan->options, "scanStatus", *res->scanStatus);
1872     if (res->numberOfEntriesReturned)
1873         ZOOM_options_set_int (scan->options, "number",
1874                               *res->numberOfEntriesReturned);
1875     nmem_destroy (nmem);
1876     return 1;
1877 }
1878
1879 static zoom_ret send_sort (ZOOM_connection c)
1880 {
1881     ZOOM_resultset  resultset;
1882
1883     if (!c->tasks || c->tasks->which != ZOOM_TASK_SEARCH)
1884         return zoom_complete;
1885
1886     resultset = c->tasks->u.search.resultset;
1887
1888     if (c->error)
1889     {
1890         resultset->r_sort_spec = 0;
1891         return zoom_complete;
1892     }
1893     if (resultset->r_sort_spec)
1894     {
1895         Z_APDU *apdu = zget_APDU(c->odr_out, Z_APDU_sortRequest);
1896         Z_SortRequest *req = apdu->u.sortRequest;
1897         
1898         req->num_inputResultSetNames = 1;
1899         req->inputResultSetNames = (Z_InternationalString **)
1900             odr_malloc (c->odr_out, sizeof(*req->inputResultSetNames));
1901         req->inputResultSetNames[0] =
1902             odr_strdup (c->odr_out, resultset->setname);
1903         req->sortedResultSetName = odr_strdup (c->odr_out, resultset->setname);
1904         req->sortSequence = resultset->r_sort_spec;
1905         resultset->r_sort_spec = 0;
1906         return send_APDU (c, apdu);
1907     }
1908     return zoom_complete;
1909 }
1910
1911 static zoom_ret send_present (ZOOM_connection c)
1912 {
1913     Z_APDU *apdu = 0;
1914     Z_PresentRequest *req = 0;
1915     int i = 0;
1916     const char *syntax = 0;
1917     const char *elementSetName = 0;
1918     ZOOM_resultset  resultset;
1919
1920     if (!c->tasks)
1921         return zoom_complete;
1922
1923     switch (c->tasks->which)
1924     {
1925     case ZOOM_TASK_SEARCH:
1926         resultset = c->tasks->u.search.resultset;
1927         break;
1928     case ZOOM_TASK_RETRIEVE:
1929         resultset = c->tasks->u.retrieve.resultset;
1930         resultset->start = c->tasks->u.retrieve.start;
1931         resultset->count = c->tasks->u.retrieve.count;
1932
1933         if (resultset->start >= resultset->size)
1934             return zoom_complete;
1935         if (resultset->start + resultset->count > resultset->size)
1936             resultset->count = resultset->size - resultset->start;
1937         break;
1938     default:
1939         return zoom_complete;
1940     }
1941
1942     syntax = ZOOM_resultset_option_get (resultset, "preferredRecordSyntax");
1943     elementSetName = ZOOM_resultset_option_get (resultset, "elementSetName");
1944
1945     if (c->error)                  /* don't continue on error */
1946         return zoom_complete;
1947     if (resultset->start < 0)
1948         return zoom_complete;
1949     for (i = 0; i<resultset->count; i++)
1950     {
1951         ZOOM_record rec =
1952             record_cache_lookup (resultset, i + resultset->start);
1953         if (!rec)
1954             break;
1955     }
1956     if (i == resultset->count)
1957         return zoom_complete;
1958
1959     apdu = zget_APDU(c->odr_out, Z_APDU_presentRequest);
1960     req = apdu->u.presentRequest;
1961
1962     resultset->start += i;
1963     resultset->count -= i;
1964     *req->resultSetStartPoint = resultset->start + 1;
1965     *req->numberOfRecordsRequested = resultset->step>0 ?
1966         resultset->step : resultset->count;
1967     assert (*req->numberOfRecordsRequested > 0);
1968
1969     if (syntax && *syntax)
1970         req->preferredRecordSyntax =
1971             yaz_str_to_z3950oid (c->odr_out, CLASS_RECSYN, syntax);
1972
1973     if (resultset->schema && *resultset->schema)
1974     {
1975         Z_RecordComposition *compo = (Z_RecordComposition *)
1976             odr_malloc (c->odr_out, sizeof(*compo));
1977
1978         req->recordComposition = compo;
1979         compo->which = Z_RecordComp_complex;
1980         compo->u.complex = (Z_CompSpec *)
1981             odr_malloc(c->odr_out, sizeof(*compo->u.complex));
1982         compo->u.complex->selectAlternativeSyntax = (bool_t *) 
1983             odr_malloc(c->odr_out, sizeof(bool_t));
1984         *compo->u.complex->selectAlternativeSyntax = 0;
1985
1986         compo->u.complex->generic = (Z_Specification *)
1987             odr_malloc(c->odr_out, sizeof(*compo->u.complex->generic));
1988
1989         compo->u.complex->generic->which = Z_Schema_oid;
1990         compo->u.complex->generic->schema.oid = (Odr_oid *)
1991             yaz_str_to_z3950oid (c->odr_out, CLASS_SCHEMA, resultset->schema);
1992
1993         if (!compo->u.complex->generic->schema.oid)
1994         {
1995             /* OID wasn't a schema! Try record syntax instead. */
1996
1997             compo->u.complex->generic->schema.oid = (Odr_oid *)
1998                 yaz_str_to_z3950oid (c->odr_out, CLASS_RECSYN, resultset->schema);
1999         }
2000         if (elementSetName && *elementSetName)
2001         {
2002             compo->u.complex->generic->elementSpec = (Z_ElementSpec *)
2003                 odr_malloc(c->odr_out, sizeof(Z_ElementSpec));
2004             compo->u.complex->generic->elementSpec->which =
2005                 Z_ElementSpec_elementSetName;
2006             compo->u.complex->generic->elementSpec->u.elementSetName =
2007                 odr_strdup (c->odr_out, elementSetName);
2008         }
2009         else
2010             compo->u.complex->generic->elementSpec = 0;
2011         compo->u.complex->num_dbSpecific = 0;
2012         compo->u.complex->dbSpecific = 0;
2013         compo->u.complex->num_recordSyntax = 0;
2014         compo->u.complex->recordSyntax = 0;
2015     }
2016     else if (elementSetName && *elementSetName)
2017     {
2018         Z_ElementSetNames *esn = (Z_ElementSetNames *)
2019             odr_malloc (c->odr_out, sizeof(*esn));
2020         Z_RecordComposition *compo = (Z_RecordComposition *)
2021             odr_malloc (c->odr_out, sizeof(*compo));
2022         
2023         esn->which = Z_ElementSetNames_generic;
2024         esn->u.generic = odr_strdup (c->odr_out, elementSetName);
2025         compo->which = Z_RecordComp_simple;
2026         compo->u.simple = esn;
2027         req->recordComposition = compo;
2028     }
2029     req->resultSetId = odr_strdup(c->odr_out, resultset->setname);
2030     return send_APDU (c, apdu);
2031 }
2032
2033 ZOOM_API(ZOOM_scanset)
2034 ZOOM_connection_scan (ZOOM_connection c, const char *start)
2035 {
2036     ZOOM_scanset scan = (ZOOM_scanset) xmalloc (sizeof(*scan));
2037
2038     scan->connection = c;
2039     scan->odr = odr_createmem (ODR_DECODE);
2040     scan->options = ZOOM_options_create_with_parent (c->options);
2041     scan->refcount = 1;
2042     scan->scan_response = 0;
2043
2044     if ((scan->termListAndStartPoint =
2045          p_query_scan(scan->odr, PROTO_Z3950, &scan->attributeSet,
2046                       start)))
2047     {
2048         ZOOM_task task = ZOOM_connection_add_task (c, ZOOM_TASK_SCAN);
2049         task->u.scan.scan = scan;
2050         
2051         (scan->refcount)++;
2052         if (!c->async)
2053         {
2054             while (ZOOM_event (1, &c))
2055                 ;
2056         }
2057     }
2058     return scan;
2059 }
2060
2061 ZOOM_API(void)
2062 ZOOM_scanset_destroy (ZOOM_scanset scan)
2063 {
2064     if (!scan)
2065         return;
2066     (scan->refcount)--;
2067     if (scan->refcount == 0)
2068     {
2069         odr_destroy (scan->odr);
2070         
2071         ZOOM_options_destroy (scan->options);
2072         xfree (scan);
2073     }
2074 }
2075
2076 static zoom_ret send_package (ZOOM_connection c)
2077 {
2078     ZOOM_Event event;
2079     if (!c->tasks)
2080         return zoom_complete;
2081     assert (c->tasks->which == ZOOM_TASK_PACKAGE);
2082     
2083     event = ZOOM_Event_create (ZOOM_EVENT_SEND_APDU);
2084     ZOOM_connection_put_event (c, event);
2085     
2086     return do_write_ex (c, c->tasks->u.package->buf_out,
2087                         c->tasks->u.package->len_out);
2088 }
2089
2090 static zoom_ret send_scan (ZOOM_connection c)
2091 {
2092     ZOOM_scanset scan;
2093     Z_APDU *apdu = zget_APDU(c->odr_out, Z_APDU_scanRequest);
2094     Z_ScanRequest *req = apdu->u.scanRequest;
2095     if (!c->tasks)
2096         return zoom_complete;
2097     assert (c->tasks->which == ZOOM_TASK_SCAN);
2098     scan = c->tasks->u.scan.scan;
2099
2100     req->termListAndStartPoint = scan->termListAndStartPoint;
2101     req->attributeSet = scan->attributeSet;
2102
2103     *req->numberOfTermsRequested =
2104         ZOOM_options_get_int(scan->options, "number", 10);
2105
2106     req->preferredPositionInResponse =
2107         odr_intdup (c->odr_out,
2108                     ZOOM_options_get_int(scan->options, "position", 1));
2109
2110     req->stepSize =
2111         odr_intdup (c->odr_out,
2112                     ZOOM_options_get_int(scan->options, "stepSize", 0));
2113     
2114     req->databaseNames = set_DatabaseNames (c, scan->options, 
2115                                             &req->num_databaseNames);
2116
2117     return send_APDU (c, apdu);
2118 }
2119
2120 ZOOM_API(size_t)
2121 ZOOM_scanset_size (ZOOM_scanset scan)
2122 {
2123     if (!scan || !scan->scan_response || !scan->scan_response->entries)
2124         return 0;
2125     return scan->scan_response->entries->num_entries;
2126 }
2127
2128 ZOOM_API(const char *)
2129 ZOOM_scanset_term (ZOOM_scanset scan, size_t pos,
2130                                int *occ, int *len)
2131 {
2132     const char *term = 0;
2133     size_t noent = ZOOM_scanset_size (scan);
2134     Z_ScanResponse *res = scan->scan_response;
2135     
2136     *len = 0;
2137     *occ = 0;
2138     if (pos >= noent)
2139         return 0;
2140     if (res->entries->entries[pos]->which == Z_Entry_termInfo)
2141     {
2142         Z_TermInfo *t = res->entries->entries[pos]->u.termInfo;
2143         
2144         if (t->term->which == Z_Term_general)
2145         {
2146             term = (const char *) t->term->u.general->buf;
2147             *len = t->term->u.general->len;
2148         }
2149         *occ = t->globalOccurrences ? *t->globalOccurrences : 0;
2150     }
2151     return term;
2152 }
2153
2154 ZOOM_API(const char *)
2155 ZOOM_scanset_option_get (ZOOM_scanset scan, const char *key)
2156 {
2157     return ZOOM_options_get (scan->options, key);
2158 }
2159
2160 ZOOM_API(void)
2161 ZOOM_scanset_option_set (ZOOM_scanset scan, const char *key,
2162                               const char *val)
2163 {
2164     ZOOM_options_set (scan->options, key, val);
2165 }
2166
2167 static Z_APDU *create_es_package (ZOOM_package p, int type)
2168 {
2169     const char *str;
2170     Z_APDU *apdu = zget_APDU(p->odr_out, Z_APDU_extendedServicesRequest);
2171     Z_ExtendedServicesRequest *req = apdu->u.extendedServicesRequest;
2172     
2173     *req->function = Z_ExtendedServicesRequest_create;
2174     
2175     str = ZOOM_options_get(p->options, "package-name");
2176     if (str && *str)
2177         req->packageName = nmem_strdup (p->odr_out->mem, str);
2178     
2179     str = ZOOM_options_get(p->options, "user-id");
2180     if (str)
2181         req->userId = nmem_strdup (p->odr_out->mem, str);
2182     
2183     req->packageType = yaz_oidval_to_z3950oid(p->odr_out, CLASS_EXTSERV,
2184                                               type);
2185
2186     str = ZOOM_options_get(p->options, "function");
2187     if (str)
2188     {
2189         if (!strcmp (str, "create"))
2190             *req->function = 1;
2191         if (!strcmp (str, "delete"))
2192             *req->function = 2;
2193         if (!strcmp (str, "modify"))
2194             *req->function = 3;
2195     }
2196     return apdu;
2197 }
2198
2199 static const char *ill_array_lookup (void *clientData, const char *idx)
2200 {
2201     ZOOM_package p = (ZOOM_package) clientData;
2202     return ZOOM_options_get (p->options, idx+4);
2203 }
2204
2205 static Z_External *encode_ill_request (ZOOM_package p)
2206 {
2207     ODR out = p->odr_out;
2208     ILL_Request *req;
2209     Z_External *r = 0;
2210     struct ill_get_ctl ctl;
2211         
2212     ctl.odr = p->odr_out;
2213     ctl.clientData = p;
2214     ctl.f = ill_array_lookup;
2215         
2216     req = ill_get_ILLRequest(&ctl, "ill", 0);
2217         
2218     if (!ill_Request (out, &req, 0, 0))
2219     {
2220         int ill_request_size;
2221         char *ill_request_buf = odr_getbuf (out, &ill_request_size, 0);
2222         if (ill_request_buf)
2223             odr_setbuf (out, ill_request_buf, ill_request_size, 1);
2224         return 0;
2225     }
2226     else
2227     {
2228         oident oid;
2229         int illRequest_size = 0;
2230         char *illRequest_buf = odr_getbuf (out, &illRequest_size, 0);
2231                 
2232         oid.proto = PROTO_GENERAL;
2233         oid.oclass = CLASS_GENERAL;
2234         oid.value = VAL_ISO_ILL_1;
2235                 
2236         r = (Z_External *) odr_malloc (out, sizeof(*r));
2237         r->direct_reference = odr_oiddup(out,oid_getoidbyent(&oid)); 
2238         r->indirect_reference = 0;
2239         r->descriptor = 0;
2240         r->which = Z_External_single;
2241                 
2242         r->u.single_ASN1_type = (Odr_oct *)
2243             odr_malloc (out, sizeof(*r->u.single_ASN1_type));
2244         r->u.single_ASN1_type->buf = (unsigned char*)
2245             odr_malloc (out, illRequest_size);
2246         r->u.single_ASN1_type->len = illRequest_size;
2247         r->u.single_ASN1_type->size = illRequest_size;
2248         memcpy (r->u.single_ASN1_type->buf, illRequest_buf, illRequest_size);
2249     }
2250     return r;
2251 }
2252
2253 static Z_ItemOrder *encode_item_order(ZOOM_package p)
2254 {
2255     Z_ItemOrder *req = (Z_ItemOrder *) odr_malloc (p->odr_out, sizeof(*req));
2256     const char *str;
2257     
2258     req->which=Z_IOItemOrder_esRequest;
2259     req->u.esRequest = (Z_IORequest *) 
2260         odr_malloc(p->odr_out,sizeof(Z_IORequest));
2261
2262     /* to keep part ... */
2263     req->u.esRequest->toKeep = (Z_IOOriginPartToKeep *)
2264         odr_malloc(p->odr_out,sizeof(Z_IOOriginPartToKeep));
2265     req->u.esRequest->toKeep->supplDescription = 0;
2266     req->u.esRequest->toKeep->contact = (Z_IOContact *)
2267         odr_malloc (p->odr_out, sizeof(*req->u.esRequest->toKeep->contact));
2268         
2269     str = ZOOM_options_get(p->options, "contact-name");
2270     req->u.esRequest->toKeep->contact->name = str ?
2271         nmem_strdup (p->odr_out->mem, str) : 0;
2272         
2273     str = ZOOM_options_get(p->options, "contact-phone");
2274     req->u.esRequest->toKeep->contact->phone = str ?
2275         nmem_strdup (p->odr_out->mem, str) : 0;
2276         
2277     str = ZOOM_options_get(p->options, "contact-email");
2278     req->u.esRequest->toKeep->contact->email = str ?
2279         nmem_strdup (p->odr_out->mem, str) : 0;
2280         
2281     req->u.esRequest->toKeep->addlBilling = 0;
2282         
2283     /* not to keep part ... */
2284     req->u.esRequest->notToKeep = (Z_IOOriginPartNotToKeep *)
2285         odr_malloc(p->odr_out,sizeof(Z_IOOriginPartNotToKeep));
2286         
2287     str = ZOOM_options_get(p->options, "itemorder-setname");
2288     if (!str)
2289         str = "default";
2290
2291     if (!*str) 
2292         req->u.esRequest->notToKeep->resultSetItem = 0;
2293     else
2294     {
2295         req->u.esRequest->notToKeep->resultSetItem = (Z_IOResultSetItem *)
2296            odr_malloc(p->odr_out, sizeof(Z_IOResultSetItem));
2297
2298         req->u.esRequest->notToKeep->resultSetItem->resultSetId =
2299            nmem_strdup (p->odr_out->mem, str);
2300         req->u.esRequest->notToKeep->resultSetItem->item =
2301             (int *) odr_malloc(p->odr_out, sizeof(int));
2302         
2303         str = ZOOM_options_get(p->options, "itemorder-item");
2304         *req->u.esRequest->notToKeep->resultSetItem->item =
2305             (str ? atoi(str) : 1);
2306     }
2307     req->u.esRequest->notToKeep->itemRequest = encode_ill_request(p);
2308     
2309     return req;
2310 }
2311
2312 ZOOM_API(void)
2313     ZOOM_package_send (ZOOM_package p, const char *type)
2314 {
2315     Z_APDU *apdu = 0;
2316     ZOOM_connection c;
2317     if (!p)
2318         return;
2319     c = p->connection;
2320     odr_reset (p->odr_out);
2321     xfree (p->buf_out);
2322     p->buf_out = 0;
2323     if (!strcmp(type, "itemorder"))
2324     {
2325         Z_External *r;
2326         apdu = create_es_package (p, VAL_ITEMORDER);
2327         if (apdu)
2328         {
2329             r = (Z_External *) odr_malloc (p->odr_out, sizeof(*r));
2330             
2331             r->direct_reference =
2332                 yaz_oidval_to_z3950oid(p->odr_out, CLASS_EXTSERV,
2333                                        VAL_ITEMORDER);
2334             r->descriptor = 0;
2335             r->which = Z_External_itemOrder;
2336             r->indirect_reference = 0;
2337             r->u.itemOrder = encode_item_order (p);
2338
2339             apdu->u.extendedServicesRequest->taskSpecificParameters = r;
2340         }
2341     }
2342     if (apdu)
2343     {
2344         if (encode_APDU(p->connection, apdu, p->odr_out) == 0)
2345         {
2346             char *buf;
2347
2348             ZOOM_task task = ZOOM_connection_add_task (c, ZOOM_TASK_PACKAGE);
2349             task->u.package = p;
2350             buf = odr_getbuf(p->odr_out, &p->len_out, 0);
2351             p->buf_out = (char *) xmalloc (p->len_out);
2352             memcpy (p->buf_out, buf, p->len_out);
2353             
2354             (p->refcount)++;
2355             if (!c->async)
2356             {
2357                 while (ZOOM_event (1, &c))
2358                     ;
2359             }
2360         }
2361     }
2362 }
2363
2364 ZOOM_API(ZOOM_package)
2365     ZOOM_connection_package (ZOOM_connection c, ZOOM_options options)
2366 {
2367     ZOOM_package p = (ZOOM_package) xmalloc (sizeof(*p));
2368
2369     p->connection = c;
2370     p->odr_out = odr_createmem (ODR_ENCODE);
2371     p->options = ZOOM_options_create_with_parent2 (options, c->options);
2372     p->refcount = 1;
2373     p->buf_out = 0;
2374     p->len_out = 0;
2375     return p;
2376 }
2377
2378 ZOOM_API(void)
2379     ZOOM_package_destroy(ZOOM_package p)
2380 {
2381     if (!p)
2382         return;
2383     (p->refcount)--;
2384     if (p->refcount == 0)
2385     {
2386         odr_destroy (p->odr_out);
2387         xfree (p->buf_out);
2388         
2389         ZOOM_options_destroy (p->options);
2390         xfree (p);
2391     }
2392 }
2393
2394 ZOOM_API(const char *)
2395 ZOOM_package_option_get (ZOOM_package p, const char *key)
2396 {
2397     return ZOOM_options_get (p->options, key);
2398 }
2399
2400
2401 ZOOM_API(void)
2402 ZOOM_package_option_set (ZOOM_package p, const char *key,
2403                               const char *val)
2404 {
2405     ZOOM_options_set (p->options, key, val);
2406 }
2407
2408 static int ZOOM_connection_exec_task (ZOOM_connection c)
2409 {
2410     ZOOM_task task = c->tasks;
2411     zoom_ret ret = zoom_complete;
2412
2413     if (!task)
2414     {
2415         yaz_log (LOG_DEBUG, "ZOOM_connection_exec_task task=<null>");
2416         return 0;
2417     }
2418     yaz_log (LOG_DEBUG, "ZOOM_connection_exec_task type=%d run=%d",
2419              task->which, task->running);
2420     if (c->error != ZOOM_ERROR_NONE)
2421     {
2422         yaz_log (LOG_DEBUG, "remove tasks because of error = %d", c->error);
2423         ZOOM_connection_remove_tasks (c);
2424         return 0;
2425     }
2426     if (task->running)
2427     {
2428         yaz_log (LOG_DEBUG, "task already running");
2429         return 0;
2430     }
2431     task->running = 1;
2432     ret = zoom_complete;
2433     if (c->cs || task->which == ZOOM_TASK_CONNECT)
2434     {
2435         switch (task->which)
2436         {
2437         case ZOOM_TASK_SEARCH:
2438             if (c->proto == PROTO_HTTP)
2439                 ret = ZOOM_connection_srw_send_search(c);
2440             else
2441                 ret = ZOOM_connection_send_search(c);
2442             break;
2443         case ZOOM_TASK_RETRIEVE:
2444             if (c->proto == PROTO_HTTP)
2445                 ret = ZOOM_connection_srw_send_search(c);
2446             else
2447                 ret = send_present (c);
2448             break;
2449         case ZOOM_TASK_CONNECT:
2450             ret = do_connect(c);
2451             break;
2452         case ZOOM_TASK_SCAN:
2453             ret = send_scan(c);
2454             break;
2455         case ZOOM_TASK_PACKAGE:
2456             ret = send_package(c);
2457             break;
2458         }
2459     }
2460     else
2461     {
2462         yaz_log (LOG_DEBUG, "remove tasks because no connection exist");
2463         ZOOM_connection_remove_tasks (c);
2464     }
2465     if (ret == zoom_complete)
2466     {
2467         yaz_log (LOG_DEBUG, "task removed (complete)");
2468         ZOOM_connection_remove_task (c);
2469         return 0;
2470     }
2471     yaz_log (LOG_DEBUG, "task pending");
2472     return 1;
2473 }
2474
2475 static zoom_ret send_sort_present (ZOOM_connection c)
2476 {
2477     zoom_ret r = send_sort (c);
2478     if (r == zoom_complete)
2479         r = send_present (c);
2480     return r;
2481 }
2482
2483 static int es_response (ZOOM_connection c,
2484                         Z_ExtendedServicesResponse *res)
2485 {
2486     if (!c->tasks || c->tasks->which != ZOOM_TASK_PACKAGE)
2487         return 0;
2488     if (res->diagnostics && res->num_diagnostics > 0)
2489         response_diag(c, res->diagnostics[0]);
2490     if (res->taskPackage &&
2491         res->taskPackage->which == Z_External_extendedService)
2492     {
2493         Z_TaskPackage *taskPackage = res->taskPackage->u.extendedService;
2494         Odr_oct *id = taskPackage->targetReference;
2495         
2496         if (id)
2497             ZOOM_options_setl (c->tasks->u.package->options,
2498                                "targetReference", (char*) id->buf, id->len);
2499     }
2500     return 1;
2501 }
2502
2503
2504 static void handle_apdu (ZOOM_connection c, Z_APDU *apdu)
2505 {
2506     Z_InitResponse *initrs;
2507     
2508     c->mask = 0;
2509     yaz_log (LOG_DEBUG, "recv APDU type=%d", apdu->which);
2510     switch(apdu->which)
2511     {
2512     case Z_APDU_initResponse:
2513         initrs = apdu->u.initResponse;
2514         ZOOM_connection_option_set(c, "targetImplementationId",
2515                                    initrs->implementationId ?
2516                                    initrs->implementationId : "");
2517         ZOOM_connection_option_set(c, "targetImplementationName",
2518                                    initrs->implementationName ?
2519                                    initrs->implementationName : "");
2520         ZOOM_connection_option_set(c, "targetImplementationVersion",
2521                                    initrs->implementationVersion ?
2522                                    initrs->implementationVersion : "");
2523         if (!*initrs->result)
2524         {
2525             set_ZOOM_error(c, ZOOM_ERROR_INIT, 0);
2526         }
2527         else
2528         {
2529             char *cookie =
2530                 yaz_oi_get_string_oidval (&apdu->u.initResponse->otherInfo,
2531                                           VAL_COOKIE, 1, 0);
2532             xfree (c->cookie_in);
2533             c->cookie_in = 0;
2534             if (cookie)
2535                 c->cookie_in = xstrdup(cookie);
2536             if (ODR_MASK_GET(initrs->options, Z_Options_namedResultSets) &&
2537                 ODR_MASK_GET(initrs->protocolVersion, Z_ProtocolVersion_3))
2538                 c->support_named_resultsets = 1;
2539             if (c->tasks)
2540             {
2541                 assert (c->tasks->which == ZOOM_TASK_CONNECT);
2542                 ZOOM_connection_remove_task (c);
2543             }
2544             ZOOM_connection_exec_task (c);
2545         }
2546         if (ODR_MASK_GET(initrs->options, Z_Options_negotiationModel))
2547         {
2548             NMEM tmpmem = nmem_create();
2549             Z_CharSetandLanguageNegotiation *p =
2550                 yaz_get_charneg_record(initrs->otherInfo);
2551             
2552             if (p)
2553             {
2554                 char *charset=NULL, *lang=NULL;
2555                 int sel;
2556                 
2557                 yaz_get_response_charneg(tmpmem, p, &charset, &lang, &sel);
2558                 yaz_log(LOG_DEBUG, "Target accepted: charset %s, "
2559                         "language %s, select %d",
2560                         charset ? charset : "none", lang ? lang : "none", sel);
2561                 if (charset)
2562                     ZOOM_connection_option_set (c, "negotiation-charset",
2563                                                 charset);
2564                 if (lang)
2565                     ZOOM_connection_option_set (c, "negotiation-lang",
2566                                                 lang);
2567                 nmem_destroy(tmpmem);
2568             }
2569         }       
2570         break;
2571     case Z_APDU_searchResponse:
2572         handle_search_response (c, apdu->u.searchResponse);
2573         if (send_sort_present (c) == zoom_complete)
2574             ZOOM_connection_remove_task (c);
2575         break;
2576     case Z_APDU_presentResponse:
2577         handle_present_response (c, apdu->u.presentResponse);
2578         if (send_present (c) == zoom_complete)
2579             ZOOM_connection_remove_task (c);
2580         break;
2581     case Z_APDU_sortResponse:
2582         sort_response (c, apdu->u.sortResponse);
2583         if (send_present (c) == zoom_complete)
2584             ZOOM_connection_remove_task (c);
2585         break;
2586     case Z_APDU_scanResponse:
2587         scan_response (c, apdu->u.scanResponse);
2588         ZOOM_connection_remove_task (c);
2589         break;
2590     case Z_APDU_extendedServicesResponse:
2591         es_response (c, apdu->u.extendedServicesResponse);
2592         ZOOM_connection_remove_task (c);
2593         break;
2594     case Z_APDU_close:
2595         if (c->reconnect_ok)
2596         {
2597             do_close(c);
2598             c->tasks->running = 0;
2599             ZOOM_connection_insert_task (c, ZOOM_TASK_CONNECT);
2600         }
2601         else
2602         {
2603             set_ZOOM_error(c, ZOOM_ERROR_CONNECTION_LOST, 0);
2604             do_close(c);
2605         }
2606         break;
2607     default:
2608         set_ZOOM_error(c, ZOOM_ERROR_DECODE, 0);
2609         do_close(c);
2610     }
2611 }
2612
2613 #if HAVE_XML2
2614 static void handle_srw_response(ZOOM_connection c,
2615                                 Z_SRW_searchRetrieveResponse *res)
2616 {
2617     ZOOM_resultset resultset = 0;
2618     int i;
2619     NMEM nmem;
2620     ZOOM_Event event;
2621
2622     if (!c->tasks)
2623         return;
2624
2625     if (c->tasks->which == ZOOM_TASK_SEARCH)
2626         resultset = c->tasks->u.search.resultset;
2627     else if (c->tasks->which == ZOOM_TASK_RETRIEVE)
2628         resultset = c->tasks->u.retrieve.resultset;
2629     else
2630         return ;
2631
2632     event = ZOOM_Event_create(ZOOM_EVENT_RECV_SEARCH);
2633     ZOOM_connection_put_event(c, event);
2634
2635     resultset->size = 0;
2636
2637     yaz_log(LOG_DEBUG, "got SRW response OK");
2638     
2639     if (res->numberOfRecords)
2640         resultset->size = *res->numberOfRecords;
2641
2642     for (i = 0; i<res->num_records; i++)
2643     {
2644         int pos;
2645
2646         Z_NamePlusRecord *npr = (Z_NamePlusRecord *)
2647             odr_malloc(c->odr_in, sizeof(Z_NamePlusRecord));
2648
2649         if (res->records[i].recordPosition && 
2650             *res->records[i].recordPosition > 0)
2651             pos = *res->records[i].recordPosition - 1;
2652         else
2653             pos = resultset->start + i;
2654         
2655         npr->databaseName = 0;
2656         npr->which = Z_NamePlusRecord_databaseRecord;
2657         npr->u.databaseRecord = (Z_External *)
2658             odr_malloc(c->odr_in, sizeof(Z_External));
2659         npr->u.databaseRecord->descriptor = 0;
2660         npr->u.databaseRecord->direct_reference =
2661             yaz_oidval_to_z3950oid(c->odr_in, CLASS_RECSYN, VAL_TEXT_XML);
2662         npr->u.databaseRecord->which = Z_External_octet;
2663         npr->u.databaseRecord->u.octet_aligned = (Odr_oct *)
2664             odr_malloc(c->odr_in, sizeof(Odr_oct));
2665         npr->u.databaseRecord->u.octet_aligned->buf = 
2666             res->records[i].recordData_buf;
2667         npr->u.databaseRecord->u.octet_aligned->len = 
2668             npr->u.databaseRecord->u.octet_aligned->size = 
2669             res->records[i].recordData_len;
2670         record_cache_add (resultset, npr, pos);
2671     }
2672     if (res->num_diagnostics > 0)
2673     {
2674         set_dset_error(c, *res->diagnostics[0].code, "SRW",
2675                        res->diagnostics[0].details, 0);
2676     }
2677     nmem = odr_extract_mem(c->odr_in);
2678     nmem_transfer(resultset->odr->mem, nmem);
2679     nmem_destroy(nmem);
2680 }
2681 #endif
2682
2683 #if HAVE_XML2
2684 static void handle_http(ZOOM_connection c, Z_HTTP_Response *hres)
2685 {
2686     int ret = -1;
2687     const char *content_type = z_HTTP_header_lookup(hres->headers,
2688                                                     "Content-Type");
2689     const char *connection_head = z_HTTP_header_lookup(hres->headers,
2690                                                        "Connection");
2691     c->mask = 0;
2692     yaz_log (LOG_DEBUG, "handle_http");
2693
2694     if (content_type && !yaz_strcmp_del("text/xml", content_type, "; "))
2695     {
2696         Z_SOAP *soap_package = 0;
2697         ODR o = odr_createmem(ODR_DECODE);
2698         Z_SOAP_Handler soap_handlers[2] = {
2699             {"http://www.loc.gov/zing/srw/v1.0/", 0,
2700              (Z_SOAP_fun) yaz_srw_codec},
2701             {0, 0, 0}
2702         };
2703         ret = z_soap_codec(o, &soap_package,
2704                            &hres->content_buf, &hres->content_len,
2705                            soap_handlers);
2706         if (!ret && soap_package->which == Z_SOAP_generic &&
2707             soap_package->u.generic->no == 0)
2708         {
2709             Z_SRW_PDU *sr = soap_package->u.generic->p;
2710             if (sr->which == Z_SRW_searchRetrieve_response)
2711                 handle_srw_response(c, sr->u.response);
2712             else
2713                 ret = -1;
2714         }
2715         else if (!ret && (soap_package->which == Z_SOAP_fault
2716                           || soap_package->which == Z_SOAP_error))
2717         {
2718             set_HTTP_error(c, hres->code,
2719                            soap_package->u.fault->fault_code,
2720                            soap_package->u.fault->fault_string);
2721         }
2722         else
2723             ret = -1;
2724         odr_destroy(o);
2725     }
2726     if (ret)
2727     {
2728         if (hres->code != 200)
2729             set_HTTP_error(c, hres->code, 0, 0);
2730         else
2731             set_ZOOM_error(c, ZOOM_ERROR_DECODE, 0);
2732         do_close (c);
2733     }
2734     ZOOM_connection_remove_task(c);
2735     if (!strcmp(hres->version, "1.0"))
2736     {
2737         /* HTTP 1.0: only if Keep-Alive we stay alive.. */
2738         if (!connection_head || strcmp(connection_head, "Keep-Alive"))
2739             do_close(c);
2740     }
2741     else 
2742     {
2743         /* HTTP 1.1: only if no close we stay alive .. */
2744         if (connection_head && !strcmp(connection_head, "close"))
2745             do_close(c);
2746     }
2747 }
2748 #endif
2749
2750 static int do_read (ZOOM_connection c)
2751 {
2752     int r, more;
2753     ZOOM_Event event;
2754     
2755     event = ZOOM_Event_create (ZOOM_EVENT_RECV_DATA);
2756     ZOOM_connection_put_event (c, event);
2757     
2758
2759     r = cs_get (c->cs, &c->buf_in, &c->len_in);
2760     more = cs_more(c->cs);
2761     yaz_log (LOG_DEBUG, "do_read len=%d more=%d", r, more);
2762     if (r == 1)
2763         return 0;
2764     if (r <= 0)
2765     {
2766         if (c->reconnect_ok)
2767         {
2768             do_close (c);
2769             c->reconnect_ok = 0;
2770             yaz_log (LOG_DEBUG, "reconnect read");
2771             c->tasks->running = 0;
2772             ZOOM_connection_insert_task (c, ZOOM_TASK_CONNECT);
2773         }
2774         else
2775         {
2776             set_ZOOM_error(c, ZOOM_ERROR_CONNECTION_LOST, 0);
2777             do_close (c);
2778         }
2779     }
2780     else
2781     {
2782         Z_GDU *gdu;
2783         ZOOM_Event event;
2784
2785         odr_reset (c->odr_in);
2786         odr_setbuf (c->odr_in, c->buf_in, r, 0);
2787         event = ZOOM_Event_create (ZOOM_EVENT_RECV_APDU);
2788         ZOOM_connection_put_event (c, event);
2789
2790         if (!z_GDU (c->odr_in, &gdu, 0, 0))
2791         {
2792             set_ZOOM_error(c, ZOOM_ERROR_DECODE, 0);
2793             do_close (c);
2794         }
2795         else if (gdu->which == Z_GDU_Z3950)
2796             handle_apdu (c, gdu->u.z3950);
2797         else if (gdu->which == Z_GDU_HTTP_Response)
2798         {
2799 #if HAVE_XML2
2800             handle_http (c, gdu->u.HTTP_Response);
2801 #else
2802             set_ZOOM_error(c, ZOOM_ERROR_DECODE, 0);
2803             do_close (c);
2804 #endif
2805         }
2806         c->reconnect_ok = 0;
2807     }
2808     return 1;
2809 }
2810
2811 static zoom_ret do_write_ex (ZOOM_connection c, char *buf_out, int len_out)
2812 {
2813     int r;
2814     ZOOM_Event event;
2815     
2816     event = ZOOM_Event_create(ZOOM_EVENT_SEND_DATA);
2817     ZOOM_connection_put_event (c, event);
2818
2819     yaz_log (LOG_DEBUG, "do_write_ex len=%d", len_out);
2820     if ((r=cs_put (c->cs, buf_out, len_out)) < 0)
2821     {
2822         if (c->reconnect_ok)
2823         {
2824             do_close (c);
2825             c->reconnect_ok = 0;
2826             yaz_log (LOG_DEBUG, "reconnect write");
2827             c->tasks->running = 0;
2828             ZOOM_connection_insert_task (c, ZOOM_TASK_CONNECT);
2829             return zoom_pending;
2830         }
2831         if (c->state == STATE_CONNECTING)
2832             set_ZOOM_error(c, ZOOM_ERROR_CONNECT, 0);
2833         else
2834             set_ZOOM_error(c, ZOOM_ERROR_CONNECTION_LOST, 0);
2835         do_close (c);
2836         return zoom_complete;
2837     }
2838     else if (r == 1)
2839     {    
2840         c->mask = ZOOM_SELECT_EXCEPT;
2841         if (c->cs->io_pending & CS_WANT_WRITE)
2842             c->mask += ZOOM_SELECT_WRITE;
2843         if (c->cs->io_pending & CS_WANT_READ)
2844             c->mask += ZOOM_SELECT_READ;
2845         yaz_log (LOG_DEBUG, "do_write_ex 1 mask=%d", c->mask);
2846     }
2847     else
2848     {
2849         c->mask = ZOOM_SELECT_READ|ZOOM_SELECT_EXCEPT;
2850         yaz_log (LOG_DEBUG, "do_write_ex 2 mask=%d", c->mask);
2851     }
2852     return zoom_pending;
2853 }
2854
2855 static zoom_ret do_write(ZOOM_connection c)
2856 {
2857     return do_write_ex (c, c->buf_out, c->len_out);
2858 }
2859
2860
2861 ZOOM_API(const char *)
2862 ZOOM_connection_option_get (ZOOM_connection c, const char *key)
2863 {
2864     return ZOOM_options_get (c->options, key);
2865 }
2866
2867 ZOOM_API(void)
2868 ZOOM_connection_option_set (ZOOM_connection c, const char *key,
2869                                   const char *val)
2870 {
2871     ZOOM_options_set (c->options, key, val);
2872 }
2873
2874 ZOOM_API(const char *)
2875 ZOOM_resultset_option_get (ZOOM_resultset r, const char *key)
2876 {
2877     return ZOOM_options_get (r->options, key);
2878 }
2879
2880 ZOOM_API(void)
2881 ZOOM_resultset_option_set (ZOOM_resultset r, const char *key,
2882                                   const char *val)
2883 {
2884     ZOOM_options_set (r->options, key, val);
2885 }
2886
2887
2888 ZOOM_API(int)
2889 ZOOM_connection_errcode (ZOOM_connection c)
2890 {
2891     return ZOOM_connection_error (c, 0, 0);
2892 }
2893
2894 ZOOM_API(const char *)
2895 ZOOM_connection_errmsg (ZOOM_connection c)
2896 {
2897     const char *msg;
2898     ZOOM_connection_error (c, &msg, 0);
2899     return msg;
2900 }
2901
2902 ZOOM_API(const char *)
2903 ZOOM_connection_addinfo (ZOOM_connection c)
2904 {
2905     const char *addinfo;
2906     ZOOM_connection_error (c, 0, &addinfo);
2907     return addinfo;
2908 }
2909
2910 ZOOM_API(const char *)
2911 ZOOM_diag_str (int error)
2912 {
2913     switch (error)
2914     {
2915     case ZOOM_ERROR_NONE:
2916         return "No error";
2917     case ZOOM_ERROR_CONNECT:
2918         return "Connect failed";
2919     case ZOOM_ERROR_MEMORY:
2920         return "Out of memory";
2921     case ZOOM_ERROR_ENCODE:
2922         return "Encoding failed";
2923     case ZOOM_ERROR_DECODE:
2924         return "Decoding failed";
2925     case ZOOM_ERROR_CONNECTION_LOST:
2926         return "Connection lost";
2927     case ZOOM_ERROR_INIT:
2928         return "Init rejected";
2929     case ZOOM_ERROR_INTERNAL:
2930         return "Internal failure";
2931     case ZOOM_ERROR_TIMEOUT:
2932         return "Timeout";
2933     case ZOOM_ERROR_UNSUPPORTED_PROTOCOL:
2934         return "Unsupported protocol";
2935     case ZOOM_ERROR_UNSUPPORTED_QUERY:
2936         return "Unsupported query type";
2937     default:
2938         return diagbib1_str (error);
2939     }
2940 }
2941
2942 ZOOM_API(int)
2943 ZOOM_connection_error_x (ZOOM_connection c, const char **cp,
2944                          const char **addinfo, const char **diagset)
2945 {
2946     int error = c->error;
2947     if (cp)
2948     {
2949         if (!c->diagset || !strcmp(c->diagset, "ZOOM"))
2950             *cp = ZOOM_diag_str(error);
2951         else if (!strcmp(c->diagset, "HTTP"))
2952             *cp = z_HTTP_errmsg(c->error);
2953         else if (!strcmp(c->diagset, "Bib-1"))
2954             *cp = ZOOM_diag_str(error);
2955         else if (!strcmp(c->diagset, "SRW"))
2956             *cp = yaz_diag_srw_str(c->error);
2957         else
2958             *cp = "Unknown error and diagnostic set";
2959     }
2960     if (addinfo)
2961         *addinfo = c->addinfo ? c->addinfo : "";
2962     if (diagset)
2963         *diagset = c->diagset ? c->diagset : "";
2964     return c->error;
2965 }
2966
2967 ZOOM_API(int)
2968 ZOOM_connection_error (ZOOM_connection c, const char **cp,
2969                        const char **addinfo)
2970 {
2971     return ZOOM_connection_error_x(c, cp, addinfo, 0);
2972 }
2973
2974 static int ZOOM_connection_do_io(ZOOM_connection c, int mask)
2975 {
2976     ZOOM_Event event = 0;
2977     int r = cs_look(c->cs);
2978     yaz_log (LOG_DEBUG, "ZOOM_connection_do_io c=%p mask=%d cs_look=%d",
2979              c, mask, r);
2980     
2981     if (r == CS_NONE)
2982     {
2983         event = ZOOM_Event_create (ZOOM_EVENT_CONNECT);
2984         set_ZOOM_error(c, ZOOM_ERROR_CONNECT, 0);
2985         do_close (c);
2986         ZOOM_connection_put_event (c, event);
2987     }
2988     else if (r == CS_CONNECT)
2989     {
2990         int ret;
2991         event = ZOOM_Event_create (ZOOM_EVENT_CONNECT);
2992
2993         ret = cs_rcvconnect (c->cs);
2994         yaz_log (LOG_DEBUG, "cs_rcvconnect returned %d", ret);
2995         if (ret == 1)
2996         {
2997             c->mask = ZOOM_SELECT_EXCEPT;
2998             if (c->cs->io_pending & CS_WANT_WRITE)
2999                 c->mask += ZOOM_SELECT_WRITE;
3000             if (c->cs->io_pending & CS_WANT_READ)
3001                 c->mask += ZOOM_SELECT_READ;
3002             ZOOM_connection_put_event (c, event);
3003         }
3004         else if (ret == 0)
3005         {
3006             ZOOM_connection_put_event (c, event);
3007             if (c->proto == PROTO_Z3950)
3008                 ZOOM_connection_send_init(c);
3009             else
3010             {
3011                 /* no init request for SRW .. */
3012                 assert (c->tasks->which == ZOOM_TASK_CONNECT);
3013                 ZOOM_connection_remove_task (c);
3014                 c->mask = 0;
3015                 ZOOM_connection_exec_task (c);
3016             }
3017             c->state = STATE_ESTABLISHED;
3018         }
3019         else
3020         {
3021             set_ZOOM_error(c, ZOOM_ERROR_CONNECT, 0);
3022             do_close (c);
3023             ZOOM_connection_put_event (c, event);
3024         }
3025     }
3026     else
3027     {
3028         if (mask & ZOOM_SELECT_READ)
3029             do_read (c);
3030         if (c->cs && (mask & ZOOM_SELECT_WRITE))
3031             do_write (c);
3032     }
3033     return 1;
3034 }
3035
3036 ZOOM_API(int)
3037 ZOOM_connection_last_event(ZOOM_connection cs)
3038 {
3039     if (!cs)
3040         return ZOOM_EVENT_NONE;
3041     return cs->last_event;
3042 }
3043
3044 ZOOM_API(int)
3045 ZOOM_event (int no, ZOOM_connection *cs)
3046 {
3047     int timeout = 5000;
3048 #if HAVE_SYS_POLL_H
3049     struct pollfd pollfds[1024];
3050     ZOOM_connection poll_cs[1024];
3051 #else
3052     struct timeval tv;
3053     fd_set input, output, except;
3054 #endif
3055     int i, r, nfds;
3056     int max_fd = 0;
3057
3058     for (i = 0; i<no; i++)
3059     {
3060         ZOOM_connection c = cs[i];
3061         ZOOM_Event event;
3062         if (c && (event = ZOOM_connection_get_event(c)))
3063         {
3064             ZOOM_Event_destroy (event);
3065             return i+1;
3066         }
3067     }
3068     for (i = 0; i<no; i++)
3069     {
3070         ZOOM_connection c = cs[i];
3071         ZOOM_Event event;
3072         if (c && ZOOM_connection_exec_task (c))
3073         {
3074             if ((event = ZOOM_connection_get_event(c)))
3075             {
3076                 ZOOM_Event_destroy (event);
3077                 return i+1;
3078             }
3079         }
3080     }
3081 #if HAVE_SYS_POLL_H
3082
3083 #else
3084     FD_ZERO (&input);
3085     FD_ZERO (&output);
3086     FD_ZERO (&except);
3087 #endif
3088     nfds = 0;
3089     for (i = 0; i<no; i++)
3090     {
3091         ZOOM_connection c = cs[i];
3092         int fd, mask;
3093         int this_timeout;
3094         
3095         if (!c)
3096             continue;
3097         fd = z3950_connection_socket(c);
3098         mask = z3950_connection_mask(c);
3099
3100         if (fd == -1)
3101             continue;
3102         if (max_fd < fd)
3103             max_fd = fd;
3104
3105         this_timeout = ZOOM_options_get_int (c->options, "timeout", -1);
3106         if (this_timeout != -1 && this_timeout < timeout)
3107             timeout = this_timeout;
3108 #if HAVE_SYS_POLL_H
3109         if (mask)
3110         {
3111             short poll_events = 0;
3112
3113             if (mask & ZOOM_SELECT_READ)
3114                 poll_events += POLLIN;
3115             if (mask & ZOOM_SELECT_WRITE)
3116                 poll_events += POLLOUT;
3117             if (mask & ZOOM_SELECT_EXCEPT)
3118                 poll_events += POLLERR;
3119             pollfds[nfds].fd = fd;
3120             pollfds[nfds].events = poll_events;
3121             pollfds[nfds].revents = 0;
3122             poll_cs[nfds] = c;
3123             nfds++;
3124         }
3125 #else
3126         if (mask & ZOOM_SELECT_READ)
3127         {
3128             FD_SET (fd, &input);
3129             nfds++;
3130         }
3131         if (mask & ZOOM_SELECT_WRITE)
3132         {
3133             FD_SET (fd, &output);
3134             nfds++;
3135         }
3136         if (mask & ZOOM_SELECT_EXCEPT)
3137         {
3138             FD_SET (fd, &except);
3139             nfds++;
3140         }
3141 #endif
3142     }
3143     if (timeout >= 5000)
3144         timeout = 30;
3145
3146     if (!nfds)
3147         return 0;
3148
3149 #if HAVE_SYS_POLL_H
3150     r = poll (pollfds, nfds, timeout * 1000);
3151     for (i = 0; i<nfds; i++)
3152     {
3153         ZOOM_connection c = poll_cs[i];
3154         if (r && c->mask)
3155         {
3156             int mask = 0;
3157             if (pollfds[i].revents & POLLIN)
3158                 mask += ZOOM_SELECT_READ;
3159             if (pollfds[i].revents & POLLOUT)
3160                 mask += ZOOM_SELECT_WRITE;
3161             if (pollfds[i].revents & POLLERR)
3162                 mask += ZOOM_SELECT_EXCEPT;
3163             if (mask)
3164                 ZOOM_connection_do_io(c, mask);
3165         }
3166         else if (r == 0 && c->mask)
3167         {
3168             ZOOM_Event event = ZOOM_Event_create(ZOOM_EVENT_TIMEOUT);
3169             /* timeout and this connection was waiting */
3170             set_ZOOM_error(c, ZOOM_ERROR_TIMEOUT, 0);
3171             do_close (c);
3172             ZOOM_connection_put_event(c, event);
3173         }
3174     }
3175 #else
3176     tv.tv_sec = timeout;
3177     tv.tv_usec = 0;
3178     yaz_log (LOG_DEBUG, "select start");
3179     r = select (max_fd+1, &input, &output, &except, &tv);
3180     yaz_log (LOG_DEBUG, "select stop, returned r=%d", r);
3181     for (i = 0; i<no; i++)
3182     {
3183         ZOOM_connection c = cs[i];
3184         int fd, mask;
3185
3186         if (!c)
3187             continue;
3188         fd = z3950_connection_socket(c);
3189         mask = 0;
3190         if (r && c->mask)
3191         {
3192             /* no timeout and real socket */
3193             if (FD_ISSET(fd, &input))
3194                 mask += ZOOM_SELECT_READ;
3195             if (FD_ISSET(fd, &output))
3196                 mask += ZOOM_SELECT_WRITE;
3197             if (FD_ISSET(fd, &except))
3198                 mask += ZOOM_SELECT_EXCEPT;
3199             if (mask)
3200                 ZOOM_connection_do_io(c, mask);
3201         }
3202         if (r == 0 && c->mask)
3203         {
3204             ZOOM_Event event = ZOOM_Event_create(ZOOM_EVENT_TIMEOUT);
3205             /* timeout and this connection was waiting */
3206             set_ZOOM_error(c, ZOOM_ERROR_TIMEOUT, 0);
3207             do_close (c);
3208             yaz_log (LOG_DEBUG, "timeout");
3209             ZOOM_connection_put_event(c, event);
3210         }
3211     }
3212 #endif
3213     for (i = 0; i<no; i++)
3214     {
3215         ZOOM_connection c = cs[i];
3216         ZOOM_Event event;
3217         if (c && (event = ZOOM_connection_get_event(c)))
3218         {
3219             ZOOM_Event_destroy (event);
3220             return i+1;
3221         }
3222     }
3223     return 0;
3224 }