send-over-http

Share a file with someone on your local network using QR Code
git clone git://git.server.ky/slackcoder/send-over-http
Log | Files | Refs | README

send_over_http.go (3135B)


      1 package sendoverhttp
      2 
      3 import (
      4 	"errors"
      5 	"io/fs"
      6 	"log"
      7 	"net"
      8 	"net/http"
      9 	"net/url"
     10 	"os"
     11 	"path/filepath"
     12 )
     13 
     14 // Config contains the configuration parameters for the service.
     15 type Config struct {
     16 	// FilePath is the file to serve.
     17 	FilePath string
     18 	// ListenAddress is the network address to listen for connections.
     19 	ListenAddress string
     20 	// Network type to listen on.
     21 	Network string
     22 }
     23 
     24 // Source a single listening address to listen on.
     25 func getListenAddress(config Config) (string, error) {
     26 	switch config.Network {
     27 	case "tcp", "tcp4", "tcp6":
     28 	default:
     29 		return config.ListenAddress, nil
     30 	}
     31 
     32 	addr, port, err := net.SplitHostPort(config.ListenAddress)
     33 	if err != nil {
     34 		addr = config.ListenAddress
     35 		port = "0"
     36 	}
     37 
     38 	if addr != "" {
     39 		ip := net.ParseIP(addr)
     40 		if ip == nil {
     41 			return config.ListenAddress, nil
     42 		}
     43 		if !ip.IsUnspecified() {
     44 			return config.ListenAddress, nil
     45 		}
     46 	}
     47 
     48 	ips, err := net.InterfaceAddrs()
     49 	if err != nil {
     50 		return "", err
     51 	}
     52 
     53 	// Valid fallback if no other interfaces available
     54 	var loopback *net.IP = nil
     55 
     56 	for _, addr := range ips {
     57 		ipNet, ok := addr.(*net.IPNet)
     58 		if !ok {
     59 			continue
     60 		}
     61 		if config.Network == "tcp6" && ipNet.IP.To4() != nil {
     62 			continue
     63 		}
     64 		if ipNet.IP.IsLoopback() {
     65 			loopback = &ipNet.IP
     66 			continue
     67 		}
     68 		return net.JoinHostPort(ipNet.IP.String(), port), nil
     69 	}
     70 
     71 	if loopback != nil {
     72 		return net.JoinHostPort((*loopback).String(), port), nil
     73 	}
     74 	return "", errors.New("no valid network address could be found")
     75 }
     76 
     77 type Server struct {
     78 	config Config
     79 
     80 	closeCh chan struct{}
     81 	mr      *MultiRunner
     82 }
     83 
     84 type SingleFileDir string
     85 
     86 func (s SingleFileDir) Open(name string) (http.File, error) {
     87 	if name == "index.html" {
     88 		return nil, fs.ErrNotExist
     89 	}
     90 
     91 	fp := string(s)
     92 	stat, err := os.Stat(fp)
     93 	if err != nil {
     94 		return nil, err
     95 	}
     96 	if stat.IsDir() {
     97 		return http.Dir(fp).Open(name)
     98 	}
     99 
    100 	switch name {
    101 	case "/":
    102 		return nil, os.ErrNotExist
    103 	case "/" + filepath.Base(fp):
    104 		return os.Open(fp)
    105 	default:
    106 		return nil, os.ErrNotExist
    107 
    108 	}
    109 }
    110 
    111 func NewServer(config Config) *Server {
    112 	return &Server{
    113 		config: config,
    114 
    115 		closeCh: make(chan struct{}),
    116 		mr:      NewMultiRunner(),
    117 	}
    118 }
    119 
    120 func (s *Server) Start() error {
    121 	listenAddr, err := getListenAddress(s.config)
    122 	if err != nil {
    123 		log.Fatal(err)
    124 	}
    125 
    126 	l, err := net.Listen(s.config.Network, listenAddr)
    127 	if err != nil {
    128 		log.Fatal(err)
    129 	}
    130 
    131 	u := url.URL{
    132 		Scheme: "http",
    133 		Host:   l.Addr().String(),
    134 	}
    135 	if stat, _ := os.Stat(s.config.FilePath); !stat.IsDir() {
    136 		u.Path = filepath.Base(s.config.FilePath)
    137 	}
    138 
    139 	qrCodeShower := NewTerminalQRShower(u.String())
    140 	go s.mr.Run(qrCodeShower)
    141 
    142 	var d http.FileSystem = http.Dir(s.config.FilePath)
    143 	if stat, _ := os.Stat(s.config.FilePath); !stat.IsDir() {
    144 		// Show user the name of the file
    145 		d = SingleFileDir(s.config.FilePath)
    146 	}
    147 	httpSrv := &http.Server{
    148 		Handler: http.FileServer(d),
    149 	}
    150 
    151 	httpRunner := NewRunner()
    152 	httpRunner.OnStart = func() error {
    153 		return httpSrv.Serve(l)
    154 	}
    155 	httpRunner.OnStop = func() {
    156 		_ = httpSrv.Close()
    157 	}
    158 	go s.mr.Run(httpRunner)
    159 
    160 	<-s.closeCh
    161 	close(s.closeCh)
    162 	return nil
    163 }
    164 
    165 func (s *Server) Stop() {
    166 	s.mr.Stop()
    167 	return
    168 }