/*
* Zed Attack Proxy (ZAP) and its related class files.
*
* ZAP is an HTTP/HTTPS proxy for assessing web application security.
*
* Copyright 2010 psiinon@gmail.com
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.zaproxy.zap;
import java.io.IOException;
import java.io.InputStream;
import java.net.Socket;
import org.apache.commons.httpclient.Header;
import org.apache.commons.httpclient.HttpConnection;
import org.apache.commons.httpclient.HttpException;
import org.apache.commons.httpclient.HttpState;
import org.apache.commons.httpclient.methods.GetMethod;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.zaproxy.zap.network.ZapHttpParser;
/**
* Do not ignore HTTP status code of 101 and keep {@link Socket} &
* {@link InputStream} open, as 101 states a protocol switch.
*
* Is essential for the WebSockets extension.
* <p>
* Malformed HTTP response header lines are ignored.
* </p>
*/
public class ZapGetMethod extends GetMethod {
private static final Log LOG = LogFactory.getLog(ZapGetMethod.class);
/**
* If we have got an <em>Connection: Upgrade</em>,
* this will be set to the current connection.
*/
private Socket upgradedSocket;
/**
* If we have got an <em>Connection: Upgrade</em>,
* this will be set to the input stream of the upgraded socket.
*/
private InputStream inputStream;
/**
* Constructor.
*/
public ZapGetMethod() {
super();
}
/**
* Constructor.
*
* @param uri the request URI
*/
public ZapGetMethod(String uri) {
super(uri);
}
/**
* Allow response code 101, that is Switching Protocols.
*
* @see GetMethod#readResponse(HttpState, HttpConnection)
*/
@Override
protected void readResponse(HttpState state, HttpConnection conn)
throws IOException, HttpException {
LOG.trace("enter HttpMethodBase.readResponse(HttpState, HttpConnection)");
boolean isUpgrade = false;
while (getStatusLine() == null) {
readStatusLine(state, conn);
processStatusLine(state, conn);
readResponseHeaders(state, conn);
processResponseHeaders(state, conn);
int status = this.statusLine.getStatusCode();
if (status == 101) {
LOG.debug("Retrieved HTTP status code '101 Switching Protocols'. Keep connection open!");
// This means the requester has asked the server to switch protocols
// and the server is acknowledging that it will do so
// e.g.: upgrade to websocket
if (conn instanceof ZapHttpConnection) {
isUpgrade = true;
// avoid connection release of HttpClient library
conn.setHttpConnectionManager(null);
}
} else if ((status >= 100) && (status < 200)) {
if (LOG.isInfoEnabled()) {
LOG.info("Discarding unexpected response: " + this.statusLine.toString());
}
this.statusLine = null;
}
}
// get socket and input stream out of HttpClient
if (conn instanceof ZapHttpConnection) {
ZapHttpConnection zapConn = (ZapHttpConnection) conn;
upgradedSocket = zapConn.getSocket();
inputStream = zapConn.getResponseInputStream();
}
if (!isUpgrade) {
// read & process rest of response
// only if connection should not be kept
readResponseBody(state, conn);
processResponseBody(state, conn);
}
}
/**
* If this response included the header <em>Connection: Upgrade</em>, then
* this method provides the corresponding connection.
*
* @return Upgraded Socket or null
*/
public Socket getUpgradedConnection() {
return upgradedSocket;
}
/**
* If this response included the header <em>Connection: Upgrade</em>, then
* this method provides the corresponding input stream.
*
* It might happen, that WebSocket frames are sent directly after the
* WebSocket handshake response. In this case the frames are buffered in
* that stream.
*
* @return Input stream from response or null
*/
public InputStream getUpgradedInputStream() {
return inputStream;
}
/**
* Avoid releasing connection on event stream that is used in Server-Sent
* Events.
*/
@Override
public void releaseConnection() {
Header header = getResponseHeader("content-type");
if (header != null) {
String contentTypeHeader = header.getValue();
if (contentTypeHeader != null && contentTypeHeader.equals("text/event-stream")) {
return;
}
}
super.releaseConnection();
}
/**
* {@inheritDoc}
*
* <strong>Note:</strong> Malformed HTTP header lines are ignored (instead of throwing an exception).
*/
/*
* Implementation copied from HttpMethodBase#readResponseHeaders(HttpState, HttpConnection) but changed to use a custom
* header parser (ZapHttpParser#parseHeaders(InputStream, String)).
*/
@Override
protected void readResponseHeaders(HttpState state, HttpConnection conn) throws IOException, HttpException {
getResponseHeaderGroup().clear();
Header[] headers = ZapHttpParser.parseHeaders(conn.getResponseInputStream(), getParams().getHttpElementCharset());
// Wire logging moved to HttpParser
getResponseHeaderGroup().setHeaders(headers);
}
}