diff --git a/Cmd b/Cmd index 9979560..53089b8 100755 Binary files a/Cmd and b/Cmd differ diff --git a/Gateway.go b/Gateway.go index c2a3c58..f4cadd2 100644 --- a/Gateway.go +++ b/Gateway.go @@ -7,278 +7,278 @@ Author: Peter Kleissner package main import ( - "crypto/tls" - "encoding/hex" - "fmt" - "io" - "net" - "net/http" - "path" - "strings" - "time" + "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" + "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 := mux.NewRouter() - router.PathPrefix("/static/").Handler( - http.StripPrefix("/static/", - http.FileServer( - http.Dir("./html"), - ), - ), - ) + router.PathPrefix("/static/").Handler( + http.StripPrefix("/static/", + http.FileServer( + http.Dir("./html"), + ), + ), + ) - router.PathPrefix("/").Handler(http.HandlerFunc(webGatewayHandler(backend))).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)) - } + for _, listen := range config.WebListen { + go startWebServer(backend, listen, config.WebUseSSL, config.WebCertificateFile, config.WebCertificateKey, router, "Web Listen", parseDuration(config.WebTimeoutRead), parseDuration(config.WebTimeoutWrite)) + } - if config.Redirect80 != "" { - go webRedirect80(config.Redirect80) - } + if config.Redirect80 != "" { + go webRedirect80(config.Redirect80) + } - // wait forever - select {} + // wait forever + select {} } // startWebServer starts the web-server and may block forever func startWebServer(backend *core.Backend, WebListen string, UseSSL bool, CertificateFile, CertificateKey string, Handler http.Handler, Info string, ReadTimeout, WriteTimeout time.Duration) { - //func startWebServer(backend *core.Backend, webListen string, useSSL bool, certificateFile, certificateKey string, server *http.Server) { - backend.LogError("startWebServer", "Web Gateway to listen on '%s'", WebListen) + //func startWebServer(backend *core.Backend, webListen string, useSSL bool, certificateFile, certificateKey string, server *http.Server) { + backend.LogError("startWebServer", "Web Gateway to listen on '%s'", WebListen) - tlsConfig := &tls.Config{MinVersion: tls.VersionTLS12} // for security reasons disable TLS 1.0/1.1 + tlsConfig := &tls.Config{MinVersion: tls.VersionTLS12} // for security reasons disable TLS 1.0/1.1 - server := &http.Server{ - Addr: WebListen, - Handler: Handler, - ReadTimeout: ReadTimeout, // ReadTimeout is the maximum duration for reading the entire request, including the body. - WriteTimeout: WriteTimeout, // WriteTimeout is the maximum duration before timing out writes of the response. This includes processing time and is therefore the max time any HTTP function may take. - //IdleTimeout: IdleTimeout, // IdleTimeout is the maximum amount of time to wait for the next request when keep-alives are enabled. - TLSConfig: tlsConfig, - } + server := &http.Server{ + Addr: WebListen, + Handler: Handler, + ReadTimeout: ReadTimeout, // ReadTimeout is the maximum duration for reading the entire request, including the body. + WriteTimeout: WriteTimeout, // WriteTimeout is the maximum duration before timing out writes of the response. This includes processing time and is therefore the max time any HTTP function may take. + //IdleTimeout: IdleTimeout, // IdleTimeout is the maximum amount of time to wait for the next request when keep-alives are enabled. + TLSConfig: tlsConfig, + } - if UseSSL { - // HTTPS - if err := server.ListenAndServeTLS(CertificateFile, CertificateKey); err != nil { - backend.LogError("startWebServer", "Error listening on '%s': %v\n", WebListen, err) - } - } else { - // HTTP - if err := server.ListenAndServe(); err != nil { - backend.LogError("startWebServer", "Error listening on '%s': %v\n", WebListen, err) - } - } + if UseSSL { + // HTTPS + if err := server.ListenAndServeTLS(CertificateFile, CertificateKey); err != nil { + backend.LogError("startWebServer", "Error listening on '%s': %v\n", WebListen, err) + } + } else { + // HTTP + if err := server.ListenAndServe(); err != nil { + backend.LogError("startWebServer", "Error listening on '%s': %v\n", WebListen, err) + } + } } // parseDuration is the same as time.ParseDuration without returning an error. Valid units are ms, s, m, h. For example "10s". func parseDuration(input string) (result time.Duration) { - result, _ = time.ParseDuration(input) - return + result, _ = time.ParseDuration(input) + return } func redirect(w http.ResponseWriter, r *http.Request) { - http.Redirect(w, r, "https://"+r.Host+r.URL.String(), http.StatusMovedPermanently) + http.Redirect(w, r, "https://"+r.Host+r.URL.String(), http.StatusMovedPermanently) } func webRedirect80(listen80 string) { - // redirect HTTP -> HTTPS - http.ListenAndServe(net.JoinHostPort(listen80, "80"), http.HandlerFunc(redirect)) + // redirect HTTP -> HTTPS + http.ListenAndServe(net.JoinHostPort(listen80, "80"), http.HandlerFunc(redirect)) } 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 - case "/download": - http.ServeFile(w, r, path.Join(config.WebFiles, "download.html")) - return - } + 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 + case "/download": + http.ServeFile(w, r, path.Join(config.WebFiles, "download.html")) + return + } - // Assume it is in the format "/[blockchain public key]/[file hash]". + // Assume it is in the format "/[blockchain public key]/[file hash]". - // Remove slash prefix and suffix. - pathA := strings.TrimPrefix(strings.TrimSuffix(r.URL.Path, "/"), "/") + // 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 - } + 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 + // 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) + // 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, "Invalid NodeID", http.StatusBadRequest) - return - } - if !validNodeID { - nodeID = []byte{} - } + if !validNodeID && errPK != nil { + http.Error(w, "Invalid NodeID", http.StatusBadRequest) + 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 - } + // 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) - } + 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 + // Check if an arbitrary directory on a specific blockchain is requested. + // Directories are identified by name. + // TODO - //http.Error(w, "test handler", http.StatusOK) - } + //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 - } + 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) + // 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) } // MetaDataResponse meta data struct type MetaDataResponse struct { - Name string - Hash []byte - Size uint64 + Name string + Hash []byte + Size uint64 } 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 - } + 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: Try webapi.serveFileFromWarehouse - // Todo: Cache. + // Todo: Cache. - offset := 0 - limit := 0 + offset := 0 + limit := 0 - // start the reader - //reader, fileSize, transferSize, err := webapi.FileStartReader(peer, fileHash, uint64(offset), uint64(limit), r.Context().Done()) - reader, fileSize, 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 - } + // start the reader + //reader, fileSize, transferSize, err := webapi.FileStartReader(peer, fileHash, uint64(offset), uint64(limit), r.Context().Done()) + reader, fileSize, 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 + } - // if get request ?download=true - // trigger download of the file - download := r.URL.Query().Get("download") - metadata := r.URL.Query().Get("metadata") - filename := r.URL.Query().Get("filename") + // if get request ?download=true + // trigger download of the file + download := r.URL.Query().Get("download") + metadata := r.URL.Query().Get("metadata") + filename := r.URL.Query().Get("filename") - //// look up file based on NodeID - //var resultMap map[uuid.UUID]*search.SearchIndexRecord - //data, _ := peer.Backend.SearchIndex.Database.Get(fileHash) - //err = peer.Backend.SearchIndex.LookupHash(search.SearchSelector{Hash: fileHash}, resultMap) - //if err != nil { - // fmt.Println(err) - //} - // - //var fileIndexRecord search.SearchIndexRecord - //// looping over results and ensuring the public key matches - //// This is to ensure the correct NodeID matches with the correct hash - //for i, _ := range resultMap { - // if resultMap[i].PublicKey == peer.PublicKey { - // resultMap[i] = &fileIndexRecord - // break - // } - //} + //// look up file based on NodeID + //var resultMap map[uuid.UUID]*search.SearchIndexRecord + //data, _ := peer.Backend.SearchIndex.Database.Get(fileHash) + //err = peer.Backend.SearchIndex.LookupHash(search.SearchSelector{Hash: fileHash}, resultMap) + //if err != nil { + // fmt.Println(err) + //} + // + //var fileIndexRecord search.SearchIndexRecord + //// looping over results and ensuring the public key matches + //// This is to ensure the correct NodeID matches with the correct hash + //for i, _ := range resultMap { + // if resultMap[i].PublicKey == peer.PublicKey { + // resultMap[i] = &fileIndexRecord + // break + // } + //} - //// Get information from the blockchain about a particular file - //file, _, found, err := backend.ReadFile(peer.PublicKey, peer.BlockchainVersion, fileIndexRecord.BlockNumber, fileIndexRecord.FileID) - //if err != nil { - // fmt.Println(err) - // fmt.Println(found) - //} + //// Get information from the blockchain about a particular file + //file, _, found, err := backend.ReadFile(peer.PublicKey, peer.BlockchainVersion, fileIndexRecord.BlockNumber, fileIndexRecord.FileID) + //if err != nil { + // fmt.Println(err) + // fmt.Println(found) + //} - var responseMetadata MetaDataResponse + var responseMetadata MetaDataResponse - //// Get name from file information - //for i, _ := range file.Tags { - // // This is for the file name - // if blockchain.TagName == file.Tags[i].Type { - // responseMetadata.Name = file.Tags[i].Text() - // } - //} + //// Get name from file information + //for i, _ := range file.Tags { + // // This is for the file name + // if blockchain.TagName == file.Tags[i].Type { + // responseMetadata.Name = file.Tags[i].Text() + // } + //} - if filename != "" { - responseMetadata.Name = filename - } + if filename != "" { + responseMetadata.Name = filename + } - if download == "true" { - w.Header().Set("Content-Disposition", "attachment; filename="+responseMetadata.Name) - // JSON response with meta data instead - io.Copy(w, io.LimitReader(reader, int64(transferSize))) - return - } else if metadata == "true" { - // JSON response of the file meta-data - responseMetadata.Size = fileSize - responseMetadata.Hash = fileHash + if download == "true" { + w.Header().Set("Content-Disposition", "attachment; filename="+responseMetadata.Name) + // JSON response with meta data instead + io.Copy(w, io.LimitReader(reader, int64(transferSize))) + return + } else if metadata == "true" { + // JSON response of the file meta-data + responseMetadata.Size = fileSize + responseMetadata.Hash = fileHash - webapi.EncodeJSON(backend, w, r, responseMetadata) - return - } + webapi.EncodeJSON(backend, w, r, responseMetadata) + return + } - // set the right headers - //webapi.setContentLengthRangeHeader(w, uint64(offset), transferSize, fileSize, ranges) + // set the right headers + //webapi.setContentLengthRangeHeader(w, uint64(offset), transferSize, fileSize, ranges) - http.ServeFile(w, r, path.Join(config.WebFiles, "download.html")) + http.ServeFile(w, r, path.Join(config.WebFiles, "download.html")) - // Start sending the data! - //io.Copy(w, io.LimitReader(reader, int64(transferSize))) + // Start sending the data! + //io.Copy(w, io.LimitReader(reader, int64(transferSize))) }