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 }