Stroika Library 3.0d23x
 
Loading...
Searching...
No Matches
InternetMediaTypeRegistry.cpp
1/*
2 * Copyright(c) Sophist Solutions, Inc. 1990-2026. All rights reserved
3 */
4#include "Stroika/Foundation/StroikaPreComp.h"
5
6#include <filesystem>
7
8#include "Stroika/Foundation/Cache/SynchronizedLRUCache.h"
13#if qStroika_Foundation_Common_Platform_Windows
14#include "Stroika/Foundation/Common/Platform/Windows/Registry.h"
15#endif
17#if qStroika_Foundation_Common_Platform_Windows
18#include "Stroika/Foundation/Execution/Platform/Windows/Exception.h"
19#endif
23
25
26using namespace Stroika::Foundation;
30using namespace Stroika::Foundation::Execution;
31
32using Memory::MakeSharedPtr;
33using Memory::NullCoalesce;
34
35// Comment this in to turn on aggressive noisy DbgTrace in this module
36//#define USE_NOISY_TRACE_IN_THIS_MODULE_ 1
37
39
40/*
41 ********************************************************************************
42 ****************** InternetMediaTypeRegistry::OverrideRecord *******************
43 ********************************************************************************
44 */
46{
48 sb << "{"sv;
49 if (fTypePrintName) {
50 sb << "TypePrintName: " << fTypePrintName;
51 }
52 if (fFileSuffixes) {
53 sb << ", FileSuffixes: " << fFileSuffixes;
54 }
55 if (fPreferredSuffix) {
56 sb << ", PreferredSuffix: " << fPreferredSuffix;
57 }
58 sb << "}"sv;
59 return sb;
60}
61
62/*
63 ********************************************************************************
64 ******************** InternetMediaTypeRegistry::FrontendRep_ *******************
65 ********************************************************************************
66 */
67
68/**
69 * @todo NYI UPDATING the frontend. Implement APIs to externally add mappings and be sure copying the InternetMediaTypeRegistry and using that
70 * in isolation works as well (use COW)
71 *
72 * \note - Structurally, we do caching in the backend (as needed) because each backend stores data differently
73 * and the logic of what makes sense to cache changes.
74 *
75 * The frontend simply defines 'API-Driven OVERRIDES' of the values returned. (so far not fully implemented - no setters/manipulators)
76 */
77struct InternetMediaTypeRegistry::FrontendRep_ : InternetMediaTypeRegistry::IFrontendRep_ {
78
80
81 // Baked in predefined initial user-overrides.
82 // These are adjustable by API, serve the purpose of providing a default on systems with no MIME content database -- LGP 2020-07-27
83
84 static inline const Mapping<InternetMediaType, OverrideRecord> kDefaults_{initializer_list<KeyValuePair<InternetMediaType, OverrideRecord>>{
85 {InternetMediaTypes::kText_PLAIN, OverrideRecord{nullopt, Containers::Set<String>{".txt"sv}, ".txt"sv}},
86 {InternetMediaTypes::kCSS, OverrideRecord{nullopt, Containers::Set<String>{".css"sv}, ".css"sv}},
87 {InternetMediaTypes::kHTML, OverrideRecord{nullopt, Containers::Set<String>{".htm"sv, ".html"sv}, ".htm"sv}},
88 {InternetMediaTypes::kJavascript, OverrideRecord{nullopt, Containers::Set<String>{".js"sv}, ".js"sv}},
89 {InternetMediaTypes::kJSON, OverrideRecord{nullopt, Containers::Set<String>{".json"sv}, ".json"sv}},
90 {InternetMediaTypes::kPNG, OverrideRecord{nullopt, Containers::Set<String>{".png"sv}, ".png"sv}},
91 {InternetMediaTypes::kXML, OverrideRecord{nullopt, Containers::Set<String>{".xml"sv}, ".xml"sv}},
92 }};
93
94 // OVERRIDE values (take precedence over backend) and any other data we need to keep locked (synchronized)
95 struct Data_ {
96 shared_ptr<IBackendRep> fBackendRep; // lazy construct on first call to usage (since that construction can be slow)
97
100 };
101 mutable Synchronized<Data_> fData_;
102
103 // NULL backendRep IS allowed - use that to on-demand construct the backend
104 FrontendRep_ (const shared_ptr<IBackendRep>& backendRep)
105 : FrontendRep_{backendRep, kDefaults_}
106 {
107 }
108 FrontendRep_ (const shared_ptr<IBackendRep>& backendRep, const Mapping<InternetMediaType, OverrideRecord>& overrides)
109 : fData_{Data_{.fBackendRep = backendRep}}
110 {
111 SetOverrides (overrides);
112 }
113 virtual Mapping<InternetMediaType, OverrideRecord> GetOverrides () const override
114 {
115 auto lockedData = fData_.rwget ();
116 return lockedData->fOverrides;
117 }
118 virtual void SetOverrides (const Mapping<InternetMediaType, OverrideRecord>& overrides) override
119 {
120 auto lockedData = fData_.rwget ();
121 lockedData->fOverrides = overrides;
122 lockedData->fSuffix2MediaTypeMap.clear ();
123 for (const auto& i : lockedData->fOverrides) {
124 if (i.fValue.fFileSuffixes) {
125 for (const auto& si : *i.fValue.fFileSuffixes) {
126 lockedData->fSuffix2MediaTypeMap.Add (si, i.fKey, AddReplaceMode::eAddIfMissing);
127 }
128 }
129 }
130 }
131 virtual void AddOverride (const InternetMediaType& mediaType, const OverrideRecord& overrideRec) override
132 {
133 auto lockedData = fData_.rwget ();
134 lockedData->fOverrides.Add (mediaType, overrideRec);
135 lockedData->fSuffix2MediaTypeMap.clear ();
136 for (const auto& i : lockedData->fOverrides) {
137 if (i.fValue.fFileSuffixes) {
138 for (const auto& si : *i.fValue.fFileSuffixes) {
139 lockedData->fSuffix2MediaTypeMap.Add (si, i.fKey, AddReplaceMode::eAddIfMissing);
140 }
141 }
142 }
143 }
144 virtual shared_ptr<IBackendRep> GetBackendRep () const override
145 {
146 auto lockedData = fData_.rwget ();
147 return lockedData->fBackendRep;
148 }
149 virtual Containers::Set<InternetMediaType> GetMediaTypes (optional<InternetMediaType::AtomType> majorType) const override
150 {
151 using AtomType = InternetMediaType::AtomType;
152 auto lockedData = fData_.rwget ();
153 CheckData_ (&lockedData);
154 Containers::Set<InternetMediaType> result = lockedData->fBackendRep->GetMediaTypes (majorType);
155 if (majorType == nullopt) {
156 result += lockedData->fOverrides.Keys ();
157 }
158 else {
159 lockedData->fOverrides.Keys ().Apply ([&] (const InternetMediaType& i) {
160 if (i.GetType<AtomType> () == majorType) {
161 result += i;
162 }
163 });
164 }
165 return result;
166 }
167 virtual optional<FileSuffixType> GetPreferredAssociatedFileSuffix (const InternetMediaType& ct) const override
168 {
169 auto lockedData = fData_.rwget ();
170 CheckData_ (&lockedData);
171 if (auto o = lockedData->fOverrides.Lookup (ct)) {
172 if (o->fPreferredSuffix) {
173 return *o->fPreferredSuffix;
174 }
175 }
176 return lockedData->fBackendRep->GetPreferredAssociatedFileSuffix (ct);
177 }
178 virtual Containers::Set<FileSuffixType> GetAssociatedFileSuffixes (const InternetMediaType& ct) const override
179 {
180 auto lockedData = fData_.rwget ();
181 CheckData_ (&lockedData);
182 Containers::Set<String> result = lockedData->fOverrides.LookupValue (ct).fFileSuffixes.value_or (Containers::Set<FileSuffixType>{});
183 result += lockedData->fBackendRep->GetAssociatedFileSuffixes (ct);
184 return result;
185 }
186 virtual optional<String> GetAssociatedPrettyName (const InternetMediaType& ct) const override
187 {
188 auto lockedData = fData_.rwget ();
189 CheckData_ (&lockedData);
190 if (auto o = lockedData->fOverrides.Lookup (ct)) {
191 if (o->fTypePrintName) {
192 return *o->fTypePrintName;
193 }
194 }
195 return lockedData->fBackendRep->GetAssociatedPrettyName (ct);
196 }
197 virtual optional<InternetMediaType> GetAssociatedContentType (const FileSuffixType& fileSuffix) const override
198 {
199 Require (fileSuffix[0] == '.');
200 auto lockedData = fData_.rwget ();
201 CheckData_ (&lockedData);
202 if (auto o = lockedData->fSuffix2MediaTypeMap.Lookup (fileSuffix)) {
203 return *o;
204 }
205 return lockedData->fBackendRep->GetAssociatedContentType (fileSuffix);
206 }
207 virtual bool IsA (const InternetMediaType& moreGeneralType, const InternetMediaType& moreSpecificType) const override
208 {
209 /**
210 * Generally simple to compare because AtomType code and parser handle case and breaking off bits like +xml, and ; parameters
211 *
212 * Only trick is that no good way to tell more general relationships between types, but doesn't appear well defined (like CCR is a kind of XML).
213 */
214 using AtomType = InternetMediaType::AtomType;
215 AtomType generalType = moreGeneralType.GetType<AtomType> ();
216 AtomType generalSubType = moreGeneralType.GetSubType<AtomType> ();
217 AtomType specificType = moreSpecificType.GetType<AtomType> ();
218 AtomType specificSubType = moreSpecificType.GetSubType<AtomType> ();
219
220 if (specificType == generalType and specificSubType == generalSubType) {
221 return true;
222 }
223
224 // Handle wildcard 'moreGeneralType' - if its exactly Type/'empty' - treating empty as wildcard for IsA...
225 if (moreGeneralType == InternetMediaType{generalType, {}}) {
226 if (specificType == generalType) {
227 return true;
228 }
229 }
230
231 // @todo find a better way - generalize... But for now - Stroika v3.0d12x... - just copy old logic for a bunch of special cases we had - then later
232 // maybe add "override" records for this too....
233 if (moreGeneralType == InternetMediaTypes::Wildcards::kText) {
234 if (IsA (InternetMediaTypes::kXML, moreSpecificType)) {
235 return true;
236 }
237 if (IsA (InternetMediaTypes::kJSON, moreSpecificType)) {
238 return true;
239 }
240 // well known types that can be treated as text (@todo need some way to extend this API)? - Maybe not here but in REGISTRY
241 if (specificType == InternetMediaTypes::Types::kApplication) {
242 Assert (InternetMediaTypes::kRTF.GetType<AtomType> () == InternetMediaTypes::Types::kApplication);
243 if (specificSubType == InternetMediaTypes::kRTF.GetSubType<AtomType> ()) {
244 return true;
245 }
246 }
247 }
248 else if (moreGeneralType == InternetMediaTypes::kXML) {
249 if (specificType == InternetMediaTypes::Types::kApplication) {
250 Assert (InternetMediaTypes::kXML.GetType<AtomType> () == InternetMediaTypes::Types::kApplication);
251 if (specificSubType == InternetMediaTypes::kXML.GetSubType<AtomType> ()) {
252 return true;
253 }
254 Assert (InternetMediaTypes::kXSLT.GetType<AtomType> () == InternetMediaTypes::Types::kApplication);
255 if (specificSubType == InternetMediaTypes::kXSLT.GetSubType<AtomType> ()) {
256 return true;
257 }
258 }
259 if (specificType == InternetMediaTypes::Types::kText) {
260 static const AtomType kXMLAtom_ = "xml"sv;
261 if (specificSubType == kXMLAtom_) {
262 return true;
263 }
264 }
265 }
266
267 // look for suffixes
268 if (auto suffix = moreSpecificType.GetSuffix<AtomType> ()) {
269 if (moreGeneralType == InternetMediaTypes::kJSON) {
270 static const AtomType kSuffix_{"json"sv};
271 if (suffix == kSuffix_) {
272 return true;
273 }
274 }
275 else if (moreGeneralType == InternetMediaTypes::kXML) {
276 static const AtomType kSuffix_{"xml"sv};
277 if (suffix == kSuffix_) {
278 return true;
279 }
280 }
281 }
282
283 return false;
284 }
285 static void CheckData_ (Synchronized<Data_>::WritableReference* lockedData)
286 {
287 if (lockedData->rwref ().fBackendRep == nullptr) {
288 lockedData->rwref ().fBackendRep = InternetMediaTypeRegistry::DefaultBackend ();
289 }
290 }
291};
292inline InternetMediaTypeRegistry::FrontendRep_ InternetMediaTypeRegistry::kDefaultFrontEndForNoBackend_{nullptr};
293
294/*
295 ********************************************************************************
296 ******************** InternetMediaTypeRegistry::Rep_Cloner_ ********************
297 ********************************************************************************
298 */
299auto InternetMediaTypeRegistry::Rep_Cloner_::operator() (const IFrontendRep_& t) const -> shared_ptr<IFrontendRep_>
300{
301 return MakeSharedPtr<FrontendRep_> (t.GetBackendRep (), t.GetOverrides ());
302};
303
304/*
305 ********************************************************************************
306 *************************** InternetMediaTypeRegistry **************************
307 ********************************************************************************
308 */
309InternetMediaTypeRegistry::InternetMediaTypeRegistry (const shared_ptr<IBackendRep>& backendRep)
310 // note because can be constructed before main () - not safe to Memory::MakeSharedPtr<FrontendRep_> - so delay construction and use kDefaultFrontEndForNoBackend_ if needed
311 : fFrontEndRep_{backendRep == nullptr ? nullptr : MakeSharedPtr<FrontendRep_> (backendRep)}
312{
313}
314
316{
317 return NullCoalesce (fFrontEndRep_, kDefaultFrontEndForNoBackend_).GetOverrides ();
318}
319
321{
322 if (fFrontEndRep_ == nullptr) {
323 fFrontEndRep_ = MakeSharedPtr<FrontendRep_> (kDefaultFrontEndForNoBackend_);
324 }
325 AssertNotNull (fFrontEndRep_);
326 fFrontEndRep_->SetOverrides (overrides);
327}
328
330{
331 if (fFrontEndRep_ == nullptr) {
332 fFrontEndRep_ = MakeSharedPtr<FrontendRep_> (kDefaultFrontEndForNoBackend_);
333 }
334 AssertNotNull (fFrontEndRep_);
335 fFrontEndRep_->AddOverride (mediaType, overrideRec);
336}
337
338optional<InternetMediaTypeRegistry::FileSuffixType> InternetMediaTypeRegistry::GetPreferredAssociatedFileSuffix (const InternetMediaType& ct) const
339{
340 return Memory::NullCoalesce (fFrontEndRep_, kDefaultFrontEndForNoBackend_).GetPreferredAssociatedFileSuffix (ct);
341}
342
344{
346 r = Memory::NullCoalesce (fFrontEndRep_, kDefaultFrontEndForNoBackend_).GetAssociatedFileSuffixes (ct);
347 // if a MediaType has a builtin suffix, include that as well...
348 if (auto os = ct.GetSuffix<String> ()) {
349 r += *os;
350 }
351 return r;
352}
353
355{
356 return Memory::NullCoalesce (fFrontEndRep_, kDefaultFrontEndForNoBackend_).GetAssociatedPrettyName (ct);
357}
358
359shared_ptr<InternetMediaTypeRegistry::IBackendRep> InternetMediaTypeRegistry::DefaultBackend ()
360{
361 Debug::TraceContextBumper ctx{"InternetMediaTypeRegistry::DefaultBackend"};
362#if qStroika_Foundation_Common_Platform_Windows
363 return WindowsRegistryDefaultBackend ();
364#endif
365 // @todo fix for MacOS - which doesn't support these - http://stroika-bugs.sophists.com/browse/STK-795
366 if (filesystem::exists ("/usr/share/mime"sv)) {
367 try {
368 return UsrSharedDefaultBackend ();
369 }
370 catch (...) {
371 // LOG/WRN
372 }
373 }
374 if (filesystem::exists ("/etc/mime.types"sv)) {
375 try {
377 }
378 catch (...) {
379 // LOG/WRN
380 }
381 }
382 return BakedInDefaultBackend (); // always works (but sucks)
383}
384
386{
387 Debug::TraceContextBumper ctx{"InternetMediaTypeRegistry::EtcMimeTypesDefaultBackend"};
388 /*
389 * Use the file /etc/mime.types
390 *
391 * not sure this is useful - not sure who uses it that doesn't support /usr/share/mime...
392 *
393 * Preload the entire DB since its not practical to scan looking for the intended record (due to the time this would take).
394 */
395 struct EtcMimeTypesRep_ : IBackendRep {
396 Mapping<FileSuffixType, InternetMediaType> fSuffix2MediaTypeMap_;
397 Mapping<InternetMediaType, FileSuffixType> fMediaType2PreferredSuffixMap_;
399
400 EtcMimeTypesRep_ ()
401 {
402#if USE_NOISY_TRACE_IN_THIS_MODULE_
403 Debug::TraceContextBumper ctx{"InternetMediaTypeRegistry::{}::EtcMimeTypesRep_::CTOR"};
404#endif
406 IO::FileSystem::FileInputStream::New ("/etc/mime.types"sv))) {
407 if (line.length () >= 2 and not line[0].StartsWith ("#"_k)) {
409 try {
410 ct = InternetMediaType{line[0]};
411 }
412 catch (...) {
413 DbgTrace ("Ignoring exception looking parsing potential media type entry ({}): {}"_f, line[0], current_exception ());
414 }
415 // a line starts with a content type, but then contains any number of file suffixes (without the leading .)
417 for (size_t i = 1; i < line.length (); ++i) {
418 if (line[i].empty ()) {
419 DbgTrace ("Ignoring bad looking parsing potential media type entry ({})"_f, line);
420 }
421 else {
422 Assert (not line[i].empty ());
423 String suffix = "."sv + line[i];
424 fSuffix2MediaTypeMap_.Add (suffix, ct);
425 fMediaType2PreferredSuffixMap_.Add (ct, suffix, AddReplaceMode::eAddIfMissing);
426 fileSuffixes.Add (suffix);
427 }
428 }
429 fMediaType2SuffixesMap_.Add (ct, fileSuffixes);
430 }
431 }
432 // Because on raspberrypi/debian, this comes out with a crazy default for text\plain -- LGP 2020-07-27
433 fMediaType2PreferredSuffixMap_.Add (InternetMediaTypes::kText_PLAIN, ".txt"sv);
434#if USE_NOISY_TRACE_IN_THIS_MODULE_
435 DbgTrace (L"succeeded with {} fSuffix2MediaTypeMap entries, and {} fMediaType2PreferredSuffixMap entries"_f,
436 fSuffix2MediaTypeMap_.size (), fMediaType2PreferredSuffixMap_.size ());
437#endif
438 }
439 virtual Containers::Set<InternetMediaType> GetMediaTypes (optional<InternetMediaType::AtomType> majorType) const override
440 {
442 for (const InternetMediaType& imt : fMediaType2PreferredSuffixMap_.Keys ()) {
443 if (majorType != nullopt and (imt.GetType<InternetMediaType::AtomType> () != *majorType)) {
444 continue;
445 }
446 results += imt;
447 }
448 return results;
449 }
450 virtual optional<FileSuffixType> GetPreferredAssociatedFileSuffix (const InternetMediaType& ct) const override
451 {
452 if (auto o = fMediaType2PreferredSuffixMap_.Lookup (ct)) {
453 return *o;
454 }
455 return nullopt;
456 }
457 virtual Containers::Set<FileSuffixType> GetAssociatedFileSuffixes (const InternetMediaType& ct) const override
458 {
459 if (auto i = fMediaType2SuffixesMap_.Lookup (ct)) {
460 return *i;
461 }
463 }
464 virtual optional<String> GetAssociatedPrettyName (const InternetMediaType& /*ct*/) const override
465 {
466 return nullopt; // not supported in this file
467 }
468 virtual optional<InternetMediaType> GetAssociatedContentType (const FileSuffixType& fileSuffix) const override
469 {
470 Require (fileSuffix[0] == '.');
471 if (auto o = fSuffix2MediaTypeMap_.Lookup (fileSuffix)) {
472 return *o;
473 }
474 return nullopt;
475 }
476 };
477 return MakeSharedPtr<EtcMimeTypesRep_> ();
478}
479
481{
482 Debug::TraceContextBumper ctx{"InternetMediaTypeRegistry::UsrSharedDefaultBackend"};
483 /*
484 * Documented to some small degree in https://www.linuxtopia.org/online_books/linux_desktop_guides/gnome_2.14_admin_guide/mimetypes-database.html
485 */
486 struct UsrShareMIMERep_ : IBackendRep {
487 Iterable<filesystem::path> fDataRoots_{"~/.local/share/mime/"sv, "/usr/local/share/mime/"sv, "/usr/share/mime"sv};
488
489 /*
490 * NOTE - for fSuffix2MediaTypeMap_ and fMediaType2PreferredSuffixMap, we cannot use Bijection,
491 * because multiple media-types can map to a single filetype and not all mediatypes have a filetype.
492 *
493 * We CANNOT use a cache, or dynamically fetch this data from files, because the data for each file suffix
494 * is not indexed (by file suffix) - it is indexed by content type (so those lookups COULD be dynamic). But
495 * we can easily construct both at the same time reading the summary file, so we do.
496 */
497 Mapping<FileSuffixType, InternetMediaType> fSuffix2MediaTypeMap_;
498 Mapping<InternetMediaType, FileSuffixType> fMediaType2PreferredSuffixMap_;
500
501 mutable Synchronized<Mapping<InternetMediaType, String>> fMediaType2PrettyNameCache; // incrementally build as needed
502
503 UsrShareMIMERep_ ()
504 {
505#if USE_NOISY_TRACE_IN_THIS_MODULE_
506 Debug::TraceContextBumper ctx{"InternetMediaTypeRegistry::{}UsrShareMIMERep_::CTOR"};
507#endif
508 // @todo consider using globs2 file support, but little point since they seem to be written in priority order
509 auto loadGlobsFromFile = [&] (const filesystem::path& fn) {
510 if (filesystem::exists (fn)) {
511 Debug::TraceContextBumper ctx1{"UsrShareMIMERep_::CTOR::loadGlobsFromFile", "exists=true,fn={}"_f, fn};
512 try {
513 for (Sequence<String> line :
514 DataExchange::Variant::CharacterDelimitedLines::Reader{{':'}}.ReadMatrix (IO::FileSystem::FileInputStream::New (fn))) {
515 if (line.length () == 2) {
516 String glob = line[1];
517 if (glob.StartsWith ('*')) {
518 glob = glob.SubString (1);
519 }
520 // Use AddReplaceMode::eAddIfMissing - so first (appears empirically to be the preferred value) wins
522 try {
523 imt = InternetMediaType{line[0]};
524 }
525 catch (...) {
526 DbgTrace ("Ignoring exception looking parsing potential media type entry ({}): {}"_f, line[0], current_exception ());
527 }
528 fSuffix2MediaTypeMap_.Add (glob, imt, AddReplaceMode::eAddIfMissing);
529 fMediaType2PreferredSuffixMap_.Add (imt, glob, AddReplaceMode::eAddIfMissing);
530
531 // update the set of mapped suffixes
532 Containers::Set<FileSuffixType> prevSuffixes = fMediaType2SuffixesMap_.LookupValue (imt);
533 prevSuffixes.Add (glob);
534 fMediaType2SuffixesMap_.Add (imt, prevSuffixes);
535 }
536 }
537
538 // Because on raspberrypi/Debian, this comes out with a crazy default for text\plain -- LGP 2020-07-27
539 fMediaType2PreferredSuffixMap_.Add (InternetMediaTypes::kText_PLAIN, ".txt"_k);
540 }
541 catch (...) {
542 // log error
543 }
544 }
545 };
546 // override files loaded first, tied to use of AddReplaceMode::eAddIfMissing - not replacing
547 for (const auto& p : fDataRoots_) {
548 loadGlobsFromFile (p / "globs");
549 }
550
551#if USE_NOISY_TRACE_IN_THIS_MODULE_
552 DbgTrace ("succeeded with {} fSuffix2MediaTypeMap_ entries, and {} fMediaType2PreferredSuffixMap entries"_f,
553 fSuffix2MediaTypeMap_.size (), fMediaType2PreferredSuffixMap_.size ());
554#endif
555 }
556 virtual Containers::Set<InternetMediaType> GetMediaTypes (optional<InternetMediaType::AtomType> majorType) const override
557 {
558#if USE_NOISY_TRACE_IN_THIS_MODULE_
559 Debug::TraceContextBumper ctx{Stroika_Foundation_Debug_OptionalizeTraceArgs ("UsrShareMIMERep_::GetMediaTypes", "majorType={}"_f, majorType)};
560#endif
562 for (const auto& imt : fMediaType2PreferredSuffixMap_.Keys ()) {
563 if (majorType) {
564 if (imt.GetType<InternetMediaType::AtomType> () != *majorType) {
565 continue; // skip non-matching types
566 }
567 }
568 results += imt;
569 }
570 return results;
571 }
572 virtual optional<FileSuffixType> GetPreferredAssociatedFileSuffix (const InternetMediaType& ct) const override
573 {
574 if (auto o = fMediaType2PreferredSuffixMap_.Lookup (ct)) {
575 return *o;
576 }
577 return nullopt;
578 }
579 virtual Containers::Set<FileSuffixType> GetAssociatedFileSuffixes (const InternetMediaType& ct) const override
580 {
581 if (auto i = fMediaType2SuffixesMap_.Lookup (ct)) {
582 return *i;
583 }
585 }
586 virtual optional<String> GetAssociatedPrettyName (const InternetMediaType& ct) const override
587 {
588 return LookupAndUpdateFromUsrShareMimePrettyName_ (ct);
589 }
590 virtual optional<InternetMediaType> GetAssociatedContentType (const FileSuffixType& fileSuffix) const override
591 {
592 Require (fileSuffix[0] == '.');
593 if (auto o = fSuffix2MediaTypeMap_.Lookup (fileSuffix)) {
594 return *o;
595 }
596 return nullopt;
597 }
598 optional<String> LookupAndUpdateFromUsrShareMimePrettyName_ (const InternetMediaType& ct) const
599 {
600#if USE_NOISY_TRACE_IN_THIS_MODULE_
601 Debug::TraceContextBumper ctx{"{}MIMEDB_::LookupAndUpdateFromUsrShareMimePrettyName"};
602#endif
603 // @todo combine lock calls in this procedure
604 if (auto o = fMediaType2PrettyNameCache.cget ()->Lookup (ct)) {
605 return *o;
606 }
607 // SAX parse /usr/share/mime/TYPE/SUBTYPE.xml file and look for <comment> element (default with no language for now)
608 // Simpler - just take the first - seems empirically fine/OK
609#if qStroika_Foundation_DataExchange_XML_SupportParsing
610 try {
611 using Name = StructuredStreamEvents::Name;
612 struct myHander_ : StructuredStreamEvents::IConsumer {
613 optional<String> fResult;
614 bool onContentElt{false};
615 StringBuilder<> fAccum;
616 virtual void StartElement (const Name& name, [[maybe_unused]] const Mapping<Name, String>& attributes) override
617 {
618 if (name == Name{"content"_k} and not fResult.has_value ()) {
619 onContentElt = true;
620 }
621 }
622 virtual void EndElement ([[maybe_unused]] const Name& name) override
623 {
624 if (onContentElt) {
625 Assert (not fResult);
626 fResult = fAccum.str ();
627 }
628 }
629 virtual void TextInsideElement (const String& t) override
630 {
631 if (onContentElt) {
632 fAccum << t;
633 }
634 }
635 };
636 filesystem::path mimeRoot{"/usr/share/mime/"sv};
637 myHander_ handler;
638 // @todo validate ct.GetType () to make sure not a ../../ ATTACK
639 DataExchange::XML::SAXParse (IO::FileSystem::FileInputStream::New (
640 mimeRoot / (ct.GetType () + "/"_k + ct.GetSubType () + ".xml"_k).As<filesystem::path> ()),
641 &handler);
642 if (handler.fResult) {
643 fMediaType2PrettyNameCache.rwget ()->Add (ct, *handler.fResult);
644 return *handler.fResult;
645 }
646 }
647 catch (...) {
648#if USE_NOISY_TRACE_IN_THIS_MODULE_
649 DbgTrace ("failure ignored"_f);
650#endif
651 }
652#else
653 DbgTrace ("/usr/share/mime/ ignored cuz no xml reader - not compiled with libxml2 or Xerces"_f);
654#endif
655 return nullopt;
656 }
657 };
658 return MakeSharedPtr<UsrShareMIMERep_> ();
659}
660
661auto InternetMediaTypeRegistry::BakedInDefaultBackend () -> shared_ptr<IBackendRep>
662{
663 Debug::TraceContextBumper ctx{"InternetMediaTypeRegistry::BakedInDefaultBackend"};
664 struct DefaultEmptyBackendRep_ : IBackendRep {
665 virtual Containers::Set<InternetMediaType> GetMediaTypes ([[maybe_unused]] optional<InternetMediaType::AtomType> majorType) const override
666 {
668 }
669 virtual optional<FileSuffixType> GetPreferredAssociatedFileSuffix ([[maybe_unused]] const InternetMediaType& ct) const override
670 {
671 return nullopt;
672 }
673 virtual Containers::Set<FileSuffixType> GetAssociatedFileSuffixes ([[maybe_unused]] const InternetMediaType& ct) const override
674 {
676 }
677 virtual optional<String> GetAssociatedPrettyName (const InternetMediaType& /*ct*/) const override
678 {
679 return nullopt;
680 }
681 virtual optional<InternetMediaType> GetAssociatedContentType ([[maybe_unused]] const FileSuffixType& fileSuffix) const override
682 {
683 Require (fileSuffix[0] == '.');
684 return nullopt;
685 }
686 };
687 return MakeSharedPtr<DefaultEmptyBackendRep_> ();
688}
689
690#if qStroika_Foundation_Common_Platform_Windows
691auto InternetMediaTypeRegistry::WindowsRegistryDefaultBackend () -> shared_ptr<IBackendRep>
692{
693 /*
694 * I can find no documentation on how this works, but at least https://stackoverflow.com/questions/3442607/mime-types-in-the-windows-registry
695 * mentions it.
696 *
697 * Empirically you can usually find:
698 * HKEY_CLASSES_ROOT\MIME\Database
699 * Content Type\CT\Extension
700 * This layout does not appear to accommodate ever having more than one extension for a given mime type
701 *
702 * HKEY_CLASSES_ROOT\FILE_SUFFIX
703 * {default} pretty name
704 * Content Type: 'internet media type'
705 *
706 * \note On Docker windows server core images, this is often missing! (but addressed with the default values baked into the frontend) -- LGP 2020-07-28
707 */
708 Debug::TraceContextBumper ctx{"InternetMediaTypeRegistry::WindowsRegistryDefaultBackend"};
709 struct WinRep_ : IBackendRep {
710 // underlying windows code fast so use small cache sizes
711 mutable Cache::SynchronizedLRUCache<FileSuffixType, optional<String>, equal_to<FileSuffixType>, hash<FileSuffixType>> fFileSuffix2PrettyNameCache_{
712 25, 7};
713 mutable Cache::SynchronizedLRUCache<FileSuffixType, optional<InternetMediaType>, equal_to<FileSuffixType>, hash<FileSuffixType>> fSuffix2MediaTypeCache_{
714 25, 7};
715 mutable Cache::SynchronizedLRUCache<InternetMediaType, optional<FileSuffixType>, equal_to<InternetMediaType>, hash<InternetMediaType>> fContentType2FileSuffixCache_{
716 25, 7};
717 mutable Cache::SynchronizedLRUCache<InternetMediaType, Containers::Set<FileSuffixType>, equal_to<InternetMediaType>, hash<InternetMediaType>> fContentType2FileSuffixesCache_{
718 25, 7};
719
720 virtual Containers::Set<InternetMediaType> GetMediaTypes (optional<InternetMediaType::AtomType> majorType) const override
721 {
723 //
724 // rarely do we fetch all MIME types, so don't cache - just re-fetch each time
725 //
726 // On Windows, in registry, easiest way appears to be to enumerate ALL registry entries in HKCR that start with .,
727 // and look for sub-field 'Content-type'
728 //
729 using RegistryKey = Common::Platform::Windows::RegistryKey;
730 for (shared_ptr<RegistryKey> sk : RegistryKey{HKEY_CLASSES_ROOT}.EnumerateSubKeys ()) {
731 String name = sk->GetFullPathOfKey ().Tokenize ({'\\'}).LastValue ();
732 if (name.StartsWith ('.')) {
733 if (auto o = sk->Lookup ("Content Type"sv)) {
735 try {
736 imt = InternetMediaType{o.As<String> ()};
737 }
738 catch (...) {
739 // ignore bad format - such as .sqlproj has Content-Type "string" which my read of the RFC says is illegal
740 DbgTrace ("Ignoring exception parsing registry key ({}): {}"_f, o, current_exception ());
741 continue;
742 }
743 if (majorType) {
744 if (imt.GetType<InternetMediaType::AtomType> () != *majorType) {
745 continue; // skip non-matching types
746 }
747 }
748 result.Add (imt);
749 }
750 }
751 }
752 return result;
753 }
754 virtual optional<FileSuffixType> GetPreferredAssociatedFileSuffix (const InternetMediaType& ct) const override
755 {
756 return fContentType2FileSuffixCache_.LookupValue (ct, [] (const InternetMediaType& ct) -> optional<FileSuffixType> {
757 if (auto fs = Common::Platform::Windows::RegistryKey{HKEY_CLASSES_ROOT}.Lookup ("MIME\\Database\\Content Type\\{}\\Extension"_f(ct))) {
758 return fs.As<String> ();
759 }
760 return nullopt;
761 });
762 }
763 virtual Containers::Set<FileSuffixType> GetAssociatedFileSuffixes (const InternetMediaType& ct) const override
764 {
765 // This is expensive to compute, and we could compute all and cache, but I don't think we will need to lookup very often, so just
766 // compute as needed and cache a few
767 return fContentType2FileSuffixesCache_.LookupValue (ct, [] (const InternetMediaType& ct) -> Containers::Set<FileSuffixType> {
770 for (shared_ptr<RegistryKey> sk : RegistryKey{HKEY_CLASSES_ROOT}.EnumerateSubKeys ()) {
771 String name = sk->GetFullPathOfKey ().Tokenize ({'\\'}).LastValue ();
772 if (name.StartsWith ("."_k)) {
773 if (auto o = sk->Lookup ("Content Type"sv)) {
775 try {
776 imt = InternetMediaType{o.As<String> ()};
777 }
778 catch (...) {
779 // ignore bad format - such as .sqlproj has Content-Type "string" which my read of the RFC says is illegal
780 DbgTrace ("Ignoring exception parsing registry key ({}): {}"_f, o, current_exception ());
781 continue;
782 }
783 if (ct.GetType () == imt.GetType () and ct.GetSubType () == imt.GetSubType ()) {
784 result += name;
785 }
786 }
787 }
788 }
789 return result;
790 });
791 }
792 virtual optional<String> GetAssociatedPrettyName (const InternetMediaType& ct) const override
793 {
794 if (optional<FileSuffixType> fileSuffix = GetPreferredAssociatedFileSuffix (ct)) {
795 return fFileSuffix2PrettyNameCache_.LookupValue (*fileSuffix, [] (const String& suffix) -> optional<String> {
796 if (auto fileTypeID = Common::Platform::Windows::RegistryKey{HKEY_CLASSES_ROOT}.Lookup (suffix + "\\"_k)) {
797 if (auto prettyName = Common::Platform::Windows::RegistryKey{HKEY_CLASSES_ROOT}.Lookup (fileTypeID.As<String> () + "\\"_k)) {
798 return prettyName.As<String> ();
799 }
800 }
801 return nullopt;
802 });
803 }
804 return nullopt;
805 }
806 virtual optional<InternetMediaType> GetAssociatedContentType (const FileSuffixType& fileSuffix) const override
807 {
808 Require (fileSuffix[0] == '.');
809 return fSuffix2MediaTypeCache_.LookupValue (fileSuffix, [] (const FileSuffixType& fileSuffix) -> optional<InternetMediaType> {
811 // only do registry lookup if needed, since (probably) more costly than local map lookup
812 if (auto oct = RegistryKey{HKEY_CLASSES_ROOT}.Lookup ("{}\\Content Type"_f(fileSuffix))) {
813 InternetMediaType mediaType{oct.As<String> ()};
814 return mediaType;
815 }
816 return nullopt;
817 });
818 }
819 };
820 return MakeSharedPtr<WinRep_> ();
821}
822#endif
823
824Set<InternetMediaType> InternetMediaTypeRegistry::GetMediaTypes () const
825{
826 return NullCoalesce (fFrontEndRep_, kDefaultFrontEndForNoBackend_).GetMediaTypes (nullopt);
827}
828
829Set<InternetMediaType> InternetMediaTypeRegistry::GetMediaTypes (InternetMediaType::AtomType majorType) const
830{
831 return NullCoalesce (fFrontEndRep_, kDefaultFrontEndForNoBackend_).GetMediaTypes (majorType);
832}
833
835{
837 for (const auto& ct : mediaTypes) {
838 for (const auto& i : GetAssociatedFileSuffixes (ct)) {
839 result += i;
840 }
841 }
842 return result;
843}
844
845optional<InternetMediaType> InternetMediaTypeRegistry::GetAssociatedContentType (const FileSuffixType& fileSuffix) const
846{
847 if (fileSuffix.empty ()) {
848 return nullopt;
849 }
850 Assert (fileSuffix[0] == '.');
851 return NullCoalesce (fFrontEndRep_, kDefaultFrontEndForNoBackend_).GetAssociatedContentType (fileSuffix);
852}
853
855{
856 return IsA (InternetMediaTypes::Wildcards::kText, ct);
857}
858
860{
861 return IsA (InternetMediaTypes::Wildcards::kImage, ct);
862}
863
865{
866 return IsA (InternetMediaTypes::kXML, ct);
867}
868
869bool InternetMediaTypeRegistry::IsA (const InternetMediaType& moreGeneralType, const InternetMediaType& moreSpecificType) const
870{
871 using AtomType = InternetMediaType::AtomType;
872 // shortcut this one case
873 if (moreSpecificType.GetType<AtomType> () == moreGeneralType.GetType<AtomType> () and
874 moreSpecificType.GetSubType<AtomType> () == moreGeneralType.GetSubType<AtomType> ()) {
875 return true;
876 }
877 return NullCoalesce (fFrontEndRep_, kDefaultFrontEndForNoBackend_).IsA (moreGeneralType, moreSpecificType);
878}
#define AssertNotNull(p)
Definition Assertions.h:333
const OT & NullCoalesce(const OT &l, const OT &r)
return one of l, or r, with first preference for which is engaged, and second preference for left-to-...
Definition Optional.inl:134
#define DbgTrace
Definition Trace.h:309
#define Stroika_Foundation_Debug_OptionalizeTraceArgs(...)
Definition Trace.h:270
LRUCache implements a simple least-recently-used caching strategy, with optional hashing (of keys) to...
Definition LRUCache.h:94
Similar to String, but intended to more efficiently construct a String. Mutable type (String is large...
String is like std::u32string, except it is much easier to use, often much more space efficient,...
Definition String.h:201
nonvirtual String SubString(SZ from) const
nonvirtual bool StartsWith(const Character &c, CompareOptions co=eWithCase) const
Definition String.cpp:1060
nonvirtual Containers::Sequence< String > Tokenize() const
Definition String.cpp:1235
nonvirtual DataExchange::VariantValue Lookup(const Characters::String &valuePath) const
Definition Registry.cpp:143
nonvirtual bool Add(ArgByValueType< key_type > key, ArgByValueType< mapped_type > newElt, AddReplaceMode addReplaceMode=AddReplaceMode::eAddReplaces)
Definition Mapping.inl:188
nonvirtual optional< mapped_type > Lookup(ArgByValueType< key_type > key) const
Definition Mapping.inl:142
nonvirtual mapped_type LookupValue(ArgByValueType< key_type > key, ArgByValueType< mapped_type > defaultValue=mapped_type{}) const
Definition Mapping.inl:166
nonvirtual Iterable< key_type > Keys() const
Definition Mapping.inl:111
A generalization of a vector: a container whose elements are keyed by the natural numbers.
Set<T> is a container of T, where once an item is added, additionally adds () do nothing.
nonvirtual void Add(ArgByValueType< value_type > item)
Definition Set.inl:138
nonvirtual RETURN_TYPE GetType() const
Gets the primary (major) type of the full internet media type (as a string or atom)
nonvirtual T As() const
convert to type T supported types: o String o wstring
nonvirtual optional< RETURN_TYPE > GetSuffix() const
this is the +XXX part of the internet media type (e.g. +xml) and is often omitted (but note this omit...
nonvirtual Mapping< InternetMediaType, OverrideRecord > GetOverrides() const
static shared_ptr< IBackendRep > DefaultBackend()
Generally no need to use this - handled automatically - but returns the default, OS-provided MIME Int...
nonvirtual bool IsXMLFormat(const InternetMediaType &ct) const
static shared_ptr< IBackendRep > UsrSharedDefaultBackend()
Generally no need to use this - handled automatically.
nonvirtual bool IsA(const InternetMediaType &moreGeneralType, const InternetMediaType &moreSpecificType) const
return true if moreSpecificType 'isa' moreGeneralType
nonvirtual void SetOverrides(const Mapping< InternetMediaType, OverrideRecord > &overrides)
nonvirtual optional< InternetMediaType > GetAssociatedContentType(const FileSuffixType &fileSuffix) const
nonvirtual optional< String > GetAssociatedPrettyName(const InternetMediaType &ct) const
static shared_ptr< IBackendRep > EtcMimeTypesDefaultBackend()
Generally no need to use this - handled automatically.
static shared_ptr< IBackendRep > BakedInDefaultBackend()
Generally no need to use this - handled automatically.
InternetMediaTypeRegistry(const shared_ptr< IBackendRep > &backendRep=nullptr)
nonvirtual optional< FileSuffixType > GetPreferredAssociatedFileSuffix(const InternetMediaType &ct) const
nonvirtual Containers::Set< FileSuffixType > GetAssociatedFileSuffixes(const InternetMediaType &ct) const
nonvirtual void AddOverride(const InternetMediaType &mediaType, const OverrideRecord &overrideRec)
bool IsTextFormat(const InternetMediaType &ct) const
returns true if you can expect to treat as some sort of text and reasonably view - like text/html,...
This COULD be easily used to read CSV files, or tab-delimited files, for example.
nonvirtual Iterable< Sequence< String > > ReadMatrix(const Streams::InputStream::Ptr< byte > &in) const
Wrap any object with Synchronized<> and it can be used similarly to the base type,...
nonvirtual WritableReference rwget()
get a read-write smart pointer to the underlying Synchronized<> object, holding the full lock the who...
nonvirtual ReadableReference cget() const
get a read-only smart pointer to the underlying Synchronized<> object, holding the readlock the whole...
Iterable<T> is a base class for containers which easily produce an Iterator<T> to traverse them.
Definition Iterable.h:237
nonvirtual void Apply(const function< void(ArgByValueType< T > item)> &doToElement, Execution::SequencePolicy seq=Execution::SequencePolicy::eDEFAULT) const
Run the argument function (or lambda) on each element of the container.
nonvirtual size_t size() const
Returns the number of items contained.
Definition Iterable.inl:303
const InternetMediaType::AtomType kApplication
'application'
for OS facilities not updatable - or controllable - just usable.