X-Git-Url: https://git.sesse.net/?a=blobdiff_plain;f=ffprobe.c;h=ef4ccafe0c7d5f58e0f94899e4a837ea3d2a6c2b;hb=004f3b154b7c23662263f1ff118f69ff54e55013;hp=80a286b20ac1a4b90e3d44813fee7166046fe388;hpb=d67d3e29779ac113fbf806754ef9398e52cd288b;p=ffmpeg diff --git a/ffprobe.c b/ffprobe.c index 80a286b20ac..ef4ccafe0c7 100644 --- a/ffprobe.c +++ b/ffprobe.c @@ -258,6 +258,13 @@ typedef struct WriterContext WriterContext; #define WRITER_FLAG_DISPLAY_OPTIONAL_FIELDS 1 #define WRITER_FLAG_PUT_PACKETS_AND_FRAMES_IN_SAME_CHAPTER 2 +typedef enum { + WRITER_STRING_VALIDATION_FAIL, + WRITER_STRING_VALIDATION_REPLACE, + WRITER_STRING_VALIDATION_IGNORE, + WRITER_STRING_VALIDATION_NB +} StringValidation; + typedef struct Writer { const AVClass *priv_class; ///< private class of the writer, if any int priv_size; ///< private size for the writer context @@ -298,6 +305,10 @@ struct WriterContext { unsigned int nb_section_packet; ///< number of the packet section in case we are in "packets_and_frames" section unsigned int nb_section_frame; ///< number of the frame section in case we are in "packets_and_frames" section unsigned int nb_section_packet_frame; ///< nb_section_packet or nb_section_frame according if is_packets_and_frames + + StringValidation string_validation; + char *string_validation_replacement; + unsigned int string_validation_utf8_flags; }; static const char *writer_get_name(void *p) @@ -306,11 +317,36 @@ static const char *writer_get_name(void *p) return wctx->writer->name; } +#define OFFSET(x) offsetof(WriterContext, x) + +static const AVOption writer_options[] = { + { "string_validation", "set string validation mode", + OFFSET(string_validation), AV_OPT_TYPE_INT, {.i64=WRITER_STRING_VALIDATION_REPLACE}, 0, WRITER_STRING_VALIDATION_NB-1, .unit = "sv" }, + { "sv", "set string validation mode", + OFFSET(string_validation), AV_OPT_TYPE_INT, {.i64=WRITER_STRING_VALIDATION_REPLACE}, 0, WRITER_STRING_VALIDATION_NB-1, .unit = "sv" }, + { "ignore", NULL, 0, AV_OPT_TYPE_CONST, {.i64 = WRITER_STRING_VALIDATION_IGNORE}, .unit = "sv" }, + { "replace", NULL, 0, AV_OPT_TYPE_CONST, {.i64 = WRITER_STRING_VALIDATION_REPLACE}, .unit = "sv" }, + { "fail", NULL, 0, AV_OPT_TYPE_CONST, {.i64 = WRITER_STRING_VALIDATION_FAIL}, .unit = "sv" }, + { "string_validation_replacement", "set string validation replacement string", OFFSET(string_validation_replacement), AV_OPT_TYPE_STRING, {.str=""}}, + { "svr", "set string validation replacement string", OFFSET(string_validation_replacement), AV_OPT_TYPE_STRING, {.str=""}}, + { NULL } +}; + +static void *writer_child_next(void *obj, void *prev) +{ + WriterContext *ctx = obj; + if (!prev && ctx->writer && ctx->writer->priv_class && ctx->priv) + return ctx->priv; + return NULL; +} + static const AVClass writer_class = { "Writer", writer_get_name, NULL, LIBAVUTIL_VERSION_INT, + .option = writer_options, + .child_next = writer_child_next, }; static void writer_close(WriterContext **wctx) @@ -330,6 +366,15 @@ static void writer_close(WriterContext **wctx) av_freep(wctx); } +static void bprint_bytes(AVBPrint *bp, const uint8_t *ubuf, size_t ubuf_size) +{ + int i; + av_bprintf(bp, "0X"); + for (i = 0; i < ubuf_size; i++) + av_bprintf(bp, "%02X", ubuf[i]); +} + + static int writer_open(WriterContext **wctx, const Writer *writer, const char *args, const struct section *sections, int nb_sections) { @@ -351,14 +396,55 @@ static int writer_open(WriterContext **wctx, const Writer *writer, const char *a (*wctx)->sections = sections; (*wctx)->nb_sections = nb_sections; + av_opt_set_defaults(*wctx); + if (writer->priv_class) { void *priv_ctx = (*wctx)->priv; *((const AVClass **)priv_ctx) = writer->priv_class; av_opt_set_defaults(priv_ctx); + } - if (args && - (ret = av_set_options_string(priv_ctx, args, "=", ":")) < 0) + /* convert options to dictionary */ + if (args) { + AVDictionary *opts = NULL; + AVDictionaryEntry *opt = NULL; + + if ((ret = av_dict_parse_string(&opts, args, "=", ":", 0)) < 0) { + av_log(*wctx, AV_LOG_ERROR, "Failed to parse option string '%s' provided to writer context\n", args); + av_dict_free(&opts); goto fail; + } + + while ((opt = av_dict_get(opts, "", opt, AV_DICT_IGNORE_SUFFIX))) { + if ((ret = av_opt_set(*wctx, opt->key, opt->value, AV_OPT_SEARCH_CHILDREN)) < 0) { + av_log(*wctx, AV_LOG_ERROR, "Failed to set option '%s' with value '%s' provided to writer context\n", + opt->key, opt->value); + av_dict_free(&opts); + goto fail; + } + } + + av_dict_free(&opts); + } + + /* validate replace string */ + { + const uint8_t *p = (*wctx)->string_validation_replacement; + const uint8_t *endp = p + strlen(p); + while (*p) { + const uint8_t *p0 = p; + int32_t code; + ret = av_utf8_decode(&code, &p, endp, (*wctx)->string_validation_utf8_flags); + if (ret < 0) { + AVBPrint bp; + av_bprint_init(&bp, 0, AV_BPRINT_SIZE_AUTOMATIC); + bprint_bytes(&bp, p0, p-p0), + av_log(wctx, AV_LOG_ERROR, + "Invalid UTF8 sequence %s found in string validation replace '%s'\n", + bp.str, (*wctx)->string_validation_replacement); + return ret; + } + } } for (i = 0; i < SECTION_MAX_NB_LEVELS; i++) @@ -428,18 +514,98 @@ static inline void writer_print_integer(WriterContext *wctx, } } -static inline void writer_print_string(WriterContext *wctx, - const char *key, const char *val, int opt) +static inline int validate_string(WriterContext *wctx, char **dstp, const char *src) +{ + const uint8_t *p, *endp; + AVBPrint dstbuf; + int invalid_chars_nb = 0, ret = 0; + + av_bprint_init(&dstbuf, 0, AV_BPRINT_SIZE_UNLIMITED); + + endp = src + strlen(src); + for (p = (uint8_t *)src; *p;) { + uint32_t code; + int invalid = 0; + const uint8_t *p0 = p; + + if (av_utf8_decode(&code, &p, endp, wctx->string_validation_utf8_flags) < 0) { + AVBPrint bp; + av_bprint_init(&bp, 0, AV_BPRINT_SIZE_AUTOMATIC); + bprint_bytes(&bp, p0, p-p0); + av_log(wctx, AV_LOG_DEBUG, + "Invalid UTF-8 sequence %s found in string '%s'\n", bp.str, src); + invalid = 1; + } + + if (invalid) { + invalid_chars_nb++; + + switch (wctx->string_validation) { + case WRITER_STRING_VALIDATION_FAIL: + av_log(wctx, AV_LOG_ERROR, + "Invalid UTF-8 sequence found in string '%s'\n", src); + ret = AVERROR_INVALIDDATA; + goto end; + break; + + case WRITER_STRING_VALIDATION_REPLACE: + av_bprintf(&dstbuf, "%s", wctx->string_validation_replacement); + break; + } + } + + if (!invalid || wctx->string_validation == WRITER_STRING_VALIDATION_IGNORE) + av_bprint_append_data(&dstbuf, p0, p-p0); + } + + if (invalid_chars_nb && wctx->string_validation == WRITER_STRING_VALIDATION_REPLACE) { + av_log(wctx, AV_LOG_WARNING, + "%d invalid UTF-8 sequence(s) found in string '%s', replaced with '%s'\n", + invalid_chars_nb, src, wctx->string_validation_replacement); + } + +end: + av_bprint_finalize(&dstbuf, dstp); + return ret; +} + +#define PRINT_STRING_OPT 1 +#define PRINT_STRING_VALIDATE 2 + +static inline int writer_print_string(WriterContext *wctx, + const char *key, const char *val, int flags) { const struct section *section = wctx->section[wctx->level]; + int ret = 0; - if (opt && !(wctx->writer->flags & WRITER_FLAG_DISPLAY_OPTIONAL_FIELDS)) - return; + if ((flags & PRINT_STRING_OPT) + && !(wctx->writer->flags & WRITER_FLAG_DISPLAY_OPTIONAL_FIELDS)) + return 0; if (section->show_all_entries || av_dict_get(section->entries_to_show, key, NULL, 0)) { - wctx->writer->print_string(wctx, key, val); + if (flags & PRINT_STRING_VALIDATE) { + char *key1 = NULL, *val1 = NULL; + ret = validate_string(wctx, &key1, key); + if (ret < 0) goto end; + ret = validate_string(wctx, &val1, val); + if (ret < 0) goto end; + wctx->writer->print_string(wctx, key1, val1); + end: + if (ret < 0) { + av_log(wctx, AV_LOG_ERROR, + "Invalid key=value string combination %s=%s in section %s\n", + key, val, section->unique_name); + } + av_free(key1); + av_free(val1); + } else { + wctx->writer->print_string(wctx, key, val); + } + wctx->nb_item[wctx->level]++; } + + return ret; } static inline void writer_print_rational(WriterContext *wctx, @@ -457,7 +623,7 @@ static void writer_print_time(WriterContext *wctx, const char *key, char buf[128]; if ((!is_duration && ts == AV_NOPTS_VALUE) || (is_duration && ts == 0)) { - writer_print_string(wctx, key, "N/A", 1); + writer_print_string(wctx, key, "N/A", PRINT_STRING_OPT); } else { double d = ts * av_q2d(*time_base); struct unit_value uv; @@ -471,7 +637,7 @@ static void writer_print_time(WriterContext *wctx, const char *key, static void writer_print_ts(WriterContext *wctx, const char *key, int64_t ts, int is_duration) { if ((!is_duration && ts == AV_NOPTS_VALUE) || (is_duration && ts == 0)) { - writer_print_string(wctx, key, "N/A", 1); + writer_print_string(wctx, key, "N/A", PRINT_STRING_OPT); } else { writer_print_integer(wctx, key, ts); } @@ -554,6 +720,7 @@ typedef struct DefaultContext { int nested_section[SECTION_MAX_NB_LEVELS]; } DefaultContext; +#undef OFFSET #define OFFSET(x) offsetof(DefaultContext, x) static const AVOption default_options[] = { @@ -1440,7 +1607,8 @@ static void writer_register_all(void) #define print_int(k, v) writer_print_integer(w, k, v) #define print_q(k, v, s) writer_print_rational(w, k, v, s) #define print_str(k, v) writer_print_string(w, k, v, 0) -#define print_str_opt(k, v) writer_print_string(w, k, v, 1) +#define print_str_opt(k, v) writer_print_string(w, k, v, PRINT_STRING_OPT) +#define print_str_validate(k, v) writer_print_string(w, k, v, PRINT_STRING_VALIDATE) #define print_time(k, v, tb) writer_print_time(w, k, v, tb, 0) #define print_ts(k, v) writer_print_ts(w, k, v, 0) #define print_duration_time(k, v, tb) writer_print_time(w, k, v, tb, 1) @@ -1455,16 +1623,22 @@ static void writer_register_all(void) #define print_section_header(s) writer_print_section_header(w, s) #define print_section_footer(s) writer_print_section_footer(w, s) -static inline void show_tags(WriterContext *wctx, AVDictionary *tags, int section_id) +static inline int show_tags(WriterContext *w, AVDictionary *tags, int section_id) { AVDictionaryEntry *tag = NULL; + int ret = 0; if (!tags) - return; - writer_print_section_header(wctx, section_id); - while ((tag = av_dict_get(tags, "", tag, AV_DICT_IGNORE_SUFFIX))) - writer_print_string(wctx, tag->key, tag->value, 0); - writer_print_section_footer(wctx); + return 0; + writer_print_section_header(w, section_id); + + while ((tag = av_dict_get(tags, "", tag, AV_DICT_IGNORE_SUFFIX))) { + if ((ret = print_str_validate(tag->key, tag->value)) < 0) + break; + } + writer_print_section_footer(w); + + return ret; } static void show_packet(WriterContext *w, AVFormatContext *fmt_ctx, AVPacket *pkt, int packet_idx) @@ -1723,7 +1897,7 @@ end: return ret; } -static void read_packets(WriterContext *w, AVFormatContext *fmt_ctx) +static int read_packets(WriterContext *w, AVFormatContext *fmt_ctx) { int i, ret = 0; int64_t cur_ts = fmt_ctx->start_time; @@ -1738,9 +1912,11 @@ static void read_packets(WriterContext *w, AVFormatContext *fmt_ctx) break; } } + + return ret; } -static void show_stream(WriterContext *w, AVFormatContext *fmt_ctx, int stream_idx, int in_program) +static int show_stream(WriterContext *w, AVFormatContext *fmt_ctx, int stream_idx, int in_program) { AVStream *stream = fmt_ctx->streams[stream_idx]; AVCodecContext *dec_ctx; @@ -1749,6 +1925,7 @@ static void show_stream(WriterContext *w, AVFormatContext *fmt_ctx, int stream_i const char *s; AVRational sar, dar; AVBPrint pbuf; + int ret = 0; av_bprint_init(&pbuf, 1, AV_BPRINT_SIZE_UNLIMITED); @@ -1903,26 +2080,34 @@ static void show_stream(WriterContext *w, AVFormatContext *fmt_ctx, int stream_i writer_print_section_footer(w); } - show_tags(w, stream->metadata, in_program ? SECTION_ID_PROGRAM_STREAM_TAGS : SECTION_ID_STREAM_TAGS); + ret = show_tags(w, stream->metadata, in_program ? SECTION_ID_PROGRAM_STREAM_TAGS : SECTION_ID_STREAM_TAGS); writer_print_section_footer(w); av_bprint_finalize(&pbuf, NULL); fflush(stdout); + + return ret; } -static void show_streams(WriterContext *w, AVFormatContext *fmt_ctx) +static int show_streams(WriterContext *w, AVFormatContext *fmt_ctx) { - int i; + int i, ret = 0; + writer_print_section_header(w, SECTION_ID_STREAMS); for (i = 0; i < fmt_ctx->nb_streams; i++) - if (selected_streams[i]) - show_stream(w, fmt_ctx, i, 0); + if (selected_streams[i]) { + ret = show_stream(w, fmt_ctx, i, 0); + if (ret < 0) + break; + } writer_print_section_footer(w); + + return ret; } -static void show_program(WriterContext *w, AVFormatContext *fmt_ctx, AVProgram *program) +static int show_program(WriterContext *w, AVFormatContext *fmt_ctx, AVProgram *program) { - int i; + int i, ret = 0; writer_print_section_header(w, SECTION_ID_PROGRAM); print_int("program_id", program->id); @@ -1934,35 +2119,45 @@ static void show_program(WriterContext *w, AVFormatContext *fmt_ctx, AVProgram * print_time("start_time", program->start_time, &AV_TIME_BASE_Q); print_ts("end_pts", program->end_time); print_time("end_time", program->end_time, &AV_TIME_BASE_Q); - show_tags(w, program->metadata, SECTION_ID_PROGRAM_TAGS); + ret = show_tags(w, program->metadata, SECTION_ID_PROGRAM_TAGS); + if (ret < 0) + goto end; writer_print_section_header(w, SECTION_ID_PROGRAM_STREAMS); for (i = 0; i < program->nb_stream_indexes; i++) { - if (selected_streams[program->stream_index[i]]) - show_stream(w, fmt_ctx, program->stream_index[i], 1); + if (selected_streams[program->stream_index[i]]) { + ret = show_stream(w, fmt_ctx, program->stream_index[i], 1); + if (ret < 0) + break; + } } writer_print_section_footer(w); +end: writer_print_section_footer(w); + return ret; } -static void show_programs(WriterContext *w, AVFormatContext *fmt_ctx) +static int show_programs(WriterContext *w, AVFormatContext *fmt_ctx) { - int i; + int i, ret = 0; writer_print_section_header(w, SECTION_ID_PROGRAMS); for (i = 0; i < fmt_ctx->nb_programs; i++) { AVProgram *program = fmt_ctx->programs[i]; if (!program) continue; - show_program(w, fmt_ctx, program); + ret = show_program(w, fmt_ctx, program); + if (ret < 0) + break; } writer_print_section_footer(w); + return ret; } -static void show_chapters(WriterContext *w, AVFormatContext *fmt_ctx) +static int show_chapters(WriterContext *w, AVFormatContext *fmt_ctx) { - int i; + int i, ret = 0; writer_print_section_header(w, SECTION_ID_CHAPTERS); for (i = 0; i < fmt_ctx->nb_chapters; i++) { @@ -1975,19 +2170,22 @@ static void show_chapters(WriterContext *w, AVFormatContext *fmt_ctx) print_time("start_time", chapter->start, &chapter->time_base); print_int("end", chapter->end); print_time("end_time", chapter->end, &chapter->time_base); - show_tags(w, chapter->metadata, SECTION_ID_CHAPTER_TAGS); + ret = show_tags(w, chapter->metadata, SECTION_ID_CHAPTER_TAGS); writer_print_section_footer(w); } writer_print_section_footer(w); + + return ret; } -static void show_format(WriterContext *w, AVFormatContext *fmt_ctx) +static int show_format(WriterContext *w, AVFormatContext *fmt_ctx) { char val_str[128]; int64_t size = fmt_ctx->pb ? avio_size(fmt_ctx->pb) : -1; + int ret = 0; writer_print_section_header(w, SECTION_ID_FORMAT); - print_str("filename", fmt_ctx->filename); + print_str_validate("filename", fmt_ctx->filename); print_int("nb_streams", fmt_ctx->nb_streams); print_int("nb_programs", fmt_ctx->nb_programs); print_str("format_name", fmt_ctx->iformat->name); @@ -2002,10 +2200,11 @@ static void show_format(WriterContext *w, AVFormatContext *fmt_ctx) if (fmt_ctx->bit_rate > 0) print_val ("bit_rate", fmt_ctx->bit_rate, unit_bit_per_second_str); else print_str_opt("bit_rate", "N/A"); print_int("probe_score", av_format_get_probe_score(fmt_ctx)); - show_tags(w, fmt_ctx->metadata, SECTION_ID_FORMAT_TAGS); + ret = show_tags(w, fmt_ctx->metadata, SECTION_ID_FORMAT_TAGS); writer_print_section_footer(w); fflush(stdout); + return ret; } static void show_error(WriterContext *w, int err) @@ -2111,6 +2310,8 @@ static int probe_file(WriterContext *wctx, const char *filename) if (ret < 0) return ret; +#define CHECK_END if (ret < 0) goto end + nb_streams_frames = av_calloc(fmt_ctx->nb_streams, sizeof(*nb_streams_frames)); nb_streams_packets = av_calloc(fmt_ctx->nb_streams, sizeof(*nb_streams_packets)); selected_streams = av_calloc(fmt_ctx->nb_streams, sizeof(*selected_streams)); @@ -2120,8 +2321,7 @@ static int probe_file(WriterContext *wctx, const char *filename) ret = avformat_match_stream_specifier(fmt_ctx, fmt_ctx->streams[i], stream_specifier); - if (ret < 0) - goto end; + CHECK_END; else selected_streams[i] = ret; ret = 0; @@ -2140,18 +2340,28 @@ static int probe_file(WriterContext *wctx, const char *filename) section_id = SECTION_ID_FRAMES; if (do_show_frames || do_show_packets) writer_print_section_header(wctx, section_id); - read_packets(wctx, fmt_ctx); + ret = read_packets(wctx, fmt_ctx); if (do_show_frames || do_show_packets) writer_print_section_footer(wctx); + CHECK_END; + } + if (do_show_programs) { + ret = show_programs(wctx, fmt_ctx); + CHECK_END; + } + + if (do_show_streams) { + ret = show_streams(wctx, fmt_ctx); + CHECK_END; + } + if (do_show_chapters) { + ret = show_chapters(wctx, fmt_ctx); + CHECK_END; + } + if (do_show_format) { + ret = show_format(wctx, fmt_ctx); + CHECK_END; } - if (do_show_programs) - show_programs(wctx, fmt_ctx); - if (do_show_streams) - show_streams(wctx, fmt_ctx); - if (do_show_chapters) - show_chapters(wctx, fmt_ctx); - if (do_show_format) - show_format(wctx, fmt_ctx); end: close_input_file(&fmt_ctx); @@ -2676,6 +2886,9 @@ int main(int argc, char **argv) if ((ret = writer_open(&wctx, w, w_args, sections, FF_ARRAY_ELEMS(sections))) >= 0) { + if (w == &xml_writer) + wctx->string_validation_utf8_flags |= AV_UTF8_FLAG_EXCLUDE_XML_INVALID_CONTROL_CODES; + writer_print_section_header(wctx, SECTION_ID_ROOT); if (do_show_program_version)