diff --git a/include/libgourou.h b/include/libgourou.h index e3baa9b..37cc68e 100644 --- a/include/libgourou.h +++ b/include/libgourou.h @@ -67,10 +67,11 @@ namespace gourou * @brief Fulfill ACSM file to server in order to retrieve ePub fulfillment item * * @param ACSMFile Path of ACSMFile + * @param notify Notify server if requested by response * * @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. @@ -102,8 +103,9 @@ namespace gourou * * @param loanID Loan ID received during fulfill * @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//.config/adept) @@ -156,7 +158,7 @@ namespace gourou * @brief Send HTTP POST request to URL with document as POSTData */ ByteArray sendRequest(const pugi::xml_document& document, const std::string& url); - + /** * @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 fetchLicenseServiceCertificate(const std::string& licenseURL, 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); 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); diff --git a/include/libgourou_common.h b/include/libgourou_common.h index 12a717e..a6f2a91 100644 --- a/include/libgourou_common.h +++ b/include/libgourou_common.h @@ -235,12 +235,7 @@ namespace gourou return ltrim(rtrim(s, t), t); } - /** - * @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) + static inline pugi::xml_node getNode(const pugi::xml_node& root, const char* tagName, bool throwOnNull=true) { pugi::xpath_node xpath_node = root.select_node(tagName); @@ -249,10 +244,23 @@ namespace gourou if (throwOnNull) 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) { @@ -266,6 +274,30 @@ namespace gourou 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 * It can throw an exception if attribute does not exists @@ -273,17 +305,9 @@ namespace gourou */ 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) - { - if (throwOnNull) - EXCEPTION(GOUROU_TAG_NOT_FOUND, "Tag " << tagName << " not found"); - - return ""; - } - - pugi::xml_attribute attr = xpath_node.node().attribute(attributeName); + pugi::xml_attribute attr = node.attribute(attributeName); if (!attr) { diff --git a/src/libgourou.cpp b/src/libgourou.cpp index 31704d6..2bbc201 100644 --- a/src/libgourou.cpp +++ b/src/libgourou.cpp @@ -495,7 +495,7 @@ namespace gourou user->updateActivationFile(activationDoc); } - FulfillmentItem* DRMProcessor::fulfill(const std::string& ACSMFile) + FulfillmentItem* DRMProcessor::fulfill(const std::string& ACSMFile, bool notify) { if (!user->getPKCS12().length()) EXCEPTION(FF_NOT_ACTIVATED, "Device not activated"); @@ -580,7 +580,12 @@ namespace gourou 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) @@ -873,7 +878,8 @@ namespace gourou #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; @@ -881,9 +887,73 @@ namespace gourou 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) { const unsigned char* deviceKey = device->getDeviceKey();