Source file src/mime/type.go

     1  // Copyright 2010 The Go Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  // Package mime implements parts of the MIME spec.
     6  package mime
     7  
     8  import (
     9  	"fmt"
    10  	"slices"
    11  	"strings"
    12  	"sync"
    13  )
    14  
    15  var (
    16  	mimeTypes      sync.Map // map[string]string; ".Z" => "application/x-compress"
    17  	mimeTypesLower sync.Map // map[string]string; ".z" => "application/x-compress"
    18  
    19  	// extensions maps from MIME type to list of lowercase file
    20  	// extensions: "image/jpeg" => [".jfif", ".jpg", ".jpeg", ".pjp", ".pjpeg"]
    21  	extensionsMu sync.Mutex // Guards stores (but not loads) on extensions.
    22  	extensions   sync.Map   // map[string][]string; slice values are append-only.
    23  )
    24  
    25  // setMimeTypes is used by initMime's non-test path, and by tests.
    26  func setMimeTypes(lowerExt, mixExt map[string]string) {
    27  	mimeTypes.Clear()
    28  	mimeTypesLower.Clear()
    29  	extensions.Clear()
    30  
    31  	for k, v := range lowerExt {
    32  		mimeTypesLower.Store(k, v)
    33  	}
    34  	for k, v := range mixExt {
    35  		mimeTypes.Store(k, v)
    36  	}
    37  
    38  	extensionsMu.Lock()
    39  	defer extensionsMu.Unlock()
    40  	for k, v := range lowerExt {
    41  		justType, _, err := ParseMediaType(v)
    42  		if err != nil {
    43  			panic(err)
    44  		}
    45  		var exts []string
    46  		if ei, ok := extensions.Load(justType); ok {
    47  			exts = ei.([]string)
    48  		}
    49  		extensions.Store(justType, append(exts, k))
    50  	}
    51  }
    52  
    53  // A type is listed here if both Firefox and Chrome included them in their own
    54  // lists.  In the case where they contradict they are deconflicted using IANA's
    55  // listed media types https://www.iana.org/assignments/media-types/media-types.xhtml
    56  //
    57  // Chrome's MIME mappings to file extensions are defined at
    58  // https://chromium.googlesource.com/chromium/src.git/+/refs/heads/main/net/base/mime_util.cc
    59  //
    60  // Firefox's MIME types can be found at
    61  // https://github.com/mozilla-firefox/firefox/blob/main/netwerk/mime/nsMimeTypes.h
    62  // and the mappings to file extensions at
    63  // https://github.com/mozilla-firefox/firefox/blob/main/uriloader/exthandler/nsExternalHelperAppService.cpp
    64  var builtinTypesLower = map[string]string{
    65  	".ai":    "application/postscript",
    66  	".apk":   "application/vnd.android.package-archive",
    67  	".apng":  "image/apng",
    68  	".avif":  "image/avif",
    69  	".bin":   "application/octet-stream",
    70  	".bmp":   "image/bmp",
    71  	".com":   "application/octet-stream",
    72  	".css":   "text/css; charset=utf-8",
    73  	".csv":   "text/csv; charset=utf-8",
    74  	".doc":   "application/msword",
    75  	".docx":  "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
    76  	".ehtml": "text/html; charset=utf-8",
    77  	".eml":   "message/rfc822",
    78  	".eps":   "application/postscript",
    79  	".exe":   "application/octet-stream",
    80  	".flac":  "audio/flac",
    81  	".gif":   "image/gif",
    82  	".gz":    "application/gzip",
    83  	".htm":   "text/html; charset=utf-8",
    84  	".html":  "text/html; charset=utf-8",
    85  	".ico":   "image/vnd.microsoft.icon",
    86  	".ics":   "text/calendar; charset=utf-8",
    87  	".jfif":  "image/jpeg",
    88  	".jpeg":  "image/jpeg",
    89  	".jpg":   "image/jpeg",
    90  	".js":    "text/javascript; charset=utf-8",
    91  	".json":  "application/json",
    92  	".m4a":   "audio/mp4",
    93  	".mjs":   "text/javascript; charset=utf-8",
    94  	".mp3":   "audio/mpeg",
    95  	".mp4":   "video/mp4",
    96  	".oga":   "audio/ogg",
    97  	".ogg":   "audio/ogg",
    98  	".ogv":   "video/ogg",
    99  	".opus":  "audio/ogg",
   100  	".pdf":   "application/pdf",
   101  	".pjp":   "image/jpeg",
   102  	".pjpeg": "image/jpeg",
   103  	".png":   "image/png",
   104  	".ppt":   "application/vnd.ms-powerpoint",
   105  	".pptx":  "application/vnd.openxmlformats-officedocument.presentationml.presentation",
   106  	".ps":    "application/postscript",
   107  	".rdf":   "application/rdf+xml",
   108  	".rtf":   "application/rtf",
   109  	".shtml": "text/html; charset=utf-8",
   110  	".svg":   "image/svg+xml",
   111  	".text":  "text/plain; charset=utf-8",
   112  	".tif":   "image/tiff",
   113  	".tiff":  "image/tiff",
   114  	".txt":   "text/plain; charset=utf-8",
   115  	".vtt":   "text/vtt; charset=utf-8",
   116  	".wasm":  "application/wasm",
   117  	".wav":   "audio/wav",
   118  	".weba":  "audio/webm",
   119  	".webm":  "video/webm",
   120  	".webp":  "image/webp",
   121  	".xbl":   "text/xml; charset=utf-8",
   122  	".xbm":   "image/x-xbitmap",
   123  	".xht":   "application/xhtml+xml",
   124  	".xhtml": "application/xhtml+xml",
   125  	".xls":   "application/vnd.ms-excel",
   126  	".xlsx":  "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
   127  	".xml":   "text/xml; charset=utf-8",
   128  	".xsl":   "text/xml; charset=utf-8",
   129  	".zip":   "application/zip",
   130  }
   131  
   132  var once sync.Once // guards initMime
   133  
   134  var testInitMime, osInitMime func()
   135  
   136  func initMime() {
   137  	if fn := testInitMime; fn != nil {
   138  		fn()
   139  	} else {
   140  		setMimeTypes(builtinTypesLower, builtinTypesLower)
   141  		osInitMime()
   142  	}
   143  }
   144  
   145  // TypeByExtension returns the MIME type associated with the file extension ext.
   146  // The extension ext should begin with a leading dot, as in ".html".
   147  // When ext has no associated type, TypeByExtension returns "".
   148  //
   149  // Extensions are looked up first case-sensitively, then case-insensitively.
   150  //
   151  // The built-in table is small but on unix it is augmented by the local
   152  // system's MIME-info database or mime.types file(s) if available under one or
   153  // more of these names:
   154  //
   155  //	/usr/local/share/mime/globs2
   156  //	/usr/share/mime/globs2
   157  //	/etc/mime.types
   158  //	/etc/apache2/mime.types
   159  //	/etc/apache/mime.types
   160  //	/etc/httpd/conf/mime.types
   161  //
   162  // On Windows, MIME types are extracted from the registry.
   163  //
   164  // Text types have the charset parameter set to "utf-8" by default.
   165  func TypeByExtension(ext string) string {
   166  	once.Do(initMime)
   167  
   168  	// Case-sensitive lookup.
   169  	if v, ok := mimeTypes.Load(ext); ok {
   170  		return v.(string)
   171  	}
   172  
   173  	// Case-insensitive lookup.
   174  	// Optimistically assume a short ASCII extension and be
   175  	// allocation-free in that case.
   176  	var buf [10]byte
   177  	lower := buf[:0]
   178  	const utf8RuneSelf = 0x80 // from utf8 package, but not importing it.
   179  	for i := 0; i < len(ext); i++ {
   180  		c := ext[i]
   181  		if c >= utf8RuneSelf {
   182  			// Slow path.
   183  			si, _ := mimeTypesLower.Load(strings.ToLower(ext))
   184  			s, _ := si.(string)
   185  			return s
   186  		}
   187  		if 'A' <= c && c <= 'Z' {
   188  			lower = append(lower, c+('a'-'A'))
   189  		} else {
   190  			lower = append(lower, c)
   191  		}
   192  	}
   193  	si, _ := mimeTypesLower.Load(string(lower))
   194  	s, _ := si.(string)
   195  	return s
   196  }
   197  
   198  // ExtensionsByType returns the extensions known to be associated with the MIME
   199  // type typ. The returned extensions will each begin with a leading dot, as in
   200  // ".html". When typ has no associated extensions, ExtensionsByType returns an
   201  // nil slice.
   202  //
   203  // The built-in table is small but on unix it is augmented by the local
   204  // system's MIME-info database or mime.types file(s) if available under one or
   205  // more of these names:
   206  //
   207  //	/usr/local/share/mime/globs2
   208  //	/usr/share/mime/globs2
   209  //	/etc/mime.types
   210  //	/etc/apache2/mime.types
   211  //	/etc/apache/mime.types
   212  //	/etc/httpd/conf/mime.types
   213  //
   214  // On Windows, extensions are extracted from the registry.
   215  func ExtensionsByType(typ string) ([]string, error) {
   216  	justType, _, err := ParseMediaType(typ)
   217  	if err != nil {
   218  		return nil, err
   219  	}
   220  
   221  	once.Do(initMime)
   222  	s, ok := extensions.Load(justType)
   223  	if !ok {
   224  		return nil, nil
   225  	}
   226  	ret := append([]string(nil), s.([]string)...)
   227  	slices.Sort(ret)
   228  	return ret, nil
   229  }
   230  
   231  // AddExtensionType sets the MIME type associated with
   232  // the extension ext to typ. The extension should begin with
   233  // a leading dot, as in ".html".
   234  func AddExtensionType(ext, typ string) error {
   235  	if !strings.HasPrefix(ext, ".") {
   236  		return fmt.Errorf("mime: extension %q missing leading dot", ext)
   237  	}
   238  	once.Do(initMime)
   239  	return setExtensionType(ext, typ)
   240  }
   241  
   242  func setExtensionType(extension, mimeType string) error {
   243  	justType, param, err := ParseMediaType(mimeType)
   244  	if err != nil {
   245  		return err
   246  	}
   247  	if strings.HasPrefix(mimeType, "text/") && param["charset"] == "" {
   248  		param["charset"] = "utf-8"
   249  		mimeType = FormatMediaType(mimeType, param)
   250  	}
   251  	extLower := strings.ToLower(extension)
   252  
   253  	mimeTypes.Store(extension, mimeType)
   254  	mimeTypesLower.Store(extLower, mimeType)
   255  
   256  	extensionsMu.Lock()
   257  	defer extensionsMu.Unlock()
   258  	var exts []string
   259  	if ei, ok := extensions.Load(justType); ok {
   260  		exts = ei.([]string)
   261  	}
   262  	for _, v := range exts {
   263  		if v == extLower {
   264  			return nil
   265  		}
   266  	}
   267  	extensions.Store(justType, append(exts, extLower))
   268  	return nil
   269  }
   270  

View as plain text