No-op; Fix GPL address and mention libdcp by name.
[libdcp.git] / src / util.cc
1 /*
2     Copyright (C) 2012-2014 Carl Hetherington <cth@carlh.net>
3
4     This file is part of libdcp.
5
6     libdcp is free software; you can redistribute it and/or modify
7     it under the terms of the GNU General Public License as published by
8     the Free Software Foundation; either version 2 of the License, or
9     (at your option) any later version.
10
11     libdcp is distributed in the hope that it will be useful,
12     but WITHOUT ANY WARRANTY; without even the implied warranty of
13     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14     GNU General Public License for more details.
15
16     You should have received a copy of the GNU General Public License
17     along with libdcp.  If not, see <http://www.gnu.org/licenses/>.
18
19 */
20
21 /** @file  src/util.cc
22  *  @brief Utility methods.
23  */
24
25 #include "util.h"
26 #include "exceptions.h"
27 #include "types.h"
28 #include "certificate.h"
29 #include "openjpeg_image.h"
30 #include "dcp_assert.h"
31 #include "compose.hpp"
32 #include "KM_util.h"
33 #include "KM_fileio.h"
34 #include "AS_DCP.h"
35 #include <xmlsec/xmldsig.h>
36 #include <xmlsec/dl.h>
37 #include <xmlsec/app.h>
38 #include <xmlsec/crypto.h>
39 #include <libxml++/nodes/element.h>
40 #include <libxml++/document.h>
41 #include <openssl/sha.h>
42 #include <boost/filesystem.hpp>
43 #include <boost/algorithm/string.hpp>
44 #include <stdexcept>
45 #include <sstream>
46 #include <iostream>
47 #include <iomanip>
48
49 using std::string;
50 using std::wstring;
51 using std::cout;
52 using std::stringstream;
53 using std::min;
54 using std::max;
55 using std::list;
56 using std::setw;
57 using std::setfill;
58 using std::ostream;
59 using boost::shared_ptr;
60 using boost::shared_array;
61 using boost::optional;
62 using boost::function;
63 using boost::algorithm::trim;
64 using namespace dcp;
65
66 /** Create a UUID.
67  *  @return UUID.
68  */
69 string
70 dcp::make_uuid ()
71 {
72         char buffer[64];
73         Kumu::UUID id;
74         Kumu::GenRandomValue (id);
75         id.EncodeHex (buffer, 64);
76         return string (buffer);
77 }
78
79
80 /** Create a digest for a file.
81  *  @param filename File name.
82  *  @param progress Optional progress reporting function.  The function will be called
83  *  with a progress value between 0 and 1.
84  *  @return Digest.
85  */
86 string
87 dcp::make_digest (boost::filesystem::path filename, function<void (float)> progress)
88 {
89         Kumu::FileReader reader;
90         Kumu::Result_t r = reader.OpenRead (filename.string().c_str ());
91         if (ASDCP_FAILURE (r)) {
92                 boost::throw_exception (FileError ("could not open file to compute digest", filename, r));
93         }
94
95         SHA_CTX sha;
96         SHA1_Init (&sha);
97
98         int const buffer_size = 65536;
99         Kumu::ByteString read_buffer (buffer_size);
100
101         Kumu::fsize_t done = 0;
102         Kumu::fsize_t const size = reader.Size ();
103         while (1) {
104                 ui32_t read = 0;
105                 Kumu::Result_t r = reader.Read (read_buffer.Data(), read_buffer.Capacity(), &read);
106
107                 if (r == Kumu::RESULT_ENDOFFILE) {
108                         break;
109                 } else if (ASDCP_FAILURE (r)) {
110                         boost::throw_exception (FileError ("could not read file to compute digest", filename, r));
111                 }
112
113                 SHA1_Update (&sha, read_buffer.Data(), read);
114
115                 if (progress) {
116                         progress (float (done) / size);
117                         done += read;
118                 }
119         }
120
121         byte_t byte_buffer[SHA_DIGEST_LENGTH];
122         SHA1_Final (byte_buffer, &sha);
123
124         char digest[64];
125         return Kumu::base64encode (byte_buffer, SHA_DIGEST_LENGTH, digest, 64);
126 }
127
128 /** Convert a content kind to a string which can be used in a
129  *  &lt;ContentKind&gt; node.
130  *  @param kind ContentKind.
131  *  @return string.
132  */
133 string
134 dcp::content_kind_to_string (ContentKind kind)
135 {
136         switch (kind) {
137         case FEATURE:
138                 return "feature";
139         case SHORT:
140                 return "short";
141         case TRAILER:
142                 return "trailer";
143         case TEST:
144                 return "test";
145         case TRANSITIONAL:
146                 return "transitional";
147         case RATING:
148                 return "rating";
149         case TEASER:
150                 return "teaser";
151         case POLICY:
152                 return "policy";
153         case PUBLIC_SERVICE_ANNOUNCEMENT:
154                 return "psa";
155         case ADVERTISEMENT:
156                 return "advertisement";
157         }
158
159         DCP_ASSERT (false);
160 }
161
162 /** Convert a string from a &lt;ContentKind&gt; node to a libdcp ContentKind.
163  *  Reasonably tolerant about varying case.
164  *  @param kind Content kind string.
165  *  @return libdcp ContentKind.
166  */
167 dcp::ContentKind
168 dcp::content_kind_from_string (string kind)
169 {
170         transform (kind.begin(), kind.end(), kind.begin(), ::tolower);
171
172         if (kind == "feature") {
173                 return FEATURE;
174         } else if (kind == "short") {
175                 return SHORT;
176         } else if (kind == "trailer") {
177                 return TRAILER;
178         } else if (kind == "test") {
179                 return TEST;
180         } else if (kind == "transitional") {
181                 return TRANSITIONAL;
182         } else if (kind == "rating") {
183                 return RATING;
184         } else if (kind == "teaser") {
185                 return TEASER;
186         } else if (kind == "policy") {
187                 return POLICY;
188         } else if (kind == "psa") {
189                 return PUBLIC_SERVICE_ANNOUNCEMENT;
190         } else if (kind == "advertisement") {
191                 return ADVERTISEMENT;
192         }
193
194         DCP_ASSERT (false);
195 }
196
197 /** @param s A string.
198  *  @return true if the string contains only space, newline or tab characters, or is empty.
199  */
200 bool
201 dcp::empty_or_white_space (string s)
202 {
203         for (size_t i = 0; i < s.length(); ++i) {
204                 if (s[i] != ' ' && s[i] != '\n' && s[i] != '\t') {
205                         return false;
206                 }
207         }
208
209         return true;
210 }
211
212 /** Set up various bits that the library needs.  Should be called one
213  *  by client applications.
214  */
215 void
216 dcp::init ()
217 {
218         if (xmlSecInit() < 0) {
219                 throw MiscError ("could not initialise xmlsec");
220         }
221
222 #ifdef XMLSEC_CRYPTO_DYNAMIC_LOADING
223         if (xmlSecCryptoDLLoadLibrary(BAD_CAST XMLSEC_CRYPTO) < 0) {
224                 throw MiscError ("unable to load default xmlsec-crypto library");
225         }
226 #endif
227
228         if (xmlSecCryptoAppInit(0) < 0) {
229                 throw MiscError ("could not initialise crypto");
230         }
231
232         if (xmlSecCryptoInit() < 0) {
233                 throw MiscError ("could not initialise xmlsec-crypto");
234         }
235 }
236
237 bool dcp::operator== (dcp::Size const & a, dcp::Size const & b)
238 {
239         return (a.width == b.width && a.height == b.height);
240 }
241
242 bool dcp::operator!= (dcp::Size const & a, dcp::Size const & b)
243 {
244         return !(a == b);
245 }
246
247 ostream& dcp::operator<< (ostream& s, dcp::Size const & a)
248 {
249         s << a.width << "x" << a.height;
250         return s;
251 }
252
253 /** Decode a base64 string.  The base64 decode routine in KM_util.cpp
254  *  gives different values to both this and the command-line base64
255  *  for some inputs.  Not sure why.
256  *
257  *  @param in base64-encoded string.
258  *  @param out Output buffer.
259  *  @param out_length Length of output buffer.
260  *  @return Number of characters written to the output buffer.
261  */
262 int
263 dcp::base64_decode (string const & in, unsigned char* out, int out_length)
264 {
265         BIO* b64 = BIO_new (BIO_f_base64 ());
266
267         /* This means the input should have no newlines */
268         BIO_set_flags (b64, BIO_FLAGS_BASE64_NO_NL);
269
270         /* Copy our input string, removing newlines */
271         char in_buffer[in.size() + 1];
272         char* p = in_buffer;
273         for (size_t i = 0; i < in.size(); ++i) {
274                 if (in[i] != '\n' && in[i] != '\r') {
275                         *p++ = in[i];
276                 }
277         }
278
279         BIO* bmem = BIO_new_mem_buf (in_buffer, p - in_buffer);
280         bmem = BIO_push (b64, bmem);
281         int const N = BIO_read (bmem, out, out_length);
282         BIO_free_all (bmem);
283
284         return N;
285 }
286
287 /** @param p Path to open.
288  *  @param t mode flags, as for fopen(3).
289  *  @return FILE pointer or 0 on error.
290  *
291  *  Apparently there is no way to create an ofstream using a UTF-8
292  *  filename under Windows.  We are hence reduced to using fopen
293  *  with this wrapper.
294  */
295 FILE *
296 dcp::fopen_boost (boost::filesystem::path p, string t)
297 {
298 #ifdef LIBDCP_WINDOWS
299         wstring w (t.begin(), t.end());
300         /* c_str() here should give a UTF-16 string */
301         return _wfopen (p.c_str(), w.c_str ());
302 #else
303         return fopen (p.c_str(), t.c_str ());
304 #endif
305 }
306
307 optional<boost::filesystem::path>
308 dcp::relative_to_root (boost::filesystem::path root, boost::filesystem::path file)
309 {
310         boost::filesystem::path::const_iterator i = root.begin ();
311         boost::filesystem::path::const_iterator j = file.begin ();
312
313         while (i != root.end() && j != file.end() && *i == *j) {
314                 ++i;
315                 ++j;
316         }
317
318         if (i != root.end ()) {
319                 return optional<boost::filesystem::path> ();
320         }
321
322         boost::filesystem::path rel;
323         while (j != file.end ()) {
324                 rel /= *j++;
325         }
326
327         return rel;
328 }
329
330 bool
331 dcp::ids_equal (string a, string b)
332 {
333         transform (a.begin(), a.end(), a.begin(), ::tolower);
334         transform (b.begin(), b.end(), b.begin(), ::tolower);
335         trim (a);
336         trim (b);
337         return a == b;
338 }
339
340 string
341 dcp::file_to_string (boost::filesystem::path p, uintmax_t max_length)
342 {
343         uintmax_t len = boost::filesystem::file_size (p);
344         if (len > max_length) {
345                 throw MiscError ("Unexpectedly long file");
346         }
347
348         FILE* f = fopen_boost (p, "r");
349         if (!f) {
350                 throw FileError ("could not open file", p, errno);
351         }
352
353         char* c = new char[len];
354         /* This may read less than `len' if we are on Windows and we have CRLF in the file */
355         int const N = fread (c, 1, len, f);
356         fclose (f);
357
358         string s (c, N);
359         delete[] c;
360
361         return s;
362 }
363
364 /** @param key RSA private key in PEM format (optionally with -----BEGIN... / -----END...)
365  *  @return SHA1 fingerprint of key
366  */
367 string
368 dcp::private_key_fingerprint (string key)
369 {
370         boost::replace_all (key, "-----BEGIN RSA PRIVATE KEY-----\n", "");
371         boost::replace_all (key, "\n-----END RSA PRIVATE KEY-----\n", "");
372
373         unsigned char buffer[4096];
374         int const N = base64_decode (key, buffer, sizeof (buffer));
375
376         SHA_CTX sha;
377         SHA1_Init (&sha);
378         SHA1_Update (&sha, buffer, N);
379         uint8_t digest[20];
380         SHA1_Final (digest, &sha);
381
382         char digest_base64[64];
383         return Kumu::base64encode (digest, 20, digest_base64, 64);
384 }
385
386 xmlpp::Node *
387 dcp::find_child (xmlpp::Node const * node, string name)
388 {
389         xmlpp::Node::NodeList c = node->get_children ();
390         xmlpp::Node::NodeList::iterator i = c.begin();
391         while (i != c.end() && (*i)->get_name() != name) {
392                 ++i;
393         }
394
395         DCP_ASSERT (i != c.end ());
396         return *i;
397 }
398
399 string
400 dcp::remove_urn_uuid (string raw)
401 {
402         DCP_ASSERT (raw.substr(0, 9) == "urn:uuid:");
403         return raw.substr (9);
404 }