/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.cxf.transport.websocket;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.Map;
import java.util.Map.Entry;
import java.util.TreeMap;
/**
*
*/
public final class WebSocketUtils {
public static final String URI_KEY = "$uri";
public static final String METHOD_KEY = "$method";
public static final String SC_KEY = "$sc";
public static final String FLUSHED_KEY = "$flushed";
private static final byte[] CRLF = "\r\n".getBytes();
private static final byte[] COLSP = ": ".getBytes();
private WebSocketUtils() {
}
/**
* Read header properties from the specified input stream.
*
* Only a restricted syntax is allowed as the syntax is in our control.
* Not allowed are:
* - multiline or line-wrapped headers are not not
* - charset other than utf-8. (although i would have preferred iso-8859-1 ;-)
*
* @param in the input stream
* @param req true if the input stream includes the request line
* @return a map of name value pairs.
* @throws IOException
*/
public static Map<String, String> readHeaders(InputStream in, boolean req) throws IOException {
Map<String, String> headers = new TreeMap<String, String>(String.CASE_INSENSITIVE_ORDER);
String line;
int del;
if (req) {
// read the request line
line = readLine(in);
del = line.indexOf(' ');
if (del < 0) {
throw new IOException("invalid request: " + line);
}
headers.put(METHOD_KEY, line.substring(0, del).trim());
headers.put(URI_KEY, line.substring(del + 1).trim());
}
// read headers
while ((line = readLine(in)) != null) {
if (line.length() > 0) {
del = line.indexOf(':');
if (del < 0) {
headers.put(line.trim(), "");
} else {
headers.put(line.substring(0, del).trim(), line.substring(del + 1).trim());
}
}
}
return headers;
}
public static Map<String, String> readHeaders(InputStream in) throws IOException {
return readHeaders(in, true);
}
/**
* Read a line terminated by '\n' optionally preceded by '\r' from the
* specified input stream.
* @param in the input stream
* @return
* @throws IOException
*/
// this is copied from AttachmentDeserializer with a minor change to restrict the line termination rule.
public static String readLine(InputStream in) throws IOException {
StringBuilder buffer = new StringBuilder(128);
int c;
while ((c = in.read()) != -1) {
// a linefeed is a terminator, always.
if (c == '\n') {
break;
} else if (c == '\r') {
//just ignore the CR. The next character SHOULD be an NL. If not, we're
//just going to discard this
continue;
} else {
// just add to the buffer
buffer.append((char)c);
}
}
// no characters found...this was either an eof or a null line.
if (buffer.length() == 0) {
return null;
}
return buffer.toString();
}
public static byte[] readBody(InputStream in) throws IOException {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
byte[] buf = new byte[8192];
for (int n = in.read(buf); n > -1; n = in.read(buf)) {
baos.write(buf, 0, n);
}
return baos.toByteArray();
}
/**
* Build response bytes with the status and type information specified in the headers.
*
* @param headers
* @param data
* @param offset
* @param length
* @return
*/
public static byte[] buildResponse(Map<String, String> headers, byte[] data, int offset, int length) {
ByteArrayBuilder sb = new ByteArrayBuilder();
String v = headers.get(SC_KEY);
if (v != null) {
sb.append(v).append(CRLF);
}
sb.append(headers);
if (data != null && length > 0) {
sb.append(CRLF).append(data, offset, length);
}
return sb.toByteArray();
}
/**
* Build response bytes with some generated headers.
*
* @param headers
* @param data
* @param offset
* @param length
* @return
*/
public static byte[] buildResponse(byte[] headers, byte[] data, int offset, int length) {
final int hlen = headers != null ? headers.length : 0;
byte[] longdata = new byte[length + 2 + hlen];
if (hlen > 0) {
System.arraycopy(headers, 0, longdata, 0, hlen);
}
if (data != null && length > 0) {
System.arraycopy(CRLF, 0, longdata, hlen, CRLF.length);
System.arraycopy(data, offset, longdata, hlen + CRLF.length, length);
}
return longdata;
}
/**
* Build response bytes without status and type information.
*
* @param headers
* @param data
* @param offset
* @param length
* @return
*/
public static byte[] buildResponse(byte[] data, int offset, int length) {
return buildResponse((byte[])null, data, offset, length);
}
//FIXME (consolidate the response building code)
public static byte[] buildHeaderLine(String name, String value) {
byte[] hl = new byte[name.length() + COLSP.length + value.length() + CRLF.length];
System.arraycopy(name.getBytes(), 0, hl, 0, name.length());
System.arraycopy(COLSP, 0, hl, name.length(), COLSP.length);
System.arraycopy(value.getBytes(), 0, hl, name.length() + COLSP.length, value.length());
System.arraycopy(CRLF, 0, hl, name.length() + COLSP.length + value.length(), CRLF.length);
return hl;
}
/**
* Build request bytes with the specified method, url, headers, and content entity.
*
* @param method
* @param url
* @param headers
* @param data
* @param offset
* @param length
* @return
*/
public static byte[] buildRequest(String method, String url, Map<String, String> headers,
byte[] data, int offset, int length) {
ByteArrayBuilder sb = new ByteArrayBuilder();
sb.append(method).append(' ').append(url).append(CRLF).append(headers);
if (data != null && length > 0) {
sb.append(CRLF).append(data, offset, length);
}
return sb.toByteArray();
}
private static class ByteArrayBuilder {
private ByteArrayOutputStream baos;
ByteArrayBuilder() {
baos = new ByteArrayOutputStream();
}
public ByteArrayBuilder append(byte[] b) {
try {
baos.write(b);
} catch (IOException e) {
// ignore;
}
return this;
}
public ByteArrayBuilder append(byte[] b, int offset, int length) {
baos.write(b, offset, length);
return this;
}
public ByteArrayBuilder append(String s) {
try {
baos.write(s.getBytes("utf-8"));
} catch (IOException e) {
// ignore
}
return this;
}
public ByteArrayBuilder append(int c) {
baos.write(c);
return this;
}
public ByteArrayBuilder append(Map<String, String> map) {
for (Entry<String, String> m : map.entrySet()) {
if (!m.getKey().startsWith("$")) {
append(m.getKey()).append(COLSP).append(m.getValue()).append(CRLF);
}
}
return this;
}
public byte[] toByteArray() {
return baos.toByteArray();
}
}
}