# HG changeset patch # User Ivo Smits # Date 1380741450 -7200 # Node ID ba4e2cb031e035605ae2294c5098fe8c61f3cbf7 # Parent df5534af12e9c3503b79de09a16330cf513295f8 Added general purpose tar archive reader class diff -r df5534af12e9 -r ba4e2cb031e0 Net/HTTP.cs --- a/Net/HTTP.cs Wed Oct 02 21:17:08 2013 +0200 +++ b/Net/HTTP.cs Wed Oct 02 21:17:30 2013 +0200 @@ -189,11 +189,11 @@ } return; -SendError400AndClose: + SendError400AndClose: State = HTTPConnectionState.ProcessingRequest; SendErrorAndClose(400); return; -SendError500AndClose: + SendError500AndClose: State = HTTPConnectionState.ProcessingRequest; SendErrorAndClose(500); return; @@ -391,56 +391,38 @@ return; } String reqname1 = context.RequestPath; - if (reqname1.Length > 0 && reqname1[0] == '/') reqname1 = reqname1.Substring(1); + if (reqname1.StartsWith("/")) reqname1 = reqname1.Substring(1); String reqname2 = reqname1; if (reqname2.Length > 0 && !reqname2.EndsWith("/")) reqname2 += "/"; reqname2 += "index.htm"; - using (FileStream fs = File.OpenRead(TarFileName)) { - while (true) { - Byte[] header = new Byte[512]; - if (fs.Read(header, 0, 512) != 512) break; - int flen = Array.IndexOf(header, 0, 0, 100); - if (flen == 0) continue; - if (flen == -1) flen = 100; - String fname = Encoding.ASCII.GetString(header, 0, flen); - String fsize = Encoding.ASCII.GetString(header, 124, 11); - int fsizei = Convert.ToInt32(fsize, 8); - if (fname.StartsWith("./")) fname = fname.Length == 2 ? "/" : fname.Substring(2); - if (reqname1.Equals(fname, StringComparison.OrdinalIgnoreCase) || reqname2.Equals(fname)) { - context.SendStatus(200); - context.SendHeader("Content-Length", fsizei.ToString()); - String ctype = null; - switch (Path.GetExtension(fname).ToLowerInvariant()) { - case ".txt": ctype = "text/plain"; break; - case ".htm": - case ".html": ctype = "text/html"; break; - case ".css": ctype = "text/css"; break; - case ".js": ctype = "application/x-javascript"; break; - case ".png": ctype = "image/png"; break; - case ".jpg": - case ".jpeg": ctype = "image/jpeg"; break; - case ".gif": ctype = "image/gif"; break; - case ".ico": ctype = "image/x-icon"; break; - } - if (ctype != null) context.SendHeader("Content-Type", ctype); - Stream response = context.GetResponseStream(); - int left = fsizei; - byte[] buffer = new byte[Math.Min(left, 1024 * 10)]; - while (left > 0) { - int len = fs.Read(buffer, 0, Math.Min(left, buffer.Length)); - if (len <= 0) break; - left -= len; - response.Write(buffer, 0, len); - } - response.Close(); - return; - } else { - fs.Seek(fsizei, SeekOrigin.Current); + foreach (TarchiveEntry file in new TarchiveReader(TarFileName)) { + if (!file.IsFile) continue; + if (!reqname1.Equals(file.Name, StringComparison.OrdinalIgnoreCase) && !reqname2.Equals(file.Name, StringComparison.OrdinalIgnoreCase)) continue; + context.SendStatus(200); + context.SendHeader("Content-Length", file.Size.ToString()); + String ctype = null; + switch (Path.GetExtension(file.Name).ToLowerInvariant()) { + case ".txt": ctype = "text/plain"; break; + case ".htm": + case ".html": ctype = "text/html"; break; + case ".css": ctype = "text/css"; break; + case ".js": ctype = "application/x-javascript"; break; + case ".png": ctype = "image/png"; break; + case ".jpg": + case ".jpeg": ctype = "image/jpeg"; break; + case ".gif": ctype = "image/gif"; break; + case ".ico": ctype = "image/x-icon"; break; + } + if (ctype != null) context.SendHeader("Content-Type", ctype); + using (Stream response = context.GetResponseStream(), source = file.GetStream()) { + byte[] buffer = new byte[Math.Min(source.Length, 1024 * 10)]; + while (source.CanRead) { + int len = source.Read(buffer, 0, buffer.Length); + if (len <= 0) break; + response.Write(buffer, 0, len); } - int padding = fsizei % 512; - if (padding != 0) padding = 512 - padding; - fs.Seek(padding, SeekOrigin.Current); } + return; } context.SendErrorAndClose(404); } diff -r df5534af12e9 -r ba4e2cb031e0 UCIS.Core.csproj --- a/UCIS.Core.csproj Wed Oct 02 21:17:08 2013 +0200 +++ b/UCIS.Core.csproj Wed Oct 02 21:17:30 2013 +0200 @@ -174,6 +174,7 @@ + diff -r df5534af12e9 -r ba4e2cb031e0 Util/TapeArchive.cs --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Util/TapeArchive.cs Wed Oct 02 21:17:30 2013 +0200 @@ -0,0 +1,187 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.IO; +using System.Text; + +namespace UCIS.Util { + public class TarchiveReader : IDisposable, IEnumerator, IEnumerable { + public Stream Source { get; private set; } + public TarchiveEntry CurrentEntry { get; private set; } + private Int64 SourceOffsetBase = 0; + private Int64 SourceOffset = 0; + private Boolean CanSeek = false; + public TarchiveReader(String tarFile) : this(File.OpenRead(tarFile)) { } + public TarchiveReader(Type type, String resource) : this(type.Assembly.GetManifestResourceStream(type, resource)) { } + public TarchiveReader(Stream tar) { + this.Source = tar; + CanSeek = Source.CanSeek; + if (CanSeek) SourceOffset = SourceOffsetBase = Source.Position; + } + public void Dispose() { + Source.Dispose(); + } + private static String ReadString(Byte[] header, int offset, int maxlength) { + int end = Array.IndexOf(header, 0, offset, maxlength); + int length = (end == -1) ? maxlength : end - offset; + return Encoding.UTF8.GetString(header, offset, length); + } + private void SeekForward(Int64 position) { + if (CanSeek) { + Source.Seek(position, SeekOrigin.Begin); + SourceOffset = position; + } else { + if (position < SourceOffset) throw new ArgumentOutOfRangeException("Can not seek backwards"); + Byte[] buffer = new Byte[1024]; + while (position > SourceOffset) { + int read = Source.Read(buffer, 0, (int)Math.Min(1024, position - SourceOffset)); + if (read <= 0) throw new EndOfStreamException(); + SourceOffset += read; + } + } + } + private Int32 GetPaddedSize(Int32 size) { + int padding = size % 512; + if (padding != 0) padding = 512 - padding; + return size + padding; + } + private Byte[] ReadAll(int length) { + Byte[] buffer = new Byte[length]; + int offset = 0; + while (length > 0) { + int read = Source.Read(buffer, offset, length); + if (read <= 0) throw new EndOfStreamException(); + offset += read; + length -= read; + SourceOffset += read; + } + return buffer; + } + private Boolean IsAllZero(Byte[] header, int offset, int length) { + length += offset; + for (int i = offset; i < length; i++) if (header[i] != 0) return false; + return true; + } + public TarchiveEntry ReadNext() { + if (CurrentEntry != null) SeekForward(CurrentEntry.Offset + 512 + GetPaddedSize(CurrentEntry.Size)); + CurrentEntry = null; + Byte[] header = ReadAll(512); + if (IsAllZero(header, 0, header.Length)) return null; + Boolean ustar = (ReadString(header, 257, 6) == "ustar"); + String fname = ReadString(header, 0, 100); + String fsizes = ReadString(header, 124, 11); + Int32 fsize = Convert.ToInt32(fsizes, 8); + if (ustar) fname = ReadString(header, 345, 155) + fname; + String ffname = fname.StartsWith("./") ? (fname.Length == 2 ? "/" : fname.Substring(2)) : fname; + ffname = ffname.TrimEnd('/'); + TarchiveEntry entry = new TarchiveEntry() { Reader = this, OriginalName = fname, Offset = SourceOffset - 512, Size = fsize, Name = ffname }; + if (ustar) { + entry.IsDirectory = header[156] == '5'; + entry.IsDirectory = header[156] == '0' || header[156] == 0; + } else { + entry.IsDirectory = fname.EndsWith("/"); + entry.IsFile = !entry.IsDirectory && header[156] == '0'; + } + return CurrentEntry = entry; + } + public int ReadEntryData(TarchiveEntry entry, int fileoffset, Byte[] buffer, int bufferoffset, int count) { + if (entry.Reader != this) throw new ArgumentException("The specified entry is not part of this archive"); + if (count < 0) throw new ArgumentOutOfRangeException("count", "Count is negative"); + if (count == 0) return 0; + if (!entry.IsFile) throw new ArgumentException("entry", "Specified entry is not a file"); + if (fileoffset > entry.Size) throw new ArgumentOutOfRangeException("fileoffset", "File offset exceeds file size"); + if (fileoffset == entry.Size) return 0; + if (bufferoffset < 0) throw new ArgumentOutOfRangeException("bufferoffset", "Buffer offset is negative"); + if (bufferoffset + count > buffer.Length) throw new ArgumentOutOfRangeException("count", "Buffer offset and count exceed buffer dimensions"); + SeekForward(entry.Offset + 512 + fileoffset); + int read = Source.Read(buffer, bufferoffset, Math.Min(count, entry.Size - fileoffset)); + if (read > 0) SourceOffset += read; + return read; + } + public void SeekToEntry(TarchiveEntry entry) { + if (entry.Reader != this) throw new ArgumentException("The specified entry is not part of this archive"); + SeekForward(entry.Offset); + } + public Stream GetFileStream(TarchiveEntry entry) { + if (entry.Reader != this) throw new ArgumentException("The specified entry is not part of this archive"); + return new ReaderStream(this, entry); + } + TarchiveEntry IEnumerator.Current { + get { return CurrentEntry; } + } + object IEnumerator.Current { + get { return CurrentEntry; } + } + bool IEnumerator.MoveNext() { + return ReadNext() != null; + } + void IEnumerator.Reset() { + SeekForward(SourceOffsetBase); + } + IEnumerator IEnumerable.GetEnumerator() { + return this; + } + IEnumerator IEnumerable.GetEnumerator() { + return this; + } + + class ReaderStream : Stream { + long position = 0; + public TarchiveReader Source { get; private set; } + public TarchiveEntry File { get; private set; } + public ReaderStream(TarchiveReader source, TarchiveEntry entry) { + this.Source = source; + this.File = entry; + } + public override bool CanRead { get { return Source != null && Source.Source.CanRead && Position < Length; } } + public override bool CanSeek { get { return Source != null && Source.Source.CanSeek; } } + public override bool CanWrite { get { return false; } } + protected override void Dispose(bool disposing) { + base.Dispose(disposing); + Source = null; + File = null; + } + public override long Length { get { return File.Size; } } + public override long Position { + get { return position; } + set { + if (value < 0 || value > Length) throw new ArgumentOutOfRangeException("value"); + position = value; + } + } + public override long Seek(long offset, SeekOrigin origin) { + switch (origin) { + case SeekOrigin.Begin: Position = offset; break; + case SeekOrigin.Current: Position += offset; break; + case SeekOrigin.End: Position = Length - offset; break; + } + return position; + } + public override int Read(byte[] buffer, int offset, int count) { + int read = Source.ReadEntryData(File, (int)position, buffer, offset, count); + position += read; + return read; + } + public override void Write(byte[] buffer, int offset, int count) { throw new NotSupportedException(); } + public override void SetLength(long value) { throw new NotSupportedException(); } + public override void Flush() { } + } + } + public class TarchiveEntry { + public TarchiveReader Reader { get; internal set; } + public String Name { get; internal set; } + public String OriginalName { get; internal set; } + public Boolean IsDirectory { get; internal set; } + public Boolean IsFile { get; internal set; } + public Int32 Size { get; internal set; } + public Int64 Offset { get; internal set; } + + public int Read(int fileoffset, Byte[] buffer, int bufferoffset, int count) { + return Reader.ReadEntryData(this, fileoffset, buffer, bufferoffset, count); + } + + public Stream GetStream() { + return Reader.GetFileStream(this); + } + } +}