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