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