1 // Copyright 2015 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #include "net/http/http_status_line_validator.h"
9 #include "base/strings/string_split.h"
10 #include "base/strings/string_util.h"
14 using base::StringPiece
;
16 HttpStatusLineValidator::StatusLineStatus
17 HttpStatusLineValidator::ValidateStatusLine(const StringPiece
& status_line
) {
18 std::vector
<StringPiece
> fields
= base::SplitStringPiece(
19 status_line
, " ", base::KEEP_WHITESPACE
, base::SPLIT_WANT_NONEMPTY
);
20 // Permissively split fields, meaning:
21 // 1) Extra whitespace separating fields ignored
22 // 2) Extra leading/trailing whitespace removed
23 std::vector
<StringPiece
> loose_fields
= base::SplitStringPiece(
24 status_line
, " ", base::KEEP_WHITESPACE
, base::SPLIT_WANT_ALL
);
26 // Fields: HTTP-version status-code reason-phrase
27 if (fields
.empty() || loose_fields
.empty())
28 return STATUS_LINE_EMPTY
;
30 StatusLineStatus rv
= CheckHttpVersionSyntax(fields
[0]);
31 if (rv
!= STATUS_LINE_OK
)
34 if (fields
.size() < 2)
35 return STATUS_LINE_MISSING_STATUS_CODE
;
37 rv
= CheckStatusCodeSyntax(fields
[1]);
38 if (rv
!= STATUS_LINE_OK
)
41 // At this point the field splitting could be wrong, so check for extra
42 // whitespace/padding on the result code.
43 // Note that there is no such thing as "extra whitespace" on the
44 // reason-phrase, since spaces are legal inside it.
45 if (loose_fields
[1] != fields
[1])
46 return STATUS_LINE_EXCESS_WHITESPACE
;
48 // An empty reason phrase will cause there to be only two fields in |fields|
49 // but more than that in |loose_fields|.
50 if (loose_fields
.size() < 3)
51 return STATUS_LINE_MISSING_REASON_PHRASE
;
52 return CheckReasonPhraseSyntax(fields
, 2);
55 HttpStatusLineValidator::StatusLineStatus
56 HttpStatusLineValidator::CheckHttpVersionSyntax(const StringPiece
& version
) {
57 static const char kProtoName
[] = "HTTP";
58 static const char kDigits
[] = "0123456789";
59 if (!base::StartsWith(version
, kProtoName
,
60 base::CompareCase::INSENSITIVE_ASCII
)) {
61 return STATUS_LINE_NOT_HTTP
;
63 if (!base::StartsWith(version
, kProtoName
, base::CompareCase::SENSITIVE
))
64 return STATUS_LINE_HTTP_CASE_MISMATCH
;
65 // Okay, definitely "HTTP" at the start. Now check for the separating slash
66 // and version number components:
67 size_t slash
= version
.find('/');
68 if (slash
!= strlen(kProtoName
))
69 return STATUS_LINE_HTTP_NO_VERSION
;
70 StringPiece rest
= version
.substr(slash
+ 1);
71 size_t sep
= rest
.find('.');
72 if (sep
== StringPiece::npos
) {
73 return STATUS_LINE_INVALID_VERSION
;
75 StringPiece major
= rest
.substr(0, sep
);
76 if (major
.length() == 0)
77 return STATUS_LINE_INVALID_VERSION
;
78 StringPiece minor
= rest
.substr(sep
+ 1);
79 if (minor
.length() == 0)
80 return STATUS_LINE_INVALID_VERSION
;
81 if (major
.find_first_not_of(kDigits
) != major
.npos
||
82 minor
.find_first_not_of(kDigits
) != minor
.npos
) {
83 return STATUS_LINE_INVALID_VERSION
;
86 if (major
.length() != 1 || minor
.length() != 1)
87 return STATUS_LINE_MULTI_DIGIT_VERSION
;
89 // It is now known that version looks like:
91 // For single digits x and y.
92 // Check that x == '1' and y == '0' or '1'
93 if (major
[0] != '1' || (minor
[0] != '0' && minor
[0] != '1')) {
94 if (major
[0] == '0' && minor
[0] == '9')
95 return STATUS_LINE_EXPLICIT_0_9
;
97 return STATUS_LINE_UNKNOWN_VERSION
;
99 return STATUS_LINE_OK
;
102 HttpStatusLineValidator::StatusLineStatus
103 HttpStatusLineValidator::CheckStatusCodeSyntax(const StringPiece
& status_code
) {
104 if (status_code
.length() < 3)
105 return STATUS_LINE_INVALID_STATUS_CODE
;
106 if (!isdigit(status_code
[0]) || !isdigit(status_code
[1]) ||
107 !isdigit(status_code
[2])) {
108 return STATUS_LINE_INVALID_STATUS_CODE
;
110 if (status_code
.length() > 3)
111 return STATUS_LINE_STATUS_CODE_TRAILING
;
112 // The only valid codes are 1xx through 5xx, see RFC 7231 S6
113 if (status_code
[0] < '1' || status_code
[0] > '5')
114 return STATUS_LINE_RESERVED_STATUS_CODE
;
115 return STATUS_LINE_OK
;
118 HttpStatusLineValidator::StatusLineStatus
119 HttpStatusLineValidator::CheckReasonPhraseSyntax(
120 const std::vector
<StringPiece
>& fields
,
121 size_t start_index
) {
122 for (size_t i
= start_index
; i
< fields
.size(); ++i
) {
123 for (size_t j
= 0; j
< fields
[i
].length(); ++j
) {
124 // VCHAR is any "visible" ASCII character, meaning any non-control
125 // character, so >= ' ' but not DEL (\x7f).
126 // obs-text is any character between \x80 and \xff.
127 // HTAB is \t, SP is ' '
128 char c
= fields
[i
][j
];
129 if (c
== '\x7f' || (c
< ' ' && c
!= '\t'))
130 return STATUS_LINE_REASON_DISALLOWED_CHARACTER
;
133 return STATUS_LINE_OK
;