using System; using System.IO; using System.Threading.Tasks; using Newtonsoft.Json; using Tesses.WebServer; using Scriban; using System.Collections.Generic; using Tesses.WebServer.Swagme; using System.Net; using System.Web; using System.Text; using System.Xml; using Microsoft.SyndicationFeed.Rss; using Microsoft.SyndicationFeed.Atom; using Microsoft.SyndicationFeed; using PlaylistsNET.Models; using PlaylistsNET.Content; using System.Security.Cryptography; using System.Collections.Concurrent; using System.Diagnostics; using System.Linq; namespace Tesses.CMS { public class CMSServer { public Dictionary Sessions {get;set;}=new Dictionary(); Template pageShell; Template pageIndex; Template pageDevcenter; Template pageMovie; Template pageMovies; Template pageWatchMovie; Template pageUpload; Template pageUsers; Template pageEditMovieDetails; RouteServer routeServer; PathValueServer usersPathValueServer; PathValueServer moviePathValueServer; MountableServer usersMountableServer; RouteServer movieRouteServer; IContentProvider provider; string path; public CMSServer(string configpath,IContentProvider provider) { this.provider=provider; usersMountableServer=new MountableServer(); usersPathValueServer = new PathValueServer(usersMountableServer); movieRouteServer=new RouteServer(); moviePathValueServer = new PathValueServer(movieRouteServer); string configJson = Path.Combine(configpath,"config.json"); if(File.Exists(configJson)) { Configuration = JsonConvert.DeserializeObject(File.ReadAllText(configJson)); } if(Configuration.BittorrentTrackers.Count == 0) { Configuration.BittorrentTrackers.AddRange( new string[]{ "https://t1.hloli.org:443/announce", "http://1337.abcvg.info:80/announce", "http://tracker.renfei.net:8080/announce", "http://tracker.gbitt.info:80/announce", "http://p2p.0g.cx:6969/announce", "https://tracker.foreverpirates.co:443/announce" }); } pageShell=Template.Parse(AssetProvider.ReadAllText("/PageShell.html")); pageIndex = Template.Parse(AssetProvider.ReadAllText("/Index.html")); pageDevcenter = Template.Parse(AssetProvider.ReadAllText("/Devcenter.html")); pageMovie = Template.Parse(AssetProvider.ReadAllText("/MoviePage.html")); pageMovies = Template.Parse(AssetProvider.ReadAllText("/MoviesPage.html")); pageUsers = Template.Parse(AssetProvider.ReadAllText("/UsersPage.html")); pageWatchMovie = Template.Parse(AssetProvider.ReadAllText("/WatchMovie.html")); pageUpload = Template.Parse(AssetProvider.ReadAllText("/Upload.html")); pageEditMovieDetails = Template.Parse(AssetProvider.ReadAllText("/EditMovieDetails.html")); MountableServer mountableServer = new MountableServer(new AssetProvider()); path=Path.Combine(configpath,"content"); mountableServer.Mount("/content/",new StaticServer(path){AllowListingDirectories=true}); mountableServer.Mount("/api/v1/",CreateSwagme()); mountableServer.Mount("/user/",usersPathValueServer); routeServer = new RouteServer(mountableServer); routeServer.Add("/",Index,"GET"); routeServer.Add("/devcenter",Devcenter,"GET"); routeServer.Add("/upload",UploadPage1,"GET"); routeServer.Add("/upload",Upload,"POST"); routeServer.Add("/users",UsersAsync,"GET"); routeServer.Add("/login",LoginAsync); routeServer.Add("/login",LoginPostAsync,"POST"); routeServer.Add("/signup",SignupAsync); routeServer.Add("/signup",SignupPostAsync,"POST"); RegisterUsersPath(); Task.Factory.StartNew(async()=>{ while(Running) { if(tasks.TryDequeue(out var item)) { await item(); } await Task.Delay(TimeSpan.FromSeconds(0.125)); } }).Wait(0); } public bool Running =true; public async Task LoginAsync(ServerContext ctx) { await ctx.SendTextAsync(await RenderHtmlAsync(false,await AssetProvider.ReadAllTextAsync("/LoginPage.html"))); } public async Task SignupAsync(ServerContext ctx) { await ctx.SendTextAsync(await RenderHtmlAsync(false,await AssetProvider.ReadAllTextAsync("/SignupPage.html"))); } public async Task SignupPostAsync(ServerContext ctx) { ctx.ParseBody(); if(ctx.QueryParams.TryGetFirst("email", out var email) && ctx.QueryParams.TryGetFirst("name", out var name) && ctx.QueryParams.TryGetFirst("proper_name",out var proper_name) && ctx.QueryParams.TryGetFirst("password",out var password) && ctx.QueryParams.TryGetFirst("confirm_password",out var confirm_password)) { bool error = false,emailTaken=false, nameTaken=false, properNameTaken=false, passwordDontMatch=false, passwordNotGoodEnough=false; foreach(var user in provider.GetUsers()) { if(user.Username == name) { nameTaken=true; error=true; } if(user.Email == email) { emailTaken=true; error=true; } if(user.ProperName == proper_name) { properNameTaken=true; error=true; } } if(password != confirm_password) { error=true; passwordDontMatch=true; } if(password.Length < 10) { error=true; passwordNotGoodEnough=true; } if(error) { StringBuilder b=new StringBuilder(); if(emailTaken) { b.AppendLine("

Email is taken

"); } if(nameTaken) { b.AppendLine("

Name is taken

"); } if(properNameTaken) { b.AppendLine("

Proper Name is taken"); } if(passwordNotGoodEnough) { b.AppendLine("

Password not good enough

"); } if(passwordDontMatch) { b.AppendLine("

Passwords don't match

"); } await ctx.SendTextAsync(b.ToString()); } else { provider.CreateUser(Configuration,name,proper_name,email,password); await ctx.SendRedirectAsync($"{Configuration.Root.TrimEnd('/')}/"); } } } public async Task LoginPostAsync(ServerContext ctx) { ctx.ParseBody(); if(ctx.QueryParams.TryGetFirst("email",out var email) && ctx.QueryParams.TryGetFirst("password",out var password)) { foreach(var a in provider.GetUsers()) { if(a.Email != email) continue; if(a.Email == email && a.PasswordCorrect(password)) { //we got it byte[] bytes=new byte[32]; string cookie; using(var rng = RandomNumberGenerator.Create()) do { rng.GetBytes(bytes); cookie=Convert.ToBase64String(bytes); } while(Sessions.ContainsKey(cookie)); Sessions.Add(cookie,a); ctx.ResponseHeaders.Add("Set-Cookie",$"Session={cookie}; Path=/"); await ctx.SendRedirectAsync($"{Configuration.Root.TrimEnd('/')}/"); return; } } ctx.StatusCode=401; await ctx.SendTextAsync("

Incorrect

Home | Login"); } } private UserAccount GetAccount(ServerContext ctx) { if(ctx.RequestHeaders.TryGetValue("Cookie",out var cookies)) { foreach(var c in cookies) { var co = c.Split(new char[]{'='},2); if(co.Length == 2 && co[0] == "Session") { if(Sessions.TryGetValue(co[1],out var account)) return account; } } } return null; } private async Task UsersAsync(ServerContext ctx) { List users=new List(); foreach(var user in provider.GetUsers()) { users.Add(user.Scriban()); } await ctx.SendTextAsync(await RenderHtmlAsync(false,await pageUsers.RenderAsync(new { Users=users, rooturl=$"{Configuration.Root.TrimEnd('/')}/" }))); } const string badC="/\\\"&,?|:;*@!# "; private string FixString(string str) { StringBuilder b=new StringBuilder(); foreach(var item in str) { if(char.IsControl(item)) { continue; } if(item >= 127) continue; if(badC.Contains(item.ToString())) continue; b.Append(item.ToString()); } return b.ToString(); } private async Task Upload(ServerContext ctx) { ctx.ParseBody(); if(ctx.QueryParams.TryGetFirst("name",out var name) && ctx.QueryParams.TryGetFirst("proper_name",out var proper_name) && ctx.QueryParams.TryGetFirst("type", out var type) && ctx.QueryParams.TryGetFirst("description",out var description)) { var account=GetAccount(ctx); if(account != null) { if(!account.IsAdmin && Configuration.Publish == CMSPublish.Admin) { await ctx.SendTextAsync(await RenderHtmlAsync(false,"

You can't upload content

")); return; } if(!(account.IsAdmin || account.IsInvited) && Configuration.Publish == CMSPublish.RequireInvite) { await ctx.SendTextAsync(await RenderHtmlAsync(false,"

You can't upload content

")); return; } if(!(account.IsAdmin || account.IsInvited || account.IsVerified)) { await ctx.SendTextAsync(await RenderHtmlAsync(false,"

You can't upload content

")); return; } name = FixString(name); switch(type) { case "movie": provider.CreateMovie(account.Username,name,proper_name,description); await ctx.SendRedirectAsync($"{Configuration.Root.TrimEnd('/')}/user/{account.Username}/movie/{name}/edit"); break; } } else { await ctx.SendRedirectAsync($"{Configuration.Root.TrimEnd('/')}/login"); } } else { await ctx.SendRedirectAsync($"{Configuration.Root.TrimEnd('/')}/"); } } public async Task UploadPage1(ServerContext ctx) { await ctx.SendTextAsync(await RenderHtmlAsync(false,await RenderUpload1Async(),Configuration.RelativeNavUrl("Devcenter","devcenter"))); } public IServer Movies() { RouteServer routeServer=new RouteServer(); routeServer.Add("/",MoviesAsync); return routeServer; } public async Task MoviesAsync(ServerContext ctx) { string user=usersPathValueServer.GetValue(ctx); List movies=new List(); foreach(var item in provider.GetMovies(user)) { var data = provider.GetMovieContentMetaData(Configuration,user,item.Name); movies.Add(item.Scriban(data.ThumbnailUrl)); } await ctx.SendTextAsync(await RenderHtmlAsync(false,await pageMovies.RenderAsync(new{Movies=movies}))); } private void RegisterUsersPath() { RouteServer routeServer=new RouteServer(); routeServer.Add("/",UserPageAsync); usersMountableServer.Mount("/",routeServer); usersMountableServer.Mount("/movies/",Movies()); usersMountableServer.Mount("/movie/",moviePathValueServer); RegisterMoviePath(); } private async Task UserPageAsync(ServerContext ctx) { await ctx.SendTextAsync(await RenderHtmlAsync(false,await AssetProvider.ReadAllTextAsync("/UserPage.html"))); } ConcurrentQueue> tasks=new ConcurrentQueue>(); private void RegisterMoviePath() { movieRouteServer.Add("/",MoviePageAsync); movieRouteServer.Add("/play",PlayMoviePageAsync); movieRouteServer.Add("/edit",EditPageAsync); movieRouteServer.Add("/edit",EditPagePostAsync,"POST"); movieRouteServer.Add("/upload",UploadStreamAsync,"POST"); } private class PieceStream : Stream { public const int PieceLength = 16*1024; public int CurpieceOffset {get;private set;} =0; public List Pieces {get;private set;}=new List(); public byte[] CalculateCurPiece() { using(var sha1=SHA1.Create()) { return sha1.ComputeHash(Curpiece,0,CurpieceOffset); } } public byte[] Curpiece = new byte[PieceLength]; public override bool CanRead => false; public override bool CanSeek => false; public override bool CanWrite => true; long len; public override long Length => len; public bool HasPiece=>CurpieceOffset>0; public override long Position { get => throw new NotImplementedException(); set => throw new NotImplementedException(); } public override void Flush() { } public override int Read(byte[] buffer, int offset, int count) { throw new NotImplementedException(); } public override long Seek(long offset, SeekOrigin origin) { throw new NotImplementedException(); } public override void SetLength(long value) { } public override void Write(byte[] buffer, int offset, int count) { for(int i = 0;i paths = new List(); paths.Add(new string[]{$"{movie}.mp4"}); paths.Add(new string[]{"thumbnail.jpg"}); paths.Add(new string[]{"poster.jpg"}); if(Directory.Exists(subtitles)) GetExtras(paths,new string[]{"subtitles"},subtitles); List lengths = new List(); foreach(var _path in paths.Select(e=>Path.Combine(e))) using(var movieStrm = File.OpenRead(Path.Combine(movieDir,_path))) { lengths.Add(movieStrm.Length); await movieStrm.CopyToAsync(strm); } Torrent torrent =new Torrent(); torrent.AnnounceList.AddRange(Configuration.BittorrentTrackers); torrent.CreationDate=DateTime.Now; torrent.UrlList = $"{Configuration.Root.TrimEnd('/')}/content/{user}/movie/"; torrent.Info.PieceLength = PieceStream.PieceLength; torrent.Info.Name = movie; foreach(var piece in strm.Pieces) { torrent.Info.Pieces.Add(piece); } if(strm.HasPiece) torrent.Info.Pieces.Add(strm.CalculateCurPiece()); for(int i = 0;i paths2 = new List(); GetExtras(paths2,new string[]{"extras"},extrasDir); foreach(var _path in paths2.Select(e=>Path.Combine(e))) using(var movieStrm = File.OpenRead(Path.Combine(movieDir,_path))) { lengths.Add(movieStrm.Length); await movieStrm.CopyToAsync(strm); } paths.AddRange(paths2); torrent=new Torrent(); torrent.AnnounceList.AddRange(Configuration.BittorrentTrackers); torrent.CreationDate=DateTime.Now; torrent.UrlList = $"{Configuration.Root.TrimEnd('/')}/content/{user}/movie/"; torrent.Info.PieceLength = PieceStream.PieceLength; torrent.Info.Name = movie; foreach(var piece in strm.Pieces) { torrent.Info.Pieces.Add(piece); } if(strm.HasPiece) torrent.Info.Pieces.Add(strm.CalculateCurPiece()); for(int i = 0;i paths2, string[] torrentPath, string extrasDir) { foreach(var dir in Directory.GetDirectories(extrasDir)) { string dirname = Path.GetFileName(dir); string[] path = new string[torrentPath.Length+1]; Array.Copy(torrentPath,path,torrentPath.Length); path[path.Length-1] = dirname; GetExtras(paths2,path,dir); } foreach(var file in Directory.GetFiles(extrasDir)) { string filename = Path.GetFileName(file); string[] path = new string[torrentPath.Length+1]; Array.Copy(torrentPath,path,torrentPath.Length); path[path.Length-1] = filename; paths2.Add(path); } } private async Task UploadStreamAsync(ServerContext ctx) { string user=usersPathValueServer.GetValue(ctx); string movie=moviePathValueServer.GetValue(ctx); var me = GetAccount(ctx); var _movie = provider.GetMovie(user,movie); if(me != null && me.Username != user && !me.IsAdmin) { me=null; } if(me != null) { Directory.CreateDirectory(Path.Combine(path,user,"movie",movie)); var tmpFile = Path.Combine(path,user,"movie",movie,$"tmp{DateTime.Now.ToFileTime().ToString()}.bin"); foreach(var item in ctx.ParseBody((n,fn,ct)=>File.Create(tmpFile))) item.Value.Dispose(); if(_movie != null) { if(ctx.QueryParams.TryGetFirst("type",out var type)) { switch(type) { case "thumbnail": File.Move(tmpFile,Path.Combine(path,user,"movie",movie,"thumbnail.jpg")); break; case "poster": File.Move(tmpFile,Path.Combine(path,user,"movie",movie,"poster.jpg")); break; case "movie": File.Move(tmpFile,Path.Combine(path,user,"movie",movie,$"{movie}.mp4")); ScheduleFFmpeg($"-i \"{Path.Combine(path,user,"movie",movie,$"{movie}.mp4")}\" {Configuration.BrowserTranscode} \"{Path.Combine(path,user,"movie",movie,"browser.mp4")}\""); break; } ScheduleTask(async()=>{ await GenerateBittorentFileMovieAsync(user,movie); }); await ctx.SendTextAsync(await RenderHtmlAsync(false,"

Success

<- Back")); return; } } } await ctx.SendTextAsync(await RenderHtmlAsync(false,"

Failed

<- Back")); } private void ScheduleFFmpeg(string command) { ScheduleTask(async()=>{ using(Process process=new Process()) { process.StartInfo.Arguments = command; process.StartInfo.FileName = "ffmpeg"; if(process.Start()) { await Task.Run(process.WaitForExit); } } }); } public void ScheduleTask(Func task) { tasks.Enqueue(task); } public async Task EditPagePostAsync(ServerContext ctx) { ctx.ParseBody(); string user=usersPathValueServer.GetValue(ctx); string movie=moviePathValueServer.GetValue(ctx); var me = GetAccount(ctx); var _movie = provider.GetMovie(user,movie); if(me != null && me.Username != user && !me.IsAdmin) { me=null; } if(me != null) { if(_movie != null) { if(ctx.QueryParams.TryGetFirst("proper_name",out var proper_name) && ctx.QueryParams.TryGetFirst("description",out var description)) { _movie.ProperName = proper_name; _movie.Description = description; provider.UpdateMovie(_movie); await ctx.SendTextAsync(await RenderHtmlAsync(false,"

Success

<- Back")); return; } } } await ctx.SendTextAsync(await RenderHtmlAsync(false,"

Failed

<- Back")); } public async Task EditPageAsync(ServerContext ctx) { string user=usersPathValueServer.GetValue(ctx); string movie=moviePathValueServer.GetValue(ctx); var me = GetAccount(ctx); var _movie = provider.GetMovie(user,movie); if(me != null && me.Username != user && !me.IsAdmin) { me=null; } if(me != null) { if(_movie != null) await ctx.SendTextAsync(await RenderHtmlAsync(false,await pageEditMovieDetails.RenderAsync(new{Propername=System.Web.HttpUtility.HtmlAttributeEncode( _movie.ProperName),Description=System.Web.HttpUtility.HtmlEncode(_movie.Description)}))); } else { await ctx.SendTextAsync(await RenderHtmlAsync(false,"

You are unauthorized to edit this

")); } } private async Task PlayMoviePageAsync(ServerContext ctx) { string user=usersPathValueServer.GetValue(ctx); string movie=moviePathValueServer.GetValue(ctx); var _movie= provider.GetMovie(user,movie); object value; if(_movie != null) { var data=provider.GetMovieContentMetaData(Configuration,user,movie); string thumb = data.ThumbnailUrl; value = new{ username=user, rooturl=$"{Configuration.Root.TrimEnd('/')}/", title=Configuration.Title,hasmovie=true,moviethumbnail=thumb,movieurl=movie,moviename=_movie.Name,moviedescription=_movie.Description,movieposter = data.PosterUrl, moviebrowserurl=data.BrowserStream}; } else { value = new{ username=user, rooturl=$"{Configuration.Root.TrimEnd('/')}/", title=Configuration.Title,hasmovie=false}; } await ctx.SendTextAsync(await pageWatchMovie.RenderAsync(value)); } private async Task MoviePageAsync(ServerContext ctx) { string user=usersPathValueServer.GetValue(ctx); string movie=moviePathValueServer.GetValue(ctx); var _movie= provider.GetMovie(user,movie); var _user = provider.GetUserAccount(user); var me = GetAccount(ctx); if(me != null && me.Username != user && !me.IsAdmin) { me=null; } object value; if(_movie != null && _user != null) { var data=provider.GetMovieContentMetaData(Configuration,user,movie); string movieDir = Path.Combine(this.path,user,"movie",movie); bool torrent= File.Exists(Path.Combine(movieDir,$"{movie}.torrent")); bool torrent_wextra= File.Exists(Path.Combine(movieDir,$"{movie}_withextras.torrent")); string thumb = data.ThumbnailUrl; value = new{ downloadurl=data.DownloadStream,torrentexists=torrent, torrentwextraexists=torrent_wextra, torrent=data.MovieTorrentUrl,torrentwextra=data.MovieWithExtrasTorrentUrl , editable=me!=null, userproper=HttpUtility.HtmlEncode(_user.ProperName), username=HttpUtility.HtmlEncode(user), rooturl=$"{Configuration.Root.TrimEnd('/')}/", title=Configuration.Title,hasmovie=true,moviethumbnail=thumb,movieurl=movie,movieproperattr=HttpUtility.HtmlAttributeEncode(_movie.ProperName),movieproper=HttpUtility.HtmlEncode(_movie.ProperName),moviename=HttpUtility.HtmlEncode(_movie.Name),moviedescription=HttpUtility.HtmlEncode(_movie.Description)}; } else { value = new{ username=user, rooturl=$"{Configuration.Root.TrimEnd('/')}/", title=Configuration.Title,hasmovie=false}; } await ctx.SendTextAsync(await RenderHtmlAsync(false,await pageMovie.RenderAsync(value))); } private IServer CreateSwagme() { SwagmeServer swagmeServer=new SwagmeServer(); swagmeServer.AbsoluteUrl=true; swagmeServer.Add("/MovieFile",ApiMovieFileAsync,new SwagmeDocumentation("Get the movie resource"),"GET","Movies"); swagmeServer.Add("/GetMovies",ApiGetMoviesAsync,new SwagmeDocumentation("Get a list of movies","user: the user of the movie
type: format of list (defaults to json): pls, json, m3u8 or rss"),"GET","Movies"); return swagmeServer; } private async Task ApiMovieFileAsync(ServerContext ctx) { if(ctx.QueryParams.TryGetFirst("user",out var user) && ctx.QueryParams.TryGetFirst("movie", out var movie)) { if(!ctx.QueryParams.TryGetFirst("type", out var type)) type="download"; var info = provider.GetMovieContentMetaData(Configuration,user,movie); switch(type) { case "download": await ctx.SendRedirectAsync( info.DownloadStream); break; case "browser": await ctx.SendRedirectAsync(info.BrowserStream); break; case "thumbnail": await ctx.SendRedirectAsync(info.ThumbnailUrl); break; case "poster": await ctx.SendRedirectAsync(info.PosterUrl); break; case "torrent": await ctx.SendRedirectAsync(info.MovieTorrentUrl); break; case "torrent_extra": await ctx.SendRedirectAsync(info.MovieWithExtrasTorrentUrl); break; case "json": await ctx.SendJsonAsync(info); break; } } } private async Task ApiGetMoviesAsync(ServerContext ctx) { UserAccount a; if(!ctx.QueryParams.TryGetFirst("type",out var type)) type="json"; if(ctx.QueryParams.TryGetFirst("user",out var user)) { a = provider.GetUserAccount(user); } else { a = provider.GetFirstUser(); } List movies=new List(); if(a != null) { movies.AddRange(provider.GetMovies(a.Id)); } if(type=="json") { await ctx.SendJsonAsync(movies); } if(type == "pls") { StringBuilder b=new StringBuilder(); b.AppendLine("[Playlist]"); int i = 1; foreach(var item in movies) { b.AppendLine($"File{i}={Configuration.Root.TrimEnd('/')}/api/v1/MovieFile?user={HttpUtility.UrlEncode(user)}&movie={HttpUtility.UrlEncode(item.Name)}&type=download"); b.AppendLine($"Length{i}=0"); b.AppendLine($"Title{i}={IniEscape(item.ProperName)}"); i++; } await ctx.SendTextAsync(b.ToString(),"audio/x-scpls"); } if(type == "m3u8") { M3uPlaylist playlist=new M3uPlaylist(); foreach(var item in movies) { M3uPlaylistEntry entry=new M3uPlaylistEntry(); entry.Path = $"{Configuration.Root.TrimEnd('/')}/api/v1/MovieFile?user={HttpUtility.UrlEncode(user)}&movie={HttpUtility.UrlEncode(item.Name)}&type=download"; entry.Title = item.ProperName; playlist.PlaylistEntries.Add(entry); } M3uContent content = new M3uContent(); await ctx.SendTextAsync(content.ToText(playlist),"application/x-mpegurl"); } if(type == "rss") { StringWriter sw = new StringWriter(); using (XmlWriter xmlWriter = XmlWriter.Create(sw, new XmlWriterSettings() { Async = true, Indent = false,OmitXmlDeclaration=true, Encoding= Encoding.UTF8 })) { var rss = new RssFeedWriter(xmlWriter); await rss.WriteTitle($"{a.ProperName}'s Movies"); await rss.WriteGenerator("TessesCMS"); await rss.WriteValue("link",$"{Configuration.Root.TrimEnd('/')}/"); foreach(var item in movies) { AtomEntry entry=new AtomEntry(); entry.Title = item.ProperName; entry.Description = $"View here
{item.Description}"; entry.LastUpdated = item.LastUpdated; entry.Published = item.CreationTime; await rss.Write(entry); } } await ctx.SendTextAsync(sw.GetStringBuilder().ToString(),"application/rss+xml"); } } private string IniEscape(string properName) { StringBuilder b=new StringBuilder(); foreach(var c in properName) { if(c == '\\' || c ==';' || c == '\"' || c == '\'' || c == '#' || c == '=' || c == ':') { b.Append($"\\{c}"); } else if(c == '\0') { b.Append("\\0"); } else if(c == '\b') { b.Append("\\b"); } else if(c == '\a') { b.Append("\\a"); } else if(c == '\t') { b.Append("\\t"); } else if(c == '\r') { b.Append("\\r"); }else if(c == '\n') { b.Append("\\n"); } else if(c > sbyte.MaxValue) { b.Append($"\\x{((int)c).ToString("X4")}"); } else { b.Append(c); } } return b.ToString(); } private async Task RenderHtmlAsync(bool isHome,string body,params CMSNavUrl[] urls) { List _urls = new List(); foreach(var item in urls) { _urls.Add(new{text=item.Text, url=item.Url,active=item.Active}); } foreach(var item in Configuration.Urls) { _urls.Add(new{text=item.Text,url=item.Url,active=item.Active}); } return await pageShell.RenderAsync(new{ ishome = isHome, body = body, urls=_urls, rooturl=$"{Configuration.Root.TrimEnd('/')}/", title=Configuration.Title }); } private async Task Index(ServerContext ctx) { var account=GetAccount(ctx); CMSNavUrl accountLink=account != null ? Configuration.RelativeNavUrl(account.ProperName,"account") : Configuration.RelativeNavUrl("Login","login"); await ctx.SendTextAsync(await RenderHtmlAsync(true,await RenderIndexAsync(),Configuration.RelativeNavUrl("Devcenter","devcenter"), accountLink)); } private async Task Devcenter(ServerContext ctx) { var link = Configuration.RelativeNavUrl("Devcenter","devcenter"); link.Active=true; await ctx.SendTextAsync(await RenderHtmlAsync(false,await RenderDevcenterAsync(),link)); } private async Task RenderDevcenterAsync() { return await pageDevcenter.RenderAsync(new{title=Configuration.Title,rooturl=$"{Configuration.Root.TrimEnd('/')}/"}); } private async Task RenderIndexAsync() { return await pageIndex.RenderAsync(new{title=Configuration.Title,rooturl=$"{Configuration.Root.TrimEnd('/')}/"}); } public async Task RenderUpload1Async() { return await pageUpload.RenderAsync(new{title=Configuration.Title,rooturl=$"{Configuration.Root.TrimEnd('/')}/"}); } public CMSConfiguration Configuration {get;set;}=new CMSConfiguration(); public IServer Server => routeServer; } }