Exemple #1
0
TEST(UtilTest, ParseContentLength) {
    bstr *str = bstr_dup_c("134");

    EXPECT_EQ(134, htp_parse_content_length(str));

    bstr_free(str);
}
Exemple #2
0
/**
 * Determines presence (and encoding) of a response body.
 *
 * @param[in] connp
 * @returns HTP_OK on state change, HTP_ERROR on error, or HTP_DATA when more data is needed.
 */
htp_status_t htp_connp_RES_BODY_DETERMINE(htp_connp_t *connp) {
    // If the request uses the CONNECT method, then not only are we
    // to assume there's no body, but we need to ignore all
    // subsequent data in the stream.
    if (connp->out_tx->request_method_number == HTP_M_CONNECT) {
        if ((connp->out_tx->response_status_number >= 200)
                && (connp->out_tx->response_status_number <= 299)) {
            // This is a successful CONNECT stream, which means
            // we need to switch into tunnelling mode.
            connp->in_status = HTP_STREAM_TUNNEL;
            connp->out_status = HTP_STREAM_TUNNEL;
            connp->out_state = htp_connp_RES_FINALIZE;
            return HTP_OK;
        } else {
            // This is a failed CONNECT stream, which means that
            // we can unblock request parsing
            connp->in_status = HTP_STREAM_DATA;

            // We are going to continue processing this transaction,
            // adding a note for ourselves to stop at the end (because
            // we don't want to see the beginning of a new transaction).
            connp->out_data_other_at_tx_end = 1;
        }
    }

    // Check for an interim "100 Continue" response. Ignore it if found, and revert back to RES_FIRST_LINE.
    if (connp->out_tx->response_status_number == 100) {
        if (connp->out_tx->seen_100continue != 0) {
            htp_log(connp, HTP_LOG_MARK, HTP_LOG_ERROR, 0, "Already seen 100-Continue.");
            return HTP_ERROR;
        }

        // Ignore any response headers seen so far.
        htp_header_t *h = NULL;
        for (int i = 0, n = htp_table_size(connp->out_tx->response_headers); i < n; i++) {
            h = htp_table_get_index(connp->out_tx->response_headers, i, NULL);
            bstr_free(h->name);
            bstr_free(h->value);
            free(h);
        }

        htp_table_clear(connp->out_tx->response_headers);

        // Expecting to see another response line next.
        connp->out_state = htp_connp_RES_LINE;
        connp->out_tx->progress = HTP_RESPONSE_LINE;
        connp->out_tx->seen_100continue++;

        return HTP_OK;
    }

    // 1. Any response message which MUST NOT include a message-body
    //  (such as the 1xx, 204, and 304 responses and any response to a HEAD
    //  request) is always terminated by the first empty line after the
    //  header fields, regardless of the entity-header fields present in the
    //  message.
    if (((connp->out_tx->response_status_number >= 100) && (connp->out_tx->response_status_number <= 199))
            || (connp->out_tx->response_status_number == 204) || (connp->out_tx->response_status_number == 304)
            || (connp->out_tx->request_method_number == HTP_M_HEAD)) {
        // There's no response body
        connp->out_tx->response_transfer_coding = HTP_CODING_NO_BODY;
        connp->out_state = htp_connp_RES_FINALIZE;
    } else {
        // We have a response body

        htp_header_t *ct = htp_table_get_c(connp->out_tx->response_headers, "content-type");
        htp_header_t *cl = htp_table_get_c(connp->out_tx->response_headers, "content-length");
        htp_header_t *te = htp_table_get_c(connp->out_tx->response_headers, "transfer-encoding");

        if (ct != NULL) {
            connp->out_tx->response_content_type = bstr_dup_lower(ct->value);
            if (connp->out_tx->response_content_type == NULL) return HTP_ERROR;

            // Ignore parameters
            unsigned char *data = bstr_ptr(connp->out_tx->response_content_type);
            size_t len = bstr_len(ct->value);
            size_t newlen = 0;
            while (newlen < len) {
                // TODO Some platforms may do things differently here.
                if (htp_is_space(data[newlen]) || (data[newlen] == ';')) {
                    bstr_adjust_len(connp->out_tx->response_content_type, newlen);
                    break;
                }

                newlen++;
            }
        }

        // 2. If a Transfer-Encoding header field (section 14.40) is present and
        //   indicates that the "chunked" transfer coding has been applied, then
        //   the length is defined by the chunked encoding (section 3.6).
        if ((te != NULL) && (bstr_cmp_c(te->value, "chunked") == 0)) {
            // If the T-E header is present we are going to use it.
            connp->out_tx->response_transfer_coding = HTP_CODING_CHUNKED;

            // We are still going to check for the presence of C-L
            if (cl != NULL) {
                // This is a violation of the RFC
                connp->out_tx->flags |= HTP_REQUEST_SMUGGLING;
            }

            connp->out_state = htp_connp_RES_BODY_CHUNKED_LENGTH;
            connp->out_tx->progress = HTP_RESPONSE_BODY;
        }// 3. If a Content-Length header field (section 14.14) is present, its
            //   value in bytes represents the length of the message-body.
        else if (cl != NULL) {
            // We know the exact length
            connp->out_tx->response_transfer_coding = HTP_CODING_IDENTITY;

            // Check for multiple C-L headers
            if (cl->flags & HTP_FIELD_REPEATED) {
                connp->out_tx->flags |= HTP_REQUEST_SMUGGLING;
            }

            // Get body length
            connp->out_tx->response_content_length = htp_parse_content_length(cl->value);
            if (connp->out_tx->response_content_length < 0) {
                htp_log(connp, HTP_LOG_MARK, HTP_LOG_ERROR, 0, "Invalid C-L field in response: %d",
                        connp->out_tx->response_content_length);
                return HTP_ERROR;
            } else {
                connp->out_content_length = connp->out_tx->response_content_length;
                connp->out_body_data_left = connp->out_content_length;

                if (connp->out_content_length != 0) {
                    connp->out_state = htp_connp_RES_BODY_IDENTITY_CL_KNOWN;
                    connp->out_tx->progress = HTP_RESPONSE_BODY;
                } else {
                    connp->out_state = htp_connp_RES_FINALIZE;
                }
            }
        } else {
            // 4. If the message uses the media type "multipart/byteranges", which is
            //   self-delimiting, then that defines the length. This media type MUST
            //   NOT be used unless the sender knows that the recipient can parse it;
            //   the presence in a request of a Range header with multiple byte-range
            //   specifiers implies that the client can parse multipart/byteranges
            //   responses.
            if (ct != NULL) {
                // TODO Handle multipart/byteranges
                if (bstr_index_of_c_nocase(ct->value, "multipart/byteranges") != -1) {
                    htp_log(connp, HTP_LOG_MARK, HTP_LOG_ERROR, 0,
                            "C-T multipart/byteranges in responses not supported");
                    return HTP_ERROR;
                }
            }

            // 5. By the server closing the connection. (Closing the connection
            //   cannot be used to indicate the end of a request body, since that
            //   would leave no possibility for the server to send back a response.)
            connp->out_state = htp_connp_RES_BODY_IDENTITY_STREAM_CLOSE;
            connp->out_tx->response_transfer_coding = HTP_CODING_IDENTITY;
            connp->out_tx->progress = HTP_RESPONSE_BODY;
            connp->out_body_data_left = -1;
        }
    }

    // NOTE We do not need to check for short-style HTTP/0.9 requests here because
    //      that is done earlier, before response line parsing begins

    int rc = htp_tx_state_response_headers(connp->out_tx);
    if (rc != HTP_OK) return rc;

    return HTP_OK;
}
/**
 * Generic response header line(s) processor, which assembles folded lines
 * into a single buffer before invoking the parsing function.
 * 
 * @param[in] connp
 * @param[in] data
 * @param[in] len
 * @return HTP status
 */
htp_status_t htp_process_response_header_generic(htp_connp_t *connp, unsigned char *data, size_t len) {
    // Create a new header structure.
    htp_header_t *h = calloc(1, sizeof (htp_header_t));
    if (h == NULL) return HTP_ERROR;

    if (htp_parse_response_header_generic(connp, h, data, len) != HTP_OK) {
        free(h);
        return HTP_ERROR;
    }

    #ifdef HTP_DEBUG
    fprint_bstr(stderr, "Header name", h->name);
    fprint_bstr(stderr, "Header value", h->value);
    #endif

    // Do we already have a header with the same name?
    htp_header_t *h_existing = htp_table_get(connp->out_tx->response_headers, h->name);
    if (h_existing != NULL) {
        // Keep track of repeated same-name headers.
        h_existing->flags |= HTP_FIELD_REPEATED;
                
        // Having multiple C-L headers is against the RFC but many
        // browsers ignore the subsequent headers if the values are the same.
        if (bstr_cmp_c_nocase(h->name, "Content-Length") == 0) {
            // Don't use string comparison here because we want to
            // ignore small formatting differences.

            int64_t existing_cl, new_cl;

            existing_cl = htp_parse_content_length(h_existing->value);
            new_cl = htp_parse_content_length(h->value);
            if ((existing_cl == -1) || (new_cl == -1) || (existing_cl != new_cl)) {
                // Ambiguous response C-L value.
                htp_log(connp, HTP_LOG_MARK, HTP_LOG_ERROR, 0, "Ambiguous response C-L value");

                bstr_free(h->name);
                bstr_free(h->value);
                free(h);
                
                return HTP_ERROR;
            }

            // Ignoring the new C-L header that has the same value as the previous ones.
        } else {            
            // Add to the existing header.

            bstr *new_value = bstr_expand(h_existing->value, bstr_len(h_existing->value) + 2 + bstr_len(h->value));
            if (new_value == NULL) {
                bstr_free(h->name);
                bstr_free(h->value);
                free(h);
                return HTP_ERROR;
            }

            h_existing->value = new_value;
            bstr_add_mem_noex(h_existing->value, (unsigned char *) ", ", 2);
            bstr_add_noex(h_existing->value, h->value);
        }

        // The new header structure is no longer needed.
        bstr_free(h->name);
        bstr_free(h->value);
        free(h);       
    } else {
        // Add as a new header.
        if (htp_table_add(connp->out_tx->response_headers, h->name, h) != HTP_OK) {
            bstr_free(h->name);
            bstr_free(h->value);
            free(h);
            return HTP_ERROR;
        }
    }
   
    return HTP_OK;
}
static htp_status_t htp_tx_process_request_headers(htp_tx_t *tx) {
    if (tx == NULL) return HTP_ERROR;

    // Determine if we have a request body, and how it is packaged.

    htp_status_t rc = HTP_OK;

    htp_header_t *cl = htp_table_get_c(tx->request_headers, "content-length");
    htp_header_t *te = htp_table_get_c(tx->request_headers, "transfer-encoding");

    // Check for the Transfer-Encoding header, which would indicate a chunked request body.
    if (te != NULL) {
        // Make sure it contains "chunked" only.
        // TODO The HTTP/1.1 RFC also allows the T-E header to contain "identity", which
        //      presumably should have the same effect as T-E header absence. However, Apache
        //      (2.2.22 on Ubuntu 12.04 LTS) instead errors out with "Unknown Transfer-Encoding: identity".
        //      And it behaves strangely, too, sending a 501 and proceeding to process the request
        //      (e.g., PHP is run), but without the body. It then closes the connection.
        if (bstr_cmp_c(te->value, "chunked") != 0) {
            // Invalid T-E header value.
            tx->request_transfer_coding = HTP_CODING_INVALID;
            tx->flags |= HTP_REQUEST_INVALID_T_E;
            tx->flags |= HTP_REQUEST_INVALID;
        } else {
            // Chunked encoding is a HTTP/1.1 feature, so check that an earlier protocol
            // version is not used. The flag will also be set if the protocol could not be parsed.
            //
            // TODO IIS 7.0, for example, would ignore the T-E header when it
            //      it is used with a protocol below HTTP 1.1. This should be a
            //      personality trait.
            if (tx->request_protocol_number < HTP_PROTOCOL_1_1) {
                tx->flags |= HTP_REQUEST_INVALID_T_E;
                tx->flags |= HTP_REQUEST_SMUGGLING;
            }

            // If the T-E header is present we are going to use it.
            tx->request_transfer_coding = HTP_CODING_CHUNKED;

            // We are still going to check for the presence of C-L.
            if (cl != NULL) {
                // According to the HTTP/1.1 RFC (section 4.4):
                //
                // "The Content-Length header field MUST NOT be sent
                //  if these two lengths are different (i.e., if a Transfer-Encoding
                //  header field is present). If a message is received with both a
                //  Transfer-Encoding header field and a Content-Length header field,
                //  the latter MUST be ignored."
                //
                tx->flags |= HTP_REQUEST_SMUGGLING;
            }
        }
    } else if (cl != NULL) {        
        // Check for a folded C-L header.
        if (cl->flags & HTP_FIELD_FOLDED) {
            tx->flags |= HTP_REQUEST_SMUGGLING;
        }

        // Check for multiple C-L headers.
        if (cl->flags & HTP_FIELD_REPEATED) {
            tx->flags |= HTP_REQUEST_SMUGGLING;
            // TODO Personality trait to determine which C-L header to parse.
            //      At the moment we're parsing the combination of all instances,
            //      which is bound to fail (because it will contain commas).
        }

        // Get the body length.
        tx->request_content_length = htp_parse_content_length(cl->value);
        if (tx->request_content_length < 0) {
            tx->request_transfer_coding = HTP_CODING_INVALID;
            tx->flags |= HTP_REQUEST_INVALID_C_L;
            tx->flags |= HTP_REQUEST_INVALID;
        } else {
            // We have a request body of known length.
            tx->request_transfer_coding = HTP_CODING_IDENTITY;
        }
    } else {
        // No body.
        tx->request_transfer_coding = HTP_CODING_NO_BODY;
    }

    // If we could not determine the correct body handling,
    // consider the request invalid.
    if (tx->request_transfer_coding == HTP_CODING_UNKNOWN) {
        tx->request_transfer_coding = HTP_CODING_INVALID;
        tx->flags |= HTP_REQUEST_INVALID;
    }

    // Check for PUT requests, which we need to treat as file uploads.
    if (tx->request_method_number == HTP_M_PUT) {
        if (htp_tx_req_has_body(tx)) {
            // Prepare to treat PUT request body as a file.
            tx->connp->put_file = calloc(1, sizeof (htp_file_t));
            if (tx->connp->put_file == NULL) return HTP_ERROR;
            tx->connp->put_file->source = HTP_FILE_PUT;
        } else {
            // TODO Warn about PUT request without a body.
        }

        return HTP_OK;
    }

    // Determine hostname.

    // Use the hostname from the URI, when available.   
    if (tx->parsed_uri->hostname != NULL) {
        tx->request_hostname = bstr_dup(tx->parsed_uri->hostname);
        if (tx->request_hostname == NULL) return HTP_ERROR;
    }

    tx->request_port_number = tx->parsed_uri->port_number;

    // Examine the Host header.

    htp_header_t *h = htp_table_get_c(tx->request_headers, "host");
    if (h == NULL) {
        // No host information in the headers.

        // HTTP/1.1 requires host information in the headers.
        if (tx->request_protocol_number >= HTP_PROTOCOL_1_1) {
            tx->flags |= HTP_HOST_MISSING;
        }
    } else {
        // Host information available in the headers.

        bstr *hostname;
        int port;

        rc = htp_parse_header_hostport(h->value, &hostname, &port, &(tx->flags));
        if (rc != HTP_OK) return rc;

        // Is there host information in the URI?
        if (tx->request_hostname == NULL) {
            // There is no host information in the URI. Place the
            // hostname from the headers into the parsed_uri structure.
            tx->request_hostname = hostname;
            tx->request_port_number = port;
        } else {
            // The host information appears in the URI and in the headers. It's
            // OK if both have the same thing, but we want to check for differences.
            if ((bstr_cmp_nocase(hostname, tx->request_hostname) != 0) || (port != tx->request_port_number)) {
                // The host information is different in the headers and the URI. The
                // HTTP RFC states that we should ignore the header copy.
                tx->flags |= HTP_HOST_AMBIGUOUS;
            }

            bstr_free(hostname);
        }
    }

    // Determine Content-Type.
    htp_header_t *ct = htp_table_get_c(tx->request_headers, "content-type");
    if (ct != NULL) {
        rc = htp_parse_ct_header(ct->value, &tx->request_content_type);
        if (rc != HTP_OK) return rc;
    }

    // Parse cookies.
    if (tx->connp->cfg->parse_request_cookies) {
        rc = htp_parse_cookies_v0(tx->connp);
        if (rc != HTP_OK) return rc;
    }

    // Parse authentication information.
    if (tx->connp->cfg->parse_request_auth) {
        rc = htp_parse_authorization(tx->connp);
        if (rc == HTP_DECLINED) {
            // Don't fail the stream if an authorization header is invalid, just set a flag.
            tx->flags |= HTP_AUTH_INVALID;
        } else {
            if (rc != HTP_OK) return rc;
        }
    }

    // Finalize sending raw header data.
    rc = htp_connp_req_receiver_finalize_clear(tx->connp);
    if (rc != HTP_OK) return rc;

    // Run hook REQUEST_HEADERS.
    rc = htp_hook_run_all(tx->connp->cfg->hook_request_headers, tx);
    if (rc != HTP_OK) return rc;

    // We cannot proceed if the request is invalid.
    if (tx->flags & HTP_REQUEST_INVALID) {
        return HTP_ERROR;
    }

    return HTP_OK;
}
static htp_status_t htp_tx_process_request_headers(htp_tx_t *tx) {
    // Remember how many header lines there were before trailers.
    tx->request_header_lines_no_trailers = htp_list_size(tx->request_header_lines);

    // Determine if we have a request body, and how it is packaged.
    htp_header_t *cl = htp_table_get_c(tx->request_headers, "content-length");
    htp_header_t *te = htp_table_get_c(tx->request_headers, "transfer-encoding");

    // Check for the Transfer-Encoding header, which would indicate a chunked request body.
    if (te != NULL) {
        // Make sure it contains "chunked" only.
        if (bstr_cmp_c(te->value, "chunked") != 0) {
            // Invalid T-E header value.
            tx->flags |= HTP_INVALID_CHUNKING;

            htp_log(tx->connp, HTP_LOG_MARK, HTP_LOG_ERROR, 0,
                    "Invalid T-E value in request");
        }

        // Chunked encoding is a HTTP/1.1 feature. Check that some other protocol is not
        // used. The flag will also be set if the protocol could not be parsed.
        //
        // TODO IIS 7.0, for example, would ignore the T-E header when it
        //      it is used with a protocol below HTTP 1.1.
        if (tx->request_protocol_number < HTP_PROTOCOL_1_1) {
            tx->flags |= HTP_INVALID_CHUNKING;
        }

        // If the T-E header is present we are going to use it.
        tx->request_transfer_coding = HTP_CODING_CHUNKED;

        // We are still going to check for the presence of C-L.
        if (cl != NULL) {
            // This is a violation of the RFC.
            tx->flags |= HTP_REQUEST_SMUGGLING;
        }
    } else if (cl != NULL) {
        // We have a request body of known length.
        tx->request_transfer_coding = HTP_CODING_IDENTITY;

        // Check for a folded C-L header.
        if (cl->flags & HTP_FIELD_FOLDED) {
            tx->flags |= HTP_REQUEST_SMUGGLING;
        }

        // Check for multiple C-L headers.
        if (cl->flags & HTP_FIELD_REPEATED) {
            tx->flags |= HTP_REQUEST_SMUGGLING;
        }

        // Get body length.
        tx->request_content_length = htp_parse_content_length(cl->value);
        if (tx->request_content_length < 0) {
            htp_log(tx->connp, HTP_LOG_MARK, HTP_LOG_ERROR, 0, "Invalid C-L field in request");
            return HTP_ERROR;
        }
    } else {
        // No body.
        tx->request_transfer_coding = HTP_CODING_NO_BODY;
    }

    // Check for PUT requests, which we need to treat as file uploads.
    if (tx->request_method_number == HTP_M_PUT) {
        if (htp_tx_req_has_body(tx)) {
            // Prepare to treat PUT request body as a file.
            tx->connp->put_file = calloc(1, sizeof (htp_file_t));
            if (tx->connp->put_file == NULL) return HTP_ERROR;
            tx->connp->put_file->source = HTP_FILE_PUT;
        } else {
            // TODO Warn about PUT request without a body.
        }

        return HTP_OK;
    }

    // Host resolution
    htp_header_t *h = htp_table_get_c(tx->request_headers, "host");
    if (h == NULL) {
        // No host information in the headers.

        // HTTP/1.1 requires host information in the headers.
        if (tx->request_protocol_number >= HTP_PROTOCOL_1_1) {
            tx->flags |= HTP_HOST_MISSING;
            htp_log(tx->connp, HTP_LOG_MARK, HTP_LOG_WARNING, 0,
                    "Host information in request headers required by HTTP/1.1");
        }
    } else {
        // Host information available in the headers.

        bstr *hostname;
        int port;

        if (htp_parse_hostport(h->value, &hostname, &port, &(tx->flags)) != HTP_OK) return HTP_ERROR;

        // Is there host information in the URI?
        if (tx->parsed_uri->hostname == NULL) {
            // There is no host information in the URI. Place the
            // hostname from the headers into the parsed_uri structure.
            tx->parsed_uri->hostname = hostname;
            tx->parsed_uri->port_number = port;
        } else {
            if ((bstr_cmp_nocase(hostname, tx->parsed_uri->hostname) != 0) || (port != tx->parsed_uri->port_number)) {
                // The host information is different in the
                // headers and the URI. The HTTP RFC states that
                // we should ignore the header copy.
                tx->flags |= HTP_HOST_AMBIGUOUS;
                htp_log(tx->connp, HTP_LOG_MARK, HTP_LOG_WARNING, 0, "Host information ambiguous");
            }

            bstr_free(hostname);
        }
    }

    // Parse the Content-Type header.
    htp_header_t *ct = htp_table_get_c(tx->request_headers, "content-type");
    if (ct != NULL) {
        if (htp_parse_ct_header(ct->value, &tx->request_content_type) != HTP_OK) return HTP_ERROR;
    }

    // Parse cookies.
    if (tx->connp->cfg->parse_request_cookies) {
        htp_parse_cookies_v0(tx->connp);
    }

    // Parse authentication information.
    if (tx->connp->cfg->parse_request_http_authentication) {
        htp_parse_authorization(tx->connp);
    }

    // Run hook REQUEST_HEADERS.
    int rc = htp_hook_run_all(tx->connp->cfg->hook_request_headers, tx->connp);
    if (rc != HTP_OK) return rc;

    return HTP_OK;
}
Exemple #6
0
/**
 * Determines presence (and encoding) of a request body.
 *
 * @param connp
 * @returns HTP_OK on state change, HTTP_ERROR on error, or HTP_DATA when more data is needed.
 */
int htp_connp_REQ_BODY_DETERMINE(htp_connp_t *connp) {
    htp_header_t *cl = table_get_c(connp->in_tx->request_headers, "content-length");
    htp_header_t *te = table_get_c(connp->in_tx->request_headers, "transfer-encoding");

    // Check for the Transfer-Encoding header, which
    // would indicate a chunked request body
    if (te != NULL) {
        // Make sure it contains "chunked" only
        if (bstr_cmp_c(te->value, "chunked") != 0) {
            // Invalid T-E header value
            htp_log(connp, HTP_LOG_MARK, HTP_LOG_ERROR, 0,
                "Invalid T-E value in request");
        }

        // Chunked encoding is a HTTP/1.1 feature. Check
        // that some other protocol is not used. The flag will
        // also be set if the protocol could not be parsed.
        //
        // TODO IIS 7.0, for example, would ignore the T-E header when it
        //      it is used with a protocol below HTTP 1.1.
        if (connp->in_tx->request_protocol_number < HTTP_1_1) {
            connp->in_tx->flags |= HTP_INVALID_CHUNKING;
            // TODO Log
        }

        // If the T-E header is present we are going to use it.
        connp->in_tx->request_transfer_coding = CHUNKED;

        // We are still going to check for the presence of C-L
        if (cl != NULL) {
            // This is a violation of the RFC
            connp->in_tx->flags |= HTP_REQUEST_SMUGGLING;
            // TODO Log
        }

        connp->in_state = htp_connp_REQ_BODY_CHUNKED_LENGTH;
        connp->in_tx->progress = TX_PROGRESS_REQ_BODY;
    } else
        // Next check for the presence of the Content-Length header
        if (cl != NULL) {
        // It seems that we have a request body.
        connp->in_tx->request_transfer_coding = IDENTITY;

        // Check for a folded C-L header
        if (cl->flags & HTP_FIELD_FOLDED) {
            connp->in_tx->flags |= HTP_REQUEST_SMUGGLING;
            // TODO Log
        }

        // Check for multiple C-L headers
        if (cl->flags & HTP_FIELD_REPEATED) {
            connp->in_tx->flags |= HTP_REQUEST_SMUGGLING;
            // TODO Log
        }

        // Get body length
        int i = htp_parse_content_length(cl->value);
        if (i < 0) {
            htp_log(connp, HTP_LOG_MARK, HTP_LOG_ERROR, 0, "Invalid C-L field in request");
            return HTP_ERROR;
        } else {
            connp->in_content_length = i;
            connp->in_body_data_left = connp->in_content_length;

            if (connp->in_content_length != 0) {
                connp->in_state = htp_connp_REQ_BODY_IDENTITY;
                connp->in_tx->progress = TX_PROGRESS_REQ_BODY;
            } else {
                connp->in_state = htp_connp_REQ_IDLE;
                connp->in_tx->progress = TX_PROGRESS_WAIT;
            }
        }
    } else {
        // This request does not have a body, which
        // means that we're done with it
        connp->in_state = htp_connp_REQ_IDLE;
        connp->in_tx->progress = TX_PROGRESS_WAIT;
    }

    // Check for PUT requests, which we need to treat as file uploads
    if (connp->in_tx->request_method_number == M_PUT) {
        if (connp->in_tx->connp->in_tx->request_transfer_coding != 0) {
            // Prepare to treat PUT request body as a file
            connp->put_file = calloc(1, sizeof (htp_file_t));
            if (connp->put_file == NULL) return HTP_ERROR;
            connp->put_file->source = HTP_FILE_PUT;
        } else {
            // TODO Warn about PUT request without a body
        }

        return HTP_OK;
    }

    // Host resolution    
    htp_header_t *h = table_get_c(connp->in_tx->request_headers, "host");
    if (h == NULL) {
        // No host information in the headers

        // HTTP/1.1 requires host information in the headers
        if (connp->in_tx->request_protocol_number >= HTTP_1_1) {
            connp->in_tx->flags |= HTP_HOST_MISSING;
            htp_log(connp, HTP_LOG_MARK, HTP_LOG_WARNING, 0,
                "Host information in request headers required by HTTP/1.1");
        }
    } else {
        // Host information available in the headers

        // Is there host information in the URI?
        if (connp->in_tx->parsed_uri->hostname == NULL) {
            // There is no host information in the URI. Place the
            // hostname from the headers into the parsed_uri structure.
            htp_replace_hostname(connp, connp->in_tx->parsed_uri, h->value);
        } else if (bstr_cmp_nocase(h->value, connp->in_tx->parsed_uri->hostname) != 0) {
            // The host information is different in the
            // headers and the URI. The HTTP RFC states that
            // we should ignore the headers copy.
            connp->in_tx->flags |= HTP_AMBIGUOUS_HOST;
            htp_log(connp, HTP_LOG_MARK, HTP_LOG_WARNING, 0, "Host information ambiguous");
        }
    }

    // Parse Content-Type
    htp_header_t *ct = table_get_c(connp->in_tx->request_headers, "content-type");
    if (ct != NULL) {
        connp->in_tx->request_content_type = bstr_dup_lower(ct->value);
        if (connp->in_tx->request_content_type == NULL) {
            return HTP_ERROR;
        }
        
        // Ignore parameters        
        char *data = bstr_ptr(connp->in_tx->request_content_type);
        size_t len = bstr_len(ct->value);
        size_t newlen = 0;
        while (newlen < len) {
            // TODO Some platforms may do things differently here
            if (htp_is_space(data[newlen]) || (data[newlen] == ';')) {
                bstr_util_adjust_len(connp->in_tx->request_content_type, newlen);
                break;
            }

            newlen++;
        }
    }

    // Parse cookies
    if (connp->cfg->parse_request_cookies) {
        htp_parse_cookies_v0(connp);
    }

    // Parse authentication information
    if (connp->cfg->parse_request_http_authentication) {
        htp_parse_authorization(connp);
    }

    // Run hook REQUEST_HEADERS
    int rc = hook_run_all(connp->cfg->hook_request_headers, connp);
    if (rc != HOOK_OK) {
        switch (rc) {
            case HOOK_STOP:
                return HTP_STOP;
            case HOOK_ERROR:
            case HOOK_DECLINED:
            default:
                htp_log(connp, HTP_LOG_MARK, HTP_LOG_ERROR, 0,
                    "Request headers callback returned error (%d)", rc);
                return HTP_ERROR;
        }
    }

    return HTP_OK;
}
Exemple #7
0
/**
 * Determines presence (and encoding) of a response body.
 *
 * @param connp
 * @returns HTP_OK on state change, HTTP_ERROR on error, or HTP_DATA when more data is needed.
 */
int htp_connp_RES_BODY_DETERMINE(htp_connp_t *connp) {
    // If the request uses the CONNECT method, then not only are we
    // to assume there's no body, but we need to ignore all
    // subsequent data in the stream.
    if (connp->out_tx->request_method_number == M_CONNECT) {
        if ((connp->out_tx->response_status_number >= 200)
            && (connp->out_tx->response_status_number <= 299))
        {
            // This is a successful CONNECT stream, which means
            // we need to switch into tunnelling mode.
            connp->in_status = STREAM_STATE_TUNNEL;
            connp->out_status = STREAM_STATE_TUNNEL;
            connp->out_state = htp_connp_RES_IDLE;
            connp->out_tx->progress = TX_PROGRESS_DONE;
            return HTP_OK;
        } else {
            // This is a failed CONNECT stream, which means that
            // we can unblock request parsing
            connp->in_status = STREAM_STATE_DATA;
            
            // We are going to continue processing this transaction,
            // adding a note for ourselves to stop at the end (because
            // we don't want to see the beginning of a new transaction).
            connp->out_data_other_at_tx_end = 1;
        }       
    }

    // Check for an interim "100 Continue"
    // response. Ignore it if found, and revert back to RES_FIRST_LINE.
    if (connp->out_tx->response_status_number == 100) {
        if (connp->out_tx->seen_100continue != 0) {
            htp_log(connp, HTP_LOG_MARK, HTP_LOG_ERROR, 0, "Already seen 100-Continue");
            return HTP_ERROR;
        }

        // Ignore any response headers set
        table_clear(connp->out_tx->response_headers);

        connp->out_state = htp_connp_RES_LINE;
        connp->out_tx->progress = TX_PROGRESS_RES_LINE;
        connp->out_tx->seen_100continue++;

        return HTP_OK;
    }

    // Check for compression
    if (connp->cfg->response_decompression_enabled) {
        htp_header_t *ce = table_get_c(connp->out_tx->response_headers, "content-encoding");
        if (ce != NULL) {
            if ((bstr_cmp_c(ce->value, "gzip") == 0) || (bstr_cmp_c(ce->value, "x-gzip") == 0)) {
                connp->out_tx->response_content_encoding = COMPRESSION_GZIP;
            } else if ((bstr_cmp_c(ce->value, "deflate") == 0) || (bstr_cmp_c(ce->value, "x-deflate") == 0)) {
                connp->out_tx->response_content_encoding = COMPRESSION_DEFLATE;
            }

            if (connp->out_tx->response_content_encoding != COMPRESSION_NONE) {
                connp->out_decompressor = (htp_decompressor_t *) htp_gzip_decompressor_create(connp,
                    connp->out_tx->response_content_encoding);
                if (connp->out_decompressor != NULL) {
                    connp->out_decompressor->callback = htp_connp_RES_BODY_DECOMPRESSOR_CALLBACK;
                } else {
                    // No need to do anything; the error will have already
                    // been reported by the failed decompressor.
                }
            }
        }
    }

    // 1. Any response message which MUST NOT include a message-body
    //  (such as the 1xx, 204, and 304 responses and any response to a HEAD
    //  request) is always terminated by the first empty line after the
    //  header fields, regardless of the entity-header fields present in the
    //  message.
    if (((connp->out_tx->response_status_number >= 100) && (connp->out_tx->response_status_number <= 199))
        || (connp->out_tx->response_status_number == 204) || (connp->out_tx->response_status_number == 304)
        || (connp->out_tx->request_method_number == M_HEAD)) {
        // There's no response body        
        connp->out_state = htp_connp_RES_IDLE;
    } else {
        // We have a response body

        htp_header_t *cl = table_get_c(connp->out_tx->response_headers, "content-length");
        htp_header_t *te = table_get_c(connp->out_tx->response_headers, "transfer-encoding");

        // 2. If a Transfer-Encoding header field (section 14.40) is present and
        //   indicates that the "chunked" transfer coding has been applied, then
        //   the length is defined by the chunked encoding (section 3.6).
        if ((te != NULL) && (bstr_cmp_c(te->value, "chunked") == 0)) {
            // If the T-E header is present we are going to use it.
            connp->out_tx->response_transfer_coding = CHUNKED;

            // We are still going to check for the presence of C-L
            if (cl != NULL) {
                // This is a violation of the RFC
                connp->out_tx->flags |= HTP_REQUEST_SMUGGLING;
                // TODO
            }

            connp->out_state = htp_connp_RES_BODY_CHUNKED_LENGTH;
            connp->out_tx->progress = TX_PROGRESS_RES_BODY;
        }// 3. If a Content-Length header field (section 14.14) is present, its
            //   value in bytes represents the length of the message-body.
        else if (cl != NULL) {
            // We know the exact length
            connp->out_tx->response_transfer_coding = IDENTITY;

            // Check for multiple C-L headers
            if (cl->flags & HTP_FIELD_REPEATED) {
                connp->out_tx->flags |= HTP_REQUEST_SMUGGLING;
                // TODO Log
            }

            // Get body length
            int i = htp_parse_content_length(cl->value);
            if (i < 0) {
                htp_log(connp, HTP_LOG_MARK, HTP_LOG_ERROR, 0, "Invalid C-L field in response: %d", i);
                return HTP_ERROR;
            } else {
                connp->out_content_length = i;
                connp->out_body_data_left = connp->out_content_length;

                if (connp->out_content_length != 0) {
                    connp->out_state = htp_connp_RES_BODY_IDENTITY;
                    connp->out_tx->progress = TX_PROGRESS_RES_BODY;
                } else {
                    connp->out_state = htp_connp_RES_IDLE;
                    connp->out_tx->progress = TX_PROGRESS_DONE;
                }
            }
        } else {
            // 4. If the message uses the media type "multipart/byteranges", which is
            //   self-delimiting, then that defines the length. This media type MUST
            //   NOT be used unless the sender knows that the recipient can parse it;
            //   the presence in a request of a Range header with multiple byte-range
            //   specifiers implies that the client can parse multipart/byteranges
            //   responses.
            htp_header_t *ct = table_get_c(connp->out_tx->response_headers, "content-type");
            if (ct != NULL) {
                // TODO Handle multipart/byteranges

                if (bstr_index_of_c_nocase(ct->value, "multipart/byteranges") != -1) {
                    htp_log(connp, HTP_LOG_MARK, HTP_LOG_ERROR, 0,
                        "C-T multipart/byteranges in responses not supported");
                    return HTP_ERROR;
                }
            }

            // 5. By the server closing the connection. (Closing the connection
            //   cannot be used to indicate the end of a request body, since that
            //   would leave no possibility for the server to send back a response.)
            connp->out_state = htp_connp_RES_BODY_IDENTITY;
            connp->out_tx->progress = TX_PROGRESS_RES_BODY;
        }
    }

    // NOTE We do not need to check for short-style HTTP/0.9 requests here because
    //      that is done earlier, before response line parsing begins

    // Run hook RESPONSE_HEADERS_COMPLETE
    int rc = hook_run_all(connp->cfg->hook_response_headers, connp);
    if (rc != HOOK_OK) {
        htp_log(connp, HTP_LOG_MARK, HTP_LOG_ERROR, 0,
            "Response headers callback returned error (%d)", rc);

        return HTP_ERROR;
    }

    return HTP_OK;
}
Exemple #8
0
/**
 * Determines presence (and encoding) of a request body.
 *
 * @param connp
 * @returns HTP_OK on state change, HTTP_ERROR on error, or HTP_DATA when more data is needed.
 */
int htp_connp_REQ_BODY_DETERMINE(htp_connp_t *connp) {
    htp_header_t *cl = table_getc(connp->in_tx->request_headers, "content-length");
    htp_header_t *te = table_getc(connp->in_tx->request_headers, "transfer-encoding");

    // Check for the Transfer-Encoding header, which
    // would indicate a chunked request body
    if (te != NULL && te->value != NULL) {
        // Make sure it contains "chunked" only
        if (bstr_cmpc(te->value, "chunked") != 0) {
            // Invalid T-E header value
            htp_log(connp, HTP_LOG_MARK, HTP_LOG_ERROR, 0,
                "Invalid T-E value in request");
        }

        // Chunked encoding is a HTTP/1.1 feature. Check
        // that some other protocol is not used. The flag will
        // also be set if the protocol could not be parsed.
        //
        // TODO IIS 7.0, for example, would ignore the T-E header when it
        //      it is used with a protocol below HTTP 1.1.
        if (connp->in_tx->request_protocol_number < HTTP_1_1) {
            connp->in_tx->flags |= HTP_INVALID_CHUNKING;
            // TODO Log
        }

        // If the T-E header is present we are going to use it.
        connp->in_tx->request_transfer_coding = CHUNKED;

        // We are still going to check for the presence of C-L
        if (cl != NULL) {
            // This is a violation of the RFC
            connp->in_tx->flags |= HTP_REQUEST_SMUGGLING;
            // TODO Log
        }

        connp->in_state = htp_connp_REQ_BODY_CHUNKED_LENGTH;
        connp->in_tx->progress[0] = TX_PROGRESS_REQ_BODY;
    } else
        // Next check for the presence of the Content-Length header
        if (cl != NULL && cl->value != NULL) {
        // It seems that we have a request body.
        connp->in_tx->request_transfer_coding = IDENTITY;

        // Check for a folded C-L header
        if (cl->flags & HTP_FIELD_FOLDED) {
            connp->in_tx->flags |= HTP_REQUEST_SMUGGLING;
            // TODO Log
        }

        // Check for multiple C-L headers
        if (cl->flags & HTP_FIELD_REPEATED) {
            connp->in_tx->flags |= HTP_REQUEST_SMUGGLING;
            // TODO Log
        }

        // Get body length
        int i = htp_parse_content_length(cl->value);
        if (i < 0) {
            htp_log(connp, HTP_LOG_MARK, HTP_LOG_ERROR, 0, "Invalid C-L field in request");
            return HTP_ERROR;
        } else {
            connp->in_content_length = i;
            connp->in_body_data_left = connp->in_content_length;

            if (connp->in_content_length != 0) {
                connp->in_state = htp_connp_REQ_BODY_IDENTITY;
                connp->in_tx->progress[0] = TX_PROGRESS_REQ_BODY;
            } else {
                connp->in_state = htp_connp_REQ_IDLE;
                connp->in_tx->progress[0] = TX_PROGRESS_WAIT;
            }
        }
    } else {
        // This request does not have a body, which
        // means that we're done with it
        connp->in_state = htp_connp_REQ_IDLE;
        connp->in_tx->progress[0] = TX_PROGRESS_WAIT;
    }

    // Host resolution    
    htp_header_t *h = table_getc(connp->in_tx->request_headers, "host");
    if (h == NULL) {
        // No host information in the headers

        // HTTP/1.1 requires host information in the headers
        if (connp->in_tx->request_protocol_number >= HTTP_1_1) {
            connp->in_tx->flags |= HTP_HOST_MISSING;
            htp_log(connp, HTP_LOG_MARK, HTP_LOG_WARNING, 0,
                "Host information in request headers required by HTTP/1.1");
        }
    } else {
        // Host information available in the headers

        // Is there host information in the URI?
        if (connp->in_tx->parsed_uri->hostname == NULL) {
            // There is no host information in the URI. Place the
            // hostname from the headers into the parsed_uri structure.
            htp_replace_hostname(connp, connp->in_tx->parsed_uri, h->value);
        } else if (bstr_cmp_nocase(h->value, connp->in_tx->parsed_uri->hostname) != 0) {
            // The host information is different in the
            // headers and the URI. The HTTP RFC states that
            // we should ignore the headers copy.
            connp->in_tx->flags |= HTP_AMBIGUOUS_HOST;
            htp_log(connp, HTP_LOG_MARK, HTP_LOG_WARNING, 0, "Host information ambiguous");
        }
    }

    // Run hook REQUEST_HEADERS
    int rc = hook_run_all(connp->cfg->hook_request_headers, connp);
    if (rc != HOOK_OK) {
        htp_log(connp, HTP_LOG_MARK, HTP_LOG_ERROR, 0,
            "Request headers callback returned error (%d)", rc);
        return HTP_ERROR;
    }

    return HTP_OK;
}