/** * Parses a cookie header after the initial "Cookie:" [WS][$]token[WS]=[WS](token|QV)[;|,] RFC * 2965 JVK */ protected final void processCookieHeader(byte bytes[], int off, int len) { if (len <= 0 || bytes == null) { return; } int end = off + len; int pos = off; int nameStart = 0; int nameEnd = 0; int valueStart = 0; int valueEnd = 0; int version = 0; ServerCookie sc = null; boolean isSpecial; boolean isQuoted; while (pos < end) { isSpecial = false; isQuoted = false; // Skip whitespace and non-token characters (separators) while (pos < end && (CookieSupport.isHttpSeparator((char) bytes[pos]) && !CookieSupport.ALLOW_HTTP_SEPARATORS_IN_V0 || CookieSupport.isV0Separator((char) bytes[pos]) || isWhiteSpace(bytes[pos]))) { pos++; } if (pos >= end) { return; } // Detect Special cookies if (bytes[pos] == '$') { isSpecial = true; pos++; } // Get the cookie/attribute name. This must be a token valueEnd = valueStart = nameStart = pos; pos = nameEnd = getTokenEndPosition(bytes, pos, end, version, true); // Skip whitespace while (pos < end && isWhiteSpace(bytes[pos])) { pos++; } // Check for an '=' -- This could also be a name-only // cookie at the end of the cookie header, so if we // are past the end of the header, but we have a name // skip to the name-only part. if (pos < (end - 1) && bytes[pos] == '=') { // Skip whitespace do { pos++; } while (pos < end && isWhiteSpace(bytes[pos])); if (pos >= end) { return; } // Determine what type of value this is, quoted value, // token, name-only with an '=', or other (bad) switch (bytes[pos]) { case '"': // Quoted Value isQuoted = true; valueStart = pos + 1; // strip " // getQuotedValue returns the position before // at the last quote. This must be dealt with // when the bytes are copied into the cookie valueEnd = getQuotedValueEndPosition(bytes, valueStart, end); // We need pos to advance pos = valueEnd; // Handles cases where the quoted value is // unterminated and at the end of the header, // e.g. [myname="value] if (pos >= end) { return; } break; case ';': case ',': // Name-only cookie with an '=' after the name token // This may not be RFC compliant valueStart = valueEnd = -1; // The position is OK (On a delimiter) break; default: if (version == 0 && !CookieSupport.isV0Separator((char) bytes[pos]) && CookieSupport.ALLOW_HTTP_SEPARATORS_IN_V0 || !CookieSupport.isHttpSeparator((char) bytes[pos]) || bytes[pos] == '=' && CookieSupport.ALLOW_EQUALS_IN_VALUE) { // Token valueStart = pos; // getToken returns the position at the delimiter // or other non-token character valueEnd = getTokenEndPosition(bytes, valueStart, end, version, false); // We need pos to advance pos = valueEnd; } else { // INVALID COOKIE, advance to next delimiter // The starting character of the cookie value was // not valid. UserDataHelper.Mode logMode = userDataLog.getNextMode(); if (logMode != null) { String message = sm.getString("cookies.invalidCookieToken"); switch (logMode) { case INFO_THEN_DEBUG: message += sm.getString("cookies.fallToDebug"); // $FALL-THROUGH$ case INFO: log.info(message); break; case DEBUG: log.debug(message); } } while (pos < end && bytes[pos] != ';' && bytes[pos] != ',') { pos++; } pos++; // Make sure no special avpairs can be attributed to // the previous cookie by setting the current cookie // to null sc = null; continue; } } } else { // Name only cookie valueStart = valueEnd = -1; pos = nameEnd; } // We should have an avpair or name-only cookie at this // point. Perform some basic checks to make sure we are // in a good state. // Skip whitespace while (pos < end && isWhiteSpace(bytes[pos])) { pos++; } // Make sure that after the cookie we have a separator. This // is only important if this is not the last cookie pair while (pos < end && bytes[pos] != ';' && bytes[pos] != ',') { pos++; } pos++; // All checks passed. Add the cookie, start with the // special avpairs first if (isSpecial) { isSpecial = false; // $Version must be the first avpair in the cookie header // (sc must be null) if (equals("Version", bytes, nameStart, nameEnd) && sc == null) { // Set version if (bytes[valueStart] == '1' && valueEnd == (valueStart + 1)) { version = 1; } else { // unknown version (Versioning is not very strict) } continue; } // We need an active cookie for Path/Port/etc. if (sc == null) { continue; } // Domain is more common, so it goes first if (equals("Domain", bytes, nameStart, nameEnd)) { sc.getDomain().setBytes(bytes, valueStart, valueEnd - valueStart); continue; } if (equals("Path", bytes, nameStart, nameEnd)) { sc.getPath().setBytes(bytes, valueStart, valueEnd - valueStart); continue; } // v2 cookie attributes - skip them if (equals("Port", bytes, nameStart, nameEnd)) { continue; } if (equals("CommentURL", bytes, nameStart, nameEnd)) { continue; } // Unknown cookie, complain UserDataHelper.Mode logMode = userDataLog.getNextMode(); if (logMode != null) { String message = sm.getString("cookies.invalidSpecial"); switch (logMode) { case INFO_THEN_DEBUG: message += sm.getString("cookies.fallToDebug"); // $FALL-THROUGH$ case INFO: log.info(message); break; case DEBUG: log.debug(message); } } } else { // Normal Cookie if (valueStart == -1 && !CookieSupport.ALLOW_NAME_ONLY) { // Skip name only cookies if not supported continue; } sc = addCookie(); sc.setVersion(version); sc.getName().setBytes(bytes, nameStart, nameEnd - nameStart); if (valueStart != -1) { // Normal AVPair sc.getValue().setBytes(bytes, valueStart, valueEnd - valueStart); if (isQuoted) { // We know this is a byte value so this is safe unescapeDoubleQuotes(sc.getValue().getByteChunk()); } } else { // Name Only sc.getValue().setString(""); } continue; } } }
private void processParameters(byte bytes[], int start, int len, Charset charset) { if (log.isDebugEnabled()) { log.debug(sm.getString("parameters.bytes", new String(bytes, start, len, DEFAULT_CHARSET))); } int decodeFailCount = 0; int pos = start; int end = start + len; while (pos < end) { int nameStart = pos; int nameEnd = -1; int valueStart = -1; int valueEnd = -1; boolean parsingName = true; boolean decodeName = false; boolean decodeValue = false; boolean parameterComplete = false; do { switch (bytes[pos]) { case '=': if (parsingName) { // Name finished. Value starts from next character nameEnd = pos; parsingName = false; valueStart = ++pos; } else { // Equals character in value pos++; } break; case '&': if (parsingName) { // Name finished. No value. nameEnd = pos; } else { // Value finished valueEnd = pos; } parameterComplete = true; pos++; break; case '%': case '+': // Decoding required if (parsingName) { decodeName = true; } else { decodeValue = true; } pos++; break; default: pos++; break; } } while (!parameterComplete && pos < end); if (pos == end) { if (nameEnd == -1) { nameEnd = pos; } else if (valueStart > -1 && valueEnd == -1) { valueEnd = pos; } } if (log.isDebugEnabled() && valueStart == -1) { log.debug( sm.getString( "parameters.noequal", Integer.valueOf(nameStart), Integer.valueOf(nameEnd), new String(bytes, nameStart, nameEnd - nameStart, DEFAULT_CHARSET))); } if (nameEnd <= nameStart) { if (valueStart == -1) { // && if (log.isDebugEnabled()) { log.debug(sm.getString("parameters.emptyChunk")); } // Do not flag as error continue; } // &=foo& UserDataHelper.Mode logMode = userDataLog.getNextMode(); if (logMode != null) { String extract; if (valueEnd > nameStart) { extract = new String(bytes, nameStart, valueEnd - nameStart, DEFAULT_CHARSET); } else { extract = ""; } String message = sm.getString( "parameters.invalidChunk", Integer.valueOf(nameStart), Integer.valueOf(valueEnd), extract); switch (logMode) { case INFO_THEN_DEBUG: message += sm.getString("parameters.fallToDebug"); // $FALL-THROUGH$ case INFO: log.info(message); break; case DEBUG: log.debug(message); } } parseFailed = true; continue; // invalid chunk - it's better to ignore } tmpName.setBytes(bytes, nameStart, nameEnd - nameStart); if (valueStart >= 0) { tmpValue.setBytes(bytes, valueStart, valueEnd - valueStart); } else { tmpValue.setBytes(bytes, 0, 0); } // Take copies as if anything goes wrong originals will be // corrupted. This means original values can be logged. // For performance - only done for debug if (log.isDebugEnabled()) { try { origName.append(bytes, nameStart, nameEnd - nameStart); if (valueStart >= 0) { origValue.append(bytes, valueStart, valueEnd - valueStart); } else { origValue.append(bytes, 0, 0); } } catch (IOException ioe) { // Should never happen... log.error(sm.getString("parameters.copyFail"), ioe); } } try { String name; String value; if (decodeName) { urlDecode(tmpName); } tmpName.setCharset(charset); name = tmpName.toString(); if (valueStart >= 0) { if (decodeValue) { urlDecode(tmpValue); } tmpValue.setCharset(charset); value = tmpValue.toString(); } else { value = ""; } try { addParameter(name, value); } catch (IllegalStateException ise) { // Hitting limit stops processing further params but does // not cause request to fail. parseFailed = true; UserDataHelper.Mode logMode = maxParamCountLog.getNextMode(); if (logMode != null) { String message = ise.getMessage(); switch (logMode) { case INFO_THEN_DEBUG: message += sm.getString("parameters.maxCountFail.fallToDebug"); // $FALL-THROUGH$ case INFO: log.info(message); break; case DEBUG: log.debug(message); } } break; } } catch (IOException e) { parseFailed = true; decodeFailCount++; if (decodeFailCount == 1 || log.isDebugEnabled()) { if (log.isDebugEnabled()) { log.debug( sm.getString( "parameters.decodeFail.debug", origName.toString(), origValue.toString()), e); } else if (log.isInfoEnabled()) { UserDataHelper.Mode logMode = userDataLog.getNextMode(); if (logMode != null) { String message = sm.getString( "parameters.decodeFail.info", tmpName.toString(), tmpValue.toString()); switch (logMode) { case INFO_THEN_DEBUG: message += sm.getString("parameters.fallToDebug"); // $FALL-THROUGH$ case INFO: log.info(message); break; case DEBUG: log.debug(message); } } } } } tmpName.recycle(); tmpValue.recycle(); // Only recycle copies if we used them if (log.isDebugEnabled()) { origName.recycle(); origValue.recycle(); } } if (decodeFailCount > 1 && !log.isDebugEnabled()) { UserDataHelper.Mode logMode = userDataLog.getNextMode(); if (logMode != null) { String message = sm.getString("parameters.multipleDecodingFail", Integer.valueOf(decodeFailCount)); switch (logMode) { case INFO_THEN_DEBUG: message += sm.getString("parameters.fallToDebug"); // $FALL-THROUGH$ case INFO: log.info(message); break; case DEBUG: log.debug(message); } } } }