-
Notifications
You must be signed in to change notification settings - Fork 18
/
Copy pathheader.jl
147 lines (136 loc) · 4.46 KB
/
header.jl
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
"""
The `Header` type is a struct representing the essential metadata for a single
record in a tar file with this definition:
struct Header
path :: String # path relative to the root
type :: Symbol # type indicator (see below)
mode :: UInt16 # mode/permissions (best viewed in octal)
size :: Int64 # size of record data in bytes
link :: String # target path of a symlink
end
Types are represented with the following symbols: `file`, `hardlink`, `symlink`,
`chardev`, `blockdev`, `directory`, `fifo`, or for unknown types, the typeflag
character as a symbol. Note that [`extract`](@ref) refuses to extract records
types other than `file`, `symlink` and `directory`; [`list`](@ref) will only
list other kinds of records if called with `strict=false`.
The tar format includes various other metadata about records, including user and
group IDs, user and group names, and timestamps. The `Tar` package, by design,
completely ignores these. When creating tar files, these fields are always set
to zero/empty. When reading tar files, these fields are ignored aside from
verifying header checksums for each header record for all fields.
"""
struct Header
path::String
type::Symbol
mode::UInt16
size::Int64
link::String
end
function Base.show(io::IO, hdr::Header)
show(io, Header)
print(io, "(")
show(io, hdr.path)
print(io, ", ")
show(io, hdr.type)
print(io, ", 0o", string(hdr.mode, base=8, pad=3), ", ")
show(io, hdr.size)
print(io, ", ")
show(io, hdr.link)
print(io, ")")
end
const TYPE_SYMBOLS = (
'0' => :file,
'\0' => :file, # legacy encoding
'1' => :hardlink,
'2' => :symlink,
'3' => :chardev,
'4' => :blockdev,
'5' => :directory,
'6' => :fifo,
)
function to_symbolic_type(type::Char)
for (t, s) in TYPE_SYMBOLS
type == t && return s
end
isascii(type) ||
error("invalid type flag: $(repr(type))")
return Symbol(type)
end
function from_symbolic_type(sym::Symbol)
for (t, s) in TYPE_SYMBOLS
sym == s && return t
end
str = String(sym)
ncodeunits(str) == 1 && isascii(str[1]) ||
throw(ArgumentError("invalid type symbol: $(repr(sym))"))
return str[1]
end
function check_header(hdr::Header)
errors = String[]
err(e::String) = push!(errors, e)
# error checks
isempty(hdr.path) &&
err("path is empty")
0x0 in codeunits(hdr.path) &&
err("path contains NUL bytes")
0x0 in codeunits(hdr.link) &&
err("link contains NUL bytes")
!isempty(hdr.path) && hdr.path[1] == '/' &&
err("path is absolute")
occursin(r"(^|/)\.\.(/|$)", hdr.path) &&
err("path contains '..' component")
hdr.type in (:file, :symlink, :directory) ||
err("unsupported entry type")
hdr.type ∉ (:hardlink, :symlink) && !isempty(hdr.link) &&
err("non-link with link path")
hdr.type == :symlink && hdr.size != 0 &&
err("symlink with non-zero size")
hdr.type == :directory && hdr.size != 0 &&
err("directory with non-zero size")
hdr.type != :directory && endswith(hdr.path, "/") &&
err("non-directory path ending with '/'")
hdr.type != :directory && (hdr.path == "." || endswith(hdr.path, "/.")) &&
err("non-directory path ending with '.' component")
hdr.size < 0 &&
err("negative file size")
isempty(errors) && return
# contruct error message
if length(errors) == 1
msg = errors[1] * "\n"
else
msg = "tar header with multiple errors:\n"
for e in sort!(errors)
msg *= " * $e\n"
end
end
msg *= repr(hdr)
error(msg)
end
function path_header(sys_path::AbstractString, tar_path::AbstractString)
st = lstat(sys_path)
if islink(st)
size = 0
type = :symlink
mode = 0o755
link = readlink(sys_path)
elseif isdir(st)
size = 0
type = :directory
mode = 0o755
link = ""
elseif isfile(st)
size = filesize(st)
type = :file
# Windows can't re-use the `lstat` result, we need to ask
# the system whether it's executable or not via `access()`
if Sys.iswindows()
mode = Sys.isexecutable(sys_path) ? 0o755 : 0o644
else
mode = iszero(filemode(st) & 0o100) ? 0o644 : 0o755
end
link = ""
else
error("unsupported file type: $(repr(sys_path))")
end
Header(tar_path, type, mode, size, link)
end