changeset 54:ba4e2cb031e0

Added general purpose tar archive reader class
author Ivo Smits <Ivo@UCIS.nl>
date Wed, 02 Oct 2013 21:17:30 +0200
parents df5534af12e9
children ec222fb577dd
files Net/HTTP.cs UCIS.Core.csproj Util/TapeArchive.cs
diffstat 3 files changed, 217 insertions(+), 47 deletions(-) [+]
line wrap: on
line diff
--- 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<Byte>(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);
 		}
--- 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 @@
     <Compile Include="Util\PipeStream.cs" />
     <Compile Include="Util\PrebufferingStream.cs" />
     <Compile Include="Util\QueuedPacketStream.cs" />
+    <Compile Include="Util\TapeArchive.cs" />
     <Compile Include="VNCServer\IFramebuffer.cs" />
     <Compile Include="VNCServer\VNCServer.cs" />
     <Compile Include="Windows\ServiceManager.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<TarchiveEntry>, IEnumerable<TarchiveEntry> {
+		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<Byte>(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<TarchiveEntry>.Current {
+			get { return CurrentEntry; }
+		}
+		object IEnumerator.Current {
+			get { return CurrentEntry; }
+		}
+		bool IEnumerator.MoveNext() {
+			return ReadNext() != null;
+		}
+		void IEnumerator.Reset() {
+			SeekForward(SourceOffsetBase);
+		}
+		IEnumerator<TarchiveEntry> IEnumerable<TarchiveEntry>.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);
+		}
+	}
+}