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