<?php
/**
 * File containing the ezcWebdavLockHeaderHandler 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 //autogentag//
 * @license http://www.apache.org/licenses/LICENSE-2.0 Apache License, Version 2.0
 *
 * @access private
 */
/**
 * Header handler for the lock plugin.
 *
 * An object of this class can be used to parse the following headers:
 *
 * <ul>
 *  <li>Timeout - {@link parseTimeoutHeader()}</li>
 *  <li>If - {@link parseIfHeader()}</li>
 *  <li>Lock-Token - {@link parseLockTokenHeader()}</li>
 * </ul>
 * 
 * @package Webdav
 * @version //autogen//
 *
 * @access private
 */
class ezcWebdavLockHeaderHandler
{
    /**
     * Parses the Timeout header content.
     *
     * Parses the Timeout header. Might return an empty array, in case no
     * Timeout header is present or none of the values in the header could be
     * parsed. The values of the returned array are seconds and indicate the
     * number of seconds, that the client wishes the lock to disappear after it
     * was created or used the last time.
     * 
     * @return array(int)
     */
    public function parseTimeoutHeader()
    {
        if ( !isset( $_SERVER['HTTP_TIMEOUT'] ) )
        {
            return null;
        }
        
        $result = array();

        $content = explode( ', ', $_SERVER['HTTP_TIMEOUT'] );
        foreach ( $content as $timeVal )
        {
            // Sanitize
            $timeVal = trim( $timeVal );

            // We only react on 'Second-' values for now
            if ( substr( $timeVal, 0, 7 ) === 'Second-' )
            {
                $result[] = (int) substr( $timeVal, 7 );
                continue;
            }
            
            // Ignore all other for now.
            // @TODO: Let's see, what clients send.
        }

        return $result;
    }

    /**
     * Parses either type of If header content.
     *
     * Returns an {@link ezcWebdavLockIfHeaderList} or null, if the header was
     * not submitted.
     * 
     * @return ezcWebdavLockIfHeaderList
     *
     * @throws ezcWebdavInvalidHeaderException
     *         if any unexpected character occurs while parsing.
     */
    public function parseIfHeader()
    {
        if ( !isset( $_SERVER['HTTP_IF'] ) )
        {
            return null;
        }
        
        $headerContent = $_SERVER['HTTP_IF'];

        if ( $headerContent[0] === '<' )
        {
            return $this->parseTaggedList( $headerContent );
        }
        else
        {
            return $this->parseNonTagList( $headerContent );
        }
    }

    /**
     * Parses the Lock-Token header.
     *
     * Trims the < and > from a given lock token in the Lock-Token header.
     * 
     * @return string|null
     */
    public function parseLockTokenHeader()
    {
        if ( !isset( $_SERVER['HTTP_LOCK_TOKEN'] ) )
        {
            return null;
        }

        // Strip < and >
        return substr( trim( $_SERVER['HTTP_LOCK_TOKEN'] ), 1, -1 );
    }

    /**
     * Parses tagged If header content.
     *
     * This method parses content of an If header that is tagged. The content has the format:
     *
     * <code>
     * &lthttp://www.foo.bar/resource1&gt (&lt;locktoken:a-write-lock-token&gt; [W/"A weak ETag"]) (["strong ETag"]) &lt;http://www.bar.bar/random&gt: (["another strong ETag"])
     * </code>
     * 
     * @param string $content 
     * @return void
     *
     * @throws ezcWebdavInvalidHeaderException
     *         if any unexpected character occurs while parsing.
     */
    protected function parseTaggedList( $content )
    {
        $len = strlen( $content );
        $i   = 0;
    
        $list = new ezcWebdavLockIfHeaderTaggedList();
        $items = array();

        while ( $i < $len )
        {
            switch ( $content[$i] )
            {
                case '<':
                    if ( $items !== array() )
                    {
                        // Store last parsed list
                        $list[$currentPath] = $items;
                        $items = array();
                    }
                    ++$i;
                    $currentPath = $this->parseTagPath( $content, $len, $i );
                    break;

                case '(':
                    ++$i;
                    $items[] = $this->parseConditionList( $content, $len, $i );
                    break;
                    
                case ' ':
                case "\t":
                case "\n":
                    // skip
                    break;

                default:
                    throw new ezcWebdavInvalidHeaderException(
                        'If',
                        $content,
                        "Headers without invalid character '{$content[$i]}' in tagged list, position $i"
                    );
            }
            ++$i;
        }
        $list[$currentPath] = $items;

        return $list;
    }

    /**
     * Parses the tag of a tagged list.
     * 
     * @param string $content 
     * @param int $len 
     * @param int $i 
     * @return string
     *
     * @throws ezcWebdavInvalidHeaderException
     *         if any unexpected character occurs while parsing.
     */
    protected function parseTagPath( $content, $len, &$i )
    {
        $uri = '';
        
        while ( $i < $len && $content[$i] !== '>' )
        {
            $uri .= $content[$i++];
        }
        ++$i;

        return ezcWebdavServer::getInstance()->pathFactory->parseUriToPath( $uri );
    }

    /**
     * Parses not-tagged If header content.
     *
     * This method parses content of an If header that is tagged. The content has the format:
     *
     * <code>
     * (&lt;locktoken:a-write-lock-token&gt; [W/"A weak ETag"]) (["strong ETag"])(["another strong ETag"])
     * </code>
     * 
     * @param string $content 
     * @return ezcWebdavLockIfHeaderNoTagList
     *
     * @throws ezcWebdavInvalidHeaderException
     *         if any unexpected character occurs while parsing.
     */
    protected function parseNonTagList( $content )
    {
        $len = strlen( $content );
        $i   = 0;
    
        $items = array();

        while ( $i < $len )
        {
            switch ( $content[$i] )
            {
                case '(':
                    ++$i;
                    $items[] = $this->parseConditionList( $content, $len, $i );
                    break;
                case ' ':
                case "\t":
                case "\n":
                    // ignore
                    break;

                default:
                    throw new ezcWebdavInvalidHeaderException(
                        'If',
                        $content,
                        "Headers without invalid character '{$content[$id]}' in non-tagged list, position $i"
                    );
            }
            ++$i;
        }

        return new ezcWebdavLockIfHeaderNoTagList( $items );
    }

    /**
     * Parses a list of lock tokens and etags into a list item.
     * 
     * @param string $content 
     * @param int $len 
     * @param int $i 
     * @return ezcWebdavLockIfHeaderListItem
     *
     * @throws ezcWebdavInvalidHeaderException
     *         if any unexpected character occurs while parsing.
     */
    protected function parseConditionList( $content, $len, &$i )
    {
        $lockTokens = array();
        $eTags      = array();

        $negated = false;

        // Walk complete list and scan tokens/etags
        while ( $i < $len && $content[$i] !== ')' )
        {
            switch ( $content[$i] )
            {
                case '<':
                    ++$i;
                    $lockToken = $this->parseLockToken( $content, $len, $i );
                    if ( $negated )
                    {
                        $lockToken->negated = true;
                    }
                    $lockTokens[] = $lockToken;
                    $negated = false;
                    break;

                case '[':
                    ++$i;
                    $eTag = $this->parseEtag( $content, $len, $i );
                    if ( $negated )
                    {
                        $eTag->negated = true;
                    }
                    $eTags[] = $eTag;
                    $negated = false;
                    break;

                case 'N':
                case 'n':
                    if ( strtolower( substr( $content, $i, 3 ) ) === 'not' )
                    {
                        $negated = true;
                    }
                    $i += 3;
                    break;

                case ' ':
                case "\t":
                case "\n":
                    // ignore
                    break;

                default:
                    throw new ezcWebdavInvalidHeaderException(
                        'If',
                        $content,
                        "Headers without invalid character '{$content[$i]}' in etag/lock-token list, position $i"
                    );
            }
            ++$i;
        }

        return new ezcWebdavLockIfHeaderListItem( $lockTokens, $eTags, $negated );
    }

    /**
     * Parses a single lock token.
     * 
     * @param string $content 
     * @param int $len 
     * @param int $i 
     * @return string
     *
     * @throws ezcWebdavInvalidHeaderException
     *         if any unexpected character occurs while parsing.
     */
    protected function parseLockToken( $content, $len, &$i )
    {
        $token = '';
        
        while ( $i < $len && $content[$i] !== '>' )
        {
            $token .= $content[$i++];
        }

        return new ezcWebdavLockIfHeaderCondition( $token );
    }

    /**
     * Parses a single ETag.
     *
     * Attention, this method ignores weak tags, since the whole Webdav
     * component does not support those.
     * 
     * @param string $content 
     * @param int $len 
     * @param int $i 
     * @return string
     *
     * @throws ezcWebdavInvalidHeaderException
     *         if any unexpected character occurs while parsing.
     */
    protected function parseEtag( $content, $len, &$i )
    {
        $etag = '';

        while ( $i < $len && $content[$i] !== ']' )
        {
            switch ( $content[$i] )
            {
                case '/':
                    // ETag marked as weak, ignore parts before
                    $etag = '';
                    break;

                case '"':
                    // Etag wrapped in "", ignore
                    break;

                default:
                    $etag .= $content[$i];
                    break;
            }
            ++$i;
        }

        return new ezcWebdavLockIfHeaderCondition( $etag );
    }
}

?>
