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