/src/ffmpeg/libavformat/url.c
Line | Count | Source |
1 | | /* |
2 | | * URL utility functions |
3 | | * Copyright (c) 2000, 2001, 2002 Fabrice Bellard |
4 | | * |
5 | | * This file is part of FFmpeg. |
6 | | * |
7 | | * FFmpeg is free software; you can redistribute it and/or |
8 | | * modify it under the terms of the GNU Lesser General Public |
9 | | * License as published by the Free Software Foundation; either |
10 | | * version 2.1 of the License, or (at your option) any later version. |
11 | | * |
12 | | * FFmpeg is distributed in the hope that it will be useful, |
13 | | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
14 | | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
15 | | * Lesser General Public License for more details. |
16 | | * |
17 | | * You should have received a copy of the GNU Lesser General Public |
18 | | * License along with FFmpeg; if not, write to the Free Software |
19 | | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA |
20 | | */ |
21 | | |
22 | | #include <string.h> |
23 | | |
24 | | #include "config.h" |
25 | | #include "avio.h" |
26 | | #include "url.h" |
27 | | #if CONFIG_NETWORK |
28 | | #include "network.h" |
29 | | #endif |
30 | | #include "libavutil/avassert.h" |
31 | | #include "libavutil/avstring.h" |
32 | | #include "libavutil/error.h" |
33 | | #include "libavutil/mem.h" |
34 | | |
35 | | /** |
36 | | * @file |
37 | | * URL utility functions. |
38 | | */ |
39 | | |
40 | | int ff_url_join(char *str, int size, const char *proto, |
41 | | const char *authorization, const char *hostname, |
42 | | int port, const char *fmt, ...) |
43 | 0 | { |
44 | 0 | #if CONFIG_NETWORK |
45 | 0 | struct addrinfo hints = { 0 }, *ai; |
46 | 0 | #endif |
47 | |
|
48 | 0 | str[0] = '\0'; |
49 | 0 | if (proto) |
50 | 0 | av_strlcatf(str, size, "%s://", proto); |
51 | 0 | if (authorization && authorization[0]) |
52 | 0 | av_strlcatf(str, size, "%s@", authorization); |
53 | 0 | #if CONFIG_NETWORK && defined(AF_INET6) |
54 | | /* Determine if hostname is a numerical IPv6 address, |
55 | | * properly escape it within [] in that case. */ |
56 | 0 | hints.ai_flags = AI_NUMERICHOST; |
57 | 0 | if (!getaddrinfo(hostname, NULL, &hints, &ai)) { |
58 | 0 | if (ai->ai_family == AF_INET6) { |
59 | 0 | av_strlcat(str, "[", size); |
60 | 0 | av_strlcat(str, hostname, size); |
61 | 0 | av_strlcat(str, "]", size); |
62 | 0 | } else { |
63 | 0 | av_strlcat(str, hostname, size); |
64 | 0 | } |
65 | 0 | freeaddrinfo(ai); |
66 | 0 | } else |
67 | 0 | #endif |
68 | | /* Not an IPv6 address, just output the plain string. */ |
69 | 0 | av_strlcat(str, hostname, size); |
70 | |
|
71 | 0 | if (port >= 0) |
72 | 0 | av_strlcatf(str, size, ":%d", port); |
73 | 0 | if (fmt) { |
74 | 0 | va_list vl; |
75 | 0 | size_t len = strlen(str); |
76 | |
|
77 | 0 | va_start(vl, fmt); |
78 | 0 | vsnprintf(str + len, size > len ? size - len : 0, fmt, vl); |
79 | 0 | va_end(vl); |
80 | 0 | } |
81 | 0 | return strlen(str); |
82 | 0 | } |
83 | | |
84 | | static const char *find_delim(const char *delim, const char *cur, const char *end) |
85 | 0 | { |
86 | 0 | while (cur < end && !strchr(delim, *cur)) |
87 | 0 | cur++; |
88 | 0 | return cur; |
89 | 0 | } |
90 | | |
91 | | int ff_url_decompose(URLComponents *uc, const char *url, const char *end) |
92 | 0 | { |
93 | 0 | const char *cur, *aend, *p; |
94 | |
|
95 | 0 | av_assert0(url); |
96 | 0 | if (!end) |
97 | 0 | end = url + strlen(url); |
98 | 0 | cur = uc->url = url; |
99 | | |
100 | | /* scheme */ |
101 | 0 | uc->scheme = cur; |
102 | 0 | p = find_delim(":/?#", cur, end); /* lavf "schemes" can contain options but not some RFC 3986 delimiters */ |
103 | 0 | if (*p == ':') |
104 | 0 | cur = p + 1; |
105 | | |
106 | | /* authority */ |
107 | 0 | uc->authority = cur; |
108 | 0 | if (end - cur >= 2 && cur[0] == '/' && cur[1] == '/') { |
109 | 0 | cur += 2; |
110 | 0 | aend = find_delim("/?#", cur, end); |
111 | | |
112 | | /* userinfo */ |
113 | 0 | uc->userinfo = cur; |
114 | 0 | p = find_delim("@", cur, aend); |
115 | 0 | if (*p == '@') |
116 | 0 | cur = p + 1; |
117 | | |
118 | | /* host */ |
119 | 0 | uc->host = cur; |
120 | 0 | if (*cur == '[') { /* hello IPv6, thanks for using colons! */ |
121 | 0 | p = find_delim("]", cur, aend); |
122 | 0 | if (*p != ']') |
123 | 0 | return AVERROR(EINVAL); |
124 | 0 | if (p + 1 < aend && p[1] != ':') |
125 | 0 | return AVERROR(EINVAL); |
126 | 0 | cur = p + 1; |
127 | 0 | } else { |
128 | 0 | cur = find_delim(":", cur, aend); |
129 | 0 | } |
130 | | |
131 | | /* port */ |
132 | 0 | uc->port = cur; |
133 | 0 | cur = aend; |
134 | 0 | } else { |
135 | 0 | uc->userinfo = uc->host = uc->port = cur; |
136 | 0 | } |
137 | | |
138 | | /* path */ |
139 | 0 | uc->path = cur; |
140 | 0 | cur = find_delim("?#", cur, end); |
141 | | |
142 | | /* query */ |
143 | 0 | uc->query = cur; |
144 | 0 | if (*cur == '?') |
145 | 0 | cur = find_delim("#", cur, end); |
146 | | |
147 | | /* fragment */ |
148 | 0 | uc->fragment = cur; |
149 | |
|
150 | 0 | uc->end = end; |
151 | 0 | return 0; |
152 | 0 | } |
153 | | |
154 | | static int is_fq_dos_path(const char *path) |
155 | 0 | { |
156 | 0 | if ((path[0] >= 'a' && path[0] <= 'z' || path[0] >= 'A' && path[0] <= 'Z') && |
157 | 0 | path[1] == ':' && |
158 | 0 | (path[2] == '/' || path[2] == '\\')) |
159 | 0 | return 1; |
160 | 0 | if ((path[0] == '/' || path[0] == '\\') && |
161 | 0 | (path[1] == '/' || path[1] == '\\')) |
162 | 0 | return 1; |
163 | 0 | return 0; |
164 | 0 | } |
165 | | |
166 | | static int append_path(char *root, char *out_end, char **rout, |
167 | | const char *in, const char *in_end) |
168 | 0 | { |
169 | 0 | char *out = *rout; |
170 | 0 | const char *d, *next; |
171 | |
|
172 | 0 | if (in < in_end && *in == '/') |
173 | 0 | in++; /* already taken care of */ |
174 | 0 | while (in < in_end) { |
175 | 0 | d = find_delim("/", in, in_end); |
176 | 0 | next = d + (d < in_end && *d == '/'); |
177 | 0 | if (d - in == 1 && in[0] == '.') { |
178 | | /* skip */ |
179 | 0 | } else if (d - in == 2 && in[0] == '.' && in[1] == '.') { |
180 | 0 | av_assert1(out[-1] == '/'); |
181 | 0 | if (out - root > 1) |
182 | 0 | while (out > root && (--out)[-1] != '/'); |
183 | 0 | } else { |
184 | 0 | if (out_end - out < next - in) |
185 | 0 | return AVERROR(ENOMEM); |
186 | 0 | memmove(out, in, next - in); |
187 | 0 | out += next - in; |
188 | 0 | } |
189 | 0 | in = next; |
190 | 0 | } |
191 | 0 | *rout = out; |
192 | 0 | return 0; |
193 | 0 | } |
194 | | |
195 | | int ff_make_absolute_url2(char *buf, int size, const char *base, |
196 | | const char *rel, int handle_dos_paths) |
197 | 0 | { |
198 | 0 | URLComponents ub, uc; |
199 | 0 | char *out, *out_end, *path; |
200 | 0 | const char *keep, *base_path_end; |
201 | 0 | int use_base_path, simplify_path = 0, ret; |
202 | 0 | const char *base_separators = "/"; |
203 | | |
204 | | /* This is tricky. |
205 | | For HTTP, http://server/site/page + ../media/file |
206 | | should resolve into http://server/media/file |
207 | | but for filesystem access, dir/playlist + ../media/file |
208 | | should resolve into dir/../media/file |
209 | | because dir could be a symlink, and .. points to |
210 | | the actual parent of the target directory. |
211 | | |
212 | | We'll consider that URLs with an actual scheme and authority, |
213 | | i.e. starting with scheme://, need parent dir simplification, |
214 | | while bare paths or pseudo-URLs starting with proto: without |
215 | | the double slash do not. |
216 | | |
217 | | For real URLs, the processing is similar to the algorithm described |
218 | | here: |
219 | | https://tools.ietf.org/html/rfc3986#section-5 |
220 | | */ |
221 | |
|
222 | 0 | if (!size) |
223 | 0 | return AVERROR(ENOMEM); |
224 | 0 | out = buf; |
225 | 0 | out_end = buf + size - 1; |
226 | |
|
227 | 0 | if (!base) |
228 | 0 | base = ""; |
229 | 0 | if (handle_dos_paths) { |
230 | 0 | if ((ret = ff_url_decompose(&ub, base, NULL)) < 0) |
231 | 0 | goto error; |
232 | 0 | if (is_fq_dos_path(base) || av_strstart(base, "file:", NULL) || ub.path == ub.url) { |
233 | 0 | base_separators = "/\\"; |
234 | 0 | if (is_fq_dos_path(rel)) |
235 | 0 | base = ""; |
236 | 0 | } |
237 | 0 | } |
238 | 0 | if ((ret = ff_url_decompose(&ub, base, NULL)) < 0 || |
239 | 0 | (ret = ff_url_decompose(&uc, rel, NULL)) < 0) |
240 | 0 | goto error; |
241 | | |
242 | 0 | keep = ub.url; |
243 | 0 | #define KEEP(component, also) do { \ |
244 | 0 | if (uc.url_component_end_##component == uc.url && \ |
245 | 0 | ub.url_component_end_##component > keep) { \ |
246 | 0 | keep = ub.url_component_end_##component; \ |
247 | 0 | also \ |
248 | 0 | } \ |
249 | 0 | } while (0) |
250 | 0 | KEEP(scheme, ); |
251 | 0 | KEEP(authority_full, simplify_path = 1;); |
252 | 0 | KEEP(path,); |
253 | 0 | KEEP(query,); |
254 | 0 | KEEP(fragment,); |
255 | 0 | #undef KEEP |
256 | 0 | #define COPY(start, end) do { \ |
257 | 0 | size_t len = end - start; \ |
258 | 0 | if (len > out_end - out) { \ |
259 | 0 | ret = AVERROR(ENOMEM); \ |
260 | 0 | goto error; \ |
261 | 0 | } \ |
262 | 0 | memmove(out, start, len); \ |
263 | 0 | out += len; \ |
264 | 0 | } while (0) |
265 | 0 | COPY(ub.url, keep); |
266 | 0 | COPY(uc.url, uc.path); |
267 | | |
268 | 0 | use_base_path = URL_COMPONENT_HAVE(ub, path) && keep <= ub.path; |
269 | 0 | if (uc.path > uc.url) |
270 | 0 | use_base_path = 0; |
271 | 0 | if (URL_COMPONENT_HAVE(uc, path) && uc.path[0] == '/') |
272 | 0 | use_base_path = 0; |
273 | 0 | if (use_base_path) { |
274 | 0 | base_path_end = ub.url_component_end_path; |
275 | 0 | if (URL_COMPONENT_HAVE(uc, path)) |
276 | 0 | while (base_path_end > ub.path && !strchr(base_separators, base_path_end[-1])) |
277 | 0 | base_path_end--; |
278 | 0 | } |
279 | 0 | if (keep > ub.path) |
280 | 0 | simplify_path = 0; |
281 | 0 | if (URL_COMPONENT_HAVE(uc, scheme)) |
282 | 0 | simplify_path = 0; |
283 | 0 | if (URL_COMPONENT_HAVE(uc, authority)) |
284 | 0 | simplify_path = 1; |
285 | | /* No path at all, leave it */ |
286 | 0 | if (!use_base_path && !URL_COMPONENT_HAVE(uc, path)) |
287 | 0 | simplify_path = 0; |
288 | |
|
289 | 0 | if (simplify_path) { |
290 | 0 | const char *root = "/"; |
291 | 0 | COPY(root, root + 1); |
292 | 0 | path = out; |
293 | 0 | if (use_base_path) { |
294 | 0 | ret = append_path(path, out_end, &out, ub.path, base_path_end); |
295 | 0 | if (ret < 0) |
296 | 0 | goto error; |
297 | 0 | } |
298 | 0 | if (URL_COMPONENT_HAVE(uc, path)) { |
299 | 0 | ret = append_path(path, out_end, &out, uc.path, uc.url_component_end_path); |
300 | 0 | if (ret < 0) |
301 | 0 | goto error; |
302 | 0 | } |
303 | 0 | } else { |
304 | 0 | if (use_base_path) |
305 | 0 | COPY(ub.path, base_path_end); |
306 | 0 | COPY(uc.path, uc.url_component_end_path); |
307 | 0 | } |
308 | | |
309 | 0 | COPY(uc.url_component_end_path, uc.end); |
310 | 0 | #undef COPY |
311 | 0 | *out = 0; |
312 | 0 | return 0; |
313 | | |
314 | 0 | error: |
315 | 0 | snprintf(buf, size, "invalid:%s", |
316 | 0 | ret == AVERROR(ENOMEM) ? "truncated" : |
317 | 0 | ret == AVERROR(EINVAL) ? "syntax_error" : ""); |
318 | 0 | return ret; |
319 | 0 | } |
320 | | |
321 | | int ff_make_absolute_url(char *buf, int size, const char *base, |
322 | | const char *rel) |
323 | 0 | { |
324 | 0 | return ff_make_absolute_url2(buf, size, base, rel, HAVE_DOS_PATHS); |
325 | 0 | } |
326 | | |
327 | | AVIODirEntry *ff_alloc_dir_entry(void) |
328 | 0 | { |
329 | 0 | AVIODirEntry *entry = av_mallocz(sizeof(AVIODirEntry)); |
330 | 0 | if (entry) { |
331 | 0 | entry->type = AVIO_ENTRY_UNKNOWN; |
332 | 0 | entry->size = -1; |
333 | 0 | entry->modification_timestamp = -1; |
334 | 0 | entry->access_timestamp = -1; |
335 | 0 | entry->status_change_timestamp = -1; |
336 | 0 | entry->user_id = -1; |
337 | 0 | entry->group_id = -1; |
338 | 0 | entry->filemode = -1; |
339 | 0 | } |
340 | 0 | return entry; |
341 | 0 | } |