Start work of multi target search
[metaproxy-moved-to-github.git] / src / filter_zoom.cpp
1 /* This file is part of Metaproxy.
2    Copyright (C) Index Data
3
4 Metaproxy is free software; you can redistribute it and/or modify it under
5 the terms of the GNU General Public License as published by the Free
6 Software Foundation; either version 2, or (at your option) any later
7 version.
8
9 Metaproxy is distributed in the hope that it will be useful, but WITHOUT ANY
10 WARRANTY; without even the implied warranty of MERCHANTABILITY or
11 FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
12 for more details.
13
14 You should have received a copy of the GNU General Public License
15 along with this program; if not, write to the Free Software
16 Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
17 */
18
19 #include "config.hpp"
20
21 #include <stdlib.h>
22 #include <sys/types.h>
23 #include "filter_zoom.hpp"
24 #include <metaproxy/package.hpp>
25 #include <metaproxy/util.hpp>
26 #include <metaproxy/xmlutil.hpp>
27 #include <yaz/comstack.h>
28 #include <yaz/poll.h>
29 #include "torus.hpp"
30
31 #include <libxslt/xsltutils.h>
32 #include <libxslt/transform.h>
33
34 #include <boost/thread/mutex.hpp>
35 #include <boost/thread/condition.hpp>
36
37 #include <yaz/yaz-version.h>
38 #include <yaz/tpath.h>
39 #include <yaz/srw.h>
40 #include <yaz/ccl_xml.h>
41 #include <yaz/ccl.h>
42 #include <yaz/rpn2cql.h>
43 #include <yaz/rpn2solr.h>
44 #include <yaz/pquery.h>
45 #include <yaz/cql.h>
46 #include <yaz/oid_db.h>
47 #include <yaz/diagbib1.h>
48 #include <yaz/log.h>
49 #include <yaz/zgdu.h>
50 #include <yaz/querytowrbuf.h>
51 #include <yaz/sortspec.h>
52 #include <yaz/tokenizer.h>
53 #include <yaz/zoom.h>
54 #include <yaz/otherinfo.h>
55 #include <yaz/match_glob.h>
56
57 namespace mp = metaproxy_1;
58 namespace yf = mp::filter;
59
60 namespace metaproxy_1 {
61     namespace filter {
62         class Zoom::Searchable : boost::noncopyable {
63           public:
64             std::string authentication;
65             std::string authenticationMode;
66             std::string contentAuthentication;
67             std::string cfAuth;
68             std::string cfProxy;
69             std::string cfSubDB;
70             std::string udb;
71             std::string target;
72             std::string query_encoding;
73             std::string sru;
74             std::string sru_version;
75             std::string request_syntax;
76             std::string element_set;
77             std::string record_encoding;
78             std::string transform_xsl_fname;
79             std::string transform_xsl_content;
80             std::string urlRecipe;
81             std::string contentConnector;
82             std::string sortStrategy;
83             std::string extraArgs;
84             std::string rpn2cql_fname;
85             std::string retry_on_failure;
86             bool use_turbomarc;
87             bool piggyback;
88             CCL_bibset ccl_bibset;
89             std::map<std::string, std::string> sortmap;
90             Searchable(CCL_bibset base);
91             ~Searchable();
92         };
93         class Zoom::Backend : boost::noncopyable {
94             friend class Impl;
95             friend class Frontend;
96             mp::wrbuf m_apdu_wrbuf;
97             ZOOM_connection m_connection;
98             ZOOM_resultset m_resultset;
99             SearchablePtr sptr;
100             xsltStylesheetPtr xsp;
101             std::string cproxy_host;
102             bool enable_cproxy;
103             xmlDoc *explain_doc;
104             std::string m_proxy;
105             cql_transform_t cqlt;
106             std::string retry_on_failure;
107         public:
108             Backend();
109             ~Backend();
110             void connect(std::string zurl, int *error, char **addinfo,
111                          ODR odr);
112             void search(ZOOM_query q, Odr_int *hits,
113                         int *error, char **addinfo, Z_FacetList **fl, ODR odr);
114             void present(Odr_int start, Odr_int number, ZOOM_record *recs,
115                          int *error, char **addinfo, ODR odr);
116             void set_option(const char *name, const char *value);
117             void set_option(const char *name, const char *value, size_t l);
118             void set_option(const char *name, std::string value);
119             const char *get_option(const char *name);
120             void get_zoom_error(int *error, char **addinfo, ODR odr);
121         };
122         class Zoom::Frontend : boost::noncopyable {
123             friend class Impl;
124             Impl *m_p;
125             bool m_is_virtual;
126             bool m_in_use;
127             bool enable_explain;
128             std::string session_realm;
129             std::string m_frontend_database;
130             yazpp_1::GDU m_init_gdu;
131             // BackendPtr m_backend;
132             std::list<BackendPtr> m_backend_list;
133             void handle_package(mp::Package &package);
134             void handle_search(mp::Package &package);
135             void auth(mp::Package &package, Z_InitRequest *req,
136                       int *error, char **addinfo, ODR odr);
137
138             void explain_search(mp::Package &package,
139                                 std::string &database,
140                                 int *error,
141                                 char **addinfo,
142                                 mp::odr &odr,
143                                 std::string torus_url,
144                                 std::string &torus_db,
145                                 std::string &realm);
146             void handle_present(mp::Package &package);
147             void get_backend_from_databases(mp::Package &package,
148                                             std::string &database,
149                                             int *error,
150                                             char **addinfo,
151                                             mp::odr &odr,
152                                             int *proxy_step);
153             void connect_searchable(mp::Package &package,
154                                     SearchablePtr sptr,
155                                     std::string &database,
156                                     int *error, char **addinfo,
157                                     mp::odr &odr,
158                                     int no_out_args,
159                                     const char **out_names,
160                                     const char **out_values,
161                                     std::string content_proxy,
162                                     std::string proxy,
163                                     const char *param_content_password,
164                                     const char *param_content_user,
165                                     const char *param_nocproxy,
166                                     const char *param_password,
167                                     const char *param_retry,
168                                     const char *param_user,
169                                     std::string realm);
170             bool create_content_session(mp::Package &package,
171                                         BackendPtr b,
172                                         int *error,
173                                         char **addinfo,
174                                         ODR odr,
175                                         std::string authentication,
176                                         std::string proxy,
177                                         std::string realm);
178
179             void prepare_elements(BackendPtr b,
180                                   Odr_oid *preferredRecordSyntax,
181                                   const char *element_set_name,
182                                   bool &enable_pz2_retrieval,
183                                   bool &enable_pz2_transform,
184                                   bool &enable_record_transform,
185                                   bool &assume_marc8_charset);
186
187             Z_Records *get_records(Package &package,
188                                    Odr_int start,
189                                    Odr_int number_to_present,
190                                    int *error,
191                                    char **addinfo,
192                                    Odr_int *number_of_records_returned,
193                                    ODR odr, BackendPtr b,
194                                    Odr_oid *preferredRecordSyntax,
195                                    const char *element_set_name);
196             Z_Records *get_explain_records(Package &package,
197                                            Odr_int start,
198                                            Odr_int number_to_present,
199                                            int *error,
200                                            char **addinfo,
201                                            Odr_int *number_of_records_returned,
202                                            ODR odr, BackendPtr b,
203                                            Odr_oid *preferredRecordSyntax,
204                                            const char *element_set_name);
205             bool retry(mp::Package &package,
206                        mp::odr &odr,
207                        BackendPtr b,
208                        int &error, char **addinfo,
209                        int &proxy_step, int &same_retries,
210                        int &proxy_retries);
211             void log_diagnostic(mp::Package &package,
212                                 int error, const char *addinfo);
213         public:
214             Frontend(Impl *impl);
215             ~Frontend();
216         };
217         class Zoom::Impl {
218             friend class Frontend;
219         public:
220             Impl();
221             ~Impl();
222             void process(metaproxy_1::Package & package);
223             void configure(const xmlNode * ptr, bool test_only,
224                            const char *path);
225         private:
226             void configure_local_records(const xmlNode * ptr, bool test_only);
227             bool check_proxy(const char *proxy);
228
229
230
231             FrontendPtr get_frontend(mp::Package &package);
232             void release_frontend(mp::Package &package);
233             SearchablePtr parse_torus_record(const xmlNode *ptr);
234             struct cql_node *convert_cql_fields(struct cql_node *cn, ODR odr);
235             std::map<mp::Session, FrontendPtr> m_clients;
236             boost::mutex m_mutex;
237             boost::condition m_cond_session_ready;
238             std::string torus_searchable_url;
239             std::string torus_content_url;
240             std::string torus_auth_url;
241             std::string torus_allow_ip;
242             std::string default_realm;
243             std::string torus_auth_hostname;
244             std::map<std::string,std::string> fieldmap;
245             std::string xsldir;
246             std::string file_path;
247             std::string content_proxy_server;
248             std::string content_tmp_file;
249             std::string content_config_file;
250             bool apdu_log;
251             CCL_bibset bibset;
252             std::string element_transform;
253             std::string element_raw;
254             std::string element_passthru;
255             std::string proxy;
256             xsltStylesheetPtr explain_xsp;
257             xsltStylesheetPtr record_xsp;
258             std::map<std::string,SearchablePtr> s_map;
259             std::string zoom_timeout;
260             int proxy_timeout;
261         };
262     }
263 }
264
265
266 static xmlNode *xml_node_search(xmlNode *ptr, int *num, int m)
267 {
268     while (ptr)
269     {
270         if (ptr->type == XML_ELEMENT_NODE &&
271             !strcmp((const char *) ptr->name, "recordData"))
272         {
273             (*num)++;
274             if (m == *num)
275                 return ptr;
276         }
277         else  // else: we don't want to find nested nodes
278         {
279             xmlNode *ret_node = xml_node_search(ptr->children, num, m);
280             if (ret_node)
281                 return ret_node;
282         }
283         ptr = ptr->next;
284     }
285     return 0;
286 }
287
288 // define Pimpl wrapper forwarding to Impl
289
290 yf::Zoom::Zoom() : m_p(new Impl)
291 {
292 }
293
294 yf::Zoom::~Zoom()
295 {  // must have a destructor because of boost::scoped_ptr
296 }
297
298 void yf::Zoom::configure(const xmlNode *xmlnode, bool test_only,
299                          const char *path)
300 {
301     m_p->configure(xmlnode, test_only, path);
302 }
303
304 void yf::Zoom::process(mp::Package &package) const
305 {
306     m_p->process(package);
307 }
308
309
310 // define Implementation stuff
311
312 yf::Zoom::Backend::Backend()
313 {
314     m_connection = ZOOM_connection_create(0);
315     ZOOM_connection_save_apdu_wrbuf(m_connection, m_apdu_wrbuf);
316     m_resultset = 0;
317     xsp = 0;
318     enable_cproxy = true;
319     explain_doc = 0;
320     cqlt = 0;
321 }
322
323 yf::Zoom::Backend::~Backend()
324 {
325     if (xsp)
326         xsltFreeStylesheet(xsp);
327     if (explain_doc)
328         xmlFreeDoc(explain_doc);
329     cql_transform_close(cqlt);
330     ZOOM_connection_destroy(m_connection);
331     ZOOM_resultset_destroy(m_resultset);
332 }
333
334
335 void yf::Zoom::Backend::get_zoom_error(int *error, char **addinfo,
336                                        ODR odr)
337 {
338     const char *msg = 0;
339     const char *zoom_addinfo = 0;
340     const char *dset = 0;
341     int error0 = ZOOM_connection_error_x(m_connection, &msg,
342                                          &zoom_addinfo, &dset);
343     if (error0)
344     {
345         if (!dset)
346             dset = "Unknown";
347
348         if (!strcmp(dset, "info:srw/diagnostic/1"))
349             *error = yaz_diag_srw_to_bib1(error0);
350         else if (!strcmp(dset, "Bib-1"))
351             *error = error0;
352         else if (!strcmp(dset, "ZOOM"))
353         {
354             *error = YAZ_BIB1_TEMPORARY_SYSTEM_ERROR;
355             if (error0 == ZOOM_ERROR_INIT)
356                 *error = YAZ_BIB1_INIT_AC_AUTHENTICATION_SYSTEM_ERROR;
357             else if (error0 == ZOOM_ERROR_DECODE)
358             {
359                 if (zoom_addinfo)
360                 {
361                     if (strstr(zoom_addinfo, "Authentication") ||
362                         strstr(zoom_addinfo, "authentication"))
363                         *error = YAZ_BIB1_INIT_AC_AUTHENTICATION_SYSTEM_ERROR;
364                 }
365             }
366         }
367         else
368             *error = YAZ_BIB1_TEMPORARY_SYSTEM_ERROR;
369
370         *addinfo = (char *) odr_malloc(
371             odr, 30 + strlen(dset) + strlen(msg) +
372             (zoom_addinfo ? strlen(zoom_addinfo) : 0));
373         **addinfo = '\0';
374         if (zoom_addinfo && *zoom_addinfo)
375         {
376             strcpy(*addinfo, zoom_addinfo);
377             strcat(*addinfo, " ");
378         }
379         sprintf(*addinfo + strlen(*addinfo), "(%s %d %s)", dset, error0, msg);
380     }
381 }
382
383 void yf::Zoom::Backend::connect(std::string zurl,
384                                 int *error, char **addinfo,
385                                 ODR odr)
386 {
387     size_t h = zurl.find_first_of('#');
388     if (h != std::string::npos)
389         zurl.erase(h);
390     ZOOM_connection_connect(m_connection, zurl.length() ? zurl.c_str() : 0, 0);
391     get_zoom_error(error, addinfo, odr);
392
393 }
394
395 void yf::Zoom::Backend::search(ZOOM_query q, Odr_int *hits,
396                                int *error, char **addinfo, Z_FacetList **flp,
397                                ODR odr)
398 {
399     ZOOM_resultset_destroy(m_resultset);
400     m_resultset = 0;
401     if (*flp)
402     {
403         WRBUF w = wrbuf_alloc();
404         yaz_facet_list_to_wrbuf(w, *flp);
405         set_option("facets", wrbuf_cstr(w));
406         wrbuf_destroy(w);
407     }
408     m_resultset = ZOOM_connection_search(m_connection, q);
409     get_zoom_error(error, addinfo, odr);
410     if (*error == 0)
411         *hits = ZOOM_resultset_size(m_resultset);
412     else
413         *hits = 0;
414     *flp = 0;
415     size_t num_facets = ZOOM_resultset_facets_size(m_resultset);
416     if (num_facets > 0)
417     {
418         size_t i;
419         Z_FacetList *fl = (Z_FacetList *) odr_malloc(odr, sizeof(*fl));
420         fl->elements = (Z_FacetField **)
421             odr_malloc(odr, num_facets * sizeof(*fl->elements));
422         for (i = 0; i < num_facets; i++)
423         {
424             ZOOM_facet_field ff =
425                 ZOOM_resultset_get_facet_field_by_index(m_resultset, i);
426             if (!ff)
427                 break;
428             Z_AttributeList *al = (Z_AttributeList *)
429                 odr_malloc(odr, sizeof(*al));
430             al->num_attributes = 1;
431             al->attributes = (Z_AttributeElement **)
432                 odr_malloc(odr, sizeof(*al->attributes));
433             Z_AttributeElement *ae = al->attributes[0] = (Z_AttributeElement *)
434                 odr_malloc(odr, sizeof(**al->attributes));
435             ae->attributeSet = 0;
436             ae->attributeType = odr_intdup(odr, 1);
437             ae->which = Z_AttributeValue_complex;
438             ae->value.complex = (Z_ComplexAttribute *)
439                 odr_malloc(odr, sizeof(*ae->value.complex));
440             ae->value.complex->num_list = 1;
441             ae->value.complex->list = (Z_StringOrNumeric **)
442                 odr_malloc(odr, sizeof(**ae->value.complex->list));
443             ae->value.complex->list[0] = (Z_StringOrNumeric *)
444                 odr_malloc(odr, sizeof(**ae->value.complex->list));
445             ae->value.complex->list[0]->which = Z_StringOrNumeric_string;
446             ae->value.complex->list[0]->u.string =
447                 odr_strdup(odr, ZOOM_facet_field_name(ff));
448             ae->value.complex->num_semanticAction = 0;
449             ae->value.complex->semanticAction = 0;
450
451             int num_terms = ZOOM_facet_field_term_count(ff);
452             fl->elements[i] = (Z_FacetField *)
453                 odr_malloc(odr, sizeof(Z_FacetField));
454             fl->elements[i]->attributes = al;
455             fl->elements[i]->num_terms = num_terms;
456             fl->elements[i]->terms = (Z_FacetTerm **)
457                 odr_malloc(odr, num_terms * sizeof(Z_FacetTerm *));
458             int j;
459             for (j = 0; j < num_terms; j++)
460             {
461                 int freq;
462                 const char *a_term = ZOOM_facet_field_get_term(ff, j, &freq);
463                 Z_FacetTerm *ft = (Z_FacetTerm *) odr_malloc(odr, sizeof(*ft));
464                 ft->term = z_Term_create(odr, Z_Term_general, a_term,
465                                          strlen(a_term));
466                 ft->count = odr_intdup(odr, freq);
467
468                 fl->elements[i]->terms[j] = ft;
469             }
470         }
471         fl->num = i;
472         *flp = fl;
473     }
474 }
475
476 void yf::Zoom::Backend::present(Odr_int start, Odr_int number,
477                                 ZOOM_record *recs,
478                                 int *error, char **addinfo, ODR odr)
479 {
480     ZOOM_resultset_records(m_resultset, recs, start, number);
481     get_zoom_error(error, addinfo, odr);
482 }
483
484
485 void yf::Zoom::Backend::set_option(const char *name, const char *value, size_t l)
486 {
487     ZOOM_connection_option_setl(m_connection, name, value, l);
488 }
489
490 void yf::Zoom::Backend::set_option(const char *name, const char *value)
491 {
492     ZOOM_connection_option_set(m_connection, name, value);
493     if (m_resultset)
494         ZOOM_resultset_option_set(m_resultset, name, value);
495 }
496
497 void yf::Zoom::Backend::set_option(const char *name, std::string value)
498 {
499     set_option(name, value.c_str());
500 }
501
502 const char *yf::Zoom::Backend::get_option(const char *name)
503 {
504     return ZOOM_connection_option_get(m_connection, name);
505 }
506
507 yf::Zoom::Searchable::Searchable(CCL_bibset base)
508 {
509     piggyback = true;
510     use_turbomarc = true;
511     sortStrategy = "embed";
512     retry_on_failure = "1"; // existing default (should have been false)
513     ccl_bibset = ccl_qual_dup(base);
514 }
515
516 yf::Zoom::Searchable::~Searchable()
517 {
518     ccl_qual_rm(&ccl_bibset);
519 }
520
521 yf::Zoom::Frontend::Frontend(Impl *impl) :
522     m_p(impl), m_is_virtual(false), m_in_use(true), enable_explain(false)
523 {
524 }
525
526 yf::Zoom::Frontend::~Frontend()
527 {
528 }
529
530 yf::Zoom::FrontendPtr yf::Zoom::Impl::get_frontend(mp::Package &package)
531 {
532     boost::mutex::scoped_lock lock(m_mutex);
533
534     std::map<mp::Session,yf::Zoom::FrontendPtr>::iterator it;
535
536     while(true)
537     {
538         it = m_clients.find(package.session());
539         if (it == m_clients.end())
540             break;
541
542         if (!it->second->m_in_use)
543         {
544             it->second->m_in_use = true;
545             return it->second;
546         }
547         m_cond_session_ready.wait(lock);
548     }
549     FrontendPtr f(new Frontend(this));
550     m_clients[package.session()] = f;
551     f->m_in_use = true;
552     return f;
553 }
554
555 void yf::Zoom::Impl::release_frontend(mp::Package &package)
556 {
557     boost::mutex::scoped_lock lock(m_mutex);
558     std::map<mp::Session,yf::Zoom::FrontendPtr>::iterator it;
559
560     it = m_clients.find(package.session());
561     if (it != m_clients.end())
562     {
563         if (package.session().is_closed())
564         {
565             m_clients.erase(it);
566         }
567         else
568         {
569             it->second->m_in_use = false;
570         }
571         m_cond_session_ready.notify_all();
572     }
573 }
574
575 yf::Zoom::Impl::Impl() :
576     apdu_log(false), element_transform("pz2") , element_raw("raw") ,
577     element_passthru("F"),
578     zoom_timeout("40"), proxy_timeout(1)
579 {
580     bibset = ccl_qual_mk();
581
582     explain_xsp = 0;
583     record_xsp = 0;
584     srand((unsigned int) time(0));
585 }
586
587 yf::Zoom::Impl::~Impl()
588 {
589     if (explain_xsp)
590         xsltFreeStylesheet(explain_xsp);
591     ccl_qual_rm(&bibset);
592 }
593
594 yf::Zoom::SearchablePtr yf::Zoom::Impl::parse_torus_record(const xmlNode *ptr)
595 {
596     Zoom::SearchablePtr s(new Searchable(bibset));
597
598     for (ptr = ptr->children; ptr; ptr = ptr->next)
599     {
600         if (ptr->type != XML_ELEMENT_NODE)
601             continue;
602         if (!strcmp((const char *) ptr->name, "layer"))
603             ptr = ptr->children;
604         else if (!strcmp((const char *) ptr->name,
605                          "authentication"))
606         {
607             s->authentication = mp::xml::get_text(ptr);
608         }
609         else if (!strcmp((const char *) ptr->name,
610                          "authenticationMode"))
611         {
612             s->authenticationMode = mp::xml::get_text(ptr);
613         }
614         else if (!strcmp((const char *) ptr->name,
615                          "contentAuthentication"))
616         {
617             s->contentAuthentication = mp::xml::get_text(ptr);
618         }
619         else if (!strcmp((const char *) ptr->name,
620                          "cfAuth"))
621         {
622             s->cfAuth = mp::xml::get_text(ptr);
623         }
624         else if (!strcmp((const char *) ptr->name,
625                          "cfProxy"))
626         {
627             s->cfProxy = mp::xml::get_text(ptr);
628         }
629         else if (!strcmp((const char *) ptr->name,
630                          "cfSubDB"))
631         {
632             s->cfSubDB = mp::xml::get_text(ptr);
633         }
634         else if (!strcmp((const char *) ptr->name,
635                          "contentConnector"))
636         {
637             s->contentConnector = mp::xml::get_text(ptr);
638         }
639         else if (!strcmp((const char *) ptr->name, "udb"))
640         {
641             s->udb = mp::xml::get_text(ptr);
642         }
643         else if (!strcmp((const char *) ptr->name, "zurl"))
644         {
645             s->target = mp::xml::get_text(ptr);
646         }
647         else if (!strcmp((const char *) ptr->name, "sru"))
648         {
649             s->sru = mp::xml::get_text(ptr);
650         }
651         else if (!strcmp((const char *) ptr->name, "SRUVersion") ||
652                  !strcmp((const char *) ptr->name, "sruVersion"))
653         {
654             s->sru_version = mp::xml::get_text(ptr);
655         }
656         else if (!strcmp((const char *) ptr->name,
657                          "queryEncoding"))
658         {
659             s->query_encoding = mp::xml::get_text(ptr);
660         }
661         else if (!strcmp((const char *) ptr->name,
662                          "piggyback"))
663         {
664             s->piggyback = mp::xml::get_bool(ptr, true);
665         }
666         else if (!strcmp((const char *) ptr->name,
667                          "requestSyntax"))
668         {
669             s->request_syntax = mp::xml::get_text(ptr);
670         }
671         else if (!strcmp((const char *) ptr->name,
672                          "elementSet"))
673         {
674             s->element_set = mp::xml::get_text(ptr);
675         }
676         else if (!strcmp((const char *) ptr->name,
677                          "recordEncoding"))
678         {
679             s->record_encoding = mp::xml::get_text(ptr);
680         }
681         else if (!strcmp((const char *) ptr->name,
682                          "transform"))
683         {
684             s->transform_xsl_fname = mp::xml::get_text(ptr);
685         }
686         else if (!strcmp((const char *) ptr->name,
687                          "literalTransform"))
688         {
689             s->transform_xsl_content = mp::xml::get_text(ptr);
690         }
691         else if (!strcmp((const char *) ptr->name,
692                          "urlRecipe"))
693         {
694             s->urlRecipe = mp::xml::get_text(ptr);
695         }
696         else if (!strcmp((const char *) ptr->name,
697                          "useTurboMarc"))
698         {
699             ; // useTurboMarc is ignored
700         }
701         else if (!strncmp((const char *) ptr->name,
702                           "cclmap_", 7))
703         {
704             std::string value = mp::xml::get_text(ptr);
705             if (value.length() > 0)
706             {
707                 ccl_qual_fitem(s->ccl_bibset, value.c_str(),
708                                (const char *) ptr->name + 7);
709             }
710         }
711         else if (!strncmp((const char *) ptr->name,
712                           "sortmap_", 8))
713         {
714             std::string value = mp::xml::get_text(ptr);
715             s->sortmap[(const char *) ptr->name + 8] = value;
716         }
717         else if (!strcmp((const char *) ptr->name,
718                           "sortStrategy"))
719         {
720             s->sortStrategy = mp::xml::get_text(ptr);
721         }
722         else if (!strcmp((const char *) ptr->name,
723                           "extraArgs"))
724         {
725             s->extraArgs = mp::xml::get_text(ptr);
726         }
727         else if (!strcmp((const char *) ptr->name, "rpn2cql"))
728             s->rpn2cql_fname = mp::xml::get_text(ptr);
729         else if (!strcmp((const char *) ptr->name,
730                           "retryOnFailure"))
731         {
732             s->retry_on_failure = mp::xml::get_text(ptr);
733         }
734     }
735     return s;
736 }
737
738 void yf::Zoom::Impl::configure_local_records(const xmlNode *ptr, bool test_only)
739 {
740     while (ptr && ptr->type != XML_ELEMENT_NODE)
741         ptr = ptr->next;
742
743     if (ptr)
744     {
745         if (!strcmp((const char *) ptr->name, "records"))
746         {
747             for (ptr = ptr->children; ptr; ptr = ptr->next)
748             {
749                 if (ptr->type != XML_ELEMENT_NODE)
750                     continue;
751                 if (!strcmp((const char *) ptr->name, "record"))
752                 {
753                     SearchablePtr s = parse_torus_record(ptr);
754                     if (s)
755                     {
756                         std::string udb = s->udb;
757                         if (udb.length())
758                             s_map[s->udb] = s;
759                         else
760                         {
761                             throw mp::filter::FilterException
762                                 ("No udb for local torus record");
763                         }
764                     }
765                 }
766                 else
767                 {
768                     throw mp::filter::FilterException
769                         ("Bad element "
770                          + std::string((const char *) ptr->name)
771                          + " in zoom filter inside element "
772                          "<torus><records>");
773                 }
774             }
775         }
776         else
777         {
778             throw mp::filter::FilterException
779                 ("Bad element "
780                  + std::string((const char *) ptr->name)
781                  + " in zoom filter inside element <torus>");
782         }
783     }
784 }
785
786 void yf::Zoom::Impl::configure(const xmlNode *ptr, bool test_only,
787                                const char *path)
788 {
789     std::string explain_xslt_fname;
790     std::string record_xslt_fname;
791
792     if (path && *path)
793     {
794         file_path = path;
795     }
796     for (ptr = ptr->children; ptr; ptr = ptr->next)
797     {
798         if (ptr->type != XML_ELEMENT_NODE)
799             continue;
800         else if (!strcmp((const char *) ptr->name, "torus"))
801         {
802             const struct _xmlAttr *attr;
803             for (attr = ptr->properties; attr; attr = attr->next)
804             {
805                 if (!strcmp((const char *) attr->name, "url"))
806                     torus_searchable_url = mp::xml::get_text(attr->children);
807                 else if (!strcmp((const char *) attr->name, "content_url"))
808                     torus_content_url = mp::xml::get_text(attr->children);
809                 else if (!strcmp((const char *) attr->name, "auth_url"))
810                     torus_auth_url = mp::xml::get_text(attr->children);
811                 else if (!strcmp((const char *) attr->name, "allow_ip"))
812                     torus_allow_ip = mp::xml::get_text(attr->children);
813                 else if (!strcmp((const char *) attr->name, "realm"))
814                     default_realm = mp::xml::get_text(attr->children);
815                 else if (!strcmp((const char *) attr->name, "auth_hostname"))
816                     torus_auth_hostname = mp::xml::get_text(attr->children);
817                 else if (!strcmp((const char *) attr->name, "xsldir"))
818                     xsldir = mp::xml::get_text(attr->children);
819                 else if (!strcmp((const char *) attr->name, "element_transform"))
820                     element_transform = mp::xml::get_text(attr->children);
821                 else if (!strcmp((const char *) attr->name, "element_raw"))
822                     element_raw = mp::xml::get_text(attr->children);
823                 else if (!strcmp((const char *) attr->name, "element_passthru"))
824                     element_passthru = mp::xml::get_text(attr->children);
825                 else if (!strcmp((const char *) attr->name, "proxy"))
826                     proxy = mp::xml::get_text(attr->children);
827                 else if (!strcmp((const char *) attr->name, "explain_xsl"))
828                     explain_xslt_fname = mp::xml::get_text(attr->children);
829                 else if (!strcmp((const char *) attr->name, "record_xsl"))
830                     record_xslt_fname = mp::xml::get_text(attr->children);
831                 else
832                     throw mp::filter::FilterException(
833                         "Bad attribute " + std::string((const char *)
834                                                        attr->name));
835             }
836             // If content_url is not given, use value of searchable, to
837             // ensure backwards compatibility
838             if (!torus_content_url.length())
839                 torus_content_url = torus_searchable_url;
840             configure_local_records(ptr->children, test_only);
841         }
842         else if (!strcmp((const char *) ptr->name, "cclmap"))
843         {
844             const char *addinfo = 0;
845             ccl_xml_config(bibset, ptr, &addinfo);
846         }
847         else if (!strcmp((const char *) ptr->name, "fieldmap"))
848         {
849             const struct _xmlAttr *attr;
850             std::string ccl_field;
851             std::string cql_field;
852             for (attr = ptr->properties; attr; attr = attr->next)
853             {
854                 if (!strcmp((const char *) attr->name, "ccl"))
855                     ccl_field = mp::xml::get_text(attr->children);
856                 else if (!strcmp((const char *) attr->name, "cql"))
857                     cql_field = mp::xml::get_text(attr->children);
858                 else
859                     throw mp::filter::FilterException(
860                         "Bad attribute " + std::string((const char *)
861                                                        attr->name));
862             }
863             if (cql_field.length())
864                 fieldmap[cql_field] = ccl_field;
865         }
866         else if (!strcmp((const char *) ptr->name, "contentProxy"))
867         {
868             const struct _xmlAttr *attr;
869             for (attr = ptr->properties; attr; attr = attr->next)
870             {
871                 if (!strcmp((const char *) attr->name, "server"))
872                 {
873                     yaz_log(YLOG_WARN,
874                             "contentProxy's server attribute is deprecated");
875                     yaz_log(YLOG_LOG,
876                             "Specify config_file instead. For example:");
877                     yaz_log(YLOG_LOG,
878                             " content_file=\"/etc/cf-proxy/cproxy.cfg\"");
879                     content_proxy_server = mp::xml::get_text(attr->children);
880                 }
881                 else if (!strcmp((const char *) attr->name, "tmp_file"))
882                     content_tmp_file = mp::xml::get_text(attr->children);
883                 else if (!strcmp((const char *) attr->name, "config_file"))
884                     content_config_file = mp::xml::get_text(attr->children);
885                 else
886                     throw mp::filter::FilterException(
887                         "Bad attribute " + std::string((const char *)
888                                                        attr->name));
889             }
890         }
891         else if (!strcmp((const char *) ptr->name, "log"))
892         {
893             const struct _xmlAttr *attr;
894             for (attr = ptr->properties; attr; attr = attr->next)
895             {
896                 if (!strcmp((const char *) attr->name, "apdu"))
897                     apdu_log = mp::xml::get_bool(attr->children, false);
898                 else
899                     throw mp::filter::FilterException(
900                         "Bad attribute " + std::string((const char *)
901                                                        attr->name));
902             }
903         }
904         else if (!strcmp((const char *) ptr->name, "zoom"))
905         {
906             const struct _xmlAttr *attr;
907             for (attr = ptr->properties; attr; attr = attr->next)
908             {
909                 if (!strcmp((const char *) attr->name, "timeout"))
910                     zoom_timeout = mp::xml::get_text(attr->children);
911                 else if (!strcmp((const char *) attr->name, "proxy_timeout"))
912                     proxy_timeout = mp::xml::get_int(attr->children, 1);
913                 else
914                     throw mp::filter::FilterException(
915                         "Bad attribute " + std::string((const char *)
916                                                        attr->name));
917             }
918         }
919         else
920         {
921             throw mp::filter::FilterException
922                 ("Bad element "
923                  + std::string((const char *) ptr->name)
924                  + " in zoom filter");
925         }
926     }
927
928     if (explain_xslt_fname.length())
929     {
930         const char *path = 0;
931
932         if (xsldir.length())
933             path = xsldir.c_str();
934         else
935             path = file_path.c_str();
936
937         char fullpath[1024];
938         char *cp = yaz_filepath_resolve(explain_xslt_fname.c_str(),
939                                         path, 0, fullpath);
940         if (!cp)
941         {
942             throw mp::filter::FilterException
943                 ("Cannot read XSLT " + explain_xslt_fname);
944         }
945
946         xmlDoc *xsp_doc = xmlParseFile(cp);
947         if (!xsp_doc)
948         {
949             throw mp::filter::FilterException
950                 ("Cannot parse XSLT " + explain_xslt_fname);
951         }
952
953         explain_xsp = xsltParseStylesheetDoc(xsp_doc);
954         if (!explain_xsp)
955         {
956             xmlFreeDoc(xsp_doc);
957             throw mp::filter::FilterException
958                 ("Cannot parse XSLT " + explain_xslt_fname);
959
960         }
961     }
962
963     if (record_xslt_fname.length())
964     {
965         const char *path = 0;
966
967         if (xsldir.length())
968             path = xsldir.c_str();
969         else
970             path = file_path.c_str();
971
972         char fullpath[1024];
973         char *cp = yaz_filepath_resolve(record_xslt_fname.c_str(),
974                                         path, 0, fullpath);
975         if (!cp)
976         {
977             throw mp::filter::FilterException
978                 ("Cannot read XSLT " + record_xslt_fname);
979         }
980
981         xmlDoc *xsp_doc = xmlParseFile(cp);
982         if (!xsp_doc)
983         {
984             throw mp::filter::FilterException
985                 ("Cannot parse XSLT " + record_xslt_fname);
986         }
987
988         record_xsp = xsltParseStylesheetDoc(xsp_doc);
989         if (!record_xsp)
990         {
991             xmlFreeDoc(xsp_doc);
992             throw mp::filter::FilterException
993                 ("Cannot parse XSLT " + record_xslt_fname);
994
995         }
996     }
997 }
998
999 bool yf::Zoom::Frontend::create_content_session(mp::Package &package,
1000                                                 BackendPtr b,
1001                                                 int *error, char **addinfo,
1002                                                 ODR odr,
1003                                                 std::string authentication,
1004                                                 std::string proxy,
1005                                                 std::string realm)
1006 {
1007     if (b->sptr->contentConnector.length())
1008     {
1009         std::string proxyhostname;
1010         std::string tmp_file;
1011         bool legacy_format = false;
1012
1013         if (m_p->content_proxy_server.length())
1014         {
1015             proxyhostname = m_p->content_proxy_server;
1016             legacy_format = true;
1017         }
1018
1019         if (m_p->content_tmp_file.length())
1020             tmp_file = m_p->content_tmp_file;
1021
1022         if (m_p->content_config_file.length())
1023         {
1024             FILE *inf = fopen(m_p->content_config_file.c_str(), "r");
1025             if (inf)
1026             {
1027                 char buf[1024];
1028                 while (fgets(buf, sizeof(buf)-1, inf))
1029                 {
1030                     char *cp;
1031                     cp = strchr(buf, '#');
1032                     if (cp)
1033                         *cp = '\0';
1034                     cp = strchr(buf, '\n');
1035                     if (cp)
1036                         *cp = '\0';
1037                     cp = strchr(buf, ':');
1038                     if (cp)
1039                     {
1040                         char *cp1 = cp;
1041                         while (cp1 != buf && cp1[-1] == ' ')
1042                             cp1--;
1043                         *cp1 = '\0';
1044                         cp++;
1045                         while (*cp == ' ')
1046                             cp++;
1047                         if (!strcmp(buf, "proxyhostname"))
1048                             proxyhostname = cp;
1049                         if (!strcmp(buf, "sessiondir") && *cp)
1050                         {
1051                             if (cp[strlen(cp)-1] == '/')
1052                                 cp[strlen(cp)-1] = '\0';
1053                             tmp_file = std::string(cp) + std::string("/cf.XXXXXX.p");
1054                         }
1055                     }
1056                 }
1057                 fclose(inf);
1058             }
1059             else
1060             {
1061                 package.log("zoom", YLOG_WARN|YLOG_ERRNO,
1062                             "unable to open content config %s",
1063                             m_p->content_config_file.c_str());
1064                 *error = YAZ_BIB1_TEMPORARY_SYSTEM_ERROR;
1065                 *addinfo = (char *)  odr_malloc(odr, 70 + tmp_file.length());
1066                 sprintf(*addinfo, "zoom: unable to open content config %s",
1067                         m_p->content_config_file.c_str());
1068                 return false;
1069             }
1070         }
1071
1072         if (proxyhostname.length() == 0)
1073         {
1074             package.log("zoom", YLOG_WARN, "no proxyhostname");
1075             return true;
1076         }
1077         if (tmp_file.length() == 0)
1078         {
1079             package.log("zoom", YLOG_WARN, "no tmp_file");
1080             return true;
1081         }
1082
1083         char *fname = xstrdup(tmp_file.c_str());
1084         char *xx = strstr(fname, "XXXXXX");
1085         if (!xx)
1086         {
1087             package.log("zoom", YLOG_WARN, "bad tmp_file %s", tmp_file.c_str());
1088             *error = YAZ_BIB1_TEMPORARY_SYSTEM_ERROR;
1089             *addinfo = (char *)  odr_malloc(odr, 60 + tmp_file.length());
1090             sprintf(*addinfo, "zoom: bad format of content tmp_file: %s",
1091                     tmp_file.c_str());
1092             xfree(fname);
1093             return false;
1094         }
1095         char tmp_char = xx[6];
1096         sprintf(xx, "%06d", ((unsigned) rand()) % 1000000);
1097         if (legacy_format)
1098             b->cproxy_host = std::string(xx) + "." + proxyhostname;
1099         else
1100             b->cproxy_host = proxyhostname + "/" + xx;
1101         xx[6] = tmp_char;
1102
1103         FILE *file = fopen(fname, "w");
1104         if (!file)
1105         {
1106             package.log("zoom", YLOG_WARN|YLOG_ERRNO, "create %s", fname);
1107             *error = YAZ_BIB1_TEMPORARY_SYSTEM_ERROR;
1108             *addinfo = (char *) odr_malloc(odr, 50 + strlen(fname));
1109             sprintf(*addinfo, "zoom: could not create %s", fname);
1110             xfree(fname);
1111             return false;
1112         }
1113         mp::wrbuf w;
1114         wrbuf_puts(w, "#content_proxy\n");
1115         wrbuf_printf(w, "connector: %s\n", b->sptr->contentConnector.c_str());
1116         if (authentication.length())
1117             wrbuf_printf(w, "auth: %s\n", authentication.c_str());
1118         if (proxy.length())
1119             wrbuf_printf(w, "proxy: %s\n", proxy.c_str());
1120         if (realm.length())
1121             wrbuf_printf(w, "realm: %s\n", realm.c_str());
1122
1123         fwrite(w.buf(), 1, w.len(), file);
1124         fclose(file);
1125         package.log("zoom", YLOG_LOG, "content file: %s", fname);
1126         xfree(fname);
1127     }
1128     return true;
1129 }
1130
1131 void yf::Zoom::Frontend::connect_searchable(mp::Package &package,
1132                                             SearchablePtr sptr,
1133                                             std::string &database,
1134                                             int *error, char **addinfo,
1135                                             mp::odr &odr,
1136                                             int no_out_args,
1137                                             const char **out_names,
1138                                             const char **out_values,
1139                                             std::string content_proxy,
1140                                             std::string proxy,
1141                                             const char *param_content_password,
1142                                             const char *param_content_user,
1143                                             const char *param_nocproxy,
1144                                             const char *param_password,
1145                                             const char *param_retry,
1146                                             const char *param_user,
1147                                             std::string realm)
1148 {
1149     xsltStylesheetPtr xsp = 0;
1150     if (sptr->transform_xsl_content.length())
1151     {
1152         xmlDoc *xsp_doc = xmlParseMemory(sptr->transform_xsl_content.c_str(),
1153                                          sptr->transform_xsl_content.length());
1154         if (!xsp_doc)
1155         {
1156             *error = YAZ_BIB1_TEMPORARY_SYSTEM_ERROR;
1157             *addinfo = odr_strdup(odr, "zoom: xmlParseMemory failed "
1158                                   "for literalTransform XSL");
1159             return;
1160         }
1161         xsp = xsltParseStylesheetDoc(xsp_doc);
1162         if (!xsp)
1163         {
1164             *error = YAZ_BIB1_DATABASE_DOES_NOT_EXIST;
1165             *addinfo =
1166                 odr_strdup(odr,"zoom: xsltParseStylesheetDoc failed "
1167                            "for literalTransform XSL");
1168             xmlFreeDoc(xsp_doc);
1169             return;
1170         }
1171     }
1172     else if (sptr->transform_xsl_fname.length())
1173     {
1174         const char *path = 0;
1175
1176         if (m_p->xsldir.length())
1177             path = m_p->xsldir.c_str();
1178         else
1179             path = m_p->file_path.c_str();
1180         std::string fname;
1181
1182         char fullpath[1024];
1183         char *cp = yaz_filepath_resolve(sptr->transform_xsl_fname.c_str(),
1184                                         path, 0, fullpath);
1185         if (cp)
1186             fname.assign(cp);
1187         else
1188         {
1189             *error = YAZ_BIB1_TEMPORARY_SYSTEM_ERROR;
1190             *addinfo = (char *)
1191                 odr_malloc(odr, 40 + sptr->transform_xsl_fname.length());
1192             sprintf(*addinfo, "zoom: could not open file %s",
1193                     sptr->transform_xsl_fname.c_str());
1194             return;
1195         }
1196         xmlDoc *xsp_doc = xmlParseFile(fname.c_str());
1197         if (!xsp_doc)
1198         {
1199             *error = YAZ_BIB1_TEMPORARY_SYSTEM_ERROR;
1200             *addinfo = (char *) odr_malloc(odr, 50 + fname.length());
1201             sprintf(*addinfo, "zoom: xmlParseFile failed for file %s",
1202                     fname.c_str());
1203             return;
1204         }
1205         xsp = xsltParseStylesheetDoc(xsp_doc);
1206         if (!xsp)
1207         {
1208             *error = YAZ_BIB1_TEMPORARY_SYSTEM_ERROR;
1209             *addinfo = (char *) odr_malloc(odr, 50 + fname.length());
1210             sprintf(*addinfo, "zoom: xsltParseStylesheetDoc failed "
1211                     "for file %s", fname.c_str());
1212             xmlFreeDoc(xsp_doc);
1213             return;
1214         }
1215     }
1216
1217     cql_transform_t cqlt = 0;
1218     if (sptr->rpn2cql_fname.length())
1219     {
1220         char fullpath[1024];
1221         char *cp = yaz_filepath_resolve(sptr->rpn2cql_fname.c_str(),
1222                                         m_p->file_path.c_str(), 0, fullpath);
1223         if (cp)
1224             cqlt = cql_transform_open_fname(fullpath);
1225     }
1226     else
1227         cqlt = cql_transform_create();
1228
1229     if (!cqlt)
1230     {
1231         *error = YAZ_BIB1_TEMPORARY_SYSTEM_ERROR;
1232         *addinfo = odr_strdup(odr, "zoom: missing/invalid cql2rpn file");
1233         xsltFreeStylesheet(xsp);
1234         return;
1235     }
1236
1237     BackendPtr b(new Backend);
1238
1239     b->cqlt = cqlt;
1240     b->sptr = sptr;
1241     b->xsp = xsp;
1242     b->enable_cproxy = param_nocproxy ? false : true;
1243
1244     if (param_retry)
1245         b->retry_on_failure = param_retry;
1246     else
1247         b->retry_on_failure = b->sptr->retry_on_failure;
1248
1249     if (sptr->query_encoding.length())
1250         b->set_option("rpnCharset", sptr->query_encoding);
1251
1252     std::string extraArgs = sptr->extraArgs;
1253
1254     b->set_option("timeout", m_p->zoom_timeout.c_str());
1255
1256     if (m_p->apdu_log)
1257         b->set_option("apdulog", "1");
1258
1259     if (sptr->piggyback && sptr->sru.length())
1260         b->set_option("count", "1"); /* some SRU servers INSIST on getting
1261                                         maximumRecords > 0 */
1262     b->set_option("piggyback", sptr->piggyback ? "1" : "0");
1263
1264     std::string authentication = sptr->authentication;
1265     if (param_user)
1266     {
1267         authentication = std::string(param_user);
1268         if (param_password)
1269             authentication += "/" + std::string(param_password);
1270     }
1271     std::string content_authentication = sptr->contentAuthentication;
1272     if (param_content_user)
1273     {
1274         content_authentication = std::string(param_content_user);
1275         if (param_content_password)
1276             content_authentication += "/" + std::string(param_content_password);
1277     }
1278
1279     if (proxy.length() == 0)
1280         proxy = sptr->cfProxy;
1281     b->m_proxy = proxy;
1282
1283     if (sptr->cfAuth.length())
1284     {
1285         // A CF target
1286         b->set_option("user", sptr->cfAuth);
1287         if (param_user)
1288         {
1289             out_names[no_out_args] = "user";
1290             out_values[no_out_args++] = odr_strdup(odr, param_user);
1291             if (param_password)
1292             {
1293                 out_names[no_out_args] = "password";
1294                 out_values[no_out_args++] = odr_strdup(odr, param_password);
1295             }
1296         }
1297         else if (authentication.length())
1298         {
1299             size_t found = authentication.find('/');
1300             if (found != std::string::npos)
1301             {
1302                 out_names[no_out_args] = "user";
1303                 out_values[no_out_args++] =
1304                     odr_strdup(odr, authentication.substr(0, found).c_str());
1305
1306                 out_names[no_out_args] = "password";
1307                 out_values[no_out_args++] =
1308                     odr_strdup(odr, authentication.substr(found+1).c_str());
1309             }
1310             else
1311             {
1312                 out_names[no_out_args] = "user";
1313                 out_values[no_out_args++] =
1314                     odr_strdup(odr, authentication.c_str());
1315             }
1316         }
1317         if (proxy.length())
1318         {
1319             out_names[no_out_args] = "proxy";
1320             out_values[no_out_args++] = odr_strdup(odr, proxy.c_str());
1321         }
1322         if (sptr->cfSubDB.length())
1323         {
1324             out_names[no_out_args] = "subdatabase";
1325             out_values[no_out_args++] = odr_strdup(odr, sptr->cfSubDB.c_str());
1326         }
1327         if (!param_nocproxy && b->sptr->contentConnector.length())
1328             param_nocproxy = "1";
1329
1330         if (param_nocproxy)
1331         {
1332             out_names[no_out_args] = "nocproxy";
1333             out_values[no_out_args++] = odr_strdup(odr, param_nocproxy);
1334         }
1335     }
1336     else
1337     {
1338         const char *auth = authentication.c_str();
1339         const char *cp1 = strchr(auth, ' ');
1340         if (!cp1 && sptr->sru.length())
1341             cp1 =  strchr(auth, '/');
1342         if (!cp1)
1343         {
1344             /* Z39.50 user/password style, or no password for SRU */
1345             b->set_option("user", auth);
1346         }
1347         else
1348         {
1349             /* now consider group as well */
1350             const char *cp2 = strchr(cp1 + 1, ' ');
1351
1352             b->set_option("user", auth, cp1 - auth);
1353             if (!cp2)
1354                 b->set_option("password", cp1 + 1);
1355             else
1356             {
1357                 b->set_option("group", cp1 + 1, cp2 - cp1 - 1);
1358                 b->set_option("password", cp2 + 1);
1359             }
1360         }
1361         if (sptr->authenticationMode.length())
1362             b->set_option("authenticationMode", sptr->authenticationMode);
1363         if (proxy.length())
1364             b->set_option("proxy", proxy);
1365     }
1366     if (extraArgs.length())
1367         b->set_option("extraArgs", extraArgs);
1368
1369     std::string url(sptr->target);
1370     if (sptr->sru.length())
1371     {
1372         b->set_option("sru", sptr->sru);
1373         if (url.find("://") == std::string::npos)
1374             url = "http://" + url;
1375         if (sptr->sru_version.length())
1376             b->set_option("sru_version", sptr->sru_version);
1377     }
1378     if (no_out_args)
1379     {
1380         char *x_args = 0;
1381         out_names[no_out_args] = 0; // terminate list
1382
1383         yaz_array_to_uri(&x_args, odr, (char **) out_names,
1384                          (char **) out_values);
1385         url += "," + std::string(x_args);
1386     }
1387     package.log("zoom", YLOG_LOG, "url: %s", url.c_str());
1388     b->connect(url, error, addinfo, odr);
1389     if (*error == 0 && b->enable_cproxy)
1390         create_content_session(package, b, error, addinfo, odr,
1391                                content_authentication.length() ?
1392                                content_authentication : authentication,
1393                                content_proxy.length() ? content_proxy : proxy,
1394                                realm);
1395     if (*error == 0)
1396     {
1397         m_backend_list.push_back(b);
1398     }
1399 }
1400
1401 void yf::Zoom::Frontend::get_backend_from_databases(
1402     mp::Package &package,
1403     std::string &database, int *error, char **addinfo, mp::odr &odr,
1404     int *proxy_step)
1405 {
1406     std::string proxy;
1407     bool connection_reuse = false;
1408
1409     if (m_backend_list.size() > 0 && !enable_explain &&
1410         m_frontend_database == database)
1411     {
1412         // TODO !! proxy = m_backend->m_proxy;
1413         connection_reuse = true;
1414     }
1415
1416     std::string input_args;
1417     std::string torus_db;
1418     size_t db_arg_pos = database.find(',');
1419     if (db_arg_pos != std::string::npos)
1420     {
1421         torus_db = database.substr(0, db_arg_pos);
1422         input_args = database.substr(db_arg_pos + 1);
1423     }
1424     else
1425         torus_db = database;
1426
1427     std::string content_proxy;
1428     std::string realm = session_realm;
1429     if (realm.length() == 0)
1430         realm = m_p->default_realm;
1431
1432     const char *param_user = 0;
1433     const char *param_password = 0;
1434     const char *param_content_user = 0;
1435     const char *param_content_password = 0;
1436     const char *param_nocproxy = 0;
1437     const char *param_retry = 0;
1438     int no_parms = 0;
1439
1440     char **names;
1441     char **values;
1442     int no_out_args = 0;
1443     if (input_args.length())
1444         no_parms = yaz_uri_to_array(input_args.c_str(),
1445                                     odr, &names, &values);
1446     // adding 10 because we'll be adding other URL args
1447     const char **out_names = (const char **)
1448         odr_malloc(odr, (10 + no_parms) * sizeof(*out_names));
1449     const char **out_values = (const char **)
1450         odr_malloc(odr, (10 + no_parms) * sizeof(*out_values));
1451
1452     // may be changed if it's a content connection
1453     std::string torus_url = m_p->torus_searchable_url;
1454     int i;
1455     for (i = 0; i < no_parms; i++)
1456     {
1457         const char *name = names[i];
1458         const char *value = values[i];
1459         assert(name);
1460         assert(value);
1461         if (!strcmp(name, "user"))
1462             param_user = value;
1463         else if (!strcmp(name, "password"))
1464             param_password = value;
1465         else if (!strcmp(name, "content-user"))
1466             param_content_user = value;
1467         else if (!strcmp(name, "content-password"))
1468             param_content_password = value;
1469         else if (!strcmp(name, "content-proxy"))
1470             content_proxy = value;
1471         else if (!strcmp(name, "nocproxy"))
1472             param_nocproxy = value;
1473         else if (!strcmp(name, "retry"))
1474             param_retry = value;
1475         else if (!strcmp(name, "proxy"))
1476         {
1477             char **dstr;
1478             int dnum = 0;
1479             nmem_strsplit(((ODR) odr)->mem, ",", value, &dstr, &dnum);
1480             if (connection_reuse)
1481             {
1482                 // find the step after our current proxy
1483                 int i;
1484                 for (i = 0; i < dnum; i++)
1485                     if (!strcmp(proxy.c_str(), dstr[i]))
1486                         break;
1487                 if (i >= dnum - 1)
1488                     *proxy_step = 0;
1489                 else
1490                     *proxy_step = i + 1;
1491             }
1492             else
1493             {
1494                 // step is known.. Guess our proxy from it
1495                 if (*proxy_step >= dnum)
1496                     *proxy_step = 0;
1497                 else
1498                 {
1499                     proxy = dstr[*proxy_step];
1500
1501                     (*proxy_step)++;
1502                     if (*proxy_step == dnum)
1503                         *proxy_step = 0;
1504                 }
1505             }
1506         }
1507         else if (!strcmp(name, "cproxysession"))
1508         {
1509             out_names[no_out_args] = name;
1510             out_values[no_out_args++] = value;
1511             torus_url = m_p->torus_content_url;
1512         }
1513         else if (!strcmp(name, "realm") && session_realm.length() == 0)
1514             realm = value;
1515         else if (!strcmp(name, "torus_url") && session_realm.length() == 0)
1516             torus_url = value;
1517         else if (name[0] == 'x' && name[1] == '-')
1518         {
1519             out_names[no_out_args] = name;
1520             out_values[no_out_args++] = value;
1521         }
1522         else
1523         {
1524             BackendPtr notfound;
1525             char *msg = (char*) odr_malloc(odr, strlen(name) + 30);
1526             *error = YAZ_BIB1_TEMPORARY_SYSTEM_ERROR;
1527             sprintf(msg, "zoom: bad database argument: %s", name);
1528             *addinfo = msg;
1529             return;
1530         }
1531     }
1532     if (proxy.length())
1533         package.log("zoom", YLOG_LOG, "proxy: %s", proxy.c_str());
1534
1535     if (connection_reuse)
1536     {
1537         std::list<BackendPtr>::iterator it = m_backend_list.begin();
1538         for (; it != m_backend_list.end(); it++)
1539             (*it)->connect("", error, addinfo, odr);
1540         return;
1541     }
1542
1543     m_frontend_database = database;
1544     m_backend_list.clear();
1545
1546     if (torus_db.compare("IR-Explain---1") == 0)
1547         return explain_search(package, database, error, addinfo, odr, torus_url,
1548                               torus_db, realm);
1549     enable_explain = false;
1550
1551     SearchablePtr sptr;
1552
1553     std::map<std::string,SearchablePtr>::iterator it;
1554     it = m_p->s_map.find(torus_db);
1555     if (it != m_p->s_map.end())
1556     {
1557         sptr = it->second;
1558         connect_searchable(package, sptr, database, error, addinfo, odr,
1559                            no_out_args, out_names, out_values,
1560                            content_proxy, proxy,
1561                            param_content_password,
1562                            param_content_user,
1563                            param_nocproxy,
1564                            param_password,
1565                            param_retry,
1566                            param_user,
1567                            realm);
1568     }
1569     else if (torus_db == "all_local")
1570     {
1571         for (it = m_p->s_map.begin(); it != m_p->s_map.end(); it++)
1572         {
1573             sptr = it->second;
1574             connect_searchable(package, sptr, database, error, addinfo, odr,
1575                                no_out_args, out_names, out_values,
1576                                content_proxy, proxy,
1577                                param_content_password,
1578                                param_content_user,
1579                                param_nocproxy,
1580                                param_password,
1581                                param_retry,
1582                                param_user,
1583                                realm);
1584         }
1585     }
1586     else if (torus_url.length() > 0)
1587     {
1588         std::string torus_addinfo;
1589         std::string torus_query = "udb==" + torus_db;
1590         xmlDoc *doc = mp::get_searchable(package,torus_url, torus_db,
1591                                          torus_query,
1592                                          realm, m_p->proxy,
1593                                          torus_addinfo);
1594         if (!doc)
1595         {
1596             *error = YAZ_BIB1_UNSPECIFIED_ERROR;
1597             if (torus_addinfo.length())
1598                 *addinfo = odr_strdup(odr, torus_addinfo.c_str());
1599             return;
1600         }
1601         const xmlNode *ptr = xmlDocGetRootElement(doc);
1602         if (ptr && ptr->type == XML_ELEMENT_NODE)
1603         {
1604             if (!strcmp((const char *) ptr->name, "record"))
1605             {
1606                 sptr = m_p->parse_torus_record(ptr);
1607                 connect_searchable(package, sptr, database,
1608                                    error, addinfo, odr,
1609                                    no_out_args, out_names, out_values,
1610                                    content_proxy, proxy,
1611                                    param_content_password,
1612                                    param_content_user,
1613                                    param_nocproxy,
1614                                    param_password,
1615                                    param_retry,
1616                                    param_user,
1617                                    realm);
1618             }
1619             else if (!strcmp((const char *) ptr->name, "records"))
1620             {
1621                 for (ptr = ptr->children; ptr; ptr = ptr->next)
1622                 {
1623                     if (ptr->type == XML_ELEMENT_NODE
1624                         && !strcmp((const char *) ptr->name, "record"))
1625                     {
1626                         if (sptr)
1627                         {
1628                             *error = YAZ_BIB1_UNSPECIFIED_ERROR;
1629                             *addinfo = (char*)
1630                                 odr_malloc(odr, 40 + torus_db.length());
1631                             sprintf(*addinfo, "multiple records for udb=%s",
1632                                     database.c_str());
1633                             xmlFreeDoc(doc);
1634                             return;
1635                         }
1636                         sptr = m_p->parse_torus_record(ptr);
1637                         connect_searchable(package, sptr, database,
1638                                            error, addinfo, odr,
1639                                            no_out_args, out_names, out_values,
1640                                            content_proxy, proxy,
1641                                            param_content_password,
1642                                            param_content_user,
1643                                            param_nocproxy,
1644                                            param_password,
1645                                            param_retry,
1646                                            param_user,
1647                                            realm);
1648                     }
1649                 }
1650             }
1651             else
1652             {
1653                 *error = YAZ_BIB1_UNSPECIFIED_ERROR;
1654                 *addinfo = (char*) odr_malloc(
1655                     odr, 40 + strlen((const char *) ptr->name));
1656                 sprintf(*addinfo, "bad root element for torus: %s", ptr->name);
1657                 xmlFreeDoc(doc);
1658                 return;
1659             }
1660         }
1661         xmlFreeDoc(doc);
1662     }
1663
1664     if (m_backend_list.size() == 0)
1665     {
1666         *error = YAZ_BIB1_DATABASE_DOES_NOT_EXIST;
1667         *addinfo = odr_strdup(odr, torus_db.c_str());
1668     }
1669 }
1670
1671 void yf::Zoom::Frontend::prepare_elements(BackendPtr b,
1672                                           Odr_oid *preferredRecordSyntax,
1673                                           const char *element_set_name,
1674                                           bool &enable_pz2_retrieval,
1675                                           bool &enable_pz2_transform,
1676                                           bool &enable_record_transform,
1677                                           bool &assume_marc8_charset)
1678 {
1679     char oid_name_str[OID_STR_MAX];
1680     const char *syntax_name = 0;
1681
1682     if (preferredRecordSyntax &&
1683         !oid_oidcmp(preferredRecordSyntax, yaz_oid_recsyn_xml))
1684     {
1685         if (element_set_name &&
1686             !strcmp(element_set_name, m_p->element_transform.c_str()))
1687         {
1688             enable_pz2_retrieval = true;
1689             enable_pz2_transform = true;
1690         }
1691         else if (element_set_name &&
1692                  !strcmp(element_set_name, m_p->element_raw.c_str()))
1693         {
1694             enable_pz2_retrieval = true;
1695         }
1696         else if (m_p->record_xsp)
1697         {
1698             enable_pz2_retrieval = true;
1699             enable_pz2_transform = true;
1700             enable_record_transform = true;
1701         }
1702     }
1703
1704     if (enable_pz2_retrieval)
1705     {
1706         std::string configured_request_syntax = b->sptr->request_syntax;
1707         if (configured_request_syntax.length())
1708         {
1709             syntax_name = configured_request_syntax.c_str();
1710             const Odr_oid *syntax_oid =
1711                 yaz_string_to_oid(yaz_oid_std(), CLASS_RECSYN, syntax_name);
1712             if (!oid_oidcmp(syntax_oid, yaz_oid_recsyn_usmarc)
1713                 || !oid_oidcmp(syntax_oid, yaz_oid_recsyn_opac))
1714                 assume_marc8_charset = true;
1715         }
1716     }
1717     else if (preferredRecordSyntax)
1718         syntax_name =
1719             yaz_oid_to_string_buf(preferredRecordSyntax, 0, oid_name_str);
1720
1721     if (b->sptr->sru.length())
1722         syntax_name = "XML";
1723
1724     b->set_option("preferredRecordSyntax", syntax_name);
1725
1726     if (enable_pz2_retrieval)
1727     {
1728         if (element_set_name && !strcmp(element_set_name,
1729                                         m_p->element_passthru.c_str()))
1730             ;
1731         else
1732         {
1733             element_set_name = 0;
1734             if (b->sptr->element_set.length())
1735                 element_set_name = b->sptr->element_set.c_str();
1736         }
1737     }
1738
1739     b->set_option("elementSetName", element_set_name);
1740     if (b->sptr->sru.length() && element_set_name)
1741         b->set_option("schema", element_set_name);
1742 }
1743
1744 Z_Records *yf::Zoom::Frontend::get_explain_records(
1745     mp::Package &package,
1746     Odr_int start,
1747     Odr_int number_to_present,
1748     int *error,
1749     char **addinfo,
1750     Odr_int *number_of_records_returned,
1751     ODR odr,
1752     BackendPtr b,
1753     Odr_oid *preferredRecordSyntax,
1754     const char *element_set_name)
1755 {
1756     Odr_int i;
1757     Z_Records *records = 0;
1758
1759     if (!b->explain_doc)
1760     {
1761         return records;
1762     }
1763     if (number_to_present > 10000)
1764         number_to_present = 10000;
1765
1766     xmlNode *ptr = xmlDocGetRootElement(b->explain_doc);
1767
1768     Z_NamePlusRecordList *npl = (Z_NamePlusRecordList *)
1769         odr_malloc(odr, sizeof(*npl));
1770     npl->records = (Z_NamePlusRecord **)
1771         odr_malloc(odr, number_to_present * sizeof(*npl->records));
1772
1773     for (i = 0; i < number_to_present; i++)
1774     {
1775         int num = 0;
1776         xmlNode *res = xml_node_search(ptr, &num, start + i + 1);
1777         if (!res)
1778             break;
1779         xmlBufferPtr xml_buf = xmlBufferCreate();
1780         xmlNode *tmp_node = xmlCopyNode(res->children, 1);
1781         xmlNodeDump(xml_buf, tmp_node->doc, tmp_node, 0, 0);
1782
1783         Z_NamePlusRecord *npr =
1784             (Z_NamePlusRecord *) odr_malloc(odr, sizeof(*npr));
1785         npr->databaseName = odr_strdup(odr, m_frontend_database.c_str());
1786         npr->which = Z_NamePlusRecord_databaseRecord;
1787         npr->u.databaseRecord =
1788             z_ext_record_xml(odr,
1789                              (const char *) xml_buf->content, xml_buf->use);
1790         npl->records[i] = npr;
1791         xmlFreeNode(tmp_node);
1792         xmlBufferFree(xml_buf);
1793     }
1794     records = (Z_Records*) odr_malloc(odr, sizeof(*records));
1795     records->which = Z_Records_DBOSD;
1796     records->u.databaseOrSurDiagnostics = npl;
1797
1798     npl->num_records = i;
1799     *number_of_records_returned = i;
1800     return records;
1801 }
1802
1803
1804 Z_Records *yf::Zoom::Frontend::get_records(mp::Package &package,
1805                                            Odr_int start,
1806                                            Odr_int number_to_present,
1807                                            int *error,
1808                                            char **addinfo,
1809                                            Odr_int *number_of_records_returned,
1810                                            ODR odr,
1811                                            BackendPtr b,
1812                                            Odr_oid *preferredRecordSyntax,
1813                                            const char *element_set_name)
1814 {
1815     *number_of_records_returned = 0;
1816     Z_Records *records = 0;
1817     bool enable_pz2_retrieval = false; // whether target profile is used
1818     bool enable_pz2_transform = false; // whether XSLT is used as well
1819     bool assume_marc8_charset = false;
1820     bool enable_record_transform = false;
1821
1822     prepare_elements(b, preferredRecordSyntax,
1823                      element_set_name,
1824                      enable_pz2_retrieval,
1825                      enable_pz2_transform,
1826                      enable_record_transform,
1827                      assume_marc8_charset);
1828
1829     package.log("zoom", YLOG_LOG, "pz2_retrieval: %s . pz2_transform: %s",
1830                 enable_pz2_retrieval ? "yes" : "no",
1831                 enable_pz2_transform ? "yes" : "no");
1832
1833     if (start < 0 || number_to_present <=0)
1834         return records;
1835
1836     if (number_to_present > 10000)
1837         number_to_present = 10000;
1838
1839     ZOOM_record *recs = (ZOOM_record *)
1840         odr_malloc(odr, (size_t) number_to_present * sizeof(*recs));
1841
1842     b->present(start, number_to_present, recs, error, addinfo, odr);
1843
1844     int i = 0;
1845     if (!*error)
1846     {
1847         for (i = 0; i < number_to_present; i++)
1848         {
1849             if (!recs[i])
1850                 break;
1851
1852             const char *addinfo;
1853             int sur_error = ZOOM_record_error(recs[i], 0 /* msg */,
1854                                               &addinfo, 0 /* diagset */);
1855             if (sur_error ==
1856                 YAZ_BIB1_SYSTEM_ERROR_IN_PRESENTING_RECORDS && addinfo &&
1857                 !strcmp(addinfo,
1858                         "ZOOM C generated. Present phase and no records"))
1859                 break;
1860         }
1861     }
1862     if (i > 0)
1863     {  // only return records if no error and at least one record
1864
1865         const char *xsl_parms[3];
1866         mp::wrbuf cproxy_host;
1867
1868         if (b->enable_cproxy && b->cproxy_host.length())
1869         {
1870             wrbuf_puts(cproxy_host, "\"");
1871             wrbuf_puts(cproxy_host, b->cproxy_host.c_str());
1872             wrbuf_puts(cproxy_host, "/\"");
1873
1874             xsl_parms[0] = "cproxyhost";
1875             xsl_parms[1] = wrbuf_cstr(cproxy_host);
1876             xsl_parms[2] = 0;
1877         }
1878         else
1879         {
1880             xsl_parms[0] = 0;
1881         }
1882
1883         char *odr_database = odr_strdup(odr, m_frontend_database.c_str());
1884         Z_NamePlusRecordList *npl = (Z_NamePlusRecordList *)
1885             odr_malloc(odr, sizeof(*npl));
1886         *number_of_records_returned = i;
1887         npl->num_records = i;
1888         npl->records = (Z_NamePlusRecord **)
1889             odr_malloc(odr, i * sizeof(*npl->records));
1890         for (i = 0; i < npl->num_records; i++)
1891         {
1892             Z_NamePlusRecord *npr = 0;
1893             const char *addinfo;
1894
1895             int sur_error = ZOOM_record_error(recs[i], 0 /* msg */,
1896                                               &addinfo, 0 /* diagset */);
1897
1898             if (sur_error)
1899             {
1900                 log_diagnostic(package, sur_error, addinfo);
1901                 npr = zget_surrogateDiagRec(odr, odr_database, sur_error,
1902                                             addinfo);
1903             }
1904             else if (enable_pz2_retrieval)
1905             {
1906                 char rec_type_str[100];
1907                 const char *record_encoding = 0;
1908
1909                 if (b->sptr->record_encoding.length())
1910                     record_encoding = b->sptr->record_encoding.c_str();
1911                 else if (assume_marc8_charset)
1912                     record_encoding = "marc8";
1913
1914                 strcpy(rec_type_str, b->sptr->use_turbomarc ? "txml" : "xml");
1915                 if (record_encoding)
1916                 {
1917                     strcat(rec_type_str, "; charset=");
1918                     strcat(rec_type_str, record_encoding);
1919                 }
1920
1921                 package.log("zoom", YLOG_LOG, "Getting record of type %s",
1922                             rec_type_str);
1923                 int rec_len;
1924                 xmlChar *xmlrec_buf = 0;
1925                 const char *rec_buf = ZOOM_record_get(recs[i], rec_type_str,
1926                                                       &rec_len);
1927                 if (!rec_buf && !npr)
1928                 {
1929                     std::string addinfo("ZOOM_record_get failed for type ");
1930
1931                     int error = YAZ_BIB1_SYSTEM_ERROR_IN_PRESENTING_RECORDS;
1932                     addinfo += rec_type_str;
1933                     log_diagnostic(package, error, addinfo.c_str());
1934                     npr = zget_surrogateDiagRec(odr, odr_database,
1935                                                 error, addinfo.c_str());
1936                 }
1937                 else
1938                 {
1939                     package.log_write(rec_buf, rec_len);
1940                     package.log_write("\r\n", 2);
1941                 }
1942
1943                 if (rec_buf && b->xsp && enable_pz2_transform)
1944                 {
1945                     xmlDoc *rec_doc = xmlParseMemory(rec_buf, rec_len);
1946                     if (!rec_doc)
1947                     {
1948                         const char *addinfo = "xml parse failed for record";
1949                         int error = YAZ_BIB1_SYSTEM_ERROR_IN_PRESENTING_RECORDS;
1950                         log_diagnostic(package, error, addinfo);
1951                         npr = zget_surrogateDiagRec(
1952                             odr, odr_database, error, addinfo);
1953                     }
1954                     else
1955                     {
1956                         // first stage XSLT - per target
1957                         xsltStylesheetPtr xsp = b->xsp;
1958                         xmlDoc *rec_res = xsltApplyStylesheet(xsp, rec_doc,
1959                                                               xsl_parms);
1960                         // insert generated-url
1961                         if (rec_res)
1962                         {
1963                             std::string res =
1964                                 mp::xml::url_recipe_handle(rec_res,
1965                                                            b->sptr->urlRecipe);
1966                             if (res.length())
1967                             {
1968                                 xmlNode *ptr = xmlDocGetRootElement(rec_res);
1969                                 while (ptr && ptr->type != XML_ELEMENT_NODE)
1970                                     ptr = ptr->next;
1971                                 xmlNode *c =
1972                                     xmlNewChild(ptr, 0, BAD_CAST "metadata", 0);
1973                                 xmlNewProp(c, BAD_CAST "type", BAD_CAST
1974                                            "generated-url");
1975                                 xmlNode * t = xmlNewText(BAD_CAST res.c_str());
1976                                 xmlAddChild(c, t);
1977                             }
1978                         }
1979                         // second stage XSLT - common
1980                         if (rec_res && m_p->record_xsp &&
1981                             enable_record_transform)
1982                         {
1983                             xmlDoc *tmp_doc = rec_res;
1984
1985                             xsp = m_p->record_xsp;
1986                             rec_res = xsltApplyStylesheet(xsp, tmp_doc,
1987                                                           xsl_parms);
1988                             xmlFreeDoc(tmp_doc);
1989                         }
1990                         // get result out of it
1991                         if (rec_res)
1992                         {
1993                             xsltSaveResultToString(&xmlrec_buf, &rec_len,
1994                                                    rec_res, xsp);
1995                             rec_buf = (const char *) xmlrec_buf;
1996                             package.log_write(rec_buf, rec_len);
1997
1998                             xmlFreeDoc(rec_res);
1999                         }
2000                         if (!rec_buf)
2001                         {
2002                             std::string addinfo;
2003                             int error =
2004                                 YAZ_BIB1_SYSTEM_ERROR_IN_PRESENTING_RECORDS;
2005
2006                             addinfo = "xslt apply failed for "
2007                                 + b->sptr->transform_xsl_fname;
2008                             log_diagnostic(package, error, addinfo.c_str());
2009                             npr = zget_surrogateDiagRec(
2010                                 odr, odr_database, error, addinfo.c_str());
2011                         }
2012                         xmlFreeDoc(rec_doc);
2013                     }
2014                 }
2015
2016                 if (!npr)
2017                 {
2018                     if (!rec_buf)
2019                         npr = zget_surrogateDiagRec(
2020                             odr, odr_database,
2021                             YAZ_BIB1_SYSTEM_ERROR_IN_PRESENTING_RECORDS,
2022                             rec_type_str);
2023                     else
2024                     {
2025                         npr = (Z_NamePlusRecord *)
2026                             odr_malloc(odr, sizeof(*npr));
2027                         npr->databaseName = odr_database;
2028                         npr->which = Z_NamePlusRecord_databaseRecord;
2029                         npr->u.databaseRecord =
2030                             z_ext_record_xml(odr, rec_buf, rec_len);
2031                     }
2032                 }
2033                 if (xmlrec_buf)
2034                     xmlFree(xmlrec_buf);
2035             }
2036             else
2037             {
2038                 Z_External *ext =
2039                     (Z_External *) ZOOM_record_get(recs[i], "ext", 0);
2040                 if (ext)
2041                 {
2042                     npr = (Z_NamePlusRecord *) odr_malloc(odr, sizeof(*npr));
2043                     npr->databaseName = odr_database;
2044                     npr->which = Z_NamePlusRecord_databaseRecord;
2045                     npr->u.databaseRecord = ext;
2046                 }
2047                 else
2048                 {
2049                     npr = zget_surrogateDiagRec(
2050                         odr, odr_database,
2051                         YAZ_BIB1_SYSTEM_ERROR_IN_PRESENTING_RECORDS,
2052                         "ZOOM_record, type ext");
2053                 }
2054             }
2055             npl->records[i] = npr;
2056         }
2057         records = (Z_Records*) odr_malloc(odr, sizeof(*records));
2058         records->which = Z_Records_DBOSD;
2059         records->u.databaseOrSurDiagnostics = npl;
2060     }
2061     return records;
2062 }
2063
2064 struct cql_node *yf::Zoom::Impl::convert_cql_fields(struct cql_node *cn,
2065                                                     ODR odr)
2066 {
2067     struct cql_node *r = 0;
2068     if (!cn)
2069         return 0;
2070     switch (cn->which)
2071     {
2072     case CQL_NODE_ST:
2073         if (cn->u.st.index)
2074         {
2075             std::map<std::string,std::string>::const_iterator it;
2076             it = fieldmap.find(cn->u.st.index);
2077             if (it == fieldmap.end())
2078                 return cn;
2079             if (it->second.length())
2080                 cn->u.st.index = odr_strdup(odr, it->second.c_str());
2081             else
2082                 cn->u.st.index = 0;
2083         }
2084         break;
2085     case CQL_NODE_BOOL:
2086         r = convert_cql_fields(cn->u.boolean.left, odr);
2087         if (!r)
2088             r = convert_cql_fields(cn->u.boolean.right, odr);
2089         break;
2090     case CQL_NODE_SORT:
2091         r = convert_cql_fields(cn->u.sort.search, odr);
2092         break;
2093     }
2094     return r;
2095 }
2096
2097 void yf::Zoom::Frontend::log_diagnostic(mp::Package &package,
2098                                         int error, const char *addinfo)
2099 {
2100     const char *err_msg = yaz_diag_bib1_str(error);
2101     if (addinfo)
2102         package.log("zoom", YLOG_WARN, "Diagnostic %d %s: %s",
2103                     error, err_msg, addinfo);
2104     else
2105         package.log("zoom", YLOG_WARN, "Diagnostic %d %s:",
2106                     error, err_msg);
2107 }
2108
2109 void yf::Zoom::Frontend::explain_search(mp::Package &package,
2110                                         std::string &database,
2111                                         int *error,
2112                                         char **addinfo,
2113                                         mp::odr &odr,
2114                                         std::string torus_url,
2115                                         std::string &torus_db,
2116                                         std::string &realm)
2117 {
2118     BackendPtr b(new Backend);
2119
2120     enable_explain = true;
2121
2122     Z_GDU *gdu = package.request().get();
2123     Z_APDU *apdu_req = gdu->u.z3950;
2124     Z_SearchRequest *sr = apdu_req->u.searchRequest;
2125     Z_Query *query = sr->query;
2126
2127     if (!m_p->explain_xsp)
2128     {
2129         *error = YAZ_BIB1_UNSPECIFIED_ERROR;
2130         *addinfo =
2131             odr_strdup(odr, "IR-Explain---1 unsupported. "
2132                        "Torus explain_xsl not defined");
2133         return;
2134     }
2135     else if (query->which == Z_Query_type_104 &&
2136         query->u.type_104->which == Z_External_CQL)
2137     {
2138         std::string torus_addinfo;
2139         std::string torus_query(query->u.type_104->u.cql);
2140         xmlDoc *doc = mp::get_searchable(package, torus_url, "",
2141                                          torus_query,
2142                                          realm, m_p->proxy,
2143                                          torus_addinfo);
2144         if (m_p->explain_xsp)
2145         {
2146             xmlDoc *rec_res =  xsltApplyStylesheet(m_p->explain_xsp, doc, 0);
2147
2148             xmlFreeDoc(doc);
2149             doc = rec_res;
2150         }
2151         if (!doc)
2152         {
2153             *error = YAZ_BIB1_UNSPECIFIED_ERROR;
2154             if (torus_addinfo.length())
2155                 *addinfo = odr_strdup(odr, torus_addinfo.c_str());
2156         }
2157         else
2158         {
2159             xmlNode *ptr = xmlDocGetRootElement(doc);
2160             int hits = 0;
2161
2162             xml_node_search(ptr, &hits, 0);
2163
2164             Z_APDU *apdu_res = odr.create_searchResponse(apdu_req, 0, 0);
2165             apdu_res->u.searchResponse->resultCount = odr_intdup(odr, hits);
2166             package.response() = apdu_res;
2167             m_backend_list.push_back(b);
2168         }
2169         if (b->explain_doc)
2170             xmlFreeDoc(b->explain_doc);
2171         b->explain_doc = doc;
2172     }
2173     else
2174     {
2175         *error = YAZ_BIB1_QUERY_TYPE_UNSUPP;
2176         *addinfo = odr_strdup(odr, "IR-Explain---1 only supports CQL");
2177     }
2178 }
2179
2180 static bool wait_conn(COMSTACK cs, int secs)
2181 {
2182     struct yaz_poll_fd pfd;
2183
2184     yaz_poll_add(pfd.input_mask, yaz_poll_except);
2185     if (cs->io_pending & CS_WANT_WRITE)
2186         yaz_poll_add(pfd.input_mask, yaz_poll_write);
2187     if (cs->io_pending & CS_WANT_READ)
2188         yaz_poll_add(pfd.input_mask, yaz_poll_read);
2189
2190     pfd.fd = cs_fileno(cs);
2191     pfd.client_data = 0;
2192
2193     int ret = yaz_poll(&pfd, 1, secs, 0);
2194     return ret > 0;
2195 }
2196
2197 bool yf::Zoom::Impl::check_proxy(const char *proxy)
2198 {
2199     COMSTACK conn = 0;
2200     const char *uri = "http://localhost/";
2201     void *add;
2202     mp::odr odr;
2203     bool outcome = false;
2204     conn = cs_create_host_proxy(uri, 0, &add, proxy);
2205
2206     if (!conn)
2207         return false;
2208
2209     Z_GDU *gdu = z_get_HTTP_Request_uri(odr, uri, 0, 1);
2210     gdu->u.HTTP_Request->method = odr_strdup(odr, "GET");
2211
2212     if (z_GDU(odr, &gdu, 0, 0))
2213     {
2214         int len;
2215         char *buf = odr_getbuf(odr, &len, 0);
2216
2217         int ret = cs_connect(conn, add);
2218         if (ret > 0 || (ret == 0 && wait_conn(conn, 1)))
2219         {
2220             while (1)
2221             {
2222                 ret = cs_put(conn, buf, len);
2223                 if (ret != 1)
2224                     break;
2225                 if (!wait_conn(conn, proxy_timeout))
2226                     break;
2227             }
2228             if (ret == 0)
2229                 outcome = true;
2230         }
2231     }
2232     cs_close(conn);
2233     return outcome;
2234 }
2235
2236 bool yf::Zoom::Frontend::retry(mp::Package &package,
2237                                mp::odr &odr,
2238                                BackendPtr b,
2239                                int &error, char **addinfo,
2240                                int &proxy_step, int &same_retries,
2241                                int &proxy_retries)
2242 {
2243     if (b && b->m_proxy.length() && !m_p->check_proxy(b->m_proxy.c_str()))
2244     {
2245         log_diagnostic(package, error, *addinfo);
2246         package.log("zoom", YLOG_LOG, "proxy %s fails", b->m_proxy.c_str());
2247         m_backend_list.clear();
2248         if (proxy_step) // there is a failover
2249         {
2250             proxy_retries++;
2251             package.log("zoom", YLOG_WARN, "search failed: trying next proxy");
2252             return true;
2253         }
2254         error = YAZ_BIB1_PROXY_FAILURE;
2255         *addinfo = odr_strdup(odr, b->m_proxy.c_str());
2256     }
2257     else if (b && b->retry_on_failure.compare("0")
2258              && same_retries == 0 && proxy_retries == 0)
2259     {
2260         log_diagnostic(package, error, *addinfo);
2261         same_retries++;
2262         package.log("zoom", YLOG_WARN, "search failed: retry");
2263         m_backend_list.clear();
2264         proxy_step = 0;
2265         return true;
2266     }
2267     return false;
2268 }
2269
2270 void yf::Zoom::Frontend::handle_search(mp::Package &package)
2271 {
2272     Z_GDU *gdu = package.request().get();
2273     Z_APDU *apdu_req = gdu->u.z3950;
2274     Z_APDU *apdu_res = 0;
2275     mp::odr odr;
2276     Z_SearchRequest *sr = apdu_req->u.searchRequest;
2277     if (sr->num_databaseNames != 1)
2278     {
2279         int error = YAZ_BIB1_TOO_MANY_DATABASES_SPECIFIED;
2280         log_diagnostic(package, error, 0);
2281         apdu_res = odr.create_searchResponse(apdu_req, error, 0);
2282         package.response() = apdu_res;
2283         return;
2284     }
2285     int proxy_step = 0;
2286     int same_retries = 0;
2287     int proxy_retries = 0;
2288
2289 next_proxy:
2290
2291     int error = 0;
2292     char *addinfo = 0;
2293     std::string db(sr->databaseNames[0]);
2294
2295     get_backend_from_databases(package, db, &error,
2296                                &addinfo, odr, &proxy_step);
2297     if (error && m_backend_list.size() == 1)
2298     {
2299         std::list<BackendPtr>::iterator it = m_backend_list.begin();
2300         BackendPtr b = *it;
2301         if (retry(package, odr, b, error, &addinfo, proxy_step,
2302                   same_retries, proxy_retries))
2303             goto next_proxy;
2304     }
2305     if (error)
2306     {
2307         log_diagnostic(package, error, addinfo);
2308         apdu_res = odr.create_searchResponse(apdu_req, error, addinfo);
2309         package.response() = apdu_res;
2310         return;
2311     }
2312     if (m_backend_list.size() == 0 || enable_explain)
2313         return;
2314
2315     std::list<BackendPtr>::iterator it = m_backend_list.begin();
2316     for (; it != m_backend_list.end(); it++)
2317     {
2318         BackendPtr b = *it;
2319         b->set_option("setname", "default");
2320
2321         bool enable_pz2_retrieval = false;
2322         bool enable_pz2_transform = false;
2323         bool enable_record_transform = false;
2324         bool assume_marc8_charset = false;
2325         prepare_elements(b, sr->preferredRecordSyntax, 0 /*element_set_name */,
2326                          enable_pz2_retrieval,
2327                          enable_pz2_transform,
2328                          enable_record_transform,
2329                          assume_marc8_charset);
2330
2331         Odr_int hits = 0;
2332         Z_Query *query = sr->query;
2333         mp::wrbuf ccl_wrbuf;
2334         mp::wrbuf pqf_wrbuf;
2335         std::string sortkeys;
2336
2337         if (query->which == Z_Query_type_1 || query->which == Z_Query_type_101)
2338         {
2339             // RPN
2340             yaz_rpnquery_to_wrbuf(pqf_wrbuf, query->u.type_1);
2341         }
2342         else if (query->which == Z_Query_type_2)
2343         {
2344             // CCL
2345             wrbuf_write(ccl_wrbuf, (const char *) query->u.type_2->buf,
2346                         query->u.type_2->len);
2347         }
2348         else if (query->which == Z_Query_type_104 &&
2349                  query->u.type_104->which == Z_External_CQL)
2350         {
2351             // CQL
2352             const char *cql = query->u.type_104->u.cql;
2353             CQL_parser cp = cql_parser_create();
2354             int r = cql_parser_string(cp, cql);
2355             package.log("zoom", YLOG_LOG, "CQL: %s", cql);
2356             if (r)
2357             {
2358                 cql_parser_destroy(cp);
2359                 error = YAZ_BIB1_MALFORMED_QUERY;
2360                 const char *addinfo = "CQL syntax error";
2361                 log_diagnostic(package, error, addinfo);
2362                 apdu_res =
2363                     odr.create_searchResponse(apdu_req, error, addinfo);
2364                 package.response() = apdu_res;
2365                 return;
2366             }
2367             struct cql_node *cn = cql_parser_result(cp);
2368             struct cql_node *cn_error = m_p->convert_cql_fields(cn, odr);
2369             if (cn_error)
2370             {
2371                 // hopefully we are getting a ptr to a index+relation+term node
2372                 error = YAZ_BIB1_UNSUPP_USE_ATTRIBUTE;
2373                 addinfo = 0;
2374                 if (cn_error->which == CQL_NODE_ST)
2375                     addinfo = cn_error->u.st.index;
2376                 log_diagnostic(package, error, addinfo);
2377                 apdu_res = odr.create_searchResponse(apdu_req, error, addinfo);
2378                 package.response() = apdu_res;
2379                 cql_parser_destroy(cp);
2380                 return;
2381             }
2382             r = cql_to_ccl(cn, wrbuf_vp_puts,  ccl_wrbuf);
2383             if (r)
2384             {
2385                 error = YAZ_BIB1_MALFORMED_QUERY;
2386                 const char *addinfo = "CQL to CCL conversion error";
2387                 log_diagnostic(package, error, addinfo);
2388                 apdu_res = odr.create_searchResponse(apdu_req, error, addinfo);
2389                 package.response() = apdu_res;
2390                 cql_parser_destroy(cp);
2391                 return;
2392             }
2393             mp::wrbuf sru_sortkeys_wrbuf;
2394             if (cql_sortby_to_sortkeys(cn, wrbuf_vp_puts, sru_sortkeys_wrbuf))
2395             {
2396                 error = YAZ_BIB1_ILLEGAL_SORT_RELATION;
2397                 const char *addinfo = "CQL to CCL sortby conversion";
2398                 log_diagnostic(package, error, addinfo);
2399                 apdu_res = odr.create_searchResponse(apdu_req, error, addinfo);
2400                 package.response() = apdu_res;
2401                 cql_parser_destroy(cp);
2402                 return;
2403             }
2404             mp::wrbuf sort_spec_wrbuf;
2405             yaz_srw_sortkeys_to_sort_spec(wrbuf_cstr(sru_sortkeys_wrbuf),
2406                                           sort_spec_wrbuf);
2407             yaz_tok_cfg_t tc = yaz_tok_cfg_create();
2408             yaz_tok_parse_t tp =
2409                 yaz_tok_parse_buf(tc, wrbuf_cstr(sort_spec_wrbuf));
2410             yaz_tok_cfg_destroy(tc);
2411             /* go through sortspec and map fields */
2412             int token = yaz_tok_move(tp);
2413             while (token != YAZ_TOK_EOF)
2414             {
2415                 if (token == YAZ_TOK_STRING)
2416                 {
2417                     const char *field = yaz_tok_parse_string(tp);
2418                     std::map<std::string,std::string>::iterator it;
2419                     it = b->sptr->sortmap.find(field);
2420                     if (it != b->sptr->sortmap.end())
2421                         sortkeys += it->second;
2422                     else
2423                         sortkeys += field;
2424                 }
2425                 sortkeys += " ";
2426                 token = yaz_tok_move(tp);
2427                 if (token == YAZ_TOK_STRING)
2428                 {
2429                     sortkeys += yaz_tok_parse_string(tp);
2430                 }
2431                 if (token != YAZ_TOK_EOF)
2432                 {
2433                     sortkeys += " ";
2434                     token = yaz_tok_move(tp);
2435                 }
2436             }
2437             yaz_tok_parse_destroy(tp);
2438             cql_parser_destroy(cp);
2439         }
2440         else
2441         {
2442             error = YAZ_BIB1_QUERY_TYPE_UNSUPP;
2443             const char *addinfo = 0;
2444             log_diagnostic(package, error, addinfo);
2445             apdu_res =  odr.create_searchResponse(apdu_req, error, addinfo);
2446             package.response() = apdu_res;
2447             return;
2448         }
2449         if (ccl_wrbuf.len())
2450         {
2451             // CCL to PQF
2452             assert(pqf_wrbuf.len() == 0);
2453             int cerror, cpos;
2454             struct ccl_rpn_node *cn;
2455             package.log("zoom", YLOG_LOG, "CCL: %s", wrbuf_cstr(ccl_wrbuf));
2456             cn = ccl_find_str(b->sptr->ccl_bibset, wrbuf_cstr(ccl_wrbuf),
2457                               &cerror, &cpos);
2458             if (!cn)
2459             {
2460                 char *addinfo = odr_strdup_null(odr, ccl_err_msg(cerror));
2461                 error = YAZ_BIB1_MALFORMED_QUERY;
2462                 switch (cerror)
2463                 {
2464                 case CCL_ERR_UNKNOWN_QUAL:
2465                 case CCL_ERR_TRUNC_NOT_LEFT:
2466                 case CCL_ERR_TRUNC_NOT_RIGHT:
2467                 case CCL_ERR_TRUNC_NOT_BOTH:
2468 #ifdef CCL_ERR_TRUNC_NOT_EMBED
2469                 case CCL_ERR_TRUNC_NOT_EMBED:
2470 #endif
2471 #ifdef CCL_ERR_TRUNC_NOT_SINGLE
2472                 case CCL_ERR_TRUNC_NOT_SINGLE:
2473 #endif
2474                     error = YAZ_BIB1_UNSUPP_SEARCH;
2475                     break;
2476                 }
2477                 log_diagnostic(package, error, addinfo);
2478                 apdu_res = odr.create_searchResponse(apdu_req, error, addinfo);
2479                 package.response() = apdu_res;
2480                 return;
2481             }
2482             ccl_pquery(pqf_wrbuf, cn);
2483             package.log("zoom", YLOG_LOG, "RPN: %s", wrbuf_cstr(pqf_wrbuf));
2484             ccl_rpn_delete(cn);
2485         }
2486         assert(pqf_wrbuf.len());
2487         ZOOM_query q = ZOOM_query_create();
2488         ZOOM_query_sortby2(q, b->sptr->sortStrategy.c_str(), sortkeys.c_str());
2489         Z_FacetList *fl = 0;
2490         // Facets for request.. And later for reponse
2491         if (!fl)
2492             fl = yaz_oi_get_facetlist(&sr->otherInfo);
2493         if (!fl)
2494             fl = yaz_oi_get_facetlist(&sr->additionalSearchInfo);
2495         if (b->get_option("sru"))
2496         {
2497             Z_RPNQuery *zquery;
2498             zquery = p_query_rpn(odr, wrbuf_cstr(pqf_wrbuf));
2499             mp::wrbuf wrb_cql;
2500             mp::wrbuf wrb_addinfo;
2501             if (!strcmp(b->get_option("sru"), "solr"))
2502                 error = solr_transform_rpn2solr_stream_r(b->cqlt, wrb_addinfo,
2503                                                      wrbuf_vp_puts, wrb_cql,
2504                                                          zquery);
2505             else
2506                 error = cql_transform_rpn2cql_stream_r(b->cqlt, wrb_addinfo,
2507                                                        wrbuf_vp_puts, wrb_cql,
2508                                                        zquery);
2509             if (error)
2510             {
2511                 log_diagnostic(package, error, wrb_addinfo.c_str_null());
2512                 apdu_res = odr.create_searchResponse(apdu_req, error,
2513                                                      wrb_addinfo.c_str_null());
2514                 package.response() = apdu_res;
2515                 return;
2516             }
2517             ZOOM_query_cql(q, wrb_cql.c_str());
2518             package.log("zoom", YLOG_LOG, "search CQL: %s", wrb_cql.c_str());
2519             b->search(q, &hits, &error, &addinfo, &fl, odr);
2520             ZOOM_query_destroy(q);
2521         }
2522         else
2523         {
2524             ZOOM_query_prefix(q, pqf_wrbuf.c_str());
2525             package.log("zoom", YLOG_LOG, "search PQF: %s", pqf_wrbuf.c_str());
2526             b->search(q, &hits, &error, &addinfo, &fl, odr);
2527             ZOOM_query_destroy(q);
2528         }
2529         if (error && m_backend_list.size() == 1)
2530         {
2531             if (retry(package, odr, b, error, &addinfo, proxy_step,
2532                       same_retries, proxy_retries))
2533                 goto next_proxy;
2534         }
2535         const char *element_set_name = 0;
2536         Odr_int number_to_present = 0;
2537         if (!error)
2538             mp::util::piggyback_sr(sr, hits,
2539                                    number_to_present, &element_set_name);
2540         Odr_int number_of_records_returned = 0;
2541         Z_Records *records = get_records(
2542             package,
2543             0, number_to_present, &error, &addinfo,
2544             &number_of_records_returned, odr, b, sr->preferredRecordSyntax,
2545             element_set_name);
2546         if (error)
2547             log_diagnostic(package, error, addinfo);
2548         apdu_res = odr.create_searchResponse(apdu_req, error, addinfo);
2549         if (records)
2550         {
2551             apdu_res->u.searchResponse->records = records;
2552             apdu_res->u.searchResponse->numberOfRecordsReturned =
2553                 odr_intdup(odr, number_of_records_returned);
2554         }
2555         apdu_res->u.searchResponse->resultCount = odr_intdup(odr, hits);
2556         if (fl)
2557             yaz_oi_set_facetlist(
2558                 &apdu_res->u.searchResponse->additionalSearchInfo,
2559                 odr, fl);
2560         package.response() = apdu_res;
2561     }
2562 }
2563
2564 #if 0
2565 void yf::Zoom::Frontend::handle_present(mp::Package &package)
2566 {
2567     Z_GDU *gdu = package.request().get();
2568     Z_APDU *apdu_req = gdu->u.z3950;
2569     Z_APDU *apdu_res = 0;
2570     Z_PresentRequest *pr = apdu_req->u.presentRequest;
2571
2572     mp::odr odr;
2573     if (!m_backend)
2574     {
2575         package.response() = odr.create_presentResponse(
2576             apdu_req, YAZ_BIB1_SPECIFIED_RESULT_SET_DOES_NOT_EXIST, 0);
2577         return;
2578     }
2579     const char *element_set_name = 0;
2580     Z_RecordComposition *comp = pr->recordComposition;
2581     if (comp && comp->which != Z_RecordComp_simple)
2582     {
2583         package.response() = odr.create_presentResponse(
2584             apdu_req,
2585             YAZ_BIB1_PRESENT_COMP_SPEC_PARAMETER_UNSUPP, 0);
2586         return;
2587     }
2588     if (comp && comp->u.simple->which == Z_ElementSetNames_generic)
2589         element_set_name = comp->u.simple->u.generic;
2590     Odr_int number_of_records_returned = 0;
2591     int error = 0;
2592     char *addinfo = 0;
2593
2594     if (m_backend->enable_explain)
2595     {
2596         Z_Records *records =
2597             get_explain_records(
2598                 package,
2599                 *pr->resultSetStartPoint - 1, *pr->numberOfRecordsRequested,
2600                 &error, &addinfo, &number_of_records_returned, odr, m_backend,
2601                 pr->preferredRecordSyntax, element_set_name);
2602
2603         apdu_res = odr.create_presentResponse(apdu_req, error, addinfo);
2604         if (records)
2605         {
2606             apdu_res->u.presentResponse->records = records;
2607             apdu_res->u.presentResponse->numberOfRecordsReturned =
2608                 odr_intdup(odr, number_of_records_returned);
2609         }
2610         package.response() = apdu_res;
2611     }
2612     else
2613     {
2614         Z_Records *records =
2615             get_records(package,
2616                         *pr->resultSetStartPoint - 1, *pr->numberOfRecordsRequested,
2617                         &error, &addinfo, &number_of_records_returned, odr, m_backend,
2618                         pr->preferredRecordSyntax, element_set_name);
2619
2620         apdu_res = odr.create_presentResponse(apdu_req, error, addinfo);
2621         if (records)
2622         {
2623             apdu_res->u.presentResponse->records = records;
2624             apdu_res->u.presentResponse->numberOfRecordsReturned =
2625                 odr_intdup(odr, number_of_records_returned);
2626         }
2627         package.response() = apdu_res;
2628     }
2629 }
2630 #endif
2631
2632 void yf::Zoom::Frontend::handle_package(mp::Package &package)
2633 {
2634     Z_GDU *gdu = package.request().get();
2635     if (!gdu)
2636         ;
2637     else if (gdu->which == Z_GDU_Z3950)
2638     {
2639         Z_APDU *apdu_req = gdu->u.z3950;
2640         std::list<BackendPtr>::iterator it;
2641         for (it = m_backend_list.begin(); it != m_backend_list.end(); it++)
2642             wrbuf_rewind((*it)->m_apdu_wrbuf);
2643         if (apdu_req->which == Z_APDU_initRequest)
2644         {
2645             mp::odr odr;
2646             package.response() = odr.create_close(
2647                 apdu_req,
2648                 Z_Close_protocolError,
2649                 "double init");
2650         }
2651         else if (apdu_req->which == Z_APDU_searchRequest)
2652         {
2653             handle_search(package);
2654         }
2655 #if 0
2656         else if (apdu_req->which == Z_APDU_presentRequest)
2657         {
2658             handle_present(package);
2659         }
2660 #endif
2661         else
2662         {
2663             mp::odr odr;
2664             package.response() = odr.create_close(
2665                 apdu_req,
2666                 Z_Close_protocolError,
2667                 "zoom filter cannot handle this APDU");
2668             package.session().close();
2669         }
2670         for (it = m_backend_list.begin(); it != m_backend_list.end(); it++)
2671         {
2672             WRBUF w = (*it)->m_apdu_wrbuf;
2673             package.log_write(wrbuf_buf(w), wrbuf_len(w));
2674         }
2675     }
2676     else
2677     {
2678         package.session().close();
2679     }
2680 }
2681
2682 std::string escape_cql_term(std::string inp)
2683 {
2684     std::string res;
2685     size_t l = inp.length();
2686     size_t i;
2687     for (i = 0; i < l; i++)
2688     {
2689         if (strchr("*?^\"", inp[i]))
2690             res += "\\";
2691         res += inp[i];
2692     }
2693     return res;
2694 }
2695
2696 void yf::Zoom::Frontend::auth(mp::Package &package, Z_InitRequest *req,
2697                               int *error, char **addinfo, ODR odr)
2698 {
2699     if (m_p->torus_auth_url.length() == 0)
2700         return;
2701
2702     std::string user;
2703     std::string password;
2704     if (req->idAuthentication)
2705     {
2706         Z_IdAuthentication *auth = req->idAuthentication;
2707         switch (auth->which)
2708         {
2709         case Z_IdAuthentication_open:
2710             if (auth->u.open)
2711             {
2712                 const char *cp = strchr(auth->u.open, '/');
2713                 if (cp)
2714                 {
2715                     user.assign(auth->u.open, cp - auth->u.open);
2716                     password.assign(cp + 1);
2717                 }
2718             }
2719             break;
2720         case Z_IdAuthentication_idPass:
2721             if (auth->u.idPass->userId)
2722                 user.assign(auth->u.idPass->userId);
2723             if (auth->u.idPass->password)
2724                 password.assign(auth->u.idPass->password);
2725             break;
2726         }
2727     }
2728
2729     Z_OtherInformation **oi = &req->otherInfo;
2730     const char *ip_cstr =
2731         yaz_oi_get_string_oid(oi, yaz_oid_userinfo_client_ip, 1, 0);
2732     std::string ip;
2733     if (ip_cstr)
2734         ip = ip_cstr;
2735     else
2736         ip = package.origin().get_address();
2737
2738     yaz_log(YLOG_LOG, "IP=%s", ip.c_str());
2739
2740     {
2741         NMEM nmem = nmem_create();
2742         char **darray;
2743         int i, num;
2744         nmem_strsplit_blank(nmem, m_p->torus_allow_ip.c_str(), &darray, &num);
2745         for (i = 0; i < num; i++)
2746         {
2747             yaz_log(YLOG_LOG, "check against %s+%s", darray[i], ip.c_str());
2748             if (yaz_match_glob(darray[i], ip.c_str()))
2749                 break;
2750         }
2751         nmem_destroy(nmem);
2752         if (i < num)
2753             return;  /* allow this IP */
2754     }
2755     std::string torus_query;
2756     int failure_code;
2757
2758     if (user.length() && password.length())
2759     {
2760         torus_query = "userName==\"" + escape_cql_term(user) +
2761             "\" and password==\"" + escape_cql_term(password) + "\"";
2762         failure_code = YAZ_BIB1_INIT_AC_BAD_USERID_AND_OR_PASSWORD;
2763     }
2764     else
2765     {
2766         torus_query = "ipRanges encloses/net.ipaddress \"";
2767         torus_query += escape_cql_term(std::string(ip));
2768         torus_query += "\"";
2769
2770         if (m_p->torus_auth_hostname.length())
2771         {
2772             torus_query += " AND hostName == \"";
2773             torus_query += escape_cql_term(m_p->torus_auth_hostname);
2774             torus_query += "\"";
2775         }
2776         failure_code = YAZ_BIB1_INIT_AC_BLOCKED_NETWORK_ADDRESS;
2777     }
2778
2779     std::string dummy_db;
2780     std::string dummy_realm;
2781     std::string torus_addinfo;
2782     xmlDoc *doc = mp::get_searchable(package, m_p->torus_auth_url, dummy_db,
2783                                      torus_query, dummy_realm, m_p->proxy,
2784                                      torus_addinfo);
2785     if (!doc)
2786     {
2787         // something fundamental broken in lookup.
2788         *error = YAZ_BIB1_TEMPORARY_SYSTEM_ERROR;
2789         if (torus_addinfo.length())
2790             *addinfo = odr_strdup(odr, torus_addinfo.c_str());
2791         return;
2792     }
2793     const xmlNode *ptr = xmlDocGetRootElement(doc);
2794     if (ptr && ptr->type == XML_ELEMENT_NODE)
2795     {
2796         if (strcmp((const char *) ptr->name, "records") == 0)
2797         {
2798             ptr = ptr->children;
2799             while (ptr && ptr->type != XML_ELEMENT_NODE)
2800                 ptr = ptr->next;
2801         }
2802         if (ptr && strcmp((const char *) ptr->name, "record") == 0)
2803         {
2804             ptr = ptr->children;
2805             while (ptr && ptr->type != XML_ELEMENT_NODE)
2806                 ptr = ptr->next;
2807         }
2808         if (ptr && strcmp((const char *) ptr->name, "layer") == 0)
2809         {
2810             ptr = ptr->children;
2811             while (ptr && ptr->type != XML_ELEMENT_NODE)
2812                 ptr = ptr->next;
2813         }
2814         while (ptr)
2815         {
2816             if (ptr && ptr->type == XML_ELEMENT_NODE &&
2817                 !strcmp((const char *) ptr->name, "identityId"))
2818                 break;
2819             ptr = ptr->next;
2820         }
2821     }
2822     if (!ptr)
2823     {
2824         *error = failure_code;
2825         return;
2826     }
2827     session_realm = mp::xml::get_text(ptr);
2828 }
2829
2830 void yf::Zoom::Impl::process(mp::Package &package)
2831 {
2832     FrontendPtr f = get_frontend(package);
2833     Z_GDU *gdu = package.request().get();
2834
2835     if (f->m_is_virtual)
2836     {
2837         f->handle_package(package);
2838     }
2839     else if (gdu && gdu->which == Z_GDU_Z3950 && gdu->u.z3950->which ==
2840              Z_APDU_initRequest)
2841     {
2842         Z_InitRequest *req = gdu->u.z3950->u.initRequest;
2843         f->m_init_gdu = gdu;
2844
2845         mp::odr odr;
2846         Z_APDU *apdu = odr.create_initResponse(gdu->u.z3950, 0, 0);
2847         Z_InitResponse *resp = apdu->u.initResponse;
2848
2849         int i;
2850         static const int masks[] = {
2851             Z_Options_search,
2852             Z_Options_present,
2853             -1
2854         };
2855         for (i = 0; masks[i] != -1; i++)
2856             if (ODR_MASK_GET(req->options, masks[i]))
2857                 ODR_MASK_SET(resp->options, masks[i]);
2858
2859         static const int versions[] = {
2860             Z_ProtocolVersion_1,
2861             Z_ProtocolVersion_2,
2862             Z_ProtocolVersion_3,
2863             -1
2864         };
2865         for (i = 0; versions[i] != -1; i++)
2866             if (ODR_MASK_GET(req->protocolVersion, versions[i]))
2867                 ODR_MASK_SET(resp->protocolVersion, versions[i]);
2868             else
2869                 break;
2870
2871         *resp->preferredMessageSize = *req->preferredMessageSize;
2872         *resp->maximumRecordSize = *req->maximumRecordSize;
2873
2874         int error = 0;
2875         char *addinfo = 0;
2876         f->auth(package, req, &error, &addinfo, odr);
2877         if (error)
2878         {
2879             resp->userInformationField =
2880                 zget_init_diagnostics(odr, error, addinfo);
2881             *resp->result = 0;
2882             package.session().close();
2883         }
2884         else
2885             f->m_is_virtual = true;
2886         package.response() = apdu;
2887     }
2888     else
2889         package.move();
2890
2891     release_frontend(package);
2892 }
2893
2894
2895 static mp::filter::Base* filter_creator()
2896 {
2897     return new mp::filter::Zoom;
2898 }
2899
2900 extern "C" {
2901     struct metaproxy_1_filter_struct metaproxy_1_filter_zoom = {
2902         0,
2903         "zoom",
2904         filter_creator
2905     };
2906 }
2907
2908
2909 /*
2910  * Local variables:
2911  * c-basic-offset: 4
2912  * c-file-style: "Stroustrup"
2913  * indent-tabs-mode: nil
2914  * End:
2915  * vim: shiftwidth=4 tabstop=8 expandtab
2916  */
2917