Coverage Report

Created: 2024-09-06 07:53

/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
};