blob: fc9f5f3e28614ce97ab59c2268147e4718c25467 [file] [log] [blame]
// Copyright (c) 2011 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "net/tools/fetch/http_listen_socket.h"
#include <map>
#include "base/compiler_specific.h"
#include "base/logging.h"
#include "base/message_loop.h"
#include "base/string_number_conversions.h"
#include "net/tools/fetch/http_server_request_info.h"
#include "net/tools/fetch/http_server_response_info.h"
// must run in the IO thread
HttpListenSocket::HttpListenSocket(SOCKET s,
HttpListenSocket::Delegate* delegate)
: ALLOW_THIS_IN_INITIALIZER_LIST(net::ListenSocket(s, this)),
delegate_(delegate) {
}
// must run in the IO thread
HttpListenSocket::~HttpListenSocket() {
}
void HttpListenSocket::Listen() {
net::ListenSocket::Listen();
}
void HttpListenSocket::Accept() {
SOCKET conn = net::ListenSocket::Accept(socket_);
DCHECK_NE(conn, net::ListenSocket::kInvalidSocket);
if (conn == net::ListenSocket::kInvalidSocket) {
// TODO
} else {
scoped_refptr<HttpListenSocket> sock(
new HttpListenSocket(conn, delegate_));
// it's up to the delegate to AddRef if it wants to keep it around
DidAccept(this, sock);
}
}
HttpListenSocket* HttpListenSocket::Listen(
const std::string& ip,
int port,
HttpListenSocket::Delegate* delegate) {
SOCKET s = net::ListenSocket::Listen(ip, port);
if (s == net::ListenSocket::kInvalidSocket) {
// TODO (ibrar): error handling
} else {
HttpListenSocket *serv = new HttpListenSocket(s, delegate);
serv->Listen();
return serv;
}
return NULL;
}
//
// HTTP Request Parser
// This HTTP request parser uses a simple state machine to quickly parse
// through the headers. The parser is not 100% complete, as it is designed
// for use in this simple test driver.
//
// Known issues:
// - does not handle whitespace on first HTTP line correctly. Expects
// a single space between the method/url and url/protocol.
// Input character types.
enum header_parse_inputs {
INPUT_SPACE,
INPUT_CR,
INPUT_LF,
INPUT_COLON,
INPUT_DEFAULT,
MAX_INPUTS
};
// Parser states.
enum header_parse_states {
ST_METHOD, // Receiving the method
ST_URL, // Receiving the URL
ST_PROTO, // Receiving the protocol
ST_HEADER, // Starting a Request Header
ST_NAME, // Receiving a request header name
ST_SEPARATOR, // Receiving the separator between header name and value
ST_VALUE, // Receiving a request header value
ST_DONE, // Parsing is complete and successful
ST_ERR, // Parsing encountered invalid syntax.
MAX_STATES
};
// State transition table
int parser_state[MAX_STATES][MAX_INPUTS] = {
/* METHOD */ { ST_URL, ST_ERR, ST_ERR, ST_ERR, ST_METHOD },
/* URL */ { ST_PROTO, ST_ERR, ST_ERR, ST_URL, ST_URL },
/* PROTOCOL */ { ST_ERR, ST_HEADER, ST_NAME, ST_ERR, ST_PROTO },
/* HEADER */ { ST_ERR, ST_ERR, ST_NAME, ST_ERR, ST_ERR },
/* NAME */ { ST_SEPARATOR, ST_DONE, ST_ERR, ST_SEPARATOR, ST_NAME },
/* SEPARATOR */ { ST_SEPARATOR, ST_ERR, ST_ERR, ST_SEPARATOR, ST_VALUE },
/* VALUE */ { ST_VALUE, ST_HEADER, ST_NAME, ST_VALUE, ST_VALUE },
/* DONE */ { ST_DONE, ST_DONE, ST_DONE, ST_DONE, ST_DONE },
/* ERR */ { ST_ERR, ST_ERR, ST_ERR, ST_ERR, ST_ERR }
};
// Convert an input character to the parser's input token.
int charToInput(char ch) {
switch(ch) {
case ' ':
return INPUT_SPACE;
case '\r':
return INPUT_CR;
case '\n':
return INPUT_LF;
case ':':
return INPUT_COLON;
}
return INPUT_DEFAULT;
}
HttpServerRequestInfo* HttpListenSocket::ParseHeaders() {
int pos = 0;
int data_len = recv_data_.length();
int state = ST_METHOD;
HttpServerRequestInfo* info = new HttpServerRequestInfo();
std::string buffer;
std::string header_name;
std::string header_value;
while (pos < data_len) {
char ch = recv_data_[pos++];
int input = charToInput(ch);
int next_state = parser_state[state][input];
bool transition = (next_state != state);
if (transition) {
// Do any actions based on state transitions.
switch (state) {
case ST_METHOD:
info->method = buffer;
buffer.clear();
break;
case ST_URL:
info->url = GURL(buffer);
buffer.clear();
break;
case ST_PROTO:
// TODO(mbelshe): Deal better with parsing protocol.
DCHECK(buffer == "HTTP/1.1");
buffer.clear();
break;
case ST_NAME:
header_name = buffer;
buffer.clear();
break;
case ST_VALUE:
header_value = buffer;
// TODO(mbelshe): Deal better with duplicate headers
DCHECK(info->headers.find(header_name) == info->headers.end());
info->headers[header_name] = header_value;
buffer.clear();
break;
}
state = next_state;
} else {
// Do any actions based on current state
switch (state) {
case ST_METHOD:
case ST_URL:
case ST_PROTO:
case ST_VALUE:
case ST_NAME:
buffer.append(&ch, 1);
break;
case ST_DONE:
recv_data_ = recv_data_.substr(pos);
return info;
case ST_ERR:
delete info;
return NULL;
}
}
}
// No more characters, but we haven't finished parsing yet.
delete info;
return NULL;
}
void HttpListenSocket::DidAccept(net::ListenSocket* server,
net::ListenSocket* connection) {
connection->AddRef();
}
void HttpListenSocket::DidRead(net::ListenSocket* connection,
const char* data,
int len) {
recv_data_.append(data, len);
while (recv_data_.length()) {
HttpServerRequestInfo* request = ParseHeaders();
if (!request)
break;
delegate_->OnRequest(this, request);
delete request;
}
}
void HttpListenSocket::DidClose(net::ListenSocket* sock) {
sock->Release();
}
// Convert the numeric status code to a string.
// e.g. 200 -> "200 OK"
std::string ServerStatus(int code) {
switch(code) {
case 200:
return std::string("200 OK");
// TODO(mbelshe): handle other codes.
}
NOTREACHED();
return std::string();
}
void HttpListenSocket::Respond(HttpServerResponseInfo* info,
std::string& data) {
std::string response;
// status line
response = info->protocol + " ";
response += ServerStatus(info->status);
response += "\r\n";
// standard headers
if (info->content_type.length())
response += "Content-type: " + info->content_type + "\r\n";
if (info->content_length > 0)
response += "Content-length: " + base::IntToString(info->content_length) +
"\r\n";
if (info->connection_close)
response += "Connection: close\r\n";
// TODO(mbelshe): support additional headers
// End of headers
response += "\r\n";
// Add data
response += data;
// Write it all out.
this->Send(response, false);
}