From a283a255a584283f30a6b94e47ee7d944164a0e3 Mon Sep 17 00:00:00 2001 From: Malcolm Tyrrell Date: Wed, 6 Oct 2021 13:41:45 +0100 Subject: [PATCH] Allow schema checking of glTF2 file. --- code/AssetLib/glTF2/glTF2Asset.h | 13 ++++- code/AssetLib/glTF2/glTF2Asset.inl | 77 +++++++++++++++++++++------ code/AssetLib/glTF2/glTF2Importer.cpp | 25 ++++----- code/AssetLib/glTF2/glTF2Importer.h | 4 ++ code/Common/Importer.cpp | 20 +++++++ code/Common/Importer.h | 8 ++- include/assimp/Importer.hpp | 15 ++++++ include/assimp/config.h.in | 9 ++++ 8 files changed, 139 insertions(+), 32 deletions(-) diff --git a/code/AssetLib/glTF2/glTF2Asset.h b/code/AssetLib/glTF2/glTF2Asset.h index 9a50ede99..862fb861d 100644 --- a/code/AssetLib/glTF2/glTF2Asset.h +++ b/code/AssetLib/glTF2/glTF2Asset.h @@ -74,6 +74,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include #include #include +#include #if (__GNUC__ == 8 && __GNUC_MINOR__ >= 0) # pragma GCC diagnostic pop @@ -1066,6 +1067,7 @@ class Asset { private: IOSystem *mIOSystem; + rapidjson::IRemoteSchemaDocumentProvider *mSchemaDocumentProvider; std::string mCurrentAssetDir; @@ -1125,8 +1127,9 @@ public: Ref scene; public: - Asset(IOSystem *io = nullptr) : + Asset(IOSystem *io = nullptr, rapidjson::IRemoteSchemaDocumentProvider *schemaDocumentProvider = nullptr) : mIOSystem(io), + mSchemaDocumentProvider(schemaDocumentProvider), asset(), accessors(*this, "accessors"), animations(*this, "animations"), @@ -1149,6 +1152,9 @@ public: //! Main function void Load(const std::string &file, bool isBinary = false); + //! Parse the AssetMetadata and check that the version is 2. + bool CanRead(const std::string &pFile, bool isBinary = false); + //! Enables binary encoding on the asset void SetAsBinary(); @@ -1160,6 +1166,11 @@ public: private: void ReadBinaryHeader(IOStream &stream, std::vector &sceneData); + //! Obtain a JSON document from the stream. + // \param second argument is a buffer used by the document. It must be kept + // alive while the document is in use. + Document ReadDocument(IOStream& stream, bool isBinary, std::vector& sceneData); + void ReadExtensionsUsed(Document &doc); void ReadExtensionsRequired(Document &doc); diff --git a/code/AssetLib/glTF2/glTF2Asset.inl b/code/AssetLib/glTF2/glTF2Asset.inl index 4832995cc..b531b5a98 100644 --- a/code/AssetLib/glTF2/glTF2Asset.inl +++ b/code/AssetLib/glTF2/glTF2Asset.inl @@ -1777,28 +1777,16 @@ inline void Asset::ReadBinaryHeader(IOStream &stream, std::vector &sceneDa } } -inline void Asset::Load(const std::string &pFile, bool isBinary) { +inline rapidjson::Document Asset::ReadDocument(IOStream &stream, bool isBinary, std::vector &sceneData) +{ ASSIMP_LOG_DEBUG("Loading GLTF2 asset"); - mCurrentAssetDir.clear(); - /*int pos = std::max(int(pFile.rfind('/')), int(pFile.rfind('\\'))); - if (pos != int(std::string::npos)) */ - - if (0 != strncmp(pFile.c_str(), AI_MEMORYIO_MAGIC_FILENAME, AI_MEMORYIO_MAGIC_FILENAME_LENGTH)) { - mCurrentAssetDir = glTFCommon::getCurrentAssetDir(pFile); - } - - shared_ptr stream(OpenFile(pFile.c_str(), "rb", true)); - if (!stream) { - throw DeadlyImportError("GLTF: Could not open file for reading"); - } // is binary? then read the header - std::vector sceneData; if (isBinary) { SetAsBinary(); // also creates the body buffer - ReadBinaryHeader(*stream, sceneData); + ReadBinaryHeader(stream, sceneData); } else { - mSceneLength = stream->FileSize(); + mSceneLength = stream.FileSize(); mBodyLength = 0; // read the scene data @@ -1806,7 +1794,7 @@ inline void Asset::Load(const std::string &pFile, bool isBinary) { sceneData.resize(mSceneLength + 1); sceneData[mSceneLength] = '\0'; - if (stream->Read(&sceneData[0], 1, mSceneLength) != mSceneLength) { + if (stream.Read(&sceneData[0], 1, mSceneLength) != mSceneLength) { throw DeadlyImportError("GLTF: Could not read the file contents"); } } @@ -1826,6 +1814,42 @@ inline void Asset::Load(const std::string &pFile, bool isBinary) { throw DeadlyImportError("GLTF: JSON document root must be a JSON object"); } + return doc; +} + +inline void Asset::Load(const std::string &pFile, bool isBinary) +{ + mCurrentAssetDir.clear(); + /*int pos = std::max(int(pFile.rfind('/')), int(pFile.rfind('\\'))); + if (pos != int(std::string::npos)) */ + + if (0 != strncmp(pFile.c_str(), AI_MEMORYIO_MAGIC_FILENAME, AI_MEMORYIO_MAGIC_FILENAME_LENGTH)) { + mCurrentAssetDir = glTFCommon::getCurrentAssetDir(pFile); + } + + shared_ptr stream(OpenFile(pFile.c_str(), "rb", true)); + if (!stream) { + throw DeadlyImportError("GLTF: Could not open file for reading"); + } + + std::vector sceneData; + rapidjson::Document doc = ReadDocument(*stream, isBinary, sceneData); + + // If a schemaDocumentProvider is available, see if the glTF schema is present. + // If so, use it to validate the document. + if (mSchemaDocumentProvider) { + if (const rapidjson::SchemaDocument *gltfSchema = mSchemaDocumentProvider->GetRemoteDocument("glTF.schema.json", 16)) { + rapidjson::SchemaValidator validator(*gltfSchema); + if (!doc.Accept(validator)) { + rapidjson::StringBuffer pathBuffer; + validator.GetInvalidSchemaPointer().StringifyUriFragment(pathBuffer); + rapidjson::StringBuffer argumentBuffer; + validator.GetInvalidDocumentPointer().StringifyUriFragment(argumentBuffer); + throw DeadlyImportError("GLTF: The JSON document did not satisfy the glTF2 schema. Schema keyword: ", validator.GetInvalidSchemaKeyword(), ", document path: ", pathBuffer.GetString(), ", argument: ", argumentBuffer.GetString()); + } + } + } + // Fill the buffer instance for the current file embedded contents if (mBodyLength > 0) { if (!mBodyBuffer->LoadFromStream(*stream, mBodyLength, mBodyOffset)) { @@ -1882,6 +1906,25 @@ inline void Asset::Load(const std::string &pFile, bool isBinary) { } } +inline bool Asset::CanRead(const std::string &pFile, bool isBinary) +{ + try + { + shared_ptr stream(OpenFile(pFile.c_str(), "rb", true)); + if (!stream) { + return false; + } + std::vector sceneData; + rapidjson::Document doc = ReadDocument(*stream, isBinary, sceneData); + asset.Read(doc); + } + catch (...) + { + return false; + } + return true; +} + inline void Asset::SetAsBinary() { if (!mBodyBuffer) { mBodyBuffer = buffers.Create("binary_glTF"); diff --git a/code/AssetLib/glTF2/glTF2Importer.cpp b/code/AssetLib/glTF2/glTF2Importer.cpp index 6c92bdc87..02f07a935 100644 --- a/code/AssetLib/glTF2/glTF2Importer.cpp +++ b/code/AssetLib/glTF2/glTF2Importer.cpp @@ -111,19 +111,16 @@ const aiImporterDesc *glTF2Importer::GetInfo() const { return &desc; } -bool glTF2Importer::CanRead(const std::string &pFile, IOSystem *pIOHandler, bool /* checkSig */) const { - const std::string &extension = GetExtension(pFile); +bool glTF2Importer::CanRead(const std::string &pFile, IOSystem *pIOHandler, bool checkSig ) const { + const std::string &extension = GetExtension(pFile); - if (extension != "gltf" && extension != "glb") { - return false; - } + if (!checkSig && (extension != "gltf") && (extension != "glb")) + return false; - if (pIOHandler) { - glTF2::Asset asset(pIOHandler); - asset.Load(pFile, extension == "glb"); - std::string version = asset.asset.version; - return !version.empty() && version[0] == '2'; - } + if (pIOHandler) { + glTF2::Asset asset(pIOHandler); + return asset.CanRead(pFile, extension == "glb"); + } return false; } @@ -1587,7 +1584,7 @@ void glTF2Importer::InternReadFile(const std::string &pFile, aiScene *pScene, IO this->mScene = pScene; // read the asset file - glTF2::Asset asset(pIOHandler); + glTF2::Asset asset(pIOHandler, static_cast(mSchemaDocumentProvider)); asset.Load(pFile, GetExtension(pFile) == "glb"); if (asset.scene) { pScene->mName = asset.scene->name; @@ -1613,4 +1610,8 @@ void glTF2Importer::InternReadFile(const std::string &pFile, aiScene *pScene, IO } } +void glTF2Importer::SetupProperties(const Importer *pImp) { + mSchemaDocumentProvider = static_cast(pImp->GetPropertyPointer(AI_CONFIG_IMPORT_SCHEMA_DOCUMENT_PROVIDER)); +} + #endif // ASSIMP_BUILD_NO_GLTF_IMPORTER diff --git a/code/AssetLib/glTF2/glTF2Importer.h b/code/AssetLib/glTF2/glTF2Importer.h index e586dc6cc..23830c0eb 100644 --- a/code/AssetLib/glTF2/glTF2Importer.h +++ b/code/AssetLib/glTF2/glTF2Importer.h @@ -65,6 +65,7 @@ public: protected: const aiImporterDesc *GetInfo() const override; void InternReadFile(const std::string &pFile, aiScene *pScene, IOSystem *pIOHandler) override; + virtual void SetupProperties(const Importer *pImp) override; private: void ImportEmbeddedTextures(glTF2::Asset &a); @@ -80,6 +81,9 @@ private: std::vector meshOffsets; std::vector embeddedTexIdxs; aiScene *mScene; + + /// An instance of rapidjson::IRemoteSchemaDocumentProvider + void *mSchemaDocumentProvider = nullptr; }; } // namespace Assimp diff --git a/code/Common/Importer.cpp b/code/Common/Importer.cpp index d0ed3c788..41b73fd12 100644 --- a/code/Common/Importer.cpp +++ b/code/Common/Importer.cpp @@ -1072,6 +1072,18 @@ bool Importer::SetPropertyMatrix(const char* szName, const aiMatrix4x4& value) { return existing; } +// ------------------------------------------------------------------------------------------------ +// Set a configuration property +bool Importer::SetPropertyPointer(const char* szName, void* value) { + ai_assert(nullptr != pimpl); + + bool existing; + ASSIMP_BEGIN_EXCEPTION_REGION(); + existing = SetGenericProperty(pimpl->mPointerProperties, szName,value); + ASSIMP_END_EXCEPTION_REGION(bool); + return existing; +} + // ------------------------------------------------------------------------------------------------ // Get a configuration property int Importer::GetPropertyInteger(const char* szName, int iErrorReturn /*= 0xffffffff*/) const { @@ -1104,6 +1116,14 @@ aiMatrix4x4 Importer::GetPropertyMatrix(const char* szName, const aiMatrix4x4& i return GetGenericProperty(pimpl->mMatrixProperties,szName,iErrorReturn); } +// ------------------------------------------------------------------------------------------------ +// Get a configuration property +void* Importer::GetPropertyPointer(const char* szName, void* iErrorReturn /*= nullptr*/) const { + ai_assert(nullptr != pimpl); + + return GetGenericProperty(pimpl->mPointerProperties,szName,iErrorReturn); +} + // ------------------------------------------------------------------------------------------------ // Get the memory requirements of a single node inline diff --git a/code/Common/Importer.h b/code/Common/Importer.h index 0e04f9452..f79b38214 100644 --- a/code/Common/Importer.h +++ b/code/Common/Importer.h @@ -73,12 +73,12 @@ public: // Data type to store the key hash typedef unsigned int KeyType; - // typedefs for our four configuration maps. - // We don't need more, so there is no need for a generic solution + // typedefs for our configuration maps. typedef std::map IntPropertyMap; typedef std::map FloatPropertyMap; typedef std::map StringPropertyMap; typedef std::map MatrixPropertyMap; + typedef std::map PointerPropertyMap; /** IO handler to use for all file accesses. */ IOSystem* mIOHandler; @@ -116,6 +116,9 @@ public: /** List of Matrix properties */ MatrixPropertyMap mMatrixProperties; + /** List of pointer properties */ + PointerPropertyMap mPointerProperties; + /** Used for testing - extra verbose mode causes the ValidateDataStructure-Step * to be executed before and after every single post-process step */ bool bExtraVerbose; @@ -142,6 +145,7 @@ ImporterPimpl::ImporterPimpl() AI_NO_EXCEPT : mFloatProperties(), mStringProperties(), mMatrixProperties(), + mPointerProperties(), bExtraVerbose( false ), mPPShared( nullptr ) { // empty diff --git a/include/assimp/Importer.hpp b/include/assimp/Importer.hpp index 09b5b6883..6ce327c4f 100644 --- a/include/assimp/Importer.hpp +++ b/include/assimp/Importer.hpp @@ -245,6 +245,12 @@ public: */ bool SetPropertyMatrix(const char *szName, const aiMatrix4x4 &sValue); + // ------------------------------------------------------------------- + /** Set a pointer configuration property. + * @see SetPropertyInteger() + */ + bool SetPropertyPointer(const char *szName, void *sValue); + // ------------------------------------------------------------------- /** Get a configuration property. * @param szName Name of the property. All supported properties @@ -297,6 +303,15 @@ public: aiMatrix4x4 GetPropertyMatrix(const char *szName, const aiMatrix4x4 &sErrorReturn = aiMatrix4x4()) const; + // ------------------------------------------------------------------- + /** Get a pointer configuration property + * + * The return value remains valid until the property is modified. + * @see GetPropertyInteger() + */ + void* GetPropertyPointer(const char *szName, + void *sErrorReturn = nullptr) const; + // ------------------------------------------------------------------- /** Supplies a custom IO handler to the importer to use to open and * access files. If you need the importer to use custom IO logic to diff --git a/include/assimp/config.h.in b/include/assimp/config.h.in index a89d4e837..bf0076572 100644 --- a/include/assimp/config.h.in +++ b/include/assimp/config.h.in @@ -547,6 +547,15 @@ enum aiComponent // Various stuff to fine-tune the behaviour of specific importer plugins. // ########################################################################### +// --------------------------------------------------------------------------- +/** @brief Importers which parse JSON may use this to obtain a pointer to a + * rapidjson::IRemoteSchemaDocumentProvider. + * + * The default value is nullptr + * Property type: void* + */ +#define AI_CONFIG_IMPORT_SCHEMA_DOCUMENT_PROVIDER \ + "IMPORT_SCHEMA_DOCUMENT_PROVIDER" // --------------------------------------------------------------------------- /** @brief Set whether the fbx importer will merge all geometry layers present