View Javadoc
1   package com.ericsson.research.transport.ws.spi;
2   
3   /*
4    * ##_BEGIN_LICENSE_##
5    * Transport Abstraction Package (trap)
6    * ----------
7    * Copyright (C) 2014 Ericsson AB
8    * ----------
9    * Redistribution and use in source and binary forms, with or without modification,
10   * are permitted provided that the following conditions are met:
11   * 
12   * 1. Redistributions of source code must retain the above copyright notice, this
13   *    list of conditions and the following disclaimer.
14   * 
15   * 2. Redistributions in binary form must reproduce the above copyright notice,
16   *    this list of conditions and the following disclaimer in the documentation
17   *    and/or other materials provided with the distribution.
18   * 
19   * 3. Neither the name of the Ericsson AB nor the names of its contributors
20   *    may be used to endorse or promote products derived from this software without
21   *    specific prior written permission.
22   * 
23   * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
24   * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
25   * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
26   * IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
27   * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
28   * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
29   * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
30   * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
31   * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
32   * OF THE POSSIBILITY OF SUCH DAMAGE.
33   * ##_END_LICENSE_##
34   */
35  
36  import java.io.IOException;
37  import java.io.OutputStream;
38  import java.io.UnsupportedEncodingException;
39  import java.security.MessageDigest;
40  import java.util.HashMap;
41  import java.util.StringTokenizer;
42  
43  import com.ericsson.research.transport.ws.WSException;
44  
45  public abstract class WSAbstractHandshake implements WSConstants {
46  
47  	protected WSAbstractProtocol protocol;
48  	protected String preamble;
49  	protected HashMap<String, String> headers = new HashMap<String, String>(10);
50  	protected byte[] body;
51  	protected int expectedHandshakeBodyLength = 0;
52  	
53  	public WSAbstractHandshake(WSAbstractProtocol protocol) {
54  		this.protocol = protocol;
55  	}
56  	
57  	public String getHeader(String name) {
58  		return this.headers.get(name.toLowerCase());
59  	}
60  	
61  	public abstract void sendHandshake(OutputStream os) throws IOException;
62  	
63  	public void preambleRead() throws WSException {
64  		StringTokenizer st = new StringTokenizer(this.preamble, " ");
65  		if(st.hasMoreTokens()) {
66  			if(this.protocol.client) {
67  					this.checkHTTPProtocol(st.nextToken(), 1, 1);
68  					if(st.hasMoreTokens()) {
69  						String status = st.nextToken().trim();
70  						if(!"101".equals(status))
71  							throw new WSException("Invalid status code "+status);
72  					}
73  					if(st.hasMoreTokens())
74  						return;
75  			} else {
76  				if(!GET.equals(st.nextToken()))
77  					throw new WSException("Invalid HTTP method, only GET is supported");
78  				if(st.hasMoreTokens()) {
79  					this.protocol.resource = st.nextToken();
80  					if(st.hasMoreTokens()) {
81  						this.checkHTTPProtocol(st.nextToken(), 1, 1);
82  						return;
83  					}
84  				}
85  			}
86  		}
87  		throw new WSException("Invalid preamble ("+this.preamble+")");
88  	}
89  	
90  	public abstract void headersRead() throws WSException;
91  	public void bodyRead() throws WSException {}
92  
93      private static final byte PREAMBLE = 0;
94  	private static final byte HEADER_NAME = 1;
95  	private static final byte HEADER_VALUE = 2;
96  	private static final byte BODY = 3;
97  	
98  	private byte state = PREAMBLE; 
99  	private boolean crRead = false;
100 	private int pos = 0;
101 	private int h = 0;
102 	private int c = 0;
103 	
104 	public int deserialize(byte[] data, int length) throws WSException {
105 		for(;;) {
106 			if(length <= this.pos)
107 				return 0;
108 			if(this.crRead && data[this.pos] != '\n')
109 				throw new WSException("Missing expected LF");
110 			switch(this.state) {
111 				case PREAMBLE:
112 					if(this.crRead) {
113 						try {
114 							this.preamble = new String(data, 0, this.pos-1, UTF_8);
115 						} catch (UnsupportedEncodingException e) {
116 							throw new WSException(e);
117 						}
118 						this.crRead = false;
119 						this.state = HEADER_NAME;
120 						this.h = this.pos + 1;
121 						this.preambleRead();
122 					}
123 					break;
124 				case HEADER_NAME:
125 					if(this.crRead) {
126 						if(this.h+1 == this.pos) {
127 							if(!WEBSOCKET_VALUE.equalsIgnoreCase(this.getHeader(UPGRADE_HEADER)))
128 								throw new WSException("Invalid "+UPGRADE_HEADER+" header");
129 							boolean upgrade = false;
130 							if(this.getHeader(CONNECTION_HEADER)!=null) {
131 								StringTokenizer st = new StringTokenizer(this.getHeader(CONNECTION_HEADER), " ,");
132 								while(st.hasMoreTokens()) {
133 									String s = st.nextToken();
134 									if(s.equalsIgnoreCase(UPGRADE_VALUE)) {
135 										upgrade = true;
136 										break;
137 									}
138 								}
139 							}
140 							if(!upgrade)
141 								throw new WSException("Invalid "+CONNECTION_HEADER+" header");
142 							this.headersRead();
143 							this.state = BODY;
144 							if(this.expectedHandshakeBodyLength > 0) {
145 								this.h = this.pos + 1;
146 								this.pos += this.expectedHandshakeBodyLength;
147 								this.crRead = false;
148 								continue;
149 							} else
150 								return this.pos+1;
151 						} else
152 							throw new WSException("Unexpected end of header");
153 					} else
154 						if(data[this.pos] == ':') {
155 							this.state = HEADER_VALUE;
156 							this.c = this.pos;
157 						}
158 					break;
159 				case HEADER_VALUE:
160 					if(this.crRead) {
161 						try {
162 							String name = new String(data, this.h, this.c-this.h, UTF_8).trim().toLowerCase();
163 							String value = new String(data, this.c+1, this.pos-this.c-2, UTF_8);
164 							//FIXME: Hixie-76 may use spaces in arbitrary places of the header value
165 							if(value.startsWith(" "))
166 								value = value.substring(1);
167 							this.headers.put(name, value);
168 						} catch (UnsupportedEncodingException e) {
169 							throw new WSException(e);
170 						}
171 						this.crRead = false;
172 						this.state = HEADER_NAME;
173 						this.h = this.pos + 1;
174 					}
175 					break;
176 				case BODY:
177 					this.body = new byte[this.expectedHandshakeBodyLength];
178 					if(this.expectedHandshakeBodyLength > 0)
179 						System.arraycopy(data, this.h, this.body, 0, this.expectedHandshakeBodyLength);
180 					this.bodyRead();
181 					return this.pos + 1;
182 			}
183 			if(data[this.pos] == '\r')
184 				this.crRead = true;
185 			this.pos++;
186 		}
187 	}
188 	
189 	public void sendResponse() throws IOException {
190 		this.sendHandshake(this.protocol.getRawOutput());
191 	}
192 	
193 	protected byte[] computeSHA1(String s) throws WSException {
194 		try {
195 			MessageDigest md = MessageDigest.getInstance("SHA-1");
196 			md.update(s.getBytes(ISO_8859_1), 0, s.length());
197 			return md.digest();
198 		} catch (Exception e) {
199 			throw new WSException(e);			
200 		}
201 	}
202 	
203 	protected void checkHTTPProtocol(String protocol, int minMaj, int minMin) throws WSException {
204 		if(!protocol.startsWith("HTTP/"))
205 			throw new WSException("Invalid protocol, only HTTP supported");
206 		String version = protocol.substring(5);
207 		int p = version.indexOf(".");
208 		if(p == -1)
209 			throw new WSException("Invalid HTTP version "+version);
210 		int major = Integer.parseInt(version.substring(0, p));
211 		int minor = Integer.parseInt(version.substring(p + 1));
212 		if (major < minMaj || (major == minMaj && minor < minMin))
213 			throw new WSException("Invalid protocol version, MUST be "+minMaj+"."+minMin+" or higher.");
214 	}
215 	
216 }