|
|
@ -30,6 +30,7 @@
|
|
|
|
#include <algorithm>
|
|
|
|
#include <algorithm>
|
|
|
|
#include <cassert>
|
|
|
|
#include <cassert>
|
|
|
|
#include <string>
|
|
|
|
#include <string>
|
|
|
|
|
|
|
|
#include <expat.h>
|
|
|
|
|
|
|
|
|
|
|
|
// Adobe XMP Toolkit
|
|
|
|
// Adobe XMP Toolkit
|
|
|
|
#ifdef EXV_HAVE_XMP_TOOLKIT
|
|
|
|
#ifdef EXV_HAVE_XMP_TOOLKIT
|
|
|
@ -42,6 +43,168 @@
|
|
|
|
# include <XMP.incl_cpp>
|
|
|
|
# include <XMP.incl_cpp>
|
|
|
|
#endif // EXV_HAVE_XMP_TOOLKIT
|
|
|
|
#endif // EXV_HAVE_XMP_TOOLKIT
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// This anonymous namespace contains a class named XMLValidator, which uses
|
|
|
|
|
|
|
|
// libexpat to do a basic validation check on an XML document. This is to
|
|
|
|
|
|
|
|
// reduce the chance of hitting a bug in the (third-party) xmpsdk
|
|
|
|
|
|
|
|
// library. For example, it is easy to a trigger a stack overflow in xmpsdk
|
|
|
|
|
|
|
|
// with a deeply nested tree.
|
|
|
|
|
|
|
|
namespace {
|
|
|
|
|
|
|
|
using namespace Exiv2;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class XMLValidator {
|
|
|
|
|
|
|
|
size_t element_depth_ = 0;
|
|
|
|
|
|
|
|
size_t namespace_depth_ = 0;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// These fields are used to record whether an error occurred during
|
|
|
|
|
|
|
|
// parsing. Why do we need to store the error for later, rather
|
|
|
|
|
|
|
|
// than throw an exception immediately? Because expat is a C
|
|
|
|
|
|
|
|
// library, so it isn't designed to be able to handle exceptions
|
|
|
|
|
|
|
|
// thrown by the callback functions. Throwing exceptions during
|
|
|
|
|
|
|
|
// parsing is an example of one of the things that xmpsdk does
|
|
|
|
|
|
|
|
// wrong, leading to problems like https://github.com/Exiv2/exiv2/issues/1821.
|
|
|
|
|
|
|
|
bool haserror_ = false;
|
|
|
|
|
|
|
|
std::string errmsg_;
|
|
|
|
|
|
|
|
XML_Size errlinenum_ = 0;
|
|
|
|
|
|
|
|
XML_Size errcolnum_ = 0;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Very deeply nested XML trees can cause a stack overflow in
|
|
|
|
|
|
|
|
// xmpsdk. They are also very unlikely to be valid XMP, so we
|
|
|
|
|
|
|
|
// error out if the depth exceeds this limit.
|
|
|
|
|
|
|
|
static const size_t max_recursion_limit_ = 1000;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const XML_Parser parser_;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
public:
|
|
|
|
|
|
|
|
// Runs an XML parser on `buf`. Throws an exception if the XML is invalid.
|
|
|
|
|
|
|
|
static void check(const char* buf, size_t buflen) {
|
|
|
|
|
|
|
|
XMLValidator validator;
|
|
|
|
|
|
|
|
validator.check_internal(buf, buflen);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
private:
|
|
|
|
|
|
|
|
// Private constructor, because this class is only constructed by
|
|
|
|
|
|
|
|
// the (static) check method.
|
|
|
|
|
|
|
|
XMLValidator() : parser_(XML_ParserCreateNS(0, '@')) {
|
|
|
|
|
|
|
|
if (!parser_) {
|
|
|
|
|
|
|
|
throw Error(kerXMPToolkitError, "Could not create expat parser");
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
~XMLValidator() {
|
|
|
|
|
|
|
|
XML_ParserFree(parser_);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
void setError(const char* msg) {
|
|
|
|
|
|
|
|
const XML_Size errlinenum = XML_GetCurrentLineNumber(parser_);
|
|
|
|
|
|
|
|
const XML_Size errcolnum = XML_GetCurrentColumnNumber(parser_);
|
|
|
|
|
|
|
|
#ifndef SUPPRESS_WARNINGS
|
|
|
|
|
|
|
|
EXV_INFO << "Invalid XML at line " << errlinenum
|
|
|
|
|
|
|
|
<< ", column " << errcolnum
|
|
|
|
|
|
|
|
<< ": " << msg << "\n";
|
|
|
|
|
|
|
|
#endif
|
|
|
|
|
|
|
|
// If this is the first error, then save it.
|
|
|
|
|
|
|
|
if (!haserror_) {
|
|
|
|
|
|
|
|
haserror_ = true;
|
|
|
|
|
|
|
|
errmsg_ = msg;
|
|
|
|
|
|
|
|
errlinenum_ = errlinenum;
|
|
|
|
|
|
|
|
errcolnum_ = errcolnum;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
void check_internal(const char* buf, size_t buflen) {
|
|
|
|
|
|
|
|
if (buflen > static_cast<size_t>(std::numeric_limits<int>::max())) {
|
|
|
|
|
|
|
|
throw Error(kerXMPToolkitError, "Buffer length is greater than INT_MAX");
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
XML_SetUserData(parser_, this);
|
|
|
|
|
|
|
|
XML_SetElementHandler(parser_, startElement_cb, endElement_cb);
|
|
|
|
|
|
|
|
XML_SetNamespaceDeclHandler(parser_, startNamespace_cb, endNamespace_cb);
|
|
|
|
|
|
|
|
XML_SetStartDoctypeDeclHandler(parser_, startDTD_cb);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const XML_Status result = XML_Parse(parser_, buf, static_cast<int>(buflen), true);
|
|
|
|
|
|
|
|
if (result == XML_STATUS_ERROR) {
|
|
|
|
|
|
|
|
setError(XML_ErrorString(XML_GetErrorCode(parser_)));
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (haserror_) {
|
|
|
|
|
|
|
|
throw XMP_Error(kXMPErr_BadXML, "Error in XMLValidator");
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
void startElement(const XML_Char*, const XML_Char**) noexcept {
|
|
|
|
|
|
|
|
if (element_depth_ > max_recursion_limit_) {
|
|
|
|
|
|
|
|
setError("Too deeply nested");
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
++element_depth_;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
void endElement(const XML_Char*) noexcept {
|
|
|
|
|
|
|
|
if (element_depth_ > 0) {
|
|
|
|
|
|
|
|
--element_depth_;
|
|
|
|
|
|
|
|
} else {
|
|
|
|
|
|
|
|
setError("Negative depth");
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
void startNamespace(const XML_Char*, const XML_Char*) noexcept {
|
|
|
|
|
|
|
|
if (namespace_depth_ > max_recursion_limit_) {
|
|
|
|
|
|
|
|
setError("Too deeply nested");
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
++namespace_depth_;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
void endNamespace(const XML_Char*) noexcept {
|
|
|
|
|
|
|
|
if (namespace_depth_ > 0) {
|
|
|
|
|
|
|
|
--namespace_depth_;
|
|
|
|
|
|
|
|
} else {
|
|
|
|
|
|
|
|
setError("Negative depth");
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
void startDTD(const XML_Char*, const XML_Char*, const XML_Char*, int) noexcept {
|
|
|
|
|
|
|
|
// DOCTYPE is used for XXE attacks.
|
|
|
|
|
|
|
|
setError("DOCTYPE not supported");
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// This callback function is called by libexpat. It's a static wrapper
|
|
|
|
|
|
|
|
// around startElement().
|
|
|
|
|
|
|
|
static void XMLCALL startElement_cb(
|
|
|
|
|
|
|
|
void* userData, const XML_Char* name, const XML_Char* *attrs
|
|
|
|
|
|
|
|
) noexcept {
|
|
|
|
|
|
|
|
static_cast<XMLValidator*>(userData)->startElement(name, attrs);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// This callback function is called by libexpat. It's a static wrapper
|
|
|
|
|
|
|
|
// around endElement().
|
|
|
|
|
|
|
|
static void XMLCALL endElement_cb(void* userData, const XML_Char* name) noexcept {
|
|
|
|
|
|
|
|
static_cast<XMLValidator*>(userData)->endElement(name);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// This callback function is called by libexpat. It's a static wrapper
|
|
|
|
|
|
|
|
// around startNamespace().
|
|
|
|
|
|
|
|
static void XMLCALL startNamespace_cb(
|
|
|
|
|
|
|
|
void* userData, const XML_Char* prefix, const XML_Char* uri
|
|
|
|
|
|
|
|
) noexcept {
|
|
|
|
|
|
|
|
static_cast<XMLValidator*>(userData)->startNamespace(prefix, uri);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// This callback function is called by libexpat. It's a static wrapper
|
|
|
|
|
|
|
|
// around endNamespace().
|
|
|
|
|
|
|
|
static void XMLCALL endNamespace_cb(void* userData, const XML_Char* prefix) noexcept {
|
|
|
|
|
|
|
|
static_cast<XMLValidator*>(userData)->endNamespace(prefix);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
static void XMLCALL startDTD_cb(
|
|
|
|
|
|
|
|
void *userData, const XML_Char *doctypeName, const XML_Char *sysid,
|
|
|
|
|
|
|
|
const XML_Char *pubid, int has_internal_subset
|
|
|
|
|
|
|
|
) noexcept {
|
|
|
|
|
|
|
|
static_cast<XMLValidator*>(userData)->startDTD(
|
|
|
|
|
|
|
|
doctypeName, sysid, pubid, has_internal_subset);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
} // namespace
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// *****************************************************************************
|
|
|
|
// *****************************************************************************
|
|
|
|
// local declarations
|
|
|
|
// local declarations
|
|
|
|
namespace {
|
|
|
|
namespace {
|
|
|
@ -601,6 +764,7 @@ namespace Exiv2 {
|
|
|
|
return 2;
|
|
|
|
return 2;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
XMLValidator::check(xmpPacket.data(), xmpPacket.size());
|
|
|
|
SXMPMeta meta(xmpPacket.data(), static_cast<XMP_StringLen>(xmpPacket.size()));
|
|
|
|
SXMPMeta meta(xmpPacket.data(), static_cast<XMP_StringLen>(xmpPacket.size()));
|
|
|
|
SXMPIterator iter(meta);
|
|
|
|
SXMPIterator iter(meta);
|
|
|
|
std::string schemaNs, propPath, propValue;
|
|
|
|
std::string schemaNs, propPath, propValue;
|
|
|
|