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