Case Sensitivity Issues in Frontend Development#
Case sensitivity is a topic in software development that refers to scenarios where different capitalizations of the same "word" can lead to different effects.
Next, let's discuss some common case-sensitive and case-insensitive scenarios in the field of frontend development:
- HTTP Header
- HTTP Method
- URL
- Cookie
- E-Mail Address
- HTML5 Tags and Attribute names
- CSS Property
- File names in Git
Are HTTP Headers Case Sensitive?#
The name fields of HTTP Headers are case-insensitive.
RFC 7230 - Hypertext Transfer Protocol (HTTP/1.1)#section-3.2 states:
Each header field consists of a case-insensitive field name followed
by a colon (":"), optional leading whitespace, the field value, and
optional trailing whitespace.
However, it is important to note that HTTP/2 has additional restrictions due to header compression, requiring that names be converted to lowercase before encoding.
RFC 7540 - Hypertext Transfer Protocol Version 2 (HTTP/2)#section-8.1.2 states:
However, header field names MUST be converted to lowercase prior to their
encoding in HTTP/2.
Nowadays, most clients and HTTP servers default to converting HTTP Header names to lowercase to avoid the need for users to handle case conversion logic repeatedly.
For example, the HTTP module in NodeJS automatically converts Header fields to lowercase node/_http_outgoing.js at main · nodejs/node · GitHub:
// lib/_http_outgoing.js
ObjectDefineProperty(OutgoingMessage.prototype, '_headers', {
__proto__: null,
get: internalUtil.deprecate(function() {
return this.getHeaders();
}, 'OutgoingMessage.prototype._headers is deprecated', 'DEP0066'),
set: internalUtil.deprecate(function(val) {
if (val == null) {
this[kOutHeaders] = null;
} else if (typeof val === 'object') {
const headers = this[kOutHeaders] = ObjectCreate(null);
const keys = ObjectKeys(val);
// Retain for(;;) loop for performance reasons
// Refs: https://github.com/nodejs/node/pull/30958
for (let i = 0; i < keys.length; ++i) {
const name = keys[i];
headers[StringPrototypeToLowerCase(name)] = [name, val[name]];
}
}
}, 'OutgoingMessage.prototype._headers is deprecated', 'DEP0066')
});
PS. While writing this article, I found an error in this part of the NodeJS documentation, where the example for getting headers contained uppercase letters, so I submitted a PR, which has now been merged. doc: fix typo in http.md by airingursb · Pull Request #43933 · nodejs/node · GitHub
In addition, the HTTP module in Rust also defaults to converting Header field names to lowercase for faster processing.
Documentation can be found at http::header - Rust:
The
HeaderName
type represents both standard header names as well as custom header names. The type handles the case insensitive nature of header names and is used as the key portion ofHeaderMap
. Header names are normalized to lower case. In other words, when creating aHeaderName
with a string, even if upper case characters are included, when getting a string representation of theHeaderName
, it will be all lower case. This allows for fasterHeaderMap
comparison operations.
Source code can be found at: src/headers/name.rs - http 0.1.3 - Docs.rs
As frontend developers, we pay more attention to how Chromium and WebKit handle this.
For request headers, we can conduct an experiment in Chrome:
var ajax = new XMLHttpRequest();
ajax.open("GET", "https://y.qq.com/lib/interaction/h5/interaction-component-1.2.min.js");
ajax.setRequestHeader("X-test", "AA");
ajax.send();
For HTTP2 requests, after capturing the packet, we can see that X-test
has been rewritten to lowercase x-test
:
To be thorough, I also used Chrome's built-in netlog-viewer to capture and check the packets, and indeed the request header was converted to lowercase:
Note: The above test also yields the same result in Safari, but it must be captured to see. If only viewed in Safari's console, it displays the case incorrectly, while Chrome's DevTools does not have this issue. This is suspected to be a bug in Safari.
I then read the source code of XMLHttpRequest in Blink, but unfortunately, I couldn't find where it converts to lowercase.
I also checked the XMLHttpRequest standard, and XMLHttpRequest Standard does not mention the need to convert header field names to lowercase.
There has even been a suggestion in the XHR standard to directly convert header field names to lowercase (Should XHR store and send HTTP header names in lower case? · Issue #34 · whatwg/xhr · GitHub), but it was rejected.
According to the specification, HTTP2 header names must be converted to lowercase, but I tested an HTTP1.1 site:
var ajax = new XMLHttpRequest();
ajax.open("get", "http://www.cn1t.com/airing.js");
ajax.setRequestHeader("X-test", "AA");
ajax.send();
I found that the header field names were not converted to lowercase:
It can be concluded that the case conversion of request header field names is not done by XMLHttpRequest, but rather by the underlying network library logic.
PS. I debugged this for a long time and couldn't find exactly where it converts to lowercase. If anyone knows, please feel free to comment and let me know. Thank you very much.
The field names in response headers are different. If you use methods like getAllResponseHeaders
in XMLHttpRequest to retrieve response headers, Blink will convert the header name fields to lowercase:
String XMLHttpRequest::getAllResponseHeaders() const {
// ...
for (const auto& header : headers) {
string_builder.Append(header.first.LowerASCII());
string_builder.Append(':');
string_builder.Append(' ');
string_builder.Append(header.second);
string_builder.Append('\r');
string_builder.Append('\n');
}
// ...
}
Additionally, when Chromium parses network response packets, if it encounters uppercase letters in HTTP2 protocol header field names, it will directly cause the network packet parsing to fail:
absl::optional<ParsedHeaders> ConvertCBORValueToHeaders(
const cbor::Value& headers_value) {
// |headers_value| of headers must be a map.
if (!headers_value.is_map())
return absl::nullopt;
ParsedHeaders result;
for (const auto& item : headers_value.GetMap()) {
if (!item.first.is_bytestring() || !item.second.is_bytestring())
return absl::nullopt;
base::StringPiece name = item.first.GetBytestringAsString();
base::StringPiece value = item.second.GetBytestringAsString();
// If name contains any upper-case or non-ASCII characters, return an error.
// This matches the requirement in Section 8.1.2 of [RFC7540].
if (!base::IsStringASCII(name) ||
std::any_of(name.begin(), name.end(), base::IsAsciiUpper<char>))
return absl::nullopt;
// ...other
}
return result;
}
Are HTTP Methods Case Sensitive?#
According to the specification RFC 7230 - Hypertext Transfer Protocol (HTTP/1.1)#section-3.1.1:
The method token indicates the request method to be performed on the target resource. The request method is case-sensitive.
HTTP Methods are case-sensitive and must be uppercase.
However, if you pass a lowercase method to your XMLHttpRequest instance (e.g., calling ajax.open("get", "https://ursb.me")
), it doesn't matter; browser engines like Blink will normalize it to uppercase Source/core/xmlhttprequest/XMLHttpRequest.cpp - chromium/blink - Git at Google:
void XMLHttpRequest::open(const AtomicString& method,
const KURL& url,
bool async,
ExceptionState& exception_state) {
//...
method_ = FetchUtils::NormalizeMethod(method);
// ...
}
AtomicString FetchUtils::NormalizeMethod(const AtomicString& method) {
// https://fetch.spec.whatwg.org/#concept-method-normalize
// We place GET and POST first because they are more commonly used than
// others.
const char* const kMethods[] = {
"GET", "POST", "DELETE", "HEAD", "OPTIONS", "PUT",
};
for (auto* const known : kMethods) {
if (EqualIgnoringASCIICase(method, known)) {
// Don't bother allocating a new string if it's already all
// uppercase.
return method == known ? method : known;
}
}
return method;
}
Are URLs Case Sensitive?#
According to the specification RFC 7230 - Hypertext Transfer Protocol (HTTP/1.1)#section-2.7.3:
The scheme and host are case-insensitive and normally provided in lowercase; all other components are compared in a case-sensitive manner.
The HTTP protocol (scheme/protocol) and domain (host) are case-insensitive, but the path, query, and fragment are case-sensitive.
In Chromium, URLs are managed uniformly using the kUrl
class, which internally normalizes the protocol
and host
to lowercase.
Interestingly, while the specification states that the path
is case-sensitive, the actual situation depends on the underlying file system of the Web Server, so the path may also be case-insensitive.
For example, IIS servers are case-insensitive because they depend on the Windows file system, which uses NTFS and FAT file systems that are case-insensitive by default (but case-preserving). Correspondingly, if an Apache server is deployed on a case-insensitive Mac (HFS), it is also case-insensitive.
Here are some mainstream operating systems and their underlying file systems:
- Windows uses NTFS and FAT file systems, which are case-insensitive by default but case-preserving.
- macOS uses APFS and HFS+ file systems, which are case-insensitive by default but case-preserving.
- Linux uses ext3/ext4 file systems, which are case-sensitive.
Additionally, it also relates to the server's policy; for example, https://en.wikipedia.org/wiki/Case_sensitivity and https://en.wikipedia.org/wiki/case_sensitivity point to the same article, but it cannot be simply assumed to be case-insensitive because https://en.wikipedia.org/wiki/CASE_SENSITIVITY results in a 404 error.
Are Cookies Case Sensitive?#
To conclude, it aligns with intuition, the names of Cookies are case-sensitive. However, the evolution of the specification has been quite rocky.
In the early specification RFC 2109 - HTTP State Management Mechanism, Cookie names were case-insensitive:
Attributes (names) (attr) are case-insensitive. White space is permitted between tokens. Note that while the above syntax description shows value as optional, most attrs require them.
In RFC 2965, it deprecated RFC 2109:
This document reflects implementation experience with RFC 2109 and obsoletes it.
However, the latest Cookie specification RFC 6265 does not explicitly state whether Cookies are case-sensitive, so it can be assumed that they are case-sensitive, as mainstream browsers like Chrome and Firefox implement case sensitivity for Cookies.
Are E-Mail Addresses Case Sensitive?#
According to the specification RFC 5321: Simple Mail Transfer Protocol#section-2.3.11:
The standard mailbox naming convention is defined to be "local-part@domain"; contemporary usage permits a much broader set of applications than simple "user names". Consequently, and due to a long history of problems when intermediate hosts have attempted to optimize transport by modifying them, the local-part MUST be interpreted and assigned semantics only by the host specified in the domain part of the address.
The domain part follows RFC 1035: Domain names - implementation and specification#section3.1:
"Name servers and resolvers must compare [domains] in a case-insensitive manner"
In summary: The domain name in an email address is case-insensitive, while the username (local-part) case sensitivity depends on the email service provider.
Are HTML5 Tags and Attribute Names Case Sensitive?#
According to the specification HTML Live Standard#section-13.1, HTML tags and attribute names are case-insensitive:
Many strings in the HTML syntax (e.g. the names of elements and their attributes) are case-insensitive, but only for characters in the ranges U+0041 to U+005A (LATIN CAPITAL LETTER A to LATIN CAPITAL LETTER Z) and U+0061 to U+007A (LATIN SMALL LETTER A to LATIN SMALL LETTER Z). For convenience, in this section this is just referred to as "case-insensitive".
This means that the document type <!DOCTYPE html>
can also be written as <!doctype html>
.
It is important to note that data attributes are an exception; they must be lowercase. HTML Live Standard#section3.2.6.6:
A custom data attribute is an attribute in no namespace whose name starts with the string "
data-
", has at least one character after the hyphen, is XML-compatible, and contains no ASCII upper alphas.
Blink has a correction logic, meaning that even if you write <div data-Name="airing"></div>
, it will ultimately be converted to <div data-Name="airing"></div>
. However, for compatibility, the attribute name should still be lowercase data-name
.
Is CSS Case Sensitive?#
According to CSS Selectors Level 3:
All Selectors syntax is case-insensitive within the ASCII range (i.e. [a-z] and [A-Z] are equivalent), except for parts that are not under the control of Selectors. The case sensitivity of document language element names, attribute names, and attribute values in selectors depends on the document language. For example, in HTML, element names are case-insensitive, but in XML, they are case-sensitive. Case sensitivity of namespace prefixes is defined in [CSS3NAMESPACE].
The CSS selector syntax is case-insensitive, while whether attribute names and values are case-sensitive depends on the document language. For instance, in an XHTML DOCTYPE, they are case-sensitive, but in an HTML DOCTYPE, they are not.
Are Git File Names Case Sensitive?#
Git is case-insensitive by default.
Therefore, if we are not careful about file case sensitivity, we may encounter the following issues in practice:
- If someone in the team develops on a Linux system or a macOS or Windows with case-sensitive file systems, they might ignore an existing
RankItem.tsx
file and create a newrankItem.tsx
file, which would be successfully committed; - At this point, both
RankItem.tsx
andrankItem.tsx
files exist on the Git server, and developers on Windows or macOS (default file system) will not be able to pull both files correctly.
It is recommended to disable Git's case sensitivity feature:
git config --global core.ignorecase false
Also, when renaming files on Windows or macOS, use git mv
:
git mv --force rankItem.jsx RankItem.jsx
If this is not enabled, for example, changing readme.md
to README.md
, git status
will not detect the change:
However, using git mv
, it can be detected normally:
Therefore, during development, it is strongly recommended to disable Git's case sensitivity ignore configuration and use git mv
for renaming operations. If conditions permit, it is also advisable to change the default file system of macOS or Windows to a case-sensitive one.
The above summarizes some case sensitivity issues in frontend development:
- The keys of HTTP Headers are case-insensitive, but most frameworks convert the response header keys to lowercase. [RFC 7230]
- HTTP2 requires lowercase due to header compression, and browsers will convert them to lowercase. [RFC 7540]
- HTTP Methods are case-sensitive and must be uppercase. [RFC 7230]
- The protocol and host of URLs are case-insensitive, and browsers will automatically convert them to lowercase. [RFC 7230]
- The path of URLs is case-sensitive according to the specification, but whether it is case-sensitive in practice depends on the file system and server configuration. [RFC 7230]
- The query and fragment of URLs are case-sensitive. [RFC 7230]
- The keys of Cookies are case-sensitive, although the specification does not explicitly state this. [RFC 6265]
- The domain part of an email is case-insensitive. [RFC 1035]
- Whether the username part of an email is case-sensitive depends on the email service provider. [RFC 5321]
- HTML5 tag names and general attribute keys are case-insensitive. [HTML Live Standard 13.1]
- HTML5 data attributes are case-sensitive. [HTML Live Standard 3.2.6.6]
- CSS selector syntax is case-insensitive, while attribute names and values' case sensitivity depends on the document language. [CSS Selectors Level 3]
- Git is case-insensitive by default.
- Windows uses NTFS and FAT file systems, which are case-insensitive by default but case-preserving.
- macOS uses APFS and HFS+ file systems, which are case-insensitive by default but case-preserving.
- Linux uses ext3/ext4 file systems, which are case-sensitive.