gitea源码

embed.go 8.7KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376
  1. // Copyright 2025 The Gitea Authors. All rights reserved.
  2. // SPDX-License-Identifier: MIT
  3. package assetfs
  4. import (
  5. "bytes"
  6. "compress/gzip"
  7. "io"
  8. "io/fs"
  9. "os"
  10. "path"
  11. "path/filepath"
  12. "strings"
  13. "sync"
  14. "time"
  15. "code.gitea.io/gitea/modules/json"
  16. "code.gitea.io/gitea/modules/util"
  17. )
  18. type EmbeddedFile interface {
  19. io.ReadSeeker
  20. fs.ReadDirFile
  21. ReadDir(n int) ([]fs.DirEntry, error)
  22. }
  23. type EmbeddedFileInfo interface {
  24. fs.FileInfo
  25. fs.DirEntry
  26. GetGzipContent() ([]byte, bool)
  27. }
  28. type decompressor interface {
  29. io.Reader
  30. Close() error
  31. Reset(io.Reader) error
  32. }
  33. type embeddedFileInfo struct {
  34. fs *embeddedFS
  35. fullName string
  36. data []byte
  37. BaseName string `json:"n"`
  38. OriginSize int64 `json:"s,omitempty"`
  39. DataBegin int64 `json:"b,omitempty"`
  40. DataLen int64 `json:"l,omitempty"`
  41. Children []*embeddedFileInfo `json:"c,omitempty"`
  42. }
  43. func (fi *embeddedFileInfo) GetGzipContent() ([]byte, bool) {
  44. // when generating the bindata, if the compressed data equals or is larger than the original data, we store the original data
  45. if fi.DataLen == fi.OriginSize {
  46. return nil, false
  47. }
  48. return fi.data, true
  49. }
  50. type EmbeddedFileBase struct {
  51. info *embeddedFileInfo
  52. dataReader io.ReadSeeker
  53. seekPos int64
  54. }
  55. func (f *EmbeddedFileBase) ReadDir(n int) ([]fs.DirEntry, error) {
  56. // this method is used to satisfy the "func (f ioFile) ReadDir(...)" in httpfs
  57. l, err := f.info.fs.ReadDir(f.info.fullName)
  58. if err != nil {
  59. return nil, err
  60. }
  61. if n < 0 || n > len(l) {
  62. return l, nil
  63. }
  64. return l[:n], nil
  65. }
  66. type EmbeddedOriginFile struct {
  67. EmbeddedFileBase
  68. }
  69. type EmbeddedCompressedFile struct {
  70. EmbeddedFileBase
  71. decompressor decompressor
  72. decompressorPos int64
  73. }
  74. type embeddedFS struct {
  75. meta func() *EmbeddedMeta
  76. files map[string]*embeddedFileInfo
  77. filesMu sync.RWMutex
  78. data []byte
  79. }
  80. type EmbeddedMeta struct {
  81. Root *embeddedFileInfo
  82. }
  83. func NewEmbeddedFS(data []byte) fs.ReadDirFS {
  84. efs := &embeddedFS{data: data, files: make(map[string]*embeddedFileInfo)}
  85. efs.meta = sync.OnceValue(func() *EmbeddedMeta {
  86. var meta EmbeddedMeta
  87. p := bytes.LastIndexByte(data, '\n')
  88. if p < 0 {
  89. return &meta
  90. }
  91. if err := json.Unmarshal(data[p+1:], &meta); err != nil {
  92. panic("embedded file is not valid")
  93. }
  94. return &meta
  95. })
  96. return efs
  97. }
  98. var _ fs.ReadDirFS = (*embeddedFS)(nil)
  99. func (e *embeddedFS) ReadDir(name string) (l []fs.DirEntry, err error) {
  100. fi, err := e.getFileInfo(name)
  101. if err != nil {
  102. return nil, err
  103. }
  104. if !fi.IsDir() {
  105. return nil, fs.ErrNotExist
  106. }
  107. l = make([]fs.DirEntry, len(fi.Children))
  108. for i, child := range fi.Children {
  109. l[i], err = e.getFileInfo(name + "/" + child.BaseName)
  110. if err != nil {
  111. return nil, err
  112. }
  113. }
  114. return l, nil
  115. }
  116. func (e *embeddedFS) getFileInfo(fullName string) (*embeddedFileInfo, error) {
  117. // no need to do heavy "path.Clean()" because we don't want to support "foo/../bar" or absolute paths
  118. fullName = strings.TrimPrefix(fullName, "./")
  119. if fullName == "" {
  120. fullName = "."
  121. }
  122. e.filesMu.RLock()
  123. fi := e.files[fullName]
  124. e.filesMu.RUnlock()
  125. if fi != nil {
  126. return fi, nil
  127. }
  128. fields := strings.Split(fullName, "/")
  129. fi = e.meta().Root
  130. if fullName != "." {
  131. found := true
  132. for _, field := range fields {
  133. for _, child := range fi.Children {
  134. if found = child.BaseName == field; found {
  135. fi = child
  136. break
  137. }
  138. }
  139. if !found {
  140. return nil, fs.ErrNotExist
  141. }
  142. }
  143. }
  144. e.filesMu.Lock()
  145. defer e.filesMu.Unlock()
  146. if fi != nil {
  147. fi.fs = e
  148. fi.fullName = fullName
  149. fi.data = e.data[fi.DataBegin : fi.DataBegin+fi.DataLen]
  150. e.files[fullName] = fi // do not cache nil, otherwise keeping accessing random non-existing file will cause OOM
  151. return fi, nil
  152. }
  153. return nil, fs.ErrNotExist
  154. }
  155. func (e *embeddedFS) Open(name string) (fs.File, error) {
  156. info, err := e.getFileInfo(name)
  157. if err != nil {
  158. return nil, err
  159. }
  160. base := EmbeddedFileBase{info: info}
  161. base.dataReader = bytes.NewReader(base.info.data)
  162. if info.DataLen != info.OriginSize {
  163. decomp, err := gzip.NewReader(base.dataReader)
  164. if err != nil {
  165. return nil, err
  166. }
  167. return &EmbeddedCompressedFile{EmbeddedFileBase: base, decompressor: decomp}, nil
  168. }
  169. return &EmbeddedOriginFile{base}, nil
  170. }
  171. var (
  172. _ EmbeddedFileInfo = (*embeddedFileInfo)(nil)
  173. _ EmbeddedFile = (*EmbeddedOriginFile)(nil)
  174. _ EmbeddedFile = (*EmbeddedCompressedFile)(nil)
  175. )
  176. func (f *EmbeddedOriginFile) Read(p []byte) (n int, err error) {
  177. return f.dataReader.Read(p)
  178. }
  179. func (f *EmbeddedCompressedFile) Read(p []byte) (n int, err error) {
  180. if f.decompressorPos > f.seekPos {
  181. if err = f.decompressor.Reset(bytes.NewReader(f.info.data)); err != nil {
  182. return 0, err
  183. }
  184. f.decompressorPos = 0
  185. }
  186. if f.decompressorPos < f.seekPos {
  187. if _, err = io.CopyN(io.Discard, f.decompressor, f.seekPos-f.decompressorPos); err != nil {
  188. return 0, err
  189. }
  190. f.decompressorPos = f.seekPos
  191. }
  192. n, err = f.decompressor.Read(p)
  193. f.decompressorPos += int64(n)
  194. f.seekPos = f.decompressorPos
  195. return n, err
  196. }
  197. func (f *EmbeddedFileBase) Seek(offset int64, whence int) (int64, error) {
  198. switch whence {
  199. case io.SeekStart:
  200. f.seekPos = offset
  201. case io.SeekCurrent:
  202. f.seekPos += offset
  203. case io.SeekEnd:
  204. f.seekPos = f.info.OriginSize + offset
  205. }
  206. return f.seekPos, nil
  207. }
  208. func (f *EmbeddedFileBase) Stat() (fs.FileInfo, error) {
  209. return f.info, nil
  210. }
  211. func (f *EmbeddedOriginFile) Close() error {
  212. return nil
  213. }
  214. func (f *EmbeddedCompressedFile) Close() error {
  215. return f.decompressor.Close()
  216. }
  217. func (fi *embeddedFileInfo) Name() string {
  218. return fi.BaseName
  219. }
  220. func (fi *embeddedFileInfo) Size() int64 {
  221. return fi.OriginSize
  222. }
  223. func (fi *embeddedFileInfo) Mode() fs.FileMode {
  224. return util.Iif(fi.IsDir(), fs.ModeDir|0o555, 0o444)
  225. }
  226. func (fi *embeddedFileInfo) ModTime() time.Time {
  227. return getExecutableModTime()
  228. }
  229. func (fi *embeddedFileInfo) IsDir() bool {
  230. return fi.Children != nil
  231. }
  232. func (fi *embeddedFileInfo) Sys() any {
  233. return nil
  234. }
  235. func (fi *embeddedFileInfo) Type() fs.FileMode {
  236. return util.Iif(fi.IsDir(), fs.ModeDir, 0)
  237. }
  238. func (fi *embeddedFileInfo) Info() (fs.FileInfo, error) {
  239. return fi, nil
  240. }
  241. // getExecutableModTime returns the modification time of the executable file.
  242. // In bindata, we can't use the ModTime of the files because we need to make the build reproducible
  243. var getExecutableModTime = sync.OnceValue(func() (modTime time.Time) {
  244. exePath, err := os.Executable()
  245. if err != nil {
  246. return modTime
  247. }
  248. exePath, err = filepath.Abs(exePath)
  249. if err != nil {
  250. return modTime
  251. }
  252. exePath, err = filepath.EvalSymlinks(exePath)
  253. if err != nil {
  254. return modTime
  255. }
  256. st, err := os.Stat(exePath)
  257. if err != nil {
  258. return modTime
  259. }
  260. return st.ModTime()
  261. })
  262. func GenerateEmbedBindata(fsRootPath, outputFile string) error {
  263. output, err := os.OpenFile(outputFile, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, os.ModePerm)
  264. if err != nil {
  265. return err
  266. }
  267. defer output.Close()
  268. meta := &EmbeddedMeta{}
  269. meta.Root = &embeddedFileInfo{}
  270. var outputOffset int64
  271. var embedFiles func(parent *embeddedFileInfo, fsPath, embedPath string) error
  272. embedFiles = func(parent *embeddedFileInfo, fsPath, embedPath string) error {
  273. dirEntries, err := os.ReadDir(fsPath)
  274. if err != nil {
  275. return err
  276. }
  277. for _, dirEntry := range dirEntries {
  278. if err != nil {
  279. return err
  280. }
  281. if dirEntry.IsDir() {
  282. child := &embeddedFileInfo{
  283. BaseName: dirEntry.Name(),
  284. Children: []*embeddedFileInfo{}, // non-nil means it's a directory
  285. }
  286. parent.Children = append(parent.Children, child)
  287. if err = embedFiles(child, filepath.Join(fsPath, dirEntry.Name()), path.Join(embedPath, dirEntry.Name())); err != nil {
  288. return err
  289. }
  290. } else {
  291. data, err := os.ReadFile(filepath.Join(fsPath, dirEntry.Name()))
  292. if err != nil {
  293. return err
  294. }
  295. var compressed bytes.Buffer
  296. gz, _ := gzip.NewWriterLevel(&compressed, gzip.BestCompression)
  297. if _, err = gz.Write(data); err != nil {
  298. return err
  299. }
  300. if err = gz.Close(); err != nil {
  301. return err
  302. }
  303. // only use the compressed data if it is smaller than the original data
  304. outputBytes := util.Iif(len(compressed.Bytes()) < len(data), compressed.Bytes(), data)
  305. child := &embeddedFileInfo{
  306. BaseName: dirEntry.Name(),
  307. OriginSize: int64(len(data)),
  308. DataBegin: outputOffset,
  309. DataLen: int64(len(outputBytes)),
  310. }
  311. if _, err = output.Write(outputBytes); err != nil {
  312. return err
  313. }
  314. outputOffset += child.DataLen
  315. parent.Children = append(parent.Children, child)
  316. }
  317. }
  318. return nil
  319. }
  320. if err = embedFiles(meta.Root, fsRootPath, ""); err != nil {
  321. return err
  322. }
  323. jsonBuf, err := json.Marshal(meta) // can't use json.NewEncoder here because it writes extra EOL
  324. if err != nil {
  325. return err
  326. }
  327. _, _ = output.Write([]byte{'\n'})
  328. _, err = output.Write(jsonBuf)
  329. return err
  330. }