1/*
2 * Copyright (C) 2011 Google Inc.
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.google.mockwebserver;
18
19import java.io.UnsupportedEncodingException;
20import java.net.Socket;
21import java.security.Principal;
22import java.security.cert.Certificate;
23import java.util.ArrayList;
24import java.util.List;
25
26import javax.net.ssl.SSLPeerUnverifiedException;
27import javax.net.ssl.SSLSession;
28import javax.net.ssl.SSLSocket;
29
30/**
31 * An HTTP request that came into the mock web server.
32 */
33public final class RecordedRequest {
34    private final String requestLine;
35    private final String method;
36    private final String path;
37    private final List<String> headers;
38    private final List<Integer> chunkSizes;
39    private final int bodySize;
40    private final byte[] body;
41    private final int sequenceNumber;
42    private final String sslProtocol;
43    private final String sslCipherSuite;
44    private final Principal sslLocalPrincipal;
45    private final Principal sslPeerPrincipal;
46    private final Certificate[] sslLocalCertificates;
47    private final Certificate[] sslPeerCertificates;
48
49    public RecordedRequest(String requestLine, List<String> headers, List<Integer> chunkSizes,
50            int bodySize, byte[] body, int sequenceNumber, Socket socket) {
51        this.requestLine = requestLine;
52        this.headers = headers;
53        this.chunkSizes = chunkSizes;
54        this.bodySize = bodySize;
55        this.body = body;
56        this.sequenceNumber = sequenceNumber;
57
58        if (socket instanceof SSLSocket) {
59            SSLSocket sslSocket = (SSLSocket) socket;
60            SSLSession session = sslSocket.getSession();
61            sslProtocol = session.getProtocol();
62            sslCipherSuite = session.getCipherSuite();
63            sslLocalPrincipal = session.getLocalPrincipal();
64            sslLocalCertificates = session.getLocalCertificates();
65            Principal peerPrincipal = null;
66            Certificate[] peerCertificates = null;
67            try {
68                peerPrincipal = session.getPeerPrincipal();
69                peerCertificates = session.getPeerCertificates();
70            } catch (SSLPeerUnverifiedException e) {
71                // No-op: use nulls instead
72            }
73            sslPeerPrincipal = peerPrincipal;
74            sslPeerCertificates = peerCertificates;
75        } else {
76            sslProtocol = null;
77            sslCipherSuite = null;
78            sslLocalPrincipal = null;
79            sslLocalCertificates = null;
80            sslPeerPrincipal = null;
81            sslPeerCertificates = null;
82        }
83
84        if (requestLine != null) {
85            int methodEnd = requestLine.indexOf(' ');
86            int pathEnd = requestLine.indexOf(' ', methodEnd + 1);
87            this.method = requestLine.substring(0, methodEnd);
88            this.path = requestLine.substring(methodEnd + 1, pathEnd);
89        } else {
90            this.method = null;
91            this.path = null;
92        }
93    }
94
95    public String getRequestLine() {
96        return requestLine;
97    }
98
99    public String getMethod() {
100        return method;
101    }
102
103    public String getPath() {
104        return path;
105    }
106
107    /**
108     * Returns all headers.
109     */
110    public List<String> getHeaders() {
111        return headers;
112    }
113
114    /**
115     * Returns the first header named {@code name}, or null if no such header
116     * exists.
117     */
118    public String getHeader(String name) {
119        name += ":";
120        for (String header : headers) {
121            if (name.regionMatches(true, 0, header, 0, name.length())) {
122                return header.substring(name.length()).trim();
123            }
124        }
125        return null;
126    }
127
128    /**
129     * Returns the headers named {@code name}.
130     */
131    public List<String> getHeaders(String name) {
132        List<String> result = new ArrayList<String>();
133        name += ":";
134        for (String header : headers) {
135            if (name.regionMatches(true, 0, header, 0, name.length())) {
136                result.add(header.substring(name.length()).trim());
137            }
138        }
139        return result;
140    }
141
142    /**
143     * Returns the sizes of the chunks of this request's body, or an empty list
144     * if the request's body was empty or unchunked.
145     */
146    public List<Integer> getChunkSizes() {
147        return chunkSizes;
148    }
149
150    /**
151     * Returns the total size of the body of this POST request (before
152     * truncation).
153     */
154    public int getBodySize() {
155        return bodySize;
156    }
157
158    /**
159     * Returns the body of this POST request. This may be truncated.
160     */
161    public byte[] getBody() {
162        return body;
163    }
164
165    /**
166     * Returns the body of this POST request decoded as a UTF-8 string.
167     */
168    public String getUtf8Body() {
169        try {
170            return new String(body, "UTF-8");
171        } catch (UnsupportedEncodingException e) {
172            throw new AssertionError();
173        }
174    }
175
176    /**
177     * Returns the index of this request on its HTTP connection. Since a single
178     * HTTP connection may serve multiple requests, each request is assigned its
179     * own sequence number.
180     */
181    public int getSequenceNumber() {
182        return sequenceNumber;
183    }
184
185    /**
186     * Returns the SSL connection's protocol like {@code TLSv1}, {@code SSLv3},
187     * {@code NONE} or {@code null} if the connection doesn't use SSL.
188     */
189    public String getSslProtocol() {
190        return sslProtocol;
191    }
192
193    /**
194     * Returns the SSL connection's cipher protocol retrieved using
195     * {@code sslSocket.getSession().getCipherSuite()} or {@code null} if the connection doesn't
196     * use SSL.
197     */
198    public String getSslCipherSuite() {
199        return sslCipherSuite;
200    }
201
202    /**
203     * Returns the SSL connection's local principal retrieved using
204     * {@code sslSocket.getSession().getLocalPrincipal()} or {@code null} if the connection doesn't
205     * use SSL.
206     */
207    public Principal getSslLocalPrincipal() {
208        return sslLocalPrincipal;
209    }
210
211    /**
212     * Returns the SSL connection's local certificates retrieved using
213     * {@code sslSocket.getSession().getLocalCertificates()} or {@code null} if the connection
214     * doesn't use SSL.
215     */
216    public Certificate[] getSslLocalCertificates() {
217        return sslLocalCertificates;
218    }
219
220    /**
221     * Returns the SSL connection's peer principal retrieved using
222     * {@code sslSocket.getSession().getPeerPrincipal()}, or {@code null} if the connection doesn't
223     * use SSL or the peer has not been verified.
224     */
225    public Principal getSslPeerPrincipal() {
226        return sslPeerPrincipal;
227    }
228
229    /**
230     * Returns the SSL connection's peer certificates retrieved using
231     * {@code sslSocket.getSession().getPeerCertificates()}, or {@code null} if the connection
232     * doesn't use SSL or the peer has not been verified.
233     */
234    public Certificate[] getSslPeerCertificates() {
235        return sslPeerCertificates;
236    }
237
238    @Override public String toString() {
239        return "RecordedRequest {" + requestLine + "}";
240    }
241}
242