From 33af09195b60fd7364e2190934717a9345ce6cad Mon Sep 17 00:00:00 2001 From: Slack Coder Date: Thu, 14 Sep 2023 13:54:49 -0500 Subject: Allow user to set network address Give the user the ability to set which network address to listen on and the network type (IP 4 or 6 etc.). Randomly choose an available port to avoid conflicting with other services listening on a predefined one. --- README.md | 10 ++++- cmd/send-over-http/main.go | 21 ++++++--- qr_code.go | 2 +- send_over_http.go | 103 +++++++++++++++++++++++++++++++-------------- 4 files changed, 95 insertions(+), 41 deletions(-) diff --git a/README.md b/README.md index e48ac52..0e03ba6 100644 --- a/README.md +++ b/README.md @@ -19,10 +19,16 @@ Inspired by the android app [Share Via HTTP](https://github.com/marcosdiez/share ``` send-over-http --help -# Usage of send-over-http [target] +# Usage send-over-http [target] # -# target - file or directory to share (default: .) +# target: file or directory to share (default: .) # +# Options: +# +# -address string +# network address to accept connections (127.0.0.1:0) +# -net string +# network type to listen on (tcp, tcp4, tcp6) (default "tcp") ``` ## Build Requirements diff --git a/cmd/send-over-http/main.go b/cmd/send-over-http/main.go index 3c587ff..4bd9750 100644 --- a/cmd/send-over-http/main.go +++ b/cmd/send-over-http/main.go @@ -17,27 +17,34 @@ func exitOnErr(err error) { } func usage() { - fmt.Fprintf(flag.CommandLine.Output(), "Usage of %s [target]\n", os.Args[0]) + fmt.Fprintf(flag.CommandLine.Output(), "Usage %s [target]\n", os.Args[0]) fmt.Fprintln(flag.CommandLine.Output()) - fmt.Fprintf(flag.CommandLine.Output(), "\ttarget - file or directory to share (default: .)\n") + fmt.Fprintf(flag.CommandLine.Output(), " target: file or directory to share (default: .)\n") + fmt.Fprintln(flag.CommandLine.Output()) + fmt.Fprintln(flag.CommandLine.Output(), "Options:") + fmt.Fprintln(flag.CommandLine.Output()) + flag.PrintDefaults() fmt.Fprintln(flag.CommandLine.Output()) } func main() { + config := sendoverhttp.Config{} + + flag.StringVar(&config.Network, "net", "tcp", "network type to listen on (tcp, tcp4, tcp6)") + flag.StringVar(&config.ListenAddress, "address", "", "network address to accept connections (example: 127.0.0.1:1234)") flag.Usage = usage flag.Parse() - var fp string if len(os.Args) == 2 { - fp = os.Args[1] + config.FilePath = os.Args[1] } - if fp == "" { + if config.FilePath == "" { v, err := os.Getwd() exitOnErr(err) - fp = v + config.FilePath = v } - s := sendoverhttp.NewServer(fp) + s := sendoverhttp.NewServer(config) go s.Start() time.Sleep(250 * time.Millisecond) diff --git a/qr_code.go b/qr_code.go index 13a4aa0..f21d01f 100644 --- a/qr_code.go +++ b/qr_code.go @@ -25,7 +25,7 @@ func NewTerminalQRShower(str string) *TerminalQRShower { func (s *TerminalQRShower) Start() error { qrterminal.Generate(s.str, qrterminal.L, os.Stdout) fmt.Println() - fmt.Printf("serving on '%s'", s.str) + fmt.Printf("Hosting file at %s", s.str) fmt.Println() fmt.Println() s.waiter.Wait() diff --git a/send_over_http.go b/send_over_http.go index 98b7e6e..2af7890 100644 --- a/send_over_http.go +++ b/send_over_http.go @@ -1,7 +1,7 @@ package sendoverhttp import ( - "fmt" + "errors" "io/fs" "log" "net" @@ -9,32 +9,73 @@ import ( "net/url" "os" "path/filepath" - "strconv" ) -// port is the IP port address to listen on. -const port = 8081 +// Config contains the configuration parameters for the service. +type Config struct { + // FilePath is the file to serve. + FilePath string + // ListenAddress is the network address to listen for connections. + ListenAddress string + // Network type to listen on. + Network string +} + +// Source a single listening address to listen on. +func getListenAddress(config Config) (string, error) { + switch config.Network { + case "tcp", "tcp4", "tcp6": + default: + return config.ListenAddress, nil + } + + addr, port, err := net.SplitHostPort(config.ListenAddress) + if err != nil { + addr = config.ListenAddress + port = "0" + } + + if addr != "" { + ip := net.ParseIP(addr) + if ip == nil { + return config.ListenAddress, nil + } + if !ip.IsUnspecified() { + return config.ListenAddress, nil + } + } -func preferredIP() (*net.IP, error) { ips, err := net.InterfaceAddrs() if err != nil { - return nil, err + return "", err } + + // Valid fallback if no other interfaces available + var loopback *net.IP = nil + for _, addr := range ips { ipNet, ok := addr.(*net.IPNet) if !ok { continue } + if config.Network == "tcp6" && ipNet.IP.To4() != nil { + continue + } if ipNet.IP.IsLoopback() { + loopback = &ipNet.IP continue } - return &ipNet.IP, nil + return net.JoinHostPort(ipNet.IP.String(), port), nil + } + + if loopback != nil { + return net.JoinHostPort((*loopback).String(), port), nil } - return nil, nil + return "", errors.New("no valid network address could be found") } type Server struct { - filepath string + config Config closeCh chan struct{} mr *MultiRunner @@ -67,48 +108,48 @@ func (s SingleFileDir) Open(name string) (http.File, error) { } } -func NewServer(fp string) *Server { +func NewServer(config Config) *Server { return &Server{ - closeCh: make(chan struct{}), - filepath: fp, - mr: NewMultiRunner(), + config: config, + + closeCh: make(chan struct{}), + mr: NewMultiRunner(), } } func (s *Server) Start() error { - ip, err := preferredIP() + listenAddr, err := getListenAddress(s.config) if err != nil { - return err + log.Fatal(err) + } + + l, err := net.Listen(s.config.Network, listenAddr) + if err != nil { + log.Fatal(err) } u := url.URL{ Scheme: "http", - Host: fmt.Sprintf("%s:%d", ip, port), + Host: l.Addr().String(), } - if stat, _ := os.Stat(s.filepath); !stat.IsDir() { - u.Path = filepath.Base(s.filepath) + if stat, _ := os.Stat(s.config.FilePath); !stat.IsDir() { + u.Path = filepath.Base(s.config.FilePath) } qrCodeShower := NewTerminalQRShower(u.String()) go s.mr.Run(qrCodeShower) - httpRunner := NewRunner() - httpSrv := &http.Server{ - Addr: ":" + strconv.Itoa(port), - } - - var d http.FileSystem = http.Dir(s.filepath) - if stat, _ := os.Stat(s.filepath); !stat.IsDir() { + var d http.FileSystem = http.Dir(s.config.FilePath) + if stat, _ := os.Stat(s.config.FilePath); !stat.IsDir() { // Show user the name of the file - d = SingleFileDir(s.filepath) + d = SingleFileDir(s.config.FilePath) + } + httpSrv := &http.Server{ + Handler: http.FileServer(d), } - httpSrv.Handler = http.FileServer(d) + httpRunner := NewRunner() httpRunner.OnStart = func() error { - l, err := net.Listen("tcp4", u.Host) - if err != nil { - log.Fatal(err) - } return httpSrv.Serve(l) } httpRunner.OnStop = func() { -- cgit v1.2.3