Libgourou

Libgourou Git Source Tree

Root/utils/acsmdownloader.cpp

1/*
2 Copyright (c) 2021, Grégory Soutadé
3
4 All rights reserved.
5 Redistribution and use in source and binary forms, with or without
6 modification, are permitted provided that the following conditions are met:
7
8 * Redistributions of source code must retain the above copyright
9 notice, this list of conditions and the following disclaimer.
10 * Redistributions in binary form must reproduce the above copyright
11 notice, this list of conditions and the following disclaimer in the
12 documentation and/or other materials provided with the distribution.
13 * Neither the name of the copyright holder nor the
14 names of its contributors may be used to endorse or promote products
15 derived from this software without specific prior written permission.
16
17 THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND ANY
18 EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
19 WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
20 DISCLAIMED. IN NO EVENT SHALL THE REGENTS AND CONTRIBUTORS BE LIABLE FOR ANY
21 DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
22 (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
23 LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
24 ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25 (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
26 SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27*/
28
29#include <getopt.h>
30#include <libgen.h>
31
32#include <iostream>
33#include <algorithm>
34
35#include <libgourou.h>
36#include <libgourou_common.h>
37
38#include "drmprocessorclientimpl.h"
39#include "utils_common.h"
40
41static const char* deviceFile = "device.xml";
42static const char* activationFile = "activation.xml";
43static const char* devicekeyFile = "devicesalt";
44static const char* acsmFile = 0;
45static bool exportPrivateKey = false;
46static const char* outputFile = 0;
47static const char* outputDir = 0;
48static bool resume = false;
49
50
51class ACSMDownloader
52{
53public:
54
55 int run()
56 {
57int ret = 0;
58try
59{
60 gourou::DRMProcessor processor(&client, deviceFile, activationFile, devicekeyFile);
61 gourou::User* user = processor.getUser();
62
63 if (exportPrivateKey)
64 {
65std::string filename;
66if (!outputFile)
67 filename = std::string("Adobe_PrivateLicenseKey--") + user->getUsername() + ".der";
68else
69 filename = outputFile;
70
71if (outputDir)
72{
73 if (!fileExists(outputDir))
74mkpath(outputDir);
75
76 filename = std::string(outputDir) + "/" + filename;
77}
78
79processor.exportPrivateLicenseKey(filename);
80
81std::cout << "Private license key exported to " << filename << std::endl;
82 }
83 else
84 {
85gourou::FulfillmentItem* item = processor.fulfill(acsmFile);
86
87std::string filename;
88if (!outputFile)
89{
90 filename = item->getMetadata("title");
91 if (filename == "")
92filename = "output";
93 else
94 {
95// Remove invalid characters
96std::replace(filename.begin(), filename.end(), '/', '_');
97 }
98}
99else
100 filename = outputFile;
101
102if (outputDir)
103{
104 if (!fileExists(outputDir))
105mkpath(outputDir);
106
107 filename = std::string(outputDir) + "/" + filename;
108}
109
110gourou::DRMProcessor::ITEM_TYPE type = processor.download(item, filename, resume);
111
112if (!outputFile)
113{
114 std::string finalName = filename;
115 if (type == gourou::DRMProcessor::ITEM_TYPE::PDF)
116finalName += ".pdf";
117 else
118finalName += ".epub";
119 rename(filename.c_str(), finalName.c_str());
120 filename = finalName;
121}
122std::cout << "Created " << filename << std::endl;
123
124serializeLoanToken(item);
125 }
126} catch(std::exception& e)
127{
128 std::cout << e.what() << std::endl;
129 ret = 1;
130}
131
132return ret;
133 }
134
135 void serializeLoanToken(gourou::FulfillmentItem* item)
136 {
137gourou::LoanToken* token = item->getLoanToken();
138
139// No loan token available
140if (!token)
141 return;
142
143pugi::xml_document doc;
144
145pugi::xml_node decl = doc.append_child(pugi::node_declaration);
146decl.append_attribute("version") = "1.0";
147
148pugi::xml_node root = doc.append_child("loanToken");
149gourou::appendTextElem(root, "id", (*token)["id"]);
150gourou::appendTextElem(root, "operatorURL", (*token)["operatorURL"]);
151gourou::appendTextElem(root, "validity", (*token)["validity"]);
152gourou::appendTextElem(root, "name", item->getMetadata("title"));
153
154char * activationDir = strdup(deviceFile);
155activationDir = dirname(activationDir);
156
157gourou::StringXMLWriter xmlWriter;
158doc.save(xmlWriter, " ");
159std::string xmlStr = xmlWriter.getResult();
160
161// Use first bytes of SHA1(id) as filename
162unsigned char sha1[gourou::SHA1_LEN];
163client.digest("SHA1", (unsigned char*)(*token)["id"].c_str(), (*token)["id"].size(), sha1);
164gourou::ByteArray tmp(sha1, sizeof(sha1));
165std::string filenameHex = tmp.toHex();
166std::string filename(filenameHex.c_str(), ID_HASH_SIZE);
167std::string fullPath = std::string(activationDir);
168fullPath += std::string ("/") + std::string(LOANS_DIR);
169mkpath(fullPath.c_str());
170fullPath += filename + std::string(".xml");
171gourou::writeFile(fullPath, xmlStr);
172
173std::cout << "Loan token serialized into " << fullPath << std::endl;
174
175free(activationDir);
176 }
177
178private:
179 DRMProcessorClientImpl client;
180};
181
182
183static void usage(const char* cmd)
184{
185 std::cout << basename((char*)cmd) << " download EPUB file from ACSM request file" << std::endl << std::endl;
186 std::cout << "Usage: " << basename((char*)cmd) << " [OPTIONS] file.acsm" << std::endl << std::endl;
187 std::cout << "Global Options:" << std::endl;
188 std::cout << " " << "-O|--output-dir" << "\t" << "Optional output directory were to put result (default ./)" << std::endl;
189 std::cout << " " << "-o|--output-file" << "\t" << "Optional output filename (default <title.(epub|pdf|der)>)" << std::endl;
190 std::cout << " " << "-f|--acsm-file" << "\t" << "Backward compatibility: ACSM request file for epub download" << std::endl;
191 std::cout << " " << "-e|--export-private-key"<< "\t" << "Export private key in DER format" << std::endl;
192 std::cout << " " << "-r|--resume" << "\t\t" << "Try to resume download (in case of previous failure)" << std::endl;
193 std::cout << " " << "-v|--verbose" << "\t\t" << "Increase verbosity, can be set multiple times" << std::endl;
194 std::cout << " " << "-V|--version" << "\t\t" << "Display libgourou version" << std::endl;
195 std::cout << " " << "-h|--help" << "\t\t" << "This help" << std::endl;
196
197 std::cout << "ADEPT Options:" << std::endl;
198 std::cout << " " << "-D|--adept-directory" << "\t" << ".adept directory that must contains device.xml, activation.xml and devicesalt" << std::endl;
199 std::cout << " " << "-d|--device-file" << "\t" << "device.xml file from eReader" << std::endl;
200 std::cout << " " << "-a|--activation-file" << "\t" << "activation.xml file from eReader" << std::endl;
201 std::cout << " " << "-k|--device-key-file" << "\t" << "private device key file (eg devicesalt/devkey.bin) from eReader" << std::endl;
202
203 std::cout << std::endl;
204
205 std::cout << "Environment:" << std::endl;
206 std::cout << "Device file, activation file and device key file are optionals. If not set, they are looked into :" << std::endl << std::endl;
207 std::cout << " * $ADEPT_DIR environment variable" << std::endl;
208 std::cout << " * /home/<user>/.config/adept" << std::endl;
209 std::cout << " * Current directory" << std::endl;
210 std::cout << " * .adept" << std::endl;
211 std::cout << " * adobe-digital-editions directory" << std::endl;
212 std::cout << " * .adobe-digital-editions directory" << std::endl;
213}
214
215int main(int argc, char** argv)
216{
217 int c, ret = -1;
218 std::string _deviceFile, _activationFile, _devicekeyFile;
219
220 const char** files[] = {&devicekeyFile, &deviceFile, &activationFile};
221 int verbose = gourou::DRMProcessor::getLogLevel();
222
223 while (1) {
224int option_index = 0;
225static struct option long_options[] = {
226 {"adept-directory", required_argument, 0, 'D' },
227 {"device-file", required_argument, 0, 'd' },
228 {"activation-file", required_argument, 0, 'a' },
229 {"device-key-file", required_argument, 0, 'k' },
230 {"output-dir", required_argument, 0, 'O' },
231 {"output-file", required_argument, 0, 'o' },
232 {"acsm-file", required_argument, 0, 'f' },
233 {"export-private-key",no_argument, 0, 'e' },
234 {"resume", no_argument, 0, 'r' },
235 {"verbose", no_argument, 0, 'v' },
236 {"version", no_argument, 0, 'V' },
237 {"help", no_argument, 0, 'h' },
238 {0, 0, 0, 0 }
239};
240
241c = getopt_long(argc, argv, "D:d:a:k:O:o:f:ervVh",
242 long_options, &option_index);
243if (c == -1)
244 break;
245
246switch (c) {
247case 'D':
248 _deviceFile = std::string(optarg) + "/device.xml";
249 _activationFile = std::string(optarg) + "/activation.xml";
250 _devicekeyFile = std::string(optarg) + "/devicesalt";
251 deviceFile = _deviceFile.c_str();
252 activationFile = _activationFile.c_str();
253 devicekeyFile = _devicekeyFile.c_str();
254 break;
255case 'd':
256 deviceFile = optarg;
257 break;
258case 'a':
259 activationFile = optarg;
260 break;
261case 'k':
262 devicekeyFile = optarg;
263 break;
264case 'f':
265 acsmFile = optarg;
266 break;
267case 'O':
268 outputDir = optarg;
269 break;
270case 'o':
271 outputFile = optarg;
272 break;
273case 'e':
274 exportPrivateKey = true;
275 break;
276case 'r':
277 resume = true;
278 break;
279case 'v':
280 verbose++;
281 break;
282case 'V':
283 version();
284 return 0;
285case 'h':
286 usage(argv[0]);
287 return 0;
288default:
289 usage(argv[0]);
290 return -1;
291}
292 }
293
294 gourou::DRMProcessor::setLogLevel(verbose);
295
296 if (optind == argc-1)
297acsmFile = argv[optind];
298
299 if ((!acsmFile && !exportPrivateKey) || (outputDir && !outputDir[0]) ||
300(outputFile && !outputFile[0]))
301 {
302usage(argv[0]);
303return -1;
304 }
305
306 ACSMDownloader downloader;
307
308 int i;
309 bool hasErrors = false;
310 const char* orig;
311 for (i=0; i<(int)ARRAY_SIZE(files); i++)
312 {
313orig = *files[i];
314*files[i] = findFile(*files[i]);
315if (!*files[i])
316{
317 std::cout << "Error : " << orig << " doesn't exists, did you activate your device ?" << std::endl;
318 ret = -1;
319 hasErrors = true;
320}
321 }
322
323 if (hasErrors)
324goto end;
325
326 if (exportPrivateKey)
327 {
328if (acsmFile)
329{
330 usage(argv[0]);
331 return -1;
332}
333 }
334 else
335 {
336if (!fileExists(acsmFile))
337{
338 std::cout << "Error : " << acsmFile << " doesn't exists" << std::endl;
339 ret = -1;
340 goto end;
341}
342 }
343
344 ret = downloader.run();
345
346end:
347 for (i=0; i<(int)ARRAY_SIZE(files); i++)
348 {
349if (*files[i])
350 free((void*)*files[i]);
351 }
352
353 return ret;
354}

Archive Download this file