/* Copyright (c) Citrix Systems Inc. * All rights reserved. * * Redistribution and use in source and binary forms, * with or without modification, are permitted provided * that the following conditions are met: * * * Redistributions of source code must retain the above * copyright notice, this list of conditions and the * following disclaimer. * * Redistributions in binary form must reproduce the above * copyright notice, this list of conditions and the * following disclaimer in the documentation and/or other * materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ using System; using System.Collections.Generic; using System.Collections; using System.Net; using System.IO; using System.Text; /* Thrown if we fail to verify a tar header checksum */ public class HeaderChecksumFailed : ApplicationException { private uint expected; private uint received; public HeaderChecksumFailed(uint expected, uint received){ this.expected = expected; this.received = received; } public override string ToString() { string expected = Convert.ToString(this.expected); string received = Convert.ToString(this.received); return "Failed to verify the tar header checksum: received = " + received + "; expected = " + expected; } } /* Thrown when we find the end of archive marker (two zero blocks) */ class EndOfArchive : ApplicationException { public EndOfArchive(){ } public override string ToString(){ return "End of tar archive"; } } public class Header{ public string file_name; public int file_mode; public int user_id; public int group_id; public uint file_size; public uint mod_time; public bool link; public int link_name; /* Length of a header block */ public static uint length = 512; /* http://en.wikipedia.org/w/index.php?title=Tar_%28file_format%29&oldid=83554041 */ private static int file_name_off = 0; private static int file_name_len = 100; private static int file_mode_off = 100; private static int file_mode_len = 8; private static int user_id_off = 108; private static int user_id_len = 8; private static int group_id_off = 116; private static int group_id_len = 8; private static int file_size_off = 124; private static int file_size_len = 12; private static int mod_time_off = 136; private static int mod_time_len = 12; private static int chksum_off = 148; private static int chksum_len = 8; private static int link_off = 156; private static int link_len = 1; private static int link_name_off = 156; private static int link_name_len = 100; /* True if a buffer contains all zeroes */ public static bool all_zeroes(byte[] buffer){ bool zeroes = true; for (int i = 0; i < buffer.Length && zeroes; i++) { if (buffer[i] != 0) zeroes = false; } return zeroes; } /* Return a sub-array of bytes */ private byte[] slice(byte[] input, int offset, int length){ byte[] result = new byte[length]; for (int i = 0; i < length; i++) { result[i] = input[offset + i]; } return result; } /* Remove NULLs and spaces from the end of a string */ private string trim_trailing_stuff(string x){ char[] trimmed = { '\0', ' '}; return x.TrimEnd(trimmed); } /* Convert the byte array into a string (assume UTF8) */ private string unmarshal_string(byte[] buffer){ Decoder decoder = Encoding.UTF8.GetDecoder(); char[] chars = new char[decoder.GetCharCount(buffer, 0, (int)buffer.Length)]; decoder.GetChars(buffer, 0, (int)buffer.Length, chars, 0); return trim_trailing_stuff(new string(chars)); } /* Unmarshal an octal string into an int32 */ private uint unmarshal_int32(byte[] buffer){ string octal = "0" + unmarshal_string(buffer); return System.Convert.ToUInt32(octal, 8); } /* Unmarshal an octal string into an int */ private int unmarshal_int(byte[] buffer){ string octal = "0" + unmarshal_string(buffer); return System.Convert.ToInt32(octal, 8); } /* Recompute the (weak) header checksum */ private uint compute_checksum(byte[] buffer){ uint total = 0; for(int i = 0; i < buffer.Length; i++){ /* treat the checksum digits as ' ' */ if ((i >= chksum_off) && (i < (chksum_off + chksum_len))){ total += 32; /* ' ' */ } else { total += buffer[i]; } } return total; } /* Compute the required length of padding data to follow the data payload */ public uint paddingLength(){ /* round up to the next whole number of blocks */ uint next_block_length = (file_size + length - 1) / length * length; return next_block_length - file_size; } /* pretty-print a header */ public override string ToString(){ return String.Format("{0}/{1} {2:000000000000} {3:000000000000} {4}", user_id, group_id, file_size, mod_time, file_name); } /* Unmarshal a header from a buffer, throw an exception if the checksum doesn't validate */ public Header(byte[] buffer){ file_name = unmarshal_string(slice(buffer, file_name_off, file_name_len)); file_mode = unmarshal_int(slice(buffer, file_mode_off, file_mode_len)); user_id = unmarshal_int(slice(buffer, user_id_off, user_id_len)); group_id = unmarshal_int(slice(buffer, group_id_off, group_id_len)); file_size = unmarshal_int32(slice(buffer, file_size_off, file_size_len)); mod_time = unmarshal_int32(slice(buffer, mod_time_off, mod_time_len)); link = unmarshal_string(slice(buffer, link_off, link_len)) == "1"; link_name = unmarshal_int(slice(buffer, link_name_off, link_name_len)); uint chksum = unmarshal_int32(slice(buffer, chksum_off, chksum_len)); uint recomputed = compute_checksum(buffer); if (chksum != recomputed) throw new HeaderChecksumFailed(recomputed, chksum); } /* Read a tar header from a stream */ public static Header fromStream(Stream input){ byte[] one = IO.unmarshal_n(input, length); if (all_zeroes(one)){ byte[] two = IO.unmarshal_n(input, length); if (all_zeroes(two)) throw new EndOfArchive(); return new Header(two); } return new Header(one); } } public class Archive{ public static void list(Stream stream){ try { while (true){ Header x = Header.fromStream(stream); Console.WriteLine(x); IO.skip(stream, x.file_size); IO.skip(stream, x.paddingLength()); } }catch(EndOfArchive){ Console.WriteLine("EOF"); } } }