1//
2// Automated Testing Framework (atf)
3//
4// Copyright (c) 2007, 2008, 2010 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 <sstream>
31
32#include "parser.hpp"
33#include "sanity.hpp"
34#include "text.hpp"
35
36namespace impl = atf::parser;
37#define IMPL_NAME "atf::parser"
38
39// ------------------------------------------------------------------------
40// The "parse_error" class.
41// ------------------------------------------------------------------------
42
43impl::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
49impl::parse_error::~parse_error(void)
50    throw()
51{
52}
53
54const char*
55impl::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
68impl::parse_error::operator std::string(void)
69    const
70{
71    return atf::text::to_string(first) + ": " + second;
72}
73
74// ------------------------------------------------------------------------
75// The "parse_errors" class.
76// ------------------------------------------------------------------------
77
78impl::parse_errors::parse_errors(void) :
79    std::runtime_error("No parsing errors yet")
80{
81    m_msg.clear();
82}
83
84impl::parse_errors::~parse_errors(void)
85    throw()
86{
87}
88
89const char*
90impl::parse_errors::what(void)
91    const throw()
92{
93    try {
94        m_msg = atf::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
105impl::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
114impl::token::token(void) :
115    m_inited(false)
116{
117}
118
119impl::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
129size_t
130impl::token::lineno(void)
131    const
132{
133    return m_line;
134}
135
136const impl::token_type&
137impl::token::type(void)
138    const
139{
140    return m_type;
141}
142
143const std::string&
144impl::token::text(void)
145    const
146{
147    return m_text;
148}
149
150impl::token::operator bool(void)
151    const
152{
153    return m_inited;
154}
155
156bool
157impl::token::operator!(void)
158    const
159{
160    return !m_inited;
161}
162
163// ------------------------------------------------------------------------
164// The "header_entry" class.
165// ------------------------------------------------------------------------
166
167impl::header_entry::header_entry(void)
168{
169}
170
171impl::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
179const std::string&
180impl::header_entry::name(void) const
181{
182    return m_name;
183}
184
185const std::string&
186impl::header_entry::value(void) const
187{
188    return m_value;
189}
190
191const impl::attrs_map&
192impl::header_entry::attrs(void) const
193{
194    return m_attrs;
195}
196
197bool
198impl::header_entry::has_attr(const std::string& n) const
199{
200    return m_attrs.find(n) != m_attrs.end();
201}
202
203const std::string&
204impl::header_entry::get_attr(const std::string& n) const
205{
206    attrs_map::const_iterator iter = m_attrs.find(n);
207    PRE(iter != m_attrs.end());
208    return (*iter).second;
209}
210
211// ------------------------------------------------------------------------
212// The header tokenizer.
213// ------------------------------------------------------------------------
214
215namespace header {
216
217static const impl::token_type eof_type = 0;
218static const impl::token_type nl_type = 1;
219static const impl::token_type text_type = 2;
220static const impl::token_type colon_type = 3;
221static const impl::token_type semicolon_type = 4;
222static const impl::token_type dblquote_type = 5;
223static const impl::token_type equal_type = 6;
224
225class tokenizer : public impl::tokenizer< std::istream > {
226public:
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
238static
239impl::parser< header::tokenizer >&
240read(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
279static
280std::ostream&
281write(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        PRE((*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
302std::pair< size_t, impl::headers_map >
303impl::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
347void
348impl::write_headers(const impl::headers_map& hm, std::ostream& os)
349{
350    PRE(!hm.empty());
351    headers_map::const_iterator ct = hm.find("Content-Type");
352    PRE(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
362void
363impl::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 = atf::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