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