Below is the file 'vocab.cc' from this revision. You can also download the file.
// copyright (C) 2002, 2003 graydon hoare <graydon@pobox.com> // all rights reserved. // licensed to the public under the terms of the GNU GPL (>= 2) // see the file COPYING for details #include <string> #include <iostream> #include <boost/filesystem/path.hpp> #include <boost/filesystem/exception.hpp> #include <boost/version.hpp> #include "constants.hh" #include "file_io.hh" #include "sanity.hh" #include "vocab.hh" // verifiers for various types of data using namespace std; // the verify() stuff gets a little complicated; there doesn't seem to be a // really nice way to achieve what we want with c++'s type system. the // problem is this: we want to give verify(file_path) and verify(local_path) // access to the internals of file_path and local_path, i.e. make them // friends, so they can normalize the file paths they're given. this means // that verify() needs to be declared publically, so that the definition of // these classes can refer to them. it also means that they -- and all other // ATOMIC types -- cannot fall back on a templated version of verify if no // other version is defined, because, well, the friend thing and the template // thing just don't work out, as far as I can tell. So, every ATOMIC type // needs an explicitly defined verify() function, so we have both ATOMIC() and // ATOMIC_NOVERIFY() macros, the latter of which defines a type-specific noop // verify function. DECORATE and ENCODING, on the other hand, cannot make use // of a trick like these, because they are template types themselves, and we // want to be able to define verify(hexenc<id>) without defining // verify(hexenc<data>) at the same time, for instance. Fortunately, these // types never need to be friends with their verify functions (yet...), so we // _can_ use a templated fallback function. This templated function is used // _only_ by DECORATE and ENCODING; it would be nice to make it take an // argument of type T1<T2> to document that, but for some reason that doesn't // work either. template <typename T> static inline void verify(T & val) {} inline void verify(hexenc<id> & val) { if (val.ok) return; if (val() == "") return; N(val().size() == constants::idlen, F("hex encoded ID '%s' size != %d") % val % constants::idlen); string::size_type pos = val().find_first_not_of(constants::legal_id_bytes); N(pos == string::npos, F("bad character '%c' in id name '%s'") % val().at(pos) % val); val.ok = true; } inline void verify(ace & val) { if (val.ok) return; string::size_type pos = val().find_first_not_of(constants::legal_ace_bytes); N(pos == string::npos, F("bad character '%c' in ace string '%s'") % val().at(pos) % val); val.ok = true; } inline void verify(cert_name & val) { if (val.ok) return; string::size_type pos = val().find_first_not_of(constants::legal_cert_name_bytes); N(pos == string::npos, F("bad character '%c' in cert name '%s'") % val().at(pos) % val); val.ok = true; } inline void verify(rsa_keypair_id & val) { if (val.ok) return; string::size_type pos = val().find_first_not_of(constants::legal_key_name_bytes); N(pos == string::npos, F("bad character '%c' in key name '%s'") % val().at(pos) % val); val.ok = true; } inline void verify(netsync_session_key & val) { if (val.ok) return; if (val().size() == 0) { val.s.append(constants::netsync_session_key_length_in_bytes, 0); return; } N(val().size() == constants::netsync_session_key_length_in_bytes, F("Invalid key length of %d bytes") % val().length()); val.ok = true; } inline void verify(netsync_hmac_value & val) { if (val.ok) return; if (val().size() == 0) { val.s.append(constants::netsync_hmac_value_length_in_bytes, 0); return; } N(val().size() == constants::netsync_hmac_value_length_in_bytes, F("Invalid key length of %d bytes") % val().length()); val.ok = true; } inline void verify(local_path & val) { if (val.ok) return; using boost::filesystem::path; boost::filesystem::path p; try { p = mkpath(val()); p = p.normalize(); } catch (std::runtime_error &re) { throw informative_failure(re.what()); } catch (fs::filesystem_error &fse) { throw informative_failure(fse.what()); } N(! (p.has_root_path() || p.has_root_name() || p.has_root_directory()), F("prohibited absolute path '%s'") % val); for(path::iterator i = p.begin(); i != p.end(); ++i) { N(!( *i == "" && (! p.empty())), F("empty path component in '%s'") % val); N((*i != ".."), F("prohibited path component '%s' in '%s'") % *i % val); string::size_type pos = i->find_first_of(constants::illegal_path_bytes); N(pos == string::npos, F("bad character '%d' in path component '%s' of '%s'") % static_cast<int>(i->at(pos)) % *i % val); string s = val(); for (string::const_iterator j = s.begin(); j != s.end(); ++j) N(*j != '\0', F("null byte in path component '%s' of '%s'") % *i % val); } // save back the normalized string val.s = p.string(); val.ok = true; } // returns true if the given string is obviously a normalized file path (no // . or .. components, a relative path, no doubled //s, does not end in /, // does not start with MT) inline bool trivially_safe_file_path(std::string const & f) { const static std::string bad_chars = std::string("\\:") + constants::illegal_path_bytes + std::string(1, '\0'); const static char sep_char('/'); const static std::string bad_after_sep_chars("./"); if (f.empty()) return true; char prev = sep_char; for (std::string::const_iterator i = f.begin(); i != f.end(); ++i) { if (bad_chars.find(*i) != std::string::npos) return false; if (prev == sep_char && bad_after_sep_chars.find(*i) != std::string::npos) return false; prev = *i; } if (prev == sep_char) return false; if (f.size() >= 2 && f[0] == 'M' && f[1] == 'T') return false; return true; } inline void verify(file_path & val) { static std::map<std::string, std::string> known_good; if (val.ok) return; std::map<std::string, std::string>::const_iterator j = known_good.find(val()); if (j == known_good.end()) { if (trivially_safe_file_path(val())) known_good.insert(std::make_pair(val(), val())); else { local_path loc(val()); verify(loc); N(!book_keeping_file(loc), F("prohibited book-keeping path in '%s'") % val); const std::string & normalized_val = loc(); val.s = normalized_val; known_good.insert(std::make_pair(val(), normalized_val)); } } else { val.s = j->second; } val.ok = true; } // instantiation of various vocab functions #define ATOMIC(ty) \ \ ty::ty(string const & str) : \ s(str), ok(false) \ { verify(*this); } \ \ ty::ty(ty const & other) : \ s(other.s), ok(other.ok) \ { verify(*this); } \ \ ty const & ty::operator=(ty const & other) \ { s = other.s; ok = other.ok; \ verify(*this); return *this; } \ \ ostream & operator<<(ostream & o, \ ty const & a) \ { return (o << a.s); } #define ATOMIC_NOVERIFY(ty) ATOMIC(ty) #define ENCODING(enc) \ \ template<typename INNER> \ enc<INNER>::enc(string const & s) : i(s), ok(false) \ { verify(*this); } \ \ template<typename INNER> \ enc<INNER>::enc(enc<INNER> const & other) \ : i(other.i()), ok(other.ok) { verify(*this); } \ \ template<typename INNER> \ enc<INNER>::enc(INNER const & inner) : \ i(inner), ok(false) \ { verify(*this); } \ \ template<typename INNER> \ enc<INNER> const & \ enc<INNER>::operator=(enc<INNER> const & other) \ { i = other.i; ok = other.ok; \ verify(*this); return *this;} \ \ template <typename INNER> \ ostream & operator<<(ostream & o, enc<INNER> const & e) \ { return (o << e.i); } #define DECORATE(dec) \ \ template<typename INNER> \ dec<INNER>::dec(dec<INNER> const & other) \ : i(other.i), ok(other.ok) { verify(*this); } \ \ template<typename INNER> \ dec<INNER>::dec(INNER const & inner) : \ i(inner), ok(false) \ { verify(*this); } \ \ template<typename INNER> \ dec<INNER> const & \ dec<INNER>::operator=(dec<INNER> const & other) \ { i = other.i; ok = other.ok; \ verify(*this); return *this;} \ \ template <typename INNER> \ ostream & operator<<(ostream & o, dec<INNER> const & d) \ { return (o << d.i); } #define EXTERN #include "vocab_terms.hh" #undef EXTERN #undef ATOMIC #undef DECORATE template class revision<cert>; template class manifest<cert>; // the rest is unit tests #ifdef BUILD_UNIT_TESTS #include "unit_tests.hh" static void test_file_path_verification() { char const * baddies [] = {"../escape", "foo/../../escape", "/rooted", "foo//nonsense", "MT/foo", #ifdef _WIN32 "c:\\windows\\rooted", "c:/windows/rooted", "c:thing", "//unc/share", "//unc", #endif 0 }; for (char const ** c = baddies; *c; ++c) BOOST_CHECK_THROW(file_path p(*c), informative_failure); char const * bad = "\t\r\n\v\f\a\b"; char badboy[] = "bad"; for (char const * c = bad; *c; ++c) { badboy[1] = *c; BOOST_CHECK_THROW(file_path p(badboy), informative_failure); } BOOST_CHECK_THROW(file_path p(std::string(1, '\0')), informative_failure); char const * goodies [] = {"unrooted", "unrooted.txt", "fun_with_underscore.png", "fun-with-hyphen.tiff", "unrooted/../unescaping", "unrooted/general/path", "here/..", 0 }; for (char const ** c = goodies; *c; ++c) BOOST_CHECK_NOT_THROW(file_path p(*c), informative_failure); } static void test_file_path_normalization() { BOOST_CHECK(file_path("./foo") == file_path("foo")); BOOST_CHECK(file_path("foo/bar/./baz") == file_path("foo/bar/baz")); BOOST_CHECK(file_path("foo/bar/../baz") == file_path("foo/baz")); BOOST_CHECK(file_path("foo/bar/baz/") == file_path("foo/bar/baz")); } void add_vocab_tests(test_suite * suite) { I(suite); suite->add(BOOST_TEST_CASE(&test_file_path_verification)); suite->add(BOOST_TEST_CASE(&test_file_path_normalization)); } #endif // BUILD_UNIT_TESTS