1 /* 2 * Copyright (C) 2017 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 #define LOG_TAG "ValidateAudioConfig" 18 #include <utils/Log.h> 19 20 #include <numeric> 21 22 #define LIBXML_SCHEMAS_ENABLED 23 #include <libxml/xmlschemastypes.h> 24 #define LIBXML_XINCLUDE_ENABLED 25 #include <libxml/xinclude.h> 26 27 #include <memory> 28 #include <string> 29 30 #include "ValidateXml.h" 31 32 namespace android { 33 namespace hardware { 34 namespace audio { 35 namespace common { 36 namespace test { 37 namespace utility { 38 39 /** Map libxml2 structures to their corresponding deleters. */ 40 template <class T> 41 constexpr void (*xmlDeleter)(T* t); 42 template <> 43 constexpr auto xmlDeleter<xmlSchema> = xmlSchemaFree; 44 template <> 45 constexpr auto xmlDeleter<xmlDoc> = xmlFreeDoc; 46 template <> 47 constexpr auto xmlDeleter<xmlSchemaParserCtxt> = xmlSchemaFreeParserCtxt; 48 template <> 49 constexpr auto xmlDeleter<xmlSchemaValidCtxt> = xmlSchemaFreeValidCtxt; 50 51 /** @return a unique_ptr with the correct deleter for the libxml2 object. */ 52 template <class T> 53 constexpr auto make_xmlUnique(T* t) { 54 // Wrap deleter in lambda to enable empty base optimization 55 auto deleter = [](T* t) { xmlDeleter<T>(t); }; 56 return std::unique_ptr<T, decltype(deleter)>{t, deleter}; 57 } 58 59 /** Class that handles libxml2 initialization and cleanup. NOT THREAD SAFE*/ 60 struct Libxml2Global { 61 Libxml2Global() { 62 xmlLineNumbersDefault(1); // Better error message 63 xmlSetGenericErrorFunc(this, errorCb); 64 } 65 ~Libxml2Global() { 66 // TODO: check if all those cleanup are needed 67 xmlSetGenericErrorFunc(nullptr, nullptr); 68 xmlSchemaCleanupTypes(); 69 xmlCleanupParser(); 70 xmlCleanupThreads(); 71 } 72 73 const std::string& getErrors() { return errors; } 74 75 private: 76 static void errorCb(void* ctxt, const char* msg, ...) { 77 auto* self = static_cast<Libxml2Global*>(ctxt); 78 va_list args; 79 va_start(args, msg); 80 81 char* formatedMsg; 82 if (vasprintf(&formatedMsg, msg, args) >= 0) { 83 LOG_PRI(ANDROID_LOG_ERROR, LOG_TAG, "%s", formatedMsg); 84 self->errors += "Error: "; 85 self->errors += formatedMsg; 86 } 87 free(formatedMsg); 88 89 va_end(args); 90 } 91 std::string errors; 92 }; 93 94 ::testing::AssertionResult validateXml(const char* xmlFilePathExpr, const char* xsdFilePathExpr, 95 const char* xmlFilePath, const char* xsdFilePath) { 96 Libxml2Global libxml2; 97 98 auto context = [&]() { 99 return std::string() + " While validating: " + xmlFilePathExpr + 100 "\n Which is: " + xmlFilePath + "\nAgainst the schema: " + xsdFilePathExpr + 101 "\n Which is: " + xsdFilePath + "\nLibxml2 errors:\n" + libxml2.getErrors(); 102 }; 103 104 auto schemaParserCtxt = make_xmlUnique(xmlSchemaNewParserCtxt(xsdFilePath)); 105 auto schema = make_xmlUnique(xmlSchemaParse(schemaParserCtxt.get())); 106 if (schema == nullptr) { 107 return ::testing::AssertionFailure() << "Failed to parse schema (xsd)\n" << context(); 108 } 109 110 auto doc = make_xmlUnique(xmlReadFile(xmlFilePath, nullptr, 0)); 111 if (doc == nullptr) { 112 return ::testing::AssertionFailure() << "Failed to parse xml\n" << context(); 113 } 114 115 if (xmlXIncludeProcess(doc.get()) == -1) { 116 return ::testing::AssertionFailure() << "Failed to resolve xincludes in xml\n" << context(); 117 } 118 119 auto schemaCtxt = make_xmlUnique(xmlSchemaNewValidCtxt(schema.get())); 120 int ret = xmlSchemaValidateDoc(schemaCtxt.get(), doc.get()); 121 if (ret > 0) { 122 return ::testing::AssertionFailure() << "XML is not valid according to the xsd\n" 123 << context(); 124 } 125 if (ret < 0) { 126 return ::testing::AssertionFailure() << "Internal or API error\n" << context(); 127 } 128 129 return ::testing::AssertionSuccess(); 130 } 131 132 ::testing::AssertionResult validateXmlMultipleLocations( 133 const char* xmlFileNameExpr, const char* xmlFileLocationsExpr, const char* xsdFilePathExpr, 134 const char* xmlFileName, std::vector<const char*> xmlFileLocations, const char* xsdFilePath) { 135 using namespace std::string_literals; 136 137 std::vector<std::string> errors; 138 std::vector<std::string> foundFiles; 139 140 for (const char* location : xmlFileLocations) { 141 std::string xmlFilePath = location + "/"s + xmlFileName; 142 if (access(xmlFilePath.c_str(), F_OK) != 0) { 143 // If the file does not exist ignore this location and fallback on the next one 144 continue; 145 } 146 foundFiles.push_back(" " + xmlFilePath + '\n'); 147 auto result = validateXml("xmlFilePath", xsdFilePathExpr, xmlFilePath.c_str(), xsdFilePath); 148 if (!result) { 149 errors.push_back(result.message()); 150 } 151 } 152 153 if (foundFiles.empty()) { 154 errors.push_back("No xml file found in provided locations.\n"); 155 } 156 157 return ::testing::AssertionResult(errors.empty()) 158 << errors.size() << " error" << (errors.size() == 1 ? " " : "s ") 159 << std::accumulate(begin(errors), end(errors), "occurred during xml validation:\n"s) 160 << " While validating all: " << xmlFileNameExpr 161 << "\n Which is: " << xmlFileName 162 << "\n In the following folders: " << xmlFileLocationsExpr 163 << "\n Which is: " << ::testing::PrintToString(xmlFileLocations); 164 } 165 166 } // namespace utility 167 } // namespace test 168 } // namespace common 169 } // namespace audio 170 } // namespace hardware 171 } // namespace android 172