http_client.c revision 8d520ff1dc2da35cdca849e982051b86468016d8
1/*
2 * http_client - HTTP client
3 * Copyright (c) 2009, Jouni Malinen <j@w1.fi>
4 *
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License version 2 as
7 * published by the Free Software Foundation.
8 *
9 * Alternatively, this software may be distributed under the terms of BSD
10 * license.
11 *
12 * See README and COPYING for more details.
13 */
14
15#include "includes.h"
16#include <fcntl.h>
17
18#include "common.h"
19#include "eloop.h"
20#include "httpread.h"
21#include "http_client.h"
22
23
24#define HTTP_CLIENT_TIMEOUT_SEC 30
25
26
27struct http_client {
28	struct sockaddr_in dst;
29	int sd;
30	struct wpabuf *req;
31	size_t req_pos;
32	size_t max_response;
33
34	void (*cb)(void *ctx, struct http_client *c,
35		   enum http_client_event event);
36	void *cb_ctx;
37	struct httpread *hread;
38	struct wpabuf body;
39};
40
41
42static void http_client_timeout(void *eloop_data, void *user_ctx)
43{
44	struct http_client *c = eloop_data;
45	wpa_printf(MSG_DEBUG, "HTTP: Timeout (c=%p)", c);
46	c->cb(c->cb_ctx, c, HTTP_CLIENT_TIMEOUT);
47}
48
49
50static void http_client_got_response(struct httpread *handle, void *cookie,
51				     enum httpread_event e)
52{
53	struct http_client *c = cookie;
54
55	wpa_printf(MSG_DEBUG, "HTTP: httpread callback: handle=%p cookie=%p "
56		   "e=%d", handle, cookie, e);
57
58	eloop_cancel_timeout(http_client_timeout, c, NULL);
59	switch (e) {
60	case HTTPREAD_EVENT_FILE_READY:
61		if (httpread_hdr_type_get(c->hread) == HTTPREAD_HDR_TYPE_REPLY)
62		{
63			int reply_code = httpread_reply_code_get(c->hread);
64			if (reply_code == 200 /* OK */) {
65				wpa_printf(MSG_DEBUG, "HTTP: Response OK from "
66					   "%s:%d",
67					   inet_ntoa(c->dst.sin_addr),
68					   ntohs(c->dst.sin_port));
69				c->cb(c->cb_ctx, c, HTTP_CLIENT_OK);
70			} else {
71				wpa_printf(MSG_DEBUG, "HTTP: Error %d from "
72					   "%s:%d", reply_code,
73					   inet_ntoa(c->dst.sin_addr),
74					   ntohs(c->dst.sin_port));
75				c->cb(c->cb_ctx, c, HTTP_CLIENT_INVALID_REPLY);
76			}
77		} else
78			c->cb(c->cb_ctx, c, HTTP_CLIENT_INVALID_REPLY);
79		break;
80	case HTTPREAD_EVENT_TIMEOUT:
81		c->cb(c->cb_ctx, c, HTTP_CLIENT_TIMEOUT);
82		break;
83	case HTTPREAD_EVENT_ERROR:
84		c->cb(c->cb_ctx, c, HTTP_CLIENT_FAILED);
85		break;
86	}
87}
88
89
90static void http_client_tx_ready(int sock, void *eloop_ctx, void *sock_ctx)
91{
92	struct http_client *c = eloop_ctx;
93	int res;
94
95	wpa_printf(MSG_DEBUG, "HTTP: Send client request to %s:%d (%lu of %lu "
96		   "bytes remaining)",
97		   inet_ntoa(c->dst.sin_addr), ntohs(c->dst.sin_port),
98		   (unsigned long) wpabuf_len(c->req),
99		   (unsigned long) wpabuf_len(c->req) - c->req_pos);
100
101	res = send(c->sd, wpabuf_head(c->req) + c->req_pos,
102		   wpabuf_len(c->req) - c->req_pos, 0);
103	if (res < 0) {
104		wpa_printf(MSG_DEBUG, "HTTP: Failed to send buffer: %s",
105			   strerror(errno));
106		eloop_unregister_sock(c->sd, EVENT_TYPE_WRITE);
107		c->cb(c->cb_ctx, c, HTTP_CLIENT_FAILED);
108		return;
109	}
110
111	if ((size_t) res < wpabuf_len(c->req) - c->req_pos) {
112		wpa_printf(MSG_DEBUG, "HTTP: Sent %d of %lu bytes; %lu bytes "
113			   "remaining",
114			   res, (unsigned long) wpabuf_len(c->req),
115			   (unsigned long) wpabuf_len(c->req) - c->req_pos -
116			   res);
117		c->req_pos += res;
118		return;
119	}
120
121	wpa_printf(MSG_DEBUG, "HTTP: Full client request sent to %s:%d",
122		   inet_ntoa(c->dst.sin_addr), ntohs(c->dst.sin_port));
123	eloop_unregister_sock(c->sd, EVENT_TYPE_WRITE);
124	wpabuf_free(c->req);
125	c->req = NULL;
126
127	c->hread = httpread_create(c->sd, http_client_got_response, c,
128				   c->max_response, HTTP_CLIENT_TIMEOUT_SEC);
129	if (c->hread == NULL) {
130		c->cb(c->cb_ctx, c, HTTP_CLIENT_FAILED);
131		return;
132	}
133}
134
135
136struct http_client * http_client_addr(struct sockaddr_in *dst,
137				      struct wpabuf *req, size_t max_response,
138				      void (*cb)(void *ctx,
139						 struct http_client *c,
140						 enum http_client_event event),
141				      void *cb_ctx)
142{
143	struct http_client *c;
144
145	c = os_zalloc(sizeof(*c));
146	if (c == NULL)
147		return NULL;
148	c->sd = -1;
149	c->dst = *dst;
150	c->max_response = max_response;
151	c->cb = cb;
152	c->cb_ctx = cb_ctx;
153
154	c->sd = socket(AF_INET, SOCK_STREAM, 0);
155	if (c->sd < 0) {
156		http_client_free(c);
157		return NULL;
158	}
159
160	if (fcntl(c->sd, F_SETFL, O_NONBLOCK) != 0) {
161		wpa_printf(MSG_DEBUG, "HTTP: fnctl(O_NONBLOCK) failed: %s",
162			   strerror(errno));
163		http_client_free(c);
164		return NULL;
165	}
166
167	if (connect(c->sd, (struct sockaddr *) dst, sizeof(*dst))) {
168		if (errno != EINPROGRESS) {
169			wpa_printf(MSG_DEBUG, "HTTP: Failed to connect: %s",
170				   strerror(errno));
171			http_client_free(c);
172			return NULL;
173		}
174
175		/*
176		 * Continue connecting in the background; eloop will call us
177		 * once the connection is ready (or failed).
178		 */
179	}
180
181	if (eloop_register_sock(c->sd, EVENT_TYPE_WRITE, http_client_tx_ready,
182				c, NULL)) {
183		http_client_free(c);
184		return NULL;
185	}
186
187	if (eloop_register_timeout(HTTP_CLIENT_TIMEOUT_SEC, 0,
188				   http_client_timeout, c, NULL)) {
189		http_client_free(c);
190		return NULL;
191	}
192
193	c->req = req;
194
195	return c;
196}
197
198
199char * http_client_url_parse(const char *url, struct sockaddr_in *dst,
200			     char **ret_path)
201{
202	char *u, *addr, *port, *path;
203
204	u = os_strdup(url);
205	if (u == NULL)
206		return NULL;
207
208	os_memset(dst, 0, sizeof(*dst));
209	dst->sin_family = AF_INET;
210	addr = u + 7;
211	path = os_strchr(addr, '/');
212	port = os_strchr(addr, ':');
213	if (path == NULL) {
214		path = "/";
215	} else {
216		*path = '\0'; /* temporary nul termination for address */
217		if (port > path)
218			port = NULL;
219	}
220	if (port)
221		*port++ = '\0';
222
223	if (inet_aton(addr, &dst->sin_addr) == 0) {
224		/* TODO: name lookup */
225		wpa_printf(MSG_DEBUG, "HTTP: Unsupported address in URL '%s' "
226			   "(addr='%s' port='%s')",
227			   url, addr, port);
228		os_free(u);
229		return NULL;
230	}
231
232	if (port)
233		dst->sin_port = htons(atoi(port));
234	else
235		dst->sin_port = htons(80);
236
237	if (*path == '\0') {
238		/* remove temporary nul termination for address */
239		*path = '/';
240	}
241
242	*ret_path = path;
243
244	return u;
245}
246
247
248struct http_client * http_client_url(const char *url,
249				     struct wpabuf *req, size_t max_response,
250				     void (*cb)(void *ctx,
251						struct http_client *c,
252						enum http_client_event event),
253				     void *cb_ctx)
254{
255	struct sockaddr_in dst;
256	struct http_client *c;
257	char *u, *path;
258	struct wpabuf *req_buf = NULL;
259
260	if (os_strncmp(url, "http://", 7) != 0)
261		return NULL;
262	u = http_client_url_parse(url, &dst, &path);
263	if (u == NULL)
264		return NULL;
265
266	if (req == NULL) {
267		req_buf = wpabuf_alloc(os_strlen(url) + 1000);
268		if (req_buf == NULL) {
269			os_free(u);
270			return NULL;
271		}
272		req = req_buf;
273		wpabuf_printf(req,
274			      "GET %s HTTP/1.1\r\n"
275			      "Cache-Control: no-cache\r\n"
276			      "Pragma: no-cache\r\n"
277			      "Accept: text/xml, application/xml\r\n"
278			      "User-Agent: wpa_supplicant\r\n"
279			      "Host: %s:%d\r\n"
280			      "\r\n",
281			      path, inet_ntoa(dst.sin_addr),
282			      ntohs(dst.sin_port));
283	}
284	os_free(u);
285
286	c = http_client_addr(&dst, req, max_response, cb, cb_ctx);
287	if (c == NULL) {
288		wpabuf_free(req_buf);
289		return NULL;
290	}
291
292	return c;
293}
294
295
296void http_client_free(struct http_client *c)
297{
298	if (c == NULL)
299		return;
300	httpread_destroy(c->hread);
301	wpabuf_free(c->req);
302	if (c->sd >= 0) {
303		eloop_unregister_sock(c->sd, EVENT_TYPE_WRITE);
304		close(c->sd);
305	}
306	eloop_cancel_timeout(http_client_timeout, c, NULL);
307	os_free(c);
308}
309
310
311struct wpabuf * http_client_get_body(struct http_client *c)
312{
313	if (c->hread == NULL)
314		return NULL;
315	wpabuf_set(&c->body, httpread_data_get(c->hread),
316		   httpread_length_get(c->hread));
317	return &c->body;
318}
319
320
321char * http_client_get_hdr_line(struct http_client *c, const char *tag)
322{
323	if (c->hread == NULL)
324		return NULL;
325	return httpread_hdr_line_get(c->hread, tag);
326}
327
328
329char * http_link_update(char *url, const char *base)
330{
331	char *n;
332	size_t len;
333	const char *pos;
334
335	/* RFC 2396, Chapter 5.2 */
336	/* TODO: consider adding all cases described in RFC 2396 */
337
338	if (url == NULL)
339		return NULL;
340
341	if (os_strncmp(url, "http://", 7) == 0)
342		return url; /* absolute link */
343
344	if (os_strncmp(base, "http://", 7) != 0)
345		return url; /* unable to handle base URL */
346
347	len = os_strlen(url) + 1 + os_strlen(base) + 1;
348	n = os_malloc(len);
349	if (n == NULL)
350		return url; /* failed */
351
352	if (url[0] == '/') {
353		pos = os_strchr(base + 7, '/');
354		if (pos == NULL) {
355			os_snprintf(n, len, "%s%s", base, url);
356		} else {
357			os_memcpy(n, base, pos - base);
358			os_memcpy(n + (pos - base), url, os_strlen(url) + 1);
359		}
360	} else {
361		pos = os_strrchr(base + 7, '/');
362		if (pos == NULL) {
363			os_snprintf(n, len, "%s/%s", base, url);
364		} else {
365			os_memcpy(n, base, pos - base + 1);
366			os_memcpy(n + (pos - base) + 1, url, os_strlen(url) +
367				  1);
368		}
369	}
370
371	os_free(url);
372
373	return n;
374}
375