/*
  Copyright 2021 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 <arpa/inet.h>
#include <sys/time.h>
#include <time.h>
#include <vector>
#include <ctime>

#include <uPDFParser.h>

#include <libgourou.h>
#include <libgourou_common.h>
#include <libgourou_log.h>

#define LOCAL_ADEPT_DIR       "./.adept"

#define ASN_NONE        0x00
#define ASN_NS_TAG      0x01
#define ASN_CHILD       0x02
#define ASN_END_TAG     0x03
#define ASN_TEXT        0x04
#define ASN_ATTRIBUTE   0x05

namespace gourou
{
    GOUROU_LOG_LEVEL logLevel = LG_LOG_WARN;
    const std::string DRMProcessor::VERSION = LIBGOUROU_VERSION;

    DRMProcessor::DRMProcessor(DRMProcessorClient* client):client(client), device(0), user(0)
    {
        if (!client)
            EXCEPTION(GOUROU_INVALID_CLIENT, "DRMProcessorClient is NULL");
    }

    DRMProcessor::DRMProcessor(DRMProcessorClient* client,
                               const std::string& deviceFile, const std::string& activationFile,
                               const std::string& deviceKeyFile):
        client(client), device(0), user(0)
    {
        if (!client)
            EXCEPTION(GOUROU_INVALID_CLIENT, "DRMProcessorClient is NULL");

        device = new Device(this, deviceFile, deviceKeyFile);
        user = new User(this, activationFile);

        if (user->getDeviceFingerprint() != "" &&
            (*device)["fingerprint"] != user->getDeviceFingerprint())
            EXCEPTION(GOUROU_DEVICE_DOES_NOT_MATCH, "User and device fingerprint does not match");
    }

    DRMProcessor::~DRMProcessor()
    {
        if (device) delete device;
        if (user) delete user;
    }

    // function to parse a date or time string.
    // https://www.geeksforgeeks.org/cpp/date-and-time-parsing-in-cpp/
    static time_t parseDateTime(const char* datetimeString, const char* format)
    {
        struct tm tmStruct;
        strptime(datetimeString, format, &tmStruct);
        return mktime(&tmStruct);
    }

    DRMProcessor* DRMProcessor::createDRMProcessor(DRMProcessorClient* client, bool randomSerial, std::string dirName,
                                                   const std::string& hobbes, const std::string& ACSServer)
    {
        DRMProcessor* processor = new DRMProcessor(client);

        if (dirName == "")
            dirName = getDefaultAdeptDir();

        Device* device = Device::createDevice(processor, dirName, hobbes, randomSerial);
        processor->device = device;

        User* user = User::createUser(processor, dirName, ACSServer);
        processor->user = user;

        return processor;
    }


    void DRMProcessor::pushString(void* sha_ctx, const std::string& string)
    {
        int length = string.length();
        uint16_t nlength = htons(length);
        char c;

        if (logLevel >= LG_LOG_TRACE)
            printf("%02x %02x ", ((uint8_t*)&nlength)[0], ((uint8_t*)&nlength)[1]);

        client->digestUpdate(sha_ctx, (unsigned char*)&nlength, sizeof(nlength));

        for(int i=0; i<length; i++)
        {
            c = string[i];
            client->digestUpdate(sha_ctx, (unsigned char*)&c, 1);
            if (logLevel >= LG_LOG_TRACE)
                printf("%c", c);
        }
        if (logLevel >= LG_LOG_TRACE)
            printf("\n");
    }

    void DRMProcessor::pushTag(void* sha_ctx, uint8_t tag)
    {
        client->digestUpdate(sha_ctx, &tag, sizeof(tag));
        if (logLevel >= LG_LOG_TRACE)
            printf("%02x ", tag);
    }

    void DRMProcessor::hashNode(const pugi::xml_node& root, void *sha_ctx, std::map<std::string,std::string> nsHash)
    {
        switch(root.type())
        {
        case pugi::node_element:
        {
            std::string name = root.name();

            // Look for "xmlns[:]" attribute
            for (pugi::xml_attribute_iterator ait = root.attributes_begin();
                 ait != root.attributes_end(); ++ait)
            {
                std::string attrName(ait->name());

                if (attrName.find("xmlns") == 0)
                {
                    std::string ns("GENERICNS");
                    // Compound xmlns:Name attribute
                    if (attrName.find(':') != std::string::npos)
                        ns = attrName.substr(attrName.find(':')+1);

                    nsHash[ns] = ait->value();
                    // Don't break here because we may multiple xmlns definitions
                    // break;
                }
            }

            // Remove namespace from tag
            // If we have a namespace for the first time, put it to hash
            if (name.find(':') != std::string::npos)
            {
                size_t nsIndex = name.find(':');
                std::string nodeNS = name.substr(0, nsIndex);

                pushTag(sha_ctx, ASN_NS_TAG);
                pushString(sha_ctx, nsHash[nodeNS]);

                name = name.substr(nsIndex+1);
            }
            // Global xmlns, always send to hash
            else if (nsHash.find("GENERICNS") != nsHash.end())
            {
                pushTag(sha_ctx, ASN_NS_TAG);
                pushString(sha_ctx, nsHash["GENERICNS"]);
            }

            pushString(sha_ctx, name);

            std::vector<std::string> attributes;
            pugi::xml_attribute attr;

            for (attr = root.first_attribute();
                 attr; attr = attr.next_attribute())
            {
                if (std::string(attr.name()).find("xmlns") != std::string::npos)
                    continue;

                attributes.push_back(attr.name());
            }

            // Attributes must be handled in alphabetical order
            std::sort(attributes.begin(), attributes.end());

            std::vector<std::string>::iterator attributesIt;
            for(attributesIt = attributes.begin();
                attributesIt != attributes.end();
                attributesIt++)
            {
                attr = root.attribute(attributesIt->c_str());

                pushTag(sha_ctx, ASN_ATTRIBUTE);
                pushString(sha_ctx, "");

                pushString(sha_ctx, attr.name());
                pushString(sha_ctx, attr.value());
            }

            pushTag(sha_ctx, ASN_CHILD);

            for (pugi::xml_node child : root.children())
                hashNode(child, sha_ctx, nsHash);

            pushTag(sha_ctx, ASN_END_TAG);

            break;
        }
        case pugi::node_pcdata:
        {
            std::string trimmed = root.value();
            trimmed = trim(trimmed);

            if (trimmed.length())
            {
                pushTag(sha_ctx, ASN_TEXT);
                pushString(sha_ctx, trimmed);
            }

            break;
        }
        default:
            break;
        }
    }

    void DRMProcessor::hashNode(const pugi::xml_node& root, unsigned char* sha_out)
    {
        void* sha_ctx = client->createDigest("SHA1");

        std::map<std::string, std::string> nsHash;

        hashNode(root, sha_ctx, nsHash);

        client->digestFinalize(sha_ctx, sha_out);

        dumpBuffer(gourou::LG_LOG_DEBUG, "\nSHA OUT : ", sha_out, SHA1_LEN);
    }

    void DRMProcessor::signNode(pugi::xml_node& rootNode)
    {
        // Compute hash
        unsigned char sha_out[SHA1_LEN];

        hashNode(rootNode, sha_out);

        // Sign with private key
        unsigned char res[RSA_KEY_SIZE];
        ByteArray deviceKey(device->getDeviceKey(), Device::DEVICE_KEY_SIZE);
        std::string pkcs12 = user->getPKCS12();
        ByteArray privateRSAKey = ByteArray::fromBase64(pkcs12);

        client->RSAPrivateEncrypt(privateRSAKey.data(), privateRSAKey.length(),
                                  RSAInterface::RSA_KEY_PKCS12, deviceKey.toBase64().data(),
                                  sha_out, sizeof(sha_out), res);

        dumpBuffer(gourou::LG_LOG_DEBUG, "Sig : ", res, sizeof(res));

        std::string signature = ByteArray(res, sizeof(res)).toBase64();
        appendTextElem(rootNode, "adept:signature", signature);
    }

    void DRMProcessor::addNonce(pugi::xml_node& root)
    {
        /*
          r4 = tp->time
          r3 = 0
          r2 = tm->militime
          r0 = 0x6f046000
          r1 = 0x388a

          r3 += high(r4*1000)
          r2 += low(r4*1000)

          r0 += r2
          r1 += r3
        */
        struct timeval tv;
        gettimeofday(&tv, 0);
        uint32_t nonce32[2] = {0x6f046000, 0x388a};
#ifdef STATIC_NONCE
        uint64_t bigtime = 0xAA001122BBCCAAULL;
#else
        uint64_t bigtime = tv.tv_sec*1000;
#endif
        nonce32[0] += (bigtime & 0xFFFFFFFF) + (tv.tv_usec/1000);
        nonce32[1] += ((bigtime >> 32) & 0xFFFFFFFF);

        ByteArray nonce((const unsigned char*)&nonce32, sizeof(nonce32));
        uint32_t tmp = 0;
        nonce.append((const unsigned char*)&tmp, sizeof(tmp));
        appendTextElem(root, "adept:nonce", nonce.toBase64().data());

        time_t _time = time(0) + 10*60; // Cur time + 10 minutes
        struct tm* tm_info = gmtime(&_time);
        char buffer[32];

        strftime(buffer, sizeof(buffer), "%Y-%m-%dT%H:%M:%SZ", tm_info);
        appendTextElem(root, "adept:expiration", buffer);
    }

    ByteArray DRMProcessor::sendRequest(const std::string& URL, const std::string& POSTdata, const char* contentType, std::map<std::string, std::string>* responseHeaders, int fd, bool resume)
    {
        if (contentType == 0)
            contentType = "";
        std::string reply = client->sendHTTPRequest(URL, POSTdata, contentType, responseHeaders, fd, resume);

        if (fd) return ByteArray();

        pugi::xml_document replyDoc;
        replyDoc.load_buffer(reply.c_str(), reply.length());

        pugi::xml_node root = replyDoc.first_child();
        if (std::string(root.name()) == "error")
        {
            EXCEPTION(GOUROU_ADEPT_ERROR, root.attribute("data").value());
        }

        return ByteArray(reply);
    }

    ByteArray DRMProcessor::sendRequest(const pugi::xml_document& document, const std::string& url)
    {
        StringXMLWriter xmlWriter;
        document.save(xmlWriter, "  ");
        std::string xmlStr = xmlWriter.getResult();

        return sendRequest(url, xmlStr, (const char*)"application/vnd.adobe.adept+xml");
    }

    void DRMProcessor::buildAuthRequest(pugi::xml_document& authReq)
    {
        pugi::xml_node decl = authReq.append_child(pugi::node_declaration);
        decl.append_attribute("version") = "1.0";

        pugi::xml_node root = authReq.append_child("adept:credentials");
        root.append_attribute("xmlns:adept") = ADOBE_ADEPT_NS;

        appendTextElem(root, "adept:user",       user->getUUID());

        ByteArray deviceKey(device->getDeviceKey(), Device::DEVICE_KEY_SIZE);
        unsigned char* pkcs12 = 0;
        unsigned int pkcs12Length;
        ByteArray pkcs12Cert = ByteArray::fromBase64(user->getPKCS12());

        client->extractCertificate(pkcs12Cert.data(), pkcs12Cert.length(),
                                   RSAInterface::RSA_KEY_PKCS12, deviceKey.toBase64().data(),
                                   &pkcs12, &pkcs12Length);
        ByteArray privateCertificate(pkcs12, pkcs12Length);
        free(pkcs12);

        appendTextElem(root, "adept:certificate",               privateCertificate.toBase64());
        appendTextElem(root, "adept:licenseCertificate",        user->getProperty("//adept:licenseCertificate"));
        appendTextElem(root, "adept:authenticationCertificate", user->getProperty("//adept:authenticationCertificate"));
    }

    void DRMProcessor::buildInitLicenseServiceRequest(pugi::xml_document& initLicReq, std::string operatorURL)
    {
        pugi::xml_node decl = initLicReq.append_child(pugi::node_declaration);
        decl.append_attribute("version") = "1.0";

        pugi::xml_node root = initLicReq.append_child("adept:licenseServiceRequest");
        root.append_attribute("xmlns:adept") = ADOBE_ADEPT_NS;
        root.append_attribute("identity") = "user";

        appendTextElem(root, "adept:operatorURL", operatorURL);
        addNonce(root);
        appendTextElem(root, "adept:user",        user->getUUID());

        signNode(root);
    }

    void DRMProcessor::doOperatorAuth(std::string operatorURL)
    {
        pugi::xml_document authReq;
        buildAuthRequest(authReq);
        std::string authURL = operatorURL;
        unsigned int fulfillPos = authURL.rfind("Fulfill");
        if (fulfillPos == (authURL.size() - (sizeof("Fulfill")-1)))
            authURL = authURL.substr(0, fulfillPos-1);
        ByteArray replyData = sendRequest(authReq, authURL + "/Auth");

        pugi::xml_document initLicReq;
        std::string activationURL = user->getProperty("//adept:activationURL");
        buildInitLicenseServiceRequest(initLicReq, authURL);
        sendRequest(initLicReq, activationURL + "/InitLicenseService");
    }

    void DRMProcessor::operatorAuth(std::string operatorURL)
    {
        pugi::xpath_node_set operatorList = user->getProperties("//adept:operatorURL");

        for (pugi::xpath_node_set::const_iterator operatorIt = operatorList.begin();
             operatorIt != operatorList.end(); ++operatorIt)
        {
            std::string value = operatorIt->node().first_child().value();
            if (trim(value) == operatorURL)
            {
                GOUROU_LOG(DEBUG, "Already authenticated to operator " << operatorURL);
                return;
            }
        }

        doOperatorAuth(operatorURL);
    }

    void DRMProcessor::buildFulfillRequest(pugi::xml_document& acsmDoc, pugi::xml_document& fulfillReq)
    {
        pugi::xml_node decl = fulfillReq.append_child(pugi::node_declaration);
        decl.append_attribute("version") = "1.0";

        pugi::xml_node root = fulfillReq.append_child("adept:fulfill");
        root.append_attribute("xmlns:adept") = ADOBE_ADEPT_NS;

        appendTextElem(root, "adept:user",       user->getUUID());
        appendTextElem(root, "adept:device",     user->getDeviceUUID());
        appendTextElem(root, "adept:deviceType", (*device)["deviceType"]);

        root.append_copy(acsmDoc.first_child());

        pugi::xml_node targetDevice = root.append_child("adept:targetDevice");
        appendTextElem(targetDevice, "adept:softwareVersion", (*device)["hobbes"]);
        appendTextElem(targetDevice, "adept:clientOS",        (*device)["clientOS"]);
        appendTextElem(targetDevice, "adept:clientLocale",    (*device)["clientLocale"]);
        appendTextElem(targetDevice, "adept:clientVersion",   (*device)["deviceClass"]);
        appendTextElem(targetDevice, "adept:deviceType",      (*device)["deviceType"]);
        appendTextElem(targetDevice, "adept:fingerprint",     (*device)["fingerprint"]);

        pugi::xml_node activationToken = targetDevice.append_child("adept:activationToken");
        appendTextElem(activationToken, "adept:user",   user->getUUID());
        appendTextElem(activationToken, "adept:device", user->getDeviceUUID());
    }

    void DRMProcessor::fetchLicenseServiceCertificate(const std::string& licenseURL,
                                                      const std::string& operatorURL)
    {
        if (user->getLicenseServiceCertificate(licenseURL) != "")
            return;

        std::string licenseServiceInfoReq = operatorURL + "/LicenseServiceInfo?licenseURL=" + licenseURL;

        ByteArray replyData;
        replyData = sendRequest(licenseServiceInfoReq);

        pugi::xml_document licenseServicesDoc;
        licenseServicesDoc.load_buffer(replyData.data(), replyData.length());

        // Add new license certificate
        pugi::xml_document activationDoc;
        user->readActivation(activationDoc);

        pugi::xml_node root;
        pugi::xpath_node xpathRes = activationDoc.select_node("//adept:licenseServices");

        // Create adept:licenseServices if it doesn't exists
        if (!xpathRes)
        {
            xpathRes = activationDoc.select_node("/activationInfo");
            root = xpathRes.node();
            root = root.append_child("adept:licenseServices");
            root.append_attribute("xmlns:adept") = ADOBE_ADEPT_NS;
        }
        else
            root = xpathRes.node();

        root = root.append_child("adept:licenseServiceInfo");

        std::string certificate = extractTextElem(licenseServicesDoc,
                                                  "/licenseServiceInfo/certificate");

        appendTextElem(root, "adept:licenseURL", licenseURL);
        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);
    }

    FulfillmentItem* DRMProcessor::fulfill(const std::string& ACSMFile, bool notify)
    {
        if (!user->getPKCS12().length())
            EXCEPTION(FF_NOT_ACTIVATED, "Device not activated");

        pugi::xml_document acsmDoc;

        if (!acsmDoc.load_file(ACSMFile.c_str(), pugi::parse_ws_pcdata_single|pugi::parse_escapes, pugi::encoding_utf8))
            EXCEPTION(FF_INVALID_ACSM_FILE, "Invalid ACSM file " << ACSMFile);

        // Could be an server internal error
        pugi::xml_node rootNode = acsmDoc.first_child();
        if (std::string(rootNode.name()) == "error")
        {
            EXCEPTION(FF_SERVER_INTERNAL_ERROR, rootNode.attribute("data").value());
        }

        GOUROU_LOG(INFO, "Fulfill " << ACSMFile);

        std::string expiration = extractTextElem(rootNode, "expiration", false);

        if (expiration != "")
        {
            time_t expirationTime = parseDateTime(expiration.c_str(), "%Y-%m-%dT%H:%M:%S");

            if (time(NULL) > expirationTime)
                GOUROU_LOG(WARN, "WARNING: ACSM file expired (" << expiration << "), It may not work.");
        }

        // Build req file
        pugi::xml_document fulfillReq;

        buildFulfillRequest(acsmDoc, fulfillReq);
        pugi::xpath_node root = fulfillReq.select_node("//adept:fulfill");
        rootNode = root.node();

        // Remove HMAC
        pugi::xpath_node xpathRes = fulfillReq.select_node("//hmac");

        if (!xpathRes)
            EXCEPTION(FF_NO_HMAC_IN_ACSM_FILE, "hmac tag not found in ACSM file");

        pugi::xml_node hmacNode = xpathRes.node();
        pugi::xml_node hmacParentNode = hmacNode.parent();

        hmacParentNode.remove_child(hmacNode);

        signNode(rootNode);

        // Add removed HMAC
        appendTextElem(hmacParentNode, hmacNode.name(), hmacNode.first_child().value());

        pugi::xpath_node node = acsmDoc.select_node("//operatorURL");
        if (!node)
            EXCEPTION(FF_NO_OPERATOR_URL, "OperatorURL not found in ACSM document");

        std::string operatorURL = node.node().first_child().value();
        operatorURL = trim(operatorURL);
        std::string fulfillURL = operatorURL + "/Fulfill";

        operatorAuth(fulfillURL);

        ByteArray replyData;

        try
        {
            replyData = sendRequest(fulfillReq, fulfillURL);
        }
        catch (gourou::Exception& e)
        {
            /*
              Operator requires authentication even if it's already in
              our operator list
            */
            std::string errorMsg(e.what());
            if (e.getErrorCode() == GOUROU_ADEPT_ERROR &&
                errorMsg.find("E_ADEPT_DISTRIBUTOR_AUTH") != std::string::npos)
            {
                doOperatorAuth(fulfillURL);
                replyData = sendRequest(fulfillReq, fulfillURL);
            }
            else
            {
                throw e;
            }
        }

        pugi::xml_document fulfillReply;

        fulfillReply.load_string((const char*)replyData.data());

        std::string licenseURL = extractTextElem(fulfillReply, "//licenseToken/licenseURL");

        fetchLicenseServiceCertificate(licenseURL, operatorURL);

        FulfillmentItem* item = new FulfillmentItem(fulfillReply, user);

        if (notify)
            notifyServer(fulfillReply);

        return item;
    }

    DRMProcessor::ITEM_TYPE DRMProcessor::download(FulfillmentItem* item, std::string path, bool resume)
    {
        ITEM_TYPE res = EPUB;

        if (!item)
            EXCEPTION(DW_NO_ITEM, "No item");

        std::map<std::string, std::string> headers;

        int fd = createNewFile(path, !resume);

        sendRequest(item->getDownloadURL(), "", 0, &headers, fd, resume);

        close(fd);

        GOUROU_LOG(INFO, "Download into " << path);

        ByteArray rightsStr(item->getRights());

        if (item->getMetadata("format").find("application/pdf") != std::string::npos)
            res = PDF;

        if (headers.count("Content-Type") &&
            headers["Content-Type"].find("application/pdf") != std::string::npos)
            res = PDF;

        if (res == EPUB)
        {
            void* handler = client->zipOpen(path);
            client->zipWriteFile(handler, "META-INF/rights.xml", rightsStr);
            client->zipClose(handler);
        }
        else if (res == PDF)
        {
            uPDFParser::Parser parser;
            bool EBXHandlerFound = false;

            try
            {
                GOUROU_LOG(DEBUG, "Parse PDF");
                parser.parse(path);
            }
            catch(std::invalid_argument& e)
            {
                GOUROU_LOG(ERROR, "Invalid PDF");
                return res;
            }

            std::vector<uPDFParser::Object*> objects = parser.objects();
            std::vector<uPDFParser::Object*>::reverse_iterator it;

            for(it = objects.rbegin(); it != objects.rend(); it++)
            {
                // Update EBX_HANDLER with rights
                if ((*it)->hasKey("Filter") && (**it)["Filter"]->str() == "/EBX_HANDLER")
                {
                    EBXHandlerFound = true;
                    uPDFParser::Object* ebx = (*it)->clone();
                    (*ebx)["ADEPT_ID"] = new uPDFParser::String(item->getResource());
                    (*ebx)["EBX_BOOKID"] = new uPDFParser::String(item->getResource());
                    ByteArray zipped;
                    client->deflate(rightsStr, zipped);
                    (*ebx)["ADEPT_LICENSE"] = new uPDFParser::String(zipped.toBase64());
                    parser.addObject(ebx);
                    break;
                }
            }

            if (EBXHandlerFound)
                parser.write(path, true);
            else
            {
                EXCEPTION(DW_NO_EBX_HANDLER, "EBX_HANDLER not found");
            }
        }

        return res;
    }

    void DRMProcessor::buildSignInRequest(pugi::xml_document& signInRequest,
                                          const std::string& adobeID, const std::string& adobePassword,
                                          const std::string& authenticationCertificate)
    {
        pugi::xml_node decl = signInRequest.append_child(pugi::node_declaration);
        decl.append_attribute("version") = "1.0";
        pugi::xml_node signIn = signInRequest.append_child("adept:signIn");
        signIn.append_attribute("xmlns:adept") = ADOBE_ADEPT_NS;
        std::string loginMethod = user->getLoginMethod();

        if (adobeID == "anonymous")
            signIn.append_attribute("method") = "anonymous";
        else if (loginMethod.size())
            signIn.append_attribute("method") = loginMethod.c_str();
        else
            signIn.append_attribute("method") = "AdobeID";

        unsigned char encryptedSignInData[RSA_KEY_SIZE];
        const unsigned char* deviceKey = device->getDeviceKey();

        ByteArray _authenticationCertificate = ByteArray::fromBase64(authenticationCertificate);

        // Build buffer <deviceKey> <len username> <username> <len password> <password>
        ByteArray ar(deviceKey, Device::DEVICE_KEY_SIZE);
        ar.append((unsigned char)adobeID.length());
        ar.append(adobeID);
        ar.append((unsigned char)adobePassword.length());
        ar.append(adobePassword);

        // Encrypt with authentication certificate (public part)
        client->RSAPublicEncrypt(_authenticationCertificate.data(),
                                 _authenticationCertificate.length(),
                                 RSAInterface::RSA_KEY_X509,
                                 ar.data(), ar.length(), encryptedSignInData);

        ar = ByteArray(encryptedSignInData, sizeof(encryptedSignInData));
        appendTextElem(signIn, "adept:signInData", ar.toBase64());

        // Generate Auth key and License Key
        void* rsaAuth = client->generateRSAKey(RSA_KEY_SIZE_BITS);
        void* rsaLicense = client->generateRSAKey(RSA_KEY_SIZE_BITS);

        std::string serializedData = serializeRSAPublicKey(rsaAuth);
        appendTextElem(signIn, "adept:publicAuthKey", serializedData);
        serializedData = serializeRSAPrivateKey(rsaAuth);
        appendTextElem(signIn, "adept:encryptedPrivateAuthKey", serializedData.data());

        serializedData = serializeRSAPublicKey(rsaLicense);
        appendTextElem(signIn, "adept:publicLicenseKey", serializedData.data());
        serializedData = serializeRSAPrivateKey(rsaLicense);
        appendTextElem(signIn, "adept:encryptedPrivateLicenseKey", serializedData.data());

        client->destroyRSAHandler(rsaAuth);
        client->destroyRSAHandler(rsaLicense);
    }

    void DRMProcessor::signIn(const std::string& adobeID, const std::string& adobePassword)
    {
        pugi::xml_document signInRequest;
        std::string authenticationCertificate = user->getAuthenticationCertificate();

        buildSignInRequest(signInRequest, adobeID, adobePassword, authenticationCertificate);

        GOUROU_LOG(INFO, "SignIn " << adobeID);

        std::string signInURL = user->getProperty("//adept:authURL");
        signInURL += "/SignInDirect";

        ByteArray credentials = sendRequest(signInRequest, signInURL);

        pugi::xml_document credentialsDoc;
        if (!credentialsDoc.load_buffer(credentials.data(), credentials.length()))
            EXCEPTION(SIGN_INVALID_CREDENTIALS, "Invalid credentials reply");

        struct adeptWalker: pugi::xml_tree_walker
        {
            void changeName(pugi::xml_node& node)
            {
                std::string name = std::string("adept:") + node.name();
                node.set_name(name.c_str());
            }

            bool begin(pugi::xml_node& node)
            {
                changeName(node);
                return true;
            }

            virtual bool for_each(pugi::xml_node& node)
            {
                if (node.type() == pugi::node_element)
                    changeName(node);
                return true; // continue traversal
            }
        } adeptWalker;

        pugi::xml_node credentialsNode = credentialsDoc.first_child();

        if (std::string(credentialsNode.name()) != "credentials")
            EXCEPTION(SIGN_INVALID_CREDENTIALS, "Invalid credentials reply");

        pugi::xpath_node encryptedPrivateLicenseKey = credentialsNode.select_node("encryptedPrivateLicenseKey");
        const char* privateKeyData = encryptedPrivateLicenseKey.node().first_child().value();
        ByteArray privateKeyDataStr = ByteArray::fromBase64(privateKeyData);
        ByteArray privateKey = decryptWithDeviceKey(privateKeyDataStr.data(), privateKeyDataStr.length());
        credentialsNode.remove_child(encryptedPrivateLicenseKey.node());
        appendTextElem(credentialsNode, "privateLicenseKey", privateKey.toBase64().data());

        // Add "adept:" prefix to all nodes
        credentialsNode.remove_attribute("xmlns");
        credentialsNode.append_attribute("xmlns:adept") = ADOBE_ADEPT_NS;
        credentialsNode.traverse(adeptWalker);

        appendTextElem(credentialsNode, "adept:authenticationCertificate", authenticationCertificate.data());

        pugi::xml_document activationDoc;
        user->readActivation(activationDoc);
        pugi::xml_node activationInfo = activationDoc.select_node("activationInfo").node();
        activationInfo.append_copy(credentialsNode);

        user->updateActivationFile(activationDoc);
    }

    void DRMProcessor::buildActivateReq(pugi::xml_document& activateReq)
    {
        pugi::xml_node decl = activateReq.append_child(pugi::node_declaration);
        decl.append_attribute("version") = "1.0";

        pugi::xml_node root = activateReq.append_child("adept:activate");
        root.append_attribute("xmlns:adept") = ADOBE_ADEPT_NS;
        root.append_attribute("requestType") = "initial";

        appendTextElem(root, "adept:fingerprint",   (*device)["fingerprint"]);
        appendTextElem(root, "adept:deviceType",    (*device)["deviceType"]);
        appendTextElem(root, "adept:clientOS",      (*device)["clientOS"]);
        appendTextElem(root, "adept:clientLocale",  (*device)["clientLocale"]);
        appendTextElem(root, "adept:clientVersion", (*device)["deviceClass"]);

        pugi::xml_node targetDevice = root.append_child("adept:targetDevice");
        appendTextElem(targetDevice, "adept:softwareVersion", (*device)["hobbes"]);
        appendTextElem(targetDevice, "adept:clientOS",        (*device)["clientOS"]);
        appendTextElem(targetDevice, "adept:clientLocale",    (*device)["clientLocale"]);
        appendTextElem(targetDevice, "adept:clientVersion",   (*device)["deviceClass"]);
        appendTextElem(targetDevice, "adept:deviceType",      (*device)["deviceType"]);
        appendTextElem(targetDevice, "adept:fingerprint",     (*device)["fingerprint"]);

        addNonce(root);

        appendTextElem(root, "adept:user", user->getUUID());
    }

    void DRMProcessor::activateDevice()
    {
        pugi::xml_document activateReq;

        GOUROU_LOG(INFO, "Activate device");

        buildActivateReq(activateReq);

        pugi::xml_node root = activateReq.select_node("adept:activate").node();

        signNode(root);

        pugi::xml_document activationDoc;
        user->readActivation(activationDoc);

        std::string activationURL = user->getProperty("//adept:activationURL");
        activationURL += "/Activate";

        ByteArray reply = sendRequest(activateReq, activationURL);

        pugi::xml_document activationToken;
        activationToken.load_buffer(reply.data(), reply.length());

        root = activationDoc.select_node("activationInfo").node();
        root.append_copy(activationToken.first_child());
        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);
    }

    std::string DRMProcessor::getDefaultAdeptDir(void)
    {
#ifndef DEFAULT_ADEPT_DIR
        const char* home = getenv("HOME");

        if (home)
            return home + std::string("/.config/adept/");
        else
        {
            const char* user = getenv("USER");

            if (user && user[0])
            {
                return std::string("/home/") + user + std::string("/.config/adept/");
            }
            else
                return LOCAL_ADEPT_DIR;
        }
#else
        return DEFAULT_ADEPT_DIR "/";
#endif
    }

    void DRMProcessor::returnLoan(const std::string& loanID, const std::string& operatorURL,
                                  bool notify)
    {
        pugi::xml_document returnReq;

        GOUROU_LOG(INFO, "Return loan " << loanID);

        buildReturnReq(returnReq, loanID, operatorURL);

        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();
        unsigned int outLen;
        int remain = 0;
        if ((len % 16))
            remain = 16 - (len%16);
        int encrypted_data_len = 16 + len + remain; // IV + data + pad
        unsigned char* encrypted_data = new unsigned char[encrypted_data_len];

        // Generate IV in front
        client->randBytes(encrypted_data, 16);

        client->encrypt(CryptoInterface::ALGO_AES, CryptoInterface::CHAIN_CBC,
                        deviceKey, 16, encrypted_data, 16,
                        data, len,
                        encrypted_data+16, &outLen);

        ByteArray res(encrypted_data, outLen+16);

        delete[] encrypted_data;

        return res;
    }

    /* First 16 bytes of data is IV for CBC chaining */
    ByteArray DRMProcessor::decryptWithDeviceKey(const unsigned char* data, unsigned int len)
    {
        unsigned int outLen;
        const unsigned char* deviceKey = device->getDeviceKey();
        unsigned char* decrypted_data = new unsigned char[len-16];

        client->decrypt(CryptoInterface::ALGO_AES, CryptoInterface::CHAIN_CBC,
                        deviceKey, 16, data, 16,
                        data+16, len-16,
                        decrypted_data, &outLen);

        ByteArray res(decrypted_data, outLen);

        delete[] decrypted_data;

        return res;
    }

    std::string DRMProcessor::serializeRSAPublicKey(void* rsa)
    {
        unsigned char* data = 0;
        unsigned int len;

        client->extractRSAPublicKey(rsa, &data, &len);

        ByteArray res(data, len);

        free(data);

        return res.toBase64();
    }

    std::string DRMProcessor::serializeRSAPrivateKey(void* rsa)
    {
        unsigned char* data = 0;
        unsigned int len;

        client->extractRSAPrivateKey(rsa, &data, &len);

        ByteArray res = encryptWithDeviceKey(data, len);

        free(data);

        return res.toBase64();
    }

    void DRMProcessor::exportPrivateLicenseKey(std::string path)
    {
        int fd = open(path.c_str(), O_CREAT|O_TRUNC|O_WRONLY, S_IRWXU);
        int ret;

        if (fd <= 0)
            EXCEPTION(GOUROU_FILE_ERROR, "Unable to open " << path);

        ByteArray privateLicenseKey = ByteArray::fromBase64(user->getPrivateLicenseKey());
        /* In adobekey.py, we get base64 decoded data [26:] */
        ret = write(fd, privateLicenseKey.data()+26, privateLicenseKey.length()-26);
        close(fd);
        if (ret != (int)(privateLicenseKey.length()-26))
        {
            EXCEPTION(gourou::GOUROU_FILE_ERROR, "Error writing " << path);
        }
    }

    int DRMProcessor::getLogLevel() {return (int)gourou::logLevel;}
    void DRMProcessor::setLogLevel(int logLevel) {gourou::logLevel = (GOUROU_LOG_LEVEL)logLevel;}

    /**
     * RSA Key can be over encrypted with AES128-CBC if keyType attribute is set
     * remainder = keyType % 16
     * Key = SHA256(keyType)[remainder*2:remainder*2+(16-remainder)] || SHA256(keyType)[16-remainder:16]
     * IV = DeviceID ^ FulfillmentId ^ VoucherId
     *
     * @return Base64 encoded decrypted key
     */
    std::string DRMProcessor::encryptedKeyFirstPass(pugi::xml_document& rightsDoc, const std::string& encryptedKey, const std::string& keyType)
    {
        unsigned char digest[32], key[16], iv[16];
        unsigned int dataOutLength;
        std::string id;

        client->digest("SHA256", (unsigned char*)keyType.c_str(), keyType.size(), digest);

        dumpBuffer(gourou::LG_LOG_DEBUG, "SHA of KeyType : ", digest, sizeof(digest));

        long nonce = std::stol(keyType);
        int remainder = nonce % 16;

        memcpy(key, &digest[remainder*2], 16-remainder);
        memcpy(&key[16-remainder], &digest[remainder], remainder);

        id = extractTextElem(rightsDoc, "/adept:rights/licenseToken/device");
        if (id == "")
            EXCEPTION(DRM_ERR_ENCRYPTION_KEY_FP, "Device id not found in rights.xml");
        ByteArray deviceId = ByteArray::fromHex(extractIdFromUUID(id));
        unsigned char* _deviceId = deviceId.data();

        id = extractTextElem(rightsDoc, "/adept:rights/licenseToken/fulfillment");
        if (id == "")
            EXCEPTION(DRM_ERR_ENCRYPTION_KEY_FP, "Fulfillment id not found in rights.xml");
        ByteArray fulfillmentId = ByteArray::fromHex(extractIdFromUUID(id));
        unsigned char* _fulfillmentId = fulfillmentId.data();

        id = extractTextElem(rightsDoc, "/adept:rights/licenseToken/voucher");
        if (id == "")
            EXCEPTION(DRM_ERR_ENCRYPTION_KEY_FP, "Voucher id not found in rights.xml");
        ByteArray voucherId = ByteArray::fromHex(extractIdFromUUID(id));
        unsigned char* _voucherId = voucherId.data();

        if (deviceId.size() < sizeof(iv) || fulfillmentId.size() < sizeof(iv) || voucherId.size() < sizeof(iv))
            EXCEPTION(DRM_ERR_ENCRYPTION_KEY_FP, "One id has a bad length");

        for(unsigned int i=0; i<sizeof(iv); i++)
            iv[i] = _deviceId[i] ^ _fulfillmentId[i] ^ _voucherId[i];

        dumpBuffer(gourou::LG_LOG_DEBUG, "First pass key : ", key, sizeof(key));
        dumpBuffer(gourou::LG_LOG_DEBUG, "First pass IV  : ", iv, sizeof(iv));

        ByteArray arrayEncryptedKey = ByteArray::fromBase64(encryptedKey);
        unsigned char* clearRSAKey = new unsigned char[arrayEncryptedKey.size()];

        client->decrypt(CryptoInterface::ALGO_AES, CryptoInterface::CHAIN_CBC,
                        (const unsigned char*)key, (unsigned int)sizeof(key),
                        (const unsigned char*)iv, (unsigned int)sizeof(iv),
                        (const unsigned char*)arrayEncryptedKey.data(), arrayEncryptedKey.size(),
                        (unsigned char*)clearRSAKey, &dataOutLength);

        dumpBuffer(gourou::LG_LOG_DEBUG, "\nDecrypted key : ", clearRSAKey, dataOutLength);

        /* Last block could be 0x10*16 which is OpenSSL padding, remove it if it's the case */
        bool skipLastLine = true;
        for(unsigned int i=dataOutLength-16; i<dataOutLength; i++)
        {
            if (clearRSAKey[i] != 0x10)
            {
                skipLastLine = false;
                break;
            }
        }

        ByteArray res(clearRSAKey, (skipLastLine)?dataOutLength-16:dataOutLength);

        delete[] clearRSAKey;

        return res.toBase64();
    }

    void DRMProcessor::decryptADEPTKey(pugi::xml_document& rightsDoc, unsigned char* decryptedKey, const unsigned char* encryptionKey, unsigned encryptionKeySize)
    {
        unsigned char rsaKey[RSA_KEY_SIZE];

        std::string user = extractTextElem(rightsDoc, "/adept:rights/licenseToken/user");

        if (!encryptionKey)
        {
            if (this->user->getUUID() != user)
            {
                EXCEPTION(DRM_INVALID_USER, "This book has been downloaded for another user (" << user << ")");
            }

            std::string encryptedKey = extractTextElem(rightsDoc, "/adept:rights/licenseToken/encryptedKey");
            std::string keyType = extractTextAttribute(rightsDoc, "/adept:rights/licenseToken/encryptedKey", "keyType", false);

            if (keyType != "")
                encryptedKey = encryptedKeyFirstPass(rightsDoc, encryptedKey, keyType);

            if (encryptedKey.size() != 172)
                EXCEPTION(DRM_INVALID_KEY_SIZE, "Invalid encrypted key size (" << encryptedKey.size() << "). DRM version not supported");

            ByteArray arrayEncryptedKey = ByteArray::fromBase64(encryptedKey);

            std::string privateKeyData = this->user->getPrivateLicenseKey();
            ByteArray privateRSAKey = ByteArray::fromBase64(privateKeyData);

            dumpBuffer(gourou::LG_LOG_DEBUG, "To decrypt : ", arrayEncryptedKey.data(), arrayEncryptedKey.length());

            client->RSAPrivateDecrypt(privateRSAKey.data(), privateRSAKey.length(),
                                      RSAInterface::RSA_KEY_PKCS8, "",
                                      arrayEncryptedKey.data(), arrayEncryptedKey.length(), rsaKey);

            dumpBuffer(gourou::LG_LOG_DEBUG, "Decrypted : ", rsaKey, sizeof(rsaKey));

            if (rsaKey[0] != 0x00 || rsaKey[1] != 0x02 ||
                rsaKey[RSA_KEY_SIZE-16-1] != 0x00)
                EXCEPTION(DRM_ERR_ENCRYPTION_KEY, "Unable to retrieve encryption key");

            memcpy(decryptedKey, &rsaKey[sizeof(rsaKey)-16], 16);
        }
        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, encryptionKey, encryptionKeySize);
        }
    }

    void DRMProcessor::removeEPubDRM(const std::string& filenameIn, const std::string& filenameOut,
                                     const unsigned char* encryptionKey, unsigned encryptionKeySize)
    {
        ByteArray zipData;
        bool removeEncryptionXML = true;
        void* zipHandler = client->zipOpen(filenameOut);

        client->zipReadFile(zipHandler, "META-INF/rights.xml", zipData);
        pugi::xml_document rightsDoc;
        rightsDoc.load_string((const char*)zipData.data());

        unsigned char decryptedKey[16];

        decryptADEPTKey(rightsDoc, decryptedKey, encryptionKey, encryptionKeySize);

        client->zipReadFile(zipHandler, "META-INF/encryption.xml", zipData);
        pugi::xml_document encryptionDoc;
        encryptionDoc.load_string((const char*)zipData.data());

        pugi::xpath_node_set nodeSet = encryptionDoc.select_nodes("//EncryptedData");

        for (pugi::xpath_node_set::const_iterator it = nodeSet.begin();
             it != nodeSet.end(); ++it)
        {
            pugi::xml_node encryptionMethod = it->node().child("EncryptionMethod");
            pugi::xml_node cipherReference  = it->node().child("CipherData").child("CipherReference");

            std::string encryptionType = encryptionMethod.attribute("Algorithm").value();
            std::string encryptedFile = cipherReference.attribute("URI").value();

            if (encryptionType == "")
            {
                EXCEPTION(DRM_MISSING_PARAMETER, "Missing Algorithm attribute in encryption.xml");
            }
            else if (encryptionType == "http://www.w3.org/2001/04/xmlenc#aes128-cbc")
            {
                if (encryptedFile == "")
                {
                    EXCEPTION(DRM_MISSING_PARAMETER, "Missing URI attribute in encryption.xml");
                }

                GOUROU_LOG(DEBUG, "Encrypted file " << encryptedFile);

                client->zipReadFile(zipHandler, encryptedFile, zipData, false);

                unsigned char* _data = zipData.data();
                ByteArray clearData(zipData.length()-16+1, true); /* Reserve 1 byte for 'Z' */
                unsigned char* _clearData = clearData.data();
                gourou::ByteArray inflateData(true);
                unsigned int dataOutLength;

                client->decrypt(CryptoInterface::ALGO_AES, CryptoInterface::CHAIN_CBC,
                                decryptedKey, sizeof(decryptedKey), /* Key */
                                _data, 16, /* IV */
                                &_data[16], zipData.length()-16,
                                _clearData, &dataOutLength);

                // Add 'Z' at the end, done in ineptepub.py
                _clearData[dataOutLength] = 'Z';
                clearData.resize(dataOutLength+1);

                try
                {
                    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());
            }
            else
            {
                GOUROU_LOG(WARN, "Unsupported encryption algorithm " << encryptionType << ", for file " << encryptedFile);
                removeEncryptionXML = false;
            }
        }

        client->zipDeleteFile(zipHandler, "META-INF/rights.xml");
        if (removeEncryptionXML)
            client->zipDeleteFile(zipHandler, "META-INF/encryption.xml");
        else
        {
            StringXMLWriter xmlWriter;
            encryptionDoc.save(xmlWriter, "  ");
            std::string xmlStr = xmlWriter.getResult();
            ByteArray ba(xmlStr);
            client->zipWriteFile(zipHandler, "META-INF/encryption.xml", ba);
        }

        client->zipClose(zipHandler);
    }

    void DRMProcessor::generatePDFObjectKey(int version,
                                            const unsigned char* masterKey, unsigned int masterKeyLength,
                                            int objectId, int objectGenerationNumber,
                                            unsigned char* keyOut)
    {
        switch(version)
        {
        case 4:
            ByteArray toHash(masterKey, masterKeyLength);
            uint32_t _objectId = objectId;
            uint32_t _objectGenerationNumber = objectGenerationNumber;
            toHash.append((const unsigned char*)&_objectId, 3); // Fill 3 bytes
            toHash.append((const unsigned char*)&_objectGenerationNumber, 2); // Fill 2 bytes

            client->digest("md5", toHash.data(), toHash.length(), keyOut);
            break;
        }
    }

    void DRMProcessor::removePDFDRM(const std::string& filenameIn, const std::string& filenameOut,
                                    const unsigned char* encryptionKey, unsigned encryptionKeySize)
    {
        uPDFParser::Parser parser;
        bool EBXHandlerFound = false;

        if (filenameIn == filenameOut)
        {
            EXCEPTION(DRM_IN_OUT_EQUALS, "PDF IN must be different of PDF OUT");
        }

        try
        {
            GOUROU_LOG(DEBUG, "Parse PDF");
            parser.parse(filenameIn);
        }
        catch(std::invalid_argument& e)
        {
            GOUROU_LOG(ERROR, "Invalid PDF");
            return;
        }

        uPDFParser::Integer* ebxVersion;
        std::vector<uPDFParser::Object*> objects = parser.objects();
        std::vector<uPDFParser::Object*>::iterator it, ebxIt;
        std::vector<uPDFParser::Object*>::reverse_iterator rIt;
        std::vector<uPDFParser::Object*> ebxObjects;
        unsigned char decryptedKey[16];
        int ebxId;

        for(rIt = objects.rbegin(); rIt != objects.rend(); rIt++)
        {
            // Update EBX_HANDLER with rights
            if ((*rIt)->hasKey("Filter") && (**rIt)["Filter"]->str() == "/EBX_HANDLER")
            {
                EBXHandlerFound = true;
                uPDFParser::Object* ebx = *rIt;

                ebxVersion  = (uPDFParser::Integer*)(*ebx)["V"];
                if (ebxVersion->value() != 4)
                {
                    EXCEPTION(DRM_VERSION_NOT_SUPPORTED, "EBX encryption version not supported " << ebxVersion->value());
                }

                if (!(ebx->hasKey("ADEPT_LICENSE")))
                {
                    EXCEPTION(DRM_ERR_ENCRYPTION_KEY, "No ADEPT_LICENSE found");
                }

                uPDFParser::String* licenseObject = (uPDFParser::String*)(*ebx)["ADEPT_LICENSE"];

                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;
                client->inflate(zippedData, rightsStr);

                pugi::xml_document rightsDoc;
                rightsDoc.load_string((const char*)rightsStr.data());

                decryptADEPTKey(rightsDoc, decryptedKey, encryptionKey, encryptionKeySize);

                ebxId = ebx->objectId();

                break;
            }
        }

        if (!EBXHandlerFound)
        {
            EXCEPTION(DRM_ERR_ENCRYPTION_KEY, "EBX_HANDLER not found");
        }

        for(it = objects.begin(); it != objects.end(); it++)
        {
            uPDFParser::Object* object = *it;

            if (object->objectId() == ebxId)
            {
                ebxObjects.push_back(object);
                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[sizeof(decryptedKey)];

            generatePDFObjectKey(ebxVersion->value(),
                                 decryptedKey, sizeof(decryptedKey),
                                 object->objectId(), object->generationNumber(),
                                 tmpKey);

            uPDFParser::Dictionary& dictionary = object->dictionary();
            std::map<std::string, uPDFParser::DataType*>& dictValues = dictionary.value();
            std::map<std::string, uPDFParser::DataType*>::iterator dictIt;
            std::map<std::string, uPDFParser::DataType*> decodedStrings;
            std::string string;

            /* Parse dictionary */
            for (dictIt = dictValues.begin(); dictIt != dictValues.end(); dictIt++)
            {
                uPDFParser::DataType* dictData = dictIt->second;
                if (dictData->type() == uPDFParser::DataType::STRING)
                {
                    string = ((uPDFParser::String*) dictData)->unescapedValue();

                    unsigned char* encryptedData = (unsigned char*)string.c_str();
                    unsigned int dataLength = string.size();
                    unsigned char* clearData = new unsigned char[dataLength];
                    unsigned int dataOutLength;

                    GOUROU_LOG(DEBUG, "Decrypt string " << dictIt->first << " " << dataLength);

                    client->decrypt(CryptoInterface::ALGO_RC4, CryptoInterface::CHAIN_ECB,
                                    tmpKey, sizeof(tmpKey), /* Key */
                                    NULL, 0, /* IV */
                                    encryptedData, dataLength,
                                    clearData, &dataOutLength);

                    decodedStrings[dictIt->first] = new uPDFParser::String(
                        std::string((const char*)clearData, dataOutLength));

                    delete[] clearData;
                }
                else if (dictData->type() == uPDFParser::DataType::HEXASTRING)
                {
                    string = ((uPDFParser::HexaString*) dictData)->value();
                    ByteArray hexStr = ByteArray::fromHex(string);

                    unsigned char* encryptedData = hexStr.data();
                    unsigned int dataLength = hexStr.size();
                    unsigned char* clearData = new unsigned char[dataLength];
                    unsigned int dataOutLength;

                    GOUROU_LOG(DEBUG, "Decrypt hexa string " << dictIt->first << " " << dataLength);

                    client->decrypt(CryptoInterface::ALGO_RC4, CryptoInterface::CHAIN_ECB,
                                    tmpKey, sizeof(tmpKey), /* Key */
                                    NULL, 0, /* IV */
                                    encryptedData, dataLength,
                                    clearData, &dataOutLength);

                    ByteArray clearHexStr = ByteArray(clearData, dataOutLength);
                    decodedStrings[dictIt->first] = new uPDFParser::HexaString(
                        clearHexStr.toHex());

                    delete[] clearData;
                }
            }

            for (dictIt = decodedStrings.begin(); dictIt != decodedStrings.end(); dictIt++)
                dictionary.replace(dictIt->first, dictIt->second);

            std::vector<uPDFParser::DataType*>::iterator datasIt;
            std::vector<uPDFParser::DataType*>& datas = object->data();
            uPDFParser::Stream* stream;

            for (datasIt = datas.begin(); datasIt != datas.end(); datasIt++)
            {
                if ((*datasIt)->type() != uPDFParser::DataType::STREAM)
                    continue;

                stream = (uPDFParser::Stream*) (*datasIt);
                unsigned char* encryptedData = stream->data();
                unsigned int dataLength = stream->dataLength();
                unsigned char* clearData = new unsigned char[dataLength];
                unsigned int dataOutLength;

                GOUROU_LOG(DEBUG, "Decrypt stream id " << object->objectId() << ", size " << stream->dataLength());

                client->decrypt(CryptoInterface::ALGO_RC4, CryptoInterface::CHAIN_ECB,
                                tmpKey, sizeof(tmpKey), /* Key */
                                NULL, 0, /* IV */
                                encryptedData, dataLength,
                                clearData, &dataOutLength);

                stream->setData(clearData, dataOutLength, true);
                if (dataOutLength != dataLength)
                    GOUROU_LOG(DEBUG, "New size " << dataOutLength);
            }
        }

        /* Delete objects that reference EBX objects,
           except in trailer and objects that references Info */
        for(it = objects.begin(); it != objects.end(); it++)
        {
            uPDFParser::Object* object = *it;

            if (object->hasKey("Encrypt") && (*object)["Encrypt"]->type() == uPDFParser::DataType::REFERENCE)
            {
                uPDFParser::Reference* encrypt = (uPDFParser::Reference*)(*object)["Encrypt"];
                
                if (object->hasKey("Info"))
                {
                    object->deleteKey("Encrypt");
                    continue;
                }
                
                /* Delete EBX objects */
                for(ebxIt = ebxObjects.begin(); ebxIt != ebxObjects.end(); ebxIt++)
                {
                    if (encrypt->value() == (*ebxIt)->objectId())
                    {
                        GOUROU_LOG(DEBUG, "Delete stream id " << object->objectId());
                        
                        parser.removeObject(object);
                        break;
                    }
                }
            }
        }
        
        /* Delete EBX objects */
        for(it = ebxObjects.begin(); it != ebxObjects.end(); it++)
            parser.removeObject(*it);

        uPDFParser::Object& trailer = parser.getTrailer();
        trailer.deleteKey("Encrypt");

        parser.write(filenameOut);
    }

    void DRMProcessor::removeDRM(const std::string& filenameIn, const std::string& filenameOut,
                                 ITEM_TYPE type, const unsigned char* encryptionKey, unsigned encryptionKeySize)
    {
        if (type == PDF)
            removePDFDRM(filenameIn, filenameOut, encryptionKey, encryptionKeySize);
        else
            removeEPubDRM(filenameIn, filenameOut, encryptionKey, encryptionKeySize);
    }
}
