From 45b9073b61d3aa569adb8cff46531a5b06b8bb7a Mon Sep 17 00:00:00 2001 From: Aaron Date: Fri, 31 Aug 2012 15:35:39 -0700 Subject: [PATCH] [FileBackend] Added Content-Disposition support to Swift. * Renamed $handle => $op to avoid line breaks in a few spots. Change-Id: I4598e685cc27552425a47f9d97eaeddaaf1a64a1 --- includes/filebackend/FileBackend.php | 48 ++++++++++++++++++----- includes/filebackend/FileBackendStore.php | 4 ++ includes/filebackend/FileOp.php | 40 ++++++------------- includes/filebackend/SwiftFileBackend.php | 40 +++++++++++++------ 4 files changed, 83 insertions(+), 49 deletions(-) diff --git a/includes/filebackend/FileBackend.php b/includes/filebackend/FileBackend.php index cb2433aea2..2ab765503f 100644 --- a/includes/filebackend/FileBackend.php +++ b/includes/filebackend/FileBackend.php @@ -39,8 +39,7 @@ * All "storage paths" are of the format "mwstore:////". * The "" portion is a relative path that uses UNIX file system (FS) * notation, though any particular backend may not actually be using a local - * filesystem. - * Therefore, the relative paths are only virtual. + * filesystem. Therefore, the relative paths are only virtual. * * Backend contents are stored under wiki-specific container names by default. * For legacy reasons, this has no effect for the FS backend class, and per-wiki @@ -171,7 +170,8 @@ abstract class FileBackend { * 'dst' => , * 'content' => , * 'overwrite' => , - * 'overwriteSame' => + * 'overwriteSame' => , + * 'disposition' => * ); * @endcode * @@ -182,7 +182,8 @@ abstract class FileBackend { * 'src' => , * 'dst' => , * 'overwrite' => , - * 'overwriteSame' => + * 'overwriteSame' => , + * 'disposition' => * ) * @endcode * @@ -193,7 +194,8 @@ abstract class FileBackend { * 'src' => , * 'dst' => , * 'overwrite' => , - * 'overwriteSame' => + * 'overwriteSame' => , + * 'disposition' => * ) * @endcode * @@ -204,7 +206,8 @@ abstract class FileBackend { * 'src' => , * 'dst' => , * 'overwrite' => , - * 'overwriteSame' => + * 'overwriteSame' => , + * 'disposition' => * ) * @endcode * @@ -231,6 +234,10 @@ abstract class FileBackend { * - overwriteSame : An error will not be given if a file already * exists at the destination that has the same * contents as the new contents to be written there. + * - disposition : When supplied, the backend will add a Content-Disposition + * header when GETs/HEADs of the destination file are made. + * Backends that don't support file metadata will ignore this. + * See http://tools.ietf.org/html/rfc6266 (since 1.20). * * $opts is an associative of boolean flags, including: * - force : Operation precondition errors no longer trigger an abort. @@ -400,7 +407,8 @@ abstract class FileBackend { * array( * 'op' => 'create', * 'dst' => , - * 'content' => + * 'content' => , + * 'disposition' => * ) * @endcode * b) Copy a file system file into storage @@ -408,7 +416,8 @@ abstract class FileBackend { * array( * 'op' => 'store', * 'src' => , - * 'dst' => + * 'dst' => , + * 'disposition' => * ) * @endcode * c) Copy a file within storage @@ -416,7 +425,8 @@ abstract class FileBackend { * array( * 'op' => 'copy', * 'src' => , - * 'dst' => + * 'dst' => , + * 'disposition' => * ) * @endcode * d) Move a file within storage @@ -424,7 +434,8 @@ abstract class FileBackend { * array( * 'op' => 'move', * 'src' => , - * 'dst' => + * 'dst' => , + * 'disposition' => * ) * @endcode * e) Delete a file within storage @@ -445,6 +456,10 @@ abstract class FileBackend { * @par Boolean flags for operations (operation-specific): * - ignoreMissingSource : The operation will simply succeed and do * nothing if the source file does not exist. + * - disposition : When supplied, the backend will add a Content-Disposition + * header when GETs/HEADs of the destination file are made. + * Backends that don't support file metadata will ignore this. + * See http://tools.ietf.org/html/rfc6266 (since 1.20). * * $opts is an associative of boolean flags, including: * - bypassReadOnly : Allow writes in read-only mode (since 1.20) @@ -1089,6 +1104,19 @@ abstract class FileBackend { return ( self::normalizeContainerPath( $path ) !== null ); } + /** + * Build a Content-Disposition header value per RFC 6266 + * + * @param $type string One of (attachment, inline) + * @param $filename string Suggested file name (should not contain slashes) + * @return string + */ + final public static function makeContentDisposition( $type, $filename ) { + $type = strtolower( $type ); + $type = in_array( $type, array( 'inline', 'attachment' ) ) ? $type : 'inline'; + return "$type; filename*=UTF-8''" . rawurlencode( basename( $filename ) ); + } + /** * Validate and normalize a relative storage path. * Null is returned if the path involves directory traversal. diff --git a/includes/filebackend/FileBackendStore.php b/includes/filebackend/FileBackendStore.php index d53f7b3603..57715605b2 100644 --- a/includes/filebackend/FileBackendStore.php +++ b/includes/filebackend/FileBackendStore.php @@ -89,6 +89,7 @@ abstract class FileBackendStore extends FileBackend { * - content : the raw file contents * - dst : destination storage path * - overwrite : overwrite any file that exists at the destination + * - disposition : Content-Disposition header value for the destination * - async : Status will be returned immediately if supported. * If the status is OK, then its value field will be * set to a FileBackendStoreOpHandle object. @@ -127,6 +128,7 @@ abstract class FileBackendStore extends FileBackend { * - src : source path on disk * - dst : destination storage path * - overwrite : overwrite any file that exists at the destination + * - disposition : Content-Disposition header value for the destination * - async : Status will be returned immediately if supported. * If the status is OK, then its value field will be * set to a FileBackendStoreOpHandle object. @@ -165,6 +167,7 @@ abstract class FileBackendStore extends FileBackend { * - src : source storage path * - dst : destination storage path * - overwrite : overwrite any file that exists at the destination + * - disposition : Content-Disposition header value for the destination * - async : Status will be returned immediately if supported. * If the status is OK, then its value field will be * set to a FileBackendStoreOpHandle object. @@ -228,6 +231,7 @@ abstract class FileBackendStore extends FileBackend { * - src : source storage path * - dst : destination storage path * - overwrite : overwrite any file that exists at the destination + * - disposition : Content-Disposition header value for the destination * - async : Status will be returned immediately if supported. * If the status is OK, then its value field will be * set to a FileBackendStoreOpHandle object. diff --git a/includes/filebackend/FileOp.php b/includes/filebackend/FileOp.php index fa87c3aa72..7c43c48908 100644 --- a/includes/filebackend/FileOp.php +++ b/includes/filebackend/FileOp.php @@ -431,18 +431,15 @@ abstract class FileOp { /** * Store a file into the backend from a file on the file system. - * Parameters similar to FileBackendStore::storeInternal(), which include: - * - src : source path on file system - * - dst : destination storage path - * - overwrite : do nothing and pass if an identical file exists at destination - * - overwriteSame : override any existing file at destination + * Parameters for this operation are outlined in FileBackend::doOperations(). */ class StoreFileOp extends FileOp { /** * @return array */ protected function allowedParams() { - return array( array( 'src', 'dst' ), array( 'overwrite', 'overwriteSame' ) ); + return array( array( 'src', 'dst' ), + array( 'overwrite', 'overwriteSame', 'disposition' ) ); } /** @@ -508,15 +505,12 @@ class StoreFileOp extends FileOp { /** * Create a file in the backend with the given content. - * Parameters similar to FileBackendStore::createInternal(), which include: - * - content : the raw file contents - * - dst : destination storage path - * - overwrite : do nothing and pass if an identical file exists at destination - * - overwriteSame : override any existing file at destination + * Parameters for this operation are outlined in FileBackend::doOperations(). */ class CreateFileOp extends FileOp { protected function allowedParams() { - return array( array( 'content', 'dst' ), array( 'overwrite', 'overwriteSame' ) ); + return array( array( 'content', 'dst' ), + array( 'overwrite', 'overwriteSame', 'disposition' ) ); } protected function doPrecheck( array &$predicates ) { @@ -571,18 +565,15 @@ class CreateFileOp extends FileOp { /** * Copy a file from one storage path to another in the backend. - * Parameters similar to FileBackendStore::copyInternal(), which include: - * - src : source storage path - * - dst : destination storage path - * - overwrite : do nothing and pass if an identical file exists at destination - * - overwriteSame : override any existing file at destination + * Parameters for this operation are outlined in FileBackend::doOperations(). */ class CopyFileOp extends FileOp { /** * @return array */ protected function allowedParams() { - return array( array( 'src', 'dst' ), array( 'overwrite', 'overwriteSame' ) ); + return array( array( 'src', 'dst' ), + array( 'overwrite', 'overwriteSame', 'disposition' ) ); } /** @@ -642,18 +633,15 @@ class CopyFileOp extends FileOp { /** * Move a file from one storage path to another in the backend. - * Parameters similar to FileBackendStore::moveInternal(), which include: - * - src : source storage path - * - dst : destination storage path - * - overwrite : do nothing and pass if an identical file exists at destination - * - overwriteSame : override any existing file at destination + * Parameters for this operation are outlined in FileBackend::doOperations(). */ class MoveFileOp extends FileOp { /** * @return array */ protected function allowedParams() { - return array( array( 'src', 'dst' ), array( 'overwrite', 'overwriteSame' ) ); + return array( array( 'src', 'dst' ), + array( 'overwrite', 'overwriteSame', 'disposition' ) ); } /** @@ -719,9 +707,7 @@ class MoveFileOp extends FileOp { /** * Delete a file at the given storage path from the backend. - * Parameters similar to FileBackendStore::deleteInternal(), which include: - * - src : source storage path - * - ignoreMissingSource : don't return an error if the file does not exist + * Parameters for this operation are outlined in FileBackend::doOperations(). */ class DeleteFileOp extends FileOp { /** diff --git a/includes/filebackend/SwiftFileBackend.php b/includes/filebackend/SwiftFileBackend.php index 4d47302fbd..88727e4e96 100644 --- a/includes/filebackend/SwiftFileBackend.php +++ b/includes/filebackend/SwiftFileBackend.php @@ -210,9 +210,13 @@ class SwiftFileBackend extends FileBackendStore { if ( !strlen( $obj->content_type ) ) { // special case $obj->content_type = 'unknown/unknown'; } + // Set the Content-Disposition header if requested + if ( isset( $params['disposition'] ) ) { + $obj->headers['Content-Disposition'] = $params['disposition']; + } if ( !empty( $params['async'] ) ) { // deferred - $handle = $obj->write_async( $params['content'] ); - $status->value = new SwiftFileOpHandle( $this, $params, 'Create', $handle ); + $op = $obj->write_async( $params['content'] ); + $status->value = new SwiftFileOpHandle( $this, $params, 'Create', $op ); if ( !empty( $params['overwrite'] ) ) { // file possibly mutated $status->value->affectedObjects[] = $obj; } @@ -296,6 +300,10 @@ class SwiftFileBackend extends FileBackendStore { if ( !strlen( $obj->content_type ) ) { // special case $obj->content_type = 'unknown/unknown'; } + // Set the Content-Disposition header if requested + if ( isset( $params['disposition'] ) ) { + $obj->headers['Content-Disposition'] = $params['disposition']; + } if ( !empty( $params['async'] ) ) { // deferred wfSuppressWarnings(); $fp = fopen( $params['src'], 'rb' ); @@ -303,8 +311,8 @@ class SwiftFileBackend extends FileBackendStore { if ( !$fp ) { $status->fatal( 'backend-fail-copy', $params['src'], $params['dst'] ); } else { - $handle = $obj->write_async( $fp, filesize( $params['src'] ), true ); - $status->value = new SwiftFileOpHandle( $this, $params, 'Store', $handle ); + $op = $obj->write_async( $fp, filesize( $params['src'] ), true ); + $status->value = new SwiftFileOpHandle( $this, $params, 'Store', $op ); $status->value->resourcesToClose[] = $fp; if ( !empty( $params['overwrite'] ) ) { // file possibly mutated $status->value->affectedObjects[] = $obj; @@ -382,14 +390,18 @@ class SwiftFileBackend extends FileBackendStore { // (b) Actually copy the file to the destination try { $dstObj = new CF_Object( $dContObj, $dstRel, false, false ); // skip HEAD + $hdrs = array(); // source file headers to override with new values + if ( isset( $params['disposition'] ) ) { + $hdrs['Content-Disposition'] = $params['disposition']; + } if ( !empty( $params['async'] ) ) { // deferred - $handle = $sContObj->copy_object_to_async( $srcRel, $dContObj, $dstRel ); - $status->value = new SwiftFileOpHandle( $this, $params, 'Copy', $handle ); + $op = $sContObj->copy_object_to_async( $srcRel, $dContObj, $dstRel, null, $hdrs ); + $status->value = new SwiftFileOpHandle( $this, $params, 'Copy', $op ); if ( !empty( $params['overwrite'] ) ) { // file possibly mutated $status->value->affectedObjects[] = $dstObj; } } else { // actually write the object in Swift - $sContObj->copy_object_to( $srcRel, $dContObj, $dstRel ); + $sContObj->copy_object_to( $srcRel, $dContObj, $dstRel, null, $hdrs ); if ( !empty( $params['overwrite'] ) ) { // file possibly mutated $this->purgeCDNCache( array( $dstObj ) ); } @@ -457,15 +469,19 @@ class SwiftFileBackend extends FileBackendStore { try { $srcObj = new CF_Object( $sContObj, $srcRel, false, false ); // skip HEAD $dstObj = new CF_Object( $dContObj, $dstRel, false, false ); // skip HEAD + $hdrs = array(); // source file headers to override with new values + if ( isset( $params['disposition'] ) ) { + $hdrs['Content-Disposition'] = $params['disposition']; + } if ( !empty( $params['async'] ) ) { // deferred - $handle = $sContObj->move_object_to_async( $srcRel, $dContObj, $dstRel ); - $status->value = new SwiftFileOpHandle( $this, $params, 'Move', $handle ); + $op = $sContObj->move_object_to_async( $srcRel, $dContObj, $dstRel, null, $hdrs ); + $status->value = new SwiftFileOpHandle( $this, $params, 'Move', $op ); $status->value->affectedObjects[] = $srcObj; if ( !empty( $params['overwrite'] ) ) { // file possibly mutated $status->value->affectedObjects[] = $dstObj; } } else { // actually write the object in Swift - $sContObj->move_object_to( $srcRel, $dContObj, $dstRel ); + $sContObj->move_object_to( $srcRel, $dContObj, $dstRel, null, $hdrs ); $this->purgeCDNCache( array( $srcObj ) ); if ( !empty( $params['overwrite'] ) ) { // file possibly mutated $this->purgeCDNCache( array( $dstObj ) ); @@ -510,8 +526,8 @@ class SwiftFileBackend extends FileBackendStore { $sContObj = $this->getContainer( $srcCont ); $srcObj = new CF_Object( $sContObj, $srcRel, false, false ); // skip HEAD if ( !empty( $params['async'] ) ) { // deferred - $handle = $sContObj->delete_object_async( $srcRel ); - $status->value = new SwiftFileOpHandle( $this, $params, 'Delete', $handle ); + $op = $sContObj->delete_object_async( $srcRel ); + $status->value = new SwiftFileOpHandle( $this, $params, 'Delete', $op ); $status->value->affectedObjects[] = $srcObj; } else { // actually write the object in Swift $sContObj->delete_object( $srcRel ); -- 2.20.1