static void
ngx_rtmp_eval_append_var(void *ctx, ngx_buf_t *b, ngx_rtmp_eval_t **e,
                         ngx_str_t *name, ngx_log_t *log)
{
    ngx_uint_t          k;
    ngx_str_t           v;
    ngx_rtmp_eval_t    *ee;

    for (; *e; ++e) {
        for (k = 0, ee = *e; ee->handler; ++k, ++ee) {
            if (ee->name.len == name->len &&
                    ngx_memcmp(ee->name.data, name->data, name->len) == 0)
            {
                ee->handler(ctx, ee, &v);
                ngx_rtmp_eval_append(b, v.data, v.len, log);
            }
        }
    }
}
static void
ngx_rtmp_eval_append_var(ngx_rtmp_session_t *s, ngx_buf_t *b,
                         ngx_rtmp_eval_t **e, ngx_str_t *name)
{
    ngx_uint_t          k;
    ngx_str_t           v;
    ngx_rtmp_eval_t    *ee;

    for (; *e; ++e) {
        for (k = 0, ee = *e; ee->handler; ++k, ++ee) {
            if (ee->name.len == name->len &&
                    ngx_memcmp(ee->name.data, name->data, name->len) == 0)
            {
                ee->handler(s, ee, &v);
                ngx_rtmp_eval_append(s, b, v.data, v.len);
            }
        }
    }
}
ngx_int_t
ngx_rtmp_eval(void *ctx, ngx_str_t *in, ngx_rtmp_eval_t **e, ngx_str_t *out,
              ngx_log_t *log)
{
    u_char      c, *p;
    ngx_str_t   name;
    ngx_buf_t   b;
    ngx_uint_t  n;

    enum {
        NORMAL,
        ESCAPE,
        NAME,
        SNAME
    } state = NORMAL;

    b.pos = b.last = b.start = ngx_alloc(NGX_RTMP_EVAL_BUFLEN, log);
    if (b.pos == NULL) {
        return NGX_ERROR;
    }

    b.end = b.pos + NGX_RTMP_EVAL_BUFLEN;
    name.data = NULL;

    for (n = 0; n < in->len; ++n) {
        p = &in->data[n];
        c = *p;

        switch (state) {
        case SNAME:
            if (c != '}') {
                continue;
            }

            name.len = p - name.data;
            ngx_rtmp_eval_append_var(ctx, &b, e, &name, log);

            state = NORMAL;

            continue;

        case NAME:
            if (c == '{' && name.data == p) {
                ++name.data;
                state = SNAME;
                continue;
            }
            if ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z')) {
                continue;
            }

            name.len = p - name.data;
            ngx_rtmp_eval_append_var(ctx, &b, e, &name, log);

        case NORMAL:
            switch (c) {
            case '$':
                name.data = p + 1;
                state = NAME;
                continue;
            case '\\':
                state = ESCAPE;
                continue;
            }

        case ESCAPE:
            ngx_rtmp_eval_append(&b, &c, 1, log);
            state = NORMAL;
            break;

        }
    }

    if (state == NAME) {
        p = &in->data[n];
        name.len = p - name.data;
        ngx_rtmp_eval_append_var(ctx, &b, e, &name, log);
    }

    c = 0;
    ngx_rtmp_eval_append(&b, &c, 1, log);

    out->data = b.pos;
    out->len  = b.last - b.pos - 1;

    return NGX_OK;
}