001/* 002 * This file is part of the JDrupes non-blocking HTTP Codec 003 * Copyright (C) 2016, 2017 Michael N. Lipp 004 * 005 * This program is free software; you can redistribute it and/or modify it 006 * under the terms of the GNU Lesser General Public License as published 007 * by the Free Software Foundation; either version 3 of the License, or 008 * (at your option) any later version. 009 * 010 * This program is distributed in the hope that it will be useful, but 011 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY 012 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public 013 * License for more details. 014 * 015 * You should have received a copy of the GNU Lesser General Public License along 016 * with this program; if not, see <http://www.gnu.org/licenses/>. 017 */ 018 019package org.jdrupes.httpcodec.protocols.websocket; 020 021import java.io.UnsupportedEncodingException; 022import java.security.MessageDigest; 023import java.security.NoSuchAlgorithmException; 024import java.security.SecureRandom; 025import java.util.Base64; 026import java.util.Optional; 027 028import org.jdrupes.httpcodec.Decoder; 029import org.jdrupes.httpcodec.Encoder; 030import org.jdrupes.httpcodec.ProtocolException; 031import org.jdrupes.httpcodec.plugin.UpgradeProvider; 032import org.jdrupes.httpcodec.protocols.http.HttpConstants.HttpStatus; 033import org.jdrupes.httpcodec.protocols.http.HttpField; 034import org.jdrupes.httpcodec.protocols.http.HttpRequest; 035import org.jdrupes.httpcodec.protocols.http.HttpResponse; 036import org.jdrupes.httpcodec.types.Converters; 037 038/** 039 * A protocol provider for the WebSocket protocol. 040 * 041 * The web socket protocol is an upgrade from the HTTP protocol. 042 * 043 * ![WsProtocolProvider](WsProtocolProvider.svg) 044 * 045 * @startuml WsProtocolProvider.svg 046 * 047 * class ProtocolProvider 048 * 049 * class WsProtocolProvider { 050 * +boolean supportsProtocol(String protocol) 051 * +void augmentInitialResponse(HttpResponse response) 052 * +Encoder<?> createRequestEncoder(String protocol) 053 * +Decoder<?,?> createRequestDecoder(String protocol) 054 * +Encoder<?> createResponseEncoder(String protocol) 055 * +ResponseDecoder<?,?> createResponseDecoder(String protocol) 056 * } 057 * 058 * ProtocolProvider <|-- WsProtocolProvider 059 * 060 * @enduml 061 * 062 */ 063public class WsProtocolProvider extends UpgradeProvider { 064 065 private static SecureRandom random = new SecureRandom(); 066 067 /* (non-Javadoc) 068 * @see ProtocolProvider#supportsProtocol(java.lang.String) 069 */ 070 @Override 071 public boolean supportsProtocol(String protocol) { 072 return protocol.equalsIgnoreCase("websocket"); 073 } 074 075 /* (non-Javadoc) 076 * @see org.jdrupes.httpcodec.plugin.UpgradeProvider#augmentInitialRequest 077 */ 078 @Override 079 public void augmentInitialRequest(HttpRequest request) { 080 Optional<HttpField<Long>> version = request.findField( 081 "Sec-WebSocket-Version", Converters.LONG); 082 if (version.isPresent() && version.get().value() != 13) { 083 // Sombody else's job... 084 return; 085 } 086 request.setField(new HttpField<>( 087 "Sec-WebSocket-Version", 13L, Converters.LONG)); 088 if (!request.findField("Sec-WebSocket-Key", Converters.UNQUOTED_STRING) 089 .isPresent()) { 090 byte[] randomBytes = new byte[16]; 091 random.nextBytes(randomBytes); 092 request.setField(new HttpField<String>("Sec-WebSocket-Key", 093 Base64.getEncoder().encodeToString(randomBytes), 094 Converters.UNQUOTED_STRING)); 095 } 096 } 097 098 /* (non-Javadoc) 099 * @see org.jdrupes.httpcodec.plugin.UpgradeProvider#checkSwitchingResponse 100 */ 101 @Override 102 public void checkSwitchingResponse(HttpRequest request, 103 HttpResponse response) throws ProtocolException { 104 Optional<String> accept = response.findStringValue( 105 "Sec-WebSocket-Accept"); 106 if (!accept.isPresent()) { 107 throw new ProtocolException( 108 "Header field Sec-WebSocket-Accept is missing."); 109 } 110 String wsKey = request.findStringValue("Sec-WebSocket-Key").get(); 111 String magic = wsKey + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"; 112 try { 113 MessageDigest crypt = MessageDigest.getInstance("SHA-1"); 114 byte[] sha1 = crypt.digest(magic.getBytes("ascii")); 115 String expected = Base64.getEncoder().encodeToString(sha1); 116 if (!accept.get().equals(expected)) { 117 throw new ProtocolException( 118 "Invalid value in Sec-WebSocket-Accept header field."); 119 } 120 } catch (NoSuchAlgorithmException | UnsupportedEncodingException e) { 121 throw new ProtocolException(e); 122 } 123 } 124 125 /* (non-Javadoc) 126 * @see ProtocolProvider#augmentInitialResponse 127 */ 128 @Override 129 public void augmentInitialResponse(HttpResponse response) { 130 Optional<String> wsKey = response.request() 131 .flatMap(r -> r.findStringValue("Sec-WebSocket-Key")); 132 if (!wsKey.isPresent()) { 133 response.setStatus(HttpStatus.BAD_REQUEST) 134 .setHasPayload(false).clearHeaders(); 135 return; 136 } 137 // RFC 6455 4.1 138 if(response.request().flatMap(r -> r.findField( 139 "Sec-WebSocket-Version", Converters.LONG)) 140 .map(HttpField<Long>::value).orElse(-1L) != 13) { 141 response.setStatus(HttpStatus.BAD_REQUEST) 142 .setHasPayload(false).clearHeaders(); 143 // RFC 6455 4.4 144 response.setField(new HttpField<>( 145 "Sec-WebSocket-Version", 13L, Converters.LONG)); 146 return; 147 148 } 149 String magic = wsKey.get() + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"; 150 try { 151 MessageDigest crypt = MessageDigest.getInstance("SHA-1"); 152 byte[] sha1 = crypt.digest(magic.getBytes("ascii")); 153 String accept = Base64.getEncoder().encodeToString(sha1); 154 response.setField(new HttpField<String>( 155 "Sec-WebSocket-Accept", accept, Converters.UNQUOTED_STRING)); 156 } catch (NoSuchAlgorithmException | UnsupportedEncodingException e) { 157 response.setStatus(HttpStatus.INTERNAL_SERVER_ERROR) 158 .setHasPayload(false).clearHeaders(); 159 return; 160 } 161 } 162 163 /* (non-Javadoc) 164 * @see ProtocolProvider#createRequestEncoder() 165 */ 166 @Override 167 public Encoder<?, ?> createRequestEncoder(String protocol) { 168 return new WsEncoder(true); 169 } 170 171 /* (non-Javadoc) 172 * @see ProtocolProvider#createRequestDecoder() 173 */ 174 @Override 175 public Decoder<?, ?> createRequestDecoder(String protocol) { 176 return new WsDecoder(); 177 } 178 179 /* (non-Javadoc) 180 * @see ProtocolProvider#createResponseEncoder() 181 */ 182 @Override 183 public Encoder<?, ?> createResponseEncoder(String protocol) { 184 return new WsEncoder(false); 185 } 186 187 /* (non-Javadoc) 188 * @see ProtocolProvider#createResponseDecoder() 189 */ 190 @Override 191 public Decoder<?, ?> createResponseDecoder(String protocol) { 192 return new WsDecoder(); 193 } 194 195}