protected $mFilteredName, $mFinalExtension;
protected $mLocalFile, $mFileSize, $mFileProps;
protected $mBlackListedExtensions;
- protected $mJavaDetected;
+ protected $mJavaDetected, $mSVGNSError;
protected static $safeXmlEncodings = array( 'UTF-8', 'ISO-8859-1', 'ISO-8859-2', 'UTF-16', 'UTF-32' );
return array( 'uploadscripted' );
}
if ( $this->mFinalExtension == 'svg' || $mime == 'image/svg+xml' ) {
- if ( $this->detectScriptInSvg( $this->mTempPath ) ) {
+ $svgStatus = $this->detectScriptInSvg( $this->mTempPath );
+ if ( $svgStatus !== false ) {
wfProfileOut( __METHOD__ );
- return array( 'uploadscripted' );
+ return $svgStatus;
}
}
}
// Check dupes against archives
$archivedImage = new ArchivedFile( null, 0, "{$hash}.{$this->mFinalExtension}" );
if ( $archivedImage->getID() > 0 ) {
- $warnings['duplicate-archive'] = $archivedImage->getName();
+ if ( $archivedImage->userCan( File::DELETED_FILE ) ) {
+ $warnings['duplicate-archive'] = $archivedImage->getName();
+ } else {
+ $warnings['duplicate-archive'] = '';
+ }
}
wfProfileOut( __METHOD__ );
/**
* @param $filename string
- * @return bool
+ * @return mixed false of the file is verified (does not contain scripts), array otherwise.
*/
protected function detectScriptInSvg( $filename ) {
- $check = new XmlTypeCheck( $filename, array( $this, 'checkSvgScriptCallback' ) );
- return $check->filterMatch;
+ $this->mSVGNSError = false;
+ $check = new XmlTypeCheck(
+ $filename,
+ array( $this, 'checkSvgScriptCallback' ),
+ true,
+ array( 'processing_instruction_handler' => 'UploadBase::checkSvgPICallback' )
+ );
+ if ( $check->wellFormed !== true ) {
+ // Invalid xml (bug 58553)
+ return array( 'uploadinvalidxml' );
+ } elseif ( $check->filterMatch ) {
+ if ( $this->mSVGNSError ) {
+ return array( 'uploadscriptednamespace', $this->mSVGNSError );
+ }
+ return array( 'uploadscripted' );
+ }
+ return false;
+ }
+
+ /**
+ * Callback to filter SVG Processing Instructions.
+ * @param $target string processing instruction name
+ * @param $data string processing instruction attribute and value
+ * @return bool (true if the filter identified something bad)
+ */
+ public static function checkSvgPICallback( $target, $data ) {
+ // Don't allow external stylesheets (bug 57550)
+ if ( preg_match( '/xml-stylesheet/i', $target) ) {
+ return true;
+ }
+ return false;
}
/**
* @return bool
*/
public function checkSvgScriptCallback( $element, $attribs ) {
- $strippedElement = $this->stripXmlNamespace( $element );
+ list( $namespace, $strippedElement ) = $this->splitXmlNamespace( $element );
+
+ static $validNamespaces = array(
+ '',
+ 'adobe:ns:meta/',
+ 'http://creativecommons.org/ns#',
+ 'http://inkscape.sourceforge.net/dtd/sodipodi-0.dtd',
+ 'http://ns.adobe.com/adobeillustrator/10.0/',
+ 'http://ns.adobe.com/adobesvgviewerextensions/3.0/',
+ 'http://ns.adobe.com/extensibility/1.0/',
+ 'http://ns.adobe.com/flows/1.0/',
+ 'http://ns.adobe.com/illustrator/1.0/',
+ 'http://ns.adobe.com/imagereplacement/1.0/',
+ 'http://ns.adobe.com/pdf/1.3/',
+ 'http://ns.adobe.com/photoshop/1.0/',
+ 'http://ns.adobe.com/saveforweb/1.0/',
+ 'http://ns.adobe.com/variables/1.0/',
+ 'http://ns.adobe.com/xap/1.0/',
+ 'http://ns.adobe.com/xap/1.0/g/',
+ 'http://ns.adobe.com/xap/1.0/g/img/',
+ 'http://ns.adobe.com/xap/1.0/mm/',
+ 'http://ns.adobe.com/xap/1.0/rights/',
+ 'http://ns.adobe.com/xap/1.0/stype/dimensions#',
+ 'http://ns.adobe.com/xap/1.0/stype/font#',
+ 'http://ns.adobe.com/xap/1.0/stype/manifestitem#',
+ 'http://ns.adobe.com/xap/1.0/stype/resourceevent#',
+ 'http://ns.adobe.com/xap/1.0/stype/resourceref#',
+ 'http://ns.adobe.com/xap/1.0/t/pg/',
+ 'http://purl.org/dc/elements/1.1/',
+ 'http://purl.org/dc/elements/1.1',
+ 'http://schemas.microsoft.com/visio/2003/svgextensions/',
+ 'http://sodipodi.sourceforge.net/dtd/sodipodi-0.dtd',
+ 'http://web.resource.org/cc/',
+ 'http://www.freesoftware.fsf.org/bkchem/cdml',
+ 'http://www.inkscape.org/namespaces/inkscape',
+ 'http://www.w3.org/1999/02/22-rdf-syntax-ns#',
+ 'http://www.w3.org/2000/svg',
+ );
+
+ if ( !in_array( $namespace, $validNamespaces ) ) {
+ wfDebug( __METHOD__ . ": Non-svg namespace '$namespace' in uploaded file.\n" );
+ // @TODO return a status object to a closure in XmlTypeCheck, for MW1.21+
+ $this->mSVGNSError = $namespace;
+ return true;
+ }
/*
* check for elements that can contain javascript
return true;
}
+ # Block iframes, in case they pass the namespace check
+ if ( $strippedElement == 'iframe' ) {
+ wfDebug( __METHOD__ . ": iframe in uploaded file.\n" );
+ return true;
+ }
+
+
foreach ( $attribs as $attrib => $value ) {
$stripped = $this->stripXmlNamespace( $attrib );
$value = strtolower( $value );
return false; //No scripts detected
}
+ /**
+ * Divide the element name passed by the xml parser to the callback into URI and prifix.
+ * @param $name string
+ * @return array containing the namespace URI and prefix
+ */
+ private static function splitXmlNamespace( $element ) {
+ // 'http://www.w3.org/2000/svg:script' -> array( 'http://www.w3.org/2000/svg', 'script' )
+ $parts = explode( ':', strtolower( $element ) );
+ $name = array_pop( $parts );
+ $ns = implode( ':', $parts );
+ return array( $ns, $name );
+ }
+
/**
* @param $name string
* @return string