org.eclipse.paho.client.mqttv3.internal.websocket.WebSocketHandshake Maven / Gradle / Ivy
/*******************************************************************************
* Copyright (c) 2009, 2014 IBM Corp.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* and Eclipse Distribution License v1.0 which accompany this distribution.
*
* The Eclipse Public License is available at
* http://www.eclipse.org/legal/epl-v10.html
* and the Eclipse Distribution License is available at
* http://www.eclipse.org/org/documents/edl-v10.php.
*
* Contributors:
* James Sutton - Bug 459142 - WebSocket support for the Java client.
*/
package org.eclipse.paho.client.mqttv3.internal.websocket;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.net.URI;
import java.net.URISyntaxException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
/**
* Helper class to execute a WebSocket Handshake.
*/
public class WebSocketHandshake {
// Do not change: https://tools.ietf.org/html/rfc6455#section-1.3
private static final String ACCEPT_SALT = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
private static final String SHA1_PROTOCOL = "SHA1";
private static final String HTTP_HEADER_SEC_WEBSOCKET_ACCEPT = "sec-websocket-accept";
private static final String HTTP_HEADER_UPGRADE = "upgrade";
private static final String HTTP_HEADER_UPGRADE_WEBSOCKET = "websocket";
private static final String EMPTY = "";
private static final String LINE_SEPARATOR = "\r\n";
private static final String HTTP_HEADER_CONNECTION = "connection";
private static final String HTTP_HEADER_CONNECTION_VALUE = "upgrade";
private static final String HTTP_HEADER_SEC_WEBSOCKET_PROTOCOL = "sec-websocket-protocol";
InputStream input;
OutputStream output;
String uri;
String host;
int port;
public WebSocketHandshake(InputStream input, OutputStream output, String uri, String host, int port){
this.input = input;
this.output = output;
this.uri = uri;
this.host = host;
this.port = port;
}
/**
* Executes a Websocket Handshake.
* Will throw an IOException if the handshake fails
* @throws IOException
*/
public void execute() throws IOException {
String key = "mqtt-" + (System.currentTimeMillis()/1000);
String b64Key = Base64.encode(key);
sendHandshakeRequest(b64Key);
receiveHandshakeResponse(b64Key);
}
/**
* Builds and sends the HTTP Header GET Request
* for the socket.
* @param Base64 encoded key
* @throws IOException
*/
private void sendHandshakeRequest(String key) throws IOException{
try {
String path = "/mqtt";
URI srvUri = new URI(uri);
if (srvUri.getRawPath() != null && !srvUri.getRawPath().isEmpty()) {
path = srvUri.getRawPath();
if (srvUri.getRawQuery() != null && !srvUri.getRawQuery().isEmpty()) {
path += "?" + srvUri.getRawQuery();
}
}
PrintWriter pw = new PrintWriter(output);
pw.print("GET " + path + " HTTP/1.1" + LINE_SEPARATOR);
pw.print("Host: " + host + ":" + port + LINE_SEPARATOR);
pw.print("Upgrade: websocket" + LINE_SEPARATOR);
pw.print("Connection: Upgrade" + LINE_SEPARATOR);
pw.print("Sec-WebSocket-Key: " + key + LINE_SEPARATOR);
pw.print("Sec-WebSocket-Protocol: mqttv3.1" + LINE_SEPARATOR);
pw.print("Sec-WebSocket-Version: 13" + LINE_SEPARATOR);
pw.print(LINE_SEPARATOR);
pw.flush();
} catch (URISyntaxException e) {
throw new IllegalStateException(e);
}
}
/**
* Receives the Handshake response and verifies that it is valid.
* @param Base64 encoded key
* @throws IOException
*/
private void receiveHandshakeResponse(String key) throws IOException {
BufferedReader in = new BufferedReader(new InputStreamReader(input));
ArrayList responseLines = new ArrayList();
String line = in.readLine();
if(line == null){
throw new IOException("WebSocket Response header: Invalid response from Server, It may not support WebSockets.");
}
while(!line.equals(EMPTY) ) {
responseLines.add(line);
line = in.readLine();
}
Map headerMap = getHeaders(responseLines);
String connectionHeader = (String) headerMap.get(HTTP_HEADER_CONNECTION);
if (connectionHeader == null || connectionHeader.equalsIgnoreCase(HTTP_HEADER_CONNECTION_VALUE)) {
throw new IOException("WebSocket Response header: Incorrect connection header");
}
String upgradeHeader = (String) headerMap.get(HTTP_HEADER_UPGRADE);
if(!upgradeHeader.toLowerCase().contains(HTTP_HEADER_UPGRADE_WEBSOCKET)){
throw new IOException("WebSocket Response header: Incorrect upgrade.");
}
String secWebsocketProtocolHeader = (String) headerMap.get(HTTP_HEADER_SEC_WEBSOCKET_PROTOCOL);
if (secWebsocketProtocolHeader == null) {
throw new IOException("WebSocket Response header: empty sec-websocket-protocol");
}
if(!headerMap.containsKey(HTTP_HEADER_SEC_WEBSOCKET_ACCEPT)){
throw new IOException("WebSocket Response header: Missing Sec-WebSocket-Accept");
}
try {
verifyWebSocketKey(key, (String)headerMap.get(HTTP_HEADER_SEC_WEBSOCKET_ACCEPT));
} catch (NoSuchAlgorithmException e) {
throw new IOException(e.getMessage());
} catch (HandshakeFailedException e) {
throw new IOException("WebSocket Response header: Incorrect Sec-WebSocket-Key");
}
}
/**
* Returns a Hashmap of HTTP headers
* @param ArrayList of headers
* @return A Hashmap of the headers
*/
private Map getHeaders(ArrayList headers){
Map headerMap = new HashMap();
for(int i = 1; i < headers.size(); i++){
String headerPre = (String) headers.get(i);
String[] header = headerPre.split(":");
headerMap.put(header[0].toLowerCase(), header[1]);
}
return headerMap;
}
/**
* Verifies that the Accept key provided is correctly built from the
* original key sent.
* @param key
* @param accept
* @throws NoSuchAlgorithmException
* @throws HandshakeFailedException
*/
private void verifyWebSocketKey(String key, String accept) throws NoSuchAlgorithmException, HandshakeFailedException{
// We build up the accept in the same way the server should
// then we check that the response is the same.
byte[] sha1Bytes = sha1(key + ACCEPT_SALT);
String encodedSha1Bytes = Base64.encodeBytes(sha1Bytes).trim();
if(!encodedSha1Bytes.equals(encodedSha1Bytes)){
throw new HandshakeFailedException();
}
}
/**
* Returns the sha1 byte array of the provided string.
* @param input
* @return
* @throws NoSuchAlgorithmException
*/
private byte[] sha1(String input) throws NoSuchAlgorithmException {
MessageDigest mDigest = MessageDigest.getInstance(SHA1_PROTOCOL);
byte[] result = mDigest.digest(input.getBytes());
return result;
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy