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
*~
utils/acsmdownloader
utils/activate
utils/adept_activate
.adept

View File

@ -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:

View File

@ -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
-----

View File

@ -21,6 +21,7 @@
#define _DRMPROCESSORCLIENT_H_
#include <string>
#include <bytearray.h>
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<std::string, std::string>* 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, \

View File

@ -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);
};

View File

@ -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<std::string, std::string>* responseHeaders=0);
/**
* @brief Send HTTP POST request to URL with document as POSTData

View File

@ -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);
}
/**

View File

@ -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

View File

@ -22,6 +22,8 @@
#include <time.h>
#include <vector>
#include <uPDFParser.h>
#include <libgourou.h>
#include <libgourou_common.h>
#include <libgourou_log.h>
@ -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<std::string, std::string>* 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<std::string, std::string> 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<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,

View File

@ -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),)

View File

@ -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)
{

View File

@ -38,6 +38,7 @@
#include <QFile>
#include <zip.h>
#include <zlib.h>
#include <libgourou_common.h>
#include <libgourou_log.h>
@ -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<std::string, std::string>* 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<QByteArray> headers = reply->rawHeaderList();
for (int i = 0; i < headers.size(); ++i) {
QList<QByteArray> 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));
}

View File

@ -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<std::string, std::string>* 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