/* -*- 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 "nsNetUtil.h" #include "nsUnicharUtils.h" 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 && NS_IsHTTPWhitespace(*pos)) { ++pos; } if (pos == end) { return nullptr; } while (end > pos && NS_IsHTTPWhitespace(*(end - 1))) { --end; } // Steps 3-4 const char_type* typeStart = pos; while (pos < end && *pos != '/') { if (!NS_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 (!NS_IsHTTPTokenPoint(*pos)) { // If we hit a whitespace, check that the rest of // the subtype is whitespace, otherwise fail. if (NS_IsHTTPWhitespace(*pos)) { subtypeEnd = pos; ++pos; while (pos < end && *pos != ';') { if (!NS_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 && NS_IsHTTPWhitespace(*pos)) { ++pos; } const char_type* namePos = pos; // Steps 11.3 and 11.4 nsTString paramName; bool paramNameHadInvalidChars = false; while (pos < end && *pos != ';' && *pos != '=') { if (!NS_IsHTTPTokenPoint(*pos)) { paramNameHadInvalidChars = true; } paramName.Append(ToLowerCaseASCII(*pos)); ++pos; } // Might as well check for base64 now if (*pos != '=') { // trim leading and trailing spaces while (namePos < pos && NS_IsHTTPWhitespace(*namePos)) { ++namePos; } if (namePos < pos && ToLowerCaseASCII(*namePos) == 'b' && ++namePos < pos && ToLowerCaseASCII(*namePos) == 'a' && ++namePos < pos && ToLowerCaseASCII(*namePos) == 's' && ++namePos < pos && ToLowerCaseASCII(*namePos) == 'e' && ++namePos < pos && ToLowerCaseASCII(*namePos) == '6' && ++namePos < pos && ToLowerCaseASCII(*namePos) == '4') { while (++namePos < pos && NS_IsHTTPWhitespace(*namePos)) { } mimeType->mIsBase64 = namePos == 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 (!NS_IsHTTPQuotedStringTokenPoint(*pos)) { paramValueHadInvalidChars = true; } if (!NS_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 (!NS_IsHTTPQuotedStringTokenPoint(*pos)) { paramValueHadInvalidChars = true; } if (!NS_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 && NS_IsHTTPWhitespace(*paramValueLastChar)) { --paramValueLastChar; } // Step 11.9.3 if (paramValueStart > paramValueLastChar) { continue; } for (const char_type* c = paramValueStart; c <= paramValueLastChar; ++c) { if (!NS_IsHTTPQuotedStringTokenPoint(*c)) { paramValueHadInvalidChars = true; } if (!NS_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 /* static */ nsTArray> TMimeType::SplitMimetype(const nsTSubstring& aMimeType) { nsTArray> mimeTypeParts; bool inQuotes = false; size_t start = 0; for (size_t i = 0; i < aMimeType.Length(); i++) { char_type c = aMimeType[i]; if (c == '\"' && (i == 0 || aMimeType[i - 1] != '\\')) { inQuotes = !inQuotes; } else if (c == ',' && !inQuotes) { mimeTypeParts.AppendElement(Substring(aMimeType, start, i - start)); start = i + 1; } } if (start < aMimeType.Length()) { mimeTypeParts.AppendElement(Substring(aMimeType, start)); } return mimeTypeParts; } template /* static */ bool TMimeType::Parse( const nsTSubstring& aMimeType, nsTSubstring& aOutEssence, nsTSubstring& aOutCharset) { static char_type kCHARSET[] = {'c', 'h', 'a', 'r', 's', 'e', 't'}; static nsTDependentSubstring kCharset(kCHARSET, 7); mozilla::UniquePtr> parsed; nsTAutoString prevContentType; nsTAutoString prevCharset; prevContentType.Assign(aOutEssence); prevCharset.Assign(aOutCharset); nsTArray> mimeTypeParts = SplitMimetype(aMimeType); for (auto& mimeTypeString : mimeTypeParts) { if (mimeTypeString.EqualsLiteral("error")) { continue; } parsed = Parse(mimeTypeString); if (!parsed) { aOutEssence.Truncate(); aOutCharset.Truncate(); return false; } parsed->GetEssence(aOutEssence); if (aOutEssence.EqualsLiteral("*/*")) { aOutEssence.Assign(prevContentType); continue; } bool eq = !prevContentType.IsEmpty() && aOutEssence.Equals(prevContentType); if (!eq) { prevContentType.Assign(aOutEssence); } bool typeHasCharset = false; if (parsed->GetParameterValue(kCharset, aOutCharset, false, false)) { typeHasCharset = true; } else if (eq) { aOutCharset.Assign(prevCharset); } if ((!eq && !prevCharset.IsEmpty()) || typeHasCharset) { prevCharset.Assign(aOutCharset); } } return true; } 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::GetEssence(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, bool aWithQuotes) const { if (!aAppend) { aOutput.Truncate(); } ParameterValue value; if (!mParameters.Get(aName, &value)) { return false; } if (aWithQuotes && (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 bool TMimeType::Parse( const nsTSubstring& aMimeType, nsTSubstring& aOutEssence, nsTSubstring& aOutCharset); template bool TMimeType::Parse(const nsTSubstring& aMimeType, nsTSubstring& aOutEssence, nsTSubstring& aOutCharset); template nsTArray> TMimeType::SplitMimetype( const nsTSubstring& aMimeType); template void TMimeType::Serialize( nsTSubstring& aOutput) const; template void TMimeType::Serialize(nsTSubstring& aOutput) const; template void TMimeType::GetEssence( nsTSubstring& aOutput) const; template void TMimeType::GetEssence(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, bool aWithQuotes) const; template bool TMimeType::GetParameterValue( const nsTSubstring& aName, nsTSubstring& aOutput, bool aAppend, bool aWithQuotes) const; template void TMimeType::SetParameterValue( const nsTSubstring& aName, const nsTSubstring& aValue); template void TMimeType::SetParameterValue( const nsTSubstring& aName, const nsTSubstring& aValue);