ACSMDownloader/include/rmsdk_wrapper.h

685 lines
20 KiB
C++

/*
Copyright 2021 Grégory Soutadé
This is a free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
It 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 General Public License for more details.
You should have received a copy of the GNU General Public License
along with it. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef RMSDK_WRAPPER_H
#define RMSDK_WRAPPER_H
#include <iostream>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
#include <exception>
#include <adept.h>
#include <librmsdk.h>
#include <dpdrm.h>
#include <dpio.h>
#include <dpdev.h>
#include <dpnet.h>
#include <zip.h>
#include <QNetworkAccessManager>
#include <QNetworkReply>
#include <QCoreApplication>
#include <QFile>
#include <QDir>
#include <log.h>
class MockDevice;
class MockCallback : public dp::Callback
{
virtual void* getOptionalInterface(char const*p0) {return 0;}
virtual void reportError(dp::String const&) {LOG_FUNC();}
virtual void invoke(dp::Unknown*) {LOG_FUNC();}
virtual void virtfunc0(){LOG_FUNC();}
virtual void virtfunc4(){LOG_FUNC();}
virtual void virtfunc8(){LOG_FUNC();}
virtual void virtfunc12(){LOG_FUNC();}
};
class MockArchiveListener : public zip::ArchiveListener
{
virtual void endReadDirectory(zip::Archive* archive){LOG_FUNC();}
virtual void virtfunc20(){LOG_FUNC();}
};
class MockDRMProcessorClient: public dpdrm::DRMProcessorClient
{
public:
MockDRMProcessorClient(const char* outputDir, const char* outputFile, const char* tmpDir=0):
outputDir(outputDir), outputFile(outputFile), tmpDir(tmpDir),
sourceName(0), targetName(0), rightsXML(0), errors(0)
{}
virtual ~MockDRMProcessorClient() {
if (sourceName) free(sourceName);
if (targetName) free(targetName);
if (rightsXML) free(rightsXML);
}
virtual void mockclientfn0() {LOG_FUNC();}
virtual void mockclientfn1() {LOG_FUNC();}
virtual void workflowFinished(unsigned int workflow, dp::String& str)
{
LOG(DEBUG, "Workflow finished " << workflow);
if (str.utf8())
{
LOG(DEBUG, str.utf8());
}
if ((workflow & WORKFLOW_FULFILLMENT) && !errors)
{
dp::list<dpdrm::FulfillmentItem> list = processor->getFulfillmentItems();
if (list.length())
{
dp::String title = getMetadata(list[0], "title");
uft::String fullName;
if (outputFile)
targetName = strdup(outputFile);
else
{
if (title.utf8())
{
uft::String fullName = title.uft() + ".epub";
targetName = fullName.utf8();
// Duplicate code here because fullName is destroyed
if (!targetName || !strlen(targetName))
targetName = strdup("output.epub");
else
targetName = strdup(targetName);
}
else
{
if (!targetName || !strlen(targetName))
targetName = strdup("output.epub");
else
targetName = strdup(targetName);
}
}
dp::String author = getMetadata(list[0], "creator");
dp::String publisher = getMetadata(list[0], "publisher");
dp::String language = getMetadata(list[0], "language");
LOG(INFO, "Title : " << title.utf8());
LOG(INFO, "Author : " << author.utf8());
LOG(INFO, "Publisher : " << publisher.utf8());
LOG(INFO, "Language : " << language.utf8());
dp::Data postData = list[0]->getPostData();
if (postData.data())
{
LOG(DEBUG, "Post data " << std::endl << postData.data());
}
dp::ref<dpdrm::Rights> rights = list[0]->getRights();
dp::Data _rightsXML = rights->serialize();
if (_rightsXML.data())
{
rightsXML = strdup((const char*)_rightsXML.data());
LOG(DEBUG, "Rights : " << std::endl << rightsXML);
}
// Downloaded file
dp::list<dpdrm::License> licenses = rights->getLicenses();
// Should be wrote @ /tmp/<voucher_id>
if (licenses.length())
{
uft::StringBuffer fullTmpName = uft::String("file:///tmp/");
fullTmpName.append(licenses[0]->getVoucherID().utf8());
sourceName = strdup(fullTmpName.utf8());
}
}
}
else if (workflow & WORKFLOW_AUTH_SIGN_IN)
{
}
else if (workflow & WORKFLOW_ACTIVATION)
{
QDir dir;
if (!errors)
{
LOG(DEBUG, "Move dir " << tmpDir << "/.adept -> " << outputDir);
if (dir.rename(QString(tmpDir) + "/.adept", QString(outputDir)))
{
LOG(INFO, "Created " << outputDir);
dir.setPath(tmpDir);
dir.removeRecursively();
}
else
{
LOG(ERROR, "Error : Unable to move " << tmpDir << "/.adept into " << outputDir);
}
}
else
{
if (verbose >= DEBUG)
{
LOG(DEBUG, "Temp path available at " << tmpDir << "/.adept");
}
else
{
LOG(INFO, "Cleaning temp path");
dir.setPath(tmpDir);
dir.removeRecursively();
}
}
}
}
virtual void mockclientfn3(void) {LOG_FUNC();}
virtual void mockclientfn4(void) {LOG_FUNC();}
virtual void mockclientfn5(void) {LOG_FUNC();}
virtual void workflowProgress(unsigned int workflow, dp::String& title, double progress)
{
LOG(WARN, "Progress " << workflow << " " << title.utf8() << " " << progress << " %%");
if (workflow & WORKFLOW_DOWNLOAD)
{
if (progress == 100.0)
{
// Copy tmp file into destination
if (!outputDir)
outputDir = ".";
// Add rights into downloaded file
MockCallback callback;
MockArchiveListener archiveListener;
dpdev::DeviceProvider* provider = dpdev::DeviceProvider::getProvider(0);
dpio::Partition* partition = provider->getDevice(0)->getPartition(0);
dpio::BufferedStreamClient streamClient;
LOG(DEBUG, "Insert rights into " << sourceName);
// Auto deleted
dpio::Stream* stream = partition->readFile(sourceName, &streamClient, 0);
if (stream)
{
zip::Archive archive(stream, &archiveListener);
archive.readDirectory();
// Auto deleted
zip::EditableStream* editableStream = new zip::EditableStream(&archive);
editableStream->addFile("META-INF/rights.xml", dp::String(rightsXML));
uft::StringBuffer targetFileName(outputDir);
targetFileName.append("/");
targetFileName.append(targetName);
partition->writeFile(targetFileName.utf8(), editableStream, &callback);
partition->removeFile(sourceName, &callback);
LOG(INFO, "Created " << outputDir << "/" << targetName);
}
else
{
LOG(ERROR, "Source file " << sourceName << " not found");
}
}
}
}
virtual void workflowError(unsigned int workflow, dp::String& error)
{
LOG(ERROR, "Workflow error " << workflow << " " << error.utf8());
errors |= workflow;
}
void setProcessor(adept::DRMProcessorImpl* processor)
{ this->processor = processor; }
unsigned int getErrors(void) {return errors;}
private:
adept::DRMProcessorImpl* processor;
const char *outputDir, *outputFile, *tmpDir;
char* sourceName;
char* targetName;
char* rightsXML;
unsigned int errors;
/*
Try to get metadata with :
* DC. + name
* dc. + name
* name
*/
dp::String getMetadata(dp::ref<dpdrm::FulfillmentItem> item, const char* name)
{
uft::String newName = uft::String("DC.") + name;
dp::String res = item->getMetadata(newName.utf8());
if (res.utf8())
return res;
newName = uft::String("dc.") + name;
res = item->getMetadata(newName);
if (res.utf8())
return res;
return item->getMetadata(name);
}
};
class MockPartition : public dpio::FilesystemPartition
{
public:
MockPartition(dpdev::Device* device, int index,
dp::String name, dp::String type,
dp::String rootPath, dp::String docPath,
dp::String tempPath):
FilesystemPartition(device, index, type, rootPath, docPath, tempPath),
docPath(docPath), tempPath(tempPath)
{
}
virtual void* getInterfaceID(){return 0;}
virtual dp::String getDocumentFolderURL() {LOG_FUNC(); return docPath;}
virtual dp::String getTemporaryFolderURL() {LOG_FUNC(); return tempPath;}
virtual void createUniqueFile( const dp::String& path, const dp::String& extension, dp::Callback* callback )
{
LOG_FUNC();
uft::StringBuffer _path(path.utf8());
_path.append(extension.utf8());
QFile file(_path.utf8());
file.open(QIODevice::WriteOnly|QIODevice::Truncate);
file.close();
}
virtual void writeFile( const dp::String& path, dpio::Stream* streamData, dp::Callback* callback )
{
LOG_FUNC();
QFile file(path.utf8());
bool ret = file.open(QIODevice::WriteOnly|QIODevice::Truncate);
if (!ret)
{
LOG(ERROR, "Unable to open file " << path.utf8());
return;
}
else
LOG(WARN, "Write file " << path.utf8());
dp::Data data = dpio::Stream::readSynchronousStream(streamData);
LOG(DEBUG, "Read from stream " << data.length() << " bytes");
file.write((const char*)data.data(), data.length());
file.close();
callback->reportProgress(100.0);
}
private:
dp::String docPath;
dp::String tempPath;
};
class MockProvider : public dpdev::DeviceProvider
{
public:
MockProvider(dpdev::Device* device):device(device) {}
void getIdentifier() {LOG_FUNC();}
virtual int getIndex() {LOG_FUNC(); return 0;}
virtual dpdev::Device* getDevice( int index ){
LOG_FUNC();
if (index == 0)
return device;
else
return 0;
}
virtual bool mount(const dp::String& root, const dp::String& name, const dp::String& type)
{
LOG(INFO, "Mount " << root.utf8() << " " << name.utf8() << " " << type.utf8());
return true;
}
virtual bool unmount(const dp::String& root) {LOG_FUNC(); return true;}
virtual void mockproviderfn0(void) {LOG_FUNC();}
virtual void mockproviderfn1(void) {LOG_FUNC();}
virtual void mockproviderfn2(void) {LOG_FUNC();}
virtual void mockproviderfn3(void) {LOG_FUNC();}
private:
dpdev::Device* device;
};
class MockDevice : public dpdev::Device, public mdom::DocumentHandler
{
public:
class InvalidDeviceFile : public std::exception {
const char * what () const throw () {
return "Invalid device File";
}
};
class InvalidDeviceKeyFile : public std::exception {
const char * what () const throw () {
return "Invalid device key File";
}
};
MockDevice(dpdrm::DRMProcessorClient* processorClient, const char* deviceFile, const char* activationFile, const char* devkeyFile) :
deviceProvider(0), processorClient(processorClient), activationFile(activationFile)
{
partition = new MockPartition(this, 0, "root", "Fixed", "/", "/tmp", "/tmp");
readFile(activationFile, &activationRecord, &activationRecordLength);
uft::String librmsdkVersion = dp::getVersionInfo("hobbes");
/* Default values */
dp::setVersionInfo("hobbes", "9.2.38311");
dp::setVersionInfo("clientVersion", "Boo Reader");
dp::setVersionInfo("clientOS", "Linux 2.6.32 armv7l");
dp::setVersionInfo("clientLocale", "fr");
unsigned char* deviceXML;
readFile(deviceFile, &deviceXML, 0, true);
wisdom = adept::parseXML((const char*)deviceXML);
if (!wisdom)
throw MockDevice::InvalidDeviceFile();
mdom::Node node = wisdom->getRoot();
node.walkBranch(this);
uft::String deviceVersion = dp::getVersionInfo("hobbes");
if (librmsdkVersion != deviceVersion)
{
LOG(INFO, "Device RMSDK version " << deviceVersion.utf8());
}
// deviceXML is already freed...
// delete[] deviceXML;
readFile(devkeyFile, &devkey, &devkeyLength);
if (devkeyLength != 16)
throw MockDevice::InvalidDeviceKeyFile();
}
void setProvider(dpdev::DeviceProvider* deviceProvider)
{this->deviceProvider = deviceProvider;}
void setProcessorClient(dpdrm::DRMProcessorClient* processorClient)
{this->processorClient = processorClient;}
virtual void * getOptionalInterface( const char * name ) {LOG_FUNC(); return 0;}
virtual void prepareDeviceKey() {LOG_FUNC();}
virtual dpdev::DeviceProvider* getProvider() {LOG_FUNC(); return deviceProvider;}
virtual int getIndex() {LOG_FUNC(); return 0;}
virtual dp::String getDeviceName() {LOG_FUNC(); return deviceName;}
virtual dp::Data getDeviceKey() {LOG_FUNC(); return dp::Data(devkey, devkeyLength);}
virtual dp::String getDeviceType() {LOG_FUNC(); return deviceType;}
virtual dp::Data getFingerprint() {LOG_FUNC(); return fingerprint;}
virtual dp::Data getActivationRecord() {LOG_FUNC(); return dp::Data(activationRecord, activationRecordLength);}
virtual void setActivationRecord(const dp::Data& data);
virtual dpio::Partition* getPartition(int index) {LOG_FUNC(); return partition;}
virtual dp::String getVersionInfo(const dp::String& name);
virtual bool isTrusted() {LOG_FUNC();return true;};
/* mdom::DocumentHandler */
virtual bool characters(uft::Value const&) {return true;}
virtual bool comment(uft::Value const&) {return true;}
virtual bool endDocument() {return true;}
virtual bool endElement(uft::Value const& p0, uft::Value const& p1, uft::Value const& p2) {return true;}
virtual bool endEntity(uft::Value const&) {return true;}
virtual bool processingInstruction(uft::Value const&, uft::Value const&) {return true;}
virtual bool startDocument() {return true;}
virtual bool startElement(mdom::Node const& node, uft::Value const& xmlns, uft::Value const& element, uft::Value const& ns, mdom::NameValueIterator* attributesIterator) {
if (!element.isNull() && element.isString())
{
char* name = element.toString().c_str();
uft::String value = adept::getChildValue(node, 3);
if (!strcmp(name, "deviceClass"))
deviceClass = dp::String(value.utf8());
else if (!strcmp(name, "deviceSerial"))
deviceSerial = dp::String(value.utf8());
else if (!strcmp(name, "deviceName"))
deviceName = dp::String(value.utf8());
else if (!strcmp(name, "deviceType"))
deviceType = dp::String(value.utf8());
// <adept:version name="hobbes" value="9.2.38311">
else if (!strcmp(name, "version"))
{
/* Hardcode decode because p0 is a structure */
uft::Value p0, p1;
attributesIterator->next(&p0, &p1);
char* attrName = p1.toString().c_str();
attributesIterator->next(&p0, &p1);
char* attrValue = p1.toString().c_str();
if (attrName && attrValue)
dp::setVersionInfo(attrName, attrValue);
}
else if (!strcmp(name, "fingerprint"))
{
unsigned char rawFingerprint[32];
int res = dp::decodeBase64(value.c_str(), rawFingerprint, sizeof(rawFingerprint));
fingerprint = dp::Data(rawFingerprint, res);
}
}
return true;
}
virtual bool startEntity(uft::Value const&) {return true;}
static int readFile(const char* filePath, unsigned char** res, int* length = 0, bool addFinalZero=false)
{
int _length, fd;
QFile file(filePath);
*res = 0;
if (!length) length = &_length;
if (!file.exists())
{
*length = 0;
return 0;
}
if (addFinalZero)
*res = new unsigned char[file.size()+1];
else
*res = new unsigned char[file.size()];
fd = open(filePath, O_RDONLY);
*length = read(fd, *res, file.size());
close (fd);
if (*length > 0 && addFinalZero)
{
(*res)[*length] = 0;
*length += 1;
}
return *length;
}
private:
dpdev::DeviceProvider* deviceProvider;
dpdrm::DRMProcessorClient* processorClient;
const char *activationFile;
unsigned char *activationRecord;
int activationRecordLength;
unsigned char *devkey;
int devkeyLength;
dpio::Partition* partition;
dp::String deviceSerial;
dp::String deviceClass;
dp::String deviceName;
dp::String deviceType;
dp::Data fingerprint;
MetroWisDOM* wisdom;
};
void MockDevice::setActivationRecord( const dp::Data& data ) {
LOG_FUNC();
if (activationRecord)
delete[] activationRecord;
activationRecordLength = data.length();
activationRecord = new unsigned char[activationRecordLength];
memcpy(activationRecord, data.data(), activationRecordLength);
LOG(DEBUG, "New activation record :" << std::endl << activationRecord);
if (activationFile)
{
QFile file(activationFile);
bool ret = file.open(QIODevice::WriteOnly|QIODevice::Truncate);
if (!ret)
{
LOG(ERROR, "Unable to open file " << activationFile);
return;
}
else
LOG(WARN, "Write file " << activationFile);
file.write((const char*)data.data(), data.length());
file.close();
}
}
dp::String MockDevice::getVersionInfo( const dp::String& name ) {
LOG_FUNC();
LOG(DEBUG, ">>> " << name.utf8());
dp::String res = dp::getVersionInfo(name);
return res;
}
class MockStream : public dpio::Stream {
public:
MockStream(const dp::String& method, const dp::String& url, dpio::StreamClient* client,
dpio::Stream* postData):
method(method), url(url), client(client)
{
LOG(DEBUG, "New stream " << method.utf8() << " " << url.utf8());
QNetworkRequest request(QUrl(url.utf8()));
request.setRawHeader("Accept", "*/*");
request.setRawHeader("User-Agent", "book2png");
if (method.uft() == "POST")
{
request.setRawHeader("Content-Type", "application/vnd.adobe.adept+xml");
dp::Data data = dpio::Stream::readSynchronousStream(postData);
LOG(DEBUG, "Len " << data.length());
LOG(DEBUG, "data " << data.data());
reply = networkManager.post(request, QByteArray((const char*)data.data(), data.length()));
}
else
{
reply = networkManager.get(request);
}
}
virtual void release() {LOG_FUNC();}
virtual void setStreamClient(dpio::StreamClient * client)
{
LOG_FUNC();
this->client = client;
}
virtual int getCapabilities()
{
LOG_FUNC();
return 1;
}
virtual void requestInfo()
{
LOG_FUNC();
QCoreApplication* app = QCoreApplication::instance();
networkManager.moveToThread(app->thread());
while (!reply->isFinished())
app->processEvents();
requestBytes(0, -1);
}
virtual void requestBytes(unsigned int offset, unsigned int len)
{
LOG_FUNC();
LOG(DEBUG, "Offset " << offset << ", Len " << len);
if (!client)
{
LOG(ERROR, "Error, No client");
return;
}
size_t bytes = (size_t)reply->bytesAvailable();
QByteArray _replyData = reply->readAll();
const unsigned char* replyData = (const unsigned char*)_replyData.constData();
LOG(DEBUG, "Reply " << bytes << " bytes");
if (reply->hasRawHeader("Content-Type"))
{
client->propertyReady("Content-Type", reply->rawHeader("Content-Type").constData());
LOG(DEBUG, "Content-Type " << reply->rawHeader("Content-Type").constData());
if (!strcmp(reply->rawHeader("Content-Type").constData(), "application/vnd.adobe.adept+xml"))
LOG(DEBUG, "<<< " << replyData);
}
if (reply->hasRawHeader("Content-Length"))
client->propertyReady("Content-Length", reply->rawHeader("Content-Length").constData());
client->totalLengthReady(bytes);
client->bytesReady(0, dp::Data(replyData, bytes), true);
}
virtual void reportWriteError(const dp::String& error)
{
LOG_FUNC();
}
virtual void adjustModifiedStream(unsigned int offset, unsigned int len, void* param2, dpio::StreamClient* client)
{
LOG_FUNC();
setStreamClient(client);
requestBytes(offset, len);
}
virtual void streamfn2() {LOG_FUNC();}
virtual void streamfn3() {LOG_FUNC();}
virtual void streamfn4() {LOG_FUNC();}
private:
dp::String method;
dp::String url;
dpio::StreamClient *client;
QNetworkAccessManager networkManager;
QNetworkReply *reply;
};
class MockNetProvider : public dpnet::NetProvider
{
virtual void netfn0() {LOG_FUNC();}
virtual void netfn1() {LOG_FUNC();}
virtual void netfn2() {LOG_FUNC();}
virtual void netfn3() {LOG_FUNC();}
virtual dpio::Stream * netfn4(dp::String& method, dp::String& url, adept::UrlLoader<adept::DRMProcessorImpl>* client, unsigned int param1, dpio::Stream* stream) {
LOG_FUNC();
return new MockStream(method, url, (dpio::StreamClient*)(client), stream);
}
virtual void netfn5() {LOG_FUNC();}
virtual void netfn6() {LOG_FUNC();}
};
#endif