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  	".webm":  "audio/webm",
   119  	".webp":  "image/webp",
   120  	".xbl":   "text/xml; charset=utf-8",
   121  	".xbm":   "image/x-xbitmap",
   122  	".xht":   "application/xhtml+xml",
   123  	".xhtml": "application/xhtml+xml",
   124  	".xls":   "application/vnd.ms-excel",
   125  	".xlsx":  "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
   126  	".xml":   "text/xml; charset=utf-8",
   127  	".xsl":   "text/xml; charset=utf-8",
   128  	".zip":   "application/zip",
   129  }
   130  
   131  var once sync.Once // guards initMime
   132  
   133  var testInitMime, osInitMime func()
   134  
   135  func initMime() {
   136  	if fn := testInitMime; fn != nil {
   137  		fn()
   138  	} else {
   139  		setMimeTypes(builtinTypesLower, builtinTypesLower)
   140  		osInitMime()
   141  	}
   142  }
   143  
   144  // TypeByExtension returns the MIME type associated with the file extension ext.
   145  // The extension ext should begin with a leading dot, as in ".html".
   146  // When ext has no associated type, TypeByExtension returns "".
   147  //
   148  // Extensions are looked up first case-sensitively, then case-insensitively.
   149  //
   150  // The built-in table is small but on unix it is augmented by the local
   151  // system's MIME-info database or mime.types file(s) if available under one or
   152  // more of these names:
   153  //
   154  //	/usr/local/share/mime/globs2
   155  //	/usr/share/mime/globs2
   156  //	/etc/mime.types
   157  //	/etc/apache2/mime.types
   158  //	/etc/apache/mime.types
   159  //	/etc/httpd/conf/mime.types
   160  //
   161  // On Windows, MIME types are extracted from the registry.
   162  //
   163  // Text types have the charset parameter set to "utf-8" by default.
   164  func TypeByExtension(ext string) string {
   165  	once.Do(initMime)
   166  
   167  	// Case-sensitive lookup.
   168  	if v, ok := mimeTypes.Load(ext); ok {
   169  		return v.(string)
   170  	}
   171  
   172  	// Case-insensitive lookup.
   173  	// Optimistically assume a short ASCII extension and be
   174  	// allocation-free in that case.
   175  	var buf [10]byte
   176  	lower := buf[:0]
   177  	const utf8RuneSelf = 0x80 // from utf8 package, but not importing it.
   178  	for i := 0; i < len(ext); i++ {
   179  		c := ext[i]
   180  		if c >= utf8RuneSelf {
   181  			// Slow path.
   182  			si, _ := mimeTypesLower.Load(strings.ToLower(ext))
   183  			s, _ := si.(string)
   184  			return s
   185  		}
   186  		if 'A' <= c && c <= 'Z' {
   187  			lower = append(lower, c+('a'-'A'))
   188  		} else {
   189  			lower = append(lower, c)
   190  		}
   191  	}
   192  	si, _ := mimeTypesLower.Load(string(lower))
   193  	s, _ := si.(string)
   194  	return s
   195  }
   196  
   197  // ExtensionsByType returns the extensions known to be associated with the MIME
   198  // type typ. The returned extensions will each begin with a leading dot, as in
   199  // ".html". When typ has no associated extensions, ExtensionsByType returns an
   200  // nil slice.
   201  //
   202  // The built-in table is small but on unix it is augmented by the local
   203  // system's MIME-info database or mime.types file(s) if available under one or
   204  // more of these names:
   205  //
   206  //	/usr/local/share/mime/globs2
   207  //	/usr/share/mime/globs2
   208  //	/etc/mime.types
   209  //	/etc/apache2/mime.types
   210  //	/etc/apache/mime.types
   211  //	/etc/httpd/conf/mime.types
   212  //
   213  // On Windows, extensions are extracted from the registry.
   214  func ExtensionsByType(typ string) ([]string, error) {
   215  	justType, _, err := ParseMediaType(typ)
   216  	if err != nil {
   217  		return nil, err
   218  	}
   219  
   220  	once.Do(initMime)
   221  	s, ok := extensions.Load(justType)
   222  	if !ok {
   223  		return nil, nil
   224  	}
   225  	ret := append([]string(nil), s.([]string)...)
   226  	slices.Sort(ret)
   227  	return ret, nil
   228  }
   229  
   230  // AddExtensionType sets the MIME type associated with
   231  // the extension ext to typ. The extension should begin with
   232  // a leading dot, as in ".html".
   233  func AddExtensionType(ext, typ string) error {
   234  	if !strings.HasPrefix(ext, ".") {
   235  		return fmt.Errorf("mime: extension %q missing leading dot", ext)
   236  	}
   237  	once.Do(initMime)
   238  	return setExtensionType(ext, typ)
   239  }
   240  
   241  func setExtensionType(extension, mimeType string) error {
   242  	justType, param, err := ParseMediaType(mimeType)
   243  	if err != nil {
   244  		return err
   245  	}
   246  	if strings.HasPrefix(mimeType, "text/") && param["charset"] == "" {
   247  		param["charset"] = "utf-8"
   248  		mimeType = FormatMediaType(mimeType, param)
   249  	}
   250  	extLower := strings.ToLower(extension)
   251  
   252  	mimeTypes.Store(extension, mimeType)
   253  	mimeTypesLower.Store(extLower, mimeType)
   254  
   255  	extensionsMu.Lock()
   256  	defer extensionsMu.Unlock()
   257  	var exts []string
   258  	if ei, ok := extensions.Load(justType); ok {
   259  		exts = ei.([]string)
   260  	}
   261  	for _, v := range exts {
   262  		if v == extLower {
   263  			return nil
   264  		}
   265  	}
   266  	extensions.Store(justType, append(exts, extLower))
   267  	return nil
   268  }
   269  

View as plain text