Compare commits
24 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f568b5d3a8 | ||
|
|
8c413b4f34 | ||
|
|
8fe8ba2808 | ||
|
|
570ad83747 | ||
|
|
2e7e352e35 | ||
|
|
9556fe862f | ||
|
|
2f2e4e193e | ||
|
|
5d3112bc38 | ||
|
|
e149af9e17 | ||
|
|
1221b2a95a | ||
|
|
2ce6142596 | ||
|
|
0f475423c0 | ||
|
|
9b946a62b4 | ||
|
|
432eb6f6cb | ||
|
|
85b65f8d61 | ||
|
|
25f5049ab9 | ||
|
|
16a13eed89 | ||
|
|
479869b7f2 | ||
|
|
41f1a1e980 | ||
|
|
9648157bf7 | ||
|
|
a97a915bc8 | ||
|
|
a623a3d796 | ||
|
|
7d161133c3 | ||
|
|
7d93817e49 |
3
.gitignore
vendored
3
.gitignore
vendored
@@ -6,4 +6,5 @@ lib
|
|||||||
utils/acsmdownloader
|
utils/acsmdownloader
|
||||||
utils/adept_activate
|
utils/adept_activate
|
||||||
utils/adept_remove
|
utils/adept_remove
|
||||||
.adept
|
utils/adept_loan_mgt
|
||||||
|
.adept*
|
||||||
|
|||||||
6
Makefile
6
Makefile
@@ -36,7 +36,7 @@ TARGETDIR := bin
|
|||||||
SRCEXT := cpp
|
SRCEXT := cpp
|
||||||
OBJEXT := o
|
OBJEXT := o
|
||||||
|
|
||||||
SOURCES = src/libgourou.cpp src/user.cpp src/device.cpp src/fulfillment_item.cpp src/bytearray.cpp src/pugixml.cpp
|
SOURCES = src/libgourou.cpp src/user.cpp src/device.cpp src/fulfillment_item.cpp src/loan_token.cpp src/bytearray.cpp src/pugixml.cpp
|
||||||
OBJECTS := $(patsubst $(SRCDIR)/%,$(BUILDDIR)/%,$(SOURCES:.$(SRCEXT)=.$(OBJEXT)))
|
OBJECTS := $(patsubst $(SRCDIR)/%,$(BUILDDIR)/%,$(SOURCES:.$(SRCEXT)=.$(OBJEXT)))
|
||||||
|
|
||||||
all: lib obj $(TARGETS)
|
all: lib obj $(TARGETS)
|
||||||
@@ -53,8 +53,8 @@ $(BUILDDIR)/%.$(OBJEXT): $(SRCDIR)/%.$(SRCEXT)
|
|||||||
|
|
||||||
libgourou: libgourou.a libgourou.so
|
libgourou: libgourou.a libgourou.so
|
||||||
|
|
||||||
libgourou.a: $(OBJECTS)
|
libgourou.a: $(OBJECTS) $(UPDFPARSERLIB)
|
||||||
$(AR) crs $@ obj/*.o ./lib/updfparser/obj/*.o
|
$(AR) crs $@ obj/*.o $(UPDFPARSERLIB)
|
||||||
|
|
||||||
libgourou.so: $(OBJECTS) $(UPDFPARSERLIB)
|
libgourou.so: $(OBJECTS) $(UPDFPARSERLIB)
|
||||||
$(CXX) obj/*.o $(LDFLAGS) -o $@ -shared
|
$(CXX) obj/*.o $(LDFLAGS) -o $@ -shared
|
||||||
|
|||||||
25
README.md
25
README.md
@@ -16,7 +16,7 @@ Main fucntions to use from gourou::DRMProcessor are :
|
|||||||
* Create a new device : _createDRMProcessor()_
|
* Create a new device : _createDRMProcessor()_
|
||||||
* Register a new device : _signIn()_ and _activateDevice()_
|
* Register a new device : _signIn()_ and _activateDevice()_
|
||||||
* Remove DRM : _removeDRM()_
|
* Remove DRM : _removeDRM()_
|
||||||
|
* Return loaned book : _returnLoan()_
|
||||||
|
|
||||||
You can import configuration from (at least) :
|
You can import configuration from (at least) :
|
||||||
|
|
||||||
@@ -39,8 +39,7 @@ For libgourou :
|
|||||||
|
|
||||||
For utils :
|
For utils :
|
||||||
|
|
||||||
* QT5Core
|
* libcurl
|
||||||
* QT5Network
|
|
||||||
* OpenSSL
|
* OpenSSL
|
||||||
* libzip
|
* libzip
|
||||||
|
|
||||||
@@ -70,10 +69,10 @@ BUILD_SHARED build libgourou.so if 1, nothing if 0, can be combined with BUILD_S
|
|||||||
Utils
|
Utils
|
||||||
-----
|
-----
|
||||||
|
|
||||||
You can import configuration from your eReader or create a new one with utils/activate :
|
You can import configuration from your eReader or create a new one with _utils/adept\_activate_ :
|
||||||
|
|
||||||
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:$PWD
|
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:$PWD
|
||||||
./utils/activate -u <AdobeID USERNAME>
|
./utils/adept_activate -u <AdobeID USERNAME>
|
||||||
|
|
||||||
Then a _./.adept_ directory is created with all configuration file
|
Then a _./.adept_ directory is created with all configuration file
|
||||||
|
|
||||||
@@ -82,7 +81,7 @@ To download an ePub/PDF :
|
|||||||
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:$PWD
|
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:$PWD
|
||||||
./utils/acsmdownloader -f <ACSM_FILE>
|
./utils/acsmdownloader -f <ACSM_FILE>
|
||||||
|
|
||||||
To export your private key :
|
To export your private key (for DeDRM software) :
|
||||||
|
|
||||||
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:$PWD
|
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:$PWD
|
||||||
./utils/acsmdownloader --export-private-key [-o adobekey_1.der]
|
./utils/acsmdownloader --export-private-key [-o adobekey_1.der]
|
||||||
@@ -92,6 +91,19 @@ To remove ADEPT DRM :
|
|||||||
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:$PWD
|
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:$PWD
|
||||||
./utils/adept_remove -f <encryptedFile>
|
./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
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
Copyright
|
Copyright
|
||||||
---------
|
---------
|
||||||
@@ -113,3 +125,4 @@ Special thanks
|
|||||||
--------------
|
--------------
|
||||||
|
|
||||||
* _Jens_ for all test samples and utils testing
|
* _Jens_ for all test samples and utils testing
|
||||||
|
* _Milian_ for debug & code
|
||||||
|
|||||||
@@ -1,69 +0,0 @@
|
|||||||
Introduction
|
|
||||||
------------
|
|
||||||
|
|
||||||
libgourou is a free implementation of Adobe's ADEPT protocol used to add DRM on ePub/PDF files. It overcome the lacks of Adobe support for Linux platforms.
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
Dependencies
|
|
||||||
------------
|
|
||||||
|
|
||||||
For libgourou :
|
|
||||||
|
|
||||||
* None
|
|
||||||
|
|
||||||
For utils :
|
|
||||||
|
|
||||||
* QT5Core
|
|
||||||
* QT5Network
|
|
||||||
* OpenSSL
|
|
||||||
* libzip
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
Utils
|
|
||||||
-----
|
|
||||||
|
|
||||||
You can import configuration from your eReader or create a new one with utils/activate :
|
|
||||||
|
|
||||||
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:$PWD
|
|
||||||
./activate -u <AdobeID USERNAME>
|
|
||||||
|
|
||||||
Then a _./.adept_ directory is created with all configuration file
|
|
||||||
|
|
||||||
To download an ePub/PDF :
|
|
||||||
|
|
||||||
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:$PWD
|
|
||||||
./acsmdownloader -f <ACSM_FILE>
|
|
||||||
|
|
||||||
To export your private key :
|
|
||||||
|
|
||||||
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:$PWD
|
|
||||||
./acsmdownloader --export-private-key [-o adobekey_1.der]
|
|
||||||
|
|
||||||
To remove ADEPT DRM :
|
|
||||||
|
|
||||||
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:$PWD
|
|
||||||
./adept_remove -f <encryptedFile>
|
|
||||||
|
|
||||||
|
|
||||||
Copyright
|
|
||||||
---------
|
|
||||||
|
|
||||||
Grégory Soutadé
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
License
|
|
||||||
-------
|
|
||||||
|
|
||||||
libgourou : LGPL v3 or later
|
|
||||||
|
|
||||||
utils : BSD
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
Special thanks
|
|
||||||
--------------
|
|
||||||
|
|
||||||
* _Jens_ for all test samples and utils testing
|
|
||||||
@@ -98,10 +98,12 @@ namespace gourou
|
|||||||
* @param POSTData POST data if needed, if not set, a GET request is done
|
* @param POSTData POST data if needed, if not set, a GET request is done
|
||||||
* @param contentType Optional content type of POST Data
|
* @param contentType Optional content type of POST Data
|
||||||
* @param responseHeaders Optional Response headers of HTTP request
|
* @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
|
* @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) = 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
|
class RSAInterface
|
||||||
|
|||||||
@@ -20,7 +20,7 @@
|
|||||||
#ifndef _FULFILLMENT_ITEM_H_
|
#ifndef _FULFILLMENT_ITEM_H_
|
||||||
#define _FULFILLMENT_ITEM_H_
|
#define _FULFILLMENT_ITEM_H_
|
||||||
|
|
||||||
#include "bytearray.h"
|
#include "loan_token.h"
|
||||||
|
|
||||||
#include <pugixml.hpp>
|
#include <pugixml.hpp>
|
||||||
|
|
||||||
@@ -42,6 +42,8 @@ namespace gourou
|
|||||||
*/
|
*/
|
||||||
FulfillmentItem(pugi::xml_document& doc, User* user);
|
FulfillmentItem(pugi::xml_document& doc, User* user);
|
||||||
|
|
||||||
|
~FulfillmentItem();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Return metadata value from ACSM metadata section
|
* @brief Return metadata value from ACSM metadata section
|
||||||
*
|
*
|
||||||
@@ -64,12 +66,18 @@ namespace gourou
|
|||||||
*/
|
*/
|
||||||
std::string getResource();
|
std::string getResource();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Return loan token if there is one
|
||||||
|
*/
|
||||||
|
LoanToken* getLoanToken();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
pugi::xml_document fulfillDoc;
|
pugi::xml_document fulfillDoc;
|
||||||
pugi::xml_node metadatas;
|
pugi::xml_node metadatas;
|
||||||
pugi::xml_document rights;
|
pugi::xml_document rights;
|
||||||
std::string downloadURL;
|
std::string downloadURL;
|
||||||
std::string resource;
|
std::string resource;
|
||||||
|
LoanToken* loanToken;
|
||||||
|
|
||||||
void buildRights(const pugi::xml_node& licenseToken, User* user);
|
void buildRights(const pugi::xml_node& licenseToken, User* user);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -40,7 +40,7 @@
|
|||||||
#define ACS_SERVER "http://adeactivate.adobe.com/adept"
|
#define ACS_SERVER "http://adeactivate.adobe.com/adept"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#define LIBGOUROU_VERSION "0.5.1"
|
#define LIBGOUROU_VERSION "0.7"
|
||||||
|
|
||||||
namespace gourou
|
namespace gourou
|
||||||
{
|
{
|
||||||
@@ -81,10 +81,11 @@ namespace gourou
|
|||||||
*
|
*
|
||||||
* @param item Item from fulfill() method
|
* @param item Item from fulfill() method
|
||||||
* @param path Output file path
|
* @param path Output file path
|
||||||
|
* @param resume false if target file should be truncated, true to try resume download
|
||||||
*
|
*
|
||||||
* @return Type of downloaded item
|
* @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)
|
* @brief SignIn into ACS Server (required to activate device)
|
||||||
@@ -99,6 +100,14 @@ namespace gourou
|
|||||||
*/
|
*/
|
||||||
void activateDevice();
|
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).
|
* @brief Create a new ADEPT environment (device.xml, devicesalt and activation.xml).
|
||||||
*
|
*
|
||||||
@@ -130,14 +139,16 @@ namespace gourou
|
|||||||
/**
|
/**
|
||||||
* @brief Send HTTP (GET or POST) request
|
* @brief Send HTTP (GET or POST) request
|
||||||
*
|
*
|
||||||
* @param URL HTTP URL
|
* @param URL HTTP URL
|
||||||
* @param POSTData POST data if needed, if not set, a GET request is done
|
* @param POSTData POST data if needed, if not set, a GET request is done
|
||||||
* @param contentType Optional content type of POST Data
|
* @param contentType Optional content type of POST Data
|
||||||
* @param responseHeaders Optional Response headers of HTTP request
|
* @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
|
* @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);
|
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
|
* @brief Send HTTP POST request to URL with document as POSTData
|
||||||
@@ -185,10 +196,16 @@ namespace gourou
|
|||||||
DRMProcessorClient* getClient() { return client; }
|
DRMProcessorClient* getClient() { return client; }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Remove ADEPT DRM.
|
* @brief Remove ADEPT DRM
|
||||||
* Warning: for PDF format, filenameIn must be different than filenameOut
|
* Warning: for PDF format, filenameIn must be different than filenameOut
|
||||||
|
*
|
||||||
|
* @param filenameIn Input file (with ADEPT DRM)
|
||||||
|
* @param filenameOut Output file (without ADEPT DRM)
|
||||||
|
* @param type Type of file (ePub or PDF)
|
||||||
|
* @param encryptionKey Optional encryption key, do not try to decrypt the one inside input file
|
||||||
|
* @param encryptionKeySize Size of encryption key (if provided)
|
||||||
*/
|
*/
|
||||||
void removeDRM(const std::string& filenameIn, const std::string& filenameOut, ITEM_TYPE type);
|
void removeDRM(const std::string& filenameIn, const std::string& filenameOut, ITEM_TYPE type, const unsigned char* encryptionKey=0, unsigned encryptionKeySize=0);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
gourou::DRMProcessorClient* client;
|
gourou::DRMProcessorClient* client;
|
||||||
@@ -201,7 +218,7 @@ namespace gourou
|
|||||||
void pushTag(void* sha_ctx, uint8_t tag);
|
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, void *sha_ctx, std::map<std::string,std::string> nsHash);
|
||||||
void hashNode(const pugi::xml_node& root, unsigned char* sha_out);
|
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 addNonce(pugi::xml_node& root);
|
||||||
void buildAuthRequest(pugi::xml_document& authReq);
|
void buildAuthRequest(pugi::xml_document& authReq);
|
||||||
void buildInitLicenseServiceRequest(pugi::xml_document& initLicReq, std::string operatorURL);
|
void buildInitLicenseServiceRequest(pugi::xml_document& initLicReq, std::string operatorURL);
|
||||||
@@ -209,17 +226,18 @@ namespace gourou
|
|||||||
void operatorAuth(std::string operatorURL);
|
void operatorAuth(std::string operatorURL);
|
||||||
void buildFulfillRequest(pugi::xml_document& acsmDoc, pugi::xml_document& fulfillReq);
|
void buildFulfillRequest(pugi::xml_document& acsmDoc, pugi::xml_document& fulfillReq);
|
||||||
void buildActivateReq(pugi::xml_document& activateReq);
|
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);
|
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 buildSignInRequest(pugi::xml_document& signInRequest, const std::string& adobeID, const std::string& adobePassword, const std::string& authenticationCertificate);
|
||||||
void fetchLicenseServiceCertificate(const std::string& licenseURL,
|
void fetchLicenseServiceCertificate(const std::string& licenseURL,
|
||||||
const std::string& operatorURL);
|
const std::string& operatorURL);
|
||||||
void decryptADEPTKey(const std::string& encryptedKey, unsigned char* decryptedKey);
|
void decryptADEPTKey(const std::string& encryptedKey, unsigned char* decryptedKey);
|
||||||
void removeEPubDRM(const std::string& filenameIn, const std::string& filenameOut);
|
void removeEPubDRM(const std::string& filenameIn, const std::string& filenameOut, const unsigned char* encryptionKey, unsigned encryptionKeySize);
|
||||||
void generatePDFObjectKey(int version,
|
void generatePDFObjectKey(int version,
|
||||||
const unsigned char* masterKey, unsigned int masterKeyLength,
|
const unsigned char* masterKey, unsigned int masterKeyLength,
|
||||||
int objectId, int objectGenerationNumber,
|
int objectId, int objectGenerationNumber,
|
||||||
unsigned char* keyOut);
|
unsigned char* keyOut);
|
||||||
void removePDFDRM(const std::string& filenameIn, const std::string& filenameOut);
|
void removePDFDRM(const std::string& filenameIn, const std::string& filenameOut, const unsigned char* encryptionKey, unsigned encryptionKeySize);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -55,7 +55,8 @@ namespace gourou
|
|||||||
GOUROU_INVALID_CLIENT,
|
GOUROU_INVALID_CLIENT,
|
||||||
GOUROU_TAG_NOT_FOUND,
|
GOUROU_TAG_NOT_FOUND,
|
||||||
GOUROU_ADEPT_ERROR,
|
GOUROU_ADEPT_ERROR,
|
||||||
GOUROU_FILE_ERROR
|
GOUROU_FILE_ERROR,
|
||||||
|
GOUROU_INVALID_PROPERTY
|
||||||
};
|
};
|
||||||
|
|
||||||
enum FULFILL_ERROR {
|
enum FULFILL_ERROR {
|
||||||
@@ -92,10 +93,12 @@ namespace gourou
|
|||||||
USER_INVALID_ACTIVATION_FILE,
|
USER_INVALID_ACTIVATION_FILE,
|
||||||
USER_NO_AUTHENTICATION_URL,
|
USER_NO_AUTHENTICATION_URL,
|
||||||
USER_NO_PROPERTY,
|
USER_NO_PROPERTY,
|
||||||
|
USER_INVALID_INPUT,
|
||||||
};
|
};
|
||||||
|
|
||||||
enum FULFILL_ITEM_ERROR {
|
enum FULFILL_ITEM_ERROR {
|
||||||
FFI_INVALID_FULFILLMENT_DATA = 0x4000
|
FFI_INVALID_FULFILLMENT_DATA = 0x4000,
|
||||||
|
FFI_INVALID_LOAN_TOKEN
|
||||||
};
|
};
|
||||||
|
|
||||||
enum CLIENT_ERROR {
|
enum CLIENT_ERROR {
|
||||||
@@ -110,6 +113,8 @@ namespace gourou
|
|||||||
CLIENT_ZIP_ERROR,
|
CLIENT_ZIP_ERROR,
|
||||||
CLIENT_GENERIC_EXCEPTION,
|
CLIENT_GENERIC_EXCEPTION,
|
||||||
CLIENT_NETWORK_ERROR,
|
CLIENT_NETWORK_ERROR,
|
||||||
|
CLIENT_INVALID_PKCS8,
|
||||||
|
CLIENT_FILE_ERROR
|
||||||
};
|
};
|
||||||
|
|
||||||
enum DRM_REMOVAL_ERROR {
|
enum DRM_REMOVAL_ERROR {
|
||||||
@@ -118,7 +123,8 @@ namespace gourou
|
|||||||
DRM_FILE_ERROR,
|
DRM_FILE_ERROR,
|
||||||
DRM_FORMAT_NOT_SUPPORTED,
|
DRM_FORMAT_NOT_SUPPORTED,
|
||||||
DRM_IN_OUT_EQUALS,
|
DRM_IN_OUT_EQUALS,
|
||||||
DRM_MISSING_PARAMETER
|
DRM_MISSING_PARAMETER,
|
||||||
|
DRM_INVALID_KEY_SIZE
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -282,15 +288,33 @@ namespace gourou
|
|||||||
node.append_child(pugi::node_pcdata).set_value(value.c_str());
|
node.append_child(pugi::node_pcdata).set_value(value.c_str());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @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, bool truncate=true)
|
||||||
|
{
|
||||||
|
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);
|
||||||
|
|
||||||
|
return fd;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Write data in a file. If it already exists, it's truncated
|
* @brief Write data in a file. If it already exists, it's truncated
|
||||||
*/
|
*/
|
||||||
static inline void writeFile(std::string path, const unsigned char* data, unsigned int length)
|
static inline void writeFile(std::string path, const unsigned char* data, unsigned int length)
|
||||||
{
|
{
|
||||||
int fd = open(path.c_str(), O_CREAT|O_WRONLY|O_TRUNC, S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP|S_IROTH);
|
int fd = createNewFile(path);
|
||||||
|
|
||||||
if (fd <= 0)
|
|
||||||
EXCEPTION(GOUROU_FILE_ERROR, "Unable to create " << path);
|
|
||||||
|
|
||||||
if (write(fd, data, length) != length)
|
if (write(fd, data, length) != length)
|
||||||
EXCEPTION(GOUROU_FILE_ERROR, "Write error for file " << path);
|
EXCEPTION(GOUROU_FILE_ERROR, "Write error for file " << 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
|
namespace gourou
|
||||||
{
|
{
|
||||||
FulfillmentItem::FulfillmentItem(pugi::xml_document& doc, User* user)
|
FulfillmentItem::FulfillmentItem(pugi::xml_document& doc, User* user)
|
||||||
: fulfillDoc()
|
: fulfillDoc(), loanToken(0)
|
||||||
{
|
{
|
||||||
fulfillDoc.reset(doc); /* We must keep a copy */
|
fulfillDoc.reset(doc); /* We must keep a copy */
|
||||||
metadatas = fulfillDoc.select_node("//metadata").node();
|
metadatas = fulfillDoc.select_node("//metadata").node();
|
||||||
@@ -50,6 +50,23 @@ namespace gourou
|
|||||||
EXCEPTION(FFI_INVALID_FULFILLMENT_DATA, "Any license token in document");
|
EXCEPTION(FFI_INVALID_FULFILLMENT_DATA, "Any license token in document");
|
||||||
|
|
||||||
buildRights(licenseToken, user);
|
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)
|
void FulfillmentItem::buildRights(const pugi::xml_node& licenseToken, User* user)
|
||||||
@@ -103,4 +120,9 @@ namespace gourou
|
|||||||
{
|
{
|
||||||
return resource;
|
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
|
// Compute hash
|
||||||
unsigned char sha_out[SHA1_LEN];
|
unsigned char sha_out[SHA1_LEN];
|
||||||
@@ -260,9 +260,8 @@ namespace gourou
|
|||||||
printf("\n");
|
printf("\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
ByteArray signature(res, sizeof(res));
|
std::string signature = ByteArray(res, sizeof(res)).toBase64();
|
||||||
|
appendTextElem(rootNode, "adept:signature", signature);
|
||||||
return signature.toBase64();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void DRMProcessor::addNonce(pugi::xml_node& root)
|
void DRMProcessor::addNonce(pugi::xml_node& root)
|
||||||
@@ -300,11 +299,13 @@ namespace gourou
|
|||||||
appendTextElem(root, "adept:expiration", buffer);
|
appendTextElem(root, "adept:expiration", buffer);
|
||||||
}
|
}
|
||||||
|
|
||||||
ByteArray DRMProcessor::sendRequest(const std::string& URL, const std::string& POSTdata, const char* contentType, std::map<std::string, std::string>* responseHeaders)
|
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)
|
if (contentType == 0)
|
||||||
contentType = "";
|
contentType = "";
|
||||||
std::string reply = client->sendHTTPRequest(URL, POSTdata, contentType, responseHeaders);
|
std::string reply = client->sendHTTPRequest(URL, POSTdata, contentType, responseHeaders, fd, resume);
|
||||||
|
|
||||||
|
if (fd) return ByteArray();
|
||||||
|
|
||||||
pugi::xml_document replyDoc;
|
pugi::xml_document replyDoc;
|
||||||
replyDoc.load_buffer(reply.c_str(), reply.length());
|
replyDoc.load_buffer(reply.c_str(), reply.length());
|
||||||
@@ -366,8 +367,7 @@ namespace gourou
|
|||||||
addNonce(root);
|
addNonce(root);
|
||||||
appendTextElem(root, "adept:user", user->getUUID());
|
appendTextElem(root, "adept:user", user->getUUID());
|
||||||
|
|
||||||
std::string signature = signNode(root);
|
signNode(root);
|
||||||
appendTextElem(root, "adept:signature", signature);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void DRMProcessor::doOperatorAuth(std::string operatorURL)
|
void DRMProcessor::doOperatorAuth(std::string operatorURL)
|
||||||
@@ -528,13 +528,11 @@ namespace gourou
|
|||||||
|
|
||||||
hmacParentNode.remove_child(hmacNode);
|
hmacParentNode.remove_child(hmacNode);
|
||||||
|
|
||||||
std::string signature = signNode(rootNode);
|
signNode(rootNode);
|
||||||
|
|
||||||
// Add removed HMAC
|
// Add removed HMAC
|
||||||
appendTextElem(hmacParentNode, hmacNode.name(), hmacNode.first_child().value());
|
appendTextElem(hmacParentNode, hmacNode.name(), hmacNode.first_child().value());
|
||||||
|
|
||||||
appendTextElem(rootNode, "adept:signature", signature);
|
|
||||||
|
|
||||||
pugi::xpath_node node = acsmDoc.select_node("//operatorURL");
|
pugi::xpath_node node = acsmDoc.select_node("//operatorURL");
|
||||||
if (!node)
|
if (!node)
|
||||||
EXCEPTION(FF_NO_OPERATOR_URL, "OperatorURL not found in ACSM document");
|
EXCEPTION(FF_NO_OPERATOR_URL, "OperatorURL not found in ACSM document");
|
||||||
@@ -581,7 +579,7 @@ namespace gourou
|
|||||||
return new FulfillmentItem(fulfillReply, user);
|
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;
|
ITEM_TYPE res = EPUB;
|
||||||
|
|
||||||
@@ -590,9 +588,11 @@ namespace gourou
|
|||||||
|
|
||||||
std::map<std::string, std::string> headers;
|
std::map<std::string, std::string> headers;
|
||||||
|
|
||||||
ByteArray replyData = sendRequest(item->getDownloadURL(), "", 0, &headers);
|
int fd = createNewFile(path, !resume);
|
||||||
|
|
||||||
writeFile(path, replyData);
|
sendRequest(item->getDownloadURL(), "", 0, &headers, fd, resume);
|
||||||
|
|
||||||
|
close(fd);
|
||||||
|
|
||||||
GOUROU_LOG(INFO, "Download into " << path);
|
GOUROU_LOG(INFO, "Download into " << path);
|
||||||
|
|
||||||
@@ -667,7 +667,10 @@ namespace gourou
|
|||||||
pugi::xml_node signIn = signInRequest.append_child("adept:signIn");
|
pugi::xml_node signIn = signInRequest.append_child("adept:signIn");
|
||||||
signIn.append_attribute("xmlns:adept") = ADOBE_ADEPT_NS;
|
signIn.append_attribute("xmlns:adept") = ADOBE_ADEPT_NS;
|
||||||
std::string loginMethod = user->getLoginMethod();
|
std::string loginMethod = user->getLoginMethod();
|
||||||
if (loginMethod.size())
|
|
||||||
|
if (adobeID == "anonymous")
|
||||||
|
signIn.append_attribute("method") = "anonymous";
|
||||||
|
else if (loginMethod.size())
|
||||||
signIn.append_attribute("method") = loginMethod.c_str();
|
signIn.append_attribute("method") = loginMethod.c_str();
|
||||||
else
|
else
|
||||||
signIn.append_attribute("method") = "AdobeID";
|
signIn.append_attribute("method") = "AdobeID";
|
||||||
@@ -816,10 +819,7 @@ namespace gourou
|
|||||||
|
|
||||||
pugi::xml_node root = activateReq.select_node("adept:activate").node();
|
pugi::xml_node root = activateReq.select_node("adept:activate").node();
|
||||||
|
|
||||||
std::string signature = signNode(root);
|
signNode(root);
|
||||||
|
|
||||||
root = activateReq.select_node("adept:activate").node();
|
|
||||||
appendTextElem(root, "adept:signature", signature);
|
|
||||||
|
|
||||||
pugi::xml_document activationDoc;
|
pugi::xml_document activationDoc;
|
||||||
user->readActivation(activationDoc);
|
user->readActivation(activationDoc);
|
||||||
@@ -837,6 +837,33 @@ namespace gourou
|
|||||||
user->updateActivationFile(activationDoc);
|
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)
|
ByteArray DRMProcessor::encryptWithDeviceKey(const unsigned char* data, unsigned int len)
|
||||||
{
|
{
|
||||||
const unsigned char* deviceKey = device->getDeviceKey();
|
const unsigned char* deviceKey = device->getDeviceKey();
|
||||||
@@ -927,8 +954,12 @@ namespace gourou
|
|||||||
|
|
||||||
void DRMProcessor::decryptADEPTKey(const std::string& encryptedKey, unsigned char* decryptedKey)
|
void DRMProcessor::decryptADEPTKey(const std::string& encryptedKey, unsigned char* decryptedKey)
|
||||||
{
|
{
|
||||||
|
if (encryptedKey.size() != 172)
|
||||||
|
EXCEPTION(DRM_INVALID_KEY_SIZE, "Invalid encrypted key size (" << encryptedKey.size() << "). DRM version not supported");
|
||||||
|
|
||||||
ByteArray arrayEncryptedKey = ByteArray::fromBase64(encryptedKey);
|
ByteArray arrayEncryptedKey = ByteArray::fromBase64(encryptedKey);
|
||||||
|
|
||||||
|
|
||||||
std::string privateKeyData = user->getPrivateLicenseKey();
|
std::string privateKeyData = user->getPrivateLicenseKey();
|
||||||
ByteArray privateRSAKey = ByteArray::fromBase64(privateKeyData);
|
ByteArray privateRSAKey = ByteArray::fromBase64(privateKeyData);
|
||||||
|
|
||||||
@@ -945,7 +976,8 @@ namespace gourou
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void DRMProcessor::removeEPubDRM(const std::string& filenameIn, const std::string& filenameOut)
|
void DRMProcessor::removeEPubDRM(const std::string& filenameIn, const std::string& filenameOut,
|
||||||
|
const unsigned char* encryptionKey, unsigned encryptionKeySize)
|
||||||
{
|
{
|
||||||
ByteArray zipData;
|
ByteArray zipData;
|
||||||
bool removeEncryptionXML = true;
|
bool removeEncryptionXML = true;
|
||||||
@@ -958,7 +990,16 @@ namespace gourou
|
|||||||
std::string encryptedKey = extractTextElem(rightsDoc, "/adept:rights/licenseToken/encryptedKey");
|
std::string encryptedKey = extractTextElem(rightsDoc, "/adept:rights/licenseToken/encryptedKey");
|
||||||
unsigned char decryptedKey[RSA_KEY_SIZE];
|
unsigned char decryptedKey[RSA_KEY_SIZE];
|
||||||
|
|
||||||
decryptADEPTKey(encryptedKey, decryptedKey);
|
if (!encryptionKey)
|
||||||
|
decryptADEPTKey(encryptedKey, decryptedKey);
|
||||||
|
else
|
||||||
|
{
|
||||||
|
GOUROU_LOG(DEBUG, "Use provided encryption key");
|
||||||
|
if (encryptionKeySize != 16)
|
||||||
|
EXCEPTION(DRM_ERR_ENCRYPTION_KEY, "Provided encryption key must be 16 bytes");
|
||||||
|
|
||||||
|
memcpy(&decryptedKey[sizeof(decryptedKey)-16], encryptionKey, encryptionKeySize);
|
||||||
|
}
|
||||||
|
|
||||||
client->zipReadFile(zipHandler, "META-INF/encryption.xml", zipData);
|
client->zipReadFile(zipHandler, "META-INF/encryption.xml", zipData);
|
||||||
pugi::xml_document encryptionDoc;
|
pugi::xml_document encryptionDoc;
|
||||||
@@ -997,7 +1038,7 @@ namespace gourou
|
|||||||
unsigned int dataOutLength;
|
unsigned int dataOutLength;
|
||||||
|
|
||||||
client->Decrypt(CryptoInterface::ALGO_AES, CryptoInterface::CHAIN_CBC,
|
client->Decrypt(CryptoInterface::ALGO_AES, CryptoInterface::CHAIN_CBC,
|
||||||
decryptedKey+RSA_KEY_SIZE-16, 16, /* Key */
|
decryptedKey+sizeof(decryptedKey)-16, 16, /* Key */
|
||||||
_data, 16, /* IV */
|
_data, 16, /* IV */
|
||||||
&_data[16], zipData.length()-16,
|
&_data[16], zipData.length()-16,
|
||||||
_clearData, &dataOutLength);
|
_clearData, &dataOutLength);
|
||||||
@@ -1006,9 +1047,20 @@ namespace gourou
|
|||||||
_clearData[dataOutLength] = 'Z';
|
_clearData[dataOutLength] = 'Z';
|
||||||
clearData.resize(dataOutLength+1);
|
clearData.resize(dataOutLength+1);
|
||||||
|
|
||||||
client->inflate(clearData, inflateData);
|
try
|
||||||
|
{
|
||||||
client->zipWriteFile(zipHandler, encryptedFile, inflateData);
|
client->inflate(clearData, inflateData);
|
||||||
|
client->zipWriteFile(zipHandler, encryptedFile, inflateData);
|
||||||
|
}
|
||||||
|
catch(gourou::Exception& e)
|
||||||
|
{
|
||||||
|
if (e.getErrorCode() == CLIENT_ZIP_ERROR)
|
||||||
|
{
|
||||||
|
GOUROU_LOG(ERROR, e.what() << std::endl << "Skip file " << encryptedFile);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
|
||||||
it->node().parent().remove_child(it->node());
|
it->node().parent().remove_child(it->node());
|
||||||
}
|
}
|
||||||
@@ -1053,7 +1105,8 @@ namespace gourou
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void DRMProcessor::removePDFDRM(const std::string& filenameIn, const std::string& filenameOut)
|
void DRMProcessor::removePDFDRM(const std::string& filenameIn, const std::string& filenameOut,
|
||||||
|
const unsigned char* encryptionKey, unsigned encryptionKeySize)
|
||||||
{
|
{
|
||||||
uPDFParser::Parser parser;
|
uPDFParser::Parser parser;
|
||||||
bool EBXHandlerFound = false;
|
bool EBXHandlerFound = false;
|
||||||
@@ -1076,16 +1129,18 @@ namespace gourou
|
|||||||
|
|
||||||
uPDFParser::Integer* ebxVersion;
|
uPDFParser::Integer* ebxVersion;
|
||||||
std::vector<uPDFParser::Object*> objects = parser.objects();
|
std::vector<uPDFParser::Object*> objects = parser.objects();
|
||||||
std::vector<uPDFParser::Object*>::reverse_iterator it;
|
std::vector<uPDFParser::Object*>::iterator it;
|
||||||
|
std::vector<uPDFParser::Object*>::reverse_iterator rIt;
|
||||||
unsigned char decryptedKey[RSA_KEY_SIZE];
|
unsigned char decryptedKey[RSA_KEY_SIZE];
|
||||||
|
int ebxId;
|
||||||
|
|
||||||
for(it = objects.rbegin(); it != objects.rend(); it++)
|
for(rIt = objects.rbegin(); rIt != objects.rend(); rIt++)
|
||||||
{
|
{
|
||||||
// Update EBX_HANDLER with rights
|
// Update EBX_HANDLER with rights
|
||||||
if ((*it)->hasKey("Filter") && (**it)["Filter"]->str() == "/EBX_HANDLER")
|
if ((*rIt)->hasKey("Filter") && (**rIt)["Filter"]->str() == "/EBX_HANDLER")
|
||||||
{
|
{
|
||||||
EBXHandlerFound = true;
|
EBXHandlerFound = true;
|
||||||
uPDFParser::Object* ebx = *it;
|
uPDFParser::Object* ebx = *rIt;
|
||||||
|
|
||||||
ebxVersion = (uPDFParser::Integer*)(*ebx)["V"];
|
ebxVersion = (uPDFParser::Integer*)(*ebx)["V"];
|
||||||
if (ebxVersion->value() != 4)
|
if (ebxVersion->value() != 4)
|
||||||
@@ -1100,7 +1155,15 @@ namespace gourou
|
|||||||
|
|
||||||
uPDFParser::String* licenseObject = (uPDFParser::String*)(*ebx)["ADEPT_LICENSE"];
|
uPDFParser::String* licenseObject = (uPDFParser::String*)(*ebx)["ADEPT_LICENSE"];
|
||||||
|
|
||||||
ByteArray zippedData = ByteArray::fromBase64(licenseObject->value());
|
std::string value = licenseObject->value();
|
||||||
|
// Pad with '='
|
||||||
|
while ((value.size() % 4))
|
||||||
|
value += "=";
|
||||||
|
ByteArray zippedData = ByteArray::fromBase64(value);
|
||||||
|
|
||||||
|
if (zippedData.size() == 0)
|
||||||
|
EXCEPTION(DRM_ERR_ENCRYPTION_KEY, "Invalid ADEPT_LICENSE");
|
||||||
|
|
||||||
ByteArray rightsStr;
|
ByteArray rightsStr;
|
||||||
client->inflate(zippedData, rightsStr);
|
client->inflate(zippedData, rightsStr);
|
||||||
|
|
||||||
@@ -1109,7 +1172,19 @@ namespace gourou
|
|||||||
|
|
||||||
std::string encryptedKey = extractTextElem(rightsDoc, "/adept:rights/licenseToken/encryptedKey");
|
std::string encryptedKey = extractTextElem(rightsDoc, "/adept:rights/licenseToken/encryptedKey");
|
||||||
|
|
||||||
decryptADEPTKey(encryptedKey, decryptedKey);
|
if (!encryptionKey)
|
||||||
|
decryptADEPTKey(encryptedKey, decryptedKey);
|
||||||
|
else
|
||||||
|
{
|
||||||
|
GOUROU_LOG(DEBUG, "Use provided encryption key");
|
||||||
|
if (encryptionKeySize != 16)
|
||||||
|
EXCEPTION(DRM_ERR_ENCRYPTION_KEY, "Provided encryption key must be 16 bytes");
|
||||||
|
|
||||||
|
memcpy(&decryptedKey[sizeof(decryptedKey)-16], encryptionKey, encryptionKeySize);
|
||||||
|
}
|
||||||
|
|
||||||
|
ebxId = ebx->objectId();
|
||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1119,28 +1194,29 @@ namespace gourou
|
|||||||
EXCEPTION(DRM_ERR_ENCRYPTION_KEY, "EBX_HANDLER not found");
|
EXCEPTION(DRM_ERR_ENCRYPTION_KEY, "EBX_HANDLER not found");
|
||||||
}
|
}
|
||||||
|
|
||||||
std::vector<uPDFParser::XRefValue> xrefTable = parser.xrefTable();
|
for(it = objects.begin(); it != objects.end(); it++)
|
||||||
std::vector<uPDFParser::XRefValue>::iterator xrefIt;
|
|
||||||
|
|
||||||
for(xrefIt = xrefTable.begin(); xrefIt != xrefTable.end(); xrefIt++)
|
|
||||||
{
|
{
|
||||||
GOUROU_LOG(DEBUG, "XREF obj " << (*xrefIt).objectId() << " used " << (*xrefIt).used());
|
uPDFParser::Object* object = *it;
|
||||||
|
|
||||||
if (!(*xrefIt).used())
|
if (object->objectId() == ebxId)
|
||||||
continue;
|
|
||||||
|
|
||||||
uPDFParser::Object* object = (*xrefIt).object();
|
|
||||||
|
|
||||||
if (!object)
|
|
||||||
{
|
{
|
||||||
GOUROU_LOG(DEBUG, "No object");
|
// object->deleteKey("Filter");
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Should not decrypt XRef stream
|
||||||
|
if (object->hasKey("Type") && (*object)["Type"]->str() == "/XRef")
|
||||||
|
{
|
||||||
|
GOUROU_LOG(DEBUG, "XRef stream at " << object->offset());
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
GOUROU_LOG(DEBUG, "Obj " << object->objectId());
|
||||||
|
|
||||||
unsigned char tmpKey[16];
|
unsigned char tmpKey[16];
|
||||||
|
|
||||||
generatePDFObjectKey(ebxVersion->value(),
|
generatePDFObjectKey(ebxVersion->value(),
|
||||||
decryptedKey+RSA_KEY_SIZE-16, 16,
|
decryptedKey+sizeof(decryptedKey)-16, 16,
|
||||||
object->objectId(), object->generationNumber(),
|
object->objectId(), object->generationNumber(),
|
||||||
tmpKey);
|
tmpKey);
|
||||||
|
|
||||||
@@ -1182,7 +1258,7 @@ namespace gourou
|
|||||||
dictionary.replace(dictIt->first, dictIt->second);
|
dictionary.replace(dictIt->first, dictIt->second);
|
||||||
|
|
||||||
std::vector<uPDFParser::DataType*>::iterator datasIt;
|
std::vector<uPDFParser::DataType*>::iterator datasIt;
|
||||||
std::vector<uPDFParser::DataType*>& datas = (*xrefIt).object()->data();
|
std::vector<uPDFParser::DataType*>& datas = object->data();
|
||||||
uPDFParser::Stream* stream;
|
uPDFParser::Stream* stream;
|
||||||
|
|
||||||
for (datasIt = datas.begin(); datasIt != datas.end(); datasIt++)
|
for (datasIt = datas.begin(); datasIt != datas.end(); datasIt++)
|
||||||
@@ -1190,14 +1266,14 @@ namespace gourou
|
|||||||
if ((*datasIt)->type() != uPDFParser::DataType::STREAM)
|
if ((*datasIt)->type() != uPDFParser::DataType::STREAM)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
GOUROU_LOG(DEBUG, "Decrypt stream id " << object->objectId());
|
|
||||||
|
|
||||||
stream = (uPDFParser::Stream*) (*datasIt);
|
stream = (uPDFParser::Stream*) (*datasIt);
|
||||||
unsigned char* encryptedData = stream->data();
|
unsigned char* encryptedData = stream->data();
|
||||||
unsigned int dataLength = stream->dataLength();
|
unsigned int dataLength = stream->dataLength();
|
||||||
unsigned char* clearData = new unsigned char[dataLength];
|
unsigned char* clearData = new unsigned char[dataLength];
|
||||||
unsigned int dataOutLength;
|
unsigned int dataOutLength;
|
||||||
|
|
||||||
|
GOUROU_LOG(DEBUG, "Decrypt stream id " << object->objectId() << ", size " << stream->dataLength());
|
||||||
|
|
||||||
client->Decrypt(CryptoInterface::ALGO_RC4, CryptoInterface::CHAIN_ECB,
|
client->Decrypt(CryptoInterface::ALGO_RC4, CryptoInterface::CHAIN_ECB,
|
||||||
tmpKey, 16, /* Key */
|
tmpKey, 16, /* Key */
|
||||||
NULL, 0, /* IV */
|
NULL, 0, /* IV */
|
||||||
@@ -1205,6 +1281,8 @@ namespace gourou
|
|||||||
clearData, &dataOutLength);
|
clearData, &dataOutLength);
|
||||||
|
|
||||||
stream->setData(clearData, dataOutLength, true);
|
stream->setData(clearData, dataOutLength, true);
|
||||||
|
if (dataOutLength != dataLength)
|
||||||
|
GOUROU_LOG(DEBUG, "New size " << dataOutLength);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1215,11 +1293,11 @@ namespace gourou
|
|||||||
}
|
}
|
||||||
|
|
||||||
void DRMProcessor::removeDRM(const std::string& filenameIn, const std::string& filenameOut,
|
void DRMProcessor::removeDRM(const std::string& filenameIn, const std::string& filenameOut,
|
||||||
ITEM_TYPE type)
|
ITEM_TYPE type, const unsigned char* encryptionKey, unsigned encryptionKeySize)
|
||||||
{
|
{
|
||||||
if (type == PDF)
|
if (type == PDF)
|
||||||
removePDFDRM(filenameIn, filenameOut);
|
removePDFDRM(filenameIn, filenameOut, encryptionKey, encryptionKeySize);
|
||||||
else
|
else
|
||||||
removeEPubDRM(filenameIn, filenameOut);
|
removeEPubDRM(filenameIn, filenameOut, encryptionKey, encryptionKeySize);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
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,10 +1,10 @@
|
|||||||
|
|
||||||
TARGETS=acsmdownloader adept_activate adept_remove
|
TARGETS=acsmdownloader adept_activate adept_remove adept_loan_mgt
|
||||||
|
|
||||||
CXXFLAGS=-Wall `pkg-config --cflags Qt5Core Qt5Network` -fPIC -I$(ROOT)/include -I$(ROOT)/lib/pugixml/src/
|
CXXFLAGS=-Wall -fPIC -I$(ROOT)/include -I$(ROOT)/lib/pugixml/src/
|
||||||
|
|
||||||
STATIC_DEP=
|
STATIC_DEP=
|
||||||
LDFLAGS=`pkg-config --libs Qt5Core Qt5Network` -L$(ROOT) -lcrypto -lzip -lz
|
LDFLAGS=-L$(ROOT) -lcrypto -lzip -lz -lcurl
|
||||||
|
|
||||||
ifneq ($(STATIC_UTILS),)
|
ifneq ($(STATIC_UTILS),)
|
||||||
STATIC_DEP = $(ROOT)/libgourou.a
|
STATIC_DEP = $(ROOT)/libgourou.a
|
||||||
@@ -18,17 +18,26 @@ else
|
|||||||
CXXFLAGS += -O2
|
CXXFLAGS += -O2
|
||||||
endif
|
endif
|
||||||
|
|
||||||
COMMON_DEPS = drmprocessorclientimpl.cpp $(STATIC_DEP)
|
COMMON_DEPS = drmprocessorclientimpl.cpp utils_common.cpp
|
||||||
|
COMMON_OBJECTS = $(COMMON_DEPS:.cpp=.o)
|
||||||
|
COMMON_LIB = utils.a
|
||||||
|
|
||||||
all: $(TARGETS)
|
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 $@
|
$(CXX) $(CXXFLAGS) $^ $(LDFLAGS) -o $@
|
||||||
|
|
||||||
adept_activate: adept_activate.cpp $(COMMON_DEPS)
|
adept_activate: adept_activate.cpp ${COMMON_LIB}
|
||||||
$(CXX) $(CXXFLAGS) $^ $(LDFLAGS) -o $@
|
$(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 $@
|
$(CXX) $(CXXFLAGS) $^ $(LDFLAGS) -o $@
|
||||||
|
|
||||||
clean:
|
clean:
|
||||||
|
|||||||
@@ -26,21 +26,17 @@
|
|||||||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include <unistd.h>
|
|
||||||
#include <getopt.h>
|
#include <getopt.h>
|
||||||
|
#include <libgen.h>
|
||||||
|
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
|
#include <algorithm>
|
||||||
#include <QFile>
|
|
||||||
#include <QDir>
|
|
||||||
#include <QCoreApplication>
|
|
||||||
#include <QRunnable>
|
|
||||||
#include <QThreadPool>
|
|
||||||
|
|
||||||
#include <libgourou.h>
|
#include <libgourou.h>
|
||||||
#include "drmprocessorclientimpl.h"
|
#include <libgourou_common.h>
|
||||||
|
|
||||||
#define ARRAY_SIZE(arr) (sizeof(arr)/sizeof(arr[0]))
|
#include "drmprocessorclientimpl.h"
|
||||||
|
#include "utils_common.h"
|
||||||
|
|
||||||
static const char* deviceFile = "device.xml";
|
static const char* deviceFile = "device.xml";
|
||||||
static const char* activationFile = "activation.xml";
|
static const char* activationFile = "activation.xml";
|
||||||
@@ -49,28 +45,18 @@ static const char* acsmFile = 0;
|
|||||||
static bool exportPrivateKey = false;
|
static bool exportPrivateKey = false;
|
||||||
static const char* outputFile = 0;
|
static const char* outputFile = 0;
|
||||||
static const char* outputDir = 0;
|
static const char* outputDir = 0;
|
||||||
static const char* defaultDirs[] = {
|
static bool resume = false;
|
||||||
".adept/",
|
|
||||||
"./adobe-digital-editions/",
|
|
||||||
"./.adobe-digital-editions/"
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
class ACSMDownloader: public QRunnable
|
class ACSMDownloader
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
ACSMDownloader(QCoreApplication* app):
|
|
||||||
app(app)
|
|
||||||
{
|
|
||||||
setAutoDelete(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
void run()
|
int run()
|
||||||
{
|
{
|
||||||
int ret = 0;
|
int ret = 0;
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
DRMProcessorClientImpl client;
|
|
||||||
gourou::DRMProcessor processor(&client, deviceFile, activationFile, devicekeyFile);
|
gourou::DRMProcessor processor(&client, deviceFile, activationFile, devicekeyFile);
|
||||||
gourou::User* user = processor.getUser();
|
gourou::User* user = processor.getUser();
|
||||||
|
|
||||||
@@ -84,9 +70,8 @@ public:
|
|||||||
|
|
||||||
if (outputDir)
|
if (outputDir)
|
||||||
{
|
{
|
||||||
QDir dir(outputDir);
|
if (!fileExists(outputDir))
|
||||||
if (!dir.exists(outputDir))
|
mkpath(outputDir);
|
||||||
dir.mkpath(outputDir);
|
|
||||||
|
|
||||||
filename = std::string(outputDir) + "/" + filename;
|
filename = std::string(outputDir) + "/" + filename;
|
||||||
}
|
}
|
||||||
@@ -116,14 +101,13 @@ public:
|
|||||||
|
|
||||||
if (outputDir)
|
if (outputDir)
|
||||||
{
|
{
|
||||||
QDir dir(outputDir);
|
if (!fileExists(outputDir))
|
||||||
if (!dir.exists(outputDir))
|
mkpath(outputDir);
|
||||||
dir.mkpath(outputDir);
|
|
||||||
|
|
||||||
filename = std::string(outputDir) + "/" + filename;
|
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)
|
if (!outputFile)
|
||||||
{
|
{
|
||||||
@@ -132,11 +116,12 @@ public:
|
|||||||
finalName += ".pdf";
|
finalName += ".pdf";
|
||||||
else
|
else
|
||||||
finalName += ".epub";
|
finalName += ".epub";
|
||||||
QDir dir;
|
rename(filename.c_str(), finalName.c_str());
|
||||||
dir.rename(filename.c_str(), finalName.c_str());
|
|
||||||
filename = finalName;
|
filename = finalName;
|
||||||
}
|
}
|
||||||
std::cout << "Created " << filename << std::endl;
|
std::cout << "Created " << filename << std::endl;
|
||||||
|
|
||||||
|
serializeLoanToken(item);
|
||||||
}
|
}
|
||||||
} catch(std::exception& e)
|
} catch(std::exception& e)
|
||||||
{
|
{
|
||||||
@@ -144,43 +129,62 @@ public:
|
|||||||
ret = 1;
|
ret = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
this->app->exit(ret);
|
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:
|
private:
|
||||||
QCoreApplication* app;
|
DRMProcessorClientImpl client;
|
||||||
};
|
};
|
||||||
|
|
||||||
static const char* findFile(const char* filename, bool inDefaultDirs=true)
|
|
||||||
{
|
|
||||||
QFile file(filename);
|
|
||||||
|
|
||||||
if (file.exists())
|
|
||||||
return strdup(filename);
|
|
||||||
|
|
||||||
if (!inDefaultDirs) return 0;
|
|
||||||
|
|
||||||
for (int i=0; i<(int)ARRAY_SIZE(defaultDirs); i++)
|
|
||||||
{
|
|
||||||
QString path = QString(defaultDirs[i]) + QString(filename);
|
|
||||||
file.setFileName(path);
|
|
||||||
if (file.exists())
|
|
||||||
return strdup(path.toStdString().c_str());
|
|
||||||
}
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void version(void)
|
|
||||||
{
|
|
||||||
std::cout << "Current libgourou version : " << gourou::DRMProcessor::VERSION << std::endl ;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void usage(const char* cmd)
|
static void usage(const char* cmd)
|
||||||
{
|
{
|
||||||
std::cout << "Download EPUB file from ACSM request file" << std::endl;
|
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 << " " << "-d|--device-file" << "\t" << "device.xml file from eReader" << std::endl;
|
||||||
std::cout << " " << "-a|--activation-file" << "\t" << "activation.xml file from eReader" << std::endl;
|
std::cout << " " << "-a|--activation-file" << "\t" << "activation.xml file from eReader" << std::endl;
|
||||||
@@ -189,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 << " " << "-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 << " " << "-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 << " " << "-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|--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 << " " << "-V|--version" << "\t\t" << "Display libgourou version" << std::endl;
|
||||||
std::cout << " " << "-h|--help" << "\t\t" << "This help" << std::endl;
|
std::cout << " " << "-h|--help" << "\t\t" << "This help" << std::endl;
|
||||||
@@ -218,13 +223,14 @@ int main(int argc, char** argv)
|
|||||||
{"output-file", required_argument, 0, 'o' },
|
{"output-file", required_argument, 0, 'o' },
|
||||||
{"acsm-file", required_argument, 0, 'f' },
|
{"acsm-file", required_argument, 0, 'f' },
|
||||||
{"export-private-key",no_argument, 0, 'e' },
|
{"export-private-key",no_argument, 0, 'e' },
|
||||||
|
{"resume", no_argument, 0, 'r' },
|
||||||
{"verbose", no_argument, 0, 'v' },
|
{"verbose", no_argument, 0, 'v' },
|
||||||
{"version", no_argument, 0, 'V' },
|
{"version", no_argument, 0, 'V' },
|
||||||
{"help", no_argument, 0, 'h' },
|
{"help", no_argument, 0, 'h' },
|
||||||
{0, 0, 0, 0 }
|
{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);
|
long_options, &option_index);
|
||||||
if (c == -1)
|
if (c == -1)
|
||||||
break;
|
break;
|
||||||
@@ -251,6 +257,9 @@ int main(int argc, char** argv)
|
|||||||
case 'e':
|
case 'e':
|
||||||
exportPrivateKey = true;
|
exportPrivateKey = true;
|
||||||
break;
|
break;
|
||||||
|
case 'r':
|
||||||
|
resume = true;
|
||||||
|
break;
|
||||||
case 'v':
|
case 'v':
|
||||||
verbose++;
|
verbose++;
|
||||||
break;
|
break;
|
||||||
@@ -275,8 +284,7 @@ int main(int argc, char** argv)
|
|||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
QCoreApplication app(argc, argv);
|
ACSMDownloader downloader;
|
||||||
ACSMDownloader downloader(&app);
|
|
||||||
|
|
||||||
int i;
|
int i;
|
||||||
bool hasErrors = false;
|
bool hasErrors = false;
|
||||||
@@ -306,8 +314,7 @@ int main(int argc, char** argv)
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
QFile file(acsmFile);
|
if (!fileExists(acsmFile))
|
||||||
if (!file.exists())
|
|
||||||
{
|
{
|
||||||
std::cout << "Error : " << acsmFile << " doesn't exists" << std::endl;
|
std::cout << "Error : " << acsmFile << " doesn't exists" << std::endl;
|
||||||
ret = -1;
|
ret = -1;
|
||||||
@@ -315,9 +322,7 @@ int main(int argc, char** argv)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
QThreadPool::globalInstance()->start(&downloader);
|
ret = downloader.run();
|
||||||
|
|
||||||
ret = app.exec();
|
|
||||||
|
|
||||||
end:
|
end:
|
||||||
for (i=0; i<(int)ARRAY_SIZE(files); i++)
|
for (i=0; i<(int)ARRAY_SIZE(files); i++)
|
||||||
|
|||||||
@@ -28,22 +28,16 @@
|
|||||||
|
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
#include <getopt.h>
|
#include <getopt.h>
|
||||||
#include <stdlib.h>
|
|
||||||
#include <termios.h>
|
#include <termios.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <limits.h>
|
||||||
|
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
#include <ostream>
|
#include <ostream>
|
||||||
|
|
||||||
#include <QFile>
|
|
||||||
#include <QDir>
|
|
||||||
#include <QCoreApplication>
|
|
||||||
#include <QRunnable>
|
|
||||||
#include <QThreadPool>
|
|
||||||
|
|
||||||
#include <libgourou.h>
|
#include <libgourou.h>
|
||||||
#include "drmprocessorclientimpl.h"
|
#include "drmprocessorclientimpl.h"
|
||||||
|
#include "utils_common.h"
|
||||||
#define ARRAY_SIZE(arr) (sizeof(arr)/sizeof(arr[0]))
|
|
||||||
|
|
||||||
static const char* username = 0;
|
static const char* username = 0;
|
||||||
static const char* password = 0;
|
static const char* password = 0;
|
||||||
@@ -100,16 +94,11 @@ static std::string getpass(const char *prompt, bool show_asterisk=false)
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
class ADEPTActivate: public QRunnable
|
class ADEPTActivate
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
ADEPTActivate(QCoreApplication* app):
|
|
||||||
app(app)
|
|
||||||
{
|
|
||||||
setAutoDelete(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
void run()
|
int run()
|
||||||
{
|
{
|
||||||
int ret = 0;
|
int ret = 0;
|
||||||
try
|
try
|
||||||
@@ -128,17 +117,10 @@ public:
|
|||||||
ret = 1;
|
ret = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
this->app->exit(ret);
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
|
||||||
QCoreApplication* app;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
static void version(void)
|
|
||||||
{
|
|
||||||
std::cout << "Current libgourou version : " << gourou::DRMProcessor::VERSION << std::endl ;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void usage(const char* cmd)
|
static void usage(const char* cmd)
|
||||||
{
|
{
|
||||||
@@ -162,8 +144,8 @@ static void usage(const char* cmd)
|
|||||||
static const char* abspath(const char* filename)
|
static const char* abspath(const char* filename)
|
||||||
{
|
{
|
||||||
const char* root = getcwd(0, PATH_MAX);
|
const char* root = getcwd(0, PATH_MAX);
|
||||||
QString fullPath = QString(root) + QString("/") + QString(filename);
|
std::string fullPath = std::string(root) + std::string("/") + filename;
|
||||||
const char* res = strdup(fullPath.toStdString().c_str());
|
const char* res = strdup(fullPath.c_str());
|
||||||
|
|
||||||
free((void*)root);
|
free((void*)root);
|
||||||
|
|
||||||
@@ -255,9 +237,8 @@ int main(int argc, char** argv)
|
|||||||
// Relative path
|
// Relative path
|
||||||
if (_outputDir[0] == '.' || _outputDir[0] != '/')
|
if (_outputDir[0] == '.' || _outputDir[0] != '/')
|
||||||
{
|
{
|
||||||
QFile file(_outputDir);
|
|
||||||
// realpath doesn't works if file/dir doesn't exists
|
// realpath doesn't works if file/dir doesn't exists
|
||||||
if (file.exists())
|
if (fileExists(_outputDir))
|
||||||
outputDir = strdup(realpath(_outputDir, 0));
|
outputDir = strdup(realpath(_outputDir, 0));
|
||||||
else
|
else
|
||||||
outputDir = strdup(abspath(_outputDir));
|
outputDir = strdup(abspath(_outputDir));
|
||||||
@@ -266,10 +247,8 @@ int main(int argc, char** argv)
|
|||||||
outputDir = strdup(_outputDir);
|
outputDir = strdup(_outputDir);
|
||||||
}
|
}
|
||||||
|
|
||||||
QCoreApplication app(argc, argv);
|
std::string pass;
|
||||||
|
if (fileExists(outputDir))
|
||||||
QFile file(outputDir);
|
|
||||||
if (file.exists())
|
|
||||||
{
|
{
|
||||||
int key;
|
int key;
|
||||||
|
|
||||||
@@ -282,27 +261,26 @@ int main(int argc, char** argv)
|
|||||||
goto end;
|
goto end;
|
||||||
if (key == 'y' || key == 'Y')
|
if (key == 'y' || key == 'Y')
|
||||||
break;
|
break;
|
||||||
// Clean STDIN buf
|
|
||||||
while ((key = getchar()) != '\n')
|
|
||||||
;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Clean STDIN buf
|
||||||
|
while ((key = getchar()) != '\n')
|
||||||
|
;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!password)
|
if (!password)
|
||||||
{
|
{
|
||||||
char prompt[128];
|
char prompt[128];
|
||||||
std::snprintf(prompt, sizeof(prompt), "Enter password for <%s> : ", username);
|
std::snprintf(prompt, sizeof(prompt), "Enter password for <%s> : ", username);
|
||||||
std::string pass = getpass((const char*)prompt, false);
|
pass = getpass((const char*)prompt, false);
|
||||||
password = pass.c_str();
|
password = pass.c_str();
|
||||||
}
|
}
|
||||||
|
|
||||||
ADEPTActivate activate(&app);
|
ADEPTActivate activate;
|
||||||
QThreadPool::globalInstance()->start(&activate);
|
|
||||||
|
|
||||||
ret = app.exec();
|
ret = activate.run();
|
||||||
|
|
||||||
end:
|
end:
|
||||||
|
|
||||||
free((void*)outputDir);
|
free((void*)outputDir);
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|||||||
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;
|
||||||
|
}
|
||||||
@@ -26,23 +26,15 @@
|
|||||||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include <unistd.h>
|
|
||||||
#include <getopt.h>
|
#include <getopt.h>
|
||||||
|
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
|
|
||||||
#include <QFile>
|
|
||||||
#include <QDir>
|
|
||||||
#include <QCoreApplication>
|
|
||||||
#include <QRunnable>
|
|
||||||
#include <QThreadPool>
|
|
||||||
#include <QTemporaryFile>
|
|
||||||
|
|
||||||
#include <libgourou.h>
|
#include <libgourou.h>
|
||||||
#include <libgourou_common.h>
|
#include <libgourou_common.h>
|
||||||
#include "drmprocessorclientimpl.h"
|
|
||||||
|
|
||||||
#define ARRAY_SIZE(arr) (sizeof(arr)/sizeof(arr[0]))
|
#include "drmprocessorclientimpl.h"
|
||||||
|
#include "utils_common.h"
|
||||||
|
|
||||||
static const char* deviceFile = "device.xml";
|
static const char* deviceFile = "device.xml";
|
||||||
static const char* activationFile = "activation.xml";
|
static const char* activationFile = "activation.xml";
|
||||||
@@ -50,27 +42,35 @@ static const char* devicekeyFile = "devicesalt";
|
|||||||
static const char* inputFile = 0;
|
static const char* inputFile = 0;
|
||||||
static const char* outputFile = 0;
|
static const char* outputFile = 0;
|
||||||
static const char* outputDir = 0;
|
static const char* outputDir = 0;
|
||||||
static const char* defaultDirs[] = {
|
|
||||||
".adept/",
|
static char* encryptionKeyUser = 0;
|
||||||
"./adobe-digital-editions/",
|
static unsigned char* encryptionKey = 0;
|
||||||
"./.adobe-digital-editions/"
|
static unsigned encryptionKeySize = 0;
|
||||||
};
|
|
||||||
|
static inline unsigned char htoi(unsigned char c)
|
||||||
|
{
|
||||||
|
if (c >= '0' && c <= '9')
|
||||||
|
c -= '0';
|
||||||
|
else if (c >= 'a' && c <= 'f')
|
||||||
|
c -= 'a' - 10;
|
||||||
|
else if (c >= 'A' && c <= 'F')
|
||||||
|
c -= 'A' - 10;
|
||||||
|
else
|
||||||
|
EXCEPTION(gourou::USER_INVALID_INPUT, "Invalid character " << c << " in encryption key");
|
||||||
|
|
||||||
|
return c;
|
||||||
|
}
|
||||||
|
|
||||||
static inline bool endsWith(const std::string& s, const std::string& suffix)
|
static inline bool endsWith(const std::string& s, const std::string& suffix)
|
||||||
{
|
{
|
||||||
return s.rfind(suffix) == std::abs((int)(s.size()-suffix.size()));
|
return s.rfind(suffix) == std::abs((int)(s.size()-suffix.size()));
|
||||||
}
|
}
|
||||||
|
|
||||||
class ADEPTRemove: public QRunnable
|
class ADEPTRemove
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
ADEPTRemove(QCoreApplication* app):
|
|
||||||
app(app)
|
|
||||||
{
|
|
||||||
setAutoDelete(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
void run()
|
int run()
|
||||||
{
|
{
|
||||||
int ret = 0;
|
int ret = 0;
|
||||||
try
|
try
|
||||||
@@ -87,9 +87,8 @@ public:
|
|||||||
|
|
||||||
if (outputDir)
|
if (outputDir)
|
||||||
{
|
{
|
||||||
QDir dir(outputDir);
|
if (!fileExists(outputDir))
|
||||||
if (!dir.exists(outputDir))
|
mkpath(outputDir);
|
||||||
dir.mkpath(outputDir);
|
|
||||||
|
|
||||||
filename = std::string(outputDir) + "/" + filename;
|
filename = std::string(outputDir) + "/" + filename;
|
||||||
}
|
}
|
||||||
@@ -105,12 +104,9 @@ public:
|
|||||||
|
|
||||||
if (inputFile != filename)
|
if (inputFile != filename)
|
||||||
{
|
{
|
||||||
QFile::remove(filename.c_str());
|
unlink(filename.c_str());
|
||||||
if (!QFile::copy(inputFile, filename.c_str()))
|
fileCopy(inputFile, filename.c_str());
|
||||||
{
|
processor.removeDRM(inputFile, filename, type, encryptionKey, encryptionKeySize);
|
||||||
EXCEPTION(gourou::DRM_FILE_ERROR, "Unable to copy " << inputFile << " into " << filename);
|
|
||||||
}
|
|
||||||
processor.removeDRM(inputFile, filename, type);
|
|
||||||
std::cout << "DRM removed into new file " << filename << std::endl;
|
std::cout << "DRM removed into new file " << filename << std::endl;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@@ -118,21 +114,19 @@ public:
|
|||||||
// Use temp file for PDF
|
// Use temp file for PDF
|
||||||
if (type == gourou::DRMProcessor::ITEM_TYPE::PDF)
|
if (type == gourou::DRMProcessor::ITEM_TYPE::PDF)
|
||||||
{
|
{
|
||||||
QTemporaryFile tempFile;
|
char* tempFile = tempnam("/tmp", NULL);
|
||||||
tempFile.open();
|
processor.removeDRM(inputFile, tempFile, type, encryptionKey, encryptionKeySize);
|
||||||
tempFile.setAutoRemove(false); // In case of failure
|
|
||||||
processor.removeDRM(inputFile, tempFile.fileName().toStdString(), type);
|
|
||||||
/* Original file must be removed before doing a copy... */
|
/* Original file must be removed before doing a copy... */
|
||||||
QFile origFile(inputFile);
|
unlink(inputFile);
|
||||||
origFile.remove();
|
if (!rename(tempFile, filename.c_str()))
|
||||||
if (!QFile::copy(tempFile.fileName(), filename.c_str()))
|
|
||||||
{
|
{
|
||||||
EXCEPTION(gourou::DRM_FILE_ERROR, "Unable to copy " << tempFile.fileName().toStdString() << " into " << filename);
|
free(tempFile);
|
||||||
|
EXCEPTION(gourou::DRM_FILE_ERROR, "Unable to copy " << tempFile << " into " << filename);
|
||||||
}
|
}
|
||||||
tempFile.setAutoRemove(true);
|
free(tempFile);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
processor.removeDRM(inputFile, filename, type);
|
processor.removeDRM(inputFile, filename, type, encryptionKey, encryptionKeySize);
|
||||||
std::cout << "DRM removed from " << filename << std::endl;
|
std::cout << "DRM removed from " << filename << std::endl;
|
||||||
}
|
}
|
||||||
} catch(std::exception& e)
|
} catch(std::exception& e)
|
||||||
@@ -141,38 +135,10 @@ public:
|
|||||||
ret = 1;
|
ret = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
this->app->exit(ret);
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
|
||||||
QCoreApplication* app;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
static const char* findFile(const char* filename, bool inDefaultDirs=true)
|
|
||||||
{
|
|
||||||
QFile file(filename);
|
|
||||||
|
|
||||||
if (file.exists())
|
|
||||||
return strdup(filename);
|
|
||||||
|
|
||||||
if (!inDefaultDirs) return 0;
|
|
||||||
|
|
||||||
for (int i=0; i<(int)ARRAY_SIZE(defaultDirs); i++)
|
|
||||||
{
|
|
||||||
QString path = QString(defaultDirs[i]) + QString(filename);
|
|
||||||
file.setFileName(path);
|
|
||||||
if (file.exists())
|
|
||||||
return strdup(path.toStdString().c_str());
|
|
||||||
}
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void version(void)
|
|
||||||
{
|
|
||||||
std::cout << "Current libgourou version : " << gourou::DRMProcessor::VERSION << std::endl ;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void usage(const char* cmd)
|
static void usage(const char* cmd)
|
||||||
{
|
{
|
||||||
std::cout << "Remove ADEPT DRM (from Adobe) of EPUB/PDF file" << std::endl;
|
std::cout << "Remove ADEPT DRM (from Adobe) of EPUB/PDF file" << std::endl;
|
||||||
@@ -213,14 +179,14 @@ int main(int argc, char** argv)
|
|||||||
{"output-dir", required_argument, 0, 'O' },
|
{"output-dir", required_argument, 0, 'O' },
|
||||||
{"output-file", required_argument, 0, 'o' },
|
{"output-file", required_argument, 0, 'o' },
|
||||||
{"input-file", required_argument, 0, 'f' },
|
{"input-file", required_argument, 0, 'f' },
|
||||||
{"export-private-key",no_argument, 0, 'e' },
|
{"encryption-key", required_argument, 0, 'K' }, // Private option
|
||||||
{"verbose", no_argument, 0, 'v' },
|
{"verbose", no_argument, 0, 'v' },
|
||||||
{"version", no_argument, 0, 'V' },
|
{"version", no_argument, 0, 'V' },
|
||||||
{"help", no_argument, 0, 'h' },
|
{"help", no_argument, 0, 'h' },
|
||||||
{0, 0, 0, 0 }
|
{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:K:vVh",
|
||||||
long_options, &option_index);
|
long_options, &option_index);
|
||||||
if (c == -1)
|
if (c == -1)
|
||||||
break;
|
break;
|
||||||
@@ -244,6 +210,9 @@ int main(int argc, char** argv)
|
|||||||
case 'o':
|
case 'o':
|
||||||
outputFile = optarg;
|
outputFile = optarg;
|
||||||
break;
|
break;
|
||||||
|
case 'K':
|
||||||
|
encryptionKeyUser = optarg;
|
||||||
|
break;
|
||||||
case 'v':
|
case 'v':
|
||||||
verbose++;
|
verbose++;
|
||||||
break;
|
break;
|
||||||
@@ -268,8 +237,7 @@ int main(int argc, char** argv)
|
|||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
QCoreApplication app(argc, argv);
|
ADEPTRemove remover;
|
||||||
ADEPTRemove remover(&app);
|
|
||||||
|
|
||||||
int i;
|
int i;
|
||||||
bool hasErrors = false;
|
bool hasErrors = false;
|
||||||
@@ -286,12 +254,36 @@ int main(int argc, char** argv)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (encryptionKeyUser)
|
||||||
|
{
|
||||||
|
int size = std::string(encryptionKeyUser).size();
|
||||||
|
if ((size % 2))
|
||||||
|
{
|
||||||
|
std::cout << "Error : Encryption key must be odd length" << std::endl;
|
||||||
|
goto end;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (encryptionKeyUser[0] == '0' && encryptionKeyUser[1] == 'x')
|
||||||
|
{
|
||||||
|
encryptionKeyUser += 2;
|
||||||
|
size -= 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
encryptionKey = new unsigned char[size/2];
|
||||||
|
|
||||||
|
for(i=0; i<size; i+=2)
|
||||||
|
{
|
||||||
|
encryptionKey[i/2] = htoi(encryptionKeyUser[i]) << 4;
|
||||||
|
encryptionKey[i/2] |= htoi(encryptionKeyUser[i+1]);
|
||||||
|
}
|
||||||
|
|
||||||
|
encryptionKeySize = size/2;
|
||||||
|
}
|
||||||
|
|
||||||
if (hasErrors)
|
if (hasErrors)
|
||||||
goto end;
|
goto end;
|
||||||
|
|
||||||
QThreadPool::globalInstance()->start(&remover);
|
ret = remover.run();
|
||||||
|
|
||||||
ret = app.exec();
|
|
||||||
|
|
||||||
end:
|
end:
|
||||||
for (i=0; i<(int)ARRAY_SIZE(files); i++)
|
for (i=0; i<(int)ARRAY_SIZE(files); i++)
|
||||||
@@ -300,5 +292,8 @@ end:
|
|||||||
free((void*)*files[i]);
|
free((void*)*files[i]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (encryptionKey)
|
||||||
|
free(encryptionKey);
|
||||||
|
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,17 +25,18 @@
|
|||||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
*/
|
*/
|
||||||
|
#include <bytearray.h>
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include <cctype>
|
||||||
|
#include <locale>
|
||||||
|
|
||||||
#include <openssl/rand.h>
|
#include <openssl/rand.h>
|
||||||
#include <openssl/pkcs12.h>
|
#include <openssl/pkcs12.h>
|
||||||
#include <openssl/evp.h>
|
#include <openssl/evp.h>
|
||||||
#include <openssl/err.h>
|
#include <openssl/err.h>
|
||||||
|
|
||||||
#include <QCoreApplication>
|
#include <curl/curl.h>
|
||||||
#include <QNetworkReply>
|
|
||||||
#include <QNetworkRequest>
|
|
||||||
#include <QNetworkAccessManager>
|
|
||||||
#include <QFile>
|
|
||||||
|
|
||||||
#include <zlib.h>
|
#include <zlib.h>
|
||||||
#include <zip.h>
|
#include <zip.h>
|
||||||
@@ -88,11 +89,78 @@ void DRMProcessorClientImpl::randBytes(unsigned char* bytesOut, unsigned int len
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* HTTP interface */
|
/* HTTP interface */
|
||||||
std::string DRMProcessorClientImpl::sendHTTPRequest(const std::string& URL, const std::string& POSTData, const std::string& contentType, std::map<std::string, std::string>* responseHeaders)
|
#define HTTP_REQ_MAX_RETRY 5
|
||||||
|
#define DISPLAY_THRESHOLD 10*1024 // Threshold to display download progression
|
||||||
|
static unsigned downloadedBytes;
|
||||||
|
|
||||||
|
static int downloadProgress(void *clientp, curl_off_t dltotal, curl_off_t dlnow,
|
||||||
|
curl_off_t ultotal, curl_off_t ulnow)
|
||||||
{
|
{
|
||||||
QNetworkRequest request(QUrl(URL.c_str()));
|
// For "big" files only
|
||||||
QNetworkAccessManager networkManager;
|
if (dltotal >= DISPLAY_THRESHOLD && gourou::logLevel >= gourou::WARN)
|
||||||
QByteArray replyData;
|
{
|
||||||
|
int percent = 0;
|
||||||
|
if (dltotal)
|
||||||
|
percent = (dlnow * 100) / dltotal;
|
||||||
|
|
||||||
|
std::cout << "\rDownload " << percent << "%" << std::flush;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static size_t curlRead(void *data, size_t size, size_t nmemb, void *userp)
|
||||||
|
{
|
||||||
|
gourou::ByteArray* replyData = (gourou::ByteArray*) userp;
|
||||||
|
|
||||||
|
replyData->append((unsigned char*)data, size*nmemb);
|
||||||
|
|
||||||
|
return size*nmemb;
|
||||||
|
}
|
||||||
|
|
||||||
|
static size_t curlReadFd(void *data, size_t size, size_t nmemb, void *userp)
|
||||||
|
{
|
||||||
|
int fd = *(int*) userp;
|
||||||
|
|
||||||
|
size_t res = write(fd, data, size*nmemb);
|
||||||
|
|
||||||
|
downloadedBytes += res;
|
||||||
|
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
static size_t curlHeaders(char *buffer, size_t size, size_t nitems, void *userdata)
|
||||||
|
{
|
||||||
|
std::map<std::string, std::string>* responseHeaders = (std::map<std::string, std::string>*)userdata;
|
||||||
|
std::string::size_type pos = 0;
|
||||||
|
std::string buf(buffer, size*nitems);
|
||||||
|
|
||||||
|
pos = buf.find(":", pos);
|
||||||
|
|
||||||
|
if (pos != std::string::npos)
|
||||||
|
{
|
||||||
|
std::string key = std::string(buffer, pos);
|
||||||
|
std::string value = std::string(&buffer[pos+1], (size*nitems)-(pos+1));
|
||||||
|
|
||||||
|
key = gourou::trim(key);
|
||||||
|
value = gourou::trim(value);
|
||||||
|
|
||||||
|
(*responseHeaders)[key] = value;
|
||||||
|
|
||||||
|
if (gourou::logLevel >= gourou::DEBUG)
|
||||||
|
std::cout << key << " : " << value << std::endl;
|
||||||
|
}
|
||||||
|
|
||||||
|
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, bool resume)
|
||||||
|
{
|
||||||
|
gourou::ByteArray replyData;
|
||||||
|
std::map<std::string, std::string> localHeaders;
|
||||||
|
|
||||||
|
if (!responseHeaders)
|
||||||
|
responseHeaders = &localHeaders;
|
||||||
|
|
||||||
GOUROU_LOG(gourou::INFO, "Send request to " << URL);
|
GOUROU_LOG(gourou::INFO, "Send request to " << URL);
|
||||||
if (POSTData.size())
|
if (POSTData.size())
|
||||||
@@ -100,48 +168,111 @@ std::string DRMProcessorClientImpl::sendHTTPRequest(const std::string& URL, cons
|
|||||||
GOUROU_LOG(gourou::DEBUG, "<<< " << std::endl << POSTData);
|
GOUROU_LOG(gourou::DEBUG, "<<< " << std::endl << POSTData);
|
||||||
}
|
}
|
||||||
|
|
||||||
request.setRawHeader("Accept", "*/*");
|
unsigned prevDownloadedBytes;
|
||||||
request.setRawHeader("User-Agent", "book2png");
|
downloadedBytes = 0;
|
||||||
if (contentType.size())
|
if (fd && resume)
|
||||||
request.setRawHeader("Content-Type", contentType.c_str());
|
{
|
||||||
|
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");
|
||||||
|
}
|
||||||
|
|
||||||
QNetworkReply* reply;
|
CURL *curl = curl_easy_init();
|
||||||
|
CURLcode res;
|
||||||
|
curl_easy_setopt(curl, CURLOPT_URL, URL.c_str());
|
||||||
|
curl_easy_setopt(curl, CURLOPT_USERAGENT, "book2png");
|
||||||
|
curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1);
|
||||||
|
|
||||||
|
|
||||||
|
struct curl_slist *list = NULL;
|
||||||
|
list = curl_slist_append(list, "Accept: */*");
|
||||||
|
std::string _contentType;
|
||||||
|
if (contentType.size())
|
||||||
|
{
|
||||||
|
_contentType = "Content-Type: " + contentType;
|
||||||
|
list = curl_slist_append(list, _contentType.c_str());
|
||||||
|
}
|
||||||
|
|
||||||
|
curl_easy_setopt(curl, CURLOPT_HTTPHEADER, list);
|
||||||
|
|
||||||
if (POSTData.size())
|
if (POSTData.size())
|
||||||
reply = networkManager.post(request, POSTData.c_str());
|
|
||||||
else
|
|
||||||
reply = networkManager.get(request);
|
|
||||||
|
|
||||||
QCoreApplication* app = QCoreApplication::instance();
|
|
||||||
networkManager.moveToThread(app->thread());
|
|
||||||
while (!reply->isFinished())
|
|
||||||
app->processEvents();
|
|
||||||
|
|
||||||
QByteArray location = reply->rawHeader("Location");
|
|
||||||
if (location.size() != 0)
|
|
||||||
{
|
{
|
||||||
GOUROU_LOG(gourou::DEBUG, "New location");
|
curl_easy_setopt(curl, CURLOPT_POST, 1L);
|
||||||
return sendHTTPRequest(location.constData(), POSTData, contentType, responseHeaders);
|
curl_easy_setopt(curl, CURLOPT_POSTFIELDSIZE, POSTData.size());
|
||||||
|
curl_easy_setopt(curl, CURLOPT_POSTFIELDS, POSTData.data());
|
||||||
}
|
}
|
||||||
|
|
||||||
if (reply->error() != QNetworkReply::NoError)
|
if (fd)
|
||||||
EXCEPTION(gourou::CLIENT_NETWORK_ERROR, "Error " << reply->errorString().toStdString());
|
{
|
||||||
|
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, curlReadFd);
|
||||||
QList<QByteArray> headers = reply->rawHeaderList();
|
curl_easy_setopt(curl, CURLOPT_WRITEDATA, (void*)&fd);
|
||||||
for (int i = 0; i < headers.size(); ++i) {
|
}
|
||||||
if (gourou::logLevel >= gourou::DEBUG)
|
else
|
||||||
std::cout << headers[i].constData() << " : " << reply->rawHeader(headers[i]).constData() << std::endl;
|
{
|
||||||
if (responseHeaders)
|
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, curlRead);
|
||||||
(*responseHeaders)[headers[i].constData()] = reply->rawHeader(headers[i]).constData();
|
curl_easy_setopt(curl, CURLOPT_WRITEDATA, (void*)&replyData);
|
||||||
}
|
}
|
||||||
|
|
||||||
replyData = reply->readAll();
|
curl_easy_setopt(curl, CURLOPT_HEADERFUNCTION, curlHeaders);
|
||||||
if (reply->rawHeader("Content-Type") == "application/vnd.adobe.adept+xml")
|
curl_easy_setopt(curl, CURLOPT_HEADERDATA, (void*)responseHeaders);
|
||||||
|
|
||||||
|
curl_easy_setopt(curl, CURLOPT_XFERINFOFUNCTION, downloadProgress);
|
||||||
|
curl_easy_setopt(curl, CURLOPT_NOPROGRESS, 0);
|
||||||
|
|
||||||
|
for (int i=0; i<HTTP_REQ_MAX_RETRY; i++)
|
||||||
|
{
|
||||||
|
prevDownloadedBytes = downloadedBytes;
|
||||||
|
if (downloadedBytes)
|
||||||
|
curl_easy_setopt(curl, CURLOPT_RESUME_FROM, downloadedBytes);
|
||||||
|
|
||||||
|
res = curl_easy_perform(curl);
|
||||||
|
|
||||||
|
// Connexion failed, wait & retry
|
||||||
|
if (res == CURLE_COULDNT_CONNECT)
|
||||||
|
{
|
||||||
|
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
|
||||||
|
else if (res == CURLE_RECV_ERROR)
|
||||||
|
{
|
||||||
|
if (prevDownloadedBytes != downloadedBytes)
|
||||||
|
{
|
||||||
|
GOUROU_LOG(gourou::WARN, "\nConnection broken, but data received, try again");
|
||||||
|
i--;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
GOUROU_LOG(gourou::WARN, "\nConnection broken and no data received, attempt " << (i+1) << "/" << HTTP_REQ_MAX_RETRY);
|
||||||
|
}
|
||||||
|
// Other error --> fail
|
||||||
|
else
|
||||||
|
break;
|
||||||
|
|
||||||
|
// Wait a little bit (250ms * i)
|
||||||
|
usleep((250 * 1000) * (i+1));
|
||||||
|
}
|
||||||
|
|
||||||
|
curl_slist_free_all(list);
|
||||||
|
curl_easy_cleanup(curl);
|
||||||
|
|
||||||
|
if (res != CURLE_OK)
|
||||||
|
EXCEPTION(gourou::CLIENT_NETWORK_ERROR, "Error " << curl_easy_strerror(res));
|
||||||
|
|
||||||
|
if ((downloadedBytes >= DISPLAY_THRESHOLD || replyData.size() >= DISPLAY_THRESHOLD) &&
|
||||||
|
gourou::logLevel >= gourou::WARN)
|
||||||
|
std::cout << std::endl;
|
||||||
|
|
||||||
|
if ((*responseHeaders)["Content-Type"] == "application/vnd.adobe.adept+xml")
|
||||||
{
|
{
|
||||||
GOUROU_LOG(gourou::DEBUG, ">>> " << std::endl << replyData.data());
|
GOUROU_LOG(gourou::DEBUG, ">>> " << std::endl << replyData.data());
|
||||||
}
|
}
|
||||||
|
|
||||||
return std::string(replyData.data(), replyData.length());
|
return std::string((char*)replyData.data(), replyData.length());
|
||||||
}
|
}
|
||||||
|
|
||||||
void DRMProcessorClientImpl::RSAPrivateEncrypt(const unsigned char* RSAKey, unsigned int RSAKeyLength,
|
void DRMProcessorClientImpl::RSAPrivateEncrypt(const unsigned char* RSAKey, unsigned int RSAKeyLength,
|
||||||
@@ -184,7 +315,7 @@ void DRMProcessorClientImpl::RSAPrivateDecrypt(const unsigned char* RSAKey, unsi
|
|||||||
PKCS8_PRIV_KEY_INFO* p8inf = d2i_PKCS8_PRIV_KEY_INFO_bio(mem, NULL);
|
PKCS8_PRIV_KEY_INFO* p8inf = d2i_PKCS8_PRIV_KEY_INFO_bio(mem, NULL);
|
||||||
|
|
||||||
if (!p8inf)
|
if (!p8inf)
|
||||||
EXCEPTION(gourou::CLIENT_INVALID_PKCS12, ERR_error_string(ERR_get_error(), NULL));
|
EXCEPTION(gourou::CLIENT_INVALID_PKCS8, ERR_error_string(ERR_get_error(), NULL));
|
||||||
|
|
||||||
EVP_PKEY* pkey = EVP_PKCS82PKEY(p8inf);
|
EVP_PKEY* pkey = EVP_PKCS82PKEY(p8inf);
|
||||||
RSA * rsa;
|
RSA * rsa;
|
||||||
@@ -510,7 +641,12 @@ void DRMProcessorClientImpl::inflate(gourou::ByteArray& data, gourou::ByteArray&
|
|||||||
ret = ::inflate(&infstream, Z_FINISH);
|
ret = ::inflate(&infstream, Z_FINISH);
|
||||||
while (ret == Z_OK || ret == Z_STREAM_END || ret == Z_BUF_ERROR)
|
while (ret == Z_OK || ret == Z_STREAM_END || ret == Z_BUF_ERROR)
|
||||||
{
|
{
|
||||||
|
// Real error
|
||||||
|
if (ret == Z_BUF_ERROR && infstream.avail_out == (uInt)dataSize)
|
||||||
|
break;
|
||||||
|
|
||||||
result.append(buffer, dataSize-infstream.avail_out);
|
result.append(buffer, dataSize-infstream.avail_out);
|
||||||
|
|
||||||
if ((ret == Z_OK && infstream.avail_out != 0) || ret == Z_STREAM_END)
|
if ((ret == Z_OK && infstream.avail_out != 0) || ret == Z_STREAM_END)
|
||||||
break;
|
break;
|
||||||
infstream.avail_out = (uInt)dataSize; // size of output
|
infstream.avail_out = (uInt)dataSize; // size of output
|
||||||
@@ -518,7 +654,6 @@ void DRMProcessorClientImpl::inflate(gourou::ByteArray& data, gourou::ByteArray&
|
|||||||
ret = ::inflate(&infstream, Z_FINISH);
|
ret = ::inflate(&infstream, Z_FINISH);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
if (ret == Z_STREAM_END)
|
if (ret == Z_STREAM_END)
|
||||||
ret = inflateEnd(&infstream);
|
ret = inflateEnd(&infstream);
|
||||||
|
|
||||||
|
|||||||
@@ -46,7 +46,7 @@ public:
|
|||||||
virtual void randBytes(unsigned char* bytesOut, unsigned int length);
|
virtual void randBytes(unsigned char* bytesOut, unsigned int length);
|
||||||
|
|
||||||
/* HTTP interface */
|
/* HTTP interface */
|
||||||
virtual std::string sendHTTPRequest(const std::string& URL, const std::string& POSTData=std::string(""), const std::string& contentType=std::string(""), std::map<std::string, std::string>* responseHeaders=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,
|
virtual void RSAPrivateEncrypt(const unsigned char* RSAKey, unsigned int RSAKeyLength,
|
||||||
const RSA_KEY_TYPE keyType, const std::string& password,
|
const RSA_KEY_TYPE keyType, const std::string& password,
|
||||||
|
|||||||
127
utils/utils_common.cpp
Normal file
127
utils/utils_common.cpp
Normal file
@@ -0,0 +1,127 @@
|
|||||||
|
/*
|
||||||
|
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 <sys/types.h>
|
||||||
|
#include <sys/stat.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <fcntl.h>
|
||||||
|
#include <limits.h>
|
||||||
|
|
||||||
|
#include <iostream>
|
||||||
|
|
||||||
|
#include <libgourou.h>
|
||||||
|
#include <libgourou_common.h>
|
||||||
|
#include "utils_common.h"
|
||||||
|
|
||||||
|
static const char* defaultDirs[] = {
|
||||||
|
".adept/",
|
||||||
|
"./adobe-digital-editions/",
|
||||||
|
"./.adobe-digital-editions/"
|
||||||
|
};
|
||||||
|
|
||||||
|
void version(void)
|
||||||
|
{
|
||||||
|
std::cout << "Current libgourou version : " << gourou::DRMProcessor::VERSION << std::endl ;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool fileExists(const char* filename)
|
||||||
|
{
|
||||||
|
struct stat _stat;
|
||||||
|
int ret = stat(filename, &_stat);
|
||||||
|
|
||||||
|
return (ret == 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
const char* findFile(const char* filename, bool inDefaultDirs)
|
||||||
|
{
|
||||||
|
if (fileExists(filename))
|
||||||
|
return strdup(filename);
|
||||||
|
|
||||||
|
if (!inDefaultDirs) return 0;
|
||||||
|
|
||||||
|
for (int i=0; i<(int)ARRAY_SIZE(defaultDirs); i++)
|
||||||
|
{
|
||||||
|
std::string path = std::string(defaultDirs[i]) + filename;
|
||||||
|
if (fileExists(path.c_str()))
|
||||||
|
return strdup(path.c_str());
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// https://stackoverflow.com/questions/2336242/recursive-mkdir-system-call-on-unix
|
||||||
|
void mkpath(const char *dir)
|
||||||
|
{
|
||||||
|
char tmp[PATH_MAX];
|
||||||
|
char *p = NULL;
|
||||||
|
size_t len;
|
||||||
|
|
||||||
|
snprintf(tmp, sizeof(tmp),"%s",dir);
|
||||||
|
len = strlen(tmp);
|
||||||
|
if (tmp[len - 1] == '/')
|
||||||
|
tmp[len - 1] = 0;
|
||||||
|
for (p = tmp + 1; *p; p++)
|
||||||
|
if (*p == '/') {
|
||||||
|
*p = 0;
|
||||||
|
mkdir(tmp, S_IRWXU);
|
||||||
|
*p = '/';
|
||||||
|
}
|
||||||
|
mkdir(tmp, S_IRWXU);
|
||||||
|
}
|
||||||
|
|
||||||
|
void fileCopy(const char* in, const char* out)
|
||||||
|
{
|
||||||
|
char buffer[4096];
|
||||||
|
int ret, fdIn, fdOut;
|
||||||
|
|
||||||
|
fdIn = open(in, O_RDONLY);
|
||||||
|
|
||||||
|
if (!fdIn)
|
||||||
|
EXCEPTION(gourou::CLIENT_FILE_ERROR, "Unable to open " << in);
|
||||||
|
|
||||||
|
fdOut = gourou::createNewFile(out);
|
||||||
|
|
||||||
|
if (!fdOut)
|
||||||
|
{
|
||||||
|
close (fdIn);
|
||||||
|
EXCEPTION(gourou::CLIENT_FILE_ERROR, "Unable to open " << out);
|
||||||
|
}
|
||||||
|
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
ret = ::read(fdIn, buffer, sizeof(buffer));
|
||||||
|
if (ret <= 0)
|
||||||
|
break;
|
||||||
|
::write(fdOut, buffer, ret);
|
||||||
|
}
|
||||||
|
|
||||||
|
close (fdIn);
|
||||||
|
close (fdOut);
|
||||||
|
}
|
||||||
67
utils/utils_common.h
Normal file
67
utils/utils_common.h
Normal file
@@ -0,0 +1,67 @@
|
|||||||
|
/*
|
||||||
|
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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#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]))
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Display libgourou version
|
||||||
|
*/
|
||||||
|
void version(void);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Find a given filename in current directory and/or in default directories
|
||||||
|
*
|
||||||
|
* @param filename Filename to search
|
||||||
|
* @param inDefaultDirs Search is default directories or not
|
||||||
|
*
|
||||||
|
* @return A copy of full path
|
||||||
|
*/
|
||||||
|
const char* findFile(const char* filename, bool inDefaultDirs=true);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Does the file (or directory exists)
|
||||||
|
*/
|
||||||
|
bool fileExists(const char* filename);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Recursively created dir
|
||||||
|
*/
|
||||||
|
void mkpath(const char *dir);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Copy file in into file out
|
||||||
|
*/
|
||||||
|
void fileCopy(const char* in, const char* out);
|
||||||
|
|
||||||
|
#endif
|
||||||
Reference in New Issue
Block a user