Odr_oct updates - YAZ 4 and 5.
[metaproxy-moved-to-github.git] / src / util.cpp
1 /* This file is part of Metaproxy.
2    Copyright (C) 2005-2013 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 #include <metaproxy/util.hpp>
21
22 #include <yaz/odr.h>
23 #include <yaz/comstack.h>
24 #include <yaz/pquery.h>
25 #include <yaz/otherinfo.h>
26 #include <yaz/querytowrbuf.h>
27 #include <yaz/oid_db.h>
28 #include <yaz/srw.h>
29
30 #include <iostream>
31
32 namespace mp = metaproxy_1;
33
34 // Doxygen doesn't like mp::util, so we use this instead
35 namespace mp_util = metaproxy_1::util;
36
37 const char *
38 mp_util::record_composition_to_esn(Z_RecordComposition *comp)
39 {
40     if (comp && comp->which == Z_RecordComp_complex)
41     {
42         if (comp->u.complex->generic
43             && comp->u.complex->generic->elementSpec
44             && (comp->u.complex->generic->elementSpec->which ==
45                 Z_ElementSpec_elementSetName))
46             return comp->u.complex->generic->elementSpec->u.elementSetName;
47     }
48     else if (comp && comp->which == Z_RecordComp_simple &&
49              comp->u.simple->which == Z_ElementSetNames_generic)
50         return comp->u.simple->u.generic;
51     return 0;
52 }
53
54
55
56 std::string mp_util::http_header_value(const Z_HTTP_Header* header,
57                                        const std::string name)
58 {
59     while (header && header->name
60            && std::string(header->name) !=  name)
61         header = header->next;
62
63     if (header && header->name && std::string(header->name) == name
64         && header->value)
65         return std::string(header->value);
66
67     return std::string();
68 }
69
70 std::string mp_util::http_headers_debug(const Z_HTTP_Request &http_req)
71 {
72     std::string message("<html>\n<body>\n<h1>"
73                         "Metaproxy SRUtoZ3950 filter"
74                         "</h1>\n");
75
76     message += "<h3>HTTP Info</h3><br/>\n";
77     message += "<p>\n";
78     message += "<b>Method: </b> " + std::string(http_req.method) + "<br/>\n";
79     message += "<b>Version:</b> " + std::string(http_req.version) + "<br/>\n";
80     message += "<b>Path:   </b> " + std::string(http_req.path) + "<br/>\n";
81
82     message += "<b>Content-Type:</b>"
83         + mp_util::http_header_value(http_req.headers, "Content-Type")
84         + "<br/>\n";
85     message += "<b>Content-Length:</b>"
86         + mp_util::http_header_value(http_req.headers, "Content-Length")
87         + "<br/>\n";
88     message += "</p>\n";
89
90     message += "<h3>Headers</h3><br/>\n";
91     message += "<p>\n";
92     Z_HTTP_Header* header = http_req.headers;
93     while (header){
94         message += "<b>Header: </b> <i>"
95             + std::string(header->name) + ":</i> "
96             + std::string(header->value) + "<br/>\n";
97         header = header->next;
98     }
99     message += "</p>\n";
100     message += "</body>\n</html>\n";
101     return message;
102 }
103
104
105 void mp_util::http_response(metaproxy_1::Package &package,
106                      const std::string &content,
107                      int http_code)
108 {
109
110     Z_GDU *zgdu_req = package.request().get();
111     Z_GDU *zgdu_res = 0;
112     mp::odr odr;
113     zgdu_res
114        = odr.create_HTTP_Response(package.session(),
115                                   zgdu_req->u.HTTP_Request,
116                                   http_code);
117
118     zgdu_res->u.HTTP_Response->content_len = content.size();
119     zgdu_res->u.HTTP_Response->content_buf
120         = (char*) odr_malloc(odr, zgdu_res->u.HTTP_Response->content_len);
121
122     strncpy(zgdu_res->u.HTTP_Response->content_buf,
123             content.c_str(),  zgdu_res->u.HTTP_Response->content_len);
124
125     //z_HTTP_header_add(odr, &hres->headers,
126     //                  "Content-Type", content_type.c_str());
127     package.response() = zgdu_res;
128 }
129
130
131 int mp_util::memcmp2(const void *buf1, int len1,
132                      const void *buf2, int len2)
133 {
134     int d = len1 - len2;
135
136     // compare buffer (common length)
137     int c = memcmp(buf1, buf2, d > 0 ? len2 : len1);
138     if (c > 0)
139         return 1;
140     else if (c < 0)
141         return -1;
142
143     // compare (remaining bytes)
144     if (d > 0)
145         return 1;
146     else if (d < 0)
147         return -1;
148     return 0;
149 }
150
151
152 std::string mp_util::database_name_normalize(const std::string &s)
153 {
154     std::string r = s;
155     size_t i;
156     for (i = 0; i < r.length(); i++)
157     {
158         int ch = r[i];
159         if (ch >= 'A' && ch <= 'Z')
160             r[i] = ch + 'a' - 'A';
161     }
162     return r;
163
164 }
165
166 Z_RecordComposition *mp_util::piggyback_to_RecordComposition(
167     ODR odr, Odr_int result_set_size, Z_SearchRequest *sreq)
168 {
169     Z_RecordComposition *comp = 0;
170     Odr_int present_dummy;
171     const char *element_set_name = 0;
172     mp::util::piggyback_sr(sreq, result_set_size,
173                            present_dummy, &element_set_name);
174     if (element_set_name)
175     {
176         comp  = (Z_RecordComposition *) odr_malloc(odr, sizeof(*comp));
177         comp->which = Z_RecordComp_simple;
178         comp->u.simple = (Z_ElementSetNames *)
179             odr_malloc(odr, sizeof(Z_ElementSetNames));
180         comp->u.simple->which = Z_ElementSetNames_generic;
181         comp->u.simple->u.generic = odr_strdup(odr, element_set_name);
182     }
183     return comp;
184 }
185
186 void mp_util::piggyback_sr(Z_SearchRequest *sreq,
187                            Odr_int result_set_size,
188                            Odr_int &number_to_present,
189                            const char **element_set_name)
190 {
191     Z_ElementSetNames *esn;
192     const char *smallSetElementSetNames = 0;
193     const char *mediumSetElementSetNames = 0;
194
195     esn = sreq->smallSetElementSetNames;
196     if (esn && esn->which == Z_ElementSetNames_generic)
197         smallSetElementSetNames = esn->u.generic;
198
199     esn = sreq->mediumSetElementSetNames;
200     if (esn && esn->which == Z_ElementSetNames_generic)
201         mediumSetElementSetNames = esn->u.generic;
202
203     piggyback(*sreq->smallSetUpperBound,
204               *sreq->largeSetLowerBound,
205               *sreq->mediumSetPresentNumber,
206               smallSetElementSetNames,
207               mediumSetElementSetNames,
208               result_set_size,
209               number_to_present,
210               element_set_name);
211 }
212
213 void mp_util::piggyback(int smallSetUpperBound,
214                         int largeSetLowerBound,
215                         int mediumSetPresentNumber,
216                         int result_set_size,
217                         int &number_to_present)
218 {
219     Odr_int tmp = number_to_present;
220     piggyback(smallSetUpperBound, largeSetLowerBound, mediumSetPresentNumber,
221               0, 0, result_set_size, tmp, 0);
222     number_to_present = tmp;
223 }
224
225 void mp_util::piggyback(Odr_int smallSetUpperBound,
226                         Odr_int largeSetLowerBound,
227                         Odr_int mediumSetPresentNumber,
228                         const char *smallSetElementSetNames,
229                         const char *mediumSetElementSetNames,
230                         Odr_int result_set_size,
231                         Odr_int &number_to_present,
232                         const char **element_set_name)
233 {
234     // deal with piggyback
235
236     if (result_set_size < smallSetUpperBound)
237     {
238         // small set . Return all records in set
239         number_to_present = result_set_size;
240         if (element_set_name && smallSetElementSetNames)
241             *element_set_name = smallSetElementSetNames;
242
243     }
244     else if (result_set_size > largeSetLowerBound)
245     {
246         // large set . Return no records
247         number_to_present = 0;
248         if (element_set_name)
249             *element_set_name = 0;
250     }
251     else
252     {
253         // medium set . Return mediumSetPresentNumber records
254         number_to_present = mediumSetPresentNumber;
255         if (number_to_present > result_set_size)
256             number_to_present = result_set_size;
257         if (element_set_name && mediumSetElementSetNames)
258             *element_set_name = mediumSetElementSetNames;
259     }
260 }
261
262 bool mp_util::pqf(ODR odr, Z_APDU *apdu, const std::string &q)
263 {
264     YAZ_PQF_Parser pqf_parser = yaz_pqf_create();
265
266     Z_RPNQuery *rpn = yaz_pqf_parse(pqf_parser, odr, q.c_str());
267     if (!rpn)
268     {
269         yaz_pqf_destroy(pqf_parser);
270         return false;
271     }
272     yaz_pqf_destroy(pqf_parser);
273     Z_Query *query = (Z_Query *) odr_malloc(odr, sizeof(Z_Query));
274     query->which = Z_Query_type_1;
275     query->u.type_1 = rpn;
276
277     apdu->u.searchRequest->query = query;
278     return true;
279 }
280
281
282 std::string mp_util::zQueryToString(Z_Query *query)
283 {
284     std::string query_str = "";
285
286     if (query && query->which == Z_Query_type_1)
287     {
288         Z_RPNQuery *rpn = query->u.type_1;
289
290         if (rpn)
291         {
292             mp::wrbuf w;
293
294             // put query in w
295             yaz_rpnquery_to_wrbuf(w, rpn);
296
297             // from w to std::string
298             query_str = std::string(w.buf(), w.len());
299         }
300     }
301
302 #if 0
303     if (query && query->which == Z_Query_type_1){
304
305         // allocate wrbuf (strings in YAZ!)
306         WRBUF w = wrbuf_alloc();
307
308         // put query in w
309         yaz_query_to_wrbuf(w, query);
310
311         // from w to std::string
312         query_str = std::string(wrbuf_buf(w), wrbuf_len(w));
313
314         // destroy wrbuf
315         wrbuf_free(w, 1);
316     }
317 #endif
318     return query_str;
319 }
320
321 void mp_util::get_default_diag(Z_DefaultDiagFormat *r,
322                                          int &error_code, std::string &addinfo)
323 {
324     error_code = *r->condition;
325     switch (r->which)
326     {
327     case Z_DefaultDiagFormat_v2Addinfo:
328         addinfo = std::string(r->u.v2Addinfo);
329         break;
330     case Z_DefaultDiagFormat_v3Addinfo:
331         addinfo = r->u.v3Addinfo;
332         break;
333     }
334 }
335
336 void mp_util::get_init_diagnostics(
337     Z_InitResponse *initrs, int &error_code, std::string &addinfo)
338 {
339
340     Z_DefaultDiagFormat *df = yaz_decode_init_diag(0, initrs);
341
342     if (df)
343         get_default_diag(df, error_code, addinfo);
344 }
345
346 int mp_util::get_or_remove_vhost_otherinfo(
347     Z_OtherInformation **otherInformation,
348     bool remove_flag,
349     std::list<std::string> &vhosts)
350 {
351     int cat;
352     for (cat = 1; ; cat++)
353     {
354         // check virtual host
355         const char *vhost =
356             yaz_oi_get_string_oid(otherInformation,
357                                   yaz_oid_userinfo_proxy,
358                                   cat /* categoryValue */,
359                                   remove_flag /* delete flag */);
360         if (!vhost)
361             break;
362         vhosts.push_back(std::string(vhost));
363     }
364     --cat;
365     return cat;
366 }
367
368 void mp_util::get_vhost_otherinfo(
369     Z_OtherInformation *otherInformation,
370     std::list<std::string> &vhosts)
371 {
372     get_or_remove_vhost_otherinfo(&otherInformation, false, vhosts);
373 }
374
375 int mp_util::remove_vhost_otherinfo(
376     Z_OtherInformation **otherInformation,
377     std::list<std::string> &vhosts)
378 {
379     return get_or_remove_vhost_otherinfo(otherInformation, true, vhosts);
380 }
381
382 void mp_util::set_vhost_otherinfo(
383     Z_OtherInformation **otherInformation, ODR odr,
384     const std::list<std::string> &vhosts)
385 {
386     int cat;
387     std::list<std::string>::const_iterator it = vhosts.begin();
388
389     for (cat = 1; it != vhosts.end() ; cat++, it++)
390     {
391         yaz_oi_set_string_oid(otherInformation, odr,
392                               yaz_oid_userinfo_proxy, cat, it->c_str());
393     }
394 }
395
396 void mp_util::set_vhost_otherinfo(
397     Z_OtherInformation **otherInformation, ODR odr,
398     const std::string vhost, const int cat)
399 {
400     yaz_oi_set_string_oid(otherInformation, odr,
401                           yaz_oid_userinfo_proxy, cat, vhost.c_str());
402 }
403
404 void mp_util::split_zurl(std::string zurl, std::string &host,
405                          std::list<std::string> &db)
406 {
407     const char *zurl_cstr = zurl.c_str();
408     const char *args = 0;
409     cs_get_host_args(zurl_cstr, &args);
410
411     if (args && *args)
412     {
413         host = std::string(zurl_cstr, args - zurl_cstr);
414
415         const char *cp1 = args;
416         while (1)
417         {
418             const char *cp2 = strchr(cp1, '+');
419             if (cp2)
420                 db.push_back(std::string(cp1, cp2 - cp1));
421             else
422             {
423                 db.push_back(std::string(cp1));
424                 break;
425             }
426             cp1 = cp2+1;
427         }
428     }
429     else
430         host = zurl;
431 }
432
433 bool mp_util::set_databases_from_zurl(
434     ODR odr, std::string zurl,
435     int *db_num, char ***db_strings)
436 {
437     std::string host;
438     std::list<std::string> dblist;
439
440     split_zurl(zurl, host, dblist);
441
442     if (dblist.size() == 0)
443         return false;
444     *db_num = dblist.size();
445     *db_strings = (char **) odr_malloc(odr, sizeof(char*) * (*db_num));
446
447     std::list<std::string>::const_iterator it = dblist.begin();
448     for (int i = 0; it != dblist.end(); it++, i++)
449         (*db_strings)[i] = odr_strdup(odr, it->c_str());
450     return true;
451 }
452
453 mp::odr::odr(int type)
454 {
455     m_odr = odr_createmem(type);
456 }
457
458 mp::odr::odr()
459 {
460     m_odr = odr_createmem(ODR_ENCODE);
461 }
462
463 mp::odr::~odr()
464 {
465     odr_destroy(m_odr);
466 }
467
468 mp::odr::operator ODR() const
469 {
470     return m_odr;
471 }
472
473 Z_APDU *mp::odr::create_close(const Z_APDU *in_apdu,
474                               int reason, const char *addinfo)
475 {
476     Z_APDU *apdu = create_APDU(Z_APDU_close, in_apdu);
477
478     *apdu->u.close->closeReason = reason;
479     if (addinfo)
480         apdu->u.close->diagnosticInformation = odr_strdup(m_odr, addinfo);
481     return apdu;
482 }
483
484 Z_APDU *mp::odr::create_APDU(int type, const Z_APDU *in_apdu)
485 {
486     return mp::util::create_APDU(m_odr, type, in_apdu);
487 }
488
489 Z_APDU *mp_util::create_APDU(ODR odr, int type, const Z_APDU *in_apdu)
490 {
491     Z_APDU *out_apdu = zget_APDU(odr, type);
492     transfer_referenceId(odr, in_apdu, out_apdu);
493     return out_apdu;
494 }
495
496 void mp_util::transfer_referenceId(ODR odr, const Z_APDU *src, Z_APDU *dst)
497 {
498     Z_ReferenceId **id_to = mp::util::get_referenceId(dst);
499     *id_to = 0;
500     if (src)
501     {
502         Z_ReferenceId **id_from = mp::util::get_referenceId(src);
503         if (id_from && *id_from)
504             *id_to = odr_create_Odr_oct(odr, (*id_from)->buf,
505                                         (*id_from)->len);
506     }
507 }
508
509 Z_APDU *mp::odr::create_initResponse(const Z_APDU *in_apdu,
510                                      int error, const char *addinfo)
511 {
512     Z_APDU *apdu = create_APDU(Z_APDU_initResponse, in_apdu);
513     if (error)
514     {
515         apdu->u.initResponse->userInformationField =
516             zget_init_diagnostics(m_odr, error, addinfo);
517         *apdu->u.initResponse->result = 0;
518     }
519     apdu->u.initResponse->implementationName =
520         odr_prepend(m_odr, "Metaproxy",
521                     apdu->u.initResponse->implementationName);
522     apdu->u.initResponse->implementationVersion =
523         odr_prepend(m_odr,
524                     VERSION, apdu->u.initResponse->implementationVersion);
525
526     return apdu;
527 }
528
529 Z_APDU *mp::odr::create_searchResponse(const Z_APDU *in_apdu,
530                                        int error, const char *addinfo)
531 {
532     Z_APDU *apdu = create_APDU(Z_APDU_searchResponse, in_apdu);
533     if (error)
534     {
535         Z_Records *rec = (Z_Records *) odr_malloc(m_odr, sizeof(Z_Records));
536         *apdu->u.searchResponse->searchStatus = 0;
537         apdu->u.searchResponse->records = rec;
538         rec->which = Z_Records_NSD;
539         rec->u.nonSurrogateDiagnostic =
540             zget_DefaultDiagFormat(m_odr, error, addinfo);
541
542     }
543     return apdu;
544 }
545
546 Z_APDU *mp::odr::create_presentResponse(const Z_APDU *in_apdu,
547                                         int error, const char *addinfo)
548 {
549     Z_APDU *apdu = create_APDU(Z_APDU_presentResponse, in_apdu);
550     if (error)
551     {
552         Z_Records *rec = (Z_Records *) odr_malloc(m_odr, sizeof(Z_Records));
553         apdu->u.presentResponse->records = rec;
554
555         rec->which = Z_Records_NSD;
556         rec->u.nonSurrogateDiagnostic =
557             zget_DefaultDiagFormat(m_odr, error, addinfo);
558         *apdu->u.presentResponse->presentStatus = Z_PresentStatus_failure;
559     }
560     return apdu;
561 }
562
563 Z_APDU *mp::odr::create_scanResponse(const Z_APDU *in_apdu,
564                                      int error, const char *addinfo)
565 {
566     Z_APDU *apdu = create_APDU(Z_APDU_scanResponse, in_apdu);
567     Z_ScanResponse *res = apdu->u.scanResponse;
568     res->entries = (Z_ListEntries *) odr_malloc(m_odr, sizeof(*res->entries));
569     res->entries->num_entries = 0;
570     res->entries->entries = 0;
571
572     if (error)
573     {
574         *res->scanStatus = Z_Scan_failure;
575
576         res->entries->num_nonsurrogateDiagnostics = 1;
577         res->entries->nonsurrogateDiagnostics = (Z_DiagRec **)
578             odr_malloc(m_odr, sizeof(Z_DiagRec *));
579         res->entries->nonsurrogateDiagnostics[0] =
580             zget_DiagRec(m_odr, error, addinfo);
581     }
582     else
583     {
584         res->entries->num_nonsurrogateDiagnostics = 0;
585         res->entries->nonsurrogateDiagnostics = 0;
586     }
587     return apdu;
588 }
589
590 Z_GDU *mp::odr::create_HTTP_Response_details(mp::Session &session,
591                                              Z_HTTP_Request *hreq, int code,
592                                              const char *details)
593 {
594     const char *response_version = "1.0";
595     bool keepalive = false;
596     if (!strcmp(hreq->version, "1.0"))
597     {
598         const char *v = z_HTTP_header_lookup(hreq->headers, "Connection");
599         if (v && !strcmp(v, "Keep-Alive"))
600             keepalive = true;
601         else
602             session.close();
603         response_version = "1.0";
604     }
605     else
606     {
607         const char *v = z_HTTP_header_lookup(hreq->headers, "Connection");
608         if (v && !strcmp(v, "close"))
609             session.close();
610         else
611             keepalive = true;
612         response_version = "1.1";
613     }
614
615     Z_GDU *gdu = z_get_HTTP_Response_details(m_odr, code, details);
616     Z_HTTP_Response *hres = gdu->u.HTTP_Response;
617     hres->version = odr_strdup(m_odr, response_version);
618     if (keepalive)
619         z_HTTP_header_add(m_odr, &hres->headers, "Connection", "Keep-Alive");
620
621     return gdu;
622 }
623
624 Z_GDU *mp::odr::create_HTTP_Response(mp::Session &session,
625                                      Z_HTTP_Request *hreq, int code)
626 {
627     return create_HTTP_Response_details(session, hreq, code, 0);
628
629 }
630
631 Z_ReferenceId **mp_util::get_referenceId(const Z_APDU *apdu)
632 {
633     switch (apdu->which)
634     {
635     case  Z_APDU_initRequest:
636         return &apdu->u.initRequest->referenceId;
637     case  Z_APDU_initResponse:
638         return &apdu->u.initResponse->referenceId;
639     case  Z_APDU_searchRequest:
640         return &apdu->u.searchRequest->referenceId;
641     case  Z_APDU_searchResponse:
642         return &apdu->u.searchResponse->referenceId;
643     case  Z_APDU_presentRequest:
644         return &apdu->u.presentRequest->referenceId;
645     case  Z_APDU_presentResponse:
646         return &apdu->u.presentResponse->referenceId;
647     case  Z_APDU_deleteResultSetRequest:
648         return &apdu->u.deleteResultSetRequest->referenceId;
649     case  Z_APDU_deleteResultSetResponse:
650         return &apdu->u.deleteResultSetResponse->referenceId;
651     case  Z_APDU_accessControlRequest:
652         return &apdu->u.accessControlRequest->referenceId;
653     case  Z_APDU_accessControlResponse:
654         return &apdu->u.accessControlResponse->referenceId;
655     case  Z_APDU_resourceControlRequest:
656         return &apdu->u.resourceControlRequest->referenceId;
657     case  Z_APDU_resourceControlResponse:
658         return &apdu->u.resourceControlResponse->referenceId;
659     case  Z_APDU_triggerResourceControlRequest:
660         return &apdu->u.triggerResourceControlRequest->referenceId;
661     case  Z_APDU_resourceReportRequest:
662         return &apdu->u.resourceReportRequest->referenceId;
663     case  Z_APDU_resourceReportResponse:
664         return &apdu->u.resourceReportResponse->referenceId;
665     case  Z_APDU_scanRequest:
666         return &apdu->u.scanRequest->referenceId;
667     case  Z_APDU_scanResponse:
668         return &apdu->u.scanResponse->referenceId;
669     case  Z_APDU_sortRequest:
670         return &apdu->u.sortRequest->referenceId;
671     case  Z_APDU_sortResponse:
672         return &apdu->u.sortResponse->referenceId;
673     case  Z_APDU_segmentRequest:
674         return &apdu->u.segmentRequest->referenceId;
675     case  Z_APDU_extendedServicesRequest:
676         return &apdu->u.extendedServicesRequest->referenceId;
677     case  Z_APDU_extendedServicesResponse:
678         return &apdu->u.extendedServicesResponse->referenceId;
679     case  Z_APDU_close:
680         return &apdu->u.close->referenceId;
681     }
682     return 0;
683 }
684
685 std::string mp_util::uri_encode(std::string s)
686 {
687     char *x = (char *) xmalloc(1 + s.length() * 3);
688     yaz_encode_uri_component(x, s.c_str());
689     std::string result(x);
690     xfree(x);
691     return result;
692 }
693
694
695 std::string mp_util::uri_decode(std::string s)
696 {
697     char *x = (char *) xmalloc(1 + s.length());
698     yaz_decode_uri_component(x, s.c_str(), s.length());
699     std::string result(x);
700     xfree(x);
701     return result;
702 }
703
704 mp::wrbuf::wrbuf()
705 {
706     m_wrbuf = wrbuf_alloc();
707 }
708
709 mp::wrbuf::~wrbuf()
710 {
711     wrbuf_destroy(m_wrbuf);
712 }
713
714 mp::wrbuf::operator WRBUF() const
715 {
716     return m_wrbuf;
717 }
718
719 size_t mp::wrbuf::len()
720 {
721     return wrbuf_len(m_wrbuf);
722 }
723
724 const char *mp::wrbuf::buf()
725 {
726     return wrbuf_buf(m_wrbuf);
727 }
728
729
730 /*
731  * Local variables:
732  * c-basic-offset: 4
733  * c-file-style: "Stroustrup"
734  * indent-tabs-mode: nil
735  * End:
736  * vim: shiftwidth=4 tabstop=8 expandtab
737  */
738