Newer
Older
Sunfish / Sunfish / Services / WebService.cs
using Sunfish.Middleware;
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Drawing.Imaging;
using System.IO;

namespace Sunfish.Services
{
    [DefineConfigurator(typeof(WebServiceConfigurator))]
    class WebService : SunfishService
    {
        private VFS vfs = new VFS();
        private string index;
        private bool allowNavigation;
        private bool allowDelete;
        private bool allowExec;
        private bool readOnly;
        public WebService(SunfishServiceConfiguration ssc) : base(ssc)
        {
            vfs.AddVirtualFolder(null, new VFSFolderFileSystem(ssc.GetConf<string>(WebServiceConfigurator.CFG_PATH)));
            index = ssc.GetConf<string>(WebServiceConfigurator.CFG_INDEX);
            if (string.IsNullOrWhiteSpace(index))
                index = null;
            allowNavigation = ssc.GetConf<bool>(WebServiceConfigurator.CFG_SHARE);
            allowDelete = ssc.GetConf<bool>(WebServiceConfigurator.CFG_DELETE);
            allowExec = ssc.GetConf<bool>(WebServiceConfigurator.CFG_EXECUTE);
            readOnly = ssc.GetConf<bool>(WebServiceConfigurator.CFG_RONLY);
        }

        #region WebServer

        private void ErrorPage(int code, HttpCall call, string text)
        {
            call.Response.StatusCode = code;
            call.Write(text);
        }

        private void Error500(HttpCall call, string text)
        {
            ErrorPage(500, call, text);
        }

        private void DownloadAt(VFSItem item, HttpCall call)
        {
            using (Stream s = item.OpenRead())
            {
                if (s == null)
                {
                    Error500(call, "Problem transfering file");
                    return;
                }
                call.Response.ContentType = MimeTypes.GetMimeType(Path.GetExtension(item.Name));
                call.Out.BaseStream.TransferFrom(s);
            }
        }

        public override void Process(string path, HttpCall call)
        {
            string action;
            if (!call.Parameters.TryGetValue("action", out action))
                action = null;
            else if (string.IsNullOrEmpty(action))
                action = null;
            if (call.Request.HttpMethod == "PUT")
                action = "upload";
            switch (action)
            {
                case null:
                    ProcessGET(path, call);
                    break;
                case "icon":
                    ProcessIcon(path, call);
                    break;
                case "delete":
                    ProcessDelete(path, call);
                    break;
                case "rename":
                    ProcessRename(path, call);
                    break;
                case "open":
                    ProcessOpen(path, call);
                    break;
                case "upload":
                    ProcessUpload(path, call);
                    break;
                case "zip":
                    ProcessFolderZip(path, call);
                    break;
                default:
                    call.HTTPBadRequest();
                    break;
            }
        }

        private void ProcessGET(string path, HttpCall call)
        {
            if (path.EndsWith("/"))
            {
                VFSItem idx = vfs.GetItem(path + index);
                //Directory entry, go for index file or navigation
                if (index != null)
                {
                    if (idx != null && !idx.Directory)
                    {
                        DownloadAt(idx, call);
                        return;
                    }
                }
                if (allowNavigation)
                {
                    //TODO: Block subfolder navigation if not allowed checking path
                    idx = vfs.GetItem(path);
                    if (idx != null && idx.Directory)
                        WriteIndex(path, idx, call);
                    else
                        call.HTTPNotFound();
                }
                else
                    call.HTTPForbidden();
            }
            else
            {
                VFSItem idx = vfs.GetItem(path);
                if (idx != null)
                {
                    if (idx.Directory)
                        call.Redirect(call.Request.Url.LocalPath + "/");
                    else
                        DownloadAt(idx, call);
                }
                else
                    call.HTTPNotFound();
            }
        }

        private void ProcessIcon(string path, HttpCall call)
        {
            if (path.EndsWith("/"))
            {
                call.HTTPForbidden();
            }
            else
            {
                VFSItem fil = vfs.GetItem(path);
                VFSFolder fold = fil.Folder;

                try
                {
                    if (fil.Length < 10485760) //10Mb
                        using (Stream fstream = fil.OpenRead())
                        using (Image i = Image.FromStream(fstream))
                        using (Image t = i.GetThumbnailImage(32, 32, null, IntPtr.Zero))
                        {
                            WritePNG((Bitmap)t, call);
                            return;
                        }
                }
                catch { };

                if (fold is VFSFolderFileSystem)
                {
                    string realpath = ((VFSFolderFileSystem)fold).GetFSPath(fil.Path);
                    using (ShellIcon i = new ShellIcon(realpath))
                        WritePNG(i.Image, call);
                }
                else
                {
                    using (ShellIcon i = new ShellIcon(path))
                        WritePNG(i.Image, call);
                }
            }
        }

        private void ProcessDelete(string path, HttpCall call)
        {
            VFSItem fil = !readOnly && allowDelete ? vfs.GetItem(path) : null;
            if (fil != null)
                call.Write(fil.Delete() ? "OK" : "KO");
            else
                call.Write("KO");
        }

        private void ProcessRename(string path, HttpCall call)
        {
            if (readOnly)
            {
                call.Write("KO");
                return;
            }
            char[] forbiddenTo = { '/', '\\' };
            string to;
            if (!call.Parameters.TryGetValue("to", out to))
            {
                call.Write("KO (missing to)");
                return;
            }
            if (to.IndexOfAny(forbiddenTo) >= 0 || to == "." || to == "..")
            {
                call.Write("KO (forbidden)");
                return;
            }

            VFSItem fil = vfs.GetItem(path);
            if (fil != null)
                call.Write(fil.RenameTo(to) ? "OK" : "KO");
            else
                call.Write("KO");
        }

        private void ProcessOpen(string path, HttpCall call)
        {
            if (!allowExec)
            {
                call.Write("KO");
                return;
            }
            VFSItem fil = vfs.GetItem(path);
            if (fil != null)
            {
                string fpath = ((VFSFolderFileSystem)fil.Folder).GetFSPath(fil.Path);
                System.Diagnostics.Process.Start(fpath);
                call.Write("OK");
            }
            else
                call.Write("KO");
        }

        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))
                call.Parameters.TryGetValue("offset", out soffset);
            if (string.IsNullOrEmpty(slength))
                call.Parameters.TryGetValue("length", out slength);
            int pos, len;
            int.TryParse(soffset, out pos);
            int.TryParse(slength, out len);
            try
            {
                VFSItem fil = vfs.GetItem(path);
                if (fil == null)
                    fil = vfs.Create(path);
                if (fil.Directory && len > 0)
                {
                    call.Write("KO: Exists as directory");
                    return;
                }
                if (len > 0)
                    using (Stream s = fil.OpenWrite())
                    {
                        s.Position = pos;
                        using (Stream sin = call.Request.InputStream)
                        {
                            s.TransferFrom(sin, len);
                        }
                    }
                call.Write("OK");
            }
            catch (Exception e)
            {
                call.Write("KO: " + e.GetType().Name + ":" + e.Message);
            }
        }

        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)
        {
            call.Response.ContentType = "image/vnd.microsoft.icon";
            using (MemoryStream ms = new MemoryStream())
            {
                image.Save(ms);
                ms.Position = 0;
                call.Write(ms);
            }
        }

        public void WritePNG(Image image, HttpCall call)
        {
            call.Response.ContentType = "image/png";
            using (MemoryStream ms = new MemoryStream())
            {
                image.Save(ms, ImageFormat.Png);
                ms.Position = 0;
                call.Write(ms);
            }
        }

        private void WriteIndex(string path, VFSItem dir, HttpCall call)
        {
            List<WebUILink> items = new List<WebUILink>();
            List<string> fileList = new List<string>();
            foreach (VFSItem vd in dir.ListDirectories())
            {
                items.Add(new WebUILink()
                {
                    Icon = "/$sunfish/folder.png",
                    Name = vd.Name,
                    Description = "Directory",
                    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",
                            Tooltip="Rename",
                            Click="sunfish.renameFile(this)"
                        },
                        allowDelete?new WebUILink()
                        {
                            Icon="delete",
                            Tooltip="Delete folder",
                            Click="sunfish.deleteFile(this);"
                        }:null
                    }
                });
            }
            fileList.Clear();
            foreach (VFSItem vf in dir.ListFiles())
            {
                items.Add(new WebUILink()
                {
                    Icon = vf.Name + "?action=icon",
                    Name = vf.Name,
                    Description = "<span class='size'>" + vf.Length.ToSize() + "</span> <span class='info'>(" + MimeTypes.GetMimeType(Path.GetExtension(vf.Name)) + ")</span>",
                    Link = vf.Name,
                    Actions = new WebUILink[]{
                        allowExec?new WebUILink()
                        {
                            Icon="api",
                            Tooltip="Open in server",
                            Click="sunfish.openFile(this)"
                        }:null,
                        readOnly?null:new WebUILink()
                        {
                            Icon="drive_file_rename_outline",
                            Tooltip="Rename",
                            Click="sunfish.renameFile(this)"
                        },
                        allowDelete?new WebUILink()
                        {
                            Icon="delete",
                            Tooltip="Delete file",
                            Click="sunfish.deleteFile(this)"
                        }:null
                    }
                }); ;
            }
            Dictionary<string, object> data = new Dictionary<string, object>();
            data["Breadcrumb"] = GetBreadcrumb(path);
            data["Actions"] = new WebUILink[] {
                readOnly?null:new WebUILink()
                {
                    Icon="create_new_folder",
                    Tooltip="New folder",
                    Click="sunfish.newFolder(this)",
                },
                readOnly?null:new WebUILink()
                {
                    Icon="note_add",
                    Tooltip="New file",
                    Click="sunfish.newFile(this)",
                },
                readOnly?null:new WebUILink()
                {
                    Icon="upload",
                    Tooltip="Upload. Drop files or fonders here",
                    Click="sunfish.uploadFile(this)",
                    //Style="upload-drop",
                }
            };
            data["Items"] = items;
            data["Include"] = "<script src=\"/$sunfish/sunfish-directory.js\"></script>";
            WebUI.WriteTemplate("directory-index", call, data);
        }

        private WebUILink[] GetBreadcrumb(string path)
        {
            string link = "";
            path = Configuration.Location + path;
            List<WebUILink> lst = new List<WebUILink>();
            lst.Add(RootService.LinkHome);
            foreach (string p in path.Split('/'))
            {
                if (p.Length == 0)
                    continue;
                link += "/" + p;
                lst.Add(new WebUILink()
                {
                    Name = p,
                    Link = link,
                });
            }
            return lst.ToArray();
        }

        #endregion

        protected override void Start()
        {
        }

        protected override void Stop()
        {
        }

        public override string Description => "For Webpages or file sharing";
    }
}