Home | History | Annotate | Download | only in mime
      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 	"strings"
     11 	"sync"
     12 )
     13 
     14 var (
     15 	mimeLock       sync.RWMutex      // guards following 3 maps
     16 	mimeTypes      map[string]string // ".Z" => "application/x-compress"
     17 	mimeTypesLower map[string]string // ".z" => "application/x-compress"
     18 
     19 	// extensions maps from MIME type to list of lowercase file
     20 	// extensions: "image/jpeg" => [".jpg", ".jpeg"]
     21 	extensions map[string][]string
     22 )
     23 
     24 // setMimeTypes is used by initMime's non-test path, and by tests.
     25 // The two maps must not be the same, or nil.
     26 func setMimeTypes(lowerExt, mixExt map[string]string) {
     27 	if lowerExt == nil || mixExt == nil {
     28 		panic("nil map")
     29 	}
     30 	mimeTypesLower = lowerExt
     31 	mimeTypes = mixExt
     32 	extensions = invert(lowerExt)
     33 }
     34 
     35 var builtinTypesLower = map[string]string{
     36 	".css":  "text/css; charset=utf-8",
     37 	".gif":  "image/gif",
     38 	".htm":  "text/html; charset=utf-8",
     39 	".html": "text/html; charset=utf-8",
     40 	".jpg":  "image/jpeg",
     41 	".js":   "application/x-javascript",
     42 	".pdf":  "application/pdf",
     43 	".png":  "image/png",
     44 	".svg":  "image/svg+xml",
     45 	".xml":  "text/xml; charset=utf-8",
     46 }
     47 
     48 func clone(m map[string]string) map[string]string {
     49 	m2 := make(map[string]string, len(m))
     50 	for k, v := range m {
     51 		m2[k] = v
     52 		if strings.ToLower(k) != k {
     53 			panic("keys in builtinTypesLower must be lowercase")
     54 		}
     55 	}
     56 	return m2
     57 }
     58 
     59 func invert(m map[string]string) map[string][]string {
     60 	m2 := make(map[string][]string, len(m))
     61 	for k, v := range m {
     62 		justType, _, err := ParseMediaType(v)
     63 		if err != nil {
     64 			panic(err)
     65 		}
     66 		m2[justType] = append(m2[justType], k)
     67 	}
     68 	return m2
     69 }
     70 
     71 var once sync.Once // guards initMime
     72 
     73 var testInitMime, osInitMime func()
     74 
     75 func initMime() {
     76 	if fn := testInitMime; fn != nil {
     77 		fn()
     78 	} else {
     79 		setMimeTypes(builtinTypesLower, clone(builtinTypesLower))
     80 		osInitMime()
     81 	}
     82 }
     83 
     84 // TypeByExtension returns the MIME type associated with the file extension ext.
     85 // The extension ext should begin with a leading dot, as in ".html".
     86 // When ext has no associated type, TypeByExtension returns "".
     87 //
     88 // Extensions are looked up first case-sensitively, then case-insensitively.
     89 //
     90 // The built-in table is small but on unix it is augmented by the local
     91 // system's mime.types file(s) if available under one or more of these
     92 // names:
     93 //
     94 //   /etc/mime.types
     95 //   /etc/apache2/mime.types
     96 //   /etc/apache/mime.types
     97 //
     98 // On Windows, MIME types are extracted from the registry.
     99 //
    100 // Text types have the charset parameter set to "utf-8" by default.
    101 func TypeByExtension(ext string) string {
    102 	once.Do(initMime)
    103 	mimeLock.RLock()
    104 	defer mimeLock.RUnlock()
    105 
    106 	// Case-sensitive lookup.
    107 	if v := mimeTypes[ext]; v != "" {
    108 		return v
    109 	}
    110 
    111 	// Case-insensitive lookup.
    112 	// Optimistically assume a short ASCII extension and be
    113 	// allocation-free in that case.
    114 	var buf [10]byte
    115 	lower := buf[:0]
    116 	const utf8RuneSelf = 0x80 // from utf8 package, but not importing it.
    117 	for i := 0; i < len(ext); i++ {
    118 		c := ext[i]
    119 		if c >= utf8RuneSelf {
    120 			// Slow path.
    121 			return mimeTypesLower[strings.ToLower(ext)]
    122 		}
    123 		if 'A' <= c && c <= 'Z' {
    124 			lower = append(lower, c+('a'-'A'))
    125 		} else {
    126 			lower = append(lower, c)
    127 		}
    128 	}
    129 	// The conversion from []byte to string doesn't allocate in
    130 	// a map lookup.
    131 	return mimeTypesLower[string(lower)]
    132 }
    133 
    134 // ExtensionsByType returns the extensions known to be associated with the MIME
    135 // type typ. The returned extensions will each begin with a leading dot, as in
    136 // ".html". When typ has no associated extensions, ExtensionsByType returns an
    137 // nil slice.
    138 func ExtensionsByType(typ string) ([]string, error) {
    139 	justType, _, err := ParseMediaType(typ)
    140 	if err != nil {
    141 		return nil, err
    142 	}
    143 
    144 	once.Do(initMime)
    145 	mimeLock.RLock()
    146 	defer mimeLock.RUnlock()
    147 	s, ok := extensions[justType]
    148 	if !ok {
    149 		return nil, nil
    150 	}
    151 	return append([]string{}, s...), nil
    152 }
    153 
    154 // AddExtensionType sets the MIME type associated with
    155 // the extension ext to typ. The extension should begin with
    156 // a leading dot, as in ".html".
    157 func AddExtensionType(ext, typ string) error {
    158 	if !strings.HasPrefix(ext, ".") {
    159 		return fmt.Errorf("mime: extension %q missing leading dot", ext)
    160 	}
    161 	once.Do(initMime)
    162 	return setExtensionType(ext, typ)
    163 }
    164 
    165 func setExtensionType(extension, mimeType string) error {
    166 	justType, param, err := ParseMediaType(mimeType)
    167 	if err != nil {
    168 		return err
    169 	}
    170 	if strings.HasPrefix(mimeType, "text/") && param["charset"] == "" {
    171 		param["charset"] = "utf-8"
    172 		mimeType = FormatMediaType(mimeType, param)
    173 	}
    174 	extLower := strings.ToLower(extension)
    175 
    176 	mimeLock.Lock()
    177 	defer mimeLock.Unlock()
    178 	mimeTypes[extension] = mimeType
    179 	mimeTypesLower[extLower] = mimeType
    180 	for _, v := range extensions[justType] {
    181 		if v == extLower {
    182 			return nil
    183 		}
    184 	}
    185 	extensions[justType] = append(extensions[justType], extLower)
    186 	return nil
    187 }
    188