| | 1 | | using System; |
| | 2 | | using System.IO; |
| | 3 | |
|
| | 4 | | namespace ICSharpCode.SharpZipLib.Tar |
| | 5 | | { |
| | 6 | | /// <summary> |
| | 7 | | /// This class represents an entry in a Tar archive. It consists |
| | 8 | | /// of the entry's header, as well as the entry's File. Entries |
| | 9 | | /// can be instantiated in one of three ways, depending on how |
| | 10 | | /// they are to be used. |
| | 11 | | /// <p> |
| | 12 | | /// TarEntries that are created from the header bytes read from |
| | 13 | | /// an archive are instantiated with the TarEntry( byte[] ) |
| | 14 | | /// constructor. These entries will be used when extracting from |
| | 15 | | /// or listing the contents of an archive. These entries have their |
| | 16 | | /// header filled in using the header bytes. They also set the File |
| | 17 | | /// to null, since they reference an archive entry not a file.</p> |
| | 18 | | /// <p> |
| | 19 | | /// TarEntries that are created from files that are to be written |
| | 20 | | /// into an archive are instantiated with the CreateEntryFromFile(string) |
| | 21 | | /// pseudo constructor. These entries have their header filled in using |
| | 22 | | /// the File's information. They also keep a reference to the File |
| | 23 | | /// for convenience when writing entries.</p> |
| | 24 | | /// <p> |
| | 25 | | /// Finally, TarEntries can be constructed from nothing but a name. |
| | 26 | | /// This allows the programmer to construct the entry by hand, for |
| | 27 | | /// instance when only an InputStream is available for writing to |
| | 28 | | /// the archive, and the header information is constructed from |
| | 29 | | /// other information. In this case the header fields are set to |
| | 30 | | /// defaults and the File is set to null.</p> |
| | 31 | | /// <see cref="TarHeader"/> |
| | 32 | | /// </summary> |
| | 33 | | public class TarEntry : ICloneable |
| | 34 | | { |
| | 35 | | #region Constructors |
| | 36 | | /// <summary> |
| | 37 | | /// Initialise a default instance of <see cref="TarEntry"/>. |
| | 38 | | /// </summary> |
| 79 | 39 | | private TarEntry() |
| | 40 | | { |
| 79 | 41 | | header = new TarHeader(); |
| 79 | 42 | | } |
| | 43 | |
|
| | 44 | | /// <summary> |
| | 45 | | /// Construct an entry from an archive's header bytes. File is set |
| | 46 | | /// to null. |
| | 47 | | /// </summary> |
| | 48 | | /// <param name = "headerBuffer"> |
| | 49 | | /// The header bytes from a tar archive entry. |
| | 50 | | /// </param> |
| 1 | 51 | | public TarEntry(byte[] headerBuffer) |
| | 52 | | { |
| 1 | 53 | | header = new TarHeader(); |
| 1 | 54 | | header.ParseBuffer(headerBuffer); |
| 1 | 55 | | } |
| | 56 | |
|
| | 57 | | /// <summary> |
| | 58 | | /// Construct a TarEntry using the <paramref name="header">header</paramref> provided |
| | 59 | | /// </summary> |
| | 60 | | /// <param name="header">Header details for entry</param> |
| 1 | 61 | | public TarEntry(TarHeader header) |
| | 62 | | { |
| 1 | 63 | | if (header == null) { |
| 0 | 64 | | throw new ArgumentNullException(nameof(header)); |
| | 65 | | } |
| | 66 | |
|
| 1 | 67 | | this.header = (TarHeader)header.Clone(); |
| 1 | 68 | | } |
| | 69 | | #endregion |
| | 70 | |
|
| | 71 | | #region ICloneable Members |
| | 72 | | /// <summary> |
| | 73 | | /// Clone this tar entry. |
| | 74 | | /// </summary> |
| | 75 | | /// <returns>Returns a clone of this entry.</returns> |
| | 76 | | public object Clone() |
| | 77 | | { |
| 1 | 78 | | var entry = new TarEntry(); |
| 1 | 79 | | entry.file = file; |
| 1 | 80 | | entry.header = (TarHeader)header.Clone(); |
| 1 | 81 | | entry.Name = Name; |
| 1 | 82 | | return entry; |
| | 83 | | } |
| | 84 | | #endregion |
| | 85 | |
|
| | 86 | | /// <summary> |
| | 87 | | /// Construct an entry with only a <paramref name="name">name</paramref>. |
| | 88 | | /// This allows the programmer to construct the entry's header "by hand". |
| | 89 | | /// </summary> |
| | 90 | | /// <param name="name">The name to use for the entry</param> |
| | 91 | | /// <returns>Returns the newly created <see cref="TarEntry"/></returns> |
| | 92 | | public static TarEntry CreateTarEntry(string name) |
| | 93 | | { |
| 78 | 94 | | var entry = new TarEntry(); |
| 78 | 95 | | TarEntry.NameTarHeader(entry.header, name); |
| 78 | 96 | | return entry; |
| | 97 | | } |
| | 98 | |
|
| | 99 | | /// <summary> |
| | 100 | | /// Construct an entry for a file. File is set to file, and the |
| | 101 | | /// header is constructed from information from the file. |
| | 102 | | /// </summary> |
| | 103 | | /// <param name = "fileName">The file name that the entry represents.</param> |
| | 104 | | /// <returns>Returns the newly created <see cref="TarEntry"/></returns> |
| | 105 | | public static TarEntry CreateEntryFromFile(string fileName) |
| | 106 | | { |
| 0 | 107 | | var entry = new TarEntry(); |
| 0 | 108 | | entry.GetFileTarHeader(entry.header, fileName); |
| 0 | 109 | | return entry; |
| | 110 | | } |
| | 111 | |
|
| | 112 | | /// <summary> |
| | 113 | | /// Determine if the two entries are equal. Equality is determined |
| | 114 | | /// by the header names being equal. |
| | 115 | | /// </summary> |
| | 116 | | /// <param name="obj">The <see cref="Object"/> to compare with the current Object.</param> |
| | 117 | | /// <returns> |
| | 118 | | /// True if the entries are equal; false if not. |
| | 119 | | /// </returns> |
| | 120 | | public override bool Equals(object obj) |
| | 121 | | { |
| 0 | 122 | | var localEntry = obj as TarEntry; |
| | 123 | |
|
| 0 | 124 | | if (localEntry != null) { |
| 0 | 125 | | return Name.Equals(localEntry.Name); |
| | 126 | | } |
| 0 | 127 | | return false; |
| | 128 | | } |
| | 129 | |
|
| | 130 | | /// <summary> |
| | 131 | | /// Derive a Hash value for the current <see cref="Object"/> |
| | 132 | | /// </summary> |
| | 133 | | /// <returns>A Hash code for the current <see cref="Object"/></returns> |
| | 134 | | public override int GetHashCode() |
| | 135 | | { |
| 0 | 136 | | return Name.GetHashCode(); |
| | 137 | | } |
| | 138 | |
|
| | 139 | | /// <summary> |
| | 140 | | /// Determine if the given entry is a descendant of this entry. |
| | 141 | | /// Descendancy is determined by the name of the descendant |
| | 142 | | /// starting with this entry's name. |
| | 143 | | /// </summary> |
| | 144 | | /// <param name = "toTest"> |
| | 145 | | /// Entry to be checked as a descendent of this. |
| | 146 | | /// </param> |
| | 147 | | /// <returns> |
| | 148 | | /// True if entry is a descendant of this. |
| | 149 | | /// </returns> |
| | 150 | | public bool IsDescendent(TarEntry toTest) |
| | 151 | | { |
| 0 | 152 | | if (toTest == null) { |
| 0 | 153 | | throw new ArgumentNullException(nameof(toTest)); |
| | 154 | | } |
| | 155 | |
|
| 0 | 156 | | return toTest.Name.StartsWith(Name, StringComparison.Ordinal); |
| | 157 | | } |
| | 158 | |
|
| | 159 | | /// <summary> |
| | 160 | | /// Get this entry's header. |
| | 161 | | /// </summary> |
| | 162 | | /// <returns> |
| | 163 | | /// This entry's TarHeader. |
| | 164 | | /// </returns> |
| | 165 | | public TarHeader TarHeader { |
| | 166 | | get { |
| 78 | 167 | | return header; |
| | 168 | | } |
| | 169 | | } |
| | 170 | |
|
| | 171 | | /// <summary> |
| | 172 | | /// Get/Set this entry's name. |
| | 173 | | /// </summary> |
| | 174 | | public string Name { |
| | 175 | | get { |
| 73 | 176 | | return header.Name; |
| | 177 | | } |
| | 178 | | set { |
| 2 | 179 | | header.Name = value; |
| 1 | 180 | | } |
| | 181 | | } |
| | 182 | |
|
| | 183 | | /// <summary> |
| | 184 | | /// Get/set this entry's user id. |
| | 185 | | /// </summary> |
| | 186 | | public int UserId { |
| | 187 | | get { |
| 0 | 188 | | return header.UserId; |
| | 189 | | } |
| | 190 | | set { |
| 0 | 191 | | header.UserId = value; |
| 0 | 192 | | } |
| | 193 | | } |
| | 194 | |
|
| | 195 | | /// <summary> |
| | 196 | | /// Get/set this entry's group id. |
| | 197 | | /// </summary> |
| | 198 | | public int GroupId { |
| | 199 | | get { |
| 2 | 200 | | return header.GroupId; |
| | 201 | | } |
| | 202 | | set { |
| 1 | 203 | | header.GroupId = value; |
| 1 | 204 | | } |
| | 205 | | } |
| | 206 | |
|
| | 207 | | /// <summary> |
| | 208 | | /// Get/set this entry's user name. |
| | 209 | | /// </summary> |
| | 210 | | public string UserName { |
| | 211 | | get { |
| 2 | 212 | | return header.UserName; |
| | 213 | | } |
| | 214 | | set { |
| 2 | 215 | | header.UserName = value; |
| 2 | 216 | | } |
| | 217 | | } |
| | 218 | |
|
| | 219 | | /// <summary> |
| | 220 | | /// Get/set this entry's group name. |
| | 221 | | /// </summary> |
| | 222 | | public string GroupName { |
| | 223 | | get { |
| 3 | 224 | | return header.GroupName; |
| | 225 | | } |
| | 226 | | set { |
| 2 | 227 | | header.GroupName = value; |
| 2 | 228 | | } |
| | 229 | | } |
| | 230 | |
|
| | 231 | | /// <summary> |
| | 232 | | /// Convenience method to set this entry's group and user ids. |
| | 233 | | /// </summary> |
| | 234 | | /// <param name="userId"> |
| | 235 | | /// This entry's new user id. |
| | 236 | | /// </param> |
| | 237 | | /// <param name="groupId"> |
| | 238 | | /// This entry's new group id. |
| | 239 | | /// </param> |
| | 240 | | public void SetIds(int userId, int groupId) |
| | 241 | | { |
| 0 | 242 | | UserId = userId; |
| 0 | 243 | | GroupId = groupId; |
| 0 | 244 | | } |
| | 245 | |
|
| | 246 | | /// <summary> |
| | 247 | | /// Convenience method to set this entry's group and user names. |
| | 248 | | /// </summary> |
| | 249 | | /// <param name="userName"> |
| | 250 | | /// This entry's new user name. |
| | 251 | | /// </param> |
| | 252 | | /// <param name="groupName"> |
| | 253 | | /// This entry's new group name. |
| | 254 | | /// </param> |
| | 255 | | public void SetNames(string userName, string groupName) |
| | 256 | | { |
| 0 | 257 | | UserName = userName; |
| 0 | 258 | | GroupName = groupName; |
| 0 | 259 | | } |
| | 260 | |
|
| | 261 | | /// <summary> |
| | 262 | | /// Get/Set the modification time for this entry |
| | 263 | | /// </summary> |
| | 264 | | public DateTime ModTime { |
| | 265 | | get { |
| 2 | 266 | | return header.ModTime; |
| | 267 | | } |
| | 268 | | set { |
| 2 | 269 | | header.ModTime = value; |
| 1 | 270 | | } |
| | 271 | | } |
| | 272 | |
|
| | 273 | | /// <summary> |
| | 274 | | /// Get this entry's file. |
| | 275 | | /// </summary> |
| | 276 | | /// <returns> |
| | 277 | | /// This entry's file. |
| | 278 | | /// </returns> |
| | 279 | | public string File { |
| | 280 | | get { |
| 2 | 281 | | return file; |
| | 282 | | } |
| | 283 | | } |
| | 284 | |
|
| | 285 | | /// <summary> |
| | 286 | | /// Get/set this entry's recorded file size. |
| | 287 | | /// </summary> |
| | 288 | | public long Size { |
| | 289 | | get { |
| 73 | 290 | | return header.Size; |
| | 291 | | } |
| | 292 | | set { |
| 70 | 293 | | header.Size = value; |
| 69 | 294 | | } |
| | 295 | | } |
| | 296 | |
|
| | 297 | | /// <summary> |
| | 298 | | /// Return true if this entry represents a directory, false otherwise |
| | 299 | | /// </summary> |
| | 300 | | /// <returns> |
| | 301 | | /// True if this entry is a directory. |
| | 302 | | /// </returns> |
| | 303 | | public bool IsDirectory { |
| | 304 | | get { |
| 72 | 305 | | if (file != null) { |
| 0 | 306 | | return Directory.Exists(file); |
| | 307 | | } |
| | 308 | |
|
| 72 | 309 | | if (header != null) { |
| 72 | 310 | | if ((header.TypeFlag == TarHeader.LF_DIR) || Name.EndsWith("/", StringComparison.Ordinal)) { |
| 0 | 311 | | return true; |
| | 312 | | } |
| | 313 | | } |
| 72 | 314 | | return false; |
| | 315 | | } |
| | 316 | | } |
| | 317 | |
|
| | 318 | | /// <summary> |
| | 319 | | /// Fill in a TarHeader with information from a File. |
| | 320 | | /// </summary> |
| | 321 | | /// <param name="header"> |
| | 322 | | /// The TarHeader to fill in. |
| | 323 | | /// </param> |
| | 324 | | /// <param name="file"> |
| | 325 | | /// The file from which to get the header information. |
| | 326 | | /// </param> |
| | 327 | | public void GetFileTarHeader(TarHeader header, string file) |
| | 328 | | { |
| 0 | 329 | | if (header == null) { |
| 0 | 330 | | throw new ArgumentNullException(nameof(header)); |
| | 331 | | } |
| | 332 | |
|
| 0 | 333 | | if (file == null) { |
| 0 | 334 | | throw new ArgumentNullException(nameof(file)); |
| | 335 | | } |
| | 336 | |
|
| 0 | 337 | | this.file = file; |
| | 338 | |
|
| | 339 | | // bugfix from torhovl from #D forum: |
| 0 | 340 | | string name = file; |
| | 341 | |
|
| | 342 | | // 23-Jan-2004 GnuTar allows device names in path where the name is not local to the current directory |
| 0 | 343 | | if (name.IndexOf(Environment.CurrentDirectory, StringComparison.Ordinal) == 0) { |
| 0 | 344 | | name = name.Substring(Environment.CurrentDirectory.Length); |
| | 345 | | } |
| | 346 | |
|
| | 347 | | /* |
| | 348 | | if (Path.DirectorySeparatorChar == '\\') |
| | 349 | | { |
| | 350 | | // check if the OS is Windows |
| | 351 | | // Strip off drive letters! |
| | 352 | | if (name.Length > 2) |
| | 353 | | { |
| | 354 | | char ch1 = name[0]; |
| | 355 | | char ch2 = name[1]; |
| | 356 | |
|
| | 357 | | if (ch2 == ':' && Char.IsLetter(ch1)) |
| | 358 | | { |
| | 359 | | name = name.Substring(2); |
| | 360 | | } |
| | 361 | | } |
| | 362 | | } |
| | 363 | | */ |
| | 364 | |
|
| 0 | 365 | | name = name.Replace(Path.DirectorySeparatorChar, '/'); |
| | 366 | |
|
| | 367 | | // No absolute pathnames |
| | 368 | | // Windows (and Posix?) paths can start with UNC style "\\NetworkDrive\", |
| | 369 | | // so we loop on starting /'s. |
| 0 | 370 | | while (name.StartsWith("/", StringComparison.Ordinal)) { |
| 0 | 371 | | name = name.Substring(1); |
| | 372 | | } |
| | 373 | |
|
| 0 | 374 | | header.LinkName = String.Empty; |
| 0 | 375 | | header.Name = name; |
| | 376 | |
|
| 0 | 377 | | if (Directory.Exists(file)) { |
| 0 | 378 | | header.Mode = 1003; // Magic number for security access for a UNIX filesystem |
| 0 | 379 | | header.TypeFlag = TarHeader.LF_DIR; |
| 0 | 380 | | if ((header.Name.Length == 0) || header.Name[header.Name.Length - 1] != '/') { |
| 0 | 381 | | header.Name = header.Name + "/"; |
| | 382 | | } |
| | 383 | |
|
| 0 | 384 | | header.Size = 0; |
| 0 | 385 | | } else { |
| 0 | 386 | | header.Mode = 33216; // Magic number for security access for a UNIX filesystem |
| 0 | 387 | | header.TypeFlag = TarHeader.LF_NORMAL; |
| 0 | 388 | | header.Size = new FileInfo(file.Replace('/', Path.DirectorySeparatorChar)).Length; |
| | 389 | | } |
| | 390 | |
|
| 0 | 391 | | header.ModTime = System.IO.File.GetLastWriteTime(file.Replace('/', Path.DirectorySeparatorChar)).ToUniversalTime() |
| 0 | 392 | | header.DevMajor = 0; |
| 0 | 393 | | header.DevMinor = 0; |
| 0 | 394 | | } |
| | 395 | |
|
| | 396 | | /// <summary> |
| | 397 | | /// Get entries for all files present in this entries directory. |
| | 398 | | /// If this entry doesnt represent a directory zero entries are returned. |
| | 399 | | /// </summary> |
| | 400 | | /// <returns> |
| | 401 | | /// An array of TarEntry's for this entry's children. |
| | 402 | | /// </returns> |
| | 403 | | public TarEntry[] GetDirectoryEntries() |
| | 404 | | { |
| 0 | 405 | | if ((file == null) || !Directory.Exists(file)) { |
| 0 | 406 | | return new TarEntry[0]; |
| | 407 | | } |
| | 408 | |
|
| 0 | 409 | | string[] list = Directory.GetFileSystemEntries(file); |
| 0 | 410 | | TarEntry[] result = new TarEntry[list.Length]; |
| | 411 | |
|
| 0 | 412 | | for (int i = 0; i < list.Length; ++i) { |
| 0 | 413 | | result[i] = TarEntry.CreateEntryFromFile(list[i]); |
| | 414 | | } |
| | 415 | |
|
| 0 | 416 | | return result; |
| | 417 | | } |
| | 418 | |
|
| | 419 | | /// <summary> |
| | 420 | | /// Write an entry's header information to a header buffer. |
| | 421 | | /// </summary> |
| | 422 | | /// <param name = "outBuffer"> |
| | 423 | | /// The tar entry header buffer to fill in. |
| | 424 | | /// </param> |
| | 425 | | public void WriteEntryHeader(byte[] outBuffer) |
| | 426 | | { |
| 70 | 427 | | header.WriteHeader(outBuffer); |
| 70 | 428 | | } |
| | 429 | |
|
| | 430 | | /// <summary> |
| | 431 | | /// Convenience method that will modify an entry's name directly |
| | 432 | | /// in place in an entry header buffer byte array. |
| | 433 | | /// </summary> |
| | 434 | | /// <param name="buffer"> |
| | 435 | | /// The buffer containing the entry header to modify. |
| | 436 | | /// </param> |
| | 437 | | /// <param name="newName"> |
| | 438 | | /// The new name to place into the header buffer. |
| | 439 | | /// </param> |
| | 440 | | static public void AdjustEntryName(byte[] buffer, string newName) |
| | 441 | | { |
| 0 | 442 | | TarHeader.GetNameBytes(newName, buffer, 0, TarHeader.NAMELEN); |
| 0 | 443 | | } |
| | 444 | |
|
| | 445 | | /// <summary> |
| | 446 | | /// Fill in a TarHeader given only the entry's name. |
| | 447 | | /// </summary> |
| | 448 | | /// <param name="header"> |
| | 449 | | /// The TarHeader to fill in. |
| | 450 | | /// </param> |
| | 451 | | /// <param name="name"> |
| | 452 | | /// The tar entry name. |
| | 453 | | /// </param> |
| | 454 | | static public void NameTarHeader(TarHeader header, string name) |
| | 455 | | { |
| 78 | 456 | | if (header == null) { |
| 0 | 457 | | throw new ArgumentNullException(nameof(header)); |
| | 458 | | } |
| | 459 | |
|
| 78 | 460 | | if (name == null) { |
| 0 | 461 | | throw new ArgumentNullException(nameof(name)); |
| | 462 | | } |
| | 463 | |
|
| 78 | 464 | | bool isDir = name.EndsWith("/", StringComparison.Ordinal); |
| | 465 | |
|
| 78 | 466 | | header.Name = name; |
| 78 | 467 | | header.Mode = isDir ? 1003 : 33216; |
| 78 | 468 | | header.UserId = 0; |
| 78 | 469 | | header.GroupId = 0; |
| 78 | 470 | | header.Size = 0; |
| | 471 | |
|
| 78 | 472 | | header.ModTime = DateTime.UtcNow; |
| | 473 | |
|
| 78 | 474 | | header.TypeFlag = isDir ? TarHeader.LF_DIR : TarHeader.LF_NORMAL; |
| | 475 | |
|
| 78 | 476 | | header.LinkName = String.Empty; |
| 78 | 477 | | header.UserName = String.Empty; |
| 78 | 478 | | header.GroupName = String.Empty; |
| | 479 | |
|
| 78 | 480 | | header.DevMajor = 0; |
| 78 | 481 | | header.DevMinor = 0; |
| 78 | 482 | | } |
| | 483 | |
|
| | 484 | | #region Instance Fields |
| | 485 | | /// <summary> |
| | 486 | | /// The name of the file this entry represents or null if the entry is not based on a file. |
| | 487 | | /// </summary> |
| | 488 | | string file; |
| | 489 | |
|
| | 490 | | /// <summary> |
| | 491 | | /// The entry's header information. |
| | 492 | | /// </summary> |
| | 493 | | TarHeader header; |
| | 494 | | #endregion |
| | 495 | | } |
| | 496 | | } |