mirror of https://github.com/OISF/suricata
				
				
				
			
			You cannot select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
				
	
	
		
			805 lines
		
	
	
		
			30 KiB
		
	
	
	
		
			C
		
	
			
		
		
	
	
			805 lines
		
	
	
		
			30 KiB
		
	
	
	
		
			C
		
	
/*
 | 
						|
 * LibHTP (http://www.libhtp.org)
 | 
						|
 * Copyright 2009,2010 Ivan Ristic <ivanr@webkreator.com>
 | 
						|
 *
 | 
						|
 * LibHTP is an open source product, released under terms of the General Public Licence
 | 
						|
 * version 2 (GPLv2). Please refer to the file LICENSE, which contains the complete text
 | 
						|
 * of the license.
 | 
						|
 *
 | 
						|
 * In addition, there is a special exception that allows LibHTP to be freely
 | 
						|
 * used with any OSI-approved open source licence. Please refer to the file
 | 
						|
 * LIBHTP_LICENSING_EXCEPTION for the full text of the exception.
 | 
						|
 *
 | 
						|
 */
 | 
						|
 | 
						|
#include <stdlib.h>
 | 
						|
#include "htp.h"
 | 
						|
 | 
						|
/**
 | 
						|
 * Invoked whenever decompressed response body data becomes available.
 | 
						|
 *
 | 
						|
 * @param d
 | 
						|
 * @return HTP_OK on state change, HTP_ERROR on error.
 | 
						|
 */
 | 
						|
static int htp_connp_RES_BODY_DECOMPRESSOR_CALLBACK(htp_tx_data_t *d) {
 | 
						|
    // Invoke all callbacks
 | 
						|
    int rc = hook_run_all(d->tx->connp->cfg->hook_response_body_data, d);
 | 
						|
    if (rc != HTP_OK) {
 | 
						|
        htp_log(d->tx->connp, HTP_LOG_MARK, HTP_LOG_ERROR, 0,
 | 
						|
            "Response body data callback returned error (%d)", rc);
 | 
						|
        return HTP_ERROR;
 | 
						|
    }
 | 
						|
 | 
						|
    return HTP_OK;
 | 
						|
}
 | 
						|
 | 
						|
/**
 | 
						|
 * Consumes bytes until the end of the current line.
 | 
						|
 *
 | 
						|
 * @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_CHUNKED_DATA_END(htp_connp_t *connp) {
 | 
						|
    // TODO We shouldn't really see anything apart from CR and LF,
 | 
						|
    // so we should warn about anything else.
 | 
						|
 | 
						|
    for (;;) {
 | 
						|
        OUT_NEXT_BYTE_OR_RETURN(connp);
 | 
						|
 | 
						|
        connp->out_tx->request_message_len++;
 | 
						|
 | 
						|
        if (connp->out_next_byte == LF) {
 | 
						|
            connp->out_state = htp_connp_RES_BODY_CHUNKED_LENGTH;
 | 
						|
 | 
						|
            return HTP_OK;
 | 
						|
        }
 | 
						|
    }
 | 
						|
}
 | 
						|
 | 
						|
/**
 | 
						|
 * Processes a chunk of data.
 | 
						|
 *
 | 
						|
 * @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_CHUNKED_DATA(htp_connp_t *connp) {
 | 
						|
    htp_tx_data_t d;
 | 
						|
 | 
						|
    d.tx = connp->out_tx;
 | 
						|
    d.data = &connp->out_current_data[connp->out_current_offset];
 | 
						|
    d.len = 0;
 | 
						|
 | 
						|
    for (;;) {
 | 
						|
        OUT_NEXT_BYTE(connp);
 | 
						|
 | 
						|
        if (connp->out_next_byte == -1) {
 | 
						|
            if (connp->out_tx->response_content_encoding != COMPRESSION_NONE) {
 | 
						|
                connp->out_decompressor->decompress(connp->out_decompressor, &d);
 | 
						|
            } else {
 | 
						|
                // Send data to callbacks
 | 
						|
                int rc = hook_run_all(connp->cfg->hook_response_body_data, &d);
 | 
						|
                if (rc != HOOK_OK) {
 | 
						|
                    htp_log(connp, HTP_LOG_MARK, HTP_LOG_ERROR, 0,
 | 
						|
                        "Response body data callback returned error (%d)", rc);
 | 
						|
                    return HTP_ERROR;
 | 
						|
                }
 | 
						|
            }
 | 
						|
 | 
						|
            // Ask for more data
 | 
						|
            return HTP_DATA;
 | 
						|
        } else {
 | 
						|
            connp->out_tx->response_message_len++;
 | 
						|
            connp->out_tx->response_entity_len++;
 | 
						|
            connp->out_chunked_length--;
 | 
						|
            d.len++;
 | 
						|
 | 
						|
            if (connp->out_chunked_length == 0) {
 | 
						|
                // End of data chunk
 | 
						|
 | 
						|
                if (connp->out_tx->response_content_encoding != COMPRESSION_NONE) {
 | 
						|
                    connp->out_decompressor->decompress(connp->out_decompressor, &d);
 | 
						|
                } else {
 | 
						|
                    // Send data to callbacks
 | 
						|
                    int rc = hook_run_all(connp->cfg->hook_response_body_data, &d);
 | 
						|
                    if (rc != HOOK_OK) {
 | 
						|
                        htp_log(connp, HTP_LOG_MARK, HTP_LOG_ERROR, 0,
 | 
						|
                            "Response body data callback returned error (%d)", rc);
 | 
						|
                        return HTP_ERROR;
 | 
						|
                    }
 | 
						|
                }
 | 
						|
 | 
						|
                connp->out_state = htp_connp_RES_BODY_CHUNKED_DATA_END;
 | 
						|
 | 
						|
                return HTP_OK;
 | 
						|
            }
 | 
						|
        }
 | 
						|
    }
 | 
						|
}
 | 
						|
 | 
						|
/**
 | 
						|
 * Extracts chunk length.
 | 
						|
 *
 | 
						|
 * @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_CHUNKED_LENGTH(htp_connp_t *connp) {
 | 
						|
    for (;;) {
 | 
						|
        OUT_COPY_BYTE_OR_RETURN(connp);
 | 
						|
 | 
						|
        connp->out_tx->response_message_len++;
 | 
						|
 | 
						|
        // Have we reached the end of the line?
 | 
						|
        if (connp->out_next_byte == LF) {
 | 
						|
            htp_chomp(connp->out_line, &connp->out_line_len);
 | 
						|
 | 
						|
            // Extract chunk length
 | 
						|
            connp->out_chunked_length = htp_parse_chunked_length(connp->out_line, connp->out_line_len);
 | 
						|
 | 
						|
            // Cleanup for the next line
 | 
						|
            connp->out_line_len = 0;
 | 
						|
 | 
						|
            // Handle chunk length
 | 
						|
            if (connp->out_chunked_length > 0) {
 | 
						|
                // More data available
 | 
						|
                // TODO Add a check for chunk length
 | 
						|
                connp->out_state = htp_connp_RES_BODY_CHUNKED_DATA;
 | 
						|
            } else if (connp->out_chunked_length == 0) {
 | 
						|
                // End of data
 | 
						|
                connp->out_state = htp_connp_RES_HEADERS;
 | 
						|
                connp->out_tx->progress = TX_PROGRESS_RES_TRAILER;
 | 
						|
            } else {
 | 
						|
                // Invalid chunk length
 | 
						|
                htp_log(connp, HTP_LOG_MARK, HTP_LOG_ERROR, 0,
 | 
						|
                    "Response chunk encoding: Invalid chunk length: %d", connp->out_chunked_length);
 | 
						|
                return HTP_ERROR;
 | 
						|
            }
 | 
						|
 | 
						|
            return HTP_OK;
 | 
						|
        }
 | 
						|
    }
 | 
						|
}
 | 
						|
 | 
						|
/**
 | 
						|
 * Processes identity 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_IDENTITY(htp_connp_t *connp) {
 | 
						|
    htp_tx_data_t d;
 | 
						|
 | 
						|
    d.tx = connp->out_tx;
 | 
						|
    d.data = &connp->out_current_data[connp->out_current_offset];
 | 
						|
    d.len = 0;
 | 
						|
 | 
						|
    for (;;) {
 | 
						|
        OUT_NEXT_BYTE(connp);
 | 
						|
 | 
						|
        if (connp->out_next_byte == -1) {
 | 
						|
            // End of chunk
 | 
						|
 | 
						|
            // Send data to callbacks
 | 
						|
            if (d.len != 0) {
 | 
						|
                if (connp->out_tx->response_content_encoding != COMPRESSION_NONE) {
 | 
						|
                    connp->out_decompressor->decompress(connp->out_decompressor, &d);
 | 
						|
                } else {
 | 
						|
                    int rc = hook_run_all(connp->cfg->hook_response_body_data, &d);
 | 
						|
                    if (rc != HOOK_OK) {
 | 
						|
                        htp_log(connp, HTP_LOG_MARK, HTP_LOG_ERROR, 0,
 | 
						|
                            "Response body data callback returned error (%d)", rc);
 | 
						|
                        return HTP_ERROR;
 | 
						|
                    }
 | 
						|
                }
 | 
						|
            }
 | 
						|
 | 
						|
            // If we don't know the length, then we must check
 | 
						|
            // to see if the stream closed; that would signal the
 | 
						|
            // end of the response body (and the end of the transaction).
 | 
						|
            if ((connp->out_content_length == -1) && (connp->out_status == STREAM_STATE_CLOSED)) {
 | 
						|
                connp->out_state = htp_connp_RES_IDLE;
 | 
						|
                connp->out_tx->progress = TX_PROGRESS_DONE;
 | 
						|
 | 
						|
                return HTP_OK;
 | 
						|
            } else {
 | 
						|
                // Ask for more data
 | 
						|
                return HTP_DATA;
 | 
						|
            }
 | 
						|
        } else {
 | 
						|
            connp->out_tx->response_message_len++;
 | 
						|
            connp->out_tx->response_entity_len++;
 | 
						|
 | 
						|
            if (connp->out_body_data_left > 0) {
 | 
						|
                // We know the length of response body
 | 
						|
 | 
						|
                connp->out_body_data_left--;
 | 
						|
                d.len++;
 | 
						|
 | 
						|
                if (connp->out_body_data_left == 0) {
 | 
						|
                    // End of body
 | 
						|
 | 
						|
                    // Send data to callbacks
 | 
						|
                    if (d.len != 0) {
 | 
						|
                        if (connp->out_tx->response_content_encoding != COMPRESSION_NONE) {
 | 
						|
                            connp->out_decompressor->decompress(connp->out_decompressor, &d);
 | 
						|
                        } else {
 | 
						|
                            int rc = hook_run_all(connp->cfg->hook_response_body_data, &d);
 | 
						|
                            if (rc != HOOK_OK) {
 | 
						|
                                htp_log(connp, HTP_LOG_MARK, HTP_LOG_ERROR, 0,
 | 
						|
                                    "Response body data callback returned error (%d)", rc);
 | 
						|
                                return HTP_ERROR;
 | 
						|
                            }
 | 
						|
                        }
 | 
						|
                    }
 | 
						|
 | 
						|
                    // Done
 | 
						|
                    connp->out_state = htp_connp_RES_IDLE;
 | 
						|
                    connp->out_tx->progress = TX_PROGRESS_DONE;
 | 
						|
 | 
						|
                    return HTP_OK;
 | 
						|
                }
 | 
						|
            } else {
 | 
						|
                // We don't know the length of the response body, which means
 | 
						|
                // that the body will consume all data until the connection
 | 
						|
                // is closed.
 | 
						|
                //
 | 
						|
                // We don't need to do anything here.
 | 
						|
            }
 | 
						|
        }
 | 
						|
    }
 | 
						|
}
 | 
						|
 | 
						|
/**
 | 
						|
 * 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)
 | 
						|
        &&(connp->out_tx->response_status_number >= 200)
 | 
						|
        &&(connp->out_tx->response_status_number <= 299))
 | 
						|
    {
 | 
						|
        connp->out_status = STREAM_STATE_TUNNEL;
 | 
						|
        connp->out_state = htp_connp_RES_IDLE;
 | 
						|
        connp->out_tx->progress = TX_PROGRESS_DONE;
 | 
						|
 | 
						|
        return HTP_OK;
 | 
						|
    }
 | 
						|
 | 
						|
    // 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
 | 
						|
    htp_header_t *ce = table_getc(connp->out_tx->response_headers, "content-encoding");
 | 
						|
    if (ce != NULL) {
 | 
						|
        // TODO Improve detection
 | 
						|
        // TODO How would a Content-Range header affect us?
 | 
						|
        if ((bstr_cmpc(ce->value, "gzip") == 0) || (bstr_cmpc(ce->value, "x-gzip") == 0)) {
 | 
						|
            connp->out_decompressor = (htp_decompressor_t *) htp_gzip_decompressor_create(connp);
 | 
						|
            if (connp->out_decompressor != NULL) {
 | 
						|
                connp->out_tx->response_content_encoding = COMPRESSION_GZIP;
 | 
						|
                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_getc(connp->out_tx->response_headers, "content-length");
 | 
						|
        htp_header_t *te = table_getc(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) {
 | 
						|
            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 response");
 | 
						|
            }
 | 
						|
 | 
						|
            // 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");
 | 
						|
                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_getc(connp->out_tx->response_headers, "content-type");
 | 
						|
            if (ct != NULL) {
 | 
						|
                // TODO Handle multipart/byteranges
 | 
						|
 | 
						|
                if (bstr_indexofc_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;
 | 
						|
}
 | 
						|
 | 
						|
/**
 | 
						|
 * Parses response headers.
 | 
						|
 *
 | 
						|
 * @param connp
 | 
						|
 * @returns HTP_OK on state change, HTTP_ERROR on error, or HTP_DATA when more data is needed.
 | 
						|
 */
 | 
						|
int htp_connp_RES_HEADERS(htp_connp_t *connp) {
 | 
						|
    for (;;) {
 | 
						|
        OUT_COPY_BYTE_OR_RETURN(connp);
 | 
						|
 | 
						|
        if (connp->out_header_line == NULL) {
 | 
						|
            connp->out_header_line = calloc(1, sizeof (htp_header_line_t));
 | 
						|
            if (connp->out_header_line == NULL) return HTP_ERROR;
 | 
						|
            connp->out_header_line->first_nul_offset = -1;
 | 
						|
        }
 | 
						|
 | 
						|
        // Keep track of NUL bytes
 | 
						|
        if (connp->out_next_byte == 0) {
 | 
						|
            // Store the offset of the first NUL
 | 
						|
            if (connp->out_header_line->has_nulls == 0) {
 | 
						|
                connp->out_header_line->first_nul_offset = connp->out_line_len;
 | 
						|
            }
 | 
						|
 | 
						|
            // Remember how many NULs there were
 | 
						|
            connp->out_header_line->flags |= HTP_FIELD_NUL_BYTE;
 | 
						|
            connp->out_header_line->has_nulls++;
 | 
						|
        }
 | 
						|
 | 
						|
        // Have we reached the end of the line?
 | 
						|
        if (connp->out_next_byte == LF) {
 | 
						|
            #ifdef HTP_DEBUG
 | 
						|
            fprint_raw_data(stderr, __FUNCTION__, connp->out_line, connp->out_line_len);
 | 
						|
            #endif
 | 
						|
 | 
						|
            // Should we terminate headers?
 | 
						|
            if (htp_connp_is_line_terminator(connp, connp->out_line, connp->out_line_len)) {
 | 
						|
                // Terminator line
 | 
						|
 | 
						|
                // Parse previous header, if any
 | 
						|
                if (connp->out_header_line_index != -1) {
 | 
						|
                    if (connp->cfg->process_response_header(connp) != HTP_OK) {
 | 
						|
                        // Note: downstream responsible for error logging
 | 
						|
                        return HTP_ERROR;
 | 
						|
                    }
 | 
						|
 | 
						|
                    // Reset index
 | 
						|
                    connp->out_header_line_index = -1;
 | 
						|
                }
 | 
						|
 | 
						|
                // Cleanup
 | 
						|
                free(connp->out_header_line);
 | 
						|
                connp->out_line_len = 0;
 | 
						|
                connp->out_header_line = NULL;
 | 
						|
 | 
						|
                // We've seen all response headers
 | 
						|
                if (connp->out_tx->progress == TX_PROGRESS_RES_HEADERS) {
 | 
						|
                    // Determine if this response has a body
 | 
						|
                    connp->out_state = htp_connp_RES_BODY_DETERMINE;
 | 
						|
                } else {
 | 
						|
                    // Run hook response_TRAILER
 | 
						|
                    int rc = hook_run_all(connp->cfg->hook_response_trailer, connp);
 | 
						|
                    if (rc != HOOK_OK) {
 | 
						|
                        htp_log(connp, HTP_LOG_MARK, HTP_LOG_ERROR, 0,
 | 
						|
                            "Response trailer callback returned error (%d)", rc);
 | 
						|
                        return HTP_ERROR;
 | 
						|
                    }
 | 
						|
 | 
						|
                    // We've completed parsing this response
 | 
						|
                    connp->out_state = htp_connp_RES_IDLE;
 | 
						|
                }
 | 
						|
 | 
						|
                return HTP_OK;
 | 
						|
            }
 | 
						|
 | 
						|
            // Prepare line for consumption
 | 
						|
            htp_chomp(connp->out_line, &connp->out_line_len);
 | 
						|
 | 
						|
            // Check for header folding
 | 
						|
            if (htp_connp_is_line_folded(connp->out_line, connp->out_line_len) == 0) {
 | 
						|
                // New header line
 | 
						|
 | 
						|
                // Parse previous header, if any
 | 
						|
                if (connp->out_header_line_index != -1) {
 | 
						|
                    if (connp->cfg->process_response_header(connp) != HTP_OK) {
 | 
						|
                        // Note: downstream responsible for error logging
 | 
						|
                        return HTP_ERROR;
 | 
						|
                    }
 | 
						|
 | 
						|
                    // Reset index
 | 
						|
                    connp->out_header_line_index = -1;
 | 
						|
                }
 | 
						|
 | 
						|
                // Remember the index of the fist header line
 | 
						|
                connp->out_header_line_index = connp->out_header_line_counter;
 | 
						|
            } else {
 | 
						|
                // Folding; check that there's a previous header line to add to
 | 
						|
                if (connp->out_header_line_index == -1) {
 | 
						|
                    if (!(connp->out_tx->flags & HTP_INVALID_FOLDING)) {
 | 
						|
                        connp->out_tx->flags |= HTP_INVALID_FOLDING;
 | 
						|
                        htp_log(connp, HTP_LOG_MARK, HTP_LOG_WARNING, 0, "Invalid response field folding");
 | 
						|
                    }
 | 
						|
                }
 | 
						|
            }
 | 
						|
 | 
						|
            // Add the raw header line to the list
 | 
						|
            connp->out_header_line->line = bstr_memdup((char *) connp->out_line, connp->out_line_len);
 | 
						|
            list_add(connp->out_tx->response_header_lines, connp->out_header_line);
 | 
						|
            connp->out_header_line = NULL;
 | 
						|
 | 
						|
            // Cleanup for the next line
 | 
						|
            connp->out_line_len = 0;
 | 
						|
            if (connp->out_header_line_index == -1) {
 | 
						|
                connp->out_header_line_index = connp->out_header_line_counter;
 | 
						|
            }
 | 
						|
 | 
						|
            connp->out_header_line_counter++;
 | 
						|
        }
 | 
						|
    }
 | 
						|
}
 | 
						|
 | 
						|
/**
 | 
						|
 * Parses response line.
 | 
						|
 *
 | 
						|
 * @param connp
 | 
						|
 * @returns HTP_OK on state change, HTTP_ERROR on error, or HTP_DATA when more data is needed.
 | 
						|
 */
 | 
						|
int htp_connp_RES_LINE(htp_connp_t *connp) {
 | 
						|
    for (;;) {
 | 
						|
        // Get one byte
 | 
						|
        OUT_COPY_BYTE_OR_RETURN(connp);
 | 
						|
 | 
						|
        // Have we reached the end of the line?
 | 
						|
        if (connp->out_next_byte == LF) {
 | 
						|
            #ifdef HTP_DEBUG
 | 
						|
            fprint_raw_data(stderr, __FUNCTION__, connp->out_line, connp->out_line_len);
 | 
						|
            #endif
 | 
						|
 | 
						|
            // Is this a line that should be ignored?
 | 
						|
            if (htp_connp_is_line_ignorable(connp, connp->out_line, connp->out_line_len)) {
 | 
						|
                // We have an empty/whitespace line, which we'll note, ignore and move on
 | 
						|
                connp->out_tx->response_ignored_lines++;
 | 
						|
 | 
						|
                // TODO How many lines are we willing to accept?
 | 
						|
 | 
						|
                // Start again
 | 
						|
                connp->out_line_len = 0;
 | 
						|
 | 
						|
                return HTP_OK;
 | 
						|
            }
 | 
						|
 | 
						|
            // Process response line
 | 
						|
 | 
						|
            htp_chomp(connp->out_line, &connp->out_line_len);
 | 
						|
 | 
						|
            // Deallocate previous response line allocations, which we woud have on a 100 response
 | 
						|
            // TODO Consider moving elsewhere; no need to make these checks on every response
 | 
						|
            if (connp->out_tx->response_line != NULL) {
 | 
						|
                bstr_free(connp->out_tx->response_line);
 | 
						|
            }
 | 
						|
 | 
						|
            if (connp->out_tx->response_protocol != NULL) {
 | 
						|
                bstr_free(connp->out_tx->response_protocol);
 | 
						|
            }
 | 
						|
 | 
						|
            if (connp->out_tx->response_status != NULL) {
 | 
						|
                bstr_free(connp->out_tx->response_status);
 | 
						|
            }
 | 
						|
 | 
						|
            if (connp->out_tx->response_message != NULL) {
 | 
						|
                bstr_free(connp->out_tx->response_message);
 | 
						|
            }
 | 
						|
 | 
						|
            connp->out_tx->response_line = bstr_memdup((char *) connp->out_line, connp->out_line_len);
 | 
						|
 | 
						|
            // Parse response line
 | 
						|
            if (connp->cfg->parse_response_line(connp) != HTP_OK) {
 | 
						|
                // Note: downstream responsible for error logging
 | 
						|
                return HTP_ERROR;
 | 
						|
            }
 | 
						|
 | 
						|
            // Run hook RESPONSE_LINE
 | 
						|
            int rc = hook_run_all(connp->cfg->hook_response_line, connp);
 | 
						|
            if (rc != HOOK_OK) {
 | 
						|
                htp_log(connp, HTP_LOG_MARK, HTP_LOG_ERROR, 0,
 | 
						|
                    "Response line callback returned error (%d)", rc);
 | 
						|
                return HTP_ERROR;
 | 
						|
            }
 | 
						|
 | 
						|
            // Clean up.
 | 
						|
            connp->out_line_len = 0;
 | 
						|
 | 
						|
            // Move on to the next phase.
 | 
						|
            connp->out_state = htp_connp_RES_HEADERS;
 | 
						|
            connp->out_tx->progress = TX_PROGRESS_RES_HEADERS;
 | 
						|
 | 
						|
            return HTP_OK;
 | 
						|
        }
 | 
						|
    }
 | 
						|
}
 | 
						|
 | 
						|
size_t htp_connp_res_data_consumed(htp_connp_t *connp) {
 | 
						|
    return connp->out_current_offset;
 | 
						|
}
 | 
						|
 | 
						|
/**
 | 
						|
 * The response idle state will initialize response processing, as well as
 | 
						|
 * finalize each transactions after we are done with it.
 | 
						|
 *
 | 
						|
 * @param connp
 | 
						|
 * @returns HTP_OK on state change, HTTP_ERROR on error, or HTP_DATA when more data is needed.
 | 
						|
 */
 | 
						|
int htp_connp_RES_IDLE(htp_connp_t * connp) {
 | 
						|
    // If we're here and an outgoing transaction object exists that
 | 
						|
    // means we've just completed parsing a response. We need
 | 
						|
    // to run the final hook in a transaction and start over.
 | 
						|
    if (connp->out_tx != NULL) {
 | 
						|
        // Shut down the decompressor, if we've used one
 | 
						|
        if (connp->out_decompressor != NULL) {
 | 
						|
            connp->out_decompressor->destroy(connp->out_decompressor);
 | 
						|
            connp->out_decompressor = NULL;
 | 
						|
        }
 | 
						|
 | 
						|
        connp->out_tx->progress = TX_PROGRESS_DONE;
 | 
						|
 | 
						|
        // Run hook RESPONSE
 | 
						|
        int rc = hook_run_all(connp->cfg->hook_response, connp);
 | 
						|
        if (rc != HTP_OK) {
 | 
						|
            htp_log(connp, HTP_LOG_MARK, HTP_LOG_ERROR, 0,
 | 
						|
                "Response callback returned error (%d)", rc);
 | 
						|
            return HTP_ERROR;
 | 
						|
        }
 | 
						|
 | 
						|
        // Check if the inbound parser is waiting on us. If it is that means that
 | 
						|
        // there might be request data that the inbound parser hasn't consumed yet.
 | 
						|
        // If we don't stop parsing we might encounter a response without a
 | 
						|
        // request.
 | 
						|
        if ((connp->in_status == STREAM_STATE_DATA_OTHER) && (connp->in_tx == connp->out_tx)) {
 | 
						|
            connp->out_tx = NULL;
 | 
						|
            return HTP_DATA_OTHER;
 | 
						|
        }
 | 
						|
 | 
						|
        // Start afresh
 | 
						|
        connp->out_tx = NULL;
 | 
						|
    }
 | 
						|
 | 
						|
    // We want to start parsing the next response (and change
 | 
						|
    // the state from IDLE) only if there's at least one
 | 
						|
    // byte of data available. Otherwise we could be creating
 | 
						|
    // new structures even if there's no more data on the
 | 
						|
    // connection.
 | 
						|
    OUT_TEST_NEXT_BYTE_OR_RETURN(connp);
 | 
						|
 | 
						|
    // Parsing a new response
 | 
						|
 | 
						|
    // Find the next outgoing transaction
 | 
						|
    connp->out_tx = list_get(connp->conn->transactions, connp->out_next_tx_index);
 | 
						|
    if (connp->out_tx == NULL) {
 | 
						|
        htp_log(connp, HTP_LOG_MARK, HTP_LOG_ERROR, 0,
 | 
						|
            "Unable to match response to request");
 | 
						|
        return HTP_ERROR;
 | 
						|
    }
 | 
						|
 | 
						|
    // We've used one transaction
 | 
						|
    connp->out_next_tx_index++;
 | 
						|
 | 
						|
    // TODO Detect state mismatch
 | 
						|
 | 
						|
    connp->out_content_length = -1;
 | 
						|
    connp->out_body_data_left = -1;
 | 
						|
    connp->out_header_line_index = -1;
 | 
						|
    connp->out_header_line_counter = 0;
 | 
						|
 | 
						|
    // Change state into response line parsing, except if we're following
 | 
						|
    // a short HTTP/0.9 request, because such requests to not have a
 | 
						|
    // response line and headers.
 | 
						|
    if (connp->out_tx->protocol_is_simple) {
 | 
						|
        connp->out_tx->response_transfer_coding = IDENTITY;
 | 
						|
        connp->out_state = htp_connp_RES_BODY_IDENTITY;
 | 
						|
        connp->out_tx->progress = TX_PROGRESS_RES_BODY;
 | 
						|
    } else {
 | 
						|
        connp->out_state = htp_connp_RES_LINE;
 | 
						|
        connp->out_tx->progress = TX_PROGRESS_RES_LINE;
 | 
						|
    }
 | 
						|
 | 
						|
    return HTP_OK;
 | 
						|
}
 | 
						|
 | 
						|
/**
 | 
						|
 * Process a chunk of outbound (server or response) data.
 | 
						|
 *
 | 
						|
 * @param connp
 | 
						|
 * @param timestamp
 | 
						|
 * @param data
 | 
						|
 * @param len
 | 
						|
 * @return HTP_OK on state change, HTTP_ERROR on error, or HTP_DATA when more data is needed
 | 
						|
 */
 | 
						|
int htp_connp_res_data(htp_connp_t *connp, htp_time_t timestamp, unsigned char *data, size_t len) {
 | 
						|
    #ifdef HTP_DEBUG
 | 
						|
    fprintf(stderr, "htp_connp_res_data(connp->out_status %x)\n", connp->out_status);
 | 
						|
    fprint_raw_data(stderr, __FUNCTION__, data, len);
 | 
						|
    #endif
 | 
						|
 | 
						|
    // Return if the connection has had a fatal error
 | 
						|
    if (connp->out_status == STREAM_STATE_ERROR) {
 | 
						|
        htp_log(connp, HTP_LOG_MARK, HTP_LOG_ERROR, 0, "Outbound parser is in STREAM_STATE_ERROR");
 | 
						|
 | 
						|
        #ifdef HTP_DEBUG
 | 
						|
        fprintf(stderr, "htp_connp_res_data: returning STREAM_STATE_DATA (previous error)\n");
 | 
						|
        #endif
 | 
						|
        return STREAM_STATE_ERROR;
 | 
						|
    }
 | 
						|
 | 
						|
    // If the length of the supplied data chunk is zero, proceed
 | 
						|
    // only if the stream has been closed. We do not allow zero-sized
 | 
						|
    // chunks in the API, but we use it internally to force the parsers
 | 
						|
    // to finalize parsing.
 | 
						|
    if ((len == 0) && (connp->out_status != STREAM_STATE_CLOSED)) {
 | 
						|
        htp_log(connp, HTP_LOG_MARK, HTP_LOG_ERROR, 0, "Zero-length data chunks are not allowed");
 | 
						|
 | 
						|
        #ifdef HTP_DEBUG
 | 
						|
        fprintf(stderr, "htp_connp_res_data: returning STREAM_STATE_DATA (zero-length chunk)\n");
 | 
						|
        #endif
 | 
						|
        return STREAM_STATE_ERROR;
 | 
						|
    }
 | 
						|
 | 
						|
    // Store the current chunk information
 | 
						|
    connp->out_timestamp = timestamp;
 | 
						|
    connp->out_current_data = data;
 | 
						|
    connp->out_current_len = len;
 | 
						|
    connp->out_current_offset = 0;
 | 
						|
    connp->conn->out_data_counter += len;
 | 
						|
    connp->conn->out_packet_counter++;
 | 
						|
 | 
						|
    // Return without processing any data if the stream is in tunneling
 | 
						|
    // mode (which it would be after an initial CONNECT transaction.
 | 
						|
    if (connp->out_status == STREAM_STATE_TUNNEL) {
 | 
						|
        #ifdef HTP_DEBUG
 | 
						|
        fprintf(stderr, "htp_connp_res_data: returning STREAM_STATE_DATA (tunnel)\n");
 | 
						|
        #endif
 | 
						|
        return STREAM_STATE_DATA;
 | 
						|
    }
 | 
						|
 | 
						|
    // Invoke a processor, in a loop, until an error
 | 
						|
    // occurs or until we run out of data. Many processors
 | 
						|
    // will process a request, each pointing to the next
 | 
						|
    // processor that needs to run.
 | 
						|
    for (;;) {
 | 
						|
        #ifdef HTP_DEBUG
 | 
						|
        fprintf(stderr, "htp_connp_res_data: out state=%s, progress=%s\n",
 | 
						|
            htp_connp_out_state_as_string(connp),
 | 
						|
            htp_tx_progress_as_string(connp->out_tx));
 | 
						|
        #endif
 | 
						|
        // Return if there's been an error
 | 
						|
        // or if we've run out of data. We are relying
 | 
						|
        // on processors to add error messages, so we'll
 | 
						|
        // keep quiet here.
 | 
						|
        int rc = connp->out_state(connp);
 | 
						|
        if (rc != HTP_OK) {
 | 
						|
            // Do we need more data?
 | 
						|
            if (rc == HTP_DATA) {
 | 
						|
                return STREAM_STATE_DATA;
 | 
						|
            }
 | 
						|
 | 
						|
            // Check for suspended parsing
 | 
						|
            if (rc == HTP_DATA_OTHER) {
 | 
						|
                // We might have actually consumed the entire data chunk?
 | 
						|
                if (connp->out_current_offset >= connp->out_current_len) {
 | 
						|
                    // Do not send STREAM_DATE_DATA_OTHER if we've
 | 
						|
                    // consumed the entire chunk
 | 
						|
                    #ifdef HTP_DEBUG
 | 
						|
                    fprintf(stderr, "htp_connp_res_data: returning STREAM_STATE_DATA (suspended parsing)\n");
 | 
						|
                    #endif
 | 
						|
                    return STREAM_STATE_DATA;
 | 
						|
                } else {
 | 
						|
                    // Partial chunk consumption
 | 
						|
                    #ifdef HTP_DEBUG
 | 
						|
                    fprintf(stderr, "htp_connp_req_data: returning STREAM_STATE_DATA_OTHER\n");
 | 
						|
                    #endif
 | 
						|
                    return STREAM_STATE_DATA_OTHER;
 | 
						|
                }
 | 
						|
            }
 | 
						|
 | 
						|
            // Remember that we've had an error. Errors are
 | 
						|
            // not possible to recover from.
 | 
						|
            connp->out_status = STREAM_STATE_ERROR;
 | 
						|
 | 
						|
            return STREAM_STATE_ERROR;
 | 
						|
        }
 | 
						|
    }
 | 
						|
}
 | 
						|
 |