1214501Srpaulo/*
2214501Srpaulo * http_client - HTTP client
3214501Srpaulo * Copyright (c) 2009, Jouni Malinen <j@w1.fi>
4214501Srpaulo *
5252726Srpaulo * This software may be distributed under the terms of the BSD license.
6252726Srpaulo * See README for more details.
7214501Srpaulo */
8214501Srpaulo
9214501Srpaulo#include "includes.h"
10214501Srpaulo#include <fcntl.h>
11214501Srpaulo
12214501Srpaulo#include "common.h"
13214501Srpaulo#include "eloop.h"
14214501Srpaulo#include "httpread.h"
15214501Srpaulo#include "http_client.h"
16214501Srpaulo
17214501Srpaulo
18252726Srpaulo#define HTTP_CLIENT_TIMEOUT_SEC 30
19214501Srpaulo
20214501Srpaulo
21214501Srpaulostruct http_client {
22214501Srpaulo	struct sockaddr_in dst;
23214501Srpaulo	int sd;
24214501Srpaulo	struct wpabuf *req;
25214501Srpaulo	size_t req_pos;
26214501Srpaulo	size_t max_response;
27214501Srpaulo
28214501Srpaulo	void (*cb)(void *ctx, struct http_client *c,
29214501Srpaulo		   enum http_client_event event);
30214501Srpaulo	void *cb_ctx;
31214501Srpaulo	struct httpread *hread;
32214501Srpaulo	struct wpabuf body;
33214501Srpaulo};
34214501Srpaulo
35214501Srpaulo
36214501Srpaulostatic void http_client_timeout(void *eloop_data, void *user_ctx)
37214501Srpaulo{
38214501Srpaulo	struct http_client *c = eloop_data;
39252726Srpaulo	wpa_printf(MSG_DEBUG, "HTTP: Timeout (c=%p)", c);
40214501Srpaulo	c->cb(c->cb_ctx, c, HTTP_CLIENT_TIMEOUT);
41214501Srpaulo}
42214501Srpaulo
43214501Srpaulo
44214501Srpaulostatic void http_client_got_response(struct httpread *handle, void *cookie,
45214501Srpaulo				     enum httpread_event e)
46214501Srpaulo{
47214501Srpaulo	struct http_client *c = cookie;
48214501Srpaulo
49252726Srpaulo	wpa_printf(MSG_DEBUG, "HTTP: httpread callback: handle=%p cookie=%p "
50252726Srpaulo		   "e=%d", handle, cookie, e);
51252726Srpaulo
52214501Srpaulo	eloop_cancel_timeout(http_client_timeout, c, NULL);
53214501Srpaulo	switch (e) {
54214501Srpaulo	case HTTPREAD_EVENT_FILE_READY:
55214501Srpaulo		if (httpread_hdr_type_get(c->hread) == HTTPREAD_HDR_TYPE_REPLY)
56214501Srpaulo		{
57214501Srpaulo			int reply_code = httpread_reply_code_get(c->hread);
58214501Srpaulo			if (reply_code == 200 /* OK */) {
59214501Srpaulo				wpa_printf(MSG_DEBUG, "HTTP: Response OK from "
60214501Srpaulo					   "%s:%d",
61214501Srpaulo					   inet_ntoa(c->dst.sin_addr),
62214501Srpaulo					   ntohs(c->dst.sin_port));
63214501Srpaulo				c->cb(c->cb_ctx, c, HTTP_CLIENT_OK);
64214501Srpaulo			} else {
65214501Srpaulo				wpa_printf(MSG_DEBUG, "HTTP: Error %d from "
66214501Srpaulo					   "%s:%d", reply_code,
67214501Srpaulo					   inet_ntoa(c->dst.sin_addr),
68214501Srpaulo					   ntohs(c->dst.sin_port));
69214501Srpaulo				c->cb(c->cb_ctx, c, HTTP_CLIENT_INVALID_REPLY);
70214501Srpaulo			}
71214501Srpaulo		} else
72214501Srpaulo			c->cb(c->cb_ctx, c, HTTP_CLIENT_INVALID_REPLY);
73214501Srpaulo		break;
74214501Srpaulo	case HTTPREAD_EVENT_TIMEOUT:
75214501Srpaulo		c->cb(c->cb_ctx, c, HTTP_CLIENT_TIMEOUT);
76214501Srpaulo		break;
77214501Srpaulo	case HTTPREAD_EVENT_ERROR:
78214501Srpaulo		c->cb(c->cb_ctx, c, HTTP_CLIENT_FAILED);
79214501Srpaulo		break;
80214501Srpaulo	}
81214501Srpaulo}
82214501Srpaulo
83214501Srpaulo
84214501Srpaulostatic void http_client_tx_ready(int sock, void *eloop_ctx, void *sock_ctx)
85214501Srpaulo{
86214501Srpaulo	struct http_client *c = eloop_ctx;
87214501Srpaulo	int res;
88214501Srpaulo
89214501Srpaulo	wpa_printf(MSG_DEBUG, "HTTP: Send client request to %s:%d (%lu of %lu "
90214501Srpaulo		   "bytes remaining)",
91214501Srpaulo		   inet_ntoa(c->dst.sin_addr), ntohs(c->dst.sin_port),
92214501Srpaulo		   (unsigned long) wpabuf_len(c->req),
93214501Srpaulo		   (unsigned long) wpabuf_len(c->req) - c->req_pos);
94214501Srpaulo
95214501Srpaulo	res = send(c->sd, wpabuf_head(c->req) + c->req_pos,
96214501Srpaulo		   wpabuf_len(c->req) - c->req_pos, 0);
97214501Srpaulo	if (res < 0) {
98214501Srpaulo		wpa_printf(MSG_DEBUG, "HTTP: Failed to send buffer: %s",
99214501Srpaulo			   strerror(errno));
100214501Srpaulo		eloop_unregister_sock(c->sd, EVENT_TYPE_WRITE);
101214501Srpaulo		c->cb(c->cb_ctx, c, HTTP_CLIENT_FAILED);
102214501Srpaulo		return;
103214501Srpaulo	}
104214501Srpaulo
105214501Srpaulo	if ((size_t) res < wpabuf_len(c->req) - c->req_pos) {
106214501Srpaulo		wpa_printf(MSG_DEBUG, "HTTP: Sent %d of %lu bytes; %lu bytes "
107214501Srpaulo			   "remaining",
108214501Srpaulo			   res, (unsigned long) wpabuf_len(c->req),
109214501Srpaulo			   (unsigned long) wpabuf_len(c->req) - c->req_pos -
110214501Srpaulo			   res);
111214501Srpaulo		c->req_pos += res;
112214501Srpaulo		return;
113214501Srpaulo	}
114214501Srpaulo
115214501Srpaulo	wpa_printf(MSG_DEBUG, "HTTP: Full client request sent to %s:%d",
116214501Srpaulo		   inet_ntoa(c->dst.sin_addr), ntohs(c->dst.sin_port));
117214501Srpaulo	eloop_unregister_sock(c->sd, EVENT_TYPE_WRITE);
118214501Srpaulo	wpabuf_free(c->req);
119214501Srpaulo	c->req = NULL;
120214501Srpaulo
121214501Srpaulo	c->hread = httpread_create(c->sd, http_client_got_response, c,
122252726Srpaulo				   c->max_response, HTTP_CLIENT_TIMEOUT_SEC);
123214501Srpaulo	if (c->hread == NULL) {
124214501Srpaulo		c->cb(c->cb_ctx, c, HTTP_CLIENT_FAILED);
125214501Srpaulo		return;
126214501Srpaulo	}
127214501Srpaulo}
128214501Srpaulo
129214501Srpaulo
130214501Srpaulostruct http_client * http_client_addr(struct sockaddr_in *dst,
131214501Srpaulo				      struct wpabuf *req, size_t max_response,
132214501Srpaulo				      void (*cb)(void *ctx,
133214501Srpaulo						 struct http_client *c,
134214501Srpaulo						 enum http_client_event event),
135214501Srpaulo				      void *cb_ctx)
136214501Srpaulo{
137214501Srpaulo	struct http_client *c;
138214501Srpaulo
139214501Srpaulo	c = os_zalloc(sizeof(*c));
140214501Srpaulo	if (c == NULL)
141214501Srpaulo		return NULL;
142214501Srpaulo	c->sd = -1;
143214501Srpaulo	c->dst = *dst;
144214501Srpaulo	c->max_response = max_response;
145214501Srpaulo	c->cb = cb;
146214501Srpaulo	c->cb_ctx = cb_ctx;
147214501Srpaulo
148214501Srpaulo	c->sd = socket(AF_INET, SOCK_STREAM, 0);
149214501Srpaulo	if (c->sd < 0) {
150214501Srpaulo		http_client_free(c);
151214501Srpaulo		return NULL;
152214501Srpaulo	}
153214501Srpaulo
154214501Srpaulo	if (fcntl(c->sd, F_SETFL, O_NONBLOCK) != 0) {
155214501Srpaulo		wpa_printf(MSG_DEBUG, "HTTP: fnctl(O_NONBLOCK) failed: %s",
156214501Srpaulo			   strerror(errno));
157214501Srpaulo		http_client_free(c);
158214501Srpaulo		return NULL;
159214501Srpaulo	}
160214501Srpaulo
161214501Srpaulo	if (connect(c->sd, (struct sockaddr *) dst, sizeof(*dst))) {
162214501Srpaulo		if (errno != EINPROGRESS) {
163214501Srpaulo			wpa_printf(MSG_DEBUG, "HTTP: Failed to connect: %s",
164214501Srpaulo				   strerror(errno));
165214501Srpaulo			http_client_free(c);
166214501Srpaulo			return NULL;
167214501Srpaulo		}
168214501Srpaulo
169214501Srpaulo		/*
170214501Srpaulo		 * Continue connecting in the background; eloop will call us
171214501Srpaulo		 * once the connection is ready (or failed).
172214501Srpaulo		 */
173214501Srpaulo	}
174214501Srpaulo
175214501Srpaulo	if (eloop_register_sock(c->sd, EVENT_TYPE_WRITE, http_client_tx_ready,
176214501Srpaulo				c, NULL)) {
177214501Srpaulo		http_client_free(c);
178214501Srpaulo		return NULL;
179214501Srpaulo	}
180214501Srpaulo
181252726Srpaulo	if (eloop_register_timeout(HTTP_CLIENT_TIMEOUT_SEC, 0,
182252726Srpaulo				   http_client_timeout, c, NULL)) {
183214501Srpaulo		http_client_free(c);
184214501Srpaulo		return NULL;
185214501Srpaulo	}
186214501Srpaulo
187214501Srpaulo	c->req = req;
188214501Srpaulo
189214501Srpaulo	return c;
190214501Srpaulo}
191214501Srpaulo
192214501Srpaulo
193214501Srpaulochar * http_client_url_parse(const char *url, struct sockaddr_in *dst,
194214501Srpaulo			     char **ret_path)
195214501Srpaulo{
196214501Srpaulo	char *u, *addr, *port, *path;
197214501Srpaulo
198214501Srpaulo	u = os_strdup(url);
199214501Srpaulo	if (u == NULL)
200214501Srpaulo		return NULL;
201214501Srpaulo
202214501Srpaulo	os_memset(dst, 0, sizeof(*dst));
203214501Srpaulo	dst->sin_family = AF_INET;
204214501Srpaulo	addr = u + 7;
205214501Srpaulo	path = os_strchr(addr, '/');
206214501Srpaulo	port = os_strchr(addr, ':');
207214501Srpaulo	if (path == NULL) {
208214501Srpaulo		path = "/";
209214501Srpaulo	} else {
210214501Srpaulo		*path = '\0'; /* temporary nul termination for address */
211214501Srpaulo		if (port > path)
212214501Srpaulo			port = NULL;
213214501Srpaulo	}
214214501Srpaulo	if (port)
215214501Srpaulo		*port++ = '\0';
216214501Srpaulo
217214501Srpaulo	if (inet_aton(addr, &dst->sin_addr) == 0) {
218214501Srpaulo		/* TODO: name lookup */
219214501Srpaulo		wpa_printf(MSG_DEBUG, "HTTP: Unsupported address in URL '%s' "
220214501Srpaulo			   "(addr='%s' port='%s')",
221214501Srpaulo			   url, addr, port);
222214501Srpaulo		os_free(u);
223214501Srpaulo		return NULL;
224214501Srpaulo	}
225214501Srpaulo
226214501Srpaulo	if (port)
227214501Srpaulo		dst->sin_port = htons(atoi(port));
228214501Srpaulo	else
229214501Srpaulo		dst->sin_port = htons(80);
230214501Srpaulo
231214501Srpaulo	if (*path == '\0') {
232214501Srpaulo		/* remove temporary nul termination for address */
233214501Srpaulo		*path = '/';
234214501Srpaulo	}
235214501Srpaulo
236214501Srpaulo	*ret_path = path;
237214501Srpaulo
238214501Srpaulo	return u;
239214501Srpaulo}
240214501Srpaulo
241214501Srpaulo
242214501Srpaulostruct http_client * http_client_url(const char *url,
243214501Srpaulo				     struct wpabuf *req, size_t max_response,
244214501Srpaulo				     void (*cb)(void *ctx,
245214501Srpaulo						struct http_client *c,
246214501Srpaulo						enum http_client_event event),
247214501Srpaulo				     void *cb_ctx)
248214501Srpaulo{
249214501Srpaulo	struct sockaddr_in dst;
250214501Srpaulo	struct http_client *c;
251214501Srpaulo	char *u, *path;
252214501Srpaulo	struct wpabuf *req_buf = NULL;
253214501Srpaulo
254214501Srpaulo	if (os_strncmp(url, "http://", 7) != 0)
255214501Srpaulo		return NULL;
256214501Srpaulo	u = http_client_url_parse(url, &dst, &path);
257214501Srpaulo	if (u == NULL)
258214501Srpaulo		return NULL;
259214501Srpaulo
260214501Srpaulo	if (req == NULL) {
261214501Srpaulo		req_buf = wpabuf_alloc(os_strlen(url) + 1000);
262214501Srpaulo		if (req_buf == NULL) {
263214501Srpaulo			os_free(u);
264214501Srpaulo			return NULL;
265214501Srpaulo		}
266214501Srpaulo		req = req_buf;
267214501Srpaulo		wpabuf_printf(req,
268214501Srpaulo			      "GET %s HTTP/1.1\r\n"
269214501Srpaulo			      "Cache-Control: no-cache\r\n"
270214501Srpaulo			      "Pragma: no-cache\r\n"
271214501Srpaulo			      "Accept: text/xml, application/xml\r\n"
272214501Srpaulo			      "User-Agent: wpa_supplicant\r\n"
273214501Srpaulo			      "Host: %s:%d\r\n"
274214501Srpaulo			      "\r\n",
275214501Srpaulo			      path, inet_ntoa(dst.sin_addr),
276214501Srpaulo			      ntohs(dst.sin_port));
277214501Srpaulo	}
278214501Srpaulo	os_free(u);
279214501Srpaulo
280214501Srpaulo	c = http_client_addr(&dst, req, max_response, cb, cb_ctx);
281214501Srpaulo	if (c == NULL) {
282214501Srpaulo		wpabuf_free(req_buf);
283214501Srpaulo		return NULL;
284214501Srpaulo	}
285214501Srpaulo
286214501Srpaulo	return c;
287214501Srpaulo}
288214501Srpaulo
289214501Srpaulo
290214501Srpaulovoid http_client_free(struct http_client *c)
291214501Srpaulo{
292214501Srpaulo	if (c == NULL)
293214501Srpaulo		return;
294214501Srpaulo	httpread_destroy(c->hread);
295214501Srpaulo	wpabuf_free(c->req);
296214501Srpaulo	if (c->sd >= 0) {
297214501Srpaulo		eloop_unregister_sock(c->sd, EVENT_TYPE_WRITE);
298214501Srpaulo		close(c->sd);
299214501Srpaulo	}
300214501Srpaulo	eloop_cancel_timeout(http_client_timeout, c, NULL);
301214501Srpaulo	os_free(c);
302214501Srpaulo}
303214501Srpaulo
304214501Srpaulo
305214501Srpaulostruct wpabuf * http_client_get_body(struct http_client *c)
306214501Srpaulo{
307214501Srpaulo	if (c->hread == NULL)
308214501Srpaulo		return NULL;
309214501Srpaulo	wpabuf_set(&c->body, httpread_data_get(c->hread),
310214501Srpaulo		   httpread_length_get(c->hread));
311214501Srpaulo	return &c->body;
312214501Srpaulo}
313214501Srpaulo
314214501Srpaulo
315214501Srpaulochar * http_client_get_hdr_line(struct http_client *c, const char *tag)
316214501Srpaulo{
317214501Srpaulo	if (c->hread == NULL)
318214501Srpaulo		return NULL;
319214501Srpaulo	return httpread_hdr_line_get(c->hread, tag);
320214501Srpaulo}
321214501Srpaulo
322214501Srpaulo
323214501Srpaulochar * http_link_update(char *url, const char *base)
324214501Srpaulo{
325214501Srpaulo	char *n;
326214501Srpaulo	size_t len;
327214501Srpaulo	const char *pos;
328214501Srpaulo
329214501Srpaulo	/* RFC 2396, Chapter 5.2 */
330214501Srpaulo	/* TODO: consider adding all cases described in RFC 2396 */
331214501Srpaulo
332214501Srpaulo	if (url == NULL)
333214501Srpaulo		return NULL;
334214501Srpaulo
335214501Srpaulo	if (os_strncmp(url, "http://", 7) == 0)
336214501Srpaulo		return url; /* absolute link */
337214501Srpaulo
338214501Srpaulo	if (os_strncmp(base, "http://", 7) != 0)
339214501Srpaulo		return url; /* unable to handle base URL */
340214501Srpaulo
341214501Srpaulo	len = os_strlen(url) + 1 + os_strlen(base) + 1;
342214501Srpaulo	n = os_malloc(len);
343214501Srpaulo	if (n == NULL)
344214501Srpaulo		return url; /* failed */
345214501Srpaulo
346214501Srpaulo	if (url[0] == '/') {
347214501Srpaulo		pos = os_strchr(base + 7, '/');
348214501Srpaulo		if (pos == NULL) {
349214501Srpaulo			os_snprintf(n, len, "%s%s", base, url);
350214501Srpaulo		} else {
351214501Srpaulo			os_memcpy(n, base, pos - base);
352214501Srpaulo			os_memcpy(n + (pos - base), url, os_strlen(url) + 1);
353214501Srpaulo		}
354214501Srpaulo	} else {
355214501Srpaulo		pos = os_strrchr(base + 7, '/');
356214501Srpaulo		if (pos == NULL) {
357214501Srpaulo			os_snprintf(n, len, "%s/%s", base, url);
358214501Srpaulo		} else {
359214501Srpaulo			os_memcpy(n, base, pos - base + 1);
360214501Srpaulo			os_memcpy(n + (pos - base) + 1, url, os_strlen(url) +
361214501Srpaulo				  1);
362214501Srpaulo		}
363214501Srpaulo	}
364214501Srpaulo
365214501Srpaulo	os_free(url);
366214501Srpaulo
367214501Srpaulo	return n;
368214501Srpaulo}
369