static INLINE int hi_server_extract_status_code(HI_SESSION *Session, const u_char *start, const u_char *ptr, const u_char *end, URI_PTR *result) { int iRet = HI_SUCCESS; SkipBlankSpace(start,end,&ptr); result->uri = ptr; while( hi_util_in_bounds(start, end, ptr) ) { if(isdigit((int)*ptr)) { SkipDigits(start, end, &ptr); if ( hi_util_in_bounds(start, end, ptr) ) { if(isspace((int)*ptr)) { result->uri_end = ptr; iRet = STAT_END; return iRet; } else { iRet = HI_NONFATAL_ERR; return iRet; } } else { iRet = HI_OUT_OF_BOUNDS; return iRet; } } else { if(hi_eo_generate_event(Session, HI_EO_SERVER_INVALID_STATCODE)) { hi_eo_server_event_log(Session, HI_EO_SERVER_INVALID_STATCODE, NULL, NULL); } ptr++; } } iRet = HI_OUT_OF_BOUNDS; return iRet; }
/** ** Set the ub_ptr and update the URI_NORM_STATE. ** ** The main point of this function is to take care of the details in ** updating the directory stack and setting the buffer pointer to the ** last directory. ** ** @param norm_state pointer to the normalization state struct ** @param ub_ptr double pointer to the normalized buffer ** ** @return integer ** ** @retval HI_SUCCESS function successful ** ** @see hi_norm_uri() */ static int DirTrav(HI_SESSION *Session, URI_NORM_STATE *norm_state, u_char *ub_start,u_char **ub_ptr) { HTTPINSPECT_CONF *ServerConf = Session->server_conf; hi_stats.dir_trav++; if(norm_state->dir_count) { *ub_ptr = norm_state->dir_track[norm_state->dir_count - 1]; /* ** Check to make sure that we aren't at the beginning */ if(norm_state->dir_count >= 1) { norm_state->dir_count--; } } else { /* ** This is a special case where there was no / seen before ** we see a /../. When this happens, we just reset the ub_ptr ** back to the beginning of the norm buffer and let the slash ** get written on the next iteration of the loop. */ *ub_ptr = ub_start; /* ** Let's put the alert here for webroot dir traversal. */ if(hi_eo_generate_event(Session, ServerConf->webroot.alert) && !norm_state->param) { hi_eo_client_event_log(Session, HI_EO_CLIENT_WEBROOT_DIR, NULL, NULL); } } return HI_SUCCESS; }
/** ** This is the final GetByte routine. The value that is returned from this ** routine is the final decoded byte, and normalization can begin. This ** routine handles the double phase of decoding that IIS is fond of. ** ** So to recap all the decoding up until this point. ** ** The first phase is to call GetByte(). GetByte() returns the first stage ** of decoding, which handles the UTF-8 decoding. If we have decoded a ** % of some type, then we head into DoubleDecode() if the ServerConf ** allows it. ** ** What returns from DoubleDecode is the final result. ** ** @param ServerConf the server configuration ** @param start the start of the URI ** @param end the end of the URI ** @param ptr the current pointer into the URI ** @param norm_state the pointer to the URI norm state ** ** @return integer ** ** @retval END_OF_BUFFER While decoding, the end of buffer was reached. ** @retval char The resultant decoded char. ** ** @see DoubleDecode(); ** @see GetByte(); */ static int GetDecodedByte(HI_SESSION *Session, const u_char *start, const u_char *end, const u_char **ptr, URI_NORM_STATE *norm_state, uint16_t *encodeType) { HTTPINSPECT_CONF *ServerConf = Session->server_conf; int iChar; iChar = GetByte(Session,start,end,ptr, norm_state,encodeType); if(iChar == END_OF_BUFFER) return END_OF_BUFFER; if(ServerConf->double_decoding.on && (u_char)iChar == '%') { iChar = DoubleDecode(Session,start,end,ptr,norm_state,encodeType); } /* ** Let's change '\' to '/' if possible */ if(ServerConf->iis_backslash.on && (u_char)iChar == 0x5c) { if(hi_eo_generate_event(Session, ServerConf->iis_backslash.alert) && !norm_state->param) { hi_eo_client_event_log(Session, HI_EO_CLIENT_IIS_BACKSLASH, NULL, NULL); } iChar = 0x2f; } if( (u_char)iChar == '+') { iChar = 0x20; } return iChar; }
/** ** We check the directory length against the global config. ** ** @param Session pointer to the current session ** @param uri_ptr pointer to the URI state ** @param ptr pointer to the current index in buffer ** ** @return integer ** ** @retval HI_SUCCESS */ static INLINE int CheckLongDir(HI_SESSION *Session, URI_PTR *uri_ptr, u_char *ptr) { int iDirLen; /* ** Check for oversize directory */ if(Session->server_conf->long_dir && uri_ptr->last_dir && !uri_ptr->param) { iDirLen = ptr - uri_ptr->last_dir; if(iDirLen > Session->server_conf->long_dir && hi_eo_generate_event(Session, HI_EO_CLIENT_OVERSIZE_DIR)) { hi_eo_client_event_log(Session, HI_EO_CLIENT_OVERSIZE_DIR, NULL, NULL); } } return HI_SUCCESS; }
/** ** This function inspects the normalized chars for any other processing ** that we need to do, such as directory traversals. ** ** The main things that we check for here are '/' and '?'. There reason ** for '/' is that we do directory traversals. If it's a slash, we call ** the routine that will normalize mutli-slashes, self-referential dirs, ** and dir traversals. We do all that processing here and call the ** appropriate functions. ** ** The '?' is so we can mark the parameter field, and check for oversize ** directories one last time. Once the parameter field is set, we don't ** do any more oversize directory checks since we aren't in the url ** any more. ** ** @param Session pointer to the current session ** @param iChar the char to inspect ** @param norm_state the normalization state ** @param start the start of the URI buffer ** @param end the end of the URI buffer ** @param ptr the address of the pointer index into the URI buffer ** @param ub_start the start of the norm buffer ** @param ub_end the end of the norm buffer ** @param ub_ptr the address of the pointer index into the norm buffer ** ** @return integer ** ** @retval END_OF_BUFFER we've reached the end of the URI or norm buffer ** @retval HI_NONFATAL_ERR no special char, so just write the char and ** increment the ub_ptr. ** @retval HI_SUCCESS normalized the special char and already ** incremented the buffers. */ static inline int InspectUriChar(HI_SESSION *Session, int iChar, URI_NORM_STATE *norm_state, const u_char *start, const u_char *end, const u_char **ptr, u_char *ub_start, u_char *ub_end, u_char **ub_ptr, uint16_t *encodeType) { HTTPINSPECT_CONF *ServerConf = Session->server_conf; int iDir; /* ** Let's add absolute URI/proxy support everyone. */ if(!norm_state->dir_count && (u_char)iChar == ':' && hi_util_in_bounds(start, end, ((*ptr)+2))) { if(**ptr == '/' && *((*ptr)+1) == '/') { /* ** We've found absolute vodka. */ if(!hi_util_in_bounds(ub_start, ub_end, ((*ub_ptr)+2))) return END_OF_BUFFER; /* ** Write the : */ **ub_ptr = (u_char)iChar; (*ub_ptr)++; /* ** This increments us past the first slash, so at the next ** slash we will track a directory. ** ** The reason we do this is so that an attacker can't trick ** us into normalizing a directory away that ended in a :. ** For instance, if we got a URL that was separated in by a ** packet boundary like this, and we were looking for the ** URL real_dir:/file.html: ** real_dir://obfuscate_dir/../file.html ** we would normalize it with proxy support to: ** /file.html ** because we never tracked the :// as a valid directory. So ** even though this isn't the best solution, it is the best ** we can do given that we are working with stateless ** inspection. */ (*ptr)++; return HI_SUCCESS; } } /* ** Now that we have the "true" byte, we check this byte for other ** types of normalization: ** - directory traversals ** - multiple slashes */ if((u_char)iChar == '/') { /* ** First thing we do is check for a long directory. */ CheckLongDir(Session, norm_state, *ub_ptr); iDir = DirNorm(Session, start, end, ptr, norm_state, encodeType); if(iDir == DIR_TRAV) { /* ** This is the case where we have a directory traversal. ** ** The DirTrav function will reset the ub_ptr to the previous ** slash. After that, we just continue through the loop because ** DirNorm has already set ptr to the slash, so we can just ** continue on. */ DirTrav(Session,norm_state, ub_start, ub_ptr); } else { /* ** This is the case where we didn't have a directory traversal, ** and we are now just writing the char after the '/'. ** ** We call DirSet, because all this function does is write a ** '/' into the buffer and increment the ub_ptr. We then ** check the return code and return END_OF_BUFFER if ** needed. */ DirSet(norm_state, ub_ptr); if(iDir == END_OF_BUFFER) return END_OF_BUFFER; /* ** We check the bounds before we write the next byte */ if(!hi_util_in_bounds(ub_start, ub_end, *ub_ptr)) return END_OF_BUFFER; /* ** Set the char to what we got in DirNorm() */ /* ** Look for user-defined Non-Rfc chars. If we find them ** then log an alert. */ if(ServerConf->non_rfc_chars[(u_char)iDir]) { if(hi_eo_generate_event(Session, HI_EO_CLIENT_NON_RFC_CHAR) && !norm_state->param) { hi_eo_client_event_log(Session, HI_EO_CLIENT_NON_RFC_CHAR, NULL, NULL); } } **ub_ptr = (u_char)iDir; (*ub_ptr)++; } return HI_SUCCESS; } if((!byte_decoded && (u_char)iChar == '?')) { /* ** We assume that this is the beginning of the parameter field, ** and check for a long directory following. Event though seeing ** a question mark does not guarantee the parameter field, thanks ** IIS. */ CheckLongDir(Session, norm_state, *ub_ptr); norm_state->param = *ub_ptr; } /* ** This is neither char, so we just bail and let the loop finish ** for us. */ return HI_NONFATAL_ERR; }
/** ** The double decoding routine for IIS good times. ** ** Coming into this function means that we just decoded a % or that ** we just saw two percents in a row. We know which state we are ** in depending if the first char is a '%' or not. ** ** In the IIS world, there are two decodes, but only some of the decode ** options are valid. All options are valid in the first decode ** stage, but the second decode stage only supports: ** - %u encoding ** - ascii ** ** Knowing this, we can decode appropriately. ** ** @param ServerConf the server configuration ** @param start the start of the URI ** @param end the end of the URI ** @param ptr the current pointer into the URI ** @param norm_state the ptr to the URI norm state ** ** @return integer ** ** @retval NON_ASCII_CHAR End of buffer reached while decoding ** @retval char The decoded char */ static int DoubleDecode(HI_SESSION *Session, const u_char *start, const u_char *end, const u_char **ptr, URI_NORM_STATE *norm_state, uint16_t *encodeType) { HTTPINSPECT_CONF *ServerConf = Session->server_conf; int iByte; int iNorm; *encodeType |= HTTP_ENCODE_TYPE__DOUBLE_ENCODE; /* ** We now know that we have seen a previous % and that we need to ** decode the remaining bytes. We are in one of multiple cases: ** ** - %25xxxx ** - %%xx%xx ** - %u0025xxxx ** - etc. ** ** But, the one common factor is that they each started out with a ** % encoding of some type. ** ** So now we just get the remaining bytes and do the processing ** ourselves in this routine. */ iByte = GetByte(Session, start, end, ptr, norm_state, encodeType); if(iByte == END_OF_BUFFER) return NON_ASCII_CHAR; if(valid_lookup[(u_char)iByte] < 0) { if(ServerConf->u_encoding.on && (toupper(iByte) == 'U')) { iNorm = UDecode(Session, start, end, ptr, GetByte, norm_state, encodeType); if(iNorm == END_OF_BUFFER) { /* ** We have reached the end of the buffer while ** processing a U encoding. We keep the current ** pointer and return a NON_ASCII char for the ** bad encoding. */ return NON_ASCII_CHAR; } return iNorm; } return iByte; } iNorm = (hex_lookup[(u_char)iByte]<<4); iByte = GetByte(Session, start, end, ptr, norm_state, encodeType); if(iByte == END_OF_BUFFER) return NON_ASCII_CHAR; if(valid_lookup[(u_char)iByte] < 0) { return iByte; } iNorm = (iNorm | (hex_lookup[(u_char)iByte])) & 0xff; if(hi_eo_generate_event(Session, ServerConf->double_decoding.alert) && (norm_state->param == NULL)) { hi_eo_client_event_log(Session, HI_EO_CLIENT_DOUBLE_DECODE, NULL, NULL); } byte_decoded = true; return iNorm; }
/* ** Decode the UTF-8 sequences and check for valid codepoints via the ** Unicode standard and the IIS standard. ** ** We decode up to 3 bytes of UTF-8 because that's all I've been able to ** get to work on various servers, so let's reduce some false positives. ** So we decode valid UTF-8 sequences and then check the value. If the ** value is ASCII, then it's decoded to that. Otherwise, if iis_unicode ** is turned on, we will check the unicode codemap for valid IIS mappings. ** If a mapping turns up, then we return the mapped ASCII. ** ** @param ServerConf the server configuration ** @param start the start of the URI ** @param end the end of the URI ** @param ptr the current pointer into the URI ** ** @return integer ** ** @retval NON_ASCII_CHAR Reached end of buffer while decoding ** @retval char return the decoded or badly decoded char ** ** @see GetByte() ** @see UnicodeDecode() */ static int UTF8Decode(HI_SESSION *Session, const u_char *start, const u_char *end, const u_char **ptr, int iFirst, URI_NORM_STATE *norm_state, uint16_t *encodeType) { HTTPINSPECT_CONF *ServerConf = Session->server_conf; int iBareByte; int iNorm; int iNumBytes; int iCtr; int iByte; /* ** Right now we support up to 3 byte unicode sequences. We can add ** more if any of the HTTP servers support more. */ if((iFirst & 0xe0) == 0xc0) { iNumBytes = 1; iNorm = iFirst & 0x1f; } else if((iFirst & 0xf0) == 0xe0) { iNumBytes = 2; iNorm = iFirst & 0x0f; } else { *encodeType |= HTTP_ENCODE_TYPE__NONASCII; hi_stats.non_ascii++; /* ** This means that we have an invalid first sequence byte for ** a unicode sequence. So we just return the byte and move on. */ return iFirst; } /* ** This is the main loop for UTF-8 decoding. We check for the only ** valid sequence after the first byte whish is 0x80. Otherwise, ** it was invalid and we setnd a NON_ASCII_CHAR and continue on ** with our processing. */ for(iCtr = 0; iCtr < iNumBytes; iCtr++) { iByte = GetChar(Session, start, end, ptr, &iBareByte, norm_state, encodeType); if(iByte == END_OF_BUFFER || iByte == NON_ASCII_CHAR || iBareByte) return NON_ASCII_CHAR; if((iByte & 0xc0) == 0x80) { iNorm <<= 6; iNorm |= (iByte & 0x3f); } else { *encodeType |= HTTP_ENCODE_TYPE__NONASCII; hi_stats.non_ascii++; /* ** This means that we don't have a valid unicode sequence, so ** we just bail. */ return NON_ASCII_CHAR; } } /* ** Check for unicode as ASCII and if there is not an ASCII char then ** we return the space holder char. */ if(iNorm > 0x7f) { if(ServerConf->iis_unicode.on) { iNorm = ServerConf->iis_unicode_map[iNorm]; if(iNorm == HI_UI_NON_ASCII_CODEPOINT) { iNorm = NON_ASCII_CHAR; } if(hi_eo_generate_event(Session, ServerConf->iis_unicode.alert) && !norm_state->param) { hi_eo_client_event_log(Session, HI_EO_CLIENT_IIS_UNICODE, NULL, NULL); } *encodeType |= HTTP_ENCODE_TYPE__IIS_UNICODE; hi_stats.unicode++; return iNorm; } else { *encodeType |= HTTP_ENCODE_TYPE__NONASCII; hi_stats.non_ascii++; iNorm = NON_ASCII_CHAR; } } *encodeType |= HTTP_ENCODE_TYPE__UTF8_UNICODE; if(hi_eo_generate_event(Session, ServerConf->utf_8.alert) && !norm_state->param) { hi_eo_client_event_log(Session, HI_EO_CLIENT_UTF_8, NULL, NULL); } return iNorm; }
/** ** Wrapper for PercentDecode() and handles the return values from ** PercentDecode(). ** ** This really decodes the chars for UnicodeDecode(). If the char is ** a percent then we process stuff, otherwise we just increment the ** pointer and return. ** ** @param ServerConf the server configuration ** @param start the start of the URI ** @param end the end of the URI ** @param ptr the current pointer into the URI ** @param bare_byte value for a non-ASCII char or a decoded non-ASCII char ** ** @return integer ** ** @retval END_OF_BUFFER End of the buffer has been reached before decode. ** @retval NON_ASCII_CHAR End of buffer during decoding, return decoded char. ** @retval char return the valid decoded/undecoded char ** ** @see PercentDecode() ** @see GetByte() */ static int GetChar(HI_SESSION *Session, const u_char *start, const u_char *end, const u_char **ptr, int *bare_byte, URI_NORM_STATE *norm_state, uint16_t *encodeType) { HTTPINSPECT_CONF *ServerConf = Session->server_conf; int iNorm; if(!hi_util_in_bounds(start, end, *ptr)) return END_OF_BUFFER; iNorm = (int)(**ptr); if(**ptr == '%' && ServerConf->ascii.on) { /* ** We go into percent encoding. */ iNorm = PercentDecode(Session, start, end, ptr, norm_state, encodeType); /* ** If during the course of PercentDecode() we run into the end ** of the buffer, then we return early (WITHOUT INCREMENTING ptr) ** with a NON_ASCII_CHAR. */ if(iNorm == END_OF_BUFFER) return NON_ASCII_CHAR; *bare_byte = 0; } else { if(ServerConf->bare_byte.on && (u_char)iNorm > 0x7f) { *encodeType |= HTTP_ENCODE_TYPE__BARE_BYTE; if(hi_eo_generate_event(Session, ServerConf->bare_byte.alert) && !norm_state->param) { hi_eo_client_event_log(Session, HI_EO_CLIENT_BARE_BYTE, NULL, NULL); } /* ** Set the bare_byte flag */ *bare_byte = 0; } else { /* ** Set the bare_byte flag negative. */ *bare_byte = 1; } } /* ** Increment the buffer. */ (*ptr)++; return iNorm; }
/** ** This is the first level of decoding, and deals with ASCII, U, and ** double decoding. ** ** This function is the main decoding function. It handles all the ASCII ** encoding and the U encoding, and tells us when there is a double ** encoding. ** ** We use the GetPtr() routine to get the bytes for us. This routine ** checks for DOUBLE_ENCODING and tells us about it if it finds something, ** so we can reset the ptrs and run it through the double decoding ** routine. ** ** The philosophy behind this routine is that if we run out of buffer ** we return such, the only other thing we return besides the decodes ** char is a NON_ASCII_CHAR in the case that we try and decode something ** like %tt. This is no good, so we return a place holder. ** ** @param ServerConf the server configuration ** @param start the start of the URI ** @param end the end of the URI ** @param ptr the current pointer into the URI ** ** @return integer ** ** @retval END_OF_BUFFER We've hit the end of buffer while decoding. ** @retval NON_ASCII_CHAR Invalid hex encoding, so we return a placeholder. ** @retval char return the valid char ** ** @see GetPtr() */ static int PercentDecode(HI_SESSION *Session, const u_char *start, const u_char *end, const u_char **ptr, URI_NORM_STATE *norm_state, uint16_t *encodeType) { HTTPINSPECT_CONF *ServerConf = Session->server_conf; int iByte; const u_char *orig_ptr; int iNorm; orig_ptr = *ptr; iByte = GetPtr(Session, start, end, ptr, norm_state, encodeType); if(iByte & GET_ERR) { if(iByte == END_OF_BUFFER) return END_OF_BUFFER; if(iByte == DOUBLE_ENCODING) { *ptr = orig_ptr; return (int)**ptr; } } /* ** Initialize the normalization byte */ iNorm = 0; /* ** hex values */ if(valid_lookup[(u_char)iByte] < 0) { /* ** Check for %u encoding. ** ** The u-encoding loop always returns something. */ if(ServerConf->u_encoding.on && (toupper(iByte) == 'U')) { iNorm = UDecode(Session, start, end, ptr, GetPtr, norm_state, encodeType); /* ** We have to handle the double meaning of END_OF_BUFFER ** when using the GetPtr() function. */ if(iNorm & GET_ERR) { if(iNorm == END_OF_BUFFER) { /* ** We have reached the end of the buffer while ** processing a U encoding. */ return END_OF_BUFFER; } if(iNorm == DOUBLE_ENCODING) { *encodeType |= HTTP_ENCODE_TYPE__DOUBLE_ENCODE; *ptr = orig_ptr; return (int)**ptr; } } return iNorm; } else { *encodeType |= HTTP_ENCODE_TYPE__NONASCII; hi_stats.non_ascii++; return NON_ASCII_CHAR; } } iNorm = (hex_lookup[(u_char)iByte]<<4); iByte = GetPtr(Session, start, end, ptr, norm_state, encodeType); if(iByte & GET_ERR) { if(iByte == END_OF_BUFFER) return END_OF_BUFFER; if(iByte == DOUBLE_ENCODING) { *ptr = orig_ptr; return (int)**ptr; } } if(valid_lookup[(u_char)iByte] < 0) { *encodeType |= HTTP_ENCODE_TYPE__NONASCII; hi_stats.non_ascii++; return NON_ASCII_CHAR; } iNorm = (iNorm | (hex_lookup[(u_char)iByte])) & 0xff; *encodeType |= HTTP_ENCODE_TYPE__ASCII; byte_decoded = true; if(hi_eo_generate_event(Session,ServerConf->ascii.alert) && !norm_state->param) { hi_eo_client_event_log(Session, HI_EO_CLIENT_ASCII, NULL, NULL); } return iNorm; }
/** ** Handles the single decode for %U encoding. ** ** This routine receives the ptr pointing to the u. We check the bounds ** and continue with processing. %u encoding works by specifying the ** exact codepoint to be used. For example, %u002f would be /. So this ** all seems fine. BUT, the problem is that IIS maps multiple codepoints ** to ASCII characters. So, %u2044 also maps to /. So this is what we ** need to handle here. ** ** This routine only handles the single encoding. For double decoding, ** %u is handled in DoubleDecode(). It's the same routine, with just ** the GetByte function different. ** ** We use a get_byte function to get the bytes, so we can use this ** routine for PercentDecode and for DoubleDecode. ** ** @param ServerConf the server configuration ** @param start the start of the URI ** @param end the end of the URI ** @param ptr the current pointer into the URI ** @param get_byte the function pointer to get bytes. ** ** @return integer ** ** @retval END_OF_BUFFER we are at the end of the buffer ** @retval DOUBLE_ENCODING this U encoding is possible double encoded ** @retval NON_ASCII_CHAR return this char for non-ascii or bad decodes ** @retval iChar this is the char that we decoded. */ static int UDecode(HI_SESSION *Session, const u_char *start, const u_char *end, const u_char **ptr, DECODE_FUNC get_byte, URI_NORM_STATE *norm_state, uint16_t *encodeType) { HTTPINSPECT_CONF *ServerConf = Session->server_conf; int iByte; int iNorm; int iCtr; iNorm = 0; *encodeType |= HTTP_ENCODE_TYPE__UENCODE; hi_stats.unicode++; for(iCtr = 0; iCtr < 4; iCtr++) { iByte = get_byte(Session, start, end, ptr, norm_state, encodeType); if(iByte & GET_ERR) return iByte; if(valid_lookup[(u_char)iByte] < 0) { *encodeType |= HTTP_ENCODE_TYPE__NONASCII; hi_stats.non_ascii++; return NON_ASCII_CHAR; } iNorm <<= 4; iNorm = (iNorm | (hex_lookup[(u_char)iByte])); } /* ** If the decoded codepoint is greater than a single byte value, ** then we return a NON_ASCII_CHAR. */ if(iNorm > 0xff) { /* ** We check here for IIS codepoints that map to ASCII chars. */ if(ServerConf->iis_unicode.on && iNorm <= 0xffff) { iNorm = ServerConf->iis_unicode_map[iNorm]; if(iNorm == HI_UI_NON_ASCII_CODEPOINT) { *encodeType |= HTTP_ENCODE_TYPE__NONASCII; hi_stats.non_ascii++; iNorm = NON_ASCII_CHAR; } if(hi_eo_generate_event(Session, ServerConf->iis_unicode.alert) && !norm_state->param) { hi_eo_client_event_log(Session, HI_EO_CLIENT_IIS_UNICODE, NULL, NULL); } *encodeType |= HTTP_ENCODE_TYPE__IIS_UNICODE; } else { *encodeType |= HTTP_ENCODE_TYPE__NONASCII; hi_stats.non_ascii++; return NON_ASCII_CHAR; } } /* ** Check if we alert on this encoding */ if(hi_eo_generate_event(Session, ServerConf->u_encoding.alert) && !norm_state->param) { hi_eo_client_event_log(Session, HI_EO_CLIENT_U_ENCODE, NULL, NULL); } byte_decoded = true; return iNorm; }
/** ** Normalize the URI into the URI normalize buffer. ** ** This is the routine that users call to normalize the URI. It iterates ** through the URI buffer decoding the next character and is then checked ** for any directory problems before writing the decoded character into the ** normalizing buffer. ** ** We return the length of the normalized URI buffer in the variable, ** uribuf_size. This value is passed in as the max size of the normalization ** buffer, which we then set in iMaxUriBufSize for later reference. ** ** If there was some sort of problem during normalizing we set the normalized ** URI buffer size to 0 and return HI_NONFATAL_ERR. ** ** @param ServerConf the pointer to the server configuration ** @param uribuf the pointer to the normalize uri buffer ** @param uribuf_size the size of the normalize buffer ** @param uri the pointer to the unnormalized uri buffer ** @param uri_size the size of the unnormalized uri buffer ** ** @return integer ** ** @retval HI_NONFATAL_ERR there was a problem during normalizing, the ** uribuf_size is also set to 0 ** @retval HI_SUCCESS Normalizing the URI was successful */ int hi_norm_uri(HI_SESSION *Session, u_char *uribuf, int *uribuf_size, const u_char *uri, int uri_size, uint16_t *encodeType) { HTTPINSPECT_CONF *ServerConf; int iChar; int iRet; int iMaxUriBufSize; URI_NORM_STATE norm_state; u_char *ub_ptr; const u_char *ptr; const u_char *start; const u_char *end; u_char *ub_start; u_char *ub_end; ServerConf = Session->server_conf; iMaxUriBufSize = *uribuf_size; start = uri; end = uri + uri_size; ub_start = uribuf; ub_end = uribuf + iMaxUriBufSize; ub_ptr = uribuf; ptr = uri; /* ** Initialize the URI directory normalization state */ norm_state.dir_count = 0; norm_state.param = NULL; while(hi_util_in_bounds(ub_start, ub_end, ub_ptr)) { byte_decoded = false; iChar = GetDecodedByte(Session, start, end, &ptr, &norm_state, encodeType); if(iChar == END_OF_BUFFER) break; /* ** Look for user-defined Non-Rfc chars. If we find them ** then log an alert. */ if(ServerConf->non_rfc_chars[(u_char)iChar]) { if(hi_eo_generate_event(Session, HI_EO_CLIENT_NON_RFC_CHAR) && !norm_state.param) { hi_eo_client_event_log(Session, HI_EO_CLIENT_NON_RFC_CHAR, NULL, NULL); } } iRet = InspectUriChar(Session, iChar, &norm_state, start, end, &ptr, ub_start, ub_end, &ub_ptr, encodeType); if (iRet) { if(iRet == END_OF_BUFFER) break; /* ** This is the default case when we don't want anything to do with ** the char besides writing the value into the buffer. */ *ub_ptr = (u_char)iChar; ub_ptr++; } } /* ** Now that we are done, let's make sure that we didn't just have a ** single large directory, with the rest in the next packet. */ CheckLongDir(Session, &norm_state, ub_ptr); /* ** This means that we got to the end of the URI, so we set the length, ** check it, and move on. */ *uribuf_size = ub_ptr - ub_start; if(*uribuf_size > uri_size || *uribuf_size < 1) return HI_NONFATAL_ERR; return HI_SUCCESS; }
static INLINE int hi_server_extract_body( HI_SESSION *Session, HttpSessionData *sd, const u_char *ptr, const u_char *end, URI_PTR *result) { HTTPINSPECT_CONF *ServerConf; const u_char *start = ptr; int iRet = HI_SUCCESS; const u_char *post_end = end; int chunk_size = 0; int chunk_read = 0; int bytes_to_read = 0; ServerConf = Session->server_conf; switch(ServerConf->server_flow_depth) { case -1: result->uri = result->uri_end = NULL; return iRet; case 0: break; default: if(sd->resp_state.flow_depth_read < ServerConf->server_flow_depth) { bytes_to_read = ServerConf->server_flow_depth - sd->resp_state.flow_depth_read; if((end-ptr) > bytes_to_read ) { end = ptr + bytes_to_read; } sd->resp_state.flow_depth_read +=bytes_to_read; } else { result->uri = result->uri_end = NULL; return iRet; } } /* if( ServerConf->server_flow_depth && ((end - ptr) > ServerConf->server_flow_depth) ) { end = ptr + ServerConf->server_flow_depth; }*/ if (!(sd->resp_state.last_pkt_contlen)) { if( ServerConf->chunk_length ) { if(CheckChunkEncoding(Session, start, end, &post_end, (u_char *)DecodeBuffer.data, sizeof(DecodeBuffer.data), sd->resp_state.last_chunk_size, &chunk_size, &chunk_read ) == 1) { sd->resp_state.last_chunk_size = chunk_size; sd->resp_state.last_pkt_chunked = 1; result->uri = (u_char *)DecodeBuffer.data; result->uri_end = result->uri + chunk_read; return iRet; } else { if(!(sd->resp_state.last_pkt_chunked)) { if(hi_eo_generate_event(Session, HI_EO_SERVER_NO_CONTLEN)) { hi_eo_server_event_log(Session, HI_EO_SERVER_NO_CONTLEN, NULL, NULL); } } else { sd->resp_state.last_pkt_chunked = 0; sd->resp_state.last_chunk_size = 0; } result->uri = start; result->uri_end = end; } } else { result->uri = start; result->uri_end = end; return iRet; } } result->uri = start; result->uri_end = end; return STAT_END; }
/** ** The main function for dealing with multiple slashes, self-referential ** directories, and directory traversals. ** ** This routine does GetDecodedByte() while looking for directory foo. It's ** called every time that we see a slash in the main hi_norm_uri. Most of ** the time we just enter this loop, find a non-directory-foo char and ** return that char. hi_norm_uri() takes care of the directory state ** updating and so forth. ** ** But when we run into trouble with directories, this function takes care ** of that. We loop through multiple slashes until we get to the next ** directory. We also loop through self-referential directories until we ** get to the next directory. Then finally we deal with directory ** traversals. ** ** With directory traversals we do a kind of "look ahead". We verify that ** there is indeed a directory traversal, and then set the ptr back to ** the beginning of the '/', so when we iterate through hi_norm_uri() we ** catch it. ** ** The return value for this function is usually the character after ** the directory. When there was a directory traversal, it returns the ** value DIR_TRAV. And when END_OF_BUFFER is returned, it means that we've ** really hit the end of the buffer, or we were looping through multiple ** slashes and self-referential directories until the end of the URI ** buffer. ** ** @param ServerConf pointer to the Server configuration ** @param start pointer to the start of the URI buffer ** @param end pointer to the end of the URI buffer ** @param ptr pointer to the index in the URI buffer ** ** @return integer ** ** @retval END_OF_BUFFER we've reached the end of buffer ** @retval DIR_TRAV we found a directory traversal ** @retval char return the next char after the directory ** ** @see hi_norm_uri() ** @see GetDecodedByte() */ static int DirNorm(HI_SESSION *Session, const u_char *start, const u_char *end, const u_char **ptr, URI_NORM_STATE *norm_state, uint16_t *encodeType) { HTTPINSPECT_CONF *ServerConf = Session->server_conf; int iChar; int iDir; const u_char *orig_ptr; const u_char *dir_ptr; // save the directory path here to check for unicode attack while((iChar = GetDecodedByte(Session, start, end, ptr, norm_state, encodeType)) != END_OF_BUFFER) { orig_ptr = *ptr; /* ** This is kind of a short cut to get out of here as soon as we ** can. If the character is over 0x2f then we know that is can't ** be either the '.' or the '/', so we break and return the ** char. */ if((u_char)iChar < 0x30) { /* ** We check for multiple slashes. If we find multiple slashes ** then we just continue on until we find something interesting. */ if(ServerConf->multiple_slash.on && (u_char)iChar == '/') { hi_stats.slashes++; if(hi_eo_generate_event(Session, ServerConf->multiple_slash.alert) && !norm_state->param) { hi_eo_client_event_log(Session, HI_EO_CLIENT_MULTI_SLASH, NULL, NULL); } continue; } /* ** This is where we start looking for self-referential dirs ** and directory traversals. */ else if(ServerConf->directory.on && (u_char)iChar == '.' && !norm_state->param) { iDir = GetDecodedByte(Session,start,end,ptr,norm_state,encodeType); if(iDir != END_OF_BUFFER) { if((u_char)iDir == '.') { /* ** This sets the dir_ptr to the beginning of the ** byte that may be a dir. So if it is a slash, ** we can get back to that slash and continue ** processing. */ dir_ptr = *ptr; iDir = GetDecodedByte(Session,start,end,ptr,norm_state,encodeType); if(iDir != END_OF_BUFFER) { if((u_char)iDir == '/') { hi_stats.self_ref++; /* ** We found a real live directory traversal ** so we reset the pointer to before the ** '/' and finish up after the return. */ if(hi_eo_generate_event(Session, ServerConf->directory.alert) && !norm_state->param) { hi_eo_client_event_log(Session, HI_EO_CLIENT_DIR_TRAV, NULL, NULL); } *ptr = dir_ptr; return DIR_TRAV; } } *ptr = orig_ptr; return iChar; } else if((u_char)iDir == '/') { /* ** We got a self-referential directory traversal. ** ** Keep processing until we stop seeing self ** referential directories. */ if(hi_eo_generate_event(Session, ServerConf->directory.alert) && !norm_state->param) { hi_eo_client_event_log(Session, HI_EO_CLIENT_SELF_DIR_TRAV, NULL, NULL); } continue; } } /* ** This means that we saw '.' and then another char, so ** it was just a file/dir that started with a '.'. */ *ptr = orig_ptr; return iChar; } } /* ** This is where we write the chars after the slash */ return iChar; } return END_OF_BUFFER; }
/** ** Update the URI_PTR fields spaces, find the next non-white space char, ** and validate the HTTP version identifier after the spaces. ** ** This is the main part of the URI algorithm. This verifies that there ** isn't too many spaces in the data to be a URI, it checks that after the ** second space that there is an HTTP identifier or otherwise it's no good. ** Also, if we've found an identifier after the first whitespace, and ** find another whitespace, there is no URI. ** ** The uri and uri_end pointers are updated in this function depending ** on what space we are at, and if the space was followed by the HTTP ** identifier. (NOTE: the HTTP delimiter is no longer "HTTP/", but ** can also be "\r\n", "\n", or "\r". This is the defunct method, and ** we deal with it in the IsHttpVersion and delimiter functions.) ** ** @param ServerConf pointer to the server configuration ** @param start pointer to the start of payload ** @param end pointer to the end of the payload ** @param ptr pointer to the pointer of the current index ** @param uri_ptr pointer to the URI_PTR construct ** ** @return integer ** ** @retval HI_SUCCESS found the next non-whitespace ** @retval HI_OUT_OF_BOUNDS whitespace to the end of the buffer ** @retval URI_END delimiter found, end of URI ** @retval NO_URI */ static int NextNonWhiteSpace(HI_SESSION *Session, u_char *start, u_char *end, u_char **ptr, URI_PTR *uri_ptr) { HTTPINSPECT_CONF *ServerConf = Session->server_conf; u_char **start_sp; u_char **end_sp; /* ** Reset the identifier, because we've just seen another space. We ** should only see the identifier immediately after a space followed ** by a delimiter. */ if(uri_ptr->ident) { if(ServerConf->non_strict) { /* ** In non-strict mode it is ok to see spaces after the ** "identifier", so we just increment the ptr and return. */ (*ptr)++; return HI_SUCCESS; } else { /* ** This means that we've already seen a space and a version ** identifier, and now that we've seen another space, we know ** that this can't be the URI so we just bail out with no ** URI. */ return NO_URI; } } uri_ptr->ident = NULL; /* ** We only check for one here, because both should be set if one ** is. */ if(uri_ptr->first_sp_end) { /* ** If the second space has been set, then this means that we have ** seen a third space, which we shouldn't see in the URI so we ** are now done and know there is no URI in this packet. */ if(uri_ptr->second_sp_end) { return NO_URI; } /* ** Since we've seen the second space, we need to update the uri ptr ** to the end of the first space, since the URI cannot be before the ** first space. */ uri_ptr->uri = uri_ptr->first_sp_end; uri_ptr->second_sp_start = *ptr; uri_ptr->second_sp_end = NULL; start_sp = &uri_ptr->second_sp_start; end_sp = &uri_ptr->second_sp_end; } else { /* ** This means that there is whitespace at the beginning of the line ** and we unset the URI so we can set it later if need be. ** ** This is mainly so we handle data that is all spaces correctly. ** ** In the normal case where we've seen text and then the first space, ** we leave the uri ptr pointing at the beginning of the data, and ** set the uri end after we've determined where to put it. */ if(start == *ptr) uri_ptr->uri = NULL; uri_ptr->first_sp_start = *ptr; uri_ptr->first_sp_end = NULL; start_sp = &uri_ptr->first_sp_start; end_sp = &uri_ptr->first_sp_end; } while(hi_util_in_bounds(start, end, *ptr)) { /* ** Check for whitespace */ if(**ptr == ' ') { (*ptr)++; continue; } else if((**ptr == '\t')) { if(ServerConf->apache_whitespace.on) { if(hi_eo_generate_event(Session, ServerConf->apache_whitespace.alert)) { hi_eo_client_event_log(Session, HI_EO_CLIENT_APACHE_WS, NULL, NULL); } (*ptr)++; continue; } else { return NO_URI; } } else { /* ** This sets the sp_end for whatever space delimiter we are on, ** whether that is the first space or the second space. */ *end_sp = *ptr; if(!IsHttpVersion(ptr, end)) { /* ** This is the default method and what we've been doing ** since the start of development. */ if(uri_ptr->second_sp_start) { /* ** There is no HTTP version indentifier at the beginning ** of the second space, and this means that there is no ** URI. */ if(ServerConf->non_strict) { /* ** In non-strict mode, we must assume the URI is ** between the first and second space, so now ** that we've seen the second space that's the ** identifier. */ uri_ptr->ident = *end_sp; uri_ptr->uri_end = *start_sp; return HI_SUCCESS; } else { /* ** Since we are in strict mode here, it means that ** we haven't seen a valid identifier, so there was ** no URI. */ return NO_URI; } } /* ** RESET NECESSARY URI_PTRs HERE. This is the place where ** the uri is updated. It can only happen once, so do it ** right here. ** ** When we get here it means that we have found the end of ** the FIRST whitespace, and that there was no delimiter, ** so we reset the uri pointers and other related ** pointers. */ uri_ptr->uri = *end_sp; uri_ptr->uri_end = end; uri_ptr->norm = NULL; uri_ptr->last_dir = NULL; uri_ptr->param = NULL; uri_ptr->proxy = NULL; } else { /* ** Means we found the HTTP version identifier and we reset ** the uri_end pointer to point to the beginning of the ** whitespace detected. ** ** This works for both "uri_is_here HTTP/1.0" and ** "METHOD uri_is_here HTTP/1.0", so it works when the ** identifier is after either the first or the second ** whitespace. */ uri_ptr->ident = *end_sp; uri_ptr->uri_end = *start_sp; } /* ** We found a non-whitespace char */ return HI_SUCCESS; } } /* ** This is the case where we've seen text and found a whitespace until ** the end of the buffer. In that case, we set the uri_end to the ** beginning of the whitespace. */ uri_ptr->uri_end = *start_sp; return HI_OUT_OF_BOUNDS; }
/** ** Find the URI and determine whether the URI needs to be normalized. ** ** This is a big step in stateless inspection, because we need to reliably ** find the URI and when possible filter out non-URIs. We do this using a ** simple state machine that is based on characters found in the data ** buffer. ** ** Another important aspect of the stateless inspection is the ability to ** track and inspect pipelined requests. It is VERY IMPORTANT to reset the ** pipeline_req pointer, since we don't memset the whole structure. This ** pointer is reset in the hi_si_session_inspection() function. Check there ** for more details. ** ** Normalization is detected when we are looking at the packet for the URI. ** We look for the following issues: ** - //// ** - /../ ** - /./ ** - non-ascii charss ** - % ** - \ ** When these things are seen we point to the first occurence in the URI, or ** where we have to start normalizing. If the URI is updated to a new ** pointer, then the normalization pointer is reset and we start over. ** Using this method should cut down the memcpy()s per URI, since most ** URIs are not normalized. ** ** If this function returns HI_NONFATAL_ERR, we return out of mode_inspection ** with an error and abort HttpInspect processing, and continue on with ** any other processing we do. The Session parameters that we use here are ** reset in the next time that we do session_inspection, so we don't do ** any initialization here. ** ** @param Session pointer to the HTTP session ** @param data pointer to the start of the packet payload ** @param dsize size of the payload ** ** @return integer ** ** @retval HI_INVALID_ARG invalid argument ** @retval HI_NONFATAL_ERR no URI detected ** @retval HI_SUCCESS URI detected and Session pointers updated */ static int StatelessInspection(HI_SESSION *Session, unsigned char *data, int dsize) { HTTPINSPECT_CONF *ServerConf; HTTPINSPECT_CONF *ClientConf; HI_CLIENT *Client; URI_PTR uri_ptr; u_char *start; u_char *end; u_char *ptr; int iRet; if(!Session || !data || dsize < 1) { return HI_INVALID_ARG; } ServerConf = Session->server_conf; if(!ServerConf) { return HI_INVALID_ARG; } ClientConf = Session->client_conf; if(!ClientConf) { return HI_INVALID_ARG; } Client = &Session->client; memset(&uri_ptr, 0x00, sizeof(URI_PTR)); /* ** We set the starting boundary depending on whether this request is ** a normal request or a pipeline request. The end boundary is always ** the same whether it is a pipeline request or other. */ if(Client->request.pipeline_req) { start = Client->request.pipeline_req; } else { start = data; } end = data + dsize; ptr = start; /* ** Apache and IIS strike again . . . Thanks Kanatoko ** - Ignore CRLFs at the beginning of the request. */ while(hi_util_in_bounds(start, end, ptr)) { if(*ptr < 0x21) { if(*ptr < 0x0E && *ptr > 0x08) { ptr++; continue; } else { if(*ptr == 0x20) { ptr++; continue; } } } break; } uri_ptr.uri = ptr; uri_ptr.uri_end = end; /* ** This loop compares each char to an array of functions ** (one for each char) and calling that function if there is one. ** ** If there is no function, then we just increment the char ptr and ** continue processing. ** ** If there is a function, we call that function and process. It's ** important to note that the function that is called is responsible ** for incrementing the ptr to the next char to be inspected. The ** loop does not increment the pointer when a function is called to ** allow the maximum flexibility to the functions. */ while(hi_util_in_bounds(start, end, ptr)) { if(lookup_table[*ptr]) { if((iRet = (lookup_table[*ptr])(Session, start, end, &ptr, &uri_ptr))) { if(iRet == URI_END) { /* ** You found a URI, let's break and check it out. */ break; } else if(iRet == HI_OUT_OF_BOUNDS) { /* ** Means you've reached the end of the buffer. THIS ** DOESN'T MEAN YOU HAVEN'T FOUND A URI. */ break; } else /* NO_URI */ { /* ** Check for chunk encoding, because the delimiter can ** also be a space, which would look like a pipeline request ** to us if we don't do this first. */ if(Session->server_conf->chunk_length) CheckChunkEncoding(Session, start, end); /* ** We only inspect the packet for another pipeline ** request if there wasn't a previous pipeline request. ** The reason that we do this is because */ if(!Client->request.pipeline_req) { /* ** Just because there was no URI in the first part ** the packet, doesn't mean that this isn't a ** pipelined request that has been segmented. */ if(!ServerConf->no_pipeline) { if((Client->request.pipeline_req = FindPipelineReq(ptr, end))) { return HI_SUCCESS; } } } return HI_NONFATAL_ERR; } } else { /* ** This means that we found the next non-whitespace char ** and since we are already pointed there, so we just ** continue. */ continue; } } ptr++; } /* ** If there is a pipelined request in this packet, we should always ** see the first space followed by text (which is the URI). Without ** that first space, then we never get to the URI, so we should just ** return, since there is nothing else to inspect. */ if(Client->request.pipeline_req) { if(uri_ptr.uri != uri_ptr.first_sp_end) { if(Session->server_conf->chunk_length) CheckChunkEncoding(Session, start, end); return HI_NONFATAL_ERR; } } /* ** We set the HI_CLIENT variables from the URI_PTR structure. We also ** do error checking for the values in this routine as well. */ if((iRet = SetClientVars(Client, &uri_ptr, dsize))) { return iRet; } /* ** One last check for an oversize directory. This gets the long ** directory when there is a beginning slash and no other slashes ** until the end of the packet. ** ** We do this check after we set the variables, just in case there ** was some errors while setting the variables. This could save some ** false positives on a bad URI setting. */ CheckLongDir(Session, &uri_ptr, ptr); /* ** Check for absolute URI and alert for proxy comm if necessary ** ** NOTE: ** Also check ClientConf for proxy configuration so we don't ** alert on outbound requests from legitimate proxies. */ if(uri_ptr.proxy && Session->global_conf->proxy_alert && (!ServerConf->allow_proxy && !ClientConf->allow_proxy)) { if(hi_eo_generate_event(Session, HI_EO_CLIENT_PROXY_USE)) { hi_eo_client_event_log(Session, HI_EO_CLIENT_PROXY_USE, NULL, NULL); } } /* ** Find the next pipeline request, if one is there. If we don't find ** a pipeline request, then we return NULL here, so this is always ** set to the correct value. */ if(!ServerConf->no_pipeline) { Client->request.pipeline_req = FindPipelineReq(uri_ptr.delimiter, end); } else { Client->request.pipeline_req = NULL; } if(Session->server_conf->chunk_length) CheckChunkEncoding(Session, uri_ptr.delimiter, end); return HI_SUCCESS; }
/** ** This routine checks for chunk encoding anomalies in an HTTP client request ** packet. ** ** We convert potential chunk lengths and test them against the user-defined ** max chunk length. We log events on any chunk lengths that are over this ** defined chunk lengths. ** ** Chunks are skipped to save time when the chunk is contained in the packet. ** ** We assume coming into this function that we are pointed at the beginning ** of what may be a chunk length. That's why the iCheckChunk var is set ** to 1. ** ** @param Session pointer to the Session construct ** @param start pointer to where to beginning of buffer ** @param end pointer to the end of buffer ** ** @return integer ** ** @retval HI_SUCCESS function successful ** @retval HI_INVALID_ARG invalid argument */ static int CheckChunkEncoding(HI_SESSION *Session, u_char *start, u_char *end) { u_int iChunkLen = 0; int iChunkChars = 0; int iCheckChunk = 1; u_char *ptr; u_char *jump_ptr; if(!start || !end) return HI_INVALID_ARG; ptr = start; while(hi_util_in_bounds(start, end, ptr)) { if(*ptr == '\n' || *ptr == ' ' || *ptr == '\t') { if(iCheckChunk && iChunkLen != 0) { if(Session->server_conf->chunk_length < iChunkLen && hi_eo_generate_event(Session, HI_EO_CLIENT_LARGE_CHUNK)) { hi_eo_client_event_log(Session, HI_EO_CLIENT_LARGE_CHUNK, NULL, NULL); } jump_ptr = ptr + iChunkLen; if(jump_ptr <= ptr) { break; } if(hi_util_in_bounds(start, end, jump_ptr)) { ptr = jump_ptr; } else { /* ** Chunk too large for packet, so we bail */ break; } } /* ** If we've already evaluated the chunk, or we have a valid delimiter ** for handling new chunks, we reset and starting evaluating possible ** chunk lengths. */ if(iCheckChunk || *ptr == '\n') { iCheckChunk = 1; iChunkLen = 0; iChunkChars = 0; } ptr++; continue; } if(iCheckChunk) { if(hex_lookup[*ptr] == INVALID_HEX_VAL) { if(*ptr == '\r') { ptr++; if(!hi_util_in_bounds(start, end, ptr)) break; if(*ptr == '\n') continue; } else if(*ptr == ';') { /* ** This is where we skip through the chunk name=value ** field. */ ptr++; while(hi_util_in_bounds(start, end, ptr)) { if(*ptr == '\n') break; ptr++; } continue; } iCheckChunk = 0; iChunkLen = 0; iChunkChars = 0; } else { if(iChunkChars >= 8) { iCheckChunk = 0; iChunkLen = 0; iChunkChars = 0; } else { iChunkLen <<= 4; iChunkLen |= (unsigned int)hex_lookup[*ptr]; iChunkChars++; } } } ptr++; } return HI_SUCCESS; }
static INLINE int hi_server_decompress(HI_SESSION *Session, HttpSessionData *sd, const u_char *ptr, const u_char *end, URI_PTR *result) { const u_char *start = ptr; int rawbuf_size = end - ptr; int iRet = HI_SUCCESS; int zRet = HI_FATAL_ERR; int compr_depth, decompr_depth; int compr_bytes_read, decompr_bytes_read; int compr_avail, decompr_avail; int total_bytes_read = 0; int chunk_size = 0; int chunk_read = 0; u_char *compr_buffer; u_char *decompr_buffer; compr_depth = sd->decomp_state->compr_depth; decompr_depth = sd->decomp_state->decompr_depth; compr_bytes_read = sd->decomp_state->compr_bytes_read; decompr_bytes_read = sd->decomp_state->decompr_bytes_read; compr_buffer = sd->decomp_state->compr_buffer; decompr_buffer = sd->decomp_state->decompr_buffer; if(Session->server_conf->unlimited_decompress) { compr_avail = compr_depth; decompr_avail = decompr_depth; } else { compr_avail = compr_depth-compr_bytes_read; decompr_avail = decompr_depth - decompr_bytes_read; } /* Apply the server flow depth * If the server flow depth is set then we need to decompress only upto the * server flow depth */ switch ( Session->server_conf->server_flow_depth) { case -1: decompr_avail=0; break; case 0: break; default: if(sd->resp_state.flow_depth_read < Session->server_conf->server_flow_depth) { if(decompr_avail > (Session->server_conf->server_flow_depth - sd->resp_state.flow_depth_read)) decompr_avail = Session->server_conf->server_flow_depth - sd->resp_state.flow_depth_read; } else { decompr_avail = 0; } break; } if(compr_avail <=0 || decompr_avail <=0 || (!compr_buffer) || (!decompr_buffer)) { ResetGzipState(sd->decomp_state); ResetRespState(&(sd->resp_state)); return iRet; } if(rawbuf_size < compr_avail) { compr_avail = rawbuf_size; } if(!(sd->resp_state.last_pkt_contlen)) { if(CheckChunkEncoding(Session, start, end, NULL, compr_buffer, compr_avail, sd->resp_state.last_chunk_size, &chunk_size, &chunk_read ) == 1) { sd->resp_state.last_chunk_size = chunk_size; compr_avail = chunk_read; zRet = uncompress_gzip(decompr_buffer,decompr_avail,compr_buffer, compr_avail, sd, &total_bytes_read, sd->decomp_state->compress_fmt); } else { /* No Content-Length or Transfer-Encoding : chunked */ if(hi_eo_generate_event(Session, HI_EO_SERVER_NO_CONTLEN)) { hi_eo_server_event_log(Session, HI_EO_SERVER_NO_CONTLEN, NULL, NULL); } memcpy(compr_buffer, ptr, compr_avail); zRet = uncompress_gzip(decompr_buffer,decompr_avail,compr_buffer, compr_avail, sd, &total_bytes_read, sd->decomp_state->compress_fmt); } } else { memcpy(compr_buffer, ptr, compr_avail); zRet = uncompress_gzip(decompr_buffer,decompr_avail,compr_buffer, compr_avail, sd, &total_bytes_read, sd->decomp_state->compress_fmt); } sd->decomp_state->compr_bytes_read += compr_avail; hi_stats.compr_bytes_read += compr_avail; if((zRet == HI_SUCCESS) || (zRet == HI_NONFATAL_ERR)) { if(decompr_buffer) { result->uri = decompr_buffer; if ( total_bytes_read < decompr_avail ) { result->uri_end = decompr_buffer + total_bytes_read; sd->decomp_state->decompr_bytes_read += total_bytes_read; sd->resp_state.flow_depth_read += total_bytes_read; hi_stats.decompr_bytes_read += total_bytes_read; } else { result->uri_end = decompr_buffer + decompr_avail; sd->decomp_state->decompr_bytes_read += decompr_avail; sd->resp_state.flow_depth_read += decompr_avail; hi_stats.decompr_bytes_read += decompr_avail; } } } else { ResetGzipState(sd->decomp_state); ResetRespState(&(sd->resp_state)); } return iRet; }