diff --git a/assets/README.md b/assets/README.md
index 02af2f19ca0..b342d080101 100644
--- a/assets/README.md
+++ b/assets/README.md
@@ -3,13 +3,3 @@
This directory contains the go-ipfs assets:
* Getting started documentation (`init-doc`).
-* Directory listing HTML template (`dir-index-html`).
-
-## Re-generating
-
-Edit the source files and use `go generate` from within the
-assets directory:
-
-```
-go generate .
-```
diff --git a/assets/assets.go b/assets/assets.go
index 37cbbfad5db..00792d511d3 100644
--- a/assets/assets.go
+++ b/assets/assets.go
@@ -1,30 +1,22 @@
-//go:generate npm run build --prefix ./dir-index-html/
package assets
import (
"embed"
"fmt"
- "io"
- "io/fs"
gopath "path"
- "strconv"
"github.com/ipfs/kubo/core"
"github.com/ipfs/kubo/core/coreapi"
- "github.com/cespare/xxhash"
cid "github.com/ipfs/go-cid"
"github.com/ipfs/go-libipfs/files"
options "github.com/ipfs/interface-go-ipfs-core/options"
"github.com/ipfs/interface-go-ipfs-core/path"
)
-//go:embed init-doc dir-index-html/dir-index.html dir-index-html/knownIcons.txt
+//go:embed init-doc
var Asset embed.FS
-// AssetHash a non-cryptographic hash of all embedded assets
-var AssetHash string
-
// initDocPaths lists the paths for the docs we want to seed during --init
var initDocPaths = []string{
gopath.Join("init-doc", "about"),
@@ -36,32 +28,6 @@ var initDocPaths = []string{
gopath.Join("init-doc", "ping"),
}
-func init() {
- sum := xxhash.New()
- err := fs.WalkDir(Asset, ".", func(path string, d fs.DirEntry, err error) error {
- if err != nil {
- return err
- }
-
- if d.IsDir() {
- return nil
- }
-
- file, err := Asset.Open(path)
- if err != nil {
- return err
- }
- defer file.Close()
- _, err = io.Copy(sum, file)
- return err
- })
- if err != nil {
- panic("error creating asset sum: " + err.Error())
- }
-
- AssetHash = strconv.FormatUint(sum.Sum64(), 32)
-}
-
// SeedInitDocs adds the list of embedded init documentation to the passed node, pins it and returns the root key
func SeedInitDocs(nd *core.IpfsNode) (cid.Cid, error) {
return addAssetList(nd, initDocPaths)
diff --git a/assets/dag-index-html/README.md b/assets/dag-index-html/README.md
deleted file mode 100644
index de38a9504a1..00000000000
--- a/assets/dag-index-html/README.md
+++ /dev/null
@@ -1,3 +0,0 @@
-# dag-index-html
-
-> HTML representation for non-UnixFS DAGs such as DAG-CBOR.
diff --git a/assets/dag-index-html/index.go b/assets/dag-index-html/index.go
deleted file mode 100644
index 214b06a3822..00000000000
--- a/assets/dag-index-html/index.go
+++ /dev/null
@@ -1,81 +0,0 @@
-package dagindexhtml
-
-import "html/template"
-
-// TODO: DagIndexTemplate - replace static CSS with shared one with ../dir-index-html
-
-// DagIndexTemplate is HTML-based template for non-UnixFS DAGs when request was
-// made with Accept: text/html (web browsers).
-var DagIndexTemplate = template.Must(template.New("redirect").Parse(`
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- {{ .Path }}
-
-
-
-
-
-
-
-
-
-
- Preview as JSON (application/json
)
-
-
-
-
- Or download as:
-
-
-
-
-
-
-
-
-`))
-
-type DagIndexTemplateData struct {
- Path string
- CID string
- CodecName string
- CodecHex string
-}
diff --git a/assets/dir-index-html/README.md b/assets/dir-index-html/README.md
deleted file mode 100644
index 3dd45eb5905..00000000000
--- a/assets/dir-index-html/README.md
+++ /dev/null
@@ -1,26 +0,0 @@
-# dir-index-html
-
-> Directory listing HTML for HTTP gateway
-
-![](https://user-images.githubusercontent.com/157609/88379209-ce6f0600-cda2-11ea-9620-20b9237bb441.png)
-
-## Updating
-
-When making updates to the directory listing page template, please note the following:
-
-1. Make your changes to the (human-friendly) source documents in the `src` directory and run `npm run build`
-3. Before testing or releasing, go to the top-level `./assets` directory and make sure to run the `go generate .` script to update the bindata version
-
-## Testing
-
-1. Make sure you have [Go](https://golang.org/dl/) installed
-2. Start the test server, which lives in its own directory:
-
-```bash
-> cd test
-> go run .
-```
-This will listen on [`localhost:3000`](http://localhost:3000/) and reload the template every time you refresh the page.
-
-If you get a "no such file or directory" error upon trying `go run .`, make sure you ran `npm run build` to generate the minified artifact that the test is looking for.
-
diff --git a/assets/dir-index-html/dir-index.html b/assets/dir-index-html/dir-index.html
deleted file mode 100644
index d861cb65700..00000000000
--- a/assets/dir-index-html/dir-index.html
+++ /dev/null
@@ -1,99 +0,0 @@
-
-{{ $root := . }}
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-{{ .Path }}
-
-
-
-
-
-
-
-
- {{ if .BackLink }}
-
-
-
-
-
- ..
-
-
-
-
- {{ end }}
- {{ range .Listing }}
-
-
-
-
-
- {{ .Name }}
-
-
- {{ if .Hash }}
-
- {{ .ShortHash }}
-
- {{ end }}
-
- {{ .Size }}
-
- {{ end }}
-
-
-
-
-
diff --git a/assets/dir-index-html/index.go b/assets/dir-index-html/index.go
deleted file mode 100644
index 98933e3f612..00000000000
--- a/assets/dir-index-html/index.go
+++ /dev/null
@@ -1 +0,0 @@
-package dirindexhtml
diff --git a/assets/dir-index-html/knownIcons.txt b/assets/dir-index-html/knownIcons.txt
deleted file mode 100644
index c110530ea59..00000000000
--- a/assets/dir-index-html/knownIcons.txt
+++ /dev/null
@@ -1,65 +0,0 @@
-.aac
-.aiff
-.ai
-.avi
-.bmp
-.c
-.cpp
-.css
-.dat
-.dmg
-.doc
-.dotx
-.dwg
-.dxf
-.eps
-.exe
-.flv
-.gif
-.h
-.hpp
-.html
-.ics
-.iso
-.java
-.jpg
-.jpeg
-.js
-.key
-.less
-.mid
-.mkv
-.mov
-.mp3
-.mp4
-.mpg
-.odf
-.ods
-.odt
-.otp
-.ots
-.ott
-.pdf
-.php
-.png
-.ppt
-.psd
-.py
-.qt
-.rar
-.rb
-.rtf
-.sass
-.scss
-.sql
-.tga
-.tgz
-.tiff
-.txt
-.wav
-.wmv
-.xls
-.xlsx
-.xml
-.yml
-.zip
diff --git a/assets/dir-index-html/package.json b/assets/dir-index-html/package.json
deleted file mode 100644
index 4b242757406..00000000000
--- a/assets/dir-index-html/package.json
+++ /dev/null
@@ -1,17 +0,0 @@
-{
- "name": "dir-index-html",
- "description": "Directory listing HTML for go-ipfs gateways",
- "version": "1.3.0",
- "private": true,
- "homepage": "https://github.com/ipfs/go-ipfs",
- "license": "MIT",
- "scripts": {
- "start": "cd test && go run .",
- "build": "npm run build:clean && npm run build:remove-style-links && npm run build:minify-wrap-css && npm run build:combine-html-css && npm run build:remove-unused",
- "build:clean": "rm dir-index.html",
- "build:remove-style-links": "sed '/ ./base-html.html",
- "build:minify-wrap-css": "(echo \"\") > ./minified-wrapped-style.html",
- "build:combine-html-css": "sed '/<\\/title>/ r ./minified-wrapped-style.html' ./base-html.html > ./dir-index.html",
- "build:remove-unused": "rm ./base-html.html && rm ./minified-wrapped-style.html"
- }
-}
diff --git a/assets/dir-index-html/src/dir-index.html b/assets/dir-index-html/src/dir-index.html
deleted file mode 100644
index 109c7afbf44..00000000000
--- a/assets/dir-index-html/src/dir-index.html
+++ /dev/null
@@ -1,98 +0,0 @@
-
-{{ $root := . }}
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-{{ .Path }}
-
-
-
-
-
-
-
- {{ if .BackLink }}
-
-
-
-
-
- ..
-
-
-
-
- {{ end }}
- {{ range .Listing }}
-
-
-
-
-
- {{ .Name }}
-
-
- {{ if .Hash }}
-
- {{ .ShortHash }}
-
- {{ end }}
-
- {{ .Size }}
-
- {{ end }}
-
-
-
-
-
diff --git a/assets/dir-index-html/src/icons.css b/assets/dir-index-html/src/icons.css
deleted file mode 100644
index dcdbd3cd9e2..00000000000
--- a/assets/dir-index-html/src/icons.css
+++ /dev/null
@@ -1,403 +0,0 @@
-/* Source - fileicons.org */
-
-.ipfs-_blank {
- background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAWBJREFUeNqEUj1LxEAQnd1MVA4lyIEWx6UIKEGUExGsbC3tLfwJ/hT/g7VlCnubqxXBwg/Q4hQP/LhKL5nZuBsvuGfW5MGyuzM7jzdvVuR5DgYnZ+f99ai7Vt5t9K9unu4HLweI3qWYxI6PDosdy0fhcntxO44CcOBzPA7mfEyuHwf7ntQk4jcnywOxIlfxOCNYaLVgb6cXbkTdhJXq2SIlNMC0xIqhHczDbi8OVzpLSUa0WebRfmigLHqj1EcPZnwf7gbDIrYVRyEinurj6jTBHyI7pqVrFQqEbt6TEmZ9v1NRAJNC1xTYxIQh/MmRUlmFQE3qWOW1nqB2TWk1/3tgJV0waVvkFIEeZbHq4ElyKzAmEXOx6gnEVJuWBzmkRJBRPYGZBDsVaOlpSgVJE2yVaAe/0kx/3azBRO0VsbMFZE3CDSZKweZfYIVg+DZ6v7h9GDVOwZPw/PoxKu/fAgwALbDAXf7DdQkAAAAASUVORK5CYII=);
- background-repeat:no-repeat;
- background-size:contain
-}
-
-.ipfs-_page {
- background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAmhJREFUeNpsUztv01AYPfdhOy/XTZ80VV1VoCqlA2zQqUgwMEErWBALv4GJDfEDmOEHsFTqVCTExAiiSI2QEKJKESVFFBWo04TESRzfy2c7LY/kLtf2d8+555zvM9NaI1ora5svby9OnbUEBxgDlIKiWjXQeLy19/X17sEtcPY2rtHS96/Hu0RvXXLz+cUzM87zShsI29DpHCYt4E6Box4IZzTnbDx7V74GjhOSfwgE0H2638K9h08A3iHGVbjTw7g6YmAyw/BgecHNGGJjvfQhIfmfIFDAXJpjuugi7djIFVI4P0plctgJQ0xnFe5eOO02OwEp2VkhSCnC8WOCdqgwnzFx4/IyppwRVN+XYXsecqZA1pB48ekAnw9/4GZx3L04N/GoTwEjX4cNH5vlPfjtAIYp8cWrQutxrC5Mod3VsXVTMFSqtaE+gl9dhaUxE2tXZiF7nYiiatJ3v5s8R/1yOCNLOuwjkELiTbmC9dJHpIaGASsDkoFQGJQwHWMcHWJYOmUj1OjvQotuytt5nHMLEGkCyx6QU384jwkUAd2sxJbS/QShZtg/8rHzzQOzSaFhxQrA6YgQMQHojCUlgnCAAvKFBoXXaHfArSCZDE0gyWJgFIKmvUFKO4MUNIk2a4+hODtDUVuJ/J732AKS6ZtImdTyAQQB3bZN8l9t75IFh0JMUdVKsohsUPqRgnka0tYgggYpCHkKGTsHI5NOMojB4iTICCepvX53AIEfQta1iUCmoTiBmdEri2RgddKFhuJoqb/af/yw/d3zTNM6UkaOfis62aUgddAbnz+rXuPY+Vnzjt9/CzAAbmLjCrfBiRgAAAAASUVORK5CYII=);
- background-repeat:no-repeat;
- background-size:contain
-}
-
-.ipfs-aac {
- background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAnhJREFUeNp0Uk1PE0EYftruVlvAUkhVEPoBcsEoLRJBY01MPHjCs3cvogcT/4qJJN5NvHhoohcOnPw4YEGIkCh+oLGBKm3Z7nZ3dme2vjOhTcjiJJvZzPvOM8/HG2q325Dr3kLp7Y1ibpIxjs4KhQBZfvV6s7K5Vb0bjeof5ZlcGysP1a51mifODybvzE8mzCbrAoTDIThMoGXZiZ4YSiurf+Z1XeuCqJ7Oj+sK3jQcNAmg8xkGQ71mYejcAB49vpmeuzJccl0+dUj6KIAvfHCPg3N+uAv4vg9BOxcCmfEzuP/genpmeqhEMgude10Jwm+DuUIyUdTlqu2byoMfX/dRermBeExHsTiWNi3+lMpzRwDki8zxCIATmzbevfmClukiP5NFhJgwkjeRTeLShdOoVJqnAgwkgCAZ6+UdLC9twjQZ8pdzioFkZBHY3q6B3l4dJEEEPOCeD4cYVH7Xsf15F+FImC775INAJBJSkVoWo0QY9YqgiR4ZZzRaGBkdwK3bFxGLRZUfB3Rm2x4x9CGtsUxH9QYkKICDFuLxKAozGZwdTqBRs2FbLlXbiPdECMCHadj/AaDXZNFqedCIvnRcS4UpRo7+hC5zUmw8Ope9wUFinvpmZ7NKt2RTmB4hKZo6n8qP4Oq1HBkKlVYAQBrUlziB0XQSif4YmQhksgNIJk9iaLhPaV9b/Um+uJSCdzyDbGZQRSkvjo+n4JNxubGUSsCj+ZCpODYjkGMAND2k7exUsfhkCd+29yguB88Wl7FW/o6tT7/gcXqAgGv7hhx1LWBireHVn79YP6ChQ3njb/eFlfWqGqT3H3ZlGIhGI2i2UO/U/wkwAAmoalcxlNA1AAAAAElFTkSuQmCC);
- background-repeat:no-repeat;
- background-size:contain
-}
-
-.ipfs-ai {
- background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAk5JREFUeNpsU01vElEUPTPzZqBAQaSFQiJYUmlKYhoTF41L3Tbu/Q/+AvsX3Bp/gPsuWLrqyqQ7TUxMtAvF1tYGoXwNw7wv7zwYgtKX3Lw379575p5z77O01ohW+/DVh8zj7aYKhflGdG9ZsGwLNydffgVfr19YHvsEa+Zu/nxndob5StQK+dyzvZzyw/gKlmMj7IygFM+xvNcanp4/t5dAomXHBy2UUBOO2MAl/B9/cPb6PULuoHx0WM0e3GvpUOxD3wZAJWutZqYUYmqpSg5OMgH3YQObL59W0/ullpryR3HegkKEqiWBSGV4R3vQ7sIhScTZFTpHx3A215B5sluVY/WWMg7+ATB/lcLsKpTonHzD+OMFEuTz8ikkt9Kwt9YJZB38cpBdoQAZJdLvCGByfoPB6Xdk90pYy6Xg3c/DaWwArg09DaG5lCsUFN0pckZAojdC8m4auBqaALuSgez7VB1RtDSUWOQvUaBLFUzJBMJ2DwmPgd1Jwm0WoSgJfjDvrTKxtwAIyEkAOQ5hU//Zdg5uowDlUNMnwZLW0sSuUuACYhwQRwFvJxupCjEYUUccOkoaKmdOlZnY1TkgAcXAhxhOwLsDsHoN3u4O5JTDfVCH6I9nfjId3gIgSUATFJk/hVevGtOMwS0XwQ3AzB/FrlKg8Q27I2javVoZrFgwD4qVipAEyMlnaFArzaj/D0DiMXlJAFQyK2r8fnMMRZp4lQ1MaSL5tU/1kqAkMCh2tYI+7+kh70cjPbr4bEZ51jZr8TJnB9PJXpz3V4ABAPOQVJn2Q60GAAAAAElFTkSuQmCC);
- background-repeat:no-repeat;
- background-size:contain
-}
-
-.ipfs-aiff {
- background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAohJREFUeNpkU9tqE1EUXZmZpE3aTBLbJFPTtFURtSCthr7UCyKKFJ/9An3og6Ag/oXfoUj7og9asCBYKT6UIPHaWtpq7NU2aZK5z5wZ9xxMpMwZDuewz9prr32ZiO/7CNaDx3OLt6fOjBqGg/aKRCIInp8+KzfKH7fudnVF58nE16el+/yU2mBFSWZKpWJKVc0OgUBo02K4NDmU6o75Mx+Wdu9IUXFeiOA/pn1xHeYaugVDdzpbp91qGlAKGTx8dC19/Wpxhjnsxj/RRwk85hGJC9d1O6fneWAuoztDYSSLe9OT6SuXB2ccx73Z9uukwDwfls1g0xZIY/Ad/Gnyt/XVfbyYrSDRE8PExHB6/8B6QuaxIwRBFMt0iIAiMx+LCys8jfGJEUik2WpZOD2SQf9oDtVqQwopCAiY66FS/om3b75CVS2MlU7AJ2WiJBCZjZ2dJuRkDJZFwFAR7UCBja3fNfxY2YEoCtRCj9em3Tpds6FpJseGCBxS0GgYGBzqw62p84gnYnAI2CSbSbPhEpFAaE2zODaUAlWWwDoS5DheGqbWpVE/0CmqCY9qkEyINBceb2uADRNQ8bSWAVVzIFKomCQim+0luS4yKYlsHlRyZo7EsSEC23K5vAsXh/H92zZkuRvxeBS5nEx2yp2KqhxPoV5TYS/8CtdApylM9sZQKKSQzyeRTseRV2QoAzIYY8jme5DN9fI0dQoUIjANGydP9VM7PZw9p/AiBpNYrdbw/t0yTJqRtdU9UrfJCUMpSJIgbWzsYe51BcViHzLHeqCRqhZ1YX1tFwNfZBxS9O3NWkAcHqR606k/n/3coKAoV/Y7vQ/OYCZevlrmv3c0GsFh06u3/f4KMABvSWfDHmbK2gAAAABJRU5ErkJggg==);
- background-repeat:no-repeat;
- background-size:contain
-}
-
-.ipfs-avi {
- background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAm1JREFUeNpsU8tu00AUPXZcN0nzTpq2KQ3pAwkIAnWHqCoeexBb+AQ+ABZ8A2s+AIkdm266QUJIFWKBkHg1KpRHi5omJGkbJ3bGHj+4M1EQrTvSyGPPueeec++1EgQBxHp+/9mbyuriRZdxjJaiKBD3W+u1+p9a856max+gDO8ebT+WT20Ezi9NZi/crqadvn2MQBAGfpCOpqNru2937vxPIpY6Onjccx3Twck9MBiSU0ncfHirXFmZX3Md9wqCUwiEVN/zaQfHt0vfbBe5uQyuPVgpl5Zn11ybL4/i/lkICOw5niQRGQShoiqI6Bo43W2ub8n3hRtLZT7gTynk6gkCX9gAOxpAnxhHZDwC1/aI1EViJolu/QhKRMHZ1UX0Gr1USIEn5FPWHy+/wTokkrQOq2vBaHZBN4hmY9Jwfr4An/teiEB45ZZDwDiMhoExT0N+sYDCuUkkplLIlXP4/XEXdo+RUhdhBSSfUwtVTUG8MIHK9QVqI7D/uY6vr2pwmCPrkz+Tk9gwARWQ9WxppbXZhNnpw+ya4A5HZi6L4lIR8WyCcL6sTZiAWjWgAmpxkn5+kqTamK6WkCwmERmLDLvjB0ML9ikWXPLFuozYOap3L8HYN6DHdbS/d5CeTVBndBz87FCBLYkNTyIjBQemnIEsSY5lYrK1+UoWcToLMjEHAyIQ2BCBSx/NVh+ZUhrqmEqBebS3WyhdLg0zt/ugAaIklsSGLHCLa6zDMGhZ2HjyGsnpFPqNHnY2fmHv3R5SMymYbROszSQ2ROAY9qHiofvlxSc5xsKKqqnY3diRE9h4X5d/pzg7lnM4ivsrwADe9Wg/CQJgFAAAAABJRU5ErkJggg==);
- background-repeat:no-repeat;
- background-size:contain
-}
-
-.ipfs-bmp {
- background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAmZJREFUeNp0U+1rUlEY/13v9YV0vq2wttI5CdpL9aEGBZUDv0df668I6n+or0UQ/RuuD0EgVDAZrsKF4AR1a6COKW5qXvXec27PuVeda3bgcF6e8/ye5/d7niMZhgExnK9fbTrm5pbBGMZDkgCyq+VyhTUaT6Eo2ZHJePPWXJXRhez3B1yxmM/QdctXUSCgtV4Py4CvY3cky4e1x5DlLCaGbbzjXDcousG5OQe5HPRSCQPK4PpsEM/XH4WvhS4noeu3JwHGGRiULhsMoKZS4I0GtEIB9mgULJGA0+9DPBpBT7sffvf1W/Lg6OgJufw8C0CRGEXWazUwiiyFQjA8bsjVKjaJzovMD/Q5gxyJhG2cvyeXe2cAuADQNGBmBvLaGuTFRaDfh31lBTWi9pumjbK0B4JQul3vOQpM8JdskOLrdCvDcDjAsjtg5TIkoiKLaokMNR2cnZbqNAMycqG7XbHKR2fMzwO/dsxSwu0BiBJsNsv2LwAJAJCI5ux2gXYbqNetcz5PoORI1cDS0n8AxGW7A+zvEYBKZ2ZlcsEtJLbedMjePBaCTQMghx45ulyWkzxMVUQ2RMQhLfFO16YAqCrixPnm6iqKrRb2W23EfF4cUNSrHg90cr7hDyB33MTnSmUKALVs4uIlROjxg+AsPhGVl3fuIl2tIOB0Ya91gkOi9mxhAal0ekork1ic/kGLBORMxy2K1qS9V1ZQbNThIj2EGh+2tsyOnSai8r1UxMNIBB+LRTTULr4Uds0K1tU/uOLxIrmbNz8XXSrnASSpubG9fbKRyVh1n/zSw29t9oC1b47MfwUYAAUsLiWr4QUJAAAAAElFTkSuQmCC);
- background-repeat:no-repeat;
- background-size:contain
-}
-
-.ipfs-c {
- background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAcxJREFUeNqEUk1rE0EYfmZnkgoJCaGNCehuJTalhJZSUZB66a0HwXsP/Qn+FM+9+hty0LNYCr2I7UVLIW0Fc0hpQpSS7O7MrO9MspuvVV8YMnk/nn2e5x0WRRFMvP/w6WSz5jbi/9NxfP693Wp3DrJCnMW5d28P7a+IE15lufR8o1ZEStwPhkWHsWbrZ+eNEPxsuubEF6m0TBv2Q4liPofXuzveulttSqW2UwH+GjqC0horpSL2njU89+FyMwjlTlxOJMTa9ZQHzDQIjgwdom9zLzfXPc75kbnOAswBJTlC2XrqQRMLxhi442DgB4UFBhgPpm3B5pgBHNUUxQKAHs8pHf3TEuFMetM9IKr/i2mWMwC0SnuSFTG2YKyppwKYVdGO7TFhzBqGIenVeLCUtfURgErucx5ECKREKBU4d3B718PHz6cICGT/1Qs8qpQtGOdyhtGEARWDQFqQJSeDL98u4VbLaKw9IRAJPwjtoJGlVAoDQ800+fRFTTYXcjlcXN2g++s36p5Lzzlve1iEROa8BGH1EbrSAeqrjxEqicHQt8/YSDHMpaNs7wJAp9vvfb287idboAVkRAa5fBYXP9rxO4Mgf0xvPPdHgAEA8OoGd40i1j0AAAAASUVORK5CYII=);
- background-repeat:no-repeat;
- background-size:contain
-}
-
-.ipfs-cpp {
- background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAfJJREFUeNqEUs9PE0EU/mZ2WgqpXX+QIDFdalVslh8NlAOQaOKFAwfvHvwT/FM8e/U/MOnBmwcj8WD0ACEGghIkbU0baaEthe3OTJ0ZWV26q37JZt68ee/b9733yGAwgMbL12/fz+azbnAPY2Nrt7Zfqz9JMrYZ+J4/e2pOFjiciRvXlgp5GzHonXk2o6S8V6k/TjBrM/xGA4MLyeOSPZ8jkx7D+uqCU3Amy1yIYizB36AlCSkwfjWDR4uu40yMl/s+XwjeWThQQ4Z6QNSnSkYykcDXasP4lmfvOZTSF9q8TDBEFPbN5bOqCglCCCxK0TvvZyIV4CIxbgpC+4gm/PUmFCIE8iJPyME/e8Lon9j4HvyHYLjKSwRCSEUgf9+15mFbx8QS6CZJMzJ9SlBCwX3fJDLG4PX7ykcwkmQmJtpEhWa7g1dvNlSwjwelebz7tAXLolh0p/Fxe9fErK2WDFGEgKjxfNjegX0lDTc/heNuF99/HGEslcKXwyoazWNDdlCr6+DoJgrBzdI0T9rYO6yg2zszMlaKM3Dv5OBzbuyZuzm1B16U4Nzz2f3cFOx0Gq12F9cztpExncsqYoaHpSIKtx0zJdVIFpHQ6py29muNk1uTN829o/6SHEnh80HFaE6NjmLnWxUJy1LyTltB3k8BBgBeEeQTiWRskAAAAABJRU5ErkJggg==);
- background-repeat:no-repeat;
- background-size:contain
-}
-
-.ipfs-css {
- background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAk1JREFUeNpsUktvUlEQ/u5DoCLl/RAKKKUvWmIxjYntQtcu3LvwJ/hTXLt16coFC2PsojEaMKZtCqFaTdGmjbS0CG3By+vei3OOBSGXSU7uzNyZ78z3zRF6vR6YvXzzPrMUCyf68bB9zO+VfpROn5hkOdfPPX/2lH/lfiLidztX5mN2jLGG0rKLENIE8liWpdzwP7HvqJqujmvudFU4bFY8Wk1FZsOBtKppd8YCDNu77CZevd3gflfTUFcUhP0ePLibiIR9rjSBpgwAfe4dVcV6dhtep4PH5msylGYLrzeybErcT85FYiH/CyPAf74gObC2vMhzsiRhPhpC6eQUM+EA1pJzILEnjRSuJsju7MJqsUCSRei6Dp3yXqcdGlHZ/rLPazQWGCn8+6YW4pAkEW0SjzUzanWlCa/LgcR0lNfovTEi6lcIkzesnM/R8RlN0INGp3h4DHoDsE5YRvQyiKiRSMzikRAOS2WoqoZWu41K7RwzlOOAVDMMMHhIGvFlRxJFrKYW0ep0IYgC3SDh4b1lTJjNfENsrazOAMAw680mPuW+8lFno1P4XDigRhOiwQAyJK7TbsNS/PaA7giAIAhYz2yRgBIfsVA8wIetPG6FAqhdNrC5u0f+TUyHgyMTDDToEt/ftQsEvW4EPG5OZcrvw0mlimarTXkPfpXPcNlQoGtjACgpryQXsPNtH/nvRXqBJpoKHMzGNkNB0Odls7LNyAYKpUq1dt1iuvB7fRDp9kr9D1xOFwkpoksXusmXaZWFn0coV89r/b6/AgwAkUENaQaRxswAAAAASUVORK5CYII=);
- background-repeat:no-repeat;
- background-size:contain
-}
-
-.ipfs-dat {
- background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAfVJREFUeNqMU01PE1EUPe/Na0uptmlASg3MoiZgCA3hQ8PHAjbqwsS9C3+CP8W1W/+BSReyYUPwI4QAVkAgUEgIbVIg1FZb2pl5b3zv2cHBjsaTTOa+e989OffcGeK6LhTevFv+OJoZHPHOfrz/sl86KpWfhxnLe7lXL1/oN/MSZqonOXU/k0AA6lfNhEFIrlAsP2PMyPtr1AscLpyg5pbtIHErhqez4+awmc45nI8FEvwNaiQuBHqTcSxMjJhmX0/Osp1xr878FxWEzwMinxAzEA4xFIpnOjedHTKpYbxW4U2CP4j8uWxmUKsghMCgFI2mFe9QgHZj0Ba4yhFF+KvGJToIRLuPC/efnjD6+26wB1Lq/xgbSCBXKeWJG/OTdky8cWTdT3C9RmWSGk2XCLlWo4xTNbfN5qh7PpXM72GjZeHt0gpq9QbmH4whGb+NpU/reDQ7hcWVVXxvXOHxzCQopQEKXKEbL6o1ZIcy+LC5g62DY2zsHeC0fA4zndIrHOjvg2XbAQRSfsuy9XxC2qzi/H5B6/68W0AsGkW0KyJPBLbDO0fg3JX/CUM81i0bD6WKe6j9qOPJ3EMcF0tSNsFA6g6alqW+VtZBUL78Vtk+Oqne7U9rs5qOQCjSheJFBeFIFOfVujSUYu3rIc4uqxWv76cAAwCwbvRb3SgYxQAAAABJRU5ErkJggg==);
- background-repeat:no-repeat;
- background-size:contain
-}
-
-.ipfs-dmg {
- background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAn9JREFUeNpsU01rE1EUPe9lkk47yWTStCmtNhFSWxos2EXVhSsRcasuxYV05V8Qf4DgD/AvCK5EV1oFI7iUBqmCNdDvppq2mWSSzEzy3vPOpFFq+uDNfR/3nnvueXeYUgrBWH1/9/NE7k5BKRnuRcfF2qdnmJq9DeF9tQ+2isuMsxXGWHh/a1mEVsPJSI5fSU3OPEj291IIlN49RXz0KqzEQjIeZS/L5Y/3wPGhDxIM/i/A7fZWgVG0t5EaG0ZUa0JGM8gvPrZmLt58QYwv91mfAqCIE0sAqgumBFITGQzpUYhuF0KfRa7waDyXXXolpVrsh/0tgSLDr5I+wUZo1UHCSkAficPzY6juFSmbRPrC/azjq+fkcO00gAqoU7B0ETKkfWbuCTjTYeq5oESAauexcTScX+ZACWFm0YQSLZKhHdr67+/wW0e0dgjYo3sCEXXybYtBDVSHLp2es3IpsILS24c42lkBg6DzRjgRzCDZ/xr0GNRJwwYiWgzt+hYMawleu0V3wbkT+kUirOc7IGJAz68R/Qak1BAlx3hqASPGBJRXpXOv58dkz3eAgQoOm4hyj57NgZm0MHvpBmK6QdUdg/DAg9cRkhicBSDaKJdeo1bdxmR2DtWDDUxl51HZ+QHTysD3XdQO95Gfv06aeGcAdBrY3Chi8lwO3768QWX7J5q1XWyVSxgajiOXLyBG2hzurRKV9lmt7ISNkkjo6HhNyjoK+2gXRsKE57ZIE2ot10Z1fz0Ue4ABVw3NMjnW14rInh8jTYywoTg3EOFpOM4mXNfH9PQUfGlrAwBOs3I8ljbtuMWhRWzIIPrkn+GcYcgIWEowbZ+0qB334/4IMADESjqbnHbH0gAAAABJRU5ErkJggg==);
- background-repeat:no-repeat;
- background-size:contain
-}
-
-.ipfs-doc {
- background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAppJREFUeNpsU79PFEEU/mZ39vZu77g7DokcP04BBSUmiEKCSCxs7Ei00JAYO2NlTKyMrX+CJhaGwopSQ0dMtFEsbDRBgiZEQIF4IHcg+2t2Z8eZ5QDlnM1mZ9+8973vfe8NEUJArfSNhzPG0VIfeIiDRSDkw1cWVt3N8rhG6SdSO2Gvn8dfuueqZwuNZqk3Jxg7iNcIfBbgXD6ZC8u5qffzX8eoYeyDxC77uygKhcouovgVUQj1H4YB2ovNuD9+tTTU0zMVBmG/+C8AIYh8F361DL/yE5HnADKYlVdg6MDAmW7cuz5WGuw+PsWDYGAvbL8ECFUt4K7/AHd/I9c7BLaxinD2Ld5Zo7g78RLuRhlBS2cpWbGfStfhfwCEpK0nUjCbWuGsLciSOELPhkq/YgdY3l6HsLfRcLYf+pHNbH0JigEPkLAyMsiEJ7NrqQzM1i7wyhoMZqOhvQs6Z0ovXgdAJACRoulEg5HOwrOroKk0zOY2BDtVpTF0CU6kLkQJXa+BNEoG0lMSsBBKQXWNQktmoGcaYeSaQCIVWOvUYQAiWZFQtk5mSMoSzEILtBrTfEcviC5bwVwQmoh96wA0ic5dB57ngeoaTIPCdb34zDITYNLOOIeVSsW+dQC+7+NSWx6jJ4tY/rWNV7PfcGv0tBoPTM7M4eKJVgx2FTE9u4QPS6x+kHzfw/mOAjarW2hJG3hy8zIceweuY+PRtREMdzbjzcd5WBqPB6xeRGUMGRzHjWvMmxQ7tiOF1JBN6FiTd6Sy9RuFbHpX7MMMqOD088Ii+op5OUAO7jyeRGfBwrF8Cg8mXuDL4neMXzgFwhwZz+hf7a9d5yu3Z6DTPjVQIY9k7erO7Y63Lvc8ErEeyq6JaM6efjai4v4IMABI0DEPqPKkigAAAABJRU5ErkJggg==);
- background-repeat:no-repeat;
- background-size:contain
-}
-
-.ipfs-dotx {
- background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAndJREFUeNpsU01rE1EUPTPzJk0y+WhMStW2qdVWxUVEQUF0I+4ELQiC7lz4N9z0T+hG9wrdZKUgLqulhrbSag1CKpT0g7RpYjqZmffle5NEKdMHlzfvvXvPPffcO4aUEno9f3Vt4dTp+BXOe+fB0u/NbVpv7h89NU1j1TCM8H7+xY9wJwPHZMbOjRadLAvE/2gToJTiTPx89k+OlVd/LT+0TPIPpO/SzyQk40xCMxBSZ9Z3CoAx5DOjeHT7SbE0XSpzwa8OWB9jINELolQg8AR0EgUKn1PIlIWpkUt4cPNxkTOU12trs8p95RiAXpqaztqou8q6SKQJJmZSqGwsodFsIJk1kcyLYv7IeafcLx4HUNkFF4jFTExMZ0B9DrfD4HUEusYhWs4GPEJg5wly/tBYRIOeDhpEwlS34xcyajdQr3UwOT2MlJOEBRuGNHWp9AQRVXDfQiFV/U5GBSiQ5p6ngBEa5z3fiIhC6g6IMDBwOdoHPkYnHPVyhN0tF7E4QSpr94CEOKELffq+y9Bq+DCJ7rWBoQQBVbPR2O6G4OlsLASJMtCZfQqm0NP5IVWnamdAkUxbyuIYtD7wWegb0YAzAVMkkI6NwPM9xEwHloyDGAmk7AKS9rAS0FKOdugbYeAHPu7OPEM+MY7q3hIKqTFQHmC3XcONc/fxdfMDrk/ew/edzyhvvTmBAddocVRqH3Frahau56qpZDho7+PnTgXffi/gbHYmLEvPSIQBp5JU62sYz13G609zKBXvoOMdYn2zgm7Xg2MVML/4Eu3uPgxhk2gXmNl8v/i2pcXTP8tKdTEcbWLZqDQXwu/l6pfwbEnSGsT9FWAA4mdHv2/9YJ4AAAAASUVORK5CYII=);
- background-repeat:no-repeat;
- background-size:contain
-}
-
-.ipfs-dwg {
- background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAoFJREFUeNpsU0tPE2EUPfOg006hD4rQh8WgbCSwkKgbF2owujaCiQsXxpX+D6MmbtXEsHCLmIAbE6NLo8YlGIxREIshIqVl+mQ6j8/zFVCb4UtuZua795577rl3FCEE5Bl79vPd5LHYiOP7cH1AUWi85ytmvlas1bJ9E5ryBntH3BpuP/X9i7ovkluuiE8N9SDepaLpCcRCCqa/VDCaMuIjSWP25Upl6n+QDoCz6Yh7KKzh3sI2LuUimPtRRyaqodj0MDloYiITSTi+mH29Wu0AUf9CsZPJoW5czJl48LmCc5kIKo5Al67B9gUGYxrun+5NnMlFZ+GKiQADj2a7AquseLIvjMv5KMaSBu4sWVir+3i8VIVKYSby0UTdFU8Znu8AYBHQgVOJEN5uOXi4UsdawwU0FSf6TaSoyw6DRvukPkgGWpDKy4F8a3jImCrqFDFn6rhKPR4VGnhvOTAY3WLcjifcQAsqRfhUc/Gq1MKNbBh9nIAMDjEppocxs9HCMktfGTCwP/oOBkUKNk/qF3pDYC6Ktk8RfWzyaaoKrqdDaBDwya8W1m0/CPCR3kFy7CcnmWQRUJqcRJFUKtTnPCeR71LwoeYF92CYyVnCFZpCTrRtCv5to2St8SOrKxiPqEEA4fkYT+mI0rdoeUiH1XZVuQPpsIKqw2QmfifTsnOABiWySlH9uU0Hh2MqjsZV5LtpPSoGeN9rKnhBX7ehoOSLIIPfnGONXGMMWN7xUfVldYDbjM3mrh5HCDgS17DhHgDQcIU+XbBxnDTn1x1UuQcJ9iv7l5Q5e1zLGri92EDJFnoAgHtcfr6wbbVXUqq193+0z97n3UJt1+d51n7aHwEGAAHXJoAuZNlzAAAAAElFTkSuQmCC);
- background-repeat:no-repeat;
- background-size:contain
-}
-
-.ipfs-dxf {
- background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAo5JREFUeNpsU0trE1EYPfNMmtdoH2kDNmJbaVFcaBVFpAsREQpFwY0bu3HjQnTj1mVd+ANcuC3qQixmry6E0kWFVIQ+bKy2tbFJm3emyXTujGca+4DkwsedfLnn3POd77uS67rw1vC79ek7fZEzpu3AYUqS9tKQGZPLpa3VXP0uFCmJ/8t9OLC3q/uJbcs5bkIybvdHoMsSbLKENRmvU2WcNnTjRFD7ML1WGSPJHI6sA4KRWMAWVDPxLYex3iCmfpuIh1QsFSyMxQO4GvXHHwOJ6XWSyIck8v6HQsnjAxFc7vTj2VwBg4aG78VdBHQFCk+dbVcxMdwev9gTSEC455sIBOu2KLsoJFzqasP9vjCeDBlYqzn4VXXwarGKZN7Crd5QfLDT/7KpBM84c9fFUFjFp2wdk6smflRsKKqMa7EgfJJ3Ac2OKlit2pEmBTQfngdpnupoU7BUtRGiiTe7fXiRqmK+KuDn6TpvYogmBRJcrOwIJLIWxmM+dOsyLKryQAaJpjJ1/AxrGO3SqdZt7kKZJrzJWBg5piHENuY8vV6e0UOye1TyftvC5l+gZB8SHJTwpSx4q4JeTUKaxhXoR57h7Rn+3iFolJ3xvPhab6HgJG/pJ7jsNP4sUX+jZiCgEsWd/DjH5IrSYpBUAr0yHpzSoXKOP25a6OBhndh0zcX1qIYM2RIbu6i0KiHD5B/GTMHG03kTGpEL7H80wHFOWwhqDZ+SpkBOtCDYJDhZE4gRcKNbYynAqbCMbXpwpVPFbEng0aKJGbYzK1p4wIegLlcEPmdt+DjXbzcsxFlCynRwwVAwW6hjqeg0Zt521SYCWCJvbe0Un29UDx7Hgrs3IEitHXkw3jOv2fl92D8BBgAJeyqBh90ENQAAAABJRU5ErkJggg==);
- background-repeat:no-repeat;
- background-size:contain
-}
-
-.ipfs-eps {
- background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAmlJREFUeNp0U01vElEUPfMFCEVArdoSqEA0KV246UJdUJM2Lo2JK/9FjXu3utJqTNz4D9worrsQExbFpAFT0TYp0CZ8pIAiyMfMvBnvm2Foa9uX3Lw7c98979x77hNM0wRf7ufPsq7Z2SQYw2QJAkDxQalUZa3WI8hy3gmZr15bu+z8kILBkCeRCJi6bufKMji0NhwiCQR6iitdatTvQ5LyOLLEiWcYukm3m4Zhmbq1BX13FyoxuH7xAlbvpqKRK1fT0PWbRwEmDEyiy1QVg/V1GO02tO1tKLEY2PIy3KEAlmJRDLXb0TeZL+n9g4MHlLJ5HIBuYnSzXq+DlcsQLk/D9Hoh1WrIUjlPcpsYGQzS3LWoaBhvKeXWMQCDA1D9pt8PaXERUjwOjEZQFhZQp9L2yERiqYRCkPt/z58ogTGqHQLE1BLgUmC6XGD5AlipBIFKkbhanKHGYLBDqQ4ZED0OAbfLlo8OIxwGvhVgyTHlA3xkomjH/gegBgDURMv6faDbBZpN+/tHkUApkdTA/PwZAPxntwdUyjYA/+ZMqJHjLgM9iv/6zRt2GgMaIE21aVIjnSm0DGPfmhzyde0UAE2Dj+p7urKCPvkZku9eJILOSMUnkvVhIo7GYIB3xSKYdhoA1erXGVKXpvFxZwdBonnD68PQ7YEwM4O4xwMPxc8RYE87g4FIcz+kvfmnA0YzIJIy77/m0OCqsTkkCTysKPjJG3viLei63Gm3kCO6UWqcMejjxecMPmxsoFKtYop6UNirYL9Wtc5OHqzznIXHq1na7OfMJROcK8a6O7MjW7nfzZdrd7jzT4ABACh3NGsh3GcdAAAAAElFTkSuQmCC);
- background-repeat:no-repeat;
- background-size:contain
-}
-
-.ipfs-exe {
- background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAo1JREFUeNp0k8tPE1EUxr+ZzvRJO62lUAQaKIQ0FVJFjBBdoIkrDDHuXJi4NnHtX+HCjW408Q/QmHTRaCRRohIJifgiiBICTQu29mHfnc7MHc+MlECKdxZz595zf+c737nD6boOYzxJLC6Nhwej7e/24HkO779s7G6mMjcEwfKZ21+/d+em+RbagaFev28qEpZwzKg3ZckqCPH1nfS8hScIdyhBe6JqTG3PfyTTeLrwFhvbKdy9/xi5QglXL0yGJsKDccZY7LDIAwWHpSferWBh+RN8ni4UylVER8MY6PHj0uSpUK0hxzfTmWsUtnoEwO3rer64jEyxim6/Hy67DXaHExvJX3jw7CX8XjfORUdDlOohhU4fAVjILCPbm9V1yIqK2FgYt+ZmsZcv4lH8Nb5upXD7+hVMjIRQa8qeDg8UTYPU5cTcxSk4nS709XTD53ZhpD+IYMAPj+TBz93fZiz5oHV4AP1fGdlyHZIkIZkrI7GyhnK9CZXy+Aig6p1+HQAY003AcF8AVtGGfLWG9XTO4MLZ5cL0WAixoT4zVmPHADSiMo3hzHA/xgeDWFjbNg8H3A7kKnX0koEcPdTu/ylgRGZgOjNv38zoSXC8BZJDRKOlwGEV0VJVGM0y4joAPO1spXbx6sNHeD1uRIYGUCxVSRlDt1fC8rfvcDnsmJ+dOaLgoAs6AVLZPJJ7WdhEkUyT8GJpBflSBcVKDTvpDBw2GzQqQT1OgaZqUOhtFQUTUKnVTVWNpgy51YLVKph7sqKYkA4A1ScEfT66vm5kC3+ofh6Xz59FQ5bpkvE4QW3M5Apoyorhl9ABIKnFgNdTOh2NkJG6WSf9eRBJtmFwLDJmriUzeaOkYvvcXwEGAIVNH6cDA1DkAAAAAElFTkSuQmCC);
- background-repeat:no-repeat;
- background-size:contain
-}
-
-.ipfs-flv {
- background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAmtJREFUeNpsUl1PE0EUPbssLYUCXdpaC9gWoSTgAyFigiRGY+KjvuuTr/4A44MP/gx/gMYfwIsan0RjIjGiJIZgSIGFIoXSD0t3Z3dnd70zpITazuZmJzP3nnvumaMEQQCx3jx69SV3a3KWMxetpSgKxP3m242Do43SQy2k/YRydvds67n8a63k+FRSn7l/bdg5tdsAuM3he/5weDC8vLdqPLgIIpba2niux52mg//DqlsYSg3iztO7mczN3DJ3+ByCLgCBH4hOFEF7cDpzPCRyOpaeLGXSc2PL3HbnW3XaRQCPEgWI2MsRVAVqrwbX9bHxbhOKpiJ/bzpDOr2k68V2BtRNzMtqDEqPejY/4zSGjb54BM0mQ8k4xsDoIMauXxnqYOD7PmwScP31d0SS/eAuh1lrolFpIBQNQw2pqJdqsAlIceB1AJCIkkE/FZskXDQVRXw6IYHiE0nBEcaPXSSvJnGwWkQXAE4acAhbxPMJpOdHweoMhc9b2F8zwKizbdlyPLVH7QLg+JKBYzoorxzjz3oRzUoToaEw9KyO8XQW5AE5jrFT6AbAYVVNxCZ0Ka3So+DSTAoDiej5ywTySbls1OEDobhFlMcXxrHw+AbINEjNXgb7y6BndLhk8cRkHHbD7g4gEhiJFxsdhrDqaamBaDKKerGGSKwPI9kR9EZCaNA5ubE7A5s8IFhsrxQkgJhZoa/06xC5xRz2v+3BOjFlbqcGlquxsondT9vY+2pAJdeZR6fI355CgQCN2A4O1w7gkQ7cdLUOAKdhV6uFSv3kd/n8mT68eC8dKWLnY4FsfeZQh7nVVt0/AQYAsf5g+SvepeQAAAAASUVORK5CYII=);
- background-repeat:no-repeat;
- background-size:contain
-}
-
-.ipfs-gif {
- background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAmVJREFUeNp0U0tPE1EU/trplAqlL0laiw40xASByEJIZFGVnSvj1j+gWxNXJq7VrbrwF7h10cSNhMRHojEuACVBKmH6SJQyJeXRxzzv9dyZPiCtN5lMe8853znf953xcc4hztDzZ1+C6fQMHAfd4/MBFG+p6h/n4OAeAoGNToi/eOm+A50LKRaLh6amoty2vVpZdotNXccMEK3LwZxa2bsDSdrAqePv/mLM5tSdMwYBYqyvw9zdhUn/L59P4OGtG8qlZCoH254/DdCdQBCxqZu+ugqnWoW9swN5ehp2NotgIo6bGQWGtaS8+vQ5V9a0u5S+1gfABEilAqdUgm98HDwUQkDT8JXoPPq+BoM5kCYmFT9jryn1+hkAt7heBx8dhbSwACmTAUwTgdlZ/CVKJaLnI1GD8TikZiPSR8Gxib8chH95mZTxgwWHwH7+gFMswqcokIRbjMO2HDCnZ1VvArpjEmnKZc8+cZJJYGsLsMiZ8AgwEqaY6Mb6RQR33JFhGECzCRyfAFXNu9v+RVNRZWIMuDJNuYMAaDycUFGhCOgtuAtFVDA83G5A8TrFDw+F5QMAxAKJJxz2xnW3RPJGbm+rCyjotZetH4DGzaSSeDA3h4Zl4R0JOEZWTpIzF4n/m995bNdqZwB6m0gFft3Ak6vz+KYWwFsGlqIxXItEcDt1ARMEtKdVgZb+fwA0G2C2hXM0ZTZNRcSf0b1pmXi7uYnjI+Lfanm5fRQsK8BIxKcrK7i/uIgP+Tw+FlREqHN5fx/vyU4uHBE6UO4gDWqk/JFaLuMxcXeFk6TuJ90V0HOk1in7J8AAjmgkPfjU+isAAAAASUVORK5CYII=);
- background-repeat:no-repeat;
- background-size:contain
-}
-
-.ipfs-h {
- background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAbRJREFUeNqMUk1Lw0AQnf0woK0ttVqp0hwqVCl+UBERT94F7x78Cf4Uz179DT14F8WbYHtRkBYRLNqDtdaPZLObuLs1NGlXcWDJZGbey+x7QUEQgIqT07PL5WKhHL5H46J+22q22vsWpbWwdnR4oJ80LNiz2czGUjENhvj4ctIE4Wrj8XmPUlKL9nCYcOFzE9j1OKSTCdjdrtiLdr7KhVgzEvwW6krC92E6k4Kd9bJt57JV5vFK2KfRQRV+RAMkzxglYI1RaDy2dW1rpWRjQo5VGicYIorWVooFvQVCCAjG8Omw1MgG8AM0uSBUDSnCfk/IGCHwf3DCD/7UhOLBrFkDuep/hDUSSCv1iYo4rIfqGwmUSNJjfYbBcQKhZw0aBMA4B48LwBhBt/cON80HmM9NQ6fXg/Wlku4TwmNWDzaQqzHG+0PSKod5cH5Vh2RiAhYKc8DlV1UPSyuFMGygVlMg1/P6BC6DqXQK8jNZDXAYA1f21V34wMXYFaiyVw0rJyzLgs3VMkxOjGtix/V0XWChZ0cI2i/dzvXdfTd0Qf91BMPrhyNzgKfOmxaWypqaDXHfAgwAtCL8XOfF47gAAAAASUVORK5CYII=);
- background-repeat:no-repeat;
- background-size:contain
-}
-
-.ipfs-hpp {
- background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAehJREFUeNqEUk1v00AUHK/XKf1yZdESVRBXjRSRFqMQVBA5Ic5I3DnwE/gpnLnyG3LgXglx4UDDLZS0RWkDLiRxSusk9u6GXSembmLgWZbX7+2bnZl92mg0goo3b3ffO/ncdvyfjHef6q2Dlvs8Q2ktzr16+SL60jhhZ69bO8X8ClLC7w9XdKJVG8fuM0r1WrJG4gXjgqU1D0MGc2kBTytl+7a9XmWcl1IB/hZKEhccq5aJJ/e3bTu7Wg1CVo7rNLlRhUh4oMnXoDoyhoHGyWmUe+QUbELIa7W8CjAFlMzdzeckCwFN06ATAn8QmDMMMGlMuwWucpoCHNe4jBkAMenjYvRPTyi53JvuwX8AplleAeBcRFrH6rXIxLim9I/pi3QA1RhKaYxdjkN8IwalCMIwWs9ljMkh0wzk+9M7w179C3LZNXxve2h+c3Hu91HeKmD/6zHOLnw83ilB1/V0CeqU3Q81LC/O41b2Btx2N2JVP2riR8eTUxmi0TzBwrKZMsqMoz8MsDh/DWuWhUBKURLKxQIeOMWoptYPnS1c+INZBkwISomOSsmBZS7B+3WOzZvrKGzkMAiGqNy7g+LmRkRfekBnANy2163PZXrSbrQ6vch19Xz8fPDHyL39QzkHBKedXjfu+y3AAGU37INBJto1AAAAAElFTkSuQmCC);
- background-repeat:no-repeat;
- background-size:contain
-}
-
-.ipfs-html {
- background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAmBJREFUeNqEUktPE1EU/mY605a+hhZTBNKRDApNrWIRA4nEBUZdmCgLNi4MK5f+FNdu3bFv1J1EXODCR1JJSMTwpqUP6NiCpe10Zjz3hj5Mm3iSybl37jnf+c53jmDbNpi9eb+6Ftcisea909bWNzNb6dwzSXKkhIt/r14+515qBqmDA8HpqKagh53XaopblpIbe+knDpFAhPab2Dw0TKvRK7lmNODzePBgZlK9oUWSpmVNdpIU8T+jaMsyMaD4MDcZVa+NhJMN00w0n6V2nN3yQgdHWZag+LzYPTomIAtT0THVtPGanmb/BbjwLFkvn2IttYGYplKyDzsHh7gdmyAWfh5zVq0Guhg4RAHFUhmfvq3j134aXo8bd+ITnMFOOovU5jbGRoZwNxFn1cxuAIcDW/sZDjA/c4u+BNxOJyxqaenpI3z88gMfPn9Hv98HQZS6RazW6kjExvFi8TGdDSy/W0Emf4LS6R8sv11BmfzSwkPcm74Jo9Ei0GZgmkw8QCOao8OXcaz/5vSZnPdnp3ApqBBLkWJE0Ci7ASzbIhCLLQ1E0iOkBDh9NpUgiUejo8oNuJwyn0YPABtn51UYFFivG3yBGCNZkuDtc/MW+ZQI3OrYpBaARCKufk3B5XIiWyhiL5ODp8+FfFHH+KiKSqWKUL8fC/NznGlPBmz+24dZjKnD0CJDcMoyW0SqXuMtHBFw7rhIAD1ErNUNafxKBNevapwu65NpEQ4FqXIA+RMd6VwBP3cPSERb6gLIFIq61+UqGWaFdcrVt/lmAuWjAi2aiMFwmOYuIJ/N6M28vwIMAMoNDyg4rcU9AAAAAElFTkSuQmCC);
- background-repeat:no-repeat;
- background-size:contain
-}
-
-.ipfs-ics {
- background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAhRJREFUeNqEUkFPE0EU/mZ2dra7bLNpi2AxQFKalkJrohICiYkXPagXrx78Df4K48GDBzmQePLMhUODNxQ5ciEkJVqDtJGmMWrCATRbd2ecoS5u3aovmezsvu9973vfPiKlhI4XL7c2r5YL81LIELEghLA3u/udxmHnPmfGW/Wuv+LpwwdneRYBx7PeWK0wOYYhcXxyckGV1fdbnbuMsXcklqPRJQxFMKz4RxDCtVO4s3xlRjWoB0FYjlQPEEBieChwKCRGMx5uLtaKs1P5ei8IKlGa/YkXMXYtlTEDlsnw/mMXhBJcqxSK6vlcpa4PEpCooUyIqs5M6hG1o2CUwqA091cFcYLf/sjzcX75EiQIojI9779CTYR4jwTBf+r7GAwh0AxCiL6JMT/04vQ79u8aI2O/7Jzg69o6Go8ewycUahtBpADhHKLnK/eVbkMdtROWIv80NQ2sPhncA9Htwn+9hZG0rY6DzFwJl+7dhs0ZstUy8rduwPS/wd/ehmi3kwq4zTHiWUgXp+EuL8FvNvFl5Rn4xAS86iyI2kY3n0Mv48ByrOQmancdi8I0Kcj3U5iuA29xAelKCUHrEIayzltagG2E4IwkFaQgSC6lYI09iN0d8It5uNV5nG5sgJdKYC0G8WoTOZvBISFNEBxnsuzD3GX4vfDsszzqAu0jkJQDedCGbB6AWg54pYbPo+NGVPdTgAEAqQq70PytIL0AAAAASUVORK5CYII=);
- background-repeat:no-repeat;
- background-size:contain
-}
-
-.ipfs-iso {
- background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAjlJREFUeNp0kstrU0EUxr/k5qbJzdPYpGkpsUJoA2q1oLjTdiGiIC5cuXHlxv9BEOrStTvBnQvRrSAIsejCrlqpsURq2hCJNQ+TNLm5uc/x3MmzJh34mDNnvvnNzOE4GGOwx8+t9XQkfn0VE0Y5/7Z+kHm+dvOhtd3P9c/xwNZh7nWaMYtNUmX/Fct/vlN7/8J5aRRgyzm8xzpRDjGE2aVH4VTqdnoUYg/XkEhmy+Cx3DhA5tMzdFolvg5Mx3Fx9SmH0JIg79Zo3j4GADMIokJTKtjbfAKXU4Y/2NvSfyH75TFOxa9Cmr0XnlPFl5ReOQ6wNMDsoFX6AElqQlNV1KsOuNwS/AGFjEUIDhmn5+/DMM16/9igBowAzFKIswPJr6MjlxFP3sV04gaP7RzMPe6xvWM1gNUBM2UKYlBau3QghGphg29J3gDlLLilWNdD3gkvIIDRhD9yGe2mCV0V4HFXuCxT5Dlv8Dz3sIkAs03FalDxBMQSt9BRBMhNncuO7dyU28c9tnf8C/Q0ZtR4GImeQSj8APLRH772BWcgiFODffCv/t8H9tO0v3RjV7VqkeeXLlzDfvYjj88uXhl4JwIsrYxmLY/M1gYclIvGE9jZfNPrSCD3/QgLyeWTADV6wW9AryIcCkB0u1Aq/oCPumlufoF72vIheaLDr4wCLIOqrYnULA14PSoqpSJEAUilZrD77Sv3LK+cI0+Be8cAbbmAOrob0agtD491LYfkoqvnyZLsWRkA/gkwABL4S3L78XYyAAAAAElFTkSuQmCC);
- background-repeat:no-repeat;
- background-size:contain
-}
-
-.ipfs-java {
- background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAjxJREFUeNp8U01v00AUnNiOEyepQyhQobRBSlVIoRCBEPTAjQsSEneE+An8FM5cuXLNoQduIAE3qopKNJAIIppA2jrOR93aa6/N8yZuUxyxkrXr3ffmzczbTQRBgHC83nj3ca28dD36nx6fvnzrNNrdp4oibyUmey9fPBezEgWVFuYLdyvlPGaMY4fl1aRS+9pqP5ElAkmcnknRwuO+Nyt5u/ETYfyj9WrpZnmpxn2/Ok1Swn/GvtnH5k4TLue4kNfxoFoprRQv1TzOb8cAIu3+ZD7oD/Hm7XuxzqRUNDtdkuLiTmW5tFxceBXlnXgQTAORSMt2oGezUJJJrK9dFWdEH7Ik4dB29LiESeUEJXd7/dAT3L+1ivlCHr8NEzutXTBvbJPPSdO/AH5wysChwM/1HzCGlmAzOrKxu2eCud6Z2Jke2MwThpUXL6Nn2ZAVFTlNw70bK0iRnGAq9qwHtOmTRpsx1NsHyKRVnNPnoMoK9kc2BjbD4vk5JGV5NkBoEPM4FFnCteJFWOS4ntHEfphQyKaFTWFLw704AJ26ZFx/ZEEi3YyY0O1Dmr4EKTUHA8hUnS6siI0DEHLYog+b28RCRuNXR/iQUpPUEQ+NVht6Lodnjx+GXYgDSFRnq97Ed2pXSlXhUSeGhxYc5sKlNXM5DGLR2TMwfZVPAIi+otGNWy1fEZUKeo4qc4ysI+F8VksLIJfYcD9QYgB/DNPMptWBlsnBIS86xmDMTBo/PWd0LB6VZfdEbJT3V4ABAA5HIzlv9dtdAAAAAElFTkSuQmCC);
- background-repeat:no-repeat;
- background-size:contain
-}
-
-.ipfs-jpeg,
-.ipfs-jpg {
- background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAmlJREFUeNpsU8luUlEY/s4dmMpkWxRopGJNNbiwhk1tItbGtXHr0hcwmvgOdWld6Bu4coXumtREE3ZKu8FgOlC1kIoXtC3jPfdc/8PUIpzkBM7wf+f/hsts24YczuerGUc0moBlYTAYA+i8sbdXtAzjITRtq39kr73s/Gr9DTUYPOeamwvYnHdrdR0SnDebuCbswJGqpX+Uf92Hqm7hzFAG/4TgNr1uCwEJ0trcBC8U0Kb1/PQkHt9JxSLnL6TB+Y2zAIMOJBGLXmtsbEAYBsx8HnqCGKVScAX8uHf5EpqmGXv18VO6VDEe0PXsKABN8+AAgiabmYFNNJTDQ2RUFc8+Z9G0OPR4PKYwvKari0MAgiY/OQGCAajhMNR4nDZMaInrKBGl70SPMScck1NQG3X/CAWLE3/dAWV5hRRVIJxOWNksrP19sFgMqqAebUGYHMI6teq0A9oTVAhqu2sfbYYjsL7lCZ3683gA70T3TK7/B4BNoO020GwB9TpwfAz8LgMtWn/NkV8EHgoB81c7nYwCyBZlEVkHcqMTKFnkmehJTOPvEfCnKi0fAyADJKfXC/h83TaZTJjaa5lANLpOFqAXtlEAorAwO9u5syT5UxLfU0e3o1FMu1x4u7ODYq02BKAMAVSrSNLrK1MhLPj8mNF0vFm+C1ZvwKBwXXE4AGn1WAASazESwUW3BzUSMeJ2o1Aq4sPurvQYSRLwlhRR6mSaYyi0WlpAJrFRx3ouh5/lMt5lv8BLwXp0M4lSpYL17e2uK5wP6lj/c2ZPn2RI+YT8fDvqoyegVLyfG5kBKaQQOfvF2pLc+ifAABiQH3PEc1i/AAAAAElFTkSuQmCC);
- background-repeat:no-repeat;
- background-size:contain
-}
-
-.ipfs-js {
- background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyJpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMy1jMDExIDY2LjE0NTY2MSwgMjAxMi8wMi8wNi0xNDo1NjoyNyAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENTNiAoV2luZG93cykiIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6RUQ5ODY5Q0NGMTE4MTFFMTlDRjlDN0VBQTY3QTk0MTEiIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6RUQ5ODY5Q0RGMTE4MTFFMTlDRjlDN0VBQTY3QTk0MTEiPiA8eG1wTU06RGVyaXZlZEZyb20gc3RSZWY6aW5zdGFuY2VJRD0ieG1wLmlpZDpFRDk4NjlDQUYxMTgxMUUxOUNGOUM3RUFBNjdBOTQxMSIgc3RSZWY6ZG9jdW1lbnRJRD0ieG1wLmRpZDpFRDk4NjlDQkYxMTgxMUUxOUNGOUM3RUFBNjdBOTQxMSIvPiA8L3JkZjpEZXNjcmlwdGlvbj4gPC9yZGY6UkRGPiA8L3g6eG1wbWV0YT4gPD94cGFja2V0IGVuZD0iciI/PoT8zQ8AAAJdSURBVHjadFNbTxNREP52t7S0bktbKFAvTUVaw60YqkExUTD6oD74qC/yD/wp/gh885XEEI0RAyYQUiMpIBGMkYR6o23abi+73e2uc04v1LROMtnZPTPffvPNHMGyLDB7sbJ2ciUSli3U35smkK9t7x9v7n2dD/g8KUkUwWqeP3vKz23NxJGzgwOx0RC6mSgIo+WKuvP56MeUzy2nJEk8PWsGJVVTuhWbpgmHw47FB7d98Wg4mVWK52o1sxOg3Va3PmFp+Q2PdUquaFUM9/vw+O6cP3bxwm46Xwh1ALR3/vL1e+hGjcc9koScUsTSq3coVDQsXJ3wzo5HEs3clgZNMTVdx1T0Ep7cn6//QRQwMhzA6uZHLD5cIFEFSKIU+G8LK+tb0KsGZKcTJoEyP08AbpcLy6sbPKdQrigdAGaDwWxsDH1uGbliCYIgcM8WFPg8Mq5Pjzdyu4jYbCE44EepXMHuwXe+A8x3KKYxYsjvbUzmlPGpBmYdgI1oYjSMbL4Ao1YXMkcM2Dd2xnbAamPQAqg1GORLZdycmYTdJqFKk2DPR3fmwI4zBDrg9RADqxPAbPBif2WTSB584/3/TGegEOit+DRcvQ4OZJi1LgwIQKVCg2i6nb1I7H3Br3QWqT9pBAP9uDY5xjdSM3RqxeoUkfVnEOW8UkLykERTNXjkM7h3Iw6NNvHw6JjuhAhVrba0+QeALozcI9nQR0VvNxJc/ZmxCNGvIBQcpDG6udA22kyW29HC72wu8yG579ZoiSYuR/ly2+y9CA4NceWLmo717T1i5ULqJNtapL8CDACskxPFZRxLwQAAAABJRU5ErkJggg==);
- background-repeat:no-repeat;
- background-size:contain
-}
-
-.ipfs-key {
- background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAlZJREFUeNpsU11PE0EUPbM7u/2AtJUWU6qiiSYYo5EmmPDCD9AH46sx8cEnja/+CB989z+Y+MKPgMiDsYQACcbaWBBogYD92t2Zud7ZlQZsbzKZ3bl3zj3n3IwgItjYeDO3MlWme0bjUth8e8/fO2tHzx3XqUEk50uft+Ndnhdmc3SlfNPkVZT8Cy600DoIISvVfKYtlvfX1p66XmoIYsMZdjJQWvEFbbsC/S5g2QhSkKUK7rx6OzvzqLpsovAhaAxA3DUBQn2TUFsl7KwTfm4Z9DoO5LW7uPXi9Wxpfn7ZKF09vyPxX2iWcNRkKGZz0mQWKoNs8AVB6x1yRY2pYnc2LLofuXTxMgAlmlXIfngCxNxEzM+DPv6NQa2BygLgZyX6JT83ngHTN5GAL0WSoUQkSQnXkyBh/k0GegTAaldM20sTKvet+yyhIZApECamL0jUSe3oFChx3TopM4TeEQP2gc6BgGIwb4KGNXRhCkMGxgg2kJeybRiZM45D8W61qEAknSmpHStBhywu0nFVupSCTAcM4ECwqapv+NQ6LS9JGALoMIIoPYDjZiEL1xHtbyO39AQUDaA7R1AH23DSeSA4hv5RG/VAhxomPYP8sw9A4TaC9iHkjUWmrtGvbyC18BLe3GP0m3WW4I5hEBEnPIStXzyuFIxb4EkMEJ79Qa/xHbKxCdM7xeCwzUZOjgEwnuzt7qLz6T3cySmQP43uzjeIiTJM6io6W19B/NLCKMVGCzkCoLR/0lrfOI2fNy/huKC1FTsK/rbGNeMRC8dHpHByfu+vAAMAL/0jvAVZQl0AAAAASUVORK5CYII=);
- background-repeat:no-repeat;
- background-size:contain
-}
-
-.ipfs-less {
- background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyJpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMy1jMDExIDY2LjE0NTY2MSwgMjAxMi8wMi8wNi0xNDo1NjoyNyAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENTNiAoV2luZG93cykiIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6RjZERjZENTJGMTE4MTFFMUIwOEVERjQ5MTZEMkVBREUiIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6RjZERjZENTNGMTE4MTFFMUIwOEVERjQ5MTZEMkVBREUiPiA8eG1wTU06RGVyaXZlZEZyb20gc3RSZWY6aW5zdGFuY2VJRD0ieG1wLmlpZDpGNkRGNkQ1MEYxMTgxMUUxQjA4RURGNDkxNkQyRUFERSIgc3RSZWY6ZG9jdW1lbnRJRD0ieG1wLmRpZDpGNkRGNkQ1MUYxMTgxMUUxQjA4RURGNDkxNkQyRUFERSIvPiA8L3JkZjpEZXNjcmlwdGlvbj4gPC9yZGY6UkRGPiA8L3g6eG1wbWV0YT4gPD94cGFja2V0IGVuZD0iciI/Pl1w97IAAAJhSURBVHjahJNLbxJRFMf/wPAIMIxMkUI7tS0VYqlGDLGhjdKkqyZ24cJFN925de+XcONHaHRj4k7TND6SGo1VWwmp2kSLhlqMDbQ87gzPYcY7k4GgoJ6bmdw598zvnvM/95pUVYVma+svcovx8yMnFZHAMJPJBJfDzq5vpX6+/vD5qo/z7DOMBdo/d26t6jFMJ3iY51jBz4M+LP6wxEw40Gy23qYzB3HO7fpmpZCOmfEfa7Xb4NxOrC4lvbPToe2yKE3K1PdPwNOtHdx79ESfq4qKkijB5/XgevIyHxEC24USmewDqD2ABxubaLRkfW6zMqjWGlh7/ByyAtxYnOPnL0Q2+gGGmKRaw8zUBJaTiS5QOO1FJnuIAM8hciaIWHgi8NcSNt+loVDY8JBXh2ojJAR1HbTSNFMUpV8Dxcjg0nSYBrtBxdLbqI1iheCUh9XXNGurAwCdEkb9QyBSFam9TDfoPZ1LUg1BH28IiwEARTVAQOzcFKRaHZpLoa9avY6L1Gfs0c32t4PU6W2lWsV8LAorw0Cs1nXftYWE3qZGqwWHzYp2zzlgetuolVFvtiDLbRRKFTAWCxx2G/KlMtXFhWPqOzsWHJwBx7rxKv2R7mwFz3lw9/5DLC/M4Us2RwV0g3U58XJnF7dvrsBOoX0Abbej/DFKRMKI30fTVGC32WA2m5H9cQQvhYi0vE/7Wdgczn6ARA9QPBrBszcp/XvpyqxebzQ0Tlsq6llxLhe9bD4cFMr9XdjLHpLv+SLGBYHAYiVu1kNOpAaRTWbCejgiw0zGhFGSK1aw+zXbvfK/BBgAPwADAs5GpGsAAAAASUVORK5CYII=);
- background-repeat:no-repeat;
- background-size:contain
-}
-
-.ipfs-logo {
- background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 553 235.3'%3E%3Cdefs%3E%3C/defs%3E%3Cpath fill='%23ffffff' d='M239 63h17.8v105H239V63zm35.6 0h36.3c7.9 0 14.5.9 19.6 2.6s9.2 4.1 12.1 7.1a24.45 24.45 0 0 1 6.2 10.2 40.75 40.75 0 0 1 1.8 12.1 45.69 45.69 0 0 1-1.8 12.9 26.58 26.58 0 0 1-6.2 10.8 30.59 30.59 0 0 1-12.1 7.3c-5.1 1.8-11.5 2.7-19.3 2.7h-19.1V168h-17.5V63zm36.2 51a38.37 38.37 0 0 0 11.1-1.3 16.3 16.3 0 0 0 6.8-3.7 13.34 13.34 0 0 0 3.5-5.8 29.75 29.75 0 0 0 1-7.6 25.68 25.68 0 0 0-1-7.7 12 12 0 0 0-3.6-5.5 17.15 17.15 0 0 0-6.9-3.4 41.58 41.58 0 0 0-10.9-1.2h-18.5V114h18.5zm119.9-51v15.3h-49.2V108h46.3v15.4h-46.3V168h-17.8V63h67zm26.2 72.9c.8 6.9 3.3 11.9 7.4 15s10.4 4.7 18.6 4.7a32.61 32.61 0 0 0 10.1-1.3 20.52 20.52 0 0 0 6.6-3.5 12 12 0 0 0 3.5-5.2 19.08 19.08 0 0 0 1-6.4 16.14 16.14 0 0 0-.7-4.9 12.87 12.87 0 0 0-2.6-4.5 16.59 16.59 0 0 0-5.1-3.6 35 35 0 0 0-8.2-2.4l-13.4-2.5a89.76 89.76 0 0 1-14.1-3.7 33.51 33.51 0 0 1-10.4-5.8 22.28 22.28 0 0 1-6.3-8.8 34.1 34.1 0 0 1-2.1-12.7 26 26 0 0 1 11.3-22.4 36.35 36.35 0 0 1 12.6-5.6 65.89 65.89 0 0 1 15.8-1.8c7.2 0 13.3.8 18.2 2.5a34.46 34.46 0 0 1 11.9 6.5 28.21 28.21 0 0 1 6.9 9.3 42.1 42.1 0 0 1 3.2 11l-16.8 2.6c-1.4-5.9-3.7-10.2-7.1-13.1s-8.7-4.3-16.1-4.3a43.9 43.9 0 0 0-10.5 1.1 19.47 19.47 0 0 0-6.8 3.1 11.63 11.63 0 0 0-3.7 4.6 14.08 14.08 0 0 0-1.1 5.4c0 4.6 1.2 8 3.7 10.3s6.9 4 13.2 5.3l14.5 2.8c11.1 2.1 19.2 5.6 24.4 10.5s7.8 12.1 7.8 21.4a31.37 31.37 0 0 1-2.4 12.3 25.27 25.27 0 0 1-7.4 9.8 36.58 36.58 0 0 1-12.4 6.6 56 56 0 0 1-17.3 2.4c-13.4 0-24-2.8-31.6-8.5s-11.9-14.4-12.6-26.2h18z'/%3E%3Cpath fill='%23469ea2' d='M30.3 164l84 48.5 84-48.5V67l-84-48.5-84 48.5v97z'/%3E%3Cpath fill='%236acad1' d='M105.7 30.1l-61 35.2a18.19 18.19 0 0 1 0 3.3l60.9 35.2a14.55 14.55 0 0 1 17.3 0l60.9-35.2a18.19 18.19 0 0 1 0-3.3L123 30.1a14.55 14.55 0 0 1-17.3 0zm84 48.2l-61 35.6a14.73 14.73 0 0 1-8.6 15l.1 70a15.57 15.57 0 0 1 2.8 1.6l60.9-35.2a14.73 14.73 0 0 1 8.6-15V79.9a20 20 0 0 1-2.8-1.6zm-150.8.4a15.57 15.57 0 0 1-2.8 1.6v70.4a14.38 14.38 0 0 1 8.6 15l60.9 35.2a15.57 15.57 0 0 1 2.8-1.6v-70.4a14.38 14.38 0 0 1-8.6-15L38.9 78.7z'/%3E%3Cpath fill='%23469ea2' d='M114.3 29l75.1 43.4v86.7l-75.1 43.4-75.1-43.4V72.3L114.3 29m0-10.3l-84 48.5v97l84 48.5 84-48.5v-97l-84-48.5z'/%3E%3Cpath fill='%23469ea2' d='M114.9 132h-1.2A15.66 15.66 0 0 1 98 116.3v-1.2a15.66 15.66 0 0 1 15.7-15.7h1.2a15.66 15.66 0 0 1 15.7 15.7v1.2a15.66 15.66 0 0 1-15.7 15.7zm0 64.5h-1.2a15.65 15.65 0 0 0-13.7 8l14.3 8.2 14.3-8.2a15.65 15.65 0 0 0-13.7-8zm83.5-48.5h-.6a15.66 15.66 0 0 0-15.7 15.7v1.2a15.13 15.13 0 0 0 2 7.6l14.3-8.3V148zm-14.3-89a15.4 15.4 0 0 0-2 7.6v1.2a15.66 15.66 0 0 0 15.7 15.7h.6V67.2L184.1 59zm-69.8-40.3L100 26.9a15.73 15.73 0 0 0 13.7 8.1h1.2a15.65 15.65 0 0 0 13.7-8l-14.3-8.3zM44.6 58.9l-14.3 8.3v16.3h.6a15.66 15.66 0 0 0 15.7-15.7v-1.2a16.63 16.63 0 0 0-2-7.7zM30.9 148h-.6v16.2l14.3 8.3a15.4 15.4 0 0 0 2-7.6v-1.2A15.66 15.66 0 0 0 30.9 148z'/%3E%3Cpath fill='%23083b54' fill-opacity='0.15' d='M114.3 213.2v-97.1l-84-48.5v97.1z'/%3E%3Cpath fill='%23083b54' fill-opacity='0.05' d='M198.4 163.8v-97l-84 48.5v97.1z'/%3E%3C/svg%3E%0A");
- background-repeat:no-repeat;
- background-size:contain
-}
-
-.ipfs-mid {
- background-repeat:no-repeat;
- background-size:contain
-}
-
-.ipfs-mkv {
- background-image:url("data:image/svg+xml;charset=utf8,%3Csvg id='Layer_2' xmlns='http://www.w3.org/2000/svg' viewBox='0 0 72 100'%3E%3Cstyle/%3E%3ClinearGradient id='SVGID_1_' gradientUnits='userSpaceOnUse' x1='36.2' y1='101' x2='36.2' y2='3.005' gradientTransform='matrix(1 0 0 -1 0 102)'%3E%3Cstop offset='0' stop-color='%23e2cde4'/%3E%3Cstop offset='.17' stop-color='%23e0cae2'/%3E%3Cstop offset='.313' stop-color='%23dbc0dd'/%3E%3Cstop offset='.447' stop-color='%23d2b1d4'/%3E%3Cstop offset='.575' stop-color='%23c79dc7'/%3E%3Cstop offset='.698' stop-color='%23ba84b9'/%3E%3Cstop offset='.819' stop-color='%23ab68a9'/%3E%3Cstop offset='.934' stop-color='%239c4598'/%3E%3Cstop offset='1' stop-color='%23932a8e'/%3E%3C/linearGradient%3E%3Cpath d='M45.2 1l27 26.7V99H.2V1h45z' fill='url(%23SVGID_1_)'/%3E%3Cpath d='M45.2 1l27 26.7V99H.2V1h45z' fill-opacity='0' stroke='%23882383' stroke-width='2'/%3E%3Cpath d='M7.5 91.1V71.2h6.1l3.6 13.5 3.6-13.5h6.1V91h-3.8V75.4l-4 15.6h-3.9l-4-15.6V91H7.5zm23.5 0V71.2h4V80l8.2-8.8h5.4L41.1 79l8 12.1h-5.2l-5.5-9.3-3.4 3.3v6h-4zm25.2 0L49 71.3h4.4L58.5 86l4.9-14.7h4.3l-7.2 19.8h-4.3z' fill='%23fff'/%3E%3ClinearGradient id='SVGID_2_' gradientUnits='userSpaceOnUse' x1='18.2' y1='50.023' x2='18.2' y2='50.023' gradientTransform='matrix(1 0 0 -1 0 102)'%3E%3Cstop offset='.005' stop-color='%23963491'/%3E%3Cstop offset='1' stop-color='%2370136b'/%3E%3C/linearGradient%3E%3ClinearGradient id='SVGID_3_' gradientUnits='userSpaceOnUse' x1='11.511' y1='51.716' x2='65.211' y2='51.716' gradientTransform='matrix(1 0 0 -1 0 102)'%3E%3Cstop offset='.005' stop-color='%23963491'/%3E%3Cstop offset='1' stop-color='%2370136b'/%3E%3C/linearGradient%3E%3Cpath d='M64.3 55.5c-1.7-.2-3.4-.3-5.1-.3-7.3-.1-13.3 1.6-18.8 3.7S29.6 63.6 23.3 64c-3.4.2-7.3-.6-8.5-2.4-.8-1.3-.8-3.5-1-5.7-.6-5.7-1.6-11.7-2.4-17.3.8-.9 2.1-1.3 3.4-1.7.4 1.1.2 2.7.6 3.8 7.1.7 13.6-.4 20-1.5 6.3-1.1 12.4-2.2 19.4-2.6 3.4-.2 6.9-.2 10.3 0m-9.9 15.3c.5-.2 1.1-.3 1.9-.2.2-3.7.3-7.3.3-11.2-6.2.2-11.9.9-17 2.2.2 4 .4 7.8.3 12 4-1.1 7.7-2.5 12.6-2.7m2-12.1h1.1c.4-.4.2-1.2.2-1.9-1.5-.6-1.8 1-1.3 1.9zm3.9-.2h1.5V38h-1.3c0 .7-.4.9-.2 1.7zm4 0c.5-.1.8 0 1.1.2.4-.3.2-1.2.2-1.9h-1.3v1.7zm-11.5.3h.9c.4-.3.2-1.2.2-1.9-1.4-.4-1.6 1.2-1.1 1.9zm-4 .4c.7.2.8-.3 1.5-.2v-1.7c-1.5-.4-1.7.6-1.5 1.9zm-3.6-1.1c0 .6-.1 1.4.2 1.7.5.1.5-.4 1.1-.2-.2-.6.5-2-.4-1.9-.1.4-.8.1-.9.4zm-31.5.8c.4-.1 1.1.6 1.3 0-.5 0-.1-.8-.2-1.1-.7.2-1.3.3-1.1 1.1zm28.3-.4c-.3.3.2 1.1 0 1.9.6.2.6-.3 1.1-.2-.2-.6.5-2-.4-1.9-.1.3-.4.2-.7.2zm-3.5 2.8c.5-.1.9-.2 1.3-.4.2-.8-.4-.9-.2-1.7h-.9c-.3.3-.1 1.3-.2 2.1zm26.9-1.8c-2.1-.1-3.3-.2-5.5-.2-.5 3.4 0 7.8-.5 11.2 2.4 0 3.6.1 5.8.3M33.4 41.6c.5.2.1 1.2.2 1.7.5-.1 1.1-.2 1.5-.4.6-1.9-.9-2.4-1.7-1.3zm-4.7.6v1.9c.9.2 1.2-.2 1.9-.2-.1-.7.2-1.7-.2-2.1-.5.2-1.3.1-1.7.4zm-5.3.6c.3.5 0 1.6.4 2.1.7.1.8-.4 1.5-.2-.1-.7-.3-1.2-.2-2.1-.8-.2-.9.3-1.7.2zm-7.5 2H17c.2-.9-.4-1.2-.2-2.1-.4.1-1.2-.3-1.3.2.6.2-.1 1.7.4 1.9zm3.4 1c.1 4.1.9 9.3 1.4 13.7 8 .1 13.1-2.7 19.2-4.5-.5-3.9.1-8.7-.7-12.2-6.2 1.6-12.1 3.2-19.9 3zm.5-.8h1.1c.4-.5-.2-1.2 0-2.1h-1.5c.1.7.1 1.6.4 2.1zm-5.4 7.8c.2 0 .3.2.4.4-.4-.7-.7.5-.2.6.1-.2 0-.4.2-.4.3.5-.8.7-.2.8.7-.5 1.3-1.2 2.4-1.5-.1 1.5.4 2.4.4 3.8-.7.5-1.7.7-1.9 1.7 1.2.7 2.5 1.2 4.2 1.3-.7-4.9-1.1-8.8-1.6-13.7-2.2.3-4-.8-5.1-.9.9.8.6 2.5.8 3.6 0-.2 0-.4.2-.4-.1.7.1 1.7-.2 2.1.7.3.5-.2.4.9m44.6 3.2h1.1c.3-.3.2-1.1.2-1.7h-1.3v1.7zm-4-1.4v1.3c.4.4.7-.2 1.5 0v-1.5c-.6 0-1.2 0-1.5.2zm7.6 1.4h1.3v-1.5h-1.3c.1.5 0 1 0 1.5zm-11-1v1.3h1.1c.3-.3.4-1.7-.2-1.7-.1.4-.8.1-.9.4zm-3.6.4c.1.6-.3 1.7.4 1.7 0-.3.5-.2.9-.2-.2-.5.4-1.8-.4-1.7-.1.3-.6.2-.9.2zm-3.4 1v1.5c.7.2.6-.4 1.3-.2-.2-.5.4-1.8-.4-1.7-.1.3-.8.2-.9.4zM15 57c.7-.5 1.3-1.7.2-2.3-.7.4-.8 1.6-.2 2.3zm26.1-1.3c-.1.7.4.8.2 1.5.9 0 1.2-.6 1.1-1.7-.4-.5-.8.1-1.3.2zm-3 2.7c1 0 1.2-.8 1.1-1.9h-.9c-.3.4-.1 1.3-.2 1.9zm-3.6-.4v1.7c.6-.1 1.3-.2 1.5-.8-.6 0 .3-1.6-.6-1.3 0 .4-.7.1-.9.4zM16 60.8c-.4-.7-.2-2-1.3-1.9.2.7.2 2.7 1.3 1.9zm13.8-.9c.5 0 .1.9.2 1.3.8.1 1.2-.2 1.7-.4v-1.7c-.9-.1-1.6.1-1.9.8zm-4.7.6c0 .8-.1 1.7.4 1.9 0-.5.8-.1 1.1-.2.3-.3-.2-1.1 0-1.9-.7-.2-1 .1-1.5.2zM19 62.3v-1.7c-.5 0-.6-.4-1.3-.2-.1 1.1 0 2.1 1.3 1.9zm2.5.2h1.3c.2-.9-.3-1.1-.2-1.9h-1.3c-.1.9.2 1.2.2 1.9z' fill='url(%23SVGID_3_)'/%3E%3ClinearGradient id='SVGID_4_' gradientUnits='userSpaceOnUse' x1='45.269' y1='74.206' x2='58.769' y2='87.706' gradientTransform='matrix(1 0 0 -1 0 102)'%3E%3Cstop offset='0' stop-color='%23f9eff6'/%3E%3Cstop offset='.378' stop-color='%23f8edf5'/%3E%3Cstop offset='.515' stop-color='%23f3e6f1'/%3E%3Cstop offset='.612' stop-color='%23ecdbeb'/%3E%3Cstop offset='.69' stop-color='%23e3cce2'/%3E%3Cstop offset='.757' stop-color='%23d7b8d7'/%3E%3Cstop offset='.817' stop-color='%23caa1c9'/%3E%3Cstop offset='.871' stop-color='%23bc88bb'/%3E%3Cstop offset='.921' stop-color='%23ae6cab'/%3E%3Cstop offset='.965' stop-color='%239f4d9b'/%3E%3Cstop offset='1' stop-color='%23932a8e'/%3E%3C/linearGradient%3E%3Cpath d='M45.2 1l27 26.7h-27V1z' fill='url(%23SVGID_4_)'/%3E%3Cpath d='M45.2 1l27 26.7h-27V1z' fill-opacity='0' stroke='%23882383' stroke-width='2' stroke-linejoin='bevel'/%3E%3C/svg%3E");
- background-repeat:no-repeat;
- background-size:contain
-}
-
-.ipfs-mov {
- background-image:url("data:image/svg+xml;charset=utf8,%3Csvg id='Layer_2' xmlns='http://www.w3.org/2000/svg' viewBox='0 0 72 100'%3E%3Cstyle/%3E%3ClinearGradient id='SVGID_1_' gradientUnits='userSpaceOnUse' x1='36.2' y1='101' x2='36.2' y2='3.005' gradientTransform='matrix(1 0 0 -1 0 102)'%3E%3Cstop offset='0' stop-color='%23e2cde4'/%3E%3Cstop offset='.17' stop-color='%23e0cae2'/%3E%3Cstop offset='.313' stop-color='%23dbc0dd'/%3E%3Cstop offset='.447' stop-color='%23d2b1d4'/%3E%3Cstop offset='.575' stop-color='%23c79dc7'/%3E%3Cstop offset='.698' stop-color='%23ba84b9'/%3E%3Cstop offset='.819' stop-color='%23ab68a9'/%3E%3Cstop offset='.934' stop-color='%239c4598'/%3E%3Cstop offset='1' stop-color='%23932a8e'/%3E%3C/linearGradient%3E%3Cpath d='M45.2 1l27 26.7V99H.2V1h45z' fill='url(%23SVGID_1_)'/%3E%3Cpath d='M45.2 1l27 26.7V99H.2V1h45z' fill-opacity='0' stroke='%23882383' stroke-width='2'/%3E%3Cpath d='M6.1 91.1V71.2h6.1l3.6 13.5 3.6-13.5h6.1V91h-3.8V75.4l-4 15.6h-3.9l-4-15.6V91H6.1zm22.6-9.8c0-2 .3-3.7.9-5.1.5-1 1.1-1.9 1.9-2.7.8-.8 1.7-1.4 2.6-1.8 1.2-.5 2.7-.8 4.3-.8 3 0 5.3.9 7.1 2.7 1.8 1.8 2.7 4.3 2.7 7.6 0 3.2-.9 5.7-2.6 7.5-1.8 1.8-4.1 2.7-7.1 2.7s-5.4-.9-7.1-2.7c-1.8-1.8-2.7-4.3-2.7-7.4zm4.1-.2c0 2.2.5 4 1.6 5.1 1 1.2 2.4 1.7 4 1.7s2.9-.6 4-1.7c1-1.2 1.6-2.9 1.6-5.2 0-2.3-.5-4-1.5-5.1-1-1.1-2.3-1.7-4-1.7s-3 .6-4 1.7c-1.1 1.2-1.7 3-1.7 5.2zm23.6 10l-7.2-19.8h4.4L58.7 86l4.9-14.7h4.3l-7.2 19.8h-4.3z' fill='%23fff'/%3E%3ClinearGradient id='SVGID_2_' gradientUnits='userSpaceOnUse' x1='18.2' y1='50.023' x2='18.2' y2='50.023' gradientTransform='matrix(1 0 0 -1 0 102)'%3E%3Cstop offset='.005' stop-color='%23963491'/%3E%3Cstop offset='1' stop-color='%2370136b'/%3E%3C/linearGradient%3E%3ClinearGradient id='SVGID_3_' gradientUnits='userSpaceOnUse' x1='11.511' y1='51.716' x2='65.211' y2='51.716' gradientTransform='matrix(1 0 0 -1 0 102)'%3E%3Cstop offset='.005' stop-color='%23963491'/%3E%3Cstop offset='1' stop-color='%2370136b'/%3E%3C/linearGradient%3E%3Cpath d='M64.3 55.5c-1.7-.2-3.4-.3-5.1-.3-7.3-.1-13.3 1.6-18.8 3.7S29.6 63.6 23.3 64c-3.4.2-7.3-.6-8.5-2.4-.8-1.3-.8-3.5-1-5.7-.6-5.7-1.6-11.7-2.4-17.3.8-.9 2.1-1.3 3.4-1.7.4 1.1.2 2.7.6 3.8 7.1.7 13.6-.4 20-1.5 6.3-1.1 12.4-2.2 19.4-2.6 3.4-.2 6.9-.2 10.3 0m-9.9 15.3c.5-.2 1.1-.3 1.9-.2.2-3.7.3-7.3.3-11.2-6.2.2-11.9.9-17 2.2.2 4 .4 7.8.3 12 4-1.1 7.7-2.5 12.6-2.7m2-12.1h1.1c.4-.4.2-1.2.2-1.9-1.5-.6-1.8 1-1.3 1.9zm3.9-.2h1.5V38h-1.3c0 .7-.4.9-.2 1.7zm4 0c.5-.1.8 0 1.1.2.4-.3.2-1.2.2-1.9h-1.3v1.7zm-11.5.3h.9c.4-.3.2-1.2.2-1.9-1.4-.4-1.6 1.2-1.1 1.9zm-4 .4c.7.2.8-.3 1.5-.2v-1.7c-1.5-.4-1.7.6-1.5 1.9zm-3.6-1.1c0 .6-.1 1.4.2 1.7.5.1.5-.4 1.1-.2-.2-.6.5-2-.4-1.9-.1.4-.8.1-.9.4zm-31.5.8c.4-.1 1.1.6 1.3 0-.5 0-.1-.8-.2-1.1-.7.2-1.3.3-1.1 1.1zm28.3-.4c-.3.3.2 1.1 0 1.9.6.2.6-.3 1.1-.2-.2-.6.5-2-.4-1.9-.1.3-.4.2-.7.2zm-3.5 2.8c.5-.1.9-.2 1.3-.4.2-.8-.4-.9-.2-1.7h-.9c-.3.3-.1 1.3-.2 2.1zm26.9-1.8c-2.1-.1-3.3-.2-5.5-.2-.5 3.4 0 7.8-.5 11.2 2.4 0 3.6.1 5.8.3M33.4 41.6c.5.2.1 1.2.2 1.7.5-.1 1.1-.2 1.5-.4.6-1.9-.9-2.4-1.7-1.3zm-4.7.6v1.9c.9.2 1.2-.2 1.9-.2-.1-.7.2-1.7-.2-2.1-.5.2-1.3.1-1.7.4zm-5.3.6c.3.5 0 1.6.4 2.1.7.1.8-.4 1.5-.2-.1-.7-.3-1.2-.2-2.1-.8-.2-.9.3-1.7.2zm-7.5 2H17c.2-.9-.4-1.2-.2-2.1-.4.1-1.2-.3-1.3.2.6.2-.1 1.7.4 1.9zm3.4 1c.1 4.1.9 9.3 1.4 13.7 8 .1 13.1-2.7 19.2-4.5-.5-3.9.1-8.7-.7-12.2-6.2 1.6-12.1 3.2-19.9 3zm.5-.8h1.1c.4-.5-.2-1.2 0-2.1h-1.5c.1.7.1 1.6.4 2.1zm-5.4 7.8c.2 0 .3.2.4.4-.4-.7-.7.5-.2.6.1-.2 0-.4.2-.4.3.5-.8.7-.2.8.7-.5 1.3-1.2 2.4-1.5-.1 1.5.4 2.4.4 3.8-.7.5-1.7.7-1.9 1.7 1.2.7 2.5 1.2 4.2 1.3-.7-4.9-1.1-8.8-1.6-13.7-2.2.3-4-.8-5.1-.9.9.8.6 2.5.8 3.6 0-.2 0-.4.2-.4-.1.7.1 1.7-.2 2.1.7.3.5-.2.4.9m44.6 3.2h1.1c.3-.3.2-1.1.2-1.7h-1.3v1.7zm-4-1.4v1.3c.4.4.7-.2 1.5 0v-1.5c-.6 0-1.2 0-1.5.2zm7.6 1.4h1.3v-1.5h-1.3c.1.5 0 1 0 1.5zm-11-1v1.3h1.1c.3-.3.4-1.7-.2-1.7-.1.4-.8.1-.9.4zm-3.6.4c.1.6-.3 1.7.4 1.7 0-.3.5-.2.9-.2-.2-.5.4-1.8-.4-1.7-.1.3-.6.2-.9.2zm-3.4 1v1.5c.7.2.6-.4 1.3-.2-.2-.5.4-1.8-.4-1.7-.1.3-.8.2-.9.4zM15 57c.7-.5 1.3-1.7.2-2.3-.7.4-.8 1.6-.2 2.3zm26.1-1.3c-.1.7.4.8.2 1.5.9 0 1.2-.6 1.1-1.7-.4-.5-.8.1-1.3.2zm-3 2.7c1 0 1.2-.8 1.1-1.9h-.9c-.3.4-.1 1.3-.2 1.9zm-3.6-.4v1.7c.6-.1 1.3-.2 1.5-.8-.6 0 .3-1.6-.6-1.3 0 .4-.7.1-.9.4zM16 60.8c-.4-.7-.2-2-1.3-1.9.2.7.2 2.7 1.3 1.9zm13.8-.9c.5 0 .1.9.2 1.3.8.1 1.2-.2 1.7-.4v-1.7c-.9-.1-1.6.1-1.9.8zm-4.7.6c0 .8-.1 1.7.4 1.9 0-.5.8-.1 1.1-.2.3-.3-.2-1.1 0-1.9-.7-.2-1 .1-1.5.2zM19 62.3v-1.7c-.5 0-.6-.4-1.3-.2-.1 1.1 0 2.1 1.3 1.9zm2.5.2h1.3c.2-.9-.3-1.1-.2-1.9h-1.3c-.1.9.2 1.2.2 1.9z' fill='url(%23SVGID_3_)'/%3E%3ClinearGradient id='SVGID_4_' gradientUnits='userSpaceOnUse' x1='45.269' y1='74.206' x2='58.769' y2='87.706' gradientTransform='matrix(1 0 0 -1 0 102)'%3E%3Cstop offset='0' stop-color='%23f9eff6'/%3E%3Cstop offset='.378' stop-color='%23f8edf5'/%3E%3Cstop offset='.515' stop-color='%23f3e6f1'/%3E%3Cstop offset='.612' stop-color='%23ecdbeb'/%3E%3Cstop offset='.69' stop-color='%23e3cce2'/%3E%3Cstop offset='.757' stop-color='%23d7b8d7'/%3E%3Cstop offset='.817' stop-color='%23caa1c9'/%3E%3Cstop offset='.871' stop-color='%23bc88bb'/%3E%3Cstop offset='.921' stop-color='%23ae6cab'/%3E%3Cstop offset='.965' stop-color='%239f4d9b'/%3E%3Cstop offset='1' stop-color='%23932a8e'/%3E%3C/linearGradient%3E%3Cpath d='M45.2 1l27 26.7h-27V1z' fill='url(%23SVGID_4_)'/%3E%3Cpath d='M45.2 1l27 26.7h-27V1z' fill-opacity='0' stroke='%23882383' stroke-width='2' stroke-linejoin='bevel'/%3E%3C/svg%3E");
- background-repeat:no-repeat;
- background-size:contain
-}
-
-.ipfs-mp3 {
- background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAnxJREFUeNp0U89PE0EU/ra7XWxpSsFYIbVQf9REFBHkYBRIPJh4wrN3DsZ4MPGP8b/wUCIHEw5EY0w04o9ILcREGmwVgaXbbXdnd2bXNxPahGyczebtzrz3ve99740WRRHkWn5cebu4cH6SMY7e0jRAHr9c3WxsVvcemmbys9yT6+uHJ8oaPefypdPDD5Ymh5w26wMkEho8JtDtuEOZFCrvN/4uJZNGH0T59D58X/C27aFNAL3Xthmsww5GCyN4+uzu+OLtQsUPxPQx6ZMAoQjBAw7O+bEVCMMQgqygs+LFs1h+dGd8bna0QmXO9OL6JYgwAvOFZKKoy3V44CgNfv7Yx8oLH+lUEgvzF8Ydhz+n41snAGRG5gUEwClzhHdvttFxfNyYK0EnJozKK5eGcf1qHo1GOxtjwI+pfvm4g/W1qtJgerYE2SXJSIL9+W0jk0mCShAxDXgQKgbNXxZq35vQKCiKQkSUXdc1+gcch1FHGPmKuIgBCdc66qJQHMG9+1NIpUylxxHtuW6gEiTIu+N4yjdWgty0yTmdNjFzcwKjY0MU7MLt+IjoSad16FoIx3b/A0DZ7FYXnsdpAjUMDOjI5zPgfoBsRodhhGhZHfBBU/nGAGRtxWIOg5lT2NtrI5dL0SB5KJzLodloqXaOEatPGztKq5gG3S5DNjuAK5NjKJfPYKI0okBkSdemCiSgS/rkQNLSePtxBj4LSCwfFtE0krqqX7ZVMnu9XlMXy2l7ME0dzA3iANQyY6vWxC61UY41zTyNcYh6/QCNXQvzi5dR39nHVq1BUyuMGAARsF6tbbe4iKD1r7Om5iFBdmW1SsDflLiuB6sX90+AAQDHAW7dW0YnzgAAAABJRU5ErkJggg==);
- background-repeat:no-repeat;
- background-size:contain
-}
-
-.ipfs-mp4 {
- background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAnBJREFUeNpsk99r01AUx79psrTrujVtbceabnZs4DYRHSoMh6Dgq77rn+AfoA/+If4Bok+C0CfxVRDBh+I2NqZzrpS1DVvbtU3SJPcm8SSlsJlecsn9dT73nO85V/B9H0H78OLdt/LDlQ1uMYybIAgI9n99OWxoe83nkiz9hDDae330JvxL48O51Xxm/enNtKPbVwAh0Ec6kYpXat9Pnl2GBC02HrjM5Y7h4P8+7FtIFVJ49OrxUnl7ucIdfhv+BIDv+fBcj7p/tXMPrs2RXVTw4OX2UnFTrXCbbY7tpMsA13FDSDAOQ4gJEGUJLs0PPh9CkESsPrmxxEz2lra3rnpAt3G6adgdQhBpmeLkFodNmsjpOPoXBrQTDcmFFNS7i3MRDzzPCw/vva8ikU+COQxm14BBhvJcHLGpGPTOAJxxeLbrRgAkYujBdH4G5oWJWXUW19YL4XqunAMFhnq1BqWYgaY1MAHASQOiU96zKzkU76mwehaOvx6h9uMv7KFN3RopL4oTAI4HRh4wSl399xla+00YbR3yrIzM9SzSqgJJnoKcklGrH08CcJjnBtLLCsSEGGpSWJvHtDKNoFippsJ0ulIsDDUCCATMlBQkNuahEyiZTcLsmFBKaQxaOk53TlHeKkM70AjAooCghBOk9sKtIvqtPqS4FBaRnJSRX8tj2DOh3lFB5Qw2ZNFK5LRo6w4sKt2ggAzywidAMN/9uIPSZglBLDO5FF3mRD3wHE9qVRvoHrUpfn+UEQK0/7ShtwboHJ6jdH8RZxSC57hSVETb7e5/2u0FxqPHJow+8iZ4lYY2QGu3idhIxO7Y7p8AAwALCGZKEPBGCgAAAABJRU5ErkJggg==);
- background-repeat:no-repeat;
- background-size:contain
-}
-
-.ipfs-mpg {
- background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAnxJREFUeNpsU0tPE1EU/ubRdlqmnUBboa0UeUQDiUGCC1+JmrhxoXt/gBvXJi74If4AV0Y3sNKF0YUaICqoIfjgVShEiGF4tDOdO/fOeOaSKtie5GZu7pzz3e/c7ztKGIaI4vn9p+/P3h4e4a6Pv6EoQBDiy7P5rc1P1Xt6XP8M5ejXo6UJ+dWbuemeTGdpvNdiNe9YvQLe4Bi4PmTpRmyq8m71rp74BxKF2twIHvAo+f/l1T2Yp0zceHizfOZa/xRnfBRhG4CQqAYioBWeXDyA8Di6ei1ceXC1XBwrTXHPH2vW6ccBBBMI6BsSUEQzakGL6xB0tvjyBxRNxdCtc2Xf8R9TyaWTDOg2TjfVdw6hqIoE9B2GxkEDWlLH7s4ette2kSp0oDRezrQwCIIA3oGHr0/mKMmE53qo23W4+w5S+Q5ohob9X3tgHgO8ULQACC7gMx9mKQP30EW6mEHpYi8xcJEdzMucjfkKcrTfmqmiFYBxCF/Id+gayKJwoQjHdrA5v4HK7Cq44KjZNWpagaqp7QACks0H9znW365ia24DzoEDozOJbH8eVtGShXHTwNracnsG7q6LzsEuaAlNPm9h7DSSVjLyCMkppDI+GS2StQWA1RlKo0X56n2X+6QHkmkDakxF9WMVqWyK+s/BrthYfvWz1Ug+zUDcjMPMm0h3pxEjFma3CbIuCud7oMc0LL1ZgmElpGJtW3B+15HIGNITrMYIlOH7i0U41NrInREylYbu4R5qQbQBaAh95fVKZCnpQCnb9DrWZyrRERS6NDeUw+yHaXh7rt4C4B8y+9vkwn7kwKNRpDoa9aiFKBYnF+RcREqQ2e1m3R8BBgAy9kz9ysCE6QAAAABJRU5ErkJggg==);
- background-repeat:no-repeat;
- background-size:contain
-}
-
-.ipfs-odf {
- background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAi5JREFUeNp0UktrU0EU/mbu3FfE1KRRUpWYheALNBURUVy7cy9UkO6KW/+Lbt0IPsFui4gLBbUqFaUuXETUKCYa0jS5yZ2ZO557b5MmTXpgmDPnfOc7jznMGINYPi0de5UvmpORxpjE/kbNqW005DVu8TWw1H758ZfkFgNgJmtyxSPRjJIj0QTW/RDiYGXGb7Dl32/eXrVsd0gSCx9miqC0ooCdp69g5Q/h6OLN0ty5ynIkwzMwUwh2FwMdcbDiCZQXlkqFCpEoPT/wih1YjLInANcD+/Ua9bu3wJlGvrBZCmet2+S6ME5g4oGlZ9A/I70XCDhhDexPNTFmswJBwcnuXkF86VSNZxVu0ukLSGnBcqlnN4HoCQIaIuIv7LUooMOgQ7q75LAAb59B9gCBHSKgqemRr94mMKmD24CfM8nb7THYGQNLpAkUkcb66JyGBFFEWRVL57gFEH5qj8Lxwca2qS3EZaugmzAw24dR/XQgwtsCSBjPIdWbUoE2UJLBnV8Ac/ciWHsK9/glWLnD6K2vgPszsOdOQdfeQ1c/ThKoTgDn9A3KUED/52d45xchZsvorD6Bf/Z60riV3Q9Z/0bbGU1uopYGkfERSQ3VbsMwl0qlqoIARmSoPYXWy0dor79LfBMEEd8jGs/uQ3Yl7PJFNFbuEXiV2riCf88fovXhBbo/vqP3t02/ZYmJFqTkzY160Go9uEMbFK8hR/NrdXtFuUVmnmySVGgO4v4LMAAjRgmO+SJJiQAAAABJRU5ErkJggg==);
- background-repeat:no-repeat;
- background-size:contain
-}
-
-.ipfs-ods {
- background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAetJREFUeNqMUj1IHEEU/i7u7Z23e8tGgneGQPw3hZDkkhQiSuwMQREba4uUgpVlCrvEQhurkCoWqcQQ0oTAaYKNqJygGEwgHCSB6Knn7eXcdX/GmdHVPWYFP3gw78173/vmvYkQQsAwNvckq96UnyIEh7/d4t7uUd/8y+85P+bXSX4grkhI6nJYPW7LrXpBK2YxiSoShhu4Buq1NPofDeqdrZ3Z4cl7D4J3UtA5VyVAlmJoru9Af2ZAp1lcCQ3nqgiuKmbY3l/BH+MnHM9GVLP0Ww3KNA33CQoQQnL834Fj74PUGkANEIkCSSsa8gQqgYTIcB0PVsXB318GInRiCVWCkpRFAs+j5gKlA4t29Ggh4d0t04FKt9PQqF4UFgumSEA8ApeaElilWbYRVy/lsns/N1QBkxtENF4jxPxcgcB1CZVOrvMteK5IQDtJJIGh++PcX9iYwWjXK37+vP0WdYk0Ht99jtX8JywWFkQChw4tc+cZcvlF7rMze+ubbxN40fMalRMDP/6twaiUeK7wlZ0TD0a5hLTWxo2d45KKprqHKJslTsy209s2wnMFBTYNZjc/oLt9gPvLOx+hxVJIKS2YW5pCbSyJTGMK775O8VyBwDJd2LTDl/X5i8v3S7NVw9vJb51tITDEUwEGANCx2/rXEEFFAAAAAElFTkSuQmCC);
- background-repeat:no-repeat;
- background-size:contain
-}
-
-.ipfs-odt {
- background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAepJREFUeNqMkz1II1EQx/+7Ca6JkqyYiJ8cKEpAQbBQFDm0sVOsFBS9wt5KOTgEG5twxVlZ+XEnKNiIghYKxx5nwEpIIXaiSAgKGmMi0d23u8+3T7OaZJEMLG9mmPnN/w1vBUopLPNNhRWXHOyDg0nx82TiJtZPlPVoNpftc2cTotcHtxx06kdXpSQ/BvzKESZzIDmAz6y+NojOjpDMZiqRPIgNoFyWM8DrKUV7axO+gcp4g7AzmquAdVNqOgL2z2I4id1B0wgeygOyt/rLL5buLwAIDgA9dY+L+DkuDQOCrkMgBsRglcMOqAGwIstMg8AkGsuZMNUMRMkLqE+QGloglvlA7uIOAKvZajR0qJkUj/XHe0BTIclVKKlrfKsj9qA8gA6wqSJzPaXlr7ky//tdLEUfawsBjExUFGVWbT7AxSa42H2LMfODmvd3wKb7RAMLYwM8nts8xJ/pEe7/3PmP2eGv3D+9usb35W0bINoA7RmjXSHsH0f5Z/mUSZ0Ir2JmsBtD80s8/rGyzWsLFTD5yUQCbfUBHl9d38LvkdDTXIuHVBo0k+bbt06qO+yAPGXwe/cA4wO9PN44jKDG70GougIzi2tQ00ms7/3lpwnBBgjZ37Kkd1Shht5XzBIFl/ufFtniT/lFgAEAU//g6kvdGBMAAAAASUVORK5CYII=);
- background-repeat:no-repeat;
- background-size:contain
-}
-
-.ipfs-otp {
- background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAcJJREFUeNqMkssvA1EUxr+ZjkdbrfFKVD12ErYSRELY2fkH+BMsLcQaSwsrSzZi47EjJEQkEhYkFlhYSVtFpdqOqpk717l3jKZmiC+5mZlzv/s795wzCuccQncz3YeRBj4KHz0/RrOZe2NsZPP20o255zQ3EAxzEAC+6uzTw13G4TFQAakA/CWtIYbY0KBOrx7IvwDQqlHV1o3YxKTOvyAUvfQCfqmA3e4ikyS/zRAKvOot7eoSHEgZIHrCfQAfBqBaKQQDKScQAExd8emBANg+2U2CvNMkkgSqBmrCxFB8mujeoJBWwEqARcssKTAJEGrmaGrjqK1zvNknH4BtyxKl2VUpRxmj5W+x73q9AEaZrR/ND1EJluIpS3i9JQiA+a+hSq8HwJjTsLrRaWitPTCOlhEZn5N75sM1qigmlN+dB3u++Qao5W4TtbEXXIsiszGL4PA00itTsu6XnQWo0TjMTAJqfMDx/ryBJcaVzSNSH4fW0Q+rkIf5rsjRiid7yyN7uoXS3Zn0egE0NiORAN9bQ017D1Lri7CLlP2EDr3Rf7C/itzV2bfXA/igLDaRixfngFhSCooH2xVPCWBlwKcAAwBX1suA6te+hAAAAABJRU5ErkJggg==);
- background-repeat:no-repeat;
- background-size:contain
-}
-
-.ipfs-ots {
- background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAfZJREFUeNqMUk1rE1EUPS8zmabJdDKB2glEwY9ExJYiBUEQpV25qgtBXfgbpEtXuujKf+AfEKRddOdOGHClbYVCvyKWaijT2mhjphk7Sd7Me76ZONp0EsiBYWbOvfe88+69hHOOAE9f3zTVnDKNHvhlsfqPw/rM0ovyWsRFdXJEpDIyRnSlVz0KSkmvabaJeXSJBEhgAJzTDNybmtUnS5Pmg/lrN07H5NM/f13FoMgpXDSuhiIiK3Qi6LUugX7FAbaPPsJqfIHHKCStqRsXVFPQuZgD9BBxjikSiRq41AAkgCQBzVf0+BWEBX7GBm0xgHHUqk1UbBuEcIydzyCZlOI9YEGuDxwduCCitS3Xh3viCZ4jrcq4PJ6DLHd67tjtuAAXib54dCPVEfQ5XIcik/0/2iDeOYz3ceCxrisMi904y0XiMQFfkB7lg6xFHwFxEqUMV0anUNBLWKm8xd3i4zBWOzmASx0UsiW831mA59Xjm+h7HCOygduXHqJatzA7Poey9QnXjTuoVD/j/sRcmDOWLgqnLC5A2wwST+Pn8T629lahSCo291bwu9XA7vcy3m2+gTaUR14thrk9BXasbdiOjSe3nmPpwys0xSi/HpbDd3bIQC6dx/q3ZbRb/j8BEi3Po5cTJpHI9CBNDEa++GyDBN9/BBgAwfDlCVUQaNAAAAAASUVORK5CYII=);
- background-repeat:no-repeat;
- background-size:contain
-}
-
-.ipfs-ott {
- background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAdFJREFUeNqMU89r02AYfpJ0iVm7EqhVOxw7dDBEdpiCE1RoEZRddvUgbIex/Rs7eehppyF4LOzQu4MxwYp0HgShIuwwUVSCVtl0s13afl+SzzcpyZYmyF74eN583/s+PO+PSEIIeJZdrtQVI19Cgmk/Ph39bpllXq82g7sgLxVcyKNZpIx8Uj5u5zSjc9Gov8ZihCRC8D+7On4JczevGeTGSEIC4ctKJtB1DTPXi1iCCEkIm1EFlC2Em0iwtWfinXkIzjiO0jljtDC5TtflGIGUQMB+mfja/oPv2Rx9MMjpMdJxOXyXTwkcwIkewfqQ1QtQNB385zcI14FrtQexsSb6SRysZ4Fbf+F6eHwATc9gJGNAm5iCTL5n/LCVRGADNoeaGoHqyaXj5gqQlTODovcwNk5Aj6wXqV8eCo7EDhMonEHpW+dZC7gUG98D3geo7vkb01h9cAvPdt76OGy1xntUd3bjUxAk3+l2sHJ/FgtrT0MUJNfDSm0bjQ/72Hzxxo+NK+h3B7XRNO4UrwymQtMIkdTBU0m+sBOayLsn8Ka78mQDjx/e87HXPkb1+UsfP37+AmZ1fP/suknBb6nefVQXjl06TxMlJfWKNWr+Kv8TYAAkUueexJF47QAAAABJRU5ErkJggg==);
- background-repeat:no-repeat;
- background-size:contain
-}
-
-.ipfs-pdf {
- background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAmhJREFUeNp0U0trU0EYPTP35qYxaW6TlDapNKWGbgo2FkF8rARB6rboXusf0F/hyq2U4krFqugqSBeuAyL4SERBstHa0iR9JKZJ7mvu+M0tqZGkH3x8987jzDnnm2FSSqh4ns0VU1ybFzj674Wa3uWiWbfsFQb+jrGj8Xvbm0HlvYVRxhJprpmTlGmum+OMm5uNPZNbtjk3l82ey8++8oW4Jv/H/wdA456g2kvH99FyHNiuAz2dwflbN8YW8zMK5Go/CMfQkAhpGsyQgRCtlpE4jIULyC9fHzu7MPPEl/5ib6WOE0JJNRiHHg6j86mMjw/2gG4bkbY4PW4Yj2j64skA5FTHdaEMPiAJszt1sK0d4suJmY4k0+IDDGRfqmh0u5gejQc+fG8eYCIahRQCEfgQnIuhEkgtONE+dGxYxEDj1DhiEycZ+1YXdUpHCqTMJIYyEES5aXXQsi2kYlGEia5GtHVKn+amPBeCutPgfLALPuVu+xDVPw2EQyFEjHDghbpYNm1yKVVnYjTOerepn4E6XQmLGSPkPkOXWATMSDcjQEkAaqOu6+i/rccALtFL53LI3r0Nq1ZD4/MXZJaWYFer+PXiJc6s3IEgY3+uPYZHTAcAHM+DTE8gnM1CSyaCulv+GrRy8uYyElcu4XfhLVpkpNtn/DGA5Uu0abFH36WnzzCayWAkmYJvWeCkfb9SwY+NDbSoOx4bYqJF8rZqVRRXV/HhzWtUSmWwmWl0RmN4v76OUqGASrmMOkntSHF8MOs954dT08W248wzYsJDOujRBAaqqikTpRo/qqd0/dv97c3Lat9fAQYA4z8bX9nTsb8AAAAASUVORK5CYII=);
- background-repeat:no-repeat;
- background-size:contain
-}
-
-.ipfs-php {
- background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAhNJREFUeNqMkltrE0EUx//ZbDaXNrvZzdIkbYOXGgxYQlCK2IIY6EufxGdB8Av44AdR8AP44JOPBR+Ego0PClUKTTXQSmkTYtOkmubSJrQ1e3H2yJSEJNIDs3PmP+f89pyZcdm2DcdWvn7LzkxFHmCIra7nm9ulg8yLZ09yXON55Dgjt1PM2iPs0+aW/frdh8bzV2/SvQBnCLiEqcFxLKSSodlrU9leiGPihWePBkgeEZO6ShC2dCAZNuf6ADb+ldQ5PUPx4BCFcgXfdwq4Ph1Dtd5CZi4Nw7SQiMdCXkl6yVIy/QBWgcU+yx/XsLK2cdHndqlK/lZxH/OpJO7fnsWY3z/YAq+g0TmHpoUH2vB5PXi8RD9Fo10aAmDJTgWyIuOupmK38rsPcOvqJO33XWEvwLJsmKxHRVEwf/MKWl/yUMf8mIloWN8rw+sP0D6PHQmYuzGNgCRiMZVA17IQV4OIaTI8buH/AJMFd02Tkp05PO4jnWvc57EDAINt7u1X8Pb9KgI+Lxbv3cFR8xjx6AQ+b+Txs/qL9KePlih2CMBCq92hg2qzt1AoV7H5YxdhdqhHzRbgcpFeqdUplpvQW4FhmAixZ/sws4BoWCM/qmsE5XqE3dDQCrqGAYWdejqZgK6GUD8+IV9VghBFN1RZJv3sT5diBwC15gncggCPJKF0WCPN8dun55jQdVpz3Ynl9leAAQAJhiGatD9AOgAAAABJRU5ErkJggg==);
- background-repeat:no-repeat;
- background-size:contain
-}
-
-.ipfs-png {
- background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAmtJREFUeNpsU9tOE1EUXXPp0CAUWmJbC04xBANNTF+kKhG8fID6aqL/gPEj9E0lIf6Dj30HL03wxQtVIC0QKrWxNG1Dk9Z2Oj1zxn1m0oIZTnIyZ8/ee+211z5Hsm0bYg29fLGpxWIJWBYGS5IA8ncKhT9Wvf4Yqprtu+w3q85X7f9QxseD/pmZMZsxN9fnc5JNw0ACGGv6tPSvyvEDKEoWZ5Y8OHHObKpucw4B0t3agnl4CJPs2YkQVu4s61ORaBqMJc8CDBiIRhhVM9bXYdVqYAcH8M3NgS0tQQsFcfdKHEbvlr6WyaR/V6uPKPy7B4DT7lUq4MUipMlJ2MPDUKtVfKZ2nn/5BoNbkONxXeb8LYXe/A9AJLNWCxgdhZJagDI9DZg9qIkEytRSkdqTSFQtGILSbgc8LViM+tc0yPfukzIyOJ359k9YR0eQdB2KmBbpwXoM3Dod1SkD+scpEapCI5DdpsJhIJcjajQZagcjI+5oLe4VkeQnyiZgdIH2X6BJ7dSqQLfrggjw0AQwP+/GegCIHppNoFAgEMO1RZKo7BQgRi3yN05cnwdA0BQMAgF3C6pnbuNg92M9AFT1diSCh6kb+FGvo2MxnBB9ocZxp4Mns1cde213B81e7xwAcl4jkaa0IUSjUdLJwkL0Ej6VSvArCt7l81iku6GrKnYEU89VJlSJRmR0Dax+fI9suYxSo4HlWIw6M3FBlnD9YhiXabyOsOeIqG7TzDeIYo6EDGp+ZPb2kKKqH8h+mkxiI5/D1/19J3bwYPvPWXq2skkiJVxesqt0XzghpKM8nRVV2Lv2q9eLIvSfAAMAaacnllcFBmYAAAAASUVORK5CYII=);
- background-repeat:no-repeat;
- background-size:contain
-}
-
-.ipfs-ppt {
- background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAkhJREFUeNpsU11rE0EUPTM7ySZpmzT9DNamWAtFfSiCigr+AxF9zKtv/hvf/Aki+FEi6ov4ItWHPGiwiBUKoUqqTUJImmR3M7Mz3t0kNe1m4LIwc+65595zlxljEJzdR5uf5nLmsvZx6gSvtd9W9bjhF7jg5dH9nRc/wq8YXaTSJptb0xklx7IZoKUEz1zJ2DUU69/37vFYrDxegJ9U0lC+AoIIVGg9CL+vIObP48KDQn7x0sWiVnJrnEDg7KGk+i/Ac4iUM/R7BsmrSSxtXMfa3X7el8+Kjf3KfUJ+iRJQw4w0Tc8BRyWGRAZY3rBR/VlC+XED2ayDhZyXl03+hNA3TxNQshlGLAnE44zCIL1goXZwiMNvB1i6zbC0KuAsxNITWwgNMYPeLVJiFEO9ArjHAivrAjNzBr4f4vwIgdGD4YUACsZCE8AtYGWT5jCsGQw5wEYJzP/pj5RwYTA1b07eQmfZ8P0sgdaM2FlYwWkMgMpl6NQAO33GKM0wsQWflkh1uqGVmVWblsiDkQyqxwfag35SqcktaEWTUTHYNx4iGU/C29+BvX4Lpu/C7zYgFjegSY63WySsHyXwpYHU00ieu0bAOuJbBTArBkiXKiaAmTzcvRJUV9E8rOgqBwqlY8ASs/AadbRLb8CzeTjVClqft6FdB17tL7yeCbFRBYoLr6vR/PiSEl5BZJaBD0/R2nkOZqfQ2fsKt+0SEQ+GLSIEUvJm+6jbah2+pS2aon+4g/afd4SYJVuA7vvXdC/IHQtSoTnK+yfAAIEaId1m+vudAAAAAElFTkSuQmCC);
- background-repeat:no-repeat;
- background-size:contain
-}
-
-.ipfs-psd {
- background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAqxJREFUeNpsU01ME0EYfbtdKKWGtoItRWgJHApCBE2I0YuoiSaaeDJeOJh41YN3TfTixcRwMfEk8eDJGA+Eg0YTTRRMg02KKFooCBbTlkJLS7f7P+u3K9Xo8iWT3Zn55s173/uGM00TVlwZfzJztD92iKO5ouvQGQPHcQDN380vlDPr65fdLj4Oa41i9sFt+ytgN7o7woGOrqgvvpLBaF8vWj1NUAwGTVNRM3mf5vU/zaU+XySQuTqIFXz9hxmGLkoS7r+YxvVnrzGzlgXPDOzUZPT4m3Dt/KlIuH9oUjXYEHZZ/wOgGQZi4TZcGI5hLb+FO++TSOSKcLtcMA0dI0EPrp4+HtnfG5skiUecDGwQE2MjAwiGWlFVNDz+tIyCokJhPKYSX7Gdz2I01hOJdnY9rJ/7UwPGTEiqjtbmJtw4MYx78S/4Wa3h5UoOYwPdIOp2Xi/t18rlFgcDw6o+ydiWVRwOBnCpL0oOAMmNEhLZIgSeoxwGSWcERon/M9DoBknTIdNQNAMnO4PIVGpIFXcwndlA2OtGc4MAxml27p4AIulWSIa9QVadiYSoJxhqBJivKgh5ad3k9gaw6JdlDaqq7q5wINY4F22HaLHSDZQkBW72O9cBYFEviBIURQH7a7MN0uDisUW12ZZcaGlmdq4DwCqeTo1zNtZuW7hUqGIw7MNqSUS2ImNsKEpSdEwt5lGhfQdAkQBEoub3NNrDJfAIeBuRrcrY5xGQ2RFJAjl00I8PCckJUCB9q1URBnk38XEJEuk41tmGwZAf66s1VOh2keqwoUnYpFxHH4iKIixkN3HzVQKP3iQR/5GDKMuYmE3h+fx3MHqh1sMafztHLuiCg0FAk0uFdLqcpGY5QEXbTC/j7mIaVjc18DxufUtBJ/vcggs+3ijVz/0SYABsJHPUtu/OYwAAAABJRU5ErkJggg==);
- background-repeat:no-repeat;
- background-size:contain
-}
-
-.ipfs-py {
- background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAlVJREFUeNpsUktvEmEUPTPzTUFmgJK2UqXQFG3pA6OBLrQxamJcaYwuu3Dp0l9iXLvVtRuDpgt3JIYaTVSaxtRHsJq2xEJBHgXmifebMhECXzKZme+ee+65516h2+2Cn2cb2VwyHl12//vP2/zOQaF4uD7GWN69e/LogfNm7kUsPBFaXYwHMeK0OlpQEJApHJTuykzK98dE98O0bLM/UNgr4v32Dj1fwSQRt9dSsfmZcMa0rIv9ODaqYrPVxuPnL1Cu1aEbJu7fvIZUIo4bqeVYRzcyv/8c3SPYpwECt/dmu4ON3Ed4TymI+hQc1ZqoE+F+uQLDsnHlwkKMscJTgl4eJOi9fxZLePNhGx6ZQRRFqH4VjZaGSv0Y6cQcJLpra0ZguIWegqDiw7lYBBZV6xiGk9DQDLzK5bEyF4Hi9VLMsoYI7J6Es5PjeHjnOl5ubqHaaJGBEkzbxplQAKIgDmBHekDTgI+qKKqKLvNApgmEgyquLs1CoFn2Y4cIeLJpkjoCLkWnUSIF3JxISIUsCjAoxhWNJLBIJs3YeXj/08oYZkOKY65HllE/bkMmY504YUd40HUq2JSSyW6iVPmLiXE/ZMYQCU+hXK3h1toqdNN0sEObyKtqtDQ6kXDwcadDS2TBryp4nX2HxXjsJK6bDnZIAZem6Tp5YMMmicn5OC4lztNWtvB9cg+hQABtWjKL2jH/T3GgBcYDXEE6mcDM6SlaJAGMWkivLBC54ZgniZaDHSI4rNSqn7/t1vgkGJPwZXffSeCjk2iUWz9+nSTQN8e6ef8EGAClUi/qoiOc3wAAAABJRU5ErkJggg==);
- background-repeat:no-repeat;
- background-size:contain
-}
-
-.ipfs-qt {
- background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAnVJREFUeNpsU8tu00AUPU5sp41NkzRxpfSZqi0VIIQqEEJUZYXECvbwCWxYsuBD+ABUFrDrCnWBQEJdIWigBSr6pqRJ1ebhxrE9M7aZmSrQ4o505fHMnXPPPWdGiaIIYrx89GKpNDdxmXkU3aEoCsT+z8W1Sm21+jCpJctQTvaerj+TX7WbnJ+0cpfuX8mQtn8GgJ4AZtIFY2Hz3foDVRcgyt+cRHcS0IARh+D/8G0PpmVi7smd0dLs+AIjwTVEiANEYYQwCHlEZyJgIQKfoX84g9uPZ0cHZ4YWmE9nuufU0wABCSSImMsWEgqSuoqA/39/swZFTWLy7vQo7dDnfPvWWQa8GuOV3IYLJXmyzDzG2/ChZ3pwbHdQ267BKJoYuj7SF2MQhiF8LuDK/Gf0DKTBKINz1IbTbEMzU1ANDW7LAfEIQKIgBsBFlAx6LYOz6MAcvoDCtAVGGPKlAiIu/F55F33FDA6W93EOAOMaMOl7biKPwRtD8Foetj5sYPfTDtxjl1f3Ubo5jkQieQ4ACSUD2iE4XDpAdbUiW9D7UsiN9WNkZgxajwbd0LGzt3keAJPUc1N5SVeENT0Ao2BKV6QzwlZeRBSKAYhe3aYHcZWn7l1EfjyPypcK9LQGa8qCvW9j9+MvaasQOHaRhGWdhsNLR8hwodYWf6B4tYjDjSOovRqq32rSYq/lytw4A77o1V2ERiAtzY5kkUrrsH+3QF2KY87ArTtQuQ6nAf4x6FCV1D001+vYersBM2vA4y1Rm2D7/Rac/TZIw4d/6MrcGAPf9htN0miJh7Lyuoyvr8rQeP9iVJcrSKgJ+TrFcyYebXTP/RFgAFQobmIOBxbsAAAAAElFTkSuQmCC);
- background-repeat:no-repeat;
- background-size:contain
-}
-
-.ipfs-rar {
- background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAnpJREFUeNpsUktPE1EU/u68OgylZXi0hZACQU1LEKKCMcat7jTRnQsXxsQtv4E/4M74P1iriUaNCw1FgxpjCJQKKAU60+m8mJnrmSll4XCTc8+959zz3e88GOcc8aq9evChOHl/lvMoubvWX/z4+BwTlbvw7bXdg8b7h6LE1gGW+O88CRMt4XTlR6/rYxce5Xv3jlHH19fPkBu+gWy5mlcFb3Wn/umeKOEMJF5C7xCFbtA9dRXjFoYKGiTRAlPGUV1aKU9O3VwNQ74A8DQAIZxqAuAhBPIMFYpQVAVB4CPSZjEzv1weH5tbDQN+JQ2Abu488mnzIbAAA3o/VK2PwDJo7r5Fy7ZRuvi4PFS6+qIXdVYD8Jg6BUcuOD8BozSLlRWyicgVKkTMQWwUlFF0Ooe5FIPk57BD7G0SiywyjD8bCDyHsOkeeeR3SUxEkROmU6BfQYFJMHfhWXV8efkUrb13VPMTsrcTQSzxZ/+n0GVA6EGbSGdgG9vo15fg2nFgbO8k70SRdd+mahDT81vUxTZRlJBRMsjq89C0EXCvSf7TIBZ136YZUJEiE7LgJ2dN01BZuE0dkIhxE7KcQTK1QUj+cwAEyrPZ+IydzRoyah+mLy2isbWBweESJEnB9q+1RM9Ub9GQOWkABg8HjRr2d9Yh0hTlBlRsfn+D4vg0BvUC9rZqECUJuk7Tzr1zahCYlB6HJAREPwfbbMBzLBzsbUKVI0qBgQkc+SxgWUYaIAqOpKwKXJ6bgGlaaDV/YvHaFNrtDsKTfVSrJeqIg/bRNwjclFIALeP3saybhu8SC4VBHwnhBXXIKocYRXD9QzBi4Xgchmkd9+L+CTAAMqwy+ZzluBgAAAAASUVORK5CYII=);
- background-repeat:no-repeat;
- background-size:contain
-}
-
-.ipfs-rb {
- background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAixJREFUeNqEUktvElEU/mag5f2yJhXLwxIt0kiqsVEXujP+A925cu1Pce3WtXVtYuJCF7KtTY0NrVQIpRVKeXTkMcO9F8+9ZVooJJ5kcmbmfOe733fO1YbDIWS8+/g1dycVX7W/xyO3vdsuVKqvnE7HZ230783rlyo7bVBicSGyfjsVwozomVbIPe/c+FmsPHfoRKJd1HT7hXHBZjVbA4aA14NnD9bC2VR8gwuxPi5Sx39Cp+M0XUP0ahhP1jLhW7HFD4zze3b93ILtXYyyVKlR8/5hFbnvO9gtlrGSjOF+OpXkYviWyo8mCS4R6bqO4p86vm3v4fC4DrPfw4unj1XN6JvBaQtjChzUXK43sVU4wNFJA43Tv/B73edQwTmfIhAjCVL6UdPAj1IVFSKhCdAcAI9rnjBiAjtBYEu3GEeh1sKJ0YXR68sVIujzIhzwY8DEBHZqiLRKkicQDfvABxaiQTc4Y/C65pCOXwcjcmlvJgHtlwi4epYifiQWgmoLZwPW6HQG07LgcOgKO0UglAKOTt/E+09fwAiUWU7QAE9xUK3jbvomsispZVHMVEDSZdHo9rCZ/4VIMKAu0XGjpU7d2S8hk0pCELHEzrjKnCQOYJoD+Dxu1RyiwUm5LaMDo9NFt2cqDLvY4oQFp/QpfT/MrmI5FkWebt+NpWto0j2QmQkOjZ9hpwhqjXZzM/+7LU+cc7lRrjXh8/lVLRK5ovLWXglOsiOxdt8/AQYAzv8qbmu6vgEAAAAASUVORK5CYII=);
- background-repeat:no-repeat;
- background-size:contain
-}
-
-.ipfs-rtf {
- background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAe5JREFUeNqEU01PE0EYfnZmd5FSvgLYFuwWt9EgHyEaox68eDJevHvwJ/hTPHv1N/QgZ2NC4g3kUAQKFKGhjVKqRrvbnRlnht262FHfy+y8877PPM8z71pCCKh4/ebt+rJfXEz26Vjf2mnsN5rPKKWbVpx7+eK5Xu2kyMtNTd5d8MdhiJ9BOO7atFI9ajy1UyAqSPIRMR6ZmoNehNHMMB7fX/UWvEKFMbYKE8DfQnAhwRmmJkbx6M6S5+WmK2Evup2c9yUk2nnKA0XVcSiGXAe1k5beP1i+4RFCXqnPywB/AKVzK34RjHNYlgVKCH50w7EBBogbTa/AVM5SgBdn0gc2AMDjPsbFPz2xye9asweS6n+NTbG8BCCfUtLjff2WoVnVpAH6z6hMUtJE3EykYfpF4vUiL3QNS7FMeSAQRBHW3r1Hq91B+VoBQRji4+ExFsvz6Hz7jm7Yw5OH92AcJKW9G4SoHhzhy/lXbB98Qmm2oCXN5WawsV2TACEoJXqwTKOsb3BtR2ucmZxANpPB8JUhyPnHWDaDpfJ1eZFALzJJ4MKO5MEtv4TSXB7V/br8iQLMz+almRZWbvoo5q9qRlxwewCgeXbe3qrVO5ZkUD/9jJGRLPaOm6COi92TU1DbxYe9umRD0DrrtJO+XwIMABWp9nS+FgaoAAAAAElFTkSuQmCC);
- background-repeat:no-repeat;
- background-size:contain
-}
-
-.ipfs-sass {
- background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyJpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMy1jMDExIDY2LjE0NTY2MSwgMjAxMi8wMi8wNi0xNDo1NjoyNyAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENTNiAoV2luZG93cykiIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6MDNDMTBBM0JGMTE5MTFFMTg3N0NFOTIyMTQ2QzhBNkQiIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6MDNDMTBBM0NGMTE5MTFFMTg3N0NFOTIyMTQ2QzhBNkQiPiA8eG1wTU06RGVyaXZlZEZyb20gc3RSZWY6aW5zdGFuY2VJRD0ieG1wLmlpZDowM0MxMEEzOUYxMTkxMUUxODc3Q0U5MjIxNDZDOEE2RCIgc3RSZWY6ZG9jdW1lbnRJRD0ieG1wLmRpZDowM0MxMEEzQUYxMTkxMUUxODc3Q0U5MjIxNDZDOEE2RCIvPiA8L3JkZjpEZXNjcmlwdGlvbj4gPC9yZGY6UkRGPiA8L3g6eG1wbWV0YT4gPD94cGFja2V0IGVuZD0iciI/Po72XUcAAAJcSURBVHjahFJdTxNBFD1bykc/ttvdtttWGgI0bYrUgDZoNYqRJ014kMRXHvwB/hQTH/wFhMREJfFBQxBjhMRIFEQSCAlQxKYGggiU3e3HbnfX2bFt1EU9k9m9mblz5p4zlzFNExYmpue/jmTSZw5PZAl1MAwDT0c7O72wvPdudeNakPNtOZ0tsM7cvzdOc5yN5LDAsTFRAJks/kC2PxFRVe39Si6f4byez62EpAEH/gNN18F53Ri/Ocxf7OtdLMpKT42s/ZPg1cISJp/P0tg0TBzLCoK8D7eHh4RkLLJ4cCz12AjMXwgez8yhqtVo3NbqRKlcxcSL16gZwJ2Ry8KVc8kZO0HdTKlURn+8G6PD2SZhLMQj96WAiMAh2RXFYKI78lcJcx9WYBCycICnpNbojUWpD5Y0C4Zh2D0w6hWc70uQZC+IWfQZrXF0IsHvY+meBd08haAhoVMMQFJKWF7PNZM+klhRyogGhbqxOIXAMOtEwGAqDqVcgbVkkE+5UsEAWavf0az2t0ZqvK2qabh6IU3joizDwTgwej1LdVfJXkdbK8mt2QkayO99A0/0trQ46I1lVcX+UREhnsP34yLp1AD1xibBMuntpzU8mJyi3Tc1O4+l9U06n7x8Q/8PHz1DrrALt8tlr0CrkbJMHTop9Sk5sLa1g8L+ARJdnShKClY3tunN69t5iGLYTlCtakjFY7gxNABdN3B37BaqqoYT8pyX0in4ORbRkIA46YlDRbUTbBZ2Jb/Pw4qiKFnapcpPo9pdbrg8DjAOBsFgELJmsGs7eWkkc5bu/xBgAHkWC6UPADTOAAAAAElFTkSuQmCC);
- background-repeat:no-repeat;
- background-size:contain
-}
-
-.ipfs-scss {
- background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyJpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMy1jMDExIDY2LjE0NTY2MSwgMjAxMi8wMi8wNi0xNDo1NjoyNyAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENTNiAoV2luZG93cykiIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6RkM4QjYyNDVGMTE4MTFFMTlBREZCNDNEM0ExMTk0MUIiIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6RkM4QjYyNDZGMTE4MTFFMTlBREZCNDNEM0ExMTk0MUIiPiA8eG1wTU06RGVyaXZlZEZyb20gc3RSZWY6aW5zdGFuY2VJRD0ieG1wLmlpZDpGQzhCNjI0M0YxMTgxMUUxOUFERkI0M0QzQTExOTQxQiIgc3RSZWY6ZG9jdW1lbnRJRD0ieG1wLmRpZDpGQzhCNjI0NEYxMTgxMUUxOUFERkI0M0QzQTExOTQxQiIvPiA8L3JkZjpEZXNjcmlwdGlvbj4gPC9yZGY6UkRGPiA8L3g6eG1wbWV0YT4gPD94cGFja2V0IGVuZD0iciI/Pkf1yeMAAAJbSURBVHjahFNdTxNBFD0tLULpB91uodVWPmorUIxo0VSiNSExMYYHE33l0Ud/in+C+OSjYgjRGDBRCKJIUkIEWi0WKlja0ul22+5219lJ26gLeiezuXvn7rnnnrlrUFUVms3Mvd2bjIyezRVLBA0zGAzo6jhjm1te+7EU37rFO+w7JlMbtG+ePJ5mOaZmci/nsPl6ONBtw18WDQc9tZq0sp7YjTisXV/NFKRpRvzHpHodDqsF03djzuvDg6vHJWFAprF/Arxe/oins6+YryoqCiUBvNOO+7FrXMjnWc0WyIAOQP0N4Nn8IqqSzPx2swllsYqZl28gK8DDyRvcxKXQvB6gISYpiwgH+jEVi7YAfW4nEqk0PJwDofNejAX7Pae2sPhhHQoF63U5Gai2Bn1epoPWmmaKoug1UBoMrgwHabIVVCx2jdrKFwm67TZ2plldPQGg2cK5HheIUMbaZqKV9In6giDCy3MNYXECgKI2gICxoQAEsQItpNCHWKngMo01arTY/jFIzbutShJuXh1Fm9FImYiM7tTtKOtbO+toN9Nc+fQ5SGUOIVYl7HzPIH2YRZ0y2KZ+sVzBHn2v1mpMGx0DTaR3nzfwfGEJdybGkdo/wEigDyvxLzg4yiESvojZhfd49OAeLJ2degaSLIPOO6vwgiYaaRErTRREEdn8MeJbSVZ5M7nLdNExqFLaQwEfFfACQn1+HBWKSKb3MT4Sgstuh9vVDa+bQ4DORE6o6RlspzMk9TOPfr+fiLJCLFYr3TZSKNcI7+aJwWQmPM+TkqRg49tu65f/JcAAMwMas6WUKd8AAAAASUVORK5CYII=);
- background-repeat:no-repeat;
- background-size:contain
-}
-
-.ipfs-sql {
- background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAh5JREFUeNp8kctrE1EUxr+ZyXMkoa1NBROaSkpTBE23PhZ25cql2y5duvAPUdGFS1FxIRRBXZlFQ9GVdDENIhGJxkDsw2mneZnM83ruNZlOmNoDhzlzz3d/9zv3Sowx8Ch/qlYK2XM3cEJsbH0+qjV/rd6/u6aN18b7RMFT+9aosP/Ex+0ae/puw7j36PlKEMAzctKJ3aGFamMHjV0d+wcGitkMrpWWp6hVIciEk2MAOwbUWjosx0UiFoWqJpGMx5DNzODq5aIPoa82AWBg/lyKLMH1PMp/a9XvLXLzG1cuFlBaWpiKxaIPSLY6CaC93ggQjyiQZRkeQSzLRovGaPciWLt5faSWEBoh6KBvOhiaNga0+Y9pwaFxvu7rfp8F5pWDt+qNMp2IijHGwddWCvN+33/CoAOP5nVdT9SdoQ1JkggiQ6Yvr7V60+9z7akA2gfH9cRF8hO5F5Ve4lQAF9uuK+qFsylkzsQxrcaQm04hdWkR83Mzfp9rQ3fAFzu9Ph6+WMfjl6/pGBdb2jbKmx8QlRjWy5vkyhUZBPgOeGNHN9AbDLGUz6He2hVj3Ll9C8/evsdgaMK0HV8bcmDTU0UUBYXcedR+NLGnH0I3jvDk1Rsy46FP4C/1BtrdntCGHNiOAzWZgEKQ5Qt5lIqLojbaXSQTcRy2OwT4SZqk0IYAOgkVWUE+lxX/zb0DpFNpkTzmZmfFtzewhHYcfwUYAMZmVaZQlLFHAAAAAElFTkSuQmCC);
- background-repeat:no-repeat;
- background-size:contain
-}
-
-.ipfs-tga {
- background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAnxJREFUeNp0U89PE0EU/ra725K22ILRGipb22pMG6JcSEQTbUIwnozxpBcvepeEP0KPogcT/wlNT17kIKbEmChFUYKGVtL0R2gLtNCl3Z1Z3+zSAlonmezOe/O+973vvZEsy4JYnqdPMu6RkSQYQ29JEkB+PZcrslrtPhQl23VZc8/tr9I1yMHg0EA8HrBM04lVFAhoY38fSSDQVN3pfKV8G7KcxZHl6v1xblqU3eLc3p2VFZjr6+gQgwsnhzGTuq6Nhs6kYZqXjwL0GFhEl3U60OfnwWs1GGtrUKNRsKkpeIIBpKIRtI1J7cX7hXRhc/MOhXw5DkCZGG2zXAajzFIoBMvng1ypIKOqmP30GW3OIEcimovzlxRy5RgAFwDEAIODkCcmIMdiQLsNdWwMZdJlg8pzEUt1aBhKq3XinxKYqF9yQbqRIqsMy+0Gyy47bKgUWXSLtDENE5wdtuqQATm50F1VnPbRGeEw8HXZbiV8fsDvI9ldju9vADAyihLEbrWAZhOoVp3z6iqBUiB1A4nEfwCEsbkL/M4TgE5n5jDx+oTEzp1d8m9tC8H6MaAB0imzx0NU/WKUYE+loEyawDBo2ui6TGfT6ANAxrvx87gYCGCxXEKVJvCWFsG3eh1vN/J4OD6Od4UC8o0G3TX7TGLHwI9iEQmvF9X6Fh7F4/iYy+GcLOMSlfEgGsP0qdNOmX0BiGKpVkV1bw/1nW2b/gCpf1PTcI+Y7eg6ps+G4bG4PR99SjAVo9HE4q+fKNE0vl5awuSohjeijbRefVjAtUgEQRK7Yhi9OKn7nKWZxxlSPWl3QwgnaIrW8QMhD542vUbx/W49m7sq4v4IMABOqi3Ej7bAEAAAAABJRU5ErkJggg==);
- background-repeat:no-repeat;
- background-size:contain
-}
-
-.ipfs-tgz {
- background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAnhJREFUeNpsU1trE0EYPbMzSTfdtInFtkkpiaXVWou2FRUEn/so6JugL/oH/Af+B1988if40jcFERQURNBSQdDWlLQN2lsue8neZsZvc7FoOrDszM75znfOmVmmtUYyvry++36yfOeS1qqzDtvH2P76ApPlW3Drb2sHex/uccHWAdbZX30kO2+B3siN3zhTnHuQ66+95i423jzFzOVljBdKOZNHazvVT7e5wF+SZBj9iZJ+3J11mbW2kR8T4LwFli5i4fqTUvnczTUp9RLtDhKgJx0q4dEwWAxrREKICHEsoYYXMXvlcWmquLgmY71yCkG/c0AkARgLMZpnMDMpGNzEYe0dGp6HwvmHpbHC1Wf9MnFCkHQOyYEPzSJwQ2B65Tm5NZG3Fshim6wbMNJn4bpHowMKtIqo2COgR2IcAptwjvcgo6i77igjEmVDqbY8xQJ1VwRULhiBI6+G9Zf3cbTziuzIDkmHSNqECTFgQScEcYuc2NA8TcdYwXD+GkK/TYVN+u72WrIudiAD8o6oAR2RRCmQMjis3CIy1iSpPySCXhFTXeyAgh4BR+JVw8pauLi0Cp4yCX9A90FQhnSBYtnF/k+Q+HYam9itfIZB3QvT8zj8XSW5EhNTs9ivbSLwPUzPLNPJBIMEKnaQYg6aB9+RGR5F5VsNgnNKXMI1NdJGG5WfHzFVLJ7k8c8xUngpVodlDSGbFYj8Y4yMpOG09lHf3yIFPzA3fwHZTAQVtU4JUTeFDrdgDdlI8wAz5Qy2KxswReI7QODZcOr0ZH3q2hIDBI7zq16tuk3FNPxAI4wN+pkoccYoE4YJU5EdUtM4Qst26v26PwIMAKj3P/2YUKgYAAAAAElFTkSuQmCC);
- background-repeat:no-repeat;
- background-size:contain
-}
-
-.ipfs-tiff {
- background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAmRJREFUeNp0UktPE1EU/qYzHWstlrYJNcWUElyUJsaNGh9B0g1Lo0v9Ey78EbrVxBhXuHShm25YGBJRQpAYBDEWpaEPEhksdVpbyjzveO4MfZDCTWbauefc736PIziOA77OPH2yJCcSGdg2uksQAKofFou/7VrtASRpvVNynj13f6XOhjg8HAlMTIQdy/LO+v3uYUPTkAHCTb+cK+0pdyGK6+hbvu4/xiyHbncYAwfR19ZgbG/DoO9LsSgeTd9JXoxfyMG2rvQDdBlwIZauQ5ufh12twioU4E+nYU1NIRCNIDs+Bt28mXzx8VNuZ796j9q/DgAwomwqClilAmF0FE4wCInAlkjO4y+r0JgNX2os6XPYS2q/cQyAcQatFjA0BPH6NYipccAwIGUy2CVJFZInkKlyJAqx3T4/IMGmJkeWIWSz5KgI5pdhb3yDXS5DSCYh8rTID8s0wexeVD0GtMd85KkkefFxUfE47M1NokbJkByEQl6tL+ouAI+MUwbFhnYbaJKc/Sqg0x4H4eDRGDA56fUOABA9/GsCpaIHwr8FOhQ823O5RfW66tUGADhNy3RNRDjcN41HLxdQ8J6jYTsOQLfOJBK4f+s2/uoathoNGKT1MtFeVHZxdWTEZfEq/wMKl3rCJOIzTV6ADs2R5ulYDDNkYjp0DhrF+zCVgkw31+v1UxjQZkNV0SADd2o1MIuc9gmY+/kLxb0/UFoHePd9A1qzeUoKpilx9xcLWzgg+u/zeVfuQqkM9bCN1ysrWKXxdtPgvScwUAm58XZ52W16QyPtifRUzi588GbEi1ztHPsvwAC4uC9qhnsZvwAAAABJRU5ErkJggg==);
- background-repeat:no-repeat;
- background-size:contain
-}
-
-.ipfs-txt {
- background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAeJJREFUeNp8UrtOG1EQPfsyXiyzBguIJSyChZBBEFCKpKHLo6egpErNn8CHgH8gkZIiTSIXLhJAWCgkoMgRMSiRBSK29z4y9+I1d/HCrFb3MTPnnjkzlpQSynY+fP70fGF2gQuByCz6lfdd9Uurfvrrjes6762eb3tzQ69uFJwPsqOPC+MBEmxxphi4tlU5OGmsOzaBWLc+O9oIIVhScidkyGZ8vH62nHtSKlaI4cse6TjAfSaFBBcco0EWqyvzubmpyQrj/FXk75cQaSEMeMXU8xykPA/Hjd/6/LRcyjEpt2i7HAe4A2TeLZWKUOJaVLxj27j813EHGKCXaAJExu/4BOdiAED08riQD2riOrexyRoYc3CvsAbLGAAjZga7vgZG23WMCdBvoxKJc36TRBlMiaa2JByjNqqD8qkYc1pjDK7abey+/YhrWlfKswhpiCR96aEU9o5+QE3g2ovVWDm2Sc22bBQm8vrVpbkS9r+doPr1EOWZaQ0yFoxg2PcREosEAI4uvZhJpzFMP+cSXRbq+043RManez+tNWKMI6GN0g0Z04HFR+NoNC/0yx717efZOSbzY3AcR4Op2AGA5p/W31r9e0vNgSrh9OwCrpeCkqvZuqTybnpRqx/r2CjvvwADAJC/7lzAzQmwAAAAAElFTkSuQmCC);
- background-repeat:no-repeat;
- background-size:contain
-}
-
-.ipfs-wav {
- background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAApFJREFUeNpsU1tPE0EYPXtpKbX0wqUQKVQMFdIXQBNCQBs06KP+B8ODGh+Mf4b/4IsGE54kxhcMBrkp7YOQgBRvSKG73fvsrt8Otoask0xmd+b7zpxzvm8E3/cRjPkniyulW0NFy2JoDkEAguOlpXJ9p3L8MBqVl4O9YHxae8pXuRlcGO7KPLhfTDVUqwUgigJMy4Whm6lEXHjxYf3XnByRN0QB/2KaH7btMlUxoRJAcyqKhdOaht7+DJ49n+2cvTnwynXcsb+kLwJ4rgfmMDDGWqvneXCZS9ND7mov5h9ND85M9y86Dpto5rUkuJ4Py3YDJpy6QGJPayqB+Njf+43XL220t0cwOZkfrNXsBUqZugDA6CbLdAiAwaek1ZU9LmP8Rh6S78GsGxjOp9FdzKJaVZIhBgGASzK21w/wbrnCk8euX+EMAjaaZuPHdwUdHVFYluuGPGCORwwYjg5rqOwccRk+3Ux0IEvntmsNG4ZmUayL/wAwKHUNfZfTKN0ZRaw9Cof8qJ/pMAyHy5KkAMTksSEJtnMenM7EMVMawbejMzJRh67bXEYiIXEAVTW50SEAhzqwfqrBcXx4VOhYm4RsNgHbsJFOyZTsQ1MN+hcohoUlkFiMT+TQFpMwXOjGpXgE+XwGk1N5pFJtKNCequgYGupCRBbCDOp0KBJc4VoP3dyBONW8uydBgBHUThqQKCk3mEZ/LoUG+RBioJO7VarAwEAntjYPiUUW9Hh4b2R7k9j98hN37xWx8fGAt3eIAdVMLn+uUv+b2KReSCZjZJiB9bV9jIz2ofr1BKvvd7G9dRC80lae0HzOt+cWVnrSKDrMJykifwNBpCgE/UAllEXufmDu8Zlffvvm8XSQ90eAAQA0pF7c08o4PAAAAABJRU5ErkJggg==);
- background-repeat:no-repeat;
- background-size:contain
-}
-
-.ipfs-wmv {
- background-image:url("data:image/svg+xml;charset=utf8,%3Csvg id='Layer_2' xmlns='http://www.w3.org/2000/svg' viewBox='0 0 72 100'%3E%3Cstyle/%3E%3ClinearGradient id='SVGID_1_' gradientUnits='userSpaceOnUse' x1='36.2' y1='101' x2='36.2' y2='3.005' gradientTransform='matrix(1 0 0 -1 0 102)'%3E%3Cstop offset='0' stop-color='%23e2cde4'/%3E%3Cstop offset='.17' stop-color='%23e0cae2'/%3E%3Cstop offset='.313' stop-color='%23dbc0dd'/%3E%3Cstop offset='.447' stop-color='%23d2b1d4'/%3E%3Cstop offset='.575' stop-color='%23c79dc7'/%3E%3Cstop offset='.698' stop-color='%23ba84b9'/%3E%3Cstop offset='.819' stop-color='%23ab68a9'/%3E%3Cstop offset='.934' stop-color='%239c4598'/%3E%3Cstop offset='1' stop-color='%23932a8e'/%3E%3C/linearGradient%3E%3Cpath d='M45.2 1l27 26.7V99H.2V1h45z' fill='url(%23SVGID_1_)'/%3E%3Cpath d='M45.2 1l27 26.7V99H.2V1h45z' fill-opacity='0' stroke='%23882383' stroke-width='2'/%3E%3Cpath d='M9.1 91.1L4.7 72.5h3.9l2.8 12.8 3.4-12.8h4.5l3.3 13 2.9-13h3.8l-4.6 18.6h-4L17 77.2l-3.7 13.9H9.1zm22.1 0V72.5h5.7l3.4 12.7 3.4-12.7h5.7v18.6h-3.5V76.4l-3.7 14.7h-3.7l-3.7-14.7v14.7h-3.6zm26.7 0l-6.7-18.6h4.1l4.8 13.8 4.6-13.8h4L62 91.1h-4.1z' fill='%23fff'/%3E%3ClinearGradient id='SVGID_2_' gradientUnits='userSpaceOnUse' x1='18.2' y1='50.023' x2='18.2' y2='50.023' gradientTransform='matrix(1 0 0 -1 0 102)'%3E%3Cstop offset='.005' stop-color='%23963491'/%3E%3Cstop offset='1' stop-color='%2370136b'/%3E%3C/linearGradient%3E%3ClinearGradient id='SVGID_3_' gradientUnits='userSpaceOnUse' x1='11.511' y1='51.716' x2='65.211' y2='51.716' gradientTransform='matrix(1 0 0 -1 0 102)'%3E%3Cstop offset='.005' stop-color='%23963491'/%3E%3Cstop offset='1' stop-color='%2370136b'/%3E%3C/linearGradient%3E%3Cpath d='M64.3 55.5c-1.7-.2-3.4-.3-5.1-.3-7.3-.1-13.3 1.6-18.8 3.7S29.6 63.6 23.3 64c-3.4.2-7.3-.6-8.5-2.4-.8-1.3-.8-3.5-1-5.7-.6-5.7-1.6-11.7-2.4-17.3.8-.9 2.1-1.3 3.4-1.7.4 1.1.2 2.7.6 3.8 7.1.7 13.6-.4 20-1.5 6.3-1.1 12.4-2.2 19.4-2.6 3.4-.2 6.9-.2 10.3 0m-9.9 15.3c.5-.2 1.1-.3 1.9-.2.2-3.7.3-7.3.3-11.2-6.2.2-11.9.9-17 2.2.2 4 .4 7.8.3 12 4-1.1 7.7-2.5 12.6-2.7m2-12.1h1.1c.4-.4.2-1.2.2-1.9-1.5-.6-1.8 1-1.3 1.9zm3.9-.2h1.5V38h-1.3c0 .7-.4.9-.2 1.7zm4 0c.5-.1.8 0 1.1.2.4-.3.2-1.2.2-1.9h-1.3v1.7zm-11.5.3h.9c.4-.3.2-1.2.2-1.9-1.4-.4-1.6 1.2-1.1 1.9zm-4 .4c.7.2.8-.3 1.5-.2v-1.7c-1.5-.4-1.7.6-1.5 1.9zm-3.6-1.1c0 .6-.1 1.4.2 1.7.5.1.5-.4 1.1-.2-.2-.6.5-2-.4-1.9-.1.4-.8.1-.9.4zm-31.5.8c.4-.1 1.1.6 1.3 0-.5 0-.1-.8-.2-1.1-.7.2-1.3.3-1.1 1.1zm28.3-.4c-.3.3.2 1.1 0 1.9.6.2.6-.3 1.1-.2-.2-.6.5-2-.4-1.9-.1.3-.4.2-.7.2zm-3.5 2.8c.5-.1.9-.2 1.3-.4.2-.8-.4-.9-.2-1.7h-.9c-.3.3-.1 1.3-.2 2.1zm26.9-1.8c-2.1-.1-3.3-.2-5.5-.2-.5 3.4 0 7.8-.5 11.2 2.4 0 3.6.1 5.8.3M33.4 41.6c.5.2.1 1.2.2 1.7.5-.1 1.1-.2 1.5-.4.6-1.9-.9-2.4-1.7-1.3zm-4.7.6v1.9c.9.2 1.2-.2 1.9-.2-.1-.7.2-1.7-.2-2.1-.5.2-1.3.1-1.7.4zm-5.3.6c.3.5 0 1.6.4 2.1.7.1.8-.4 1.5-.2-.1-.7-.3-1.2-.2-2.1-.8-.2-.9.3-1.7.2zm-7.5 2H17c.2-.9-.4-1.2-.2-2.1-.4.1-1.2-.3-1.3.2.6.2-.1 1.7.4 1.9zm3.4 1c.1 4.1.9 9.3 1.4 13.7 8 .1 13.1-2.7 19.2-4.5-.5-3.9.1-8.7-.7-12.2-6.2 1.6-12.1 3.2-19.9 3zm.5-.8h1.1c.4-.5-.2-1.2 0-2.1h-1.5c.1.7.1 1.6.4 2.1zm-5.4 7.8c.2 0 .3.2.4.4-.4-.7-.7.5-.2.6.1-.2 0-.4.2-.4.3.5-.8.7-.2.8.7-.5 1.3-1.2 2.4-1.5-.1 1.5.4 2.4.4 3.8-.7.5-1.7.7-1.9 1.7 1.2.7 2.5 1.2 4.2 1.3-.7-4.9-1.1-8.8-1.6-13.7-2.2.3-4-.8-5.1-.9.9.8.6 2.5.8 3.6 0-.2 0-.4.2-.4-.1.7.1 1.7-.2 2.1.7.3.5-.2.4.9m44.6 3.2h1.1c.3-.3.2-1.1.2-1.7h-1.3v1.7zm-4-1.4v1.3c.4.4.7-.2 1.5 0v-1.5c-.6 0-1.2 0-1.5.2zm7.6 1.4h1.3v-1.5h-1.3c.1.5 0 1 0 1.5zm-11-1v1.3h1.1c.3-.3.4-1.7-.2-1.7-.1.4-.8.1-.9.4zm-3.6.4c.1.6-.3 1.7.4 1.7 0-.3.5-.2.9-.2-.2-.5.4-1.8-.4-1.7-.1.3-.6.2-.9.2zm-3.4 1v1.5c.7.2.6-.4 1.3-.2-.2-.5.4-1.8-.4-1.7-.1.3-.8.2-.9.4zM15 57c.7-.5 1.3-1.7.2-2.3-.7.4-.8 1.6-.2 2.3zm26.1-1.3c-.1.7.4.8.2 1.5.9 0 1.2-.6 1.1-1.7-.4-.5-.8.1-1.3.2zm-3 2.7c1 0 1.2-.8 1.1-1.9h-.9c-.3.4-.1 1.3-.2 1.9zm-3.6-.4v1.7c.6-.1 1.3-.2 1.5-.8-.6 0 .3-1.6-.6-1.3 0 .4-.7.1-.9.4zM16 60.8c-.4-.7-.2-2-1.3-1.9.2.7.2 2.7 1.3 1.9zm13.8-.9c.5 0 .1.9.2 1.3.8.1 1.2-.2 1.7-.4v-1.7c-.9-.1-1.6.1-1.9.8zm-4.7.6c0 .8-.1 1.7.4 1.9 0-.5.8-.1 1.1-.2.3-.3-.2-1.1 0-1.9-.7-.2-1 .1-1.5.2zM19 62.3v-1.7c-.5 0-.6-.4-1.3-.2-.1 1.1 0 2.1 1.3 1.9zm2.5.2h1.3c.2-.9-.3-1.1-.2-1.9h-1.3c-.1.9.2 1.2.2 1.9z' fill='url(%23SVGID_3_)'/%3E%3ClinearGradient id='SVGID_4_' gradientUnits='userSpaceOnUse' x1='45.269' y1='74.206' x2='58.769' y2='87.706' gradientTransform='matrix(1 0 0 -1 0 102)'%3E%3Cstop offset='0' stop-color='%23f9eff6'/%3E%3Cstop offset='.378' stop-color='%23f8edf5'/%3E%3Cstop offset='.515' stop-color='%23f3e6f1'/%3E%3Cstop offset='.612' stop-color='%23ecdbeb'/%3E%3Cstop offset='.69' stop-color='%23e3cce2'/%3E%3Cstop offset='.757' stop-color='%23d7b8d7'/%3E%3Cstop offset='.817' stop-color='%23caa1c9'/%3E%3Cstop offset='.871' stop-color='%23bc88bb'/%3E%3Cstop offset='.921' stop-color='%23ae6cab'/%3E%3Cstop offset='.965' stop-color='%239f4d9b'/%3E%3Cstop offset='1' stop-color='%23932a8e'/%3E%3C/linearGradient%3E%3Cpath d='M45.2 1l27 26.7h-27V1z' fill='url(%23SVGID_4_)'/%3E%3Cpath d='M45.2 1l27 26.7h-27V1z' fill-opacity='0' stroke='%23882383' stroke-width='2' stroke-linejoin='bevel'/%3E%3C/svg%3E");
- background-repeat:no-repeat;
- background-size:contain
-}
-
-.ipfs-xls {
- background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAmxJREFUeNpsU0trFEEQ/mamZ3Y2+0zIC2MmITEkUYgERFQErx5E8KTi1b/h79A/4SW3nCNeYggBYZVEMU/y3N3Z7M7OTD/G6lk2ruw20zRdU/XV91VVG0mSQK/3n1a/jky6d6Xs3G8WXS+Pw5N6LXjLLGuna/78oZKerGsYKtrDE16uJGL1L9gEOOcYd2dL1fNwrbL//aXN7J1efPMmkUqEFAk0A0VZNbFEaQCBscIkXj975y3NLq9xye8PBkAniHOFph+j2eC4rsdoB4LsFubGl/Hq8RtvYWpxTQi52o1jvWiGYaRZL0/auDgOkC/Z8BYL2Pqxidp1FZkhoDxpeaXA/Ujuj/4HoOxKKjiOiek7RUShRNQWaNYFQuMafrYCxiw4ozZKfqbYJ0EvRdl1DQyyTs8XCNTA6UELMwvDyLpZWIZNNlNLlQOK2LMJRJ+5AkuZ1S7CFFzJzk56GnUjQWlYkqCoBWFbonEVYcLLA4dNnB624GQsDBWIgfZJEgxkoChzSFWvn4VpQemDm2VwXQsXJwF1h6c+gxlQ5jgSiEUEt0wdIe7tMES+nEG2aCLiJMOIIWIr9e0DEELAMUrwRuchVAyTKimUwO75Jm6VF3Bv7imOaj+xd7UFKVS/BPJF1b/E4tgTrE49J60O5kceoNqowiuuYKa8ghHXA48U9MT2AQgyRvTThE30bQiaSGa4yLMJNFo+Dq/2cHt4CYlwyFf2S6BHwwrMw/avDbR5C1k7h1YQ4KH3Amf+AcZyEbZPv9CItzQD1l9EbtYOjv74v/d3O9RMPTDrsEwGIWN8q2yk7XNYRs9JrRv3V4ABADSGR6eQ0/NQAAAAAElFTkSuQmCC);
- background-repeat:no-repeat;
- background-size:contain
-}
-
-.ipfs-xlsx {
- background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAmlJREFUeNpsU8tqFEEUPVXdPY/ueWZIoiYZiSYKYhJc6EbduHOhgijo3t/wH1z6B0JAhOyMILhxo4kJGk1ASTAxwWF0Mpp5dHc9vFUzYwidaoqmq+8959xzbzGtNcx69PTS26ETmQtS9r4Hy/xv7MW7jV+th5yzVcaYPX/++It9u4NAv+CVR6tBUUTqMJsDcRzjZOZM8W9ZLKx+/XDb4e5/kH5In0lpIYWGUaC0YTZnBCAEKoVR3L36oDo7NbsglZwbqD6iQKOXFMcKUVfBkBAoQhlD5xxMDp/HrSv3q1JgYW3z0x0KXzkCYJaRZljru23aHWTzLiamAyytv0O9UYdf5PArqlppBfMUfu4oALErqZBKcUxMFRCHEp0DgW5Lo4N9NIN1dF0XXsVFOUyPJTzo+WBANDidjp8tgHGG3c0DnJ4uIRf4cOCBaW5KjY8xkZL72xpJ9QcFz5bVqHUJGHZL2YtNmKi06YCyiVFb4s/vEKMTAf1p4edOG6mMi1zR6wEpdUwX+vLDtkCzHoK7ptcM6ayLmGajvtex4PliyoIkFRjmUEASelB2rXQRSfjUCT9PlWpmW21iTGzCAyEkUixPRqXhe2V4zKczbdmybgkpJ0cGOuA6Y2MTCsKoi5HsNK7N3MN+uwYaWbxYfoLLkzdxcew6lrYWaZhm8PHHG3zffp1UwJSHz9vvkU8PodbcQYYYS5lxYkxTkGdVDQdV1Js1qPgYD6JIuIE7gsXVefIhIuM05k7dwMbeMmh87a18ufIMaVYyprrJLgje2Nr+1tzYXANnDnr3zRhHj37Vvy2wpXHtNAd5/wQYAD6WMuT2CwoVAAAAAElFTkSuQmCC);
- background-repeat:no-repeat;
- background-size:contain
-}
-
-.ipfs-xml {
- background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAilJREFUeNqMks1PE0EYxh+g3W2t1G0sEqyISynUFJsSOShNwCamiYZED3LgIkcuxoN/iCZePZiYGD2aGD+i0F5KMChxlVaakAK2ykcAt+WzdLu7zkxo3WZL4pu8mXfmeeY3885ug67roPFh5nvc62m9hjoR+5LMp7MrkYf370qVtco+VtCUFpbj+jGR+JbWn76OyQ8ePwsZATQb8R/hanZgINgj9IqeuBFCw1Kt9OMBnNWCs24XwkG/QKYUEiGjVAPQof/rq0783pShET3ULQo8xz0iS5FaANmrHQH2DoqY+DSLSz6RzecWlnD9ymU47LYjd4O5BXqDTG4FM3NpTEkpdJ5rw0AowLRMbhUfp58gTOaD/UHmNQPI6YmvKWRX1zESHUJ/oBs2nmPa+Mgw0ZIM3tZyGoJwygzQNB2jNyJIZX7iB0lpPoM70UGmPX8zCU+rG8NDVxHwdiC5mKsPUFUN/gvtLLf39sFzVqaN3YrC6TjBauqhXhNA1TQoqloV7Da+pjZq1FsXUCamF29j6LvYhf3iISamZ3Fv9DZevouhRzzPfOG+3hpA9U9UyioOlTJ7pFeTCQS6RGzIebyf+oz5pSzWtmSW1EO9phvQ00slBRt/8qR3DoWdXbiczUiTzd52D+tdLmyTB14mx1rMAKVcRpEATjrsuElee/HXGmnFRyBOGD30C/nEDjNgs7CDpsYmnHG3YPegBCvHs9oYfm8nG9dJa5X4K8AAQzQX4KSN3wcAAAAASUVORK5CYII=);
- background-repeat:no-repeat;
- background-size:contain
-}
-
-.ipfs-yml {
- background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAdxJREFUeNqMUl1rE0EUPbM7m5Y0Zptu21AwWwhYpfSDFh+kvvRd8N0Hf4I/xWdf/Q158F0QoQ+CVsFKaLSQpt/dpmvztTOzzky6cetOpWcZZvbO3MO5514SxzEU3r57/3GpWllM/tP4sL3TarROXuSo/SWJvX71Uu80Cfhlr/T4UdWFAVfdnmsTUtvdP35OUyQKVnJgXDBTcj9icAsTeLax7j/052qM81UjwW1QJXEhMF0qYnN90fdnvdogYmvJPU0/VBApD4hcDrWRcyikfB17srzgW7b9Rh1vEvxDlI4tVytaBSEEtmWh0xsUMwpwnWjqAlcxogiHd1wiQyCu87iI/+sJtf6+NXsgpd7FWCMB50KvkYMGMbLdZgLlfj+K9K4+FnFQ2x7WntIs50AbmiGwLILt+k+EvzvSNIHzdigdJ/AmXQRhiHv5POSwYmG+cqPVo0HqDxj8uTK2vn1Hfa+JmdIkvtZ/4fOPXU3WPDpFeNWVyUKryCiIGMN4zsH98gym3CIcOTwT+XHdXrdQQHAZotE8kBPpSqPNHtBOr48HUmLOcXRJT9dWNMGYJFby91pHOAvaykSaITg+bwefdhrteDRTMSwyrFCgI88E056Hy+4Ah2cXQZL3R4ABALUe7fqXWFN6AAAAAElFTkSuQmCC);
- background-repeat:no-repeat;
- background-size:contain
-}
-
-.ipfs-zip {
- background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAm9JREFUeNpsk0tv00AUhc+MY6dOmgeFJg1FoVVpUWlFC0s2IFF1jxBbhKj4BSxYdscPYcEmQmIDq0gsERIViy4TpD7VFzF1Ho5je2a4thOqNhlp5Mz4zudzzp0wpRTC8fPrk0/TC6+fDtYicLH97T1Kc2vQDcs+rH3eUAxVznn0fn1DRM8E+iOdv5ct3XmZG6yVlNj6solUbgVTt0q5FGtX6vXqC6VklTE+KAO/OODHSIQPRQpsXC+kkEz2ELA0ystv84tLzyucsbWByisAGf+QAS2CCDRRLMJMmxC+i8C4jdLCm/zM7OOKFGptcO6/BTpJ0yeQB0Y+mfKQuZZG0jQgeRbW8Xdomobs9LN8scc+UPHNy4Dwq8IljotIIQEm59/RoSyM1CKkXKZNBm7kIVgyM6wgAnSgRK9vqQfHPiMFDHqyFVsLR9Cm0o4YzoAASrSjCelQfRPb1Vc4qn0EY5L2W9GEaBLcxQgFHpGbkMIDJ69e+wjJ8VXqRgKid0r7ftQdxkRs9SqA2kgAm14SSIQh9uhuLGPMnKJs/5KquL1x0N0RCsizigoDaLqBdHoMiyvrlBsHVx1wphD4BCewoqxGKKDwAgtOy8JufYuk+5golGGaGZwc1sIGoDz3AOPZSVLaHgVwydoJDM1H4DbQODughB3YpOD44HfoHgnu4e7So0uAi0stHLJ3Aud8B9bpHu6vPoSu9TtDl6tUuoFiIYOgu0+158MKmOxomtyD3Qi/3MTR7i8K0EDG1GHO5DE3X4DvNahZlJOwEkOATvdPc2//hx3mXJ5lFJaF8K8bStd0YGfnOJbMGex21x6c+yfAAOlIPDJzr7cLAAAAAElFTkSuQmCC);
- background-repeat:no-repeat;
- background-size:contain
-}
diff --git a/assets/dir-index-html/src/style.css b/assets/dir-index-html/src/style.css
deleted file mode 100644
index 3e7b8a734bc..00000000000
--- a/assets/dir-index-html/src/style.css
+++ /dev/null
@@ -1,212 +0,0 @@
-body {
- color:#34373f;
- font-family:"Helvetica Neue", Helvetica, Arial, sans-serif;
- font-size:14px;
- line-height:1.43;
- margin:0;
- word-break:break-all;
- -webkit-text-size-adjust:100%;
- -ms-text-size-adjust:100%;
- -webkit-tap-highlight-color:transparent
-}
-
-a {
- color:#117eb3;
- text-decoration:none
-}
-
-a:hover {
- color:#00b0e9;
- text-decoration:underline
-}
-
-a:active,
-a:visited {
- color:#00b0e9
-}
-
-strong {
- font-weight:700
-}
-
-table {
- border-collapse:collapse;
- border-spacing:0;
- max-width:100%;
- width:100%
-}
-
-table:last-child {
- border-bottom-left-radius:3px;
- border-bottom-right-radius:3px
-}
-
-tr:first-child td {
- border-top:0
-}
-
-tr:nth-of-type(even) {
- background-color:#f7f8fa
-}
-
-td {
- border-top:1px solid #d9dbe2;
- padding:.65em;
- vertical-align:top
-}
-
-#page-header {
- align-items:center;
- background:#0b3a53;
- border-bottom:4px solid #69c4cd;
- color:#fff;
- display:flex;
- font-size:1.12em;
- font-weight:500;
- justify-content:space-between;
- padding:0 1em
-}
-
-#page-header a {
- color:#69c4cd
-}
-
-#page-header a:active {
- color:#9ad4db
-}
-
-#page-header a:hover {
- color:#fff
-}
-
-#page-header-logo {
- height:2.25em;
- margin:.7em .7em .7em 0;
- width:7.15em
-}
-
-#page-header-menu {
- align-items:center;
- display:flex;
- margin:.65em 0
-}
-
-#page-header-menu div {
- margin:0 .6em
-}
-
-#page-header-menu div:last-child {
- margin:0 0 0 .6em
-}
-
-#page-header-menu svg {
- fill:#69c4cd;
- height:1.8em;
- margin-top:.125em
-}
-
-#page-header-menu svg:hover {
- fill:#fff
-}
-
-.menu-item-narrow {
- display:none
-}
-
-#content {
- border:1px solid #d9dbe2;
- border-radius:4px;
- margin:1em
-}
-
-#content-header {
- background-color:#edf0f4;
- border-bottom:1px solid #d9dbe2;
- border-top-left-radius:3px;
- border-top-right-radius:3px;
- padding:.7em 1em
-}
-
-.type-icon,
-.type-icon>* {
- width:1.15em
-}
-
-.no-linebreak {
- white-space:nowrap
-}
-
-.ipfs-hash {
- color:#7f8491;
- font-family:monospace
-}
-
-@media only screen and (max-width:500px) {
- .menu-item-narrow {
- display:inline
- }
- .menu-item-wide {
- display:none
- }
-}
-
-@media print {
- #page-header {
- display:none
- }
- #content-header,
- .ipfs-hash,
- body {
- color:#000
- }
- #content-header {
- border-bottom:1px solid #000
- }
- #content {
- border:1px solid #000
- }
- a,
- a:visited {
- color:#000;
- text-decoration:underline
- }
- a[href]:after {
- content:" (" attr(href) ")"
- }
- tr {
- page-break-inside:avoid
- }
- tr:nth-of-type(even) {
- background-color:transparent
- }
- td {
- border-top:1px solid #000
- }
-}
-
-@-ms-viewport {
- width:device-width
-}
-
-.d-flex {
- display:flex
-}
-
-.flex-wrap {
- flex-flow:wrap
-}
-
-.flex-shrink-1 {
- flex-shrink:1
-}
-
-.ml-auto {
- margin-left:auto
-}
-
-.table-responsive {
- display:block;
- width:100%;
- overflow-x:auto;
- -webkit-overflow-scrolling:touch
-}
diff --git a/assets/dir-index-html/test/go.mod b/assets/dir-index-html/test/go.mod
deleted file mode 100644
index c1cff1b746f..00000000000
--- a/assets/dir-index-html/test/go.mod
+++ /dev/null
@@ -1,3 +0,0 @@
-module github.com/ipfs/dir-index-html/test
-
-go 1.17
diff --git a/assets/dir-index-html/test/main.go b/assets/dir-index-html/test/main.go
deleted file mode 100644
index c02523a9f40..00000000000
--- a/assets/dir-index-html/test/main.go
+++ /dev/null
@@ -1,116 +0,0 @@
-package main
-
-import (
- "fmt"
- "net/http"
- "net/url"
- "os"
- "text/template"
-)
-
-const templateFile = "../dir-index.html"
-
-// Copied from go-ipfs/core/corehttp/gateway_indexPage.go
-type listingTemplateData struct {
- GatewayURL string
- DNSLink bool
- Listing []directoryItem
- Size string
- Path string
- Breadcrumbs []breadcrumb
- BackLink string
- Hash string
-}
-
-type directoryItem struct {
- Size string
- Name string
- Path string
- Hash string
- ShortHash string
-}
-
-type breadcrumb struct {
- Name string
- Path string
-}
-
-var testPath = "/ipfs/QmFooBarQXB2mzChmMeKY47C43LxUdg1NDJ5MWcKMKxDu7/a/b/c"
-var testData = listingTemplateData{
- GatewayURL: "//localhost:3000",
- DNSLink: true,
- Listing: []directoryItem{{
- Size: "25 MiB",
- Name: "short-film.mov",
- Path: testPath + "/short-film.mov",
- Hash: "QmbWqxBEKC3P8tqsKc98xmWNzrzDtRLMiMPL8wBuTGsMnR",
- ShortHash: "QmbW\u2026sMnR",
- }, {
- Size: "23 KiB",
- Name: "250pxيوسف_الوزاني_صورة_ملتقطة_بواسطة_مرصد_هابل_الفضائي_توضح_سديم_السرطان،_وهو_بقايا_مستعر_أعظم._.jpg",
- Path: testPath + "/250pxيوسف_الوزاني_صورة_ملتقطة_بواسطة_مرصد_هابل_الفضائي_توضح_سديم_السرطان،_وهو_بقايا_مستعر_أعظم._.jpg",
- Hash: "QmUwrKrMTrNv8QjWGKMMH5QV9FMPUtRCoQ6zxTdgxATQW6",
- ShortHash: "QmUw\u2026TQW6",
- }, {
- Size: "1 KiB",
- Name: "this-piece-of-papers-got-47-words-37-sentences-58-words-we-wanna-know.txt",
- Path: testPath + "/this-piece-of-papers-got-47-words-37-sentences-58-words-we-wanna-know.txt",
- Hash: "bafybeigdyrzt5sfp7udm7hu76uh7y26nf3efuylqabf3oclgtqy55fbzdi",
- ShortHash: "bafy\u2026bzdi",
- }},
- Size: "25 MiB",
- Path: testPath,
- Breadcrumbs: []breadcrumb{{
- Name: "ipfs",
- }, {
- Name: "QmFooBarQXB2mzChmMeKY47C43LxUdg1NDJ5MWcKMKxDu7",
- Path: testPath + "/../../..",
- }, {
- Name: "a",
- Path: testPath + "/../..",
- }, {
- Name: "b",
- Path: testPath + "/..",
- }, {
- Name: "c",
- Path: testPath,
- }},
- BackLink: testPath + "/..",
- Hash: "QmFooBazBar2mzChmMeKY47C43LxUdg1NDJ5MWcKMKxDu7",
-}
-
-func main() {
- mux := http.NewServeMux()
- mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
- if r.URL.Path != "/" {
- http.Error(w, "Ha-ha, tricked you! There are no files here!", http.StatusNotFound)
- return
- }
- listingTemplate, err := template.New("dir-index.html").Funcs(template.FuncMap{
- "iconFromExt": func(name string) string {
- return "ipfs-_blank" // place-holder
- },
- "urlEscape": func(rawUrl string) string {
- pathUrl := url.URL{Path: rawUrl}
- return pathUrl.String()
- },
- }).ParseFiles(templateFile)
- if err != nil {
- http.Error(w, fmt.Sprintf("failed to parse template file: %s", err), http.StatusInternalServerError)
- return
- }
- err = listingTemplate.Execute(w, &testData)
- if err != nil {
- http.Error(w, fmt.Sprintf("failed to execute template: %s", err), http.StatusInternalServerError)
- return
- }
- w.WriteHeader(http.StatusOK)
- })
- if _, err := os.Stat(templateFile); err != nil {
- wd, _ := os.Getwd()
- fmt.Printf("could not open template file %q, relative to %q: %s\n", templateFile, wd, err)
- os.Exit(1)
- }
- fmt.Printf("listening on localhost:3000\n")
- http.ListenAndServe("localhost:3000", mux)
-}
diff --git a/core/corehttp/gateway.go b/core/corehttp/gateway.go
index 00c2f748386..d5eccf73c27 100644
--- a/core/corehttp/gateway.go
+++ b/core/corehttp/gateway.go
@@ -1,15 +1,12 @@
package corehttp
import (
- "context"
"fmt"
"net"
"net/http"
- "sort"
- coreiface "github.com/ipfs/interface-go-ipfs-core"
+ "github.com/ipfs/go-libipfs/gateway"
options "github.com/ipfs/interface-go-ipfs-core/options"
- path "github.com/ipfs/interface-go-ipfs-core/path"
version "github.com/ipfs/kubo"
core "github.com/ipfs/kubo/core"
coreapi "github.com/ipfs/kubo/core/coreapi"
@@ -17,50 +14,6 @@ import (
"go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp"
)
-type GatewayConfig struct {
- Headers map[string][]string
- Writable bool
-}
-
-// NodeAPI defines the minimal set of API services required by a gateway handler
-type NodeAPI interface {
- // Unixfs returns an implementation of Unixfs API
- Unixfs() coreiface.UnixfsAPI
-
- // Block returns an implementation of Block API
- Block() coreiface.BlockAPI
-
- // Dag returns an implementation of Dag API
- Dag() coreiface.APIDagService
-
- // Routing returns an implementation of Routing API.
- // Used for returning signed IPNS records, see IPIP-0328
- Routing() coreiface.RoutingAPI
-
- // ResolvePath resolves the path using Unixfs resolver
- ResolvePath(context.Context, path.Path) (path.Resolved, error)
-}
-
-// A helper function to clean up a set of headers:
-// 1. Canonicalizes.
-// 2. Deduplicates.
-// 3. Sorts.
-func cleanHeaderSet(headers []string) []string {
- // Deduplicate and canonicalize.
- m := make(map[string]struct{}, len(headers))
- for _, h := range headers {
- m[http.CanonicalHeaderKey(h)] = struct{}{}
- }
- result := make([]string, 0, len(m))
- for k := range m {
- result = append(result, k)
- }
-
- // Sort
- sort.Strings(result)
- return result
-}
-
func GatewayOption(writable bool, paths ...string) ServeOption {
return func(n *core.IpfsNode, _ net.Listener, mux *http.ServeMux) (*http.ServeMux, error) {
cfg, err := n.Repo.Config()
@@ -78,14 +31,14 @@ func GatewayOption(writable bool, paths ...string) ServeOption {
headers[http.CanonicalHeaderKey(h)] = v
}
- AddAccessControlHeaders(headers)
+ gateway.AddAccessControlHeaders(headers)
offlineAPI, err := api.WithOptions(options.Api.Offline(true))
if err != nil {
return nil, err
}
- gateway := NewGatewayHandler(GatewayConfig{
+ gateway := gateway.NewHandler(gateway.Config{
Headers: headers,
Writable: writable,
}, api, offlineAPI)
@@ -99,50 +52,6 @@ func GatewayOption(writable bool, paths ...string) ServeOption {
}
}
-// AddAccessControlHeaders adds default headers used for controlling
-// cross-origin requests. This function adds several values to the
-// Access-Control-Allow-Headers and Access-Control-Expose-Headers entries.
-// If the Access-Control-Allow-Origin entry is missing a value of '*' is
-// added, indicating that browsers should allow requesting code from any
-// origin to access the resource.
-// If the Access-Control-Allow-Methods entry is missing a value of 'GET' is
-// added, indicating that browsers may use the GET method when issuing cross
-// origin requests.
-func AddAccessControlHeaders(headers map[string][]string) {
- // Hard-coded headers.
- const ACAHeadersName = "Access-Control-Allow-Headers"
- const ACEHeadersName = "Access-Control-Expose-Headers"
- const ACAOriginName = "Access-Control-Allow-Origin"
- const ACAMethodsName = "Access-Control-Allow-Methods"
-
- if _, ok := headers[ACAOriginName]; !ok {
- // Default to *all*
- headers[ACAOriginName] = []string{"*"}
- }
- if _, ok := headers[ACAMethodsName]; !ok {
- // Default to GET
- headers[ACAMethodsName] = []string{http.MethodGet}
- }
-
- headers[ACAHeadersName] = cleanHeaderSet(
- append([]string{
- "Content-Type",
- "User-Agent",
- "Range",
- "X-Requested-With",
- }, headers[ACAHeadersName]...))
-
- headers[ACEHeadersName] = cleanHeaderSet(
- append([]string{
- "Content-Length",
- "Content-Range",
- "X-Chunked-Output",
- "X-Stream-Output",
- "X-Ipfs-Path",
- "X-Ipfs-Roots",
- }, headers[ACEHeadersName]...))
-}
-
func VersionOption() ServeOption {
return func(_ *core.IpfsNode, _ net.Listener, mux *http.ServeMux) (*http.ServeMux, error) {
mux.HandleFunc("/version", func(w http.ResponseWriter, r *http.Request) {
diff --git a/core/corehttp/gateway_handler.go b/core/corehttp/gateway_handler.go
deleted file mode 100644
index c3e8fa0d63d..00000000000
--- a/core/corehttp/gateway_handler.go
+++ /dev/null
@@ -1,1117 +0,0 @@
-package corehttp
-
-import (
- "context"
- "fmt"
- "html/template"
- "io"
- "mime"
- "net/http"
- "net/textproto"
- "net/url"
- "os"
- gopath "path"
- "regexp"
- "runtime/debug"
- "strings"
- "time"
-
- cid "github.com/ipfs/go-cid"
- ipld "github.com/ipfs/go-ipld-format"
- "github.com/ipfs/go-libipfs/files"
- dag "github.com/ipfs/go-merkledag"
- mfs "github.com/ipfs/go-mfs"
- path "github.com/ipfs/go-path"
- "github.com/ipfs/go-path/resolver"
- coreiface "github.com/ipfs/interface-go-ipfs-core"
- ipath "github.com/ipfs/interface-go-ipfs-core/path"
- routing "github.com/libp2p/go-libp2p/core/routing"
- mc "github.com/multiformats/go-multicodec"
- prometheus "github.com/prometheus/client_golang/prometheus"
- "go.opentelemetry.io/otel/attribute"
- "go.opentelemetry.io/otel/trace"
- "go.uber.org/zap"
-)
-
-const (
- ipfsPathPrefix = "/ipfs/"
- ipnsPathPrefix = "/ipns/"
- immutableCacheControl = "public, max-age=29030400, immutable"
-)
-
-var (
- onlyASCII = regexp.MustCompile("[[:^ascii:]]")
- noModtime = time.Unix(0, 0) // disables Last-Modified header if passed as modtime
-)
-
-// HTML-based redirect for errors which can be recovered from, but we want
-// to provide hint to people that they should fix things on their end.
-var redirectTemplate = template.Must(template.New("redirect").Parse(`
-
-
-
-
-
-
-
- {{.ErrorMsg}} (if a redirect does not happen in 10 seconds, use "{{.SuggestedPath}}" instead)
-
-`))
-
-type redirectTemplateData struct {
- RedirectURL string
- SuggestedPath string
- ErrorMsg string
-}
-
-// gatewayHandler is a HTTP handler that serves IPFS objects (accessible by default at /ipfs/)
-// (it serves requests like GET /ipfs/QmVRzPKPzNtSrEzBFm2UZfxmPAgnaLke4DMcerbsGGSaFe/link)
-type gatewayHandler struct {
- config GatewayConfig
- api NodeAPI
- offlineAPI NodeAPI
-
- // generic metrics
- firstContentBlockGetMetric *prometheus.HistogramVec
- unixfsGetMetric *prometheus.SummaryVec // deprecated, use firstContentBlockGetMetric
-
- // response type metrics
- unixfsFileGetMetric *prometheus.HistogramVec
- unixfsGenDirGetMetric *prometheus.HistogramVec
- carStreamGetMetric *prometheus.HistogramVec
- rawBlockGetMetric *prometheus.HistogramVec
-}
-
-// StatusResponseWriter enables us to override HTTP Status Code passed to
-// WriteHeader function inside of http.ServeContent. Decision is based on
-// presence of HTTP Headers such as Location.
-type statusResponseWriter struct {
- http.ResponseWriter
-}
-
-// Custom type for collecting error details to be handled by `webRequestError`
-type requestError struct {
- Message string
- StatusCode int
- Err error
-}
-
-func (r *requestError) Error() string {
- return r.Err.Error()
-}
-
-func newRequestError(message string, err error, statusCode int) *requestError {
- return &requestError{
- Message: message,
- Err: err,
- StatusCode: statusCode,
- }
-}
-
-func (sw *statusResponseWriter) WriteHeader(code int) {
- // Check if we need to adjust Status Code to account for scheduled redirect
- // This enables us to return payload along with HTTP 301
- // for subdomain redirect in web browsers while also returning body for cli
- // tools which do not follow redirects by default (curl, wget).
- redirect := sw.ResponseWriter.Header().Get("Location")
- if redirect != "" && code == http.StatusOK {
- code = http.StatusMovedPermanently
- log.Debugw("subdomain redirect", "location", redirect, "status", code)
- }
- sw.ResponseWriter.WriteHeader(code)
-}
-
-// ServeContent replies to the request using the content in the provided ReadSeeker
-// and returns the status code written and any error encountered during a write.
-// It wraps http.ServeContent which takes care of If-None-Match+Etag,
-// Content-Length and range requests.
-func ServeContent(w http.ResponseWriter, req *http.Request, name string, modtime time.Time, content io.ReadSeeker) (int, bool, error) {
- ew := &errRecordingResponseWriter{ResponseWriter: w}
- http.ServeContent(ew, req, name, modtime, content)
-
- // When we calculate some metrics we want a flag that lets us to ignore
- // errors and 304 Not Modified, and only care when requested data
- // was sent in full.
- dataSent := ew.code/100 == 2 && ew.err == nil
-
- return ew.code, dataSent, ew.err
-}
-
-// errRecordingResponseWriter wraps a ResponseWriter to record the status code and any write error.
-type errRecordingResponseWriter struct {
- http.ResponseWriter
- code int
- err error
-}
-
-func (w *errRecordingResponseWriter) WriteHeader(code int) {
- if w.code == 0 {
- w.code = code
- }
- w.ResponseWriter.WriteHeader(code)
-}
-
-func (w *errRecordingResponseWriter) Write(p []byte) (int, error) {
- n, err := w.ResponseWriter.Write(p)
- if err != nil && w.err == nil {
- w.err = err
- }
- return n, err
-}
-
-// ReadFrom exposes errRecordingResponseWriter's underlying ResponseWriter to io.Copy
-// to allow optimized methods to be taken advantage of.
-func (w *errRecordingResponseWriter) ReadFrom(r io.Reader) (n int64, err error) {
- n, err = io.Copy(w.ResponseWriter, r)
- if err != nil && w.err == nil {
- w.err = err
- }
- return n, err
-}
-
-func newGatewaySummaryMetric(name string, help string) *prometheus.SummaryVec {
- summaryMetric := prometheus.NewSummaryVec(
- prometheus.SummaryOpts{
- Namespace: "ipfs",
- Subsystem: "http",
- Name: name,
- Help: help,
- },
- []string{"gateway"},
- )
- if err := prometheus.Register(summaryMetric); err != nil {
- if are, ok := err.(prometheus.AlreadyRegisteredError); ok {
- summaryMetric = are.ExistingCollector.(*prometheus.SummaryVec)
- } else {
- log.Errorf("failed to register ipfs_http_%s: %v", name, err)
- }
- }
- return summaryMetric
-}
-
-func newGatewayHistogramMetric(name string, help string) *prometheus.HistogramVec {
- // We can add buckets as a parameter in the future, but for now using static defaults
- // suggested in https://github.com/ipfs/kubo/issues/8441
- defaultBuckets := []float64{0.05, 0.1, 0.25, 0.5, 1, 2, 5, 10, 30, 60}
- histogramMetric := prometheus.NewHistogramVec(
- prometheus.HistogramOpts{
- Namespace: "ipfs",
- Subsystem: "http",
- Name: name,
- Help: help,
- Buckets: defaultBuckets,
- },
- []string{"gateway"},
- )
- if err := prometheus.Register(histogramMetric); err != nil {
- if are, ok := err.(prometheus.AlreadyRegisteredError); ok {
- histogramMetric = are.ExistingCollector.(*prometheus.HistogramVec)
- } else {
- log.Errorf("failed to register ipfs_http_%s: %v", name, err)
- }
- }
- return histogramMetric
-}
-
-// NewGatewayHandler returns an http.Handler that can act as a gateway to IPFS content
-// offlineApi is a version of the API that should not make network requests for missing data
-func NewGatewayHandler(c GatewayConfig, api NodeAPI, offlineAPI NodeAPI) http.Handler {
- return newGatewayHandler(c, api, offlineAPI)
-}
-
-func newGatewayHandler(c GatewayConfig, api NodeAPI, offlineAPI NodeAPI) *gatewayHandler {
- i := &gatewayHandler{
- config: c,
- api: api,
- offlineAPI: offlineAPI,
- // Improved Metrics
- // ----------------------------
- // Time till the first content block (bar in /ipfs/cid/foo/bar)
- // (format-agnostic, across all response types)
- firstContentBlockGetMetric: newGatewayHistogramMetric(
- "gw_first_content_block_get_latency_seconds",
- "The time till the first content block is received on GET from the gateway.",
- ),
-
- // Response-type specific metrics
- // ----------------------------
- // UnixFS: time it takes to return a file
- unixfsFileGetMetric: newGatewayHistogramMetric(
- "gw_unixfs_file_get_duration_seconds",
- "The time to serve an entire UnixFS file from the gateway.",
- ),
- // UnixFS: time it takes to generate static HTML with directory listing
- unixfsGenDirGetMetric: newGatewayHistogramMetric(
- "gw_unixfs_gen_dir_listing_get_duration_seconds",
- "The time to serve a generated UnixFS HTML directory listing from the gateway.",
- ),
- // CAR: time it takes to return requested CAR stream
- carStreamGetMetric: newGatewayHistogramMetric(
- "gw_car_stream_get_duration_seconds",
- "The time to GET an entire CAR stream from the gateway.",
- ),
- // Block: time it takes to return requested Block
- rawBlockGetMetric: newGatewayHistogramMetric(
- "gw_raw_block_get_duration_seconds",
- "The time to GET an entire raw Block from the gateway.",
- ),
-
- // Legacy Metrics
- // ----------------------------
- unixfsGetMetric: newGatewaySummaryMetric( // TODO: remove?
- // (deprecated, use firstContentBlockGetMetric instead)
- "unixfs_get_latency_seconds",
- "The time to receive the first UnixFS node on a GET from the gateway.",
- ),
- }
- return i
-}
-
-func parseIpfsPath(p string) (cid.Cid, string, error) {
- rootPath, err := path.ParsePath(p)
- if err != nil {
- return cid.Cid{}, "", err
- }
-
- // Check the path.
- rsegs := rootPath.Segments()
- if rsegs[0] != "ipfs" {
- return cid.Cid{}, "", fmt.Errorf("WritableGateway: only ipfs paths supported")
- }
-
- rootCid, err := cid.Decode(rsegs[1])
- if err != nil {
- return cid.Cid{}, "", err
- }
-
- return rootCid, path.Join(rsegs[2:]), nil
-}
-
-func (i *gatewayHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
- // the hour is a hard fallback, we don't expect it to happen, but just in case
- ctx, cancel := context.WithTimeout(r.Context(), time.Hour)
- defer cancel()
- r = r.WithContext(ctx)
-
- defer func() {
- if r := recover(); r != nil {
- log.Error("A panic occurred in the gateway handler!")
- log.Error(r)
- debug.PrintStack()
- }
- }()
-
- if i.config.Writable {
- switch r.Method {
- case http.MethodPost:
- i.postHandler(w, r)
- return
- case http.MethodPut:
- i.putHandler(w, r)
- return
- case http.MethodDelete:
- i.deleteHandler(w, r)
- return
- }
- }
-
- switch r.Method {
- case http.MethodGet, http.MethodHead:
- i.getOrHeadHandler(w, r)
- return
- case http.MethodOptions:
- i.optionsHandler(w, r)
- return
- }
-
- errmsg := "Method " + r.Method + " not allowed: "
- var status int
- if !i.config.Writable {
- status = http.StatusMethodNotAllowed
- errmsg = errmsg + "read only access"
- w.Header().Add("Allow", http.MethodGet)
- w.Header().Add("Allow", http.MethodHead)
- w.Header().Add("Allow", http.MethodOptions)
- } else {
- status = http.StatusBadRequest
- errmsg = errmsg + "bad request for " + r.URL.Path
- }
- http.Error(w, errmsg, status)
-}
-
-func (i *gatewayHandler) optionsHandler(w http.ResponseWriter, r *http.Request) {
- /*
- OPTIONS is a noop request that is used by the browsers to check
- if server accepts cross-site XMLHttpRequest (indicated by the presence of CORS headers)
- https://developer.mozilla.org/en-US/docs/Web/HTTP/Access_control_CORS#Preflighted_requests
- */
- i.addUserHeaders(w) // return all custom headers (including CORS ones, if set)
-}
-
-func (i *gatewayHandler) getOrHeadHandler(w http.ResponseWriter, r *http.Request) {
- begin := time.Now()
-
- logger := log.With("from", r.RequestURI)
- logger.Debug("http request received")
-
- if err := handleUnsupportedHeaders(r); err != nil {
- webRequestError(w, err)
- return
- }
-
- if requestHandled := handleProtocolHandlerRedirect(w, r, logger); requestHandled {
- return
- }
-
- if err := handleServiceWorkerRegistration(r); err != nil {
- webRequestError(w, err)
- return
- }
-
- contentPath := ipath.New(r.URL.Path)
-
- if requestHandled := i.handleOnlyIfCached(w, r, contentPath, logger); requestHandled {
- return
- }
-
- if requestHandled := handleSuperfluousNamespace(w, r, contentPath); requestHandled {
- return
- }
-
- // Detect when explicit Accept header or ?format parameter are present
- responseFormat, formatParams, err := customResponseFormat(r)
- if err != nil {
- webError(w, "error while processing the Accept header", err, http.StatusBadRequest)
- return
- }
- trace.SpanFromContext(r.Context()).SetAttributes(attribute.String("ResponseFormat", responseFormat))
-
- resolvedPath, contentPath, ok := i.handlePathResolution(w, r, responseFormat, contentPath, logger)
- if !ok {
- return
- }
- trace.SpanFromContext(r.Context()).SetAttributes(attribute.String("ResolvedPath", resolvedPath.String()))
-
- // Detect when If-None-Match HTTP header allows returning HTTP 304 Not Modified
- if inm := r.Header.Get("If-None-Match"); inm != "" {
- pathCid := resolvedPath.Cid()
- // need to check against both File and Dir Etag variants
- // because this inexpensive check happens before we do any I/O
- cidEtag := getEtag(r, pathCid)
- dirEtag := getDirListingEtag(pathCid)
- if etagMatch(inm, cidEtag, dirEtag) {
- // Finish early if client already has a matching Etag
- w.WriteHeader(http.StatusNotModified)
- return
- }
- }
-
- if err := i.handleGettingFirstBlock(r, begin, contentPath, resolvedPath); err != nil {
- webRequestError(w, err)
- return
- }
-
- if err := i.setCommonHeaders(w, r, contentPath); err != nil {
- webRequestError(w, err)
- return
- }
-
- // Support custom response formats passed via ?format or Accept HTTP header
- switch responseFormat {
- case "", "application/json", "application/cbor":
- switch mc.Code(resolvedPath.Cid().Prefix().Codec) {
- case mc.Json, mc.DagJson, mc.Cbor, mc.DagCbor:
- logger.Debugw("serving codec", "path", contentPath)
- i.serveCodec(r.Context(), w, r, resolvedPath, contentPath, begin, responseFormat)
- default:
- logger.Debugw("serving unixfs", "path", contentPath)
- i.serveUnixFS(r.Context(), w, r, resolvedPath, contentPath, begin, logger)
- }
- return
- case "application/vnd.ipld.raw":
- logger.Debugw("serving raw block", "path", contentPath)
- i.serveRawBlock(r.Context(), w, r, resolvedPath, contentPath, begin)
- return
- case "application/vnd.ipld.car":
- logger.Debugw("serving car stream", "path", contentPath)
- carVersion := formatParams["version"]
- i.serveCAR(r.Context(), w, r, resolvedPath, contentPath, carVersion, begin)
- return
- case "application/x-tar":
- logger.Debugw("serving tar file", "path", contentPath)
- i.serveTAR(r.Context(), w, r, resolvedPath, contentPath, begin, logger)
- return
- case "application/vnd.ipld.dag-json", "application/vnd.ipld.dag-cbor":
- logger.Debugw("serving codec", "path", contentPath)
- i.serveCodec(r.Context(), w, r, resolvedPath, contentPath, begin, responseFormat)
- case "application/vnd.ipfs.ipns-record":
- logger.Debugw("serving ipns record", "path", contentPath)
- i.serveIpnsRecord(r.Context(), w, r, resolvedPath, contentPath, begin, logger)
- return
- default: // catch-all for unsuported application/vnd.*
- err := fmt.Errorf("unsupported format %q", responseFormat)
- webError(w, "failed to respond with requested content type", err, http.StatusBadRequest)
- return
- }
-}
-
-func (i *gatewayHandler) postHandler(w http.ResponseWriter, r *http.Request) {
- p, err := i.api.Unixfs().Add(r.Context(), files.NewReaderFile(r.Body))
- if err != nil {
- internalWebError(w, err)
- return
- }
-
- i.addUserHeaders(w) // ok, _now_ write user's headers.
- w.Header().Set("IPFS-Hash", p.Cid().String())
- log.Debugw("CID created, http redirect", "from", r.URL, "to", p, "status", http.StatusCreated)
- http.Redirect(w, r, p.String(), http.StatusCreated)
-}
-
-func (i *gatewayHandler) putHandler(w http.ResponseWriter, r *http.Request) {
- ctx := r.Context()
- ds := i.api.Dag()
-
- // Parse the path
- rootCid, newPath, err := parseIpfsPath(r.URL.Path)
- if err != nil {
- webError(w, "WritableGateway: failed to parse the path", err, http.StatusBadRequest)
- return
- }
- if newPath == "" || newPath == "/" {
- http.Error(w, "WritableGateway: empty path", http.StatusBadRequest)
- return
- }
- newDirectory, newFileName := gopath.Split(newPath)
-
- // Resolve the old root.
-
- rnode, err := ds.Get(ctx, rootCid)
- if err != nil {
- webError(w, "WritableGateway: Could not create DAG from request", err, http.StatusInternalServerError)
- return
- }
-
- pbnd, ok := rnode.(*dag.ProtoNode)
- if !ok {
- webError(w, "Cannot read non protobuf nodes through gateway", dag.ErrNotProtobuf, http.StatusBadRequest)
- return
- }
-
- // Create the new file.
- newFilePath, err := i.api.Unixfs().Add(ctx, files.NewReaderFile(r.Body))
- if err != nil {
- webError(w, "WritableGateway: could not create DAG from request", err, http.StatusInternalServerError)
- return
- }
-
- newFile, err := ds.Get(ctx, newFilePath.Cid())
- if err != nil {
- webError(w, "WritableGateway: failed to resolve new file", err, http.StatusInternalServerError)
- return
- }
-
- // Patch the new file into the old root.
-
- root, err := mfs.NewRoot(ctx, ds, pbnd, nil)
- if err != nil {
- webError(w, "WritableGateway: failed to create MFS root", err, http.StatusBadRequest)
- return
- }
-
- if newDirectory != "" {
- err := mfs.Mkdir(root, newDirectory, mfs.MkdirOpts{Mkparents: true, Flush: false})
- if err != nil {
- webError(w, "WritableGateway: failed to create MFS directory", err, http.StatusInternalServerError)
- return
- }
- }
- dirNode, err := mfs.Lookup(root, newDirectory)
- if err != nil {
- webError(w, "WritableGateway: failed to lookup directory", err, http.StatusInternalServerError)
- return
- }
- dir, ok := dirNode.(*mfs.Directory)
- if !ok {
- http.Error(w, "WritableGateway: target directory is not a directory", http.StatusBadRequest)
- return
- }
- err = dir.Unlink(newFileName)
- switch err {
- case os.ErrNotExist, nil:
- default:
- webError(w, "WritableGateway: failed to replace existing file", err, http.StatusBadRequest)
- return
- }
- err = dir.AddChild(newFileName, newFile)
- if err != nil {
- webError(w, "WritableGateway: failed to link file into directory", err, http.StatusInternalServerError)
- return
- }
- nnode, err := root.GetDirectory().GetNode()
- if err != nil {
- webError(w, "WritableGateway: failed to finalize", err, http.StatusInternalServerError)
- return
- }
- newcid := nnode.Cid()
-
- i.addUserHeaders(w) // ok, _now_ write user's headers.
- w.Header().Set("IPFS-Hash", newcid.String())
-
- redirectURL := gopath.Join(ipfsPathPrefix, newcid.String(), newPath)
- log.Debugw("CID replaced, redirect", "from", r.URL, "to", redirectURL, "status", http.StatusCreated)
- http.Redirect(w, r, redirectURL, http.StatusCreated)
-}
-
-func (i *gatewayHandler) deleteHandler(w http.ResponseWriter, r *http.Request) {
- ctx := r.Context()
-
- // parse the path
-
- rootCid, newPath, err := parseIpfsPath(r.URL.Path)
- if err != nil {
- webError(w, "WritableGateway: failed to parse the path", err, http.StatusBadRequest)
- return
- }
- if newPath == "" || newPath == "/" {
- http.Error(w, "WritableGateway: empty path", http.StatusBadRequest)
- return
- }
- directory, filename := gopath.Split(newPath)
-
- // lookup the root
-
- rootNodeIPLD, err := i.api.Dag().Get(ctx, rootCid)
- if err != nil {
- webError(w, "WritableGateway: failed to resolve root CID", err, http.StatusInternalServerError)
- return
- }
- rootNode, ok := rootNodeIPLD.(*dag.ProtoNode)
- if !ok {
- http.Error(w, "WritableGateway: empty path", http.StatusInternalServerError)
- return
- }
-
- // construct the mfs root
-
- root, err := mfs.NewRoot(ctx, i.api.Dag(), rootNode, nil)
- if err != nil {
- webError(w, "WritableGateway: failed to construct the MFS root", err, http.StatusBadRequest)
- return
- }
-
- // lookup the parent directory
-
- parentNode, err := mfs.Lookup(root, directory)
- if err != nil {
- webError(w, "WritableGateway: failed to look up parent", err, http.StatusInternalServerError)
- return
- }
-
- parent, ok := parentNode.(*mfs.Directory)
- if !ok {
- http.Error(w, "WritableGateway: parent is not a directory", http.StatusInternalServerError)
- return
- }
-
- // delete the file
-
- switch parent.Unlink(filename) {
- case nil, os.ErrNotExist:
- default:
- webError(w, "WritableGateway: failed to remove file", err, http.StatusInternalServerError)
- return
- }
-
- nnode, err := root.GetDirectory().GetNode()
- if err != nil {
- webError(w, "WritableGateway: failed to finalize", err, http.StatusInternalServerError)
- return
- }
- ncid := nnode.Cid()
-
- i.addUserHeaders(w) // ok, _now_ write user's headers.
- w.Header().Set("IPFS-Hash", ncid.String())
-
- redirectURL := gopath.Join(ipfsPathPrefix+ncid.String(), directory)
- // note: StatusCreated is technically correct here as we created a new resource.
- log.Debugw("CID deleted, redirect", "from", r.RequestURI, "to", redirectURL, "status", http.StatusCreated)
- http.Redirect(w, r, redirectURL, http.StatusCreated)
-}
-
-func (i *gatewayHandler) addUserHeaders(w http.ResponseWriter) {
- for k, v := range i.config.Headers {
- w.Header()[k] = v
- }
-}
-
-func addCacheControlHeaders(w http.ResponseWriter, r *http.Request, contentPath ipath.Path, fileCid cid.Cid) (modtime time.Time) {
- // Set Etag to based on CID (override whatever was set before)
- w.Header().Set("Etag", getEtag(r, fileCid))
-
- // Set Cache-Control and Last-Modified based on contentPath properties
- if contentPath.Mutable() {
- // mutable namespaces such as /ipns/ can't be cached forever
-
- /* For now we set Last-Modified to Now() to leverage caching heuristics built into modern browsers:
- * https://github.com/ipfs/kubo/pull/8074#pullrequestreview-645196768
- * but we should not set it to fake values and use Cache-Control based on TTL instead */
- modtime = time.Now()
-
- // TODO: set Cache-Control based on TTL of IPNS/DNSLink: https://github.com/ipfs/kubo/issues/1818#issuecomment-1015849462
- // TODO: set Last-Modified based on /ipns/ publishing timestamp?
- } else {
- // immutable! CACHE ALL THE THINGS, FOREVER! wolololol
- w.Header().Set("Cache-Control", immutableCacheControl)
-
- // Set modtime to 'zero time' to disable Last-Modified header (superseded by Cache-Control)
- modtime = noModtime
-
- // TODO: set Last-Modified? - TBD - /ipfs/ modification metadata is present in unixfs 1.5 https://github.com/ipfs/kubo/issues/6920?
- }
-
- return modtime
-}
-
-// Set Content-Disposition if filename URL query param is present, return preferred filename
-func addContentDispositionHeader(w http.ResponseWriter, r *http.Request, contentPath ipath.Path) string {
- /* This logic enables:
- * - creation of HTML links that trigger "Save As.." dialog instead of being rendered by the browser
- * - overriding the filename used when saving subresource assets on HTML page
- * - providing a default filename for HTTP clients when downloading direct /ipfs/CID without any subpath
- */
-
- // URL param ?filename=cat.jpg triggers Content-Disposition: [..] filename
- // which impacts default name used in "Save As.." dialog
- name := getFilename(contentPath)
- urlFilename := r.URL.Query().Get("filename")
- if urlFilename != "" {
- disposition := "inline"
- // URL param ?download=true triggers Content-Disposition: [..] attachment
- // which skips rendering and forces "Save As.." dialog in browsers
- if r.URL.Query().Get("download") == "true" {
- disposition = "attachment"
- }
- setContentDispositionHeader(w, urlFilename, disposition)
- name = urlFilename
- }
- return name
-}
-
-// Set Content-Disposition to arbitrary filename and disposition
-func setContentDispositionHeader(w http.ResponseWriter, filename string, disposition string) {
- utf8Name := url.PathEscape(filename)
- asciiName := url.PathEscape(onlyASCII.ReplaceAllLiteralString(filename, "_"))
- w.Header().Set("Content-Disposition", fmt.Sprintf("%s; filename=\"%s\"; filename*=UTF-8''%s", disposition, asciiName, utf8Name))
-}
-
-// Set X-Ipfs-Roots with logical CID array for efficient HTTP cache invalidation.
-func (i *gatewayHandler) buildIpfsRootsHeader(contentPath string, r *http.Request) (string, error) {
- /*
- These are logical roots where each CID represent one path segment
- and resolves to either a directory or the root block of a file.
- The main purpose of this header is allow HTTP caches to do smarter decisions
- around cache invalidation (eg. keep specific subdirectory/file if it did not change)
-
- A good example is Wikipedia, which is HAMT-sharded, but we only care about
- logical roots that represent each segment of the human-readable content
- path:
-
- Given contentPath = /ipns/en.wikipedia-on-ipfs.org/wiki/Block_of_Wikipedia_in_Turkey
- rootCidList is a generated by doing `ipfs resolve -r` on each sub path:
- /ipns/en.wikipedia-on-ipfs.org → bafybeiaysi4s6lnjev27ln5icwm6tueaw2vdykrtjkwiphwekaywqhcjze
- /ipns/en.wikipedia-on-ipfs.org/wiki/ → bafybeihn2f7lhumh4grizksi2fl233cyszqadkn424ptjajfenykpsaiw4
- /ipns/en.wikipedia-on-ipfs.org/wiki/Block_of_Wikipedia_in_Turkey → bafkreibn6euazfvoghepcm4efzqx5l3hieof2frhp254hio5y7n3hv5rma
-
- The result is an ordered array of values:
- X-Ipfs-Roots: bafybeiaysi4s6lnjev27ln5icwm6tueaw2vdykrtjkwiphwekaywqhcjze,bafybeihn2f7lhumh4grizksi2fl233cyszqadkn424ptjajfenykpsaiw4,bafkreibn6euazfvoghepcm4efzqx5l3hieof2frhp254hio5y7n3hv5rma
-
- Note that while the top one will change every time any article is changed,
- the last root (responsible for specific article) may not change at all.
- */
- var sp strings.Builder
- var pathRoots []string
- pathSegments := strings.Split(contentPath[6:], "/")
- sp.WriteString(contentPath[:5]) // /ipfs or /ipns
- for _, root := range pathSegments {
- if root == "" {
- continue
- }
- sp.WriteString("/")
- sp.WriteString(root)
- resolvedSubPath, err := i.api.ResolvePath(r.Context(), ipath.New(sp.String()))
- if err != nil {
- return "", err
- }
- pathRoots = append(pathRoots, resolvedSubPath.Cid().String())
- }
- rootCidList := strings.Join(pathRoots, ",") // convention from rfc2616#sec4.2
- return rootCidList, nil
-}
-
-func webRequestError(w http.ResponseWriter, err *requestError) {
- webError(w, err.Message, err.Err, err.StatusCode)
-}
-
-func webError(w http.ResponseWriter, message string, err error, defaultCode int) {
- if _, ok := err.(resolver.ErrNoLink); ok {
- webErrorWithCode(w, message, err, http.StatusNotFound)
- } else if err == routing.ErrNotFound {
- webErrorWithCode(w, message, err, http.StatusNotFound)
- } else if ipld.IsNotFound(err) {
- webErrorWithCode(w, message, err, http.StatusNotFound)
- } else if err == context.DeadlineExceeded {
- webErrorWithCode(w, message, err, http.StatusRequestTimeout)
- } else {
- webErrorWithCode(w, message, err, defaultCode)
- }
-}
-
-func webErrorWithCode(w http.ResponseWriter, message string, err error, code int) {
- http.Error(w, fmt.Sprintf("%s: %s", message, err), code)
- if code >= 500 {
- log.Warnf("server error: %s: %s", message, err)
- }
-}
-
-// return a 500 error and log
-func internalWebError(w http.ResponseWriter, err error) {
- webErrorWithCode(w, "internalWebError", err, http.StatusInternalServerError)
-}
-
-func getFilename(contentPath ipath.Path) string {
- s := contentPath.String()
- if (strings.HasPrefix(s, ipfsPathPrefix) || strings.HasPrefix(s, ipnsPathPrefix)) && strings.Count(gopath.Clean(s), "/") <= 2 {
- // Don't want to treat ipfs.io in /ipns/ipfs.io as a filename.
- return ""
- }
- return gopath.Base(s)
-}
-
-// etagMatch evaluates if we can respond with HTTP 304 Not Modified
-// It supports multiple weak and strong etags passed in If-None-Matc stringh
-// including the wildcard one.
-func etagMatch(ifNoneMatchHeader string, cidEtag string, dirEtag string) bool {
- buf := ifNoneMatchHeader
- for {
- buf = textproto.TrimString(buf)
- if len(buf) == 0 {
- break
- }
- if buf[0] == ',' {
- buf = buf[1:]
- continue
- }
- // If-None-Match: * should match against any etag
- if buf[0] == '*' {
- return true
- }
- etag, remain := scanETag(buf)
- if etag == "" {
- break
- }
- // Check for match both strong and weak etags
- if etagWeakMatch(etag, cidEtag) || etagWeakMatch(etag, dirEtag) {
- return true
- }
- buf = remain
- }
- return false
-}
-
-// scanETag determines if a syntactically valid ETag is present at s. If so,
-// the ETag and remaining text after consuming ETag is returned. Otherwise,
-// it returns "", "".
-// (This is the same logic as one executed inside of http.ServeContent)
-func scanETag(s string) (etag string, remain string) {
- s = textproto.TrimString(s)
- start := 0
- if strings.HasPrefix(s, "W/") {
- start = 2
- }
- if len(s[start:]) < 2 || s[start] != '"' {
- return "", ""
- }
- // ETag is either W/"text" or "text".
- // See RFC 7232 2.3.
- for i := start + 1; i < len(s); i++ {
- c := s[i]
- switch {
- // Character values allowed in ETags.
- case c == 0x21 || c >= 0x23 && c <= 0x7E || c >= 0x80:
- case c == '"':
- return s[:i+1], s[i+1:]
- default:
- return "", ""
- }
- }
- return "", ""
-}
-
-// etagWeakMatch reports whether a and b match using weak ETag comparison.
-func etagWeakMatch(a, b string) bool {
- return strings.TrimPrefix(a, "W/") == strings.TrimPrefix(b, "W/")
-}
-
-// generate Etag value based on HTTP request and CID
-func getEtag(r *http.Request, cid cid.Cid) string {
- prefix := `"`
- suffix := `"`
- responseFormat, _, err := customResponseFormat(r)
- if err == nil && responseFormat != "" {
- // application/vnd.ipld.foo → foo
- // application/x-bar → x-bar
- shortFormat := responseFormat[strings.LastIndexAny(responseFormat, "/.")+1:]
- // Etag: "cid.shortFmt" (gives us nice compression together with Content-Disposition in block (raw) and car responses)
- suffix = `.` + shortFormat + suffix
- }
- // TODO: include selector suffix when https://github.com/ipfs/kubo/issues/8769 lands
- return prefix + cid.String() + suffix
-}
-
-// return explicit response format if specified in request as query parameter or via Accept HTTP header
-func customResponseFormat(r *http.Request) (mediaType string, params map[string]string, err error) {
- if formatParam := r.URL.Query().Get("format"); formatParam != "" {
- // translate query param to a content type
- switch formatParam {
- case "raw":
- return "application/vnd.ipld.raw", nil, nil
- case "car":
- return "application/vnd.ipld.car", nil, nil
- case "tar":
- return "application/x-tar", nil, nil
- case "json":
- return "application/json", nil, nil
- case "cbor":
- return "application/cbor", nil, nil
- case "dag-json":
- return "application/vnd.ipld.dag-json", nil, nil
- case "dag-cbor":
- return "application/vnd.ipld.dag-cbor", nil, nil
- case "ipns-record":
- return "application/vnd.ipfs.ipns-record", nil, nil
- }
- }
- // Browsers and other user agents will send Accept header with generic types like:
- // Accept:text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8
- // We only care about explicit, vendor-specific content-types and respond to the first match (in order).
- // TODO: make this RFC compliant and respect weights (eg. return CAR for Accept:application/vnd.ipld.dag-json;q=0.1,application/vnd.ipld.car;q=0.2)
- for _, header := range r.Header.Values("Accept") {
- for _, value := range strings.Split(header, ",") {
- accept := strings.TrimSpace(value)
- // respond to the very first matching content type
- if strings.HasPrefix(accept, "application/vnd.ipld") ||
- strings.HasPrefix(accept, "application/x-tar") ||
- strings.HasPrefix(accept, "application/json") ||
- strings.HasPrefix(accept, "application/cbor") ||
- strings.HasPrefix(accept, "application/vnd.ipfs") {
- mediatype, params, err := mime.ParseMediaType(accept)
- if err != nil {
- return "", nil, err
- }
- return mediatype, params, nil
- }
- }
- }
- // If none of special-cased content types is found, return empty string
- // to indicate default, implicit UnixFS response should be prepared
- return "", nil, nil
-}
-
-// returns unquoted path with all special characters revealed as \u codes
-func debugStr(path string) string {
- q := fmt.Sprintf("%+q", path)
- if len(q) >= 3 {
- q = q[1 : len(q)-1]
- }
- return q
-}
-
-// Resolve the provided contentPath including any special handling related to
-// the requested responseFormat. Returned ok flag indicates if gateway handler
-// should continue processing the request.
-func (i *gatewayHandler) handlePathResolution(w http.ResponseWriter, r *http.Request, responseFormat string, contentPath ipath.Path, logger *zap.SugaredLogger) (resolvedPath ipath.Resolved, newContentPath ipath.Path, ok bool) {
- // Attempt to resolve the provided path.
- resolvedPath, err := i.api.ResolvePath(r.Context(), contentPath)
-
- switch err {
- case nil:
- return resolvedPath, contentPath, true
- case coreiface.ErrOffline:
- webError(w, "ipfs resolve -r "+debugStr(contentPath.String()), err, http.StatusServiceUnavailable)
- return nil, nil, false
- default:
- // The path can't be resolved.
- if isUnixfsResponseFormat(responseFormat) {
- // If we have origin isolation (subdomain gw, DNSLink website),
- // and response type is UnixFS (default for website hosting)
- // check for presence of _redirects file and apply rules defined there.
- // See: https://github.com/ipfs/specs/pull/290
- if hasOriginIsolation(r) {
- resolvedPath, newContentPath, ok, hadMatchingRule := i.serveRedirectsIfPresent(w, r, resolvedPath, contentPath, logger)
- if hadMatchingRule {
- logger.Debugw("applied a rule from _redirects file")
- return resolvedPath, newContentPath, ok
- }
- }
-
- // if Accept is text/html, see if ipfs-404.html is present
- // This logic isn't documented and will likely be removed at some point.
- // Any 404 logic in _redirects above will have already run by this time, so it's really an extra fall back
- if i.serveLegacy404IfPresent(w, r, contentPath) {
- logger.Debugw("served legacy 404")
- return nil, nil, false
- }
- }
-
- // Note: webError will replace http.StatusBadRequest with StatusNotFound if necessary
- webError(w, "ipfs resolve -r "+debugStr(contentPath.String()), err, http.StatusBadRequest)
- return nil, nil, false
- }
-}
-
-// Detect 'Cache-Control: only-if-cached' in request and return data if it is already in the local datastore.
-// https://github.com/ipfs/specs/blob/main/http-gateways/PATH_GATEWAY.md#cache-control-request-header
-func (i *gatewayHandler) handleOnlyIfCached(w http.ResponseWriter, r *http.Request, contentPath ipath.Path, logger *zap.SugaredLogger) (requestHandled bool) {
- if r.Header.Get("Cache-Control") == "only-if-cached" {
- _, err := i.offlineAPI.Block().Stat(r.Context(), contentPath)
- if err != nil {
- if r.Method == http.MethodHead {
- w.WriteHeader(http.StatusPreconditionFailed)
- return true
- }
- errMsg := fmt.Sprintf("%q not in local datastore", contentPath.String())
- http.Error(w, errMsg, http.StatusPreconditionFailed)
- return true
- }
- if r.Method == http.MethodHead {
- w.WriteHeader(http.StatusOK)
- return true
- }
- }
- return false
-}
-
-func handleUnsupportedHeaders(r *http.Request) (err *requestError) {
- // X-Ipfs-Gateway-Prefix was removed (https://github.com/ipfs/kubo/issues/7702)
- // TODO: remove this after go-ipfs 0.13 ships
- if prfx := r.Header.Get("X-Ipfs-Gateway-Prefix"); prfx != "" {
- err := fmt.Errorf("X-Ipfs-Gateway-Prefix support was removed: https://github.com/ipfs/kubo/issues/7702")
- return newRequestError("unsupported HTTP header", err, http.StatusBadRequest)
- }
- return nil
-}
-
-// ?uri query param support for requests produced by web browsers
-// via navigator.registerProtocolHandler Web API
-// https://developer.mozilla.org/en-US/docs/Web/API/Navigator/registerProtocolHandler
-// TLDR: redirect /ipfs/?uri=ipfs%3A%2F%2Fcid%3Fquery%3Dval to /ipfs/cid?query=val
-func handleProtocolHandlerRedirect(w http.ResponseWriter, r *http.Request, logger *zap.SugaredLogger) (requestHandled bool) {
- if uriParam := r.URL.Query().Get("uri"); uriParam != "" {
- u, err := url.Parse(uriParam)
- if err != nil {
- webError(w, "failed to parse uri query parameter", err, http.StatusBadRequest)
- return true
- }
- if u.Scheme != "ipfs" && u.Scheme != "ipns" {
- webError(w, "uri query parameter scheme must be ipfs or ipns", err, http.StatusBadRequest)
- return true
- }
- path := u.Path
- if u.RawQuery != "" { // preserve query if present
- path = path + "?" + u.RawQuery
- }
-
- redirectURL := gopath.Join("/", u.Scheme, u.Host, path)
- logger.Debugw("uri param, redirect", "to", redirectURL, "status", http.StatusMovedPermanently)
- http.Redirect(w, r, redirectURL, http.StatusMovedPermanently)
- return true
- }
-
- return false
-}
-
-// Disallow Service Worker registration on namespace roots
-// https://github.com/ipfs/kubo/issues/4025
-func handleServiceWorkerRegistration(r *http.Request) (err *requestError) {
- if r.Header.Get("Service-Worker") == "script" {
- matched, _ := regexp.MatchString(`^/ip[fn]s/[^/]+$`, r.URL.Path)
- if matched {
- err := fmt.Errorf("registration is not allowed for this scope")
- return newRequestError("navigator.serviceWorker", err, http.StatusBadRequest)
- }
- }
-
- return nil
-}
-
-// Attempt to fix redundant /ipfs/ namespace as long as resulting
-// 'intended' path is valid. This is in case gremlins were tickled
-// wrong way and user ended up at /ipfs/ipfs/{cid} or /ipfs/ipns/{id}
-// like in bafybeien3m7mdn6imm425vc2s22erzyhbvk5n3ofzgikkhmdkh5cuqbpbq :^))
-func handleSuperfluousNamespace(w http.ResponseWriter, r *http.Request, contentPath ipath.Path) (requestHandled bool) {
- // If the path is valid, there's nothing to do
- if pathErr := contentPath.IsValid(); pathErr == nil {
- return false
- }
-
- // If there's no superflous namespace, there's nothing to do
- if !(strings.HasPrefix(r.URL.Path, "/ipfs/ipfs/") || strings.HasPrefix(r.URL.Path, "/ipfs/ipns/")) {
- return false
- }
-
- // Attempt to fix the superflous namespace
- intendedPath := ipath.New(strings.TrimPrefix(r.URL.Path, "/ipfs"))
- if err := intendedPath.IsValid(); err != nil {
- webError(w, "invalid ipfs path", err, http.StatusBadRequest)
- return true
- }
- intendedURL := intendedPath.String()
- if r.URL.RawQuery != "" {
- // we render HTML, so ensure query entries are properly escaped
- q, _ := url.ParseQuery(r.URL.RawQuery)
- intendedURL = intendedURL + "?" + q.Encode()
- }
- // return HTTP 400 (Bad Request) with HTML error page that:
- // - points at correct canonical path via header
- // - displays human-readable error
- // - redirects to intendedURL after a short delay
-
- w.WriteHeader(http.StatusBadRequest)
- if err := redirectTemplate.Execute(w, redirectTemplateData{
- RedirectURL: intendedURL,
- SuggestedPath: intendedPath.String(),
- ErrorMsg: fmt.Sprintf("invalid path: %q should be %q", r.URL.Path, intendedPath.String()),
- }); err != nil {
- webError(w, "failed to redirect when fixing superfluous namespace", err, http.StatusBadRequest)
- }
-
- return true
-}
-
-func (i *gatewayHandler) handleGettingFirstBlock(r *http.Request, begin time.Time, contentPath ipath.Path, resolvedPath ipath.Resolved) *requestError {
- // Update the global metric of the time it takes to read the final root block of the requested resource
- // NOTE: for legacy reasons this happens before we go into content-type specific code paths
- _, err := i.api.Block().Get(r.Context(), resolvedPath)
- if err != nil {
- return newRequestError("ipfs block get "+resolvedPath.Cid().String(), err, http.StatusInternalServerError)
- }
- ns := contentPath.Namespace()
- timeToGetFirstContentBlock := time.Since(begin).Seconds()
- i.unixfsGetMetric.WithLabelValues(ns).Observe(timeToGetFirstContentBlock) // deprecated, use firstContentBlockGetMetric instead
- i.firstContentBlockGetMetric.WithLabelValues(ns).Observe(timeToGetFirstContentBlock)
- return nil
-}
-
-func (i *gatewayHandler) setCommonHeaders(w http.ResponseWriter, r *http.Request, contentPath ipath.Path) *requestError {
- i.addUserHeaders(w) // ok, _now_ write user's headers.
- w.Header().Set("X-Ipfs-Path", contentPath.String())
-
- if rootCids, err := i.buildIpfsRootsHeader(contentPath.String(), r); err == nil {
- w.Header().Set("X-Ipfs-Roots", rootCids)
- } else { // this should never happen, as we resolved the contentPath already
- return newRequestError("error while resolving X-Ipfs-Roots", err, http.StatusInternalServerError)
- }
-
- return nil
-}
diff --git a/core/corehttp/gateway_handler_block.go b/core/corehttp/gateway_handler_block.go
deleted file mode 100644
index 3bf7c76be2d..00000000000
--- a/core/corehttp/gateway_handler_block.go
+++ /dev/null
@@ -1,55 +0,0 @@
-package corehttp
-
-import (
- "bytes"
- "context"
- "io"
- "net/http"
- "time"
-
- ipath "github.com/ipfs/interface-go-ipfs-core/path"
- "github.com/ipfs/kubo/tracing"
- "go.opentelemetry.io/otel/attribute"
- "go.opentelemetry.io/otel/trace"
-)
-
-// serveRawBlock returns bytes behind a raw block
-func (i *gatewayHandler) serveRawBlock(ctx context.Context, w http.ResponseWriter, r *http.Request, resolvedPath ipath.Resolved, contentPath ipath.Path, begin time.Time) {
- ctx, span := tracing.Span(ctx, "Gateway", "ServeRawBlock", trace.WithAttributes(attribute.String("path", resolvedPath.String())))
- defer span.End()
- blockCid := resolvedPath.Cid()
- blockReader, err := i.api.Block().Get(ctx, resolvedPath)
- if err != nil {
- webError(w, "ipfs block get "+blockCid.String(), err, http.StatusInternalServerError)
- return
- }
- block, err := io.ReadAll(blockReader)
- if err != nil {
- webError(w, "ipfs block get "+blockCid.String(), err, http.StatusInternalServerError)
- return
- }
- content := bytes.NewReader(block)
-
- // Set Content-Disposition
- var name string
- if urlFilename := r.URL.Query().Get("filename"); urlFilename != "" {
- name = urlFilename
- } else {
- name = blockCid.String() + ".bin"
- }
- setContentDispositionHeader(w, name, "attachment")
-
- // Set remaining headers
- modtime := addCacheControlHeaders(w, r, contentPath, blockCid)
- w.Header().Set("Content-Type", "application/vnd.ipld.raw")
- w.Header().Set("X-Content-Type-Options", "nosniff") // no funny business in the browsers :^)
-
- // ServeContent will take care of
- // If-None-Match+Etag, Content-Length and range requests
- _, dataSent, _ := ServeContent(w, r, name, modtime, content)
-
- if dataSent {
- // Update metrics
- i.rawBlockGetMetric.WithLabelValues(contentPath.Namespace()).Observe(time.Since(begin).Seconds())
- }
-}
diff --git a/core/corehttp/gateway_handler_car.go b/core/corehttp/gateway_handler_car.go
deleted file mode 100644
index 9f704d6ca95..00000000000
--- a/core/corehttp/gateway_handler_car.go
+++ /dev/null
@@ -1,99 +0,0 @@
-package corehttp
-
-import (
- "context"
- "fmt"
- "net/http"
- "time"
-
- cid "github.com/ipfs/go-cid"
- blocks "github.com/ipfs/go-libipfs/blocks"
- coreiface "github.com/ipfs/interface-go-ipfs-core"
- ipath "github.com/ipfs/interface-go-ipfs-core/path"
- "github.com/ipfs/kubo/tracing"
- gocar "github.com/ipld/go-car"
- selectorparse "github.com/ipld/go-ipld-prime/traversal/selector/parse"
- "go.opentelemetry.io/otel/attribute"
- "go.opentelemetry.io/otel/trace"
-)
-
-// serveCAR returns a CAR stream for specific DAG+selector
-func (i *gatewayHandler) serveCAR(ctx context.Context, w http.ResponseWriter, r *http.Request, resolvedPath ipath.Resolved, contentPath ipath.Path, carVersion string, begin time.Time) {
- ctx, span := tracing.Span(ctx, "Gateway", "ServeCAR", trace.WithAttributes(attribute.String("path", resolvedPath.String())))
- defer span.End()
- ctx, cancel := context.WithCancel(ctx)
- defer cancel()
-
- switch carVersion {
- case "": // noop, client does not care about version
- case "1": // noop, we support this
- default:
- err := fmt.Errorf("only version=1 is supported")
- webError(w, "unsupported CAR version", err, http.StatusBadRequest)
- return
- }
- rootCid := resolvedPath.Cid()
-
- // Set Content-Disposition
- var name string
- if urlFilename := r.URL.Query().Get("filename"); urlFilename != "" {
- name = urlFilename
- } else {
- name = rootCid.String() + ".car"
- }
- setContentDispositionHeader(w, name, "attachment")
-
- // Set Cache-Control (same logic as for a regular files)
- addCacheControlHeaders(w, r, contentPath, rootCid)
-
- // Weak Etag W/ because we can't guarantee byte-for-byte identical
- // responses, but still want to benefit from HTTP Caching. Two CAR
- // responses for the same CID and selector will be logically equivalent,
- // but when CAR is streamed, then in theory, blocks may arrive from
- // datastore in non-deterministic order.
- etag := `W/` + getEtag(r, rootCid)
- w.Header().Set("Etag", etag)
-
- // Finish early if Etag match
- if r.Header.Get("If-None-Match") == etag {
- w.WriteHeader(http.StatusNotModified)
- return
- }
-
- // Make it clear we don't support range-requests over a car stream
- // Partial downloads and resumes should be handled using requests for
- // sub-DAGs and IPLD selectors: https://github.com/ipfs/go-ipfs/issues/8769
- w.Header().Set("Accept-Ranges", "none")
-
- w.Header().Set("Content-Type", "application/vnd.ipld.car; version=1")
- w.Header().Set("X-Content-Type-Options", "nosniff") // no funny business in the browsers :^)
-
- // Same go-car settings as dag.export command
- store := dagStore{dag: i.api.Dag(), ctx: ctx}
-
- // TODO: support selectors passed as request param: https://github.com/ipfs/kubo/issues/8769
- dag := gocar.Dag{Root: rootCid, Selector: selectorparse.CommonSelector_ExploreAllRecursively}
- car := gocar.NewSelectiveCar(ctx, store, []gocar.Dag{dag}, gocar.TraverseLinksOnlyOnce())
-
- if err := car.Write(w); err != nil {
- // We return error as a trailer, however it is not something browsers can access
- // (https://github.com/mdn/browser-compat-data/issues/14703)
- // Due to this, we suggest client always verify that
- // the received CAR stream response is matching requested DAG selector
- w.Header().Set("X-Stream-Error", err.Error())
- return
- }
-
- // Update metrics
- i.carStreamGetMetric.WithLabelValues(contentPath.Namespace()).Observe(time.Since(begin).Seconds())
-}
-
-// FIXME(@Jorropo): https://github.com/ipld/go-car/issues/315
-type dagStore struct {
- dag coreiface.APIDagService
- ctx context.Context
-}
-
-func (ds dagStore) Get(_ context.Context, c cid.Cid) (blocks.Block, error) {
- return ds.dag.Get(ds.ctx, c)
-}
diff --git a/core/corehttp/gateway_handler_codec.go b/core/corehttp/gateway_handler_codec.go
deleted file mode 100644
index 93e9593b7b3..00000000000
--- a/core/corehttp/gateway_handler_codec.go
+++ /dev/null
@@ -1,257 +0,0 @@
-package corehttp
-
-import (
- "bytes"
- "context"
- "fmt"
- "html"
- "io"
- "net/http"
- "strings"
- "time"
-
- cid "github.com/ipfs/go-cid"
- ipldlegacy "github.com/ipfs/go-ipld-legacy"
- ipath "github.com/ipfs/interface-go-ipfs-core/path"
- "github.com/ipfs/kubo/assets"
- dih "github.com/ipfs/kubo/assets/dag-index-html"
- "github.com/ipfs/kubo/tracing"
- "github.com/ipld/go-ipld-prime"
- "github.com/ipld/go-ipld-prime/multicodec"
- mc "github.com/multiformats/go-multicodec"
- "go.opentelemetry.io/otel/attribute"
- "go.opentelemetry.io/otel/trace"
-)
-
-// codecToContentType maps the supported IPLD codecs to the HTTP Content
-// Type they should have.
-var codecToContentType = map[mc.Code]string{
- mc.Json: "application/json",
- mc.Cbor: "application/cbor",
- mc.DagJson: "application/vnd.ipld.dag-json",
- mc.DagCbor: "application/vnd.ipld.dag-cbor",
-}
-
-// contentTypeToRaw maps the HTTP Content Type to the respective codec that
-// allows raw response without any conversion.
-var contentTypeToRaw = map[string][]mc.Code{
- "application/json": {mc.Json, mc.DagJson},
- "application/cbor": {mc.Cbor, mc.DagCbor},
-}
-
-// contentTypeToCodec maps the HTTP Content Type to the respective codec. We
-// only add here the codecs that we want to convert-to-from.
-var contentTypeToCodec = map[string]mc.Code{
- "application/vnd.ipld.dag-json": mc.DagJson,
- "application/vnd.ipld.dag-cbor": mc.DagCbor,
-}
-
-// contentTypeToExtension maps the HTTP Content Type to the respective file
-// extension, used in Content-Disposition header when downloading the file.
-var contentTypeToExtension = map[string]string{
- "application/json": ".json",
- "application/vnd.ipld.dag-json": ".json",
- "application/cbor": ".cbor",
- "application/vnd.ipld.dag-cbor": ".cbor",
-}
-
-func (i *gatewayHandler) serveCodec(ctx context.Context, w http.ResponseWriter, r *http.Request, resolvedPath ipath.Resolved, contentPath ipath.Path, begin time.Time, requestedContentType string) {
- ctx, span := tracing.Span(ctx, "Gateway", "ServeCodec", trace.WithAttributes(attribute.String("path", resolvedPath.String()), attribute.String("requestedContentType", requestedContentType)))
- defer span.End()
-
- cidCodec := mc.Code(resolvedPath.Cid().Prefix().Codec)
- responseContentType := requestedContentType
-
- // If the resolved path still has some remainder, return error for now.
- // TODO: handle this when we have IPLD Patch (https://ipld.io/specs/patch/) via HTTP PUT
- // TODO: (depends on https://github.com/ipfs/kubo/issues/4801 and https://github.com/ipfs/kubo/issues/4782)
- if resolvedPath.Remainder() != "" {
- path := strings.TrimSuffix(resolvedPath.String(), resolvedPath.Remainder())
- err := fmt.Errorf("%q of %q could not be returned: reading IPLD Kinds other than Links (CBOR Tag 42) is not implemented: try reading %q instead", resolvedPath.Remainder(), resolvedPath.String(), path)
- webError(w, "unsupported pathing", err, http.StatusNotImplemented)
- return
- }
-
- // If no explicit content type was requested, the response will have one based on the codec from the CID
- if requestedContentType == "" {
- cidContentType, ok := codecToContentType[cidCodec]
- if !ok {
- // Should not happen unless function is called with wrong parameters.
- err := fmt.Errorf("content type not found for codec: %v", cidCodec)
- webError(w, "internal error", err, http.StatusInternalServerError)
- return
- }
- responseContentType = cidContentType
- }
-
- // Set HTTP headers (for caching etc)
- modtime := addCacheControlHeaders(w, r, contentPath, resolvedPath.Cid())
- name := setCodecContentDisposition(w, r, resolvedPath, responseContentType)
- w.Header().Set("Content-Type", responseContentType)
- w.Header().Set("X-Content-Type-Options", "nosniff")
-
- // No content type is specified by the user (via Accept, or format=). However,
- // we support this format. Let's handle it.
- if requestedContentType == "" {
- isDAG := cidCodec == mc.DagJson || cidCodec == mc.DagCbor
- acceptsHTML := strings.Contains(r.Header.Get("Accept"), "text/html")
- download := r.URL.Query().Get("download") == "true"
-
- if isDAG && acceptsHTML && !download {
- i.serveCodecHTML(ctx, w, r, resolvedPath, contentPath)
- } else {
- // This covers CIDs with codec 'json' and 'cbor' as those do not have
- // an explicit requested content type.
- i.serveCodecRaw(ctx, w, r, resolvedPath, contentPath, name, modtime)
- }
-
- return
- }
-
- // If DAG-JSON or DAG-CBOR was requested using corresponding plain content type
- // return raw block as-is, without conversion
- skipCodecs, ok := contentTypeToRaw[requestedContentType]
- if ok {
- for _, skipCodec := range skipCodecs {
- if skipCodec == cidCodec {
- i.serveCodecRaw(ctx, w, r, resolvedPath, contentPath, name, modtime)
- return
- }
- }
- }
-
- // Otherwise, the user has requested a specific content type (a DAG-* variant).
- // Let's first get the codecs that can be used with this content type.
- toCodec, ok := contentTypeToCodec[requestedContentType]
- if !ok {
- // This is never supposed to happen unless function is called with wrong parameters.
- err := fmt.Errorf("unsupported content type: %s", requestedContentType)
- webError(w, err.Error(), err, http.StatusInternalServerError)
- return
- }
-
- // This handles DAG-* conversions and validations.
- i.serveCodecConverted(ctx, w, r, resolvedPath, contentPath, toCodec, modtime)
-}
-
-func (i *gatewayHandler) serveCodecHTML(ctx context.Context, w http.ResponseWriter, r *http.Request, resolvedPath ipath.Resolved, contentPath ipath.Path) {
- // A HTML directory index will be presented, be sure to set the correct
- // type instead of relying on autodetection (which may fail).
- w.Header().Set("Content-Type", "text/html")
-
- // Clear Content-Disposition -- we want HTML to be rendered inline
- w.Header().Del("Content-Disposition")
-
- // Generated index requires custom Etag (output may change between Kubo versions)
- dagEtag := getDagIndexEtag(resolvedPath.Cid())
- w.Header().Set("Etag", dagEtag)
-
- // Remove Cache-Control for now to match UnixFS dir-index-html responses
- // (we don't want browser to cache HTML forever)
- // TODO: if we ever change behavior for UnixFS dir listings, same changes should be applied here
- w.Header().Del("Cache-Control")
-
- cidCodec := mc.Code(resolvedPath.Cid().Prefix().Codec)
- if err := dih.DagIndexTemplate.Execute(w, dih.DagIndexTemplateData{
- Path: contentPath.String(),
- CID: resolvedPath.Cid().String(),
- CodecName: cidCodec.String(),
- CodecHex: fmt.Sprintf("0x%x", uint64(cidCodec)),
- }); err != nil {
- webError(w, "failed to generate HTML listing for this DAG: try fetching raw block with ?format=raw", err, http.StatusInternalServerError)
- }
-}
-
-// serveCodecRaw returns the raw block without any conversion
-func (i *gatewayHandler) serveCodecRaw(ctx context.Context, w http.ResponseWriter, r *http.Request, resolvedPath ipath.Resolved, contentPath ipath.Path, name string, modtime time.Time) {
- blockCid := resolvedPath.Cid()
- blockReader, err := i.api.Block().Get(ctx, resolvedPath)
- if err != nil {
- webError(w, "ipfs block get "+blockCid.String(), err, http.StatusInternalServerError)
- return
- }
- block, err := io.ReadAll(blockReader)
- if err != nil {
- webError(w, "ipfs block get "+blockCid.String(), err, http.StatusInternalServerError)
- return
- }
- content := bytes.NewReader(block)
-
- // ServeContent will take care of
- // If-None-Match+Etag, Content-Length and range requests
- _, _, _ = ServeContent(w, r, name, modtime, content)
-}
-
-// serveCodecConverted returns payload converted to codec specified in toCodec
-func (i *gatewayHandler) serveCodecConverted(ctx context.Context, w http.ResponseWriter, r *http.Request, resolvedPath ipath.Resolved, contentPath ipath.Path, toCodec mc.Code, modtime time.Time) {
- obj, err := i.api.Dag().Get(ctx, resolvedPath.Cid())
- if err != nil {
- webError(w, "ipfs dag get "+html.EscapeString(resolvedPath.String()), err, http.StatusInternalServerError)
- return
- }
-
- universal, ok := obj.(ipldlegacy.UniversalNode)
- if !ok {
- err = fmt.Errorf("%T is not a valid IPLD node", obj)
- webError(w, err.Error(), err, http.StatusInternalServerError)
- return
- }
- finalNode := universal.(ipld.Node)
-
- encoder, err := multicodec.LookupEncoder(uint64(toCodec))
- if err != nil {
- webError(w, err.Error(), err, http.StatusInternalServerError)
- return
- }
-
- // Ensure IPLD node conforms to the codec specification.
- var buf bytes.Buffer
- err = encoder(finalNode, &buf)
- if err != nil {
- webError(w, err.Error(), err, http.StatusInternalServerError)
- return
- }
-
- // Sets correct Last-Modified header. This code is borrowed from the standard
- // library (net/http/server.go) as we cannot use serveFile.
- if !(modtime.IsZero() || modtime.Equal(unixEpochTime)) {
- w.Header().Set("Last-Modified", modtime.UTC().Format(http.TimeFormat))
- }
-
- _, _ = w.Write(buf.Bytes())
-}
-
-func setCodecContentDisposition(w http.ResponseWriter, r *http.Request, resolvedPath ipath.Resolved, contentType string) string {
- var dispType, name string
-
- ext, ok := contentTypeToExtension[contentType]
- if !ok {
- // Should never happen.
- ext = ".bin"
- }
-
- if urlFilename := r.URL.Query().Get("filename"); urlFilename != "" {
- name = urlFilename
- } else {
- name = resolvedPath.Cid().String() + ext
- }
-
- // JSON should be inlined, but ?download=true should still override
- if r.URL.Query().Get("download") == "true" {
- dispType = "attachment"
- } else {
- switch ext {
- case ".json": // codecs that serialize to JSON can be rendered by browsers
- dispType = "inline"
- default: // everything else is assumed binary / opaque bytes
- dispType = "attachment"
- }
- }
-
- setContentDispositionHeader(w, name, dispType)
- return name
-}
-
-func getDagIndexEtag(dagCid cid.Cid) string {
- return `"DagIndex-` + assets.AssetHash + `_CID-` + dagCid.String() + `"`
-}
diff --git a/core/corehttp/gateway_handler_ipns_record.go b/core/corehttp/gateway_handler_ipns_record.go
deleted file mode 100644
index 16d9663fada..00000000000
--- a/core/corehttp/gateway_handler_ipns_record.go
+++ /dev/null
@@ -1,71 +0,0 @@
-package corehttp
-
-import (
- "context"
- "errors"
- "fmt"
- "net/http"
- "strings"
- "time"
-
- "github.com/gogo/protobuf/proto"
- ipns_pb "github.com/ipfs/go-ipns/pb"
- path "github.com/ipfs/go-path"
- ipath "github.com/ipfs/interface-go-ipfs-core/path"
- "go.uber.org/zap"
-)
-
-func (i *gatewayHandler) serveIpnsRecord(ctx context.Context, w http.ResponseWriter, r *http.Request, resolvedPath ipath.Resolved, contentPath ipath.Path, begin time.Time, logger *zap.SugaredLogger) {
- if contentPath.Namespace() != "ipns" {
- err := fmt.Errorf("%s is not an IPNS link", contentPath.String())
- webError(w, err.Error(), err, http.StatusBadRequest)
- return
- }
-
- key := contentPath.String()
- key = strings.TrimSuffix(key, "/")
- if strings.Count(key, "/") > 2 {
- err := errors.New("cannot find ipns key for subpath")
- webError(w, err.Error(), err, http.StatusBadRequest)
- return
- }
-
- rawRecord, err := i.api.Routing().Get(ctx, key)
- if err != nil {
- webError(w, err.Error(), err, http.StatusInternalServerError)
- return
- }
-
- var record ipns_pb.IpnsEntry
- err = proto.Unmarshal(rawRecord, &record)
- if err != nil {
- webError(w, err.Error(), err, http.StatusInternalServerError)
- return
- }
-
- // Set cache control headers based on the TTL set in the IPNS record. If the
- // TTL is not present, we use the Last-Modified tag. We are tracking IPNS
- // caching on: https://github.com/ipfs/kubo/issues/1818.
- // TODO: use addCacheControlHeaders once #1818 is fixed.
- w.Header().Set("Etag", getEtag(r, resolvedPath.Cid()))
- if record.Ttl != nil {
- seconds := int(time.Duration(*record.Ttl).Seconds())
- w.Header().Set("Cache-Control", fmt.Sprintf("public, max-age=%d", seconds))
- } else {
- w.Header().Set("Last-Modified", time.Now().UTC().Format(http.TimeFormat))
- }
-
- // Set Content-Disposition
- var name string
- if urlFilename := r.URL.Query().Get("filename"); urlFilename != "" {
- name = urlFilename
- } else {
- name = path.SplitList(key)[2] + ".ipns-record"
- }
- setContentDispositionHeader(w, name, "attachment")
-
- w.Header().Set("Content-Type", "application/vnd.ipfs.ipns-record")
- w.Header().Set("X-Content-Type-Options", "nosniff")
-
- _, _ = w.Write(rawRecord)
-}
diff --git a/core/corehttp/gateway_handler_tar.go b/core/corehttp/gateway_handler_tar.go
deleted file mode 100644
index 14edf4fbf5f..00000000000
--- a/core/corehttp/gateway_handler_tar.go
+++ /dev/null
@@ -1,92 +0,0 @@
-package corehttp
-
-import (
- "context"
- "html"
- "net/http"
- "time"
-
- "github.com/ipfs/go-libipfs/files"
- ipath "github.com/ipfs/interface-go-ipfs-core/path"
- "github.com/ipfs/kubo/tracing"
- "go.opentelemetry.io/otel/attribute"
- "go.opentelemetry.io/otel/trace"
- "go.uber.org/zap"
-)
-
-var unixEpochTime = time.Unix(0, 0)
-
-func (i *gatewayHandler) serveTAR(ctx context.Context, w http.ResponseWriter, r *http.Request, resolvedPath ipath.Resolved, contentPath ipath.Path, begin time.Time, logger *zap.SugaredLogger) {
- ctx, span := tracing.Span(ctx, "Gateway", "ServeTAR", trace.WithAttributes(attribute.String("path", resolvedPath.String())))
- defer span.End()
-
- ctx, cancel := context.WithCancel(ctx)
- defer cancel()
-
- // Get Unixfs file
- file, err := i.api.Unixfs().Get(ctx, resolvedPath)
- if err != nil {
- webError(w, "ipfs cat "+html.EscapeString(contentPath.String()), err, http.StatusBadRequest)
- return
- }
- defer file.Close()
-
- rootCid := resolvedPath.Cid()
-
- // Set Cache-Control and read optional Last-Modified time
- modtime := addCacheControlHeaders(w, r, contentPath, rootCid)
-
- // Weak Etag W/ because we can't guarantee byte-for-byte identical
- // responses, but still want to benefit from HTTP Caching. Two TAR
- // responses for the same CID will be logically equivalent,
- // but when TAR is streamed, then in theory, files and directories
- // may arrive in different order (depends on TAR lib and filesystem/inodes).
- etag := `W/` + getEtag(r, rootCid)
- w.Header().Set("Etag", etag)
-
- // Finish early if Etag match
- if r.Header.Get("If-None-Match") == etag {
- w.WriteHeader(http.StatusNotModified)
- return
- }
-
- // Set Content-Disposition
- var name string
- if urlFilename := r.URL.Query().Get("filename"); urlFilename != "" {
- name = urlFilename
- } else {
- name = rootCid.String() + ".tar"
- }
- setContentDispositionHeader(w, name, "attachment")
-
- // Construct the TAR writer
- tarw, err := files.NewTarWriter(w)
- if err != nil {
- webError(w, "could not build tar writer", err, http.StatusInternalServerError)
- return
- }
- defer tarw.Close()
-
- // Sets correct Last-Modified header. This code is borrowed from the standard
- // library (net/http/server.go) as we cannot use serveFile without throwing the entire
- // TAR into the memory first.
- if !(modtime.IsZero() || modtime.Equal(unixEpochTime)) {
- w.Header().Set("Last-Modified", modtime.UTC().Format(http.TimeFormat))
- }
-
- w.Header().Set("Content-Type", "application/x-tar")
- w.Header().Set("X-Content-Type-Options", "nosniff") // no funny business in the browsers :^)
-
- // The TAR has a top-level directory (or file) named by the CID.
- if err := tarw.WriteFile(file, rootCid.String()); err != nil {
- w.Header().Set("X-Stream-Error", err.Error())
- // Trailer headers do not work in web browsers
- // (see https://github.com/mdn/browser-compat-data/issues/14703)
- // and we have limited options around error handling in browser contexts.
- // To improve UX/DX, we finish response stream with error message, allowing client to
- // (1) detect error by having corrupted TAR
- // (2) be able to reason what went wrong by instecting the tail of TAR stream
- _, _ = w.Write([]byte(err.Error()))
- return
- }
-}
diff --git a/core/corehttp/gateway_handler_unixfs.go b/core/corehttp/gateway_handler_unixfs.go
deleted file mode 100644
index 045c0f81d22..00000000000
--- a/core/corehttp/gateway_handler_unixfs.go
+++ /dev/null
@@ -1,46 +0,0 @@
-package corehttp
-
-import (
- "context"
- "fmt"
- "html"
- "net/http"
- "time"
-
- "github.com/ipfs/go-libipfs/files"
- ipath "github.com/ipfs/interface-go-ipfs-core/path"
- "github.com/ipfs/kubo/tracing"
- "go.opentelemetry.io/otel/attribute"
- "go.opentelemetry.io/otel/trace"
- "go.uber.org/zap"
-)
-
-func (i *gatewayHandler) serveUnixFS(ctx context.Context, w http.ResponseWriter, r *http.Request, resolvedPath ipath.Resolved, contentPath ipath.Path, begin time.Time, logger *zap.SugaredLogger) {
- ctx, span := tracing.Span(ctx, "Gateway", "ServeUnixFS", trace.WithAttributes(attribute.String("path", resolvedPath.String())))
- defer span.End()
-
- // Handling UnixFS
- dr, err := i.api.Unixfs().Get(ctx, resolvedPath)
- if err != nil {
- webError(w, "ipfs cat "+html.EscapeString(contentPath.String()), err, http.StatusBadRequest)
- return
- }
- defer dr.Close()
-
- // Handling Unixfs file
- if f, ok := dr.(files.File); ok {
- logger.Debugw("serving unixfs file", "path", contentPath)
- i.serveFile(ctx, w, r, resolvedPath, contentPath, f, begin)
- return
- }
-
- // Handling Unixfs directory
- dir, ok := dr.(files.Directory)
- if !ok {
- internalWebError(w, fmt.Errorf("unsupported UnixFS type"))
- return
- }
-
- logger.Debugw("serving unixfs directory", "path", contentPath)
- i.serveDirectory(ctx, w, r, resolvedPath, contentPath, dir, begin, logger)
-}
diff --git a/core/corehttp/gateway_handler_unixfs__redirects.go b/core/corehttp/gateway_handler_unixfs__redirects.go
deleted file mode 100644
index 6906683a639..00000000000
--- a/core/corehttp/gateway_handler_unixfs__redirects.go
+++ /dev/null
@@ -1,287 +0,0 @@
-package corehttp
-
-import (
- "fmt"
- "io"
- "net/http"
- gopath "path"
- "strconv"
- "strings"
-
- redirects "github.com/ipfs/go-ipfs-redirects-file"
- "github.com/ipfs/go-libipfs/files"
- ipath "github.com/ipfs/interface-go-ipfs-core/path"
- "go.uber.org/zap"
-)
-
-// Resolving a UnixFS path involves determining if the provided `path.Path` exists and returning the `path.Resolved`
-// corresponding to that path. For UnixFS, path resolution is more involved.
-//
-// When a path under requested CID does not exist, Gateway will check if a `_redirects` file exists
-// underneath the root CID of the path, and apply rules defined there.
-// See sepcification introduced in: https://github.com/ipfs/specs/pull/290
-//
-// Scenario 1:
-// If a path exists, we always return the `path.Resolved` corresponding to that path, regardless of the existence of a `_redirects` file.
-//
-// Scenario 2:
-// If a path does not exist, usually we should return a `nil` resolution path and an error indicating that the path
-// doesn't exist. However, a `_redirects` file may exist and contain a redirect rule that redirects that path to a different path.
-// We need to evaluate the rule and perform the redirect if present.
-//
-// Scenario 3:
-// Another possibility is that the path corresponds to a rewrite rule (i.e. a rule with a status of 200).
-// In this case, we don't perform a redirect, but do need to return a `path.Resolved` and `path.Path` corresponding to
-// the rewrite destination path.
-//
-// Note that for security reasons, redirect rules are only processed when the request has origin isolation.
-// See https://github.com/ipfs/specs/pull/290 for more information.
-func (i *gatewayHandler) serveRedirectsIfPresent(w http.ResponseWriter, r *http.Request, resolvedPath ipath.Resolved, contentPath ipath.Path, logger *zap.SugaredLogger) (newResolvedPath ipath.Resolved, newContentPath ipath.Path, continueProcessing bool, hadMatchingRule bool) {
- redirectsFile := i.getRedirectsFile(r, contentPath, logger)
- if redirectsFile != nil {
- redirectRules, err := i.getRedirectRules(r, redirectsFile)
- if err != nil {
- internalWebError(w, err)
- return nil, nil, false, true
- }
-
- redirected, newPath, err := i.handleRedirectsFileRules(w, r, contentPath, redirectRules)
- if err != nil {
- err = fmt.Errorf("trouble processing _redirects file at %q: %w", redirectsFile.String(), err)
- internalWebError(w, err)
- return nil, nil, false, true
- }
-
- if redirected {
- return nil, nil, false, true
- }
-
- // 200 is treated as a rewrite, so update the path and continue
- if newPath != "" {
- // Reassign contentPath and resolvedPath since the URL was rewritten
- contentPath = ipath.New(newPath)
- resolvedPath, err = i.api.ResolvePath(r.Context(), contentPath)
- if err != nil {
- internalWebError(w, err)
- return nil, nil, false, true
- }
-
- return resolvedPath, contentPath, true, true
- }
- }
- // No matching rule, paths remain the same, continue regular processing
- return resolvedPath, contentPath, true, false
-}
-
-func (i *gatewayHandler) handleRedirectsFileRules(w http.ResponseWriter, r *http.Request, contentPath ipath.Path, redirectRules []redirects.Rule) (redirected bool, newContentPath string, err error) {
- // Attempt to match a rule to the URL path, and perform the corresponding redirect or rewrite
- pathParts := strings.Split(contentPath.String(), "/")
- if len(pathParts) > 3 {
- // All paths should start with /ipfs/cid/, so get the path after that
- urlPath := "/" + strings.Join(pathParts[3:], "/")
- rootPath := strings.Join(pathParts[:3], "/")
- // Trim off the trailing /
- urlPath = strings.TrimSuffix(urlPath, "/")
-
- for _, rule := range redirectRules {
- // Error right away if the rule is invalid
- if !rule.MatchAndExpandPlaceholders(urlPath) {
- continue
- }
-
- // We have a match!
-
- // Rewrite
- if rule.Status == 200 {
- // Prepend the rootPath
- toPath := rootPath + rule.To
- return false, toPath, nil
- }
-
- // Or 4xx
- if rule.Status == 404 || rule.Status == 410 || rule.Status == 451 {
- toPath := rootPath + rule.To
- content4xxPath := ipath.New(toPath)
- err := i.serve4xx(w, r, content4xxPath, rule.Status)
- return true, toPath, err
- }
-
- // Or redirect
- if rule.Status >= 301 && rule.Status <= 308 {
- http.Redirect(w, r, rule.To, rule.Status)
- return true, "", nil
- }
- }
- }
-
- // No redirects matched
- return false, "", nil
-}
-
-func (i *gatewayHandler) getRedirectRules(r *http.Request, redirectsFilePath ipath.Resolved) ([]redirects.Rule, error) {
- // Convert the path into a file node
- node, err := i.api.Unixfs().Get(r.Context(), redirectsFilePath)
- if err != nil {
- return nil, fmt.Errorf("could not get _redirects: %w", err)
- }
- defer node.Close()
-
- // Convert the node into a file
- f, ok := node.(files.File)
- if !ok {
- return nil, fmt.Errorf("could not parse _redirects: %w", err)
- }
-
- // Parse redirect rules from file
- redirectRules, err := redirects.Parse(f)
- if err != nil {
- return nil, fmt.Errorf("could not parse _redirects: %w", err)
- }
-
- return redirectRules, nil
-}
-
-// Returns a resolved path to the _redirects file located in the root CID path of the requested path
-func (i *gatewayHandler) getRedirectsFile(r *http.Request, contentPath ipath.Path, logger *zap.SugaredLogger) ipath.Resolved {
- // contentPath is the full ipfs path to the requested resource,
- // regardless of whether path or subdomain resolution is used.
- rootPath := getRootPath(contentPath)
-
- // Check for _redirects file.
- // Any path resolution failures are ignored and we just assume there's no _redirects file.
- // Note that ignoring these errors also ensures that the use of the empty CID (bafkqaaa) in tests doesn't fail.
- path := ipath.Join(rootPath, "_redirects")
- resolvedPath, err := i.api.ResolvePath(r.Context(), path)
- if err != nil {
- return nil
- }
- return resolvedPath
-}
-
-// Returns the root CID Path for the given path
-func getRootPath(path ipath.Path) ipath.Path {
- parts := strings.Split(path.String(), "/")
- return ipath.New(gopath.Join("/", path.Namespace(), parts[2]))
-}
-
-func (i *gatewayHandler) serve4xx(w http.ResponseWriter, r *http.Request, content4xxPath ipath.Path, status int) error {
- resolved4xxPath, err := i.api.ResolvePath(r.Context(), content4xxPath)
- if err != nil {
- return err
- }
-
- node, err := i.api.Unixfs().Get(r.Context(), resolved4xxPath)
- if err != nil {
- return err
- }
- defer node.Close()
-
- f, ok := node.(files.File)
- if !ok {
- return fmt.Errorf("could not convert node for %d page to file", status)
- }
-
- size, err := f.Size()
- if err != nil {
- return fmt.Errorf("could not get size of %d page", status)
- }
-
- log.Debugf("using _redirects: custom %d file at %q", status, content4xxPath)
- w.Header().Set("Content-Type", "text/html")
- w.Header().Set("Content-Length", strconv.FormatInt(size, 10))
- addCacheControlHeaders(w, r, content4xxPath, resolved4xxPath.Cid())
- w.WriteHeader(status)
- _, err = io.CopyN(w, f, size)
- return err
-}
-
-func hasOriginIsolation(r *http.Request) bool {
- _, gw := r.Context().Value(requestContextKey("gw-hostname")).(string)
- _, dnslink := r.Context().Value("dnslink-hostname").(string)
-
- if gw || dnslink {
- return true
- }
-
- return false
-}
-
-func isUnixfsResponseFormat(responseFormat string) bool {
- // The implicit response format is UnixFS
- return responseFormat == ""
-}
-
-// Deprecated: legacy ipfs-404.html files are superseded by _redirects file
-// This is provided only for backward-compatibility, until websites migrate
-// to 404s managed via _redirects file (https://github.com/ipfs/specs/pull/290)
-func (i *gatewayHandler) serveLegacy404IfPresent(w http.ResponseWriter, r *http.Request, contentPath ipath.Path) bool {
- resolved404Path, ctype, err := i.searchUpTreeFor404(r, contentPath)
- if err != nil {
- return false
- }
-
- dr, err := i.api.Unixfs().Get(r.Context(), resolved404Path)
- if err != nil {
- return false
- }
- defer dr.Close()
-
- f, ok := dr.(files.File)
- if !ok {
- return false
- }
-
- size, err := f.Size()
- if err != nil {
- return false
- }
-
- log.Debugw("using pretty 404 file", "path", contentPath)
- w.Header().Set("Content-Type", ctype)
- w.Header().Set("Content-Length", strconv.FormatInt(size, 10))
- w.WriteHeader(http.StatusNotFound)
- _, err = io.CopyN(w, f, size)
- return err == nil
-}
-
-func (i *gatewayHandler) searchUpTreeFor404(r *http.Request, contentPath ipath.Path) (ipath.Resolved, string, error) {
- filename404, ctype, err := preferred404Filename(r.Header.Values("Accept"))
- if err != nil {
- return nil, "", err
- }
-
- pathComponents := strings.Split(contentPath.String(), "/")
-
- for idx := len(pathComponents); idx >= 3; idx-- {
- pretty404 := gopath.Join(append(pathComponents[0:idx], filename404)...)
- parsed404Path := ipath.New("/" + pretty404)
- if parsed404Path.IsValid() != nil {
- break
- }
- resolvedPath, err := i.api.ResolvePath(r.Context(), parsed404Path)
- if err != nil {
- continue
- }
- return resolvedPath, ctype, nil
- }
-
- return nil, "", fmt.Errorf("no pretty 404 in any parent folder")
-}
-
-func preferred404Filename(acceptHeaders []string) (string, string, error) {
- // If we ever want to offer a 404 file for a different content type
- // then this function will need to parse q weightings, but for now
- // the presence of anything matching HTML is enough.
- for _, acceptHeader := range acceptHeaders {
- accepted := strings.Split(acceptHeader, ",")
- for _, spec := range accepted {
- contentType := strings.SplitN(spec, ";", 1)[0]
- switch contentType {
- case "*/*", "text/*", "text/html":
- return "ipfs-404.html", "text/html", nil
- }
- }
- }
-
- return "", "", fmt.Errorf("there is no 404 file for the requested content types")
-}
diff --git a/core/corehttp/gateway_handler_unixfs_dir.go b/core/corehttp/gateway_handler_unixfs_dir.go
deleted file mode 100644
index 03d67e1c040..00000000000
--- a/core/corehttp/gateway_handler_unixfs_dir.go
+++ /dev/null
@@ -1,210 +0,0 @@
-package corehttp
-
-import (
- "context"
- "net/http"
- "net/url"
- gopath "path"
- "strings"
- "time"
-
- "github.com/dustin/go-humanize"
- cid "github.com/ipfs/go-cid"
- "github.com/ipfs/go-libipfs/files"
- path "github.com/ipfs/go-path"
- "github.com/ipfs/go-path/resolver"
- options "github.com/ipfs/interface-go-ipfs-core/options"
- ipath "github.com/ipfs/interface-go-ipfs-core/path"
- "github.com/ipfs/kubo/assets"
- "github.com/ipfs/kubo/tracing"
- "go.opentelemetry.io/otel/attribute"
- "go.opentelemetry.io/otel/trace"
- "go.uber.org/zap"
-)
-
-// serveDirectory returns the best representation of UnixFS directory
-//
-// It will return index.html if present, or generate directory listing otherwise.
-func (i *gatewayHandler) serveDirectory(ctx context.Context, w http.ResponseWriter, r *http.Request, resolvedPath ipath.Resolved, contentPath ipath.Path, dir files.Directory, begin time.Time, logger *zap.SugaredLogger) {
- ctx, span := tracing.Span(ctx, "Gateway", "ServeDirectory", trace.WithAttributes(attribute.String("path", resolvedPath.String())))
- defer span.End()
-
- // HostnameOption might have constructed an IPNS/IPFS path using the Host header.
- // In this case, we need the original path for constructing redirects
- // and links that match the requested URL.
- // For example, http://example.net would become /ipns/example.net, and
- // the redirects and links would end up as http://example.net/ipns/example.net
- requestURI, err := url.ParseRequestURI(r.RequestURI)
- if err != nil {
- webError(w, "failed to parse request path", err, http.StatusInternalServerError)
- return
- }
- originalURLPath := requestURI.Path
-
- // Ensure directory paths end with '/'
- if originalURLPath[len(originalURLPath)-1] != '/' {
- // don't redirect to trailing slash if it's go get
- // https://github.com/ipfs/kubo/pull/3963
- goget := r.URL.Query().Get("go-get") == "1"
- if !goget {
- suffix := "/"
- // preserve query parameters
- if r.URL.RawQuery != "" {
- suffix = suffix + "?" + r.URL.RawQuery
- }
- // /ipfs/cid/foo?bar must be redirected to /ipfs/cid/foo/?bar
- redirectURL := originalURLPath + suffix
- logger.Debugw("directory location moved permanently", "status", http.StatusMovedPermanently)
- http.Redirect(w, r, redirectURL, http.StatusMovedPermanently)
- return
- }
- }
-
- // Check if directory has index.html, if so, serveFile
- idxPath := ipath.Join(contentPath, "index.html")
- idx, err := i.api.Unixfs().Get(ctx, idxPath)
- switch err.(type) {
- case nil:
- f, ok := idx.(files.File)
- if !ok {
- internalWebError(w, files.ErrNotReader)
- return
- }
-
- logger.Debugw("serving index.html file", "path", idxPath)
- // write to request
- i.serveFile(ctx, w, r, resolvedPath, idxPath, f, begin)
- return
- case resolver.ErrNoLink:
- logger.Debugw("no index.html; noop", "path", idxPath)
- default:
- internalWebError(w, err)
- return
- }
-
- // See statusResponseWriter.WriteHeader
- // and https://github.com/ipfs/kubo/issues/7164
- // Note: this needs to occur before listingTemplate.Execute otherwise we get
- // superfluous response.WriteHeader call from prometheus/client_golang
- if w.Header().Get("Location") != "" {
- logger.Debugw("location moved permanently", "status", http.StatusMovedPermanently)
- w.WriteHeader(http.StatusMovedPermanently)
- return
- }
-
- // A HTML directory index will be presented, be sure to set the correct
- // type instead of relying on autodetection (which may fail).
- w.Header().Set("Content-Type", "text/html")
-
- // Generated dir index requires custom Etag (output may change between go-ipfs versions)
- dirEtag := getDirListingEtag(resolvedPath.Cid())
- w.Header().Set("Etag", dirEtag)
-
- if r.Method == http.MethodHead {
- logger.Debug("return as request's HTTP method is HEAD")
- return
- }
-
- // Optimization: use Unixfs.Ls without resolving children, but using the
- // cumulative DAG size as the file size. This allows for a fast listing
- // while keeping a good enough Size field.
- results, err := i.api.Unixfs().Ls(ctx,
- resolvedPath,
- options.Unixfs.ResolveChildren(false),
- options.Unixfs.UseCumulativeSize(true),
- )
- if err != nil {
- internalWebError(w, err)
- return
- }
-
- dirListing := make([]directoryItem, 0, len(results))
- for link := range results {
- if link.Err != nil {
- internalWebError(w, err)
- return
- }
-
- hash := link.Cid.String()
- di := directoryItem{
- Size: humanize.Bytes(uint64(link.Size)),
- Name: link.Name,
- Path: gopath.Join(originalURLPath, link.Name),
- Hash: hash,
- ShortHash: shortHash(hash),
- }
- dirListing = append(dirListing, di)
- }
-
- // construct the correct back link
- // https://github.com/ipfs/kubo/issues/1365
- backLink := originalURLPath
-
- // don't go further up than /ipfs/$hash/
- pathSplit := path.SplitList(contentPath.String())
- switch {
- // skip backlink when listing a content root
- case len(pathSplit) == 3: // url: /ipfs/$hash
- backLink = ""
-
- // skip backlink when listing a content root
- case len(pathSplit) == 4 && pathSplit[3] == "": // url: /ipfs/$hash/
- backLink = ""
-
- // add the correct link depending on whether the path ends with a slash
- default:
- if strings.HasSuffix(backLink, "/") {
- backLink += ".."
- } else {
- backLink += "/.."
- }
- }
-
- size := "?"
- if s, err := dir.Size(); err == nil {
- // Size may not be defined/supported. Continue anyways.
- size = humanize.Bytes(uint64(s))
- }
-
- hash := resolvedPath.Cid().String()
-
- // Gateway root URL to be used when linking to other rootIDs.
- // This will be blank unless subdomain or DNSLink resolution is being used
- // for this request.
- var gwURL string
-
- // Get gateway hostname and build gateway URL.
- if h, ok := r.Context().Value(requestContextKey("gw-hostname")).(string); ok {
- gwURL = "//" + h
- } else {
- gwURL = ""
- }
-
- dnslink := hasDNSLinkOrigin(gwURL, contentPath.String())
-
- // See comment above where originalUrlPath is declared.
- tplData := listingTemplateData{
- GatewayURL: gwURL,
- DNSLink: dnslink,
- Listing: dirListing,
- Size: size,
- Path: contentPath.String(),
- Breadcrumbs: breadcrumbs(contentPath.String(), dnslink),
- BackLink: backLink,
- Hash: hash,
- }
-
- logger.Debugw("request processed", "tplDataDNSLink", dnslink, "tplDataSize", size, "tplDataBackLink", backLink, "tplDataHash", hash)
-
- if err := listingTemplate.Execute(w, tplData); err != nil {
- internalWebError(w, err)
- return
- }
-
- // Update metrics
- i.unixfsGenDirGetMetric.WithLabelValues(contentPath.Namespace()).Observe(time.Since(begin).Seconds())
-}
-
-func getDirListingEtag(dirCid cid.Cid) string {
- return `"DirIndex-` + assets.AssetHash + `_CID-` + dirCid.String() + `"`
-}
diff --git a/core/corehttp/gateway_handler_unixfs_file.go b/core/corehttp/gateway_handler_unixfs_file.go
deleted file mode 100644
index 1abdc823e73..00000000000
--- a/core/corehttp/gateway_handler_unixfs_file.go
+++ /dev/null
@@ -1,104 +0,0 @@
-package corehttp
-
-import (
- "context"
- "fmt"
- "io"
- "mime"
- "net/http"
- gopath "path"
- "strings"
- "time"
-
- "github.com/gabriel-vasile/mimetype"
- "github.com/ipfs/go-libipfs/files"
- ipath "github.com/ipfs/interface-go-ipfs-core/path"
- "github.com/ipfs/kubo/tracing"
- "go.opentelemetry.io/otel/attribute"
- "go.opentelemetry.io/otel/trace"
-)
-
-// serveFile returns data behind a file along with HTTP headers based on
-// the file itself, its CID and the contentPath used for accessing it.
-func (i *gatewayHandler) serveFile(ctx context.Context, w http.ResponseWriter, r *http.Request, resolvedPath ipath.Resolved, contentPath ipath.Path, file files.File, begin time.Time) {
- _, span := tracing.Span(ctx, "Gateway", "ServeFile", trace.WithAttributes(attribute.String("path", resolvedPath.String())))
- defer span.End()
-
- // Set Cache-Control and read optional Last-Modified time
- modtime := addCacheControlHeaders(w, r, contentPath, resolvedPath.Cid())
-
- // Set Content-Disposition
- name := addContentDispositionHeader(w, r, contentPath)
-
- // Prepare size value for Content-Length HTTP header (set inside of http.ServeContent)
- size, err := file.Size()
- if err != nil {
- http.Error(w, "cannot serve files with unknown sizes", http.StatusBadGateway)
- return
- }
-
- if size == 0 {
- // We override null files to 200 to avoid issues with fragment caching reverse proxies.
- // Also whatever you are asking for, it's cheaper to just give you the complete file (nothing).
- // TODO: remove this if clause once https://github.com/golang/go/issues/54794 is fixed in two latest releases of go
- w.Header().Set("Content-Type", "text/plain")
- w.WriteHeader(http.StatusOK)
- return
- }
-
- // Lazy seeker enables efficient range-requests and HTTP HEAD responses
- content := &lazySeeker{
- size: size,
- reader: file,
- }
-
- // Calculate deterministic value for Content-Type HTTP header
- // (we prefer to do it here, rather than using implicit sniffing in http.ServeContent)
- var ctype string
- if _, isSymlink := file.(*files.Symlink); isSymlink {
- // We should be smarter about resolving symlinks but this is the
- // "most correct" we can be without doing that.
- ctype = "inode/symlink"
- } else {
- ctype = mime.TypeByExtension(gopath.Ext(name))
- if ctype == "" {
- // uses https://github.com/gabriel-vasile/mimetype library to determine the content type.
- // Fixes https://github.com/ipfs/kubo/issues/7252
- mimeType, err := mimetype.DetectReader(content)
- if err != nil {
- http.Error(w, fmt.Sprintf("cannot detect content-type: %s", err.Error()), http.StatusInternalServerError)
- return
- }
-
- ctype = mimeType.String()
- _, err = content.Seek(0, io.SeekStart)
- if err != nil {
- http.Error(w, "seeker can't seek", http.StatusInternalServerError)
- return
- }
- }
- // Strip the encoding from the HTML Content-Type header and let the
- // browser figure it out.
- //
- // Fixes https://github.com/ipfs/kubo/issues/2203
- if strings.HasPrefix(ctype, "text/html;") {
- ctype = "text/html"
- }
- }
- // Setting explicit Content-Type to avoid mime-type sniffing on the client
- // (unifies behavior across gateways and web browsers)
- w.Header().Set("Content-Type", ctype)
-
- // special fixup around redirects
- w = &statusResponseWriter{w}
-
- // ServeContent will take care of
- // If-None-Match+Etag, Content-Length and range requests
- _, dataSent, _ := ServeContent(w, r, name, modtime, content)
-
- // Was response successful?
- if dataSent {
- // Update metrics
- i.unixfsFileGetMetric.WithLabelValues(contentPath.Namespace()).Observe(time.Since(begin).Seconds())
- }
-}
diff --git a/core/corehttp/gateway_indexPage.go b/core/corehttp/gateway_indexPage.go
deleted file mode 100644
index b0db8ac1a18..00000000000
--- a/core/corehttp/gateway_indexPage.go
+++ /dev/null
@@ -1,133 +0,0 @@
-package corehttp
-
-import (
- "html/template"
- "net/url"
- "path"
- "strings"
-
- ipfspath "github.com/ipfs/go-path"
- "github.com/ipfs/kubo/assets"
-)
-
-// structs for directory listing
-type listingTemplateData struct {
- GatewayURL string
- DNSLink bool
- Listing []directoryItem
- Size string
- Path string
- Breadcrumbs []breadcrumb
- BackLink string
- Hash string
-}
-
-type directoryItem struct {
- Size string
- Name string
- Path string
- Hash string
- ShortHash string
-}
-
-type breadcrumb struct {
- Name string
- Path string
-}
-
-func breadcrumbs(urlPath string, dnslinkOrigin bool) []breadcrumb {
- var ret []breadcrumb
-
- p, err := ipfspath.ParsePath(urlPath)
- if err != nil {
- // No breadcrumbs, fallback to bare Path in template
- return ret
- }
- segs := p.Segments()
- contentRoot := segs[1]
- for i, seg := range segs {
- if i == 0 {
- ret = append(ret, breadcrumb{Name: seg})
- } else {
- ret = append(ret, breadcrumb{
- Name: seg,
- Path: "/" + strings.Join(segs[0:i+1], "/"),
- })
- }
- }
-
- // Drop the /ipns/ prefix from breadcrumb Paths when directory
- // listing on a DNSLink website (loaded due to Host header in HTTP
- // request). Necessary because the hostname most likely won't have a
- // public gateway mounted.
- if dnslinkOrigin {
- prefix := "/ipns/" + contentRoot
- for i, crumb := range ret {
- if strings.HasPrefix(crumb.Path, prefix) {
- ret[i].Path = strings.Replace(crumb.Path, prefix, "", 1)
- }
- }
- // Make contentRoot breadcrumb link to the website root
- ret[1].Path = "/"
- }
-
- return ret
-}
-
-func shortHash(hash string) string {
- if len(hash) <= 8 {
- return hash
- }
- return (hash[0:4] + "\u2026" + hash[len(hash)-4:])
-}
-
-// helper to detect DNSLink website context
-// (when hostname from gwURL is matching /ipns/ in path)
-func hasDNSLinkOrigin(gwURL string, path string) bool {
- if gwURL != "" {
- fqdn := stripPort(strings.TrimPrefix(gwURL, "//"))
- return strings.HasPrefix(path, "/ipns/"+fqdn)
- }
- return false
-}
-
-var listingTemplate *template.Template
-
-func init() {
- knownIconsBytes, err := assets.Asset.ReadFile("dir-index-html/knownIcons.txt")
- if err != nil {
- panic(err)
- }
- knownIcons := make(map[string]struct{})
- for _, ext := range strings.Split(strings.TrimSuffix(string(knownIconsBytes), "\n"), "\n") {
- knownIcons[ext] = struct{}{}
- }
-
- // helper to guess the type/icon for it by the extension name
- iconFromExt := func(name string) string {
- ext := path.Ext(name)
- _, ok := knownIcons[ext]
- if !ok {
- // default blank icon
- return "ipfs-_blank"
- }
- return "ipfs-" + ext[1:] // slice of the first dot
- }
-
- // custom template-escaping function to escape a full path, including '#' and '?'
- urlEscape := func(rawUrl string) string {
- pathURL := url.URL{Path: rawUrl}
- return pathURL.String()
- }
-
- // Directory listing template
- dirIndexBytes, err := assets.Asset.ReadFile("dir-index-html/dir-index.html")
- if err != nil {
- panic(err)
- }
-
- listingTemplate = template.Must(template.New("dir").Funcs(template.FuncMap{
- "iconFromExt": iconFromExt,
- "urlEscape": urlEscape,
- }).Parse(string(dirIndexBytes)))
-}
diff --git a/core/corehttp/gateway_test.go b/core/corehttp/gateway_test.go
index 877ac9739ae..49fa519fb3d 100644
--- a/core/corehttp/gateway_test.go
+++ b/core/corehttp/gateway_test.go
@@ -651,28 +651,3 @@ func TestVersion(t *testing.T) {
t.Fatalf("response doesn't contain protocol version:\n%s", s)
}
}
-
-func TestEtagMatch(t *testing.T) {
- for _, test := range []struct {
- header string // value in If-None-Match HTTP header
- cidEtag string
- dirEtag string
- expected bool // expected result of etagMatch(header, cidEtag, dirEtag)
- }{
- {"", `"etag"`, "", false}, // no If-None-Match
- {"", "", `"etag"`, false}, // no If-None-Match
- {`"etag"`, `"etag"`, "", true}, // file etag match
- {`W/"etag"`, `"etag"`, "", true}, // file etag match
- {`"foo", W/"bar", W/"etag"`, `"etag"`, "", true}, // file etag match (array)
- {`"foo",W/"bar",W/"etag"`, `"etag"`, "", true}, // file etag match (compact array)
- {`"etag"`, "", `W/"etag"`, true}, // dir etag match
- {`"etag"`, "", `W/"etag"`, true}, // dir etag match
- {`W/"etag"`, "", `W/"etag"`, true}, // dir etag match
- {`*`, `"etag"`, "", true}, // wildcard etag match
- } {
- result := etagMatch(test.header, test.cidEtag, test.dirEtag)
- if result != test.expected {
- t.Fatalf("unexpected result of etagMatch(%q, %q, %q), got %t, expected %t", test.header, test.cidEtag, test.dirEtag, result, test.expected)
- }
- }
-}
diff --git a/core/corehttp/hostname.go b/core/corehttp/hostname.go
index 39e857aadfb..adc47ab4ddb 100644
--- a/core/corehttp/hostname.go
+++ b/core/corehttp/hostname.go
@@ -10,6 +10,7 @@ import (
"strings"
cid "github.com/ipfs/go-cid"
+ "github.com/ipfs/go-libipfs/gateway"
namesys "github.com/ipfs/go-namesys"
core "github.com/ipfs/kubo/core"
coreapi "github.com/ipfs/kubo/core/coreapi"
@@ -225,7 +226,7 @@ func HostnameOption() ServeOption {
if !cfg.Gateway.NoDNSLink && isDNSLinkName(r.Context(), coreAPI, host) {
// rewrite path and handle as DNSLink
r.URL.Path = "/ipns/" + stripPort(host) + r.URL.Path
- ctx := context.WithValue(r.Context(), requestContextKey("dnslink-hostname"), host)
+ ctx := context.WithValue(r.Context(), gateway.DNSLinkHostnameKey, host)
childMux.ServeHTTP(w, withHostnameContext(r.WithContext(ctx), host))
return
}
@@ -247,8 +248,6 @@ type wildcardHost struct {
spec *config.GatewaySpec
}
-type requestContextKey string
-
// Extends request context to include hostname of a canonical gateway root
// (subdomain root or dnslink fqdn)
func withHostnameContext(r *http.Request, hostname string) *http.Request {
@@ -257,7 +256,7 @@ func withHostnameContext(r *http.Request, hostname string) *http.Request {
// Host header, subdomain gateways have more comples rules (knownSubdomainDetails)
// More: https://github.com/ipfs/dir-index-html/issues/42
// nolint: staticcheck // non-backward compatible change
- ctx := context.WithValue(r.Context(), requestContextKey("gw-hostname"), hostname)
+ ctx := context.WithValue(r.Context(), gateway.GatewayHostnameKey, hostname)
return r.WithContext(ctx)
}
diff --git a/core/corehttp/lazyseek.go b/core/corehttp/lazyseek.go
deleted file mode 100644
index 2a379dc918a..00000000000
--- a/core/corehttp/lazyseek.go
+++ /dev/null
@@ -1,60 +0,0 @@
-package corehttp
-
-import (
- "fmt"
- "io"
-)
-
-// The HTTP server uses seek to determine the file size. Actually _seeking_ can
-// be slow so we wrap the seeker in a _lazy_ seeker.
-type lazySeeker struct {
- reader io.ReadSeeker
-
- size int64
- offset int64
- realOffset int64
-}
-
-func (s *lazySeeker) Seek(offset int64, whence int) (int64, error) {
- switch whence {
- case io.SeekEnd:
- return s.Seek(s.size+offset, io.SeekStart)
- case io.SeekCurrent:
- return s.Seek(s.offset+offset, io.SeekStart)
- case io.SeekStart:
- if offset < 0 {
- return s.offset, fmt.Errorf("invalid seek offset")
- }
- s.offset = offset
- return s.offset, nil
- default:
- return s.offset, fmt.Errorf("invalid whence: %d", whence)
- }
-}
-
-func (s *lazySeeker) Read(b []byte) (int, error) {
- // If we're past the end, EOF.
- if s.offset >= s.size {
- return 0, io.EOF
- }
-
- // actually seek
- for s.offset != s.realOffset {
- off, err := s.reader.Seek(s.offset, io.SeekStart)
- if err != nil {
- return 0, err
- }
- s.realOffset = off
- }
- off, err := s.reader.Read(b)
- s.realOffset += int64(off)
- s.offset += int64(off)
- return off, err
-}
-
-func (s *lazySeeker) Close() error {
- if closer, ok := s.reader.(io.Closer); ok {
- return closer.Close()
- }
- return nil
-}
diff --git a/core/corehttp/lazyseek_test.go b/core/corehttp/lazyseek_test.go
deleted file mode 100644
index 49aca0a0e2a..00000000000
--- a/core/corehttp/lazyseek_test.go
+++ /dev/null
@@ -1,136 +0,0 @@
-package corehttp
-
-import (
- "fmt"
- "io"
- "strings"
- "testing"
-)
-
-type badSeeker struct {
- io.ReadSeeker
-}
-
-var errBadSeek = fmt.Errorf("bad seeker")
-
-func (bs badSeeker) Seek(offset int64, whence int) (int64, error) {
- off, err := bs.ReadSeeker.Seek(0, io.SeekCurrent)
- if err != nil {
- panic(err)
- }
- return off, errBadSeek
-}
-
-func TestLazySeekerError(t *testing.T) {
- underlyingBuffer := strings.NewReader("fubar")
- s := &lazySeeker{
- reader: badSeeker{underlyingBuffer},
- size: underlyingBuffer.Size(),
- }
- off, err := s.Seek(0, io.SeekEnd)
- if err != nil {
- t.Fatal(err)
- }
- if off != s.size {
- t.Fatal("expected to seek to the end")
- }
-
- // shouldn't have actually seeked.
- b, err := io.ReadAll(s)
- if err != nil {
- t.Fatal(err)
- }
- if len(b) != 0 {
- t.Fatal("expected to read nothing")
- }
-
- // shouldn't need to actually seek.
- off, err = s.Seek(0, io.SeekStart)
- if err != nil {
- t.Fatal(err)
- }
- if off != 0 {
- t.Fatal("expected to seek to the start")
- }
- b, err = io.ReadAll(s)
- if err != nil {
- t.Fatal(err)
- }
- if string(b) != "fubar" {
- t.Fatal("expected to read string")
- }
-
- // should fail the second time.
- off, err = s.Seek(0, io.SeekStart)
- if err != nil {
- t.Fatal(err)
- }
- if off != 0 {
- t.Fatal("expected to seek to the start")
- }
- // right here...
- b, err = io.ReadAll(s)
- if err == nil {
- t.Fatalf("expected an error, got output %s", string(b))
- }
- if err != errBadSeek {
- t.Fatalf("expected a bad seek error, got %s", err)
- }
- if len(b) != 0 {
- t.Fatalf("expected to read nothing")
- }
-}
-
-func TestLazySeeker(t *testing.T) {
- underlyingBuffer := strings.NewReader("fubar")
- s := &lazySeeker{
- reader: underlyingBuffer,
- size: underlyingBuffer.Size(),
- }
- expectByte := func(b byte) {
- t.Helper()
- var buf [1]byte
- n, err := io.ReadFull(s, buf[:])
- if err != nil {
- t.Fatal(err)
- }
- if n != 1 {
- t.Fatalf("expected to read one byte, read %d", n)
- }
- if buf[0] != b {
- t.Fatalf("expected %b, got %b", b, buf[0])
- }
- }
- expectSeek := func(whence int, off, expOff int64, expErr string) {
- t.Helper()
- n, err := s.Seek(off, whence)
- if expErr == "" {
- if err != nil {
- t.Fatal("unexpected seek error: ", err)
- }
- } else {
- if err == nil || err.Error() != expErr {
- t.Fatalf("expected %s, got %s", err, expErr)
- }
- }
- if n != expOff {
- t.Fatalf("expected offset %d, got, %d", expOff, n)
- }
- }
-
- expectSeek(io.SeekEnd, 0, s.size, "")
- b, err := io.ReadAll(s)
- if err != nil {
- t.Fatal(err)
- }
- if len(b) != 0 {
- t.Fatal("expected to read nothing")
- }
- expectSeek(io.SeekEnd, -1, s.size-1, "")
- expectByte('r')
- expectSeek(io.SeekStart, 0, 0, "")
- expectByte('f')
- expectSeek(io.SeekCurrent, 1, 2, "")
- expectByte('b')
- expectSeek(io.SeekCurrent, -100, 3, "invalid seek offset")
-}
diff --git a/docs/examples/kubo-as-a-library/go.mod b/docs/examples/kubo-as-a-library/go.mod
index 3294cd137ab..a74bfbf3e03 100644
--- a/docs/examples/kubo-as-a-library/go.mod
+++ b/docs/examples/kubo-as-a-library/go.mod
@@ -7,7 +7,7 @@ go 1.18
replace github.com/ipfs/kubo => ./../../..
require (
- github.com/ipfs/go-libipfs v0.4.0
+ github.com/ipfs/go-libipfs v0.4.1-0.20230130233950-a005a5006496
github.com/ipfs/interface-go-ipfs-core v0.10.0
github.com/ipfs/kubo v0.0.0-00010101000000-000000000000
github.com/libp2p/go-libp2p v0.24.2
diff --git a/docs/examples/kubo-as-a-library/go.sum b/docs/examples/kubo-as-a-library/go.sum
index ece45c198d4..eb37654f786 100644
--- a/docs/examples/kubo-as-a-library/go.sum
+++ b/docs/examples/kubo-as-a-library/go.sum
@@ -548,8 +548,8 @@ github.com/ipfs/go-ipld-legacy v0.1.1 h1:BvD8PEuqwBHLTKqlGFTHSwrwFOMkVESEvwIYwR2
github.com/ipfs/go-ipld-legacy v0.1.1/go.mod h1:8AyKFCjgRPsQFf15ZQgDB8Din4DML/fOmKZkkFkrIEg=
github.com/ipfs/go-ipns v0.3.0 h1:ai791nTgVo+zTuq2bLvEGmWP1M0A6kGTXUsgv/Yq67A=
github.com/ipfs/go-ipns v0.3.0/go.mod h1:3cLT2rbvgPZGkHJoPO1YMJeh6LtkxopCkKFcio/wE24=
-github.com/ipfs/go-libipfs v0.4.0 h1:TkUxJGjtPnSzAgkw7VjS0/DBay3MPjmTBa4dGdUQCDE=
-github.com/ipfs/go-libipfs v0.4.0/go.mod h1:XsU2cP9jBhDrXoJDe0WxikB8XcVmD3k2MEZvB3dbYu8=
+github.com/ipfs/go-libipfs v0.4.1-0.20230130233950-a005a5006496 h1:RVI31GQCFODREpasIFyVFkS6PjJT2bMwr/Bgr9Ryql4=
+github.com/ipfs/go-libipfs v0.4.1-0.20230130233950-a005a5006496/go.mod h1:AAPvZADZ80i+QhGCWNWCsx8IGY0t9C+IBEngLeYtySY=
github.com/ipfs/go-log v0.0.1/go.mod h1:kL1d2/hzSpI0thNYjiKfjanbVNU+IIGA/WnNESY9leM=
github.com/ipfs/go-log v1.0.2/go.mod h1:1MNjMxe0u6xvJZgeqbJ8vdo2TKaGwZ1a0Bpza+sr2Sk=
github.com/ipfs/go-log v1.0.3/go.mod h1:OsLySYkwIbiSUR/yBTdv1qPtcE4FW3WPWk/ewz9Ru+A=
@@ -597,7 +597,7 @@ github.com/ipfs/interface-go-ipfs-core v0.10.0 h1:b/psL1oqJcySdQAsIBfW5ZJJkOAsYl
github.com/ipfs/interface-go-ipfs-core v0.10.0/go.mod h1:F3EcmDy53GFkF0H3iEJpfJC320fZ/4G60eftnItrrJ0=
github.com/ipld/edelweiss v0.2.0 h1:KfAZBP8eeJtrLxLhi7r3N0cBCo7JmwSRhOJp3WSpNjk=
github.com/ipld/edelweiss v0.2.0/go.mod h1:FJAzJRCep4iI8FOFlRriN9n0b7OuX3T/S9++NpBDmA4=
-github.com/ipld/go-car v0.4.0 h1:U6W7F1aKF/OJMHovnOVdst2cpQE5GhmHibQkAixgNcQ=
+github.com/ipld/go-car v0.5.0 h1:kcCEa3CvYMs0iE5BzD5sV7O2EwMiCIp3uF8tA6APQT8=
github.com/ipld/go-car/v2 v2.5.1 h1:U2ux9JS23upEgrJScW8VQuxmE94560kYxj9CQUpcfmk=
github.com/ipld/go-codec-dagpb v1.3.0/go.mod h1:ga4JTU3abYApDC3pZ00BC2RSvC3qfBb9MSJkMLSwnhA=
github.com/ipld/go-codec-dagpb v1.5.0 h1:RspDRdsJpLfgCI0ONhTAnbHdySGD4t+LHSPK4X1+R0k=
diff --git a/go.mod b/go.mod
index 3d91f40e77f..5edf9f68a46 100644
--- a/go.mod
+++ b/go.mod
@@ -7,14 +7,12 @@ require (
github.com/blang/semver/v4 v4.0.0
github.com/cenkalti/backoff/v4 v4.1.3
github.com/ceramicnetwork/go-dag-jose v0.1.0
- github.com/cespare/xxhash v1.1.0
github.com/cheggaaa/pb v1.0.29
github.com/coreos/go-systemd/v22 v22.5.0
github.com/dustin/go-humanize v1.0.0
github.com/elgris/jsondiff v0.0.0-20160530203242-765b5c24c302
github.com/facebookgo/atomicfile v0.0.0-20151019160806-2de1f203e7d5
github.com/fsnotify/fsnotify v1.6.0
- github.com/gabriel-vasile/mimetype v1.4.1
github.com/gogo/protobuf v1.3.2
github.com/google/uuid v1.3.0
github.com/hashicorp/go-multierror v1.1.1
@@ -41,14 +39,13 @@ require (
github.com/ipfs/go-ipfs-pinner v0.2.1
github.com/ipfs/go-ipfs-posinfo v0.0.1
github.com/ipfs/go-ipfs-provider v0.8.1
- github.com/ipfs/go-ipfs-redirects-file v0.1.1
github.com/ipfs/go-ipfs-routing v0.3.0
github.com/ipfs/go-ipfs-util v0.0.2
github.com/ipfs/go-ipld-format v0.4.0
github.com/ipfs/go-ipld-git v0.1.1
github.com/ipfs/go-ipld-legacy v0.1.1
github.com/ipfs/go-ipns v0.3.0
- github.com/ipfs/go-libipfs v0.4.0
+ github.com/ipfs/go-libipfs v0.4.1-0.20230130233950-a005a5006496
github.com/ipfs/go-log v1.0.5
github.com/ipfs/go-log/v2 v2.5.1
github.com/ipfs/go-merkledag v0.9.0
@@ -62,7 +59,7 @@ require (
github.com/ipfs/go-unixfsnode v1.5.1
github.com/ipfs/go-verifcid v0.0.2
github.com/ipfs/interface-go-ipfs-core v0.10.0
- github.com/ipld/go-car v0.4.0
+ github.com/ipld/go-car v0.5.0
github.com/ipld/go-car/v2 v2.5.1
github.com/ipld/go-codec-dagpb v1.5.0
github.com/ipld/go-ipld-prime v0.19.0
@@ -121,6 +118,7 @@ require (
github.com/alexbrainman/goissue34681 v0.0.0-20191006012335-3fc7a47baff5 // indirect
github.com/beorn7/perks v1.0.1 // indirect
github.com/cenkalti/backoff v2.2.1+incompatible // indirect
+ github.com/cespare/xxhash v1.1.0 // indirect
github.com/cespare/xxhash/v2 v2.2.0 // indirect
github.com/containerd/cgroups v1.0.4 // indirect
github.com/crackcomm/go-gitignore v0.0.0-20170627025303-887ab5e44cc3 // indirect
@@ -136,6 +134,7 @@ require (
github.com/felixge/httpsnoop v1.0.2 // indirect
github.com/flynn/noise v1.0.0 // indirect
github.com/francoispqt/gojay v1.2.13 // indirect
+ github.com/gabriel-vasile/mimetype v1.4.1 // indirect
github.com/go-kit/log v0.2.0 // indirect
github.com/go-logfmt/logfmt v0.5.1 // indirect
github.com/go-logr/logr v1.2.3 // indirect
@@ -161,6 +160,7 @@ require (
github.com/ipfs/go-ipfs-delay v0.0.1 // indirect
github.com/ipfs/go-ipfs-ds-help v1.1.0 // indirect
github.com/ipfs/go-ipfs-pq v0.0.2 // indirect
+ github.com/ipfs/go-ipfs-redirects-file v0.1.1 // indirect
github.com/ipfs/go-ipld-cbor v0.0.6 // indirect
github.com/ipfs/go-peertaskqueue v0.8.0 // indirect
github.com/ipld/edelweiss v0.2.0 // indirect
diff --git a/go.sum b/go.sum
index 471acc8080b..fefe9bf7160 100644
--- a/go.sum
+++ b/go.sum
@@ -570,8 +570,8 @@ github.com/ipfs/go-ipld-legacy v0.1.1 h1:BvD8PEuqwBHLTKqlGFTHSwrwFOMkVESEvwIYwR2
github.com/ipfs/go-ipld-legacy v0.1.1/go.mod h1:8AyKFCjgRPsQFf15ZQgDB8Din4DML/fOmKZkkFkrIEg=
github.com/ipfs/go-ipns v0.3.0 h1:ai791nTgVo+zTuq2bLvEGmWP1M0A6kGTXUsgv/Yq67A=
github.com/ipfs/go-ipns v0.3.0/go.mod h1:3cLT2rbvgPZGkHJoPO1YMJeh6LtkxopCkKFcio/wE24=
-github.com/ipfs/go-libipfs v0.4.0 h1:TkUxJGjtPnSzAgkw7VjS0/DBay3MPjmTBa4dGdUQCDE=
-github.com/ipfs/go-libipfs v0.4.0/go.mod h1:XsU2cP9jBhDrXoJDe0WxikB8XcVmD3k2MEZvB3dbYu8=
+github.com/ipfs/go-libipfs v0.4.1-0.20230130233950-a005a5006496 h1:RVI31GQCFODREpasIFyVFkS6PjJT2bMwr/Bgr9Ryql4=
+github.com/ipfs/go-libipfs v0.4.1-0.20230130233950-a005a5006496/go.mod h1:AAPvZADZ80i+QhGCWNWCsx8IGY0t9C+IBEngLeYtySY=
github.com/ipfs/go-log v0.0.1/go.mod h1:kL1d2/hzSpI0thNYjiKfjanbVNU+IIGA/WnNESY9leM=
github.com/ipfs/go-log v1.0.2/go.mod h1:1MNjMxe0u6xvJZgeqbJ8vdo2TKaGwZ1a0Bpza+sr2Sk=
github.com/ipfs/go-log v1.0.3/go.mod h1:OsLySYkwIbiSUR/yBTdv1qPtcE4FW3WPWk/ewz9Ru+A=
@@ -623,8 +623,8 @@ github.com/ipfs/interface-go-ipfs-core v0.10.0 h1:b/psL1oqJcySdQAsIBfW5ZJJkOAsYl
github.com/ipfs/interface-go-ipfs-core v0.10.0/go.mod h1:F3EcmDy53GFkF0H3iEJpfJC320fZ/4G60eftnItrrJ0=
github.com/ipld/edelweiss v0.2.0 h1:KfAZBP8eeJtrLxLhi7r3N0cBCo7JmwSRhOJp3WSpNjk=
github.com/ipld/edelweiss v0.2.0/go.mod h1:FJAzJRCep4iI8FOFlRriN9n0b7OuX3T/S9++NpBDmA4=
-github.com/ipld/go-car v0.4.0 h1:U6W7F1aKF/OJMHovnOVdst2cpQE5GhmHibQkAixgNcQ=
-github.com/ipld/go-car v0.4.0/go.mod h1:Uslcn4O9cBKK9wqHm/cLTFacg6RAPv6LZx2mxd2Ypl4=
+github.com/ipld/go-car v0.5.0 h1:kcCEa3CvYMs0iE5BzD5sV7O2EwMiCIp3uF8tA6APQT8=
+github.com/ipld/go-car v0.5.0/go.mod h1:ppiN5GWpjOZU9PgpAZ9HbZd9ZgSpwPMr48fGRJOWmvE=
github.com/ipld/go-car/v2 v2.5.1 h1:U2ux9JS23upEgrJScW8VQuxmE94560kYxj9CQUpcfmk=
github.com/ipld/go-car/v2 v2.5.1/go.mod h1:jKjGOqoCj5zn6KjnabD6JbnCsMntqU2hLiU6baZVO3E=
github.com/ipld/go-codec-dagpb v1.3.0/go.mod h1:ga4JTU3abYApDC3pZ00BC2RSvC3qfBb9MSJkMLSwnhA=