1 package com.ericsson.research.transport.ws.spi;
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
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
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 }