server_resources.go 4.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168
  1. package clashapi
  2. import (
  3. "archive/zip"
  4. "context"
  5. "io"
  6. "net"
  7. "net/http"
  8. "os"
  9. "path/filepath"
  10. "strings"
  11. "github.com/sagernet/sing-box/adapter"
  12. C "github.com/sagernet/sing-box/constant"
  13. "github.com/sagernet/sing/common"
  14. E "github.com/sagernet/sing/common/exceptions"
  15. M "github.com/sagernet/sing/common/metadata"
  16. N "github.com/sagernet/sing/common/network"
  17. "github.com/sagernet/sing/service/filemanager"
  18. )
  19. func (s *Server) checkAndDownloadExternalUI() {
  20. if s.externalUI == "" {
  21. return
  22. }
  23. entries, err := os.ReadDir(s.externalUI)
  24. if err != nil {
  25. os.MkdirAll(s.externalUI, 0o755)
  26. }
  27. if len(entries) == 0 {
  28. err = s.downloadExternalUI()
  29. if err != nil {
  30. s.logger.Error("download external ui error: ", err)
  31. }
  32. }
  33. }
  34. func (s *Server) downloadExternalUI() error {
  35. var downloadURL string
  36. if s.externalUIDownloadURL != "" {
  37. downloadURL = s.externalUIDownloadURL
  38. } else {
  39. downloadURL = "https://github.com/MetaCubeX/Yacd-meta/archive/gh-pages.zip"
  40. }
  41. s.logger.Info("downloading external ui")
  42. var detour adapter.Outbound
  43. if s.externalUIDownloadDetour != "" {
  44. outbound, loaded := s.router.Outbound(s.externalUIDownloadDetour)
  45. if !loaded {
  46. return E.New("detour outbound not found: ", s.externalUIDownloadDetour)
  47. }
  48. detour = outbound
  49. } else {
  50. outbound, err := s.router.DefaultOutbound(N.NetworkTCP)
  51. if err != nil {
  52. return err
  53. }
  54. detour = outbound
  55. }
  56. httpClient := &http.Client{
  57. Transport: &http.Transport{
  58. ForceAttemptHTTP2: true,
  59. TLSHandshakeTimeout: C.TCPTimeout,
  60. DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) {
  61. return detour.DialContext(ctx, network, M.ParseSocksaddr(addr))
  62. },
  63. },
  64. }
  65. defer httpClient.CloseIdleConnections()
  66. response, err := httpClient.Get(downloadURL)
  67. if err != nil {
  68. return err
  69. }
  70. defer response.Body.Close()
  71. if response.StatusCode != http.StatusOK {
  72. return E.New("download external ui failed: ", response.Status)
  73. }
  74. err = s.downloadZIP(filepath.Base(downloadURL), response.Body, s.externalUI)
  75. if err != nil {
  76. removeAllInDirectory(s.externalUI)
  77. }
  78. return err
  79. }
  80. func (s *Server) downloadZIP(name string, body io.Reader, output string) error {
  81. tempFile, err := filemanager.CreateTemp(s.ctx, name)
  82. if err != nil {
  83. return err
  84. }
  85. defer os.Remove(tempFile.Name())
  86. _, err = io.Copy(tempFile, body)
  87. tempFile.Close()
  88. if err != nil {
  89. return err
  90. }
  91. reader, err := zip.OpenReader(tempFile.Name())
  92. if err != nil {
  93. return err
  94. }
  95. defer reader.Close()
  96. trimDir := zipIsInSingleDirectory(reader.File)
  97. for _, file := range reader.File {
  98. if file.FileInfo().IsDir() {
  99. continue
  100. }
  101. pathElements := strings.Split(file.Name, "/")
  102. if trimDir {
  103. pathElements = pathElements[1:]
  104. }
  105. saveDirectory := output
  106. if len(pathElements) > 1 {
  107. saveDirectory = filepath.Join(saveDirectory, filepath.Join(pathElements[:len(pathElements)-1]...))
  108. }
  109. err = os.MkdirAll(saveDirectory, 0o755)
  110. if err != nil {
  111. return err
  112. }
  113. savePath := filepath.Join(saveDirectory, pathElements[len(pathElements)-1])
  114. err = downloadZIPEntry(s.ctx, file, savePath)
  115. if err != nil {
  116. return err
  117. }
  118. }
  119. return nil
  120. }
  121. func downloadZIPEntry(ctx context.Context, zipFile *zip.File, savePath string) error {
  122. saveFile, err := filemanager.Create(ctx, savePath)
  123. if err != nil {
  124. return err
  125. }
  126. defer saveFile.Close()
  127. reader, err := zipFile.Open()
  128. if err != nil {
  129. return err
  130. }
  131. defer reader.Close()
  132. return common.Error(io.Copy(saveFile, reader))
  133. }
  134. func removeAllInDirectory(directory string) {
  135. dirEntries, err := os.ReadDir(directory)
  136. if err != nil {
  137. return
  138. }
  139. for _, dirEntry := range dirEntries {
  140. os.RemoveAll(filepath.Join(directory, dirEntry.Name()))
  141. }
  142. }
  143. func zipIsInSingleDirectory(files []*zip.File) bool {
  144. var singleDirectory string
  145. for _, file := range files {
  146. if file.FileInfo().IsDir() {
  147. continue
  148. }
  149. pathElements := strings.Split(file.Name, "/")
  150. if len(pathElements) == 0 {
  151. return false
  152. }
  153. if singleDirectory == "" {
  154. singleDirectory = pathElements[0]
  155. } else if singleDirectory != pathElements[0] {
  156. return false
  157. }
  158. }
  159. return true
  160. }