/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* vim: set ts=8 sts=2 et sw=2 tw=80: */ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "MimeType.h" #include "nsUnicharUtils.h" namespace { template constexpr bool IsHTTPTokenPoint(Char aChar) { using UnsignedChar = typename mozilla::detail::MakeUnsignedChar::Type; auto c = static_cast(aChar); return c == '!' || c == '#' || c == '$' || c == '%' || c == '&' || c == '\'' || c == '*' || c == '+' || c == '-' || c == '.' || c == '^' || c == '_' || c == '`' || c == '|' || c == '~' || mozilla::IsAsciiAlphanumeric(c); } template constexpr bool IsHTTPQuotedStringTokenPoint(Char aChar) { using UnsignedChar = typename mozilla::detail::MakeUnsignedChar::Type; auto c = static_cast(aChar); return c == 0x9 || (c >= ' ' && c <= '~') || mozilla::IsNonAsciiLatin1(c); } template constexpr bool IsHTTPWhitespace(Char aChar) { using UnsignedChar = typename mozilla::detail::MakeUnsignedChar::Type; auto c = static_cast(aChar); return c == 0x9 || c == 0xA || c == 0xD || c == 0x20; } } // namespace template /* static */ mozilla::UniquePtr> TMimeType::Parse(const nsTSubstring& aMimeType) { // See https://mimesniff.spec.whatwg.org/#parsing-a-mime-type // Steps 1-2 const char_type* pos = aMimeType.BeginReading(); const char_type* end = aMimeType.EndReading(); while (pos < end && IsHTTPWhitespace(*pos)) { ++pos; } if (pos == end) { return nullptr; } while (end > pos && IsHTTPWhitespace(*(end - 1))) { --end; } // Steps 3-4 const char_type* typeStart = pos; while (pos < end && *pos != '/') { if (!IsHTTPTokenPoint(*pos)) { return nullptr; } ++pos; } const char_type* typeEnd = pos; if (typeStart == typeEnd) { return nullptr; } // Step 5 if (pos == end) { return nullptr; } // Step 6 ++pos; // Step 7-9 const char_type* subtypeStart = pos; const char_type* subtypeEnd = nullptr; while (pos < end && *pos != ';') { if (!IsHTTPTokenPoint(*pos)) { // If we hit a whitespace, check that the rest of // the subtype is whitespace, otherwise fail. if (IsHTTPWhitespace(*pos)) { subtypeEnd = pos; ++pos; while (pos < end && *pos != ';') { if (!IsHTTPWhitespace(*pos)) { return nullptr; } ++pos; } break; } return nullptr; } ++pos; } if (subtypeEnd == nullptr) { subtypeEnd = pos; } if (subtypeStart == subtypeEnd) { return nullptr; } // Step 10 nsTString type; nsTString subtype; for (const char_type* c = typeStart; c < typeEnd; ++c) { type.Append(ToLowerCaseASCII(*c)); } for (const char_type* c = subtypeStart; c < subtypeEnd; ++c) { subtype.Append(ToLowerCaseASCII(*c)); } mozilla::UniquePtr> mimeType( mozilla::MakeUnique>(type, subtype)); // Step 11 while (pos < end) { // Step 11.1 ++pos; // Step 11.2 while (pos < end && IsHTTPWhitespace(*pos)) { ++pos; } // Steps 11.3 and 11.4 nsTString paramName; bool paramNameHadInvalidChars = false; while (pos < end && *pos != ';' && *pos != '=') { if (!IsHTTPTokenPoint(*pos)) { paramNameHadInvalidChars = true; } paramName.Append(ToLowerCaseASCII(*pos)); ++pos; } // Step 11.5 if (pos < end) { if (*pos == ';') { continue; } ++pos; } // Step 11.6 if (pos == end) { break; } // Step 11.7 ParameterValue paramValue; bool paramValueHadInvalidChars = false; // Step 11.8 if (*pos == '"') { // Step 11.8.1 ++pos; // Step 11.8.2 while (true) { // Step 11.8.2.1 while (pos < end && *pos != '"' && *pos != '\\') { if (!IsHTTPQuotedStringTokenPoint(*pos)) { paramValueHadInvalidChars = true; } if (!IsHTTPTokenPoint(*pos)) { paramValue.mRequiresQuoting = true; } paramValue.Append(*pos); ++pos; } // Step 11.8.2.2 if (pos < end && *pos == '\\') { // Step 11.8.2.2.1 ++pos; // Step 11.8.2.2.2 if (pos < end) { if (!IsHTTPQuotedStringTokenPoint(*pos)) { paramValueHadInvalidChars = true; } if (!IsHTTPTokenPoint(*pos)) { paramValue.mRequiresQuoting = true; } paramValue.Append(*pos); ++pos; continue; } // Step 11.8.2.2.3 paramValue.Append('\\'); paramValue.mRequiresQuoting = true; } // Step 11.8.2.3 break; } // Step 11.8.3 while (pos < end && *pos != ';') { ++pos; } // Step 11.9 } else { // Step 11.9.1 const char_type* paramValueStart = pos; while (pos < end && *pos != ';') { ++pos; } // Step 11.9.2 const char_type* paramValueLastChar = pos - 1; while (paramValueLastChar >= paramValueStart && IsHTTPWhitespace(*paramValueLastChar)) { --paramValueLastChar; } // Step 11.9.3 if (paramValueStart > paramValueLastChar) { continue; } for (const char_type* c = paramValueStart; c <= paramValueLastChar; ++c) { if (!IsHTTPQuotedStringTokenPoint(*c)) { paramValueHadInvalidChars = true; } if (!IsHTTPTokenPoint(*c)) { paramValue.mRequiresQuoting = true; } paramValue.Append(*c); } } // Step 11.10 if (!paramName.IsEmpty() && !paramNameHadInvalidChars && !paramValueHadInvalidChars) { // XXX Is the assigned value used anywhere? paramValue = mimeType->mParameters.LookupOrInsertWith(paramName, [&] { mimeType->mParameterNames.AppendElement(paramName); return paramValue; }); } } // Step 12 return mimeType; } template void TMimeType::Serialize(nsTSubstring& aOutput) const { aOutput.Assign(mType); aOutput.AppendLiteral("/"); aOutput.Append(mSubtype); for (uint32_t i = 0; i < mParameterNames.Length(); i++) { auto name = mParameterNames[i]; aOutput.AppendLiteral(";"); aOutput.Append(name); aOutput.AppendLiteral("="); GetParameterValue(name, aOutput, true); } } template void TMimeType::GetFullType(nsTSubstring& aOutput) const { aOutput.Assign(mType); aOutput.AppendLiteral("/"); aOutput.Append(mSubtype); } template bool TMimeType::HasParameter( const nsTSubstring& aName) const { return mParameters.Get(aName, nullptr); } template bool TMimeType::GetParameterValue( const nsTSubstring& aName, nsTSubstring& aOutput, bool aAppend) const { if (!aAppend) { aOutput.Truncate(); } ParameterValue value; if (!mParameters.Get(aName, &value)) { return false; } if (value.mRequiresQuoting || value.IsEmpty()) { aOutput.AppendLiteral("\""); const char_type* vcur = value.BeginReading(); const char_type* vend = value.EndReading(); while (vcur < vend) { if (*vcur == '"' || *vcur == '\\') { aOutput.AppendLiteral("\\"); } aOutput.Append(*vcur); vcur++; } aOutput.AppendLiteral("\""); } else { aOutput.Append(value); } return true; } template void TMimeType::SetParameterValue( const nsTSubstring& aName, const nsTSubstring& aValue) { mParameters.WithEntryHandle(aName, [&](auto&& entry) { if (!entry) { mParameterNames.AppendElement(aName); } ParameterValue value; value.Append(aValue); entry.InsertOrUpdate(std::move(value)); }); } template mozilla::UniquePtr> TMimeType::Parse( const nsTSubstring& aMimeType); template mozilla::UniquePtr> TMimeType::Parse( const nsTSubstring& aMimeType); template void TMimeType::Serialize( nsTSubstring& aOutput) const; template void TMimeType::Serialize(nsTSubstring& aOutput) const; template void TMimeType::GetFullType( nsTSubstring& aOutput) const; template void TMimeType::GetFullType(nsTSubstring& aOutput) const; template bool TMimeType::HasParameter( const nsTSubstring& aName) const; template bool TMimeType::HasParameter( const nsTSubstring& aName) const; template bool TMimeType::GetParameterValue( const nsTSubstring& aName, nsTSubstring& aOutput, bool aAppend) const; template bool TMimeType::GetParameterValue( const nsTSubstring& aName, nsTSubstring& aOutput, bool aAppend) const; template void TMimeType::SetParameterValue( const nsTSubstring& aName, const nsTSubstring& aValue); template void TMimeType::SetParameterValue( const nsTSubstring& aName, const nsTSubstring& aValue);