1 /*
2 * This file is part of the Sofia-SIP package
3 *
4 * Copyright (C) 2005 Nokia Corporation.
5 *
6 * Contact: Pekka Pessi <pekka.pessi@nokia.com>
7 *
8 * This library is free software; you can redistribute it and/or
9 * modify it under the terms of the GNU Lesser General Public License
10 * as published by the Free Software Foundation; either version 2.1 of
11 * the License, or (at your option) any later version.
12 *
13 * This library is distributed in the hope that it will be useful, but
14 * WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16 * Lesser General Public License for more details.
17 *
18 * You should have received a copy of the GNU Lesser General Public
19 * License along with this library; if not, write to the Free Software
20 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
21 * 02110-1301 USA
22 *
23 */
24
25 /**@file http-client.c Simple HTTP tool.
26 *
27 * @author Pekka Pessi <Pekka.Pessi@nokia.com>.
28 *
29 * @date Created: Fri Mar 30 12:05:21 2001 ppessi
30 */
31
32 #include "config.h"
33
34 /**@page http_client Make HTTP request
35 *
36 * @par Name����
37 * http-client - HTTP request tool
38 *
39 * @par Synopsis
40 *
41 * <tt>http-client [OPTIONS] url</tt>
42 *
43 * @par Description
44 *
45 * The @em http-client utility sends a HTTP request to an HTTP server or proxy.
46 *
47 * @par
48 *
49 * The @em http-client tool will print out status line and interesting
50 * headers from the response. The message body is also printed.
51 *
52 * @par Options
53 *
54 * The @e http-client utility accepts following command line options:
55 * <dl>
56 * <dt>--method=name</dt>
57 * <dd>Specify the request method name (GET by default).
58 * </dd>
59 * <dt>--proxy=url</dt>
60 * <dd>Specifies the proxy via which the request will be sent.
61 * </dd>
62 * <dt>--ua=value</dt>
63 * <dd>Specifies the User-Agent header field.
64 * </dd>
65 * <dt>--mf=n</dt>
66 * <dd>Specify the initial Max-Forwards count.
67 * </dd>
68 * <dt>--pipe</dt>
69 * <dd>Use pipelining (do not shutdown client connection after request).
70 * </dd>
71 * <dt>--extra</dt>
72 * <dd>Insert standard input to the requests.
73 * </dd>
74 * </dl>
75 *
76 * @par Examples
77 *
78 * You want to query supported features of http://connecting.nokia.com:
79 * @code
80 * $ http-client --method OPTIONS http://connecting.nokia.com
81 * @endcode
82 *
83 * @par Environment
84 * @c NTH_DEBUG, @c TPORT_DEBUG, @c TPORT_LOG.
85 *
86 * @author Pekka Pessi <Pekka.Pessi@nokia.com>
87 *
88 * <hr>
89 */
90
91 #include <stddef.h>
92 #include <stdlib.h>
93 #include <string.h>
94 #include <stdio.h>
95 #include <assert.h>
96
97 typedef struct context_s context_t;
98 #define NTH_CLIENT_MAGIC_T context_t
99
100 #include <sofia-sip/nth.h>
101 #include <sofia-sip/http_header.h>
102 #include <sofia-sip/http_tag.h>
103 #include <sofia-sip/tport_tag.h>
104 #include <sofia-sip/auth_client.h>
105
106 struct context_s {
107 su_home_t c_home[1];
108 su_root_t *c_root;
109 nth_engine_t *c_engine;
110 nth_client_t *c_clnt;
111 char const *c_user;
112 char const *c_pass;
113 auth_client_t *c_auth;
114 int c_pre;
115 int c_pending;
116 };
117
118 static
119 char const name[] = "http-client";
120
121 static
header_print(FILE * stream,char const * fmt,http_header_t const * h)122 int header_print(FILE *stream, char const *fmt, http_header_t const *h)
123 {
124 char s[1024];
125
126 msg_header_field_e(s, sizeof(s), (msg_header_t*)h, 0);
127 s[sizeof(s) - 1] = '\0';
128
129 if (fmt && strcmp(fmt, "%s"))
130 return fprintf(stream, fmt, s);
131 if (fputs(s, stream) >= 0)
132 return strlen(s);
133 return -1;
134 }
135
136 static
payload_print(FILE * stream,msg_payload_t const * pl)137 int payload_print(FILE *stream, msg_payload_t const *pl)
138 {
139 for (; pl; pl = pl->pl_next) {
140 fprintf(stream, "%.*s", (int)pl->pl_len, pl->pl_data);
141 }
142 return 0;
143 }
144
145 static
read_file(FILE * stream)146 char *read_file(FILE *stream)
147 {
148 int n;
149 char *buf;
150 off_t used, size;
151
152 if (stream == NULL) {
153 errno = EINVAL;
154 return NULL;
155 }
156
157 /* Read block by block */
158 used = 0;
159 size = 512;
160 buf = malloc(size);
161
162 while (buf) {
163 n = fread(buf + used, 1, size - used - 1, stream);
164 used += n;
165 if (n < size - used - 1) {
166 if (feof(stream))
167 ;
168 else if (ferror(stream))
169 free(buf), buf = NULL;
170 break;
171 }
172 buf = realloc(buf, 2 * size);
173 }
174
175 if (buf)
176 if (used < size)
177 buf[used] = '\0';
178
179 return buf;
180 }
181
182 char const _usage[] =
183 "usage: %s [OPTIONS] url\n"
184 " where OPTIONS are as follows\n"
185 " --method=name\n"
186 " --proxy=url\n"
187 " --user=user:password\n"
188 " --ua=value\n"
189 " --mf=n\n"
190 " --pipe\n"
191 " --extra\n";
192
193 static
usage(int rc)194 void usage(int rc)
195 {
196 fprintf(stderr, _usage, name);
197 exit(rc);
198 }
199
200 static int response(context_t *context,
201 nth_client_t *oreq,
202 http_t const *http);
203
main(int argc,char * argv[])204 int main(int argc, char *argv[])
205 {
206 su_home_t *home;
207 context_t context[1] = {{{SU_HOME_INIT(context)}}};
208 http_method_t method;
209 char
210 *o_proxy = NULL,
211 *o_user = NULL,
212 *o_max_forwards = NULL,
213 *o_method_name = "GET",
214 *o_user_agent = "http-client/1.0 " "nth/" NTH_VERSION;
215 int
216 o_pipe = 0, o_extra = 0;
217
218 char *extra = NULL;
219 char *v;
220
221 #define MATCH(s, o) \
222 ((strcmp(s, o) == 0))
223 #define MATCH1(s, o) \
224 ((strncmp(s, o, strlen(o)) == 0) && \
225 (v = (s[strlen(o)] ? s + strlen(o) : argv++[1])))
226 #define MATCH2(s, o) \
227 ((strncmp(s, o, strlen(o)) == 0) && \
228 (s[strlen(o)] == '=' || s[strlen(o)] == '\0') && \
229 (v = s[strlen(o)] ? s + strlen(o) + 1 : argv++[1]))
230
231 while ((v = argv++[1])) {
232 if (v[0] != '-') { argv--; break; }
233 else if (MATCH(v, "-")) { break; }
234 else if (MATCH2(v, "--method")) { o_method_name = v; continue; }
235 else if (MATCH2(v, "--mf")) { o_max_forwards = v; continue; }
236 else if (MATCH2(v, "--proxy")) { o_proxy = v; continue; }
237 else if (MATCH2(v, "--user")) { o_user = v; continue; }
238 else if (MATCH2(v, "--ua")) { o_user_agent = v; continue; }
239 else if (MATCH(v, "--pipe")) { o_pipe = 1; continue; }
240 else if (MATCH(v, "--extra")) { o_extra = 1; continue; }
241 else if (MATCH(v, "--help")) { usage(0); continue; }
242 else
243 usage(1);
244 }
245
246 if (!argv[1])
247 usage(1);
248
249 method = http_method_code(o_method_name);
250
251 if (o_user) {
252 char *pass = strchr(o_user, ':');
253 if (pass) *pass++ = '\0';
254 context->c_user = o_user, context->c_pass = pass;
255 }
256
257 su_init();
258
259 su_home_init(home = context->c_home);
260
261 if (o_extra) {
262 if (isatty(0))
263 fprintf(stderr,
264 "Type extra HTTP headers, empty line then HTTP message body "
265 "(^D when complete):\n");
266 fflush(stderr);
267
268 extra = read_file(stdin);
269 }
270
271 context->c_root = su_root_create(context);
272
273 if (context->c_root) {
274 context->c_engine =
275 nth_engine_create(context->c_root,
276 NTHTAG_ERROR_MSG(0),
277 TAG_END());
278
279 if (context->c_engine) {
280 while ((v = argv++[1])) {
281 nth_client_t *clnt;
282 clnt = nth_client_tcreate(context->c_engine,
283 response, context,
284 method, o_method_name,
285 URL_STRING_MAKE(v),
286 NTHTAG_PROXY(o_proxy),
287 HTTPTAG_USER_AGENT_STR(o_user_agent),
288 HTTPTAG_MAX_FORWARDS_STR(o_max_forwards),
289 TPTAG_REUSE(o_pipe),
290 HTTPTAG_HEADER_STR(extra),
291 TAG_END());
292 if (clnt)
293 context->c_pending++;
294 }
295
296 if (context->c_pending)
297 su_root_run(context->c_root);
298
299 nth_engine_destroy(context->c_engine), context->c_engine = NULL;
300 }
301 su_root_destroy(context->c_root);
302 }
303
304 su_deinit();
305
306 return 0;
307 }
308
309 /** Handle responses to request */
310 static
response(context_t * c,nth_client_t * clnt,http_t const * http)311 int response(context_t *c,
312 nth_client_t *clnt,
313 http_t const *http)
314 {
315 nth_client_t *newclnt = NULL;
316 int status;
317
318 if (http) {
319 status = http->http_status->st_status;
320 } else {
321 status = nth_client_status(clnt);
322 fprintf(stderr, "HTTP/1.1 %u Error\n", status);
323 }
324
325 if (http && (c->c_pre || status >= 200)) {
326 http_header_t *h = (http_header_t *)http->http_status;
327 char hname[64];
328
329 for (; h; h = (http_header_t *)h->sh_succ) {
330 if (h == (http_header_t *)http->http_payload)
331 continue;
332 else if (h == (http_header_t *)http->http_separator)
333 continue;
334 else if (!h->sh_class->hc_name)
335 header_print(stdout, NULL, h);
336 else if (h->sh_class->hc_name[0]) {
337 snprintf(hname, sizeof hname, "%s: %%s\n", h->sh_class->hc_name);
338 header_print(stdout, hname, h);
339 } else {
340 header_print(stdout, "%s\n", h);
341 }
342 }
343
344 printf("\n");
345 if (http->http_payload)
346 payload_print(stdout, http->http_payload);
347
348 fflush(stdout);
349 }
350
351 if (status < 200)
352 return 0;
353
354 if (status == 401 && http->http_www_authenticate) {
355 char const *user = c->c_user;
356 char const *pass = c->c_pass;
357
358 if (!user || !pass) {
359 url_t const *url = nth_client_url(clnt);
360 if (url) {
361 user = url->url_user, pass = url->url_password;
362 }
363 }
364
365 //if (user && pass &&
366 if (
367 auc_challenge(&c->c_auth, c->c_home,
368 http->http_www_authenticate,
369 http_authorization_class) > 0) {
370 char const *scheme = NULL;
371 char const *realm = NULL;
372
373 scheme = http->http_www_authenticate->au_scheme;
374 realm = msg_params_find(http->http_www_authenticate->au_params,
375 "realm=");
376 if (auc_all_credentials(&c->c_auth, scheme, realm, user, pass)
377 >= 0)
378 newclnt = nth_client_tcreate(c->c_engine,
379 NULL, NULL, HTTP_NO_METHOD, NULL,
380 NTHTAG_AUTHENTICATION(&c->c_auth),
381 NTHTAG_TEMPLATE(clnt),
382 TAG_END());
383 }
384 }
385
386 if (status == 302 && http->http_location) {
387 url_t loc[1];
388
389 *loc = *http->http_location->loc_url;
390
391 newclnt = nth_client_tcreate(c->c_engine, NULL, NULL,
392 HTTP_NO_METHOD,
393 (url_string_t *)loc,
394 NTHTAG_TEMPLATE(clnt),
395 TAG_END());
396 }
397
398
399 if (newclnt)
400 c->c_pending++;
401
402 nth_client_destroy(clnt);
403 if (c->c_pending-- == 1)
404 su_root_break(c->c_root);
405
406 return 0;
407 }
408