summaryrefslogtreecommitdiffstats
path: root/sub/filter_jsre.c
blob: 896382714ad35db1cd77acab0b5e04095d853799 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
#include <stdio.h>
#include <sys/types.h>

#include <mujs.h>

#include "common/common.h"
#include "common/msg.h"
#include "misc/bstr.h"
#include "options/options.h"
#include "sd.h"


// p_NAME are protected functions (never throw) which interact with the JS VM.
// return 0 on successful interaction, not-0 on (caught) js-error.
// on error: stack is the same as on entry + an error value

// js: global[n] = new RegExp(str, flags)
static int p_regcomp(js_State *J, int n, const char *str, int flags)
{
    if (js_try(J))
        return 1;

    js_pushnumber(J, n);  // n
    js_newregexp(J, str, flags);  // n regex
    js_setglobal(J, js_tostring(J, -2));  // n  (and global[n] is the regex)
    js_pop(J, 1);

    js_endtry(J);
    return 0;
}

// js: found = global[n].test(text)
static int p_regexec(js_State *J, int n, const char *text, int *found)
{
    if (js_try(J))
        return 1;

    js_pushnumber(J, n);  // n
    js_getglobal(J, js_tostring(J, -1));  // n global[n]
    js_getproperty(J, -1, "test");  // n global[n] global[n].test
    js_rot2(J);  // n global[n].test global[n]   (n, test(), and its `this')
    js_pushstring(J, text); // n global[n].test global[n] text
    js_call(J, 1);  // n test-result
    *found = js_toboolean(J, -1);
    js_pop(J, 2);  // the result and n

    js_endtry(J);
    return 0;
}

// protected. caller should pop the error after using the result string.
static const char *get_err(js_State *J)
{
    return js_trystring(J, -1, "unknown error");
}


struct priv {
    js_State *J;
    int num_regexes;
    int offset;
};

static void destruct_priv(void *p)
{
    js_freestate(((struct priv *)p)->J);
}

static bool jsre_init(struct sd_filter *ft)
{
    if (strcmp(ft->codec, "ass") != 0)
        return false;

    if (!ft->opts->rf_enable)
        return false;

    struct priv *p = talloc_zero(ft, struct priv);
    ft->priv = p;

    p->J = js_newstate(0, 0, JS_STRICT);
    if (!p->J) {
        MP_ERR(ft, "jsre: VM init error\n");
        return false;
    }
    talloc_set_destructor(p, destruct_priv);

    for (int n = 0; ft->opts->jsre_items && ft->opts->jsre_items[n]; n++) {
        char *item = ft->opts->jsre_items[n];

        int err = p_regcomp(p->J, p->num_regexes, item, JS_REGEXP_I);
        if (err) {
            MP_ERR(ft, "jsre: %s -- '%s'\n", get_err(p->J), item);
            js_pop(p->J, 1);
            continue;
        }

        p->num_regexes += 1;
    }

    if (!p->num_regexes)
        return false;

    p->offset = sd_ass_fmt_offset(ft->event_format);
    return true;
}

static struct demux_packet *jsre_filter(struct sd_filter *ft,
                                      struct demux_packet *pkt)
{
    struct priv *p = ft->priv;
    char *text = bstrto0(NULL, sd_ass_pkt_text(ft, pkt, p->offset));
    bool drop = false;

    for (int n = 0; n < p->num_regexes; n++) {
        int found, err = p_regexec(p->J, n, text, &found);
        if (err == 0 && found) {
            int level = ft->opts->rf_warn ? MSGL_WARN : MSGL_V;
            MP_MSG(ft, level, "jsre: regex %d => drop: '%s'\n", n, text);
            drop = true;
            break;
        } else if (err) {
            MP_WARN(ft, "jsre: test regex %d: %s.\n", n, get_err(p->J));
            js_pop(p->J, 1);
        }
    }

    talloc_free(text);
    return drop ? NULL : pkt;
}

const struct sd_filter_functions sd_filter_jsre = {
    .init   = jsre_init,
    .filter = jsre_filter,
};