Compare commits
9 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7b6b1471fe | ||
|
|
4f9b2de5a5 | ||
|
|
f568b5d3a8 | ||
|
|
8c413b4f34 | ||
|
|
8fe8ba2808 | ||
|
|
570ad83747 | ||
|
|
2e7e352e35 | ||
|
|
9556fe862f | ||
|
|
2f2e4e193e |
3
.gitignore
vendored
3
.gitignore
vendored
@@ -6,4 +6,5 @@ lib
|
||||
utils/acsmdownloader
|
||||
utils/adept_activate
|
||||
utils/adept_remove
|
||||
.adept
|
||||
utils/adept_loan_mgt
|
||||
.adept*
|
||||
|
||||
2
Makefile
2
Makefile
@@ -36,7 +36,7 @@ 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/loan_token.cpp src/bytearray.cpp src/pugixml.cpp
|
||||
OBJECTS := $(patsubst $(SRCDIR)/%,$(BUILDDIR)/%,$(SOURCES:.$(SRCEXT)=.$(OBJEXT)))
|
||||
|
||||
all: lib obj $(TARGETS)
|
||||
|
||||
12
README.md
12
README.md
@@ -16,7 +16,7 @@ Main fucntions to use from gourou::DRMProcessor are :
|
||||
* Create a new device : _createDRMProcessor()_
|
||||
* Register a new device : _signIn()_ and _activateDevice()_
|
||||
* Remove DRM : _removeDRM()_
|
||||
|
||||
* Return loaned book : _returnLoan()_
|
||||
|
||||
You can import configuration from (at least) :
|
||||
|
||||
@@ -91,6 +91,16 @@ To remove ADEPT DRM :
|
||||
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:$PWD
|
||||
./utils/adept_remove -f <encryptedFile>
|
||||
|
||||
To list loaned books :
|
||||
|
||||
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:$PWD
|
||||
./utils/adept_loan_mgt [-l]
|
||||
|
||||
To return a loaned book :
|
||||
|
||||
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:$PWD
|
||||
./utils/adept_loan_mgt -r <id>
|
||||
|
||||
You can get utils full options description with -h or --help switch
|
||||
|
||||
|
||||
|
||||
@@ -99,10 +99,11 @@ namespace gourou
|
||||
* @param contentType Optional content type of POST Data
|
||||
* @param responseHeaders Optional Response headers of HTTP request
|
||||
* @param fd Optional file descriptor to write request result
|
||||
* @param resume false if target file should be truncated, true to try resume download (works only in combination with a valid fd)
|
||||
*
|
||||
* @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(""), std::map<std::string, std::string>* responseHeaders=0, int fd=0) = 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, int fd=0, bool resume=false) = 0;
|
||||
};
|
||||
|
||||
class RSAInterface
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
#ifndef _FULFILLMENT_ITEM_H_
|
||||
#define _FULFILLMENT_ITEM_H_
|
||||
|
||||
#include "bytearray.h"
|
||||
#include "loan_token.h"
|
||||
|
||||
#include <pugixml.hpp>
|
||||
|
||||
@@ -42,6 +42,8 @@ namespace gourou
|
||||
*/
|
||||
FulfillmentItem(pugi::xml_document& doc, User* user);
|
||||
|
||||
~FulfillmentItem();
|
||||
|
||||
/**
|
||||
* @brief Return metadata value from ACSM metadata section
|
||||
*
|
||||
@@ -64,12 +66,18 @@ namespace gourou
|
||||
*/
|
||||
std::string getResource();
|
||||
|
||||
/**
|
||||
* @brief Return loan token if there is one
|
||||
*/
|
||||
LoanToken* getLoanToken();
|
||||
|
||||
private:
|
||||
pugi::xml_document fulfillDoc;
|
||||
pugi::xml_node metadatas;
|
||||
pugi::xml_document rights;
|
||||
std::string downloadURL;
|
||||
std::string resource;
|
||||
LoanToken* loanToken;
|
||||
|
||||
void buildRights(const pugi::xml_node& licenseToken, User* user);
|
||||
};
|
||||
|
||||
@@ -40,7 +40,7 @@
|
||||
#define ACS_SERVER "http://adeactivate.adobe.com/adept"
|
||||
#endif
|
||||
|
||||
#define LIBGOUROU_VERSION "0.6"
|
||||
#define LIBGOUROU_VERSION "0.7.1"
|
||||
|
||||
namespace gourou
|
||||
{
|
||||
@@ -81,10 +81,11 @@ namespace gourou
|
||||
*
|
||||
* @param item Item from fulfill() method
|
||||
* @param path Output file path
|
||||
* @param resume false if target file should be truncated, true to try resume download
|
||||
*
|
||||
* @return Type of downloaded item
|
||||
*/
|
||||
ITEM_TYPE download(FulfillmentItem* item, std::string path);
|
||||
ITEM_TYPE download(FulfillmentItem* item, std::string path, bool resume=false);
|
||||
|
||||
/**
|
||||
* @brief SignIn into ACS Server (required to activate device)
|
||||
@@ -99,6 +100,14 @@ namespace gourou
|
||||
*/
|
||||
void activateDevice();
|
||||
|
||||
/**
|
||||
* @brief Return loaned book to server
|
||||
*
|
||||
* @param loanID Loan ID received during fulfill
|
||||
* @param operatorURL URL of operator that loans this book
|
||||
*/
|
||||
void returnLoan(const std::string& loanID, const std::string& operatorURL);
|
||||
|
||||
/**
|
||||
* @brief Create a new ADEPT environment (device.xml, devicesalt and activation.xml).
|
||||
*
|
||||
@@ -135,10 +144,11 @@ namespace gourou
|
||||
* @param contentType Optional content type of POST Data
|
||||
* @param responseHeaders Optional Response headers of HTTP request
|
||||
* @param fd Optional File descriptor to write received data
|
||||
* @param resume false if target file should be truncated, true to try resume download (works only in combination of a valid fd)
|
||||
*
|
||||
* @return data of HTTP response
|
||||
*/
|
||||
ByteArray sendRequest(const std::string& URL, const std::string& POSTData=std::string(), const char* contentType=0, std::map<std::string, std::string>* responseHeaders=0, int fd=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, int fd=0, bool resume=false);
|
||||
|
||||
/**
|
||||
* @brief Send HTTP POST request to URL with document as POSTData
|
||||
@@ -208,7 +218,7 @@ namespace gourou
|
||||
void pushTag(void* sha_ctx, uint8_t tag);
|
||||
void hashNode(const pugi::xml_node& root, void *sha_ctx, std::map<std::string,std::string> nsHash);
|
||||
void hashNode(const pugi::xml_node& root, unsigned char* sha_out);
|
||||
std::string signNode(const pugi::xml_node& rootNode);
|
||||
void signNode(pugi::xml_node& rootNode);
|
||||
void addNonce(pugi::xml_node& root);
|
||||
void buildAuthRequest(pugi::xml_document& authReq);
|
||||
void buildInitLicenseServiceRequest(pugi::xml_document& initLicReq, std::string operatorURL);
|
||||
@@ -216,6 +226,7 @@ namespace gourou
|
||||
void operatorAuth(std::string operatorURL);
|
||||
void buildFulfillRequest(pugi::xml_document& acsmDoc, pugi::xml_document& fulfillReq);
|
||||
void buildActivateReq(pugi::xml_document& activateReq);
|
||||
void buildReturnReq(pugi::xml_document& returnReq, const std::string& loanID, const std::string& operatorURL);
|
||||
ByteArray sendFulfillRequest(const pugi::xml_document& document, const std::string& url);
|
||||
void buildSignInRequest(pugi::xml_document& signInRequest, const std::string& adobeID, const std::string& adobePassword, const std::string& authenticationCertificate);
|
||||
void fetchLicenseServiceCertificate(const std::string& licenseURL,
|
||||
|
||||
@@ -55,7 +55,8 @@ namespace gourou
|
||||
GOUROU_INVALID_CLIENT,
|
||||
GOUROU_TAG_NOT_FOUND,
|
||||
GOUROU_ADEPT_ERROR,
|
||||
GOUROU_FILE_ERROR
|
||||
GOUROU_FILE_ERROR,
|
||||
GOUROU_INVALID_PROPERTY
|
||||
};
|
||||
|
||||
enum FULFILL_ERROR {
|
||||
@@ -96,7 +97,8 @@ namespace gourou
|
||||
};
|
||||
|
||||
enum FULFILL_ITEM_ERROR {
|
||||
FFI_INVALID_FULFILLMENT_DATA = 0x4000
|
||||
FFI_INVALID_FULFILLMENT_DATA = 0x4000,
|
||||
FFI_INVALID_LOAN_TOKEN
|
||||
};
|
||||
|
||||
enum CLIENT_ERROR {
|
||||
@@ -287,13 +289,19 @@ namespace gourou
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Open a file descriptor on path. If it already exists, it's truncated
|
||||
* @brief Open a file descriptor on path. If it already exists and truncate == true, it's truncated
|
||||
*
|
||||
* @return Created fd, must be closed
|
||||
*/
|
||||
static inline int createNewFile(std::string path)
|
||||
static inline int createNewFile(std::string path, bool truncate=true)
|
||||
{
|
||||
int fd = open(path.c_str(), O_CREAT|O_WRONLY|O_TRUNC, S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP|S_IROTH);
|
||||
int options = O_CREAT|O_WRONLY;
|
||||
if (truncate)
|
||||
options |= O_TRUNC;
|
||||
else
|
||||
options |= O_APPEND;
|
||||
|
||||
int fd = open(path.c_str(), options, S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP|S_IROTH);
|
||||
|
||||
if (fd <= 0)
|
||||
EXCEPTION(GOUROU_FILE_ERROR, "Unable to create " << path);
|
||||
|
||||
54
include/loan_token.h
Normal file
54
include/loan_token.h
Normal file
@@ -0,0 +1,54 @@
|
||||
/*
|
||||
Copyright 2022 Grégory Soutadé
|
||||
|
||||
This file is part of libgourou.
|
||||
|
||||
libgourou is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Lesser General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
libgourou 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 Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public License
|
||||
along with libgourou. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef _LOAN_TOKEN_H_
|
||||
#define _LOAN_TOKEN_H_
|
||||
|
||||
#include <map>
|
||||
|
||||
#include <pugixml.hpp>
|
||||
|
||||
namespace gourou
|
||||
{
|
||||
/**
|
||||
* @brief This class is a container for a fulfillment object
|
||||
*/
|
||||
class LoanToken
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* @brief Main constructor. Not to be called by user
|
||||
*
|
||||
* @param doc Fulfill reply
|
||||
*/
|
||||
LoanToken(pugi::xml_document& doc);
|
||||
|
||||
/**
|
||||
* @brief Get a property (id, operatorURL, validity)
|
||||
*/
|
||||
std::string getProperty(const std::string& property, const std::string& _default=std::string(""));
|
||||
std::string operator[](const std::string& property);
|
||||
|
||||
private:
|
||||
std::map<std::string, std::string> properties;
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
#endif
|
||||
@@ -24,7 +24,7 @@
|
||||
namespace gourou
|
||||
{
|
||||
FulfillmentItem::FulfillmentItem(pugi::xml_document& doc, User* user)
|
||||
: fulfillDoc()
|
||||
: fulfillDoc(), loanToken(0)
|
||||
{
|
||||
fulfillDoc.reset(doc); /* We must keep a copy */
|
||||
metadatas = fulfillDoc.select_node("//metadata").node();
|
||||
@@ -50,6 +50,23 @@ namespace gourou
|
||||
EXCEPTION(FFI_INVALID_FULFILLMENT_DATA, "Any license token in document");
|
||||
|
||||
buildRights(licenseToken, user);
|
||||
|
||||
node = doc.select_node("/envelope/fulfillmentResult/returnable").node();
|
||||
try
|
||||
{
|
||||
if (node && node.first_child().value() == std::string("true"))
|
||||
loanToken = new LoanToken(doc);
|
||||
}
|
||||
catch(std::exception& e)
|
||||
{
|
||||
GOUROU_LOG(ERROR, "Book is returnable, but contains invalid loan token");
|
||||
GOUROU_LOG(ERROR, e.what());
|
||||
}
|
||||
}
|
||||
|
||||
FulfillmentItem::~FulfillmentItem()
|
||||
{
|
||||
if (loanToken) delete loanToken;
|
||||
}
|
||||
|
||||
void FulfillmentItem::buildRights(const pugi::xml_node& licenseToken, User* user)
|
||||
@@ -103,4 +120,9 @@ namespace gourou
|
||||
{
|
||||
return resource;
|
||||
}
|
||||
|
||||
LoanToken* FulfillmentItem::getLoanToken()
|
||||
{
|
||||
return loanToken;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -236,7 +236,7 @@ namespace gourou
|
||||
}
|
||||
}
|
||||
|
||||
std::string DRMProcessor::signNode(const pugi::xml_node& rootNode)
|
||||
void DRMProcessor::signNode(pugi::xml_node& rootNode)
|
||||
{
|
||||
// Compute hash
|
||||
unsigned char sha_out[SHA1_LEN];
|
||||
@@ -260,9 +260,8 @@ namespace gourou
|
||||
printf("\n");
|
||||
}
|
||||
|
||||
ByteArray signature(res, sizeof(res));
|
||||
|
||||
return signature.toBase64();
|
||||
std::string signature = ByteArray(res, sizeof(res)).toBase64();
|
||||
appendTextElem(rootNode, "adept:signature", signature);
|
||||
}
|
||||
|
||||
void DRMProcessor::addNonce(pugi::xml_node& root)
|
||||
@@ -300,11 +299,11 @@ namespace gourou
|
||||
appendTextElem(root, "adept:expiration", buffer);
|
||||
}
|
||||
|
||||
ByteArray DRMProcessor::sendRequest(const std::string& URL, const std::string& POSTdata, const char* contentType, std::map<std::string, std::string>* responseHeaders, int fd)
|
||||
ByteArray DRMProcessor::sendRequest(const std::string& URL, const std::string& POSTdata, const char* contentType, std::map<std::string, std::string>* responseHeaders, int fd, bool resume)
|
||||
{
|
||||
if (contentType == 0)
|
||||
contentType = "";
|
||||
std::string reply = client->sendHTTPRequest(URL, POSTdata, contentType, responseHeaders, fd);
|
||||
std::string reply = client->sendHTTPRequest(URL, POSTdata, contentType, responseHeaders, fd, resume);
|
||||
|
||||
if (fd) return ByteArray();
|
||||
|
||||
@@ -368,8 +367,7 @@ namespace gourou
|
||||
addNonce(root);
|
||||
appendTextElem(root, "adept:user", user->getUUID());
|
||||
|
||||
std::string signature = signNode(root);
|
||||
appendTextElem(root, "adept:signature", signature);
|
||||
signNode(root);
|
||||
}
|
||||
|
||||
void DRMProcessor::doOperatorAuth(std::string operatorURL)
|
||||
@@ -530,13 +528,11 @@ namespace gourou
|
||||
|
||||
hmacParentNode.remove_child(hmacNode);
|
||||
|
||||
std::string signature = signNode(rootNode);
|
||||
signNode(rootNode);
|
||||
|
||||
// Add removed HMAC
|
||||
appendTextElem(hmacParentNode, hmacNode.name(), hmacNode.first_child().value());
|
||||
|
||||
appendTextElem(rootNode, "adept:signature", signature);
|
||||
|
||||
pugi::xpath_node node = acsmDoc.select_node("//operatorURL");
|
||||
if (!node)
|
||||
EXCEPTION(FF_NO_OPERATOR_URL, "OperatorURL not found in ACSM document");
|
||||
@@ -583,7 +579,7 @@ namespace gourou
|
||||
return new FulfillmentItem(fulfillReply, user);
|
||||
}
|
||||
|
||||
DRMProcessor::ITEM_TYPE DRMProcessor::download(FulfillmentItem* item, std::string path)
|
||||
DRMProcessor::ITEM_TYPE DRMProcessor::download(FulfillmentItem* item, std::string path, bool resume)
|
||||
{
|
||||
ITEM_TYPE res = EPUB;
|
||||
|
||||
@@ -592,9 +588,9 @@ namespace gourou
|
||||
|
||||
std::map<std::string, std::string> headers;
|
||||
|
||||
int fd = createNewFile(path);
|
||||
int fd = createNewFile(path, !resume);
|
||||
|
||||
sendRequest(item->getDownloadURL(), "", 0, &headers, fd);
|
||||
sendRequest(item->getDownloadURL(), "", 0, &headers, fd, resume);
|
||||
|
||||
close(fd);
|
||||
|
||||
@@ -823,10 +819,7 @@ namespace gourou
|
||||
|
||||
pugi::xml_node root = activateReq.select_node("adept:activate").node();
|
||||
|
||||
std::string signature = signNode(root);
|
||||
|
||||
root = activateReq.select_node("adept:activate").node();
|
||||
appendTextElem(root, "adept:signature", signature);
|
||||
signNode(root);
|
||||
|
||||
pugi::xml_document activationDoc;
|
||||
user->readActivation(activationDoc);
|
||||
@@ -844,6 +837,33 @@ namespace gourou
|
||||
user->updateActivationFile(activationDoc);
|
||||
}
|
||||
|
||||
void DRMProcessor::buildReturnReq(pugi::xml_document& returnReq, const std::string& loanID, const std::string& operatorURL)
|
||||
{
|
||||
pugi::xml_node decl = returnReq.append_child(pugi::node_declaration);
|
||||
decl.append_attribute("version") = "1.0";
|
||||
|
||||
pugi::xml_node root = returnReq.append_child("adept:loanReturn");
|
||||
root.append_attribute("xmlns:adept") = ADOBE_ADEPT_NS;
|
||||
|
||||
appendTextElem(root, "adept:user", user->getUUID());
|
||||
appendTextElem(root, "adept:device", user->getDeviceUUID());
|
||||
appendTextElem(root, "adept:loan", loanID);
|
||||
|
||||
addNonce(root);
|
||||
signNode(root);
|
||||
}
|
||||
|
||||
void DRMProcessor::returnLoan(const std::string& loanID, const std::string& operatorURL)
|
||||
{
|
||||
pugi::xml_document returnReq;
|
||||
|
||||
GOUROU_LOG(INFO, "Return loan " << loanID);
|
||||
|
||||
buildReturnReq(returnReq, loanID, operatorURL);
|
||||
|
||||
sendRequest(returnReq, operatorURL + "/LoanReturn");
|
||||
}
|
||||
|
||||
ByteArray DRMProcessor::encryptWithDeviceKey(const unsigned char* data, unsigned int len)
|
||||
{
|
||||
const unsigned char* deviceKey = device->getDeviceKey();
|
||||
|
||||
77
src/loan_token.cpp
Normal file
77
src/loan_token.cpp
Normal file
@@ -0,0 +1,77 @@
|
||||
/*
|
||||
Copyright 2022 Grégory Soutadé
|
||||
|
||||
This file is part of libgourou.
|
||||
|
||||
libgourou is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Lesser General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
libgourou 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 Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public License
|
||||
along with libgourou. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "libgourou_common.h"
|
||||
#include "loan_token.h"
|
||||
|
||||
namespace gourou
|
||||
{
|
||||
LoanToken::LoanToken(pugi::xml_document& doc)
|
||||
{
|
||||
pugi::xml_node node = doc.select_node("/envelope/loanToken").node();
|
||||
|
||||
if (!node)
|
||||
EXCEPTION(FFI_INVALID_LOAN_TOKEN, "No loanToken element in document");
|
||||
|
||||
node = doc.select_node("/envelope/loanToken/loan").node();
|
||||
|
||||
if (!node)
|
||||
EXCEPTION(FFI_INVALID_LOAN_TOKEN, "No loanToken/loan element in document");
|
||||
|
||||
properties["id"] = node.first_child().value();
|
||||
|
||||
node = doc.select_node("/envelope/loanToken/operatorURL").node();
|
||||
|
||||
if (!node)
|
||||
EXCEPTION(FFI_INVALID_LOAN_TOKEN, "No loanToken/operatorURL element in document");
|
||||
|
||||
properties["operatorURL"] = node.first_child().value();
|
||||
|
||||
node = doc.select_node("/envelope/fulfillmentResult/resourceItemInfo/licenseToken/permissions/display/until").node();
|
||||
|
||||
if (node)
|
||||
properties["validity"] = node.first_child().value();
|
||||
else
|
||||
{
|
||||
node = doc.select_node("/envelope/fulfillmentResult/resourceItemInfo/licenseToken/permissions/play/until").node();
|
||||
if (node)
|
||||
properties["validity"] = node.first_child().value();
|
||||
else
|
||||
EXCEPTION(FFI_INVALID_LOAN_TOKEN, "No loanToken/operatorURL element in document");
|
||||
}
|
||||
}
|
||||
|
||||
std::string LoanToken::getProperty(const std::string& property, const std::string& _default)
|
||||
{
|
||||
if (properties.find(property) == properties.end())
|
||||
{
|
||||
if (_default == "")
|
||||
EXCEPTION(GOUROU_INVALID_PROPERTY, "Invalid property " << property);
|
||||
|
||||
return _default;
|
||||
}
|
||||
|
||||
return properties[property];
|
||||
}
|
||||
|
||||
std::string LoanToken::operator[](const std::string& property)
|
||||
{
|
||||
return getProperty(property);
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
|
||||
TARGETS=acsmdownloader adept_activate adept_remove
|
||||
TARGETS=acsmdownloader adept_activate adept_remove adept_loan_mgt
|
||||
|
||||
CXXFLAGS=-Wall -fPIC -I$(ROOT)/include -I$(ROOT)/lib/pugixml/src/
|
||||
|
||||
@@ -18,17 +18,26 @@ else
|
||||
CXXFLAGS += -O2
|
||||
endif
|
||||
|
||||
COMMON_DEPS = drmprocessorclientimpl.cpp utils_common.cpp $(STATIC_DEP)
|
||||
COMMON_DEPS = drmprocessorclientimpl.cpp utils_common.cpp
|
||||
COMMON_OBJECTS = $(COMMON_DEPS:.cpp=.o)
|
||||
COMMON_LIB = utils.a
|
||||
|
||||
all: $(TARGETS)
|
||||
|
||||
acsmdownloader: acsmdownloader.cpp $(COMMON_DEPS)
|
||||
${COMMON_LIB}: ${COMMON_DEPS} ${STATIC_DEP}
|
||||
$(CXX) $(CXXFLAGS) ${COMMON_DEPS} $(LDFLAGS) -c
|
||||
$(AR) crs $@ ${COMMON_OBJECTS} $(STATIC_DEP)
|
||||
|
||||
acsmdownloader: acsmdownloader.cpp ${COMMON_LIB}
|
||||
$(CXX) $(CXXFLAGS) $^ $(LDFLAGS) -o $@
|
||||
|
||||
adept_activate: adept_activate.cpp $(COMMON_DEPS)
|
||||
adept_activate: adept_activate.cpp ${COMMON_LIB}
|
||||
$(CXX) $(CXXFLAGS) $^ $(LDFLAGS) -o $@
|
||||
|
||||
adept_remove: adept_remove.cpp $(COMMON_DEPS)
|
||||
adept_remove: adept_remove.cpp ${COMMON_LIB}
|
||||
$(CXX) $(CXXFLAGS) $^ $(LDFLAGS) -o $@
|
||||
|
||||
adept_loan_mgt: adept_loan_mgt.cpp ${COMMON_LIB}
|
||||
$(CXX) $(CXXFLAGS) $^ $(LDFLAGS) -o $@
|
||||
|
||||
clean:
|
||||
|
||||
@@ -26,12 +26,15 @@
|
||||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#include <getopt.h>
|
||||
#include <getopt.h>
|
||||
#include <libgen.h>
|
||||
|
||||
#include <iostream>
|
||||
#include <algorithm>
|
||||
|
||||
#include <libgourou.h>
|
||||
#include <libgourou_common.h>
|
||||
|
||||
#include "drmprocessorclientimpl.h"
|
||||
#include "utils_common.h"
|
||||
|
||||
@@ -42,6 +45,7 @@ static const char* acsmFile = 0;
|
||||
static bool exportPrivateKey = false;
|
||||
static const char* outputFile = 0;
|
||||
static const char* outputDir = 0;
|
||||
static bool resume = false;
|
||||
|
||||
|
||||
class ACSMDownloader
|
||||
@@ -53,7 +57,6 @@ public:
|
||||
int ret = 0;
|
||||
try
|
||||
{
|
||||
DRMProcessorClientImpl client;
|
||||
gourou::DRMProcessor processor(&client, deviceFile, activationFile, devicekeyFile);
|
||||
gourou::User* user = processor.getUser();
|
||||
|
||||
@@ -104,7 +107,7 @@ public:
|
||||
filename = std::string(outputDir) + "/" + filename;
|
||||
}
|
||||
|
||||
gourou::DRMProcessor::ITEM_TYPE type = processor.download(item, filename);
|
||||
gourou::DRMProcessor::ITEM_TYPE type = processor.download(item, filename, resume);
|
||||
|
||||
if (!outputFile)
|
||||
{
|
||||
@@ -117,6 +120,8 @@ public:
|
||||
filename = finalName;
|
||||
}
|
||||
std::cout << "Created " << filename << std::endl;
|
||||
|
||||
serializeLoanToken(item);
|
||||
}
|
||||
} catch(std::exception& e)
|
||||
{
|
||||
@@ -126,6 +131,52 @@ public:
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
void serializeLoanToken(gourou::FulfillmentItem* item)
|
||||
{
|
||||
gourou::LoanToken* token = item->getLoanToken();
|
||||
|
||||
// No loan token available
|
||||
if (!token)
|
||||
return;
|
||||
|
||||
pugi::xml_document doc;
|
||||
|
||||
pugi::xml_node decl = doc.append_child(pugi::node_declaration);
|
||||
decl.append_attribute("version") = "1.0";
|
||||
|
||||
pugi::xml_node root = doc.append_child("loanToken");
|
||||
gourou::appendTextElem(root, "id", (*token)["id"]);
|
||||
gourou::appendTextElem(root, "operatorURL", (*token)["operatorURL"]);
|
||||
gourou::appendTextElem(root, "validity", (*token)["validity"]);
|
||||
gourou::appendTextElem(root, "name", item->getMetadata("title"));
|
||||
|
||||
char * activationDir = strdup(deviceFile);
|
||||
activationDir = dirname(activationDir);
|
||||
|
||||
gourou::StringXMLWriter xmlWriter;
|
||||
doc.save(xmlWriter, " ");
|
||||
std::string xmlStr = xmlWriter.getResult();
|
||||
|
||||
// Use first bytes of SHA1(id) as filename
|
||||
unsigned char sha1[gourou::SHA1_LEN];
|
||||
client.digest("SHA1", (unsigned char*)(*token)["id"].c_str(), (*token)["id"].size(), sha1);
|
||||
gourou::ByteArray tmp(sha1, sizeof(sha1));
|
||||
std::string filenameHex = tmp.toHex();
|
||||
std::string filename(filenameHex.c_str(), ID_HASH_SIZE);
|
||||
std::string fullPath = std::string(activationDir);
|
||||
fullPath += std::string ("/") + std::string(LOANS_DIR);
|
||||
mkpath(fullPath.c_str());
|
||||
fullPath += filename + std::string(".xml");
|
||||
gourou::writeFile(fullPath, xmlStr);
|
||||
|
||||
std::cout << "Loan token serialized into " << fullPath << std::endl;
|
||||
|
||||
free(activationDir);
|
||||
}
|
||||
|
||||
private:
|
||||
DRMProcessorClientImpl client;
|
||||
};
|
||||
|
||||
|
||||
@@ -133,7 +184,7 @@ static void usage(const char* cmd)
|
||||
{
|
||||
std::cout << "Download EPUB file from ACSM request file" << std::endl;
|
||||
|
||||
std::cout << "Usage: " << cmd << " [(-d|--device-file) device.xml] [(-a|--activation-file) activation.xml] [(-k|--device-key-file) devicesalt] [(-O|--output-dir) dir] [(-o|--output-file) output(.epub|.pdf|.der)] [(-v|--verbose)] [(-h|--help)] (-f|--acsm-file) file.acsm|(-e|--export-private-key)" << std::endl << std::endl;
|
||||
std::cout << "Usage: " << cmd << " [(-d|--device-file) device.xml] [(-a|--activation-file) activation.xml] [(-k|--device-key-file) devicesalt] [(-O|--output-dir) dir] [(-o|--output-file) output(.epub|.pdf|.der)] [(-r|--resume)] [(-v|--verbose)] [(-h|--help)] (-f|--acsm-file) file.acsm|(-e|--export-private-key)" << std::endl << std::endl;
|
||||
|
||||
std::cout << " " << "-d|--device-file" << "\t" << "device.xml file from eReader" << std::endl;
|
||||
std::cout << " " << "-a|--activation-file" << "\t" << "activation.xml file from eReader" << std::endl;
|
||||
@@ -142,6 +193,7 @@ static void usage(const char* cmd)
|
||||
std::cout << " " << "-o|--output-file" << "\t" << "Optional output filename (default <title.(epub|pdf|der)>)" << std::endl;
|
||||
std::cout << " " << "-f|--acsm-file" << "\t" << "ACSM request file for epub download" << std::endl;
|
||||
std::cout << " " << "-e|--export-private-key"<< "\t" << "Export private key in DER format" << std::endl;
|
||||
std::cout << " " << "-r|--resume" << "\t\t" << "Try to resume download (in case of previous failure)" << std::endl;
|
||||
std::cout << " " << "-v|--verbose" << "\t\t" << "Increase verbosity, can be set multiple times" << std::endl;
|
||||
std::cout << " " << "-V|--version" << "\t\t" << "Display libgourou version" << std::endl;
|
||||
std::cout << " " << "-h|--help" << "\t\t" << "This help" << std::endl;
|
||||
@@ -171,13 +223,14 @@ int main(int argc, char** argv)
|
||||
{"output-file", required_argument, 0, 'o' },
|
||||
{"acsm-file", required_argument, 0, 'f' },
|
||||
{"export-private-key",no_argument, 0, 'e' },
|
||||
{"resume", no_argument, 0, 'r' },
|
||||
{"verbose", no_argument, 0, 'v' },
|
||||
{"version", no_argument, 0, 'V' },
|
||||
{"help", no_argument, 0, 'h' },
|
||||
{0, 0, 0, 0 }
|
||||
};
|
||||
|
||||
c = getopt_long(argc, argv, "d:a:k:O:o:f:evVh",
|
||||
c = getopt_long(argc, argv, "d:a:k:O:o:f:ervVh",
|
||||
long_options, &option_index);
|
||||
if (c == -1)
|
||||
break;
|
||||
@@ -204,6 +257,9 @@ int main(int argc, char** argv)
|
||||
case 'e':
|
||||
exportPrivateKey = true;
|
||||
break;
|
||||
case 'r':
|
||||
resume = true;
|
||||
break;
|
||||
case 'v':
|
||||
verbose++;
|
||||
break;
|
||||
|
||||
479
utils/adept_loan_mgt.cpp
Normal file
479
utils/adept_loan_mgt.cpp
Normal file
@@ -0,0 +1,479 @@
|
||||
/*
|
||||
Copyright (c) 2022, Grégory Soutadé
|
||||
|
||||
All rights reserved.
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above copyright
|
||||
notice, this list of conditions and the following disclaimer in the
|
||||
documentation and/or other materials provided with the distribution.
|
||||
* Neither the name of the copyright holder nor the
|
||||
names of its contributors may be used to endorse or promote products
|
||||
derived from this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND ANY
|
||||
EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
DISCLAIMED. IN NO EVENT SHALL THE REGENTS AND CONTRIBUTORS BE LIABLE FOR ANY
|
||||
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#include <getopt.h>
|
||||
|
||||
#include <iostream>
|
||||
#include <algorithm>
|
||||
|
||||
#define _XOPEN_SOURCE 700
|
||||
#include <stdio.h>
|
||||
#include <sys/types.h>
|
||||
#include <dirent.h>
|
||||
#include <libgen.h>
|
||||
#include <time.h>
|
||||
|
||||
#include <libgourou.h>
|
||||
#include <libgourou_common.h>
|
||||
#include "drmprocessorclientimpl.h"
|
||||
#include "utils_common.h"
|
||||
|
||||
#define MAX_SIZE_BOOK_NAME 30
|
||||
|
||||
static char* activationDir = 0;
|
||||
static const char* deviceFile = "device.xml";
|
||||
static const char* activationFile = "activation.xml";
|
||||
static const char* devicekeyFile = "devicesalt";
|
||||
static bool list = false;
|
||||
static const char* returnID = 0;
|
||||
static const char* deleteID = 0;
|
||||
|
||||
struct Loan
|
||||
{
|
||||
std::string id;
|
||||
std::string operatorURL;
|
||||
std::string validity;
|
||||
std::string bookName;
|
||||
|
||||
std::string path;
|
||||
};
|
||||
|
||||
class LoanMGT
|
||||
{
|
||||
public:
|
||||
~LoanMGT()
|
||||
{
|
||||
for (const auto& kv : loanedBooks)
|
||||
delete kv.second;
|
||||
}
|
||||
|
||||
int run()
|
||||
{
|
||||
int ret = 0;
|
||||
try
|
||||
{
|
||||
DRMProcessorClientImpl client;
|
||||
gourou::DRMProcessor processor(&client, deviceFile, activationFile, devicekeyFile);
|
||||
|
||||
loadLoanedBooks();
|
||||
|
||||
if (list)
|
||||
displayLoanList();
|
||||
else if (returnID)
|
||||
returnBook(processor);
|
||||
else if (deleteID)
|
||||
deleteLoan();
|
||||
} catch(std::exception& e)
|
||||
{
|
||||
std::cout << e.what() << std::endl;
|
||||
ret = 1;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
private:
|
||||
void loadLoanedBooks()
|
||||
{
|
||||
DIR *dp;
|
||||
struct dirent *ep;
|
||||
int entryLen;
|
||||
struct Loan* loan;
|
||||
char * res;
|
||||
|
||||
std::string loanDir = std::string(activationDir) + std::string("/") + LOANS_DIR;
|
||||
|
||||
if (!fileExists(loanDir.c_str()))
|
||||
return;
|
||||
|
||||
dp = opendir (loanDir.c_str());
|
||||
|
||||
if(!dp)
|
||||
EXCEPTION(gourou::USER_INVALID_INPUT, "Cannot read directory " << loanDir);
|
||||
|
||||
while ((ep = readdir (dp)))
|
||||
{
|
||||
if (ep->d_type != DT_LNK &&
|
||||
ep->d_type != DT_REG)
|
||||
continue;
|
||||
|
||||
entryLen = strlen(ep->d_name);
|
||||
|
||||
if (entryLen <= 4 ||
|
||||
ep->d_name[entryLen-4] != '.' ||
|
||||
ep->d_name[entryLen-3] != 'x' ||
|
||||
ep->d_name[entryLen-2] != 'm' ||
|
||||
ep->d_name[entryLen-1] != 'l')
|
||||
continue;
|
||||
|
||||
std::string id = std::string(ep->d_name, entryLen-4);
|
||||
|
||||
loan = new Loan;
|
||||
loan->path = loanDir + std::string("/") + ep->d_name;
|
||||
|
||||
pugi::xml_document xmlDoc;
|
||||
pugi::xml_node node;
|
||||
|
||||
if (!xmlDoc.load_file(loan->path.c_str(), pugi::parse_ws_pcdata_single|pugi::parse_escapes, pugi::encoding_utf8))
|
||||
{
|
||||
std::cout << "Invalid loan entry " << loan->path << std::endl;
|
||||
goto error;
|
||||
}
|
||||
|
||||
// id
|
||||
node = xmlDoc.select_node("//id").node();
|
||||
if (!node)
|
||||
{
|
||||
std::cout << "Invalid loan entry " << ep->d_name << ", no id element" << std::endl;
|
||||
goto error;
|
||||
}
|
||||
loan->id = node.first_child().value();
|
||||
|
||||
// operatorURL
|
||||
node = xmlDoc.select_node("//operatorURL").node();
|
||||
if (!node)
|
||||
{
|
||||
std::cout << "Invalid loan entry " << ep->d_name << ", no operatorURL element" << std::endl;
|
||||
goto error;
|
||||
}
|
||||
loan->operatorURL = node.first_child().value();
|
||||
|
||||
// validity
|
||||
node = xmlDoc.select_node("//validity").node();
|
||||
if (!node)
|
||||
{
|
||||
std::cout << "Invalid loan entry " << ep->d_name << ", no validity element" << std::endl;
|
||||
goto error;
|
||||
}
|
||||
loan->validity = node.first_child().value();
|
||||
|
||||
// bookName
|
||||
node = xmlDoc.select_node("//name").node();
|
||||
if (!node)
|
||||
{
|
||||
std::cout << "Invalid loan entry " << ep->d_name << ", no name element" << std::endl;
|
||||
goto error;
|
||||
}
|
||||
loan->bookName = node.first_child().value();
|
||||
|
||||
struct tm tm;
|
||||
res = strptime(loan->validity.c_str(), "%Y-%m-%dT%H:%M:%S%Z", &tm);
|
||||
if (*res == 0)
|
||||
{
|
||||
if (mktime(&tm) <= time(NULL))
|
||||
loan->validity = " (Expired)";
|
||||
}
|
||||
else
|
||||
{
|
||||
std::cout << "Unable to parse validity timestamp :" << loan->validity << std::endl;
|
||||
loan->validity = " (Unknown)";
|
||||
}
|
||||
|
||||
loanedBooks[id] = loan;
|
||||
continue;
|
||||
|
||||
error:
|
||||
if (loan)
|
||||
delete loan;
|
||||
}
|
||||
|
||||
closedir (dp);
|
||||
}
|
||||
|
||||
void displayLoanList()
|
||||
{
|
||||
if (!loanedBooks.size())
|
||||
{
|
||||
std::cout << "Any book loaned" << std::endl;
|
||||
return;
|
||||
}
|
||||
|
||||
struct Loan* loan;
|
||||
unsigned int maxSizeBookName=0;
|
||||
// Compute max size
|
||||
for (const auto& kv : loanedBooks)
|
||||
{
|
||||
loan = kv.second;
|
||||
if (loan->bookName.size() > maxSizeBookName)
|
||||
maxSizeBookName = loan->bookName.size();
|
||||
}
|
||||
|
||||
if (maxSizeBookName > MAX_SIZE_BOOK_NAME)
|
||||
maxSizeBookName = MAX_SIZE_BOOK_NAME;
|
||||
else if ((maxSizeBookName % 2))
|
||||
maxSizeBookName++;
|
||||
|
||||
// std::cout << " ID Book Expiration" << std::endl;
|
||||
// std::cout << "------------------------------" << std::endl;
|
||||
|
||||
int fillID, fillBookName, fillExpiration=(20 - 10)/2;
|
||||
|
||||
fillID = (ID_HASH_SIZE - 2) / 2;
|
||||
fillBookName = (maxSizeBookName - 4) / 2;
|
||||
|
||||
std::cout.width (fillID);
|
||||
std::cout << "";
|
||||
std::cout << "ID" ;
|
||||
std::cout.width (fillID);
|
||||
std::cout << "";
|
||||
std::cout << " " ;
|
||||
|
||||
std::cout.width (fillBookName);
|
||||
std::cout << "";
|
||||
std::cout << "Book" ;
|
||||
std::cout.width (fillBookName);
|
||||
std::cout << "";
|
||||
std::cout << " " ;
|
||||
|
||||
std::cout.width (fillExpiration);
|
||||
std::cout << "";
|
||||
std::cout << "Exipration";
|
||||
std::cout.width (fillExpiration);
|
||||
std::cout << "" << std::endl;
|
||||
|
||||
std::cout.fill ('-');
|
||||
std::cout.width (ID_HASH_SIZE + 4 + maxSizeBookName + 4 + 20);
|
||||
std::cout << "" << std::endl;
|
||||
std::cout.fill (' ');
|
||||
|
||||
std::string bookName;
|
||||
|
||||
for (const auto& kv : loanedBooks)
|
||||
{
|
||||
loan = kv.second;
|
||||
|
||||
std::cout << kv.first;
|
||||
std::cout << " ";
|
||||
|
||||
if (loan->bookName.size() > MAX_SIZE_BOOK_NAME)
|
||||
bookName = std::string(loan->bookName.c_str(), MAX_SIZE_BOOK_NAME);
|
||||
else
|
||||
bookName = loan->bookName;
|
||||
|
||||
std::cout << bookName;
|
||||
std::cout.width (maxSizeBookName - bookName.size());
|
||||
std::cout << "";
|
||||
std::cout << " ";
|
||||
|
||||
std::cout << loan->validity << std::endl;
|
||||
}
|
||||
|
||||
std::cout << std::endl;
|
||||
}
|
||||
|
||||
void returnBook(gourou::DRMProcessor& processor)
|
||||
{
|
||||
struct Loan* loan = loanedBooks[std::string(returnID)];
|
||||
|
||||
if (!loan)
|
||||
{
|
||||
std::cout << "Error : Loan " << returnID << " doesn't exists" << std::endl;
|
||||
return;
|
||||
}
|
||||
|
||||
processor.returnLoan(loan->id, loan->operatorURL);
|
||||
|
||||
deleteID = returnID;
|
||||
if (deleteLoan(false))
|
||||
{
|
||||
std::cout << "Loan " << returnID << " successfully returned" << std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
bool deleteLoan(bool displayResult=true)
|
||||
{
|
||||
struct Loan* loan = loanedBooks[std::string(deleteID)];
|
||||
|
||||
if (!loan)
|
||||
{
|
||||
std::cout << "Error : Loan " << deleteID << " doesn't exists" << std::endl;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (unlink(loan->path.c_str()))
|
||||
{
|
||||
std::cout << "Error : Cannot delete " << loan->path << std::endl;
|
||||
return false;
|
||||
}
|
||||
else if (displayResult)
|
||||
{
|
||||
std::cout << "Loan " << deleteID << " deleted" << std::endl;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
std::map<std::string, struct Loan*> loanedBooks;
|
||||
};
|
||||
|
||||
|
||||
static void usage(const char* cmd)
|
||||
{
|
||||
std::cout << "Manage loaned books" << std::endl;
|
||||
|
||||
std::cout << "Usage: " << cmd << " [(-d|--activation-dir) dir] (-l|--list)|(-D|--delete loanID)|(-R|--delete loanID) [(-v|--verbose)] [(-h|--help)]" << std::endl << std::endl;
|
||||
|
||||
std::cout << " " << "-d|--activation-dir" << "\t" << "Directory of device.xml/activation.xml and device key" << std::endl;
|
||||
std::cout << " " << "-l|--list" << "\t\t" << "List all loaned books" << std::endl;
|
||||
std::cout << " " << "-r|--return" << "\t\t" << "Return a loaned book" << std::endl;
|
||||
std::cout << " " << "-D|--delete" << "\t\t" << "Delete a loan entry without returning it" << std::endl;
|
||||
std::cout << " " << "-v|--verbose" << "\t\t" << "Increase verbosity, can be set multiple times" << std::endl;
|
||||
std::cout << " " << "-V|--version" << "\t\t" << "Display libgourou version" << std::endl;
|
||||
std::cout << " " << "-h|--help" << "\t\t" << "This help" << std::endl;
|
||||
|
||||
std::cout << std::endl;
|
||||
std::cout << "Activation directory is optional. If not set, it's looked into :" << std::endl;
|
||||
std::cout << " * Current directory" << std::endl;
|
||||
std::cout << " * .adept" << std::endl;
|
||||
std::cout << " * adobe-digital-editions directory" << std::endl;
|
||||
std::cout << " * .adobe-digital-editions directory" << std::endl;
|
||||
}
|
||||
|
||||
int main(int argc, char** argv)
|
||||
{
|
||||
int c, ret = -1;
|
||||
|
||||
const char** files[] = {&devicekeyFile, &deviceFile, &activationFile};
|
||||
int verbose = gourou::DRMProcessor::getLogLevel();
|
||||
int actions = 0;
|
||||
|
||||
while (1) {
|
||||
int option_index = 0;
|
||||
static struct option long_options[] = {
|
||||
{"activation-dir", required_argument, 0, 'd' },
|
||||
{"list", no_argument, 0, 'l' },
|
||||
{"return", no_argument, 0, 'r' },
|
||||
{"delete", no_argument, 0, 'D' },
|
||||
{"verbose", no_argument, 0, 'v' },
|
||||
{"version", no_argument, 0, 'V' },
|
||||
{"help", no_argument, 0, 'h' },
|
||||
{0, 0, 0, 0 }
|
||||
};
|
||||
|
||||
c = getopt_long(argc, argv, "d:lr:D:vVh",
|
||||
long_options, &option_index);
|
||||
if (c == -1)
|
||||
break;
|
||||
|
||||
switch (c) {
|
||||
case 'd':
|
||||
activationDir = optarg;
|
||||
break;
|
||||
case 'l':
|
||||
list = true;
|
||||
actions++;
|
||||
break;
|
||||
case 'r':
|
||||
returnID = optarg;
|
||||
actions++;
|
||||
break;
|
||||
case 'D':
|
||||
deleteID = optarg;
|
||||
actions++;
|
||||
break;
|
||||
case 'v':
|
||||
verbose++;
|
||||
break;
|
||||
case 'V':
|
||||
version();
|
||||
return 0;
|
||||
case 'h':
|
||||
usage(argv[0]);
|
||||
return 0;
|
||||
default:
|
||||
usage(argv[0]);
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
gourou::DRMProcessor::setLogLevel(verbose);
|
||||
|
||||
// By default, simply list books loaned
|
||||
if (actions == 0)
|
||||
list = true;
|
||||
else if (actions != 1)
|
||||
{
|
||||
usage(argv[0]);
|
||||
return -1;
|
||||
}
|
||||
|
||||
LoanMGT loanMGT;
|
||||
|
||||
int i;
|
||||
bool hasErrors = false;
|
||||
const char* orig;
|
||||
char *filename;
|
||||
for (i=0; i<(int)ARRAY_SIZE(files); i++)
|
||||
{
|
||||
orig = *files[i];
|
||||
|
||||
if (activationDir)
|
||||
{
|
||||
std::string path = std::string(activationDir) + std::string("/") + orig;
|
||||
filename = strdup(path.c_str());
|
||||
}
|
||||
else
|
||||
filename = strdup(orig);
|
||||
*files[i] = findFile(filename);
|
||||
free(filename);
|
||||
if (!*files[i])
|
||||
{
|
||||
std::cout << "Error : " << orig << " doesn't exists, did you activate your device ?" << std::endl;
|
||||
hasErrors = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (hasErrors)
|
||||
{
|
||||
// In case of activation dir was provided by user
|
||||
activationDir = 0;
|
||||
goto end;
|
||||
}
|
||||
|
||||
if (activationDir)
|
||||
activationDir = strdup(activationDir); // For below free
|
||||
else
|
||||
{
|
||||
activationDir = strdup(deviceFile);
|
||||
activationDir = dirname(activationDir);
|
||||
}
|
||||
|
||||
ret = loanMGT.run();
|
||||
|
||||
end:
|
||||
for (i=0; i<(int)ARRAY_SIZE(files); i++)
|
||||
{
|
||||
if (*files[i])
|
||||
free((void*)*files[i]);
|
||||
}
|
||||
|
||||
if (activationDir)
|
||||
free(activationDir);
|
||||
|
||||
return ret;
|
||||
}
|
||||
@@ -114,16 +114,16 @@ public:
|
||||
// Use temp file for PDF
|
||||
if (type == gourou::DRMProcessor::ITEM_TYPE::PDF)
|
||||
{
|
||||
char* tempFile = tempnam("/tmp", NULL);
|
||||
processor.removeDRM(inputFile, tempFile, type, encryptionKey, encryptionKeySize);
|
||||
std::string tempFile = filename + ".tmp";
|
||||
/* Be sure there is not already a temp file */
|
||||
unlink(tempFile.c_str());
|
||||
processor.removeDRM(filename, tempFile, type, encryptionKey, encryptionKeySize);
|
||||
/* Original file must be removed before doing a copy... */
|
||||
unlink(inputFile);
|
||||
if (!rename(tempFile, filename.c_str()))
|
||||
unlink(filename.c_str());
|
||||
if (rename(tempFile.c_str(), filename.c_str()))
|
||||
{
|
||||
free(tempFile);
|
||||
EXCEPTION(gourou::DRM_FILE_ERROR, "Unable to copy " << tempFile << " into " << filename);
|
||||
}
|
||||
free(tempFile);
|
||||
}
|
||||
else
|
||||
processor.removeDRM(inputFile, filename, type, encryptionKey, encryptionKeySize);
|
||||
|
||||
@@ -45,27 +45,6 @@
|
||||
#include <libgourou_log.h>
|
||||
#include "drmprocessorclientimpl.h"
|
||||
|
||||
// https://stackoverflow.com/questions/216823/how-to-trim-a-stdstring
|
||||
// trim from start (in place)
|
||||
static inline void ltrim(std::string &s) {
|
||||
s.erase(s.begin(), std::find_if(s.begin(), s.end(), [](unsigned char ch) {
|
||||
return !std::isspace(ch);
|
||||
}));
|
||||
}
|
||||
|
||||
// trim from end (in place)
|
||||
static inline void rtrim(std::string &s) {
|
||||
s.erase(std::find_if(s.rbegin(), s.rend(), [](unsigned char ch) {
|
||||
return !std::isspace(ch);
|
||||
}).base(), s.end());
|
||||
}
|
||||
|
||||
// trim from both ends (in place)
|
||||
static inline void trim(std::string &s) {
|
||||
ltrim(s);
|
||||
rtrim(s);
|
||||
}
|
||||
|
||||
/* Digest interface */
|
||||
void* DRMProcessorClientImpl::createDigest(const std::string& digestName)
|
||||
{
|
||||
@@ -163,8 +142,8 @@ static size_t curlHeaders(char *buffer, size_t size, size_t nitems, void *userda
|
||||
std::string key = std::string(buffer, pos);
|
||||
std::string value = std::string(&buffer[pos+1], (size*nitems)-(pos+1));
|
||||
|
||||
trim(key);
|
||||
trim(value);
|
||||
key = gourou::trim(key);
|
||||
value = gourou::trim(value);
|
||||
|
||||
(*responseHeaders)[key] = value;
|
||||
|
||||
@@ -175,7 +154,7 @@ static size_t curlHeaders(char *buffer, size_t size, size_t nitems, void *userda
|
||||
return size*nitems;
|
||||
}
|
||||
|
||||
std::string DRMProcessorClientImpl::sendHTTPRequest(const std::string& URL, const std::string& POSTData, const std::string& contentType, std::map<std::string, std::string>* responseHeaders, int fd)
|
||||
std::string DRMProcessorClientImpl::sendHTTPRequest(const std::string& URL, const std::string& POSTData, const std::string& contentType, std::map<std::string, std::string>* responseHeaders, int fd, bool resume)
|
||||
{
|
||||
gourou::ByteArray replyData;
|
||||
std::map<std::string, std::string> localHeaders;
|
||||
@@ -191,6 +170,18 @@ std::string DRMProcessorClientImpl::sendHTTPRequest(const std::string& URL, cons
|
||||
|
||||
unsigned prevDownloadedBytes;
|
||||
downloadedBytes = 0;
|
||||
if (fd && resume)
|
||||
{
|
||||
struct stat _stat;
|
||||
if (!fstat(fd, &_stat))
|
||||
{
|
||||
GOUROU_LOG(gourou::WARN, "Resume download @ " << _stat.st_size << " bytes");
|
||||
downloadedBytes = _stat.st_size;
|
||||
}
|
||||
else
|
||||
GOUROU_LOG(gourou::WARN, "Want to resume, but fstat failed");
|
||||
}
|
||||
|
||||
CURL *curl = curl_easy_init();
|
||||
CURLcode res;
|
||||
curl_easy_setopt(curl, CURLOPT_URL, URL.c_str());
|
||||
@@ -244,7 +235,7 @@ std::string DRMProcessorClientImpl::sendHTTPRequest(const std::string& URL, cons
|
||||
// Connexion failed, wait & retry
|
||||
if (res == CURLE_COULDNT_CONNECT)
|
||||
{
|
||||
GOUROU_LOG(gourou::WARN, "Connection failed, attempt " << (i+1) << "/" << HTTP_REQ_MAX_RETRY);
|
||||
GOUROU_LOG(gourou::WARN, "\nConnection failed, attempt " << (i+1) << "/" << HTTP_REQ_MAX_RETRY);
|
||||
}
|
||||
// Transfer failed but some data has been received
|
||||
// --> try again without incrementing tries
|
||||
@@ -252,11 +243,11 @@ std::string DRMProcessorClientImpl::sendHTTPRequest(const std::string& URL, cons
|
||||
{
|
||||
if (prevDownloadedBytes != downloadedBytes)
|
||||
{
|
||||
GOUROU_LOG(gourou::WARN, "Connection broken, but data received, try again");
|
||||
GOUROU_LOG(gourou::WARN, "\nConnection broken, but data received, try again");
|
||||
i--;
|
||||
}
|
||||
else
|
||||
GOUROU_LOG(gourou::WARN, "Connection broken and no data received, attempt " << (i+1) << "/" << HTTP_REQ_MAX_RETRY);
|
||||
GOUROU_LOG(gourou::WARN, "\nConnection broken and no data received, attempt " << (i+1) << "/" << HTTP_REQ_MAX_RETRY);
|
||||
}
|
||||
// Other error --> fail
|
||||
else
|
||||
|
||||
@@ -46,7 +46,7 @@ public:
|
||||
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(""), std::map<std::string, std::string>* responseHeaders=0, int fd=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, int fd=0, bool resume=false);
|
||||
|
||||
virtual void RSAPrivateEncrypt(const unsigned char* RSAKey, unsigned int RSAKeyLength,
|
||||
const RSA_KEY_TYPE keyType, const std::string& password,
|
||||
|
||||
@@ -29,6 +29,9 @@
|
||||
#ifndef _UTILS_COMMON_H_
|
||||
#define _UTILS_COMMON_H_
|
||||
|
||||
#define LOANS_DIR "loans/"
|
||||
#define ID_HASH_SIZE 16
|
||||
|
||||
#define ARRAY_SIZE(arr) (sizeof(arr)/sizeof(arr[0]))
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user