Skip to content

Commit ba68b8c

Browse files
StypoxAudricV
authored andcommitted
[YouTube] Secure DashManifestCreator against XEE attack
Also remove duplicate lines from javadoc comments, otherwise checkstyle complaints. XEE prevention is inspired from https://github.com/ChuckerTeam/chucker/pull/201/files For an explanation of XEE see https://rules.sonarsource.com/java/RSPEC-2755, though the solution reported there causes crashes on Android
1 parent 159d05c commit ba68b8c

1 file changed

Lines changed: 69 additions & 55 deletions

File tree

extractor/src/main/java/org/schabi/newpipe/extractor/services/youtube/YoutubeDashManifestCreator.java

Lines changed: 69 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,13 @@
1313
import org.w3c.dom.Element;
1414

1515
import javax.annotation.Nonnull;
16+
import javax.xml.XMLConstants;
1617
import javax.xml.parsers.DocumentBuilder;
1718
import javax.xml.parsers.DocumentBuilderFactory;
19+
import javax.xml.parsers.ParserConfigurationException;
1820
import javax.xml.transform.OutputKeys;
1921
import javax.xml.transform.Transformer;
22+
import javax.xml.transform.TransformerException;
2023
import javax.xml.transform.TransformerFactory;
2124
import javax.xml.transform.dom.DOMSource;
2225
import javax.xml.transform.stream.StreamResult;
@@ -45,10 +48,7 @@
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
*/
5353
public 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

Comments
 (0)