Add support for PDF (needs uPDFParser library)

This commit is contained in:
Grégory Soutadé 2021-08-21 20:57:31 +02:00
parent 8bc346d139
commit 3d9e343734
13 changed files with 262 additions and 35 deletions

2
.gitignore vendored
View File

@ -4,5 +4,5 @@ lib
*.so *.so
*~ *~
utils/acsmdownloader utils/acsmdownloader
utils/activate utils/adept_activate
.adept .adept

View File

@ -2,8 +2,21 @@
AR ?= $(CROSS)ar AR ?= $(CROSS)ar
CXX ?= $(CROSS)g++ CXX ?= $(CROSS)g++
CXXFLAGS=-Wall -fPIC -I./include -I./lib -I./lib/pugixml/src/ UPDFPARSERLIB = ./lib/updfparser/libupdfparser.a
LDFLAGS=
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),) ifneq ($(DEBUG),)
CXXFLAGS += -ggdb -O0 CXXFLAGS += -ggdb -O0
@ -18,12 +31,12 @@ TARGETDIR := bin
SRCEXT := cpp SRCEXT := cpp
OBJEXT := o 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))) OBJECTS := $(patsubst $(SRCDIR)/%,$(BUILDDIR)/%,$(SOURCES:.$(SRCEXT)=.$(OBJEXT)))
.PHONY: utils .PHONY: utils
all: lib obj libgourou utils all: lib obj $(TARGETS) utils
lib: lib:
mkdir lib mkdir lib
@ -38,9 +51,9 @@ $(BUILDDIR)/%.$(OBJEXT): $(SRCDIR)/%.$(SRCEXT)
libgourou: libgourou.a libgourou.so libgourou: libgourou.a libgourou.so
libgourou.a: $(OBJECTS) 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 $(CXX) obj/*.o $(LDFLAGS) -o $@ -shared
utils: utils:

View File

@ -47,7 +47,7 @@ Compilation
Use _make_ command 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-) 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) 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 Utils
----- -----

View File

@ -21,6 +21,7 @@
#define _DRMPROCESSORCLIENT_H_ #define _DRMPROCESSORCLIENT_H_
#include <string> #include <string>
#include <bytearray.h>
namespace gourou namespace gourou
{ {
@ -93,11 +94,14 @@ namespace gourou
/** /**
* @brief Send HTTP (GET or POST) request * @brief Send HTTP (GET or POST) request
* *
* @param URL HTTP URL * @param URL HTTP URL
* @param POSTData POST data if needed, if not set, a GET request is done * @param POSTData POST data if needed, if not set, a GET request is done
* @param contentType Optional content type of POST Data * @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<std::string, std::string>* responseHeaders=0) = 0;
}; };
class RSAInterface class RSAInterface
@ -355,6 +359,27 @@ namespace gourou
* @param handler ZIP file handler * @param handler ZIP file handler
*/ */
virtual void zipClose(void* handler) = 0; 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, \ class DRMProcessorClient: public DigestInterface, public RandomInterface, public HTTPInterface, \

View File

@ -53,10 +53,16 @@ namespace gourou
*/ */
std::string getDownloadURL(); std::string getDownloadURL();
/**
* @brief Return resource value
*/
std::string getResource();
private: private:
pugi::xml_node metadatas; pugi::xml_node metadatas;
pugi::xml_document rights; pugi::xml_document rights;
std::string downloadURL; std::string downloadURL;
std::string resource;
void buildRights(const pugi::xml_node& licenseToken, User* user); void buildRights(const pugi::xml_node& licenseToken, User* user);
}; };

View File

@ -40,7 +40,7 @@
#define ACS_SERVER "http://adeactivate.adobe.com/adept" #define ACS_SERVER "http://adeactivate.adobe.com/adept"
#endif #endif
#define LIBGOUROU_VERSION "0.3.2" #define LIBGOUROU_VERSION "0.4"
namespace gourou namespace gourou
{ {
@ -53,6 +53,7 @@ namespace gourou
static const std::string VERSION; 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) * @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 item Item from fulfill() method
* @param path Output file path * @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) * @brief SignIn into ACS Server (required to activate device)
@ -130,8 +133,11 @@ namespace gourou
* @param URL HTTP URL * @param URL HTTP URL
* @param POSTData POST data if needed, if not set, a GET request is done * @param POSTData POST data if needed, if not set, a GET request is done
* @param contentType Optional content type of POST Data * @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<std::string, std::string>* responseHeaders=0);
/** /**
* @brief Send HTTP POST request to URL with document as POSTData * @brief Send HTTP POST request to URL with document as POSTData

View File

@ -233,6 +233,32 @@ namespace gourou
return trim(res); 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 * @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) static inline void writeFile(std::string path, ByteArray& data)
{ {
writeFile(path, data.data(), data.length()); writeFile(path, data.data(), data.length()-1);
} }
/** /**

View File

@ -12,3 +12,11 @@ fi
if [ ! -d lib/base64 ] ; then if [ ! -d lib/base64 ] ; then
git clone https://gist.github.com/f0fd86b6c73063283afe550bc5d77594.git lib/base64 git clone https://gist.github.com/f0fd86b6c73063283afe550bc5d77594.git lib/base64
fi 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

View File

@ -22,6 +22,8 @@
#include <time.h> #include <time.h>
#include <vector> #include <vector>
#include <uPDFParser.h>
#include <libgourou.h> #include <libgourou.h>
#include <libgourou_common.h> #include <libgourou_common.h>
#include <libgourou_log.h> #include <libgourou_log.h>
@ -299,11 +301,11 @@ namespace gourou
appendTextElem(root, "adept:expiration", buffer); 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<std::string, std::string>* responseHeaders)
{ {
if (contentType == 0) if (contentType == 0)
contentType = ""; contentType = "";
std::string reply = client->sendHTTPRequest(URL, POSTdata, contentType); std::string reply = client->sendHTTPRequest(URL, POSTdata, contentType, responseHeaders);
pugi::xml_document replyDoc; pugi::xml_document replyDoc;
replyDoc.load_buffer(reply.c_str(), reply.length()); replyDoc.load_buffer(reply.c_str(), reply.length());
@ -580,12 +582,16 @@ namespace gourou
return new FulfillmentItem(fulfillReply, user); 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) if (!item)
EXCEPTION(DW_NO_ITEM, "No item"); EXCEPTION(DW_NO_ITEM, "No item");
std::map<std::string, std::string> headers;
ByteArray replyData = sendRequest(item->getDownloadURL()); ByteArray replyData = sendRequest(item->getDownloadURL(), "", 0, &headers);
writeFile(path, replyData); writeFile(path, replyData);
@ -593,9 +599,53 @@ namespace gourou
std::string rightsStr = item->getRights(); std::string rightsStr = item->getRights();
void* handler = client->zipOpen(path); if (headers.count("Content-Type") && headers["Content-Type"] == "application/pdf")
client->zipWriteFile(handler, "META-INF/rights.xml", rightsStr); res = PDF;
client->zipClose(handler);
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<uPDFParser::Object*> objects = parser.objects();
std::vector<uPDFParser::Object*>::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, void DRMProcessor::buildSignInRequest(pugi::xml_document& signInRequest,

View File

@ -5,7 +5,7 @@ CXXFLAGS=-Wall `pkg-config --cflags Qt5Core Qt5Network` -fPIC -I$(ROOT)/include
ifneq ($(STATIC_UTILS),) ifneq ($(STATIC_UTILS),)
LDFLAGS=`pkg-config --libs Qt5Core Qt5Network` -L$(ROOT) $(ROOT)/libgourou.a -lcrypto -lzip LDFLAGS=`pkg-config --libs Qt5Core Qt5Network` -L$(ROOT) $(ROOT)/libgourou.a -lcrypto -lzip
else 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 endif
ifneq ($(DEBUG),) ifneq ($(DEBUG),)

View File

@ -79,9 +79,7 @@ public:
{ {
filename = item->getMetadata("title"); filename = item->getMetadata("title");
if (filename == "") if (filename == "")
filename = "output.epub"; filename = "output";
else
filename += ".epub";
} }
else else
filename = outputFile; filename = outputFile;
@ -95,7 +93,19 @@ public:
filename = std::string(outputDir) + "/" + filename; 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; std::cout << "Created " << filename << std::endl;
} catch(std::exception& e) } catch(std::exception& e)
{ {

View File

@ -38,6 +38,7 @@
#include <QFile> #include <QFile>
#include <zip.h> #include <zip.h>
#include <zlib.h>
#include <libgourou_common.h> #include <libgourou_common.h>
#include <libgourou_log.h> #include <libgourou_log.h>
@ -82,7 +83,7 @@ void DRMProcessorClientImpl::randBytes(unsigned char* bytesOut, unsigned int len
} }
/* HTTP interface */ /* 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<std::string, std::string>* responseHeaders)
{ {
QNetworkRequest request(QUrl(URL.c_str())); QNetworkRequest request(QUrl(URL.c_str()));
QNetworkAccessManager networkManager; QNetworkAccessManager networkManager;
@ -121,12 +122,12 @@ std::string DRMProcessorClientImpl::sendHTTPRequest(const std::string& URL, cons
if (reply->error() != QNetworkReply::NoError) if (reply->error() != QNetworkReply::NoError)
EXCEPTION(gourou::CLIENT_NETWORK_ERROR, "Error " << reply->errorString().toStdString()); EXCEPTION(gourou::CLIENT_NETWORK_ERROR, "Error " << reply->errorString().toStdString());
if (gourou::logLevel >= gourou::DEBUG) QList<QByteArray> headers = reply->rawHeaderList();
{ for (int i = 0; i < headers.size(); ++i) {
QList<QByteArray> headers = reply->rawHeaderList(); if (gourou::logLevel >= gourou::DEBUG)
for (int i = 0; i < headers.size(); ++i) {
std::cout << headers[i].constData() << " : " << reply->rawHeader(headers[i]).constData() << std::endl; 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(); replyData = reply->readAll();
@ -420,3 +421,78 @@ void DRMProcessorClientImpl::zipClose(void* handler)
{ {
zip_close((zip_t*)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));
}

View File

@ -46,7 +46,7 @@ class DRMProcessorClientImpl : public gourou::DRMProcessorClient
virtual void randBytes(unsigned char* bytesOut, unsigned int length); virtual void randBytes(unsigned char* bytesOut, unsigned int length);
/* HTTP interface */ /* 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<std::string, std::string>* responseHeaders=0);
virtual void RSAPrivateEncrypt(const unsigned char* RSAKey, unsigned int RSAKeyLength, virtual void RSAPrivateEncrypt(const unsigned char* RSAKey, unsigned int RSAKeyLength,
const RSA_KEY_TYPE keyType, const std::string& password, 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 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 #endif