tar.go 6.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280
  1. // License: GPLv3 Copyright: 2023, Kovid Goyal, <kovid at kovidgoyal.net>
  2. package utils
  3. import (
  4. "archive/tar"
  5. "errors"
  6. "fmt"
  7. "io"
  8. "io/fs"
  9. "os"
  10. "path/filepath"
  11. "runtime"
  12. "strings"
  13. )
  14. var _ = fmt.Print
  15. type TarExtractOptions struct {
  16. DontPreservePermissions bool
  17. }
  18. func volnamelen(path string) int {
  19. return len(filepath.VolumeName(path))
  20. }
  21. func EvalSymlinksThatExist(path string) (string, error) {
  22. volLen := volnamelen(path)
  23. pathSeparator := string(os.PathSeparator)
  24. if volLen < len(path) && os.IsPathSeparator(path[volLen]) {
  25. volLen++
  26. }
  27. vol := path[:volLen]
  28. dest := vol
  29. linksWalked := 0
  30. for start, end := volLen, volLen; start < len(path); start = end {
  31. for start < len(path) && os.IsPathSeparator(path[start]) {
  32. start++
  33. }
  34. end = start
  35. for end < len(path) && !os.IsPathSeparator(path[end]) {
  36. end++
  37. }
  38. // On Windows, "." can be a symlink.
  39. // We look it up, and use the value if it is absolute.
  40. // If not, we just return ".".
  41. isWindowsDot := runtime.GOOS == "windows" && path[volnamelen(path):] == "."
  42. // The next path component is in path[start:end].
  43. if end == start {
  44. // No more path components.
  45. break
  46. } else if path[start:end] == "." && !isWindowsDot {
  47. // Ignore path component ".".
  48. continue
  49. } else if path[start:end] == ".." {
  50. // Back up to previous component if possible.
  51. // Note that volLen includes any leading slash.
  52. // Set r to the index of the last slash in dest,
  53. // after the volume.
  54. var r int
  55. for r = len(dest) - 1; r >= volLen; r-- {
  56. if os.IsPathSeparator(dest[r]) {
  57. break
  58. }
  59. }
  60. if r < volLen || dest[r+1:] == ".." {
  61. // Either path has no slashes
  62. // (it's empty or just "C:")
  63. // or it ends in a ".." we had to keep.
  64. // Either way, keep this "..".
  65. if len(dest) > volLen {
  66. dest += pathSeparator
  67. }
  68. dest += ".."
  69. } else {
  70. // Discard everything since the last slash.
  71. dest = dest[:r]
  72. }
  73. continue
  74. }
  75. // Ordinary path component. Add it to result.
  76. if len(dest) > volnamelen(dest) && !os.IsPathSeparator(dest[len(dest)-1]) {
  77. dest += pathSeparator
  78. }
  79. dest += path[start:end]
  80. // Resolve symlink.
  81. fi, err := os.Lstat(dest)
  82. if err != nil {
  83. if os.IsNotExist(err) {
  84. if end < len(path) {
  85. dest += path[end:]
  86. }
  87. return filepath.Clean(dest), nil
  88. }
  89. return "", err
  90. }
  91. if fi.Mode()&fs.ModeSymlink == 0 {
  92. if !fi.Mode().IsDir() && end < len(path) {
  93. return "", fmt.Errorf("%s is not a directory while resolving symlinks in %s", dest, path)
  94. }
  95. continue
  96. }
  97. // Found symlink.
  98. linksWalked++
  99. if linksWalked > 255 {
  100. return "", fmt.Errorf("EvalSymlinksThatExist: too many symlinks in %s", path)
  101. }
  102. link, err := os.Readlink(dest)
  103. if err != nil {
  104. return "", err
  105. }
  106. if isWindowsDot && !filepath.IsAbs(link) {
  107. // On Windows, if "." is a relative symlink,
  108. // just return ".".
  109. break
  110. }
  111. path = link + path[end:]
  112. v := volnamelen(link)
  113. if v > 0 {
  114. // Symlink to drive name is an absolute path.
  115. if v < len(link) && os.IsPathSeparator(link[v]) {
  116. v++
  117. }
  118. vol = link[:v]
  119. dest = vol
  120. end = len(vol)
  121. } else if len(link) > 0 && os.IsPathSeparator(link[0]) {
  122. // Symlink to absolute path.
  123. dest = link[:1]
  124. end = 1
  125. vol = link[:1]
  126. volLen = 1
  127. } else {
  128. // Symlink to relative path; replace last
  129. // path component in dest.
  130. var r int
  131. for r = len(dest) - 1; r >= volLen; r-- {
  132. if os.IsPathSeparator(dest[r]) {
  133. break
  134. }
  135. }
  136. if r < volLen {
  137. dest = vol
  138. } else {
  139. dest = dest[:r]
  140. }
  141. end = 0
  142. }
  143. }
  144. return filepath.Clean(dest), nil
  145. }
  146. func ExtractAllFromTar(tr *tar.Reader, dest_path string, optss ...TarExtractOptions) (count int, err error) {
  147. opts := TarExtractOptions{}
  148. if len(optss) > 0 {
  149. opts = optss[0]
  150. }
  151. if !filepath.IsAbs(dest_path) {
  152. if dest_path, err = filepath.Abs(dest_path); err != nil {
  153. return
  154. }
  155. }
  156. if dest_path, err = filepath.EvalSymlinks(dest_path); err != nil {
  157. return
  158. }
  159. dest_path = filepath.Clean(dest_path)
  160. mode := func(hdr int64) fs.FileMode {
  161. return fs.FileMode(hdr) & (fs.ModePerm | fs.ModeSetgid | fs.ModeSetuid | fs.ModeSticky)
  162. }
  163. set_metadata := func(chmod func(mode fs.FileMode) error, hdr_mode int64) (err error) {
  164. if !opts.DontPreservePermissions && chmod != nil {
  165. perms := mode(hdr_mode)
  166. if err = chmod(perms); err != nil {
  167. return err
  168. }
  169. }
  170. count++
  171. return
  172. }
  173. needed_prefix := dest_path + string(os.PathSeparator)
  174. for {
  175. var hdr *tar.Header
  176. hdr, err = tr.Next()
  177. if errors.Is(err, io.EOF) {
  178. err = nil
  179. break
  180. }
  181. if err != nil {
  182. return count, err
  183. }
  184. dest := hdr.Name
  185. if !filepath.IsAbs(dest) {
  186. dest = filepath.Join(dest_path, dest)
  187. }
  188. if dest, err = EvalSymlinksThatExist(dest); err != nil {
  189. return count, err
  190. }
  191. if !strings.HasPrefix(dest, needed_prefix) {
  192. continue
  193. }
  194. switch hdr.Typeflag {
  195. case tar.TypeDir:
  196. err = os.MkdirAll(dest, 0o700)
  197. if err != nil {
  198. return
  199. }
  200. if err = set_metadata(func(m fs.FileMode) error { return os.Chmod(dest, m) }, hdr.Mode); err != nil {
  201. return
  202. }
  203. case tar.TypeReg:
  204. var d *os.File
  205. if err = os.MkdirAll(filepath.Dir(dest), 0o700); err != nil {
  206. return
  207. }
  208. if d, err = os.Create(dest); err != nil {
  209. return
  210. }
  211. err = set_metadata(d.Chmod, hdr.Mode)
  212. if err == nil {
  213. _, err = io.Copy(d, tr)
  214. }
  215. d.Close()
  216. if err != nil {
  217. return
  218. }
  219. case tar.TypeLink:
  220. if err = os.MkdirAll(filepath.Dir(dest), 0o700); err != nil {
  221. return
  222. }
  223. link_target := hdr.Linkname
  224. if !filepath.IsAbs(link_target) {
  225. link_target = filepath.Join(filepath.Dir(dest), link_target)
  226. }
  227. if err = os.Link(link_target, dest); err != nil {
  228. return
  229. }
  230. if err = set_metadata(func(m fs.FileMode) error { return os.Chmod(dest, m) }, hdr.Mode); err != nil {
  231. return
  232. }
  233. case tar.TypeSymlink:
  234. if err = os.MkdirAll(filepath.Dir(dest), 0o700); err != nil {
  235. return
  236. }
  237. link_target := hdr.Linkname
  238. if !filepath.IsAbs(link_target) {
  239. link_target = filepath.Join(filepath.Dir(dest), link_target)
  240. }
  241. // We dont care about the link target being outside dest_path as
  242. // we use EvalSymlinks on dest, so a symlink pointing outside
  243. // dest_path cannot cause writes outside dest_path.
  244. if err = os.Symlink(link_target, dest); err != nil {
  245. return
  246. }
  247. if err = set_metadata(nil, hdr.Mode); err != nil {
  248. return
  249. }
  250. }
  251. }
  252. return
  253. }