| 1 | package corehttp |
| 2 | |
| 3 | import ( |
| 4 | "fmt" |
| 5 | "net" |
| 6 | "net/http" |
| 7 | "strings" |
| 8 | |
| 9 | "github.com/ipfs/go-cid" |
| 10 | "github.com/ipfs/kubo/config" |
| 11 | core "github.com/ipfs/kubo/core" |
| 12 | ) |
| 13 | |
| 14 | // WebUI version confirmed to work with this Kubo version |
| 15 | const WebUIPath = "/ipfs/bafybeihxglpcfyarpm7apn7xpezbuoqgk3l5chyk7w4gvrjwk45rqohlmm" // v4.12.0 |
| 16 | |
| 17 | // WebUIPaths is a list of all past webUI paths. |
| 18 | var WebUIPaths = []string{ |
| 19 | WebUIPath, |
| 20 | "/ipfs/bafybeiddnr2jz65byk67sjt6jsu6g7tueddr7odhzzpzli3rgudlbnc6iq", // v4.11.1 |
| 21 | "/ipfs/bafybeidfgbcqy435sdbhhejifdxq4o64tlsezajc272zpyxcsmz47uyc64", // v4.11.0 |
| 22 | "/ipfs/bafybeidsjptidvb6wf6benznq2pxgnt5iyksgtecpmjoimlmswhtx2u5ua", // v4.10.0 |
| 23 | "/ipfs/bafybeicg7e6o2eszkfdzxg5233gmuip2a7kfzoloh7voyvt2r6ivdet54u", // v4.9.1 |
| 24 | "/ipfs/bafybeifplj2s3yegn7ko7tdnwpoxa4c5uaqnk2ajnw5geqm34slcj6b6mu", // v4.8.0 |
| 25 | "/ipfs/bafybeibfd5kbebqqruouji6ct5qku3tay273g7mt24mmrfzrsfeewaal5y", // v4.7.0 |
| 26 | "/ipfs/bafybeibpaa5kqrj4gkemiswbwndjqiryl65cks64ypwtyerxixu56gnvvm", // v4.6.0 |
| 27 | "/ipfs/bafybeiata4qg7xjtwgor6r5dw63jjxyouenyromrrb4lrewxrlvav7gzgi", // v4.5.0 |
| 28 | "/ipfs/bafybeigp3zm7cqoiciqk5anlheenqjsgovp7j7zq6hah4nu6iugdgb4nby", // v4.4.2 |
| 29 | "/ipfs/bafybeiatztgdllxnp5p6zu7bdwhjmozsmd7jprff4bdjqjljxtylitvss4", // v4.4.1 |
| 30 | "/ipfs/bafybeibgic2ex3fvzkinhy6k6aqyv3zy2o7bkbsmrzvzka24xetv7eeadm", // v4.4.0 |
| 31 | "/ipfs/bafybeid4uxz7klxcu3ffsnmn64r7ihvysamlj4ohl5h2orjsffuegcpaeq", // v4.3.3 |
| 32 | "/ipfs/bafybeif6abowqcavbkz243biyh7pde7ick5kkwwytrh7pd2hkbtuqysjxy", // v4.3.2 |
| 33 | "/ipfs/bafybeihatzsgposbr3hrngo42yckdyqcc56yean2rynnwpzxstvdlphxf4", |
| 34 | "/ipfs/bafybeigggyffcf6yfhx5irtwzx3cgnk6n3dwylkvcpckzhqqrigsxowjwe", |
| 35 | "/ipfs/bafybeidf7cpkwsjkq6xs3r6fbbxghbugilx3jtezbza7gua3k5wjixpmba", |
| 36 | "/ipfs/bafybeiamycmd52xvg6k3nzr6z3n33de6a2teyhquhj4kspdtnvetnkrfim", |
| 37 | "/ipfs/bafybeieqdeoqkf7xf4aozd524qncgiloh33qgr25lyzrkusbcre4c3fxay", |
| 38 | "/ipfs/bafybeicyp7ssbnj3hdzehcibmapmpuc3atrsc4ch3q6acldfh4ojjdbcxe", |
| 39 | "/ipfs/bafybeigs6d53gpgu34553mbi5bbkb26e4ikruoaaar75jpfdywpup2r3my", |
| 40 | "/ipfs/bafybeic4gops3d3lyrisqku37uio33nvt6fqxvkxihrwlqsuvf76yln4fm", |
| 41 | "/ipfs/bafybeifeqt7mvxaniphyu2i3qhovjaf3sayooxbh5enfdqtiehxjv2ldte", // v2.22.0 |
| 42 | "/ipfs/bafybeiequgo72mrvuml56j4gk7crewig5bavumrrzhkqbim6b3s2yqi7ty", |
| 43 | "/ipfs/bafybeibjbq3tmmy7wuihhhwvbladjsd3gx3kfjepxzkq6wylik6wc3whzy", // v2.20.0 |
| 44 | "/ipfs/bafybeiavrvt53fks6u32n5p2morgblcmck4bh4ymf4rrwu7ah5zsykmqqa", // v2.19.0 |
| 45 | "/ipfs/bafybeiageaoxg6d7npaof6eyzqbwvbubyler7bq44hayik2hvqcggg7d2y", // v2.18.1 |
| 46 | "/ipfs/bafybeidb5eryh72zajiokdggzo7yct2d6hhcflncji5im2y5w26uuygdsm", // v2.18.0 |
| 47 | "/ipfs/bafybeibozpulxtpv5nhfa2ue3dcjx23ndh3gwr5vwllk7ptoyfwnfjjr4q", // v2.15.1 |
| 48 | "/ipfs/bafybeiednzu62vskme5wpoj4bjjikeg3xovfpp4t7vxk5ty2jxdi4mv4bu", // v2.15.0 |
| 49 | "/ipfs/bafybeihcyruaeza7uyjd6ugicbcrqumejf6uf353e5etdkhotqffwtguva", // v2.13.0 |
| 50 | "/ipfs/bafybeiflkjt66aetfgcrgvv75izymd5kc47g6luepqmfq6zsf5w6ueth6y", |
| 51 | "/ipfs/bafybeid26vjplsejg7t3nrh7mxmiaaxriebbm4xxrxxdunlk7o337m5sqq", |
| 52 | "/ipfs/bafybeif4zkmu7qdhkpf3pnhwxipylqleof7rl6ojbe7mq3fzogz6m4xk3i", // v2.11.4 |
| 53 | "/ipfs/bafybeianwe4vy7sprht5sm3hshvxjeqhwcmvbzq73u55sdhqngmohkjgs4", |
| 54 | "/ipfs/bafybeicitin4p7ggmyjaubqpi3xwnagrwarsy6hiihraafk5rcrxqxju6m", |
| 55 | "/ipfs/bafybeihpetclqvwb4qnmumvcn7nh4pxrtugrlpw4jgjpqicdxsv7opdm6e", |
| 56 | "/ipfs/bafybeibnnxd4etu4tq5fuhu3z5p4rfu3buabfkeyr3o3s4h6wtesvvw6mu", |
| 57 | "/ipfs/bafybeid6luolenf4fcsuaw5rgdwpqbyerce4x3mi3hxfdtp5pwco7h7qyq", |
| 58 | "/ipfs/bafybeigkbbjnltbd4ewfj7elajsbnjwinyk6tiilczkqsibf3o7dcr6nn4", |
| 59 | "/ipfs/bafybeicp23nbcxtt2k2twyfivcbrc6kr3l5lnaiv3ozvwbemtrb7v52r6i", |
| 60 | "/ipfs/bafybeidatpz2hli6fgu3zul5woi27ujesdf5o5a7bu622qj6ugharciwjq", |
| 61 | "/ipfs/QmfQkD8pBSBCBxWEwFSu4XaDVSWK6bjnNuaWZjMyQbyDub", |
| 62 | "/ipfs/QmXc9raDM1M5G5fpBnVyQ71vR4gbnskwnB9iMEzBuLgvoZ", |
| 63 | "/ipfs/QmenEBWcAk3tN94fSKpKFtUMwty1qNwSYw3DMDFV6cPBXA", |
| 64 | "/ipfs/QmUnXcWZC5Ve21gUseouJsH5mLAyz5JPp8aHsg8qVUUK8e", |
| 65 | "/ipfs/QmSDgpiHco5yXdyVTfhKxr3aiJ82ynz8V14QcGKicM3rVh", |
| 66 | "/ipfs/QmRuvWJz1Fc8B9cTsAYANHTXqGmKR9DVfY5nvMD1uA2WQ8", |
| 67 | "/ipfs/QmQLXHs7K98JNQdWrBB2cQLJahPhmupbDjRuH1b9ibmwVa", |
| 68 | "/ipfs/QmXX7YRpU7nNBKfw75VG7Y1c3GwpSAGHRev67XVPgZFv9R", |
| 69 | "/ipfs/QmXdu7HWdV6CUaUabd9q2ZeA4iHZLVyDRj3Gi4dsJsWjbr", |
| 70 | "/ipfs/QmaaqrHyAQm7gALkRW8DcfGX3u8q9rWKnxEMmf7m9z515w", |
| 71 | "/ipfs/QmSHDxWsMPuJQKWmVA1rB5a3NX2Eme5fPqNb63qwaqiqSp", |
| 72 | "/ipfs/QmctngrQAt9fjpQUZr7Bx3BsXUcif52eZGTizWhvcShsjz", |
| 73 | "/ipfs/QmS2HL9v5YeKgQkkWMvs1EMnFtUowTEdFfSSeMT4pos1e6", |
| 74 | "/ipfs/QmR9MzChjp1MdFWik7NjEjqKQMzVmBkdK3dz14A6B5Cupm", |
| 75 | "/ipfs/QmRyWyKWmphamkMRnJVjUTzSFSAAZowYP4rnbgnfMXC9Mr", |
| 76 | "/ipfs/QmU3o9bvfenhTKhxUakbYrLDnZU7HezAVxPM6Ehjw9Xjqy", |
| 77 | "/ipfs/QmPhnvn747LqwPYMJmQVorMaGbMSgA7mRRoyyZYz3DoZRQ", |
| 78 | "/ipfs/QmQNHd1suZTktPRhP7DD4nKWG46ZRSxkwHocycHVrK3dYW", |
| 79 | "/ipfs/QmNyMYhwJUS1cVvaWoVBhrW8KPj1qmie7rZcWo8f1Bvkhz", |
| 80 | "/ipfs/QmVTiRTQ72qiH4usAGT4c6qVxCMv4hFMUH9fvU6mktaXdP", |
| 81 | "/ipfs/QmYcP4sp1nraBiCYi6i9kqdaKobrK32yyMpTrM5JDA8a2C", |
| 82 | "/ipfs/QmUtMmxgHnDvQq4bpH6Y9MaLN1hpfjJz5LZcq941BEqEXs", |
| 83 | "/ipfs/QmPURAjo3oneGH53ovt68UZEBvsc8nNmEhQZEpsVEQUMZE", |
| 84 | "/ipfs/QmeSXt32frzhvewLKwA1dePTSjkTfGVwTh55ZcsJxrCSnk", |
| 85 | "/ipfs/QmcjeTciMNgEBe4xXvEaA4TQtwTRkXucx7DmKWViXSmX7m", |
| 86 | "/ipfs/QmfNbSskgvTXYhuqP8tb9AKbCkyRcCy3WeiXwD9y5LeoqK", |
| 87 | "/ipfs/QmPkojhjJkJ5LEGBDrAvdftrjAYmi9GU5Cq27mWvZTDieW", |
| 88 | "/ipfs/Qmexhq2sBHnXQbvyP2GfUdbnY7HCagH2Mw5vUNSBn2nxip", |
| 89 | } |
| 90 | |
| 91 | // WebUIOption provides the WebUI handler for the RPC API. |
| 92 | func WebUIOption(n *core.IpfsNode, _ net.Listener, mux *http.ServeMux) (*http.ServeMux, error) { |
| 93 | cfg, err := n.Repo.Config() |
| 94 | if err != nil { |
| 95 | return nil, err |
| 96 | } |
| 97 | |
| 98 | handler := &webUIHandler{ |
| 99 | headers: cfg.API.HTTPHeaders, |
| 100 | node: n, |
| 101 | noFetch: cfg.Gateway.NoFetch, |
| 102 | deserializedResponses: cfg.Gateway.DeserializedResponses.WithDefault(config.DefaultDeserializedResponses), |
| 103 | } |
| 104 | |
| 105 | mux.Handle("/webui/", handler) |
| 106 | return mux, nil |
| 107 | } |
| 108 | |
| 109 | type webUIHandler struct { |
| 110 | headers map[string][]string |
| 111 | node *core.IpfsNode |
| 112 | noFetch bool |
| 113 | deserializedResponses bool |
| 114 | } |
| 115 | |
| 116 | func (h *webUIHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { |
| 117 | for k, v := range h.headers { |
| 118 | w.Header()[http.CanonicalHeaderKey(k)] = v |
| 119 | } |
| 120 | |
| 121 | // Check if WebUI is incompatible with current configuration |
| 122 | if !h.deserializedResponses { |
| 123 | h.writeIncompatibleError(w) |
| 124 | return |
| 125 | } |
| 126 | |
| 127 | // Check if WebUI is available locally when Gateway.NoFetch is true |
| 128 | if h.noFetch { |
| 129 | cidStr := strings.TrimPrefix(WebUIPath, "/ipfs/") |
| 130 | webUICID, err := cid.Parse(cidStr) |
| 131 | if err != nil { |
| 132 | // This should never happen with hardcoded constant |
| 133 | log.Errorf("failed to parse WebUI CID: %v", err) |
| 134 | } else { |
| 135 | has, err := h.node.Blockstore.Has(r.Context(), webUICID) |
| 136 | if err != nil { |
| 137 | log.Debugf("error checking WebUI availability: %v", err) |
| 138 | } else if !has { |
| 139 | h.writeNotAvailableError(w) |
| 140 | return |
| 141 | } |
| 142 | } |
| 143 | } |
| 144 | |
| 145 | // Default behavior: redirect to the WebUI path |
| 146 | http.Redirect(w, r, WebUIPath, http.StatusFound) |
| 147 | } |
| 148 | |
| 149 | func (h *webUIHandler) writeIncompatibleError(w http.ResponseWriter) { |
| 150 | w.Header().Set("Content-Type", "text/plain; charset=utf-8") |
| 151 | w.WriteHeader(http.StatusServiceUnavailable) |
| 152 | fmt.Fprintf(w, `IPFS WebUI Incompatible |
| 153 | |
| 154 | WebUI is not compatible with Gateway.DeserializedResponses=false. |
| 155 | |
| 156 | The WebUI requires deserializing IPFS responses to render the interface. |
| 157 | To use the WebUI, set Gateway.DeserializedResponses=true in your config. |
| 158 | `) |
| 159 | } |
| 160 | |
| 161 | func (h *webUIHandler) writeNotAvailableError(w http.ResponseWriter) { |
| 162 | w.Header().Set("Content-Type", "text/plain; charset=utf-8") |
| 163 | w.WriteHeader(http.StatusServiceUnavailable) |
| 164 | fmt.Fprintf(w, `IPFS WebUI Not Available |
| 165 | |
| 166 | WebUI at %s is not in your local node due to Gateway.NoFetch=true. |
| 167 | |
| 168 | To use the WebUI, either: |
| 169 | 1. Run: ipfs pin add --progress --name ipfs-webui %s |
| 170 | 2. Download from https://github.com/ipfs/ipfs-webui/releases and import with: ipfs dag import ipfs-webui.car |
| 171 | `, WebUIPath, WebUIPath) |
| 172 | } |