// Copyright (c) 2011 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package org.chromium.sdk.internal.websocket;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.InetSocketAddress;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
/**
* Methods, classes and constants used in all WebSocket handshake procedures.
*/
public class HandshakeUtil {
public static final Charset UTF_8_CHARSET = Charset.forName("UTF-8");
public static final Charset ASCII_CHARSET = Charset.forName("ASCII");
static ArrayList<String> createHttpFields(InetSocketAddress endpoint) {
ArrayList<String> fields = new ArrayList<String>();
fields.add("Connection: Upgrade");
return fields;
}
static void checkOriginString(String origin) {
for (int i = 0; i < origin.length(); i++) {
char ch = origin.charAt(i);
if (ch >= 'A' && ch <= 'Z') {
throw new IllegalArgumentException();
}
}
}
public interface HttpResponse {
int getCode();
Map<String, String> getFields();
String getReasonPhrase();
}
public static HttpResponse readHttpResponse(LineReader input) throws IOException {
final int code;
final String reasonPhrase;
// First line.
{
byte[] firstLine = input.readUpTo0x0D0A();
if (firstLine.length < 7 - 2) {
throw new IOException("Malformed response");
}
int space1Pos = byteIndexOf((byte)' ', firstLine, 0);
if (space1Pos == -1) {
throw new IOException("Malformed response");
}
int space2Pos = byteIndexOf((byte)' ', firstLine, space1Pos + 1);
if (space2Pos == -1) {
throw new IOException("Malformed response");
}
if (space2Pos - space1Pos != 4) {
throw new IOException("Malformed response");
}
int codeTemp = 0;
for (int i = space1Pos + 1; i < space2Pos; i++) {
codeTemp = codeTemp * 10 + firstLine[i] - (byte)'0';
}
code = codeTemp;
int reasonPhraseStart = space2Pos + 1;
reasonPhrase = new String(firstLine, reasonPhraseStart, firstLine.length - space2Pos - 1,
ASCII_CHARSET);
}
// Fields.
final Map<String, String> responseFields;
{
responseFields = new HashMap<String, String>();
while (true) {
byte[] line = input.readUpTo0x0D0A();
if (line.length == 0) {
break;
}
String lineStr = new String(line, UTF_8_CHARSET);
int colonPos = lineStr.indexOf(':');
if (colonPos == -1) {
throw new IOException("Malformed response field");
}
if (colonPos == 0) {
throw new IOException("Malformed response field: empty key");
}
String key = lineStr.substring(0, colonPos).toLowerCase();
if (lineStr.length() > colonPos + 1 && lineStr.charAt(colonPos + 1) == ' ') {
colonPos++;
}
String value = lineStr.substring(colonPos + 1);
Object conflict = responseFields.put(key, value);
if (conflict != null) {
throw new IOException("Malformed response field: duplicated field: " + key);
}
}
}
return new HttpResponse() {
@Override public int getCode() {
return code;
}
@Override public String getReasonPhrase() {
return reasonPhrase;
}
@Override public Map<String, String> getFields() {
return responseFields;
}
};
}
public static abstract class LineReader {
abstract byte[] readUpTo0x0D0A() throws IOException;
}
public static LineReader createLineReader(final InputStream input) {
return new LineReader() {
@Override
byte[] readUpTo0x0D0A() throws IOException {
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
while (true) {
// TODO(peter.rybin): this is slow (for connection logger implementation).
int i = input.read();
if (i == -1) {
throw new IOException("End of stream");
}
byte b = (byte) i;
if (b == 0x0D) {
break;
}
if (b == 0x0A) {
throw new IOException("Malformed end of line");
}
outputStream.write(b);
}
{
int i = input.read();
if (i == -1) {
throw new IOException("End of stream");
}
byte b = (byte) i;
if (b != 0x0A) {
throw new IOException("Malformed end of line");
}
}
return outputStream.toByteArray();
}
};
}
private static int byteIndexOf(byte b, byte[] array, int start) {
int i = start;
while (i < array.length) {
if (array[i] == b) {
return i;
}
i++;
}
return -1;
}
}