// CGI query parser for Boost.Program_Options // // Copyright Bruno Lalande 2008 // // Distributed under the Boost Software License, Version 1.0. // (See accompanying file LICENSE_1_0.txt // or copy at http://www.boost.org/LICENSE_1_0.txt) #include #include #include #include namespace boost { namespace program_options { namespace detail { struct escape_finder { template boost::iterator_range operator()(ForwardIterator begin, ForwardIterator end) { ForwardIterator found = find(begin, end, '%'); return (found != end || found + 3 <= end) ? make_iterator_range(found, found + 3) : make_iterator_range(end, end); } }; struct escape_formatter { template std::string operator()(const Match& match) const { if (match.end() != match.begin() + 3) return ""; std::istringstream hexa(std::string(match.begin() + 1, match.end())); int asciiCode; hexa >> std::hex >> asciiCode; return std::string(1, char(asciiCode)); } }; class BOOST_PROGRAM_OPTIONS_DECL env_var_error : public error { public: env_var_error(const std::string& name) : error(std::string("Environment variable ") .append(name) .append(" not defined")) {} }; class BOOST_PROGRAM_OPTIONS_DECL request_method_error : public error { public: request_method_error() : error("Invalid value for the REQUEST_METHOD environment variable") {} }; class BOOST_PROGRAM_OPTIONS_DECL content_type_error : public error { public: content_type_error() : error("Unsupported content type found in the CONTENT_TYPE environment variable") {} }; } // namespace detail class cgi_query_parser { private: typedef std::pair option_pair; public: cgi_query_parser() : m_desc(0), m_preserve_format(false), m_allow_unregistered(false) {} parsed_options run() { using namespace std; assert(m_desc); parsed_options result(m_desc); string query(_get_query()); if (query.empty()) return result; string content_type(_getenv("CONTENT_TYPE")); if (content_type.empty() || content_type == "application/x-www-form-urlencoded") { vector options; split(options, query, is_any_of("&")); for (vector::const_iterator i = options.begin(); i != options.end(); ++i) { _add_or_append(_parse_option(*i), result); } } else if (content_type.find("multipart/form-data") == 0) { string boundary_pattern("boundary="); string::size_type boundary_pos= content_type.find(boundary_pattern); if (boundary_pos == string::npos) throw detail::content_type_error(); string boundary(content_type.substr(boundary_pos + boundary_pattern.size())); erase_last(query, "--" + boundary + "--\r\n"); boundary = "--" + boundary + "\r\n"; erase_first(query, boundary); vector parts; iter_split(parts, query, first_finder(boundary)); for (vector::const_iterator i = parts.begin(); i != parts.end(); ++i) { string file_name; option_pair parsed(_parse_multipart_option(*i, file_name)); _add_or_append(parsed, result); if (!file_name.empty()) { string key(parsed.first + ".filename"); m_desc->add_options()(key.c_str(), value(), ""); _add_or_append(make_pair(key, file_name), result); } } } else throw detail::content_type_error(); return result; } cgi_query_parser& options(options_description& desc) { m_desc = &desc; return *this; } cgi_query_parser& preserve_format() { m_preserve_format = true; return *this; } cgi_query_parser& allow_unregistered() { m_allow_unregistered = true; return *this; } private: static std::string _getenv(const char* name, const char* defaultValue = 0) { const char* result = getenv(name); if (!result && !defaultValue) throw detail::env_var_error(name); return result ? result : defaultValue; } std::string _get_query() { std::string request_method(_getenv("REQUEST_METHOD")); if (request_method == "GET") { return _getenv("QUERY_STRING"); } else if (request_method == "POST") { int length = lexical_cast(_getenv("CONTENT_LENGTH", "0")); std::vector content(length); std::cin.read(&content[0], length); return std::string(&content[0], length); } throw detail::request_method_error(); } option_pair _parse_option(const std::string& option) { std::vector parts; split(parts, option, is_any_of("=")); if (parts.size() < 2) parts.push_back(""); if (!m_preserve_format) { using namespace detail; replace_all(parts[0], "+", " "); replace_all(parts[1], "+", " "); find_format_all(parts[0], escape_finder(), escape_formatter()); find_format_all(parts[1], escape_finder(), escape_formatter()); } return std::make_pair(parts[0], parts[1]); } option_pair _parse_multipart_option(const std::string& option, std::string& file_name) { using namespace std; string name_pattern("name=\""); string file_name_pattern("filename=\""); string name; vector header_parts; string header(option.substr(0, option.find("\r\n"))); iter_split(header_parts, header, first_finder("; ")); for (vector::iterator i = header_parts.begin(); i != header_parts.end(); ++i) { if (i->find(name_pattern) == 0) { string::size_type name_pos = name_pattern.size(); string::size_type name_len = i->find('"', name_pos) - name_pos; name = i->substr(name_pos, name_len); } else if (i->find(file_name_pattern) == 0) { string::size_type file_name_pos = file_name_pattern.size(); string::size_type file_name_len = i->find('"', file_name_pos) - file_name_pos; file_name = i->substr(file_name_pos, file_name_len); } } string data_pattern("\r\n\r\n"); string::size_type data_pos = option.find(data_pattern) + data_pattern.size(); string data(option.substr(data_pos)); erase_last(data, "\r\n"); return make_pair(name, data); } void _add_or_append(const option_pair& parsed, parsed_options& options) { bool unregistered = false; if(!m_desc->find_nothrow(parsed.first, false)) { if (!m_allow_unregistered) throw unknown_option(parsed.first); unregistered = true; } for (std::vector