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 34 { 35 #region Constructors 36 /// <summary> 37 /// Initialise a default instance of <see cref="TarEntry"/>. 38 /// </summary> TarEntry()39 private TarEntry() 40 { 41 header = new TarHeader(); 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> TarEntry(byte[] headerBuffer)51 public TarEntry(byte[] headerBuffer) 52 { 53 header = new TarHeader(); 54 header.ParseBuffer(headerBuffer); 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> TarEntry(TarHeader header)61 public TarEntry(TarHeader header) 62 { 63 if (header == null) { 64 throw new ArgumentNullException(nameof(header)); 65 } 66 67 this.header = (TarHeader)header.Clone(); 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> Clone()76 public object Clone() 77 { 78 var entry = new TarEntry(); 79 entry.file = file; 80 entry.header = (TarHeader)header.Clone(); 81 entry.Name = Name; 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> CreateTarEntry(string name)92 public static TarEntry CreateTarEntry(string name) 93 { 94 var entry = new TarEntry(); 95 TarEntry.NameTarHeader(entry.header, name); 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> CreateEntryFromFile(string fileName)105 public static TarEntry CreateEntryFromFile(string fileName) 106 { 107 var entry = new TarEntry(); 108 entry.GetFileTarHeader(entry.header, fileName); 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> Equals(object obj)120 public override bool Equals(object obj) 121 { 122 var localEntry = obj as TarEntry; 123 124 if (localEntry != null) { 125 return Name.Equals(localEntry.Name); 126 } 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> GetHashCode()134 public override int GetHashCode() 135 { 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> IsDescendent(TarEntry toTest)150 public bool IsDescendent(TarEntry toTest) 151 { 152 if (toTest == null) { 153 throw new ArgumentNullException(nameof(toTest)); 154 } 155 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 { 167 return header; 168 } 169 } 170 171 /// <summary> 172 /// Get/Set this entry's name. 173 /// </summary> 174 public string Name { 175 get { 176 return header.Name; 177 } 178 set { 179 header.Name = value; 180 } 181 } 182 183 /// <summary> 184 /// Get/set this entry's user id. 185 /// </summary> 186 public int UserId { 187 get { 188 return header.UserId; 189 } 190 set { 191 header.UserId = value; 192 } 193 } 194 195 /// <summary> 196 /// Get/set this entry's group id. 197 /// </summary> 198 public int GroupId { 199 get { 200 return header.GroupId; 201 } 202 set { 203 header.GroupId = value; 204 } 205 } 206 207 /// <summary> 208 /// Get/set this entry's user name. 209 /// </summary> 210 public string UserName { 211 get { 212 return header.UserName; 213 } 214 set { 215 header.UserName = value; 216 } 217 } 218 219 /// <summary> 220 /// Get/set this entry's group name. 221 /// </summary> 222 public string GroupName { 223 get { 224 return header.GroupName; 225 } 226 set { 227 header.GroupName = value; 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> SetIds(int userId, int groupId)240 public void SetIds(int userId, int groupId) 241 { 242 UserId = userId; 243 GroupId = groupId; 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> SetNames(string userName, string groupName)255 public void SetNames(string userName, string groupName) 256 { 257 UserName = userName; 258 GroupName = groupName; 259 } 260 261 /// <summary> 262 /// Get/Set the modification time for this entry 263 /// </summary> 264 public DateTime ModTime { 265 get { 266 return header.ModTime; 267 } 268 set { 269 header.ModTime = value; 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 { 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 { 290 return header.Size; 291 } 292 set { 293 header.Size = value; 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 { 305 if (file != null) { 306 return Directory.Exists(file); 307 } 308 309 if (header != null) { 310 if ((header.TypeFlag == TarHeader.LF_DIR) || Name.EndsWith("/", StringComparison.Ordinal)) { 311 return true; 312 } 313 } 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> GetFileTarHeader(TarHeader header, string file)327 public void GetFileTarHeader(TarHeader header, string file) 328 { 329 if (header == null) { 330 throw new ArgumentNullException(nameof(header)); 331 } 332 333 if (file == null) { 334 throw new ArgumentNullException(nameof(file)); 335 } 336 337 this.file = file; 338 339 // bugfix from torhovl from #D forum: 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 343 if (name.IndexOf(Directory.GetCurrentDirectory(), StringComparison.Ordinal) == 0) { 344 name = name.Substring(Directory.GetCurrentDirectory().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 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. 370 while (name.StartsWith("/", StringComparison.Ordinal)) { 371 name = name.Substring(1); 372 } 373 374 header.LinkName = String.Empty; 375 header.Name = name; 376 377 if (Directory.Exists(file)) { 378 header.Mode = 1003; // Magic number for security access for a UNIX filesystem 379 header.TypeFlag = TarHeader.LF_DIR; 380 if ((header.Name.Length == 0) || header.Name[header.Name.Length - 1] != '/') { 381 header.Name = header.Name + "/"; 382 } 383 384 header.Size = 0; 385 } else { 386 header.Mode = 33216; // Magic number for security access for a UNIX filesystem 387 header.TypeFlag = TarHeader.LF_NORMAL; 388 header.Size = new FileInfo(file.Replace('/', Path.DirectorySeparatorChar)).Length; 389 } 390 391 header.ModTime = System.IO.File.GetLastWriteTime(file.Replace('/', Path.DirectorySeparatorChar)).ToUniversalTime(); 392 header.DevMajor = 0; 393 header.DevMinor = 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> GetDirectoryEntries()403 public TarEntry[] GetDirectoryEntries() 404 { 405 if ((file == null) || !Directory.Exists(file)) { 406 return new TarEntry[0]; 407 } 408 409 string[] list = Directory.GetFileSystemEntries(file); 410 TarEntry[] result = new TarEntry[list.Length]; 411 412 for (int i = 0; i < list.Length; ++i) { 413 result[i] = TarEntry.CreateEntryFromFile(list[i]); 414 } 415 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> WriteEntryHeader(byte[] outBuffer)425 public void WriteEntryHeader(byte[] outBuffer) 426 { 427 header.WriteHeader(outBuffer); 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> AdjustEntryName(byte[] buffer, string newName)440 static public void AdjustEntryName(byte[] buffer, string newName) 441 { 442 TarHeader.GetNameBytes(newName, buffer, 0, TarHeader.NAMELEN); 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> NameTarHeader(TarHeader header, string name)454 static public void NameTarHeader(TarHeader header, string name) 455 { 456 if (header == null) { 457 throw new ArgumentNullException(nameof(header)); 458 } 459 460 if (name == null) { 461 throw new ArgumentNullException(nameof(name)); 462 } 463 464 bool isDir = name.EndsWith("/", StringComparison.Ordinal); 465 466 header.Name = name; 467 header.Mode = isDir ? 1003 : 33216; 468 header.UserId = 0; 469 header.GroupId = 0; 470 header.Size = 0; 471 472 header.ModTime = DateTime.UtcNow; 473 474 header.TypeFlag = isDir ? TarHeader.LF_DIR : TarHeader.LF_NORMAL; 475 476 header.LinkName = String.Empty; 477 header.UserName = String.Empty; 478 header.GroupName = String.Empty; 479 480 header.DevMajor = 0; 481 header.DevMinor = 0; 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 } 497