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;
49static bool notify = true;
50
51
52class ACSMDownloader
53{
54public:
55
56 int run()
57 {
58int ret = 0;
59try
60{
61 gourou::DRMProcessor processor(&client, deviceFile, activationFile, devicekeyFile);
62 gourou::User* user = processor.getUser();
63
64 if (exportPrivateKey)
65 {
66std::string filename;
67if (!outputFile)
68 filename = std::string("Adobe_PrivateLicenseKey--") + user->getUsername() + ".der";
69else
70 filename = outputFile;
71
72if (outputDir)
73{
74 if (!fileExists(outputDir))
75mkpath(outputDir);
76
77 filename = std::string(outputDir) + "/" + filename;
78}
79
80processor.exportPrivateLicenseKey(filename);
81
82std::cout << "Private license key exported to " << filename << std::endl;
83 }
84 else
85 {
86gourou::FulfillmentItem* item = processor.fulfill(acsmFile, notify);
87
88std::string filename;
89if (!outputFile)
90{
91 filename = item->getMetadata("title");
92 if (filename == "")
93filename = "output";
94 else
95 {
96// Remove invalid characters
97std::replace(filename.begin(), filename.end(), '/', '_');
98 }
99}
100else
101 filename = outputFile;
102
103if (outputDir)
104{
105 if (!fileExists(outputDir))
106mkpath(outputDir);
107
108 filename = std::string(outputDir) + "/" + filename;
109}
110
111gourou::DRMProcessor::ITEM_TYPE type = processor.download(item, filename, resume);
112
113if (!outputFile)
114{
115 std::string finalName = filename;
116 if (type == gourou::DRMProcessor::ITEM_TYPE::PDF)
117finalName += ".pdf";
118 else
119finalName += ".epub";
120 rename(filename.c_str(), finalName.c_str());
121 filename = finalName;
122}
123std::cout << "Created " << filename << std::endl;
124
125serializeLoanToken(item);
126 }
127} catch(std::exception& e)
128{
129 std::cout << e.what() << std::endl;
130 ret = 1;
131}
132
133return ret;
134 }
135
136 void serializeLoanToken(gourou::FulfillmentItem* item)
137 {
138gourou::LoanToken* token = item->getLoanToken();
139
140// No loan token available
141if (!token)
142 return;
143
144pugi::xml_document doc;
145
146pugi::xml_node decl = doc.append_child(pugi::node_declaration);
147decl.append_attribute("version") = "1.0";
148
149pugi::xml_node root = doc.append_child("loanToken");
150gourou::appendTextElem(root, "id", (*token)["id"]);
151gourou::appendTextElem(root, "operatorURL", (*token)["operatorURL"]);
152gourou::appendTextElem(root, "validity", (*token)["validity"]);
153gourou::appendTextElem(root, "name", item->getMetadata("title"));
154
155char * activationDir = strdup(deviceFile);
156activationDir = dirname(activationDir);
157
158gourou::StringXMLWriter xmlWriter;
159doc.save(xmlWriter, " ");
160std::string xmlStr = xmlWriter.getResult();
161
162// Use first bytes of SHA1(id) as filename
163unsigned char sha1[gourou::SHA1_LEN];
164client.digest("SHA1", (unsigned char*)(*token)["id"].c_str(), (*token)["id"].size(), sha1);
165gourou::ByteArray tmp(sha1, sizeof(sha1));
166std::string filenameHex = tmp.toHex();
167std::string filename(filenameHex.c_str(), ID_HASH_SIZE);
168std::string fullPath = std::string(activationDir);
169fullPath += std::string ("/") + std::string(LOANS_DIR);
170mkpath(fullPath.c_str());
171fullPath += filename + std::string(".xml");
172gourou::writeFile(fullPath, xmlStr);
173
174std::cout << "Loan token serialized into " << fullPath << std::endl;
175
176free(activationDir);
177 }
178
179private:
180 DRMProcessorClientImpl client;
181};
182
183
184static void usage(const char* cmd)
185{
186 std::cout << basename((char*)cmd) << " download EPUB file from ACSM request file" << std::endl << std::endl;
187 std::cout << "Usage: " << basename((char*)cmd) << " [OPTIONS] file.acsm" << std::endl << std::endl;
188 std::cout << "Global Options:" << std::endl;
189 std::cout << " " << "-O|--output-dir" << "\t" << "Optional output directory were to put result (default ./)" << std::endl;
190 std::cout << " " << "-o|--output-file" << "\t" << "Optional output filename (default <title.(epub|pdf|der)>)" << std::endl;
191 std::cout << " " << "-f|--acsm-file" << "\t" << "Backward compatibility: ACSM request file for epub download" << std::endl;
192 std::cout << " " << "-e|--export-private-key"<< "\t" << "Export private key in DER format" << std::endl;
193 std::cout << " " << "-r|--resume" << "\t\t" << "Try to resume download (in case of previous failure)" << std::endl;
194 std::cout << " " << "-N|--no-notify" << "\t\t" << "Don't notify server, even if requested" << std::endl;
195 std::cout << " " << "-v|--verbose" << "\t\t" << "Increase verbosity, can be set multiple times" << std::endl;
196 std::cout << " " << "-V|--version" << "\t\t" << "Display libgourou version" << std::endl;
197 std::cout << " " << "-h|--help" << "\t\t" << "This help" << std::endl;
198
199 std::cout << "ADEPT Options:" << std::endl;
200 std::cout << " " << "-D|--adept-directory" << "\t" << ".adept directory that must contains device.xml, activation.xml and devicesalt" << std::endl;
201 std::cout << " " << "-d|--device-file" << "\t" << "device.xml file from eReader" << std::endl;
202 std::cout << " " << "-a|--activation-file" << "\t" << "activation.xml file from eReader" << std::endl;
203 std::cout << " " << "-k|--device-key-file" << "\t" << "private device key file (eg devicesalt/devkey.bin) from eReader" << std::endl;
204
205 std::cout << std::endl;
206
207 std::cout << "Environment:" << std::endl;
208 std::cout << "Device file, activation file and device key file are optionals. If not set, they are looked into :" << std::endl << std::endl;
209 std::cout << " * $ADEPT_DIR environment variable" << std::endl;
210 std::cout << " * /home/<user>/.config/adept" << std::endl;
211 std::cout << " * Current directory" << std::endl;
212 std::cout << " * .adept" << std::endl;
213 std::cout << " * adobe-digital-editions directory" << std::endl;
214 std::cout << " * .adobe-digital-editions directory" << std::endl;
215}
216
217int main(int argc, char** argv)
218{
219 int c, ret = -1;
220 std::string _deviceFile, _activationFile, _devicekeyFile;
221
222 const char** files[] = {&devicekeyFile, &deviceFile, &activationFile};
223 int verbose = gourou::DRMProcessor::getLogLevel();
224
225 while (1) {
226int option_index = 0;
227static struct option long_options[] = {
228 {"adept-directory", required_argument, 0, 'D' },
229 {"device-file", required_argument, 0, 'd' },
230 {"activation-file", required_argument, 0, 'a' },
231 {"device-key-file", required_argument, 0, 'k' },
232 {"output-dir", required_argument, 0, 'O' },
233 {"output-file", required_argument, 0, 'o' },
234 {"acsm-file", required_argument, 0, 'f' },
235 {"export-private-key",no_argument, 0, 'e' },
236 {"resume", no_argument, 0, 'r' },
237 {"no-notify", no_argument, 0, 'N' },
238 {"verbose", no_argument, 0, 'v' },
239 {"version", no_argument, 0, 'V' },
240 {"help", no_argument, 0, 'h' },
241 {0, 0, 0, 0 }
242};
243
244c = getopt_long(argc, argv, "D:d:a:k:O:o:f:erNvVh",
245 long_options, &option_index);
246if (c == -1)
247 break;
248
249switch (c) {
250case 'D':
251 _deviceFile = std::string(optarg) + "/device.xml";
252 _activationFile = std::string(optarg) + "/activation.xml";
253 _devicekeyFile = std::string(optarg) + "/devicesalt";
254 deviceFile = _deviceFile.c_str();
255 activationFile = _activationFile.c_str();
256 devicekeyFile = _devicekeyFile.c_str();
257 break;
258case 'd':
259 deviceFile = optarg;
260 break;
261case 'a':
262 activationFile = optarg;
263 break;
264case 'k':
265 devicekeyFile = optarg;
266 break;
267case 'f':
268 acsmFile = optarg;
269 break;
270case 'O':
271 outputDir = optarg;
272 break;
273case 'o':
274 outputFile = optarg;
275 break;
276case 'e':
277 exportPrivateKey = true;
278 break;
279case 'r':
280 resume = true;
281 break;
282case 'N':
283 notify = false;
284 break;
285case 'v':
286 verbose++;
287 break;
288case 'V':
289 version();
290 return 0;
291case 'h':
292 usage(argv[0]);
293 return 0;
294default:
295 usage(argv[0]);
296 return -1;
297}
298 }
299
300 gourou::DRMProcessor::setLogLevel(verbose);
301
302 if (optind == argc-1)
303acsmFile = argv[optind];
304
305 if ((!acsmFile && !exportPrivateKey) || (outputDir && !outputDir[0]) ||
306(outputFile && !outputFile[0]))
307 {
308usage(argv[0]);
309return -1;
310 }
311
312 ACSMDownloader downloader;
313
314 int i;
315 bool hasErrors = false;
316 const char* orig;
317 for (i=0; i<(int)ARRAY_SIZE(files); i++)
318 {
319orig = *files[i];
320*files[i] = findFile(*files[i]);
321if (!*files[i])
322{
323 std::cout << "Error : " << orig << " doesn't exists, did you activate your device ?" << std::endl;
324 ret = -1;
325 hasErrors = true;
326}
327 }
328
329 if (hasErrors)
330goto end;
331
332 if (exportPrivateKey)
333 {
334if (acsmFile)
335{
336 usage(argv[0]);
337 return -1;
338}
339 }
340 else
341 {
342if (!fileExists(acsmFile))
343{
344 std::cout << "Error : " << acsmFile << " doesn't exists" << std::endl;
345 ret = -1;
346 goto end;
347}
348 }
349
350 ret = downloader.run();
351
352end:
353 for (i=0; i<(int)ARRAY_SIZE(files); i++)
354 {
355if (*files[i])
356 free((void*)*files[i]);
357 }
358
359 return ret;
360}

Archive Download this file