1 /** 2 * Common functions and constants to work with MIME types. 3 * Authors: 4 * $(LINK2 https://github.com/FreeSlave, Roman Chistokhodov) 5 * License: 6 * $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost License 1.0). 7 * Copyright: 8 * Roman Chistokhodov, 2015-2016 9 */ 10 11 module mime.common; 12 13 // should be enough for magic rules 14 package enum dataSizeToRead = 1024 * 4; 15 16 private { 17 import std.typecons; 18 import std.traits; 19 import std.range; 20 } 21 22 /** 23 * Parse MIME type name into pair of media and subtype strings. 24 * Returns: Tuple of media and subtype strings or pair of empty strings if could not parse name. 25 */ 26 @nogc @trusted auto parseMimeTypeName(String)(scope return String name) pure nothrow if (isSomeString!String && is(ElementEncodingType!String : char)) 27 { 28 alias Tuple!(String, "media", String, "subtype") MimeTypeName; 29 30 String media; 31 String subtype; 32 33 size_t i; 34 for (i=0; i<name.length; ++i) { 35 if (name[i] == '/') { 36 media = name[0..i]; 37 subtype = name[i+1..$]; 38 break; 39 } 40 } 41 42 return MimeTypeName(media, subtype); 43 } 44 45 /// 46 unittest 47 { 48 auto t = parseMimeTypeName("text/plain"); 49 assert(t.media == "text" && t.subtype == "plain"); 50 51 t = parseMimeTypeName("not mime type"); 52 assert(t.media == string.init && t.subtype == string.init); 53 } 54 55 private @nogc @trusted bool allSymbolsAreValid(scope const(char)[] name) nothrow pure 56 { 57 import std.ascii : isAlpha, isDigit; 58 for (size_t i=0; i<name.length; ++i) { 59 char c = name[i]; 60 if (!(c.isAlpha || c.isDigit || c == '-' || c == '+' || c == '.' || c == '_')) { 61 return false; 62 } 63 } 64 return true; 65 } 66 67 /** 68 * Check if name is valid MIME type name. 69 */ 70 @nogc @safe bool isValidMimeTypeName(scope const(char)[] name) nothrow pure 71 { 72 auto t = parseMimeTypeName(name); 73 return t.media.length && t.subtype.length && allSymbolsAreValid(t.media) && allSymbolsAreValid(t.subtype); 74 } 75 76 /// 77 unittest 78 { 79 assert( isValidMimeTypeName("text/plain")); 80 assert( isValidMimeTypeName("text/plain2")); 81 assert( isValidMimeTypeName("text/vnd.type")); 82 assert( isValidMimeTypeName("x-scheme-handler/http")); 83 assert(!isValidMimeTypeName("not mime type")); 84 assert(!isValidMimeTypeName("not()/valid")); 85 assert(!isValidMimeTypeName("not/valid{}")); 86 assert(!isValidMimeTypeName("text/")); 87 assert(!isValidMimeTypeName("/plain")); 88 assert(!isValidMimeTypeName("/")); 89 } 90 91 /** 92 * Default icon for MIME type. 93 * Returns: mimeType with '/' replaces with '-' or null if mimeType is not valid MIME type name. 94 */ 95 @safe string defaultIconName(scope string mimeType) nothrow pure 96 { 97 auto t = parseMimeTypeName(mimeType); 98 if (t.media.length && t.subtype.length) { 99 return t.media ~ "-" ~ t.subtype; 100 } 101 return null; 102 } 103 104 /// 105 unittest 106 { 107 assert(defaultIconName("text/plain") == "text-plain"); 108 assert(defaultIconName("not mime type") == string.init); 109 } 110 111 /** 112 * Default generic icon for MIME type. 113 * Returns: media-x-generic where media is parsed from mimeType or null if mimeType is not valid MIME type name. 114 */ 115 @trusted string defaultGenericIconName(scope string mimeType) nothrow pure 116 { 117 auto t = parseMimeTypeName(mimeType); 118 if (t.media.length) { 119 return t.media ~ "-x-generic"; 120 } 121 return null; 122 } 123 124 /// 125 unittest 126 { 127 assert(defaultGenericIconName("image/type") == "image-x-generic"); 128 assert(defaultGenericIconName("not mime type") == string.init); 129 } 130 131 /// Default glob pattern weight to use when it's not explicitly provided. 132 enum uint defaultGlobWeight = 50; 133 /// Maximum glob pattern weight as defined by spec. 134 enum uint maximumGlobWeight = 100; 135 136 /// Default magic rule priority to use when it's not explicitly provided. 137 enum uint defaultMatchWeight = 50; 138 /// Maximum magic rule priority as defined by spec. 139 enum uint maximumMatchWeight = 100; 140 141 /** 142 * Check is pattern is __NOGLOBS__. This means glob patterns from the less preferable MIME paths should be ignored. 143 */ 144 @nogc @safe bool isNoGlobs(T)(scope const(T)[] pattern) pure nothrow if (is(T == char) || is(T == ubyte) || is(T == byte) || is(T == void)) { 145 return cast(const(ubyte)[])pattern == cast(const(ubyte)[])"__NOGLOBS__"; 146 } 147 148 /// 149 unittest 150 { 151 assert(isNoGlobs("__NOGLOBS__")); 152 assert(!isNoGlobs("someglob")); 153 } 154 155 /** 156 * Check if value is __NOMAGIC__. This means magic rules from the less preferable MIME paths should be ignored. 157 */ 158 @nogc @trusted bool isNoMagic(T)(scope const(T)[] value) pure nothrow if (is(T == char) || is(T == ubyte) || is(T == byte) || is(T == void)) { 159 return cast(const(ubyte)[])value == cast(const(ubyte)[])"__NOMAGIC__"; 160 } 161 162 /// 163 unittest 164 { 165 assert(isNoMagic("__NOMAGIC__")); 166 assert(!isNoMagic("somemagic")); 167 } 168 169 /** 170 * Get implicit parent type if mimeType. This is text/plain for all text/* types 171 * and application/octet-stream for all streamable types. 172 * Returns: text/plain for text-based types, application/octet-stream for streamable types, null otherwise. 173 * Note: text/plain and application/octet-stream are not considered as parents of their own. 174 */ 175 @safe string implicitParent(scope const(char)[] mimeType) nothrow pure 176 { 177 if (mimeType == "text/plain" || mimeType == "application/octet-stream") { 178 return null; 179 } 180 181 auto t = parseMimeTypeName(mimeType); 182 if (t.media == "text") { 183 return "text/plain"; 184 } else if ( t.media == "image" || t.media == "audio" || 185 t.media == "video" || t.media == "application") 186 { 187 return "application/octet-stream"; 188 } 189 return null; 190 } 191 192 /// 193 unittest 194 { 195 assert(implicitParent("text/hmtl") == "text/plain"); 196 assert(implicitParent("text/plain") == null); 197 198 assert(implicitParent("image/png") == "application/octet-stream"); 199 assert(implicitParent("audio/ogg") == "application/octet-stream"); 200 assert(implicitParent("video/mpeg") == "application/octet-stream"); 201 assert(implicitParent("application/xml") == "application/octet-stream"); 202 assert(implicitParent("application/octet-stream") == null); 203 204 assert(implicitParent("inode/directory") == null); 205 assert(implicitParent("x-content/unix-software") == null); 206 207 assert(implicitParent("not a mimetype") == null); 208 }