static int multipart_parse_content_disposition()

in apache2/msc_multipart.c [81:296]


static int multipart_parse_content_disposition(modsec_rec *msr, char *c_d_value) {
    char *p = NULL, *t = NULL;

    /* accept only what we understand */
    if (strncmp(c_d_value, "form-data", 9) != 0) {
        return -1;
    }

    /* see if there are any other parts to parse */

    p = c_d_value + 9;
    while((*p == '\t') || (*p == ' ')) p++;
    if (*p == '\0') return 1; /* this is OK */

    if (*p != ';') return -2;
    p++;

    uint8_t filename_present = FALSE;
    uint8_t filename_ext_present = FALSE;

    /* parse the appended parts */
    while(*p != '\0') {
        char *name = NULL, *value = NULL, *start = NULL;

        /* go over the whitespace */
        while((*p == '\t') || (*p == ' ')) p++;
        if (*p == '\0') return -3;

        start = p;
        while((*p != '\0') && (*p != '=') && (*p != '\t') && (*p != ' ')) p++;
        if (*p == '\0') return -4;

        name = apr_pstrmemdup(msr->mp, start, (p - start));

        while((*p == '\t') || (*p == ' ')) p++;
        if (*p == '\0') return -5;

        if (*p != '=') return -13;
        p++;

        while((*p == '\t') || (*p == ' ')) p++;
        if (*p == '\0') return -6;

        /* Accept both quotes as some backends will accept them, but
         * technically "'" is invalid and so flag_invalid_quoting is
         * set so the user can deal with it in the rules if they so wish.
         */

        if ((*p == '"') || (*p == '\'')) {
            /* quoted */
            char quote = *p;

            if (quote == '\'') {
                msr->mpd->flag_invalid_quoting = 1;
            }

            p++;
            if (*p == '\0') return -7;

            start = p;
            value = apr_pstrdup(msr->mp, p);
            t = value;

            while(*p != '\0') {
                if (*p == '\\') {
                    if (*(p + 1) == '\0') {
                        /* improper escaping */
                        return -8;
                    }
                    /* only quote and \ can be escaped */
                    if ((*(p + 1) == quote) || (*(p + 1) == '\\')) {
                        p++;
                    }
                    else {
                        /* improper escaping */

                        /* We allow for now because IE sends
                         * improperly escaped content and there's
                         * nothing we can do about it.
                         *
                         * return -9;
                         */
                    }
                }
                else if (*p == quote) {
                    *t = '\0';
                    break;
                }

                *(t++) = *(p++);
            }
            if (*p == '\0') return -10;

            p++; /* go over the quote at the end */

        } else {
            /* not quoted */

            start = p;
            while((*p != '\0') && (is_token_char(*p))) p++;
            value = apr_pstrmemdup(msr->mp, start, (p - start));
        }

        /* evaluate part */

        if (strcmp(name, "name") == 0) {

            validate_quotes(msr, value);

            msr->multipart_name = apr_pstrdup(msr->mp, value);

            if (msr->mpd->mpp->name != NULL) {
                msr_log(msr, 4, "Multipart: Warning: Duplicate Content-Disposition name: %s",
                    log_escape_nq(msr->mp, value));
                return -14;
            }
            msr->mpd->mpp->name = value;

            if (msr->txcfg->debuglog_level >= 9) {
                msr_log(msr, 9, "Multipart: Content-Disposition name: %s",
                    log_escape_nq(msr->mp, value));
            }
        }
        else if ((strcmp(name, "filename") == 0) || (strcmp(name, "filename*") == 0))
        {

            char *decoded_filename = NULL;

            if (strcmp(name, "filename*") == 0)
            {
                // We allow only one instance of `filename*` attribute to be present in the Content-Disposition header.
                if (filename_ext_present)
                {
                    msr_log(msr, 4, "Multipart: Warning: Duplicate Content-Disposition filename*: %s",
                            log_escape_nq(msr->mp, decoded_filename));
                    return -17;
                }

                filename_ext_present = TRUE;

                // Make sure to turn of INVALID quoting since RFC 5987 expects quotes in the filename format.
                msr->mpd->flag_invalid_quoting = 0;

                decoded_filename = rfc5987_decode(msr->mp, value);
                if (!decoded_filename)
                {
                    msr_log(msr, 4, "Multipart: Could not decode extended filename parameter in RFC 5987 format: %s",
                            log_escape_nq(msr->mp, value));
                    return -16;
                }
                msr->multipart_filename = decoded_filename;

                // The `filename*` RCF 5987 encoded filename always overrides the `filename` parameter in content-disposition header.
                msr->mpd->mpp->filename = apr_pstrdup(msr->mp, decoded_filename);

                // Re-run the validation check on the filename. We shouldn't be seeing quotes in the UTF-8 formatted filename either.
                validate_quotes(msr, msr->mpd->mpp->filename);
            }
            else
            {
                // We allow only one instance of `filename` attribute to be present in the Content-Disposition header.
                if (filename_present)
                {
                    // Duplicate `filename` attributes are not allowed.
                    msr_log(msr, 4, "Multipart: Warning: Duplicate Content-Disposition filename: %s",
                            log_escape_nq(msr->mp, decoded_filename));
                    return -15;
                }

                filename_present = TRUE;

                // Process the `filename` attribute in the content-disposition header only if `filename*` does not exist.
                if (!filename_ext_present)
                {
                    // "name == 'filename'"
                    decoded_filename = value;
                    validate_quotes(msr, value);
                    msr->multipart_filename = apr_pstrdup(msr->mp, decoded_filename);
                    msr->mpd->mpp->filename = decoded_filename;
                }
            }

            

            if (msr->txcfg->debuglog_level >= 9)
            {
                msr_log(msr, 9, "Multipart: Content-Disposition filename: %s",
                        log_escape_nq(msr->mp, decoded_filename));
            }
        }
        else return -11;

        if (*p != '\0') {
            while((*p == '\t') || (*p == ' ')) p++;
            /* the next character must be a zero or a semi-colon */
            if (*p == '\0') return 1; /* this is OK */
            if (*p != ';') {
                p--;
                if(*p == '\'' || *p == '\"') {
                    if (msr->txcfg->debuglog_level >= 9) {
                        msr_log(msr, 9, "Multipart: Invalid quoting detected: %s length %zu bytes",
                                log_escape_nq(msr->mp, p), strlen(p));
                    }
                    msr->mpd->flag_invalid_quoting = 1;
                }
                p++;
                return -12;
            }
            p++; /* move over the semi-colon */
        }

        /* loop will stop when (*p == '\0') */
    }

    return 1;
}