Allow max HTTP redirects to be controlled YAZ-667
[yaz-moved-to-github.git] / src / url.c
1 /* This file is part of the YAZ toolkit.
2  * Copyright (C) 1995-2013 Index Data
3  * See the file LICENSE for details.
4  */
5 /**
6  * \file url.c
7  * \brief URL fetch utility
8  */
9 #if HAVE_CONFIG_H
10 #include <config.h>
11 #endif
12
13 #include <yaz/url.h>
14 #include <yaz/comstack.h>
15 #include <yaz/log.h>
16
17 struct yaz_url {
18     ODR odr_in;
19     ODR odr_out;
20     char *proxy;
21     int max_redirects;
22 };
23
24 yaz_url_t yaz_url_create(void)
25 {
26     yaz_url_t p = xmalloc(sizeof(*p));
27     p->odr_in = odr_createmem(ODR_DECODE);
28     p->odr_out = odr_createmem(ODR_ENCODE);
29     p->proxy = 0;
30     p->max_redirects = 10;
31     return p;
32 }
33
34 void yaz_url_destroy(yaz_url_t p)
35 {
36     if (p)
37     {
38         odr_destroy(p->odr_in);
39         odr_destroy(p->odr_out);
40         xfree(p->proxy);
41         xfree(p);
42     }
43 }
44
45 void yaz_url_set_proxy(yaz_url_t p, const char *proxy)
46 {
47     xfree(p->proxy);
48     p->proxy = 0;
49     if (proxy && *proxy)
50         p->proxy = xstrdup(proxy);
51 }
52
53 void yaz_url_set_max_redirects(yaz_url_t p, int num)
54 {
55     p->max_redirects = num;
56 }
57
58 static void extract_user_pass(NMEM nmem,
59                               const char *uri,
60                               char **uri_lean, char **http_user,
61                               char **http_pass)
62 {
63     const char *cp1 = strchr(uri, '/');
64     *uri_lean = 0;
65     *http_user = 0;
66     *http_pass = 0;
67     if (cp1 && cp1 > uri)
68     {
69         cp1--;
70
71         if (!strncmp(cp1, "://", 3))
72         {
73             const char *cp3 = 0;
74             const char *cp2 = cp1 + 3;
75             while (*cp2 && *cp2 != '/' && *cp2 != '@')
76             {
77                 if (*cp2 == ':')
78                     cp3 = cp2;
79                 cp2++;
80             }
81             if (*cp2 == '@' && cp3)
82             {
83                 *uri_lean = nmem_malloc(nmem, strlen(uri) + 1);
84                 memcpy(*uri_lean, uri, cp1 + 3 - uri);
85                 strcpy(*uri_lean + (cp1 + 3 - uri), cp2 + 1);
86
87                 *http_user = nmem_strdupn(nmem, cp1 + 3, cp3 - (cp1 + 3));
88                 *http_pass = nmem_strdupn(nmem, cp3 + 1, cp2 - (cp3 + 1));
89             }
90         }
91     }
92     if (*uri_lean == 0)
93         *uri_lean = nmem_strdup(nmem, uri);
94 }
95
96 Z_HTTP_Response *yaz_url_exec(yaz_url_t p, const char *uri,
97                               const char *method,
98                               Z_HTTP_Header *user_headers,
99                               const char *buf, size_t len)
100 {
101     Z_HTTP_Response *res = 0;
102     int number_of_redirects = 0;
103
104     while (1)
105     {
106         void *add;
107         COMSTACK conn = 0;
108         int code;
109         const char *location = 0;
110         char *http_user = 0;
111         char *http_pass = 0;
112         char *uri_lean = 0;
113         Z_GDU *gdu;
114
115         extract_user_pass(p->odr_out->mem, uri, &uri_lean,
116                           &http_user, &http_pass);
117
118         gdu = z_get_HTTP_Request_uri(p->odr_out, uri_lean, 0, p->proxy ? 1 : 0);
119         gdu->u.HTTP_Request->method = odr_strdup(p->odr_out, method);
120
121         for ( ; user_headers; user_headers = user_headers->next)
122         {
123             /* prefer new Host over user-supplied Host */
124             if (!strcmp(user_headers->name, "Host"))
125                 ;
126             /* prefer user-supplied User-Agent over YAZ' own */
127             else if (!strcmp(user_headers->name, "User-Agent"))
128                 z_HTTP_header_set(p->odr_out, &gdu->u.HTTP_Request->headers,
129                                   user_headers->name, user_headers->value);
130             else
131                 z_HTTP_header_add(p->odr_out, &gdu->u.HTTP_Request->headers,
132                                   user_headers->name, user_headers->value);
133         }
134         if (http_user && http_pass)
135             z_HTTP_header_add_basic_auth(p->odr_out,
136                                          &gdu->u.HTTP_Request->headers,
137                                          http_user, http_pass);
138
139         res = 0;
140         if (buf && len)
141         {
142             gdu->u.HTTP_Request->content_buf = (char *) buf;
143             gdu->u.HTTP_Request->content_len = len;
144         }
145         if (!z_GDU(p->odr_out, &gdu, 0, 0))
146         {
147             yaz_log(YLOG_WARN, "Can not encode HTTP request URL:%s", uri);
148             return 0;
149         }
150         conn = cs_create_host_proxy(uri_lean, 1, &add, p->proxy);
151         if (!conn)
152         {
153             yaz_log(YLOG_WARN, "Could not resolve URL: %s", uri);
154         }
155         else if (cs_connect(conn, add) < 0)
156         {
157             yaz_log(YLOG_WARN, "Can not connect to URL: %s", uri);
158         }
159         else
160         {
161             int len;
162             char *buf = odr_getbuf(p->odr_out, &len, 0);
163
164             if (cs_put(conn, buf, len) < 0)
165                 yaz_log(YLOG_WARN, "cs_put failed URL: %s", uri);
166             else
167             {
168                 char *netbuffer = 0;
169                 int netlen = 0;
170                 int cs_res = cs_get(conn, &netbuffer, &netlen);
171                 if (cs_res <= 0)
172                 {
173                     yaz_log(YLOG_WARN, "cs_get failed URL: %s", uri);
174                 }
175                 else
176                 {
177                     Z_GDU *gdu;
178                     odr_setbuf(p->odr_in, netbuffer, cs_res, 0);
179                     if (!z_GDU(p->odr_in, &gdu, 0, 0)
180                         || gdu->which != Z_GDU_HTTP_Response)
181                     {
182                         yaz_log(YLOG_WARN, "HTTP decoding failed "
183                                 "URL:%s", uri);
184                     }
185                     else
186                     {
187                         res = gdu->u.HTTP_Response;
188                     }
189                 }
190                 xfree(netbuffer);
191             }
192         }
193         if (conn)
194             cs_close(conn);
195         if (!res)
196             break;
197         code = res->code;
198         location = z_HTTP_header_lookup(res->headers, "Location");
199         if (++number_of_redirects <= p->max_redirects &&
200             location && (code == 301 || code == 302 || code == 307))
201         {
202             odr_reset(p->odr_out);
203             uri = odr_strdup(p->odr_out, location);
204             odr_reset(p->odr_in);
205         }
206         else
207             break;
208     }
209     return res;
210 }
211
212 /*
213  * Local variables:
214  * c-basic-offset: 4
215  * c-file-style: "Stroustrup"
216  * indent-tabs-mode: nil
217  * End:
218  * vim: shiftwidth=4 tabstop=8 expandtab
219  */
220