blob: 5b8284cb5a341a68382d5b3a6c1297cce557dba3 [file] [log] [blame]
// Copyright 2015 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "net/http/http_status_line_validator.h"
#include <vector>
#include "base/strings/string_split.h"
#include "base/strings/string_util.h"
namespace net {
using base::StringPiece;
HttpStatusLineValidator::StatusLineStatus
HttpStatusLineValidator::ValidateStatusLine(const StringPiece& status_line) {
std::vector<StringPiece> fields = base::SplitStringPiece(
status_line, " ", base::KEEP_WHITESPACE, base::SPLIT_WANT_NONEMPTY);
// Permissively split fields, meaning:
// 1) Extra whitespace separating fields ignored
// 2) Extra leading/trailing whitespace removed
std::vector<StringPiece> loose_fields = base::SplitStringPiece(
status_line, " ", base::KEEP_WHITESPACE, base::SPLIT_WANT_ALL);
// Fields: HTTP-version status-code reason-phrase
if (fields.empty() || loose_fields.empty())
return STATUS_LINE_EMPTY;
StatusLineStatus rv = CheckHttpVersionSyntax(fields[0]);
if (rv != STATUS_LINE_OK)
return rv;
if (fields.size() < 2)
return STATUS_LINE_MISSING_STATUS_CODE;
rv = CheckStatusCodeSyntax(fields[1]);
if (rv != STATUS_LINE_OK)
return rv;
// At this point the field splitting could be wrong, so check for extra
// whitespace/padding on the result code.
// Note that there is no such thing as "extra whitespace" on the
// reason-phrase, since spaces are legal inside it.
if (loose_fields[1] != fields[1])
return STATUS_LINE_EXCESS_WHITESPACE;
// An empty reason phrase will cause there to be only two fields in |fields|
// but more than that in |loose_fields|.
if (loose_fields.size() < 3)
return STATUS_LINE_MISSING_REASON_PHRASE;
return CheckReasonPhraseSyntax(fields, 2);
}
HttpStatusLineValidator::StatusLineStatus
HttpStatusLineValidator::CheckHttpVersionSyntax(const StringPiece& version) {
static const char kProtoName[] = "HTTP";
static const char kDigits[] = "0123456789";
if (!base::StartsWith(version, kProtoName,
base::CompareCase::INSENSITIVE_ASCII)) {
return STATUS_LINE_NOT_HTTP;
}
if (!base::StartsWith(version, kProtoName, base::CompareCase::SENSITIVE))
return STATUS_LINE_HTTP_CASE_MISMATCH;
// Okay, definitely "HTTP" at the start. Now check for the separating slash
// and version number components:
size_t slash = version.find('/');
if (slash != strlen(kProtoName))
return STATUS_LINE_HTTP_NO_VERSION;
StringPiece rest = version.substr(slash + 1);
size_t sep = rest.find('.');
if (sep == StringPiece::npos) {
return STATUS_LINE_INVALID_VERSION;
}
StringPiece major = rest.substr(0, sep);
if (major.length() == 0)
return STATUS_LINE_INVALID_VERSION;
StringPiece minor = rest.substr(sep + 1);
if (minor.length() == 0)
return STATUS_LINE_INVALID_VERSION;
if (major.find_first_not_of(kDigits) != major.npos ||
minor.find_first_not_of(kDigits) != minor.npos) {
return STATUS_LINE_INVALID_VERSION;
}
if (major.length() != 1 || minor.length() != 1)
return STATUS_LINE_MULTI_DIGIT_VERSION;
// It is now known that version looks like:
// HTTP/x.y
// For single digits x and y.
// Check that x == '1' and y == '0' or '1'
if (major[0] != '1' || (minor[0] != '0' && minor[0] != '1')) {
if (major[0] == '0' && minor[0] == '9')
return STATUS_LINE_EXPLICIT_0_9;
else
return STATUS_LINE_UNKNOWN_VERSION;
}
return STATUS_LINE_OK;
}
HttpStatusLineValidator::StatusLineStatus
HttpStatusLineValidator::CheckStatusCodeSyntax(const StringPiece& status_code) {
if (status_code.length() < 3)
return STATUS_LINE_INVALID_STATUS_CODE;
if (!isdigit(status_code[0]) || !isdigit(status_code[1]) ||
!isdigit(status_code[2])) {
return STATUS_LINE_INVALID_STATUS_CODE;
}
if (status_code.length() > 3)
return STATUS_LINE_STATUS_CODE_TRAILING;
// The only valid codes are 1xx through 5xx, see RFC 7231 S6
if (status_code[0] < '1' || status_code[0] > '5')
return STATUS_LINE_RESERVED_STATUS_CODE;
return STATUS_LINE_OK;
}
HttpStatusLineValidator::StatusLineStatus
HttpStatusLineValidator::CheckReasonPhraseSyntax(
const std::vector<StringPiece>& fields,
size_t start_index) {
for (size_t i = start_index; i < fields.size(); ++i) {
for (size_t j = 0; j < fields[i].length(); ++j) {
// VCHAR is any "visible" ASCII character, meaning any non-control
// character, so >= ' ' but not DEL (\x7f).
// obs-text is any character between \x80 and \xff.
// HTAB is \t, SP is ' '
char c = fields[i][j];
if (c == '\x7f' || (c < ' ' && c != '\t'))
return STATUS_LINE_REASON_DISALLOWED_CHARACTER;
}
}
return STATUS_LINE_OK;
}
} // namespace net