FFmpeg coverage


Directory: ../../../ffmpeg/
File: src/libavformat/mpjpegdec.c
Date: 2024-11-20 23:03:26
Exec Total Coverage
Lines: 33 173 19.1%
Functions: 4 10 40.0%
Branches: 20 128 15.6%

Line Branch Exec Source
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 2 static void trim_right(char *p)
40 {
41 char *end;
42
43
2/4
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 2 times.
2 if (!p || !*p)
44 return;
45
46 2 end = p + strlen(p);
47
4/4
✓ Branch 0 taken 3 times.
✓ Branch 1 taken 1 times.
✓ Branch 2 taken 2 times.
✓ Branch 3 taken 1 times.
4 while (end > p && av_isspace(*(end-1)))
48 2 *(--end) = '\0';
49 }
50
51 2 static int get_line(AVIOContext *pb, char *line, int line_size)
52 {
53 2 ff_get_line(pb, line, line_size);
54
55
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 2 times.
2 if (pb->error)
56 return pb->error;
57
58
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 2 times.
2 if (pb->eof_reached)
59 return AVERROR_EOF;
60
61 2 trim_right(line);
62 2 return 0;
63 }
64
65 static int split_tag_value(char **tag, char **value, char *line)
66 {
67 char *p = line;
68 int foundData = 0;
69
70 *tag = NULL;
71 *value = NULL;
72
73 while (*p != '\0' && *p != ':') {
74 if (!av_isspace(*p)) {
75 foundData = 1;
76 }
77 p++;
78 }
79 if (*p != ':')
80 return foundData ? AVERROR_INVALIDDATA : 0;
81
82 *p = '\0';
83 *tag = line;
84 trim_right(*tag);
85
86 p++;
87
88 while (av_isspace(*p))
89 p++;
90
91 *value = p;
92 trim_right(*value);
93
94 return 0;
95 }
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 {
104 MPJPEGDemuxContext *mpjpeg = s->priv_data;
105 av_freep(&mpjpeg->boundary);
106 av_freep(&mpjpeg->searchstr);
107 return 0;
108 }
109
110 7186 static int mpjpeg_read_probe(const AVProbeData *p)
111 {
112 FFIOContext pb;
113 7186 int ret = 0;
114 7186 int size = 0;
115
116
4/6
✓ Branch 0 taken 7186 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1 times.
✓ Branch 3 taken 7185 times.
✗ Branch 4 not taken.
✓ Branch 5 taken 1 times.
7186 if (p->buf_size < 2 || p->buf[0] != '-' || p->buf[1] != '-')
117 7185 return 0;
118
119 1 ffio_init_read_context(&pb, p->buf, p->buf_size);
120
121
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 1 times.
1 ret = (parse_multipart_header(&pb.pub, &size, "--", NULL) >= 0) ? AVPROBE_SCORE_MAX : 0;
122
123 1 return ret;
124 }
125
126 static int mpjpeg_read_header(AVFormatContext *s)
127 {
128 AVStream *st;
129 char boundary[70 + 2 + 1] = {0};
130 int64_t pos = avio_tell(s->pb);
131 int ret;
132
133 do {
134 ret = get_line(s->pb, boundary, sizeof(boundary));
135 if (ret < 0)
136 return ret;
137 } while (!boundary[0]);
138
139 if (strncmp(boundary, "--", 2))
140 return AVERROR_INVALIDDATA;
141
142 st = avformat_new_stream(s, NULL);
143 if (!st)
144 return AVERROR(ENOMEM);
145
146 st->codecpar->codec_type = AVMEDIA_TYPE_VIDEO;
147 st->codecpar->codec_id = AV_CODEC_ID_MJPEG;
148
149 avpriv_set_pts_info(st, 60, 1, 25);
150
151 avio_seek(s->pb, pos, SEEK_SET);
152
153 return 0;
154 }
155
156 static int parse_content_length(const char *value)
157 {
158 long int val = strtol(value, NULL, 10);
159
160 if (val == LONG_MIN || val == LONG_MAX)
161 return AVERROR(errno);
162 if (val > INT_MAX)
163 return AVERROR(ERANGE);
164 return val;
165 }
166
167 1 static int parse_multipart_header(AVIOContext *pb,
168 int* size,
169 const char* expected_boundary,
170 void *log_ctx)
171 {
172 char line[128];
173 1 int found_content_type = 0;
174 int ret;
175
176 1 *size = -1;
177
178 // get the CRLF as empty string
179 1 ret = get_line(pb, line, sizeof(line));
180
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
1 if (ret < 0)
181 return ret;
182
183 /* some implementation do not provide the required
184 * initial CRLF (see rfc1341 7.2.1)
185 */
186
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
1 while (!line[0]) {
187 ret = get_line(pb, line, sizeof(line));
188 if (ret < 0)
189 return ret;
190 }
191
192
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 1 times.
1 if (!av_strstart(line, expected_boundary, NULL)) {
193 if (log_ctx)
194 av_log(log_ctx,
195 AV_LOG_ERROR,
196 "Expected boundary '%s' not found, instead found a line of %"SIZE_SPECIFIER" bytes\n",
197 expected_boundary,
198 strlen(line));
199
200 return AVERROR_INVALIDDATA;
201 }
202
203
1/2
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
1 while (!pb->eof_reached) {
204 char *tag, *value;
205
206 1 ret = get_line(pb, line, sizeof(line));
207
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
1 if (ret < 0) {
208 if (ret == AVERROR_EOF)
209 1 break;
210 return ret;
211 }
212
213
1/2
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
1 if (line[0] == '\0')
214 1 break;
215
216 ret = split_tag_value(&tag, &value, line);
217 if (ret < 0)
218 return ret;
219 if (value==NULL || tag==NULL)
220 break;
221
222 if (!av_strcasecmp(tag, "Content-type")) {
223 if (av_strcasecmp(value, "image/jpeg")) {
224 if (log_ctx)
225 av_log(log_ctx, AV_LOG_ERROR,
226 "Unexpected %s : %s\n",
227 tag, value);
228 return AVERROR_INVALIDDATA;
229 } else
230 found_content_type = 1;
231 } else if (!av_strcasecmp(tag, "Content-Length")) {
232 *size = parse_content_length(value);
233 if ( *size < 0 )
234 if (log_ctx)
235 av_log(log_ctx, AV_LOG_WARNING,
236 "Invalid Content-Length value : %s\n",
237 value);
238 }
239 }
240
241
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
1 return found_content_type ? 0 : AVERROR_INVALIDDATA;
242 }
243
244 static char* mpjpeg_get_boundary(AVIOContext* pb)
245 {
246 uint8_t *mime_type = NULL;
247 const char *start;
248 const char *end;
249 uint8_t *res = NULL;
250 int len;
251
252 /* get MIME type, and skip to the first parameter */
253 av_opt_get(pb, "mime_type", AV_OPT_SEARCH_CHILDREN, &mime_type);
254 start = mime_type;
255 while (start != NULL && *start != '\0') {
256 start = strchr(start, ';');
257 if (!start)
258 break;
259
260 start = start+1;
261
262 while (av_isspace(*start))
263 start++;
264
265 if (av_stristart(start, "boundary=", &start)) {
266 end = strchr(start, ';');
267 if (end)
268 len = end - start - 1;
269 else
270 len = strlen(start);
271
272 /* some endpoints may enclose the boundary
273 in Content-Type in quotes */
274 if ( len>2 && *start == '"' && start[len-1] == '"' ) {
275 start++;
276 len -= 2;
277 }
278 res = av_strndup(start, len);
279 break;
280 }
281 }
282
283 av_freep(&mime_type);
284 return res;
285 }
286
287 static int mpjpeg_read_packet(AVFormatContext *s, AVPacket *pkt)
288 {
289 int size;
290 int ret;
291
292 MPJPEGDemuxContext *mpjpeg = s->priv_data;
293 if (mpjpeg->boundary == NULL) {
294 uint8_t* boundary = NULL;
295 if (mpjpeg->strict_mime_boundary) {
296 boundary = mpjpeg_get_boundary(s->pb);
297 }
298 if (boundary != NULL) {
299 mpjpeg->boundary = av_asprintf("--%s", boundary);
300 mpjpeg->searchstr = av_asprintf("\r\n--%s\r\n", boundary);
301 av_freep(&boundary);
302 } else {
303 mpjpeg->boundary = av_strdup("--");
304 mpjpeg->searchstr = av_strdup("\r\n--");
305 }
306 if (!mpjpeg->boundary || !mpjpeg->searchstr) {
307 av_freep(&mpjpeg->boundary);
308 av_freep(&mpjpeg->searchstr);
309 return AVERROR(ENOMEM);
310 }
311 mpjpeg->searchstr_len = strlen(mpjpeg->searchstr);
312 }
313
314 ret = parse_multipart_header(s->pb, &size, mpjpeg->boundary, s);
315 if (ret < 0)
316 return ret;
317
318 if (size > 0) {
319 /* size has been provided to us in MIME header */
320 ret = av_get_packet(s->pb, pkt, size);
321 } else {
322 /* no size was given -- we read until the next boundary or end-of-file */
323 int len;
324
325 const int read_chunk = 2048;
326
327 pkt->pos = avio_tell(s->pb);
328
329 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 (ret = av_append_packet(s->pb, pkt, read_chunk)) >= 0) {
331 /* scan the new data */
332 char *start;
333
334 len = ret;
335 start = pkt->data + pkt->size - len;
336 do {
337 if (!memcmp(start, mpjpeg->searchstr, mpjpeg->searchstr_len)) {
338 // got the boundary! rewind the stream
339 avio_seek(s->pb, -len, SEEK_CUR);
340 pkt->size -= len;
341 return pkt->size;
342 }
343 len--;
344 start++;
345 } while (len >= mpjpeg->searchstr_len);
346 avio_seek(s->pb, -len, SEEK_CUR);
347 pkt->size -= len;
348 }
349
350 /* error or EOF occurred */
351 if (ret == AVERROR_EOF) {
352 ret = pkt->size > 0 ? pkt->size : AVERROR_EOF;
353 }
354 }
355
356 return ret;
357 }
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 };
386