All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.glassfish.tyrus.core.Handshake Maven / Gradle / Ivy

The newest version!
/*
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
 *
 * Copyright (c) 2012-2014 Oracle and/or its affiliates. All rights reserved.
 *
 * The contents of this file are subject to the terms of either the GNU
 * General Public License Version 2 only ("GPL") or the Common Development
 * and Distribution License("CDDL") (collectively, the "License").  You
 * may not use this file except in compliance with the License.  You can
 * obtain a copy of the License at
 * http://glassfish.java.net/public/CDDL+GPL_1_1.html
 * or packager/legal/LICENSE.txt.  See the License for the specific
 * language governing permissions and limitations under the License.
 *
 * When distributing the software, include this License Header Notice in each
 * file and include the License file at packager/legal/LICENSE.txt.
 *
 * GPL Classpath Exception:
 * Oracle designates this particular file as subject to the "Classpath"
 * exception as provided by Oracle in the GPL Version 2 section of the License
 * file that accompanied this code.
 *
 * Modifications:
 * If applicable, add the following below the License Header, with the fields
 * enclosed by brackets [] replaced by your own identifying information:
 * "Portions Copyright [year] [name of copyright owner]"
 *
 * Contributor(s):
 * If you wish your version of this file to be governed by only the CDDL or
 * only the GPL Version 2, indicate your decision by adding "[Contributor]
 * elects to include this software in this distribution under the [CDDL or GPL
 * Version 2] license."  If you don't indicate a single choice of license, a
 * recipient has the option to distribute your version of this file under
 * either the CDDL, the GPL Version 2 or to extend the choice of license to
 * its licensees as provided above.  However, if you add GPL Version 2 code
 * and therefore, elected the GPL Version 2 license, then the option applies
 * only if the new code is made subject to such option by the copyright
 * holder.
 */

package org.glassfish.tyrus.core;

import java.net.URI;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;

import javax.websocket.Extension;

import org.glassfish.tyrus.spi.UpgradeRequest;
import org.glassfish.tyrus.spi.UpgradeResponse;

/**
 * @author Justin Lee
 * @author Pavel Bucek (pavel.bucek at oracle.com)
 */
public final class Handshake {

    private static final int RESPONSE_CODE_VALUE = 101;
    private static final String VERSION = "13";

    private boolean secure;
    private String origin;
    private String serverHostName;
    private int port = 80;
    private String resourcePath;
    private List subProtocols = new ArrayList();
    private List extensions = new ArrayList(); // client extensions
    // client side request!
    private UpgradeRequest request;
    private UpgradeRequest incomingRequest;
    private ExtendedExtension.ExtensionContext extensionContext;
    private SecKey secKey;


    private Handshake() {
    }

    static Handshake createClientHandShake(UpgradeRequest webSocketRequest) {
        final Handshake handshake = new Handshake();
        handshake.request = webSocketRequest;

        final URI uri = webSocketRequest.getRequestURI();
        handshake.resourcePath = uri.getPath();
        if ("".equals(handshake.resourcePath)) {
            handshake.resourcePath = "/";
        }
        if (uri.getQuery() != null) {
            handshake.resourcePath += "?" + uri.getQuery();
        }
        handshake.serverHostName = uri.getHost();
        handshake.secure = webSocketRequest.isSecure();
        handshake.port = uri.getPort();
        handshake.origin = appendPort(new StringBuilder(uri.getHost()), handshake.port, handshake.secure).toString();
        handshake.secKey = new SecKey();

        return handshake;
    }

    static Handshake createServerHandShake(UpgradeRequest request, ExtendedExtension.ExtensionContext extensionContext) {
        final Handshake handshake = new Handshake();

        handshake.incomingRequest = request;
        handshake.extensionContext = extensionContext;
        checkForHeader(request.getHeader(UpgradeRequest.UPGRADE), UpgradeRequest.UPGRADE, "WebSocket");
        checkForHeader(request.getHeader(UpgradeRequest.CONNECTION), UpgradeRequest.CONNECTION, UpgradeRequest.UPGRADE);

        handshake.origin = request.getHeader(UpgradeRequest.SEC_WS_ORIGIN_HEADER);

        if (handshake.origin == null) {
            handshake.origin = request.getHeader(UpgradeRequest.ORIGIN_HEADER);
        }
        Handshake.determineHostAndPort(handshake, request);

        // TODO - trim?
        final String protocolHeader = request.getHeader(UpgradeRequest.SEC_WEBSOCKET_PROTOCOL);
        handshake.subProtocols = (protocolHeader == null ? Collections.emptyList() : Arrays.asList(protocolHeader.split(",")));

        if (handshake.serverHostName == null) {
            throw new HandshakeException("Missing required headers for WebSocket negotiation");
        }
        handshake.resourcePath = request.getRequestUri();
        final String queryString = request.getQueryString();
        if (queryString != null) {
            if (!queryString.isEmpty()) {
                handshake.resourcePath += "?" + queryString;
            }
//            Parameters queryParameters = new Parameters();
//            queryParameters.processParameters(queryString);
//            final Set names = queryParameters.getParameterNames();
//            for (String name : names) {
//                queryParams.put(name, queryParameters.getParameterValues(name));
//            }
        }

        List value = request.getHeaders().get(UpgradeRequest.SEC_WEBSOCKET_EXTENSIONS);
        if (value != null) {
            handshake.extensions = TyrusExtension.fromHeaders(value);
        }
        handshake.secKey = SecKey.generateServerKey(new SecKey(request.getHeader(UpgradeRequest.SEC_WEBSOCKET_KEY)));

        return handshake;
    }

    private static void checkForHeader(String currentValue, String header, String validValue) {
        validate(header, validValue, currentValue);
    }

    private static void validate(String header, String validValue, String value) {
        // http://java.net/jira/browse/TYRUS-55
        // Firefox workaround (it sends "Connections: keep-alive, upgrade").
        if (header.equalsIgnoreCase(UpgradeRequest.CONNECTION)) {
            if (!value.toLowerCase().contains(validValue.toLowerCase())) {
                throw new HandshakeException(String.format("Invalid %s header returned: '%s'", header, value));
            }
        } else {
            if (!value.equalsIgnoreCase(validValue)) {
                throw new HandshakeException(String.format("Invalid %s header returned: '%s'", header, value));
            }
        }
    }

    private static void determineHostAndPort(Handshake handshake, UpgradeRequest request) {
        String header = request.getHeader(UpgradeRequest.HOST);

        final int i = header == null ? -1 : header.indexOf(":");
        if (i == -1) {
            handshake.serverHostName = header;
            handshake.port = 80;
        } else {
            handshake.serverHostName = header.substring(0, i);
            handshake.port = Integer.valueOf(header.substring(i + 1));
        }
    }

    private static StringBuilder appendPort(StringBuilder builder, int port, boolean secure) {
        if (secure) {
            if (port != 443 && port != -1) {
                builder.append(':').append(port);
            }
        } else {
            if (port != 80 && port != -1) {
                builder.append(':').append(port);
            }
        }
        return builder;
    }

    String getOrigin() {
        return origin;
    }

    int getPort() {
        return port;
    }

    String getServerHostName() {
        return serverHostName;
    }

    List getSubProtocols() {
        return subProtocols;
    }

    public void setSubProtocols(List subProtocols) {
        this.subProtocols = subProtocols;
    }

    /**
     * Define to {@link String} conversion for various types.
     *
     * @param  type for which is conversion defined.
     */
    static abstract class Stringifier {

        /**
         * Convert object to {@link String}.
         *
         * @param t object to be converted.
         * @return {@link String} representation of given object.
         */
        abstract String toString(T t);
    }

     String getHeaderFromList(List list, Stringifier stringifier) {
        StringBuilder sb = new StringBuilder();
        Iterator it = list.iterator();
        while (it.hasNext()) {
            if (stringifier != null) {
                sb.append(stringifier.toString(it.next()));
            } else {
                sb.append(it.next());
            }
            if (it.hasNext()) {
                sb.append(", ");
            }
        }
        return sb.toString();
    }

     List getStringList(List list, Stringifier stringifier) {
        List result = new ArrayList();
        for (T item : list) {
            if (stringifier != null) {
                result.add(stringifier.toString(item));
            } else {
                result.add(item.toString());
            }
        }
        return result;
    }

    List getExtensions() {
        return extensions;
    }

    public void setExtensions(List extensions) {
        this.extensions = extensions;
    }

    /**
     * Gets the {@link UpgradeRequest}.
     *
     * @return {@link UpgradeRequest} created on this HandShake.
     */
    public UpgradeRequest getRequest() {
        return request;
    }

    /**
     * Compose the {@link UpgradeRequest} and store it for further use.
     *
     * @return composed {@link UpgradeRequest}.
     */
    public UpgradeRequest prepareRequest() {
        String host = getServerHostName();
        if (port != -1 && port != 80 && port != 443) {
            host += ":" + getPort();
        }

        putSingleHeader(request, UpgradeRequest.HOST, host);
        putSingleHeader(request, UpgradeRequest.CONNECTION, UpgradeRequest.UPGRADE);
        putSingleHeader(request, UpgradeRequest.UPGRADE, UpgradeRequest.WEBSOCKET);

        putSingleHeader(request, UpgradeRequest.SEC_WEBSOCKET_KEY, secKey.toString());
        putSingleHeader(request, UpgradeRequest.SEC_WS_ORIGIN_HEADER, getOrigin());
        putSingleHeader(request, UpgradeRequest.SEC_WEBSOCKET_VERSION, VERSION);

        if (!getSubProtocols().isEmpty()) {
            putSingleHeader(request, UpgradeRequest.SEC_WEBSOCKET_PROTOCOL, getHeaderFromList(subProtocols, null));
        }

        if (!getExtensions().isEmpty()) {
            putSingleHeader(request, UpgradeRequest.SEC_WEBSOCKET_EXTENSIONS, getHeaderFromList(getExtensions(), new Stringifier() {
                @Override
                String toString(Extension extension) {
                    return TyrusExtension.toString(extension);
                }
            }));
        }

        final String headerValue = request.getHeader(UpgradeRequest.SEC_WS_ORIGIN_HEADER);
        request.getHeaders().remove(UpgradeRequest.SEC_WS_ORIGIN_HEADER);
        putSingleHeader(request, UpgradeRequest.ORIGIN_HEADER, headerValue);
        return request;
    }

    private void putSingleHeader(UpgradeRequest request, String headerName, String headerValue) {
        request.getHeaders().put(headerName, Arrays.asList(headerValue));
    }

    public void validateServerResponse(UpgradeResponse response) {
        if (RESPONSE_CODE_VALUE != response.getStatus()) {
            throw new HandshakeException(String.format("Response code was not %s: %s",
                    RESPONSE_CODE_VALUE, response.getStatus()));
        }

        checkForHeader(response.getFirstHeaderValue(UpgradeRequest.UPGRADE), UpgradeRequest.UPGRADE, UpgradeRequest.WEBSOCKET);
        checkForHeader(response.getFirstHeaderValue(UpgradeRequest.CONNECTION), UpgradeRequest.CONNECTION, UpgradeRequest.UPGRADE);

//        if (!getSubProtocols().isEmpty()) {
//            checkForHeader(response.getHeaders(), WebSocketEngine.SEC_WS_PROTOCOL_HEADER, WebSocketEngine.SEC_WS_PROTOCOL_HEADER);
//        }

        secKey.validateServerKey(response.getFirstHeaderValue(UpgradeResponse.SEC_WEBSOCKET_ACCEPT));
    }

    void respond(UpgradeResponse response, TyrusEndpoint tyrusEndpoint/*, TyrusUpgradeResponse response*/) {
        response.setStatus(101);

        response.getHeaders().put(UpgradeRequest.UPGRADE, Arrays.asList(UpgradeRequest.WEBSOCKET));
        response.getHeaders().put(UpgradeRequest.CONNECTION, Arrays.asList(UpgradeRequest.UPGRADE));
        response.setReasonPhrase(UpgradeRequest.RESPONSE_CODE_MESSAGE);
        response.getHeaders().put(UpgradeResponse.SEC_WEBSOCKET_ACCEPT, Arrays.asList(secKey.getSecKey()));

        if (subProtocols != null && !subProtocols.isEmpty()) {
            List appProtocols = tyrusEndpoint.getSupportedProtocols(subProtocols);
            if (!appProtocols.isEmpty()) {
                response.getHeaders().put(UpgradeRequest.SEC_WEBSOCKET_PROTOCOL, getStringList(appProtocols, null));
            }
        }

        if (!tyrusEndpoint.getSupportedExtensions().isEmpty()) {
            response.getHeaders().put(UpgradeRequest.SEC_WEBSOCKET_EXTENSIONS, getStringList(tyrusEndpoint.getSupportedExtensions(), new Stringifier() {
                @Override
                String toString(final Extension extension) {
                    if (extension instanceof ExtendedExtension) {
                        return TyrusExtension.toString(new Extension() {
                            @Override
                            public String getName() {
                                return extension.getName();
                            }

                            @Override
                            public List getParameters() {
                                // TODO! XXX FIXME
                                // null is there because extension is wrapped and the original parameters are stored
                                // in the wrapped instance.
                                return ((ExtendedExtension) extension).onExtensionNegotiation(extensionContext, null);
                            }
                        });
                    } else {
                        return TyrusExtension.toString(extension);
                    }
                }
            }));
        }
        tyrusEndpoint.onHandShakeResponse(incomingRequest, response);
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy