/* 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 . */ #ifndef RMSDK_WRAPPER_H #define RMSDK_WRAPPER_H #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include 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 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 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 licenses = rights->getLicenses(); // Should be wrote @ /tmp/ 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 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()); // 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* 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