server.go 9.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352
  1. package server
  2. import (
  3. "context"
  4. "crypto/tls"
  5. "fmt"
  6. "image/jpeg"
  7. "io"
  8. "log"
  9. "net"
  10. "net/http"
  11. "os"
  12. "os/exec"
  13. "os/signal"
  14. "path/filepath"
  15. "runtime"
  16. "strings"
  17. "sync"
  18. "github.com/claudiodangelis/qrcp/qr"
  19. "github.com/claudiodangelis/qrcp/config"
  20. "github.com/claudiodangelis/qrcp/pages"
  21. "github.com/claudiodangelis/qrcp/payload"
  22. "github.com/claudiodangelis/qrcp/util"
  23. "gopkg.in/cheggaaa/pb.v1"
  24. )
  25. // Server is the server
  26. type Server struct {
  27. BaseURL string
  28. // SendURL is the URL used to send the file
  29. SendURL string
  30. // ReceiveURL is the URL used to Receive the file
  31. ReceiveURL string
  32. instance *http.Server
  33. payload payload.Payload
  34. outputDir string
  35. stopChannel chan bool
  36. // expectParallelRequests is set to true when qrcp sends files, in order
  37. // to support downloading of parallel chunks
  38. expectParallelRequests bool
  39. }
  40. // ReceiveTo sets the output directory
  41. func (s *Server) ReceiveTo(dir string) error {
  42. output, err := filepath.Abs(dir)
  43. if err != nil {
  44. return err
  45. }
  46. // Check if the output dir exists
  47. fileinfo, err := os.Stat(output)
  48. if err != nil {
  49. return err
  50. }
  51. if !fileinfo.IsDir() {
  52. return fmt.Errorf("%s is not a valid directory", output)
  53. }
  54. s.outputDir = output
  55. return nil
  56. }
  57. // Send adds a handler for sending the file
  58. func (s *Server) Send(p payload.Payload) {
  59. s.payload = p
  60. s.expectParallelRequests = true
  61. }
  62. // DisplayQR creates a handler for serving the QR code in the browser
  63. func (s *Server) DisplayQR(url string) {
  64. const PATH = "/qr"
  65. qrImg := qr.RenderImage(url)
  66. http.HandleFunc(PATH, func(w http.ResponseWriter, r *http.Request) {
  67. w.Header().Set("Content-Type", "image/jpeg")
  68. if err := jpeg.Encode(w, qrImg, nil); err != nil {
  69. panic(err)
  70. }
  71. })
  72. openBrowser(s.BaseURL + PATH)
  73. }
  74. // Wait for transfer to be completed, it waits forever if kept awlive
  75. func (s Server) Wait() error {
  76. <-s.stopChannel
  77. if err := s.instance.Shutdown(context.Background()); err != nil {
  78. log.Println(err)
  79. }
  80. if s.payload.DeleteAfterTransfer {
  81. if err := s.payload.Delete(); err != nil {
  82. panic(err)
  83. }
  84. }
  85. return nil
  86. }
  87. // Shutdown the server
  88. func (s Server) Shutdown() {
  89. s.stopChannel <- true
  90. }
  91. // New instance of the server
  92. func New(cfg *config.Config) (*Server, error) {
  93. app := &Server{}
  94. // Get the address of the configured interface to bind the server to
  95. bind, err := util.GetInterfaceAddress(cfg.Interface)
  96. if err != nil {
  97. return &Server{}, err
  98. }
  99. // Create a listener. If `port: 0`, a random one is chosen
  100. listener, err := net.Listen("tcp", fmt.Sprintf("%s:%d", bind, cfg.Port))
  101. if err != nil {
  102. return nil, err
  103. }
  104. // Set the value of computed port
  105. port := listener.Addr().(*net.TCPAddr).Port
  106. // Set the host
  107. host := fmt.Sprintf("%s:%d", bind, port)
  108. // Get a random path to use
  109. path := cfg.Path
  110. if path == "" {
  111. path = util.GetRandomURLPath()
  112. }
  113. // Set the hostname
  114. hostname := fmt.Sprintf("%s:%d", bind, port)
  115. // Use external IP when using `interface: any`, unless a FQDN is set
  116. if bind == "0.0.0.0" && cfg.FQDN == "" {
  117. fmt.Println("Retrieving the external IP...")
  118. extIP, err := util.GetExernalIP()
  119. if err != nil {
  120. panic(err)
  121. }
  122. hostname = fmt.Sprintf("%s:%d", extIP.String(), port)
  123. }
  124. // Use a fully-qualified domain name if set
  125. if cfg.FQDN != "" {
  126. hostname = fmt.Sprintf("%s:%d", cfg.FQDN, port)
  127. }
  128. // Set URLs
  129. protocol := "http"
  130. if cfg.Secure {
  131. protocol = "https"
  132. }
  133. app.BaseURL = fmt.Sprintf("%s://%s", protocol, hostname)
  134. app.SendURL = fmt.Sprintf("%s/send/%s",
  135. app.BaseURL, path)
  136. app.ReceiveURL = fmt.Sprintf("%s/receive/%s",
  137. app.BaseURL, path)
  138. // Create a server
  139. httpserver := &http.Server{
  140. Addr: host,
  141. TLSConfig: &tls.Config{
  142. MinVersion: tls.VersionTLS12,
  143. CurvePreferences: []tls.CurveID{tls.CurveP521, tls.CurveP384, tls.CurveP256},
  144. PreferServerCipherSuites: true,
  145. CipherSuites: []uint16{
  146. tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,
  147. tls.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA,
  148. tls.TLS_RSA_WITH_AES_256_GCM_SHA384,
  149. tls.TLS_RSA_WITH_AES_256_CBC_SHA,
  150. },
  151. },
  152. TLSNextProto: make(map[string]func(*http.Server, *tls.Conn, http.Handler)),
  153. }
  154. // Create channel to send message to stop server
  155. app.stopChannel = make(chan bool)
  156. // Create cookie used to verify request is coming from first client to connect
  157. cookie := http.Cookie{Name: "qrcp", Value: ""}
  158. // Gracefully shutdown when an OS signal is received or when "q" is pressed
  159. sig := make(chan os.Signal, 1)
  160. signal.Notify(sig, os.Interrupt)
  161. go func() {
  162. <-sig
  163. app.stopChannel <- true
  164. }()
  165. // The handler adds and removes from the sync.WaitGroup
  166. // When the group is zero all requests are completed
  167. // and the server is shutdown
  168. var waitgroup sync.WaitGroup
  169. waitgroup.Add(1)
  170. var initCookie sync.Once
  171. // Create handlers
  172. // Send handler (sends file to caller)
  173. http.HandleFunc("/send/"+path, func(w http.ResponseWriter, r *http.Request) {
  174. if !cfg.KeepAlive && strings.HasPrefix(r.Header.Get("User-Agent"), "Mozilla") {
  175. fmt.Println("new req...")
  176. if cookie.Value == "" {
  177. initCookie.Do(func() {
  178. value, err := util.GetSessionID()
  179. if err != nil {
  180. log.Println("Unable to generate session ID", err)
  181. app.stopChannel <- true
  182. return
  183. }
  184. cookie.Value = value
  185. http.SetCookie(w, &cookie)
  186. })
  187. } else {
  188. // Check for the expected cookie and value
  189. // If it is missing or doesn't match
  190. // return a 400 status
  191. rcookie, err := r.Cookie(cookie.Name)
  192. if err != nil {
  193. http.Error(w, err.Error(), http.StatusBadRequest)
  194. return
  195. }
  196. if rcookie.Value != cookie.Value {
  197. http.Error(w, "mismatching cookie", http.StatusBadRequest)
  198. return
  199. }
  200. // If the cookie exits and matches
  201. // this is an aadditional request.
  202. // Increment the waitgroup
  203. waitgroup.Add(1)
  204. }
  205. // Remove connection from the waitgroup when done
  206. defer waitgroup.Done()
  207. }
  208. w.Header().Set("Content-Disposition", "attachment; filename="+
  209. app.payload.Filename)
  210. http.ServeFile(w, r, app.payload.Path)
  211. })
  212. // Upload handler (serves the upload page)
  213. http.HandleFunc("/receive/"+path, func(w http.ResponseWriter, r *http.Request) {
  214. htmlVariables := struct {
  215. Route string
  216. File string
  217. }{}
  218. htmlVariables.Route = "/receive/" + path
  219. switch r.Method {
  220. case "POST":
  221. filenames := util.ReadFilenames(app.outputDir)
  222. reader, err := r.MultipartReader()
  223. if err != nil {
  224. fmt.Fprintf(w, "Upload error: %v\n", err)
  225. log.Printf("Upload error: %v\n", err)
  226. app.stopChannel <- true
  227. return
  228. }
  229. transferredFiles := []string{}
  230. progressBar := pb.New64(r.ContentLength)
  231. progressBar.ShowCounters = false
  232. for {
  233. part, err := reader.NextPart()
  234. if err == io.EOF {
  235. break
  236. }
  237. // iIf part.FileName() is empty, skip this iteration.
  238. if part.FileName() == "" {
  239. continue
  240. }
  241. // Prepare the destination
  242. fileName := getFileName(filepath.Base(part.FileName()), filenames)
  243. out, err := os.Create(filepath.Join(app.outputDir, fileName))
  244. if err != nil {
  245. // Output to server
  246. fmt.Fprintf(w, "Unable to create the file for writing: %s\n", err)
  247. // Output to console
  248. log.Printf("Unable to create the file for writing: %s\n", err)
  249. // Send signal to server to shutdown
  250. app.stopChannel <- true
  251. return
  252. }
  253. defer out.Close()
  254. // Add name of new file
  255. filenames = append(filenames, fileName)
  256. // Write the content from POSTed file to the out
  257. fmt.Println("Transferring file: ", out.Name())
  258. progressBar.Prefix(out.Name())
  259. progressBar.Start()
  260. buf := make([]byte, 1024)
  261. for {
  262. // Read a chunk
  263. n, err := part.Read(buf)
  264. if err != nil && err != io.EOF {
  265. // Output to server
  266. fmt.Fprintf(w, "Unable to write file to disk: %v", err)
  267. // Output to console
  268. fmt.Printf("Unable to write file to disk: %v", err)
  269. // Send signal to server to shutdown
  270. app.stopChannel <- true
  271. return
  272. }
  273. if n == 0 {
  274. break
  275. }
  276. // Write a chunk
  277. if _, err := out.Write(buf[:n]); err != nil {
  278. // Output to server
  279. fmt.Fprintf(w, "Unable to write file to disk: %v", err)
  280. // Output to console
  281. log.Printf("Unable to write file to disk: %v", err)
  282. // Send signal to server to shutdown
  283. app.stopChannel <- true
  284. return
  285. }
  286. progressBar.Add(n)
  287. }
  288. transferredFiles = append(transferredFiles, out.Name())
  289. }
  290. progressBar.FinishPrint("File transfer completed")
  291. // Set the value of the variable to the actually transferred files
  292. htmlVariables.File = strings.Join(transferredFiles, ", ")
  293. serveTemplate("done", pages.Done, w, htmlVariables)
  294. if !cfg.KeepAlive {
  295. app.stopChannel <- true
  296. }
  297. case "GET":
  298. serveTemplate("upload", pages.Upload, w, htmlVariables)
  299. }
  300. })
  301. // Wait for all wg to be done, then send shutdown signal
  302. go func() {
  303. waitgroup.Wait()
  304. if cfg.KeepAlive || !app.expectParallelRequests {
  305. return
  306. }
  307. app.stopChannel <- true
  308. }()
  309. go func() {
  310. netListener := tcpKeepAliveListener{listener.(*net.TCPListener)}
  311. if cfg.Secure {
  312. if err := httpserver.ServeTLS(netListener, cfg.TLSCert, cfg.TLSKey); err != http.ErrServerClosed {
  313. log.Fatalln("error starting the server:", err)
  314. }
  315. } else {
  316. if err := httpserver.Serve(netListener); err != http.ErrServerClosed {
  317. log.Fatalln("error starting the server", err)
  318. }
  319. }
  320. }()
  321. app.instance = httpserver
  322. return app, nil
  323. }
  324. // openBrowser navigates to a url using the default system browser
  325. func openBrowser(url string) {
  326. var err error
  327. switch runtime.GOOS {
  328. case "linux":
  329. err = exec.Command("xdg-open", url).Start()
  330. case "windows":
  331. err = exec.Command("rundll32", "url.dll,FileProtocolHandler", url).Start()
  332. case "darwin":
  333. err = exec.Command("open", url).Start()
  334. default:
  335. err = fmt.Errorf("failed to open browser on platform: %s", runtime.GOOS)
  336. }
  337. if err != nil {
  338. log.Fatal(err)
  339. }
  340. }