blob: 4209341696ab08b20d77f054d47fc324791be613 [file] [log] [blame]
<?php
/**
* File containing the ezcWebdavHeaderHandler class.
*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*
* @package Webdav
* @version //autogen//
* @copyright Copyright (C) 2005-2010 eZ Systems AS. All rights reserved.
* @license http://www.apache.org/licenses/LICENSE-2.0 Apache License, Version 2.0
*/
/**
* An instance of this class manages header parsing and handling.
*
* An object of this class takes care about headers in {@link
* ezcWebdavTransport}. It is responsible for parsing incoming headers and
* serialize outgoing ones. Like for the {@link ezcWebdavPropertyHandler}, the
* instance of this class that is used in the current transport layer must be
* accessable for plugins.
*
* @package Webdav
* @version //autogen//
*/
class ezcWebdavHeaderHandler
{
/**
* Map of regular header names to $_SERVER keys.
*
* @var array(string=>string)
*/
protected $headerMap = array(
'Authorization' => array(
'HTTP_AUTHORIZATION',
'PHP_AUTH_DIGEST',
'PHP_AUTH_USER',
),
'Content-Length' => array(
'HTTP_CONTENT_LENGTH',
'CONTENT_LENGTH',
),
'Content-Type' => array(
'CONTENT_TYPE',
),
'Depth' => array(
'HTTP_DEPTH',
),
'Destination' => array(
'HTTP_DESTINATION',
),
'If-Match' => array(
'HTTP_IF_MATCH'
),
'If-None-Match' => array(
'HTTP_IF_NONE_MATCH'
),
'Overwrite' => array(
'HTTP_OVERWRITE',
),
'Server' => array(
'SERVER_SOFTWARE',
),
);
/**
* List of headers that should be attempted to parse for every request.
*
* @var array(string)
*/
protected $defaultHeaders = array(
'Authorization',
'If-Match',
'If-None-Match',
);
/**
* Returns an array with the given headers.
*
* Checks for the availability of headers in $headerNamess, given as an
* array of header names, and parses them according to their format.
*
* By default, this method parses an additional set of default headers
* (e.g. If-Match and If-None-Match). This can be avoided by setting the
* optional $defaultHeaders parameter to false.
*
* The returned array can be used with {@link ezcWebdavRequest->setHeaders()}.
*
* @param array(string) $headerNames
* @return array(string=>mixed)
* @param bool $defaultHeaders
*
* @throws ezcWebdavUnknownHeaderException
* if a header requested in $headerNames is not known in {@link
* $headerNames}.
*/
public function parseHeaders( array $headerNames = array(), $defaultHeaders = true )
{
if ( $defaultHeaders )
{
$headerNames = array_unique(
array_merge( $headerNames, $this->defaultHeaders )
);
}
$resultHeaders = array();
foreach ( $headerNames as $headerName )
{
if ( ( $value = $this->parseHeader( $headerName ) ) !== null )
{
$resultHeaders[$headerName] = $value;
}
}
// Ignore If-Match and If-None-Match if both are set
// @todo: RFC conform, also clients conform?
if ( isset( $resultHeaders['If-Match'] ) && isset( $resultHeaders['If-None-Match'] ) )
{
unset( $resultHeaders['If-Match'] );
unset( $resultHeaders['If-None-Match'] );
}
return $resultHeaders;
}
/**
* Parses a single header.
*
* Retrieves a $headerName and returns the processed value for it, if it
* does exist. If the requested header is unknown, a {@link
* ezcWebdavUnknownHeaderException} is thrown. If the requested header is
* not present in {@link $_SERVER} null is returned.
*
* @param string $headerName
* @return mixed
*/
public function parseHeader( $headerName )
{
if ( !isset( $this->headerMap[$headerName] ) )
{
throw new ezcWebdavUnknownHeaderException( $headerName );
}
foreach ( $this->headerMap[$headerName] as $serverHeaderName )
{
if ( isset( $_SERVER[$serverHeaderName] ) )
{
return $this->processHeader( $headerName, $_SERVER[$serverHeaderName], $serverHeaderName );
}
}
// Default to null, if header is not available
return null;
}
/**
* Processes a single header value.
*
* Takes the $headerName and $value of a header and parses the value
* accordingly, * if necessary. Returns the parsed or unmanipuled result. The
* $serverHeaderName parameter contains the key that was used to extract the
* header from the $_SERVER array.
*
* @param string $headerName
* @param string $value
* @param string $serverHeaderName
* @return mixed
*/
protected function processHeader( $headerName, $value, $serverHeaderName )
{
switch ( $headerName )
{
case 'Authorization':
$value = $this->parseAuthorizationHeader( $value, $serverHeaderName );
break;
case 'Depth':
$value = $this->parseDepthHeader( $value );
break;
case 'Destination':
$value = ezcWebdavServer::getInstance()
->pathFactory
->parseUriToPath( $value );
break;
case 'If-Match':
case 'If-None-Match':
$value = $this->parseIfMatchHeader( $value );
break;
default:
// @todo Add plugin hook
}
return $value;
}
/**
* Parses the Authorization header.
*
* Takes the string value of the Authorization header and parses it
* according to the Basic authentication scheme. The return value is an
* struct of either of the following classes:
*
* <ul>
* <li>{@link ezcWebdavDigestAuth}</li>
* <li>{@link ezcWebdavBasicAuth}</li>
* </ul>
*
* In case the header is provided but does not contain a parseable value,
* the user and pass fields are null. The $serverHeaderName parameter
* indicates, which key from the $_SERVER array was used to extract the
* header.
*
* @param string $value
* @param string $serverHeaderName
* @return array(string)
*/
protected function parseAuthorizationHeader( $value, $serverHeaderName )
{
switch ( $serverHeaderName )
{
case 'PHP_AUTH_DIGEST':
return $this->parseDigestAuthorizationHeader( $value );
case 'PHP_AUTH_USER':
return new ezcWebdavBasicAuth(
$value, $_SERVER['PHP_AUTH_PW']
);
case 'HTTP_AUTHORIZATION':
default:
if ( substr( $value, 0, 5 ) === 'Basic' )
{
return $this->parseBasicAuthorizationHeader(
trim( substr( $value, 6 ) )
);
}
elseif ( substr( $value, 0, 6 ) === 'Digest' )
{
return $this->parseDigestAuthorizationHeader(
trim( substr( $value, 7 ) )
);
}
// In case of an unknown auth method.
return new ezcWebdavAnonymousAuth();
}
}
/**
* Parses the basic authorization header.
*
* Returns a struct of type {@link ezcWebdavBasicAuth}, containing the
* parsed values, or with empty username and password, if the header could
* not be parsed.
*
* @param string $value
* @return ezcWebdavBasicAuth
*/
protected function parseBasicAuthorizationHeader( $value )
{
$res = new ezcWebdavAnonymousAuth();
$credentials = explode(
':',
base64_decode( $value ),
2
);
if ( count( $credentials ) > 1 )
{
// Credentials parsed successfully.
$res = new ezcWebdavBasicAuth( $credentials[0], $credentials[1] );
}
return $res;
}
/**
* Parses the digest authorization header.
*
* Returns an authorization credential struct of type {@link
* ezcWebdavDigestAuth}, containing the parsed data, or an instance of {@link
* ezcWebdavBasicAuth} with empty username and password, if parsing failed.
*
* @param string $value
* @return ezcWebdavDigestAuth|ezcWebdavBasicAuth
*/
protected function parseDigestAuthorizationHeader( $value )
{
// Default, if header cannot be parsed
$res = new ezcWebdavAnonymousAuth();
// Minimum 6 values, otherwise incorrect
if ( preg_match_all( '((\w+)=(?:"([^"]*)"|([A-Za-z0-9]+)))', $value, $matches, PREG_SET_ORDER ) > 5 )
{
$oldRes = $res;
$res = new ezcWebdavDigestAuth( $_SERVER['REQUEST_METHOD'] );
foreach ( $matches as $matchSet )
{
switch ( $matchSet[1] )
{
case 'username':
case 'realm':
case 'nonce':
case 'uri':
case 'response':
case 'algorithm':
case 'opaque':
$res->$matchSet[1] = $matchSet[2];
break;
// Ususally clients should not quote qop and nc, however, we check is some do
case 'qop':
$res->qualityOfProtection = !empty( $matchSet[2] ) ? $matchSet[2] : $matchSet[3];
break;
case 'nc':
$res->nonceCount = !empty( $matchSet[2] ) ? $matchSet[2] : $matchSet[3];
break;
case 'cnonce':
$res->clientNonce = $matchSet[2];
break;
// default:
// ignore
}
}
// Check for anonymous auth
if ( $res->username === '' )
{
$res = $oldRes;
}
}
return $res;
}
/**
* Parses the Depth header.
*
* Parses the values '0', '1' and 'infinity' into the corresponding
* constants:
*
* <ul>
* <li>{@linkezcWebdavRequest::DEPTH_ZERO}</li>
* <li>{@linkezcWebdavRequest::DEPTH_ONE}</li>
* <li>{@linkezcWebdavRequest::DEPTH_INFINITY}</li>
* </ul>
*
* If the header contains a different value it is left as is.
*
* @param string $value
* @return int|string
*/
protected function parseDepthHeader( $value )
{
switch ( trim( $value ) )
{
case '0':
$value = ezcWebdavRequest::DEPTH_ZERO;
break;
case '1':
$value = ezcWebdavRequest::DEPTH_ONE;
break;
case 'infinity':
$value = ezcWebdavRequest::DEPTH_INFINITY;
break;
// No default. Header stays as is, if not matched
}
return $value;
}
/**
* Parses the If-Match and If-None-Match headers.
*
*
* We do not pay attention to weak entity tags (prefixed by W\), since our
* backends don't make use of such tags. If backends want to provide weak
* entity tags, they still might do so.
*
* @param string $value
* @return array
*
* @todo Do we need to provide support for weak entity tags?
*/
protected function parseIfMatchHeader( $value )
{
// Special case
if ( trim( $value ) === '*' )
{
return true;
}
$etags = array();
$index = 0;
$length = strlen( $value );
$inTag = false;
$currentTag = '';
while ( $index < $length )
{
if ( !$inTag )
{
if ( $value[$index] === '"' )
{
$inTag = true;
}
}
else
{
if ( $value[$index] === '"' )
{
$etags[] = $currentTag;
$currentTag = '';
$inTag = false;
}
else
{
$currentTag .= $value[$index];
}
}
++$index;
}
return $etags;
}
}
?>