* 'content' => <string of new file contents>,
* 'overwrite' => <boolean>,
* 'overwriteSame' => <boolean>,
- * 'disposition' => <Content-Disposition header value>,
* 'headers' => <HTTP header name/value map> # since 1.21
* );
* @endcode
* 'dst' => <storage path>,
* 'overwrite' => <boolean>,
* 'overwriteSame' => <boolean>,
- * 'disposition' => <Content-Disposition header value>,
* 'headers' => <HTTP header name/value map> # since 1.21
* )
* @endcode
* 'overwrite' => <boolean>,
* 'overwriteSame' => <boolean>,
* 'ignoreMissingSource' => <boolean>, # since 1.21
- * 'disposition' => <Content-Disposition header value>
+ * 'headers' => <HTTP header name/value map> # since 1.21
* )
* @endcode
*
* 'overwrite' => <boolean>,
* 'overwriteSame' => <boolean>,
* 'ignoreMissingSource' => <boolean>, # since 1.21
- * 'disposition' => <Content-Disposition header value>
+ * 'headers' => <HTTP header name/value map> # since 1.21
* )
* @endcode
*
* array(
* 'op' => 'describe',
* 'src' => <storage path>,
- * 'disposition' => <Content-Disposition header value>,
* 'headers' => <HTTP header name/value map>
* )
* @endcode
* - 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 : If supplied, the backend will return a Content-Disposition
- * header when GETs/HEADs of the destination file are made.
- * Backends that don't support metadata ignore this.
- * See http://tools.ietf.org/html/rfc6266. (since 1.20)
* - headers : If supplied, the backend will return these headers when
* GETs/HEADs of the destination file are made. Header values
* should be smaller than 256 bytes, often options or numbers.
+ * Content-Disposition headers can be longer, though the system
+ * might ignore or truncate ones that are too long to store.
* Existing headers will remain, but these will replace any
* conflicting previous headers, and headers will be removed
* if they are set to an empty string.
if ( empty( $opts['force'] ) ) { // sanity
unset( $opts['nonLocking'] );
}
+ foreach ( $ops as &$op ) {
+ if ( isset( $op['disposition'] ) ) { // b/c (MW 1.20)
+ $op['headers']['Content-Disposition'] = $op['disposition'];
+ }
+ }
$scope = $this->getScopedPHPBehaviorForOps(); // try to ignore client aborts
return $this->doOperationsInternal( $ops, $opts );
}
* 'op' => 'create',
* 'dst' => <storage path>,
* 'content' => <string of new file contents>,
- * 'disposition' => <Content-Disposition header value>,
* 'headers' => <HTTP header name/value map> # since 1.21
* )
* @endcode
* 'op' => 'store',
* 'src' => <file system path>,
* 'dst' => <storage path>,
- * 'disposition' => <Content-Disposition header value>,
* 'headers' => <HTTP header name/value map> # since 1.21
* )
* @endcode
* 'src' => <storage path>,
* 'dst' => <storage path>,
* 'ignoreMissingSource' => <boolean>, # since 1.21
- * 'disposition' => <Content-Disposition header value>
+ * 'headers' => <HTTP header name/value map> # since 1.21
* )
* @endcode
*
* 'src' => <storage path>,
* 'dst' => <storage path>,
* 'ignoreMissingSource' => <boolean>, # since 1.21
- * 'disposition' => <Content-Disposition header value>
+ * 'headers' => <HTTP header name/value map> # since 1.21
* )
* @endcode
*
* array(
* 'op' => 'describe',
* 'src' => <storage path>,
- * 'disposition' => <Content-Disposition header value>,
* 'headers' => <HTTP header name/value map>
* )
* @endcode
* @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).
* - headers : If supplied with a header name/value map, the backend will
* reply with these headers when GETs/HEADs of the destination
* file are made. Header values should be smaller than 256 bytes.
+ * Content-Disposition headers can be longer, though the system
+ * might ignore or truncate ones that are too long to store.
* Existing headers will remain, but these will replace any
* conflicting previous headers, and headers will be removed
* if they are set to an empty string.
}
foreach ( $ops as &$op ) {
$op['overwrite'] = true; // avoids RTTs in key/value stores
+ if ( isset( $op['disposition'] ) ) { // b/c (MW 1.20)
+ $op['headers']['Content-Disposition'] = $op['disposition'];
+ }
}
$scope = $this->getScopedPHPBehaviorForOps(); // try to ignore client aborts
return $this->doQuickOperationsInternal( $ops );
* $params include:
* - content : the raw file contents
* - dst : destination storage path
- * - disposition : Content-Disposition header value for the destination
* - headers : HTTP header name/value map
* - async : Status will be returned immediately if supported.
* If the status is OK, then its value field will be
* $params include:
* - src : source path on disk
* - dst : destination storage path
- * - disposition : Content-Disposition header value for the destination
* - headers : HTTP header name/value map
* - async : Status will be returned immediately if supported.
* If the status is OK, then its value field will be
* - src : source storage path
* - dst : destination storage path
* - ignoreMissingSource : do nothing if the source file does not exist
- * - disposition : Content-Disposition header value for the destination
+ * - headers : HTTP header name/value map
* - async : Status will be returned immediately if supported.
* If the status is OK, then its value field will be
* set to a FileBackendStoreOpHandle object.
* - src : source storage path
* - dst : destination storage path
* - ignoreMissingSource : do nothing if the source file does not exist
- * - disposition : Content-Disposition header value for the destination
+ * - headers : HTTP header name/value map
* - async : Status will be returned immediately if supported.
* If the status is OK, then its value field will be
* set to a FileBackendStoreOpHandle object.
*
* $params include:
* - src : source storage path
- * - disposition : Content-Disposition header value for the destination
* - headers : HTTP header name/value map
* - async : Status will be returned immediately if supported.
* If the status is OK, then its value field will be
}
/**
- * Strip long HTTP headers from a file operation
+ * Strip long HTTP headers from a file operation.
+ * Most headers are just numbers, but some are allowed to be long.
+ * This function is useful for cleaning up headers and avoiding backend
+ * specific errors, especially in the middle of batch file operations.
*
* @param array $op Same format as doOperation()
* @return Array
*/
protected function stripInvalidHeadersFromOp( array $op ) {
- if ( isset( $op['headers'] ) ) {
+ static $longs = array( 'Content-Disposition' );
+ if ( isset( $op['headers'] ) ) { // op sets HTTP headers
foreach ( $op['headers'] as $name => $value ) {
- if ( strlen( $name ) > 255 || strlen( $value ) > 255 ) {
+ $maxHVLen = in_array( $name, $longs ) ? INF : 255;
+ if ( strlen( $name ) > 255 || strlen( $value ) > $maxHVLen ) {
trigger_error( "Header '$name: $value' is too long." );
unset( $op['headers'][$name] );
} elseif ( !strlen( $value ) ) {
class CreateFileOp extends FileOp {
protected function allowedParams() {
return array( array( 'content', 'dst' ),
- array( 'overwrite', 'overwriteSame', 'disposition', 'headers' ) );
+ array( 'overwrite', 'overwriteSame', 'headers' ) );
}
protected function doPrecheck( array &$predicates ) {
*/
protected function allowedParams() {
return array( array( 'src', 'dst' ),
- array( 'overwrite', 'overwriteSame', 'disposition', 'headers' ) );
+ array( 'overwrite', 'overwriteSame', 'headers' ) );
}
/**
*/
protected function allowedParams() {
return array( array( 'src', 'dst' ),
- array( 'overwrite', 'overwriteSame', 'ignoreMissingSource', 'disposition' ) );
+ array( 'overwrite', 'overwriteSame', 'ignoreMissingSource', 'headers' ) );
}
/**
*/
protected function allowedParams() {
return array( array( 'src', 'dst' ),
- array( 'overwrite', 'overwriteSame', 'ignoreMissingSource', 'disposition' ) );
+ array( 'overwrite', 'overwriteSame', 'ignoreMissingSource', 'headers' ) );
}
/**
* @return array
*/
protected function allowedParams() {
- return array( array( 'src' ), array( 'disposition', 'headers' ) );
+ return array( array( 'src' ), array( 'headers' ) );
}
/**
}
/**
- * @param string $disposition Content-Disposition header value
+ * @param $headers array
+ * @return array
+ */
+ protected function sanitizeHdrs( array $headers ) {
+ // By default, Swift has annoyingly low maximum header value limits
+ if ( isset( $headers['Content-Disposition'] ) ) {
+ $headers['Content-Disposition'] = $this->truncDisp( $headers['Content-Disposition'] );
+ }
+ return $headers;
+ }
+
+ /**
+ * @param $disposition string Content-Disposition header value
* @return string Truncated Content-Disposition header value to meet Swift limits
*/
protected function truncDisp( $disposition ) {
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'] = $this->truncDisp( $params['disposition'] );
- }
// Set any other custom headers if requested
if ( isset( $params['headers'] ) ) {
- $obj->headers += $params['headers'];
+ $obj->headers += $this->sanitizeHdrs( $params['headers'] );
}
if ( !empty( $params['async'] ) ) { // deferred
$op = $obj->write_async( $params['content'] );
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'] = $this->truncDisp( $params['disposition'] );
- }
// Set any other custom headers if requested
if ( isset( $params['headers'] ) ) {
- $obj->headers += $params['headers'];
+ $obj->headers += $this->sanitizeHdrs( $params['headers'] );
}
if ( !empty( $params['async'] ) ) { // deferred
wfSuppressWarnings();
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'] = $this->truncDisp( $params['disposition'] );
+ // Set any other custom headers if requested
+ if ( isset( $params['headers'] ) ) {
+ $hdrs += $this->sanitizeHdrs( $params['headers'] );
}
if ( !empty( $params['async'] ) ) { // deferred
$op = $sContObj->copy_object_to_async( $srcRel, $dContObj, $dstRel, null, $hdrs );
$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'] = $this->truncDisp( $params['disposition'] );
+ // Set any other custom headers if requested
+ if ( isset( $params['headers'] ) ) {
+ $hdrs += $this->sanitizeHdrs( $params['headers'] );
}
if ( !empty( $params['async'] ) ) { // deferred
$op = $sContObj->move_object_to_async( $srcRel, $dContObj, $dstRel, null, $hdrs );
return $status;
}
- $hdrs = isset( $params['headers'] ) ? $params['headers'] : array();
- // Set the Content-Disposition header if requested
- if ( isset( $params['disposition'] ) ) {
- $hdrs['Content-Disposition'] = $this->truncDisp( $params['disposition'] );
- }
-
try {
$sContObj = $this->getContainer( $srcCont );
// Get the latest version of the current metadata
$srcObj = $sContObj->get_object( $srcRel,
$this->headersFromParams( array( 'latest' => true ) ) );
// Merge in the metadata updates...
- $srcObj->headers = $hdrs + $srcObj->headers;
+ if ( isset( $params['headers'] ) ) {
+ $srcObj->headers = $this->sanitizeHdrs( $params['headers'] ) + $srcObj->headers;
+ }
$srcObj->sync_metadata(); // save to Swift
$this->purgeCDNCache( array( $srcObj ) );
} catch ( CDNNotEnabledException $e ) {