1 /*****************************************************************************
2  * resource.c: HTTP resource common code
3  *****************************************************************************
4  * Copyright (C) 2015 Rémi Denis-Courmont
5  *
6  * This program is free software; you can redistribute it and/or modify
7  * it under the terms of the GNU Lesser General Public License as published by
8  * the Free Software Foundation; either version 2.1 of the License, or
9  * (at your option) any later version.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU Lesser General Public License for more details.
15  *
16  * You should have received a copy of the GNU Lesser General Public License
17  * along with this program; if not, write to the Free Software Foundation,
18  * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
19  *****************************************************************************/
20 
21 #ifdef HAVE_CONFIG_H
22 # include <config.h>
23 #endif
24 
25 #include <errno.h>
26 #include <stdbool.h>
27 #include <stdint.h>
28 #include <stdio.h>
29 #include <stdlib.h>
30 #include <string.h>
31 
32 #include <vlc_common.h>
33 #include <vlc_url.h>
34 #include <vlc_strings.h>
35 #include "message.h"
36 #include "connmgr.h"
37 #include "resource.h"
38 
39 static struct vlc_http_msg *
vlc_http_res_req(const struct vlc_http_resource * res,void * opaque)40 vlc_http_res_req(const struct vlc_http_resource *res, void *opaque)
41 {
42     struct vlc_http_msg *req;
43 
44     req = vlc_http_req_create("GET", res->secure ? "https" : "http",
45                               res->authority, res->path);
46     if (unlikely(req == NULL))
47         return NULL;
48 
49     /* Content negotiation */
50     vlc_http_msg_add_header(req, "Accept", "*/*");
51 
52     if (res->negotiate)
53     {
54         const char *lang = vlc_gettext("C");
55         if (!strcmp(lang, "C"))
56             lang = "en_US";
57         vlc_http_msg_add_header(req, "Accept-Language", "%s", lang);
58     }
59 
60     /* Authentication */
61     if (res->username != NULL && res->password != NULL)
62         vlc_http_msg_add_creds_basic(req, false, res->username, res->password);
63 
64     /* Request context */
65     if (res->agent != NULL)
66         vlc_http_msg_add_agent(req, res->agent);
67 
68     if (res->referrer != NULL) /* TODO: validate URL */
69         vlc_http_msg_add_header(req, "Referer", "%s", res->referrer);
70 
71     vlc_http_msg_add_cookies(req, vlc_http_mgr_get_jar(res->manager));
72 
73     /* TODO: vlc_http_msg_add_header(req, "TE", "gzip, deflate"); */
74 
75     if (res->cbs->request_format(res, req, opaque))
76     {
77         vlc_http_msg_destroy(req);
78         return NULL;
79     }
80 
81     return req;
82 }
83 
vlc_http_res_open(struct vlc_http_resource * res,void * opaque)84 struct vlc_http_msg *vlc_http_res_open(struct vlc_http_resource *res,
85                                        void *opaque)
86 {
87     struct vlc_http_msg *req;
88 retry:
89     req = vlc_http_res_req(res, opaque);
90     if (unlikely(req == NULL))
91         return NULL;
92 
93     struct vlc_http_msg *resp = vlc_http_mgr_request(res->manager, res->secure,
94                                                     res->host, res->port, req);
95     vlc_http_msg_destroy(req);
96 
97     resp = vlc_http_msg_get_final(resp);
98     if (resp == NULL)
99         return NULL;
100 
101     vlc_http_msg_get_cookies(resp, vlc_http_mgr_get_jar(res->manager),
102                              res->host, res->path);
103 
104     int status = vlc_http_msg_get_status(resp);
105     if (status < 200 || status >= 599)
106         goto fail;
107 
108     if (status == 406 && res->negotiate)
109     {   /* Not Acceptable: Content negotiation failed. Normally it means
110          * one (or more) Accept or Accept-* header line does not match any
111          * representation of the entity. So we set a flag to remove those
112          * header lines (unless they accept everything), and retry.
113          * In principles, it could be any header line, and the server can
114          * pass Vary to clarify. It cannot be caused by If-*, Range, TE or the
115          * other transfer- rather than representation-affecting header lines.
116          */
117         vlc_http_msg_destroy(resp);
118         res->negotiate = false;
119         goto retry;
120     }
121 
122     if (res->cbs->response_validate(res, resp, opaque))
123         goto fail;
124 
125     return resp;
126 fail:
127     vlc_http_msg_destroy(resp);
128     return NULL;
129 }
130 
vlc_http_res_get_status(struct vlc_http_resource * res)131 int vlc_http_res_get_status(struct vlc_http_resource *res)
132 {
133     if (res->response == NULL)
134     {
135         if (res->failure)
136             return -1;
137 
138         res->response = vlc_http_res_open(res, res + 1);
139         if (res->response == NULL)
140         {
141             res->failure = true;
142             return -1;
143         }
144     }
145     return vlc_http_msg_get_status(res->response);
146 }
147 
vlc_http_res_deinit(struct vlc_http_resource * res)148 static void vlc_http_res_deinit(struct vlc_http_resource *res)
149 {
150     free(res->referrer);
151     free(res->agent);
152     free(res->password);
153     free(res->username);
154     free(res->path);
155     free(res->authority);
156     free(res->host);
157 
158     if (res->response != NULL)
159         vlc_http_msg_destroy(res->response);
160 }
161 
vlc_http_res_destroy(struct vlc_http_resource * res)162 void vlc_http_res_destroy(struct vlc_http_resource *res)
163 {
164     vlc_http_res_deinit(res);
165     free(res);
166 }
167 
vlc_http_authority(const char * host,unsigned port)168 static char *vlc_http_authority(const char *host, unsigned port)
169 {
170     static const char *const formats[4] = { "%s", "[%s]", "%s:%u", "[%s]:%u" };
171     const bool brackets = strchr(host, ':') != NULL;
172     const char *fmt = formats[brackets + 2 * (port != 0)];
173     char *authority;
174 
175     if (unlikely(asprintf(&authority, fmt, host, port) == -1))
176         return NULL;
177     return authority;
178 }
179 
vlc_http_res_init(struct vlc_http_resource * restrict res,const struct vlc_http_resource_cbs * cbs,struct vlc_http_mgr * mgr,const char * uri,const char * ua,const char * ref)180 int vlc_http_res_init(struct vlc_http_resource *restrict res,
181                       const struct vlc_http_resource_cbs *cbs,
182                       struct vlc_http_mgr *mgr,
183                       const char *uri, const char *ua, const char *ref)
184 {
185     vlc_url_t url;
186     bool secure;
187 
188     if (vlc_UrlParse(&url, uri))
189         goto error;
190     if (url.psz_protocol == NULL || url.psz_host == NULL)
191     {
192         errno = EINVAL;
193         goto error;
194     }
195 
196     if (!vlc_ascii_strcasecmp(url.psz_protocol, "https"))
197         secure = true;
198     else if (!vlc_ascii_strcasecmp(url.psz_protocol, "http"))
199         secure = false;
200     else
201     {
202         errno = ENOTSUP;
203         goto error;
204     }
205 
206     res->cbs = cbs;
207     res->response = NULL;
208     res->secure = secure;
209     res->negotiate = true;
210     res->failure = false;
211     res->host = strdup(url.psz_host);
212     res->port = url.i_port;
213     res->authority = vlc_http_authority(url.psz_host, url.i_port);
214     res->username = (url.psz_username != NULL) ? strdup(url.psz_username)
215                                                : NULL;
216     res->password = (url.psz_password != NULL) ? strdup(url.psz_password)
217                                                : NULL;
218     res->agent = (ua != NULL) ? strdup(ua) : NULL;
219     res->referrer = (ref != NULL) ? strdup(ref) : NULL;
220 
221     const char *path = url.psz_path;
222     if (path == NULL)
223         path = "/";
224 
225     if (url.psz_option != NULL)
226     {
227         if (asprintf(&res->path, "%s?%s", path, url.psz_option) == -1)
228             res->path = NULL;
229     }
230     else
231         res->path = strdup(path);
232 
233     vlc_UrlClean(&url);
234     res->manager = mgr;
235 
236     if (unlikely(res->host == NULL || res->authority == NULL
237               || res->path == NULL))
238     {
239         vlc_http_res_deinit(res);
240         return -1;
241     }
242     return 0;
243 error:
244     vlc_UrlClean(&url);
245     return -1;
246 }
247 
vlc_http_res_get_redirect(struct vlc_http_resource * restrict res)248 char *vlc_http_res_get_redirect(struct vlc_http_resource *restrict res)
249 {
250     int status = vlc_http_res_get_status(res);
251     if (status < 0)
252         return NULL;
253 
254     if ((status / 100) == 2 && !res->secure)
255     {
256         char *url;
257 
258         /* HACK: Seems like an MMS server. Redirect to MMSH scheme. */
259         const char *pragma = vlc_http_msg_get_header(res->response, "Pragma");
260         if (pragma != NULL && !vlc_ascii_strcasecmp(pragma, "features")
261          && asprintf(&url, "mmsh://%s%s", res->authority, res->path) >= 0)
262             return url;
263 
264         /* HACK: Seems like an ICY server. Redirect to ICYX scheme. */
265         if ((vlc_http_msg_get_header(res->response, "Icy-Name") != NULL
266           || vlc_http_msg_get_header(res->response, "Icy-Genre") != NULL)
267          && asprintf(&url, "icyx://%s%s", res->authority, res->path) >= 0)
268             return url;
269     }
270 
271     /* TODO: if (status == 426 Upgrade Required) */
272 
273     /* Location header is only meaningful for 201 and 3xx */
274     if (status != 201 && (status / 100) != 3)
275         return NULL;
276     if (status == 304 /* Not Modified */
277      || status == 305 /* Use Proxy (deprecated) */
278      || status == 306 /* Switch Proxy (former) */)
279         return NULL;
280 
281     const char *location = vlc_http_msg_get_header(res->response, "Location");
282     if (location == NULL)
283         return NULL;
284 
285     /* TODO: if status is 3xx, check for Retry-After and wait */
286 
287     char *base;
288 
289     if (unlikely(asprintf(&base, "http%s://%s%s", res->secure ? "s" : "",
290                           res->authority, res->path) == -1))
291         return NULL;
292 
293     char *fixed = vlc_uri_fixup(location);
294     if (fixed != NULL)
295         location = fixed;
296 
297     char *abs = vlc_uri_resolve(base, location);
298 
299     free(fixed);
300     free(base);
301 
302     if (likely(abs != NULL))
303     {
304         /* NOTE: The anchor is discarded if it is present as VLC does not support
305          * HTML anchors so far. */
306         size_t len = strcspn(abs, "#");
307         abs[len] = '\0';
308     }
309     return abs;
310 }
311 
vlc_http_res_get_type(struct vlc_http_resource * res)312 char *vlc_http_res_get_type(struct vlc_http_resource *res)
313 {
314     int status = vlc_http_res_get_status(res);
315     if (status < 200 || status >= 300)
316         return NULL;
317 
318     const char *type = vlc_http_msg_get_header(res->response, "Content-Type");
319     return (type != NULL) ? strdup(type) : NULL;
320 }
321 
vlc_http_res_read(struct vlc_http_resource * res)322 struct block_t *vlc_http_res_read(struct vlc_http_resource *res)
323 {
324     int status = vlc_http_res_get_status(res);
325     if (status < 200 || status >= 300)
326         return NULL; /* do not "read" redirect or error message */
327 
328     return vlc_http_msg_read(res->response);
329 }
330 
vlc_http_res_set_login(struct vlc_http_resource * res,const char * username,const char * password)331 int vlc_http_res_set_login(struct vlc_http_resource *res,
332                            const char *username, const char *password)
333 {
334     char *user = NULL;
335     char *pass = NULL;
336 
337     if (username != NULL)
338     {
339         user = strdup(username);
340         if (unlikely(user == NULL))
341             return -1;
342 
343         pass = strdup((password != NULL) ? password : "");
344         if (unlikely(pass == NULL))
345         {
346             free(user);
347             return -1;
348         }
349     }
350 
351     free(res->password);
352     free(res->username);
353     res->username = user;
354     res->password = pass;
355 
356     if (res->response != NULL && vlc_http_msg_get_status(res->response) == 401)
357     {
358         vlc_http_msg_destroy(res->response);
359         res->response = NULL;
360     }
361 
362     return 0;
363 }
364 
vlc_http_res_get_basic_realm(struct vlc_http_resource * res)365 char *vlc_http_res_get_basic_realm(struct vlc_http_resource *res)
366 {
367     int status = vlc_http_res_get_status(res);
368     if (status != 401)
369         return NULL;
370     return vlc_http_msg_get_basic_realm(res->response);
371 }
372