stable download metadata page

This commit is contained in:
2023-03-20 23:28:02 +00:00
parent 14a3e076dc
commit 9319b600c5
2 changed files with 209 additions and 209 deletions

BIN
Cmd

Binary file not shown.

View File

@@ -7,278 +7,278 @@ Author: Peter Kleissner
package main package main
import ( import (
"crypto/tls" "crypto/tls"
"encoding/hex" "encoding/hex"
"fmt" "fmt"
"io" "io"
"net" "net"
"net/http" "net/http"
"path" "path"
"strings" "strings"
"time" "time"
"github.com/PeernetOfficial/core" "github.com/PeernetOfficial/core"
"github.com/PeernetOfficial/core/btcec" "github.com/PeernetOfficial/core/btcec"
"github.com/PeernetOfficial/core/webapi" "github.com/PeernetOfficial/core/webapi"
"github.com/gorilla/mux" "github.com/gorilla/mux"
) )
func startWebGateway(backend *core.Backend) { func startWebGateway(backend *core.Backend) {
router := mux.NewRouter() router := mux.NewRouter()
router.PathPrefix("/static/").Handler( router.PathPrefix("/static/").Handler(
http.StripPrefix("/static/", http.StripPrefix("/static/",
http.FileServer( http.FileServer(
http.Dir("./html"), 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 { for _, listen := range config.WebListen {
go startWebServer(backend, listen, config.WebUseSSL, config.WebCertificateFile, config.WebCertificateKey, router, "Web Listen", parseDuration(config.WebTimeoutRead), parseDuration(config.WebTimeoutWrite)) go startWebServer(backend, listen, config.WebUseSSL, config.WebCertificateFile, config.WebCertificateKey, router, "Web Listen", parseDuration(config.WebTimeoutRead), parseDuration(config.WebTimeoutWrite))
} }
if config.Redirect80 != "" { if config.Redirect80 != "" {
go webRedirect80(config.Redirect80) go webRedirect80(config.Redirect80)
} }
// wait forever // wait forever
select {} select {}
} }
// startWebServer starts the web-server and may block forever // 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, Handler http.Handler, Info string, ReadTimeout, WriteTimeout time.Duration) {
//func startWebServer(backend *core.Backend, webListen string, useSSL bool, certificateFile, certificateKey string, server *http.Server) { //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) 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{ server := &http.Server{
Addr: WebListen, Addr: WebListen,
Handler: Handler, Handler: Handler,
ReadTimeout: ReadTimeout, // ReadTimeout is the maximum duration for reading the entire request, including the body. 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. 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. //IdleTimeout: IdleTimeout, // IdleTimeout is the maximum amount of time to wait for the next request when keep-alives are enabled.
TLSConfig: tlsConfig, TLSConfig: tlsConfig,
} }
if UseSSL { if UseSSL {
// HTTPS // HTTPS
if err := server.ListenAndServeTLS(CertificateFile, CertificateKey); err != nil { if err := server.ListenAndServeTLS(CertificateFile, CertificateKey); err != nil {
backend.LogError("startWebServer", "Error listening on '%s': %v\n", WebListen, err) backend.LogError("startWebServer", "Error listening on '%s': %v\n", WebListen, err)
} }
} else { } else {
// HTTP // HTTP
if err := server.ListenAndServe(); err != nil { if err := server.ListenAndServe(); err != nil {
backend.LogError("startWebServer", "Error listening on '%s': %v\n", WebListen, err) 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". // 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) { func parseDuration(input string) (result time.Duration) {
result, _ = time.ParseDuration(input) result, _ = time.ParseDuration(input)
return return
} }
func redirect(w http.ResponseWriter, r *http.Request) { 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) { func webRedirect80(listen80 string) {
// redirect HTTP -> HTTPS // redirect HTTP -> HTTPS
http.ListenAndServe(net.JoinHostPort(listen80, "80"), http.HandlerFunc(redirect)) http.ListenAndServe(net.JoinHostPort(listen80, "80"), http.HandlerFunc(redirect))
} }
func webGatewayHandler(backend *core.Backend) func(w http.ResponseWriter, r *http.Request) { func webGatewayHandler(backend *core.Backend) func(w http.ResponseWriter, r *http.Request) {
return 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. // For security and simplicity reasons, below paths are hard-coded.
// Using arbitrary user input is an avoidable security risk here. // Using arbitrary user input is an avoidable security risk here.
switch r.URL.Path { switch r.URL.Path {
case "/", "/index.html": case "/", "/index.html":
http.ServeFile(w, r, path.Join(config.WebFiles, "index.html")) http.ServeFile(w, r, path.Join(config.WebFiles, "index.html"))
return return
case "/favicon.ico": case "/favicon.ico":
http.ServeFile(w, r, path.Join(config.WebFiles, "favicon.ico")) http.ServeFile(w, r, path.Join(config.WebFiles, "favicon.ico"))
return return
case "/download": case "/download":
http.ServeFile(w, r, path.Join(config.WebFiles, "download.html")) http.ServeFile(w, r, path.Join(config.WebFiles, "download.html"))
return 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. // Remove slash prefix and suffix.
pathA := strings.TrimPrefix(strings.TrimSuffix(r.URL.Path, "/"), "/") pathA := strings.TrimPrefix(strings.TrimSuffix(r.URL.Path, "/"), "/")
pathParts := strings.Split(pathA, "/") pathParts := strings.Split(pathA, "/")
if len(pathParts) != 1 && len(pathParts) != 2 { if len(pathParts) != 1 && len(pathParts) != 2 {
http.Error(w, "404 not found", http.StatusNotFound) http.Error(w, "404 not found", http.StatusNotFound)
return return
} }
// Default timeout for connection is 10 seconds. This will be an optional parameter in the future. // Default timeout for connection is 10 seconds. This will be an optional parameter in the future.
timeout := 10 * time.Second timeout := 10 * time.Second
// First part must be the public key as peer ID or node ID, hex encoded. Form: "/[blockchain public key]" // First part must be the public key as peer ID or node ID, hex encoded. Form: "/[blockchain public key]"
nodeIDA := pathParts[0] nodeIDA := pathParts[0]
nodeID, validNodeID := webapi.DecodeBlake3Hash(nodeIDA) nodeID, validNodeID := webapi.DecodeBlake3Hash(nodeIDA)
publicKey, errPK := core.PublicKeyFromPeerID(nodeIDA) publicKey, errPK := core.PublicKeyFromPeerID(nodeIDA)
if !validNodeID && errPK != nil { if !validNodeID && errPK != nil {
http.Error(w, "Invalid NodeID", http.StatusBadRequest) http.Error(w, "Invalid NodeID", http.StatusBadRequest)
return return
} }
if !validNodeID { if !validNodeID {
nodeID = []byte{} nodeID = []byte{}
} }
// Check if a blockchain is requested. // Check if a blockchain is requested.
// The format must be "/[blockchain public key]". Part 1 = hex encoding, peer ID or node ID. // The format must be "/[blockchain public key]". Part 1 = hex encoding, peer ID or node ID.
if len(pathParts) == 1 { if len(pathParts) == 1 {
webGatewayShowBlockchain(backend, w, r, nodeID, publicKey, timeout) webGatewayShowBlockchain(backend, w, r, nodeID, publicKey, timeout)
} else if len(pathParts) == 2 { } else if len(pathParts) == 2 {
// Check if a specific file on a specific blockchain is requested. // 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. // The format must be "/[blockchain public key]/[file hash]". Part 2 = hex encoding, blake3 hash.
hash, valid := webapi.DecodeBlake3Hash(pathParts[1]) hash, valid := webapi.DecodeBlake3Hash(pathParts[1])
if !valid { if !valid {
http.Error(w, "Invalid file hash.", http.StatusBadRequest) http.Error(w, "Invalid file hash.", http.StatusBadRequest)
return 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. // Check if an arbitrary directory on a specific blockchain is requested.
// Directories are identified by name. // Directories are identified by name.
// TODO // 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) { func webGatewayShowBlockchain(backend *core.Backend, w http.ResponseWriter, r *http.Request, nodeID []byte, publicKey *btcec.PublicKey, timeout time.Duration) {
var err error var err error
var peer *core.PeerInfo var peer *core.PeerInfo
if len(nodeID) != 0 { if len(nodeID) != 0 {
peer, err = webapi.PeerConnectNode(backend, nodeID, timeout) peer, err = webapi.PeerConnectNode(backend, nodeID, timeout)
} else { } else {
peer, err = webapi.PeerConnectPublicKey(backend, publicKey, timeout) peer, err = webapi.PeerConnectPublicKey(backend, publicKey, timeout)
} }
if err != nil { if err != nil {
http.Error(w, "Could not connect to remote peer. 😢", http.StatusNotFound) http.Error(w, "Could not connect to remote peer. 😢", http.StatusNotFound)
return return
} }
// connection established! // 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) 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) http.Error(w, text, http.StatusOK)
} }
// MetaDataResponse meta data struct // MetaDataResponse meta data struct
type MetaDataResponse struct { type MetaDataResponse struct {
Name string Name string
Hash []byte Hash []byte
Size uint64 Size uint64
} }
func webGatewayShowFile(backend *core.Backend, w http.ResponseWriter, r *http.Request, nodeID []byte, publicKey *btcec.PublicKey, fileHash []byte, timeout time.Duration) { 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 err error
var peer *core.PeerInfo var peer *core.PeerInfo
if len(nodeID) != 0 { if len(nodeID) != 0 {
peer, err = webapi.PeerConnectNode(backend, nodeID, timeout) peer, err = webapi.PeerConnectNode(backend, nodeID, timeout)
} else { } else {
peer, err = webapi.PeerConnectPublicKey(backend, publicKey, timeout) peer, err = webapi.PeerConnectPublicKey(backend, publicKey, timeout)
} }
if err != nil { if err != nil {
http.Error(w, "Could not connect to remote peer. 😢", http.StatusNotFound) http.Error(w, "Could not connect to remote peer. 😢", http.StatusNotFound)
return return
} }
// Todo: Try webapi.serveFileFromWarehouse // Todo: Try webapi.serveFileFromWarehouse
// Todo: Cache. // Todo: Cache.
offset := 0 offset := 0
limit := 0 limit := 0
// start the reader // 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())
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 { if reader != nil {
defer reader.Close() defer reader.Close()
} }
if err != nil || reader == nil { if err != nil || reader == nil {
http.Error(w, "File not found.", http.StatusNotFound) http.Error(w, "File not found.", http.StatusNotFound)
return return
} }
// if get request ?download=true // if get request ?download=true
// trigger download of the file // trigger download of the file
download := r.URL.Query().Get("download") download := r.URL.Query().Get("download")
metadata := r.URL.Query().Get("metadata") metadata := r.URL.Query().Get("metadata")
filename := r.URL.Query().Get("filename") filename := r.URL.Query().Get("filename")
//// look up file based on NodeID //// look up file based on NodeID
//var resultMap map[uuid.UUID]*search.SearchIndexRecord //var resultMap map[uuid.UUID]*search.SearchIndexRecord
//data, _ := peer.Backend.SearchIndex.Database.Get(fileHash) //data, _ := peer.Backend.SearchIndex.Database.Get(fileHash)
//err = peer.Backend.SearchIndex.LookupHash(search.SearchSelector{Hash: fileHash}, resultMap) //err = peer.Backend.SearchIndex.LookupHash(search.SearchSelector{Hash: fileHash}, resultMap)
//if err != nil { //if err != nil {
// fmt.Println(err) // fmt.Println(err)
//} //}
// //
//var fileIndexRecord search.SearchIndexRecord //var fileIndexRecord search.SearchIndexRecord
//// looping over results and ensuring the public key matches //// looping over results and ensuring the public key matches
//// This is to ensure the correct NodeID matches with the correct hash //// This is to ensure the correct NodeID matches with the correct hash
//for i, _ := range resultMap { //for i, _ := range resultMap {
// if resultMap[i].PublicKey == peer.PublicKey { // if resultMap[i].PublicKey == peer.PublicKey {
// resultMap[i] = &fileIndexRecord // resultMap[i] = &fileIndexRecord
// break // break
// } // }
//} //}
//// Get information from the blockchain about a particular file //// Get information from the blockchain about a particular file
//file, _, found, err := backend.ReadFile(peer.PublicKey, peer.BlockchainVersion, fileIndexRecord.BlockNumber, fileIndexRecord.FileID) //file, _, found, err := backend.ReadFile(peer.PublicKey, peer.BlockchainVersion, fileIndexRecord.BlockNumber, fileIndexRecord.FileID)
//if err != nil { //if err != nil {
// fmt.Println(err) // fmt.Println(err)
// fmt.Println(found) // fmt.Println(found)
//} //}
var responseMetadata MetaDataResponse var responseMetadata MetaDataResponse
//// Get name from file information //// Get name from file information
//for i, _ := range file.Tags { //for i, _ := range file.Tags {
// // This is for the file name // // This is for the file name
// if blockchain.TagName == file.Tags[i].Type { // if blockchain.TagName == file.Tags[i].Type {
// responseMetadata.Name = file.Tags[i].Text() // responseMetadata.Name = file.Tags[i].Text()
// } // }
//} //}
if filename != "" { if filename != "" {
responseMetadata.Name = filename responseMetadata.Name = filename
} }
if download == "true" { if download == "true" {
w.Header().Set("Content-Disposition", "attachment; filename="+responseMetadata.Name) w.Header().Set("Content-Disposition", "attachment; filename="+responseMetadata.Name)
// JSON response with meta data instead // JSON response with meta data instead
io.Copy(w, io.LimitReader(reader, int64(transferSize))) io.Copy(w, io.LimitReader(reader, int64(transferSize)))
return return
} else if metadata == "true" { } else if metadata == "true" {
// JSON response of the file meta-data // JSON response of the file meta-data
responseMetadata.Size = fileSize responseMetadata.Size = fileSize
responseMetadata.Hash = fileHash responseMetadata.Hash = fileHash
webapi.EncodeJSON(backend, w, r, responseMetadata) webapi.EncodeJSON(backend, w, r, responseMetadata)
return return
} }
// set the right headers // set the right headers
//webapi.setContentLengthRangeHeader(w, uint64(offset), transferSize, fileSize, ranges) //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! // Start sending the data!
//io.Copy(w, io.LimitReader(reader, int64(transferSize))) //io.Copy(w, io.LimitReader(reader, int64(transferSize)))
} }