
/*
 * Copyright (C) Igor Sysoev
 * Copyright (C) Nginx, Inc.
 * Copyright (C) Winshining
 */


#include <ngx_config.h>
#include <ngx_core.h>
#include "ngx_rtmp.h"


#define NGX_RTMP_PARSE_INVALID_REQUEST    11


static uint32_t  usual[] = {
    0xffffdbfe, /* 1111 1111 1111 1111  1101 1011 1111 1110 */

                /* ?>=< ;:98 7654 3210  /.-, +*)( '&%$ #"!  */
    0x7fff37d6, /* 0111 1111 1111 1111  0011 0111 1101 0110 */

                /* _^]\ [ZYX WVUT SRQP  ONML KJIH GFED CBA@ */
#if (NGX_WIN32)
    0xefffffff, /* 1110 1111 1111 1111  1111 1111 1111 1111 */
#else
    0xffffffff, /* 1111 1111 1111 1111  1111 1111 1111 1111 */
#endif

                /*  ~}| {zyx wvut srqp  onml kjih gfed cba` */
    0xffffffff, /* 1111 1111 1111 1111  1111 1111 1111 1111 */

    0xffffffff, /* 1111 1111 1111 1111  1111 1111 1111 1111 */
    0xffffffff, /* 1111 1111 1111 1111  1111 1111 1111 1111 */
    0xffffffff, /* 1111 1111 1111 1111  1111 1111 1111 1111 */
    0xffffffff  /* 1111 1111 1111 1111  1111 1111 1111 1111 */
};


ngx_int_t
ngx_rtmp_parse_request_line(ngx_rtmp_session_t *s, ngx_buf_t *b)
{
    u_char  c, ch, *p;
    enum {
        sw_start = 0,
        sw_schema,
        sw_schema_slash,
        sw_schema_slash_slash,
        sw_host_start,
        sw_host,
        sw_host_end,
        sw_host_ip_literal,
        sw_port,
        sw_after_slash_in_uri,
        sw_check_uri,
        sw_uri
    } state;

    state = sw_start;

    for (p = b->pos; p < b->last; p++) {
        ch = *p;

        switch (state) {

        case sw_start:

            s->schema_start = p;
            state = sw_schema;

            /* fall through */

        case sw_schema:

            c = (u_char) (ch | 0x20);
            if (c >= 'a' && c <= 'z') {
                break;
            }

            switch (ch) {
            case ':':
                s->schema_end = p;
                state = sw_schema_slash;
                break;
            default:
                return NGX_RTMP_PARSE_INVALID_REQUEST;
            }
            break;

        case sw_schema_slash:
            switch (ch) {
            case '/':
                state = sw_schema_slash_slash;
                break;
            default:
                return NGX_RTMP_PARSE_INVALID_REQUEST;
            }
            break;

        case sw_schema_slash_slash:
            switch (ch) {
            case '/':
                state = sw_host_start;
                break;
            default:
                return NGX_RTMP_PARSE_INVALID_REQUEST;
            }
            break;

        case sw_host_start:

            s->host_start = p;

            if (ch == '[') {
                state = sw_host_ip_literal;
                break;
            }

            state = sw_host;

            /* fall through */

        case sw_host:

            c = (u_char) (ch | 0x20);
            if (c >= 'a' && c <= 'z') {
                break;
            }

            if ((ch >= '0' && ch <= '9') || ch == '.' || ch == '-') {
                break;
            }

            /* fall through */

        case sw_host_end:

            s->host_end = p;

            switch (ch) {
            case ':':
                s->port_start = p + 1;
                state = sw_port;
                break;
            case '/':
                s->uri_start = p;
                state = sw_after_slash_in_uri;
                break;
            default:
                return NGX_RTMP_PARSE_INVALID_REQUEST;
            }
            break;

        case sw_host_ip_literal:

            if (ch >= '0' && ch <= '9') {
                break;
            }

            c = (u_char) (ch | 0x20);
            if (c >= 'a' && c <= 'z') {
                break;
            }

            switch (ch) {
            case ':':
                break;
            case ']':
                state = sw_host_end;
                break;
            case '-':
            case '.':
            case '_':
            case '~':
                /* unreserved */
                break;
            case '!':
            case '$':
            case '&':
            case '\'':
            case '(':
            case ')':
            case '*':
            case '+':
            case ',':
            case ';':
            case '=':
                /* sub-delims */
                break;
            default:
                return NGX_RTMP_PARSE_INVALID_REQUEST;
            }
            break;

        case sw_port:
            if (ch >= '0' && ch <= '9') {
                break;
            }

            switch (ch) {
            case '/':
                s->port_end = p;
                s->uri_start = p;
                state = sw_after_slash_in_uri;
                break;
            default:
                return NGX_RTMP_PARSE_INVALID_REQUEST;
            }
            break;

        /* check "/.", "//", "%", and "\" (Win32) in URI */
        case sw_after_slash_in_uri:

            if (usual[ch >> 5] & (1U << (ch & 0x1f))) {
                state = sw_check_uri;
                break;
            }

            switch (ch) {
            case '.':
                s->complex_uri = 1;
                state = sw_uri;
                break;
            case '%':
                s->quoted_uri = 1;
                state = sw_uri;
                break;
            case '/':
                s->complex_uri = 1;
                state = sw_uri;
                break;
#if (NGX_WIN32)
            case '\\':
                s->complex_uri = 1;
                state = sw_uri;
                break;
#endif
            case '?':
                s->args_start = p + 1;
                state = sw_uri;
                break;
            case '#':
                s->complex_uri = 1;
                state = sw_uri;
                break;
            case '+':
                s->plus_in_uri = 1;
                break;
            case '\0':
                return NGX_RTMP_PARSE_INVALID_REQUEST;
            default:
                state = sw_check_uri;
                break;
            }
            break;

        /* check "/", "%" and "\" (Win32) in URI */
        case sw_check_uri:

            if (usual[ch >> 5] & (1U << (ch & 0x1f))) {
                break;
            }

            switch (ch) {
            case '/':
                state = sw_after_slash_in_uri;
                break;
            case '.':
                break;
#if (NGX_WIN32)
            case '\\':
                s->complex_uri = 1;
                state = sw_after_slash_in_uri;
                break;
#endif
            case '%':
                s->quoted_uri = 1;
                state = sw_uri;
                break;
            case '?':
                s->args_start = p + 1;
                state = sw_uri;
                break;
            case '#':
                s->complex_uri = 1;
                state = sw_uri;
                break;
            case '+':
                s->plus_in_uri = 1;
                break;
            case '\0':
                return NGX_RTMP_PARSE_INVALID_REQUEST;
            }
            break;

        /* URI */
        case sw_uri:

            if (usual[ch >> 5] & (1U << (ch & 0x1f))) {
                break;
            }

            switch (ch) {
            case '#':
                s->complex_uri = 1;
                break;
            case '\0':
                return NGX_RTMP_PARSE_INVALID_REQUEST;
            }
        }
    }

    /* end of request line */
    s->uri_end = p;

    return NGX_OK;
}


ngx_int_t
ngx_rtmp_process_request_uri(ngx_rtmp_session_t *s)
{
    ngx_rtmp_core_srv_conf_t  *cscf;

    if (s->args_start) {
        s->uri.len = s->args_start - 1 - s->uri_start;
    } else {
        s->uri.len = s->uri_end - s->uri_start;
    }

    if (s->complex_uri || s->quoted_uri) {

        s->uri.data = ngx_pnalloc(s->connection->pool, s->uri.len + 1);
        if (s->uri.data == NULL) {
            return NGX_ERROR;
        }

        cscf = ngx_rtmp_get_module_srv_conf(s, ngx_rtmp_core_module);

        if (ngx_rtmp_parse_complex_uri(s, cscf->merge_slashes) != NGX_OK) {
            s->uri.len = 0;

            ngx_log_error(NGX_LOG_INFO, s->connection->log, 0,
                          "client sent invalid request");
            return NGX_ERROR;
        }

    } else {
        s->uri.data = s->uri_start;
    }

    s->unparsed_uri.len = s->uri_end - s->uri_start;
    s->unparsed_uri.data = s->uri_start;

    s->valid_unparsed_uri = s->space_in_uri ? 0 : 1;

    if (s->args_start && s->uri_end > s->args_start) {
        s->args.len = s->uri_end - s->args_start;
        s->args.data = s->args_start;
    }

#if (NGX_WIN32)
    {
    u_char  *p, *last;

    p = s->uri.data;
    last = s->uri.data + s->uri.len;

    while (p < last) {

        if (*p++ == ':') {

            /*
             * this check covers "::$data", "::$index_allocation" and
             * ":$i30:$index_allocation"
             */

            if (p < last && *p == '$') {
                ngx_log_error(NGX_LOG_INFO, s->connection->log, 0,
                              "client sent unsafe win32 URI");
                return NGX_ERROR;
            }
        }
    }

    p = s->uri.data + s->uri.len - 1;

    while (p > s->uri.data) {

        if (*p == ' ') {
            p--;
            continue;
        }

        if (*p == '.') {
            p--;
            continue;
        }

        break;
    }

    if (p != s->uri.data + s->uri.len - 1) {
        s->uri.len = p + 1 - s->uri.data;
    }

    }
#endif

    ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
                   "rtmp uri: \"%V\"", &s->uri);

    ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
                   "rtmp args: \"%V\"", &s->args);

    return NGX_OK;
}


ngx_int_t
ngx_rtmp_parse_complex_uri(ngx_rtmp_session_t *s, ngx_uint_t merge_slashes)
{
    u_char  c, ch, decoded, *p, *u;
    enum {
        sw_usual = 0,
        sw_slash,
        sw_dot,
        sw_dot_dot,
        sw_quoted,
        sw_quoted_second
    } state, quoted_state;

#if (NGX_SUPPRESS_WARN)
    decoded = '\0';
    quoted_state = sw_usual;
#endif

    state = sw_usual;
    p = s->uri_start;
    u = s->uri.data;
    s->args_start = NULL;

    ch = *p++;

    while (p <= s->uri_end) {

        /*
         * we use "ch = *p++" inside the cycle, it is safe,
         * because after the URI there is a character: '\r'
         */

        ngx_log_debug3(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
                       "s:%d in:'%Xd:%c'", state, ch, ch);

        switch (state) {

        case sw_usual:

            if (usual[ch >> 5] & (1U << (ch & 0x1f))) {
                *u++ = ch;
                ch = *p++;
                break;
            }

            switch (ch) {
#if (NGX_WIN32)
            case '\\':
                if (u - 2 >= s->uri.data
                    && *(u - 1) == '.' && *(u - 2) != '.')
                {
                    u--;
                }

                if (p == s->uri_start + s->uri.len) {

                    /*
                     * we omit the last "\" to cause redirect because
                     * the browsers do not treat "\" as "/" in relative URL path
                     */

                    break;
                }

                state = sw_slash;
                *u++ = '/';
                break;
#endif
            case '/':
#if (NGX_WIN32)
                if (u - 2 >= s->uri.data
                    && *(u - 1) == '.' && *(u - 2) != '.')
                {
                    u--;
                }
#endif
                state = sw_slash;
                *u++ = ch;
                break;
            case '%':
                quoted_state = state;
                state = sw_quoted;
                break;
            case '?':
                s->args_start = p;
                goto args;
            case '#':
                goto done;
            case '.':
                *u++ = ch;
                break;
            case '+':
                s->plus_in_uri = 1;

                /* fall through */

            default:
                *u++ = ch;
                break;
            }

            ch = *p++;
            break;

        case sw_slash:

            if (usual[ch >> 5] & (1U << (ch & 0x1f))) {
                state = sw_usual;
                *u++ = ch;
                ch = *p++;
                break;
            }

            switch (ch) {
#if (NGX_WIN32)
            case '\\':
                break;
#endif
            case '/':
                if (!merge_slashes) {
                    *u++ = ch;
                }
                break;
            case '.':
                state = sw_dot;
                *u++ = ch;
                break;
            case '%':
                quoted_state = state;
                state = sw_quoted;
                break;
            case '?':
                s->args_start = p;
                goto args;
            case '#':
                goto done;
            case '+':
                s->plus_in_uri = 1;

                /* fall through */

            default:
                state = sw_usual;
                *u++ = ch;
                break;
            }

            ch = *p++;
            break;

        case sw_dot:

            if (usual[ch >> 5] & (1U << (ch & 0x1f))) {
                state = sw_usual;
                *u++ = ch;
                ch = *p++;
                break;
            }

            switch (ch) {
#if (NGX_WIN32)
            case '\\':
#endif
            case '/':
                state = sw_slash;
                u--;
                break;
            case '.':
                state = sw_dot_dot;
                *u++ = ch;
                break;
            case '%':
                quoted_state = state;
                state = sw_quoted;
                break;
            case '?':
                s->args_start = p;
                goto args;
            case '#':
                goto done;
            case '+':
                s->plus_in_uri = 1;

                /* fall through */

            default:
                state = sw_usual;
                *u++ = ch;
                break;
            }

            ch = *p++;
            break;

        case sw_dot_dot:

            if (usual[ch >> 5] & (1U << (ch & 0x1f))) {
                state = sw_usual;
                *u++ = ch;
                ch = *p++;
                break;
            }

            switch (ch) {
#if (NGX_WIN32)
            case '\\':
#endif
            case '/':
                state = sw_slash;
                u -= 5;
                for ( ;; ) {
                    if (u < s->uri.data) {
                        return NGX_RTMP_PARSE_INVALID_REQUEST;
                    }
                    if (*u == '/') {
                        u++;
                        break;
                    }
                    u--;
                }
                break;
            case '%':
                quoted_state = state;
                state = sw_quoted;
                break;
            case '?':
                s->args_start = p;
                goto args;
            case '#':
                goto done;
            case '+':
                s->plus_in_uri = 1;

                /* fall through */

            default:
                state = sw_usual;
                *u++ = ch;
                break;
            }

            ch = *p++;
            break;

        case sw_quoted:
            s->quoted_uri = 1;

            if (ch >= '0' && ch <= '9') {
                decoded = (u_char) (ch - '0');
                state = sw_quoted_second;
                ch = *p++;
                break;
            }

            c = (u_char) (ch | 0x20);
            if (c >= 'a' && c <= 'f') {
                decoded = (u_char) (c - 'a' + 10);
                state = sw_quoted_second;
                ch = *p++;
                break;
            }

            return NGX_RTMP_PARSE_INVALID_REQUEST;

        case sw_quoted_second:
            if (ch >= '0' && ch <= '9') {
                ch = (u_char) ((decoded << 4) + ch - '0');

                if (ch == '%' || ch == '#') {
                    state = sw_usual;
                    *u++ = ch;
                    ch = *p++;
                    break;

                } else if (ch == '\0') {
                    return NGX_RTMP_PARSE_INVALID_REQUEST;
                }

                state = quoted_state;
                break;
            }

            c = (u_char) (ch | 0x20);
            if (c >= 'a' && c <= 'f') {
                ch = (u_char) ((decoded << 4) + c - 'a' + 10);

                if (ch == '?') {
                    state = sw_usual;
                    *u++ = ch;
                    ch = *p++;
                    break;

                } else if (ch == '+') {
                    s->plus_in_uri = 1;
                }

                state = quoted_state;
                break;
            }

            return NGX_RTMP_PARSE_INVALID_REQUEST;
        }
    }

done:

    s->uri.len = *u == CR ? (u - s->uri.data) : (u - s->uri.data + 1);

    return NGX_OK;

args:

    while (p < s->uri_end) {
        if (*p++ != '#') {
            continue;
        }

        s->args.len = p - 1 - s->args_start;
        s->args.data = s->args_start;
        s->args_start = NULL;

        break;
    }

    s->uri.len = u - s->uri.data;

    return NGX_OK;
}


ngx_int_t
ngx_rtmp_process_request_line(ngx_rtmp_session_t *s, const u_char *name,
                              const u_char *args, const u_char *cmd)
{
    size_t               rlen = 0;

    s->stream.len = name ? ngx_strlen(name) : 0;
    if (s->stream.len) {
        s->stream.data = ngx_palloc(s->connection->pool, s->stream.len);
        if (s->stream.data == NULL) {
            return NGX_ERROR;
        }

        ngx_memcpy(s->stream.data, name, ngx_strlen(name));
    }

    if (s->tc_url.data[s->tc_url.len - 1] == '/') {
        s->tc_url.len -= 1;
    }

    rlen = s->tc_url.len;

    if (s->stream.len) {
        rlen += 1 + s->stream.len;
    }

    if (args && args[0]) {
        rlen += 1 + ngx_strlen(args);
    }

    s->request_line = ngx_create_temp_buf(s->connection->pool, rlen + 1);
    if (s->request_line == NULL) {
        ngx_log_error(NGX_LOG_ERR, s->connection->log, 0,
                      "%s: failed to ngx_pcalloc for request_line", cmd);
        return NGX_ERROR;
    }

    if (s->stream.len) {
        if (args && args[0]) {
            *ngx_snprintf(s->request_line->pos, rlen + 1, "%V/%V?%s", &s->tc_url,
                          &s->stream, args) = CR;
        } else {
            *ngx_snprintf(s->request_line->pos, rlen + 1, "%V/%V", &s->tc_url,
                          &s->stream) = CR;
        }
    } else {
        if (args && args[0]) {
            *ngx_snprintf(s->request_line->pos, rlen + 1, "%V?%s", &s->tc_url,
                          args) = CR;
        } else {
            *ngx_snprintf(s->request_line->pos, rlen + 1, "%V", &s->tc_url)
                          = CR;
        }
    }

    s->request_line->last += rlen;

    if (ngx_rtmp_parse_request_line(s, s->request_line) != NGX_OK) {
        ngx_log_error(NGX_LOG_ERR, s->connection->log, 0,
                      "%s: invalid request line: '%s'", cmd, s->request_line->pos);
        return NGX_ERROR;
    }

    if (ngx_rtmp_process_request_uri(s) != NGX_OK) {
        return NGX_ERROR;
    }

    *s->request_line->last = 0;

    return NGX_OK;
}
