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