diff --git a/Sunfish/Extensions.cs b/Sunfish/Extensions.cs index 3a54f9a..6e185cb 100644 --- a/Sunfish/Extensions.cs +++ b/Sunfish/Extensions.cs @@ -44,5 +44,18 @@ return value; return def; } + + public static string ToSize(this long l) + { + string[] tails = { "bytes", "kb", "mb", "gb", "tb", "pb", "eb", "zb", "yb" }; + int scale = 0; + double cnt = l; + while (cnt > 1024) + { + scale++; + cnt /= 1024; + } + return cnt.ToString("#0.00") + " " + (scale >= tails.Length ? "^b" : tails[scale]); + } } } diff --git a/Sunfish/Middleware/HttpServer.cs b/Sunfish/Middleware/HttpServer.cs index f619e33..b9dc15c 100644 --- a/Sunfish/Middleware/HttpServer.cs +++ b/Sunfish/Middleware/HttpServer.cs @@ -95,9 +95,13 @@ { prc(this, call); } - finally + catch (Exception e) { call.Response.StatusCode = 500; + call.Write(e.GetType().Name + ": " + e.Message); + } + finally + { call.Close(); } } diff --git a/Sunfish/Middleware/VFS.cs b/Sunfish/Middleware/VFS.cs index ff5489b..cb8b210 100644 --- a/Sunfish/Middleware/VFS.cs +++ b/Sunfish/Middleware/VFS.cs @@ -94,7 +94,7 @@ return dir.Create(inner, asDir); } - public string[] ListFiles(string path) + public VFSItem[] ListFiles(string path) { VFSFolder folder = LocateFolder(ref path); if (folder == null) @@ -102,7 +102,7 @@ return folder.ListFiles(path); } - public string[] ListDirectories(string path) + public VFSItem[] ListDirectories(string path) { VFSFolder folder = LocateFolder(ref path); if (folder == null) diff --git a/Sunfish/Middleware/VFSFolder.cs b/Sunfish/Middleware/VFSFolder.cs index ae599d9..0fe8a27 100644 --- a/Sunfish/Middleware/VFSFolder.cs +++ b/Sunfish/Middleware/VFSFolder.cs @@ -12,8 +12,8 @@ public abstract Stream OpenRead(string path); public abstract Stream OpenWrite(string path); public abstract VFSItem GetItem(string path); - public abstract string[] ListFiles(string path); - public abstract string[] ListDirectories(string path); + public abstract VFSItem[] ListFiles(string path); + public abstract VFSItem[] ListDirectories(string path); public abstract bool DeleteFile(string path); public abstract bool DeleteFolder(string path); public abstract bool Rename(string from, string to); diff --git a/Sunfish/Middleware/VFSFolderFileSystem.cs b/Sunfish/Middleware/VFSFolderFileSystem.cs index 183e88e..7eef5d5 100644 --- a/Sunfish/Middleware/VFSFolderFileSystem.cs +++ b/Sunfish/Middleware/VFSFolderFileSystem.cs @@ -15,6 +15,14 @@ public VFSFolderFileSystem(string path) { basePath = path; + while (basePath[basePath.Length - 1] == (Path.DirectorySeparatorChar)) + { + if (basePath.Length < 2) + throw new Exception("Invalid path"); + basePath = basePath.Substring(0, basePath.Length - 1); + } + if (basePath.Length == 2) + basePath += Path.DirectorySeparatorChar; } public bool AllowSubfolder { get; set; } = true; @@ -54,23 +62,32 @@ return new VFSItem(this, path, di.Exists, fi.Exists ? fi.Length : 0); } - public override string[] ListDirectories(string path) + public override VFSItem[] ListDirectories(string path) { string fpath = Path.Combine(basePath, path); - List lst = new List(); + int basecut = basePath.Length; + if (basePath[basePath.Length - 1] != Path.DirectorySeparatorChar) + basecut++; + List lst = new List(); foreach (string d in Directory.GetDirectories(fpath)) - lst.Add(d.Substring(fpath.Length + 1)); - lst.Sort(); + lst.Add(new VFSItem(this, d.Substring(basecut), true, 0)); + lst.Sort((a, b) => a.Name.CompareTo(b.Name)); return lst.ToArray(); } - public override string[] ListFiles(string path) + public override VFSItem[] ListFiles(string path) { string fpath = Path.Combine(basePath, path); - List lst = new List(); + int basecut = basePath.Length; + if (basePath[basePath.Length - 1] != Path.DirectorySeparatorChar) + basecut++; + List lst = new List(); foreach (string d in Directory.GetFiles(fpath)) - lst.Add(d.Substring(fpath.Length + 1)); - lst.Sort(); + { + FileInfo fi = new FileInfo(d); + lst.Add(new VFSItem(this, d.Substring(basecut), false, fi.Length)); + } + lst.Sort((a, b) => a.Name.CompareTo(b.Name)); return lst.ToArray(); } diff --git a/Sunfish/Middleware/VFSItem.cs b/Sunfish/Middleware/VFSItem.cs index 1d1f9eb..e5f8548 100644 --- a/Sunfish/Middleware/VFSItem.cs +++ b/Sunfish/Middleware/VFSItem.cs @@ -14,7 +14,7 @@ Folder = vfolder; Path = path; Directory = isFolder; - Name = System.IO.Path.GetFileName(Path); + Name = System.IO.Path.GetFileName(path); Length = length; } @@ -28,12 +28,12 @@ return Folder.OpenWrite(Path); } - public string[] ListDirectories() + public VFSItem[] ListDirectories() { return Folder.ListDirectories(Path); } - public string[] ListFiles() + public VFSItem[] ListFiles() { return Folder.ListFiles(Path); } diff --git a/Sunfish/Middleware/ZipDownload.cs b/Sunfish/Middleware/ZipDownload.cs new file mode 100644 index 0000000..7090450 --- /dev/null +++ b/Sunfish/Middleware/ZipDownload.cs @@ -0,0 +1,52 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.IO.Compression; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace DolphinWebXplorer2.Middleware +{ + class ZipDownload : IDisposable + { + private HttpCall call; + private ZipArchive z; + private FileStream temp; + private string temppath; + public ZipDownload(HttpCall call) + { + this.call = call; + temp = new FileStream(temppath = Path.GetTempPath() + Guid.NewGuid().ToString() + ".zip", FileMode.Create, FileAccess.ReadWrite); + z = new ZipArchive(temp, ZipArchiveMode.Create, true, Encoding.UTF8); + } + + public void AddFile(VFSItem fil, string name) + { + ZipArchiveEntry zfil = z.CreateEntry(name); + using (Stream zs = zfil.Open()) + using (Stream src = fil.OpenRead()) + { + zs.TransferFrom(src); + } + } + + public void AddDirectory(VFSItem fil, string name) + { + foreach (VFSItem i in fil.ListFiles()) + AddFile(i, name == null ? i.Name : name + '/' + i.Name); + foreach (VFSItem i in fil.ListDirectories()) + AddDirectory(i, name == null ? i.Name : name + '/' + i.Name); + } + + public void Dispose() + { + z.Dispose(); + temp.Position = 0; + call.Response.ContentType = "application/zip"; + call.Response.OutputStream.TransferFrom(temp); + temp.Close(); + File.Delete(temppath); + } + } +} diff --git a/Sunfish/Services/WebService.cs b/Sunfish/Services/WebService.cs index 95059c1..6e2e496 100644 --- a/Sunfish/Services/WebService.cs +++ b/Sunfish/Services/WebService.cs @@ -4,7 +4,6 @@ using System.Drawing; using System.Drawing.Imaging; using System.IO; -using System.Runtime.CompilerServices; namespace DolphinWebXplorer2.Services { @@ -85,6 +84,9 @@ case "upload": ProcessUpload(path, call); break; + case "zip": + ProcessFolderZip(path, call); + break; default: call.HTTPBadRequest(); break; @@ -172,7 +174,7 @@ private void ProcessDelete(string path, HttpCall call) { - VFSItem fil = vfs.GetItem(path); + VFSItem fil = !readOnly && allowDelete ? vfs.GetItem(path) : null; if (fil != null) call.Write(fil.Delete() ? "OK" : "KO"); else @@ -181,6 +183,11 @@ private void ProcessRename(string path, HttpCall call) { + if (readOnly) + { + call.Write("KO"); + return; + } char[] forbiddenTo = { '/', '\\' }; string to; if (!call.Parameters.TryGetValue("to", out to)) @@ -203,6 +210,11 @@ private void ProcessOpen(string path, HttpCall call) { + if (!allowExec) + { + call.Write("KO"); + return; + } VFSItem fil = vfs.GetItem(path); if (fil != null) { @@ -216,6 +228,11 @@ private void ProcessUpload(string path, HttpCall call) { + if (readOnly) + { + call.Write("KO"); + return; + } string soffset = call.Request.Headers["X-Sunfish-Offset"]; string slength = call.Request.Headers["X-Sunfish-Length"]; if (string.IsNullOrEmpty(soffset)) @@ -252,6 +269,23 @@ } } + private void ProcessFolderZip(string path, HttpCall call) + { + VFSItem fil = vfs.GetItem(path); + if (fil != null) + { + using (ZipDownload z = new ZipDownload(call)) + { + if (fil.Directory) + z.AddDirectory(fil, null); + else + z.AddFile(fil, fil.Name); + } + } + else + call.HTTPNotFound(); + } + public void WriteIcon(Icon image, HttpCall call) { @@ -279,19 +313,23 @@ { List items = new List(); List fileList = new List(); - foreach (string d in dir.ListDirectories()) - fileList.Add(d); - fileList.Sort(); - foreach (string d in fileList) + foreach (VFSItem vd in dir.ListDirectories()) { items.Add(new WebUILink() { Icon = "/$sunfish/folder.png", - Name = d, + Name = vd.Name, Description = "Directory", - Link = d + "/", + Link = vd.Name + "/", Style = "directory", Actions = new WebUILink[]{ + new WebUILink() + { + Icon="archive", + Tooltip="Download as zip", + //Click="sunfish.openFile(this)" + Link=vd.Name + "?action=zip" + }, readOnly?null:new WebUILink() { Icon="drive_file_rename_outline", @@ -308,17 +346,14 @@ }); } fileList.Clear(); - foreach (string d in dir.ListFiles()) - fileList.Add(d); - fileList.Sort(); - foreach (string d in fileList) + foreach (VFSItem vf in dir.ListFiles()) { items.Add(new WebUILink() { - Icon = d + "?action=icon", - Name = d, - Description = "File", - Link = d, + Icon = vf.Name + "?action=icon", + Name = vf.Name, + Description = "" + vf.Length.ToSize() + " (" + MimeTypes.GetMimeType(Path.GetExtension(vf.Name)) + ")", + Link = vf.Name, Actions = new WebUILink[]{ allowExec?new WebUILink() { diff --git a/Sunfish/Sunfish.csproj b/Sunfish/Sunfish.csproj index ab44d7d..edc8a0c 100644 --- a/Sunfish/Sunfish.csproj +++ b/Sunfish/Sunfish.csproj @@ -113,6 +113,7 @@ +