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