/* -*-c++-*- */ /* osgEarth - Geospatial SDK for OpenSceneGraph * Copyright 2020 Pelican Mapping * http://osgearth.org * * osgEarth is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program. If not, see */ #ifndef OSGEARTH_URI #define OSGEARTH_URI 1 #include #include #include #include #include #include #include namespace osgUtil { class IncrementalCompileOperation; } namespace osgEarth { class URI; class ProgressCallback; using namespace Threading; /** * Context for resolving relative URIs. * * This object provides "context" for a relative URI. In other words, it provides * all of the information the system needs to resolve it to an absolute location from * which OSG can load data. * * The "referrer" is the location of an object that "points" to the object in the * corresponding URI. The location conveyed by the URI will be relative to the location of * its referrer. For example, a referrer of "http://server/folder/hello.xml" applied * to the URI "there.jpg" will resolve to "http://server/folder/there.jpg". NOTE that referrer * it not itself a location (like a folder); rather it's the object that referred to the URI * being contextualized. */ class OSGEARTH_EXPORT URIContext { public: /** Creates an empty context. */ URIContext(); /** Creates a context that specifies a referring object. */ URIContext(const std::string& referrer); /** Copy constructor. */ URIContext(const URIContext& rhs); /** dtor */ virtual ~URIContext() { } /** Serializes this context to an Options structure. This is useful when passing context information to an osgDB::ReaderWriter that takes a stream as input -- the stream is anonymous, therefore this is the preferred way to communicate the context information. */ void store( osgDB::Options* options ); /** Creates a context from the serialized version in an Options structure (see above) */ URIContext( const osgDB::Options* options ); /** Returns the referring object. */ const std::string& referrer() const { return _referrer; } /** True if the context is empty */ bool empty() const { return _referrer.empty(); } /** Parents the input context with the current object, placing the subContext's information under it. Used to re-parent relative locations with a higher-level referrer object. */ URIContext add( const URIContext& subContext ) const; /** Returns a new context with the sub path appended to the current referrer path. */ URIContext add( const std::string& subPath ) const; /** creates a string suitable for passing to an osgDB::ReaderWriter implementation */ std::string getOSGPath( const std::string& target ) const; //! Add a header name/value pair to use when requesting a remote URL void addHeader(const std::string& name, const std::string& value); //! Headers in this URIContext const Headers& getHeaders() const; //! Headers in this URIContext Headers& getHeaders(); private: friend class URI; std::string _referrer; Headers _headers; }; //-------------------------------------------------------------------- /** * Stream container for reading a URI directly from a stream */ class OSGEARTH_EXPORT URIStream { public: URIStream(const URI& uri, std::ios_base::openmode mode =std::ios_base::in); virtual ~URIStream(); public: // auto-cast to istream operator std::istream& (); protected: friend class URI; std::istream* _instream; }; //-------------------------------------------------------------------- /** * Represents the location of a resource, providing the raw (original, possibly * relative) and absolute forms. * * URI is serializable and may be used in an earth file, like in the following * example. Note that in earth files, the URI is actually called "url"; this is * simply because of an old convention and we wish to avoid breaking backwards * compatibility. * * ../path/relative/to/earth/file * * Note also that a relative URI will be relative to the location of the * parent resource (usually the earth file itself). * * You can also specify osgDB plugin options; for example: * * ../path/to/image.jpg * * Of course, options are particular to OSG plugins, so please consult the * code for your plugin for more information. */ class OSGEARTH_EXPORT URI { public: /** * Constructs an empty (and invalid) URI. */ URI(); /** * Constructs a new URI from a location (typically an absolute url) */ URI( const std::string& location ); /** * Constructs a new URI from a location and an existing context. */ URI( const std::string& location, const URIContext& context ); /** * Constructs a new URI from a string. */ URI( const char* location ); /** dtor */ virtual ~URI() { } public: /** The base (possibly relative) location string. */ const std::string& base() const { return _baseURI; } /** The fully qualified location string. */ const std::string& full() const { return _fullURI; } /** The dereference operator also returns the fully qualified URI, since it's a common operation. */ const std::string& operator * () const { return _fullURI; } /** Context with which this URI was created. */ const URIContext& context() const { return _context; } /** Whether the URI is empty */ bool empty() const { return _baseURI.empty(); } /** Whether the object of the URI is cacheable. */ bool isRemote() const; /** Returns a copy of this URI with the suffix appended */ URI append( const std::string& suffix ) const; /** String used for keying the cache */ const std::string& cacheKey() const { return !_cacheKey.empty() ? _cacheKey : _fullURI; } /** osgDB::Options option string (plugin options) */ optional& optionString() { return _optionString; } const optional& optionString() const { return _optionString; } public: /** Sets a cache key. By default the cache key is the full URI, but you can override that. */ void setCacheKey( const std::string& key ) { _cacheKey = key; } public: // read methods return a ReadResult object ReadResult readObject( const osgDB::Options* dbOptions =0L, ProgressCallback* progress =0L ) const; ReadResult readImage( const osgDB::Options* dbOptions =0L, ProgressCallback* progress =0L ) const; ReadResult readNode( const osgDB::Options* dbOptions =0L, ProgressCallback* progress =0L ) const; ReadResult readString( const osgDB::Options* dbOptions =0L, ProgressCallback* progress =0L ) const; ReadResult readConfig( const osgDB::Options* dbOptions =0L, ProgressCallback* progress =0L ) const; public: // get methods call the read* methods, then just return the raw data. osg::Object* getObject( const osgDB::Options* dbOptions =0L, ProgressCallback* progress =0L ) const { return readObject(dbOptions, progress).releaseObject(); } osg::Image* getImage( const osgDB::Options* dbOptions =0L, ProgressCallback* progress =0L ) const { return readImage(dbOptions, progress).releaseImage(); } osg::Node* getNode( const osgDB::Options* dbOptions =0L, ProgressCallback* progress =0L ) const { return readNode(dbOptions, progress).releaseNode(); } std::string getString( const osgDB::Options* dbOptions =0L, ProgressCallback* progress =0L ) const { return readString(dbOptions, progress).getString(); } public: bool operator < ( const URI& rhs ) const { return _fullURI < rhs._fullURI; } bool operator == ( const URI& rhs ) const { return _fullURI.compare(rhs._fullURI) == 0; } bool operator != ( const URI& rhs ) const { return _fullURI.compare(rhs._fullURI) != 0; } public: /** Copier */ URI( const URI& rhs ); public: // config methods Config getConfig() const; void mergeConfig(const Config& conf); public: // Static convenience methods /** Encodes text to URL safe test. Escapes special charaters */ inline static std::string urlEncode(const std::string &value); protected: std::string _baseURI; std::string _fullURI; std::string _cacheKey; URIContext _context; optional _optionString; void ctorCacheKey(); }; } //------------------------------------------------------------------------ namespace std { // std::hash specialization for URI template<> struct hash { inline size_t operator()(const osgEarth::URI& value) const { return hash()(value.full()); } }; } //------------------------------------------------------------------------ namespace osgEarth { /** * A lookup table that maps URI references to other URI references. This * is used as an optional resource mapping table. (See KML's ResourceMap * for usage example) * * WARNING: osgDB::Options will only store a raw pointer to the class, so * make sure the scope of the osgDB::Options does not exceed the scope of * the embedded alias map! */ class OSGEARTH_EXPORT URIAliasMap { public: /** * Inserts a key-value pair into the map. */ void insert( const std::string& key, const std::string& value ); /** * Resolves the input address into a URI string. */ std::string resolve(const std::string& input, const URIContext& context) const; /** * True if there are no mappings */ bool empty() const { return _map.empty(); } /** * Clears out the map. */ void clear() { _map.clear(); } /** * Loads an alias map from an Options. */ static URIAliasMap* from( const osgDB::Options* options ) { return options ? const_cast(static_cast(options->getPluginData("osgEarth::URIAliasMap"))) : 0L; } /** * Stores an alias map in an Options */ void apply( osgDB::Options* options ) { if ( options ) options->setPluginData("osgEarth::URIAliasMap", this); } protected: std::map _map; friend class Config; }; /** * A custom read callback (that you can set in an osgDB::Options) that will * attempt to resolve pathnames using a URI alias map. */ class OSGEARTH_EXPORT URIAliasMapReadCallback : public osgDB::ReadFileCallback { public: URIAliasMapReadCallback( const URIAliasMap& aliasMap, const URIContext& context ); virtual osgDB::ReaderWriter::ReadResult openArchive(const std::string& filename, osgDB::ReaderWriter::ArchiveStatus status, unsigned int indexBlockSizeHint, const osgDB::Options* useObjectCache); virtual osgDB::ReaderWriter::ReadResult readObject(const std::string& filename, const osgDB::Options* options); virtual osgDB::ReaderWriter::ReadResult readImage(const std::string& filename, const osgDB::Options* options); virtual osgDB::ReaderWriter::ReadResult readHeightField(const std::string& filename, const osgDB::Options* options); virtual osgDB::ReaderWriter::ReadResult readNode(const std::string& filename, const osgDB::Options* options); virtual osgDB::ReaderWriter::ReadResult readShader(const std::string& filename, const osgDB::Options* options); protected: const URIAliasMap& _aliasMap; URIContext _context; }; //------------------------------------------------------------------------ /** * A URI result cache that you can embed in an osgDB::Options, and if found, * URI will attempt to use it. * * WARNING: osgDB::Options will only store a raw pointer to the class, so * make sure the scope of the osgDB::Options does not exceed the scope of * the embedded cache! */ struct /*header-only*/ URIResultCache : public LRUCache { URIResultCache( bool threadsafe =true ) : LRUCache( threadsafe ) { } static URIResultCache* from(const osgDB::Options* options) { return options ? const_cast(static_cast(options->getPluginData("osgEarth::URIResultCache"))) : 0L; } void apply( osgDB::Options* options ) { if ( options ) options->setPluginData("osgEarth::URIResultCache", this); } }; //------------------------------------------------------------------------ /** * You can install a post-read callback in a osgDB::Options and the URI * class till invoke it on a ReadResult before returning. */ class /*header-only*/ URIPostReadCallback : public osg::Referenced { public: URIPostReadCallback() { } virtual ~URIPostReadCallback() { } virtual void operator()( ReadResult& result ) =0; public: void apply(osgDB::Options* options) { if ( options ) options->setPluginData("osgEarth::URIPostReadCallback", this); } static URIPostReadCallback* from(const osgDB::Options* options) { return options ? const_cast(static_cast(options->getPluginData("osgEarth::URIPostReadCallback"))) : 0L; } }; //------------------------------------------------------------------------ // Config specialization for URI: template<> inline void Config::set( const std::string& key, const optional& opt ) { if ( opt.isSet() ) { remove(key); set( key, opt->getConfig() ); } } template<> inline bool Config::get( const std::string& key, optional& output ) const { if ( hasChild(key) ) { const Config& uriconf = child(key); if (!uriconf.value().empty()) { URI uri(uriconf.value(), uriconf.referrer()); uri.mergeConfig(uriconf); output = uri; return true; } else return false; } else return false; } // Config specialization for URIAliasMap template <> inline void Config::set( const std::string& key, const optional& map ) { remove( key ); if ( map.isSet() ) { Config conf( key ); for( std::map::const_iterator i = map->_map.begin(); i != map->_map.end(); ++i ) { Config alias( "alias" ); alias.add( "source", i->first ); alias.add( "target", i->second ); conf.add( alias ); } set(conf); } } template <> inline bool Config::get( const std::string& key, optional& output ) const { Config alias = child(key); if ( !alias.empty() ) { for( ConfigSet::const_iterator i = alias.children().begin(); i != alias.children().end(); ++i ) { std::string source = i->value("source"); std::string target = i->value("target"); if ( !source.empty() && !target.empty() ) output.mutable_value().insert( source, target ); } return true; } else { return false; } } //------------------------------------------------------------------------ // Ref.: https://stackoverflow.com/questions/154536/encode-decode-urls-in-c std::string URI::urlEncode(const std::string &value) { std::ostringstream escaped; escaped.fill('0'); escaped << std::hex; for (std::string::const_iterator i = value.begin(), n = value.end(); i != n; ++i) { std::string::value_type c = (*i); // Keep alphanumeric and other accepted characters intact if (isalnum(c) || c == '-' || c == '_' || c == '.' || c == '~') { escaped << c; continue; } // Any other characters are percent-encoded escaped << std::uppercase; escaped << '%' << std::setw(2) << int((unsigned char)c); escaped << std::nouppercase; } return escaped.str(); } } #endif // OSGEARTH_URI