static function post( $url, $fileParamName, $sourcePath, $params = array(), $proxy = false, $outputFile = false, $connectTimeout = 10, $overallTimeout = 180 ) { $postObj = new WebStorePostFile( $url, $fileParamName, $sourcePath, $params, $proxy, $outputFile, $connectTimeout, $overallTimeout ); $postObj->_post(); return $postObj; }
function execute() { global $wgUploadBaseUrl, $wgUploadPath, $wgScriptPath, $wgServer; // Determine URI if ( $_SERVER['REQUEST_URI'][0] == '/' ) { $url = ( !empty( $_SERVER['HTTPS'] ) ? 'https://' : 'http://' ) . $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI']; } else { $url = $_SERVER['REQUEST_URI']; } if ( $wgUploadBaseUrl ) { $thumbBase = $wgUploadBaseUrl . $wgUploadPath . '/thumb'; } else { $thumbBase = $wgServer . $wgUploadPath . '/thumb'; } if ( substr( $url, 0, strlen( $thumbBase ) ) != $thumbBase ) { // Not a thumbnail URL header( 'X-Debug: not thumb' ); $this->real404(); return true; } $rel = substr( $url, strlen( $thumbBase ) + 1 ); // plus one for slash // Check for path traversal if ( !$this->validateFilename( $rel ) ) { header( 'X-Debug: invalid path traversal' ); $this->real404(); return false; } if ( !preg_match( '!^(\w)/(\w\w)/([^/]*)/([^/]*)$!', $rel, $parts ) ) { header( 'X-Debug: regex mismatch' ); $this->real404(); return false; } list( $all, $hash1, $hash2, $filename, $thumbName ) = $parts; $srcNamePos = strrpos( $thumbName, $filename ); if ( $srcNamePos === false ) { header( 'X-Debug: filename/fn2 mismatch' ); $this->real404(); return false; } $extraExt = substr( $thumbName, $srcNamePos + strlen( $filename ) ); if ( $extraExt != '' && $extraExt[0] != '.' ) { header( "X-Debug: invalid trailing characters in filename: $extraExt" ); $this->real404(); return false; } // Determine MIME type $extPos = strrpos( $filename, '.' ); $srcExt = $extPos === false ? '' : substr( $filename, $extPos + 1 ); $magic = MimeMagic::singleton(); $mime = $magic->guessTypesForExtension( $srcExt ); $handler = MediaHandler::getHandler( $mime ); if ( !$handler ) { header( 'X-Debug: no handler' ); $this->real404(); return false; } // Parse parameter string $paramString = substr( $thumbName, 0, $srcNamePos - 1 ); $params = $handler->parseParamString( $paramString ); if ( !$params ) { header( "X-Debug: handler for $mime says param string is invalid" ); $this->real404(); return false; } // Open the destination temporary file $dstPath = "{$this->publicDir}/thumb/$rel"; $tmpPath = "$dstPath.temp.MW_WebStore"; $tmpFile = @fopen( $tmpPath, 'a+' ); if ( !$tmpFile ) { $this->htmlError( 500, 'webstore_temp_open', $tmpPath ); return false; } // Get an exclusive lock if ( !flock( $tmpFile, LOCK_EX | LOCK_NB ) ) { wfDebug( "Waiting for shared lock..." ); if ( !flock( $tmpFile, LOCK_SH ) ) { wfDebug( "failed\n" ); $this->htmlError( 500, 'webstore_temp_lock' ); return false; } wfDebug( "OK\n" ); // Close it and see if it appears at $dstPath fclose( $tmpFile ); if ( $this->windows ) { // Rename happens after unlock on windows, so we have to wait for it usleep( 200000 ); } if ( file_exists( $dstPath ) ) { // Stream it out $magic = MimeMagic::singleton(); $type = $magic->guessMimeType( $dstPath ); $dstFile = fopen( $dstPath, 'r' ); if ( !$dstFile ) { $this->htmlError( 500, 'webstore_dest_open' ); return false; } $this->streamFile( $dstFile, $type ); fclose( $dstFile ); return true; } else { // Something went wrong, only the forwarding process knows what $this->real404(); return true; } } // Send an image scaling request to a host in the scaling cluster $error = false; $errno = false; $tmpUnlinkDone = false; do { $scalerUrl = "$wgServer$wgScriptPath/extensions/WebStore/inplace-scaler.php"; // Pick a server $servers = $this->scalerServers; shuffle( $servers ); foreach( $servers as $server ) { if ( strpos( $server, ':' ) === false ) { $server .= ':80'; } $post = WebStorePostFile::post( $scalerUrl, 'data', "{$this->publicDir}/$hash1/$hash2/$filename", $params, $server, $tmpFile, $this->httpConnectTimeout, $this->httpOverallTimeout ); // Try next server unless that one was successful if ( !$post->errno ) { break; } } if ( $post->errno ) { break; } if ( $post->responseCode != 200 ) { # Pass through image scaler errors (but don't keep the file) $info = self::$httpErrors[$post->responseCode]; header( "HTTP/1.1 {$post->responseCode} $info" ); $this->streamFile( $tmpFile ); break; } fseek( $tmpFile, 0, SEEK_END ); if ( ftell( $tmpFile ) == 0 ) { $this->htmlError( 500, 'webstore_scaler_empty_response' ); break; } // Report PHP errors if ( count( $this->phpErrors ) ) { $errors = '<ul>'; foreach ( $this->phpErrors as $error ) { $errors .= "<li>$error</li>"; } $errors .= '</ul>'; $info = self::$httpErrors[500]; header( "HTTP/1.1 500 $info" ); echo $this->dtd(); $msg = wfMsgHtml( 'webstore_php_error' ); echo <<<EOT <html><head><title>500 $info</title></head> <body> <h1>500 $info</h1> <p>$msg</p> $errors </body> </html> EOT; restore_error_handler(); break; } // Request completed successfully. // Move the file to its destination if ( $this->windows ) { fclose( $tmpFile ); // Wait for other processes to close the file if rename fails for ( $i = 0; $i < 10; $i++ ) { if ( !rename( $tmpPath, $dstPath ) ) { usleep( 50000 ); } else { break; } } $tmpFile = fopen( $dstPath, 'r' ); if ( !$tmpFile ) { $this->htmlError( 500, 'webstore_dest_open' ); } $tmpUnlinkDone = true; } else { rename( $tmpPath, $dstPath ); // Unlock so that other processes can start streaming the file out flock( $tmpFile, LOCK_UN ); $tmpUnlinkDone = true; } header( "HTTP/1.1 200 OK" ); // Stream it ourselves $this->streamFile( $tmpFile, $post->contentType ); } while (false); if ( $tmpFile ) { if ( !$tmpUnlinkDone ) { $this->closeAndDelete( $tmpFile, $tmpPath ); } else { fclose( $tmpFile ); } } if ( $post->errno ) { $this->htmlError( 500, 'webstore_curl', $post->error ); return false; } return true; }
function postFile( $url, $fileParamName, $fileName, $params = array() ) { $post = WebStorePostFile::post( $url, $fileParamName, $fileName, $params ); if ( !$post->content ) { $this->lastError = $post->error; } else { $this->lastError = false; } return $post->content; }