├── .gitignore
├── src
├── main
│ └── java
│ │ └── com
│ │ └── pmeade
│ │ └── websocket
│ │ ├── http
│ │ ├── package-info.java
│ │ └── HttpRequest.java
│ │ ├── io
│ │ ├── package-info.java
│ │ ├── LineInputStream.java
│ │ ├── WebSocketServerOutputStream.java
│ │ └── WebSocketServerInputStream.java
│ │ ├── net
│ │ ├── package-info.java
│ │ ├── WebSocketServerSocket.java
│ │ └── WebSocket.java
│ │ └── example
│ │ └── EchoServer.java
└── test
│ └── java
│ └── com
│ └── pmeade
│ └── websocket
│ ├── net
│ ├── WebSocketServerSocketTest.java
│ └── WebSocketTest.java
│ ├── io
│ ├── LineInputStreamTest.java
│ └── WebSocketServerOutputStreamTest.java
│ └── http
│ └── HttpRequestTest.java
├── start-echo-server
├── pom.xml
├── README.md
├── LICENSE
└── doc
└── rfc7235.txt
/.gitignore:
--------------------------------------------------------------------------------
1 | *.class
2 |
3 | # Mobile Tools for Java (J2ME)
4 | .mtj.tmp/
5 |
6 | # Package Files #
7 | *.jar
8 | *.war
9 | *.ear
10 |
11 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
12 | hs_err_pid*
13 | /target/
--------------------------------------------------------------------------------
/src/main/java/com/pmeade/websocket/http/package-info.java:
--------------------------------------------------------------------------------
1 | /*
2 | * package-info.java
3 | * Copyright 2014 Patrick Meade.
4 | *
5 | * This program is free software: you can redistribute it and/or modify
6 | * it under the terms of the GNU Affero General Public License as published by
7 | * the Free Software Foundation, either version 3 of the License, or
8 | * (at your option) any later version.
9 | *
10 | * This program is distributed in the hope that it will be useful,
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | * GNU Affero General Public License for more details.
14 | *
15 | * You should have received a copy of the GNU Affero General Public License
16 | * along with this program. If not, see .
17 | */
18 |
19 | /**
20 | * Provides parsing of headers in HTTP protocol requests.
21 | */
22 | package com.pmeade.websocket.http;
23 |
--------------------------------------------------------------------------------
/src/main/java/com/pmeade/websocket/io/package-info.java:
--------------------------------------------------------------------------------
1 | /*
2 | * package-info.java
3 | * Copyright 2014 Patrick Meade.
4 | *
5 | * This program is free software: you can redistribute it and/or modify
6 | * it under the terms of the GNU Affero General Public License as published by
7 | * the Free Software Foundation, either version 3 of the License, or
8 | * (at your option) any later version.
9 | *
10 | * This program is distributed in the hope that it will be useful,
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | * GNU Affero General Public License for more details.
14 | *
15 | * You should have received a copy of the GNU Affero General Public License
16 | * along with this program. If not, see .
17 | */
18 |
19 | /**
20 | * Provides for input and output through WebSocket protocol streams.
21 | */
22 | package com.pmeade.websocket.io;
23 |
--------------------------------------------------------------------------------
/src/main/java/com/pmeade/websocket/net/package-info.java:
--------------------------------------------------------------------------------
1 | /*
2 | * package-info.java
3 | * Copyright 2014 Patrick Meade.
4 | *
5 | * This program is free software: you can redistribute it and/or modify
6 | * it under the terms of the GNU Affero General Public License as published by
7 | * the Free Software Foundation, either version 3 of the License, or
8 | * (at your option) any later version.
9 | *
10 | * This program is distributed in the hope that it will be useful,
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | * GNU Affero General Public License for more details.
14 | *
15 | * You should have received a copy of the GNU Affero General Public License
16 | * along with this program. If not, see .
17 | */
18 |
19 | /**
20 | * Provides a ServerSocket that understands the WebSocket protocol.
21 | */
22 | package com.pmeade.websocket.net;
23 |
--------------------------------------------------------------------------------
/start-echo-server:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | # start-echo-server
3 | #
4 | # This program is free software: you can redistribute it and/or modify
5 | # it under the terms of the GNU Affero General Public License as published by
6 | # the Free Software Foundation, either version 3 of the License, or
7 | # (at your option) any later version.
8 | #
9 | # This program is distributed in the hope that it will be useful,
10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 | # GNU Affero General Public License for more details.
13 | #
14 | # You should have received a copy of the GNU Affero General Public License
15 | # along with this program. If not, see .
16 | #----------------------------------------------------------------------------
17 |
18 | java -cp "target/classes:target/dependency/*" com.pmeade.websocket.example.EchoServer
19 |
20 | #----------------------------------------------------------------------------
21 | # end of start-echo-server
22 |
--------------------------------------------------------------------------------
/src/main/java/com/pmeade/websocket/example/EchoServer.java:
--------------------------------------------------------------------------------
1 | /*
2 | * EchoServer.java
3 | * Copyright 2014 Patrick Meade.
4 | *
5 | * This program is free software: you can redistribute it and/or modify
6 | * it under the terms of the GNU Affero General Public License as published by
7 | * the Free Software Foundation, either version 3 of the License, or
8 | * (at your option) any later version.
9 | *
10 | * This program is distributed in the hope that it will be useful,
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | * GNU Affero General Public License for more details.
14 | *
15 | * You should have received a copy of the GNU Affero General Public License
16 | * along with this program. If not, see .
17 | */
18 |
19 | package com.pmeade.websocket.example;
20 |
21 | import com.pmeade.websocket.io.WebSocketServerOutputStream;
22 | import com.pmeade.websocket.net.WebSocket;
23 | import com.pmeade.websocket.net.WebSocketServerSocket;
24 | import java.io.IOException;
25 | import java.io.InputStream;
26 | import java.net.ServerSocket;
27 |
28 | /**
29 | * @author pmeade
30 | */
31 | public class EchoServer {
32 | public static final int PORT = 8080;
33 |
34 | public static void main(String[] args) {
35 | EchoServer echoServer = new EchoServer();
36 | try {
37 | echoServer.doIt();
38 | } catch(Exception e) {
39 | System.err.println(e.getLocalizedMessage());
40 | e.printStackTrace(System.err);
41 | }
42 | }
43 |
44 | public void doIt() throws Exception
45 | {
46 | ServerSocket serverSocket = new ServerSocket(PORT);
47 | WebSocketServerSocket webSocketServerSocket
48 | = new WebSocketServerSocket(serverSocket);
49 | while(finished == false) {
50 | WebSocket socket = webSocketServerSocket.accept();
51 | new WebSocketThread(socket).start();
52 | }
53 | }
54 |
55 | public void finish() {
56 | finished = true;
57 | }
58 |
59 | private boolean finished = false;
60 | }
61 |
62 | class WebSocketThread extends Thread {
63 | public WebSocketThread(WebSocket socket) {
64 | this.webSocket = socket;
65 | }
66 |
67 | @Override
68 | public void run() {
69 | try {
70 | WebSocketServerOutputStream wsos = webSocket.getOutputStream();
71 | InputStream wsis = webSocket.getInputStream();
72 | int data = wsis.read();
73 | while (finished == false && data != -1) {
74 | wsos.writeString("Data received: " + (char)data);
75 | data = wsis.read();
76 | }
77 | } catch (IOException e) {
78 | finished = true;
79 | System.err.println(e.getLocalizedMessage());
80 | e.printStackTrace(System.err);
81 | }
82 | try {
83 | webSocket.close();
84 | } catch (IOException e) {
85 | finished = true;
86 | System.err.println(e.getLocalizedMessage());
87 | e.printStackTrace(System.err);
88 | }
89 | }
90 |
91 | public void finish() {
92 | finished = true;
93 | }
94 |
95 | private boolean finished = false;
96 |
97 | private final WebSocket webSocket;
98 | }
99 |
--------------------------------------------------------------------------------
/src/main/java/com/pmeade/websocket/net/WebSocketServerSocket.java:
--------------------------------------------------------------------------------
1 | /*
2 | * WebSocketServerSocket.java
3 | * Copyright 2014 Patrick Meade.
4 | *
5 | * This program is free software: you can redistribute it and/or modify
6 | * it under the terms of the GNU Affero General Public License as published by
7 | * the Free Software Foundation, either version 3 of the License, or
8 | * (at your option) any later version.
9 | *
10 | * This program is distributed in the hope that it will be useful,
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | * GNU Affero General Public License for more details.
14 | *
15 | * You should have received a copy of the GNU Affero General Public License
16 | * along with this program. If not, see .
17 | */
18 |
19 | package com.pmeade.websocket.net;
20 |
21 | import java.io.IOException;
22 | import java.net.InetAddress;
23 | import java.net.ServerSocket;
24 | import java.net.SocketAddress;
25 | import java.net.SocketException;
26 | import java.nio.channels.ServerSocketChannel;
27 |
28 | /**
29 | * WebSocketServerSocket decorates a ServerSocket to accept connections
30 | * that use the WebSocket protocol as specified in RFC 6455.
31 | * @author veloxi
32 | */
33 | public class WebSocketServerSocket extends ServerSocket {
34 | /**
35 | * Construct a WebSocketServerSocket. WebSocketServerSocket provides a
36 | * ServerSocket with WebSocket (RFC 6455) behavior.
37 | * @param ss ServerSocket to be decorated with WebSocket behavior
38 | * @throws IOException if an I/O error occurs
39 | */
40 | public WebSocketServerSocket(final ServerSocket ss) throws IOException {
41 | this.serverSocket = ss;
42 | }
43 |
44 | @Override
45 | public final void bind(final SocketAddress endpoint) throws IOException {
46 | serverSocket.bind(endpoint);
47 | }
48 |
49 | @Override
50 | public final void bind(final SocketAddress endpoint, final int backlog)
51 | throws IOException {
52 | serverSocket.bind(endpoint, backlog);
53 | }
54 |
55 | @Override
56 | public final InetAddress getInetAddress() {
57 | return serverSocket.getInetAddress();
58 | }
59 |
60 | @Override
61 | public final int getLocalPort() {
62 | return serverSocket.getLocalPort();
63 | }
64 |
65 | @Override
66 | public final SocketAddress getLocalSocketAddress() {
67 | return serverSocket.getLocalSocketAddress();
68 | }
69 |
70 | @Override
71 | public final WebSocket accept() throws IOException {
72 | return new WebSocket(serverSocket.accept());
73 | }
74 |
75 | @Override
76 | public final void close() throws IOException {
77 | serverSocket.close();
78 | }
79 |
80 | @Override
81 | public final ServerSocketChannel getChannel() {
82 | throw new UnsupportedOperationException();
83 | }
84 |
85 | @Override
86 | public final boolean isBound() {
87 | return serverSocket.isBound();
88 | }
89 |
90 | @Override
91 | public final boolean isClosed() {
92 | return serverSocket.isClosed();
93 | }
94 |
95 | @Override
96 | public final synchronized void setSoTimeout(final int timeout)
97 | throws SocketException {
98 | serverSocket.setSoTimeout(timeout);
99 | }
100 |
101 | @Override
102 | public final synchronized int getSoTimeout() throws IOException {
103 | return serverSocket.getSoTimeout();
104 | }
105 |
106 | @Override
107 | public final void setReuseAddress(final boolean on)
108 | throws SocketException {
109 | serverSocket.setReuseAddress(on);
110 | }
111 |
112 | @Override
113 | public final boolean getReuseAddress() throws SocketException {
114 | return serverSocket.getReuseAddress();
115 | }
116 |
117 | @Override
118 | public final String toString() {
119 | return serverSocket.toString();
120 | }
121 |
122 | @Override
123 | public final synchronized void setReceiveBufferSize(final int size)
124 | throws SocketException {
125 | serverSocket.setReceiveBufferSize(size);
126 | }
127 |
128 | @Override
129 | public final synchronized int getReceiveBufferSize()
130 | throws SocketException {
131 | return serverSocket.getReceiveBufferSize();
132 | }
133 |
134 | @Override
135 | public final void setPerformancePreferences(
136 | final int connectionTime,
137 | final int latency,
138 | final int bandwidth) {
139 | serverSocket.setPerformancePreferences(
140 | connectionTime, latency, bandwidth);
141 | }
142 |
143 | /**
144 | * ServerSocket to be decorated with WebSocket behavior.
145 | */
146 | private final ServerSocket serverSocket;
147 | }
148 |
--------------------------------------------------------------------------------
/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
19 |
25 |
26 | 4.0.0
27 | com.pmeade
28 | websocket
29 | 1.0-SNAPSHOT
30 | jar
31 |
32 |
33 | 1.5
34 | 1.5
35 | UTF-8
36 |
37 |
38 |
39 |
40 |
41 | org.apache.maven.plugins
42 | maven-dependency-plugin
43 | 2.8
44 |
45 |
46 | copy-dependencies
47 | package
48 |
49 | copy-dependencies
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 | org.apache.maven.plugins
61 | maven-checkstyle-plugin
62 | 2.12.1
63 |
64 | true
65 |
66 |
67 |
68 | org.apache.maven.plugins
69 | maven-javadoc-plugin
70 | 2.9.1
71 |
72 |
73 | org.apache.maven.plugins
74 | maven-jxr-plugin
75 | 2.4
76 |
77 |
78 | org.apache.maven.plugins
79 | maven-pmd-plugin
80 | 3.0.1
81 |
82 | true
83 | ${project.build.sourceEncoding}
84 | ${maven.compiler.target}
85 |
86 |
87 |
88 | org.codehaus.mojo
89 | cobertura-maven-plugin
90 | 2.6
91 |
92 |
93 | org.codehaus.mojo
94 | findbugs-maven-plugin
95 | 3.0.0
96 |
97 |
98 |
99 |
100 |
101 |
102 | com.google.guava
103 | guava
104 | 18.0
105 |
106 |
107 | org.slf4j
108 | slf4j-api
109 | 1.7.7
110 |
111 |
112 | org.slf4j
113 | slf4j-simple
114 | 1.7.7
115 |
116 |
117 | junit
118 | junit
119 | 4.13.1
120 | test
121 |
122 |
123 | org.mockito
124 | mockito-all
125 | 1.9.5
126 | test
127 |
128 |
129 |
130 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # websocket
2 | High quality low grade server side WebSocket (RFC 6455) implementation.
3 |
4 | ## Motivation
5 | Web requests are typically follow a request-response pattern. The client
6 | communicates the resource that it wants, and the server responds with the
7 | content that resource (or an error status code). After this the connection
8 | between client and server is closed and communication ends.
9 |
10 | WebSocket (RFC 6455) allows an HTTP client, such as a modern web browser, to
11 | request that the HTTP connection not close, but rather be upgraded to a
12 | communication channel for an on-going two-way exchange of binary and text data.
13 | Basically, the client says: Don't hang up yet, let's chat awhile.
14 |
15 | This fills an interesting niche in client-server communications. At first,
16 | it looks and acts (and actually, is) a web (HTTP) request. This allows the
17 | communication to get around most networking/firewall problems involved with
18 | opening a direct communication channel. Yet, after the WebSocket handshake,
19 | the channel acts very much like a traditional socket. The connection stays
20 | open and any data may be put on the wire.
21 |
22 | ## websocket
23 | This component is a server-side WebSocket implementation. By using the
24 | provided WebSocketServerSocket to decorate a ServerSocket, the server can
25 | listen for WebSocket connections and treat them like a traditional socket.
26 |
27 | WebSocketServerSocket itself is a ServerSocket. So, it can be used anywhere
28 | in your application that a ServerSocket might be used. WebSocketServerSocket
29 | can decorate any ServerSocket, including SSLServerSocket, if you want to
30 | support secure WebSocket connections.
31 |
32 | ## Usage
33 | This is how you start listening for WebSocket connections:
34 |
35 | ServerSocket serverSocket = new ServerSocket(PORT);
36 | WebSocketServerSocket webSocketServerSocket = new WebSocketServerSocket(serverSocket);
37 | WebSocket webSocket = webSocketServerSocket.accept();
38 |
39 | And this is how you read data from the connecting client:
40 |
41 | InputStream is = webSocket.getInputStream();
42 | int data = is.read();
43 |
44 | And this is how you communicate back to the connecting client:
45 |
46 | WebSocketServerOutputStream os = webSocket.getOutputStream();
47 | os.writeString("This is a UTF-8 string.");
48 | os.writeBinary("Some binary data.".getBytes());
49 |
50 | Note that this usage is as raw as it gets, giving you the bytes sent over
51 | the WebSocket.
52 |
53 | ### Echo server example
54 | Included in this component is the example EchoServer. By running this, you
55 | can test the WebSocket implementation.
56 |
57 | mvn install
58 | ./start-echo-server
59 |
60 | Then point your browser to the following URL:
61 |
62 | * [http://www.websocket.org/echo.html](http://www.websocket.org/echo.html)
63 |
64 | Change the server to:
65 |
66 | localhost:8080
67 |
68 | You can then test that the server is echoing back the bytes that it has
69 | received.
70 |
71 | ## Features
72 | This WebSocket implementation is safe for very large frames. It does NOT
73 | allocate large buffers in anticipation of large frames.
74 |
75 | The code is:
76 | * Checkstyle clean
77 | * FindBugs clean
78 | * PMD/CPD clean
79 |
80 | The code has virtually 100% test coverage in Cobertura.
81 |
82 | ## Shortcomings
83 | This component is fairly specialized, and there are not a whole lot of
84 | options.
85 |
86 | ### Protocol opacity
87 | This component does its best to hide the details of the WebSocket protocol
88 | from the application.
89 |
90 | * No distinction is made between binary frames and text frames.
91 | * No markers indicate the start or end of frames in the stream.
92 | * Control frames (Ping, Pong, Close) are handled internally, and never
93 | visible to the application.
94 |
95 | If you need a nice clear channel to communicate strings or binary blobs
96 | between server and client, this component will meet your requirements.
97 | If you need to get into the guts of the WebSocket protocol, you'll
98 | need to modify the component.
99 |
100 | ### No channels
101 | Unlike a regular Socket or ServerSocket, you cannot call getChannel().
102 | Attempting to do so will throw a UnsupportedOperationException.
103 |
104 | ## Development ideas
105 | These are some ideas that I might work on in the future.
106 |
107 | * Add a client side socket, to allow a Java process to connect to a
108 | server that speaks WebSocket.
109 |
110 | * Use Guava's EventBus to create an evented I/O object that speaks WebSocket.
111 |
112 | * Create a WebSocketChannel to provide WebSocket protocol support for NIO
113 | channels.
114 |
115 | ## License
116 | This program is free software: you can redistribute it and/or modify
117 | it under the terms of the [GNU Affero General Public License]
118 | (https://www.gnu.org/licenses/agpl-3.0.html) as published by the
119 | [Free Software Foundation](http://www.fsf.org/), either version 3
120 | of the License, or (at your option) any later version.
121 |
122 | This program is distributed in the hope that it will be useful,
123 | but WITHOUT ANY WARRANTY; without even the implied warranty of
124 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
125 | [GNU Affero General Public License]
126 | (https://www.gnu.org/licenses/agpl-3.0.html) for more details.
127 |
128 | You should have received a copy of the [GNU Affero General Public License]
129 | (https://www.gnu.org/licenses/agpl-3.0.html) along with this program. If
130 | not, see [http://www.gnu.org/licenses/](http://www.gnu.org/licenses/).
131 |
--------------------------------------------------------------------------------
/src/main/java/com/pmeade/websocket/io/LineInputStream.java:
--------------------------------------------------------------------------------
1 | /*
2 | * LineInputStream.java
3 | * Copyright 2014 Patrick Meade.
4 | *
5 | * This program is free software: you can redistribute it and/or modify
6 | * it under the terms of the GNU Affero General Public License as published by
7 | * the Free Software Foundation, either version 3 of the License, or
8 | * (at your option) any later version.
9 | *
10 | * This program is distributed in the hope that it will be useful,
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | * GNU Affero General Public License for more details.
14 | *
15 | * You should have received a copy of the GNU Affero General Public License
16 | * along with this program. If not, see .
17 | */
18 |
19 | package com.pmeade.websocket.io;
20 |
21 | import java.io.ByteArrayOutputStream;
22 | import java.io.IOException;
23 | import java.io.InputStream;
24 | import java.nio.charset.StandardCharsets;
25 |
26 | import static com.google.common.base.Preconditions.checkNotNull;
27 |
28 | /**
29 | * LineInputStream decorates an InputStream for line-by-line reading.
30 | * It implements a readLine() method, similar to
31 | * BufferedReader, but does not need an intermediate
32 | * InputStreamReader to translate.
33 | *
34 | *
The reason we avoid an InputStreamReader is that it
35 | * will consume as much of the input as it possibly can in order
36 | * to optimize character encoding from the input bytes.
37 | *
38 | *
In our case, the input will be switching protocols from an HTTP
39 | * Request to WebSocket frames. We want to consume the HTTP Request
40 | * line by line and process it for a WebSocket handshake, but the
41 | * data following that must be handled in a completely different way.
42 | *
43 | *
The ability to handle the data line-by-line like
44 | * BufferedReader is able to do, is very useful for
45 | * parsing the HTTP Reqeust. Therefore we have ListInputStream to
46 | * provide this functionality as an InputStream decorator.
47 | *
48 | *
Unlike BufferedReader, which handles three kinds of
49 | * line endings (CR, LF, CRLF), the HTTP protocol has defined the line
50 | * ending to be CRLF. Therefore, the readLine() method of
51 | * this decorator requires a CRLF (or EOF) sequence to terminate a line.
52 | *
53 | * @author blinkdog
54 | */
55 | public class LineInputStream extends InputStream {
56 | /**
57 | * Constant defining Carriage Return (CR). Octet 13, Hex 0x0c.
58 | */
59 | public static final int CR = 13;
60 |
61 | /**
62 | * Constant defining the end of the stream (EOF). This is derived
63 | * from the InputStream API. Calls to read() return -1
64 | * when the end of the stream is reached.
65 | */
66 | public static final int EOF = -1;
67 |
68 | /**
69 | * Constant defining Line Feed (LF). Octet 10, Hex 0x0a
70 | */
71 | public static final int LF = 10;
72 |
73 | /**
74 | * Constant defining the canonical name of the UTF-8 character encoding.
75 | */
76 | public static final String UTF_8 = StandardCharsets.UTF_8.name();
77 |
78 | /**
79 | * Construct a LineInputStream to decorate the provided InputStream.
80 | * A NullPointerException will be thrown if the provided
81 | * InputStream is null.
82 | * @param in InputStream to be decorated by this LineInputStream
83 | */
84 | public LineInputStream(final InputStream in) {
85 | checkNotNull(in);
86 | this.inputStream = in;
87 | }
88 |
89 | /**
90 | * {@inheritDoc}
91 | * @return the next byte of data, or -1 if the end of the
92 | * stream is reached.
93 | * @throws IOException if an I/O error occurs
94 | */
95 | @Override
96 | public final int read() throws IOException {
97 | return inputStream.read();
98 | }
99 |
100 | /**
101 | * Reads a line of text. A line is considered to be terminated by a
102 | * carriage return ('\r') followed immediately by a linefeed ('\n').
103 | * This is per the HTTP specification.
104 | * @return String containing the contents of the line, not including
105 | * any line-termination characters, or null if the end of the
106 | * stream has been reached
107 | * @throws IOException if an I/O error occurs
108 | */
109 | public final String readLine() throws IOException {
110 | ByteArrayOutputStream baos = new ByteArrayOutputStream();
111 | boolean inputTaken = false;
112 | while (true) {
113 | int data = inputStream.read();
114 | // if this is the end of the stream
115 | if (data == EOF) {
116 | // if we've taken some input already
117 | if (inputTaken) {
118 | // return that input
119 | return baos.toString(UTF_8);
120 | } else {
121 | // otherwise return null
122 | return null;
123 | }
124 | // otherwise, if this is a CR
125 | } else if (data == CR) {
126 | // it may be the end of a line
127 | lastWasCarriageReturn = true;
128 | // otherwise, if this is a LF
129 | } else if (data == LF) {
130 | // if we did follow a CR
131 | if (lastWasCarriageReturn) {
132 | // then this is the end of a line
133 | lastWasCarriageReturn = false;
134 | return baos.toString(UTF_8);
135 | } else {
136 | inputTaken = true;
137 | lastWasCarriageReturn = false;
138 | baos.write(LF);
139 | }
140 | // otherwise...
141 | } else {
142 | // if the last thing was a carriage return
143 | if (lastWasCarriageReturn) {
144 | // write that CR to our line
145 | baos.write(CR);
146 | }
147 | // add the data we just read to the line
148 | inputTaken = true;
149 | lastWasCarriageReturn = false;
150 | baos.write(data);
151 | }
152 | }
153 | }
154 |
155 | /**
156 | * InputStream to be decorated by this LineInputStream. This reference
157 | * is provided at construction time.
158 | */
159 | private final InputStream inputStream;
160 |
161 | /**
162 | * Flag: Is the last character we processed a Carriage Return (Octet 13)?
163 | * true: Yes, the last character was a CR
164 | * false: No, the last character was not a CR
165 | */
166 | private boolean lastWasCarriageReturn = false;
167 | }
168 |
--------------------------------------------------------------------------------
/src/test/java/com/pmeade/websocket/net/WebSocketServerSocketTest.java:
--------------------------------------------------------------------------------
1 | /*
2 | * WebSocketServerSocketTest.java
3 | * Copyright 2014 Patrick Meade.
4 | *
5 | * This program is free software: you can redistribute it and/or modify
6 | * it under the terms of the GNU Affero General Public License as published by
7 | * the Free Software Foundation, either version 3 of the License, or
8 | * (at your option) any later version.
9 | *
10 | * This program is distributed in the hope that it will be useful,
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | * GNU Affero General Public License for more details.
14 | *
15 | * You should have received a copy of the GNU Affero General Public License
16 | * along with this program. If not, see .
17 | */
18 |
19 | package com.pmeade.websocket.net;
20 |
21 | import java.net.InetAddress;
22 | import java.net.ServerSocket;
23 | import java.net.SocketAddress;
24 | import java.nio.channels.ServerSocketChannel;
25 | import org.junit.After;
26 | import org.junit.AfterClass;
27 | import org.junit.Before;
28 | import org.junit.BeforeClass;
29 | import org.junit.Test;
30 | import static org.junit.Assert.*;
31 | import static org.mockito.Mockito.*;
32 |
33 | /**
34 | * @author veloxi
35 | */
36 | public class WebSocketServerSocketTest
37 | {
38 | private ServerSocket mockSS;
39 | private WebSocketServerSocket wsss;
40 |
41 | public WebSocketServerSocketTest() {
42 | }
43 |
44 | @BeforeClass
45 | public static void setUpClass() {
46 | }
47 |
48 | @AfterClass
49 | public static void tearDownClass() {
50 | }
51 |
52 | @Before
53 | public void setUp() throws Exception {
54 | mockSS = mock(ServerSocket.class);
55 | wsss = new WebSocketServerSocket(mockSS);
56 | }
57 |
58 | @After
59 | public void tearDown() throws Exception {
60 | }
61 |
62 | @Test
63 | public void testAlwaysSucceed() {
64 | assertTrue(true);
65 | verifyZeroInteractions(mockSS);
66 | }
67 |
68 | @Test
69 | public void testExtendsServerSocket() throws Exception {
70 | assertTrue(wsss instanceof ServerSocket);
71 | verifyZeroInteractions(mockSS);
72 | }
73 |
74 | @Test
75 | public void testBindDelegatesToServerSocket() throws Exception {
76 | SocketAddress sa = mock(SocketAddress.class);
77 | wsss.bind(sa);
78 | verify(mockSS).bind(sa);
79 | verifyZeroInteractions(sa);
80 | }
81 |
82 | @Test
83 | public void testBindWithBacklogDelegatesToServerSocket() throws Exception {
84 | SocketAddress sa = mock(SocketAddress.class);
85 | wsss.bind(sa, 12345);
86 | verify(mockSS).bind(sa, 12345);
87 | verifyZeroInteractions(sa);
88 | }
89 |
90 | @Test
91 | public void testGetInetAddressDelegatesToServerSocket() throws Exception {
92 | InetAddress ia = mock(InetAddress.class);
93 | when(mockSS.getInetAddress()).thenReturn(ia);
94 | InetAddress result = wsss.getInetAddress();
95 | assertEquals(ia, result);
96 | verify(mockSS).getInetAddress();
97 | verifyZeroInteractions(ia);
98 | }
99 |
100 | @Test
101 | public void testGetLocalPortDelegatesToServerSocket() throws Exception {
102 | when(mockSS.getLocalPort()).thenReturn(42);
103 | int result = wsss.getLocalPort();
104 | assertEquals(42, result);
105 | verify(mockSS).getLocalPort();
106 | }
107 |
108 | @Test
109 | public void testGetLocalAddressDelegatesToServerSocket() throws Exception {
110 | SocketAddress sa = mock(SocketAddress.class);
111 | when(mockSS.getLocalSocketAddress()).thenReturn(sa);
112 | SocketAddress result = wsss.getLocalSocketAddress();
113 | assertEquals(sa, result);
114 | verify(mockSS).getLocalSocketAddress();
115 | verifyZeroInteractions(sa);
116 | }
117 |
118 | @Test
119 | public void testGetChannelThrowsUnsupportedOperationException() throws Exception {
120 | try {
121 | ServerSocketChannel ssc = wsss.getChannel();
122 | fail();
123 | } catch (UnsupportedOperationException e) {
124 | // expected
125 | }
126 | verifyZeroInteractions(mockSS);
127 | }
128 |
129 | @Test
130 | public void testSetSoTimeoutDelegatesToSocket() throws Exception {
131 | wsss.setSoTimeout(123456);
132 | verify(mockSS).setSoTimeout(123456);
133 | }
134 |
135 | @Test
136 | public void testGetSoTimeoutDelegatesToSocket() throws Exception {
137 | when(mockSS.getSoTimeout()).thenReturn(123456);
138 | assertEquals(123456, wsss.getSoTimeout());
139 | verify(mockSS).getSoTimeout();
140 | }
141 |
142 | @Test
143 | public void testSetReceiveBufferSizeDelegatesToSocket() throws Exception {
144 | wsss.setReceiveBufferSize(654321);
145 | verify(mockSS).setReceiveBufferSize(654321);
146 | }
147 |
148 | @Test
149 | public void testGetReceiveBufferSizeDelegatesToSocket() throws Exception {
150 | when(mockSS.getReceiveBufferSize()).thenReturn(654321);
151 | assertEquals(654321, wsss.getReceiveBufferSize());
152 | verify(mockSS).getReceiveBufferSize();
153 | }
154 |
155 | @Test
156 | public void testSetReuseAddressDelegatesToSocket() throws Exception {
157 | wsss.setReuseAddress(true);
158 | wsss.setReuseAddress(false);
159 | verify(mockSS).setReuseAddress(true);
160 | verify(mockSS).setReuseAddress(false);
161 | }
162 |
163 | @Test
164 | public void testGetReuseAddressDelegatesToSocket() throws Exception {
165 | when(mockSS.getReuseAddress()).thenReturn(false);
166 | assertFalse(wsss.getReuseAddress());
167 | verify(mockSS).getReuseAddress();
168 | }
169 |
170 | @Test
171 | public void testCloseDelegatesToSocket() throws Exception {
172 | wsss.close();
173 | verify(mockSS).close();
174 | }
175 |
176 | @Test
177 | public void testToStringDelegatesToSocket() throws Exception {
178 | when(mockSS.toString()).thenReturn("Exterminate!");
179 | assertEquals("Exterminate!", wsss.toString());
180 | // toString() ... Mockito's Kryptonite
181 | // verify(mockSS).toString();
182 | }
183 |
184 | @Test
185 | public void testIsBoundDelegatesToSocket() throws Exception {
186 | when(mockSS.isBound()).thenReturn(true);
187 | assertTrue(wsss.isBound());
188 | verify(mockSS).isBound();
189 | }
190 |
191 | @Test
192 | public void testIsClosedDelegatesToSocket() throws Exception {
193 | when(mockSS.isClosed()).thenReturn(true);
194 | assertTrue(wsss.isClosed());
195 | verify(mockSS).isClosed();
196 | }
197 |
198 | @Test
199 | public void testSetPerformancePreferencesDelegatesToSocket() throws Exception {
200 | wsss.setPerformancePreferences(123, 456, 789);
201 | verify(mockSS).setPerformancePreferences(123, 456, 789);
202 | }
203 | }
204 |
--------------------------------------------------------------------------------
/src/test/java/com/pmeade/websocket/io/LineInputStreamTest.java:
--------------------------------------------------------------------------------
1 | /*
2 | * LineInputStreamTest.java
3 | * Copyright 2014 Patrick Meade.
4 | *
5 | * This program is free software: you can redistribute it and/or modify
6 | * it under the terms of the GNU Affero General Public License as published by
7 | * the Free Software Foundation, either version 3 of the License, or
8 | * (at your option) any later version.
9 | *
10 | * This program is distributed in the hope that it will be useful,
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | * GNU Affero General Public License for more details.
14 | *
15 | * You should have received a copy of the GNU Affero General Public License
16 | * along with this program. If not, see .
17 | */
18 |
19 | package com.pmeade.websocket.io;
20 |
21 | import java.io.BufferedReader;
22 | import java.io.ByteArrayInputStream;
23 | import java.io.IOException;
24 | import java.io.InputStream;
25 | import org.junit.After;
26 | import org.junit.AfterClass;
27 | import org.junit.Before;
28 | import org.junit.BeforeClass;
29 | import org.junit.Test;
30 |
31 | import static org.junit.Assert.*;
32 |
33 | /**
34 | * @author blinkdog
35 | */
36 | public class LineInputStreamTest
37 | {
38 | public LineInputStreamTest() {
39 | }
40 |
41 | @BeforeClass
42 | public static void setUpClass() {
43 | }
44 |
45 | @AfterClass
46 | public static void tearDownClass() {
47 | }
48 |
49 | @Before
50 | public void setUp() {
51 | }
52 |
53 | @After
54 | public void tearDown() {
55 | }
56 |
57 | @Test
58 | public void testAlwaysSucceed() {
59 | assertTrue(true);
60 | }
61 |
62 | @Test
63 | public void testRequireDecoratee() {
64 | try {
65 | LineInputStream lis = new LineInputStream(null);
66 | fail();
67 | } catch (NullPointerException e) {
68 | // expected
69 | }
70 | }
71 |
72 | @Test
73 | public void testConstructWithInputStream() throws Exception {
74 | ByteArrayInputStream bais = new ByteArrayInputStream("".getBytes());
75 | LineInputStream lis = new LineInputStream(bais);
76 | assertNotNull(lis);
77 | assertNull(lis.readLine());
78 | }
79 |
80 | @Test
81 | public void testOneLineNoLineEndings() throws Exception {
82 | ByteArrayInputStream bais = new ByteArrayInputStream("Dalek".getBytes());
83 | LineInputStream lis = new LineInputStream(bais);
84 | assertNotNull(lis);
85 | assertEquals("Dalek", lis.readLine());
86 | assertEquals(null, lis.readLine());
87 | }
88 |
89 | @Test
90 | public void testTwoLinesOneLineEnding() throws Exception {
91 | String[] inputs = {
92 | "Cyberman\r\n",
93 | "Dalek"
94 | };
95 | StringBuilder sb = new StringBuilder();
96 | for(String input : inputs) {
97 | sb.append(input);
98 | }
99 | byte[] bytes = sb.toString().getBytes();
100 | ByteArrayInputStream bais = new ByteArrayInputStream(bytes);
101 | LineInputStream lis = new LineInputStream(bais);
102 | assertNotNull(lis);
103 | assertEquals("Cyberman", lis.readLine());
104 | assertEquals("Dalek", lis.readLine());
105 | assertEquals(null, lis.readLine());
106 | }
107 |
108 | @Test
109 | public void testSeveralLinesAllEndings() throws Exception {
110 | String[] inputs = {
111 | "Alice\r\n",
112 | "Bob\r\n",
113 | "Carol\r\n",
114 | "Dave\r\n",
115 | "Eve\r\n",
116 | "Mallory\r\n",
117 | "Trent\r\n"
118 | };
119 | StringBuilder sb = new StringBuilder();
120 | for(String input : inputs) {
121 | sb.append(input);
122 | }
123 | byte[] bytes = sb.toString().getBytes();
124 | ByteArrayInputStream bais = new ByteArrayInputStream(bytes);
125 | LineInputStream lis = new LineInputStream(bais);
126 | assertNotNull(lis);
127 | assertEquals("Alice", lis.readLine());
128 | assertEquals("Bob", lis.readLine());
129 | assertEquals("Carol", lis.readLine());
130 | assertEquals("Dave", lis.readLine());
131 | assertEquals("Eve", lis.readLine());
132 | assertEquals("Mallory", lis.readLine());
133 | assertEquals("Trent", lis.readLine());
134 | assertEquals(null, lis.readLine());
135 | }
136 |
137 | @Test
138 | public void testSeveralLinesMixedEndings() throws Exception {
139 | String[] inputs = {
140 | "Alice\r",
141 | "Bob\n",
142 | "Carol\r\n",
143 | "Dave\r",
144 | "Eve\n",
145 | "Mallory\r\n",
146 | "Trent\r\n",
147 | "Eve\n",
148 | "Dave\r",
149 | "Carol\r\n",
150 | "Eve\n",
151 | };
152 | StringBuilder sb = new StringBuilder();
153 | for(String input : inputs) {
154 | sb.append(input);
155 | }
156 | byte[] bytes = sb.toString().getBytes();
157 | ByteArrayInputStream bais = new ByteArrayInputStream(bytes);
158 | LineInputStream lis = new LineInputStream(bais);
159 | assertNotNull(lis);
160 | assertEquals("Alice\rBob\nCarol", lis.readLine());
161 | assertEquals("Dave\rEve\nMallory", lis.readLine());
162 | assertEquals("Trent", lis.readLine());
163 | assertEquals("Eve\nDave\rCarol", lis.readLine());
164 | assertEquals("Eve\n", lis.readLine());
165 | assertEquals(null, lis.readLine());
166 | }
167 |
168 | @Test
169 | public void testPlainRead() throws Exception {
170 | ByteArrayInputStream bais = new ByteArrayInputStream("Dalek".getBytes());
171 | LineInputStream lis = new LineInputStream(bais);
172 | assertNotNull(lis);
173 | assertEquals((int)'D', lis.read());
174 | assertEquals((int)'a', lis.read());
175 | assertEquals((int)'l', lis.read());
176 | assertEquals((int)'e', lis.read());
177 | assertEquals((int)'k', lis.read());
178 | assertEquals(-1, lis.read());
179 | }
180 |
181 | @Test
182 | public void testDecoratedIOExceptionNotSuppressed() throws Exception {
183 | InputStream is = new InputStream() {
184 | @Override
185 | public int read() throws IOException {
186 | throw new IOException("Exterminate!");
187 | }
188 | };
189 | LineInputStream lis = new LineInputStream(is);
190 | assertNotNull(lis);
191 | try {
192 | lis.read();
193 | fail();
194 | } catch (IOException e) {
195 | // expected
196 | }
197 | }
198 |
199 | @Test
200 | public void testSeveralLinesSomeEmpty() throws Exception {
201 | String[] inputs = {
202 | "Alice\r\n",
203 | "Bob\r\n",
204 | "\r\n",
205 | "Dave\r\n",
206 | "Eve\r\n",
207 | "\r\n",
208 | "Trent\r\n"
209 | };
210 | StringBuilder sb = new StringBuilder();
211 | for(String input : inputs) {
212 | sb.append(input);
213 | }
214 | byte[] bytes = sb.toString().getBytes();
215 | ByteArrayInputStream bais = new ByteArrayInputStream(bytes);
216 | LineInputStream lis = new LineInputStream(bais);
217 | assertNotNull(lis);
218 | assertEquals("Alice", lis.readLine());
219 | assertEquals("Bob", lis.readLine());
220 | assertEquals("", lis.readLine());
221 | assertEquals("Dave", lis.readLine());
222 | assertEquals("Eve", lis.readLine());
223 | assertEquals("", lis.readLine());
224 | assertEquals("Trent", lis.readLine());
225 | assertEquals(null, lis.readLine());
226 | }
227 | }
228 |
--------------------------------------------------------------------------------
/src/main/java/com/pmeade/websocket/http/HttpRequest.java:
--------------------------------------------------------------------------------
1 | /*
2 | * HttpRequest.java
3 | * Copyright 2014 Patrick Meade.
4 | *
5 | * This program is free software: you can redistribute it and/or modify
6 | * it under the terms of the GNU Affero General Public License as published by
7 | * the Free Software Foundation, either version 3 of the License, or
8 | * (at your option) any later version.
9 | *
10 | * This program is distributed in the hope that it will be useful,
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | * GNU Affero General Public License for more details.
14 | *
15 | * You should have received a copy of the GNU Affero General Public License
16 | * along with this program. If not, see .
17 | */
18 |
19 | package com.pmeade.websocket.http;
20 |
21 | import com.pmeade.websocket.io.LineInputStream;
22 | import java.io.IOException;
23 | import java.io.InputStream;
24 | import java.util.Collection;
25 | import java.util.Collections;
26 | import java.util.HashMap;
27 | import java.util.Map;
28 | import java.util.Set;
29 | import org.slf4j.Logger;
30 | import org.slf4j.LoggerFactory;
31 |
32 | import static com.google.common.base.Preconditions.checkNotNull;
33 |
34 | /**
35 | * HttpRequest represents the headers of an HTTP Request as a Map. At
36 | * construction time, it parses the headers of the HTTP Request from
37 | * the provided InputStream, and places them into an accessible but
38 | * immutable Map.
39 | * @author blinkdog
40 | */
41 | public class HttpRequest implements Map {
42 | /**
43 | * Logging, just in case anything goes wrong.
44 | */
45 | private static final Logger LOG =
46 | LoggerFactory.getLogger(HttpRequest.class);
47 |
48 | /**
49 | * The Request Line of the HTTP Request is stored in the map under
50 | * this key.
51 | */
52 | public static final String REQUEST_LINE = "REQUEST_LINE";
53 |
54 | /**
55 | * Construct an HttpRequest object.
56 | * @param in InputStream providing the content of the HTTP Request to be
57 | * parsed
58 | */
59 | public HttpRequest(final InputStream in) {
60 | checkNotNull(in);
61 | read(in);
62 | }
63 |
64 | /**
65 | * {@inheritDoc}
66 | * @return {@inheritDoc}
67 | */
68 | public final int size() {
69 | return headerMap.size();
70 | }
71 |
72 | /**
73 | * {@inheritDoc}
74 | * @return {@inheritDoc}
75 | */
76 | public final boolean isEmpty() {
77 | return headerMap.isEmpty();
78 | }
79 |
80 | /**
81 | * {@inheritDoc}
82 | * @param key {@inheritDoc}
83 | * @return {@inheritDoc}
84 | */
85 | public final boolean containsKey(final Object key) {
86 | return headerMap.containsKey(key);
87 | }
88 |
89 | /**
90 | * {@inheritDoc}
91 | * @param value {@inheritDoc}
92 | * @return {@inheritDoc}
93 | */
94 | public final boolean containsValue(final Object value) {
95 | return headerMap.containsValue(value);
96 | }
97 |
98 | /**
99 | * {@inheritDoc}
100 | * @param key {@inheritDoc}
101 | * @return {@inheritDoc}
102 | */
103 | public final String get(final Object key) {
104 | return headerMap.get(key);
105 | }
106 |
107 | /**
108 | * {@inheritDoc}
109 | * @param key {@inheritDoc}
110 | * @param value {@inheritDoc}
111 | * @return {@inheritDoc}
112 | */
113 | public final String put(final String key, final String value) {
114 | return headerMap.put(key, value);
115 | }
116 |
117 | /**
118 | * {@inheritDoc}
119 | * @param key {@inheritDoc}
120 | * @return {@inheritDoc}
121 | */
122 | public final String remove(final Object key) {
123 | return headerMap.remove(key);
124 | }
125 |
126 | /**
127 | * {@inheritDoc}
128 | * @param m {@inheritDoc}
129 | */
130 | public final void putAll(final Map extends String, ? extends String> m) {
131 | headerMap.putAll(m);
132 | }
133 |
134 | /**
135 | * {@inheritDoc}
136 | */
137 | public final void clear() {
138 | headerMap.clear();
139 | }
140 |
141 | /**
142 | * {@inheritDoc}
143 | * @return {@inheritDoc}
144 | */
145 | public final Set keySet() {
146 | return headerMap.keySet();
147 | }
148 |
149 | /**
150 | * {@inheritDoc}
151 | * @return {@inheritDoc}
152 | */
153 | public final Collection values() {
154 | return headerMap.values();
155 | }
156 |
157 | /**
158 | * {@inheritDoc}
159 | * @return {@inheritDoc}
160 | */
161 | public final Set> entrySet() {
162 | return headerMap.entrySet();
163 | }
164 |
165 | /**
166 | * Parse the headers of the HTTP Request. The provided InputStream will
167 | * contain HTTP Request data. The headers are parsed and placed into
168 | * the Map. The Map is then made immutable, for safe querying by clients.
169 | * @param in InputStream providing the content of the HTTP Request to be
170 | * parsed
171 | */
172 | private void read(final InputStream in) {
173 | // wrap the Reader in something more convenient
174 | LineInputStream lis = new LineInputStream(in);
175 | // create a place to store the header values
176 | headerMap = new HashMap();
177 | // make sure we handle any IOExceptions
178 | try {
179 | // read the first line from the buffered reader
180 | String line = lis.readLine();
181 | // if the first line is empty
182 | while (line != null && line.isEmpty()) {
183 | // keep reading until we get a non-empty line
184 | line = lis.readLine();
185 | }
186 | // store the non-empty line as the request line
187 | headerMap.put(REQUEST_LINE, line);
188 | // read the next line from the buffered reader
189 | line = lis.readLine();
190 | // as long as we don't have a null or empty line
191 | while (line != null && !line.isEmpty()) {
192 | // determine the position of the first colon
193 | int firstColonPos = line.indexOf(":");
194 | // if there is a colon in the line
195 | if (firstColonPos > 0) {
196 | // separate the header field from the header value
197 | String key = line.substring(0, firstColonPos).trim();
198 | int length = line.length();
199 | String value = line.substring(firstColonPos + 1, length);
200 | value = value.trim();
201 | // if we got both a non-empty field and non-empty value
202 | if (!key.isEmpty() && !value.isEmpty()) {
203 | // add it to the map
204 | headerMap.put(key, value);
205 | // add it to the map in lowercase, as well
206 | headerMap.put(key.toLowerCase(), value);
207 | }
208 | }
209 | // read the next line from the header
210 | line = lis.readLine();
211 | }
212 | } catch (IOException e) {
213 | // log it, and move on
214 | LOG.error("Unable to read HTTP Request in HttpRequest.read():", e);
215 | }
216 | // after we've processed the header, lock the map
217 | headerMap = Collections.unmodifiableMap(headerMap);
218 | }
219 |
220 | /**
221 | * A Map to store the header fields and values after they are parsed
222 | * from the HTTP Request. The parsing function also makes this map
223 | * unmodifiable, so that it is safe to allow clients to query it.
224 | */
225 | private Map headerMap;
226 | }
227 |
--------------------------------------------------------------------------------
/src/main/java/com/pmeade/websocket/net/WebSocket.java:
--------------------------------------------------------------------------------
1 | /*
2 | * WebSocket.java
3 | * Copyright 2014 Patrick Meade.
4 | *
5 | * This program is free software: you can redistribute it and/or modify
6 | * it under the terms of the GNU Affero General Public License as published by
7 | * the Free Software Foundation, either version 3 of the License, or
8 | * (at your option) any later version.
9 | *
10 | * This program is distributed in the hope that it will be useful,
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | * GNU Affero General Public License for more details.
14 | *
15 | * You should have received a copy of the GNU Affero General Public License
16 | * along with this program. If not, see .
17 | */
18 |
19 | package com.pmeade.websocket.net;
20 |
21 | import com.pmeade.websocket.io.WebSocketServerInputStream;
22 | import com.pmeade.websocket.io.WebSocketServerOutputStream;
23 | import java.io.IOException;
24 | import java.net.InetAddress;
25 | import java.net.Socket;
26 | import java.net.SocketAddress;
27 | import java.net.SocketException;
28 | import java.nio.channels.SocketChannel;
29 |
30 | /**
31 | * WebSocket decorates Socket to provide the server side of a Socket that
32 | * speaks the WebSocket (RFC 6455) protocol.
33 | * @author veloxi
34 | */
35 | public class WebSocket extends Socket {
36 | /**
37 | * Construct a WebSocket. WebSocket decorates a Socket for WebSocket
38 | * (RFC 6455) behavior.
39 | * @param s Socket to be decorated with WebSocket behavior.
40 | */
41 | public WebSocket(final Socket s) {
42 | this.socket = s;
43 | }
44 |
45 | @Override
46 | public final void connect(final SocketAddress endpoint) throws IOException {
47 | socket.connect(endpoint);
48 | }
49 |
50 | @Override
51 | public final void connect(final SocketAddress endpoint, final int timeout)
52 | throws IOException {
53 | socket.connect(endpoint, timeout);
54 | }
55 |
56 | @Override
57 | public final void bind(final SocketAddress bindpoint) throws IOException {
58 | socket.bind(bindpoint);
59 | }
60 |
61 | @Override
62 | public final InetAddress getInetAddress() {
63 | return socket.getInetAddress();
64 | }
65 |
66 | @Override
67 | public final InetAddress getLocalAddress() {
68 | return socket.getLocalAddress();
69 | }
70 |
71 | @Override
72 | public final int getPort() {
73 | return socket.getPort();
74 | }
75 |
76 | @Override
77 | public final int getLocalPort() {
78 | return socket.getLocalPort();
79 | }
80 |
81 | @Override
82 | public final SocketAddress getRemoteSocketAddress() {
83 | return socket.getRemoteSocketAddress();
84 | }
85 |
86 | @Override
87 | public final SocketAddress getLocalSocketAddress() {
88 | return socket.getLocalSocketAddress();
89 | }
90 |
91 | @Override
92 | public final SocketChannel getChannel() {
93 | throw new UnsupportedOperationException();
94 | }
95 |
96 | @Override
97 | public final WebSocketServerInputStream getInputStream()
98 | throws IOException {
99 | if (wssos == null) {
100 | this.getOutputStream();
101 | }
102 | if (wssis == null) {
103 | wssis = new WebSocketServerInputStream(
104 | socket.getInputStream(), wssos);
105 | }
106 | return wssis;
107 | }
108 |
109 | @Override
110 | public final WebSocketServerOutputStream getOutputStream()
111 | throws IOException {
112 | if (wssos == null) {
113 | wssos = new WebSocketServerOutputStream(socket.getOutputStream());
114 | }
115 | return wssos;
116 | }
117 |
118 | @Override
119 | public final void setTcpNoDelay(final boolean on) throws SocketException {
120 | socket.setTcpNoDelay(on);
121 | }
122 |
123 | @Override
124 | public final boolean getTcpNoDelay() throws SocketException {
125 | return socket.getTcpNoDelay();
126 | }
127 |
128 | @Override
129 | public final void setSoLinger(final boolean on, final int linger)
130 | throws SocketException {
131 | socket.setSoLinger(on, linger);
132 | }
133 |
134 | @Override
135 | public final int getSoLinger() throws SocketException {
136 | return socket.getSoLinger();
137 | }
138 |
139 | @Override
140 | public final void sendUrgentData(final int data)
141 | throws IOException {
142 | socket.sendUrgentData(data);
143 | }
144 |
145 | @Override
146 | public final void setOOBInline(final boolean on)
147 | throws SocketException {
148 | socket.setOOBInline(on);
149 | }
150 |
151 | @Override
152 | public final boolean getOOBInline() throws SocketException {
153 | return socket.getOOBInline();
154 | }
155 |
156 | @Override
157 | public final synchronized void setSoTimeout(final int timeout)
158 | throws SocketException {
159 | socket.setSoTimeout(timeout);
160 | }
161 |
162 | @Override
163 | public final synchronized int getSoTimeout() throws SocketException {
164 | return socket.getSoTimeout();
165 | }
166 |
167 | @Override
168 | public final synchronized void setSendBufferSize(final int size)
169 | throws SocketException {
170 | socket.setSendBufferSize(size);
171 | }
172 |
173 | @Override
174 | public final synchronized int getSendBufferSize() throws SocketException {
175 | return socket.getSendBufferSize();
176 | }
177 |
178 | @Override
179 | public final synchronized void setReceiveBufferSize(final int size)
180 | throws SocketException {
181 | socket.setReceiveBufferSize(size);
182 | }
183 |
184 | @Override
185 | public final synchronized int getReceiveBufferSize()
186 | throws SocketException {
187 | return socket.getReceiveBufferSize();
188 | }
189 |
190 | @Override
191 | public final void setKeepAlive(final boolean on) throws SocketException {
192 | socket.setKeepAlive(on);
193 | }
194 |
195 | @Override
196 | public final boolean getKeepAlive() throws SocketException {
197 | return socket.getKeepAlive();
198 | }
199 |
200 | @Override
201 | public final void setTrafficClass(final int tc) throws SocketException {
202 | socket.setTrafficClass(tc);
203 | }
204 |
205 | @Override
206 | public final int getTrafficClass() throws SocketException {
207 | return socket.getTrafficClass();
208 | }
209 |
210 | @Override
211 | public final void setReuseAddress(final boolean on) throws SocketException {
212 | socket.setReuseAddress(on);
213 | }
214 |
215 | @Override
216 | public final boolean getReuseAddress() throws SocketException {
217 | return socket.getReuseAddress();
218 | }
219 |
220 | @Override
221 | public final synchronized void close() throws IOException {
222 | socket.close();
223 | }
224 |
225 | @Override
226 | public final void shutdownInput() throws IOException {
227 | socket.shutdownInput();
228 | }
229 |
230 | @Override
231 | public final void shutdownOutput() throws IOException {
232 | socket.shutdownOutput();
233 | }
234 |
235 | @Override
236 | public final String toString() {
237 | return socket.toString();
238 | }
239 |
240 | @Override
241 | public final boolean isConnected() {
242 | return socket.isConnected();
243 | }
244 |
245 | @Override
246 | public final boolean isBound() {
247 | return socket.isBound();
248 | }
249 |
250 | @Override
251 | public final boolean isClosed() {
252 | return socket.isClosed();
253 | }
254 |
255 | @Override
256 | public final boolean isInputShutdown() {
257 | return socket.isInputShutdown();
258 | }
259 |
260 | @Override
261 | public final boolean isOutputShutdown() {
262 | return socket.isOutputShutdown();
263 | }
264 |
265 | @Override
266 | public final void setPerformancePreferences(
267 | final int connectionTime,
268 | final int latency,
269 | final int bandwidth) {
270 | socket.setPerformancePreferences(connectionTime, latency, bandwidth);
271 | }
272 |
273 | /**
274 | * Socket to be decorated for WebSocket behavior.
275 | */
276 | private final Socket socket;
277 |
278 | /**
279 | * WebSocketServerInputStream that decorates the InputStream for
280 | * this Socket. Created on the first call to getInputStream().
281 | */
282 | private WebSocketServerInputStream wssis = null;
283 |
284 | /**
285 | * WebSocketServerOutputStream that decorates the OutputStream for
286 | * this Socket. Created on the first call to getInputStream()
287 | * or getOutputStream().
288 | */
289 | private WebSocketServerOutputStream wssos = null;
290 | }
291 |
--------------------------------------------------------------------------------
/src/test/java/com/pmeade/websocket/http/HttpRequestTest.java:
--------------------------------------------------------------------------------
1 | /*
2 | * HttpRequestTest.java
3 | * Copyright 2014 Patrick Meade.
4 | *
5 | * This program is free software: you can redistribute it and/or modify
6 | * it under the terms of the GNU Affero General Public License as published by
7 | * the Free Software Foundation, either version 3 of the License, or
8 | * (at your option) any later version.
9 | *
10 | * This program is distributed in the hope that it will be useful,
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | * GNU Affero General Public License for more details.
14 | *
15 | * You should have received a copy of the GNU Affero General Public License
16 | * along with this program. If not, see .
17 | */
18 |
19 | package com.pmeade.websocket.http;
20 |
21 | import com.google.common.base.Joiner;
22 | import com.google.common.collect.ImmutableMap;
23 | import java.io.ByteArrayInputStream;
24 | import java.io.IOException;
25 | import java.io.InputStream;
26 | import java.util.Arrays;
27 | import java.util.Collection;
28 | import java.util.Map;
29 | import java.util.Set;
30 | import org.junit.After;
31 | import org.junit.AfterClass;
32 | import org.junit.Before;
33 | import org.junit.BeforeClass;
34 | import org.junit.Test;
35 | import static org.junit.Assert.*;
36 |
37 | /**
38 | * @author blinkdog
39 | */
40 | public class HttpRequestTest
41 | {
42 | private static final String CLIENT_HANDSHAKE = (Joiner.on("\r\n").join(Arrays.asList(
43 | "GET / HTTP/1.1",
44 | "Host: localhost:8080",
45 | "User-Agent: Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:30.0) Gecko/20100101 Firefox/30.0",
46 | "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
47 | "Accept-Language: en-US,en;q=0.5",
48 | "Accept-Encoding: gzip, deflate",
49 | "Sec-WebSocket-Version: 13",
50 | "Origin: null",
51 | "Sec-WebSocket-Key: V76L7ym8nB/U/K96iWDjKg==",
52 | "Connection: keep-alive, Upgrade",
53 | "Pragma: no-cache",
54 | "Cache-Control: no-cache",
55 | "Upgrade: websocket",
56 | ""
57 | ))) + "\r\n";
58 |
59 | private static final String MESSED_UP_HEADERS = (Joiner.on("\r\n").join(Arrays.asList(
60 | "GET / HTTP/1.1",
61 | "Host: localhost:8080",
62 | "User-Agent Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:30.0) Gecko/20100101 Firefox/30.0",
63 | "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
64 | "Accept-Language en-US,en;q=0.5",
65 | "Accept-Encoding: gzip, deflate",
66 | "Sec-WebSocket-Version 13",
67 | "Origin: null",
68 | "Sec-WebSocket-Key V76L7ym8nB/U/K96iWDjKg==",
69 | "Connection: keep-alive, Upgrade",
70 | "Pragma no-cache",
71 | "Cache-Control: no-cache",
72 | "Upgrade websocket",
73 | ""
74 | ))) + "\r\n";
75 |
76 | private static final String INCOMPLETE_REQUEST = (Joiner.on("\r\n").join(Arrays.asList(
77 | "GET / HTTP/1.1",
78 | "Host: localhost:8080",
79 | "User-Agent: Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:30.0) Gecko/20100101 Firefox/30.0",
80 | "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
81 | "Accept-Language: en-US,en;q=0.5",
82 | "Accept-Encoding: gzip, deflate",
83 | "Sec-WebSocket-Version: 13"
84 | ))) + "\r\n";
85 |
86 | private static final String BROKEN_HEADERS = (Joiner.on("\r\n").join(Arrays.asList(
87 | "GET / HTTP/1.1",
88 | ": localhost:8080",
89 | ":::: Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:30.0) Gecko/20100101 Firefox/30.0",
90 | " : text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
91 | "JustABunchOfStuff:",
92 | ":JustABunchOfStuff",
93 | ":::JustABunchOfStuff:::",
94 | " : : : JustABunchOfStuff : : : ",
95 | "dflkjdlk j d lkjdlkjflkdjf lkj dl kfjdlkjfdlkjflkdjflkdjlkfjdlkjfdlkjflkdjflkdjlfkjdlkfjdlkfjdlk:",
96 | ""
97 | ))) + "\r\n";
98 |
99 | public HttpRequestTest() {
100 | }
101 |
102 | @BeforeClass
103 | public static void setUpClass() {
104 | }
105 |
106 | @AfterClass
107 | public static void tearDownClass() {
108 | }
109 |
110 | @Before
111 | public void setUp() {
112 | }
113 |
114 | @After
115 | public void tearDown() {
116 | }
117 |
118 | @Test
119 | public void testAlwaysSucceed() {
120 | assertTrue(true);
121 | }
122 |
123 | @Test
124 | public void testConstructNullReader() {
125 | try {
126 | HttpRequest hr = new HttpRequest(null);
127 | fail();
128 | } catch(NullPointerException e) {
129 | // expected
130 | }
131 | }
132 |
133 | @Test
134 | public void testRequestLineConstantDefined() {
135 | assertNotNull(HttpRequest.REQUEST_LINE);
136 | }
137 |
138 | @Test
139 | public void testConstructWithEmptyReader() {
140 | ByteArrayInputStream bais = new ByteArrayInputStream("".getBytes());
141 | HttpRequest hr = new HttpRequest(bais);
142 | assertEquals(1, hr.size());
143 | assertEquals(null, hr.get(HttpRequest.REQUEST_LINE));
144 | }
145 |
146 | @Test
147 | public void testWithClientHandshake() {
148 | ByteArrayInputStream bais = new ByteArrayInputStream(CLIENT_HANDSHAKE.getBytes());
149 | HttpRequest hr = new HttpRequest(bais);
150 | assertEquals(25, hr.size());
151 | assertEquals("GET / HTTP/1.1", hr.get(HttpRequest.REQUEST_LINE));
152 | assertEquals("localhost:8080", hr.get("Host"));
153 | assertEquals("Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:30.0) Gecko/20100101 Firefox/30.0", hr.get("User-Agent"));
154 | assertEquals("text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8", hr.get("Accept"));
155 | assertEquals("en-US,en;q=0.5", hr.get("Accept-Language"));
156 | assertEquals("gzip, deflate", hr.get("Accept-Encoding"));
157 | assertEquals("13", hr.get("Sec-WebSocket-Version"));
158 | assertEquals("null", hr.get("Origin"));
159 | assertEquals("V76L7ym8nB/U/K96iWDjKg==", hr.get("Sec-WebSocket-Key"));
160 | assertEquals("keep-alive, Upgrade", hr.get("Connection"));
161 | assertEquals("no-cache", hr.get("Pragma"));
162 | assertEquals("no-cache", hr.get("Cache-Control"));
163 | assertEquals("websocket", hr.get("Upgrade"));
164 | }
165 |
166 | @Test
167 | public void testConstructWithBlankLines() {
168 | ByteArrayInputStream bais = new ByteArrayInputStream("\r\n\r\n\r\n".getBytes());
169 | HttpRequest hr = new HttpRequest(bais);
170 | assertEquals(1, hr.size());
171 | assertEquals(null, hr.get(HttpRequest.REQUEST_LINE));
172 | }
173 |
174 | @Test
175 | public void testWithMessedUpHeaders() {
176 | ByteArrayInputStream bais = new ByteArrayInputStream(MESSED_UP_HEADERS.getBytes());
177 | HttpRequest hr = new HttpRequest(bais);
178 | assertEquals(15, hr.size());
179 | assertEquals("GET / HTTP/1.1", hr.get(HttpRequest.REQUEST_LINE));
180 | assertEquals("localhost:8080", hr.get("Host"));
181 | assertEquals(null, hr.get("User-Agent"));
182 | assertEquals("text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8", hr.get("Accept"));
183 | assertEquals(null, hr.get("Accept-Language"));
184 | assertEquals("gzip, deflate", hr.get("Accept-Encoding"));
185 | assertEquals(null, hr.get("Sec-WebSocket-Version"));
186 | assertEquals("null", hr.get("Origin"));
187 | assertEquals(null, hr.get("Sec-WebSocket-Key"));
188 | assertEquals("keep-alive, Upgrade", hr.get("Connection"));
189 | assertEquals(null, hr.get("Pragma"));
190 | assertEquals("no-cache", hr.get("Cache-Control"));
191 | assertEquals(null, hr.get("Upgrade"));
192 | }
193 |
194 | @Test
195 | public void testWithIncompleteRequest() {
196 | ByteArrayInputStream bais = new ByteArrayInputStream(INCOMPLETE_REQUEST.getBytes());
197 | HttpRequest hr = new HttpRequest(bais);
198 | assertEquals(13, hr.size());
199 | assertEquals("GET / HTTP/1.1", hr.get(HttpRequest.REQUEST_LINE));
200 | assertEquals("localhost:8080", hr.get("Host"));
201 | assertEquals("Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:30.0) Gecko/20100101 Firefox/30.0", hr.get("User-Agent"));
202 | assertEquals("text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8", hr.get("Accept"));
203 | assertEquals("en-US,en;q=0.5", hr.get("Accept-Language"));
204 | assertEquals("gzip, deflate", hr.get("Accept-Encoding"));
205 | assertEquals("13", hr.get("Sec-WebSocket-Version"));
206 | }
207 |
208 | @Test
209 | public void testWithIncompleteRequestThatThrows() {
210 | InputStream bytesThenThrow = new InputStream() {
211 | final byte[] bytes = INCOMPLETE_REQUEST.getBytes();
212 | final ByteArrayInputStream bais = new ByteArrayInputStream(bytes);
213 | int count = 0;
214 | @Override
215 | public int read() throws IOException {
216 | if(count < bytes.length) {
217 | int data = (int)bytes[count];
218 | count++;
219 | return data;
220 | } else {
221 | throw new IOException("Oh frell...");
222 | }
223 | }
224 | };
225 | HttpRequest hr = new HttpRequest(bytesThenThrow);
226 | assertEquals(13, hr.size());
227 | assertEquals("GET / HTTP/1.1", hr.get(HttpRequest.REQUEST_LINE));
228 | assertEquals("localhost:8080", hr.get("Host"));
229 | assertEquals("Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:30.0) Gecko/20100101 Firefox/30.0", hr.get("User-Agent"));
230 | assertEquals("text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8", hr.get("Accept"));
231 | assertEquals("en-US,en;q=0.5", hr.get("Accept-Language"));
232 | assertEquals("gzip, deflate", hr.get("Accept-Encoding"));
233 | assertEquals("13", hr.get("Sec-WebSocket-Version"));
234 | }
235 |
236 | @Test
237 | public void testMapMutatorsFailWithException() {
238 | ByteArrayInputStream bais = new ByteArrayInputStream(CLIENT_HANDSHAKE.getBytes());
239 | HttpRequest hr = new HttpRequest(bais);
240 | assertEquals(25, hr.size());
241 | assertEquals("GET / HTTP/1.1", hr.get(HttpRequest.REQUEST_LINE));
242 | assertEquals("localhost:8080", hr.get("Host"));
243 |
244 | try {
245 | hr.put("Custom-Header", "Custom Data For Header");
246 | fail();
247 | } catch(UnsupportedOperationException e) {
248 | // expected
249 | }
250 |
251 | try {
252 | hr.remove("Host");
253 | fail();
254 | } catch(UnsupportedOperationException e) {
255 | // expected
256 | }
257 |
258 | try {
259 | ImmutableMap myMap = ImmutableMap.builder()
260 | .put("Header-One", "Data One")
261 | .put("Header-Two", "Data Two")
262 | .put("Header-Three", "Data Three")
263 | .build();
264 | hr.putAll(myMap);
265 | fail();
266 | } catch(UnsupportedOperationException e) {
267 | // expected
268 | }
269 |
270 | try {
271 | hr.clear();
272 | fail();
273 | } catch(UnsupportedOperationException e) {
274 | // expected
275 | }
276 | }
277 |
278 | @Test
279 | public void testMapAccessorsWork() {
280 | ByteArrayInputStream bais = new ByteArrayInputStream(CLIENT_HANDSHAKE.getBytes());
281 | HttpRequest hr = new HttpRequest(bais);
282 | assertEquals(25, hr.size());
283 | assertEquals("GET / HTTP/1.1", hr.get(HttpRequest.REQUEST_LINE));
284 | assertEquals("localhost:8080", hr.get("Host"));
285 |
286 | assertFalse(hr.isEmpty());
287 |
288 | assertTrue(hr.containsKey("Host"));
289 | assertFalse(hr.containsKey("Dalek-Malware"));
290 |
291 | assertTrue(hr.containsValue("localhost:8080"));
292 | assertFalse(hr.containsValue("Exterminate!"));
293 |
294 | Set keys = hr.keySet();
295 | assertNotNull(keys);
296 | assertEquals(25, keys.size());
297 |
298 | Collection values = hr.values();
299 | assertNotNull(values);
300 | assertEquals(25, values.size());
301 |
302 | Set> entrySet = hr.entrySet();
303 | assertNotNull(entrySet);
304 | assertEquals(25, entrySet.size());
305 | }
306 |
307 | @Test
308 | public void testWithBrokenHeaders() {
309 | ByteArrayInputStream bais = new ByteArrayInputStream(BROKEN_HEADERS.getBytes());
310 | HttpRequest hr = new HttpRequest(bais);
311 | assertEquals(1, hr.size());
312 | assertEquals("GET / HTTP/1.1", hr.get(HttpRequest.REQUEST_LINE));
313 | }
314 | }
315 |
--------------------------------------------------------------------------------
/src/main/java/com/pmeade/websocket/io/WebSocketServerOutputStream.java:
--------------------------------------------------------------------------------
1 | /*
2 | * WebSocketServerOutputStream.java
3 | * Copyright 2014 Patrick Meade.
4 | *
5 | * This program is free software: you can redistribute it and/or modify
6 | * it under the terms of the GNU Affero General Public License as published by
7 | * the Free Software Foundation, either version 3 of the License, or
8 | * (at your option) any later version.
9 | *
10 | * This program is distributed in the hope that it will be useful,
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | * GNU Affero General Public License for more details.
14 | *
15 | * You should have received a copy of the GNU Affero General Public License
16 | * along with this program. If not, see .
17 | */
18 |
19 | package com.pmeade.websocket.io;
20 |
21 | import java.io.IOException;
22 | import java.io.OutputStream;
23 |
24 | import static com.google.common.base.Preconditions.checkNotNull;
25 | import java.nio.charset.StandardCharsets;
26 |
27 | /**
28 | * WebSocketServerOutputStream decorates an OutputStream to handle WebSocket
29 | * frames as specified in RFC 6455.
30 | * @author pmeade
31 | */
32 | public class WebSocketServerOutputStream extends OutputStream {
33 | /**
34 | * Payload length indicating that the payload's true length is a
35 | * yet-to-be-provided unsigned 16-bit integer.
36 | */
37 | public static final int LENGTH_16 = 0x7E;
38 |
39 | /**
40 | * A payload specified with 16 bits must have at least this
41 | * length in order to be considered valid.
42 | */
43 | public static final int LENGTH_16_MIN = 126;
44 |
45 | /**
46 | * Payload length indicating that the payload's true length is a
47 | * yet-to-be-provided unsigned 64-bit integer (MSB = 0).
48 | */
49 | public static final int LENGTH_64 = 0x7F;
50 |
51 | /**
52 | * A payload specified with 64 bits must have at least this
53 | * length in order to be considered valid.
54 | */
55 | public static final int LENGTH_64_MIN = 0x10000;
56 |
57 | /**
58 | * Binary mask to remove all but the bits of octet 3. We also remove the
59 | * sign bit (0x80000000) if any, to prevent it from being shifted down.
60 | */
61 | public static final int MASK_HIGH_WORD_HIGH_BYTE_NO_SIGN = 0x7f000000;
62 |
63 | /**
64 | * Binary mask to remove all but the bits of octet 2.
65 | */
66 | public static final int MASK_HIGH_WORD_LOW_BYTE = 0x00ff0000;
67 |
68 | /**
69 | * Binary mask to remove all but the bits of octet 1.
70 | */
71 | public static final int MASK_LOW_WORD_HIGH_BYTE = 0x0000ff00;
72 |
73 | /**
74 | * Binary mask to remove all but the lowest 8 bits (octet 0).
75 | */
76 | public static final int MASK_LOW_WORD_LOW_BYTE = 0x000000ff;
77 |
78 | /**
79 | * Number of bits required to shift octet 1 into the lowest 8 bits.
80 | */
81 | public static final int OCTET_ONE = 8;
82 |
83 | /**
84 | * Number of bits required to shift octet 2 into the lowest 8 bits.
85 | */
86 | public static final int OCTET_TWO = 16;
87 |
88 | /**
89 | * Number of bits required to shift octet 3 into the lowest 8 bits.
90 | */
91 | public static final int OCTET_THREE = 24;
92 |
93 | /**
94 | * WebSocket defined opcode for a Binary frame. Includes high bit (0x80)
95 | * to indicate that the frame is the final/complete frame.
96 | */
97 | public static final int OPCODE_FRAME_BINARY = 0x82;
98 |
99 | /**
100 | * WebSocket defined opcode for a Close frame. Includes high bit (0x80)
101 | * to indicate that the frame is the final/complete frame.
102 | */
103 | public static final int OPCODE_FRAME_CLOSE = 0x88;
104 |
105 | /**
106 | * WebSocket defined opcode for a Pong frame. Includes high bit (0x80)
107 | * to indicate that the frame is the final/complete frame.
108 | */
109 | public static final int OPCODE_FRAME_PONG = 0x8A;
110 |
111 | /**
112 | * WebSocket defined opcode for a Text frame. Includes high bit (0x80)
113 | * to indicate that the frame is the final/complete frame.
114 | */
115 | public static final int OPCODE_FRAME_TEXT = 0x81;
116 |
117 | /**
118 | * Create a WebSocket-speaking OutputStream from the provided OutputStream.
119 | * @param os OutputStream to be decorated as a WebSocketServerOutputStream
120 | */
121 | public WebSocketServerOutputStream(final OutputStream os) {
122 | checkNotNull(os);
123 | this.outputStream = os;
124 | }
125 |
126 | /**
127 | * Writes the specified byte to this output stream. The general contract
128 | * for write is that one byte is written to the output stream. The byte to
129 | * be written is the eight low-order bits of the argument b. The 24
130 | * high-order bits of b are ignored.
131 | * Subclasses of OutputStream must provide an implementation for this
132 | * method.
133 | * @param b the byte.
134 | * @throws IOException if an I/O error occurs. In particular, an
135 | * IOException may be thrown if the output stream has
136 | * been closed.
137 | */
138 | @Override
139 | public final void write(final int b) throws IOException {
140 | if (handshakeComplete) {
141 | byte[] ba = new byte[] {(byte) b };
142 | writeBinary(ba);
143 | } else {
144 | outputStream.write(b);
145 | }
146 | }
147 |
148 | /**
149 | * Writes len bytes from the specified byte array starting at offset off to
150 | * this output stream. The general contract for write(b, off, len) is that
151 | * some of the bytes in the array b are written to the output stream in
152 | * order; element b[off] is the first byte written and b[off+len-1] is the
153 | * last byte written by this operation.
154 | *
The write method of OutputStream calls the write method of one
155 | * argument on each of the bytes to be written out. Subclasses are
156 | * encouraged to override this method and provide a more efficient
157 | * implementation.
158 | *
If b is null, a NullPointerException is thrown.
159 | *
If off is negative, or len is negative, or off+len is greater than
160 | * the length of the array b, then an IndexOutOfBoundsException is thrown.
161 | * @param b the data.
162 | * @param off the start offset in the data.
163 | * @param len the number of bytes to write.
164 | * @throws IOException if an I/O error occurs. In particular, an
165 | * IOException is thrown if the output stream is
166 | * closed.
167 | */
168 | @Override
169 | public final void write(final byte[] b, final int off, final int len)
170 | throws IOException {
171 | if (handshakeComplete) {
172 | byte[] dst = new byte[len];
173 | System.arraycopy(b, off, dst, 0, len);
174 | writeBinary(dst);
175 | } else {
176 | super.write(b, off, len);
177 | }
178 | }
179 |
180 | /**
181 | * Writes b.length bytes from the specified byte array to this output
182 | * stream. The general contract for write(b) is that it should have
183 | * exactly the same effect as the call write(b, 0, b.length).
184 | * @param b the data.
185 | * @throws IOException if an I/O error occurs.
186 | */
187 | @Override
188 | public final void write(final byte[] b) throws IOException {
189 | if (handshakeComplete) {
190 | writeBinary(b);
191 | } else {
192 | super.write(b);
193 | }
194 | }
195 |
196 | /**
197 | * Determine if a Close control frame has been sent over the WebSocket.
198 | * @return true, iff a Close control frame has been sent,
199 | * otherwise fasle
200 | */
201 | public final boolean isCloseSent() {
202 | return closeSent;
203 | }
204 |
205 | /**
206 | * Determine if the WebSocket handshake has completed successfully.
207 | * @return true, if the WebSocket handshake has completed successfully,
208 | * otherwise false
209 | */
210 | public final boolean isHandshakeComplete() {
211 | return handshakeComplete;
212 | }
213 |
214 | /**
215 | * Tell this WebSocketServerOutputStream if the WebSocket handshake
216 | * has completed successfully.
217 | * @param complete true, if the WebSocket handshake has completed
218 | * successfully, otherwise false
219 | */
220 | public final void setHandshakeComplete(final boolean complete) {
221 | this.handshakeComplete = complete;
222 | }
223 |
224 | /**
225 | * Write the provided binary data to the WebSocket.
226 | * @param bytes byte array containing the binary data to be writen
227 | * @throws IOException if an I/O error occurs
228 | */
229 | public final void writeBinary(final byte[] bytes) throws IOException {
230 | int binLength = bytes.length;
231 | outputStream.write(OPCODE_FRAME_BINARY); // final binary-frame
232 | if (binLength < LENGTH_16_MIN) {
233 | outputStream.write(binLength); // small payload length
234 | } else if (binLength < LENGTH_64_MIN) {
235 | outputStream.write(LENGTH_16); // medium payload flag
236 | outputStream.write(
237 | (binLength & MASK_LOW_WORD_HIGH_BYTE) >> OCTET_ONE);
238 | outputStream.write(binLength & MASK_LOW_WORD_LOW_BYTE);
239 | } else {
240 | outputStream.write(LENGTH_64); // large payload flag
241 | outputStream.write(0x00); // upper bytes
242 | outputStream.write(0x00); // upper bytes
243 | outputStream.write(0x00); // upper bytes
244 | outputStream.write(0x00); // upper bytes
245 | outputStream.write(
246 | (binLength & MASK_HIGH_WORD_HIGH_BYTE_NO_SIGN) >> OCTET_THREE);
247 | outputStream.write(
248 | (binLength & MASK_HIGH_WORD_LOW_BYTE) >> OCTET_TWO);
249 | outputStream.write(
250 | (binLength & MASK_LOW_WORD_HIGH_BYTE) >> OCTET_ONE);
251 | outputStream.write(binLength & MASK_LOW_WORD_LOW_BYTE);
252 | }
253 | outputStream.write(bytes); // binary payload
254 | }
255 |
256 | /**
257 | * Write a Close control frame to the WebSocket.
258 | * @throws IOException if an I/O error occurs
259 | */
260 | public final void writeClose() throws IOException {
261 | if (!closeSent) {
262 | closeSent = true;
263 | outputStream.write(new byte[] {
264 | (byte) OPCODE_FRAME_CLOSE, (byte) 0x00
265 | });
266 | }
267 | }
268 |
269 | /**
270 | * Write a Close control frame to the WebSocket.
271 | * @param statusCode status code indicating the reason for the closure
272 | * of the WebSocket; constants defined in RFC 6455
273 | * @throws IOException if an I/O error occurs
274 | */
275 | public final void writeClose(final int statusCode) throws IOException {
276 | if (!closeSent) {
277 | closeSent = true;
278 | outputStream.write(new byte[] {
279 | (byte) OPCODE_FRAME_CLOSE, (byte) 0x02,
280 | (byte) ((statusCode & MASK_LOW_WORD_HIGH_BYTE) >> OCTET_ONE),
281 | (byte) (statusCode & MASK_LOW_WORD_LOW_BYTE)
282 | });
283 | }
284 | }
285 |
286 | /**
287 | * Write a Pong control frame to the WebSocket. Uses the provided data
288 | * as the payload data of the control frame.
289 | * @param pongPayload byte array containing payload data for the pong frame
290 | * @throws IOException if an I/O error occurs
291 | */
292 | public final void writePong(final byte[] pongPayload) throws IOException {
293 | outputStream.write(new byte[] {
294 | (byte) OPCODE_FRAME_PONG, (byte) (pongPayload.length)
295 | });
296 | outputStream.write(pongPayload);
297 | }
298 |
299 | /**
300 | * Write the provided String to the WebSocket in UTF-8 format.
301 | * @param string String to be written to the WebSocket
302 | * @throws IOException if an I/O error occurs
303 | */
304 | public final void writeString(final String string) throws IOException {
305 | byte[] utfBytes = string.getBytes(StandardCharsets.UTF_8);
306 | int utfLength = utfBytes.length;
307 | outputStream.write(OPCODE_FRAME_TEXT); // final text-frame
308 | if (utfLength < LENGTH_16_MIN) {
309 | outputStream.write(utfLength); // small payload length
310 | } else if (utfLength < LENGTH_64_MIN) {
311 | outputStream.write(LENGTH_16); // medium payload flag
312 | outputStream.write(
313 | (utfLength & MASK_LOW_WORD_HIGH_BYTE) >> OCTET_ONE);
314 | outputStream.write(utfLength & MASK_LOW_WORD_LOW_BYTE);
315 | } else {
316 | outputStream.write(LENGTH_64); // large payload flag
317 | outputStream.write(0x00); // upper bytes
318 | outputStream.write(0x00); // upper bytes
319 | outputStream.write(0x00); // upper bytes
320 | outputStream.write(0x00); // upper bytes
321 | outputStream.write(
322 | (utfLength & MASK_HIGH_WORD_HIGH_BYTE_NO_SIGN) >> OCTET_THREE);
323 | outputStream.write(
324 | (utfLength & MASK_HIGH_WORD_LOW_BYTE) >> OCTET_TWO);
325 | outputStream.write(
326 | (utfLength & MASK_LOW_WORD_HIGH_BYTE) >> OCTET_ONE);
327 | outputStream.write(utfLength & MASK_LOW_WORD_LOW_BYTE);
328 | }
329 | outputStream.write(utfBytes); // text payload
330 | }
331 |
332 | /**
333 | * Flag: Has the WebSocket connection sent a CLOSE frame?
334 | */
335 | private boolean closeSent = false;
336 |
337 | /**
338 | * Flag: Is the WebSocket handshake complete?
339 | */
340 | private boolean handshakeComplete = false;
341 |
342 | /**
343 | * OutputStream to be decorated as a WebSocket-speaking OutputStream.
344 | */
345 | private final OutputStream outputStream;
346 | }
347 |
--------------------------------------------------------------------------------
/src/test/java/com/pmeade/websocket/io/WebSocketServerOutputStreamTest.java:
--------------------------------------------------------------------------------
1 | /*
2 | * WebSocketServerOutputStreamTest.java
3 | * Copyright 2014 Patrick Meade.
4 | *
5 | * This program is free software: you can redistribute it and/or modify
6 | * it under the terms of the GNU Affero General Public License as published by
7 | * the Free Software Foundation, either version 3 of the License, or
8 | * (at your option) any later version.
9 | *
10 | * This program is distributed in the hope that it will be useful,
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | * GNU Affero General Public License for more details.
14 | *
15 | * You should have received a copy of the GNU Affero General Public License
16 | * along with this program. If not, see .
17 | */
18 |
19 | package com.pmeade.websocket.io;
20 |
21 | import java.io.ByteArrayOutputStream;
22 | import java.io.OutputStream;
23 | import org.junit.After;
24 | import org.junit.AfterClass;
25 | import org.junit.Before;
26 | import org.junit.BeforeClass;
27 | import org.junit.Test;
28 | import static org.junit.Assert.*;
29 |
30 | /**
31 | * @author pmeade
32 | */
33 | public class WebSocketServerOutputStreamTest
34 | {
35 | public WebSocketServerOutputStreamTest() {
36 | }
37 |
38 | @BeforeClass
39 | public static void setUpClass() {
40 | }
41 |
42 | @AfterClass
43 | public static void tearDownClass() {
44 | }
45 |
46 | @Before
47 | public void setUp() {
48 | }
49 |
50 | @After
51 | public void tearDown() {
52 | }
53 |
54 | @Test
55 | public void testAlwaysSucceed() {
56 | assertTrue(true);
57 | }
58 |
59 | @Test
60 | public void testExtendsOutputStream() {
61 | ByteArrayOutputStream baos = new ByteArrayOutputStream();
62 | WebSocketServerOutputStream wsos = new WebSocketServerOutputStream(baos);
63 | assertTrue(wsos instanceof OutputStream);
64 | }
65 |
66 | @Test
67 | public void testRequiresDecorableOutputStream() {
68 | try {
69 | WebSocketServerOutputStream webSocketOutputStream = new WebSocketServerOutputStream(null);
70 | fail();
71 | } catch(NullPointerException e) {
72 | // expected
73 | }
74 | }
75 |
76 | @Test
77 | public void testSetHandshakeComplete() {
78 | ByteArrayOutputStream baos = new ByteArrayOutputStream();
79 | WebSocketServerOutputStream wssos = new WebSocketServerOutputStream(baos);
80 | assertFalse(wssos.isHandshakeComplete());
81 | wssos.setHandshakeComplete(true);
82 | assertTrue(wssos.isHandshakeComplete());
83 | }
84 |
85 | @Test
86 | public void testWriteArrayTransparentBeforeHandshake() throws Exception {
87 | ByteArrayOutputStream baos = new ByteArrayOutputStream();
88 | WebSocketServerOutputStream wssos = new WebSocketServerOutputStream(baos);
89 | assertFalse(wssos.isHandshakeComplete());
90 | wssos.write(new byte[] { 0x01, 0x02, 0x03, 0x04, 0x05 });
91 | byte[] result = baos.toByteArray();
92 | assertEquals(0x01, result[0]);
93 | assertEquals(0x02, result[1]);
94 | assertEquals(0x03, result[2]);
95 | assertEquals(0x04, result[3]);
96 | assertEquals(0x05, result[4]);
97 | }
98 |
99 | @Test
100 | public void testWriteArrayNotTransparentAfterHandshake() throws Exception {
101 | ByteArrayOutputStream baos = new ByteArrayOutputStream();
102 | WebSocketServerOutputStream wssos = new WebSocketServerOutputStream(baos);
103 | assertFalse(wssos.isHandshakeComplete());
104 | wssos.setHandshakeComplete(true);
105 | assertTrue(wssos.isHandshakeComplete());
106 | wssos.write(new byte[] { 0x01, 0x02, 0x03, 0x04, 0x05 });
107 | byte[] result = baos.toByteArray();
108 | assertEquals((byte)0x82, result[0]);
109 | assertEquals(0x05, result[1]);
110 | assertEquals(0x01, result[2]);
111 | assertEquals(0x02, result[3]);
112 | assertEquals(0x03, result[4]);
113 | assertEquals(0x04, result[5]);
114 | assertEquals(0x05, result[6]);
115 | }
116 |
117 | @Test
118 | public void testWriteByteTransparentBeforeHandshake() throws Exception {
119 | ByteArrayOutputStream baos = new ByteArrayOutputStream();
120 | WebSocketServerOutputStream wssos = new WebSocketServerOutputStream(baos);
121 | assertFalse(wssos.isHandshakeComplete());
122 | wssos.write(0x05);
123 | byte[] result = baos.toByteArray();
124 | assertEquals(0x05, result[0]);
125 | }
126 |
127 | @Test
128 | public void testWriteByteNotTransparentAfterHandshake() throws Exception {
129 | ByteArrayOutputStream baos = new ByteArrayOutputStream();
130 | WebSocketServerOutputStream wssos = new WebSocketServerOutputStream(baos);
131 | assertFalse(wssos.isHandshakeComplete());
132 | wssos.setHandshakeComplete(true);
133 | assertTrue(wssos.isHandshakeComplete());
134 | wssos.write(0x05);
135 | byte[] result = baos.toByteArray();
136 | assertEquals((byte)0x82, result[0]);
137 | assertEquals(0x01, result[1]);
138 | assertEquals(0x05, result[2]);
139 | }
140 |
141 | @Test
142 | public void testWriteArrayOffsetTransparentBeforeHandshake() throws Exception {
143 | ByteArrayOutputStream baos = new ByteArrayOutputStream();
144 | WebSocketServerOutputStream wssos = new WebSocketServerOutputStream(baos);
145 | assertFalse(wssos.isHandshakeComplete());
146 | final byte[] DATA = new byte[] {
147 | 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
148 | 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f
149 | };
150 | wssos.write(DATA, 2, 4);
151 | byte[] result = baos.toByteArray();
152 | assertEquals(0x02, result[0]);
153 | assertEquals(0x03, result[1]);
154 | assertEquals(0x04, result[2]);
155 | assertEquals(0x05, result[3]);
156 | }
157 |
158 | @Test
159 | public void testWriteArrayOffsetNotTransparentAfterHandshake() throws Exception {
160 | ByteArrayOutputStream baos = new ByteArrayOutputStream();
161 | WebSocketServerOutputStream wssos = new WebSocketServerOutputStream(baos);
162 | assertFalse(wssos.isHandshakeComplete());
163 | wssos.setHandshakeComplete(true);
164 | assertTrue(wssos.isHandshakeComplete());
165 | final byte[] DATA = new byte[] {
166 | 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
167 | 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f
168 | };
169 | wssos.write(DATA, 2, 4);
170 | byte[] result = baos.toByteArray();
171 | assertEquals((byte)0x82, result[0]);
172 | assertEquals(0x04, result[1]);
173 | assertEquals(0x02, result[2]);
174 | assertEquals(0x03, result[3]);
175 | assertEquals(0x04, result[4]);
176 | assertEquals(0x05, result[5]);
177 | }
178 |
179 | @Test
180 | public void testWriteCloseTwiceOutputsOneFrame() throws Exception {
181 | ByteArrayOutputStream baos = new ByteArrayOutputStream();
182 | WebSocketServerOutputStream wssos = new WebSocketServerOutputStream(baos);
183 | assertFalse(wssos.isHandshakeComplete());
184 | wssos.setHandshakeComplete(true);
185 | assertTrue(wssos.isHandshakeComplete());
186 | wssos.writeClose();
187 | wssos.writeClose();
188 | byte[] result = baos.toByteArray();
189 | assertEquals(2, result.length);
190 | assertEquals((byte)0x88, result[0]);
191 | assertEquals(0x00, result[1]);
192 | }
193 |
194 | @Test
195 | public void testWriteCloseStatusCodeTwiceOutputsOneFrame() throws Exception {
196 | ByteArrayOutputStream baos = new ByteArrayOutputStream();
197 | WebSocketServerOutputStream wssos = new WebSocketServerOutputStream(baos);
198 | assertFalse(wssos.isHandshakeComplete());
199 | wssos.setHandshakeComplete(true);
200 | assertTrue(wssos.isHandshakeComplete());
201 | wssos.writeClose(0x1000);
202 | wssos.writeClose(0x1000);
203 | byte[] result = baos.toByteArray();
204 | assertEquals(4, result.length);
205 | assertEquals((byte)0x88, result[0]);
206 | assertEquals(0x02, result[1]);
207 | assertEquals(0x10, result[2]);
208 | assertEquals(0x00, result[3]);
209 | }
210 |
211 | @Test
212 | public void testWriteStringSmallPayload() throws Exception {
213 | ByteArrayOutputStream baos = new ByteArrayOutputStream();
214 | WebSocketServerOutputStream wssos = new WebSocketServerOutputStream(baos);
215 | assertFalse(wssos.isHandshakeComplete());
216 | wssos.setHandshakeComplete(true);
217 | assertTrue(wssos.isHandshakeComplete());
218 | wssos.writeString("\u00a9");
219 | byte[] result = baos.toByteArray();
220 | assertEquals(4, result.length);
221 | assertEquals((byte)0x81, result[0]);
222 | assertEquals(0x02, result[1]);
223 | assertEquals((byte)0xc2, result[2]);
224 | assertEquals((byte)0xa9, result[3]);
225 | }
226 |
227 | @Test
228 | public void testWriteStringMediumPayload() throws Exception {
229 | ByteArrayOutputStream baos = new ByteArrayOutputStream();
230 | WebSocketServerOutputStream wssos = new WebSocketServerOutputStream(baos);
231 | assertFalse(wssos.isHandshakeComplete());
232 | wssos.setHandshakeComplete(true);
233 | assertTrue(wssos.isHandshakeComplete());
234 | StringBuilder sb = new StringBuilder();
235 | for(int i=0; i<0x100; i++) {
236 | sb.append("\u00a9");
237 | }
238 | wssos.writeString(sb.toString());
239 | byte[] result = baos.toByteArray();
240 | assertEquals(516, result.length);
241 | assertEquals((byte)0x81, result[0]);
242 | assertEquals((byte)WebSocketServerOutputStream.LENGTH_16, result[1]);
243 | assertEquals((byte)0x02, result[2]);
244 | assertEquals((byte)0x00, result[3]);
245 | assertEquals((byte)0xc2, result[4]);
246 | assertEquals((byte)0xa9, result[5]);
247 | assertEquals((byte)0xc2, result[6]);
248 | assertEquals((byte)0xa9, result[7]);
249 | }
250 |
251 | @Test
252 | public void testWriteStringLargePayload() throws Exception {
253 | ByteArrayOutputStream baos = new ByteArrayOutputStream();
254 | WebSocketServerOutputStream wssos = new WebSocketServerOutputStream(baos);
255 | assertFalse(wssos.isHandshakeComplete());
256 | wssos.setHandshakeComplete(true);
257 | assertTrue(wssos.isHandshakeComplete());
258 | StringBuilder sb = new StringBuilder();
259 | for(int i=0; i<0x10000; i++) {
260 | sb.append("\u00a9");
261 | }
262 | wssos.writeString(sb.toString());
263 | byte[] result = baos.toByteArray();
264 | assertEquals(0x20000 + 10, result.length);
265 | assertEquals((byte)0x81, result[0]);
266 | assertEquals((byte)WebSocketServerOutputStream.LENGTH_64, result[1]);
267 | assertEquals((byte)0x00, result[2]);
268 | assertEquals((byte)0x00, result[3]);
269 | assertEquals((byte)0x00, result[4]);
270 | assertEquals((byte)0x00, result[5]);
271 | assertEquals((byte)0x00, result[6]);
272 | assertEquals((byte)0x02, result[7]);
273 | assertEquals((byte)0x00, result[8]);
274 | assertEquals((byte)0x00, result[9]);
275 | assertEquals((byte)0xc2, result[10]);
276 | assertEquals((byte)0xa9, result[11]);
277 | assertEquals((byte)0xc2, result[12]);
278 | assertEquals((byte)0xa9, result[13]);
279 | assertEquals((byte)0xc2, result[14]);
280 | assertEquals((byte)0xa9, result[15]);
281 | }
282 |
283 | @Test
284 | public void testWriteBinaryMediumPayload() throws Exception {
285 | ByteArrayOutputStream baos = new ByteArrayOutputStream();
286 | WebSocketServerOutputStream wssos = new WebSocketServerOutputStream(baos);
287 | assertFalse(wssos.isHandshakeComplete());
288 | wssos.setHandshakeComplete(true);
289 | assertTrue(wssos.isHandshakeComplete());
290 | byte[] DATA = new byte[0x200];
291 | for(int i=0; i<0x200; i++) {
292 | DATA[i] = (byte)(i & 0xff);
293 | }
294 | wssos.write(DATA);
295 | byte[] result = baos.toByteArray();
296 | assertEquals(0x200 + 4, result.length);
297 | assertEquals((byte)0x82, result[0]);
298 | assertEquals((byte)WebSocketServerOutputStream.LENGTH_16, result[1]);
299 | assertEquals((byte)0x02, result[2]);
300 | assertEquals((byte)0x00, result[3]);
301 | assertEquals((byte)0x00, result[4]);
302 | assertEquals((byte)0x01, result[5]);
303 | assertEquals((byte)0x02, result[6]);
304 | assertEquals((byte)0x03, result[7]);
305 | }
306 |
307 | @Test
308 | public void testWriteBinaryLargePayload() throws Exception {
309 | ByteArrayOutputStream baos = new ByteArrayOutputStream();
310 | WebSocketServerOutputStream wssos = new WebSocketServerOutputStream(baos);
311 | assertFalse(wssos.isHandshakeComplete());
312 | wssos.setHandshakeComplete(true);
313 | assertTrue(wssos.isHandshakeComplete());
314 | byte[] DATA = new byte[0x20000];
315 | for(int i=0; i<0x20000; i++) {
316 | DATA[i] = (byte)(i & 0xff);
317 | }
318 | wssos.write(DATA);
319 | byte[] result = baos.toByteArray();
320 | assertEquals(0x20000 + 10, result.length);
321 | assertEquals((byte)0x82, result[0]);
322 | assertEquals((byte)WebSocketServerOutputStream.LENGTH_64, result[1]);
323 | assertEquals((byte)0x00, result[2]);
324 | assertEquals((byte)0x00, result[3]);
325 | assertEquals((byte)0x00, result[4]);
326 | assertEquals((byte)0x00, result[5]);
327 | assertEquals((byte)0x00, result[6]);
328 | assertEquals((byte)0x02, result[7]);
329 | assertEquals((byte)0x00, result[8]);
330 | assertEquals((byte)0x00, result[9]);
331 | assertEquals((byte)0x00, result[10]);
332 | assertEquals((byte)0x01, result[11]);
333 | assertEquals((byte)0x02, result[12]);
334 | assertEquals((byte)0x03, result[13]);
335 | assertEquals((byte)0x04, result[14]);
336 | assertEquals((byte)0x05, result[15]);
337 | }
338 | }
339 |
--------------------------------------------------------------------------------
/src/test/java/com/pmeade/websocket/net/WebSocketTest.java:
--------------------------------------------------------------------------------
1 | /*
2 | * WebSocketTest.java
3 | * Copyright 2014 Patrick Meade.
4 | *
5 | * This program is free software: you can redistribute it and/or modify
6 | * it under the terms of the GNU Affero General Public License as published by
7 | * the Free Software Foundation, either version 3 of the License, or
8 | * (at your option) any later version.
9 | *
10 | * This program is distributed in the hope that it will be useful,
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | * GNU Affero General Public License for more details.
14 | *
15 | * You should have received a copy of the GNU Affero General Public License
16 | * along with this program. If not, see .
17 | */
18 |
19 | package com.pmeade.websocket.net;
20 |
21 | import com.pmeade.websocket.io.WebSocketServerInputStream;
22 | import com.pmeade.websocket.io.WebSocketServerOutputStream;
23 | import java.io.InputStream;
24 | import java.io.OutputStream;
25 | import java.net.InetAddress;
26 | import java.net.ServerSocket;
27 | import java.net.Socket;
28 | import java.net.SocketAddress;
29 | import java.nio.channels.SocketChannel;
30 | import org.junit.After;
31 | import org.junit.AfterClass;
32 | import org.junit.Before;
33 | import org.junit.BeforeClass;
34 | import org.junit.Test;
35 | import static org.junit.Assert.*;
36 | import static org.mockito.Mockito.*;
37 |
38 | /**
39 | * @author veloxi
40 | */
41 | public class WebSocketTest
42 | {
43 | private Socket mockS;
44 | private ServerSocket mockSS;
45 | private Socket ws;
46 | private WebSocketServerSocket wsss;
47 |
48 | public WebSocketTest() {
49 | }
50 |
51 | @BeforeClass
52 | public static void setUpClass() {
53 | }
54 |
55 | @AfterClass
56 | public static void tearDownClass() {
57 | }
58 |
59 | @Before
60 | public void setUp() throws Exception {
61 | mockS = mock(Socket.class);
62 | mockSS = mock(ServerSocket.class);
63 | when(mockSS.accept()).thenReturn(mockS);
64 | wsss = new WebSocketServerSocket(mockSS);
65 | ws = wsss.accept();
66 | }
67 |
68 | @After
69 | public void tearDown() throws Exception {
70 | verify(mockSS, times(1)).accept();
71 | }
72 |
73 | @Test
74 | public void testAlwaysSucceed() {
75 | assertTrue(true);
76 | verifyZeroInteractions(mockS);
77 | }
78 |
79 | @Test
80 | public void testExtendsSocket() {
81 | Socket s = new Socket();
82 | WebSocket webSocket = new WebSocket(s);
83 | assertTrue(webSocket instanceof Socket);
84 | verifyZeroInteractions(mockS);
85 | }
86 |
87 | @Test
88 | public void testAcceptReturnsWebSocket() throws Exception {
89 | assertTrue(ws instanceof WebSocket);
90 | verifyZeroInteractions(mockS);
91 | }
92 |
93 | @Test
94 | public void testConnectDelegatesToSocket() throws Exception {
95 | SocketAddress sa = mock(SocketAddress.class);
96 | ws.connect(sa);
97 | verify(mockS).connect(sa);
98 | verifyZeroInteractions(sa);
99 | }
100 |
101 | @Test
102 | public void testConnectWithTimeoutDelegatesToSocket() throws Exception {
103 | SocketAddress sa = mock(SocketAddress.class);
104 | ws.connect(sa, 9001);
105 | verify(mockS).connect(sa, 9001);
106 | verifyZeroInteractions(sa);
107 | }
108 |
109 | @Test
110 | public void testBindDelegatesToSocket() throws Exception {
111 | SocketAddress sa = mock(SocketAddress.class);
112 | ws.bind(sa);
113 | verify(mockS).bind(sa);
114 | verifyZeroInteractions(sa);
115 | }
116 |
117 | @Test
118 | public void testGetInetAddressDelegatesToSocket() throws Exception {
119 | InetAddress ia = mock(InetAddress.class);
120 | when(mockS.getInetAddress()).thenReturn(ia);
121 | InetAddress result = ws.getInetAddress();
122 | assertEquals(ia, result);
123 | verify(mockS).getInetAddress();
124 | verifyZeroInteractions(ia);
125 | }
126 |
127 | @Test
128 | public void testGetLocalAddressDelegatesToSocket() throws Exception {
129 | InetAddress ia = mock(InetAddress.class);
130 | when(mockS.getLocalAddress()).thenReturn(ia);
131 | InetAddress result = ws.getLocalAddress();
132 | assertEquals(ia, result);
133 | verify(mockS).getLocalAddress();
134 | verifyZeroInteractions(ia);
135 | }
136 |
137 | @Test
138 | public void testGetPortDelegatesToSocket() throws Exception {
139 | when(mockS.getPort()).thenReturn(42);
140 | int result = ws.getPort();
141 | assertEquals(42, result);
142 | verify(mockS).getPort();
143 | }
144 |
145 | @Test
146 | public void testGetLocalPortDelegatesToSocket() throws Exception {
147 | when(mockS.getLocalPort()).thenReturn(69);
148 | int result = ws.getLocalPort();
149 | assertEquals(69, result);
150 | verify(mockS).getLocalPort();
151 | }
152 |
153 | @Test
154 | public void testGetRemoteSocketAddressDelegatesToSocket() throws Exception {
155 | SocketAddress sa = mock(SocketAddress.class);
156 | when(mockS.getRemoteSocketAddress()).thenReturn(sa);
157 | SocketAddress result = ws.getRemoteSocketAddress();
158 | assertEquals(sa, result);
159 | verify(mockS).getRemoteSocketAddress();
160 | verifyZeroInteractions(sa);
161 | }
162 |
163 | @Test
164 | public void testGetLocalSocketAddressDelegatesToSocket() throws Exception {
165 | SocketAddress sa = mock(SocketAddress.class);
166 | when(mockS.getLocalSocketAddress()).thenReturn(sa);
167 | SocketAddress result = ws.getLocalSocketAddress();
168 | assertEquals(sa, result);
169 | verify(mockS).getLocalSocketAddress();
170 | verifyZeroInteractions(sa);
171 | }
172 |
173 | @Test
174 | public void testGetChannelThrowsUnsupportedOperationException() throws Exception {
175 | try {
176 | SocketChannel sc = ws.getChannel();
177 | fail();
178 | } catch (UnsupportedOperationException e) {
179 | // expected
180 | }
181 | verifyZeroInteractions(mockS);
182 | }
183 |
184 | @Test
185 | public void testGetInputStreamReturnsWebSocketServerInputStream() throws Exception {
186 | InputStream is = mock(InputStream.class);
187 | OutputStream os = mock(OutputStream.class);
188 | when(mockS.getInputStream()).thenReturn(is);
189 | when(mockS.getOutputStream()).thenReturn(os);
190 | InputStream result = ws.getInputStream();
191 | assertNotEquals(is, result);
192 | assertTrue(result instanceof WebSocketServerInputStream);
193 | verify(mockS).getInputStream();
194 | verify(mockS).getOutputStream();
195 | verifyZeroInteractions(is);
196 | verifyZeroInteractions(os);
197 | }
198 |
199 | @Test
200 | public void testWebSocketServerInputStreamHasOutputPeer() throws Exception {
201 | InputStream is = mock(InputStream.class);
202 | OutputStream os = mock(OutputStream.class);
203 | when(mockS.getInputStream()).thenReturn(is);
204 | when(mockS.getOutputStream()).thenReturn(os);
205 | InputStream result = ws.getInputStream();
206 | assertNotEquals(is, result);
207 | assertTrue(result instanceof WebSocketServerInputStream);
208 | WebSocketServerInputStream wssis = (WebSocketServerInputStream) result;
209 | assertNotNull(wssis.getOutputPeer());
210 | verify(mockS).getInputStream();
211 | verify(mockS).getOutputStream();
212 | verifyZeroInteractions(is);
213 | verifyZeroInteractions(os);
214 | }
215 |
216 | @Test
217 | public void testGetInputStreamTwiceReturnsSameObject() throws Exception {
218 | InputStream is = mock(InputStream.class);
219 | OutputStream os = mock(OutputStream.class);
220 | when(mockS.getInputStream()).thenReturn(is);
221 | when(mockS.getOutputStream()).thenReturn(os);
222 | InputStream result = ws.getInputStream();
223 | assertNotEquals(is, result);
224 | assertTrue(result instanceof WebSocketServerInputStream);
225 | WebSocketServerInputStream wssis = (WebSocketServerInputStream) result;
226 | assertNotNull(wssis.getOutputPeer());
227 | InputStream result2 = ws.getInputStream();
228 | assertNotNull(result2);
229 | assertEquals(result, result2);
230 | assertEquals(wssis, result2);
231 | verify(mockS, times(1)).getInputStream();
232 | verify(mockS, times(1)).getOutputStream();
233 | verifyZeroInteractions(is);
234 | verifyZeroInteractions(os);
235 | }
236 |
237 | @Test
238 | public void testGetOutputStreamReturnsWebSocketServerOutputStream() throws Exception {
239 | OutputStream os = mock(OutputStream.class);
240 | when(mockS.getOutputStream()).thenReturn(os);
241 | OutputStream result = ws.getOutputStream();
242 | assertNotEquals(os, result);
243 | assertTrue(result instanceof WebSocketServerOutputStream);
244 | verify(mockS).getOutputStream();
245 | verifyZeroInteractions(os);
246 | }
247 |
248 | @Test
249 | public void testGetOutputStreamTwiceReturnsSameObject() throws Exception {
250 | OutputStream os = mock(OutputStream.class);
251 | when(mockS.getOutputStream()).thenReturn(os);
252 | OutputStream result = ws.getOutputStream();
253 | assertNotEquals(os, result);
254 | assertTrue(result instanceof WebSocketServerOutputStream);
255 | WebSocketServerOutputStream wssos = (WebSocketServerOutputStream) result;
256 | OutputStream result2 = ws.getOutputStream();
257 | assertNotNull(result2);
258 | assertEquals(result, result2);
259 | assertEquals(wssos, result2);
260 | verify(mockS, times(1)).getOutputStream();
261 | verifyZeroInteractions(os);
262 | }
263 |
264 | @Test
265 | public void testSetTcpNoDelayDelegatesToSocket() throws Exception {
266 | ws.setTcpNoDelay(true);
267 | ws.setTcpNoDelay(false);
268 | verify(mockS).setTcpNoDelay(true);
269 | verify(mockS).setTcpNoDelay(false);
270 | }
271 |
272 | @Test
273 | public void testGetTcpNoDelayDelegatesToSocket() throws Exception {
274 | when(mockS.getTcpNoDelay()).thenReturn(false);
275 | assertFalse(ws.getTcpNoDelay());
276 | verify(mockS).getTcpNoDelay();
277 | }
278 |
279 | @Test
280 | public void testGetTcpNoDelayDelegatesToSocket2() throws Exception {
281 | when(mockS.getTcpNoDelay()).thenReturn(true);
282 | assertTrue(ws.getTcpNoDelay());
283 | verify(mockS).getTcpNoDelay();
284 | }
285 |
286 | @Test
287 | public void testSetSoLignerDelegatesToSocket() throws Exception {
288 | ws.setSoLinger(true, 1337);
289 | verify(mockS).setSoLinger(true, 1337);
290 | }
291 |
292 | @Test
293 | public void testGetSoLingerDelegatesToSocket() throws Exception {
294 | when(mockS.getSoLinger()).thenReturn(1337);
295 | assertEquals(1337, ws.getSoLinger());
296 | verify(mockS).getSoLinger();
297 | }
298 |
299 | @Test
300 | public void testSendUrgentDataDelegatesToSocket() throws Exception {
301 | ws.sendUrgentData(49152);
302 | verify(mockS).sendUrgentData(49152);
303 | }
304 |
305 | @Test
306 | public void testSetOobInlineDelegatesToSocket() throws Exception {
307 | ws.setOOBInline(true);
308 | verify(mockS).setOOBInline(true);
309 | }
310 |
311 | @Test
312 | public void testGetOobInlineDelegatesToSocket() throws Exception {
313 | when(mockS.getOOBInline()).thenReturn(true);
314 | assertTrue(ws.getOOBInline());
315 | verify(mockS).getOOBInline();
316 | }
317 |
318 | @Test
319 | public void testSetSoTimeoutDelegatesToSocket() throws Exception {
320 | ws.setSoTimeout(123456);
321 | verify(mockS).setSoTimeout(123456);
322 | }
323 |
324 | @Test
325 | public void testGetSoTimeoutDelegatesToSocket() throws Exception {
326 | when(mockS.getSoTimeout()).thenReturn(123456);
327 | assertEquals(123456, ws.getSoTimeout());
328 | verify(mockS).getSoTimeout();
329 | }
330 |
331 | @Test
332 | public void testSetSendBufferSizeDelegatesToSocket() throws Exception {
333 | ws.setSendBufferSize(654321);
334 | verify(mockS).setSendBufferSize(654321);
335 | }
336 |
337 | @Test
338 | public void testGetSendBufferSizeDelegatesToSocket() throws Exception {
339 | when(mockS.getSendBufferSize()).thenReturn(654321);
340 | assertEquals(654321, ws.getSendBufferSize());
341 | verify(mockS).getSendBufferSize();
342 | }
343 |
344 | @Test
345 | public void testSetReceiveBufferSizeDelegatesToSocket() throws Exception {
346 | ws.setReceiveBufferSize(654321);
347 | verify(mockS).setReceiveBufferSize(654321);
348 | }
349 |
350 | @Test
351 | public void testGetReceiveBufferSizeDelegatesToSocket() throws Exception {
352 | when(mockS.getReceiveBufferSize()).thenReturn(654321);
353 | assertEquals(654321, ws.getReceiveBufferSize());
354 | verify(mockS).getReceiveBufferSize();
355 | }
356 |
357 | @Test
358 | public void testSetKeepAliveDelegatesToSocket() throws Exception {
359 | ws.setKeepAlive(true);
360 | ws.setKeepAlive(false);
361 | verify(mockS).setKeepAlive(true);
362 | verify(mockS).setKeepAlive(false);
363 | }
364 |
365 | @Test
366 | public void testGetKeepAliveDelegatesToSocket() throws Exception {
367 | when(mockS.getKeepAlive()).thenReturn(false);
368 | assertFalse(ws.getKeepAlive());
369 | verify(mockS).getKeepAlive();
370 | }
371 |
372 | @Test
373 | public void testSetTrafficClassDelegatesToSocket() throws Exception {
374 | ws.setTrafficClass(5);
375 | verify(mockS).setTrafficClass(5);
376 | }
377 |
378 | @Test
379 | public void testGetTrafficClassDelegatesToSocket() throws Exception {
380 | when(mockS.getTrafficClass()).thenReturn(6);
381 | assertEquals(6, ws.getTrafficClass());
382 | verify(mockS).getTrafficClass();
383 | }
384 |
385 | @Test
386 | public void testSetReuseAddressDelegatesToSocket() throws Exception {
387 | ws.setReuseAddress(true);
388 | ws.setReuseAddress(false);
389 | verify(mockS).setReuseAddress(true);
390 | verify(mockS).setReuseAddress(false);
391 | }
392 |
393 | @Test
394 | public void testGetReuseAddressDelegatesToSocket() throws Exception {
395 | when(mockS.getReuseAddress()).thenReturn(false);
396 | assertFalse(ws.getReuseAddress());
397 | verify(mockS).getReuseAddress();
398 | }
399 |
400 | @Test
401 | public void testCloseDelegatesToSocket() throws Exception {
402 | ws.close();
403 | verify(mockS).close();
404 | }
405 |
406 | @Test
407 | public void testShutdownInputDelegatesToSocket() throws Exception {
408 | ws.shutdownInput();
409 | verify(mockS).shutdownInput();
410 | }
411 |
412 | @Test
413 | public void testShutdownOutputDelegatesToSocket() throws Exception {
414 | ws.shutdownOutput();
415 | verify(mockS).shutdownOutput();
416 | }
417 |
418 | @Test
419 | public void testToStringDelegatesToSocket() throws Exception {
420 | when(mockS.toString()).thenReturn("Exterminate!");
421 | assertEquals("Exterminate!", ws.toString());
422 | // toString() ... Mockito's Kryptonite
423 | // verify(mockS).toString();
424 | }
425 |
426 | @Test
427 | public void testIsConnectedDelegatesToSocket() throws Exception {
428 | when(mockS.isConnected()).thenReturn(true);
429 | assertTrue(ws.isConnected());
430 | verify(mockS).isConnected();
431 | }
432 |
433 | @Test
434 | public void testIsBoundDelegatesToSocket() throws Exception {
435 | when(mockS.isBound()).thenReturn(true);
436 | assertTrue(ws.isBound());
437 | verify(mockS).isBound();
438 | }
439 |
440 | @Test
441 | public void testIsClosedDelegatesToSocket() throws Exception {
442 | when(mockS.isClosed()).thenReturn(true);
443 | assertTrue(ws.isClosed());
444 | verify(mockS).isClosed();
445 | }
446 |
447 | @Test
448 | public void testIsInputShutdownDelegatesToSocket() throws Exception {
449 | when(mockS.isInputShutdown()).thenReturn(true);
450 | assertTrue(ws.isInputShutdown());
451 | verify(mockS).isInputShutdown();
452 | }
453 |
454 | @Test
455 | public void testIsOutputShutdownDelegatesToSocket() throws Exception {
456 | when(mockS.isOutputShutdown()).thenReturn(true);
457 | assertTrue(ws.isOutputShutdown());
458 | verify(mockS).isOutputShutdown();
459 | }
460 |
461 | @Test
462 | public void testSetPerformancePreferencesDelegatesToSocket() throws Exception {
463 | ws.setPerformancePreferences(123, 456, 789);
464 | verify(mockS).setPerformancePreferences(123, 456, 789);
465 | }
466 | }
467 |
--------------------------------------------------------------------------------
/src/main/java/com/pmeade/websocket/io/WebSocketServerInputStream.java:
--------------------------------------------------------------------------------
1 | /*
2 | * WebSocketServerInputStream.java
3 | * Copyright 2014 Patrick Meade.
4 | *
5 | * This program is free software: you can redistribute it and/or modify
6 | * it under the terms of the GNU Affero General Public License as published by
7 | * the Free Software Foundation, either version 3 of the License, or
8 | * (at your option) any later version.
9 | *
10 | * This program is distributed in the hope that it will be useful,
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | * GNU Affero General Public License for more details.
14 | *
15 | * You should have received a copy of the GNU Affero General Public License
16 | * along with this program. If not, see .
17 | */
18 |
19 | package com.pmeade.websocket.io;
20 |
21 | import com.google.common.hash.HashCode;
22 | import com.google.common.hash.HashFunction;
23 | import com.google.common.hash.Hashing;
24 | import com.google.common.io.BaseEncoding;
25 | import com.pmeade.websocket.http.HttpRequest;
26 | import java.io.IOException;
27 | import java.io.InputStream;
28 | import java.nio.charset.StandardCharsets;
29 |
30 | import static com.google.common.base.Preconditions.checkNotNull;
31 |
32 | /**
33 | * WebSocketServerInputStream decorates an InputStream to handle WebSocket
34 | * frames as specified in RFC 6455.
35 | * @author pmeade
36 | */
37 | public class WebSocketServerInputStream extends InputStream {
38 | /**
39 | * Constant indicating end of stream.
40 | */
41 | public static final int EOF = -1;
42 |
43 | /**
44 | * Number of bytes in the WebSocket handshake nonce.
45 | */
46 | public static final int HANDSHAKE_NONCE_LENGTH = 16;
47 |
48 | /**
49 | * Payload length indicating that the payload's true length is a
50 | * yet-to-be-provided unsigned 16-bit integer.
51 | */
52 | public static final int LENGTH_16 = 0x7E;
53 |
54 | /**
55 | * A payload specified with 16 bits must have at least this
56 | * length in order to be considered valid.
57 | */
58 | public static final int LENGTH_16_MIN = 126;
59 |
60 | /**
61 | * Payload length indicating that the payload's true length is a
62 | * yet-to-be-provided unsigned 64-bit integer (MSB = 0).
63 | */
64 | public static final int LENGTH_64 = 0x7F;
65 |
66 | /**
67 | * A payload specified with 64 bits must have at least this
68 | * length in order to be considered valid.
69 | */
70 | public static final int LENGTH_64_MIN = 0x10000;
71 |
72 | /**
73 | * Binary mask to limit an int to 8 bits (an unsigned byte).
74 | */
75 | public static final int MASK_BYTE = 0x000000FF;
76 |
77 | /**
78 | * Binary mask to extract the final fragment flag bit of a WebSocket frame.
79 | */
80 | public static final int MASK_FINAL = 0x80;
81 |
82 | /**
83 | * Binary mask to extract the masking flag bit of a WebSocket frame.
84 | */
85 | public static final int MASK_MASK = 0x80;
86 |
87 | /**
88 | * Binary mask to limit a value in the range [0-3] (inclusive).
89 | */
90 | public static final int MASK_MASKING_INDEX = 0x03;
91 |
92 | /**
93 | * Binary mask to extract the opcode bits of a WebSocket frame.
94 | */
95 | public static final int MASK_OPCODE = 0x0F;
96 |
97 | /**
98 | * Binary mask to extract the control bit of an opcode.
99 | */
100 | public static final int MASK_CONTROL_OPCODE = 0x08;
101 |
102 | /**
103 | * Binary mask to extract the payload size of a WebSocket frame.
104 | */
105 | public static final int MASK_PAYLOAD_SIZE = 0x7F;
106 |
107 | /**
108 | * Binary mask to extract the reserved flag bits of a WebSocket frame.
109 | */
110 | public static final int MASK_RESERVED = 0x70;
111 |
112 | /**
113 | * Number of masking bytes provided by the client.
114 | */
115 | public static final int NUM_MASKING_BYTES = 4;
116 |
117 | /**
118 | * Number of octets (bytes) in a 64-bit number.
119 | */
120 | public static final int NUM_OCTET_64 = 8;
121 |
122 | /**
123 | * Number of bits in an octet.
124 | */
125 | public static final int OCTET = 8;
126 |
127 | /**
128 | * WebSocket Opcode for a Continuation frame.
129 | */
130 | public static final int OPCODE_CONTINUATION = 0x00;
131 |
132 | /**
133 | * WebSocket Opcode for a Text frame.
134 | */
135 | public static final int OPCODE_TEXT = 0x01;
136 |
137 | /**
138 | * WebSocket Opcode for a Binary frame.
139 | */
140 | public static final int OPCODE_BINARY = 0x02;
141 |
142 | /**
143 | * Lowest WebSocket Opcode for reserved non-control frames. That is
144 | * these data frames are yet reserved (undefined).
145 | */
146 | public static final int OPCODE_RESERVED_NON_CONTROL_LOW = 0x03;
147 |
148 | /**
149 | * Highest WebSocket Opcode for reserved non-control frames. That is
150 | * these data frames are yet reserved (undefined).
151 | */
152 | public static final int OPCODE_RESERVED_NON_CONTROL_HIGH = 0x07;
153 |
154 | /**
155 | * WebSocket Opcode for a Close control frame.
156 | */
157 | public static final int OPCODE_CLOSE = 0x08;
158 |
159 | /**
160 | * WebSocket Opcode for a Ping control frame.
161 | */
162 | public static final int OPCODE_PING = 0x09;
163 |
164 | /**
165 | * WebSocket Opcode for a Pong control frame.
166 | */
167 | public static final int OPCODE_PONG = 0x0A;
168 |
169 | /**
170 | * Lowest WebSocket Opcode for reserved control frames. That is
171 | * these control frames are yet reserved (undefined).
172 | */
173 | public static final int OPCODE_RESERVED_CONTROL_LOW = 0x0B;
174 |
175 | /**
176 | * Highest WebSocket Opcode for reserved control frames. That is
177 | * these control frames are yet reserved (undefined).
178 | */
179 | public static final int OPCODE_RESERVED_CONTROL_HIGH = 0x0F;
180 |
181 | /**
182 | * Lowest WebSocket Opcode that defines a control frame.
183 | */
184 | public static final int OPCODE_CONTROL_LOW = 0x08;
185 |
186 | /**
187 | * Highest WebSocket Opcode that defines a control frame.
188 | */
189 | public static final int OPCODE_CONTROL_HIGH = 0x0F;
190 |
191 | /**
192 | * WebSocket Accept UUID. The UUID to be appended to the client-provided
193 | * security nonce, in order to complete the WebSocket handshake.
194 | */
195 | public static final String WEBSOCKET_ACCEPT_UUID =
196 | "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
197 |
198 | /**
199 | * Convert the provided byte to an unsigned int.
200 | * @param b byte to be converted
201 | * @return unsigned int (8-bit) representation of the provided byte
202 | */
203 | public static final int asUnsignedInt(final byte b) {
204 | int x = b;
205 | x &= MASK_BYTE;
206 | return x;
207 | }
208 |
209 | /**
210 | * Convert the provided String to UTF-8 encoded bytes.
211 | * @param s String to be converted to a UTF-8 representation
212 | * @return byte array containing the UTF-8 representation of the
213 | * provided String data
214 | */
215 | public static final byte[] asUTF8(final String s) {
216 | return s.getBytes(StandardCharsets.UTF_8);
217 | }
218 |
219 | /**
220 | * Check if the first String contains() the second String. This method
221 | * returns false if the first string is null.
222 | * @param s1 String to be checked if it contains
223 | * @param s2 String to check for
224 | * @return true, iff s1.contains(s2), otherwise false
225 | */
226 | public static boolean checkContains(final String s1, final String s2) {
227 | if (s1 == null) {
228 | return false;
229 | }
230 | return s1.contains(s2);
231 | }
232 |
233 | /**
234 | * Check if the first String startsWith() the second String. This method
235 | * returns false if the first String is null.
236 | * @param s1 String to be checked if it starts with
237 | * @param s2 String to check for
238 | * @return true, iff s1.startsWith(s2), otherwise false
239 | */
240 | public static boolean checkStartsWith(final String s1, final String s2) {
241 | if (s1 == null) {
242 | return false;
243 | }
244 | return s1.startsWith(s2);
245 | }
246 |
247 | /**
248 | * Create a WebSocket-speaking InputStream from the provided InputStream.
249 | * Note that an output peer is still required.
250 | * @param is InputStream to be decorated as a WebSocket-speaking InputStream
251 | */
252 | public WebSocketServerInputStream(final InputStream is) {
253 | checkNotNull(is, "is == null");
254 | this.inputStream = is;
255 | }
256 |
257 | /**
258 | * Create a WebSocket-speaking InputStream from the provided InputStream
259 | * and output peer WebSocketOutputStream.
260 | * @param is InputStream to be decorated as a WebSocket-speaking InputStream
261 | * @param wsos WebSocketOutputStream to be the output peer
262 | */
263 | public WebSocketServerInputStream(final InputStream is,
264 | final WebSocketServerOutputStream wsos) {
265 | checkNotNull(is, "is == null");
266 | checkNotNull(wsos, "wsos == null");
267 | this.inputStream = is;
268 | this.outputPeer = wsos;
269 | }
270 |
271 | /**
272 | * Obtain a reference to the peer object used for output on this stream.
273 | * @return WebSocketServerOutputStream used for output on this stream
274 | */
275 | public final WebSocketServerOutputStream getOutputPeer() {
276 | return outputPeer;
277 | }
278 |
279 | /**
280 | * Determine if this connection has sent a WebSocket Close frame.
281 | * @return true, if this connection has sent a WebSocket Close frame,
282 | * otherwise false.
283 | */
284 | private boolean isCloseSent() {
285 | return outputPeer.isCloseSent();
286 | }
287 |
288 | /**
289 | * Determine if the WebSocket connection is closed.
290 | * @return true, if the WebSocket connection is closed, otherwise false.
291 | */
292 | public final boolean isClosed() {
293 | return closeReceived && isCloseSent();
294 | }
295 |
296 | /**
297 | * Determine if the WebSocket connection has failed.
298 | * @return true, if the WebSocket connection has failed, otherwise false.
299 | */
300 | public final boolean isFailed() {
301 | return failed;
302 | }
303 |
304 | /**
305 | * Determine if the WebSocket handshake has completed successfully.
306 | * @return true, if the WebSocket handshake has completed successfully,
307 | * otherwise false.
308 | */
309 | public final boolean isHandshakeComplete() {
310 | return handshakeComplete;
311 | }
312 |
313 | /**
314 | * Reads the next byte of data from the input stream. The value byte is
315 | * returned as an int in the range 0 to 255. If no byte is available
316 | * because the end of the stream has been reached, the value -1 is
317 | * returned. This method blocks until input data is available, the end
318 | * of the stream is detected, or an exception is thrown.
319 | * @return the next byte of data, or -1 if the end of the stream is
320 | * reached.
321 | * @throws IOException if an I/O error occurs.
322 | */
323 | @Override
324 | public final int read() throws IOException {
325 | if (isClosed() || isFailed()) {
326 | return EOF;
327 | }
328 | if (!handshakeComplete) {
329 | shakeHands();
330 | if (!handshakeComplete) {
331 | failTheWebSocketConnection();
332 | return EOF;
333 | }
334 | }
335 | return nextWebSocketByte();
336 | }
337 |
338 | /**
339 | * Set the output peer for this InputStream. A WebSocketServerOutputStream
340 | * object is used to communicate back to the source of this InputStream.
341 | * This method allows a client to specify the object that does this. This
342 | * is especially useful if the client did not do so during construction.
343 | * @param op WebSocketServerOutputStream object used to communicate back
344 | * to the source of this InputStream
345 | */
346 | public final void setOutputPeer(final WebSocketServerOutputStream op) {
347 | this.outputPeer = op;
348 | }
349 |
350 | //-----------------------------------------------------------------------
351 |
352 | /**
353 | * Obtain the next byte of data from the WebSocket.
354 | * @return the next byte of data from the WebSocket
355 | * @throws IOException if anything goes wrong with the underlying stream
356 | */
357 | private int nextWebSocketByte() throws IOException {
358 | while (payloadLength == 0L) {
359 | nextWebSocketFrame();
360 | if (isClosed() || isFailed()) {
361 | return EOF;
362 | }
363 | }
364 | int data = inputStream.read() ^ maskingBytes[maskingIndex];
365 | payloadLength--;
366 | maskingIndex++;
367 | maskingIndex &= MASK_MASKING_INDEX;
368 | return data;
369 | }
370 |
371 | /**
372 | * Process the next WebSocket frame. This method reads the header
373 | * information about the frame. It sets up non-control frames to
374 | * provide their data, and handles WebSocket protocol-specifics for
375 | * the control frames.
376 | * @throws IOException if anything goes wrong with the underlying stream
377 | */
378 | private void nextWebSocketFrame() throws IOException {
379 | // byte 0: flags and opcode
380 | int flagOps = inputStream.read();
381 | if ((flagOps & MASK_RESERVED) != 0x00) {
382 | failTheWebSocketConnection();
383 | return;
384 | }
385 | int opcode = flagOps & MASK_OPCODE;
386 | if (opcode >= OPCODE_RESERVED_NON_CONTROL_LOW
387 | && opcode <= OPCODE_RESERVED_NON_CONTROL_HIGH) {
388 | failTheWebSocketConnection();
389 | return;
390 | }
391 | if (opcode >= OPCODE_RESERVED_CONTROL_LOW) {
392 | failTheWebSocketConnection();
393 | return;
394 | }
395 | boolean finalFragment = (flagOps & MASK_FINAL) == MASK_FINAL;
396 | boolean controlOpcode =
397 | (flagOps & MASK_CONTROL_OPCODE) == MASK_CONTROL_OPCODE;
398 | if (controlOpcode && !finalFragment) {
399 | failTheWebSocketConnection();
400 | return;
401 | }
402 | // byte 1: masking and payload length
403 | int maskPayload = inputStream.read();
404 | boolean masked = (maskPayload & MASK_MASK) == MASK_MASK;
405 | if (!masked) {
406 | failTheWebSocketConnection();
407 | return;
408 | }
409 | int payloadSize = maskPayload & MASK_PAYLOAD_SIZE;
410 | // byte 2-9: extended payload length, if specified
411 | if (payloadSize == LENGTH_16) {
412 | if (controlOpcode) {
413 | failTheWebSocketConnection();
414 | return;
415 | }
416 | payloadLength = (inputStream.read() << OCTET)
417 | | (inputStream.read());
418 | if (payloadLength < LENGTH_16_MIN) {
419 | failTheWebSocketConnection();
420 | return;
421 | }
422 | } else if (payloadSize == LENGTH_64) {
423 | if (controlOpcode) {
424 | failTheWebSocketConnection();
425 | return;
426 | }
427 | payloadLength = 0L;
428 | for (int i = 0; i < NUM_OCTET_64; i++) {
429 | payloadLength |=
430 | inputStream.read() << (NUM_OCTET_64 - 1 - i) * OCTET;
431 | }
432 | if (payloadLength < LENGTH_64_MIN) {
433 | failTheWebSocketConnection();
434 | return;
435 | }
436 | } else {
437 | payloadLength = payloadSize;
438 | }
439 | // byte 10-13: masking key
440 | for (int i = 0; i < NUM_MASKING_BYTES; i++) {
441 | maskingBytes[i] = inputStream.read();
442 | }
443 | maskingIndex = 0;
444 | // if this is a control opcode; handle the control frame
445 | if (opcode == OPCODE_CLOSE) {
446 | handleCloseFrame();
447 | }
448 | if (opcode == OPCODE_PING) {
449 | handlePingFrame();
450 | }
451 | if (opcode == OPCODE_PONG) {
452 | handlePongFrame();
453 | }
454 | }
455 |
456 | /**
457 | * Perform the initial WebSocket handshake. WebSockets connect with an
458 | * HTTP Request to upgrade the connection to a WebSocket connection. This
459 | * method ensures that the request is correctly formed, and provides
460 | * the appropriate response to the client. After this method is called,
461 | * further communication is performed solely with WebSocket frames.
462 | * @throws IOException if anything goes wrong with the underlying stream
463 | */
464 | private void shakeHands() throws IOException {
465 | HttpRequest req = new HttpRequest(inputStream);
466 | String requestLine = req.get(HttpRequest.REQUEST_LINE);
467 | handshakeComplete = checkStartsWith(requestLine, "GET /")
468 | && checkContains(requestLine, "HTTP/")
469 | && req.get("Host") != null
470 | && checkContains(req.get("Upgrade"), "websocket")
471 | && checkContains(req.get("Connection"), "Upgrade")
472 | && "13".equals(req.get("Sec-WebSocket-Version"))
473 | && req.get("Sec-WebSocket-Key") != null;
474 | String nonce = req.get("Sec-WebSocket-Key");
475 | if (handshakeComplete) {
476 | byte[] nonceBytes = BaseEncoding.base64().decode(nonce);
477 | if (nonceBytes.length != HANDSHAKE_NONCE_LENGTH) {
478 | handshakeComplete = false;
479 | }
480 | }
481 | // if we have met all the requirements
482 | if (handshakeComplete) {
483 | outputPeer.write(asUTF8("HTTP/1.1 101 Switching Protocols\r\n"));
484 | outputPeer.write(asUTF8("Upgrade: websocket\r\n"));
485 | outputPeer.write(asUTF8("Connection: upgrade\r\n"));
486 | outputPeer.write(asUTF8("Sec-WebSocket-Accept: "));
487 | HashFunction hf = Hashing.sha1();
488 | HashCode hc = hf.newHasher()
489 | .putString(nonce, StandardCharsets.UTF_8)
490 | .putString(WEBSOCKET_ACCEPT_UUID, StandardCharsets.UTF_8)
491 | .hash();
492 | String acceptKey = BaseEncoding.base64().encode(hc.asBytes());
493 | outputPeer.write(asUTF8(acceptKey));
494 | outputPeer.write(asUTF8("\r\n\r\n"));
495 | }
496 | outputPeer.setHandshakeComplete(handshakeComplete);
497 | }
498 |
499 | /**
500 | * Sets the WebSocketServerInputStream to a FAILED state. In this state,
501 | * no further processing of data takes place. Mostly it is used to
502 | * prevent actions that rely upon faulty WebSocket implementations.
503 | */
504 | private void failTheWebSocketConnection() {
505 | failed = true;
506 | }
507 |
508 | /**
509 | * Handle an incoming Close control frame. If we haven't sent a Close
510 | * frame to the client, we do so. We then close the underlying socket.
511 | * @throws IOException if anything goes wrong with the underlying stream
512 | */
513 | private void handleCloseFrame() throws IOException {
514 | // the client has sent us a close frame
515 | closeReceived = true;
516 | // if we already sent a close frame before
517 | if (isCloseSent()) {
518 | // then we received an acknowledgement close frame
519 | // from the client, so we need to close the underlying
520 | // TCP socket now
521 | this.close();
522 | return;
523 | }
524 | // otherwise, the client has sent us a close frame
525 | // and we will acknowledge that close frame now
526 | byte[] closePayload = consumePayload();
527 | if (closePayload.length >= 2) {
528 | int highByte = asUnsignedInt(closePayload[0]);
529 | int lowByte = asUnsignedInt(closePayload[1]);
530 | int closeStatusCode = (highByte << OCTET) | lowByte;
531 | outputPeer.writeClose(closeStatusCode);
532 | } else {
533 | outputPeer.writeClose();
534 | }
535 | // we need to close the underlying TCP socket now
536 | this.close();
537 | }
538 |
539 | /**
540 | * Handle an incoming Ping control frame. The WebSocket standard indicates
541 | * echoing a Pong frame back with an identical payload. That is what this
542 | * method does.
543 | * @throws IOException if anything goes wrong with the underlying stream
544 | */
545 | private void handlePingFrame() throws IOException {
546 | // read all of the ping payload
547 | byte[] pingPayload = consumePayload();
548 | outputPeer.writePong(pingPayload);
549 | }
550 |
551 | /**
552 | * Handle an incoming Pong control frame. This method simply consumes
553 | * the payload (if any) and disregards the frame.
554 | * @throws IOException if anything goes wrong with the underlying stream
555 | */
556 | private void handlePongFrame() throws IOException {
557 | // read all of the pong payload
558 | consumePayload();
559 | }
560 |
561 | /**
562 | * Consume the entire payload of the frame. Note that the state of the
563 | * field payloadLength is used (and altered) in this utility
564 | * method.
565 | * @return byte[] containing the bytes of the frame payload
566 | * @throws IOException if anything goes wrong with the underlying stream
567 | */
568 | private byte[] consumePayload() throws IOException {
569 | byte[] payload = new byte[(int) payloadLength];
570 | int count = 0;
571 | while (payloadLength > 0L) {
572 | payload[count] = (byte) this.read();
573 | count++;
574 | }
575 | return payload;
576 | }
577 |
578 | /**
579 | * Flag: Has the WebSocket connection received a CLOSE frame?
580 | */
581 | private boolean closeReceived = false;
582 |
583 | /**
584 | * Flag: Has the WebSocket connection failed for any reason?
585 | */
586 | private boolean failed = false;
587 |
588 | /**
589 | * Flag: Is the WebSocket handshake complete?
590 | */
591 | private boolean handshakeComplete = false;
592 |
593 | /**
594 | * InputStream to be decorated as a WebSocket-speaking InputStream.
595 | */
596 | private InputStream inputStream = null;
597 |
598 | /**
599 | * Bytes of the latest masking key provided by the client.
600 | */
601 | private final int[] maskingBytes = new int[NUM_MASKING_BYTES];
602 |
603 | /**
604 | * Index of the next maskingByte to be used on payload data.
605 | */
606 | private int maskingIndex = 0;
607 |
608 | /**
609 | * The companion OutputStream for this WebSocketInputStream.
610 | */
611 | private WebSocketServerOutputStream outputPeer = null;
612 |
613 | /**
614 | * Number of payload bytes that we still expecting before the next
615 | * WebSocket frame.
616 | */
617 | private long payloadLength = 0L;
618 | }
619 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | GNU AFFERO GENERAL PUBLIC LICENSE
2 | Version 3, 19 November 2007
3 |
4 | Copyright (C) 2007 Free Software Foundation, Inc.
5 | Everyone is permitted to copy and distribute verbatim copies
6 | of this license document, but changing it is not allowed.
7 |
8 | Preamble
9 |
10 | The GNU Affero General Public License is a free, copyleft license for
11 | software and other kinds of works, specifically designed to ensure
12 | cooperation with the community in the case of network server software.
13 |
14 | The licenses for most software and other practical works are designed
15 | to take away your freedom to share and change the works. By contrast,
16 | our General Public Licenses are intended to guarantee your freedom to
17 | share and change all versions of a program--to make sure it remains free
18 | software for all its users.
19 |
20 | When we speak of free software, we are referring to freedom, not
21 | price. Our General Public Licenses are designed to make sure that you
22 | have the freedom to distribute copies of free software (and charge for
23 | them if you wish), that you receive source code or can get it if you
24 | want it, that you can change the software or use pieces of it in new
25 | free programs, and that you know you can do these things.
26 |
27 | Developers that use our General Public Licenses protect your rights
28 | with two steps: (1) assert copyright on the software, and (2) offer
29 | you this License which gives you legal permission to copy, distribute
30 | and/or modify the software.
31 |
32 | A secondary benefit of defending all users' freedom is that
33 | improvements made in alternate versions of the program, if they
34 | receive widespread use, become available for other developers to
35 | incorporate. Many developers of free software are heartened and
36 | encouraged by the resulting cooperation. However, in the case of
37 | software used on network servers, this result may fail to come about.
38 | The GNU General Public License permits making a modified version and
39 | letting the public access it on a server without ever releasing its
40 | source code to the public.
41 |
42 | The GNU Affero General Public License is designed specifically to
43 | ensure that, in such cases, the modified source code becomes available
44 | to the community. It requires the operator of a network server to
45 | provide the source code of the modified version running there to the
46 | users of that server. Therefore, public use of a modified version, on
47 | a publicly accessible server, gives the public access to the source
48 | code of the modified version.
49 |
50 | An older license, called the Affero General Public License and
51 | published by Affero, was designed to accomplish similar goals. This is
52 | a different license, not a version of the Affero GPL, but Affero has
53 | released a new version of the Affero GPL which permits relicensing under
54 | this license.
55 |
56 | The precise terms and conditions for copying, distribution and
57 | modification follow.
58 |
59 | TERMS AND CONDITIONS
60 |
61 | 0. Definitions.
62 |
63 | "This License" refers to version 3 of the GNU Affero General Public License.
64 |
65 | "Copyright" also means copyright-like laws that apply to other kinds of
66 | works, such as semiconductor masks.
67 |
68 | "The Program" refers to any copyrightable work licensed under this
69 | License. Each licensee is addressed as "you". "Licensees" and
70 | "recipients" may be individuals or organizations.
71 |
72 | To "modify" a work means to copy from or adapt all or part of the work
73 | in a fashion requiring copyright permission, other than the making of an
74 | exact copy. The resulting work is called a "modified version" of the
75 | earlier work or a work "based on" the earlier work.
76 |
77 | A "covered work" means either the unmodified Program or a work based
78 | on the Program.
79 |
80 | To "propagate" a work means to do anything with it that, without
81 | permission, would make you directly or secondarily liable for
82 | infringement under applicable copyright law, except executing it on a
83 | computer or modifying a private copy. Propagation includes copying,
84 | distribution (with or without modification), making available to the
85 | public, and in some countries other activities as well.
86 |
87 | To "convey" a work means any kind of propagation that enables other
88 | parties to make or receive copies. Mere interaction with a user through
89 | a computer network, with no transfer of a copy, is not conveying.
90 |
91 | An interactive user interface displays "Appropriate Legal Notices"
92 | to the extent that it includes a convenient and prominently visible
93 | feature that (1) displays an appropriate copyright notice, and (2)
94 | tells the user that there is no warranty for the work (except to the
95 | extent that warranties are provided), that licensees may convey the
96 | work under this License, and how to view a copy of this License. If
97 | the interface presents a list of user commands or options, such as a
98 | menu, a prominent item in the list meets this criterion.
99 |
100 | 1. Source Code.
101 |
102 | The "source code" for a work means the preferred form of the work
103 | for making modifications to it. "Object code" means any non-source
104 | form of a work.
105 |
106 | A "Standard Interface" means an interface that either is an official
107 | standard defined by a recognized standards body, or, in the case of
108 | interfaces specified for a particular programming language, one that
109 | is widely used among developers working in that language.
110 |
111 | The "System Libraries" of an executable work include anything, other
112 | than the work as a whole, that (a) is included in the normal form of
113 | packaging a Major Component, but which is not part of that Major
114 | Component, and (b) serves only to enable use of the work with that
115 | Major Component, or to implement a Standard Interface for which an
116 | implementation is available to the public in source code form. A
117 | "Major Component", in this context, means a major essential component
118 | (kernel, window system, and so on) of the specific operating system
119 | (if any) on which the executable work runs, or a compiler used to
120 | produce the work, or an object code interpreter used to run it.
121 |
122 | The "Corresponding Source" for a work in object code form means all
123 | the source code needed to generate, install, and (for an executable
124 | work) run the object code and to modify the work, including scripts to
125 | control those activities. However, it does not include the work's
126 | System Libraries, or general-purpose tools or generally available free
127 | programs which are used unmodified in performing those activities but
128 | which are not part of the work. For example, Corresponding Source
129 | includes interface definition files associated with source files for
130 | the work, and the source code for shared libraries and dynamically
131 | linked subprograms that the work is specifically designed to require,
132 | such as by intimate data communication or control flow between those
133 | subprograms and other parts of the work.
134 |
135 | The Corresponding Source need not include anything that users
136 | can regenerate automatically from other parts of the Corresponding
137 | Source.
138 |
139 | The Corresponding Source for a work in source code form is that
140 | same work.
141 |
142 | 2. Basic Permissions.
143 |
144 | All rights granted under this License are granted for the term of
145 | copyright on the Program, and are irrevocable provided the stated
146 | conditions are met. This License explicitly affirms your unlimited
147 | permission to run the unmodified Program. The output from running a
148 | covered work is covered by this License only if the output, given its
149 | content, constitutes a covered work. This License acknowledges your
150 | rights of fair use or other equivalent, as provided by copyright law.
151 |
152 | You may make, run and propagate covered works that you do not
153 | convey, without conditions so long as your license otherwise remains
154 | in force. You may convey covered works to others for the sole purpose
155 | of having them make modifications exclusively for you, or provide you
156 | with facilities for running those works, provided that you comply with
157 | the terms of this License in conveying all material for which you do
158 | not control copyright. Those thus making or running the covered works
159 | for you must do so exclusively on your behalf, under your direction
160 | and control, on terms that prohibit them from making any copies of
161 | your copyrighted material outside their relationship with you.
162 |
163 | Conveying under any other circumstances is permitted solely under
164 | the conditions stated below. Sublicensing is not allowed; section 10
165 | makes it unnecessary.
166 |
167 | 3. Protecting Users' Legal Rights From Anti-Circumvention Law.
168 |
169 | No covered work shall be deemed part of an effective technological
170 | measure under any applicable law fulfilling obligations under article
171 | 11 of the WIPO copyright treaty adopted on 20 December 1996, or
172 | similar laws prohibiting or restricting circumvention of such
173 | measures.
174 |
175 | When you convey a covered work, you waive any legal power to forbid
176 | circumvention of technological measures to the extent such circumvention
177 | is effected by exercising rights under this License with respect to
178 | the covered work, and you disclaim any intention to limit operation or
179 | modification of the work as a means of enforcing, against the work's
180 | users, your or third parties' legal rights to forbid circumvention of
181 | technological measures.
182 |
183 | 4. Conveying Verbatim Copies.
184 |
185 | You may convey verbatim copies of the Program's source code as you
186 | receive it, in any medium, provided that you conspicuously and
187 | appropriately publish on each copy an appropriate copyright notice;
188 | keep intact all notices stating that this License and any
189 | non-permissive terms added in accord with section 7 apply to the code;
190 | keep intact all notices of the absence of any warranty; and give all
191 | recipients a copy of this License along with the Program.
192 |
193 | You may charge any price or no price for each copy that you convey,
194 | and you may offer support or warranty protection for a fee.
195 |
196 | 5. Conveying Modified Source Versions.
197 |
198 | You may convey a work based on the Program, or the modifications to
199 | produce it from the Program, in the form of source code under the
200 | terms of section 4, provided that you also meet all of these conditions:
201 |
202 | a) The work must carry prominent notices stating that you modified
203 | it, and giving a relevant date.
204 |
205 | b) The work must carry prominent notices stating that it is
206 | released under this License and any conditions added under section
207 | 7. This requirement modifies the requirement in section 4 to
208 | "keep intact all notices".
209 |
210 | c) You must license the entire work, as a whole, under this
211 | License to anyone who comes into possession of a copy. This
212 | License will therefore apply, along with any applicable section 7
213 | additional terms, to the whole of the work, and all its parts,
214 | regardless of how they are packaged. This License gives no
215 | permission to license the work in any other way, but it does not
216 | invalidate such permission if you have separately received it.
217 |
218 | d) If the work has interactive user interfaces, each must display
219 | Appropriate Legal Notices; however, if the Program has interactive
220 | interfaces that do not display Appropriate Legal Notices, your
221 | work need not make them do so.
222 |
223 | A compilation of a covered work with other separate and independent
224 | works, which are not by their nature extensions of the covered work,
225 | and which are not combined with it such as to form a larger program,
226 | in or on a volume of a storage or distribution medium, is called an
227 | "aggregate" if the compilation and its resulting copyright are not
228 | used to limit the access or legal rights of the compilation's users
229 | beyond what the individual works permit. Inclusion of a covered work
230 | in an aggregate does not cause this License to apply to the other
231 | parts of the aggregate.
232 |
233 | 6. Conveying Non-Source Forms.
234 |
235 | You may convey a covered work in object code form under the terms
236 | of sections 4 and 5, provided that you also convey the
237 | machine-readable Corresponding Source under the terms of this License,
238 | in one of these ways:
239 |
240 | a) Convey the object code in, or embodied in, a physical product
241 | (including a physical distribution medium), accompanied by the
242 | Corresponding Source fixed on a durable physical medium
243 | customarily used for software interchange.
244 |
245 | b) Convey the object code in, or embodied in, a physical product
246 | (including a physical distribution medium), accompanied by a
247 | written offer, valid for at least three years and valid for as
248 | long as you offer spare parts or customer support for that product
249 | model, to give anyone who possesses the object code either (1) a
250 | copy of the Corresponding Source for all the software in the
251 | product that is covered by this License, on a durable physical
252 | medium customarily used for software interchange, for a price no
253 | more than your reasonable cost of physically performing this
254 | conveying of source, or (2) access to copy the
255 | Corresponding Source from a network server at no charge.
256 |
257 | c) Convey individual copies of the object code with a copy of the
258 | written offer to provide the Corresponding Source. This
259 | alternative is allowed only occasionally and noncommercially, and
260 | only if you received the object code with such an offer, in accord
261 | with subsection 6b.
262 |
263 | d) Convey the object code by offering access from a designated
264 | place (gratis or for a charge), and offer equivalent access to the
265 | Corresponding Source in the same way through the same place at no
266 | further charge. You need not require recipients to copy the
267 | Corresponding Source along with the object code. If the place to
268 | copy the object code is a network server, the Corresponding Source
269 | may be on a different server (operated by you or a third party)
270 | that supports equivalent copying facilities, provided you maintain
271 | clear directions next to the object code saying where to find the
272 | Corresponding Source. Regardless of what server hosts the
273 | Corresponding Source, you remain obligated to ensure that it is
274 | available for as long as needed to satisfy these requirements.
275 |
276 | e) Convey the object code using peer-to-peer transmission, provided
277 | you inform other peers where the object code and Corresponding
278 | Source of the work are being offered to the general public at no
279 | charge under subsection 6d.
280 |
281 | A separable portion of the object code, whose source code is excluded
282 | from the Corresponding Source as a System Library, need not be
283 | included in conveying the object code work.
284 |
285 | A "User Product" is either (1) a "consumer product", which means any
286 | tangible personal property which is normally used for personal, family,
287 | or household purposes, or (2) anything designed or sold for incorporation
288 | into a dwelling. In determining whether a product is a consumer product,
289 | doubtful cases shall be resolved in favor of coverage. For a particular
290 | product received by a particular user, "normally used" refers to a
291 | typical or common use of that class of product, regardless of the status
292 | of the particular user or of the way in which the particular user
293 | actually uses, or expects or is expected to use, the product. A product
294 | is a consumer product regardless of whether the product has substantial
295 | commercial, industrial or non-consumer uses, unless such uses represent
296 | the only significant mode of use of the product.
297 |
298 | "Installation Information" for a User Product means any methods,
299 | procedures, authorization keys, or other information required to install
300 | and execute modified versions of a covered work in that User Product from
301 | a modified version of its Corresponding Source. The information must
302 | suffice to ensure that the continued functioning of the modified object
303 | code is in no case prevented or interfered with solely because
304 | modification has been made.
305 |
306 | If you convey an object code work under this section in, or with, or
307 | specifically for use in, a User Product, and the conveying occurs as
308 | part of a transaction in which the right of possession and use of the
309 | User Product is transferred to the recipient in perpetuity or for a
310 | fixed term (regardless of how the transaction is characterized), the
311 | Corresponding Source conveyed under this section must be accompanied
312 | by the Installation Information. But this requirement does not apply
313 | if neither you nor any third party retains the ability to install
314 | modified object code on the User Product (for example, the work has
315 | been installed in ROM).
316 |
317 | The requirement to provide Installation Information does not include a
318 | requirement to continue to provide support service, warranty, or updates
319 | for a work that has been modified or installed by the recipient, or for
320 | the User Product in which it has been modified or installed. Access to a
321 | network may be denied when the modification itself materially and
322 | adversely affects the operation of the network or violates the rules and
323 | protocols for communication across the network.
324 |
325 | Corresponding Source conveyed, and Installation Information provided,
326 | in accord with this section must be in a format that is publicly
327 | documented (and with an implementation available to the public in
328 | source code form), and must require no special password or key for
329 | unpacking, reading or copying.
330 |
331 | 7. Additional Terms.
332 |
333 | "Additional permissions" are terms that supplement the terms of this
334 | License by making exceptions from one or more of its conditions.
335 | Additional permissions that are applicable to the entire Program shall
336 | be treated as though they were included in this License, to the extent
337 | that they are valid under applicable law. If additional permissions
338 | apply only to part of the Program, that part may be used separately
339 | under those permissions, but the entire Program remains governed by
340 | this License without regard to the additional permissions.
341 |
342 | When you convey a copy of a covered work, you may at your option
343 | remove any additional permissions from that copy, or from any part of
344 | it. (Additional permissions may be written to require their own
345 | removal in certain cases when you modify the work.) You may place
346 | additional permissions on material, added by you to a covered work,
347 | for which you have or can give appropriate copyright permission.
348 |
349 | Notwithstanding any other provision of this License, for material you
350 | add to a covered work, you may (if authorized by the copyright holders of
351 | that material) supplement the terms of this License with terms:
352 |
353 | a) Disclaiming warranty or limiting liability differently from the
354 | terms of sections 15 and 16 of this License; or
355 |
356 | b) Requiring preservation of specified reasonable legal notices or
357 | author attributions in that material or in the Appropriate Legal
358 | Notices displayed by works containing it; or
359 |
360 | c) Prohibiting misrepresentation of the origin of that material, or
361 | requiring that modified versions of such material be marked in
362 | reasonable ways as different from the original version; or
363 |
364 | d) Limiting the use for publicity purposes of names of licensors or
365 | authors of the material; or
366 |
367 | e) Declining to grant rights under trademark law for use of some
368 | trade names, trademarks, or service marks; or
369 |
370 | f) Requiring indemnification of licensors and authors of that
371 | material by anyone who conveys the material (or modified versions of
372 | it) with contractual assumptions of liability to the recipient, for
373 | any liability that these contractual assumptions directly impose on
374 | those licensors and authors.
375 |
376 | All other non-permissive additional terms are considered "further
377 | restrictions" within the meaning of section 10. If the Program as you
378 | received it, or any part of it, contains a notice stating that it is
379 | governed by this License along with a term that is a further
380 | restriction, you may remove that term. If a license document contains
381 | a further restriction but permits relicensing or conveying under this
382 | License, you may add to a covered work material governed by the terms
383 | of that license document, provided that the further restriction does
384 | not survive such relicensing or conveying.
385 |
386 | If you add terms to a covered work in accord with this section, you
387 | must place, in the relevant source files, a statement of the
388 | additional terms that apply to those files, or a notice indicating
389 | where to find the applicable terms.
390 |
391 | Additional terms, permissive or non-permissive, may be stated in the
392 | form of a separately written license, or stated as exceptions;
393 | the above requirements apply either way.
394 |
395 | 8. Termination.
396 |
397 | You may not propagate or modify a covered work except as expressly
398 | provided under this License. Any attempt otherwise to propagate or
399 | modify it is void, and will automatically terminate your rights under
400 | this License (including any patent licenses granted under the third
401 | paragraph of section 11).
402 |
403 | However, if you cease all violation of this License, then your
404 | license from a particular copyright holder is reinstated (a)
405 | provisionally, unless and until the copyright holder explicitly and
406 | finally terminates your license, and (b) permanently, if the copyright
407 | holder fails to notify you of the violation by some reasonable means
408 | prior to 60 days after the cessation.
409 |
410 | Moreover, your license from a particular copyright holder is
411 | reinstated permanently if the copyright holder notifies you of the
412 | violation by some reasonable means, this is the first time you have
413 | received notice of violation of this License (for any work) from that
414 | copyright holder, and you cure the violation prior to 30 days after
415 | your receipt of the notice.
416 |
417 | Termination of your rights under this section does not terminate the
418 | licenses of parties who have received copies or rights from you under
419 | this License. If your rights have been terminated and not permanently
420 | reinstated, you do not qualify to receive new licenses for the same
421 | material under section 10.
422 |
423 | 9. Acceptance Not Required for Having Copies.
424 |
425 | You are not required to accept this License in order to receive or
426 | run a copy of the Program. Ancillary propagation of a covered work
427 | occurring solely as a consequence of using peer-to-peer transmission
428 | to receive a copy likewise does not require acceptance. However,
429 | nothing other than this License grants you permission to propagate or
430 | modify any covered work. These actions infringe copyright if you do
431 | not accept this License. Therefore, by modifying or propagating a
432 | covered work, you indicate your acceptance of this License to do so.
433 |
434 | 10. Automatic Licensing of Downstream Recipients.
435 |
436 | Each time you convey a covered work, the recipient automatically
437 | receives a license from the original licensors, to run, modify and
438 | propagate that work, subject to this License. You are not responsible
439 | for enforcing compliance by third parties with this License.
440 |
441 | An "entity transaction" is a transaction transferring control of an
442 | organization, or substantially all assets of one, or subdividing an
443 | organization, or merging organizations. If propagation of a covered
444 | work results from an entity transaction, each party to that
445 | transaction who receives a copy of the work also receives whatever
446 | licenses to the work the party's predecessor in interest had or could
447 | give under the previous paragraph, plus a right to possession of the
448 | Corresponding Source of the work from the predecessor in interest, if
449 | the predecessor has it or can get it with reasonable efforts.
450 |
451 | You may not impose any further restrictions on the exercise of the
452 | rights granted or affirmed under this License. For example, you may
453 | not impose a license fee, royalty, or other charge for exercise of
454 | rights granted under this License, and you may not initiate litigation
455 | (including a cross-claim or counterclaim in a lawsuit) alleging that
456 | any patent claim is infringed by making, using, selling, offering for
457 | sale, or importing the Program or any portion of it.
458 |
459 | 11. Patents.
460 |
461 | A "contributor" is a copyright holder who authorizes use under this
462 | License of the Program or a work on which the Program is based. The
463 | work thus licensed is called the contributor's "contributor version".
464 |
465 | A contributor's "essential patent claims" are all patent claims
466 | owned or controlled by the contributor, whether already acquired or
467 | hereafter acquired, that would be infringed by some manner, permitted
468 | by this License, of making, using, or selling its contributor version,
469 | but do not include claims that would be infringed only as a
470 | consequence of further modification of the contributor version. For
471 | purposes of this definition, "control" includes the right to grant
472 | patent sublicenses in a manner consistent with the requirements of
473 | this License.
474 |
475 | Each contributor grants you a non-exclusive, worldwide, royalty-free
476 | patent license under the contributor's essential patent claims, to
477 | make, use, sell, offer for sale, import and otherwise run, modify and
478 | propagate the contents of its contributor version.
479 |
480 | In the following three paragraphs, a "patent license" is any express
481 | agreement or commitment, however denominated, not to enforce a patent
482 | (such as an express permission to practice a patent or covenant not to
483 | sue for patent infringement). To "grant" such a patent license to a
484 | party means to make such an agreement or commitment not to enforce a
485 | patent against the party.
486 |
487 | If you convey a covered work, knowingly relying on a patent license,
488 | and the Corresponding Source of the work is not available for anyone
489 | to copy, free of charge and under the terms of this License, through a
490 | publicly available network server or other readily accessible means,
491 | then you must either (1) cause the Corresponding Source to be so
492 | available, or (2) arrange to deprive yourself of the benefit of the
493 | patent license for this particular work, or (3) arrange, in a manner
494 | consistent with the requirements of this License, to extend the patent
495 | license to downstream recipients. "Knowingly relying" means you have
496 | actual knowledge that, but for the patent license, your conveying the
497 | covered work in a country, or your recipient's use of the covered work
498 | in a country, would infringe one or more identifiable patents in that
499 | country that you have reason to believe are valid.
500 |
501 | If, pursuant to or in connection with a single transaction or
502 | arrangement, you convey, or propagate by procuring conveyance of, a
503 | covered work, and grant a patent license to some of the parties
504 | receiving the covered work authorizing them to use, propagate, modify
505 | or convey a specific copy of the covered work, then the patent license
506 | you grant is automatically extended to all recipients of the covered
507 | work and works based on it.
508 |
509 | A patent license is "discriminatory" if it does not include within
510 | the scope of its coverage, prohibits the exercise of, or is
511 | conditioned on the non-exercise of one or more of the rights that are
512 | specifically granted under this License. You may not convey a covered
513 | work if you are a party to an arrangement with a third party that is
514 | in the business of distributing software, under which you make payment
515 | to the third party based on the extent of your activity of conveying
516 | the work, and under which the third party grants, to any of the
517 | parties who would receive the covered work from you, a discriminatory
518 | patent license (a) in connection with copies of the covered work
519 | conveyed by you (or copies made from those copies), or (b) primarily
520 | for and in connection with specific products or compilations that
521 | contain the covered work, unless you entered into that arrangement,
522 | or that patent license was granted, prior to 28 March 2007.
523 |
524 | Nothing in this License shall be construed as excluding or limiting
525 | any implied license or other defenses to infringement that may
526 | otherwise be available to you under applicable patent law.
527 |
528 | 12. No Surrender of Others' Freedom.
529 |
530 | If conditions are imposed on you (whether by court order, agreement or
531 | otherwise) that contradict the conditions of this License, they do not
532 | excuse you from the conditions of this License. If you cannot convey a
533 | covered work so as to satisfy simultaneously your obligations under this
534 | License and any other pertinent obligations, then as a consequence you may
535 | not convey it at all. For example, if you agree to terms that obligate you
536 | to collect a royalty for further conveying from those to whom you convey
537 | the Program, the only way you could satisfy both those terms and this
538 | License would be to refrain entirely from conveying the Program.
539 |
540 | 13. Remote Network Interaction; Use with the GNU General Public License.
541 |
542 | Notwithstanding any other provision of this License, if you modify the
543 | Program, your modified version must prominently offer all users
544 | interacting with it remotely through a computer network (if your version
545 | supports such interaction) an opportunity to receive the Corresponding
546 | Source of your version by providing access to the Corresponding Source
547 | from a network server at no charge, through some standard or customary
548 | means of facilitating copying of software. This Corresponding Source
549 | shall include the Corresponding Source for any work covered by version 3
550 | of the GNU General Public License that is incorporated pursuant to the
551 | following paragraph.
552 |
553 | Notwithstanding any other provision of this License, you have
554 | permission to link or combine any covered work with a work licensed
555 | under version 3 of the GNU General Public License into a single
556 | combined work, and to convey the resulting work. The terms of this
557 | License will continue to apply to the part which is the covered work,
558 | but the work with which it is combined will remain governed by version
559 | 3 of the GNU General Public License.
560 |
561 | 14. Revised Versions of this License.
562 |
563 | The Free Software Foundation may publish revised and/or new versions of
564 | the GNU Affero General Public License from time to time. Such new versions
565 | will be similar in spirit to the present version, but may differ in detail to
566 | address new problems or concerns.
567 |
568 | Each version is given a distinguishing version number. If the
569 | Program specifies that a certain numbered version of the GNU Affero General
570 | Public License "or any later version" applies to it, you have the
571 | option of following the terms and conditions either of that numbered
572 | version or of any later version published by the Free Software
573 | Foundation. If the Program does not specify a version number of the
574 | GNU Affero General Public License, you may choose any version ever published
575 | by the Free Software Foundation.
576 |
577 | If the Program specifies that a proxy can decide which future
578 | versions of the GNU Affero General Public License can be used, that proxy's
579 | public statement of acceptance of a version permanently authorizes you
580 | to choose that version for the Program.
581 |
582 | Later license versions may give you additional or different
583 | permissions. However, no additional obligations are imposed on any
584 | author or copyright holder as a result of your choosing to follow a
585 | later version.
586 |
587 | 15. Disclaimer of Warranty.
588 |
589 | THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
590 | APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
591 | HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
592 | OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
593 | THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
594 | PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
595 | IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
596 | ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
597 |
598 | 16. Limitation of Liability.
599 |
600 | IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
601 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
602 | THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
603 | GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
604 | USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
605 | DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
606 | PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
607 | EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
608 | SUCH DAMAGES.
609 |
610 | 17. Interpretation of Sections 15 and 16.
611 |
612 | If the disclaimer of warranty and limitation of liability provided
613 | above cannot be given local legal effect according to their terms,
614 | reviewing courts shall apply local law that most closely approximates
615 | an absolute waiver of all civil liability in connection with the
616 | Program, unless a warranty or assumption of liability accompanies a
617 | copy of the Program in return for a fee.
618 |
619 | END OF TERMS AND CONDITIONS
620 |
621 | How to Apply These Terms to Your New Programs
622 |
623 | If you develop a new program, and you want it to be of the greatest
624 | possible use to the public, the best way to achieve this is to make it
625 | free software which everyone can redistribute and change under these terms.
626 |
627 | To do so, attach the following notices to the program. It is safest
628 | to attach them to the start of each source file to most effectively
629 | state the exclusion of warranty; and each file should have at least
630 | the "copyright" line and a pointer to where the full notice is found.
631 |
632 |
633 | Copyright (C)
634 |
635 | This program is free software: you can redistribute it and/or modify
636 | it under the terms of the GNU Affero General Public License as published by
637 | the Free Software Foundation, either version 3 of the License, or
638 | (at your option) any later version.
639 |
640 | This program is distributed in the hope that it will be useful,
641 | but WITHOUT ANY WARRANTY; without even the implied warranty of
642 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
643 | GNU Affero General Public License for more details.
644 |
645 | You should have received a copy of the GNU Affero General Public License
646 | along with this program. If not, see .
647 |
648 | Also add information on how to contact you by electronic and paper mail.
649 |
650 | If your software can interact with users remotely through a computer
651 | network, you should also make sure that it provides a way for users to
652 | get its source. For example, if your program is a web application, its
653 | interface could display a "Source" link that leads users to an archive
654 | of the code. There are many ways you could offer source, and different
655 | solutions will be better for different programs; see section 13 for the
656 | specific requirements.
657 |
658 | You should also get your employer (if you work as a programmer) or school,
659 | if any, to sign a "copyright disclaimer" for the program, if necessary.
660 | For more information on this, and how to apply and follow the GNU AGPL, see
661 | .
662 |
--------------------------------------------------------------------------------
/doc/rfc7235.txt:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Internet Engineering Task Force (IETF) R. Fielding, Ed.
8 | Request for Comments: 7235 Adobe
9 | Obsoletes: 2616 J. Reschke, Ed.
10 | Updates: 2617 greenbytes
11 | Category: Standards Track June 2014
12 | ISSN: 2070-1721
13 |
14 |
15 | Hypertext Transfer Protocol (HTTP/1.1): Authentication
16 |
17 | Abstract
18 |
19 | The Hypertext Transfer Protocol (HTTP) is a stateless application-
20 | level protocol for distributed, collaborative, hypermedia information
21 | systems. This document defines the HTTP Authentication framework.
22 |
23 | Status of This Memo
24 |
25 | This is an Internet Standards Track document.
26 |
27 | This document is a product of the Internet Engineering Task Force
28 | (IETF). It represents the consensus of the IETF community. It has
29 | received public review and has been approved for publication by the
30 | Internet Engineering Steering Group (IESG). Further information on
31 | Internet Standards is available in Section 2 of RFC 5741.
32 |
33 | Information about the current status of this document, any errata,
34 | and how to provide feedback on it may be obtained at
35 | http://www.rfc-editor.org/info/rfc7235.
36 |
37 | Copyright Notice
38 |
39 | Copyright (c) 2014 IETF Trust and the persons identified as the
40 | document authors. All rights reserved.
41 |
42 | This document is subject to BCP 78 and the IETF Trust's Legal
43 | Provisions Relating to IETF Documents
44 | (http://trustee.ietf.org/license-info) in effect on the date of
45 | publication of this document. Please review these documents
46 | carefully, as they describe your rights and restrictions with respect
47 | to this document. Code Components extracted from this document must
48 | include Simplified BSD License text as described in Section 4.e of
49 | the Trust Legal Provisions and are provided without warranty as
50 | described in the Simplified BSD License.
51 |
52 | This document may contain material from IETF Documents or IETF
53 | Contributions published or made publicly available before November
54 | 10, 2008. The person(s) controlling the copyright in some of this
55 |
56 |
57 |
58 | Fielding & Reschke Standards Track [Page 1]
59 |
60 | RFC 7235 HTTP/1.1 Authentication June 2014
61 |
62 |
63 | material may not have granted the IETF Trust the right to allow
64 | modifications of such material outside the IETF Standards Process.
65 | Without obtaining an adequate license from the person(s) controlling
66 | the copyright in such materials, this document may not be modified
67 | outside the IETF Standards Process, and derivative works of it may
68 | not be created outside the IETF Standards Process, except to format
69 | it for publication as an RFC or to translate it into languages other
70 | than English.
71 |
72 | Table of Contents
73 |
74 | 1. Introduction ....................................................3
75 | 1.1. Conformance and Error Handling .............................3
76 | 1.2. Syntax Notation ............................................3
77 | 2. Access Authentication Framework .................................3
78 | 2.1. Challenge and Response .....................................3
79 | 2.2. Protection Space (Realm) ...................................5
80 | 3. Status Code Definitions .........................................6
81 | 3.1. 401 Unauthorized ...........................................6
82 | 3.2. 407 Proxy Authentication Required ..........................6
83 | 4. Header Field Definitions ........................................7
84 | 4.1. WWW-Authenticate ...........................................7
85 | 4.2. Authorization ..............................................8
86 | 4.3. Proxy-Authenticate .........................................8
87 | 4.4. Proxy-Authorization ........................................9
88 | 5. IANA Considerations .............................................9
89 | 5.1. Authentication Scheme Registry .............................9
90 | 5.1.1. Procedure ...........................................9
91 | 5.1.2. Considerations for New Authentication Schemes ......10
92 | 5.2. Status Code Registration ..................................11
93 | 5.3. Header Field Registration .................................11
94 | 6. Security Considerations ........................................12
95 | 6.1. Confidentiality of Credentials ............................12
96 | 6.2. Authentication Credentials and Idle Clients ...............12
97 | 6.3. Protection Spaces .........................................13
98 | 7. Acknowledgments ................................................14
99 | 8. References .....................................................14
100 | 8.1. Normative References ......................................14
101 | 8.2. Informative References ....................................14
102 | Appendix A. Changes from RFCs 2616 and 2617 .......................16
103 | Appendix B. Imported ABNF .........................................16
104 | Appendix C. Collected ABNF ........................................17
105 | Index .............................................................18
106 |
107 |
108 |
109 |
110 |
111 |
112 |
113 |
114 | Fielding & Reschke Standards Track [Page 2]
115 |
116 | RFC 7235 HTTP/1.1 Authentication June 2014
117 |
118 |
119 | 1. Introduction
120 |
121 | HTTP provides a general framework for access control and
122 | authentication, via an extensible set of challenge-response
123 | authentication schemes, which can be used by a server to challenge a
124 | client request and by a client to provide authentication information.
125 | This document defines HTTP/1.1 authentication in terms of the
126 | architecture defined in "Hypertext Transfer Protocol (HTTP/1.1):
127 | Message Syntax and Routing" [RFC7230], including the general
128 | framework previously described in "HTTP Authentication: Basic and
129 | Digest Access Authentication" [RFC2617] and the related fields and
130 | status codes previously defined in "Hypertext Transfer Protocol --
131 | HTTP/1.1" [RFC2616].
132 |
133 | The IANA Authentication Scheme Registry (Section 5.1) lists
134 | registered authentication schemes and their corresponding
135 | specifications, including the "basic" and "digest" authentication
136 | schemes previously defined by RFC 2617.
137 |
138 | 1.1. Conformance and Error Handling
139 |
140 | The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT",
141 | "SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" in this
142 | document are to be interpreted as described in [RFC2119].
143 |
144 | Conformance criteria and considerations regarding error handling are
145 | defined in Section 2.5 of [RFC7230].
146 |
147 | 1.2. Syntax Notation
148 |
149 | This specification uses the Augmented Backus-Naur Form (ABNF)
150 | notation of [RFC5234] with a list extension, defined in Section 7 of
151 | [RFC7230], that allows for compact definition of comma-separated
152 | lists using a '#' operator (similar to how the '*' operator indicates
153 | repetition). Appendix B describes rules imported from other
154 | documents. Appendix C shows the collected grammar with all list
155 | operators expanded to standard ABNF notation.
156 |
157 | 2. Access Authentication Framework
158 |
159 | 2.1. Challenge and Response
160 |
161 | HTTP provides a simple challenge-response authentication framework
162 | that can be used by a server to challenge a client request and by a
163 | client to provide authentication information. It uses a case-
164 | insensitive token as a means to identify the authentication scheme,
165 | followed by additional information necessary for achieving
166 |
167 |
168 |
169 |
170 | Fielding & Reschke Standards Track [Page 3]
171 |
172 | RFC 7235 HTTP/1.1 Authentication June 2014
173 |
174 |
175 | authentication via that scheme. The latter can be either a comma-
176 | separated list of parameters or a single sequence of characters
177 | capable of holding base64-encoded information.
178 |
179 | Authentication parameters are name=value pairs, where the name token
180 | is matched case-insensitively, and each parameter name MUST only
181 | occur once per challenge.
182 |
183 | auth-scheme = token
184 |
185 | auth-param = token BWS "=" BWS ( token / quoted-string )
186 |
187 | token68 = 1*( ALPHA / DIGIT /
188 | "-" / "." / "_" / "~" / "+" / "/" ) *"="
189 |
190 | The token68 syntax allows the 66 unreserved URI characters
191 | ([RFC3986]), plus a few others, so that it can hold a base64,
192 | base64url (URL and filename safe alphabet), base32, or base16 (hex)
193 | encoding, with or without padding, but excluding whitespace
194 | ([RFC4648]).
195 |
196 | A 401 (Unauthorized) response message is used by an origin server to
197 | challenge the authorization of a user agent, including a
198 | WWW-Authenticate header field containing at least one challenge
199 | applicable to the requested resource.
200 |
201 | A 407 (Proxy Authentication Required) response message is used by a
202 | proxy to challenge the authorization of a client, including a
203 | Proxy-Authenticate header field containing at least one challenge
204 | applicable to the proxy for the requested resource.
205 |
206 | challenge = auth-scheme [ 1*SP ( token68 / #auth-param ) ]
207 |
208 | Note: Many clients fail to parse a challenge that contains an
209 | unknown scheme. A workaround for this problem is to list well-
210 | supported schemes (such as "basic") first.
211 |
212 | A user agent that wishes to authenticate itself with an origin server
213 | -- usually, but not necessarily, after receiving a 401 (Unauthorized)
214 | -- can do so by including an Authorization header field with the
215 | request.
216 |
217 | A client that wishes to authenticate itself with a proxy -- usually,
218 | but not necessarily, after receiving a 407 (Proxy Authentication
219 | Required) -- can do so by including a Proxy-Authorization header
220 | field with the request.
221 |
222 |
223 |
224 |
225 |
226 | Fielding & Reschke Standards Track [Page 4]
227 |
228 | RFC 7235 HTTP/1.1 Authentication June 2014
229 |
230 |
231 | Both the Authorization field value and the Proxy-Authorization field
232 | value contain the client's credentials for the realm of the resource
233 | being requested, based upon a challenge received in a response
234 | (possibly at some point in the past). When creating their values,
235 | the user agent ought to do so by selecting the challenge with what it
236 | considers to be the most secure auth-scheme that it understands,
237 | obtaining credentials from the user as appropriate. Transmission of
238 | credentials within header field values implies significant security
239 | considerations regarding the confidentiality of the underlying
240 | connection, as described in Section 6.1.
241 |
242 | credentials = auth-scheme [ 1*SP ( token68 / #auth-param ) ]
243 |
244 | Upon receipt of a request for a protected resource that omits
245 | credentials, contains invalid credentials (e.g., a bad password) or
246 | partial credentials (e.g., when the authentication scheme requires
247 | more than one round trip), an origin server SHOULD send a 401
248 | (Unauthorized) response that contains a WWW-Authenticate header field
249 | with at least one (possibly new) challenge applicable to the
250 | requested resource.
251 |
252 | Likewise, upon receipt of a request that omits proxy credentials or
253 | contains invalid or partial proxy credentials, a proxy that requires
254 | authentication SHOULD generate a 407 (Proxy Authentication Required)
255 | response that contains a Proxy-Authenticate header field with at
256 | least one (possibly new) challenge applicable to the proxy.
257 |
258 | A server that receives valid credentials that are not adequate to
259 | gain access ought to respond with the 403 (Forbidden) status code
260 | (Section 6.5.3 of [RFC7231]).
261 |
262 | HTTP does not restrict applications to this simple challenge-response
263 | framework for access authentication. Additional mechanisms can be
264 | used, such as authentication at the transport level or via message
265 | encapsulation, and with additional header fields specifying
266 | authentication information. However, such additional mechanisms are
267 | not defined by this specification.
268 |
269 | 2.2. Protection Space (Realm)
270 |
271 | The "realm" authentication parameter is reserved for use by
272 | authentication schemes that wish to indicate a scope of protection.
273 |
274 | A protection space is defined by the canonical root URI (the scheme
275 | and authority components of the effective request URI; see Section
276 | 5.5 of [RFC7230]) of the server being accessed, in combination with
277 | the realm value if present. These realms allow the protected
278 | resources on a server to be partitioned into a set of protection
279 |
280 |
281 |
282 | Fielding & Reschke Standards Track [Page 5]
283 |
284 | RFC 7235 HTTP/1.1 Authentication June 2014
285 |
286 |
287 | spaces, each with its own authentication scheme and/or authorization
288 | database. The realm value is a string, generally assigned by the
289 | origin server, that can have additional semantics specific to the
290 | authentication scheme. Note that a response can have multiple
291 | challenges with the same auth-scheme but with different realms.
292 |
293 | The protection space determines the domain over which credentials can
294 | be automatically applied. If a prior request has been authorized,
295 | the user agent MAY reuse the same credentials for all other requests
296 | within that protection space for a period of time determined by the
297 | authentication scheme, parameters, and/or user preferences (such as a
298 | configurable inactivity timeout). Unless specifically allowed by the
299 | authentication scheme, a single protection space cannot extend
300 | outside the scope of its server.
301 |
302 | For historical reasons, a sender MUST only generate the quoted-string
303 | syntax. Recipients might have to support both token and
304 | quoted-string syntax for maximum interoperability with existing
305 | clients that have been accepting both notations for a long time.
306 |
307 | 3. Status Code Definitions
308 |
309 | 3.1. 401 Unauthorized
310 |
311 | The 401 (Unauthorized) status code indicates that the request has not
312 | been applied because it lacks valid authentication credentials for
313 | the target resource. The server generating a 401 response MUST send
314 | a WWW-Authenticate header field (Section 4.1) containing at least one
315 | challenge applicable to the target resource.
316 |
317 | If the request included authentication credentials, then the 401
318 | response indicates that authorization has been refused for those
319 | credentials. The user agent MAY repeat the request with a new or
320 | replaced Authorization header field (Section 4.2). If the 401
321 | response contains the same challenge as the prior response, and the
322 | user agent has already attempted authentication at least once, then
323 | the user agent SHOULD present the enclosed representation to the
324 | user, since it usually contains relevant diagnostic information.
325 |
326 | 3.2. 407 Proxy Authentication Required
327 |
328 | The 407 (Proxy Authentication Required) status code is similar to 401
329 | (Unauthorized), but it indicates that the client needs to
330 | authenticate itself in order to use a proxy. The proxy MUST send a
331 | Proxy-Authenticate header field (Section 4.3) containing a challenge
332 | applicable to that proxy for the target resource. The client MAY
333 | repeat the request with a new or replaced Proxy-Authorization header
334 | field (Section 4.4).
335 |
336 |
337 |
338 | Fielding & Reschke Standards Track [Page 6]
339 |
340 | RFC 7235 HTTP/1.1 Authentication June 2014
341 |
342 |
343 | 4. Header Field Definitions
344 |
345 | This section defines the syntax and semantics of header fields
346 | related to the HTTP authentication framework.
347 |
348 | 4.1. WWW-Authenticate
349 |
350 | The "WWW-Authenticate" header field indicates the authentication
351 | scheme(s) and parameters applicable to the target resource.
352 |
353 | WWW-Authenticate = 1#challenge
354 |
355 | A server generating a 401 (Unauthorized) response MUST send a
356 | WWW-Authenticate header field containing at least one challenge. A
357 | server MAY generate a WWW-Authenticate header field in other response
358 | messages to indicate that supplying credentials (or different
359 | credentials) might affect the response.
360 |
361 | A proxy forwarding a response MUST NOT modify any WWW-Authenticate
362 | fields in that response.
363 |
364 | User agents are advised to take special care in parsing the field
365 | value, as it might contain more than one challenge, and each
366 | challenge can contain a comma-separated list of authentication
367 | parameters. Furthermore, the header field itself can occur multiple
368 | times.
369 |
370 | For instance:
371 |
372 | WWW-Authenticate: Newauth realm="apps", type=1,
373 | title="Login to \"apps\"", Basic realm="simple"
374 |
375 | This header field contains two challenges; one for the "Newauth"
376 | scheme with a realm value of "apps", and two additional parameters
377 | "type" and "title", and another one for the "Basic" scheme with a
378 | realm value of "simple".
379 |
380 | Note: The challenge grammar production uses the list syntax as
381 | well. Therefore, a sequence of comma, whitespace, and comma can
382 | be considered either as applying to the preceding challenge, or to
383 | be an empty entry in the list of challenges. In practice, this
384 | ambiguity does not affect the semantics of the header field value
385 | and thus is harmless.
386 |
387 |
388 |
389 |
390 |
391 |
392 |
393 |
394 | Fielding & Reschke Standards Track [Page 7]
395 |
396 | RFC 7235 HTTP/1.1 Authentication June 2014
397 |
398 |
399 | 4.2. Authorization
400 |
401 | The "Authorization" header field allows a user agent to authenticate
402 | itself with an origin server -- usually, but not necessarily, after
403 | receiving a 401 (Unauthorized) response. Its value consists of
404 | credentials containing the authentication information of the user
405 | agent for the realm of the resource being requested.
406 |
407 | Authorization = credentials
408 |
409 | If a request is authenticated and a realm specified, the same
410 | credentials are presumed to be valid for all other requests within
411 | this realm (assuming that the authentication scheme itself does not
412 | require otherwise, such as credentials that vary according to a
413 | challenge value or using synchronized clocks).
414 |
415 | A proxy forwarding a request MUST NOT modify any Authorization fields
416 | in that request. See Section 3.2 of [RFC7234] for details of and
417 | requirements pertaining to handling of the Authorization field by
418 | HTTP caches.
419 |
420 | 4.3. Proxy-Authenticate
421 |
422 | The "Proxy-Authenticate" header field consists of at least one
423 | challenge that indicates the authentication scheme(s) and parameters
424 | applicable to the proxy for this effective request URI (Section 5.5
425 | of [RFC7230]). A proxy MUST send at least one Proxy-Authenticate
426 | header field in each 407 (Proxy Authentication Required) response
427 | that it generates.
428 |
429 | Proxy-Authenticate = 1#challenge
430 |
431 | Unlike WWW-Authenticate, the Proxy-Authenticate header field applies
432 | only to the next outbound client on the response chain. This is
433 | because only the client that chose a given proxy is likely to have
434 | the credentials necessary for authentication. However, when multiple
435 | proxies are used within the same administrative domain, such as
436 | office and regional caching proxies within a large corporate network,
437 | it is common for credentials to be generated by the user agent and
438 | passed through the hierarchy until consumed. Hence, in such a
439 | configuration, it will appear as if Proxy-Authenticate is being
440 | forwarded because each proxy will send the same challenge set.
441 |
442 | Note that the parsing considerations for WWW-Authenticate apply to
443 | this header field as well; see Section 4.1 for details.
444 |
445 |
446 |
447 |
448 |
449 |
450 | Fielding & Reschke Standards Track [Page 8]
451 |
452 | RFC 7235 HTTP/1.1 Authentication June 2014
453 |
454 |
455 | 4.4. Proxy-Authorization
456 |
457 | The "Proxy-Authorization" header field allows the client to identify
458 | itself (or its user) to a proxy that requires authentication. Its
459 | value consists of credentials containing the authentication
460 | information of the client for the proxy and/or realm of the resource
461 | being requested.
462 |
463 | Proxy-Authorization = credentials
464 |
465 | Unlike Authorization, the Proxy-Authorization header field applies
466 | only to the next inbound proxy that demanded authentication using the
467 | Proxy-Authenticate field. When multiple proxies are used in a chain,
468 | the Proxy-Authorization header field is consumed by the first inbound
469 | proxy that was expecting to receive credentials. A proxy MAY relay
470 | the credentials from the client request to the next proxy if that is
471 | the mechanism by which the proxies cooperatively authenticate a given
472 | request.
473 |
474 | 5. IANA Considerations
475 |
476 | 5.1. Authentication Scheme Registry
477 |
478 | The "Hypertext Transfer Protocol (HTTP) Authentication Scheme
479 | Registry" defines the namespace for the authentication schemes in
480 | challenges and credentials. It has been created and is now
481 | maintained at .
482 |
483 | 5.1.1. Procedure
484 |
485 | Registrations MUST include the following fields:
486 |
487 | o Authentication Scheme Name
488 |
489 | o Pointer to specification text
490 |
491 | o Notes (optional)
492 |
493 | Values to be added to this namespace require IETF Review (see
494 | [RFC5226], Section 4.1).
495 |
496 |
497 |
498 |
499 |
500 |
501 |
502 |
503 |
504 |
505 |
506 | Fielding & Reschke Standards Track [Page 9]
507 |
508 | RFC 7235 HTTP/1.1 Authentication June 2014
509 |
510 |
511 | 5.1.2. Considerations for New Authentication Schemes
512 |
513 | There are certain aspects of the HTTP Authentication Framework that
514 | put constraints on how new authentication schemes can work:
515 |
516 | o HTTP authentication is presumed to be stateless: all of the
517 | information necessary to authenticate a request MUST be provided
518 | in the request, rather than be dependent on the server remembering
519 | prior requests. Authentication based on, or bound to, the
520 | underlying connection is outside the scope of this specification
521 | and inherently flawed unless steps are taken to ensure that the
522 | connection cannot be used by any party other than the
523 | authenticated user (see Section 2.3 of [RFC7230]).
524 |
525 | o The authentication parameter "realm" is reserved for defining
526 | protection spaces as described in Section 2.2. New schemes MUST
527 | NOT use it in a way incompatible with that definition.
528 |
529 | o The "token68" notation was introduced for compatibility with
530 | existing authentication schemes and can only be used once per
531 | challenge or credential. Thus, new schemes ought to use the
532 | auth-param syntax instead, because otherwise future extensions
533 | will be impossible.
534 |
535 | o The parsing of challenges and credentials is defined by this
536 | specification and cannot be modified by new authentication
537 | schemes. When the auth-param syntax is used, all parameters ought
538 | to support both token and quoted-string syntax, and syntactical
539 | constraints ought to be defined on the field value after parsing
540 | (i.e., quoted-string processing). This is necessary so that
541 | recipients can use a generic parser that applies to all
542 | authentication schemes.
543 |
544 | Note: The fact that the value syntax for the "realm" parameter is
545 | restricted to quoted-string was a bad design choice not to be
546 | repeated for new parameters.
547 |
548 | o Definitions of new schemes ought to define the treatment of
549 | unknown extension parameters. In general, a "must-ignore" rule is
550 | preferable to a "must-understand" rule, because otherwise it will
551 | be hard to introduce new parameters in the presence of legacy
552 | recipients. Furthermore, it's good to describe the policy for
553 | defining new parameters (such as "update the specification" or
554 | "use this registry").
555 |
556 | o Authentication schemes need to document whether they are usable in
557 | origin-server authentication (i.e., using WWW-Authenticate),
558 | and/or proxy authentication (i.e., using Proxy-Authenticate).
559 |
560 |
561 |
562 | Fielding & Reschke Standards Track [Page 10]
563 |
564 | RFC 7235 HTTP/1.1 Authentication June 2014
565 |
566 |
567 | o The credentials carried in an Authorization header field are
568 | specific to the user agent and, therefore, have the same effect on
569 | HTTP caches as the "private" Cache-Control response directive
570 | (Section 5.2.2.6 of [RFC7234]), within the scope of the request in
571 | which they appear.
572 |
573 | Therefore, new authentication schemes that choose not to carry
574 | credentials in the Authorization header field (e.g., using a newly
575 | defined header field) will need to explicitly disallow caching, by
576 | mandating the use of either Cache-Control request directives
577 | (e.g., "no-store", Section 5.2.1.5 of [RFC7234]) or response
578 | directives (e.g., "private").
579 |
580 | 5.2. Status Code Registration
581 |
582 | The "Hypertext Transfer Protocol (HTTP) Status Code Registry" located
583 | at has been
584 | updated with the registrations below:
585 |
586 | +-------+-------------------------------+-------------+
587 | | Value | Description | Reference |
588 | +-------+-------------------------------+-------------+
589 | | 401 | Unauthorized | Section 3.1 |
590 | | 407 | Proxy Authentication Required | Section 3.2 |
591 | +-------+-------------------------------+-------------+
592 |
593 | 5.3. Header Field Registration
594 |
595 | HTTP header fields are registered within the "Message Headers"
596 | registry maintained at
597 | .
598 |
599 | This document defines the following HTTP header fields, so the
600 | "Permanent Message Header Field Names" registry has been updated
601 | accordingly (see [BCP90]).
602 |
603 | +---------------------+----------+----------+-------------+
604 | | Header Field Name | Protocol | Status | Reference |
605 | +---------------------+----------+----------+-------------+
606 | | Authorization | http | standard | Section 4.2 |
607 | | Proxy-Authenticate | http | standard | Section 4.3 |
608 | | Proxy-Authorization | http | standard | Section 4.4 |
609 | | WWW-Authenticate | http | standard | Section 4.1 |
610 | +---------------------+----------+----------+-------------+
611 |
612 | The change controller is: "IETF (iesg@ietf.org) - Internet
613 | Engineering Task Force".
614 |
615 |
616 |
617 |
618 | Fielding & Reschke Standards Track [Page 11]
619 |
620 | RFC 7235 HTTP/1.1 Authentication June 2014
621 |
622 |
623 | 6. Security Considerations
624 |
625 | This section is meant to inform developers, information providers,
626 | and users of known security concerns specific to HTTP authentication.
627 | More general security considerations are addressed in HTTP messaging
628 | [RFC7230] and semantics [RFC7231].
629 |
630 | Everything about the topic of HTTP authentication is a security
631 | consideration, so the list of considerations below is not exhaustive.
632 | Furthermore, it is limited to security considerations regarding the
633 | authentication framework, in general, rather than discussing all of
634 | the potential considerations for specific authentication schemes
635 | (which ought to be documented in the specifications that define those
636 | schemes). Various organizations maintain topical information and
637 | links to current research on Web application security (e.g.,
638 | [OWASP]), including common pitfalls for implementing and using the
639 | authentication schemes found in practice.
640 |
641 | 6.1. Confidentiality of Credentials
642 |
643 | The HTTP authentication framework does not define a single mechanism
644 | for maintaining the confidentiality of credentials; instead, each
645 | authentication scheme defines how the credentials are encoded prior
646 | to transmission. While this provides flexibility for the development
647 | of future authentication schemes, it is inadequate for the protection
648 | of existing schemes that provide no confidentiality on their own, or
649 | that do not sufficiently protect against replay attacks.
650 | Furthermore, if the server expects credentials that are specific to
651 | each individual user, the exchange of those credentials will have the
652 | effect of identifying that user even if the content within
653 | credentials remains confidential.
654 |
655 | HTTP depends on the security properties of the underlying transport-
656 | or session-level connection to provide confidential transmission of
657 | header fields. In other words, if a server limits access to
658 | authenticated users using this framework, the server needs to ensure
659 | that the connection is properly secured in accordance with the nature
660 | of the authentication scheme used. For example, services that depend
661 | on individual user authentication often require a connection to be
662 | secured with TLS ("Transport Layer Security", [RFC5246]) prior to
663 | exchanging any credentials.
664 |
665 | 6.2. Authentication Credentials and Idle Clients
666 |
667 | Existing HTTP clients and user agents typically retain authentication
668 | information indefinitely. HTTP does not provide a mechanism for the
669 | origin server to direct clients to discard these cached credentials,
670 | since the protocol has no awareness of how credentials are obtained
671 |
672 |
673 |
674 | Fielding & Reschke Standards Track [Page 12]
675 |
676 | RFC 7235 HTTP/1.1 Authentication June 2014
677 |
678 |
679 | or managed by the user agent. The mechanisms for expiring or
680 | revoking credentials can be specified as part of an authentication
681 | scheme definition.
682 |
683 | Circumstances under which credential caching can interfere with the
684 | application's security model include but are not limited to:
685 |
686 | o Clients that have been idle for an extended period, following
687 | which the server might wish to cause the client to re-prompt the
688 | user for credentials.
689 |
690 | o Applications that include a session termination indication (such
691 | as a "logout" or "commit" button on a page) after which the server
692 | side of the application "knows" that there is no further reason
693 | for the client to retain the credentials.
694 |
695 | User agents that cache credentials are encouraged to provide a
696 | readily accessible mechanism for discarding cached credentials under
697 | user control.
698 |
699 | 6.3. Protection Spaces
700 |
701 | Authentication schemes that solely rely on the "realm" mechanism for
702 | establishing a protection space will expose credentials to all
703 | resources on an origin server. Clients that have successfully made
704 | authenticated requests with a resource can use the same
705 | authentication credentials for other resources on the same origin
706 | server. This makes it possible for a different resource to harvest
707 | authentication credentials for other resources.
708 |
709 | This is of particular concern when an origin server hosts resources
710 | for multiple parties under the same canonical root URI (Section 2.2).
711 | Possible mitigation strategies include restricting direct access to
712 | authentication credentials (i.e., not making the content of the
713 | Authorization request header field available), and separating
714 | protection spaces by using a different host name (or port number) for
715 | each party.
716 |
717 |
718 |
719 |
720 |
721 |
722 |
723 |
724 |
725 |
726 |
727 |
728 |
729 |
730 | Fielding & Reschke Standards Track [Page 13]
731 |
732 | RFC 7235 HTTP/1.1 Authentication June 2014
733 |
734 |
735 | 7. Acknowledgments
736 |
737 | This specification takes over the definition of the HTTP
738 | Authentication Framework, previously defined in RFC 2617. We thank
739 | John Franks, Phillip M. Hallam-Baker, Jeffery L. Hostetler, Scott D.
740 | Lawrence, Paul J. Leach, Ari Luotonen, and Lawrence C. Stewart for
741 | their work on that specification. See Section 6 of [RFC2617] for
742 | further acknowledgements.
743 |
744 | See Section 10 of [RFC7230] for the Acknowledgments related to this
745 | document revision.
746 |
747 | 8. References
748 |
749 | 8.1. Normative References
750 |
751 | [RFC2119] Bradner, S., "Key words for use in RFCs to Indicate
752 | Requirement Levels", BCP 14, RFC 2119, March 1997.
753 |
754 | [RFC5234] Crocker, D., Ed. and P. Overell, "Augmented BNF for Syntax
755 | Specifications: ABNF", STD 68, RFC 5234, January 2008.
756 |
757 | [RFC7230] Fielding, R., Ed. and J. Reschke, Ed., "Hypertext Transfer
758 | Protocol (HTTP/1.1): Message Syntax and Routing",
759 | RFC 7230, June 2014.
760 |
761 | [RFC7231] Fielding, R., Ed. and J. Reschke, Ed., "Hypertext Transfer
762 | Protocol (HTTP/1.1): Semantics and Content", RFC 7231,
763 | June 2014.
764 |
765 | [RFC7234] Fielding, R., Ed., Nottingham, M., Ed., and J. Reschke,
766 | Ed., "Hypertext Transfer Protocol (HTTP/1.1): Caching",
767 | RFC 7234, June 2014.
768 |
769 | 8.2. Informative References
770 |
771 | [BCP90] Klyne, G., Nottingham, M., and J. Mogul, "Registration
772 | Procedures for Message Header Fields", BCP 90, RFC 3864,
773 | September 2004.
774 |
775 | [OWASP] van der Stock, A., Ed., "A Guide to Building Secure Web
776 | Applications and Web Services", The Open Web Application
777 | Security Project (OWASP) 2.0.1, July 2005,
778 | .
779 |
780 | [RFC2616] Fielding, R., Gettys, J., Mogul, J., Frystyk, H.,
781 | Masinter, L., Leach, P., and T. Berners-Lee, "Hypertext
782 | Transfer Protocol -- HTTP/1.1", RFC 2616, June 1999.
783 |
784 |
785 |
786 | Fielding & Reschke Standards Track [Page 14]
787 |
788 | RFC 7235 HTTP/1.1 Authentication June 2014
789 |
790 |
791 | [RFC2617] Franks, J., Hallam-Baker, P., Hostetler, J., Lawrence, S.,
792 | Leach, P., Luotonen, A., and L. Stewart, "HTTP
793 | Authentication: Basic and Digest Access Authentication",
794 | RFC 2617, June 1999.
795 |
796 | [RFC3986] Berners-Lee, T., Fielding, R., and L. Masinter, "Uniform
797 | Resource Identifier (URI): Generic Syntax", STD 66,
798 | RFC 3986, January 2005.
799 |
800 | [RFC4648] Josefsson, S., "The Base16, Base32, and Base64 Data
801 | Encodings", RFC 4648, October 2006.
802 |
803 | [RFC5226] Narten, T. and H. Alvestrand, "Guidelines for Writing an
804 | IANA Considerations Section in RFCs", BCP 26, RFC 5226,
805 | May 2008.
806 |
807 | [RFC5246] Dierks, T. and E. Rescorla, "The Transport Layer Security
808 | (TLS) Protocol Version 1.2", RFC 5246, August 2008.
809 |
810 |
811 |
812 |
813 |
814 |
815 |
816 |
817 |
818 |
819 |
820 |
821 |
822 |
823 |
824 |
825 |
826 |
827 |
828 |
829 |
830 |
831 |
832 |
833 |
834 |
835 |
836 |
837 |
838 |
839 |
840 |
841 |
842 | Fielding & Reschke Standards Track [Page 15]
843 |
844 | RFC 7235 HTTP/1.1 Authentication June 2014
845 |
846 |
847 | Appendix A. Changes from RFCs 2616 and 2617
848 |
849 | The framework for HTTP Authentication is now defined by this
850 | document, rather than RFC 2617.
851 |
852 | The "realm" parameter is no longer always required on challenges;
853 | consequently, the ABNF allows challenges without any auth parameters.
854 | (Section 2)
855 |
856 | The "token68" alternative to auth-param lists has been added for
857 | consistency with legacy authentication schemes such as "Basic".
858 | (Section 2)
859 |
860 | This specification introduces the Authentication Scheme Registry,
861 | along with considerations for new authentication schemes.
862 | (Section 5.1)
863 |
864 | Appendix B. Imported ABNF
865 |
866 | The following core rules are included by reference, as defined in
867 | Appendix B.1 of [RFC5234]: ALPHA (letters), CR (carriage return),
868 | CRLF (CR LF), CTL (controls), DIGIT (decimal 0-9), DQUOTE (double
869 | quote), HEXDIG (hexadecimal 0-9/A-F/a-f), LF (line feed), OCTET (any
870 | 8-bit sequence of data), SP (space), and VCHAR (any visible US-ASCII
871 | character).
872 |
873 | The rules below are defined in [RFC7230]:
874 |
875 | BWS =
876 | OWS =
877 | quoted-string =
878 | token =
879 |
880 |
881 |
882 |
883 |
884 |
885 |
886 |
887 |
888 |
889 |
890 |
891 |
892 |
893 |
894 |
895 |
896 |
897 |
898 | Fielding & Reschke Standards Track [Page 16]
899 |
900 | RFC 7235 HTTP/1.1 Authentication June 2014
901 |
902 |
903 | Appendix C. Collected ABNF
904 |
905 | In the collected ABNF below, list rules are expanded as per Section
906 | 1.2 of [RFC7230].
907 |
908 | Authorization = credentials
909 |
910 | BWS =
911 |
912 | OWS =
913 |
914 | Proxy-Authenticate = *( "," OWS ) challenge *( OWS "," [ OWS
915 | challenge ] )
916 | Proxy-Authorization = credentials
917 |
918 | WWW-Authenticate = *( "," OWS ) challenge *( OWS "," [ OWS challenge
919 | ] )
920 |
921 | auth-param = token BWS "=" BWS ( token / quoted-string )
922 | auth-scheme = token
923 |
924 | challenge = auth-scheme [ 1*SP ( token68 / [ ( "," / auth-param ) *(
925 | OWS "," [ OWS auth-param ] ) ] ) ]
926 | credentials = auth-scheme [ 1*SP ( token68 / [ ( "," / auth-param )
927 | *( OWS "," [ OWS auth-param ] ) ] ) ]
928 |
929 | quoted-string =
930 |
931 | token =
932 | token68 = 1*( ALPHA / DIGIT / "-" / "." / "_" / "~" / "+" / "/" )
933 | *"="
934 |
935 |
936 |
937 |
938 |
939 |
940 |
941 |
942 |
943 |
944 |
945 |
946 |
947 |
948 |
949 |
950 |
951 |
952 |
953 |
954 | Fielding & Reschke Standards Track [Page 17]
955 |
956 | RFC 7235 HTTP/1.1 Authentication June 2014
957 |
958 |
959 | Index
960 |
961 | 4
962 | 401 Unauthorized (status code) 6
963 | 407 Proxy Authentication Required (status code) 6
964 |
965 | A
966 | Authorization header field 8
967 |
968 | C
969 | Canonical Root URI 5
970 |
971 | G
972 | Grammar
973 | auth-param 4
974 | auth-scheme 4
975 | Authorization 8
976 | challenge 4
977 | credentials 5
978 | Proxy-Authenticate 8
979 | Proxy-Authorization 9
980 | token68 4
981 | WWW-Authenticate 7
982 |
983 | P
984 | Protection Space 5
985 | Proxy-Authenticate header field 8
986 | Proxy-Authorization header field 9
987 |
988 | R
989 | Realm 5
990 |
991 | W
992 | WWW-Authenticate header field 7
993 |
994 |
995 |
996 |
997 |
998 |
999 |
1000 |
1001 |
1002 |
1003 |
1004 |
1005 |
1006 |
1007 |
1008 |
1009 |
1010 | Fielding & Reschke Standards Track [Page 18]
1011 |
1012 | RFC 7235 HTTP/1.1 Authentication June 2014
1013 |
1014 |
1015 | Authors' Addresses
1016 |
1017 | Roy T. Fielding (editor)
1018 | Adobe Systems Incorporated
1019 | 345 Park Ave
1020 | San Jose, CA 95110
1021 | USA
1022 |
1023 | EMail: fielding@gbiv.com
1024 | URI: http://roy.gbiv.com/
1025 |
1026 |
1027 | Julian F. Reschke (editor)
1028 | greenbytes GmbH
1029 | Hafenweg 16
1030 | Muenster, NW 48155
1031 | Germany
1032 |
1033 | EMail: julian.reschke@greenbytes.de
1034 | URI: http://greenbytes.de/tech/webdav/
1035 |
1036 |
1037 |
1038 |
1039 |
1040 |
1041 |
1042 |
1043 |
1044 |
1045 |
1046 |
1047 |
1048 |
1049 |
1050 |
1051 |
1052 |
1053 |
1054 |
1055 |
1056 |
1057 |
1058 |
1059 |
1060 |
1061 |
1062 |
1063 |
1064 |
1065 |
1066 | Fielding & Reschke Standards Track [Page 19]
1067 |
1068 |
--------------------------------------------------------------------------------