diff --git a/Gateway.go b/Gateway.go index 0685514..ee1367e 100644 --- a/Gateway.go +++ b/Gateway.go @@ -8,18 +8,24 @@ package main import ( "crypto/tls" + "encoding/hex" + "fmt" + "io" "net" "net/http" + "path" + "strings" "time" "github.com/PeernetOfficial/core" + "github.com/PeernetOfficial/core/btcec" + "github.com/PeernetOfficial/core/webapi" "github.com/gorilla/mux" ) func startWebGateway(backend *core.Backend) { router := mux.NewRouter() - router.PathPrefix("/").Handler(http.HandlerFunc(webGatewayHandler)).Methods("GET") - router.PathPrefix("/").Handler(http.StripPrefix("/", http.FileServer(http.Dir(config.WebFiles)))).Methods("GET") + router.PathPrefix("/").Handler(http.HandlerFunc(webGatewayHandler(backend))).Methods("GET") for _, listen := range config.WebListen { go startWebServer(backend, listen, config.WebUseSSL, config.WebCertificateFile, config.WebCertificateKey, router, "Web Listen", parseDuration(config.WebTimeoutRead), parseDuration(config.WebTimeoutWrite)) @@ -77,6 +83,122 @@ func webRedirect80(listen80 string) { http.ListenAndServe(net.JoinHostPort(listen80, "80"), http.HandlerFunc(redirect)) } -func webGatewayHandler(w http.ResponseWriter, r *http.Request) { - http.Error(w, "404 page not found", http.StatusNotFound) +func webGatewayHandler(backend *core.Backend) func(w http.ResponseWriter, r *http.Request) { + return func(w http.ResponseWriter, r *http.Request) { + // For security and simplicity reasons, below paths are hard-coded. + // Using arbitrary user input is an avoidable security risk here. + switch r.URL.Path { + case "/", "/index.html": + http.ServeFile(w, r, path.Join(config.WebFiles, "index.html")) + return + case "/favicon.ico": + http.ServeFile(w, r, path.Join(config.WebFiles, "favicon.ico")) + return + } + + // Assume it is in the format "/[blockchain public key]/[file hash]". + + // Remove slash prefix and suffix. + pathA := strings.TrimPrefix(strings.TrimSuffix(r.URL.Path, "/"), "/") + + pathParts := strings.Split(pathA, "/") + if len(pathParts) != 1 && len(pathParts) != 2 { + http.Error(w, "404 not found", http.StatusNotFound) + return + } + + // Default timeout for connection is 10 seconds. This will be an optional parameter in the future. + timeout := 10 * time.Second + + // First part must be the public key as peer ID or node ID, hex encoded. Form: "/[blockchain public key]" + nodeIDA := pathParts[0] + nodeID, validNodeID := webapi.DecodeBlake3Hash(nodeIDA) + publicKey, errPK := core.PublicKeyFromPeerID(nodeIDA) + + if !validNodeID && errPK != nil { + http.Error(w, "404 not found", http.StatusNotFound) + return + } + if !validNodeID { + nodeID = []byte{} + } + + // Check if a blockchain is requested. + // The format must be "/[blockchain public key]". Part 1 = hex encoding, peer ID or node ID. + if len(pathParts) == 1 { + webGatewayShowBlockchain(backend, w, r, nodeID, publicKey, timeout) + } else if len(pathParts) == 2 { + // Check if a specific file on a specific blockchain is requested. + // The format must be "/[blockchain public key]/[file hash]". Part 2 = hex encoding, blake3 hash. + hash, valid := webapi.DecodeBlake3Hash(pathParts[1]) + if !valid { + http.Error(w, "Invalid file hash.", http.StatusBadRequest) + return + } + + webGatewayShowFile(backend, w, r, nodeID, publicKey, hash, timeout) + } + + // Check if an arbitrary directory on a specific blockchain is requested. + // Directories are identified by name. + // TODO + + //http.Error(w, "test handler", http.StatusOK) + } +} + +func webGatewayShowBlockchain(backend *core.Backend, w http.ResponseWriter, r *http.Request, nodeID []byte, publicKey *btcec.PublicKey, timeout time.Duration) { + var err error + var peer *core.PeerInfo + if len(nodeID) != 0 { + peer, err = webapi.PeerConnectNode(backend, nodeID, timeout) + } else { + peer, err = webapi.PeerConnectPublicKey(backend, publicKey, timeout) + } + if err != nil { + http.Error(w, "Could not connect to remote peer. 😢", http.StatusNotFound) + return + } + + // connection established! + text := fmt.Sprintf("Peer %s blockchain height %d version %d\nUser Agent: %s\n", hex.EncodeToString(peer.NodeID), peer.BlockchainHeight, peer.BlockchainVersion, peer.UserAgent) + http.Error(w, text, http.StatusOK) +} + +func webGatewayShowFile(backend *core.Backend, w http.ResponseWriter, r *http.Request, nodeID []byte, publicKey *btcec.PublicKey, fileHash []byte, timeout time.Duration) { + var err error + var peer *core.PeerInfo + if len(nodeID) != 0 { + peer, err = webapi.PeerConnectNode(backend, nodeID, timeout) + } else { + peer, err = webapi.PeerConnectPublicKey(backend, publicKey, timeout) + } + if err != nil { + http.Error(w, "Could not connect to remote peer. 😢", http.StatusNotFound) + return + } + + // Todo: Try webapi.serveFileFromWarehouse + + // Todo: Cache. + + offset := 0 + limit := 0 + + // start the reader + //reader, fileSize, transferSize, err := webapi.FileStartReader(peer, fileHash, uint64(offset), uint64(limit), r.Context().Done()) + reader, _, transferSize, err := webapi.FileStartReader(peer, fileHash, uint64(offset), uint64(limit), r.Context().Done()) + if reader != nil { + defer reader.Close() + } + if err != nil || reader == nil { + http.Error(w, "File not found.", http.StatusNotFound) + return + } + + // set the right headers + //webapi.setContentLengthRangeHeader(w, uint64(offset), transferSize, fileSize, ranges) + + // Start sending the data! + io.Copy(w, io.LimitReader(reader, int64(transferSize))) } diff --git a/go.mod b/go.mod index 2815db3..3f98004 100644 --- a/go.mod +++ b/go.mod @@ -8,9 +8,11 @@ require ( ) require ( + github.com/IncSW/geoip2 v0.1.2 // indirect github.com/akrylysov/pogreb v0.10.1 // indirect github.com/enfipy/locker v1.1.0 // indirect github.com/google/uuid v1.3.0 // indirect + github.com/gorilla/websocket v1.5.0 // indirect github.com/klauspost/cpuid/v2 v2.0.12 // indirect golang.org/x/crypto v0.0.0-20220525230936-793ad666bf5e // indirect golang.org/x/net v0.0.0-20220531201128-c960675eff93 // indirect diff --git a/go.sum b/go.sum index 7a59a99..42285aa 100644 --- a/go.sum +++ b/go.sum @@ -1,3 +1,5 @@ +github.com/IncSW/geoip2 v0.1.2 h1:v7iAyDiNZjHES45P1JPM3SMvkw0VNeJtz0XSVxkRwOY= +github.com/IncSW/geoip2 v0.1.2/go.mod h1:adcasR40vXiUBjtzdaTTKL/6wSf+fgO4M8Gve/XzPUk= github.com/PeernetOfficial/core v0.0.0-20220601150942-0e3e8dc9885c h1:n+NGYHQulohXk//v2LYOgCVsbL9h5COupt+2ZhbtGqs= github.com/PeernetOfficial/core v0.0.0-20220601150942-0e3e8dc9885c/go.mod h1:Ek4/3aKyKnA/KDSNe/yeorX5TA0fckpN7etZ+nCelGc= github.com/akrylysov/pogreb v0.10.1 h1:FqlR8VR7uCbJdfUob916tPM+idpKgeESDXOA1K0DK4w= @@ -8,6 +10,8 @@ github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI= github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= +github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc= +github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= github.com/klauspost/cpuid/v2 v2.0.12 h1:p9dKCg8i4gmOxtv35DvrYoWqYzQrvEVdjQ762Y0OqZE= github.com/klauspost/cpuid/v2 v2.0.12/go.mod h1:g2LTdtYhdyuGPqyWyv7qRAmj1WBqxuObKfj5c0PQa7c=