Files
WebGateway-Download/Gateway.go
2023-05-17 16:36:02 +01:00

318 lines
9.9 KiB
Go

/*
File Name: Gateway.go
Copyright: 2022 Peernet s.r.o.
Author: Peter Kleissner
*/
package main
import (
"crypto/tls"
"encoding/hex"
"fmt"
"github.com/Akilan1999/p2p-rendering-computation/p2p/frp"
"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("/static/").Handler(
http.StripPrefix("/static/",
http.FileServer(
http.Dir("./html"),
),
),
)
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))
}
if config.Redirect80 != "" {
go webRedirect80(config.Redirect80)
}
// 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)
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,
}
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
}
func redirect(w http.ResponseWriter, r *http.Request) {
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))
}
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
}
// 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, "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
}
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)
}
// MetaDataResponse meta data struct
type MetaDataResponse struct {
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
}
// 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, 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")
play := r.URL.Query().Get("play")
//// 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)
//}
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()
// }
//}
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
webapi.EncodeJSON(backend, w, r, responseMetadata)
return
} else if play == "true" {
// Plays / previews the video
io.Copy(w, io.LimitReader(reader, int64(transferSize)))
}
// set the right headers
//webapi.setContentLengthRangeHeader(w, uint64(offset), transferSize, fileSize, ranges)
http.ServeFile(w, r, path.Join(config.WebFiles, "download.html"))
// Start sending the data!
//io.Copy(w, io.LimitReader(reader, int64(transferSize)))
}
func EscapeNATWebGateway() (ExposePortP2PRC string, err error) {
host, port, err := net.SplitHostPort(config.P2PRCRootPeer)
if err != nil {
return
}
serverPort, err := frp.GetFRPServerPort("http://" + host + ":" + port)
if err != nil {
return
}
time.Sleep(1 * time.Second)
_, port, err = net.SplitHostPort(config.WebListen[0])
if err != nil {
return
}
//port for the barrierKVM server
ExposePortP2PRC, err = frp.StartFRPClientForServer(host, serverPort, port, config.ExposePortP2PRC)
if err != nil {
return
}
return
}