#include "common.h"
#include "eloop.h"
+#include "httpread.h"
#include "http_server.h"
+#define HTTP_SERVER_TIMEOUT 30
+#define HTTP_SERVER_MAX_REQ_LEN 8000
+#define HTTP_SERVER_MAX_CONNECTIONS 10
+
+struct http_request {
+ struct http_request *next;
+ struct http_server *srv;
+ int fd;
+ struct sockaddr_in cli;
+ struct httpread *hread;
+};
struct http_server {
- void (*cb)(void *ctx, int fd, struct sockaddr_in *addr);
+ void (*cb)(void *ctx, struct http_request *req);
void *cb_ctx;
int fd;
int port;
+
+ struct http_request *requests;
+ unsigned int request_count;
};
+static void http_request_cb(struct httpread *handle, void *cookie,
+ enum httpread_event en)
+{
+ struct http_request *req = cookie;
+ struct http_server *srv = req->srv;
+
+ if (en == HTTPREAD_EVENT_FILE_READY) {
+ wpa_printf(MSG_DEBUG, "HTTP: Request from %s:%d received",
+ inet_ntoa(req->cli.sin_addr),
+ ntohs(req->cli.sin_port));
+ srv->cb(srv->cb_ctx, req);
+ return;
+ }
+ wpa_printf(MSG_DEBUG, "HTTP: Request from %s:%d could not be received "
+ "completely", inet_ntoa(req->cli.sin_addr),
+ ntohs(req->cli.sin_port));
+ http_request_deinit(req);
+}
+
+
+static struct http_request * http_request_init(struct http_server *srv, int fd,
+ struct sockaddr_in *cli)
+{
+ struct http_request *req;
+
+ if (srv->request_count >= HTTP_SERVER_MAX_CONNECTIONS) {
+ wpa_printf(MSG_DEBUG, "HTTP: Too many concurrent requests");
+ return NULL;
+ }
+
+ req = os_zalloc(sizeof(*req));
+ if (req == NULL)
+ return NULL;
+
+ req->srv = srv;
+ req->fd = fd;
+ req->cli = *cli;
+
+ req->hread = httpread_create(req->fd, http_request_cb, req,
+ HTTP_SERVER_MAX_REQ_LEN,
+ HTTP_SERVER_TIMEOUT);
+ if (req->hread == NULL) {
+ http_request_deinit(req);
+ return NULL;
+ }
+
+ return req;
+}
+
+
+void http_request_deinit(struct http_request *req)
+{
+ struct http_request *r, *p;
+ struct http_server *srv;
+
+ if (req == NULL)
+ return;
+
+ srv = req->srv;
+ p = NULL;
+ r = srv->requests;
+ while (r) {
+ if (r == req) {
+ if (p)
+ p->next = r->next;
+ else
+ srv->requests = r->next;
+ srv->request_count--;
+ break;
+ }
+ p = r;
+ r = r->next;
+ }
+
+ httpread_destroy(req->hread);
+ close(req->fd);
+ os_free(req);
+}
+
+
+static void http_request_free_all(struct http_request *req)
+{
+ struct http_request *prev;
+ while (req) {
+ prev = req;
+ req = req->next;
+ http_request_deinit(prev);
+ }
+}
+
+
+void http_request_send(struct http_request *req, struct wpabuf *resp)
+{
+ int res;
+
+ wpa_printf(MSG_DEBUG, "HTTP: Send %lu byte response to %s:%d",
+ wpabuf_len(resp), inet_ntoa(req->cli.sin_addr),
+ ntohs(req->cli.sin_port));
+
+ res = send(req->fd, wpabuf_head(resp), wpabuf_len(resp), 0);
+ if (res < 0) {
+ wpa_printf(MSG_DEBUG, "HTTP: Send failed: %s",
+ strerror(errno));
+ } else if ((size_t) res < wpabuf_len(resp)) {
+ wpa_printf(MSG_DEBUG, "HTTP: Sent only %d of %lu bytes",
+ res, (unsigned long) wpabuf_len(resp));
+ /* TODO: add eloop handler for sending rest of the data */
+ }
+
+ wpabuf_free(resp);
+}
+
+
+void http_request_send_and_deinit(struct http_request *req,
+ struct wpabuf *resp)
+{
+ http_request_send(req, resp);
+ http_request_deinit(req);
+}
+
+
+enum httpread_hdr_type http_request_get_type(struct http_request *req)
+{
+ return httpread_hdr_type_get(req->hread);
+}
+
+
+char * http_request_get_uri(struct http_request *req)
+{
+ return httpread_uri_get(req->hread);
+}
+
+
+char * http_request_get_hdr(struct http_request *req)
+{
+ return httpread_hdr_get(req->hread);
+}
+
+
+char * http_request_get_data(struct http_request *req)
+{
+ return httpread_data_get(req->hread);
+}
+
+
+char * http_request_get_hdr_line(struct http_request *req, const char *tag)
+{
+ return httpread_hdr_line_get(req->hread, tag);
+}
+
+
+struct sockaddr_in * http_request_get_cli_addr(struct http_request *req)
+{
+ return &req->cli;
+}
+
+
static void http_server_cb(int sd, void *eloop_ctx, void *sock_ctx)
{
struct sockaddr_in addr;
socklen_t addr_len = sizeof(addr);
struct http_server *srv = eloop_ctx;
int conn;
+ struct http_request *req;
conn = accept(srv->fd, (struct sockaddr *) &addr, &addr_len);
if (conn < 0) {
}
wpa_printf(MSG_DEBUG, "HTTP: Connection from %s:%d",
inet_ntoa(addr.sin_addr), ntohs(addr.sin_port));
- srv->cb(srv->cb_ctx, conn, &addr);
+
+ req = http_request_init(srv, conn, &addr);
+ if (req == NULL) {
+ close(conn);
+ return;
+ }
+
+ req->next = srv->requests;
+ srv->requests = req;
+ srv->request_count++;
}
struct http_server * http_server_init(struct in_addr *addr, int port,
- void (*cb)(void *ctx, int fd,
- struct sockaddr_in *addr),
+ void (*cb)(void *ctx,
+ struct http_request *req),
void *cb_ctx)
{
struct sockaddr_in sin;
eloop_unregister_sock(srv->fd, EVENT_TYPE_READ);
close(srv->fd);
}
+ http_request_free_all(srv->requests);
os_free(srv);
}
"Connection: close\r\n";
/*
- * Incoming web connections are recorded in this struct.
- * A web connection is a TCP connection to us, the server;
- * it is called a "web connection" because we use http and serve
- * data that looks like web pages.
- * State information is need to track the connection until we figure
- * out what they want and what we want to do about it.
- */
-struct web_connection {
- /* double linked list */
- struct web_connection *next;
- struct web_connection *prev;
- struct upnp_wps_device_sm *sm; /* parent */
- int sd; /* socket to read from */
- struct sockaddr_in cli_addr;
- int sd_registered; /* nonzero if we must cancel registration */
- struct httpread *hread; /* state machine for reading socket */
- int n_rcvd_data; /* how much data read so far */
- int done; /* internal flag, set when we've finished */
-};
-
-
-/*
* "Files" that we serve via HTTP. The format of these files is given by
* WFA WPS specifications. Extra white space has been removed to save space.
*/
}
-void web_connection_stop(struct web_connection *c)
-{
- struct upnp_wps_device_sm *sm = c->sm;
-
- httpread_destroy(c->hread);
- c->hread = NULL;
- close(c->sd);
- c->sd = -1;
- if (c->next == c) {
- sm->web_connections = NULL;
- } else {
- if (sm->web_connections == c)
- sm->web_connections = c->next;
- c->next->prev = c->prev;
- c->prev->next = c->next;
- }
- os_free(c);
- sm->n_web_connections--;
-}
-
-
static void http_put_reply_code(struct wpabuf *buf, enum http_reply_code code)
{
wpabuf_put_str(buf, "HTTP/1.1 ");
* Per RFC 2616, content-length: is not required but connection:close
* would appear to be required (given that we will be closing it!).
*/
-static void web_connection_parse_get(struct web_connection *c, char *filename)
+static void web_connection_parse_get(struct upnp_wps_device_sm *sm,
+ struct http_request *hreq, char *filename)
{
- struct upnp_wps_device_sm *sm = c->sm;
struct wpabuf *buf; /* output buffer, allocated */
char *put_length_here;
char *body_start;
wpa_printf(MSG_DEBUG, "WPS UPnP: HTTP GET file not found: %s",
filename);
buf = wpabuf_alloc(200);
- if (buf == NULL)
+ if (buf == NULL) {
+ http_request_deinit(hreq);
return;
+ }
wpabuf_put_str(buf,
"HTTP/1.1 404 Not Found\r\n"
"Connection: close\r\n");
}
buf = wpabuf_alloc(1000 + extra_len);
- if (buf == NULL)
+ if (buf == NULL) {
+ http_request_deinit(hreq);
return;
+ }
wpabuf_put_str(buf,
"HTTP/1.1 200 OK\r\n"
os_memcpy(put_length_here, len_buf, os_strlen(len_buf));
send_buf:
- send_wpabuf(c->sd, buf);
- wpabuf_free(buf);
+ http_request_send_and_deinit(hreq, buf);
}
"</detail>\n"
"</s:Fault>\n";
-static void web_connection_send_reply(struct web_connection *c,
+static void web_connection_send_reply(struct http_request *req,
enum http_reply_code ret,
const char *action, int action_len,
const struct wpabuf *reply,
if (buf == NULL) {
wpa_printf(MSG_INFO, "WPS UPnP: Cannot allocate reply to "
"POST");
- wpabuf_free(buf);
os_free(replydata);
+ http_request_deinit(req);
return;
}
os_memcpy(put_length_here, len_buf, os_strlen(len_buf));
}
- send_wpabuf(c->sd, buf);
- wpabuf_free(buf);
+ http_request_send_and_deinit(req, buf);
}
-static const char * web_get_action(struct web_connection *c,
+static const char * web_get_action(struct http_request *req,
const char *filename, size_t *action_len)
{
const char *match;
return NULL;
}
/* The SOAPAction line of the header tells us what we want to do */
- b = httpread_hdr_line_get(c->hread, "SOAPAction:");
+ b = http_request_get_hdr_line(req, "SOAPAction:");
if (b == NULL)
return NULL;
if (*b == '"')
* Per RFC 2616, content-length: is not required but connection:close
* would appear to be required (given that we will be closing it!).
*/
-static void web_connection_parse_post(struct web_connection *c,
+static void web_connection_parse_post(struct upnp_wps_device_sm *sm,
+ struct http_request *req,
const char *filename)
{
enum http_reply_code ret;
- struct upnp_wps_device_sm *sm = c->sm;
- char *data = httpread_data_get(c->hread); /* body of http msg */
+ char *data = http_request_get_data(req); /* body of http msg */
const char *action;
size_t action_len;
const char *replyname = NULL; /* argument name for the reply */
struct wpabuf *reply = NULL; /* data for the reply */
ret = UPNP_INVALID_ACTION;
- action = web_get_action(c, filename, &action_len);
+ action = web_get_action(req, filename, &action_len);
if (action == NULL)
goto bad;
bad:
if (ret != HTTP_OK)
wpa_printf(MSG_INFO, "WPS UPnP: POST failure ret=%d", ret);
- web_connection_send_reply(c, ret, action, action_len, reply,
+ web_connection_send_reply(req, ret, action, action_len, reply,
replyname);
wpabuf_free(reply);
}
* Per RFC 2616, content-length: is not required but connection:close
* would appear to be required (given that we will be closing it!).
*/
-static void web_connection_parse_subscribe(struct web_connection *c,
+static void web_connection_parse_subscribe(struct upnp_wps_device_sm *sm,
+ struct http_request *req,
const char *filename)
{
- struct upnp_wps_device_sm *sm = c->sm;
struct wpabuf *buf;
char *b;
- char *hdr = httpread_hdr_get(c->hread);
+ char *hdr = http_request_get_hdr(req);
char *h;
char *match;
int match_len;
enum http_reply_code ret = HTTP_INTERNAL_SERVER_ERROR;
buf = wpabuf_alloc(1000);
- if (buf == NULL)
+ if (buf == NULL) {
+ http_request_deinit(req);
return;
+ }
/* Parse/validate headers */
h = hdr;
/* And empty line to terminate header: */
wpabuf_put_str(buf, "\r\n");
- send_wpabuf(c->sd, buf);
- wpabuf_free(buf);
os_free(callback_urls);
+ http_request_send_and_deinit(req, buf);
return;
error:
* 599 Too many subscriptions (not a standard HTTP error)
*/
http_put_empty(buf, ret);
- send_wpabuf(c->sd, buf);
- wpabuf_free(buf);
+ http_request_send_and_deinit(req, buf);
}
* Per RFC 2616, content-length: is not required but connection:close
* would appear to be required (given that we will be closing it!).
*/
-static void web_connection_parse_unsubscribe(struct web_connection *c,
+static void web_connection_parse_unsubscribe(struct upnp_wps_device_sm *sm,
+ struct http_request *req,
const char *filename)
{
- struct upnp_wps_device_sm *sm = c->sm;
struct wpabuf *buf;
- char *hdr = httpread_hdr_get(c->hread);
+ char *hdr = http_request_get_hdr(req);
char *h;
char *match;
int match_len;
send_msg:
buf = wpabuf_alloc(200);
- if (buf == NULL)
+ if (buf == NULL) {
+ http_request_deinit(req);
return;
+ }
http_put_empty(buf, ret);
- send_wpabuf(c->sd, buf);
- wpabuf_free(buf);
+ http_request_send_and_deinit(req, buf);
}
/* Send error in response to unknown requests */
-static void web_connection_unimplemented(struct web_connection *c)
+static void web_connection_unimplemented(struct http_request *req)
{
struct wpabuf *buf;
buf = wpabuf_alloc(200);
- if (buf == NULL)
+ if (buf == NULL) {
+ http_request_deinit(req);
return;
+ }
http_put_empty(buf, HTTP_UNIMPLEMENTED);
- send_wpabuf(c->sd, buf);
- wpabuf_free(buf);
+ http_request_send_and_deinit(req, buf);
}
/* Called when we have gotten an apparently valid http request.
*/
-static void web_connection_check_data(struct web_connection *c)
+static void web_connection_check_data(void *ctx, struct http_request *req)
{
- struct httpread *hread = c->hread;
- enum httpread_hdr_type htype = httpread_hdr_type_get(hread);
- /* char *data = httpread_data_get(hread); */
- char *filename = httpread_uri_get(hread);
+ struct upnp_wps_device_sm *sm = ctx;
+ enum httpread_hdr_type htype = http_request_get_type(req);
+ char *filename = http_request_get_uri(req);
+ struct sockaddr_in *cli = http_request_get_cli_addr(req);
- c->done = 1;
if (!filename) {
wpa_printf(MSG_INFO, "WPS UPnP: Could not get HTTP URI");
+ http_request_deinit(req);
return;
}
/* Trim leading slashes from filename */
filename++;
wpa_printf(MSG_DEBUG, "WPS UPnP: Got HTTP request type %d from %s:%d",
- htype, inet_ntoa(c->cli_addr.sin_addr),
- htons(c->cli_addr.sin_port));
+ htype, inet_ntoa(cli->sin_addr), htons(cli->sin_port));
switch (htype) {
case HTTPREAD_HDR_TYPE_GET:
- web_connection_parse_get(c, filename);
+ web_connection_parse_get(sm, req, filename);
break;
case HTTPREAD_HDR_TYPE_POST:
- web_connection_parse_post(c, filename);
+ web_connection_parse_post(sm, req, filename);
break;
case HTTPREAD_HDR_TYPE_SUBSCRIBE:
- web_connection_parse_subscribe(c, filename);
+ web_connection_parse_subscribe(sm, req, filename);
break;
case HTTPREAD_HDR_TYPE_UNSUBSCRIBE:
- web_connection_parse_unsubscribe(c, filename);
+ web_connection_parse_unsubscribe(sm, req, filename);
break;
+
/* We are not required to support M-POST; just plain
* POST is supposed to work, so we only support that.
* If for some reason we need to support M-POST, it is
*/
default:
/* Send 501 for anything else */
- web_connection_unimplemented(c);
- break;
- }
-}
-
-
-
-/* called back when we have gotten request */
-static void web_connection_got_file_handler(struct httpread *handle,
- void *cookie,
- enum httpread_event en)
-{
- struct web_connection *c = cookie;
-
- if (en == HTTPREAD_EVENT_FILE_READY)
- web_connection_check_data(c);
- web_connection_stop(c);
-}
-
-
-/* web_connection_start - Start web connection
- * @sm: WPS UPnP state machine from upnp_wps_device_init()
- * @sd: Socket descriptor
- * @addr: Client address
- *
- * The socket descriptor sd is handed over for ownership by the WPS UPnP
- * state machine.
- */
-static void web_connection_start(void *ctx, int sd, struct sockaddr_in *addr)
-{
- struct upnp_wps_device_sm *sm = ctx;
- struct web_connection *c = NULL;
-
- /* if too many connections, bail */
- if (sm->n_web_connections >= MAX_WEB_CONNECTIONS) {
- close(sd);
- return;
- }
-
- c = os_zalloc(sizeof(*c));
- if (c == NULL)
- return;
- os_memcpy(&c->cli_addr, addr, sizeof(c->cli_addr));
- c->sm = sm;
- c->sd = sd;
-#if 0
- /*
- * Setting non-blocking should not be necessary for read, and can mess
- * up sending where blocking might be better.
- */
- if (fcntl(sd, F_SETFL, O_NONBLOCK) != 0)
+ web_connection_unimplemented(req);
break;
-#endif
- c->hread = httpread_create(c->sd, web_connection_got_file_handler,
- c /* cookie */,
- WEB_CONNECTION_MAX_READ,
- WEB_CONNECTION_TIMEOUT_SEC);
- if (c->hread == NULL)
- goto fail;
- if (sm->web_connections) {
- c->next = sm->web_connections;
- c->prev = c->next->prev;
- c->prev->next = c;
- c->next->prev = c;
- } else {
- sm->web_connections = c->next = c->prev = c;
}
- sm->n_web_connections++;
- return;
-
-fail:
- if (c)
- web_connection_stop(c);
}
{
struct in_addr addr;
addr.s_addr = sm->ip_addr;
- sm->web_srv = http_server_init(&addr, -1, web_connection_start, sm);
+ sm->web_srv = http_server_init(&addr, -1, web_connection_check_data,
+ sm);
if (sm->web_srv == NULL) {
web_listener_stop(sm);
return -1;