| 1: | // TarEntry.cs | |
| 2: | // Copyright (C) 2001 Mike Krueger | |
| 3: | // | |
| 4: | // This program is free software; you can redistribute it and/or | |
| 5: | // modify it under the terms of the GNU General Public License | |
| 6: | // as published by the Free Software Foundation; either version 2 | |
| 7: | // of the License, or (at your option) any later version. | |
| 8: | // | |
| 9: | // This program is distributed in the hope that it will be useful, | |
| 10: | // but WITHOUT ANY WARRANTY; without even the implied warranty of | |
| 11: | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
| 12: | // GNU General Public License for more details. | |
| 13: | // | |
| 14: | // You should have received a copy of the GNU General Public License | |
| 15: | // along with this program; if not, write to the Free Software | |
| 16: | // Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. | |
| 17: | // | |
| 18: | // Linking this library statically or dynamically with other modules is | |
| 19: | // making a combined work based on this library. Thus, the terms and | |
| 20: | // conditions of the GNU General Public License cover the whole | |
| 21: | // combination. | |
| 22: | // | |
| 23: | // As a special exception, the copyright holders of this library give you | |
| 24: | // permission to link this library with independent modules to produce an | |
| 25: | // executable, regardless of the license terms of these independent | |
| 26: | // modules, and to copy and distribute the resulting executable under | |
| 27: | // terms of your choice, provided that you also meet, for each linked | |
| 28: | // independent module, the terms and conditions of the license of that | |
| 29: | // module. An independent module is a module which is not derived from | |
| 30: | // or based on this library. If you modify this library, you may extend | |
| 31: | // this exception to your version of the library, but you are not | |
| 32: | // obligated to do so. If you do not wish to do so, delete this | |
| 33: | // exception statement from your version. | |
| 34: | ||
| 35: | using System; | |
| 36: | using System.IO; | |
| 37: | using System.Text; | |
| 38: | ||
| 39: | namespace ICSharpCode.SharpZipLib.Tar { | |
| 40: | ||
| 41: | /// <summary> | |
| 42: | /// This class represents an entry in a Tar archive. It consists | |
| 43: | /// of the entry's header, as well as the entry's File. Entries | |
| 44: | /// can be instantiated in one of three ways, depending on how | |
| 45: | /// they are to be used. | |
| 46: | /// <p> | |
| 47: | /// TarEntries that are created from the header bytes read from | |
| 48: | /// an archive are instantiated with the TarEntry( byte[] ) | |
| 49: | /// constructor. These entries will be used when extracting from | |
| 50: | /// or listing the contents of an archive. These entries have their | |
| 51: | /// header filled in using the header bytes. They also set the File | |
| 52: | /// to null, since they reference an archive entry not a file.</p> | |
| 53: | /// <p> | |
| 54: | /// TarEntries that are created from Files that are to be written | |
| 55: | /// into an archive are instantiated with the TarEntry( File ) | |
| 56: | /// constructor. These entries have their header filled in using | |
| 57: | /// the File's information. They also keep a reference to the File | |
| 58: | /// for convenience when writing entries.</p> | |
| 59: | /// <p> | |
| 60: | /// Finally, TarEntries can be constructed from nothing but a name. | |
| 61: | /// This allows the programmer to construct the entry by hand, for | |
| 62: | /// instance when only an InputStream is available for writing to | |
| 63: | /// the archive, and the header information is constructed from | |
| 64: | /// other information. In this case the header fields are set to | |
| 65: | /// defaults and the File is set to null.</p> | |
| 66: | /// | |
| 67: | /// <p> | |
| 68: | /// The C structure for a Tar Entry's header is: | |
| 69: | /// <pre> | |
| 70: | /// struct header { | |
| 71: | /// char name[NAMSIZ]; | |
| 72: | /// char mode[8]; | |
| 73: | /// char uid[8]; | |
| 74: | /// char gid[8]; | |
| 75: | /// char size[12]; | |
| 76: | /// char mtime[12]; | |
| 77: | /// char chksum[8]; | |
| 78: | /// char linkflag; | |
| 79: | /// char linkname[NAMSIZ]; | |
| 80: | /// char magic[8]; | |
| 81: | /// char uname[TUNMLEN]; | |
| 82: | /// char gname[TGNMLEN]; | |
| 83: | /// char devmajor[8]; | |
| 84: | /// char devminor[8]; | |
| 85: | /// } header; | |
| 86: | /// </pre> | |
| 87: | /// </p> | |
| 88: | /// <see cref="TarHeader"/> | |
| 89: | /// </summary> | |
| 90: | public class TarEntry | |
| 91: | { | |
| 92: | /// <summary> | |
| 93: | /// If this entry represents a File, this references it. | |
| 94: | /// </summary> | |
| 95: | protected string file; | |
| 96: | ||
| 97: | /// <summary> | |
| 98: | /// This is the entry's header information. | |
| 99: | /// </summary> | |
| 100: | protected TarHeader header; | |
| 101: | ||
| 102: | /// <summary> | |
| 103: | /// Only Create Entries with the static CreateXYZ methods or a headerBuffer. | |
| 104: | /// </summary> | |
| 105: | private TarEntry() | |
| 106: | { | |
| 107: | } | |
| 108: | ||
| 109: | /// <summary> | |
| 110: | /// Construct an entry from an archive's header bytes. File is set | |
| 111: | /// to null. | |
| 112: | /// </summary> | |
| 113: | /// <param name = "headerBuf"> | |
| 114: | /// The header bytes from a tar archive entry. | |
| 115: | /// </param> | |
| 116: | public TarEntry(byte[] headerBuf) | |
| 117: | { | |
| 118: | this.Initialize(); | |
| 119: | this.ParseTarHeader(this.header, headerBuf); | |
| 120: | } | |
| 121: | ||
| 122: | ||
| 123: | ||
| 124: | /// <summary> | |
| 125: | /// Construct an entry with only a name. This allows the programmer | |
| 126: | /// to construct the entry's header "by hand". File is set to null. | |
| 127: | /// </summary> | |
| 128: | public static TarEntry CreateTarEntry(string name) | |
| 129: | { | |
| 130: | TarEntry entry = new TarEntry(); | |
| 131: | entry.Initialize(); | |
| 132: | entry.NameTarHeader(entry.header, name); | |
| 133: | return entry; | |
| 134: | } | |
| 135: | ||
| 136: | /// <summary> | |
| 137: | /// Construct an entry for a file. File is set to file, and the | |
| 138: | /// header is constructed from information from the file. | |
| 139: | /// </summary> | |
| 140: | /// <param name = "fileName"> | |
| 141: | /// The file that the entry represents. | |
| 142: | /// </param> | |
| 143: | public static TarEntry CreateEntryFromFile(string fileName) | |
| 144: | { | |
| 145: | TarEntry entry = new TarEntry(); | |
| 146: | entry.Initialize(); | |
| 147: | entry.GetFileTarHeader(entry.header, fileName); | |
| 148: | return entry; | |
| 149: | } | |
| 150: | ||
| 151: | /// <summary> | |
| 152: | /// Initialization code common to all constructors. | |
| 153: | /// </summary> | |
| 154: | void Initialize() | |
| 155: | { | |
| 156: | this.file = null; | |
| 157: | this.header = new TarHeader(); | |
| 158: | } | |
| 159: | ||
| 160: | /// <summary> | |
| 161: | /// Determine if the two entries are equal. Equality is determined | |
| 162: | /// by the header names being equal. | |
| 163: | /// </summary> | |
| 164: | /// <returns> | |
| 165: | /// True if the entries are equal. | |
| 166: | /// </returns> | |
| 167: | public override bool Equals(object it) | |
| 168: | { | |
| 169: | if (!(it is TarEntry)) { | |
| 170: | return false; | |
| 171: | } | |
| 172: | return this.header.name.ToString().Equals(((TarEntry)it).header.name.ToString()); | |
| 173: | } | |
| 174: | ||
| 175: | /// <summary> | |
| 176: | /// Must be overridden when you override Equals. | |
| 177: | /// </summary> | |
| 178: | public override int GetHashCode() | |
| 179: | { | |
| 180: | return this.header.name.ToString().GetHashCode(); | |
| 181: | } | |
| 182: | ||
| 183: | ||
| 184: | /// <summary> | |
| 185: | /// Determine if the given entry is a descendant of this entry. | |
| 186: | /// Descendancy is determined by the name of the descendant | |
| 187: | /// starting with this entry's name. | |
| 188: | /// </summary> | |
| 189: | /// <param name = "desc"> | |
| 190: | /// Entry to be checked as a descendent of this. | |
| 191: | /// </param> | |
| 192: | /// <returns> | |
| 193: | /// True if entry is a descendant of this. | |
| 194: | /// </returns> | |
| 195: | public bool IsDescendent(TarEntry desc) | |
| 196: | { | |
| 197: | return desc.header.name.ToString().StartsWith(this.header.name.ToString()); | |
| 198: | } | |
| 199: | ||
| 200: | /// <summary> | |
| 201: | /// Get this entry's header. | |
| 202: | /// </summary> | |
| 203: | /// <returns> | |
| 204: | /// This entry's TarHeader. | |
| 205: | /// </returns> | |
| 206: | public TarHeader TarHeader { | |
| 207: | get { | |
| 208: | return this.header; | |
| 209: | } | |
| 210: | } | |
| 211: | ||
| 212: | /// <summary> | |
| 213: | /// Get/Set this entry's name. | |
| 214: | /// </summary> | |
| 215: | public string Name { | |
| 216: | get { | |
| 217: | return this.header.name.ToString(); | |
| 218: | } | |
| 219: | set { | |
| 220: | this.header.name = new StringBuilder(value); | |
| 221: | } | |
| 222: | } | |
| 223: | ||
| 224: | /// <summary> | |
| 225: | /// Get/set this entry's user id. | |
| 226: | /// </summary> | |
| 227: | public int UserId { | |
| 228: | get { | |
| 229: | return this.header.userId; | |
| 230: | } | |
| 231: | set { | |
| 232: | this.header.userId = value; | |
| 233: | } | |
| 234: | } | |
| 235: | ||
| 236: | /// <summary> | |
| 237: | /// Get/set this entry's group id. | |
| 238: | /// </summary> | |
| 239: | public int GroupId { | |
| 240: | get { | |
| 241: | return this.header.groupId; | |
| 242: | } | |
| 243: | set { | |
| 244: | this.header.groupId = value; | |
| 245: | } | |
| 246: | } | |
| 247: | ||
| 248: | /// <summary> | |
| 249: | /// Get/set this entry's user name. | |
| 250: | /// </summary> | |
| 251: | public string UserName { | |
| 252: | get { | |
| 253: | return this.header.userName.ToString(); | |
| 254: | } | |
| 255: | set { | |
| 256: | this.header.userName = new StringBuilder(value); | |
| 257: | } | |
| 258: | } | |
| 259: | ||
| 260: | /// <summary> | |
| 261: | /// Get/set this entry's group name. | |
| 262: | /// </summary> | |
| 263: | public string GroupName { | |
| 264: | get { | |
| 265: | return this.header.groupName.ToString(); | |
| 266: | } | |
| 267: | set { | |
| 268: | this.header.groupName = new StringBuilder(value); | |
| 269: | } | |
| 270: | } | |
| 271: | ||
| 272: | /// <summary> | |
| 273: | /// Convenience method to set this entry's group and user ids. | |
| 274: | /// </summary> | |
| 275: | /// <param name="userId"> | |
| 276: | /// This entry's new user id. | |
| 277: | /// </param> | |
| 278: | /// <param name="groupId"> | |
| 279: | /// This entry's new group id. | |
| 280: | /// </param> | |
| 281: | public void SetIds(int userId, int groupId) | |
| 282: | { | |
| 283: | UserId = userId; | |
| 284: | GroupId = groupId; | |
| 285: | } | |
| 286: | ||
| 287: | /// <summary> | |
| 288: | /// Convenience method to set this entry's group and user names. | |
| 289: | /// </summary> | |
| 290: | /// <param name="userName"> | |
| 291: | /// This entry's new user name. | |
| 292: | /// </param> | |
| 293: | /// <param name="groupName"> | |
| 294: | /// This entry's new group name. | |
| 295: | /// </param> | |
| 296: | public void SetNames(string userName, string groupName) | |
| 297: | { | |
| 298: | UserName = userName; | |
| 299: | GroupName = groupName; | |
| 300: | } | |
| 301: | ||
| 302: | // TODO : | |
| 303: | // /** | |
| 304: | // * Set this entry's modification time. The parameter passed | |
| 305: | // * to this method is in "Java time". | |
| 306: | // * | |
| 307: | // * @param time This entry's new modification time. | |
| 308: | // */ | |
| 309: | // public void setModTime( long time ) | |
| 310: | // { | |
| 311: | // this.header.modTime = time / 1000; | |
| 312: | // } | |
| 313: | ||
| 314: | /// Convert time to DateTimes | |
| 315: | /** | |
| 316: | * Get/Set this entry's modification time. | |
| 317: | * | |
| 318: | * @param time This entry's new modification time. | |
| 319: | */ | |
| 320: | public DateTime ModTime { | |
| 321: | get { | |
| 322: | return this.header.modTime; | |
| 323: | } | |
| 324: | set { | |
| 325: | this.header.modTime = value; | |
| 326: | } | |
| 327: | } | |
| 328: | ||
| 329: | /// <summary> | |
| 330: | /// Get this entry's file. | |
| 331: | /// </summary> | |
| 332: | /// <returns> | |
| 333: | /// This entry's file. | |
| 334: | /// </returns> | |
| 335: | public string File { | |
| 336: | get { | |
| 337: | return this.file; | |
| 338: | } | |
| 339: | } | |
| 340: | ||
| 341: | /// <summary> | |
| 342: | /// Get/set this entry's file size. | |
| 343: | /// </summary> | |
| 344: | public long Size { | |
| 345: | get { | |
| 346: | return this.header.size; | |
| 347: | } | |
| 348: | set { | |
| 349: | this.header.size = value; | |
| 350: | } | |
| 351: | } | |
| 352: | ||
| 353: | /// <summary> | |
| 354: | /// Convenience method that will modify an entry's name directly | |
| 355: | /// in place in an entry header buffer byte array. | |
| 356: | /// </summary> | |
| 357: | /// <param name="outbuf"> | |
| 358: | /// The buffer containing the entry header to modify. | |
| 359: | /// </param> | |
| 360: | /// <param name="newName"> | |
| 361: | /// The new name to place into the header buffer. | |
| 362: | /// </param> | |
| 363: | public void AdjustEntryName(byte[] outbuf, string newName) | |
| 364: | { | |
| 365: | int offset = 0; | |
| 366: | offset = TarHeader.GetNameBytes(new StringBuilder(newName), outbuf, offset, TarHeader.NAMELEN); | |
| 367: | } | |
| 368: | ||
| 369: | /// <summary> | |
| 370: | /// Return whether or not this entry represents a directory. | |
| 371: | /// </summary> | |
| 372: | /// <returns> | |
| 373: | /// True if this entry is a directory. | |
| 374: | /// </returns> | |
| 375: | public bool IsDirectory | |
| 376: | { | |
| 377: | get { | |
| 378: | if (this.file != null) { | |
| 379: | return Directory.Exists(file); | |
| 380: | } | |
| 381: | ||
| 382: | if (this.header != null) { | |
| 383: | if (this.header.linkFlag == TarHeader.LF_DIR || this.header.name.ToString().EndsWith( "/" )) { | |
| 384: | return true; | |
| 385: | } | |
| 386: | } | |
| 387: | return false; | |
| 388: | } | |
| 389: | } | |
| 390: | ||
| 391: | /// <summary> | |
| 392: | /// Fill in a TarHeader with information from a File. | |
| 393: | /// </summary> | |
| 394: | /// <param name="hdr"> | |
| 395: | /// The TarHeader to fill in. | |
| 396: | /// </param> | |
| 397: | /// <param name="file"> | |
| 398: | /// The file from which to get the header information. | |
| 399: | /// </param> | |
| 400: | public void GetFileTarHeader(TarHeader hdr, string file) | |
| 401: | { | |
| 402: | this.file = file; | |
| 403: | ||
| 404: | string name = Path.GetDirectoryName(file); | |
| 405: | ||
| 406: | if (Path.DirectorySeparatorChar == '\\') { // check if the OS is a windows | |
| 407: | // Strip off drive letters! | |
| 408: | if (name.Length > 2) { | |
| 409: | char ch1 = name[0]; | |
| 410: | char ch2 = name[1]; | |
| 411: | ||
| 412: | if (ch2 == ':' && Char.IsLetter(ch1)) { | |
| 413: | name = name.Substring(2); | |
| 414: | } | |
| 415: | } | |
| 416: | } | |
| 417: | ||
| 418: | name = name.Replace(Path.DirectorySeparatorChar, '/'); | |
| 419: | ||
| 420: |