diff options
author | Alexey Sharov <www.pismeco@gmail.com> | 2018-10-01 19:39:39 +0800 |
---|---|---|
committer | Anton Evangelatov <anton.evangelatov@gmail.com> | 2018-10-01 19:39:39 +0800 |
commit | dc5d643bb59812cda578fac941c2f1da316bc9d7 (patch) | |
tree | 7405f387672f0548eb4734a93581780c96cee7a9 /swarm | |
parent | b69942befeb9f1af55cad0f91953bdaea2ea3efb (diff) | |
download | dexon-dc5d643bb59812cda578fac941c2f1da316bc9d7.tar.gz dexon-dc5d643bb59812cda578fac941c2f1da316bc9d7.tar.zst dexon-dc5d643bb59812cda578fac941c2f1da316bc9d7.zip |
cmd/swarm, swarm: cross-platform Content-Type detection (#17782)
- Mime types generator (Standard "mime" package rely on system-settings, see mime.osInitMime)
- Changed swarm/api.Upload:
- simplify I/O throttling by semaphore primitive and use file name where possible
- f.Close() must be called in Defer - otherwise panic or future added early return will cause leak of file descriptors
- one error was suppressed
Diffstat (limited to 'swarm')
-rw-r--r-- | swarm/api/api.go | 51 | ||||
-rw-r--r-- | swarm/api/api_test.go | 67 | ||||
-rw-r--r-- | swarm/api/client/client.go | 9 | ||||
-rw-r--r-- | swarm/api/filesystem.go | 81 | ||||
-rw-r--r-- | swarm/api/filesystem_test.go | 4 | ||||
-rw-r--r-- | swarm/api/gen_mime.go | 1201 | ||||
-rw-r--r-- | swarm/api/http/server.go | 26 | ||||
-rw-r--r-- | swarm/api/http/server_test.go | 47 |
8 files changed, 1419 insertions, 67 deletions
diff --git a/swarm/api/api.go b/swarm/api/api.go index 70c12a757..7b8f04c13 100644 --- a/swarm/api/api.go +++ b/swarm/api/api.go @@ -16,6 +16,9 @@ package api +//go:generate mimegen --types=./../../cmd/swarm/mimegen/mime.types --package=api --out=gen_mime.go +//go:generate gofmt -s -w gen_mime.go + import ( "archive/tar" "context" @@ -29,8 +32,6 @@ import ( "path" "strings" - "github.com/ethereum/go-ethereum/swarm/storage/mru/lookup" - "bytes" "mime" "path/filepath" @@ -45,7 +46,8 @@ import ( "github.com/ethereum/go-ethereum/swarm/spancontext" "github.com/ethereum/go-ethereum/swarm/storage" "github.com/ethereum/go-ethereum/swarm/storage/mru" - opentracing "github.com/opentracing/opentracing-go" + "github.com/ethereum/go-ethereum/swarm/storage/mru/lookup" + "github.com/opentracing/opentracing-go" ) var ( @@ -757,9 +759,14 @@ func (a *API) UploadTar(ctx context.Context, bodyReader io.ReadCloser, manifestP // add the entry under the path from the request manifestPath := path.Join(manifestPath, hdr.Name) + contentType := hdr.Xattrs["user.swarm.content-type"] + if contentType == "" { + contentType = mime.TypeByExtension(filepath.Ext(hdr.Name)) + } + //DetectContentType("") entry := &ManifestEntry{ Path: manifestPath, - ContentType: hdr.Xattrs["user.swarm.content-type"], + ContentType: contentType, Mode: hdr.Mode, Size: hdr.Size, ModTime: hdr.ModTime, @@ -770,10 +777,15 @@ func (a *API) UploadTar(ctx context.Context, bodyReader io.ReadCloser, manifestP return nil, fmt.Errorf("error adding manifest entry from tar stream: %s", err) } if hdr.Name == defaultPath { + contentType := hdr.Xattrs["user.swarm.content-type"] + if contentType == "" { + contentType = mime.TypeByExtension(filepath.Ext(hdr.Name)) + } + entry := &ManifestEntry{ Hash: contentKey.Hex(), Path: "", // default entry - ContentType: hdr.Xattrs["user.swarm.content-type"], + ContentType: contentType, Mode: hdr.Mode, Size: hdr.Size, ModTime: hdr.ModTime, @@ -1033,3 +1045,32 @@ func (a *API) ResolveResourceView(ctx context.Context, uri *URI, values mru.Valu } return view, nil } + +// MimeOctetStream default value of http Content-Type header +const MimeOctetStream = "application/octet-stream" + +// DetectContentType by file file extension, or fallback to content sniff +func DetectContentType(fileName string, f io.ReadSeeker) (string, error) { + ctype := mime.TypeByExtension(filepath.Ext(fileName)) + if ctype != "" { + return ctype, nil + } + + // save/rollback to get content probe from begin of file + currentPosition, err := f.Seek(0, io.SeekCurrent) + if err != nil { + return MimeOctetStream, fmt.Errorf("seeker can't seek, %s", err) + } + + // read a chunk to decide between utf-8 text and binary + var buf [512]byte + n, _ := f.Read(buf[:]) + ctype = http.DetectContentType(buf[:n]) + + _, err = f.Seek(currentPosition, io.SeekStart) // rewind to output whole file + if err != nil { + return MimeOctetStream, fmt.Errorf("seeker can't seek, %s", err) + } + + return ctype, nil +} diff --git a/swarm/api/api_test.go b/swarm/api/api_test.go index a65bf07e2..eb896f32a 100644 --- a/swarm/api/api_test.go +++ b/swarm/api/api_test.go @@ -17,6 +17,7 @@ package api import ( + "bytes" "context" "errors" "flag" @@ -433,3 +434,69 @@ func TestDecryptOrigin(t *testing.T) { } } } + +func TestDetectContentType(t *testing.T) { + for _, tc := range []struct { + file string + content string + expectedContentType string + }{ + { + file: "file-with-correct-css.css", + content: "body {background-color: orange}", + expectedContentType: "text/css; charset=utf-8", + }, + { + file: "empty-file.css", + content: "", + expectedContentType: "text/css; charset=utf-8", + }, + { + file: "empty-file.pdf", + content: "", + expectedContentType: "application/pdf", + }, + { + file: "empty-file.md", + content: "", + expectedContentType: "text/markdown; charset=utf-8", + }, + { + file: "empty-file-with-unknown-content.strangeext", + content: "", + expectedContentType: "text/plain; charset=utf-8", + }, + { + file: "file-with-unknown-extension-and-content.strangeext", + content: "Lorem Ipsum", + expectedContentType: "text/plain; charset=utf-8", + }, + { + file: "file-no-extension", + content: "Lorem Ipsum", + expectedContentType: "text/plain; charset=utf-8", + }, + { + file: "file-no-extension-no-content", + content: "", + expectedContentType: "text/plain; charset=utf-8", + }, + { + file: "css-file-with-html-inside.css", + content: "<!doctype html><html><head></head><body></body></html>", + expectedContentType: "text/css; charset=utf-8", + }, + } { + t.Run(tc.file, func(t *testing.T) { + detected, err := DetectContentType(tc.file, bytes.NewReader([]byte(tc.content))) + if err != nil { + t.Fatal(err) + } + + if detected != tc.expectedContentType { + t.Fatalf("File: %s, Expected mime type %s, got %s", tc.file, tc.expectedContentType, detected) + } + + }) + } +} diff --git a/swarm/api/client/client.go b/swarm/api/client/client.go index a6666144a..47a6980de 100644 --- a/swarm/api/client/client.go +++ b/swarm/api/client/client.go @@ -24,7 +24,6 @@ import ( "fmt" "io" "io/ioutil" - "mime" "mime/multipart" "net/http" "net/textproto" @@ -124,10 +123,16 @@ func Open(path string) (*File, error) { f.Close() return nil, err } + + contentType, err := api.DetectContentType(f.Name(), f) + if err != nil { + return nil, err + } + return &File{ ReadCloser: f, ManifestEntry: api.ManifestEntry{ - ContentType: mime.TypeByExtension(filepath.Ext(path)), + ContentType: contentType, Mode: int64(stat.Mode()), Size: stat.Size(), ModTime: stat.ModTime(), diff --git a/swarm/api/filesystem.go b/swarm/api/filesystem.go index 8251ebc4d..43695efc1 100644 --- a/swarm/api/filesystem.go +++ b/swarm/api/filesystem.go @@ -21,7 +21,6 @@ import ( "context" "fmt" "io" - "net/http" "os" "path" "path/filepath" @@ -97,51 +96,50 @@ func (fs *FileSystem) Upload(lpath, index string, toEncrypt bool) (string, error list = append(list, entry) } - cnt := len(list) - errors := make([]error, cnt) - done := make(chan bool, maxParallelFiles) - dcnt := 0 - awg := &sync.WaitGroup{} + errors := make([]error, len(list)) + sem := make(chan bool, maxParallelFiles) + defer close(sem) for i, entry := range list { - if i >= dcnt+maxParallelFiles { - <-done - dcnt++ - } - awg.Add(1) - go func(i int, entry *manifestTrieEntry, done chan bool) { + sem <- true + go func(i int, entry *manifestTrieEntry) { + defer func() { <-sem }() + f, err := os.Open(entry.Path) - if err == nil { - stat, _ := f.Stat() - var hash storage.Address - var wait func(context.Context) error - ctx := context.TODO() - hash, wait, err = fs.api.fileStore.Store(ctx, f, stat.Size(), toEncrypt) - if hash != nil { - list[i].Hash = hash.Hex() - } - err = wait(ctx) - awg.Done() - if err == nil { - first512 := make([]byte, 512) - fread, _ := f.ReadAt(first512, 0) - if fread > 0 { - mimeType := http.DetectContentType(first512[:fread]) - if filepath.Ext(entry.Path) == ".css" { - mimeType = "text/css" - } - list[i].ContentType = mimeType - } - } - f.Close() + if err != nil { + errors[i] = err + return + } + defer f.Close() + + stat, err := f.Stat() + if err != nil { + errors[i] = err + return + } + + var hash storage.Address + var wait func(context.Context) error + ctx := context.TODO() + hash, wait, err = fs.api.fileStore.Store(ctx, f, stat.Size(), toEncrypt) + if hash != nil { + list[i].Hash = hash.Hex() } - errors[i] = err - done <- true - }(i, entry, done) + if err := wait(ctx); err != nil { + errors[i] = err + return + } + + list[i].ContentType, err = DetectContentType(f.Name(), f) + if err != nil { + errors[i] = err + return + } + + }(i, entry) } - for dcnt < cnt { - <-done - dcnt++ + for i := 0; i < cap(sem); i++ { + sem <- true } trie := &manifestTrie{ @@ -168,7 +166,6 @@ func (fs *FileSystem) Upload(lpath, index string, toEncrypt bool) (string, error if err2 == nil { hs = trie.ref.Hex() } - awg.Wait() return hs, err2 } diff --git a/swarm/api/filesystem_test.go b/swarm/api/filesystem_test.go index fe7527b1f..02f5bff65 100644 --- a/swarm/api/filesystem_test.go +++ b/swarm/api/filesystem_test.go @@ -60,7 +60,7 @@ func TestApiDirUpload0(t *testing.T) { content = readPath(t, "testdata", "test0", "index.css") resp = testGet(t, api, bzzhash, "index.css") - exp = expResponse(content, "text/css", 0) + exp = expResponse(content, "text/css; charset=utf-8", 0) checkResponse(t, resp, exp) addr := storage.Address(common.Hex2Bytes(bzzhash)) @@ -140,7 +140,7 @@ func TestApiDirUploadModify(t *testing.T) { content = readPath(t, "testdata", "test0", "index.css") resp = testGet(t, api, bzzhash, "index.css") - exp = expResponse(content, "text/css", 0) + exp = expResponse(content, "text/css; charset=utf-8", 0) checkResponse(t, resp, exp) _, _, _, _, err = api.Get(context.TODO(), nil, addr, "") diff --git a/swarm/api/gen_mime.go b/swarm/api/gen_mime.go new file mode 100644 index 000000000..109edeb50 --- /dev/null +++ b/swarm/api/gen_mime.go @@ -0,0 +1,1201 @@ +// Code generated by github.com/ethereum/go-ethereum/cmd/swarm/mimegen. DO NOT EDIT. + +package api + +import "mime" + +func init() { + var mimeTypes = map[string]string{ + ".a2l": "application/A2L", + ".aml": "application/AML", + ".ez": "application/andrew-inset", + ".atf": "application/ATF", + ".atfx": "application/ATFX", + ".atxml": "application/ATXML", + ".atom": "application/atom+xml", + ".atomcat": "application/atomcat+xml", + ".atomdeleted": "application/atomdeleted+xml", + ".atomsvc": "application/atomsvc+xml", + ".apxml": "application/auth-policy+xml", + ".xdd": "application/bacnet-xdd+zip", + ".xcs": "application/calendar+xml", + ".cbor": "application/cbor", + ".ccmp": "application/ccmp+xml", + ".ccxml": "application/ccxml+xml", + ".cdfx": "application/CDFX+XML", + ".cdmia": "application/cdmi-capability", + ".cdmic": "application/cdmi-container", + ".cdmid": "application/cdmi-domain", + ".cdmio": "application/cdmi-object", + ".cdmiq": "application/cdmi-queue", + ".cea": "application/CEA", + ".cellml": "application/cellml+xml", + ".cml": "application/cellml+xml", + ".clue": "application/clue_info+xml", + ".cmsc": "application/cms", + ".cpl": "application/cpl+xml", + ".csrattrs": "application/csrattrs", + ".mpd": "application/dash+xml", + ".mpdd": "application/dashdelta", + ".davmount": "application/davmount+xml", + ".dcd": "application/DCD", + ".dcm": "application/dicom", + ".dii": "application/DII", + ".dit": "application/DIT", + ".xmls": "application/dskpp+xml", + ".dssc": "application/dssc+der", + ".xdssc": "application/dssc+xml", + ".dvc": "application/dvcs", + ".es": "application/ecmascript", + ".efi": "application/efi", + ".emma": "application/emma+xml", + ".emotionml": "application/emotionml+xml", + ".epub": "application/epub+zip", + ".exi": "application/exi", + ".finf": "application/fastinfoset", + ".fdt": "application/fdt+xml", + ".pfr": "application/font-tdpfr", + ".geojson": "application/geo+json", + ".gml": "application/gml+xml", + ".gz": "application/gzip", + ".tgz": "application/gzip", + ".stk": "application/hyperstudio", + ".ink": "application/inkml+xml", + ".inkml": "application/inkml+xml", + ".ipfix": "application/ipfix", + ".its": "application/its+xml", + ".js": "application/javascript", + ".jrd": "application/jrd+json", + ".json": "application/json", + ".json-patch": "application/json-patch+json", + ".jsonld": "application/ld+json", + ".lgr": "application/lgr+xml", + ".wlnk": "application/link-format", + ".lostxml": "application/lost+xml", + ".lostsyncxml": "application/lostsync+xml", + ".lxf": "application/LXF", + ".hqx": "application/mac-binhex40", + ".mads": "application/mads+xml", + ".mrc": "application/marc", + ".mrcx": "application/marcxml+xml", + ".nb": "application/mathematica", + ".ma": "application/mathematica", + ".mb": "application/mathematica", + ".mml": "application/mathml+xml", + ".mbox": "application/mbox", + ".meta4": "application/metalink4+xml", + ".mets": "application/mets+xml", + ".mf4": "application/MF4", + ".mods": "application/mods+xml", + ".m21": "application/mp21", + ".mp21": "application/mp21", + ".doc": "application/msword", + ".mxf": "application/mxf", + ".nq": "application/n-quads", + ".nt": "application/n-triples", + ".orq": "application/ocsp-request", + ".ors": "application/ocsp-response", + ".bin": "application/octet-stream", + ".lha": "application/octet-stream", + ".lzh": "application/octet-stream", + ".exe": "application/octet-stream", + ".class": "application/octet-stream", + ".so": "application/octet-stream", + ".dll": "application/octet-stream", + ".img": "application/octet-stream", + ".iso": "application/octet-stream", + ".oda": "application/oda", + ".odx": "application/ODX", + ".opf": "application/oebps-package+xml", + ".ogx": "application/ogg", + ".oxps": "application/oxps", + ".relo": "application/p2p-overlay+xml", + ".pdf": "application/pdf", + ".pdx": "application/PDX", + ".pgp": "application/pgp-encrypted", + ".sig": "application/pgp-signature", + ".p10": "application/pkcs10", + ".p12": "application/pkcs12", + ".pfx": "application/pkcs12", + ".p7m": "application/pkcs7-mime", + ".p7c": "application/pkcs7-mime", + ".p7s": "application/pkcs7-signature", + ".p8": "application/pkcs8", + ".cer": "application/pkix-cert", + ".crl": "application/pkix-crl", + ".pkipath": "application/pkix-pkipath", + ".pki": "application/pkixcmp", + ".pls": "application/pls+xml", + ".ps": "application/postscript", + ".eps": "application/postscript", + ".ai": "application/postscript", + ".provx": "application/provenance+xml", + ".cw": "application/prs.cww", + ".cww": "application/prs.cww", + ".hpub": "application/prs.hpub+zip", + ".rnd": "application/prs.nprend", + ".rct": "application/prs.nprend", + ".rdf-crypt": "application/prs.rdf-xml-crypt", + ".xsf": "application/prs.xsf+xml", + ".pskcxml": "application/pskc+xml", + ".rdf": "application/rdf+xml", + ".rif": "application/reginfo+xml", + ".rnc": "application/relax-ng-compact-syntax", + ".rld": "application/resource-lists-diff+xml", + ".rl": "application/resource-lists+xml", + ".rfcxml": "application/rfc+xml", + ".rs": "application/rls-services+xml", + ".gbr": "application/rpki-ghostbusters", + ".mft": "application/rpki-manifest", + ".roa": "application/rpki-roa", + ".rtf": "application/rtf", + ".scim": "application/scim+json", + ".scq": "application/scvp-cv-request", + ".scs": "application/scvp-cv-response", + ".spq": "application/scvp-vp-request", + ".spp": "application/scvp-vp-response", + ".sdp": "application/sdp", + ".soc": "application/sgml-open-catalog", + ".shf": "application/shf+xml", + ".siv": "application/sieve", + ".sieve": "application/sieve", + ".cl": "application/simple-filter+xml", + ".smil": "application/smil+xml", + ".smi": "application/smil+xml", + ".sml": "application/smil+xml", + ".rq": "application/sparql-query", + ".srx": "application/sparql-results+xml", + ".sql": "application/sql", + ".gram": "application/srgs", + ".grxml": "application/srgs+xml", + ".sru": "application/sru+xml", + ".ssml": "application/ssml+xml", + ".tau": "application/tamp-apex-update", + ".auc": "application/tamp-apex-update-confirm", + ".tcu": "application/tamp-community-update", + ".cuc": "application/tamp-community-update-confirm", + ".ter": "application/tamp-error", + ".tsa": "application/tamp-sequence-adjust", + ".sac": "application/tamp-sequence-adjust-confirm", + ".tur": "application/tamp-update", + ".tuc": "application/tamp-update-confirm", + ".tei": "application/tei+xml", + ".teiCorpus": "application/tei+xml", + ".odd": "application/tei+xml", + ".tfi": "application/thraud+xml", + ".tsq": "application/timestamp-query", + ".tsr": "application/timestamp-reply", + ".tsd": "application/timestamped-data", + ".trig": "application/trig", + ".ttml": "application/ttml+xml", + ".gsheet": "application/urc-grpsheet+xml", + ".rsheet": "application/urc-ressheet+xml", + ".td": "application/urc-targetdesc+xml", + ".uis": "application/urc-uisocketdesc+xml", + ".plb": "application/vnd.3gpp.pic-bw-large", + ".psb": "application/vnd.3gpp.pic-bw-small", + ".pvb": "application/vnd.3gpp.pic-bw-var", + ".sms": "application/vnd.3gpp2.sms", + ".tcap": "application/vnd.3gpp2.tcap", + ".imgcal": "application/vnd.3lightssoftware.imagescal", + ".pwn": "application/vnd.3M.Post-it-Notes", + ".aso": "application/vnd.accpac.simply.aso", + ".imp": "application/vnd.accpac.simply.imp", + ".acu": "application/vnd.acucobol", + ".atc": "application/vnd.acucorp", + ".acutc": "application/vnd.acucorp", + ".swf": "application/vnd.adobe.flash.movie", + ".fcdt": "application/vnd.adobe.formscentral.fcdt", + ".fxp": "application/vnd.adobe.fxp", + ".fxpl": "application/vnd.adobe.fxp", + ".xdp": "application/vnd.adobe.xdp+xml", + ".xfdf": "application/vnd.adobe.xfdf", + ".ahead": "application/vnd.ahead.space", + ".azf": "application/vnd.airzip.filesecure.azf", + ".azs": "application/vnd.airzip.filesecure.azs", + ".azw3": "application/vnd.amazon.mobi8-ebook", + ".acc": "application/vnd.americandynamics.acc", + ".ami": "application/vnd.amiga.ami", + ".apkg": "application/vnd.anki", + ".cii": "application/vnd.anser-web-certificate-issue-initiation", + ".fti": "application/vnd.anser-web-funds-transfer-initiation", + ".dist": "application/vnd.apple.installer+xml", + ".distz": "application/vnd.apple.installer+xml", + ".pkg": "application/vnd.apple.installer+xml", + ".mpkg": "application/vnd.apple.installer+xml", + ".m3u8": "application/vnd.apple.mpegurl", + ".swi": "application/vnd.aristanetworks.swi", + ".iota": "application/vnd.astraea-software.iota", + ".aep": "application/vnd.audiograph", + ".package": "application/vnd.autopackage", + ".bmml": "application/vnd.balsamiq.bmml+xml", + ".bmpr": "application/vnd.balsamiq.bmpr", + ".mpm": "application/vnd.blueice.multipass", + ".ep": "application/vnd.bluetooth.ep.oob", + ".le": "application/vnd.bluetooth.le.oob", + ".bmi": "application/vnd.bmi", + ".rep": "application/vnd.businessobjects", + ".tlclient": "application/vnd.cendio.thinlinc.clientconf", + ".cdxml": "application/vnd.chemdraw+xml", + ".pgn": "application/vnd.chess-pgn", + ".mmd": "application/vnd.chipnuts.karaoke-mmd", + ".cdy": "application/vnd.cinderella", + ".csl": "application/vnd.citationstyles.style+xml", + ".cla": "application/vnd.claymore", + ".rp9": "application/vnd.cloanto.rp9", + ".c4g": "application/vnd.clonk.c4group", + ".c4d": "application/vnd.clonk.c4group", + ".c4f": "application/vnd.clonk.c4group", + ".c4p": "application/vnd.clonk.c4group", + ".c4u": "application/vnd.clonk.c4group", + ".c11amc": "application/vnd.cluetrust.cartomobile-config", + ".c11amz": "application/vnd.cluetrust.cartomobile-config-pkg", + ".coffee": "application/vnd.coffeescript", + ".cbz": "application/vnd.comicbook+zip", + ".ica": "application/vnd.commerce-battelle", + ".icf": "application/vnd.commerce-battelle", + ".icd": "application/vnd.commerce-battelle", + ".ic0": "application/vnd.commerce-battelle", + ".ic1": "application/vnd.commerce-battelle", + ".ic2": "application/vnd.commerce-battelle", + ".ic3": "application/vnd.commerce-battelle", + ".ic4": "application/vnd.commerce-battelle", + ".ic5": "application/vnd.commerce-battelle", + ".ic6": "application/vnd.commerce-battelle", + ".ic7": "application/vnd.commerce-battelle", + ".ic8": "application/vnd.commerce-battelle", + ".csp": "application/vnd.commonspace", + ".cst": "application/vnd.commonspace", + ".cdbcmsg": "application/vnd.contact.cmsg", + ".ign": "application/vnd.coreos.ignition+json", + ".ignition": "application/vnd.coreos.ignition+json", + ".cmc": "application/vnd.cosmocaller", + ".clkx": "application/vnd.crick.clicker", + ".clkk": "application/vnd.crick.clicker.keyboard", + ".clkp": "application/vnd.crick.clicker.palette", + ".clkt": "application/vnd.crick.clicker.template", + ".clkw": "application/vnd.crick.clicker.wordbank", + ".wbs": "application/vnd.criticaltools.wbs+xml", + ".pml": "application/vnd.ctc-posml", + ".ppd": "application/vnd.cups-ppd", + ".curl": "application/vnd.curl", + ".dart": "application/vnd.dart", + ".rdz": "application/vnd.data-vision.rdz", + ".deb": "application/vnd.debian.binary-package", + ".udeb": "application/vnd.debian.binary-package", + ".uvf": "application/vnd.dece.data", + ".uvvf": "application/vnd.dece.data", + ".uvd": "application/vnd.dece.data", + ".uvvd": "application/vnd.dece.data", + ".uvt": "application/vnd.dece.ttml+xml", + ".uvvt": "application/vnd.dece.ttml+xml", + ".uvx": "application/vnd.dece.unspecified", + ".uvvx": "application/vnd.dece.unspecified", + ".uvz": "application/vnd.dece.zip", + ".uvvz": "application/vnd.dece.zip", + ".fe_launch": "application/vnd.denovo.fcselayout-link", + ".dsm": "application/vnd.desmume.movie", + ".dna": "application/vnd.dna", + ".docjson": "application/vnd.document+json", + ".scld": "application/vnd.doremir.scorecloud-binary-document", + ".dpg": "application/vnd.dpgraph", + ".mwc": "application/vnd.dpgraph", + ".dpgraph": "application/vnd.dpgraph", + ".dfac": "application/vnd.dreamfactory", + ".fla": "application/vnd.dtg.local.flash", + ".ait": "application/vnd.dvb.ait", + ".svc": "application/vnd.dvb.service", + ".geo": "application/vnd.dynageo", + ".dzr": "application/vnd.dzr", + ".mag": "application/vnd.ecowin.chart", + ".nml": "application/vnd.enliven", + ".esf": "application/vnd.epson.esf", + ".msf": "application/vnd.epson.msf", + ".qam": "application/vnd.epson.quickanime", + ".slt": "application/vnd.epson.salt", + ".ssf": "application/vnd.epson.ssf", + ".qcall": "application/vnd.ericsson.quickcall", + ".qca": "application/vnd.ericsson.quickcall", + ".espass": "application/vnd.espass-espass+zip", + ".es3": "application/vnd.eszigno3+xml", + ".et3": "application/vnd.eszigno3+xml", + ".asice": "application/vnd.etsi.asic-e+zip", + ".sce": "application/vnd.etsi.asic-e+zip", + ".asics": "application/vnd.etsi.asic-s+zip", + ".tst": "application/vnd.etsi.timestamp-token", + ".ez2": "application/vnd.ezpix-album", + ".ez3": "application/vnd.ezpix-package", + ".dim": "application/vnd.fastcopy-disk-image", + ".fdf": "application/vnd.fdf", + ".msd": "application/vnd.fdsn.mseed", + ".mseed": "application/vnd.fdsn.mseed", + ".seed": "application/vnd.fdsn.seed", + ".dataless": "application/vnd.fdsn.seed", + ".zfc": "application/vnd.filmit.zfc", + ".gph": "application/vnd.FloGraphIt", + ".ftc": "application/vnd.fluxtime.clip", + ".sfd": "application/vnd.font-fontforge-sfd", + ".fm": "application/vnd.framemaker", + ".fnc": "application/vnd.frogans.fnc", + ".ltf": "application/vnd.frogans.ltf", + ".fsc": "application/vnd.fsc.weblaunch", + ".oas": "application/vnd.fujitsu.oasys", + ".oa2": "application/vnd.fujitsu.oasys2", + ".oa3": "application/vnd.fujitsu.oasys3", + ".fg5": "application/vnd.fujitsu.oasysgp", + ".bh2": "application/vnd.fujitsu.oasysprs", + ".ddd": "application/vnd.fujixerox.ddd", + ".xdw": "application/vnd.fujixerox.docuworks", + ".xbd": "application/vnd.fujixerox.docuworks.binder", + ".xct": "application/vnd.fujixerox.docuworks.container", + ".fzs": "application/vnd.fuzzysheet", + ".txd": "application/vnd.genomatix.tuxedo", + ".g3": "application/vnd.geocube+xml", + ".g³": "application/vnd.geocube+xml", + ".ggb": "application/vnd.geogebra.file", + ".ggt": "application/vnd.geogebra.tool", + ".gex": "application/vnd.geometry-explorer", + ".gre": "application/vnd.geometry-explorer", + ".gxt": "application/vnd.geonext", + ".g2w": "application/vnd.geoplan", + ".g3w": "application/vnd.geospace", + ".gmx": "application/vnd.gmx", + ".kml": "application/vnd.google-earth.kml+xml", + ".kmz": "application/vnd.google-earth.kmz", + ".gqf": "application/vnd.grafeq", + ".gqs": "application/vnd.grafeq", + ".gac": "application/vnd.groove-account", + ".ghf": "application/vnd.groove-help", + ".gim": "application/vnd.groove-identity-message", + ".grv": "application/vnd.groove-injector", + ".gtm": "application/vnd.groove-tool-message", + ".tpl": "application/vnd.groove-tool-template", + ".vcg": "application/vnd.groove-vcard", + ".hal": "application/vnd.hal+xml", + ".zmm": "application/vnd.HandHeld-Entertainment+xml", + ".hbci": "application/vnd.hbci", + ".hbc": "application/vnd.hbci", + ".kom": "application/vnd.hbci", + ".upa": "application/vnd.hbci", + ".pkd": "application/vnd.hbci", + ".bpd": "application/vnd.hbci", + ".hdt": "application/vnd.hdt", + ".les": "application/vnd.hhe.lesson-player", + ".hpgl": "application/vnd.hp-HPGL", + ".hpi": "application/vnd.hp-hpid", + ".hpid": "application/vnd.hp-hpid", + ".hps": "application/vnd.hp-hps", + ".jlt": "application/vnd.hp-jlyt", + ".pcl": "application/vnd.hp-PCL", + ".sfd-hdstx": "application/vnd.hydrostatix.sof-data", + ".x3d": "application/vnd.hzn-3d-crossword", + ".emm": "application/vnd.ibm.electronic-media", + ".mpy": "application/vnd.ibm.MiniPay", + ".list3820": "application/vnd.ibm.modcap", + ".listafp": "application/vnd.ibm.modcap", + ".afp": "application/vnd.ibm.modcap", + ".pseg3820": "application/vnd.ibm.modcap", + ".irm": "application/vnd.ibm.rights-management", + ".sc": "application/vnd.ibm.secure-container", + ".icc": "application/vnd.iccprofile", + ".icm": "application/vnd.iccprofile", + ".1905.1": "application/vnd.ieee.1905", + ".igl": "application/vnd.igloader", + ".imf": "application/vnd.imagemeter.folder+zip", + ".imi": "application/vnd.imagemeter.image+zip", + ".ivp": "application/vnd.immervision-ivp", + ".ivu": "application/vnd.immervision-ivu", + ".imscc": "application/vnd.ims.imsccv1p1", + ".igm": "application/vnd.insors.igm", + ".xpw": "application/vnd.intercon.formnet", + ".xpx": "application/vnd.intercon.formnet", + ".i2g": "application/vnd.intergeo", + ".qbo": "application/vnd.intu.qbo", + ".qfx": "application/vnd.intu.qfx", + ".rcprofile": "application/vnd.ipunplugged.rcprofile", + ".irp": "application/vnd.irepository.package+xml", + ".xpr": "application/vnd.is-xpr", + ".fcs": "application/vnd.isac.fcs", + ".jam": "application/vnd.jam", + ".rms": "application/vnd.jcp.javame.midlet-rms", + ".jisp": "application/vnd.jisp", + ".joda": "application/vnd.joost.joda-archive", + ".ktz": "application/vnd.kahootz", + ".ktr": "application/vnd.kahootz", + ".karbon": "application/vnd.kde.karbon", + ".chrt": "application/vnd.kde.kchart", + ".kfo": "application/vnd.kde.kformula", + ".flw": "application/vnd.kde.kivio", + ".kon": "application/vnd.kde.kontour", + ".kpr": "application/vnd.kde.kpresenter", + ".kpt": "application/vnd.kde.kpresenter", + ".ksp": "application/vnd.kde.kspread", + ".kwd": "application/vnd.kde.kword", + ".kwt": "application/vnd.kde.kword", + ".htke": "application/vnd.kenameaapp", + ".kia": "application/vnd.kidspiration", + ".kne": "application/vnd.Kinar", + ".knp": "application/vnd.Kinar", + ".sdf": "application/vnd.Kinar", + ".skp": "application/vnd.koan", + ".skd": "application/vnd.koan", + ".skm": "application/vnd.koan", + ".skt": "application/vnd.koan", + ".sse": "application/vnd.kodak-descriptor", + ".lasjson": "application/vnd.las.las+json", + ".lasxml": "application/vnd.las.las+xml", + ".lbd": "application/vnd.llamagraphics.life-balance.desktop", + ".lbe": "application/vnd.llamagraphics.life-balance.exchange+xml", + ".123": "application/vnd.lotus-1-2-3", + ".wk4": "application/vnd.lotus-1-2-3", + ".wk3": "application/vnd.lotus-1-2-3", + ".wk1": "application/vnd.lotus-1-2-3", + ".apr": "application/vnd.lotus-approach", + ".vew": "application/vnd.lotus-approach", + ".prz": "application/vnd.lotus-freelance", + ".pre": "application/vnd.lotus-freelance", + ".nsf": "application/vnd.lotus-notes", + ".ntf": "application/vnd.lotus-notes", + ".ndl": "application/vnd.lotus-notes", + ".ns4": "application/vnd.lotus-notes", + ".ns3": "application/vnd.lotus-notes", + ".ns2": "application/vnd.lotus-notes", + ".nsh": "application/vnd.lotus-notes", + ".nsg": "application/vnd.lotus-notes", + ".or3": "application/vnd.lotus-organizer", + ".or2": "application/vnd.lotus-organizer", + ".org": "application/vnd.lotus-organizer", + ".scm": "application/vnd.lotus-screencam", + ".lwp": "application/vnd.lotus-wordpro", + ".sam": "application/vnd.lotus-wordpro", + ".portpkg": "application/vnd.macports.portpkg", + ".mvt": "application/vnd.mapbox-vector-tile", + ".mdc": "application/vnd.marlin.drm.mdcf", + ".mmdb": "application/vnd.maxmind.maxmind-db", + ".mcd": "application/vnd.mcd", + ".mc1": "application/vnd.medcalcdata", + ".cdkey": "application/vnd.mediastation.cdkey", + ".mwf": "application/vnd.MFER", + ".mfm": "application/vnd.mfmp", + ".flo": "application/vnd.micrografx.flo", + ".igx": "application/vnd.micrografx.igx", + ".mif": "application/vnd.mif", + ".daf": "application/vnd.Mobius.DAF", + ".dis": "application/vnd.Mobius.DIS", + ".mbk": "application/vnd.Mobius.MBK", + ".mqy": "application/vnd.Mobius.MQY", + ".msl": "application/vnd.Mobius.MSL", + ".plc": "application/vnd.Mobius.PLC", + ".txf": "application/vnd.Mobius.TXF", + ".mpn": "application/vnd.mophun.application", + ".mpc": "application/vnd.mophun.certificate", + ".xul": "application/vnd.mozilla.xul+xml", + ".3mf": "application/vnd.ms-3mfdocument", + ".cil": "application/vnd.ms-artgalry", + ".asf": "application/vnd.ms-asf", + ".cab": "application/vnd.ms-cab-compressed", + ".xls": "application/vnd.ms-excel", + ".xlm": "application/vnd.ms-excel", + ".xla": "application/vnd.ms-excel", + ".xlc": "application/vnd.ms-excel", + ".xlt": "application/vnd.ms-excel", + ".xlw": "application/vnd.ms-excel", + ".xltm": "application/vnd.ms-excel.template.macroEnabled.12", + ".xlam": "application/vnd.ms-excel.addin.macroEnabled.12", + ".xlsb": "application/vnd.ms-excel.sheet.binary.macroEnabled.12", + ".xlsm": "application/vnd.ms-excel.sheet.macroEnabled.12", + ".eot": "application/vnd.ms-fontobject", + ".chm": "application/vnd.ms-htmlhelp", + ".ims": "application/vnd.ms-ims", + ".lrm": "application/vnd.ms-lrm", + ".thmx": "application/vnd.ms-officetheme", + ".ppt": "application/vnd.ms-powerpoint", + ".pps": "application/vnd.ms-powerpoint", + ".pot": "application/vnd.ms-powerpoint", + ".ppam": "application/vnd.ms-powerpoint.addin.macroEnabled.12", + ".pptm": "application/vnd.ms-powerpoint.presentation.macroEnabled.12", + ".sldm": "application/vnd.ms-powerpoint.slide.macroEnabled.12", + ".ppsm": "application/vnd.ms-powerpoint.slideshow.macroEnabled.12", + ".potm": "application/vnd.ms-powerpoint.template.macroEnabled.12", + ".mpp": "application/vnd.ms-project", + ".mpt": "application/vnd.ms-project", + ".tnef": "application/vnd.ms-tnef", + ".tnf": "application/vnd.ms-tnef", + ".docm": "application/vnd.ms-word.document.macroEnabled.12", + ".dotm": "application/vnd.ms-word.template.macroEnabled.12", + ".wcm": "application/vnd.ms-works", + ".wdb": "application/vnd.ms-works", + ".wks": "application/vnd.ms-works", + ".wps": "application/vnd.ms-works", + ".wpl": "application/vnd.ms-wpl", + ".xps": "application/vnd.ms-xpsdocument", + ".msa": "application/vnd.msa-disk-image", + ".mseq": "application/vnd.mseq", + ".crtr": "application/vnd.multiad.creator", + ".cif": "application/vnd.multiad.creator.cif", + ".mus": "application/vnd.musician", + ".msty": "application/vnd.muvee.style", + ".taglet": "application/vnd.mynfc", + ".entity": "application/vnd.nervana", + ".request": "application/vnd.nervana", + ".bkm": "application/vnd.nervana", + ".kcm": "application/vnd.nervana", + ".nitf": "application/vnd.nitf", + ".nlu": "application/vnd.neurolanguage.nlu", + ".nds": "application/vnd.nintendo.nitro.rom", + ".sfc": "application/vnd.nintendo.snes.rom", + ".smc": "application/vnd.nintendo.snes.rom", + ".nnd": "application/vnd.noblenet-directory", + ".nns": "application/vnd.noblenet-sealer", + ".nnw": "application/vnd.noblenet-web", + ".ac": "application/vnd.nokia.n-gage.ac+xml", + ".ngdat": "application/vnd.nokia.n-gage.data", + ".n-gage": "application/vnd.nokia.n-gage.symbian.install", + ".rpst": "application/vnd.nokia.radio-preset", + ".rpss": "application/vnd.nokia.radio-presets", + ".edm": "application/vnd.novadigm.EDM", + ".edx": "application/vnd.novadigm.EDX", + ".ext": "application/vnd.novadigm.EXT", + ".odc": "application/vnd.oasis.opendocument.chart", + ".otc": "application/vnd.oasis.opendocument.chart-template", + ".odb": "application/vnd.oasis.opendocument.database", + ".odf": "application/vnd.oasis.opendocument.formula", + ".odg": "application/vnd.oasis.opendocument.graphics", + ".otg": "application/vnd.oasis.opendocument.graphics-template", + ".odi": "application/vnd.oasis.opendocument.image", + ".oti": "application/vnd.oasis.opendocument.image-template", + ".odp": "application/vnd.oasis.opendocument.presentation", + ".otp": "application/vnd.oasis.opendocument.presentation-template", + ".ods": "application/vnd.oasis.opendocument.spreadsheet", + ".ots": "application/vnd.oasis.opendocument.spreadsheet-template", + ".odt": "application/vnd.oasis.opendocument.text", + ".odm": "application/vnd.oasis.opendocument.text-master", + ".ott": "application/vnd.oasis.opendocument.text-template", + ".oth": "application/vnd.oasis.opendocument.text-web", + ".xo": "application/vnd.olpc-sugar", + ".dd2": "application/vnd.oma.dd2+xml", + ".tam": "application/vnd.onepager", + ".tamp": "application/vnd.onepagertamp", + ".tamx": "application/vnd.onepagertamx", + ".tat": "application/vnd.onepagertat", + ".tatp": "application/vnd.onepagertatp", + ".tatx": "application/vnd.onepagertatx", + ".obgx": "application/vnd.openblox.game+xml", + ".obg": "application/vnd.openblox.game-binary", + ".oeb": "application/vnd.openeye.oeb", + ".oxt": "application/vnd.openofficeorg.extension", + ".osm": "application/vnd.openstreetmap.data+xml", + ".pptx": "application/vnd.openxmlformats-officedocument.presentationml.presentation", + ".sldx": "application/vnd.openxmlformats-officedocument.presentationml.slide", + ".ppsx": "application/vnd.openxmlformats-officedocument.presentationml.slideshow", + ".potx": "application/vnd.openxmlformats-officedocument.presentationml.template", + ".xlsx": "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", + ".xltx": "application/vnd.openxmlformats-officedocument.spreadsheetml.template", + ".docx": "application/vnd.openxmlformats-officedocument.wordprocessingml.document", + ".dotx": "application/vnd.openxmlformats-officedocument.wordprocessingml.template", + ".ndc": "application/vnd.osa.netdeploy", + ".mgp": "application/vnd.osgeo.mapguide.package", + ".dp": "application/vnd.osgi.dp", + ".esa": "application/vnd.osgi.subsystem", + ".oxlicg": "application/vnd.oxli.countgraph", + ".prc": "application/vnd.palm", + ".pdb": "application/vnd.palm", + ".pqa": "application/vnd.palm", + ".oprc": "application/vnd.palm", + ".plp": "application/vnd.panoply", + ".paw": "application/vnd.pawaafile", + ".str": "application/vnd.pg.format", + ".ei6": "application/vnd.pg.osasli", + ".pil": "application/vnd.piaccess.application-license", + ".efif": "application/vnd.picsel", + ".wg": "application/vnd.pmi.widget", + ".plf": "application/vnd.pocketlearn", + ".pbd": "application/vnd.powerbuilder6", + ".preminet": "application/vnd.preminet", + ".box": "application/vnd.previewsystems.box", + ".vbox": "application/vnd.previewsystems.box", + ".mgz": "application/vnd.proteus.magazine", + ".qps": "application/vnd.publishare-delta-tree", + ".ptid": "application/vnd.pvi.ptid1", + ".bar": "application/vnd.qualcomm.brew-app-res", + ".qxd": "application/vnd.Quark.QuarkXPress", + ".qxt": "application/vnd.Quark.QuarkXPress", + ".qwd": "application/vnd.Quark.QuarkXPress", + ".qwt": "application/vnd.Quark.QuarkXPress", + ".qxl": "application/vnd.Quark.QuarkXPress", + ".qxb": "application/vnd.Quark.QuarkXPress", + ".quox": "application/vnd.quobject-quoxdocument", + ".quiz": "application/vnd.quobject-quoxdocument", + ".tree": "application/vnd.rainstor.data", + ".rar": "application/vnd.rar", + ".bed": "application/vnd.realvnc.bed", + ".mxl": "application/vnd.recordare.musicxml", + ".cryptonote": "application/vnd.rig.cryptonote", + ".link66": "application/vnd.route66.link66+xml", + ".st": "application/vnd.sailingtracker.track", + ".scd": "application/vnd.scribus", + ".sla": "application/vnd.scribus", + ".slaz": "application/vnd.scribus", + ".s3df": "application/vnd.sealed.3df", + ".scsf": "application/vnd.sealed.csf", + ".sdoc": "application/vnd.sealed.doc", + ".sdo": "application/vnd.sealed.doc", + ".s1w": "application/vnd.sealed.doc", + ".seml": "application/vnd.sealed.eml", + ".sem": "application/vnd.sealed.eml", + ".smht": "application/vnd.sealed.mht", + ".smh": "application/vnd.sealed.mht", + ".sppt": "application/vnd.sealed.ppt", + ".s1p": "application/vnd.sealed.ppt", + ".stif": "application/vnd.sealed.tiff", + ".sxls": "application/vnd.sealed.xls", + ".sxl": "application/vnd.sealed.xls", + ".s1e": "application/vnd.sealed.xls", + ".stml": "application/vnd.sealedmedia.softseal.html", + ".s1h": "application/vnd.sealedmedia.softseal.html", + ".spdf": "application/vnd.sealedmedia.softseal.pdf", + ".spd": "application/vnd.sealedmedia.softseal.pdf", + ".s1a": "application/vnd.sealedmedia.softseal.pdf", + ".see": "application/vnd.seemail", + ".sema": "application/vnd.sema", + ".semd": "application/vnd.semd", + ".semf": "application/vnd.semf", + ".ifm": "application/vnd.shana.informed.formdata", + ".itp": "application/vnd.shana.informed.formtemplate", + ".iif": "application/vnd.shana.informed.interchange", + ".ipk": "application/vnd.shana.informed.package", + ".twd": "application/vnd.SimTech-MindMapper", + ".twds": "application/vnd.SimTech-MindMapper", + ".mmf": "application/vnd.smaf", + ".notebook": "application/vnd.smart.notebook", + ".teacher": "application/vnd.smart.teacher", + ".fo": "application/vnd.software602.filler.form+xml", + ".zfo": "application/vnd.software602.filler.form-xml-zip", + ".sdkm": "application/vnd.solent.sdkm+xml", + ".sdkd": "application/vnd.solent.sdkm+xml", + ".dxp": "application/vnd.spotfire.dxp", + ".sfs": "application/vnd.spotfire.sfs", + ".smzip": "application/vnd.stepmania.package", + ".sm": "application/vnd.stepmania.stepchart", + ".wadl": "application/vnd.sun.wadl+xml", + ".sus": "application/vnd.sus-calendar", + ".susp": "application/vnd.sus-calendar", + ".xsm": "application/vnd.syncml+xml", + ".bdm": "application/vnd.syncml.dm+wbxml", + ".xdm": "application/vnd.syncml.dm+xml", + ".ddf": "application/vnd.syncml.dmddf+xml", + ".tao": "application/vnd.tao.intent-module-archive", + ".pcap": "application/vnd.tcpdump.pcap", + ".cap": "application/vnd.tcpdump.pcap", + ".dmp": "application/vnd.tcpdump.pcap", + ".qvd": "application/vnd.theqvd", + ".vfr": "application/vnd.tml", + ".viaframe": "application/vnd.tml", + ".tmo": "application/vnd.tmobile-livetv", + ".tpt": "application/vnd.trid.tpt", + ".mxs": "application/vnd.triscape.mxs", + ".tra": "application/vnd.trueapp", + ".ufdl": "application/vnd.ufdl", + ".ufd": "application/vnd.ufdl", + ".frm": "application/vnd.ufdl", + ".utz": "application/vnd.uiq.theme", + ".umj": "application/vnd.umajin", + ".unityweb": "application/vnd.unity", + ".uoml": "application/vnd.uoml+xml", + ".uo": "application/vnd.uoml+xml", + ".urim": "application/vnd.uri-map", + ".urimap": "application/vnd.uri-map", + ".vmt": "application/vnd.valve.source.material", + ".vcx": "application/vnd.vcx", + ".mxi": "application/vnd.vd-study", + ".study-inter": "application/vnd.vd-study", + ".model-inter": "application/vnd.vd-study", + ".vwx": "application/vnd.vectorworks", + ".vsc": "application/vnd.vidsoft.vidconference", + ".vsd": "application/vnd.visio", + ".vst": "application/vnd.visio", + ".vsw": "application/vnd.visio", + ".vss": "application/vnd.visio", + ".vis": "application/vnd.visionary", + ".vsf": "application/vnd.vsf", + ".sic": "application/vnd.wap.sic", + ".slc": "application/vnd.wap.slc", + ".wbxml": "application/vnd.wap.wbxml", + ".wmlc": "application/vnd.wap.wmlc", + ".wmlsc": "application/vnd.wap.wmlscriptc", + ".wtb": "application/vnd.webturbo", + ".p2p": "application/vnd.wfa.p2p", + ".wsc": "application/vnd.wfa.wsc", + ".wmc": "application/vnd.wmc", + ".m": "application/vnd.wolfram.mathematica.package", + ".nbp": "application/vnd.wolfram.player", + ".wpd": "application/vnd.wordperfect", + ".wqd": "application/vnd.wqd", + ".stf": "application/vnd.wt.stf", + ".wv": "application/vnd.wv.csp+wbxml", + ".xar": "application/vnd.xara", + ".xfdl": "application/vnd.xfdl", + ".xfd": "application/vnd.xfdl", + ".cpkg": "application/vnd.xmpie.cpkg", + ".dpkg": "application/vnd.xmpie.dpkg", + ".ppkg": "application/vnd.xmpie.ppkg", + ".xlim": "application/vnd.xmpie.xlim", + ".hvd": "application/vnd.yamaha.hv-dic", + ".hvs": "application/vnd.yamaha.hv-script", + ".hvp": "application/vnd.yamaha.hv-voice", + ".osf": "application/vnd.yamaha.openscoreformat", + ".saf": "application/vnd.yamaha.smaf-audio", + ".spf": "application/vnd.yamaha.smaf-phrase", + ".yme": "application/vnd.yaoweme", + ".cmp": "application/vnd.yellowriver-custom-menu", + ".zir": "application/vnd.zul", + ".zirz": "application/vnd.zul", + ".zaz": "application/vnd.zzazz.deck+xml", + ".vxml": "application/voicexml+xml", + ".wif": "application/watcherinfo+xml", + ".wgt": "application/widget", + ".wsdl": "application/wsdl+xml", + ".wspolicy": "application/wspolicy+xml", + ".xav": "application/xcap-att+xml", + ".xca": "application/xcap-caps+xml", + ".xdf": "application/xcap-diff+xml", + ".xel": "application/xcap-el+xml", + ".xer": "application/xcap-error+xml", + ".xns": "application/xcap-ns+xml", + ".xhtml": "application/xhtml+xml", + ".xhtm": "application/xhtml+xml", + ".xht": "application/xhtml+xml", + ".dtd": "application/xml-dtd", + ".xop": "application/xop+xml", + ".xsl": "application/xslt+xml", + ".xslt": "application/xslt+xml", + ".mxml": "application/xv+xml", + ".xhvml": "application/xv+xml", + ".xvml": "application/xv+xml", + ".xvm": "application/xv+xml", + ".yang": "application/yang", + ".yin": "application/yin+xml", + ".zip": "application/zip", + ".726": "audio/32kadpcm", + ".ac3": "audio/ac3", + ".amr": "audio/AMR", + ".awb": "audio/AMR-WB", + ".acn": "audio/asc", + ".aal": "audio/ATRAC-ADVANCED-LOSSLESS", + ".atx": "audio/ATRAC-X", + ".at3": "audio/ATRAC3", + ".aa3": "audio/ATRAC3", + ".omg": "audio/ATRAC3", + ".au": "audio/basic", + ".snd": "audio/basic", + ".dls": "audio/dls", + ".evc": "audio/EVRC", + ".evb": "audio/EVRCB", + ".enw": "audio/EVRCNW", + ".evw": "audio/EVRCWB", + ".lbc": "audio/iLBC", + ".l16": "audio/L16", + ".mxmf": "audio/mobile-xmf", + ".m4a": "audio/mp4", + ".mp3": "audio/mpeg", + ".mpga": "audio/mpeg", + ".mp1": "audio/mpeg", + ".mp2": "audio/mpeg", + ".oga": "audio/ogg", + ".ogg": "audio/ogg", + ".opus": "audio/ogg", + ".spx": "audio/ogg", + ".sid": "audio/prs.sid", + ".psid": "audio/prs.sid", + ".qcp": "audio/qcelp", + ".smv": "audio/SMV", + ".koz": "audio/vnd.audikoz", + ".uva": "audio/vnd.dece.audio", + ".uvva": "audio/vnd.dece.audio", + ".eol": "audio/vnd.digital-winds", + ".mlp": "audio/vnd.dolby.mlp", + ".dts": "audio/vnd.dts", + ".dtshd": "audio/vnd.dts.hd", + ".plj": "audio/vnd.everad.plj", + ".lvp": "audio/vnd.lucent.voice", + ".pya": "audio/vnd.ms-playready.media.pya", + ".vbk": "audio/vnd.nortel.vbk", + ".ecelp4800": "audio/vnd.nuera.ecelp4800", + ".ecelp7470": "audio/vnd.nuera.ecelp7470", + ".ecelp9600": "audio/vnd.nuera.ecelp9600", + ".rip": "audio/vnd.rip", + ".smp3": "audio/vnd.sealedmedia.softseal.mpeg", + ".smp": "audio/vnd.sealedmedia.softseal.mpeg", + ".s1m": "audio/vnd.sealedmedia.softseal.mpeg", + ".ttc": "font/collection", + ".otf": "font/otf", + ".ttf": "font/ttf", + ".woff": "font/woff", + ".woff2": "font/woff2", + ".bmp": "image/bmp", + ".dib": "image/bmp", + ".cgm": "image/cgm", + ".drle": "image/dicom-rle", + ".emf": "image/emf", + ".fits": "image/fits", + ".fit": "image/fits", + ".fts": "image/fits", + ".gif": "image/gif", + ".ief": "image/ief", + ".jls": "image/jls", + ".jp2": "image/jp2", + ".jpg2": "image/jp2", + ".jpg": "image/jpeg", + ".jpeg": "image/jpeg", + ".jpe": "image/jpeg", + ".jfif": "image/jpeg", + ".jpm": "image/jpm", + ".jpgm": "image/jpm", + ".jpx": "image/jpx", + ".jpf": "image/jpx", + ".ktx": "image/ktx", + ".png": "image/png", + ".btif": "image/prs.btif", + ".btf": "image/prs.btif", + ".pti": "image/prs.pti", + ".svg": "image/svg+xml", + ".svgz": "image/svg+xml", + ".t38": "image/t38", + ".tiff": "image/tiff", + ".tif": "image/tiff", + ".tfx": "image/tiff-fx", + ".psd": "image/vnd.adobe.photoshop", + ".azv": "image/vnd.airzip.accelerator.azv", + ".uvi": "image/vnd.dece.graphic", + ".uvvi": "image/vnd.dece.graphic", + ".uvg": "image/vnd.dece.graphic", + ".uvvg": "image/vnd.dece.graphic", + ".djvu": "image/vnd.djvu", + ".djv": "image/vnd.djvu", + ".dwg": "image/vnd.dwg", + ".dxf": "image/vnd.dxf", + ".fbs": "image/vnd.fastbidsheet", + ".fpx": "image/vnd.fpx", + ".fst": "image/vnd.fst", + ".mmr": "image/vnd.fujixerox.edmics-mmr", + ".rlc": "image/vnd.fujixerox.edmics-rlc", + ".pgb": "image/vnd.globalgraphics.pgb", + ".ico": "image/vnd.microsoft.icon", + ".apng": "image/vnd.mozilla.apng", + ".mdi": "image/vnd.ms-modi", + ".hdr": "image/vnd.radiance", + ".rgbe": "image/vnd.radiance", + ".xyze": "image/vnd.radiance", + ".spng": "image/vnd.sealed.png", + ".spn": "image/vnd.sealed.png", + ".s1n": "image/vnd.sealed.png", + ".sgif": "image/vnd.sealedmedia.softseal.gif", + ".sgi": "image/vnd.sealedmedia.softseal.gif", + ".s1g": "image/vnd.sealedmedia.softseal.gif", + ".sjpg": "image/vnd.sealedmedia.softseal.jpg", + ".sjp": "image/vnd.sealedmedia.softseal.jpg", + ".s1j": "image/vnd.sealedmedia.softseal.jpg", + ".tap": "image/vnd.tencent.tap", + ".vtf": "image/vnd.valve.source.texture", + ".wbmp": "image/vnd.wap.wbmp", + ".xif": "image/vnd.xiff", + ".pcx": "image/vnd.zbrush.pcx", + ".wmf": "image/wmf", + ".u8msg": "message/global", + ".u8dsn": "message/global-delivery-status", + ".u8mdn": "message/global-disposition-notification", + ".u8hdr": "message/global-headers", + ".eml": "message/rfc822", + ".mail": "message/rfc822", + ".art": "message/rfc822", + ".gltf": "model/gltf+json", + ".igs": "model/iges", + ".iges": "model/iges", + ".msh": "model/mesh", + ".mesh": "model/mesh", + ".silo": "model/mesh", + ".dae": "model/vnd.collada+xml", + ".dwf": "model/vnd.dwf", + ".gdl": "model/vnd.gdl", + ".gsm": "model/vnd.gdl", + ".win": "model/vnd.gdl", + ".dor": "model/vnd.gdl", + ".lmp": "model/vnd.gdl", + ".rsm": "model/vnd.gdl", + ".msm": "model/vnd.gdl", + ".ism": "model/vnd.gdl", + ".gtw": "model/vnd.gtw", + ".moml": "model/vnd.moml+xml", + ".mts": "model/vnd.mts", + ".ogex": "model/vnd.opengex", + ".x_b": "model/vnd.parasolid.transmit.binary", + ".xmt_bin": "model/vnd.parasolid.transmit.binary", + ".x_t": "model/vnd.parasolid.transmit.text", + ".xmt_txt": "model/vnd.parasolid.transmit.text", + ".bsp": "model/vnd.valve.source.compiled-map", + ".vtu": "model/vnd.vtu", + ".wrl": "model/vrml", + ".vrml": "model/vrml", + ".x3db": "model/x3d+xml", + ".x3dv": "model/x3d-vrml", + ".x3dvz": "model/x3d-vrml", + ".bmed": "multipart/vnd.bint.med-plus", + ".vpm": "multipart/voice-message", + ".appcache": "text/cache-manifest", + ".manifest": "text/cache-manifest", + ".ics": "text/calendar", + ".ifb": "text/calendar", + ".css": "text/css", + ".csv": "text/csv", + ".csvs": "text/csv-schema", + ".soa": "text/dns", + ".zone": "text/dns", + ".html": "text/html", + ".htm": "text/html", + ".cnd": "text/jcr-cnd", + ".markdown": "text/markdown", + ".md": "text/markdown", + ".miz": "text/mizar", + ".n3": "text/n3", + ".txt": "text/plain", + ".asc": "text/plain", + ".text": "text/plain", + ".pm": "text/plain", + ".el": "text/plain", + ".c": "text/plain", + ".h": "text/plain", + ".cc": "text/plain", + ".hh": "text/plain", + ".cxx": "text/plain", + ".hxx": "text/plain", + ".f90": "text/plain", + ".conf": "text/plain", + ".log": "text/plain", + ".provn": "text/provenance-notation", + ".rst": "text/prs.fallenstein.rst", + ".tag": "text/prs.lines.tag", + ".dsc": "text/prs.lines.tag", + ".rtx": "text/richtext", + ".sgml": "text/sgml", + ".sgm": "text/sgml", + ".tsv": "text/tab-separated-values", + ".t": "text/troff", + ".tr": "text/troff", + ".roff": "text/troff", + ".ttl": "text/turtle", + ".uris": "text/uri-list", + ".uri": "text/uri-list", + ".vcf": "text/vcard", + ".vcard": "text/vcard", + ".a": "text/vnd.a", + ".abc": "text/vnd.abc", + ".ascii": "text/vnd.ascii-art", + ".copyright": "text/vnd.debian.copyright", + ".dms": "text/vnd.DMClientScript", + ".sub": "text/vnd.dvb.subtitle", + ".jtd": "text/vnd.esmertec.theme-descriptor", + ".fly": "text/vnd.fly", + ".flx": "text/vnd.fmi.flexstor", + ".gv": "text/vnd.graphviz", + ".dot": "text/vnd.graphviz", + ".3dml": "text/vnd.in3d.3dml", + ".3dm": "text/vnd.in3d.3dml", + ".spot": "text/vnd.in3d.spot", + ".spo": "text/vnd.in3d.spot", + ".mpf": "text/vnd.ms-mediapackage", + ".ccc": "text/vnd.net2phone.commcenter.command", + ".uric": "text/vnd.si.uricatalogue", + ".jad": "text/vnd.sun.j2me.app-descriptor", + ".ts": "text/vnd.trolltech.linguist", + ".si": "text/vnd.wap.si", + ".sl": "text/vnd.wap.sl", + ".wml": "text/vnd.wap.wml", + ".wmls": "text/vnd.wap.wmlscript", + ".xml": "text/xml", + ".xsd": "text/xml", + ".rng": "text/xml", + ".ent": "text/xml-external-parsed-entity", + ".3gp": "video/3gpp", + ".3gpp": "video/3gpp", + ".3g2": "video/3gpp2", + ".3gpp2": "video/3gpp2", + ".m4s": "video/iso.segment", + ".mj2": "video/mj2", + ".mjp2": "video/mj2", + ".mp4": "video/mp4", + ".mpg4": "video/mp4", + ".m4v": "video/mp4", + ".mpeg": "video/mpeg", + ".mpg": "video/mpeg", + ".mpe": "video/mpeg", + ".m1v": "video/mpeg", + ".m2v": "video/mpeg", + ".ogv": "video/ogg", + ".mov": "video/quicktime", + ".qt": "video/quicktime", + ".uvh": "video/vnd.dece.hd", + ".uvvh": "video/vnd.dece.hd", + ".uvm": "video/vnd.dece.mobile", + ".uvvm": "video/vnd.dece.mobile", + ".uvu": "video/vnd.dece.mp4", + ".uvvu": "video/vnd.dece.mp4", + ".uvp": "video/vnd.dece.pd", + ".uvvp": "video/vnd.dece.pd", + ".uvs": "video/vnd.dece.sd", + ".uvvs": "video/vnd.dece.sd", + ".uvv": "video/vnd.dece.video", + ".uvvv": "video/vnd.dece.video", + ".dvb": "video/vnd.dvb.file", + ".fvt": "video/vnd.fvt", + ".mxu": "video/vnd.mpegurl", + ".m4u": "video/vnd.mpegurl", + ".pyv": "video/vnd.ms-playready.media.pyv", + ".nim": "video/vnd.nokia.interleaved-multimedia", + ".bik": "video/vnd.radgamettools.bink", + ".bk2": "video/vnd.radgamettools.bink", + ".smk": "video/vnd.radgamettools.smacker", + ".smpg": "video/vnd.sealed.mpeg1", + ".s11": "video/vnd.sealed.mpeg1", + ".s14": "video/vnd.sealed.mpeg4", + ".sswf": "video/vnd.sealed.swf", + ".ssw": "video/vnd.sealed.swf", + ".smov": "video/vnd.sealedmedia.softseal.mov", + ".smo": "video/vnd.sealedmedia.softseal.mov", + ".s1q": "video/vnd.sealedmedia.softseal.mov", + ".viv": "video/vnd.vivo", + ".cpt": "application/mac-compactpro", + ".metalink": "application/metalink+xml", + ".owx": "application/owl+xml", + ".rss": "application/rss+xml", + ".apk": "application/vnd.android.package-archive", + ".dd": "application/vnd.oma.dd+xml", + ".dcf": "application/vnd.oma.drm.content", + ".o4a": "application/vnd.oma.drm.dcf", + ".o4v": "application/vnd.oma.drm.dcf", + ".dm": "application/vnd.oma.drm.message", + ".drc": "application/vnd.oma.drm.rights+wbxml", + ".dr": "application/vnd.oma.drm.rights+xml", + ".sxc": "application/vnd.sun.xml.calc", + ".stc": "application/vnd.sun.xml.calc.template", + ".sxd": "application/vnd.sun.xml.draw", + ".std": "application/vnd.sun.xml.draw.template", + ".sxi": "application/vnd.sun.xml.impress", + ".sti": "application/vnd.sun.xml.impress.template", + ".sxm": "application/vnd.sun.xml.math", + ".sxw": "application/vnd.sun.xml.writer", + ".sxg": "application/vnd.sun.xml.writer.global", + ".stw": "application/vnd.sun.xml.writer.template", + ".sis": "application/vnd.symbian.install", + ".mms": "application/vnd.wap.mms-message", + ".anx": "application/x-annodex", + ".bcpio": "application/x-bcpio", + ".torrent": "application/x-bittorrent", + ".bz2": "application/x-bzip2", + ".vcd": "application/x-cdlink", + ".crx": "application/x-chrome-extension", + ".cpio": "application/x-cpio", + ".csh": "application/x-csh", + ".dcr": "application/x-director", + ".dir": "application/x-director", + ".dxr": "application/x-director", + ".dvi": "application/x-dvi", + ".spl": "application/x-futuresplash", + ".gtar": "application/x-gtar", + ".hdf": "application/x-hdf", + ".jar": "application/x-java-archive", + ".jnlp": "application/x-java-jnlp-file", + ".pack": "application/x-java-pack200", + ".kil": "application/x-killustrator", + ".latex": "application/x-latex", + ".nc": "application/x-netcdf", + ".cdf": "application/x-netcdf", + ".pl": "application/x-perl", + ".rpm": "application/x-rpm", + ".sh": "application/x-sh", + ".shar": "application/x-shar", + ".sit": "application/x-stuffit", + ".sv4cpio": "application/x-sv4cpio", + ".sv4crc": "application/x-sv4crc", + ".tar": "application/x-tar", + ".tcl": "application/x-tcl", + ".tex": "application/x-tex", + ".texinfo": "application/x-texinfo", + ".texi": "application/x-texinfo", + ".man": "application/x-troff-man", + ".1": "application/x-troff-man", + ".2": "application/x-troff-man", + ".3": "application/x-troff-man", + ".4": "application/x-troff-man", + ".5": "application/x-troff-man", + ".6": "application/x-troff-man", + ".7": "application/x-troff-man", + ".8": "application/x-troff-man", + ".me": "application/x-troff-me", + ".ms": "application/x-troff-ms", + ".ustar": "application/x-ustar", + ".src": "application/x-wais-source", + ".xpi": "application/x-xpinstall", + ".xspf": "application/x-xspf+xml", + ".xz": "application/x-xz", + ".mid": "audio/midi", + ".midi": "audio/midi", + ".kar": "audio/midi", + ".aif": "audio/x-aiff", + ".aiff": "audio/x-aiff", + ".aifc": "audio/x-aiff", + ".axa": "audio/x-annodex", + ".flac": "audio/x-flac", + ".mka": "audio/x-matroska", + ".mod": "audio/x-mod", + ".ult": "audio/x-mod", + ".uni": "audio/x-mod", + ".m15": "audio/x-mod", + ".mtm": "audio/x-mod", + ".669": "audio/x-mod", + ".med": "audio/x-mod", + ".m3u": "audio/x-mpegurl", + ".wax": "audio/x-ms-wax", + ".wma": "audio/x-ms-wma", + ".ram": "audio/x-pn-realaudio", + ".rm": "audio/x-pn-realaudio", + ".ra": "audio/x-realaudio", + ".s3m": "audio/x-s3m", + ".stm": "audio/x-stm", + ".wav": "audio/x-wav", + ".xyz": "chemical/x-xyz", + ".webp": "image/webp", + ".ras": "image/x-cmu-raster", + ".pnm": "image/x-portable-anymap", + ".pbm": "image/x-portable-bitmap", + ".pgm": "image/x-portable-graymap", + ".ppm": "image/x-portable-pixmap", + ".rgb": "image/x-rgb", + ".tga": "image/x-targa", + ".xbm": "image/x-xbitmap", + ".xpm": "image/x-xpixmap", + ".xwd": "image/x-xwindowdump", + ".sandboxed": "text/html-sandboxed", + ".pod": "text/x-pod", + ".etx": "text/x-setext", + ".webm": "video/webm", + ".axv": "video/x-annodex", + ".flv": "video/x-flv", + ".fxm": "video/x-javafx", + ".mkv": "video/x-matroska", + ".mk3d": "video/x-matroska-3d", + ".asx": "video/x-ms-asf", + ".wm": "video/x-ms-wm", + ".wmv": "video/x-ms-wmv", + ".wmx": "video/x-ms-wmx", + ".wvx": "video/x-ms-wvx", + ".avi": "video/x-msvideo", + ".movie": "video/x-sgi-movie", + ".ice": "x-conference/x-cooltalk", + ".sisx": "x-epoc/x-sisx-app", + } + for ext, name := range mimeTypes { + if err := mime.AddExtensionType(ext, name); err != nil { + panic(err) + } + } +} diff --git a/swarm/api/http/server.go b/swarm/api/http/server.go index 87ef05baa..5ec69373d 100644 --- a/swarm/api/http/server.go +++ b/swarm/api/http/server.go @@ -201,6 +201,13 @@ func (s *Server) HandleBzzGet(w http.ResponseWriter, r *http.Request) { defer reader.Close() w.Header().Set("Content-Type", "application/x-tar") + + fileName := uri.Addr + if found := path.Base(uri.Path); found != "" && found != "." && found != "/" { + fileName = found + } + w.Header().Set("Content-Disposition", fmt.Sprintf("inline; filename=\"%s.tar\"", fileName)) + w.WriteHeader(http.StatusOK) io.Copy(w, reader) return @@ -616,7 +623,7 @@ func (s *Server) HandleGetResource(w http.ResponseWriter, r *http.Request) { // All ok, serve the retrieved update log.Debug("Found update", "view", view.Hex(), "ruid", ruid) - w.Header().Set("Content-Type", "application/octet-stream") + w.Header().Set("Content-Type", api.MimeOctetStream) http.ServeContent(w, r, "", time.Now(), bytes.NewReader(data)) } @@ -690,11 +697,9 @@ func (s *Server) HandleGet(w http.ResponseWriter, r *http.Request) { case uri.Raw(): // allow the request to overwrite the content type using a query // parameter - contentType := "application/octet-stream" if typ := r.URL.Query().Get("content_type"); typ != "" { - contentType = typ + w.Header().Set("Content-Type", typ) } - w.Header().Set("Content-Type", contentType) http.ServeContent(w, r, "", time.Now(), reader) case uri.Hash(): w.Header().Set("Content-Type", "text/plain") @@ -850,8 +855,17 @@ func (s *Server) HandleGetFile(w http.ResponseWriter, r *http.Request) { return } - w.Header().Set("Content-Type", contentType) - http.ServeContent(w, r, "", time.Now(), newBufferedReadSeeker(reader, getFileBufferSize)) + if contentType != "" { + w.Header().Set("Content-Type", contentType) + } + + fileName := uri.Addr + if found := path.Base(uri.Path); found != "" && found != "." && found != "/" { + fileName = found + } + w.Header().Set("Content-Disposition", fmt.Sprintf("inline; filename=\"%s\"", fileName)) + + http.ServeContent(w, r, fileName, time.Now(), newBufferedReadSeeker(reader, getFileBufferSize)) } // The size of buffer used for bufio.Reader on LazyChunkReader passed to diff --git a/swarm/api/http/server_test.go b/swarm/api/http/server_test.go index 8ba4e55c3..817519a30 100644 --- a/swarm/api/http/server_test.go +++ b/swarm/api/http/server_test.go @@ -32,6 +32,7 @@ import ( "net/http" "net/url" "os" + "path" "strconv" "strings" "testing" @@ -764,6 +765,16 @@ func testBzzTar(encrypted bool, t *testing.T) { } defer resp2.Body.Close() + if h := resp2.Header.Get("Content-Type"); h != "application/x-tar" { + t.Fatalf("Content-Type header expected: application/x-tar, got: %s", h) + } + + expectedFileName := string(swarmHash) + ".tar" + expectedContentDisposition := fmt.Sprintf("inline; filename=\"%s\"", expectedFileName) + if h := resp2.Header.Get("Content-Disposition"); h != expectedContentDisposition { + t.Fatalf("Content-Disposition header expected: %s, got: %s", expectedContentDisposition, h) + } + file, err := ioutil.TempFile("", "swarm-downloaded-tarball") if err != nil { t.Fatal(err) @@ -1099,7 +1110,7 @@ func TestModify(t *testing.T) { res, body := httpDo(testCase.method, testCase.uri, reqBody, testCase.headers, testCase.verbose, t) if res.StatusCode != testCase.expectedStatusCode { - t.Fatalf("expected status code %d but got %d", testCase.expectedStatusCode, res.StatusCode) + t.Fatalf("expected status code %d but got %d, %s", testCase.expectedStatusCode, res.StatusCode, body) } if testCase.assertResponseBody != "" && !strings.Contains(body, testCase.assertResponseBody) { t.Log(body) @@ -1210,19 +1221,25 @@ func TestBzzGetFileWithResolver(t *testing.T) { hash := common.HexToHash(string(swarmHash)) resolver.hash = &hash for _, v := range []struct { - addr string - path string - expectedStatusCode int + addr string + path string + expectedStatusCode int + expectedContentType string + expectedFileName string }{ { - addr: string(swarmHash), - path: fileNames[0], - expectedStatusCode: http.StatusOK, + addr: string(swarmHash), + path: fileNames[0], + expectedStatusCode: http.StatusOK, + expectedContentType: "text/plain", + expectedFileName: path.Base(fileNames[0]), }, { - addr: "somebogusensname", - path: fileNames[0], - expectedStatusCode: http.StatusOK, + addr: "somebogusensname", + path: fileNames[0], + expectedStatusCode: http.StatusOK, + expectedContentType: "text/plain", + expectedFileName: path.Base(fileNames[0]), }, } { req, err := http.NewRequest("GET", fmt.Sprintf(srv.URL+"/bzz:/%s/%s", v.addr, v.path), nil) @@ -1237,6 +1254,16 @@ func TestBzzGetFileWithResolver(t *testing.T) { if serverResponse.StatusCode != v.expectedStatusCode { t.Fatalf("expected %d, got %d", v.expectedStatusCode, serverResponse.StatusCode) } + + if h := serverResponse.Header.Get("Content-Type"); h != v.expectedContentType { + t.Fatalf("Content-Type header expected: %s, got %s", v.expectedContentType, h) + } + + expectedContentDisposition := fmt.Sprintf("inline; filename=\"%s\"", v.expectedFileName) + if h := serverResponse.Header.Get("Content-Disposition"); h != expectedContentDisposition { + t.Fatalf("Content-Disposition header expected: %s, got: %s", expectedContentDisposition, h) + } + } } |