1313import org .w3c .dom .Element ;
1414
1515import javax .annotation .Nonnull ;
16+ import javax .xml .XMLConstants ;
1617import javax .xml .parsers .DocumentBuilder ;
1718import javax .xml .parsers .DocumentBuilderFactory ;
19+ import javax .xml .parsers .ParserConfigurationException ;
1820import javax .xml .transform .OutputKeys ;
1921import javax .xml .transform .Transformer ;
22+ import javax .xml .transform .TransformerException ;
2023import javax .xml .transform .TransformerFactory ;
2124import javax .xml .transform .dom .DOMSource ;
2225import javax .xml .transform .stream .StreamResult ;
4548
4649/**
4750 * Class to generate DASH manifests from YouTube OTF, progressive and ended/post-live-DVR streams.
48- *
49- * <p>
5051 * It relies on external classes from the {@link org.w3c.dom} and {@link javax.xml} packages.
51- * </p>
5252 */
5353public final class YoutubeDashManifestCreator {
5454
@@ -305,7 +305,7 @@ public static String createDashManifestFromOtfStreamingUrl(
305305 SEGMENTS_DURATION .clear ();
306306 DURATION_REPETITIONS .clear ();
307307
308- return buildResult (otfBaseStreamingUrl , document , GENERATED_OTF_MANIFESTS );
308+ return buildAndCacheResult (otfBaseStreamingUrl , document , GENERATED_OTF_MANIFESTS );
309309 }
310310
311311 /**
@@ -441,7 +441,7 @@ public static String createDashManifestFromPostLiveStreamDvrStreamingUrl(
441441 generateSegmentTimelineElement (document );
442442 generateSegmentElementForPostLiveDvrStreams (document , targetDurationSec , segmentCount );
443443
444- return buildResult (postLiveStreamDvrStreamingUrl , document ,
444+ return buildAndCacheResult (postLiveStreamDvrStreamingUrl , document ,
445445 GENERATED_POST_LIVE_DVR_STREAMS_MANIFESTS );
446446 }
447447
@@ -533,7 +533,7 @@ public static String createDashManifestFromProgressiveStreamingUrl(
533533 generateSegmentBaseElement (document , itagItem );
534534 generateInitializationElement (document , itagItem );
535535
536- return buildResult (progressiveStreamingBaseUrl , document ,
536+ return buildAndCacheResult (progressiveStreamingBaseUrl , document ,
537537 GENERATED_PROGRESSIVE_STREAMS_MANIFESTS );
538538 }
539539
@@ -841,13 +841,8 @@ private static Document generateDocumentAndMpdElement(@Nonnull final String[] se
841841 @ Nonnull final ItagItem itagItem ,
842842 final long durationSecondsFallback )
843843 throws YoutubeDashManifestCreationException {
844- final DocumentBuilderFactory documentBuilderFactory ;
845- final DocumentBuilder documentBuilder ;
846- final Document document ;
847844 try {
848- documentBuilderFactory = DocumentBuilderFactory .newInstance ();
849- documentBuilder = documentBuilderFactory .newDocumentBuilder ();
850- document = documentBuilder .newDocument ();
845+ final Document document = newDocument ();
851846
852847 final Element mpdElement = document .createElement ("MPD" );
853848 document .appendChild (mpdElement );
@@ -903,13 +898,13 @@ private static Document generateDocumentAndMpdElement(@Nonnull final String[] se
903898 final String durationSeconds = String .format (Locale .ENGLISH , "%.3f" , duration );
904899 mediaPresentationDurationAttribute .setValue ("PT" + durationSeconds + "S" );
905900 mpdElement .setAttributeNode (mediaPresentationDurationAttribute );
901+
902+ return document ;
906903 } catch (final Exception e ) {
907904 throw new YoutubeDashManifestCreationException (
908905 "Could not generate or append the MPD element of the DASH manifest to the "
909906 + "document" , e );
910907 }
911-
912- return document ;
913908 }
914909
915910 /**
@@ -1591,7 +1586,7 @@ private static void generateSegmentElementForPostLiveDvrStreams(
15911586 }
15921587
15931588 /**
1594- * Convert a DASH manifest {@link Document document} to a string.
1589+ * Convert a DASH manifest {@link Document document} to a string and cache it .
15951590 *
15961591 * @param originalBaseStreamingUrl the original base URL of the stream
15971592 * @param document the document to be converted
@@ -1604,59 +1599,92 @@ private static void generateSegmentElementForPostLiveDvrStreams(
16041599 * @throws YoutubeDashManifestCreationException if something goes wrong when converting the
16051600 * {@link Document document}
16061601 */
1607- private static String buildResult (
1602+ private static String buildAndCacheResult (
16081603 @ Nonnull final String originalBaseStreamingUrl ,
16091604 @ Nonnull final Document document ,
16101605 @ Nonnull final ManifestCreatorCache <String , String > manifestCreatorCache )
16111606 throws YoutubeDashManifestCreationException {
1607+
16121608 try {
1613- final StringWriter result = new StringWriter ();
1614- final TransformerFactory transformerFactory = TransformerFactory .newInstance ();
1615-
1616- final Transformer transformer = transformerFactory .newTransformer ();
1617- transformer .setOutputProperty (OutputKeys .VERSION , "1.0" );
1618- transformer .setOutputProperty (OutputKeys .ENCODING , "UTF-8" );
1619- transformer .setOutputProperty (OutputKeys .STANDALONE , "no" );
1620- transformer .transform (new DOMSource (document ), new StreamResult (result ));
1621- final String stringResult = result .toString ();
1622- manifestCreatorCache .put (originalBaseStreamingUrl , stringResult );
1623- return stringResult ;
1609+ final String documentXml = documentToXml (document );
1610+ manifestCreatorCache .put (originalBaseStreamingUrl , documentXml );
1611+ return documentXml ;
16241612 } catch (final Exception e ) {
16251613 throw new YoutubeDashManifestCreationException (
16261614 "Could not convert the DASH manifest generated to a string" , e );
16271615 }
16281616 }
16291617
16301618 /**
1631- * Get the number of cached OTF streams manifests.
1632- *
1619+ * Securing against XEE is done by passing {@code false} to {@link
1620+ * DocumentBuilderFactory#setExpandEntityReferences(boolean)}, also see
1621+ * <a href="https://github.com/ChuckerTeam/chucker/pull/201">ChuckerTeam/chucker#201</a>.
1622+ *
1623+ * @return an instance of document secured against XEE attacks, that should then be convertible
1624+ * to an XML string without security problems
1625+ * @see #documentToXml(Document) Use documentToXml to convert the created document to XML, which
1626+ * is also secured against XEE!
1627+ */
1628+ private static Document newDocument () throws ParserConfigurationException {
1629+ final DocumentBuilderFactory documentBuilderFactory
1630+ = DocumentBuilderFactory .newInstance ();
1631+ documentBuilderFactory .setExpandEntityReferences (false );
1632+
1633+ final DocumentBuilder documentBuilder = documentBuilderFactory .newDocumentBuilder ();
1634+ return documentBuilder .newDocument ();
1635+ }
1636+
1637+ /**
1638+ * Securing against XEE is done by setting {@link XMLConstants#FEATURE_SECURE_PROCESSING} to
1639+ * {@code true} in the {@link TransformerFactory}, also see
1640+ * <a href="https://github.com/ChuckerTeam/chucker/pull/201">ChuckerTeam/chucker#201</a>.
1641+ * The best way to do this would be setting the attributes {@link
1642+ * XMLConstants#ACCESS_EXTERNAL_DTD} and {@link XMLConstants#ACCESS_EXTERNAL_STYLESHEET}, but
1643+ * unfortunately the engine on Android does not support them.
1644+ *
1645+ * @param document the document to convert; must have been created using {@link #newDocument()}
1646+ * to properly prevent XEE attacks!
1647+ * @return the document converted to an XML string, making sure there can't be XEE attacks
1648+ */
1649+ private static String documentToXml (@ Nonnull final Document document )
1650+ throws TransformerException {
1651+
1652+ final TransformerFactory transformerFactory = TransformerFactory .newInstance ();
1653+ transformerFactory .setFeature (XMLConstants .FEATURE_SECURE_PROCESSING , true );
1654+
1655+ final Transformer transformer = transformerFactory .newTransformer ();
1656+ transformer .setOutputProperty (OutputKeys .VERSION , "1.0" );
1657+ transformer .setOutputProperty (OutputKeys .ENCODING , "UTF-8" );
1658+ transformer .setOutputProperty (OutputKeys .STANDALONE , "no" );
1659+
1660+ final StringWriter result = new StringWriter ();
1661+ transformer .transform (new DOMSource (document ), new StreamResult (result ));
1662+
1663+ return result .toString ();
1664+ }
1665+
1666+ /**
16331667 * @return the number of cached OTF streams manifests
16341668 */
16351669 public static int getOtfCachedManifestsSize () {
16361670 return GENERATED_OTF_MANIFESTS .size ();
16371671 }
16381672
16391673 /**
1640- * Get the number of cached post-live-DVR streams manifests.
1641- *
16421674 * @return the number of cached post-live-DVR streams manifests
16431675 */
16441676 public static int getPostLiveDvrStreamsCachedManifestsSize () {
16451677 return GENERATED_POST_LIVE_DVR_STREAMS_MANIFESTS .size ();
16461678 }
16471679
16481680 /**
1649- * Get the number of cached progressive manifests.
1650- *
16511681 * @return the number of cached progressive manifests
16521682 */
16531683 public static int getProgressiveStreamsCachedManifestsSize () {
16541684 return GENERATED_PROGRESSIVE_STREAMS_MANIFESTS .size ();
16551685 }
16561686
16571687 /**
1658- * Get the number of cached OTF, post-live-DVR streams and progressive manifests.
1659- *
16601688 * @return the number of cached OTF, post-live-DVR streams and progressive manifests.
16611689 */
16621690 public static int getSizeOfManifestsCaches () {
@@ -1666,68 +1694,54 @@ public static int getSizeOfManifestsCaches() {
16661694 }
16671695
16681696 /**
1669- * Get the clear factor of OTF streams manifests cache.
1670- *
16711697 * @return the clear factor of OTF streams manifests cache.
16721698 */
16731699 public static double getOtfStreamsClearFactor () {
16741700 return GENERATED_OTF_MANIFESTS .getClearFactor ();
16751701 }
16761702
16771703 /**
1678- * Get the clear factor of post-live-DVR streams manifests cache.
1679- *
16801704 * @return the clear factor of post-live-DVR streams manifests cache.
16811705 */
16821706 public static double getPostLiveDvrStreamsClearFactor () {
16831707 return GENERATED_POST_LIVE_DVR_STREAMS_MANIFESTS .getClearFactor ();
16841708 }
16851709
16861710 /**
1687- * Get the clear factor of progressive streams manifests cache.
1688- *
16891711 * @return the clear factor of progressive streams manifests cache.
16901712 */
16911713 public static double getProgressiveStreamsClearFactor () {
16921714 return GENERATED_PROGRESSIVE_STREAMS_MANIFESTS .getClearFactor ();
16931715 }
16941716
16951717 /**
1696- * Set the clear factor of cached OTF streams.
1697- *
16981718 * @param otfStreamsClearFactor the clear factor of OTF streams manifests cache.
16991719 */
17001720 public static void setOtfStreamsClearFactor (final double otfStreamsClearFactor ) {
17011721 GENERATED_OTF_MANIFESTS .setClearFactor (otfStreamsClearFactor );
17021722 }
17031723
17041724 /**
1705- * Set the clear factor of cached post-live-DVR streams.
1706- *
1707- * @param postLiveDvrStreamsClearFactor the clear factor of post-live-DVR streams manifests
1708- * cache.
1725+ * @param postLiveDvrStreamsClearFactor the clear factor to set for post-live-DVR streams
1726+ * manifests cache
17091727 */
17101728 public static void setPostLiveDvrStreamsClearFactor (
17111729 final double postLiveDvrStreamsClearFactor ) {
17121730 GENERATED_POST_LIVE_DVR_STREAMS_MANIFESTS .setClearFactor (postLiveDvrStreamsClearFactor );
17131731 }
17141732
17151733 /**
1716- * Set the clear factor of cached progressive streams.
1717- *
1718- * @param progressiveStreamsClearFactor the clear factor of progressive streams manifests
1719- * cache.
1734+ * @param progressiveStreamsClearFactor the clear factor to set for progressive streams
1735+ * manifests cache
17201736 */
17211737 public static void setProgressiveStreamsClearFactor (
17221738 final double progressiveStreamsClearFactor ) {
17231739 GENERATED_PROGRESSIVE_STREAMS_MANIFESTS .setClearFactor (progressiveStreamsClearFactor );
17241740 }
17251741
17261742 /**
1727- * Set the clear factor of cached OTF, post-live-DVR and progressive streams.
1728- *
1729- * @param cachesClearFactor the clear factor of OTF, post-live-DVR and progressive streams
1730- * manifests caches.
1743+ * @param cachesClearFactor the clear factor to set for OTF, post-live-DVR and progressive
1744+ * streams manifests caches
17311745 */
17321746 public static void setCachesClearFactor (final double cachesClearFactor ) {
17331747 GENERATED_OTF_MANIFESTS .setClearFactor (cachesClearFactor );
0 commit comments