8 Commits

Author SHA1 Message Date
Grégory Soutadé
e05639c09d Support HTTP error codes != 200 (exception) and cookies in drmprocessorclientimpl 2024-01-06 09:25:11 +01:00
Grégory Soutadé
69865e005b Register operatorURL only when certificate from operator is fetched 2024-01-06 09:23:03 +01:00
Grégory Soutadé
fd38e84da6 Fix for Android for adept_loan_mgt.cpp (strptime format) 2024-01-06 09:22:11 +01:00
Grégory Soutadé
92a67312bd Update README.md 2023-09-06 21:21:43 +02:00
Grégory Soutadé
29d298b373 Update libgourou version 2023-09-06 21:17:06 +02:00
Grégory Soutadé
40dcb7a041 Add --no-notify option to utils 2023-09-06 21:17:06 +02:00
Grégory Soutadé
e0bb1bd4f8 Add notify server feature 2023-09-06 21:17:06 +02:00
Grégory Soutadé
9388d82138 Loan ID must be Fullfilment ID, not <loan> value(s) 2023-09-04 18:28:47 +02:00
9 changed files with 190 additions and 70 deletions

View File

@@ -8,7 +8,7 @@ Architecture
------------ ------------
Like RMSDK, libgourou has a client/server scheme. All platform specific functions (crypto, network...) has to be implemented in a client class (that derives from DRMProcessorClient) while server implements ADEPT protocol. Like RMSDK, libgourou has a client/server scheme. All platform specific functions (crypto, network...) has to be implemented in a client class (that derives from DRMProcessorClient) while server implements ADEPT protocol.
A reference implementation using Qt, OpenSSL and libzip is provided (in _utils_ directory). A reference implementation using cURL, OpenSSL and libzip is provided (in _utils_ directory).
Main fucntions to use from gourou::DRMProcessor are : Main fucntions to use from gourou::DRMProcessor are :

View File

@@ -37,7 +37,7 @@
#define ACS_SERVER "http://adeactivate.adobe.com/adept" #define ACS_SERVER "http://adeactivate.adobe.com/adept"
#endif #endif
#define LIBGOUROU_VERSION "0.8.2" #define LIBGOUROU_VERSION "0.8.3"
namespace gourou namespace gourou
{ {
@@ -67,10 +67,11 @@ namespace gourou
* @brief Fulfill ACSM file to server in order to retrieve ePub fulfillment item * @brief Fulfill ACSM file to server in order to retrieve ePub fulfillment item
* *
* @param ACSMFile Path of ACSMFile * @param ACSMFile Path of ACSMFile
* @param notify Notify server if requested by response
* *
* @return a FulfillmentItem if all is OK * @return a FulfillmentItem if all is OK
*/ */
FulfillmentItem* fulfill(const std::string& ACSMFile); FulfillmentItem* fulfill(const std::string& ACSMFile, bool notify=true);
/** /**
* @brief Once fulfilled, ePub file needs to be downloaded. * @brief Once fulfilled, ePub file needs to be downloaded.
@@ -102,8 +103,9 @@ namespace gourou
* *
* @param loanID Loan ID received during fulfill * @param loanID Loan ID received during fulfill
* @param operatorURL URL of operator that loans this book * @param operatorURL URL of operator that loans this book
* @param notify Notify server if requested by response
*/ */
void returnLoan(const std::string& loanID, const std::string& operatorURL); void returnLoan(const std::string& loanID, const std::string& operatorURL, bool notify=true);
/** /**
* @brief Return default ADEPT directory (ie /home/<user>/.config/adept) * @brief Return default ADEPT directory (ie /home/<user>/.config/adept)
@@ -156,7 +158,7 @@ namespace gourou
* @brief Send HTTP POST request to URL with document as POSTData * @brief Send HTTP POST request to URL with document as POSTData
*/ */
ByteArray sendRequest(const pugi::xml_document& document, const std::string& url); ByteArray sendRequest(const pugi::xml_document& document, const std::string& url);
/** /**
* @brief In place encrypt data with private device key * @brief In place encrypt data with private device key
*/ */
@@ -233,6 +235,9 @@ namespace gourou
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 buildNotifyReq(pugi::xml_document& returnReq, pugi::xml_node& body);
void notifyServer(pugi::xml_node& notifyRoot);
void notifyServer(pugi::xml_document& fulfillReply);
std::string encryptedKeyFirstPass(pugi::xml_document& rightsDoc, const std::string& encryptedKey, const std::string& keyType); std::string encryptedKeyFirstPass(pugi::xml_document& rightsDoc, const std::string& encryptedKey, const std::string& keyType);
void decryptADEPTKey(pugi::xml_document& rightsDoc, unsigned char* decryptedKey, const unsigned char* encryptionKey=0, unsigned encryptionKeySize=0); void decryptADEPTKey(pugi::xml_document& rightsDoc, unsigned char* decryptedKey, const unsigned char* encryptionKey=0, unsigned encryptionKeySize=0);
void removeEPubDRM(const std::string& filenameIn, const std::string& filenameOut, const unsigned char* encryptionKey, unsigned encryptionKeySize); void removeEPubDRM(const std::string& filenameIn, const std::string& filenameOut, const unsigned char* encryptionKey, unsigned encryptionKeySize);

View File

@@ -120,6 +120,7 @@ namespace gourou
CLIENT_OSSL_ERROR, CLIENT_OSSL_ERROR,
CLIENT_CRYPT_ERROR, CLIENT_CRYPT_ERROR,
CLIENT_DIGEST_ERROR, CLIENT_DIGEST_ERROR,
CLIENT_HTTP_ERROR
}; };
enum DRM_REMOVAL_ERROR { enum DRM_REMOVAL_ERROR {
@@ -235,12 +236,7 @@ namespace gourou
return ltrim(rtrim(s, t), t); return ltrim(rtrim(s, t), t);
} }
/** static inline pugi::xml_node getNode(const pugi::xml_node& root, const char* tagName, bool throwOnNull=true)
* @brief Extract text node from tag in document
* It can throw an exception if tag does not exists
* or just return an empty value
*/
static inline std::string extractTextElem(const pugi::xml_node& root, const char* tagName, bool throwOnNull=true)
{ {
pugi::xpath_node xpath_node = root.select_node(tagName); pugi::xpath_node xpath_node = root.select_node(tagName);
@@ -249,10 +245,23 @@ namespace gourou
if (throwOnNull) if (throwOnNull)
EXCEPTION(GOUROU_TAG_NOT_FOUND, "Tag " << tagName << " not found"); EXCEPTION(GOUROU_TAG_NOT_FOUND, "Tag " << tagName << " not found");
return ""; return pugi::xml_node();
} }
pugi::xml_node node = xpath_node.node().first_child(); return xpath_node.node();
}
/**
* @brief Extract text node from tag in document
* It can throw an exception if tag does not exists
* or just return an empty value
*/
static inline std::string extractTextElem(const pugi::xml_node& root, const char* tagName, bool throwOnNull=true)
{
pugi::xml_node node = getNode(root, tagName, throwOnNull);
node = node.first_child();
if (!node) if (!node)
{ {
@@ -266,6 +275,30 @@ namespace gourou
return trim(res); return trim(res);
} }
/**
* @brief Set text node of a tag in document
* It can throw an exception if tag does not exists
*/
static inline void setTextElem(const pugi::xml_node& root, const char* tagName,
const std::string& value, bool throwOnNull=true)
{
pugi::xml_node node = getNode(root, tagName, throwOnNull);
if (!node)
{
if (throwOnNull)
EXCEPTION(GOUROU_TAG_NOT_FOUND, "Text element for tag " << tagName << " not found");
return;
}
node = node.first_child();
if (!node)
node.append_child(pugi::node_pcdata).set_value(value.c_str());
else
node.set_value(value.c_str());
}
/** /**
* @brief Extract text attribute from tag in document * @brief Extract text attribute from tag in document
* It can throw an exception if attribute does not exists * It can throw an exception if attribute does not exists
@@ -273,17 +306,9 @@ namespace gourou
*/ */
static inline std::string extractTextAttribute(const pugi::xml_node& root, const char* tagName, const char* attributeName, bool throwOnNull=true) static inline std::string extractTextAttribute(const pugi::xml_node& root, const char* tagName, const char* attributeName, bool throwOnNull=true)
{ {
pugi::xpath_node xpath_node = root.select_node(tagName); pugi::xml_node node = getNode(root, tagName, throwOnNull);
if (!xpath_node) pugi::xml_attribute attr = node.attribute(attributeName);
{
if (throwOnNull)
EXCEPTION(GOUROU_TAG_NOT_FOUND, "Tag " << tagName << " not found");
return "";
}
pugi::xml_attribute attr = xpath_node.node().attribute(attributeName);
if (!attr) if (!attr)
{ {

View File

@@ -398,31 +398,7 @@ namespace gourou
} }
} }
doOperatorAuth(operatorURL); doOperatorAuth(operatorURL);
// Add new operatorURL to list
pugi::xml_document activationDoc;
user->readActivation(activationDoc);
pugi::xml_node root;
pugi::xpath_node xpathRes = activationDoc.select_node("//adept:operatorURLList");
// Create adept:operatorURLList if it doesn't exists
if (!xpathRes)
{
xpathRes = activationDoc.select_node("/activationInfo");
root = xpathRes.node();
root = root.append_child("adept:operatorURLList");
root.append_attribute("xmlns:adept") = ADOBE_ADEPT_NS;
appendTextElem(root, "adept:user", user->getUUID());
}
else
root = xpathRes.node();
appendTextElem(root, "adept:operatorURL", operatorURL);
user->updateActivationFile(activationDoc);
} }
void DRMProcessor::buildFulfillRequest(pugi::xml_document& acsmDoc, pugi::xml_document& fulfillReq) void DRMProcessor::buildFulfillRequest(pugi::xml_document& acsmDoc, pugi::xml_document& fulfillReq)
@@ -492,10 +468,28 @@ namespace gourou
appendTextElem(root, "adept:licenseURL", licenseURL); appendTextElem(root, "adept:licenseURL", licenseURL);
appendTextElem(root, "adept:certificate", certificate); appendTextElem(root, "adept:certificate", certificate);
// Add new operatorURL to list
xpathRes = activationDoc.select_node("//adept:operatorURLList");
// Create adept:operatorURLList if it doesn't exists
if (!xpathRes)
{
xpathRes = activationDoc.select_node("/activationInfo");
root = xpathRes.node();
root = root.append_child("adept:operatorURLList");
root.append_attribute("xmlns:adept") = ADOBE_ADEPT_NS;
appendTextElem(root, "adept:user", user->getUUID());
}
else
root = xpathRes.node();
appendTextElem(root, "adept:operatorURL", operatorURL);
user->updateActivationFile(activationDoc); user->updateActivationFile(activationDoc);
} }
FulfillmentItem* DRMProcessor::fulfill(const std::string& ACSMFile) FulfillmentItem* DRMProcessor::fulfill(const std::string& ACSMFile, bool notify)
{ {
if (!user->getPKCS12().length()) if (!user->getPKCS12().length())
EXCEPTION(FF_NOT_ACTIVATED, "Device not activated"); EXCEPTION(FF_NOT_ACTIVATED, "Device not activated");
@@ -580,7 +574,12 @@ namespace gourou
fetchLicenseServiceCertificate(licenseURL, operatorURL); fetchLicenseServiceCertificate(licenseURL, operatorURL);
return new FulfillmentItem(fulfillReply, user); FulfillmentItem* item = new FulfillmentItem(fulfillReply, user);
if (notify)
notifyServer(fulfillReply);
return item;
} }
DRMProcessor::ITEM_TYPE DRMProcessor::download(FulfillmentItem* item, std::string path, bool resume) DRMProcessor::ITEM_TYPE DRMProcessor::download(FulfillmentItem* item, std::string path, bool resume)
@@ -873,7 +872,8 @@ namespace gourou
#endif #endif
} }
void DRMProcessor::returnLoan(const std::string& loanID, const std::string& operatorURL) void DRMProcessor::returnLoan(const std::string& loanID, const std::string& operatorURL,
bool notify)
{ {
pugi::xml_document returnReq; pugi::xml_document returnReq;
@@ -881,9 +881,73 @@ namespace gourou
buildReturnReq(returnReq, loanID, operatorURL); buildReturnReq(returnReq, loanID, operatorURL);
sendRequest(returnReq, operatorURL + "/LoanReturn"); ByteArray replyData = sendRequest(returnReq, operatorURL + "/LoanReturn");
pugi::xml_document fulfillReply;
fulfillReply.load_string((const char*)replyData.data());
if (notify)
notifyServer(fulfillReply);
} }
void DRMProcessor::buildNotifyReq(pugi::xml_document& returnReq, pugi::xml_node& body)
{
pugi::xml_node decl = returnReq.append_child(pugi::node_declaration);
decl.append_attribute("version") = "1.0";
pugi::xml_node root = returnReq.append_child("adept:notification");
root.append_attribute("xmlns:adept") = ADOBE_ADEPT_NS;
appendTextElem(root, "adept:user", user->getUUID());
appendTextElem(root, "adept:device", user->getDeviceUUID());
body = root.append_copy(body);
body.append_attribute("xmlns") = ADOBE_ADEPT_NS;
addNonce(root);
signNode(root);
}
void DRMProcessor::notifyServer(pugi::xml_node& notifyRoot)
{
std::string notifyUrl = extractTextElem(notifyRoot, "//notifyURL", false);
pugi::xml_node notifyBody = getNode(notifyRoot, "//body", false);
if (notifyUrl == "")
{
GOUROU_LOG(INFO, "No notify URL");
return;
}
if (!notifyBody)
{
GOUROU_LOG(INFO, "No notify body");
return;
}
pugi::xml_document notifyReq;
buildNotifyReq(notifyReq, notifyBody);
sendRequest(notifyReq, notifyUrl);
}
void DRMProcessor::notifyServer(pugi::xml_document& fulfillReply)
{
pugi::xpath_node_set notifySet = fulfillReply.select_nodes("//notify");
if (notifySet.empty())
{
GOUROU_LOG(DEBUG, "No notify request");
return;
}
for (pugi::xpath_node_set::const_iterator it = notifySet.begin(); it != notifySet.end(); ++it)
{
pugi::xml_node notifyRoot = it->node();
notifyServer(notifyRoot);
}
}
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();

View File

@@ -29,24 +29,13 @@ namespace gourou
if (!node) if (!node)
EXCEPTION(FFI_INVALID_LOAN_TOKEN, "No loanToken element in document"); EXCEPTION(FFI_INVALID_LOAN_TOKEN, "No loanToken element in document");
node = doc.select_node("/envelope/loanToken/loan").node(); node = doc.select_node("/envelope/fulfillmentResult/fulfillment").node();
if (node) if (node)
properties["id"] = node.first_child().value(); properties["id"] = node.first_child().value();
else else
{ {
node = doc.select_node("/envelope/fulfillmentResult/resourceItemInfo/licenseToken/permissions/display/loan").node(); EXCEPTION(FFI_INVALID_LOAN_TOKEN, "No fulfillment element in document");
if (node)
properties["id"] = node.first_child().value();
else
{
node = doc.select_node("/envelope/fulfillmentResult/resourceItemInfo/licenseToken/permissions/play/loan").node();
if (node)
properties["id"] = node.first_child().value();
else
EXCEPTION(FFI_INVALID_LOAN_TOKEN, "No loanToken/loan element in document");
}
} }
node = doc.select_node("/envelope/loanToken/operatorURL").node(); node = doc.select_node("/envelope/loanToken/operatorURL").node();

View File

@@ -46,6 +46,7 @@ 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 bool resume = false; static bool resume = false;
static bool notify = true;
class ACSMDownloader class ACSMDownloader
@@ -82,7 +83,7 @@ public:
} }
else else
{ {
gourou::FulfillmentItem* item = processor.fulfill(acsmFile); gourou::FulfillmentItem* item = processor.fulfill(acsmFile, notify);
std::string filename; std::string filename;
if (!outputFile) if (!outputFile)
@@ -190,6 +191,7 @@ static void usage(const char* cmd)
std::cout << " " << "-f|--acsm-file" << "\t" << "Backward compatibility: ACSM request file for epub download" << std::endl; std::cout << " " << "-f|--acsm-file" << "\t" << "Backward compatibility: 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 << " " << "-r|--resume" << "\t\t" << "Try to resume download (in case of previous failure)" << std::endl;
std::cout << " " << "-N|--no-notify" << "\t\t" << "Don't notify server, even if requested" << 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;
@@ -232,13 +234,14 @@ int main(int argc, char** argv)
{"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' }, {"resume", no_argument, 0, 'r' },
{"no-notify", no_argument, 0, 'N' },
{"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:d:a:k:O:o:f:ervVh", c = getopt_long(argc, argv, "D:d:a:k:O:o:f:erNvVh",
long_options, &option_index); long_options, &option_index);
if (c == -1) if (c == -1)
break; break;
@@ -276,6 +279,9 @@ int main(int argc, char** argv)
case 'r': case 'r':
resume = true; resume = true;
break; break;
case 'N':
notify = false;
break;
case 'v': case 'v':
verbose++; verbose++;
break; break;

View File

@@ -52,6 +52,7 @@ static const char* devicekeyFile = "devicesalt";
static bool list = false; static bool list = false;
static const char* returnID = 0; static const char* returnID = 0;
static const char* deleteID = 0; static const char* deleteID = 0;
static bool notify = true;
struct Loan struct Loan
{ {
@@ -182,8 +183,13 @@ private:
loan->bookName = node.first_child().value(); loan->bookName = node.first_child().value();
struct tm tm; struct tm tm;
#ifdef __ANDROID__
res = strptime(loan->validity.c_str(), "%Y-%m-%dT%H:%M:%S%z", &tm);
#else
res = strptime(loan->validity.c_str(), "%Y-%m-%dT%H:%M:%S%Z", &tm); res = strptime(loan->validity.c_str(), "%Y-%m-%dT%H:%M:%S%Z", &tm);
if (*res == 0) #endif
if (res != NULL && *res == 0)
{ {
if (mktime(&tm) <= time(NULL)) if (mktime(&tm) <= time(NULL))
loan->validity = " (Expired)"; loan->validity = " (Expired)";
@@ -296,7 +302,7 @@ private:
return; return;
} }
processor.returnLoan(loan->id, loan->operatorURL); processor.returnLoan(loan->id, loan->operatorURL, notify);
deleteID = returnID; deleteID = returnID;
if (deleteLoan(false)) if (deleteLoan(false))
@@ -342,6 +348,7 @@ static void usage(const char* cmd)
std::cout << " " << "-l|--list" << "\t\t" << "List all loaned books" << 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 << " " << "-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 << " " << "-d|--delete" << "\t\t" << "Delete a loan entry without returning it" << std::endl;
std::cout << " " << "-N|--no-notify" << "\t\t" << "Don't notify server, even if requested" << 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;
@@ -375,6 +382,7 @@ int main(int argc, char** argv)
{"list", no_argument, 0, 'l' }, {"list", no_argument, 0, 'l' },
{"return", no_argument, 0, 'r' }, {"return", no_argument, 0, 'r' },
{"delete", no_argument, 0, 'd' }, {"delete", no_argument, 0, 'd' },
{"no-notify", no_argument, 0, 'N' },
{"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' },
@@ -402,6 +410,9 @@ int main(int argc, char** argv)
deleteID = optarg; deleteID = optarg;
actions++; actions++;
break; break;
case 'N':
notify = false;
break;
case 'v': case 'v':
verbose++; verbose++;
break; break;

View File

@@ -30,6 +30,7 @@
#include <algorithm> #include <algorithm>
#include <cctype> #include <cctype>
#include <locale> #include <locale>
#include <stdlib.h>
#define OPENSSL_NO_DEPRECATED 1 #define OPENSSL_NO_DEPRECATED 1
@@ -60,6 +61,14 @@ DRMProcessorClientImpl::DRMProcessorClientImpl():
if (!deflt) if (!deflt)
EXCEPTION(gourou::CLIENT_OSSL_ERROR, "Error, OpenSSL default provider not available"); EXCEPTION(gourou::CLIENT_OSSL_ERROR, "Error, OpenSSL default provider not available");
#endif #endif
#ifdef WIN32
strcpy(cookiejar, "C:\\temp\\libgourou_cookie_jar_XXXXXX");
#else
strcpy(cookiejar, "/tmp/libgourou_cookie_jar_XXXXXX");
#endif
mkstemp(cookiejar);
} }
DRMProcessorClientImpl::~DRMProcessorClientImpl() DRMProcessorClientImpl::~DRMProcessorClientImpl()
@@ -71,6 +80,8 @@ DRMProcessorClientImpl::~DRMProcessorClientImpl()
if (deflt) if (deflt)
OSSL_PROVIDER_unload(deflt); OSSL_PROVIDER_unload(deflt);
#endif #endif
unlink(cookiejar);
} }
/* Digest interface */ /* Digest interface */
@@ -227,6 +238,7 @@ std::string DRMProcessorClientImpl::sendHTTPRequest(const std::string& URL, cons
} }
curl_easy_setopt(curl, CURLOPT_HTTPHEADER, list); curl_easy_setopt(curl, CURLOPT_HTTPHEADER, list);
curl_easy_setopt(curl, CURLOPT_COOKIEJAR, cookiejar);
if (POSTData.size()) if (POSTData.size())
{ {
@@ -290,7 +302,13 @@ std::string DRMProcessorClientImpl::sendHTTPRequest(const std::string& URL, cons
if (res != CURLE_OK) if (res != CURLE_OK)
EXCEPTION(gourou::CLIENT_NETWORK_ERROR, "Error " << curl_easy_strerror(res)); EXCEPTION(gourou::CLIENT_NETWORK_ERROR, "Error " << curl_easy_strerror(res));
long http_code = 400;
curl_easy_getinfo (curl, CURLINFO_RESPONSE_CODE, &http_code);
if (http_code >= 400)
EXCEPTION(gourou::CLIENT_HTTP_ERROR, "HTTP Error code " << http_code);
if ((downloadedBytes >= DISPLAY_THRESHOLD || replyData.size() >= DISPLAY_THRESHOLD) && if ((downloadedBytes >= DISPLAY_THRESHOLD || replyData.size() >= DISPLAY_THRESHOLD) &&
gourou::logLevel >= gourou::LG_LOG_WARN) gourou::logLevel >= gourou::LG_LOG_WARN)
std::cout << std::endl; std::cout << std::endl;

View File

@@ -136,6 +136,8 @@ private:
#else #else
void *legacy, *deflt; void *legacy, *deflt;
#endif #endif
char cookiejar[64];
}; };
#endif #endif