...

Source file src/mime/type_test.go

Documentation: 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
     6  
     7  import (
     8  	"internal/asan"
     9  	"slices"
    10  	"strings"
    11  	"sync"
    12  	"testing"
    13  )
    14  
    15  func setMimeInit(fn func()) (cleanup func()) {
    16  	once = sync.Once{}
    17  	testInitMime = fn
    18  	return func() {
    19  		testInitMime = nil
    20  		once = sync.Once{}
    21  	}
    22  }
    23  
    24  func clearMimeTypes() {
    25  	setMimeTypes(map[string]string{}, map[string]string{})
    26  }
    27  
    28  func setType(ext, typ string) {
    29  	if !strings.HasPrefix(ext, ".") {
    30  		panic("missing leading dot")
    31  	}
    32  	if err := setExtensionType(ext, typ); err != nil {
    33  		panic("bad test data: " + err.Error())
    34  	}
    35  }
    36  
    37  func TestTypeByExtension(t *testing.T) {
    38  	once = sync.Once{}
    39  	// initMimeForTests returns the platform-specific extension =>
    40  	// type tests. On Unix and Plan 9, this also tests the parsing
    41  	// of MIME text files (in testdata/*). On Windows, we test the
    42  	// real registry on the machine and assume that ".png" exists
    43  	// there, which empirically it always has, for all versions of
    44  	// Windows.
    45  	typeTests := initMimeForTests()
    46  
    47  	for ext, want := range typeTests {
    48  		val := TypeByExtension(ext)
    49  		if val != want {
    50  			t.Errorf("TypeByExtension(%q) = %q, want %q", ext, val, want)
    51  		}
    52  	}
    53  }
    54  
    55  func TestTypeByExtension_LocalData(t *testing.T) {
    56  	cleanup := setMimeInit(func() {
    57  		clearMimeTypes()
    58  		setType(".foo", "x/foo")
    59  		setType(".bar", "x/bar")
    60  		setType(".Bar", "x/bar; capital=1")
    61  	})
    62  	defer cleanup()
    63  
    64  	tests := map[string]string{
    65  		".foo":          "x/foo",
    66  		".bar":          "x/bar",
    67  		".Bar":          "x/bar; capital=1",
    68  		".sdlkfjskdlfj": "",
    69  		".t1":           "", // testdata shouldn't be used
    70  	}
    71  
    72  	for ext, want := range tests {
    73  		val := TypeByExtension(ext)
    74  		if val != want {
    75  			t.Errorf("TypeByExtension(%q) = %q, want %q", ext, val, want)
    76  		}
    77  	}
    78  }
    79  
    80  func TestTypeByExtensionCase(t *testing.T) {
    81  	const custom = "test/test; charset=iso-8859-1"
    82  	const caps = "test/test; WAS=ALLCAPS"
    83  
    84  	cleanup := setMimeInit(func() {
    85  		clearMimeTypes()
    86  		setType(".TEST", caps)
    87  		setType(".tesT", custom)
    88  	})
    89  	defer cleanup()
    90  
    91  	// case-sensitive lookup
    92  	if got := TypeByExtension(".tesT"); got != custom {
    93  		t.Fatalf("for .tesT, got %q; want %q", got, custom)
    94  	}
    95  	if got := TypeByExtension(".TEST"); got != caps {
    96  		t.Fatalf("for .TEST, got %q; want %s", got, caps)
    97  	}
    98  
    99  	// case-insensitive
   100  	if got := TypeByExtension(".TesT"); got != custom {
   101  		t.Fatalf("for .TesT, got %q; want %q", got, custom)
   102  	}
   103  }
   104  
   105  // Regression: setExtensionType must pass the parsed type (justType) to FormatMediaType
   106  // when defaulting charset for text/*. Passing the full MIME string breaks formatting
   107  // when the string already contains parameters (e.g. "text/foo; bar=baz"), and
   108  // FormatMediaType returns "".
   109  func TestAddExtensionType_TextMIMEWithParamsDefaultCharset(t *testing.T) {
   110  	cleanup := setMimeInit(func() {
   111  		clearMimeTypes()
   112  	})
   113  	defer cleanup()
   114  
   115  	const ext = ".regtxt"
   116  	const in = "text/x-reg; myparam=myvalue" // text/*, extra param, no charset
   117  	if err := AddExtensionType(ext, in); err != nil {
   118  		t.Fatal(err)
   119  	}
   120  	got := TypeByExtension(ext)
   121  	if got == "" {
   122  		t.Fatal("TypeByExtension: got empty string; want non-empty formatted text/* with default charset")
   123  	}
   124  	const wantSubstr = "charset=utf-8"
   125  	if !strings.Contains(got, wantSubstr) {
   126  		t.Fatalf("TypeByExtension(%q) = %q; want substring %q", ext, got, wantSubstr)
   127  	}
   128  	if !strings.HasPrefix(got, "text/x-reg") {
   129  		t.Fatalf("TypeByExtension(%q) = %q; want prefix text/x-reg", ext, got)
   130  	}
   131  }
   132  
   133  func TestExtensionsByType(t *testing.T) {
   134  	cleanup := setMimeInit(func() {
   135  		clearMimeTypes()
   136  		setType(".gif", "image/gif")
   137  		setType(".a", "foo/letter")
   138  		setType(".b", "foo/letter")
   139  		setType(".B", "foo/letter")
   140  		setType(".PNG", "image/png")
   141  	})
   142  	defer cleanup()
   143  
   144  	tests := []struct {
   145  		typ     string
   146  		want    []string
   147  		wantErr string
   148  	}{
   149  		{typ: "image/gif", want: []string{".gif"}},
   150  		{typ: "image/png", want: []string{".png"}}, // lowercase
   151  		{typ: "foo/letter", want: []string{".a", ".b"}},
   152  		{typ: "x/unknown", want: nil},
   153  	}
   154  
   155  	for _, tt := range tests {
   156  		got, err := ExtensionsByType(tt.typ)
   157  		if err != nil && tt.wantErr != "" && strings.Contains(err.Error(), tt.wantErr) {
   158  			continue
   159  		}
   160  		if err != nil {
   161  			t.Errorf("ExtensionsByType(%q) error: %v", tt.typ, err)
   162  			continue
   163  		}
   164  		if tt.wantErr != "" {
   165  			t.Errorf("ExtensionsByType(%q) = %q, %v; want error substring %q", tt.typ, got, err, tt.wantErr)
   166  			continue
   167  		}
   168  		if !slices.Equal(got, tt.want) {
   169  			t.Errorf("ExtensionsByType(%q) = %q; want %q", tt.typ, got, tt.want)
   170  		}
   171  	}
   172  }
   173  
   174  func TestLookupMallocs(t *testing.T) {
   175  	if asan.Enabled {
   176  		t.Skip("test allocates more with -asan; see #70079")
   177  	}
   178  	n := testing.AllocsPerRun(10000, func() {
   179  		TypeByExtension(".html")
   180  		TypeByExtension(".HtML")
   181  	})
   182  	if n > 0 {
   183  		t.Errorf("allocs = %v; want 0", n)
   184  	}
   185  }
   186  
   187  func BenchmarkTypeByExtension(b *testing.B) {
   188  	initMime()
   189  	b.ResetTimer()
   190  
   191  	for _, ext := range []string{
   192  		".html",
   193  		".HTML",
   194  		".unused",
   195  	} {
   196  		b.Run(ext, func(b *testing.B) {
   197  			b.RunParallel(func(pb *testing.PB) {
   198  				for pb.Next() {
   199  					TypeByExtension(ext)
   200  				}
   201  			})
   202  		})
   203  	}
   204  }
   205  
   206  func BenchmarkExtensionsByType(b *testing.B) {
   207  	initMime()
   208  	b.ResetTimer()
   209  
   210  	for _, typ := range []string{
   211  		"text/html",
   212  		"text/html; charset=utf-8",
   213  		"application/octet-stream",
   214  	} {
   215  		b.Run(typ, func(b *testing.B) {
   216  			b.RunParallel(func(pb *testing.PB) {
   217  				for pb.Next() {
   218  					if _, err := ExtensionsByType(typ); err != nil {
   219  						b.Fatal(err)
   220  					}
   221  				}
   222  			})
   223  		})
   224  	}
   225  }
   226  
   227  func TestExtensionsByType2(t *testing.T) {
   228  	cleanup := setMimeInit(func() {
   229  		clearMimeTypes()
   230  		// Initialize built-in types like in type.go before osInitMime.
   231  		setMimeTypes(builtinTypesLower, builtinTypesLower)
   232  	})
   233  	defer cleanup()
   234  
   235  	tests := []struct {
   236  		typ  string
   237  		want []string
   238  	}{
   239  		{typ: "application/postscript", want: []string{".ai", ".eps", ".ps"}},
   240  		{typ: "application/vnd.android.package-archive", want: []string{".apk"}},
   241  		{typ: "image/apng", want: []string{".apng"}},
   242  		{typ: "image/avif", want: []string{".avif"}},
   243  		{typ: "application/octet-stream", want: []string{".bin", ".com", ".exe"}},
   244  		{typ: "image/bmp", want: []string{".bmp"}},
   245  		{typ: "text/css; charset=utf-8", want: []string{".css"}},
   246  		{typ: "text/csv; charset=utf-8", want: []string{".csv"}},
   247  		{typ: "application/msword", want: []string{".doc"}},
   248  		{typ: "application/vnd.openxmlformats-officedocument.wordprocessingml.document", want: []string{".docx"}},
   249  		{typ: "text/html; charset=utf-8", want: []string{".ehtml", ".htm", ".html", ".shtml"}},
   250  		{typ: "message/rfc822", want: []string{".eml"}},
   251  		{typ: "audio/flac", want: []string{".flac"}},
   252  		{typ: "image/gif", want: []string{".gif"}},
   253  		{typ: "application/gzip", want: []string{".gz"}},
   254  		{typ: "image/vnd.microsoft.icon", want: []string{".ico"}},
   255  		{typ: "text/calendar; charset=utf-8", want: []string{".ics"}},
   256  		{typ: "image/jpeg", want: []string{".jfif", ".jpeg", ".jpg", ".pjp", ".pjpeg"}},
   257  		{typ: "text/javascript; charset=utf-8", want: []string{".js", ".mjs"}},
   258  		{typ: "application/json", want: []string{".json"}},
   259  		{typ: "audio/mp4", want: []string{".m4a"}},
   260  		{typ: "audio/mpeg", want: []string{".mp3"}},
   261  		{typ: "video/mp4", want: []string{".mp4"}},
   262  		{typ: "audio/ogg", want: []string{".oga", ".ogg", ".opus"}},
   263  		{typ: "video/ogg", want: []string{".ogv"}},
   264  		{typ: "application/pdf", want: []string{".pdf"}},
   265  		{typ: "image/png", want: []string{".png"}},
   266  		{typ: "application/vnd.ms-powerpoint", want: []string{".ppt"}},
   267  		{typ: "application/vnd.openxmlformats-officedocument.presentationml.presentation", want: []string{".pptx"}},
   268  		{typ: "application/rdf+xml", want: []string{".rdf"}},
   269  		{typ: "application/rtf", want: []string{".rtf"}},
   270  		{typ: "image/svg+xml", want: []string{".svg"}},
   271  		{typ: "text/plain; charset=utf-8", want: []string{".text", ".txt"}},
   272  		{typ: "image/tiff", want: []string{".tif", ".tiff"}},
   273  		{typ: "text/vtt; charset=utf-8", want: []string{".vtt"}},
   274  		{typ: "application/wasm", want: []string{".wasm"}},
   275  		{typ: "audio/wav", want: []string{".wav"}},
   276  		{typ: "audio/webm", want: []string{".weba"}},
   277  		{typ: "video/webm", want: []string{".webm"}},
   278  		{typ: "image/webp", want: []string{".webp"}},
   279  		{typ: "text/xml; charset=utf-8", want: []string{".xbl", ".xml", ".xsl"}},
   280  		{typ: "image/x-xbitmap", want: []string{".xbm"}},
   281  		{typ: "application/xhtml+xml", want: []string{".xht", ".xhtml"}},
   282  		{typ: "application/vnd.ms-excel", want: []string{".xls"}},
   283  		{typ: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", want: []string{".xlsx"}},
   284  		{typ: "application/zip", want: []string{".zip"}},
   285  	}
   286  
   287  	for _, tt := range tests {
   288  		got, err := ExtensionsByType(tt.typ)
   289  		if err != nil {
   290  			t.Errorf("ExtensionsByType(%q): %v", tt.typ, err)
   291  			continue
   292  		}
   293  		if !slices.Equal(got, tt.want) {
   294  			t.Errorf("ExtensionsByType(%q) = %q; want %q", tt.typ, got, tt.want)
   295  		}
   296  	}
   297  }
   298  

View as plain text