/* Copyright 2021 Grégory Soutadé This file is part of libgourou. libgourou 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 3 of the License, or (at your option) any later version. libgourou 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 libgourou. If not, see <http://www.gnu.org/licenses/>. */ #ifndef _LIBGOUROU_COMMON_H_ #define _LIBGOUROU_COMMON_H_ #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <unistd.h> #include <pugixml.hpp> #include <exception> #include <iostream> #include <sstream> #include <iomanip> #include <algorithm> #include <string.h> #include <libgourou_log.h> #include "bytearray.h" namespace gourou { /** * Some common utilities */ #define ADOBE_ADEPT_NS "http://ns.adobe.com/adept" static const int SHA1_LEN = 20; static const int RSA_KEY_SIZE = 128; static const int RSA_KEY_SIZE_BITS = (RSA_KEY_SIZE*8); enum GOUROU_ERROR { GOUROU_DEVICE_DOES_NOT_MATCH = 0x1000, GOUROU_INVALID_CLIENT, GOUROU_TAG_NOT_FOUND, GOUROU_ADEPT_ERROR, GOUROU_FILE_ERROR, GOUROU_INVALID_PROPERTY }; enum FULFILL_ERROR { FF_ACSM_FILE_NOT_EXISTS = 0x1100, FF_INVALID_ACSM_FILE, FF_NO_HMAC_IN_ACSM_FILE, FF_NOT_ACTIVATED, FF_NO_OPERATOR_URL, FF_SERVER_INTERNAL_ERROR }; enum DOWNLOAD_ERROR { DW_NO_ITEM = 0x1200, DW_NO_EBX_HANDLER, }; enum SIGNIN_ERROR { SIGN_INVALID_CREDENTIALS = 0x1300, }; enum ACTIVATE_ERROR { ACTIVATE_NOT_SIGNEDIN = 0x1400 }; enum DEV_ERROR { DEV_MKPATH = 0x2000, DEV_MAC_ERROR, DEV_INVALID_DEVICE_FILE, DEV_INVALID_DEVICE_KEY_FILE, DEV_INVALID_DEV_PROPERTY, }; enum USER_ERROR { USER_MKPATH = 0x3000, USER_INVALID_ACTIVATION_FILE, USER_NO_AUTHENTICATION_URL, USER_NO_PROPERTY, USER_INVALID_INPUT, }; enum FULFILL_ITEM_ERROR { FFI_INVALID_FULFILLMENT_DATA = 0x4000, FFI_INVALID_LOAN_TOKEN }; enum CLIENT_ERROR { CLIENT_BAD_PARAM = 0x5000, CLIENT_INVALID_PKCS12, CLIENT_INVALID_CERTIFICATE, CLIENT_NO_PRIV_KEY, CLIENT_NO_PUB_KEY, CLIENT_RSA_ERROR, CLIENT_BAD_CHAINING, CLIENT_BAD_KEY_SIZE, CLIENT_BAD_ZIP_FILE, CLIENT_ZIP_ERROR, CLIENT_GENERIC_EXCEPTION, CLIENT_NETWORK_ERROR, CLIENT_INVALID_PKCS8, CLIENT_FILE_ERROR, CLIENT_OSSL_ERROR, CLIENT_CRYPT_ERROR, CLIENT_DIGEST_ERROR, CLIENT_HTTP_ERROR }; enum DRM_REMOVAL_ERROR { DRM_ERR_ENCRYPTION_KEY = 0x6000, DRM_VERSION_NOT_SUPPORTED, DRM_FILE_ERROR, DRM_FORMAT_NOT_SUPPORTED, DRM_IN_OUT_EQUALS, DRM_MISSING_PARAMETER, DRM_INVALID_KEY_SIZE, DRM_ERR_ENCRYPTION_KEY_FP, DRM_INVALID_USER }; #ifndef _NOEXCEPT #if __STDC_VERSION__ >= 201112L # define _NOEXCEPT noexcept # define _NOEXCEPT_(x) noexcept(x) #else # define _NOEXCEPT throw() # define _NOEXCEPT_(x) #endif #endif /* !_NOEXCEPT */ /** * Generic exception class */ class Exception : public std::exception { public: Exception(int code, const char* message, const char* file, int line): code(code), line(line), file(file) { std::stringstream msg; msg << "Exception code : 0x" << std::setbase(16) << code << std::endl; msg << "Message : " << message << std::endl; if (logLevel >= LG_LOG_DEBUG) msg << "File : " << file << ":" << std::setbase(10) << line << std::endl; fullmessage = strdup(msg.str().c_str()); } Exception(const Exception& other) { this->code = other.code; this->line = line; this->file = file; this->fullmessage = strdup(other.fullmessage); } ~Exception() _NOEXCEPT { free(fullmessage); } const char * what () const throw () { return fullmessage; } int getErrorCode() {return code;} private: int code, line; const char* message, *file; char* fullmessage; }; /** * @brief Throw an exception */ #define EXCEPTION(code, message) \ {std::stringstream __msg;__msg << message; throw gourou::Exception(code, __msg.str().c_str(), __FILE__, __LINE__);} /** * Stream writer for pugi::xml */ class StringXMLWriter : public pugi::xml_writer { public: virtual void write(const void* data, size_t size) { result.append(static_cast<const char*>(data), size); } const std::string& getResult() {return result;} private: std::string result; }; static const char* ws = " \t\n\r\f\v"; /** * @brief trim from end of string (right) */ inline std::string& rtrim(std::string& s, const char* t = ws) { s.erase(s.find_last_not_of(t) + 1); return s; } /** * @brief trim from beginning of string (left) */ inline std::string& ltrim(std::string& s, const char* t = ws) { s.erase(0, s.find_first_not_of(t)); return s; } /** * @brief trim from both ends of string (right then left) */ inline std::string& trim(std::string& s, const char* t = ws) { return ltrim(rtrim(s, t), t); } static inline pugi::xml_node getNode(const pugi::xml_node& root, const char* tagName, bool throwOnNull=true) { pugi::xpath_node xpath_node = root.select_node(tagName); if (!xpath_node) { if (throwOnNull) EXCEPTION(GOUROU_TAG_NOT_FOUND, "Tag " << tagName << " not found"); return pugi::xml_node(); } return xpath_node.node(); } /** * @brief Extract text node from tag in document * It can throw an exception if tag does not exists * or just return an empty value */ static inline std::string extractTextElem(const pugi::xml_node& root, const char* tagName, bool throwOnNull=true) { pugi::xml_node node = getNode(root, tagName, throwOnNull); node = node.first_child(); if (!node) { if (throwOnNull) EXCEPTION(GOUROU_TAG_NOT_FOUND, "Text element for tag " << tagName << " not found"); return ""; } std::string res = node.value(); return trim(res); } /** * @brief Set text node of a tag in document * It can throw an exception if tag does not exists */ static inline void setTextElem(const pugi::xml_node& root, const char* tagName, const std::string& value, bool throwOnNull=true) { pugi::xml_node node = getNode(root, tagName, throwOnNull); if (!node) { if (throwOnNull) EXCEPTION(GOUROU_TAG_NOT_FOUND, "Text element for tag " << tagName << " not found"); return; } node = node.first_child(); if (!node) node.append_child(pugi::node_pcdata).set_value(value.c_str()); else node.set_value(value.c_str()); } /** * @brief Extract text attribute from tag in document * It can throw an exception if attribute does not exists * or just return an empty value */ static inline std::string extractTextAttribute(const pugi::xml_node& root, const char* tagName, const char* attributeName, bool throwOnNull=true) { pugi::xml_node node = getNode(root, tagName, throwOnNull); pugi::xml_attribute attr = node.attribute(attributeName); if (!attr) { if (throwOnNull) EXCEPTION(GOUROU_TAG_NOT_FOUND, "Attribute element " << attributeName << " for tag " << tagName << " not found"); return ""; } std::string res = attr.value(); return trim(res); } /** * @brief Append an element to root with a sub text element * * @param root Root node where to put child * @param name Tag name for child * @param value Text child value of tag element */ static inline void appendTextElem(pugi::xml_node& root, const std::string& name, const std::string& value) { pugi::xml_node node = root.append_child(name.c_str()); node.append_child(pugi::node_pcdata).set_value(value.c_str()); } /** * Remove "urn:uuid:" prefix and all '-' from uuid * urn:uuid:9cb786e8-586a-4950-8901-fff8d2ee6025 * -> * 9cb786e8586a49508901fff8d2ee6025 */ static inline std::string extractIdFromUUID(const std::string& uuid) { unsigned int i = 0; std::string res; if (uuid.find("urn:uuid:") == 0) i = 9; for(; i<uuid.size(); i++) { if (uuid[i] != '-') res += uuid[i]; } return res; } /** * @brief Open a file descriptor on path. If it already exists and truncate == true, it's truncated * * @return Created fd, must be closed */ static inline int createNewFile(std::string path, bool truncate=true) { int options = O_CREAT|O_WRONLY; if (truncate) options |= O_TRUNC; else options |= O_APPEND; int fd = open(path.c_str(), options, S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP|S_IROTH); if (fd <= 0) EXCEPTION(GOUROU_FILE_ERROR, "Unable to create " << path); return fd; } /** * @brief Write data in a file. If it already exists, it's truncated */ static inline void writeFile(std::string path, const unsigned char* data, unsigned int length) { int fd = createNewFile(path); if (write(fd, data, length) != length) EXCEPTION(GOUROU_FILE_ERROR, "Write error for file " << path); close (fd); } /** * @brief Write data in a file. If it already exists, it's truncated */ static inline void writeFile(std::string path, ByteArray& data) { writeFile(path, data.data(), data.length()); } /** * @brief Write data in a file. If it already exists, it's truncated */ static inline void writeFile(std::string path, const std::string& data) { writeFile(path, (const unsigned char*)data.c_str(), data.length()); } /** * Read data from file */ static inline void readFile(std::string path, const unsigned char* data, unsigned int length) { int fd = open(path.c_str(), O_RDONLY); if (fd <= 0) EXCEPTION(GOUROU_FILE_ERROR, "Unable to open " << path); if (read(fd, (void*)data, length) != length) EXCEPTION(GOUROU_FILE_ERROR, "Read error for file " << path); close (fd); } #define PATH_MAX_STRING_SIZE 256 // https://gist.github.com/ChisholmKyle/0cbedcd3e64132243a39 /* recursive mkdir */ static inline int mkdir_p(const char *dir, const mode_t mode) { char tmp[PATH_MAX_STRING_SIZE]; char *p = NULL; struct stat sb; size_t len; /* copy path */ len = strnlen (dir, PATH_MAX_STRING_SIZE); if (len == 0 || len == PATH_MAX_STRING_SIZE) { return -1; } memcpy (tmp, dir, len); tmp[len] = '\0'; /* remove trailing slash */ if(tmp[len - 1] == '/') { tmp[len - 1] = '\0'; } /* check if path exists and is a directory */ if (stat (tmp, &sb) == 0) { if (S_ISDIR (sb.st_mode)) { return 0; } } /* recursive mkdir */ for(p = tmp + 1; *p; p++) { if(*p == '/') { *p = 0; /* test path */ if (stat(tmp, &sb) != 0) { /* path does not exist - create directory */ if (mkdir(tmp, mode) < 0) { return -1; } } else if (!S_ISDIR(sb.st_mode)) { /* not a directory */ return -1; } *p = '/'; } } /* test path */ if (stat(tmp, &sb) != 0) { /* path does not exist - create directory */ if (mkdir(tmp, mode) < 0) { return -1; } } else if (!S_ISDIR(sb.st_mode)) { /* not a directory */ return -1; } return 0; } static inline void dumpBuffer(GOUROU_LOG_LEVEL level, const char* title, const unsigned char* data, unsigned int len) { if (gourou::logLevel < level) return; printf("%s", title); for(unsigned int i=0; i<len; i++) { if (i && !(i%16)) printf("\n"); printf("%02x ", data[i]); } printf("\n"); } } #endif