mime_util.cc 35 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933
  1. // Copyright (c) 2012 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.
  4. #include <algorithm>
  5. #include <iterator>
  6. #include <map>
  7. #include <string>
  8. #include <unordered_set>
  9. #include "base/base64.h"
  10. #include "base/check_op.h"
  11. #include "base/containers/span.h"
  12. #include "base/lazy_instance.h"
  13. #include "base/rand_util.h"
  14. #include "base/strings/string_number_conversions.h"
  15. #include "base/strings/string_piece.h"
  16. #include "base/strings/string_split.h"
  17. #include "base/strings/string_util.h"
  18. #include "base/strings/utf_string_conversions.h"
  19. #include "build/build_config.h"
  20. #include "net/base/mime_util.h"
  21. #include "net/base/platform_mime_util.h"
  22. #include "net/http/http_util.h"
  23. using std::string;
  24. namespace net {
  25. // Singleton utility class for mime types.
  26. class MimeUtil : public PlatformMimeUtil {
  27. public:
  28. bool GetMimeTypeFromExtension(const base::FilePath::StringType& ext,
  29. std::string* mime_type) const;
  30. bool GetMimeTypeFromFile(const base::FilePath& file_path,
  31. std::string* mime_type) const;
  32. bool GetWellKnownMimeTypeFromExtension(const base::FilePath::StringType& ext,
  33. std::string* mime_type) const;
  34. bool GetPreferredExtensionForMimeType(
  35. const std::string& mime_type,
  36. base::FilePath::StringType* extension) const;
  37. bool MatchesMimeType(const std::string& mime_type_pattern,
  38. const std::string& mime_type) const;
  39. bool ParseMimeTypeWithoutParameter(base::StringPiece type_string,
  40. std::string* top_level_type,
  41. std::string* subtype) const;
  42. bool IsValidTopLevelMimeType(const std::string& type_string) const;
  43. private:
  44. friend struct base::LazyInstanceTraitsBase<MimeUtil>;
  45. MimeUtil();
  46. bool GetMimeTypeFromExtensionHelper(const base::FilePath::StringType& ext,
  47. bool include_platform_types,
  48. std::string* mime_type) const;
  49. }; // class MimeUtil
  50. // This variable is Leaky because we need to access it from WorkerPool threads.
  51. static base::LazyInstance<MimeUtil>::Leaky g_mime_util =
  52. LAZY_INSTANCE_INITIALIZER;
  53. struct MimeInfo {
  54. const char* const mime_type;
  55. // Comma-separated list of possible extensions for the type. The first
  56. // extension is considered preferred.
  57. const char* const extensions;
  58. };
  59. // How to use the MIME maps
  60. // ------------------------
  61. // READ THIS BEFORE MODIFYING THE MIME MAPPINGS BELOW.
  62. //
  63. // There are two hardcoded mappings from MIME types: kPrimaryMappings and
  64. // kSecondaryMappings.
  65. //
  66. // kPrimaryMappings:
  67. //
  68. // Use this for mappings that are critical to the web platform. Mappings you
  69. // add to this list take priority over the underlying platform when converting
  70. // from file extension -> MIME type. Thus file extensions listed here will
  71. // work consistently across platforms.
  72. //
  73. // kSecondaryMappings:
  74. //
  75. // Use this for mappings that must exist, but can be overridden by user
  76. // preferences.
  77. //
  78. // The following applies to both lists:
  79. //
  80. // * The same extension can appear multiple times in the same list under
  81. // different MIME types. Extensions that appear earlier take precedence over
  82. // those that appear later.
  83. //
  84. // * A MIME type must not appear more than once in a single list. It is valid
  85. // for the same MIME type to appear in kPrimaryMappings and
  86. // kSecondaryMappings.
  87. //
  88. // The MIME maps are used for three types of lookups:
  89. //
  90. // 1) MIME type -> file extension. Implemented as
  91. // GetPreferredExtensionForMimeType().
  92. //
  93. // Sources are consulted in the following order:
  94. //
  95. // a) As a special case application/octet-stream is mapped to nothing. Web
  96. // sites are supposed to use this MIME type to indicate that the content
  97. // is opaque and shouldn't be parsed as any specific type of content. It
  98. // doesn't make sense to map this to anything.
  99. //
  100. // b) The underlying platform. If the operating system has a mapping from
  101. // the MIME type to a file extension, then that takes priority. The
  102. // platform is assumed to represent the user's preference.
  103. //
  104. // c) kPrimaryMappings. Order doesn't matter since there should only be at
  105. // most one entry per MIME type.
  106. //
  107. // d) kSecondaryMappings. Again, order doesn't matter.
  108. //
  109. // 2) File extension -> MIME type. Implemented in GetMimeTypeFromExtension().
  110. //
  111. // Sources are considered in the following order:
  112. //
  113. // a) kPrimaryMappings. Order matters here since file extensions can appear
  114. // multiple times on these lists. The first mapping in order of
  115. // appearance in the list wins.
  116. //
  117. // b) Underlying platform.
  118. //
  119. // c) kSecondaryMappings. Again, the order matters.
  120. //
  121. // 3) File extension -> Well known MIME type. Implemented as
  122. // GetWellKnownMimeTypeFromExtension().
  123. //
  124. // This is similar to 2), with the exception that b) is skipped. I.e. Only
  125. // considers the hardcoded mappings in kPrimaryMappings and
  126. // kSecondaryMappings.
  127. // See comments above for details on how this list is used.
  128. static const MimeInfo kPrimaryMappings[] = {
  129. // Must precede audio/webm .
  130. {"video/webm", "webm"},
  131. // Must precede audio/mp3
  132. {"audio/mpeg", "mp3"},
  133. {"application/wasm", "wasm"},
  134. {"application/x-chrome-extension", "crx"},
  135. {"application/xhtml+xml", "xhtml,xht,xhtm"},
  136. {"audio/flac", "flac"},
  137. {"audio/mp3", "mp3"},
  138. {"audio/ogg", "ogg,oga,opus"},
  139. {"audio/wav", "wav"},
  140. {"audio/webm", "webm"},
  141. {"audio/x-m4a", "m4a"},
  142. {"image/avif", "avif"},
  143. {"image/gif", "gif"},
  144. {"image/jpeg", "jpeg,jpg"},
  145. {"image/jxl", "jxl"},
  146. {"image/png", "png"},
  147. {"image/apng", "png"},
  148. {"image/svg+xml", "svg,svgz"},
  149. {"image/webp", "webp"},
  150. {"multipart/related", "mht,mhtml"},
  151. {"text/css", "css"},
  152. {"text/html", "html,htm,shtml,shtm"},
  153. {"text/javascript", "js,mjs"},
  154. {"text/xml", "xml"},
  155. {"video/mp4", "mp4,m4v"},
  156. {"video/ogg", "ogv,ogm"},
  157. // This is a primary mapping (overrides the platform) rather than secondary
  158. // to work around an issue when Excel is installed on Windows. Excel
  159. // registers csv as application/vnd.ms-excel instead of text/csv from RFC
  160. // 4180. See https://crbug.com/139105.
  161. {"text/csv", "csv"},
  162. };
  163. // See comments above for details on how this list is used.
  164. static const MimeInfo kSecondaryMappings[] = {
  165. // Must precede image/vnd.microsoft.icon .
  166. {"image/x-icon", "ico"},
  167. {"application/epub+zip", "epub"},
  168. {"application/font-woff", "woff"},
  169. {"application/gzip", "gz,tgz"},
  170. {"application/javascript", "js"},
  171. {"application/json", "json"}, // Per http://www.ietf.org/rfc/rfc4627.txt.
  172. {"application/msword", "doc,dot"},
  173. {"application/octet-stream", "bin,exe,com"},
  174. {"application/pdf", "pdf"},
  175. {"application/pkcs7-mime", "p7m,p7c,p7z"},
  176. {"application/pkcs7-signature", "p7s"},
  177. {"application/postscript", "ps,eps,ai"},
  178. {"application/rdf+xml", "rdf"},
  179. {"application/rss+xml", "rss"},
  180. {"application/rtf", "rtf"},
  181. {"application/vnd.android.package-archive", "apk"},
  182. {"application/vnd.mozilla.xul+xml", "xul"},
  183. {"application/vnd.ms-excel", "xls"},
  184. {"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
  185. "xlsx"},
  186. {"application/vnd.openxmlformats-officedocument.wordprocessingml.document",
  187. "docx"},
  188. {"application/x-gzip", "gz,tgz"},
  189. {"application/x-mpegurl", "m3u8"},
  190. {"application/x-shockwave-flash", "swf,swl"},
  191. {"application/x-tar", "tar"},
  192. {"application/x-x509-ca-cert", "cer,crt"},
  193. {"application/zip", "zip"},
  194. // This is the platform mapping on recent versions of Windows 10.
  195. {"audio/webm", "weba"},
  196. {"image/bmp", "bmp"},
  197. {"image/jpeg", "jfif,pjpeg,pjp"},
  198. {"image/tiff", "tiff,tif"},
  199. {"image/vnd.microsoft.icon", "ico"},
  200. {"image/x-png", "png"},
  201. {"image/x-xbitmap", "xbm"},
  202. {"message/rfc822", "eml"},
  203. {"text/calendar", "ics"},
  204. {"text/html", "ehtml"},
  205. {"text/plain", "txt,text"},
  206. {"text/x-sh", "sh"},
  207. {"text/xml", "xsl,xbl,xslt"},
  208. {"video/mpeg", "mpeg,mpg"},
  209. };
  210. // Finds mime type of |ext| from |mappings|.
  211. template <size_t num_mappings>
  212. static const char* FindMimeType(const MimeInfo (&mappings)[num_mappings],
  213. const std::string& ext) {
  214. for (const auto& mapping : mappings) {
  215. const char* extensions = mapping.extensions;
  216. for (;;) {
  217. size_t end_pos = strcspn(extensions, ",");
  218. // The length check is required to prevent the StringPiece below from
  219. // including uninitialized memory if ext is longer than extensions.
  220. if (end_pos == ext.size() &&
  221. base::EqualsCaseInsensitiveASCII(
  222. base::StringPiece(extensions, ext.size()), ext)) {
  223. return mapping.mime_type;
  224. }
  225. extensions += end_pos;
  226. if (!*extensions)
  227. break;
  228. extensions += 1; // skip over comma
  229. }
  230. }
  231. return nullptr;
  232. }
  233. static base::FilePath::StringType StringToFilePathStringType(
  234. const base::StringPiece& string_piece) {
  235. #if BUILDFLAG(IS_WIN)
  236. return base::UTF8ToWide(string_piece);
  237. #else
  238. return std::string(string_piece);
  239. #endif
  240. }
  241. // Helper used in MimeUtil::GetPreferredExtensionForMimeType() to search
  242. // preferred extension in MimeInfo arrays.
  243. template <size_t num_mappings>
  244. static bool FindPreferredExtension(const MimeInfo (&mappings)[num_mappings],
  245. const std::string& mime_type,
  246. base::FilePath::StringType* result) {
  247. // There is no preferred extension for "application/octet-stream".
  248. if (mime_type == "application/octet-stream")
  249. return false;
  250. for (const auto& mapping : mappings) {
  251. if (mapping.mime_type == mime_type) {
  252. const char* extensions = mapping.extensions;
  253. const char* extension_end = strchr(extensions, ',');
  254. size_t len =
  255. extension_end ? extension_end - extensions : strlen(extensions);
  256. *result = StringToFilePathStringType(base::StringPiece(extensions, len));
  257. return true;
  258. }
  259. }
  260. return false;
  261. }
  262. bool MimeUtil::GetMimeTypeFromExtension(const base::FilePath::StringType& ext,
  263. string* result) const {
  264. return GetMimeTypeFromExtensionHelper(ext, true, result);
  265. }
  266. bool MimeUtil::GetWellKnownMimeTypeFromExtension(
  267. const base::FilePath::StringType& ext,
  268. string* result) const {
  269. return GetMimeTypeFromExtensionHelper(ext, false, result);
  270. }
  271. bool MimeUtil::GetPreferredExtensionForMimeType(
  272. const std::string& mime_type,
  273. base::FilePath::StringType* extension) const {
  274. // Search the MIME type in the platform DB first, then in kPrimaryMappings and
  275. // kSecondaryMappings.
  276. return GetPlatformPreferredExtensionForMimeType(mime_type, extension) ||
  277. FindPreferredExtension(kPrimaryMappings, mime_type, extension) ||
  278. FindPreferredExtension(kSecondaryMappings, mime_type, extension);
  279. }
  280. bool MimeUtil::GetMimeTypeFromFile(const base::FilePath& file_path,
  281. string* result) const {
  282. base::FilePath::StringType file_name_str = file_path.Extension();
  283. if (file_name_str.empty())
  284. return false;
  285. return GetMimeTypeFromExtension(file_name_str.substr(1), result);
  286. }
  287. bool MimeUtil::GetMimeTypeFromExtensionHelper(
  288. const base::FilePath::StringType& ext,
  289. bool include_platform_types,
  290. string* result) const {
  291. DCHECK(ext.empty() || ext[0] != '.')
  292. << "extension passed in must not include leading dot";
  293. // Avoids crash when unable to handle a long file path. See crbug.com/48733.
  294. const unsigned kMaxFilePathSize = 65536;
  295. if (ext.length() > kMaxFilePathSize)
  296. return false;
  297. // Reject a string which contains null character.
  298. base::FilePath::StringType::size_type nul_pos =
  299. ext.find(FILE_PATH_LITERAL('\0'));
  300. if (nul_pos != base::FilePath::StringType::npos)
  301. return false;
  302. // We implement the same algorithm as Mozilla for mapping a file extension to
  303. // a mime type. That is, we first check a hard-coded list (that cannot be
  304. // overridden), and then if not found there, we defer to the system registry.
  305. // Finally, we scan a secondary hard-coded list to catch types that we can
  306. // deduce but that we also want to allow the OS to override.
  307. base::FilePath path_ext(ext);
  308. const string ext_narrow_str = path_ext.AsUTF8Unsafe();
  309. const char* mime_type = FindMimeType(kPrimaryMappings, ext_narrow_str);
  310. if (mime_type) {
  311. *result = mime_type;
  312. return true;
  313. }
  314. if (include_platform_types && GetPlatformMimeTypeFromExtension(ext, result))
  315. return true;
  316. mime_type = FindMimeType(kSecondaryMappings, ext_narrow_str);
  317. if (mime_type) {
  318. *result = mime_type;
  319. return true;
  320. }
  321. return false;
  322. }
  323. MimeUtil::MimeUtil() = default;
  324. // Tests for MIME parameter equality. Each parameter in the |mime_type_pattern|
  325. // must be matched by a parameter in the |mime_type|. If there are no
  326. // parameters in the pattern, the match is a success.
  327. //
  328. // According rfc2045 keys of parameters are case-insensitive, while values may
  329. // or may not be case-sensitive, but they are usually case-sensitive. So, this
  330. // function matches values in *case-sensitive* manner, however note that this
  331. // may produce some false negatives.
  332. bool MatchesMimeTypeParameters(const std::string& mime_type_pattern,
  333. const std::string& mime_type) {
  334. typedef std::map<std::string, std::string> StringPairMap;
  335. const std::string::size_type semicolon = mime_type_pattern.find(';');
  336. const std::string::size_type test_semicolon = mime_type.find(';');
  337. if (semicolon != std::string::npos) {
  338. if (test_semicolon == std::string::npos)
  339. return false;
  340. base::StringPairs pattern_parameters;
  341. base::SplitStringIntoKeyValuePairs(mime_type_pattern.substr(semicolon + 1),
  342. '=', ';', &pattern_parameters);
  343. base::StringPairs test_parameters;
  344. base::SplitStringIntoKeyValuePairs(mime_type.substr(test_semicolon + 1),
  345. '=', ';', &test_parameters);
  346. // Put the parameters to maps with the keys converted to lower case.
  347. StringPairMap pattern_parameter_map;
  348. for (const auto& pair : pattern_parameters) {
  349. pattern_parameter_map[base::ToLowerASCII(pair.first)] = pair.second;
  350. }
  351. StringPairMap test_parameter_map;
  352. for (const auto& pair : test_parameters) {
  353. test_parameter_map[base::ToLowerASCII(pair.first)] = pair.second;
  354. }
  355. if (pattern_parameter_map.size() > test_parameter_map.size())
  356. return false;
  357. for (const auto& parameter_pair : pattern_parameter_map) {
  358. const auto& test_parameter_pair_it =
  359. test_parameter_map.find(parameter_pair.first);
  360. if (test_parameter_pair_it == test_parameter_map.end())
  361. return false;
  362. if (parameter_pair.second != test_parameter_pair_it->second)
  363. return false;
  364. }
  365. }
  366. return true;
  367. }
  368. // This comparison handles absolute maching and also basic
  369. // wildcards. The plugin mime types could be:
  370. // application/x-foo
  371. // application/*
  372. // application/*+xml
  373. // *
  374. // Also tests mime parameters -- all parameters in the pattern must be present
  375. // in the tested type for a match to succeed.
  376. bool MimeUtil::MatchesMimeType(const std::string& mime_type_pattern,
  377. const std::string& mime_type) const {
  378. if (mime_type_pattern.empty())
  379. return false;
  380. std::string::size_type semicolon = mime_type_pattern.find(';');
  381. const std::string base_pattern(mime_type_pattern.substr(0, semicolon));
  382. semicolon = mime_type.find(';');
  383. const std::string base_type(mime_type.substr(0, semicolon));
  384. if (base_pattern == "*" || base_pattern == "*/*")
  385. return MatchesMimeTypeParameters(mime_type_pattern, mime_type);
  386. const std::string::size_type star = base_pattern.find('*');
  387. if (star == std::string::npos) {
  388. if (base::EqualsCaseInsensitiveASCII(base_pattern, base_type))
  389. return MatchesMimeTypeParameters(mime_type_pattern, mime_type);
  390. else
  391. return false;
  392. }
  393. // Test length to prevent overlap between |left| and |right|.
  394. if (base_type.length() < base_pattern.length() - 1)
  395. return false;
  396. base::StringPiece base_pattern_piece(base_pattern);
  397. base::StringPiece left(base_pattern_piece.substr(0, star));
  398. base::StringPiece right(base_pattern_piece.substr(star + 1));
  399. if (!base::StartsWith(base_type, left, base::CompareCase::INSENSITIVE_ASCII))
  400. return false;
  401. if (!right.empty() &&
  402. !base::EndsWith(base_type, right, base::CompareCase::INSENSITIVE_ASCII))
  403. return false;
  404. return MatchesMimeTypeParameters(mime_type_pattern, mime_type);
  405. }
  406. bool ParseMimeType(const std::string& type_str,
  407. std::string* mime_type,
  408. base::StringPairs* params) {
  409. // Trim leading and trailing whitespace from type. We include '(' in
  410. // the trailing trim set to catch media-type comments, which are not at all
  411. // standard, but may occur in rare cases.
  412. size_t type_val = type_str.find_first_not_of(HTTP_LWS);
  413. type_val = std::min(type_val, type_str.length());
  414. size_t type_end = type_str.find_first_of(HTTP_LWS ";(", type_val);
  415. if (type_end == std::string::npos)
  416. type_end = type_str.length();
  417. // Reject a mime-type if it does not include a slash.
  418. size_t slash_pos = type_str.find_first_of('/');
  419. if (slash_pos == std::string::npos || slash_pos > type_end)
  420. return false;
  421. if (mime_type)
  422. *mime_type = type_str.substr(type_val, type_end - type_val);
  423. // Iterate over parameters. Can't split the string around semicolons
  424. // preemptively because quoted strings may include semicolons. Mostly matches
  425. // logic in https://mimesniff.spec.whatwg.org/. Main differences: Does not
  426. // validate characters are HTTP token code points / HTTP quoted-string token
  427. // code points, and ignores spaces after "=" in parameters.
  428. if (params)
  429. params->clear();
  430. std::string::size_type offset = type_str.find_first_of(';', type_end);
  431. while (offset < type_str.size()) {
  432. DCHECK_EQ(';', type_str[offset]);
  433. // Trim off the semicolon.
  434. ++offset;
  435. // Trim off any following spaces.
  436. offset = type_str.find_first_not_of(HTTP_LWS, offset);
  437. std::string::size_type param_name_start = offset;
  438. // Extend parameter name until run into a semicolon or equals sign. Per
  439. // spec, trailing spaces are not removed.
  440. offset = type_str.find_first_of(";=", offset);
  441. // Nothing more to do if at end of string, or if there's no parameter
  442. // value, since names without values aren't allowed.
  443. if (offset == std::string::npos || type_str[offset] == ';')
  444. continue;
  445. auto param_name = base::MakeStringPiece(type_str.begin() + param_name_start,
  446. type_str.begin() + offset);
  447. // Now parse the value.
  448. DCHECK_EQ('=', type_str[offset]);
  449. // Trim off the '='.
  450. offset++;
  451. // Remove leading spaces. This violates the spec, though it matches
  452. // pre-existing behavior.
  453. //
  454. // TODO(mmenke): Consider doing this (only?) after parsing quotes, which
  455. // seems to align more with the spec - not the content-type spec, but the
  456. // GET spec's way of getting an encoding, and the spec for handling
  457. // boundary values as well.
  458. // See https://encoding.spec.whatwg.org/#names-and-labels.
  459. offset = type_str.find_first_not_of(HTTP_LWS, offset);
  460. std::string param_value;
  461. if (offset == std::string::npos || type_str[offset] == ';') {
  462. // Nothing to do here - an unquoted string of only whitespace should be
  463. // skipped.
  464. continue;
  465. } else if (type_str[offset] != '"') {
  466. // If the first character is not a quotation mark, copy data directly.
  467. std::string::size_type value_start = offset;
  468. offset = type_str.find_first_of(';', offset);
  469. std::string::size_type value_end = offset;
  470. // Remove terminal whitespace. If ran off the end of the string, have to
  471. // update |value_end| first.
  472. if (value_end == std::string::npos)
  473. value_end = type_str.size();
  474. while (value_end > value_start &&
  475. HttpUtil::IsLWS(type_str[value_end - 1])) {
  476. --value_end;
  477. }
  478. param_value = type_str.substr(value_start, value_end - value_start);
  479. } else {
  480. // Otherwise, append data, with special handling for backslashes, until
  481. // a close quote. Do not trim whitespace for quoted-string.
  482. // Skip open quote.
  483. DCHECK_EQ('"', type_str[offset]);
  484. ++offset;
  485. while (offset < type_str.size() && type_str[offset] != '"') {
  486. // Skip over backslash and append the next character, when not at
  487. // the end of the string. Otherwise, copy the next character (Which may
  488. // be a backslash).
  489. if (type_str[offset] == '\\' && offset + 1 < type_str.size()) {
  490. ++offset;
  491. }
  492. param_value += type_str[offset];
  493. ++offset;
  494. }
  495. offset = type_str.find_first_of(';', offset);
  496. }
  497. if (params)
  498. params->emplace_back(param_name, param_value);
  499. }
  500. return true;
  501. }
  502. bool MimeUtil::ParseMimeTypeWithoutParameter(base::StringPiece type_string,
  503. std::string* top_level_type,
  504. std::string* subtype) const {
  505. std::vector<base::StringPiece> components = base::SplitStringPiece(
  506. type_string, "/", base::KEEP_WHITESPACE, base::SPLIT_WANT_ALL);
  507. if (components.size() != 2)
  508. return false;
  509. components[0] = TrimWhitespaceASCII(components[0], base::TRIM_LEADING);
  510. components[1] = TrimWhitespaceASCII(components[1], base::TRIM_TRAILING);
  511. if (!HttpUtil::IsToken(components[0]) || !HttpUtil::IsToken(components[1]))
  512. return false;
  513. if (top_level_type)
  514. top_level_type->assign(std::string(components[0]));
  515. if (subtype)
  516. subtype->assign(std::string(components[1]));
  517. return true;
  518. }
  519. // See https://www.iana.org/assignments/media-types/media-types.xhtml
  520. static const char* const kLegalTopLevelTypes[] = {
  521. "application", "audio", "example", "font", "image",
  522. "message", "model", "multipart", "text", "video",
  523. };
  524. bool MimeUtil::IsValidTopLevelMimeType(const std::string& type_string) const {
  525. std::string lower_type = base::ToLowerASCII(type_string);
  526. for (const char* const legal_type : kLegalTopLevelTypes) {
  527. if (lower_type.compare(legal_type) == 0)
  528. return true;
  529. }
  530. return type_string.size() > 2 &&
  531. base::StartsWith(type_string, "x-",
  532. base::CompareCase::INSENSITIVE_ASCII);
  533. }
  534. //----------------------------------------------------------------------------
  535. // Wrappers for the singleton
  536. //----------------------------------------------------------------------------
  537. bool GetMimeTypeFromExtension(const base::FilePath::StringType& ext,
  538. std::string* mime_type) {
  539. return g_mime_util.Get().GetMimeTypeFromExtension(ext, mime_type);
  540. }
  541. bool GetMimeTypeFromFile(const base::FilePath& file_path,
  542. std::string* mime_type) {
  543. return g_mime_util.Get().GetMimeTypeFromFile(file_path, mime_type);
  544. }
  545. bool GetWellKnownMimeTypeFromExtension(const base::FilePath::StringType& ext,
  546. std::string* mime_type) {
  547. return g_mime_util.Get().GetWellKnownMimeTypeFromExtension(ext, mime_type);
  548. }
  549. bool GetPreferredExtensionForMimeType(const std::string& mime_type,
  550. base::FilePath::StringType* extension) {
  551. return g_mime_util.Get().GetPreferredExtensionForMimeType(mime_type,
  552. extension);
  553. }
  554. bool MatchesMimeType(const std::string& mime_type_pattern,
  555. const std::string& mime_type) {
  556. return g_mime_util.Get().MatchesMimeType(mime_type_pattern, mime_type);
  557. }
  558. bool ParseMimeTypeWithoutParameter(base::StringPiece type_string,
  559. std::string* top_level_type,
  560. std::string* subtype) {
  561. return g_mime_util.Get().ParseMimeTypeWithoutParameter(
  562. type_string, top_level_type, subtype);
  563. }
  564. bool IsValidTopLevelMimeType(const std::string& type_string) {
  565. return g_mime_util.Get().IsValidTopLevelMimeType(type_string);
  566. }
  567. namespace {
  568. // From http://www.w3schools.com/media/media_mimeref.asp and
  569. // http://plugindoc.mozdev.org/winmime.php
  570. static const char* const kStandardImageTypes[] = {"image/avif",
  571. "image/bmp",
  572. "image/cis-cod",
  573. "image/gif",
  574. "image/ief",
  575. "image/jpeg",
  576. "image/jxl",
  577. "image/webp",
  578. "image/pict",
  579. "image/pipeg",
  580. "image/png",
  581. "image/svg+xml",
  582. "image/tiff",
  583. "image/vnd.microsoft.icon",
  584. "image/x-cmu-raster",
  585. "image/x-cmx",
  586. "image/x-icon",
  587. "image/x-portable-anymap",
  588. "image/x-portable-bitmap",
  589. "image/x-portable-graymap",
  590. "image/x-portable-pixmap",
  591. "image/x-rgb",
  592. "image/x-xbitmap",
  593. "image/x-xpixmap",
  594. "image/x-xwindowdump"};
  595. static const char* const kStandardAudioTypes[] = {
  596. "audio/aac",
  597. "audio/aiff",
  598. "audio/amr",
  599. "audio/basic",
  600. "audio/flac",
  601. "audio/midi",
  602. "audio/mp3",
  603. "audio/mp4",
  604. "audio/mpeg",
  605. "audio/mpeg3",
  606. "audio/ogg",
  607. "audio/vorbis",
  608. "audio/wav",
  609. "audio/webm",
  610. "audio/x-m4a",
  611. "audio/x-ms-wma",
  612. "audio/vnd.rn-realaudio",
  613. "audio/vnd.wave"
  614. };
  615. // https://tools.ietf.org/html/rfc8081
  616. static const char* const kStandardFontTypes[] = {
  617. "font/collection", "font/otf", "font/sfnt",
  618. "font/ttf", "font/woff", "font/woff2",
  619. };
  620. static const char* const kStandardVideoTypes[] = {
  621. "video/avi",
  622. "video/divx",
  623. "video/flc",
  624. "video/mp4",
  625. "video/mpeg",
  626. "video/ogg",
  627. "video/quicktime",
  628. "video/sd-video",
  629. "video/webm",
  630. "video/x-dv",
  631. "video/x-m4v",
  632. "video/x-mpeg",
  633. "video/x-ms-asf",
  634. "video/x-ms-wmv"
  635. };
  636. struct StandardType {
  637. const char* const leading_mime_type;
  638. base::span<const char* const> standard_types;
  639. };
  640. static const StandardType kStandardTypes[] = {{"image/", kStandardImageTypes},
  641. {"audio/", kStandardAudioTypes},
  642. {"font/", kStandardFontTypes},
  643. {"video/", kStandardVideoTypes},
  644. {nullptr, {}}};
  645. // GetExtensionsFromHardCodedMappings() adds file extensions (without a leading
  646. // dot) to the set |extensions|, for all MIME types matching |mime_type|.
  647. //
  648. // The meaning of |mime_type| depends on the value of |prefix_match|:
  649. //
  650. // * If |prefix_match = false| then |mime_type| is an exact (case-insensitive)
  651. // string such as "text/plain".
  652. //
  653. // * If |prefix_match = true| then |mime_type| is treated as the prefix for a
  654. // (case-insensitive) string. For instance "Text/" would match "text/plain".
  655. void GetExtensionsFromHardCodedMappings(
  656. base::span<const MimeInfo> mappings,
  657. const std::string& mime_type,
  658. bool prefix_match,
  659. std::unordered_set<base::FilePath::StringType>* extensions) {
  660. for (const auto& mapping : mappings) {
  661. base::StringPiece cur_mime_type(mapping.mime_type);
  662. if (base::StartsWith(cur_mime_type, mime_type,
  663. base::CompareCase::INSENSITIVE_ASCII) &&
  664. (prefix_match || (cur_mime_type.length() == mime_type.length()))) {
  665. for (const base::StringPiece& this_extension : base::SplitStringPiece(
  666. mapping.extensions, ",", base::TRIM_WHITESPACE,
  667. base::SPLIT_WANT_ALL)) {
  668. extensions->insert(StringToFilePathStringType(this_extension));
  669. }
  670. }
  671. }
  672. }
  673. void GetExtensionsHelper(
  674. base::span<const char* const> standard_types,
  675. const std::string& leading_mime_type,
  676. std::unordered_set<base::FilePath::StringType>* extensions) {
  677. for (auto* standard_type : standard_types) {
  678. g_mime_util.Get().GetPlatformExtensionsForMimeType(standard_type,
  679. extensions);
  680. }
  681. // Also look up the extensions from hard-coded mappings in case that some
  682. // supported extensions are not registered in the system registry, like ogg.
  683. GetExtensionsFromHardCodedMappings(kPrimaryMappings, leading_mime_type, true,
  684. extensions);
  685. GetExtensionsFromHardCodedMappings(kSecondaryMappings, leading_mime_type,
  686. true, extensions);
  687. }
  688. // Note that the elements in the source set will be appended to the target
  689. // vector.
  690. template <class T>
  691. void UnorderedSetToVector(std::unordered_set<T>* source,
  692. std::vector<T>* target) {
  693. size_t old_target_size = target->size();
  694. target->resize(old_target_size + source->size());
  695. size_t i = 0;
  696. for (auto iter = source->begin(); iter != source->end(); ++iter, ++i)
  697. (*target)[old_target_size + i] = *iter;
  698. }
  699. // Characters to be used for mime multipart boundary.
  700. //
  701. // TODO(rsleevi): crbug.com/575779: Follow the spec or fix the spec.
  702. // The RFC 2046 spec says the alphanumeric characters plus the
  703. // following characters are legal for boundaries: '()+_,-./:=?
  704. // However the following characters, though legal, cause some sites
  705. // to fail: (),./:=+
  706. constexpr base::StringPiece kMimeBoundaryCharacters(
  707. "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ");
  708. // Size of mime multipart boundary.
  709. const size_t kMimeBoundarySize = 69;
  710. } // namespace
  711. void GetExtensionsForMimeType(
  712. const std::string& unsafe_mime_type,
  713. std::vector<base::FilePath::StringType>* extensions) {
  714. if (unsafe_mime_type == "*/*" || unsafe_mime_type == "*")
  715. return;
  716. const std::string mime_type = base::ToLowerASCII(unsafe_mime_type);
  717. std::unordered_set<base::FilePath::StringType> unique_extensions;
  718. if (base::EndsWith(mime_type, "/*", base::CompareCase::INSENSITIVE_ASCII)) {
  719. std::string leading_mime_type = mime_type.substr(0, mime_type.length() - 1);
  720. // Find the matching StandardType from within kStandardTypes, or fall
  721. // through to the last (default) StandardType.
  722. const StandardType* type = nullptr;
  723. for (const StandardType& standard_type : kStandardTypes) {
  724. type = &standard_type;
  725. if (type->leading_mime_type &&
  726. leading_mime_type == type->leading_mime_type) {
  727. break;
  728. }
  729. }
  730. DCHECK(type);
  731. GetExtensionsHelper(type->standard_types,
  732. leading_mime_type,
  733. &unique_extensions);
  734. } else {
  735. g_mime_util.Get().GetPlatformExtensionsForMimeType(mime_type,
  736. &unique_extensions);
  737. // Also look up the extensions from hard-coded mappings in case that some
  738. // supported extensions are not registered in the system registry, like ogg.
  739. GetExtensionsFromHardCodedMappings(kPrimaryMappings, mime_type, false,
  740. &unique_extensions);
  741. GetExtensionsFromHardCodedMappings(kSecondaryMappings, mime_type, false,
  742. &unique_extensions);
  743. }
  744. UnorderedSetToVector(&unique_extensions, extensions);
  745. }
  746. NET_EXPORT std::string GenerateMimeMultipartBoundary() {
  747. // Based on RFC 1341, section "7.2.1 Multipart: The common syntax":
  748. // Because encapsulation boundaries must not appear in the body parts being
  749. // encapsulated, a user agent must exercise care to choose a unique
  750. // boundary. The boundary in the example above could have been the result of
  751. // an algorithm designed to produce boundaries with a very low probability
  752. // of already existing in the data to be encapsulated without having to
  753. // prescan the data.
  754. // [...]
  755. // the boundary parameter [...] consists of 1 to 70 characters from a set of
  756. // characters known to be very robust through email gateways, and NOT ending
  757. // with white space.
  758. // [...]
  759. // boundary := 0*69<bchars> bcharsnospace
  760. // bchars := bcharsnospace / " "
  761. // bcharsnospace := DIGIT / ALPHA / "'" / "(" / ")" / "+" /
  762. // "_" / "," / "-" / "." / "/" / ":" / "=" / "?"
  763. std::string result;
  764. result.reserve(kMimeBoundarySize);
  765. result.append("----MultipartBoundary--");
  766. while (result.size() < (kMimeBoundarySize - 4)) {
  767. char c = kMimeBoundaryCharacters[base::RandInt(
  768. 0, kMimeBoundaryCharacters.size() - 1)];
  769. result.push_back(c);
  770. }
  771. result.append("----");
  772. // Not a strict requirement - documentation only.
  773. DCHECK_EQ(kMimeBoundarySize, result.size());
  774. return result;
  775. }
  776. void AddMultipartValueForUpload(const std::string& value_name,
  777. const std::string& value,
  778. const std::string& mime_boundary,
  779. const std::string& content_type,
  780. std::string* post_data) {
  781. DCHECK(post_data);
  782. // First line is the boundary.
  783. post_data->append("--" + mime_boundary + "\r\n");
  784. // Next line is the Content-disposition.
  785. post_data->append("Content-Disposition: form-data; name=\"" +
  786. value_name + "\"\r\n");
  787. if (!content_type.empty()) {
  788. // If Content-type is specified, the next line is that.
  789. post_data->append("Content-Type: " + content_type + "\r\n");
  790. }
  791. // Leave an empty line and append the value.
  792. post_data->append("\r\n" + value + "\r\n");
  793. }
  794. void AddMultipartValueForUploadWithFileName(const std::string& value_name,
  795. const std::string& file_name,
  796. const std::string& value,
  797. const std::string& mime_boundary,
  798. const std::string& content_type,
  799. std::string* post_data) {
  800. DCHECK(post_data);
  801. // First line is the boundary.
  802. post_data->append("--" + mime_boundary + "\r\n");
  803. // Next line is the Content-disposition.
  804. post_data->append("Content-Disposition: form-data; name=\"" + value_name +
  805. "\"; filename=\"" + file_name + "\"\r\n");
  806. if (!content_type.empty()) {
  807. // If Content-type is specified, the next line is that.
  808. post_data->append("Content-Type: " + content_type + "\r\n");
  809. }
  810. // Leave an empty line and append the value.
  811. post_data->append("\r\n" + value + "\r\n");
  812. }
  813. void AddMultipartFinalDelimiterForUpload(const std::string& mime_boundary,
  814. std::string* post_data) {
  815. DCHECK(post_data);
  816. post_data->append("--" + mime_boundary + "--\r\n");
  817. }
  818. } // namespace net