/src/ffmpeg/libavformat/mpjpegdec.c
Line | Count | Source (jump to first uncovered line) |
1 | | /* |
2 | | * Multipart JPEG format |
3 | | * Copyright (c) 2015 Luca Barbato |
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 "libavutil/avstring.h" |
23 | | #include "libavutil/mem.h" |
24 | | #include "libavutil/opt.h" |
25 | | |
26 | | #include "avformat.h" |
27 | | #include "demux.h" |
28 | | #include "internal.h" |
29 | | #include "avio_internal.h" |
30 | | |
31 | | typedef struct MPJPEGDemuxContext { |
32 | | const AVClass *class; |
33 | | char *boundary; |
34 | | char *searchstr; |
35 | | int searchstr_len; |
36 | | int strict_mime_boundary; |
37 | | } MPJPEGDemuxContext; |
38 | | |
39 | | static void trim_right(char *p) |
40 | 370k | { |
41 | 370k | char *end; |
42 | | |
43 | 370k | if (!p || !*p) |
44 | 86.6k | return; |
45 | | |
46 | 283k | end = p + strlen(p); |
47 | 438k | while (end > p && av_isspace(*(end-1))) |
48 | 155k | *(--end) = '\0'; |
49 | 283k | } |
50 | | |
51 | | static int get_line(AVIOContext *pb, char *line, int line_size) |
52 | 229k | { |
53 | 229k | ff_get_line(pb, line, line_size); |
54 | | |
55 | 229k | if (pb->error) |
56 | 0 | return pb->error; |
57 | | |
58 | 229k | if (pb->eof_reached) |
59 | 7.65k | return AVERROR_EOF; |
60 | | |
61 | 221k | trim_right(line); |
62 | 221k | return 0; |
63 | 229k | } |
64 | | |
65 | | static int split_tag_value(char **tag, char **value, char *line) |
66 | 75.6k | { |
67 | 75.6k | char *p = line; |
68 | 75.6k | int foundData = 0; |
69 | | |
70 | 75.6k | *tag = NULL; |
71 | 75.6k | *value = NULL; |
72 | | |
73 | 875k | while (*p != '\0' && *p != ':') { |
74 | 800k | if (!av_isspace(*p)) { |
75 | 775k | foundData = 1; |
76 | 775k | } |
77 | 800k | p++; |
78 | 800k | } |
79 | 75.6k | if (*p != ':') |
80 | 1.61k | return foundData ? AVERROR_INVALIDDATA : 0; |
81 | | |
82 | 74.0k | *p = '\0'; |
83 | 74.0k | *tag = line; |
84 | 74.0k | trim_right(*tag); |
85 | | |
86 | 74.0k | p++; |
87 | | |
88 | 101k | while (av_isspace(*p)) |
89 | 27.7k | p++; |
90 | | |
91 | 74.0k | *value = p; |
92 | 74.0k | trim_right(*value); |
93 | | |
94 | 74.0k | return 0; |
95 | 75.6k | } |
96 | | |
97 | | static int parse_multipart_header(AVIOContext *pb, |
98 | | int* size, |
99 | | const char* expected_boundary, |
100 | | void *log_ctx); |
101 | | |
102 | | static int mpjpeg_read_close(AVFormatContext *s) |
103 | 824 | { |
104 | 824 | MPJPEGDemuxContext *mpjpeg = s->priv_data; |
105 | 824 | av_freep(&mpjpeg->boundary); |
106 | 824 | av_freep(&mpjpeg->searchstr); |
107 | 824 | return 0; |
108 | 824 | } |
109 | | |
110 | | static int mpjpeg_read_probe(const AVProbeData *p) |
111 | 358k | { |
112 | 358k | FFIOContext pb; |
113 | 358k | int ret = 0; |
114 | 358k | int size = 0; |
115 | | |
116 | 358k | if (p->buf_size < 2 || p->buf[0] != '-' || p->buf[1] != '-') |
117 | 352k | return 0; |
118 | | |
119 | 5.61k | ffio_init_read_context(&pb, p->buf, p->buf_size); |
120 | | |
121 | 5.61k | ret = (parse_multipart_header(&pb.pub, &size, "--", NULL) >= 0) ? AVPROBE_SCORE_MAX : 0; |
122 | | |
123 | 5.61k | return ret; |
124 | 358k | } |
125 | | |
126 | | static int mpjpeg_read_header(AVFormatContext *s) |
127 | 5.45k | { |
128 | 5.45k | AVStream *st; |
129 | 5.45k | char boundary[70 + 2 + 1] = {0}; |
130 | 5.45k | int64_t pos = avio_tell(s->pb); |
131 | 5.45k | int ret; |
132 | | |
133 | 10.6k | do { |
134 | 10.6k | ret = get_line(s->pb, boundary, sizeof(boundary)); |
135 | 10.6k | if (ret < 0) |
136 | 4.40k | return ret; |
137 | 10.6k | } while (!boundary[0]); |
138 | | |
139 | 1.05k | if (strncmp(boundary, "--", 2)) |
140 | 227 | return AVERROR_INVALIDDATA; |
141 | | |
142 | 824 | st = avformat_new_stream(s, NULL); |
143 | 824 | if (!st) |
144 | 0 | return AVERROR(ENOMEM); |
145 | | |
146 | 824 | st->codecpar->codec_type = AVMEDIA_TYPE_VIDEO; |
147 | 824 | st->codecpar->codec_id = AV_CODEC_ID_MJPEG; |
148 | | |
149 | 824 | avpriv_set_pts_info(st, 60, 1, 25); |
150 | | |
151 | 824 | avio_seek(s->pb, pos, SEEK_SET); |
152 | | |
153 | 824 | return 0; |
154 | 824 | } |
155 | | |
156 | | static int parse_content_length(const char *value) |
157 | 0 | { |
158 | 0 | long int val = strtol(value, NULL, 10); |
159 | |
|
160 | 0 | if (val == LONG_MIN || val == LONG_MAX) |
161 | 0 | return AVERROR(errno); |
162 | 0 | if (val > INT_MAX) |
163 | 0 | return AVERROR(ERANGE); |
164 | 0 | return val; |
165 | 0 | } |
166 | | |
167 | | static int parse_multipart_header(AVIOContext *pb, |
168 | | int* size, |
169 | | const char* expected_boundary, |
170 | | void *log_ctx) |
171 | 31.9k | { |
172 | 31.9k | char line[128]; |
173 | 31.9k | int found_content_type = 0; |
174 | 31.9k | int ret; |
175 | | |
176 | 31.9k | *size = -1; |
177 | | |
178 | | // get the CRLF as empty string |
179 | 31.9k | ret = get_line(pb, line, sizeof(line)); |
180 | 31.9k | if (ret < 0) |
181 | 1.75k | return ret; |
182 | | |
183 | | /* some implementation do not provide the required |
184 | | * initial CRLF (see rfc1341 7.2.1) |
185 | | */ |
186 | 113k | while (!line[0]) { |
187 | 83.0k | ret = get_line(pb, line, sizeof(line)); |
188 | 83.0k | if (ret < 0) |
189 | 57 | return ret; |
190 | 83.0k | } |
191 | | |
192 | 30.1k | if (!av_strstart(line, expected_boundary, NULL)) { |
193 | 55 | if (log_ctx) |
194 | 55 | av_log(log_ctx, |
195 | 55 | AV_LOG_ERROR, |
196 | 55 | "Expected boundary '%s' not found, instead found a line of %"SIZE_SPECIFIER" bytes\n", |
197 | 55 | expected_boundary, |
198 | 55 | strlen(line)); |
199 | | |
200 | 55 | return AVERROR_INVALIDDATA; |
201 | 55 | } |
202 | | |
203 | 104k | while (!pb->eof_reached) { |
204 | 104k | char *tag, *value; |
205 | | |
206 | 104k | ret = get_line(pb, line, sizeof(line)); |
207 | 104k | if (ret < 0) { |
208 | 1.43k | if (ret == AVERROR_EOF) |
209 | 1.43k | break; |
210 | 0 | return ret; |
211 | 1.43k | } |
212 | | |
213 | 102k | if (line[0] == '\0') |
214 | 26.9k | break; |
215 | | |
216 | 75.6k | ret = split_tag_value(&tag, &value, line); |
217 | 75.6k | if (ret < 0) |
218 | 1.61k | return ret; |
219 | 74.0k | if (value==NULL || tag==NULL) |
220 | 0 | break; |
221 | | |
222 | 74.0k | if (!av_strcasecmp(tag, "Content-type")) { |
223 | 25.2k | if (av_strcasecmp(value, "image/jpeg")) { |
224 | 133 | if (log_ctx) |
225 | 35 | av_log(log_ctx, AV_LOG_ERROR, |
226 | 35 | "Unexpected %s : %s\n", |
227 | 35 | tag, value); |
228 | 133 | return AVERROR_INVALIDDATA; |
229 | 133 | } else |
230 | 25.1k | found_content_type = 1; |
231 | 48.7k | } else if (!av_strcasecmp(tag, "Content-Length")) { |
232 | 0 | *size = parse_content_length(value); |
233 | 0 | if ( *size < 0 ) |
234 | 0 | if (log_ctx) |
235 | 0 | av_log(log_ctx, AV_LOG_WARNING, |
236 | 0 | "Invalid Content-Length value : %s\n", |
237 | 0 | value); |
238 | 0 | } |
239 | 74.0k | } |
240 | | |
241 | 28.3k | return found_content_type ? 0 : AVERROR_INVALIDDATA; |
242 | 30.0k | } |
243 | | |
244 | | static char* mpjpeg_get_boundary(AVIOContext* pb) |
245 | 0 | { |
246 | 0 | uint8_t *mime_type = NULL; |
247 | 0 | const char *start; |
248 | 0 | const char *end; |
249 | 0 | uint8_t *res = NULL; |
250 | 0 | int len; |
251 | | |
252 | | /* get MIME type, and skip to the first parameter */ |
253 | 0 | av_opt_get(pb, "mime_type", AV_OPT_SEARCH_CHILDREN, &mime_type); |
254 | 0 | start = mime_type; |
255 | 0 | while (start != NULL && *start != '\0') { |
256 | 0 | start = strchr(start, ';'); |
257 | 0 | if (!start) |
258 | 0 | break; |
259 | | |
260 | 0 | start = start+1; |
261 | |
|
262 | 0 | while (av_isspace(*start)) |
263 | 0 | start++; |
264 | |
|
265 | 0 | if (av_stristart(start, "boundary=", &start)) { |
266 | 0 | end = strchr(start, ';'); |
267 | 0 | if (end) |
268 | 0 | len = end - start - 1; |
269 | 0 | else |
270 | 0 | len = strlen(start); |
271 | | |
272 | | /* some endpoints may enclose the boundary |
273 | | in Content-Type in quotes */ |
274 | 0 | if ( len>2 && *start == '"' && start[len-1] == '"' ) { |
275 | 0 | start++; |
276 | 0 | len -= 2; |
277 | 0 | } |
278 | 0 | res = av_strndup(start, len); |
279 | 0 | break; |
280 | 0 | } |
281 | 0 | } |
282 | |
|
283 | 0 | av_freep(&mime_type); |
284 | 0 | return res; |
285 | 0 | } |
286 | | |
287 | | static int mpjpeg_read_packet(AVFormatContext *s, AVPacket *pkt) |
288 | 26.3k | { |
289 | 26.3k | int size; |
290 | 26.3k | int ret; |
291 | | |
292 | 26.3k | MPJPEGDemuxContext *mpjpeg = s->priv_data; |
293 | 26.3k | if (mpjpeg->boundary == NULL) { |
294 | 824 | uint8_t* boundary = NULL; |
295 | 824 | if (mpjpeg->strict_mime_boundary) { |
296 | 0 | boundary = mpjpeg_get_boundary(s->pb); |
297 | 0 | } |
298 | 824 | if (boundary != NULL) { |
299 | 0 | mpjpeg->boundary = av_asprintf("--%s", boundary); |
300 | 0 | mpjpeg->searchstr = av_asprintf("\r\n--%s\r\n", boundary); |
301 | 0 | av_freep(&boundary); |
302 | 824 | } else { |
303 | 824 | mpjpeg->boundary = av_strdup("--"); |
304 | 824 | mpjpeg->searchstr = av_strdup("\r\n--"); |
305 | 824 | } |
306 | 824 | if (!mpjpeg->boundary || !mpjpeg->searchstr) { |
307 | 0 | av_freep(&mpjpeg->boundary); |
308 | 0 | av_freep(&mpjpeg->searchstr); |
309 | 0 | return AVERROR(ENOMEM); |
310 | 0 | } |
311 | 824 | mpjpeg->searchstr_len = strlen(mpjpeg->searchstr); |
312 | 824 | } |
313 | | |
314 | 26.3k | ret = parse_multipart_header(s->pb, &size, mpjpeg->boundary, s); |
315 | 26.3k | if (ret < 0) |
316 | 1.60k | return ret; |
317 | | |
318 | 24.7k | if (size > 0) { |
319 | | /* size has been provided to us in MIME header */ |
320 | 0 | ret = av_get_packet(s->pb, pkt, size); |
321 | 24.7k | } else { |
322 | | /* no size was given -- we read until the next boundary or end-of-file */ |
323 | 24.7k | int len; |
324 | | |
325 | 24.7k | const int read_chunk = 2048; |
326 | | |
327 | 24.7k | pkt->pos = avio_tell(s->pb); |
328 | | |
329 | 29.2k | while ((ret = ffio_ensure_seekback(s->pb, read_chunk)) >= 0 && /* we may need to return as much as all we've read back to the buffer */ |
330 | 29.2k | (ret = av_append_packet(s->pb, pkt, read_chunk)) >= 0) { |
331 | | /* scan the new data */ |
332 | 29.0k | char *start; |
333 | | |
334 | 29.0k | len = ret; |
335 | 29.0k | start = pkt->data + pkt->size - len; |
336 | 8.56M | do { |
337 | 8.56M | if (!memcmp(start, mpjpeg->searchstr, mpjpeg->searchstr_len)) { |
338 | | // got the boundary! rewind the stream |
339 | 24.5k | avio_seek(s->pb, -len, SEEK_CUR); |
340 | 24.5k | pkt->size -= len; |
341 | 24.5k | return pkt->size; |
342 | 24.5k | } |
343 | 8.53M | len--; |
344 | 8.53M | start++; |
345 | 8.53M | } while (len >= mpjpeg->searchstr_len); |
346 | 4.50k | avio_seek(s->pb, -len, SEEK_CUR); |
347 | 4.50k | pkt->size -= len; |
348 | 4.50k | } |
349 | | |
350 | | /* error or EOF occurred */ |
351 | 243 | if (ret == AVERROR_EOF) { |
352 | 243 | ret = pkt->size > 0 ? pkt->size : AVERROR_EOF; |
353 | 243 | } |
354 | 243 | } |
355 | | |
356 | 243 | return ret; |
357 | 24.7k | } |
358 | | |
359 | | #define OFFSET(x) offsetof(MPJPEGDemuxContext, x) |
360 | | #define DEC AV_OPT_FLAG_DECODING_PARAM |
361 | | static const AVOption mpjpeg_options[] = { |
362 | | { "strict_mime_boundary", "require MIME boundaries match", OFFSET(strict_mime_boundary), AV_OPT_TYPE_BOOL, {.i64 = 0}, 0, 1, DEC }, |
363 | | { NULL } |
364 | | }; |
365 | | |
366 | | static const AVClass mpjpeg_demuxer_class = { |
367 | | .class_name = "MPJPEG demuxer", |
368 | | .item_name = av_default_item_name, |
369 | | .option = mpjpeg_options, |
370 | | .version = LIBAVUTIL_VERSION_INT, |
371 | | }; |
372 | | |
373 | | const FFInputFormat ff_mpjpeg_demuxer = { |
374 | | .p.name = "mpjpeg", |
375 | | .p.long_name = NULL_IF_CONFIG_SMALL("MIME multipart JPEG"), |
376 | | .p.mime_type = "multipart/x-mixed-replace", |
377 | | .p.extensions = "mjpg", |
378 | | .p.priv_class = &mpjpeg_demuxer_class, |
379 | | .p.flags = AVFMT_NOTIMESTAMPS, |
380 | | .priv_data_size = sizeof(MPJPEGDemuxContext), |
381 | | .read_probe = mpjpeg_read_probe, |
382 | | .read_header = mpjpeg_read_header, |
383 | | .read_packet = mpjpeg_read_packet, |
384 | | .read_close = mpjpeg_read_close, |
385 | | }; |