View Javadoc

    1   /*******************************************************************************
    2    * Copyright (c) 2011 Michael Mimo Moratti.
    3    *
    4    * Michael Mimo Moratti licenses this file to you under the Apache License, version 2.0
    5    * (the "License"); you may not use this file except in compliance with the License.
    6    * You may obtain a copy of the License at:
    7    *     http://www.apache.org/licenses/LICENSE-2.0
    8    * Unless required by applicable law or agreed to in writing, software
    9    * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
    10   * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
    11   * License for the specific language governing permissions and limitations
    12   * under the License.
    13   *******************************************************************************/
    14  package ch.mimo.netty.handler.codec.icap;
    15
    16  import java.util.List;
    17
    18  import org.jboss.netty.buffer.ChannelBuffer;
    19
    20  /**
    21   * Decoder State that reads icap headers.
    22   *
    23   * @author Michael Mimo Moratti (mimo@mimo.ch)
    24   *
    25   * @see IcapMessageDecoder
    26   * @see StateEnum
    27   */
    28  public class ReadIcapHeaderState extends State<Object> {
    29
    30  	private static final String SYNTHETIC_ENCAPSULATED_HEADER_VALUE = "null-body=0";
    31
    32  	public ReadIcapHeaderState(String name) {
    33  		super(name);
    34  	}
    35
    36  	@Override
    37  	public void onEntry(ChannelBuffer buffer, IcapMessageDecoder icapMessageDecoder) throws DecodingException {
    38  		if(icapMessageDecoder.message == null) {
    39  			throw new IllegalArgumentException("This state requires a valid IcapMessage instance");
    40  		}
    41  	}
    42
    43  	@Override
    44  	public StateReturnValue execute(ChannelBuffer buffer, IcapMessageDecoder icapMessageDecoder) throws DecodingException {
    45  		List<String[]> headerList = IcapDecoderUtil.readHeaders(buffer,icapMessageDecoder.maxIcapHeaderSize);
    46  		icapMessageDecoder.message.clearHeaders();
    47  		for(String[] header : headerList) {
    48  			icapMessageDecoder.message.addHeader(header[0],header[1]);
    49  		}
    50  		boolean isRequest = icapMessageDecoder.message instanceof IcapRequest;
    51  		boolean isOptionsRequest = isRequest && ((IcapRequest)icapMessageDecoder.message).getMethod().equals(IcapMethod.OPTIONS);
    52
    53  		handleEncapsulationHeaderVolatility(icapMessageDecoder.message);
    54  		validateMandatoryMessageHeaders(icapMessageDecoder.message);
    55
    56  		Encapsulated encapsulated = null;
    57  		String headerValue = icapMessageDecoder.message.getHeader(IcapHeaders.Names.ENCAPSULATED);
    58  		if(headerValue != null) {
    59  			encapsulated = new Encapsulated(icapMessageDecoder.message.getHeader(IcapHeaders.Names.ENCAPSULATED));
    60  			icapMessageDecoder.message.setEncapsulatedHeader(encapsulated);
    61  		}
    62  		if(isOptionsRequest) {
    63  			return StateReturnValue.createRelevantResult(icapMessageDecoder.message);
    64  		} else if(encapsulated != null && !encapsulated.containsEntry(IcapMessageElementEnum.REQHDR) & !encapsulated.containsEntry(IcapMessageElementEnum.RESHDR)) {
    65  			return StateReturnValue.createRelevantResult(icapMessageDecoder.message);
    66  		}
    67  		return StateReturnValue.createIrrelevantResult();
    68  	}
    69
    70  	@Override
    71  	public StateEnum onExit(ChannelBuffer buffer, IcapMessageDecoder icapMessageDecoder, Object decisionInformation) throws DecodingException {
    72  		IcapMessage message = icapMessageDecoder.message;
    73  		Encapsulated encapsulated = message.getEncapsulatedHeader();
    74  		if(message instanceof IcapRequest && ((IcapRequest)message).getMethod().equals(IcapMethod.OPTIONS)) {
    75  			if(encapsulated != null && encapsulated.containsEntry(IcapMessageElementEnum.OPTBODY)) {
    76  				return StateEnum.READ_CHUNK_SIZE_STATE;
    77  			} else {
    78  				return null;
    79  			}
    80  		} else {
    81  			IcapMessageElementEnum entry = encapsulated.getNextEntry();
    82  			if(entry != null) {
    83  				if(entry.equals(IcapMessageElementEnum.REQHDR)) {
    84  					return StateEnum.READ_HTTP_REQUEST_INITIAL_AND_HEADERS;
    85  				} else if(entry.equals(IcapMessageElementEnum.RESHDR)) {
    86  					return StateEnum.READ_HTTP_RESPONSE_INITIAL_AND_HEADERS;
    87  				} else if(entry.equals(IcapMessageElementEnum.REQBODY)) {
    88  					return StateEnum.READ_CHUNK_SIZE_STATE;
    89  				} else if(entry.equals(IcapMessageElementEnum.RESBODY)) {
    90  					return StateEnum.READ_CHUNK_SIZE_STATE;
    91  				}
    92  			}
    93  		}
    94  		return null;
    95  	}
    96
    97  	private void validateMandatoryMessageHeaders(IcapMessage message) {
    98  		if(!(message instanceof IcapResponse)) {
    99  			if(!message.containsHeader(IcapHeaders.Names.HOST)) {
    100 				throw new IcapDecodingError("Mandatory ICAP message header [Host] is missing");
    101 			}
    102 		}
    103 		if(!message.containsHeader(IcapHeaders.Names.ENCAPSULATED)) {
    104 			throw new IcapDecodingError("Mandatory ICAP message header [Encapsulated] is missing");
    105 		}
    106 	}
    107
    108 	/**
    109 	 * This method handles the volatility problem of the Encapsulation header. In the RFC (3507) in section 4.4.1 is described
    110 	 * that the Encapsulated header MUST exist on all messages. This also contains OPTIONS request/response and 100,204 responses.
    111 	 *
    112 	 * In the Errata section "When to send an Encapsulated Header" E1 is the MUST requirement weakened! It is now possible to
    113 	 * send OPTIONS requests and 100 Continue, 204 No Content responses without Encapsulated headers.
    114 	 *
    115 	 * This method will create a synthetic Encapsulated header if necessary in order to simplify the downstream logic.
    116 	 */
    117 	private void handleEncapsulationHeaderVolatility(IcapMessage message) {
    118 		// Pseudo code
    119 		// IF Encapsulated header is missing
    120 			// IF OPTIONS request OR 100 Continue response OR 204 No Content response
    121 				// THEN inject synthetic null-body Encapsulated header.
    122 		boolean requiresSynthecticEncapsulationHeader = false;
    123 		if(!message.containsHeader(IcapHeaders.Names.ENCAPSULATED)) {
    124 			if(message instanceof IcapRequest && ((IcapRequest)message).getMethod().equals(IcapMethod.OPTIONS)) {
    125 				requiresSynthecticEncapsulationHeader = true;
    126 			} else if(message instanceof IcapResponse) {
    127 				IcapResponse response = (IcapResponse)message;
    128 				IcapResponseStatus status = response.getStatus();
    129 				if(status.equals(IcapResponseStatus.CONTINUE) | status.equals(IcapResponseStatus.NO_CONTENT)) {
    130 					requiresSynthecticEncapsulationHeader = true;
    131 				}
    132 			}
    133 		}
    134
    135 		if(requiresSynthecticEncapsulationHeader) {
    136 			message.addHeader(IcapHeaders.Names.ENCAPSULATED,SYNTHETIC_ENCAPSULATED_HEADER_VALUE);
    137 		}
    138 	}
    139 }