map = new HashMap<>();
266 | for (int i = 0; i < request.length; i++) {
267 | log.trace("Request {}: {}", i, request[i]);
268 | if (request[i].startsWith("GET ") || request[i].startsWith("POST ") || request[i].startsWith("PUT ")) {
269 | // "GET /chat/room1?id=publisher1 HTTP/1.1"
270 | // split it on space
271 | String requestPath = request[i].split("\\s+")[1];
272 | // get the path data for handShake
273 | int start = requestPath.indexOf('/');
274 | int end = requestPath.length();
275 | int ques = requestPath.indexOf('?');
276 | if (ques > 0) {
277 | end = ques;
278 | }
279 | log.trace("Request path: {} to {} ques: {}", start, end, ques);
280 | String path = requestPath.substring(start, end).trim();
281 | log.trace("Client request path: {}", path);
282 | conn.setPath(path);
283 | // check for '?' or included query string
284 | if (ques > 0) {
285 | // parse any included query string
286 | String qs = requestPath.substring(ques).trim();
287 | log.trace("Request querystring: {}", qs);
288 | map.put(Constants.URI_QS_PARAMETERS, parseQuerystring(qs));
289 | }
290 | // get the manager
291 | WebSocketPlugin plugin = (WebSocketPlugin) PluginRegistry.getPlugin("WebSocketPlugin");
292 | if (plugin != null) {
293 | log.trace("Found plugin");
294 | WebSocketScopeManager manager = plugin.getManager(path);
295 | log.trace("Manager was found? : {}", manager);
296 | // only check that the application is enabled, not the room or sub levels
297 | if (manager != null && manager.isEnabled(path)) {
298 | log.trace("Path enabled: {}", path);
299 | } else {
300 | // invalid scope or its application is not enabled, send disconnect message
301 | conn.close(1002, build400Response(conn));
302 | throw new WebSocketException("Handshake failed, path not enabled");
303 | }
304 | } else {
305 | log.warn("Plugin lookup failed");
306 | conn.close(1002, build400Response(conn));
307 | throw new WebSocketException("Handshake failed, missing plugin");
308 | }
309 | } else if (request[i].contains(Constants.WS_HEADER_KEY)) {
310 | map.put(Constants.WS_HEADER_KEY, extractHeaderValue(request[i]));
311 | } else if (request[i].contains(Constants.WS_HEADER_VERSION)) {
312 | map.put(Constants.WS_HEADER_VERSION, extractHeaderValue(request[i]));
313 | } else if (request[i].contains(Constants.WS_HEADER_EXTENSIONS)) {
314 | map.put(Constants.WS_HEADER_EXTENSIONS, extractHeaderValue(request[i]));
315 | } else if (request[i].contains(Constants.WS_HEADER_PROTOCOL)) {
316 | map.put(Constants.WS_HEADER_PROTOCOL, extractHeaderValue(request[i]));
317 | } else if (request[i].contains(Constants.HTTP_HEADER_HOST)) {
318 | // get the host data
319 | host = extractHeaderValue(request[i]);
320 | conn.setHost(host);
321 | } else if (request[i].contains(Constants.HTTP_HEADER_ORIGIN)) {
322 | // get the origin data
323 | origin = extractHeaderValue(request[i]);
324 | conn.setOrigin(origin);
325 | } else if (request[i].contains(Constants.HTTP_HEADER_USERAGENT)) {
326 | map.put(Constants.HTTP_HEADER_USERAGENT, extractHeaderValue(request[i]));
327 | } else if (request[i].startsWith(Constants.WS_HEADER_GENERIC_PREFIX)) {
328 | map.put(getHeaderName(request[i]), extractHeaderValue(request[i]));
329 | }
330 | }
331 | // policy checking
332 | boolean validOrigin = true;
333 | if (conn.isSameOriginPolicy()) {
334 | // if SOP / origin validation is enabled, verify the origin
335 | String trimmedHost = host;
336 | // strip protocol if its there
337 | if (host.startsWith("http")) {
338 | trimmedHost = host.substring(host.indexOf("//") + 1);
339 | }
340 | // chop off port etc..
341 | int colonIndex = trimmedHost.indexOf(':');
342 | if (colonIndex > 0) {
343 | trimmedHost = trimmedHost.substring(0, colonIndex);
344 | }
345 | log.debug("Trimmed host: {}", trimmedHost);
346 | validOrigin = origin.contains(trimmedHost);
347 | log.debug("Same Origin? {}", validOrigin);
348 | }
349 | if (conn.isCrossOriginPolicy()) {
350 | // if CORS is enabled
351 | validOrigin = conn.isValidOrigin(origin);
352 | log.debug("Origin {} valid? {}", origin, validOrigin);
353 | }
354 | if (!validOrigin) {
355 | conn.close(1008, build403Response(conn));
356 | throw new WebSocketException(String.format("Policy failure - SOP enabled: %b CORS enabled: %b", WebSocketTransport.isSameOriginPolicy(), WebSocketTransport.isCrossOriginPolicy()));
357 | } else {
358 | log.debug("Origin is valid");
359 | }
360 | return map;
361 | }
362 |
363 | /**
364 | * Returns the trimmed header name.
365 | *
366 | * @param requestHeader
367 | * @return value
368 | */
369 | private String getHeaderName(String requestHeader) {
370 | return requestHeader.substring(0, requestHeader.indexOf(':')).trim();
371 | }
372 |
373 | /**
374 | * Returns the trimmed header value.
375 | *
376 | * @param requestHeader
377 | * @return value
378 | */
379 | private String extractHeaderValue(String requestHeader) {
380 | return requestHeader.substring(requestHeader.indexOf(':') + 1).trim();
381 | }
382 |
383 | /**
384 | * Build a handshake response based on the given client key.
385 | *
386 | * @param clientKey
387 | * @return response
388 | * @throws WebSocketException
389 | */
390 | private HandshakeResponse buildHandshakeResponse(WebSocketConnection conn, String clientKey) throws WebSocketException {
391 | if (log.isDebugEnabled()) {
392 | log.debug("buildHandshakeResponse: {} client key: {}", conn, clientKey);
393 | }
394 | byte[] accept;
395 | try {
396 | // performs the accept creation routine from RFC6455 @see RFC6455
397 | // concatenate the key and magic string, then SHA1 hash and base64 encode
398 | MessageDigest md = MessageDigest.getInstance("SHA1");
399 | accept = Base64.encode(md.digest((clientKey + Constants.WEBSOCKET_MAGIC_STRING).getBytes()));
400 | } catch (NoSuchAlgorithmException e) {
401 | throw new WebSocketException("Algorithm is missing");
402 | }
403 | // make up reply data...
404 | IoBuffer buf = IoBuffer.allocate(308);
405 | buf.setAutoExpand(true);
406 | buf.put("HTTP/1.1 101 Switching Protocols".getBytes());
407 | buf.put(Constants.CRLF);
408 | buf.put("Upgrade: websocket".getBytes());
409 | buf.put(Constants.CRLF);
410 | buf.put("Connection: Upgrade".getBytes());
411 | buf.put(Constants.CRLF);
412 | buf.put("Server: Red5".getBytes());
413 | buf.put(Constants.CRLF);
414 | buf.put("Sec-WebSocket-Version-Server: 13".getBytes());
415 | buf.put(Constants.CRLF);
416 | buf.put(String.format("Sec-WebSocket-Origin: %s", conn.getOrigin()).getBytes());
417 | buf.put(Constants.CRLF);
418 | buf.put(String.format("Sec-WebSocket-Location: %s", conn.getHost()).getBytes());
419 | buf.put(Constants.CRLF);
420 | // send back extensions if enabled
421 | if (conn.hasExtensions()) {
422 | buf.put(String.format("Sec-WebSocket-Extensions: %s", conn.getExtensionsAsString()).getBytes());
423 | buf.put(Constants.CRLF);
424 | }
425 | // send back protocol if enabled
426 | if (conn.hasProtocol()) {
427 | buf.put(String.format("Sec-WebSocket-Protocol: %s", conn.getProtocol()).getBytes());
428 | buf.put(Constants.CRLF);
429 | }
430 | buf.put(String.format("Sec-WebSocket-Accept: %s", new String(accept)).getBytes());
431 | buf.put(Constants.CRLF);
432 | buf.put(Constants.CRLF);
433 | // if any bytes follow this crlf, the follow-up data will be corrupted
434 | if (log.isTraceEnabled()) {
435 | log.trace("Handshake response size: {}", buf.limit());
436 | }
437 | return new HandshakeResponse(buf);
438 | }
439 |
440 | /**
441 | * Build an HTTP 400 "Bad Request" response.
442 | *
443 | * @return response
444 | * @throws WebSocketException
445 | */
446 | private HandshakeResponse build400Response(WebSocketConnection conn) throws WebSocketException {
447 | if (log.isDebugEnabled()) {
448 | log.debug("build400Response: {}", conn);
449 | }
450 | // make up reply data...
451 | IoBuffer buf = IoBuffer.allocate(32);
452 | buf.setAutoExpand(true);
453 | buf.put("HTTP/1.1 400 Bad Request".getBytes());
454 | buf.put(Constants.CRLF);
455 | buf.put("Sec-WebSocket-Version-Server: 13".getBytes());
456 | buf.put(Constants.CRLF);
457 | buf.put(Constants.CRLF);
458 | if (log.isTraceEnabled()) {
459 | log.trace("Handshake error response size: {}", buf.limit());
460 | }
461 | return new HandshakeResponse(buf);
462 | }
463 |
464 | /**
465 | * Build an HTTP 403 "Forbidden" response.
466 | *
467 | * @return response
468 | * @throws WebSocketException
469 | */
470 | private HandshakeResponse build403Response(WebSocketConnection conn) throws WebSocketException {
471 | if (log.isDebugEnabled()) {
472 | log.debug("build403Response: {}", conn);
473 | }
474 | // make up reply data...
475 | IoBuffer buf = IoBuffer.allocate(32);
476 | buf.setAutoExpand(true);
477 | buf.put("HTTP/1.1 403 Forbidden".getBytes());
478 | buf.put(Constants.CRLF);
479 | buf.put("Sec-WebSocket-Version-Server: 13".getBytes());
480 | buf.put(Constants.CRLF);
481 | buf.put(Constants.CRLF);
482 | if (log.isTraceEnabled()) {
483 | log.trace("Handshake error response size: {}", buf.limit());
484 | }
485 | return new HandshakeResponse(buf);
486 | }
487 |
488 | /**
489 | * Decode the in buffer according to the Section 5.2. RFC 6455. If there are multiple websocket dataframes in the buffer, this will parse all and return one complete decoded buffer.
490 | *
491 | *
492 | * 0 1 2 3
493 | * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
494 | * +-+-+-+-+-------+-+-------------+-------------------------------+
495 | * |F|R|R|R| opcode|M| Payload len | Extended payload length |
496 | * |I|S|S|S| (4) |A| (7) | (16/64) |
497 | * |N|V|V|V| |S| | (if payload len==126/127) |
498 | * | |1|2|3| |K| | |
499 | * +-+-+-+-+-------+-+-------------+ - - - - - - - - - - - - - - - +
500 | * | Extended payload length continued, if payload len == 127 |
501 | * + - - - - - - - - - - - - - - - +-------------------------------+
502 | * | |Masking-key, if MASK set to 1 |
503 | * +-------------------------------+-------------------------------+
504 | * | Masking-key (continued) | Payload Data |
505 | * +-------------------------------- - - - - - - - - - - - - - - - +
506 | * : Payload Data continued ... :
507 | * + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +
508 | * | Payload Data continued ... |
509 | * +---------------------------------------------------------------+
510 | *
511 | *
512 | * @param in
513 | * @param session
514 | */
515 | public static void decodeIncommingData(IoBuffer in, IoSession session) {
516 | log.trace("Decoding: {}", in);
517 | // get decoder state
518 | DecoderState decoderState = (DecoderState) session.getAttribute(DECODER_STATE_KEY);
519 | if (decoderState.fin == Byte.MIN_VALUE) {
520 | byte frameInfo = in.get();
521 | // get FIN (1 bit)
522 | //log.debug("frameInfo: {}", Integer.toBinaryString((frameInfo & 0xFF) + 256));
523 | decoderState.fin = (byte) ((frameInfo >>> 7) & 1);
524 | log.trace("FIN: {}", decoderState.fin);
525 | // the next 3 bits are for RSV1-3 (not used here at the moment)
526 | // get the opcode (4 bits)
527 | decoderState.opCode = (byte) (frameInfo & 0x0f);
528 | log.trace("Opcode: {}", decoderState.opCode);
529 | // opcodes 3-7 and b-f are reserved for non-control frames
530 | }
531 | if (decoderState.mask == Byte.MIN_VALUE) {
532 | byte frameInfo2 = in.get();
533 | // get mask bit (1 bit)
534 | decoderState.mask = (byte) ((frameInfo2 >>> 7) & 1);
535 | log.trace("Mask: {}", decoderState.mask);
536 | // get payload length (7, 7+16, 7+64 bits)
537 | decoderState.frameLen = (frameInfo2 & (byte) 0x7F);
538 | log.trace("Payload length: {}", decoderState.frameLen);
539 | if (decoderState.frameLen == 126) {
540 | decoderState.frameLen = in.getUnsignedShort();
541 | log.trace("Payload length updated: {}", decoderState.frameLen);
542 | } else if (decoderState.frameLen == 127) {
543 | long extendedLen = in.getLong();
544 | if (extendedLen >= Integer.MAX_VALUE) {
545 | log.error("Data frame is too large for this implementation. Length: {}", extendedLen);
546 | } else {
547 | decoderState.frameLen = (int) extendedLen;
548 | }
549 | log.trace("Payload length updated: {}", decoderState.frameLen);
550 | }
551 | }
552 | // ensure enough bytes left to fill payload, if masked add 4 additional bytes
553 | if (decoderState.frameLen + (decoderState.mask == 1 ? 4 : 0) > in.remaining()) {
554 | log.info("Not enough data available to decode, socket may be closed/closing");
555 | } else {
556 | // if the data is masked (xor'd)
557 | if (decoderState.mask == 1) {
558 | // get the mask key
559 | byte maskKey[] = new byte[4];
560 | for (int i = 0; i < 4; i++) {
561 | maskKey[i] = in.get();
562 | }
563 | /* now un-mask frameLen bytes as per Section 5.3 RFC 6455
564 | Octet i of the transformed data ("transformed-octet-i") is the XOR of
565 | octet i of the original data ("original-octet-i") with octet at index
566 | i modulo 4 of the masking key ("masking-key-octet-j"):
567 | j = i MOD 4
568 | transformed-octet-i = original-octet-i XOR masking-key-octet-j
569 | */
570 | decoderState.payload = new byte[decoderState.frameLen];
571 | for (int i = 0; i < decoderState.frameLen; i++) {
572 | byte maskedByte = in.get();
573 | decoderState.payload[i] = (byte) (maskedByte ^ maskKey[i % 4]);
574 | }
575 | } else {
576 | decoderState.payload = new byte[decoderState.frameLen];
577 | in.get(decoderState.payload);
578 | }
579 | // if FIN == 0 we have fragments
580 | if (decoderState.fin == 0) {
581 | // store the fragment and continue
582 | IoBuffer fragments = (IoBuffer) session.getAttribute(DECODED_MESSAGE_FRAGMENTS_KEY);
583 | if (fragments == null) {
584 | fragments = IoBuffer.allocate(decoderState.frameLen);
585 | fragments.setAutoExpand(true);
586 | session.setAttribute(DECODED_MESSAGE_FRAGMENTS_KEY, fragments);
587 | // store message type since following type may be a continuation
588 | MessageType messageType = MessageType.CLOSE;
589 | switch (decoderState.opCode) {
590 | case 0: // continuation
591 | messageType = MessageType.CONTINUATION;
592 | break;
593 | case 1: // text
594 | messageType = MessageType.TEXT;
595 | break;
596 | case 2: // binary
597 | messageType = MessageType.BINARY;
598 | break;
599 | case 9: // ping
600 | messageType = MessageType.PING;
601 | break;
602 | case 0xa: // pong
603 | messageType = MessageType.PONG;
604 | break;
605 | }
606 | session.setAttribute(DECODED_MESSAGE_TYPE_KEY, messageType);
607 | }
608 | fragments.put(decoderState.payload);
609 | // remove decoder state
610 | session.removeAttribute(DECODER_STATE_KEY);
611 | } else {
612 | // create a message
613 | WSMessage message = new WSMessage();
614 | // check for previously set type from the first fragment (if we have fragments)
615 | MessageType messageType = (MessageType) session.getAttribute(DECODED_MESSAGE_TYPE_KEY);
616 | if (messageType == null) {
617 | switch (decoderState.opCode) {
618 | case 0: // continuation
619 | messageType = MessageType.CONTINUATION;
620 | break;
621 | case 1: // text
622 | messageType = MessageType.TEXT;
623 | break;
624 | case 2: // binary
625 | messageType = MessageType.BINARY;
626 | break;
627 | case 9: // ping
628 | messageType = MessageType.PING;
629 | break;
630 | case 0xa: // pong
631 | messageType = MessageType.PONG;
632 | break;
633 | case 8: // close
634 | messageType = MessageType.CLOSE;
635 | // handler or listener should close upon receipt
636 | break;
637 | default:
638 | // TODO throw ex?
639 | log.info("Unhandled opcode: {}", decoderState.opCode);
640 | }
641 | }
642 | // set message type
643 | message.setMessageType(messageType);
644 | // check for fragments and piece them together, otherwise just send the single completed frame
645 | IoBuffer fragments = (IoBuffer) session.removeAttribute(DECODED_MESSAGE_FRAGMENTS_KEY);
646 | if (fragments != null) {
647 | fragments.put(decoderState.payload);
648 | fragments.flip();
649 | message.setPayload(fragments);
650 | } else {
651 | // add the payload
652 | message.addPayload(decoderState.payload);
653 | }
654 | // set the message on the session
655 | session.setAttribute(DECODED_MESSAGE_KEY, message);
656 | // remove decoder state
657 | session.removeAttribute(DECODER_STATE_KEY);
658 | // remove type
659 | session.removeAttribute(DECODED_MESSAGE_TYPE_KEY);
660 | }
661 | }
662 | }
663 |
664 | /**
665 | * Returns a map of key / value pairs from a given querystring.
666 | *
667 | * @param query
668 | * @return k/v map
669 | */
670 | public static Map parseQuerystring(String query) {
671 | String[] params = query.split("&");
672 | Map map = new HashMap();
673 | for (String param : params) {
674 | String[] nameValue = param.split("=");
675 | map.put(nameValue[0], nameValue[1]);
676 | }
677 | return map;
678 | }
679 |
680 | }
681 |
--------------------------------------------------------------------------------