Home | History | Annotate | Line # | Download | only in tools
      1 //
      2 // Automated Testing Framework (atf)
      3 //
      4 // Copyright (c) 2007 The NetBSD Foundation, Inc.
      5 // All rights reserved.
      6 //
      7 // Redistribution and use in source and binary forms, with or without
      8 // modification, are permitted provided that the following conditions
      9 // are met:
     10 // 1. Redistributions of source code must retain the above copyright
     11 //    notice, this list of conditions and the following disclaimer.
     12 // 2. Redistributions in binary form must reproduce the above copyright
     13 //    notice, this list of conditions and the following disclaimer in the
     14 //    documentation and/or other materials provided with the distribution.
     15 //
     16 // THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND
     17 // CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
     18 // INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
     19 // MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
     20 // IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS BE LIABLE FOR ANY
     21 // DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
     22 // DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
     23 // GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
     24 // INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
     25 // IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
     26 // OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
     27 // IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
     28 //
     29 
     30 #include <cassert>
     31 #include <sstream>
     32 
     33 #include "parser.hpp"
     34 #include "text.hpp"
     35 
     36 namespace impl = tools::parser;
     37 #define IMPL_NAME "tools::parser"
     38 
     39 // ------------------------------------------------------------------------
     40 // The "parse_error" class.
     41 // ------------------------------------------------------------------------
     42 
     43 impl::parse_error::parse_error(size_t line, std::string msg) :
     44     std::runtime_error(msg),
     45     std::pair< size_t, std::string >(line, msg)
     46 {
     47 }
     48 
     49 impl::parse_error::~parse_error(void)
     50     throw()
     51 {
     52 }
     53 
     54 const char*
     55 impl::parse_error::what(void)
     56     const throw()
     57 {
     58     try {
     59         std::ostringstream oss;
     60         oss << "LONELY PARSE ERROR: " << first << ": " << second;
     61         m_msg = oss.str();
     62         return m_msg.c_str();
     63     } catch (...) {
     64         return "Could not format message for parsing error.";
     65     }
     66 }
     67 
     68 impl::parse_error::operator std::string(void)
     69     const
     70 {
     71     return tools::text::to_string(first) + ": " + second;
     72 }
     73 
     74 // ------------------------------------------------------------------------
     75 // The "parse_errors" class.
     76 // ------------------------------------------------------------------------
     77 
     78 impl::parse_errors::parse_errors(void) :
     79     std::runtime_error("No parsing errors yet")
     80 {
     81     m_msg.clear();
     82 }
     83 
     84 impl::parse_errors::~parse_errors(void)
     85     throw()
     86 {
     87 }
     88 
     89 const char*
     90 impl::parse_errors::what(void)
     91     const throw()
     92 {
     93     try {
     94         m_msg = tools::text::join(*this, "\n");
     95         return m_msg.c_str();
     96     } catch (...) {
     97         return "Could not format messages for parsing errors.";
     98     }
     99 }
    100 
    101 // ------------------------------------------------------------------------
    102 // The "format_error" class.
    103 // ------------------------------------------------------------------------
    104 
    105 impl::format_error::format_error(const std::string& w) :
    106     std::runtime_error(w.c_str())
    107 {
    108 }
    109 
    110 // ------------------------------------------------------------------------
    111 // The "token" class.
    112 // ------------------------------------------------------------------------
    113 
    114 impl::token::token(void) :
    115     m_inited(false)
    116 {
    117 }
    118 
    119 impl::token::token(size_t p_line,
    120                    const token_type& p_type,
    121                    const std::string& p_text) :
    122     m_inited(true),
    123     m_line(p_line),
    124     m_type(p_type),
    125     m_text(p_text)
    126 {
    127 }
    128 
    129 size_t
    130 impl::token::lineno(void)
    131     const
    132 {
    133     return m_line;
    134 }
    135 
    136 const impl::token_type&
    137 impl::token::type(void)
    138     const
    139 {
    140     return m_type;
    141 }
    142 
    143 const std::string&
    144 impl::token::text(void)
    145     const
    146 {
    147     return m_text;
    148 }
    149 
    150 impl::token::operator bool(void)
    151     const
    152 {
    153     return m_inited;
    154 }
    155 
    156 bool
    157 impl::token::operator!(void)
    158     const
    159 {
    160     return !m_inited;
    161 }
    162 
    163 // ------------------------------------------------------------------------
    164 // The "header_entry" class.
    165 // ------------------------------------------------------------------------
    166 
    167 impl::header_entry::header_entry(void)
    168 {
    169 }
    170 
    171 impl::header_entry::header_entry(const std::string& n, const std::string& v,
    172                                  attrs_map as) :
    173     m_name(n),
    174     m_value(v),
    175     m_attrs(as)
    176 {
    177 }
    178 
    179 const std::string&
    180 impl::header_entry::name(void) const
    181 {
    182     return m_name;
    183 }
    184 
    185 const std::string&
    186 impl::header_entry::value(void) const
    187 {
    188     return m_value;
    189 }
    190 
    191 const impl::attrs_map&
    192 impl::header_entry::attrs(void) const
    193 {
    194     return m_attrs;
    195 }
    196 
    197 bool
    198 impl::header_entry::has_attr(const std::string& n) const
    199 {
    200     return m_attrs.find(n) != m_attrs.end();
    201 }
    202 
    203 const std::string&
    204 impl::header_entry::get_attr(const std::string& n) const
    205 {
    206     attrs_map::const_iterator iter = m_attrs.find(n);
    207     assert(iter != m_attrs.end());
    208     return (*iter).second;
    209 }
    210 
    211 // ------------------------------------------------------------------------
    212 // The header tokenizer.
    213 // ------------------------------------------------------------------------
    214 
    215 namespace header {
    216 
    217 static const impl::token_type eof_type = 0;
    218 static const impl::token_type nl_type = 1;
    219 static const impl::token_type text_type = 2;
    220 static const impl::token_type colon_type = 3;
    221 static const impl::token_type semicolon_type = 4;
    222 static const impl::token_type dblquote_type = 5;
    223 static const impl::token_type equal_type = 6;
    224 
    225 class tokenizer : public impl::tokenizer< std::istream > {
    226 public:
    227     tokenizer(std::istream& is, size_t curline) :
    228         impl::tokenizer< std::istream >
    229             (is, true, eof_type, nl_type, text_type, curline)
    230     {
    231         add_delim(';', semicolon_type);
    232         add_delim(':', colon_type);
    233         add_delim('=', equal_type);
    234         add_quote('"', dblquote_type);
    235     }
    236 };
    237 
    238 static
    239 impl::parser< header::tokenizer >&
    240 read(impl::parser< header::tokenizer >& p, impl::header_entry& he)
    241 {
    242     using namespace header;
    243 
    244     impl::token t = p.expect(text_type, nl_type, "a header name");
    245     if (t.type() == nl_type) {
    246         he = impl::header_entry();
    247         return p;
    248     }
    249     std::string hdr_name = t.text();
    250 
    251     t = p.expect(colon_type, "`:'");
    252 
    253     t = p.expect(text_type, "a textual value");
    254     std::string hdr_value = t.text();
    255 
    256     impl::attrs_map attrs;
    257 
    258     for (;;) {
    259         t = p.expect(eof_type, semicolon_type, nl_type,
    260                      "eof, `;' or new line");
    261         if (t.type() == eof_type || t.type() == nl_type)
    262             break;
    263 
    264         t = p.expect(text_type, "an attribute name");
    265         std::string attr_name = t.text();
    266 
    267         t = p.expect(equal_type, "`='");
    268 
    269         t = p.expect(text_type, "word or quoted string");
    270         std::string attr_value = t.text();
    271         attrs[attr_name] = attr_value;
    272     }
    273 
    274     he = impl::header_entry(hdr_name, hdr_value, attrs);
    275 
    276     return p;
    277 }
    278 
    279 static
    280 std::ostream&
    281 write(std::ostream& os, const impl::header_entry& he)
    282 {
    283     std::string line = he.name() + ": " + he.value();
    284     impl::attrs_map as = he.attrs();
    285     for (impl::attrs_map::const_iterator iter = as.begin(); iter != as.end();
    286          iter++) {
    287         assert((*iter).second.find('\"') == std::string::npos);
    288         line += "; " + (*iter).first + "=\"" + (*iter).second + "\"";
    289     }
    290 
    291     os << line << "\n";
    292 
    293     return os;
    294 }
    295 
    296 } // namespace header
    297 
    298 // ------------------------------------------------------------------------
    299 // Free functions.
    300 // ------------------------------------------------------------------------
    301 
    302 std::pair< size_t, impl::headers_map >
    303 impl::read_headers(std::istream& is, size_t curline)
    304 {
    305     using impl::format_error;
    306 
    307     headers_map hm;
    308 
    309     //
    310     // Grammar
    311     //
    312     // header = entry+ nl
    313     // entry = line nl
    314     // line = text colon text
    315     //        (semicolon (text equal (text | dblquote string dblquote)))*
    316     // string = quoted_string
    317     //
    318 
    319     header::tokenizer tkz(is, curline);
    320     impl::parser< header::tokenizer > p(tkz);
    321 
    322     bool first = true;
    323     for (;;) {
    324         try {
    325             header_entry he;
    326             if (!header::read(p, he).good() || he.name().empty())
    327                 break;
    328 
    329             if (first && he.name() != "Content-Type")
    330                 throw format_error("Could not determine content type");
    331             else
    332                 first = false;
    333 
    334             hm[he.name()] = he;
    335         } catch (const impl::parse_error& pe) {
    336             p.add_error(pe);
    337             p.reset(header::nl_type);
    338         }
    339     }
    340 
    341     if (!is.good())
    342         throw format_error("Unexpected end of stream");
    343 
    344     return std::pair< size_t, headers_map >(tkz.lineno(), hm);
    345 }
    346 
    347 void
    348 impl::write_headers(const impl::headers_map& hm, std::ostream& os)
    349 {
    350     assert(!hm.empty());
    351     headers_map::const_iterator ct = hm.find("Content-Type");
    352     assert(ct != hm.end());
    353     header::write(os, (*ct).second);
    354     for (headers_map::const_iterator iter = hm.begin(); iter != hm.end();
    355          iter++) {
    356         if ((*iter).first != "Content-Type")
    357             header::write(os, (*iter).second);
    358     }
    359     os << "\n";
    360 }
    361 
    362 void
    363 impl::validate_content_type(const impl::headers_map& hm, const std::string& fmt,
    364                             int version)
    365 {
    366     using impl::format_error;
    367 
    368     headers_map::const_iterator iter = hm.find("Content-Type");
    369     if (iter == hm.end())
    370         throw format_error("Could not determine content type");
    371 
    372     const header_entry& he = (*iter).second;
    373     if (he.value() != fmt)
    374         throw format_error("Mismatched content type: expected `" + fmt +
    375                            "' but got `" + he.value() + "'");
    376 
    377     if (!he.has_attr("version"))
    378         throw format_error("Could not determine version");
    379     const std::string& vstr = tools::text::to_string(version);
    380     if (he.get_attr("version") != vstr)
    381         throw format_error("Mismatched version: expected `" +
    382                            vstr + "' but got `" +
    383                            he.get_attr("version") + "'");
    384 }
    385