web.go 4.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219
  1. /*
  2. * Rufs - Remote Union File System
  3. *
  4. * Copyright 2017 Matthias Ladkau. All rights reserved.
  5. *
  6. * This Source Code Form is subject to the terms of the MIT
  7. * License, If a copy of the MIT License was not distributed with this
  8. * file, You can obtain one at https://opensource.org/licenses/MIT.
  9. */
  10. package main
  11. import (
  12. "bytes"
  13. "fmt"
  14. "io"
  15. "io/ioutil"
  16. "net/http"
  17. "os"
  18. "path/filepath"
  19. "strings"
  20. "sync"
  21. "unicode"
  22. "devt.de/krotik/common/cryptutil"
  23. "devt.de/krotik/common/errorutil"
  24. "devt.de/krotik/common/fileutil"
  25. "devt.de/krotik/common/httputil"
  26. "devt.de/krotik/rufs"
  27. "devt.de/krotik/rufs/api"
  28. "devt.de/krotik/rufs/api/v1"
  29. )
  30. /*
  31. webdir is the web directory which will contain all html files
  32. */
  33. const webdir = "web"
  34. /*
  35. setupWebExport exports Rufs through a web interface
  36. */
  37. func setupWebExport(webExport *string, tree *rufs.Tree, certDir *string) error {
  38. var ok bool
  39. var err error
  40. // Register REST endpoints for version 1
  41. api.RegisterRestEndpoints(v1.V1EndpointMap)
  42. api.RegisterRestEndpoints(api.GeneralEndpointMap)
  43. // Set the default tree
  44. api.AddTree("default", tree)
  45. // Ensure web folder
  46. if ok, err = fileutil.PathExists(webdir); err == nil && !ok {
  47. fmt.Println("Creating web folder")
  48. err = extractWebFiles(webdir)
  49. }
  50. // Start the web server
  51. if ok, _ = fileutil.PathExists(webdir); err == nil && ok {
  52. fs := http.FileServer(http.Dir(webdir))
  53. api.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
  54. fs.ServeHTTP(w, r)
  55. })
  56. // Start HTTPS server and enable REST API
  57. hs := &httputil.HTTPServer{}
  58. var wg sync.WaitGroup
  59. wg.Add(1)
  60. weblocString := *webExport
  61. if strings.HasPrefix(weblocString, ":") {
  62. weblocString = fmt.Sprintf("<any interface>%s", weblocString)
  63. }
  64. fmt.Println(fmt.Sprintf("Starting server on: https://%s", weblocString))
  65. go hs.RunHTTPSServer(*certDir, "cert.pem", "key.pem",
  66. *webExport, &wg)
  67. // Wait until the server has started
  68. wg.Wait()
  69. if err = hs.LastError; err == nil {
  70. // Read server certificate and write a fingerprint file
  71. fpfile := filepath.Join(webdir, "fingerprint.json")
  72. fmt.Println("Writing fingerprint file: ", fpfile)
  73. certs, _ := cryptutil.ReadX509CertsFromFile(filepath.Join(*certDir, "cert.pem"))
  74. if len(certs) > 0 {
  75. buf := bytes.Buffer{}
  76. buf.WriteString("{\n")
  77. buf.WriteString(fmt.Sprintf(` "md5" : "%s",`, cryptutil.Md5CertFingerprint(certs[0])))
  78. buf.WriteString("\n")
  79. buf.WriteString(fmt.Sprintf(` "sha1" : "%s",`, cryptutil.Sha1CertFingerprint(certs[0])))
  80. buf.WriteString("\n")
  81. buf.WriteString(fmt.Sprintf(` "sha256" : "%s"`, cryptutil.Sha256CertFingerprint(certs[0])))
  82. buf.WriteString("\n")
  83. buf.WriteString("}\n")
  84. ioutil.WriteFile(fpfile, buf.Bytes(), 0644)
  85. }
  86. // Add to the wait group so we can wait for the shutdown
  87. wg.Add(1)
  88. fmt.Println("Waiting for shutdown")
  89. wg.Wait()
  90. }
  91. }
  92. return err
  93. }
  94. /*
  95. extractWebFiles extracts the web files from the executable.
  96. */
  97. func extractWebFiles(webfolder string) error {
  98. end := "####"
  99. marker := fmt.Sprintf("%v%v%v", end, "WEBZIP", end)
  100. exename, err := filepath.Abs(os.Args[0])
  101. errorutil.AssertOk(err)
  102. if ok, _ := fileutil.PathExists(exename); !ok {
  103. // Try an optional .exe suffix which might work on Windows
  104. exename += ".exe"
  105. }
  106. stat, err := os.Stat(exename)
  107. if err != nil {
  108. return err
  109. }
  110. // Open the executable
  111. f, err := os.Open(exename)
  112. if err != nil {
  113. return err
  114. }
  115. defer f.Close()
  116. found := false
  117. buf := make([]byte, 4096)
  118. buf2 := make([]byte, len(marker)+10)
  119. var pos int64
  120. // Look for the marker which marks the beginning of the attached zip file
  121. for i, err := f.Read(buf); err == nil; i, err = f.Read(buf) {
  122. // Check if the marker could be in the read string
  123. if strings.Contains(string(buf), "#") {
  124. // Marker was found - read a bit more to ensure we got the full marker
  125. if i2, err := f.Read(buf2); err == nil || err == io.EOF {
  126. candidateString := string(append(buf, buf2...))
  127. // Now determine the position if the zip file
  128. if markerIndex := strings.Index(candidateString, marker); markerIndex >= 0 {
  129. start := int64(markerIndex + len(marker))
  130. for unicode.IsSpace(rune(candidateString[start])) || unicode.IsControl(rune(candidateString[start])) {
  131. start++ // Skip final control characters \n or \r\n
  132. }
  133. pos += start
  134. found = true
  135. break
  136. }
  137. pos += int64(i2)
  138. }
  139. }
  140. pos += int64(i)
  141. }
  142. if err == nil {
  143. if found {
  144. // Extract the zip
  145. if _, err = f.Seek(pos, 0); err == nil {
  146. zipLen := stat.Size() - pos
  147. if err = os.MkdirAll(webfolder, 0755); err == nil {
  148. err = fileutil.UnzipReader(io.NewSectionReader(f, pos, zipLen), zipLen, webfolder, false)
  149. }
  150. }
  151. } else {
  152. err = fmt.Errorf("Could not find web content marker in executable - invalid executable")
  153. }
  154. }
  155. return err
  156. }