From 3d9e34373431982c29b4ff1f3152251df41ac88c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gr=C3=A9gory=20Soutad=C3=A9?= Date: Sat, 21 Aug 2021 20:57:31 +0200 Subject: [PATCH] Add support for PDF (needs uPDFParser library) --- .gitignore | 2 +- Makefile | 25 ++++++--- README.md | 5 +- include/drmprocessorclient.h | 33 ++++++++++-- include/fulfillment_item.h | 6 +++ include/libgourou.h | 12 +++-- include/libgourou_common.h | 28 +++++++++- scripts/setup.sh | 8 +++ src/libgourou.cpp | 64 ++++++++++++++++++++--- utils/Makefile | 2 +- utils/acsmdownloader.cpp | 18 +++++-- utils/drmprocessorclientimpl.cpp | 88 +++++++++++++++++++++++++++++--- utils/drmprocessorclientimpl.h | 6 ++- 13 files changed, 262 insertions(+), 35 deletions(-) diff --git a/.gitignore b/.gitignore index 4c47778..078a810 100644 --- a/.gitignore +++ b/.gitignore @@ -4,5 +4,5 @@ lib *.so *~ utils/acsmdownloader -utils/activate +utils/adept_activate .adept diff --git a/Makefile b/Makefile index e6426f0..9e2ba52 100644 --- a/Makefile +++ b/Makefile @@ -2,8 +2,21 @@ AR ?= $(CROSS)ar CXX ?= $(CROSS)g++ -CXXFLAGS=-Wall -fPIC -I./include -I./lib -I./lib/pugixml/src/ -LDFLAGS= +UPDFPARSERLIB = ./lib/updfparser/libupdfparser.a + +CXXFLAGS=-Wall -fPIC -I./include -I./lib -I./lib/pugixml/src/ -I./lib/updfparser/include +LDFLAGS = $(UPDFPARSERLIB) + +BUILD_STATIC ?= 0 +BUILD_SHARED ?= 1 + +TARGETS = +ifneq (BUILD_STATIC, 0) + TARGETS += libgourou.a +endif +ifneq (BUILD_SHARED, 0) + TARGETS += libgourou.so +endif ifneq ($(DEBUG),) CXXFLAGS += -ggdb -O0 @@ -18,12 +31,12 @@ TARGETDIR := bin SRCEXT := cpp OBJEXT := o -SOURCES=src/libgourou.cpp src/user.cpp src/device.cpp src/fulfillment_item.cpp src/bytearray.cpp src/pugixml.cpp +SOURCES = src/libgourou.cpp src/user.cpp src/device.cpp src/fulfillment_item.cpp src/bytearray.cpp src/pugixml.cpp OBJECTS := $(patsubst $(SRCDIR)/%,$(BUILDDIR)/%,$(SOURCES:.$(SRCEXT)=.$(OBJEXT))) .PHONY: utils -all: lib obj libgourou utils +all: lib obj $(TARGETS) utils lib: mkdir lib @@ -38,9 +51,9 @@ $(BUILDDIR)/%.$(OBJEXT): $(SRCDIR)/%.$(SRCEXT) libgourou: libgourou.a libgourou.so libgourou.a: $(OBJECTS) - $(AR) crs $@ obj/*.o + $(AR) crs $@ obj/*.o $(LDFLAGS) -libgourou.so: libgourou.a +libgourou.so: $(OBJECTS) $(UPDFPARSERLIB) $(CXX) obj/*.o $(LDFLAGS) -o $@ -shared utils: diff --git a/README.md b/README.md index a312f2d..ff7b51a 100644 --- a/README.md +++ b/README.md @@ -47,7 +47,7 @@ Compilation Use _make_ command - make [CROSS=XXX] [DEBUG=1] [STATIC_UTILS=1] + make [CROSS=XXX] [DEBUG=1] [STATIC_UTILS=1] [BUILD_STATIC=(0|1)] [BUILD_SHARED=(0|1)] CROSS can define a cross compiler prefix (ie arm-linux-gnueabihf-) @@ -55,6 +55,9 @@ DEBUG can be set to compile in DEBUG mode STATIC_UTILS to build utils with static library (libgourou.a) instead of default dynamic one (libgourou.so) +BUILD_STATIC build libgourou.a if 1, nothing if 0 (default value), can be combined with BUILD_SHARED + +BUILD_SHARED build libgourou.so if 1 (default value), nothing if 0, can be combined with BUILD_STATIC Utils ----- diff --git a/include/drmprocessorclient.h b/include/drmprocessorclient.h index a5c8ab5..df07042 100644 --- a/include/drmprocessorclient.h +++ b/include/drmprocessorclient.h @@ -21,6 +21,7 @@ #define _DRMPROCESSORCLIENT_H_ #include +#include namespace gourou { @@ -93,11 +94,14 @@ namespace gourou /** * @brief Send HTTP (GET or POST) request * - * @param URL HTTP URL - * @param POSTData POST data if needed, if not set, a GET request is done - * @param contentType Optional content type of POST Data + * @param URL HTTP URL + * @param POSTData POST data if needed, if not set, a GET request is done + * @param contentType Optional content type of POST Data + * @param responseHeaders Optional Response headers of HTTP request + * + * @return data of HTTP response */ - virtual std::string sendHTTPRequest(const std::string& URL, const std::string& POSTData=std::string(""), const std::string& contentType=std::string("")) = 0; + virtual std::string sendHTTPRequest(const std::string& URL, const std::string& POSTData=std::string(""), const std::string& contentType=std::string(""), std::map* responseHeaders=0) = 0; }; class RSAInterface @@ -355,6 +359,27 @@ namespace gourou * @param handler ZIP file handler */ virtual void zipClose(void* handler) = 0; + + /** + * @brief Inflate algorithm + * + * @param data Data to inflate + * @param result Zipped data + * @param wbits Window bits value for libz + */ + virtual void inflate(std::string data, gourou::ByteArray& result, + int wbits=-15) = 0; + + /** + * @brief Deflate algorithm + * + * @param data Data to deflate + * @param result Unzipped data + * @param wbits Window bits value for libz + * @param compressionLevel Compression level for libz + */ + virtual void deflate(std::string data, gourou::ByteArray& result, + int wbits=-15, int compressionLevel=8) = 0; }; class DRMProcessorClient: public DigestInterface, public RandomInterface, public HTTPInterface, \ diff --git a/include/fulfillment_item.h b/include/fulfillment_item.h index 4e12d67..28074a8 100644 --- a/include/fulfillment_item.h +++ b/include/fulfillment_item.h @@ -53,10 +53,16 @@ namespace gourou */ std::string getDownloadURL(); + /** + * @brief Return resource value + */ + std::string getResource(); + private: pugi::xml_node metadatas; pugi::xml_document rights; std::string downloadURL; + std::string resource; void buildRights(const pugi::xml_node& licenseToken, User* user); }; diff --git a/include/libgourou.h b/include/libgourou.h index 8c6e957..f949215 100644 --- a/include/libgourou.h +++ b/include/libgourou.h @@ -40,7 +40,7 @@ #define ACS_SERVER "http://adeactivate.adobe.com/adept" #endif -#define LIBGOUROU_VERSION "0.3.2" +#define LIBGOUROU_VERSION "0.4" namespace gourou { @@ -53,6 +53,7 @@ namespace gourou static const std::string VERSION; + enum ITEM_TYPE { EPUB=0, PDF }; /** * @brief Main constructor. To be used once all is configured (user has signedIn, device is activated) * @@ -80,8 +81,10 @@ namespace gourou * * @param item Item from fulfill() method * @param path Output file path + * + * @return Type of downloaded item */ - void download(FulfillmentItem* item, std::string path); + ITEM_TYPE download(FulfillmentItem* item, std::string path); /** * @brief SignIn into ACS Server (required to activate device) @@ -130,8 +133,11 @@ namespace gourou * @param URL HTTP URL * @param POSTData POST data if needed, if not set, a GET request is done * @param contentType Optional content type of POST Data + * @param responseHeaders Optional Response headers of HTTP request + * + * @return data of HTTP response */ - ByteArray sendRequest(const std::string& URL, const std::string& POSTData=std::string(), const char* contentType=0); + ByteArray sendRequest(const std::string& URL, const std::string& POSTData=std::string(), const char* contentType=0, std::map* responseHeaders=0); /** * @brief Send HTTP POST request to URL with document as POSTData diff --git a/include/libgourou_common.h b/include/libgourou_common.h index cd597b9..fc701a0 100644 --- a/include/libgourou_common.h +++ b/include/libgourou_common.h @@ -233,6 +233,32 @@ namespace gourou return trim(res); } + static inline std::string extractTextElem(const pugi::xml_node& doc, const char* tagName, bool throwOnNull=true) + { + pugi::xpath_node xpath_node = doc.select_node(tagName); + + if (!xpath_node) + { + if (throwOnNull) + EXCEPTION(GOUROU_TAG_NOT_FOUND, "Tag " << tagName << " not found"); + + return ""; + } + + pugi::xml_node node = xpath_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 Append an element to root with a sub text element * @@ -267,7 +293,7 @@ namespace gourou */ static inline void writeFile(std::string path, ByteArray& data) { - writeFile(path, data.data(), data.length()); + writeFile(path, data.data(), data.length()-1); } /** diff --git a/scripts/setup.sh b/scripts/setup.sh index a53779d..209ccea 100755 --- a/scripts/setup.sh +++ b/scripts/setup.sh @@ -12,3 +12,11 @@ fi if [ ! -d lib/base64 ] ; then git clone https://gist.github.com/f0fd86b6c73063283afe550bc5d77594.git lib/base64 fi + +# uPDFParser +if [ ! -d lib/updfparser ] ; then + git clone http://indefero.soutade.fr/p/updfparser lib/updfparser + pushd lib/updfparser + make STATIC=1 SHARED=0 + popd +fi diff --git a/src/libgourou.cpp b/src/libgourou.cpp index d86c4aa..97cd166 100644 --- a/src/libgourou.cpp +++ b/src/libgourou.cpp @@ -22,6 +22,8 @@ #include #include +#include + #include #include #include @@ -299,11 +301,11 @@ namespace gourou appendTextElem(root, "adept:expiration", buffer); } - ByteArray DRMProcessor::sendRequest(const std::string& URL, const std::string& POSTdata, const char* contentType) + ByteArray DRMProcessor::sendRequest(const std::string& URL, const std::string& POSTdata, const char* contentType, std::map* responseHeaders) { if (contentType == 0) contentType = ""; - std::string reply = client->sendHTTPRequest(URL, POSTdata, contentType); + std::string reply = client->sendHTTPRequest(URL, POSTdata, contentType, responseHeaders); pugi::xml_document replyDoc; replyDoc.load_buffer(reply.c_str(), reply.length()); @@ -580,12 +582,16 @@ namespace gourou return new FulfillmentItem(fulfillReply, user); } - void DRMProcessor::download(FulfillmentItem* item, std::string path) + DRMProcessor::ITEM_TYPE DRMProcessor::download(FulfillmentItem* item, std::string path) { + ITEM_TYPE res = EPUB; + if (!item) EXCEPTION(DW_NO_ITEM, "No item"); + + std::map headers; - ByteArray replyData = sendRequest(item->getDownloadURL()); + ByteArray replyData = sendRequest(item->getDownloadURL(), "", 0, &headers); writeFile(path, replyData); @@ -593,9 +599,53 @@ namespace gourou std::string rightsStr = item->getRights(); - void* handler = client->zipOpen(path); - client->zipWriteFile(handler, "META-INF/rights.xml", rightsStr); - client->zipClose(handler); + if (headers.count("Content-Type") && headers["Content-Type"] == "application/pdf") + res = PDF; + + if (res == EPUB) + { + void* handler = client->zipOpen(path); + client->zipWriteFile(handler, "META-INF/rights.xml", rightsStr); + client->zipClose(handler); + } + else if (res == PDF) + { + uPDFParser::Parser parser; + + try + { + GOUROU_LOG(DEBUG, "Parse PDF"); + parser.parse(path); + } + catch(std::invalid_argument& e) + { + GOUROU_LOG(ERROR, "Invalid PDF"); + return res; + } + + std::vector objects = parser.objects(); + std::vector::reverse_iterator it; + + for(it = objects.rbegin(); it != objects.rend(); it++) + { + // Update EBX_HANDLER with rights + if ((*it)->hasKey("Filter") && (**it)["Filter"]->str() == "/EBX_HANDLER") + { + uPDFParser::Object* ebx = (*it)->clone(); + (*ebx)["ADEPT_ID"] = new uPDFParser::String(item->getResource()); + (*ebx)["EBX_BOOKID"] = new uPDFParser::String(item->getResource()); + ByteArray zipped; + client->deflate(rightsStr, zipped); + (*ebx)["ADEPT_LICENSE"] = new uPDFParser::String(zipped.toBase64()); + parser.addObject(ebx); + break; + } + } + + parser.write(path, true); + } + + return res; } void DRMProcessor::buildSignInRequest(pugi::xml_document& signInRequest, diff --git a/utils/Makefile b/utils/Makefile index 701d275..07c8a49 100644 --- a/utils/Makefile +++ b/utils/Makefile @@ -5,7 +5,7 @@ CXXFLAGS=-Wall `pkg-config --cflags Qt5Core Qt5Network` -fPIC -I$(ROOT)/include ifneq ($(STATIC_UTILS),) LDFLAGS=`pkg-config --libs Qt5Core Qt5Network` -L$(ROOT) $(ROOT)/libgourou.a -lcrypto -lzip else -LDFLAGS=`pkg-config --libs Qt5Core Qt5Network` -L$(ROOT) -lgourou -lcrypto -lzip +LDFLAGS=`pkg-config --libs Qt5Core Qt5Network` -L$(ROOT) -lgourou -lcrypto -lzip -lz endif ifneq ($(DEBUG),) diff --git a/utils/acsmdownloader.cpp b/utils/acsmdownloader.cpp index af0a566..3c67567 100644 --- a/utils/acsmdownloader.cpp +++ b/utils/acsmdownloader.cpp @@ -79,9 +79,7 @@ public: { filename = item->getMetadata("title"); if (filename == "") - filename = "output.epub"; - else - filename += ".epub"; + filename = "output"; } else filename = outputFile; @@ -95,7 +93,19 @@ public: filename = std::string(outputDir) + "/" + filename; } - processor.download(item, filename); + gourou::DRMProcessor::ITEM_TYPE type = processor.download(item, filename); + + if (!outputFile) + { + std::string finalName = filename; + if (type == gourou::DRMProcessor::ITEM_TYPE::PDF) + finalName += ".pdf"; + else + finalName += ".epub"; + QDir dir; + dir.rename(filename.c_str(), finalName.c_str()); + filename = finalName; + } std::cout << "Created " << filename << std::endl; } catch(std::exception& e) { diff --git a/utils/drmprocessorclientimpl.cpp b/utils/drmprocessorclientimpl.cpp index a622325..3ede540 100644 --- a/utils/drmprocessorclientimpl.cpp +++ b/utils/drmprocessorclientimpl.cpp @@ -38,6 +38,7 @@ #include #include +#include #include #include @@ -82,7 +83,7 @@ void DRMProcessorClientImpl::randBytes(unsigned char* bytesOut, unsigned int len } /* HTTP interface */ -std::string DRMProcessorClientImpl::sendHTTPRequest(const std::string& URL, const std::string& POSTData, const std::string& contentType) +std::string DRMProcessorClientImpl::sendHTTPRequest(const std::string& URL, const std::string& POSTData, const std::string& contentType, std::map* responseHeaders) { QNetworkRequest request(QUrl(URL.c_str())); QNetworkAccessManager networkManager; @@ -121,12 +122,12 @@ std::string DRMProcessorClientImpl::sendHTTPRequest(const std::string& URL, cons if (reply->error() != QNetworkReply::NoError) EXCEPTION(gourou::CLIENT_NETWORK_ERROR, "Error " << reply->errorString().toStdString()); - if (gourou::logLevel >= gourou::DEBUG) - { - QList headers = reply->rawHeaderList(); - for (int i = 0; i < headers.size(); ++i) { + QList headers = reply->rawHeaderList(); + for (int i = 0; i < headers.size(); ++i) { + if (gourou::logLevel >= gourou::DEBUG) std::cout << headers[i].constData() << " : " << reply->rawHeader(headers[i]).constData() << std::endl; - } + if (responseHeaders) + (*responseHeaders)[headers[i].constData()] = reply->rawHeader(headers[i]).constData(); } replyData = reply->readAll(); @@ -420,3 +421,78 @@ void DRMProcessorClientImpl::zipClose(void* handler) { zip_close((zip_t*)handler); } + +void DRMProcessorClientImpl::inflate(std::string data, gourou::ByteArray& result, + int wbits) +{ + unsigned int dataSize = data.size()*2; + unsigned char* buffer = new unsigned char[dataSize]; + + z_stream infstream; + + infstream.zalloc = Z_NULL; + infstream.zfree = Z_NULL; + infstream.opaque = Z_NULL; + + infstream.avail_in = (uInt)data.size(); + infstream.next_in = (Bytef *)data.c_str(); // input char array + infstream.avail_out = (uInt)dataSize; // size of output + infstream.next_out = (Bytef *)buffer; // output char array + + int ret = inflateInit2(&infstream, wbits); + + ret = ::inflate(&infstream, Z_SYNC_FLUSH); + while (ret == Z_OK || ret == Z_STREAM_END) + { + result.append(buffer, dataSize-infstream.avail_out); + if (ret == Z_STREAM_END) break; + infstream.avail_out = (uInt)dataSize; // size of output + infstream.next_out = (Bytef *)buffer; // output char array + ret = ::inflate(&infstream, Z_SYNC_FLUSH); + } + + inflateEnd(&infstream); + + delete[] buffer; + + if (ret != Z_OK && ret != Z_STREAM_END && ret != Z_BUF_ERROR) + EXCEPTION(gourou::CLIENT_ZIP_ERROR, zError(ret)); +} + +void DRMProcessorClientImpl::deflate(std::string data, gourou::ByteArray& result, + int wbits, int compressionLevel) +{ + unsigned int dataSize = data.size(); + unsigned char* buffer = new unsigned char[dataSize]; + + z_stream defstream; + + defstream.zalloc = Z_NULL; + defstream.zfree = Z_NULL; + defstream.opaque = Z_NULL; + + defstream.avail_in = (uInt)data.size(); + defstream.next_in = (Bytef *)data.c_str(); // input char array + defstream.avail_out = (uInt)dataSize; // size of output + defstream.next_out = (Bytef *)buffer; // output char array + + int ret = deflateInit2(&defstream, Z_DEFAULT_COMPRESSION, Z_DEFLATED, wbits, + compressionLevel, Z_DEFAULT_STRATEGY); + + ret = ::deflate(&defstream, Z_SYNC_FLUSH); + while (ret == Z_OK || ret == Z_STREAM_END) + { + result.append(buffer, dataSize-defstream.avail_out); + if (ret == Z_STREAM_END) break; + defstream.avail_out = (uInt)dataSize; // size of output + defstream.next_out = (Bytef *)buffer; // output char array + ret = ::deflate(&defstream, Z_SYNC_FLUSH); + } + + deflateEnd(&defstream); + + delete[] buffer; + + if (ret != Z_OK && ret != Z_STREAM_END && ret != Z_BUF_ERROR) + EXCEPTION(gourou::CLIENT_ZIP_ERROR, zError(ret)); +} diff --git a/utils/drmprocessorclientimpl.h b/utils/drmprocessorclientimpl.h index f475d96..7e599f5 100644 --- a/utils/drmprocessorclientimpl.h +++ b/utils/drmprocessorclientimpl.h @@ -46,7 +46,7 @@ class DRMProcessorClientImpl : public gourou::DRMProcessorClient virtual void randBytes(unsigned char* bytesOut, unsigned int length); /* HTTP interface */ - virtual std::string sendHTTPRequest(const std::string& URL, const std::string& POSTData=std::string(""), const std::string& contentType=std::string("")); + virtual std::string sendHTTPRequest(const std::string& URL, const std::string& POSTData=std::string(""), const std::string& contentType=std::string(""), std::map* responseHeaders=0); virtual void RSAPrivateEncrypt(const unsigned char* RSAKey, unsigned int RSAKeyLength, const RSA_KEY_TYPE keyType, const std::string& password, @@ -108,6 +108,10 @@ class DRMProcessorClientImpl : public gourou::DRMProcessorClient virtual void zipClose(void* handler); + virtual void inflate(std::string data, gourou::ByteArray& result, int wbits=-15); + + virtual void deflate(std::string data, gourou::ByteArray& result, + int wbits=-15, int compressionLevel=8); }; #endif