Tidy handling of _raw_xml.
authorCarl Hetherington <cth@carlh.net>
Thu, 27 May 2021 12:21:35 +0000 (14:21 +0200)
committerCarl Hetherington <cth@carlh.net>
Thu, 27 May 2021 12:25:17 +0000 (14:25 +0200)
Before this if we tried to get the XML of an encrypted asset we
would just get an empty string.  Now we get a boost::none which
means the verifier can avoid trying to check details of the XML
(and instead raise a warning that you are trying to verify data
that it cannot decrypt).

src/interop_subtitle_asset.cc
src/smpte_subtitle_asset.cc
src/subtitle_asset.h
src/verify.cc
src/verify.h

index 453cad8be7ce0aff053c2cad014b3a5817a74f4e..a7be5d1a4eecab08c449fe0a464b899d23b5fe56 100644 (file)
@@ -199,9 +199,9 @@ InteropSubtitleAsset::write (boost::filesystem::path p) const
                throw FileError ("Could not open file for writing", p, -1);
        }
 
-       auto const s = xml_as_string ();
+       _raw_xml = xml_as_string ();
        /* length() here gives bytes not characters */
-       fwrite (s.c_str(), 1, s.length(), f);
+       fwrite (_raw_xml->c_str(), 1, _raw_xml->length(), f);
        fclose (f);
 
        _file = p;
index dc8acf51db120e1413a473e6a9cbb0add587fa6a..d0fbc0eb1ea140720c91e844a50d4d99df0fd203 100644 (file)
@@ -103,8 +103,10 @@ SMPTESubtitleAsset::SMPTESubtitleAsset (boost::filesystem::path file)
                _id = read_writer_info (info);
                if (!_key_id) {
                        /* Not encrypted; read it in now */
-                       reader->ReadTimedTextResource (_raw_xml);
-                       xml->read_string (_raw_xml);
+                       string xml_string;
+                       reader->ReadTimedTextResource (xml_string);
+                       _raw_xml = xml_string;
+                       xml->read_string (xml_string);
                        parse_xml (xml);
                        read_mxf_descriptor (reader);
                        read_mxf_resources (reader, make_shared<DecryptionContext>(optional<Key>(), Standard::SMPTE));
@@ -307,9 +309,11 @@ SMPTESubtitleAsset::set_key (Key key)
        }
 
        auto dec = make_shared<DecryptionContext>(key, Standard::SMPTE);
-       reader->ReadTimedTextResource (_raw_xml, dec->context(), dec->hmac());
+       string xml_string;
+       reader->ReadTimedTextResource (xml_string, dec->context(), dec->hmac());
+       _raw_xml = xml_string;
        auto xml = make_shared<cxml::Document>("SubtitleReel");
-       xml->read_string (_raw_xml);
+       xml->read_string (xml_string);
        parse_xml (xml);
        read_mxf_resources (reader, dec);
 }
@@ -431,7 +435,9 @@ SMPTESubtitleAsset::write (boost::filesystem::path p) const
                boost::throw_exception (FileError ("could not open subtitle MXF for writing", p.string(), r));
        }
 
-       r = writer.WriteTimedTextResource (xml_as_string (), enc.context(), enc.hmac());
+       _raw_xml = xml_as_string ();
+
+       r = writer.WriteTimedTextResource (*_raw_xml, enc.context(), enc.hmac());
        if (ASDCP_FAILURE (r)) {
                boost::throw_exception (MXFFileError ("could not write XML to timed text resource", p.string(), r));
        }
@@ -569,3 +575,4 @@ SMPTESubtitleAsset::add (shared_ptr<Subtitle> s)
        SubtitleAsset::add (s);
        _intrinsic_duration = latest_subtitle_out().as_editable_units_ceil(_edit_rate.numerator / _edit_rate.denominator);
 }
+
index 2c542b6e37145b8b65a56501454b307d9f36f867..8ec57fce99258acbf3dbc0f3b9c8cffebca23378 100644 (file)
@@ -120,7 +120,11 @@ public:
 
        virtual int time_code_rate () const = 0;
 
-       std::string raw_xml () const {
+       /** @return Raw XML loaded from, or written to, an on-disk asset, or boost::none if
+        *  - this object was not created from an existing on-disk asset and has not been written to one, or
+        *  - this asset is encrypted and no key is available.
+        */
+       virtual boost::optional<std::string> raw_xml () const {
                return _raw_xml;
        }
 
@@ -193,8 +197,8 @@ protected:
        /** TTF font data that we need */
        std::vector<Font> _fonts;
 
-       /** The raw XML data that we read from our asset; useful for validation */
-       std::string _raw_xml;
+       /** The raw XML data that we read from or wrote to our asset; useful for validation */
+       mutable boost::optional<std::string> _raw_xml;
 
 private:
        friend struct ::pull_fonts_test1;
index 623794cc9c276684bc00c5f9a97562726d4d0841..40f7c0b691cf8290ab549cb4d1e885c39745c146 100644 (file)
@@ -728,7 +728,11 @@ verify_subtitle_asset (
        /* Note: we must not use SubtitleAsset::xml_as_string() here as that will mean the data on disk
         * gets passed through libdcp which may clean up and therefore hide errors.
         */
-       validate_xml (asset->raw_xml(), xsd_dtd_directory, notes);
+       if (asset->raw_xml()) {
+               validate_xml (asset->raw_xml().get(), xsd_dtd_directory, notes);
+       } else {
+               notes.push_back ({VerificationNote::Type::WARNING, VerificationNote::Code::MISSED_CHECK_OF_ENCRYPTED});
+       }
 
        auto smpte = dynamic_pointer_cast<const SMPTESubtitleAsset>(asset);
        if (smpte) {
@@ -752,16 +756,20 @@ verify_closed_caption_asset (
        /* Note: we must not use SubtitleAsset::xml_as_string() here as that will mean the data on disk
         * gets passed through libdcp which may clean up and therefore hide errors.
         */
-       validate_xml (asset->raw_xml(), xsd_dtd_directory, notes);
+       auto raw_xml = asset->raw_xml();
+       if (raw_xml) {
+               validate_xml (*raw_xml, xsd_dtd_directory, notes);
+               if (raw_xml->size() > 256 * 1024) {
+                       notes.push_back ({VerificationNote::Type::BV21_ERROR, VerificationNote::Code::INVALID_CLOSED_CAPTION_XML_SIZE_IN_BYTES, raw_convert<string>(raw_xml->size()), *asset->file()});
+               }
+       } else {
+               notes.push_back ({VerificationNote::Type::WARNING, VerificationNote::Code::MISSED_CHECK_OF_ENCRYPTED});
+       }
 
        auto smpte = dynamic_pointer_cast<const SMPTESubtitleAsset>(asset);
        if (smpte) {
                verify_smpte_timed_text_asset (smpte, reel_asset_duration, notes);
        }
-
-       if (asset->raw_xml().size() > 256 * 1024) {
-               notes.push_back ({VerificationNote::Type::BV21_ERROR, VerificationNote::Code::INVALID_CLOSED_CAPTION_XML_SIZE_IN_BYTES, raw_convert<string>(asset->raw_xml().size()), *asset->file()});
-       }
 }
 
 
@@ -772,7 +780,7 @@ verify_text_timing (
        int edit_rate,
        vector<VerificationNote>& notes,
        std::function<bool (shared_ptr<Reel>)> check,
-       std::function<string (shared_ptr<Reel>)> xml,
+       std::function<optional<string> (shared_ptr<Reel>)> xml,
        std::function<int64_t (shared_ptr<Reel>)> duration
        )
 {
@@ -823,6 +831,12 @@ verify_text_timing (
                        continue;
                }
 
+               auto reel_xml = xml(reels[i]);
+               if (!reel_xml) {
+                       notes.push_back ({VerificationNote::Type::WARNING, VerificationNote::Code::MISSED_CHECK_OF_ENCRYPTED});
+                       continue;
+               }
+
                /* We need to look at <Subtitle> instances in the XML being checked, so we can't use the subtitles
                 * read in by libdcp's parser.
                 */
@@ -832,7 +846,7 @@ verify_text_timing (
                optional<Time> start_time;
                try {
                        doc = make_shared<cxml::Document>("SubtitleReel");
-                       doc->read_string (xml(reels[i]));
+                       doc->read_string (*reel_xml);
                        tcr = doc->number_child<int>("TimeCodeRate");
                        auto start_time_string = doc->optional_string_child("StartTime");
                        if (start_time_string) {
@@ -840,7 +854,7 @@ verify_text_timing (
                        }
                } catch (...) {
                        doc = make_shared<cxml::Document>("DCSubtitle");
-                       doc->read_string (xml(reels[i]));
+                       doc->read_string (*reel_xml);
                }
                parse (doc, tcr, start_time, edit_rate, i == 0);
                auto end = reel_offset + duration(reels[i]);
@@ -1605,6 +1619,8 @@ dcp::note_to_string (VerificationNote note)
                DCP_ASSERT (parts.size() == 2);
                return String::compose("The reel duration of some timed text (%1) is not the same as the ContainerDuration of its MXF (%2).", parts[0], parts[1]);
        }
+       case VerificationNote::Code::MISSED_CHECK_OF_ENCRYPTED:
+               return "Some aspect of this DCP could not be checked because it is encrypted.";
        }
 
        return "";
index 372da7644f8f6d08cb5242eb66ac0c0a9542b7e7..c0491422301c33c3142b0186f4142c2941907b39 100644 (file)
@@ -379,6 +379,8 @@ public:
                 *  file contains the asset filename
                 */
                MISMATCHED_TIMED_TEXT_DURATION,
+               /** Something could not be verified because content is encrypted and no key is available */
+               MISSED_CHECK_OF_ENCRYPTED,
        };
 
        VerificationNote (Type type, Code code)