├── LunarServer ├── Properties │ └── PublishProfiles │ │ ├── FolderProfile.pubxml.user │ │ └── FolderProfile.pubxml ├── Templates │ ├── Engine │ │ ├── SelfKey.cs │ │ ├── LiteralKey.cs │ │ ├── Enumerables.cs │ │ ├── GlobalKey.cs │ │ ├── NegationKey.cs │ │ ├── RenderingContext.cs │ │ ├── FormatNodes.cs │ │ ├── CaseNodes.cs │ │ ├── Document.cs │ │ ├── DateNodes.cs │ │ └── RenderingKey.cs │ ├── AssetNodes.cs │ └── StoreNodes.cs ├── Minifiers │ └── CSS.cs ├── Utils │ ├── RequestExtensions.cs │ ├── MD5.cs │ ├── Country.cs │ ├── Decimals.cs │ ├── Password.cs │ ├── HTTP.cs │ ├── ArrayPointer.cs │ └── DetectionUtils.cs ├── LunarServer.csproj ├── Core │ ├── Logger.cs │ ├── Date.cs │ ├── Mime.cs │ ├── Plugin.cs │ ├── Settings.cs │ ├── AssetCache.cs │ ├── MultipartParser.cs │ ├── Session.cs │ └── Router.cs ├── HTTP │ ├── RequestCache.cs │ ├── HTTPResponse.cs │ └── HTTPRequest.cs ├── Plugins │ ├── Oauth │ │ ├── OauthConnection.cs │ │ ├── Profile.cs │ │ ├── OauthPlugin.cs │ │ ├── LinkedIn.cs │ │ └── Facebook.cs │ └── RPC.cs ├── Websockets │ ├── WebSocketFrame.cs │ ├── WebSocketFrameWriter.cs │ ├── Exceptions.cs │ ├── BinaryReaderWriter.cs │ └── WebSocketFrameReader.cs ├── Entity │ ├── Connector.cs │ ├── Entity.cs │ ├── API.cs │ └── Store.cs └── Analytics │ └── AnalyticsCollection.cs ├── .gitignore ├── SynkMVC ├── Modules │ ├── Enums.cs │ ├── Users.cs │ ├── Search.cs │ ├── Files.cs │ ├── Auth.cs │ └── API.cs ├── SynkMVC.csproj ├── Model │ ├── Enum.cs │ ├── File.cs │ ├── User.cs │ └── Config.cs ├── Core │ ├── Menus.cs │ ├── Condition.cs │ └── Module.cs └── Utils │ ├── MailSender.cs │ ├── ImageUtils.cs │ └── ArrayPointer.cs ├── ServerTests ├── ServerTests.csproj └── StorageTests.cs ├── LICENSE ├── LunarServer.sln └── README.md /LunarServer/Properties/PublishProfiles/FolderProfile.pubxml.user: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .vs/ 2 | packages/ 3 | LunarServer/bin/ 4 | LunarServer/obj/ 5 | ServerTests/bin/ 6 | ServerTests/obj/ 7 | SynkMVC/bin/ 8 | SynkMVC/obj/ 9 | 10 | LunarServer/LunarServer.csproj.user 11 | -------------------------------------------------------------------------------- /SynkMVC/Modules/Enums.cs: -------------------------------------------------------------------------------- 1 | using LunarLabs.WebMVC.Model; 2 | using LunarLabs.WebMVC.Utils; 3 | using System.Text; 4 | 5 | namespace LunarLabs.WebMVC.Modules 6 | { 7 | public class Enums : CRUDModule 8 | { 9 | 10 | public Enums() 11 | { 12 | this.RegisterClass(); 13 | } 14 | } 15 | } -------------------------------------------------------------------------------- /SynkMVC/Modules/Users.cs: -------------------------------------------------------------------------------- 1 | using LunarLabs.WebMVC; 2 | using LunarLabs.WebMVC.Model; 3 | using System; 4 | using System.Collections.Generic; 5 | 6 | namespace LunarLabs.WebMVC.Modules 7 | { 8 | public class Users : CRUDModule 9 | { 10 | public Users() 11 | { 12 | this.RegisterClass(); 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /LunarServer/Properties/PublishProfiles/FolderProfile.pubxml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | Release 8 | Any CPU 9 | publish\ 10 | FileSystem 11 | <_TargetId>Folder 12 | 13 | -------------------------------------------------------------------------------- /SynkMVC/SynkMVC.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netstandard2.0 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /LunarServer/Templates/Engine/SelfKey.cs: -------------------------------------------------------------------------------- 1 | namespace LunarLabs.Templates 2 | { 3 | public class SelfKey : RenderingKey 4 | { 5 | public override RenderingType RenderingType => RenderingType.Any; 6 | 7 | public override object Evaluate(RenderingContext context) 8 | { 9 | return context.DataStack[context.DataStack.Count - 1]; 10 | } 11 | 12 | public override string ToString() 13 | { 14 | return "this"; 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /SynkMVC/Model/Enum.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | 5 | namespace LunarLabs.WebMVC.Model 6 | { 7 | public class Enum : Entity 8 | { 9 | public override void InitFields() 10 | { 11 | this.RegisterField("name").asString(60).showInGrid(); 12 | this.RegisterField("values").asText(); 13 | } 14 | 15 | public override string ToString() 16 | { 17 | return GetFieldValue("name"); 18 | } 19 | 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /ServerTests/ServerTests.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netcoreapp2.1 5 | 6 | false 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /LunarServer/Templates/Engine/LiteralKey.cs: -------------------------------------------------------------------------------- 1 | namespace LunarLabs.Templates 2 | { 3 | public class LiteralKey : RenderingKey 4 | { 5 | private object _value; 6 | 7 | private RenderingType _type; 8 | public override RenderingType RenderingType => _type; 9 | 10 | public LiteralKey(object value, RenderingType type) 11 | { 12 | this._value = value; 13 | this._type = type; 14 | } 15 | 16 | public override object Evaluate(RenderingContext context) 17 | { 18 | return _value; 19 | } 20 | 21 | public override string ToString() 22 | { 23 | return _value.ToString(); 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /LunarServer/Templates/Engine/Enumerables.cs: -------------------------------------------------------------------------------- 1 | using System.Collections; 2 | 3 | namespace LunarLabs.Templates 4 | { 5 | public static class EnumerableExtensions 6 | { 7 | public static int Count(this IEnumerable source) 8 | { 9 | int res = 0; 10 | 11 | foreach (var item in source) 12 | res++; 13 | 14 | return res; 15 | } 16 | 17 | public static bool Any(this IEnumerable source) 18 | { 19 | bool res = false; 20 | 21 | foreach (var item in source) 22 | { 23 | res = true; 24 | break; 25 | } 26 | 27 | return res; 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /LunarServer/Minifiers/CSS.cs: -------------------------------------------------------------------------------- 1 | using System.Text.RegularExpressions; 2 | 3 | namespace LunarLabs.WebServer.Minifiers 4 | { 5 | public static class CSSMinifier 6 | { 7 | public static string Compress(string css) 8 | { 9 | css = Regex.Replace(css, @"[a-zA-Z]+#", "#"); 10 | css = Regex.Replace(css, @"[\n\r]+\s*", " "); 11 | css = Regex.Replace(css, @"\s+", " "); 12 | css = Regex.Replace(css, @"\s?([:,;{}])\s?", "$1"); 13 | css = css.Replace(";}", "}"); 14 | css = Regex.Replace(css, @"([\s:]0)(px|pt|%|em)", "$1"); 15 | 16 | // Remove comments from CSS 17 | css = Regex.Replace(css, @"/\[\d\D]?\*/", string.Empty); 18 | return css; 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /SynkMVC/Core/Menus.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace LunarLabs.WebMVC 4 | { 5 | public class Menu 6 | { 7 | public class Item 8 | { 9 | public string label; 10 | public string action; 11 | public string link; 12 | 13 | public Item(string label, string action, string link) 14 | { 15 | this.label = label; 16 | this.action = action; 17 | this.link = link; 18 | } 19 | } 20 | 21 | public string title; 22 | public string link; 23 | public List items = new List(); 24 | 25 | public Menu(string title, string link) 26 | { 27 | this.title = title; 28 | this.link = link; 29 | } 30 | } 31 | 32 | } 33 | -------------------------------------------------------------------------------- /LunarServer/Utils/RequestExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using LunarLabs.WebServer.HTTP; 3 | 4 | namespace LunarLabs.WebServer.Utils 5 | { 6 | public static class RequestExtensions 7 | { 8 | public static long GetLong(this HTTPRequest request, string name) 9 | { 10 | var str = request.GetVariable(name); 11 | long result; 12 | if (long.TryParse(str, out result)) 13 | { 14 | return result; 15 | } 16 | 17 | return 0; 18 | } 19 | 20 | public static T GetEnum (this HTTPRequest request, string name) where T: struct 21 | { 22 | var str = request.GetVariable(name); 23 | 24 | var type = typeof(T); 25 | T result; 26 | 27 | if (Enum.TryParse(str, out result)) 28 | { 29 | return (T)result; 30 | } 31 | 32 | return default(T); 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /LunarServer/Utils/MD5.cs: -------------------------------------------------------------------------------- 1 | using System.Security.Cryptography; 2 | using System.Text; 3 | 4 | namespace LunarLabs.WebServer.Utils 5 | { 6 | public static class MD5Utils 7 | { 8 | public static string MD5(this string input) 9 | { 10 | byte[] inputBytes = System.Text.Encoding.ASCII.GetBytes(input); 11 | return inputBytes.MD5(); 12 | } 13 | 14 | public static string MD5(this byte[] inputBytes) 15 | { 16 | // step 1, calculate MD5 hash from input 17 | MD5 md5 = System.Security.Cryptography.MD5.Create(); 18 | 19 | byte[] hash = md5.ComputeHash(inputBytes); 20 | 21 | // step 2, convert byte array to hex string 22 | StringBuilder sb = new StringBuilder(); 23 | for (int i = 0; i < hash.Length; i++) 24 | { 25 | sb.Append(hash[i].ToString("X2")); 26 | } 27 | 28 | return sb.ToString(); 29 | } 30 | 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /LunarServer/Templates/Engine/GlobalKey.cs: -------------------------------------------------------------------------------- 1 | namespace LunarLabs.Templates 2 | { 3 | public class GlobalKey : PathRenderingKey 4 | { 5 | private string _global; 6 | 7 | public override RenderingType RenderingType => RenderingType.Any; 8 | 9 | public GlobalKey(string key) : base(key) 10 | { 11 | this._global = steps[0]; 12 | this.startingStep = 1; 13 | } 14 | 15 | public override object Evaluate(RenderingContext context) 16 | { 17 | var obj = context.Get(_global); 18 | 19 | if (this.steps.Length > 1) 20 | { 21 | int stackPointer = context.DataStack.Count - 1; 22 | var temp = context.DataStack[stackPointer]; 23 | 24 | context.DataStack[stackPointer] = obj; 25 | 26 | var result = base.Evaluate(context); 27 | 28 | context.DataStack[stackPointer] = temp; 29 | 30 | return result; 31 | } 32 | 33 | return obj; 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /LunarServer/Templates/Engine/NegationKey.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections; 3 | using System.Linq; 4 | 5 | namespace LunarLabs.Templates 6 | { 7 | public class NegationKey : RenderingKey 8 | { 9 | private RenderingKey body; 10 | 11 | public override RenderingType RenderingType => RenderingType.Bool; 12 | 13 | public NegationKey(RenderingKey body) : base() 14 | { 15 | this.body = body; 16 | } 17 | 18 | public override object Evaluate(RenderingContext context) 19 | { 20 | var obj = body.Evaluate(context); 21 | 22 | if (obj is bool) 23 | { 24 | return !((bool)obj); 25 | } 26 | 27 | if (obj is IEnumerable) 28 | { 29 | var collection = (IEnumerable)obj; 30 | return !collection.Any(); 31 | } 32 | 33 | throw new Exception("Expected bool key"); 34 | } 35 | 36 | public override string ToString() 37 | { 38 | return "!"+body.ToString(); 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Sérgio Flores 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /SynkMVC/Utils/MailSender.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Net.Mail; 6 | 7 | namespace LunarLabs.WebMVC.Utils 8 | { 9 | public class MailSender 10 | { 11 | //http://stackoverflow.com/questions/9201239/send-e-mail-via-smtp-using-c-sharp 12 | public void Send() 13 | { 14 | SmtpClient client = new SmtpClient(); 15 | client.Port = 587; 16 | client.Host = "smtp.gmail.com"; 17 | client.EnableSsl = true; 18 | client.Timeout = 10000; 19 | client.DeliveryMethod = SmtpDeliveryMethod.Network; 20 | client.UseDefaultCredentials = false; 21 | client.Credentials = new System.Net.NetworkCredential("user@gmail.com", "password"); 22 | 23 | MailMessage mm = new MailMessage("donotreply@domain.com", "sendtomyemail@domain.co.uk", "test", "test"); 24 | mm.BodyEncoding = UTF8Encoding.UTF8; 25 | mm.DeliveryNotificationOptions = DeliveryNotificationOptions.OnFailure; 26 | 27 | client.Send(mm); 28 | 29 | mm.Dispose(); 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /LunarServer/LunarServer.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netstandard2.1 5 | true 6 | 1.5.6 7 | 1.5.6 8 | 1.5.11 9 | HTTP server with minimal dependencies 10 | Sergio Flores 11 | https://github.com/Relfos/LunarServer 12 | 13 | https://github.com/Relfos/LunarServer 14 | http server cookies oauth 15 | Sergio Flores 16 | Lunar Labs 17 | LunarLabs.Server 18 | LunarLabs.Server 19 | LunarLabs.WebServer 20 | true 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /ServerTests/StorageTests.cs: -------------------------------------------------------------------------------- 1 | using LunarLabs.WebServer.Core; 2 | using LunarLabs.WebServer.HTTP; 3 | using NUnit.Framework; 4 | 5 | namespace Tests 6 | { 7 | internal struct DummyStruct 8 | { 9 | public int X; 10 | public int Y; 11 | public string name; 12 | } 13 | 14 | public class Tests 15 | { 16 | [Test] 17 | public void TestWebsocketKey() 18 | { 19 | var key = HTTPServer.GenerateWebSocketKey("dGhlIHNhbXBsZSBub25jZQ=="); 20 | Assert.IsTrue(key == "s3pPLMBiTxaQ9kYGzzhZRbK+xOo="); 21 | } 22 | 23 | [Test] 24 | public void TestSessionStructs() 25 | { 26 | var storage = new MemorySessionStorage(); 27 | 28 | var dummy = new DummyStruct() 29 | { 30 | name = "Hello", 31 | X = 10, 32 | Y = -20 33 | }; 34 | 35 | var session = storage.CreateSession(); 36 | 37 | session.SetStruct("entry", dummy); 38 | 39 | var other = session.GetStruct("entry"); 40 | 41 | Assert.IsTrue(dummy.name == other.name); 42 | Assert.IsTrue(dummy.X == other.X); 43 | Assert.IsTrue(dummy.Y == other.Y); 44 | } 45 | } 46 | } -------------------------------------------------------------------------------- /SynkMVC/Model/File.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | 4 | namespace LunarLabs.WebMVC.Model 5 | { 6 | public class File : Entity 7 | { 8 | public override void InitFields() 9 | { 10 | this.RegisterField("real_name").asString(60).showInGrid(); 11 | this.RegisterField("hash").asString(40); 12 | this.RegisterField("size").asSize().showInGrid(); 13 | this.RegisterField("local_name").asString(80).showInGrid(); 14 | this.RegisterField("thumb").asString(80); 15 | } 16 | 17 | public override string ToString() 18 | { 19 | return GetFieldValue("real_name"); 20 | } 21 | 22 | public byte[] GetBytes(SynkContext context) 23 | { 24 | var content = GetFieldValue("local_name"); 25 | 26 | var filePath = System.IO.Path.Combine("public", content.TrimStart('/', '\\')); 27 | filePath = context.site.GetFullPath(filePath); 28 | if (System.IO.File.Exists(filePath)) 29 | { 30 | return System.IO.File.ReadAllBytes(filePath); 31 | } 32 | else 33 | { 34 | return null; 35 | } 36 | } 37 | 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /LunarServer/Core/Logger.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace LunarLabs.WebServer.Core 4 | { 5 | public enum LogLevel 6 | { 7 | Default, 8 | Debug, 9 | Info, 10 | Warning, 11 | Error 12 | } 13 | 14 | public delegate void LoggerCallback(LogLevel level, string text); 15 | 16 | public static class ConsoleLogger 17 | { 18 | public static LogLevel MaxLevel = LogLevel.Default; 19 | 20 | public static bool useColors = true; 21 | 22 | public static void Write(LogLevel level, string text) 23 | { 24 | if (MaxLevel > level) return; 25 | 26 | var temp = Console.ForegroundColor; 27 | 28 | if (useColors) 29 | { 30 | ConsoleColor c; 31 | switch (level) 32 | { 33 | case LogLevel.Debug: c = ConsoleColor.Cyan; break; 34 | case LogLevel.Error: c = ConsoleColor.Red; break; 35 | case LogLevel.Warning: c = ConsoleColor.Yellow; break; 36 | default: c = ConsoleColor.Gray; break; 37 | } 38 | Console.ForegroundColor = c; 39 | } 40 | 41 | Console.WriteLine(text); 42 | 43 | if (useColors) 44 | { 45 | Console.ForegroundColor = temp; 46 | } 47 | } 48 | } 49 | } -------------------------------------------------------------------------------- /LunarServer/Utils/Country.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | 3 | namespace LunarLabs.WebServer.Utils 4 | { 5 | public static class CountryUtils 6 | { 7 | public struct IPRange 8 | { 9 | public uint start; 10 | public uint end; 11 | public string code; 12 | } 13 | 14 | private static IPRange[] _ipRanges; 15 | 16 | private static void InitRanges() 17 | { 18 | var lines = File.ReadAllLines("ranges.txt"); 19 | _ipRanges = new IPRange[lines.Length]; 20 | for (int i=0; i= p.start && ip <= p.end) 42 | { 43 | return p.code; 44 | } 45 | 46 | return null; 47 | } 48 | 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /SynkMVC/Model/User.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | 4 | namespace LunarLabs.WebMVC.Model 5 | { 6 | public class User : Entity 7 | { 8 | public override void InitFields() 9 | { 10 | this.RegisterField("username").asName(30).showInGrid(); 11 | this.RegisterField("password").asPassword(40).makeOptional(); 12 | this.RegisterField("hash").asHash("password"); 13 | this.RegisterField("permissions").asBitfield(); 14 | this.RegisterField("database").asString(64); 15 | this.RegisterField("payload").asText(); 16 | } 17 | 18 | public override string ToString() 19 | { 20 | return GetFieldValue("username"); 21 | } 22 | 23 | public override Condition GetSearch(string term) 24 | { 25 | return Condition.Contains("username", term); 26 | } 27 | 28 | public long GetPermissions() 29 | { 30 | long result; 31 | long.TryParse(GetFieldValue("permissions"), out result); 32 | return result; 33 | } 34 | 35 | public bool HasPermission(int permissionIndex) 36 | { 37 | var perms = GetPermissions(); 38 | int flag = 1 << permissionIndex; 39 | return (perms & flag) != 0; 40 | } 41 | 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /LunarServer/HTTP/RequestCache.cs: -------------------------------------------------------------------------------- 1 | using LunarLabs.WebServer.HTTP; 2 | using System; 3 | using System.Collections.Generic; 4 | 5 | namespace LunarLabs.WebServer.HTTP 6 | { 7 | internal struct RequestCacheEntry 8 | { 9 | public readonly HTTPResponse Response; 10 | public readonly DateTime dateTime; 11 | 12 | public RequestCacheEntry(HTTPResponse response, DateTime dateTime) 13 | { 14 | Response = response; 15 | this.dateTime = dateTime; 16 | } 17 | } 18 | 19 | internal class RequestCache 20 | { 21 | private Dictionary _cachedResponses = new Dictionary(); 22 | 23 | public HTTPResponse GetCachedResponse(string path, int maxSeconds) 24 | { 25 | if (_cachedResponses.ContainsKey(path)) 26 | { 27 | var entry = _cachedResponses[path]; 28 | var diff = DateTime.UtcNow - entry.dateTime; 29 | if (diff.TotalSeconds <= maxSeconds) 30 | { 31 | return entry.Response; 32 | } 33 | } 34 | 35 | return null; 36 | } 37 | 38 | public void PutCachedResponse(string path, HTTPResponse response) 39 | { 40 | var entry = new RequestCacheEntry(response, DateTime.UtcNow); 41 | _cachedResponses[path] = entry; 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /LunarServer/Templates/Engine/RenderingContext.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Text; 3 | 4 | namespace LunarLabs.Templates 5 | { 6 | public enum RenderingOperation 7 | { 8 | None, 9 | Continue, 10 | Break, 11 | } 12 | 13 | public class RenderingContext 14 | { 15 | public Queue queue; 16 | public object DataRoot; 17 | public List DataStack; 18 | public StringBuilder output; 19 | 20 | internal RenderingOperation operation; 21 | 22 | private Dictionary variables; 23 | 24 | public void Set(string key, object val) 25 | { 26 | if (variables == null) 27 | { 28 | variables = new Dictionary(); 29 | } 30 | 31 | variables[key] = val; 32 | } 33 | 34 | public object Get(string key) 35 | { 36 | if (variables == null) 37 | { 38 | return null; 39 | } 40 | 41 | if (variables.ContainsKey(key)) 42 | { 43 | return variables[key]; 44 | } 45 | 46 | return null; 47 | } 48 | 49 | public object EvaluateObject(RenderingKey key) 50 | { 51 | if (key == null) 52 | { 53 | return null; 54 | } 55 | 56 | return key.Evaluate(this); 57 | } 58 | } 59 | 60 | } 61 | -------------------------------------------------------------------------------- /LunarServer/Utils/Decimals.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | 5 | namespace LunarLabs.Server.Utils 6 | { 7 | public static class DecimalExtensions 8 | { 9 | public static decimal DecimalPlaces(this decimal value) 10 | { 11 | var precision = (Decimal.GetBits(value)[3] >> 16) & 0x000000FF; 12 | return precision; 13 | } 14 | 15 | public static decimal TruncateEx(this decimal value, int decimalPlaces) 16 | { 17 | if (decimalPlaces < 0) 18 | throw new ArgumentException("decimalPlaces must be greater than or equal to 0."); 19 | 20 | var precision = DecimalPlaces(value); 21 | if (precision <= decimalPlaces) 22 | return value; 23 | 24 | var modifier = Convert.ToDecimal(0.5 / Math.Pow(10, decimalPlaces)); 25 | return Math.Round(value >= 0 ? value - modifier : value + modifier, decimalPlaces); 26 | } 27 | 28 | public static Decimal Lerp(decimal A, decimal B, decimal t) 29 | { 30 | return A * (1m - t) + B * t; 31 | } 32 | 33 | public static Decimal GetPercentChange(decimal prevValue, decimal curValue) 34 | { 35 | if (prevValue == 0) 36 | { 37 | return 0; 38 | } 39 | 40 | return 100 * (curValue > prevValue ? (curValue / prevValue) - 1 : (1m - (curValue / prevValue)) * -1); 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /LunarServer/Plugins/Oauth/OauthConnection.cs: -------------------------------------------------------------------------------- 1 | using LunarLabs.WebServer.Core; 2 | 3 | namespace LunarLabs.WebServer.Plugins.Oauth 4 | { 5 | public enum OauthKind 6 | { 7 | Facebook, 8 | LinkedIn 9 | } 10 | 11 | public abstract class OauthConnection 12 | { 13 | public static string OAUTH_ID = "KEeF3KEYWfZI53sSTAf22"; 14 | 15 | public LoggerCallback logger { get; private set; } 16 | 17 | public string localPath { get; private set; }//Should match Site URL 18 | 19 | protected string client_id; 20 | protected string client_secret; 21 | protected string app_url; 22 | 23 | public OauthConnection(LoggerCallback log, string app_url, string client_id, string client_secret, string localPath) 24 | { 25 | if (!app_url.StartsWith("http://")) 26 | { 27 | app_url = "http://" + app_url; 28 | } 29 | 30 | this.logger = log; 31 | this.app_url = app_url; 32 | this.client_id = client_id; 33 | this.client_secret = client_secret; 34 | this.localPath = localPath; 35 | } 36 | 37 | public abstract OauthKind GetKind(); 38 | 39 | public abstract string GetLoginURL(); 40 | 41 | public abstract Profile Login(string code); 42 | 43 | public string GetRedirectURL() 44 | { 45 | var redirect_url = app_url + "/" + localPath; 46 | return redirect_url.UrlEncode(); 47 | } 48 | 49 | } 50 | 51 | } 52 | -------------------------------------------------------------------------------- /LunarServer/Utils/Password.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | 5 | namespace LunarLabs.WebServer.Utils 6 | { 7 | public static class PasswordUtils 8 | { 9 | public static string GetPasswordHash(this string password) 10 | { 11 | var key = password.MD5().ToLower(); 12 | var s = "./ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'"; 13 | s = s.Shuffle(); 14 | s = s.Substring(s.Length - 4); 15 | var salt = "$1$" + s; 16 | return Crypt.crypt(key, salt); 17 | } 18 | 19 | public static bool CheckPassword(this string password, string user_hash) 20 | { 21 | var password_md5 = password.MD5(); 22 | 23 | if (string.IsNullOrEmpty(user_hash)) 24 | { 25 | return false; 26 | } 27 | 28 | var temp_hash = Crypt.crypt(password_md5.ToLower(), user_hash); 29 | return temp_hash.Equals(user_hash); 30 | } 31 | 32 | public static string Shuffle(this string str) 33 | { 34 | char[] array = str.ToCharArray(); 35 | Random rng = new Random(); 36 | int n = array.Length; 37 | while (n > 1) 38 | { 39 | n--; 40 | int k = rng.Next(n + 1); 41 | var value = array[k]; 42 | array[k] = array[n]; 43 | array[n] = value; 44 | } 45 | return new string(array); 46 | } 47 | 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /LunarServer/Core/Date.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | 5 | namespace LunarLabs.WebServer.Core 6 | { 7 | public struct Date 8 | { 9 | public int year; 10 | public int month; 11 | public int day; 12 | 13 | public override int GetHashCode() 14 | { 15 | unchecked // Overflow is fine, just wrap 16 | { 17 | int hash = 17; 18 | hash = hash * 23 + year.GetHashCode(); 19 | hash = hash * 23 + month.GetHashCode(); 20 | hash = hash * 23 + day.GetHashCode(); 21 | return hash; 22 | } 23 | } 24 | } 25 | 26 | public static class DateUtils 27 | { 28 | public static Date FromDateTime(this DateTime date) 29 | { 30 | return new Date() { year = date.Year, month = date.Month, day = date.Day }; 31 | } 32 | 33 | public static DateTime FromDate(this Date date) 34 | { 35 | return new DateTime(date.year, date.month, date.day); 36 | } 37 | 38 | public static DateTime FixedDay(this DateTime val) 39 | { 40 | return new DateTime(val.Year, val.Month, val.Day); 41 | } 42 | 43 | public static DateTime FixedMonth(this DateTime val) 44 | { 45 | return new DateTime(val.Year, val.Month, 1); 46 | } 47 | 48 | public static DateTime FixedYear(this DateTime val) 49 | { 50 | return new DateTime(val.Year, 1, 1); 51 | } 52 | 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /SynkMVC/Modules/Search.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using LunarParser; 3 | using LunarParser.JSON; 4 | 5 | namespace LunarLabs.WebMVC.Modules 6 | { 7 | public class Search : Module 8 | { 9 | 10 | public Search() 11 | { 12 | } 13 | 14 | public void OnDefault(SynkContext context) 15 | { 16 | var entityClass = context.request.GetVariable("entity"); 17 | var term = context.request.GetVariable("term"); 18 | var required = context.request.GetVariable("required").Equals("true"); 19 | 20 | List entities = context.currentModule.Search(context, entityClass, term); 21 | 22 | var result = DataNode.CreateArray(); 23 | 24 | if (!required) 25 | { 26 | var item = DataNode.CreateObject(); 27 | item.AddField("value", "0"); 28 | item.AddField("label", context.Translate("system_none")); 29 | result.AddNode(item); 30 | } 31 | 32 | if (entities != null) 33 | { 34 | foreach (var entity in entities) 35 | { 36 | var item = DataNode.CreateObject(); 37 | item.AddField("value", entity.id.ToString()); 38 | item.AddField("label", entity.ToString()); 39 | result.AddNode(item); 40 | } 41 | } 42 | 43 | var json = JSONWriter.WriteToString(result); 44 | context.Echo(json); 45 | } 46 | 47 | } 48 | 49 | } 50 | -------------------------------------------------------------------------------- /LunarServer.sln: -------------------------------------------------------------------------------- 1 | 2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 15 4 | VisualStudioVersion = 15.0.27004.2008 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LunarServer", "LunarServer\LunarServer.csproj", "{B9AA0B55-4D5D-4151-BF9A-AEC1999DE217}" 7 | EndProject 8 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ServerTests", "ServerTests\ServerTests.csproj", "{0ACBAD14-DFA8-4F78-A788-F438BB89A39D}" 9 | EndProject 10 | Global 11 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 12 | Debug|Any CPU = Debug|Any CPU 13 | Release|Any CPU = Release|Any CPU 14 | EndGlobalSection 15 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 16 | {B9AA0B55-4D5D-4151-BF9A-AEC1999DE217}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 17 | {B9AA0B55-4D5D-4151-BF9A-AEC1999DE217}.Debug|Any CPU.Build.0 = Debug|Any CPU 18 | {B9AA0B55-4D5D-4151-BF9A-AEC1999DE217}.Release|Any CPU.ActiveCfg = Release|Any CPU 19 | {B9AA0B55-4D5D-4151-BF9A-AEC1999DE217}.Release|Any CPU.Build.0 = Release|Any CPU 20 | {0ACBAD14-DFA8-4F78-A788-F438BB89A39D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 21 | {0ACBAD14-DFA8-4F78-A788-F438BB89A39D}.Debug|Any CPU.Build.0 = Debug|Any CPU 22 | {0ACBAD14-DFA8-4F78-A788-F438BB89A39D}.Release|Any CPU.ActiveCfg = Release|Any CPU 23 | {0ACBAD14-DFA8-4F78-A788-F438BB89A39D}.Release|Any CPU.Build.0 = Release|Any CPU 24 | EndGlobalSection 25 | GlobalSection(SolutionProperties) = preSolution 26 | HideSolutionNode = FALSE 27 | EndGlobalSection 28 | GlobalSection(ExtensibilityGlobals) = postSolution 29 | SolutionGuid = {3E98DF56-3212-42A9-B4F7-2905B97EEAF3} 30 | EndGlobalSection 31 | EndGlobal 32 | -------------------------------------------------------------------------------- /SynkMVC/Utils/ImageUtils.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | 5 | namespace LunarLabs.WebMVC.Utils 6 | { 7 | public static class ImageUtils 8 | { 9 | /*public static Bitmap ResizeImage(Image image, int width, int height) 10 | { 11 | var destRect = new Rectangle(0, 0, width, height); 12 | var destImage = new Bitmap(width, height); 13 | 14 | destImage.SetResolution(image.HorizontalResolution, image.VerticalResolution); 15 | 16 | using (var graphics = Graphics.FromImage(destImage)) 17 | { 18 | graphics.CompositingMode = CompositingMode.SourceCopy; 19 | graphics.CompositingQuality = CompositingQuality.HighQuality; 20 | graphics.InterpolationMode = InterpolationMode.HighQualityBicubic; 21 | graphics.SmoothingMode = SmoothingMode.HighQuality; 22 | graphics.PixelOffsetMode = PixelOffsetMode.HighQuality; 23 | 24 | using (var wrapMode = new ImageAttributes()) 25 | { 26 | wrapMode.SetWrapMode(WrapMode.TileFlipXY); 27 | graphics.DrawImage(image, destRect, 0, 0, image.Width, image.Height, GraphicsUnit.Pixel, wrapMode); 28 | } 29 | } 30 | 31 | return destImage; 32 | } 33 | 34 | public static byte[] imageToByteArray(this Image image) 35 | { 36 | using (var ms = new MemoryStream()) 37 | { 38 | image.Save(ms, ImageFormat.Png); 39 | return ms.ToArray(); 40 | } 41 | }*/ 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /SynkMVC/Modules/Files.cs: -------------------------------------------------------------------------------- 1 | using LunarLabs.WebMVC.Model; 2 | using LunarLabs.WebMVC.Utils; 3 | using System.Text; 4 | using System; 5 | using LunarParser; 6 | using LunarParser.JSON; 7 | 8 | namespace LunarLabs.WebMVC.Modules 9 | { 10 | public class Files : CRUDModule 11 | { 12 | 13 | public Files() 14 | { 15 | this.RegisterClass(); 16 | } 17 | 18 | public void OnUpload(SynkContext context) 19 | { 20 | foreach (var upload in context.request.uploads) 21 | { 22 | var entity = context.UploadFile(upload.fileName, upload.bytes); 23 | 24 | var result = DataNode.CreateObject(); 25 | result.AddField("id", entity.id.ToString()); 26 | result.AddField("name", upload.fileName); 27 | result.AddField("hash", entity.GetFieldValue("hash")); 28 | 29 | var json = JSONWriter.WriteToString(result); 30 | context.Echo(json); 31 | break; 32 | } 33 | } 34 | 35 | public override void OnDetail(SynkContext context) 36 | { 37 | long id; 38 | long.TryParse(context.request.GetVariable("entity"), out id); 39 | 40 | var file = context.database.FetchEntityByID(id); 41 | 42 | if (file.exists) 43 | { 44 | var fileName = file.GetFieldValue("real_name"); 45 | var bytes = file.GetBytes(context); 46 | context.SendDownload(fileName, bytes); 47 | } 48 | else 49 | { 50 | context.die("File not found, id " + id); 51 | } 52 | 53 | } 54 | } 55 | 56 | } 57 | -------------------------------------------------------------------------------- /SynkMVC/Core/Condition.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | 6 | namespace LunarLabs.WebMVC 7 | { 8 | public class Condition 9 | { 10 | public enum Operator 11 | { 12 | And, 13 | Or, 14 | Contains, 15 | BeginsWith, 16 | EndsWith, 17 | Equals, 18 | LessThan, 19 | GreaterThan, 20 | LessOrEqualThan, 21 | GreaterOrEqualThan, 22 | NotEqual 23 | } 24 | 25 | public Operator op; 26 | public string fieldName; 27 | public string opValue; 28 | 29 | public Condition childA; 30 | public Condition childB; 31 | 32 | public Condition(Operator op, string fieldName, string val) 33 | { 34 | this.fieldName = fieldName; 35 | this.op = op; 36 | this.opValue = val; 37 | this.childA = null; 38 | this.childB = null; 39 | } 40 | 41 | public Condition(Operator op, Condition A, Condition B) 42 | { 43 | this.fieldName = null; 44 | this.op = op; 45 | this.opValue = null; 46 | this.childA = A; 47 | this.childB = B; 48 | } 49 | 50 | public static Condition And(Condition A, Condition B) 51 | { 52 | return new Condition(Operator.And, A, B); 53 | } 54 | 55 | public static Condition Or(Condition A, Condition B) 56 | { 57 | return new Condition(Operator.Or, A, B); 58 | } 59 | 60 | public static Condition Equal(string fieldName, string val) 61 | { 62 | return new Condition(Operator.Equals, fieldName, val); 63 | } 64 | 65 | public static Condition Contains(string fieldName, string val) 66 | { 67 | return new Condition(Operator.Contains, fieldName, val); 68 | } 69 | 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /LunarServer/Core/Mime.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Text; 5 | 6 | namespace LunarLabs.WebServer.Core 7 | { 8 | public static class MimeUtils 9 | { 10 | public static string GetContentType(string fileName, out bool shouldCompress, out bool shouldDownload) 11 | { 12 | var ext = Path.GetExtension(fileName); 13 | 14 | string contentType; 15 | shouldCompress = false; 16 | shouldDownload = true; 17 | 18 | switch (ext) 19 | { 20 | case ".png": contentType = "image/png"; shouldDownload = false; break; 21 | case ".jpg": contentType = "image/jpeg"; shouldDownload = false; break; 22 | case ".gif": contentType = "image/gif"; shouldDownload = false; break; 23 | case ".svg": contentType = "image/svg+xml"; shouldCompress = true; shouldDownload = false; break; 24 | 25 | 26 | case ".ogg": contentType = "audio/vorbis"; break; 27 | case ".mp4": contentType = "audio/mpeg"; break; 28 | 29 | case ".css": contentType = "text/css"; shouldCompress = true; break; 30 | case ".html": contentType = "text/html"; shouldCompress = true; shouldDownload = false; break; 31 | case ".csv": contentType = "text/csv"; shouldCompress = true; break; 32 | case ".txt": contentType = "text/plain"; shouldCompress = true; break; 33 | 34 | case ".js": contentType = "application/javascript"; shouldCompress = true; break; 35 | case ".json": contentType = "application/json"; shouldCompress = true; shouldDownload = false; break; 36 | case ".xml": contentType = "application/xml"; shouldCompress = true; break; 37 | case ".zip": contentType = "application/zip"; break; 38 | case ".pdf": contentType = "application/pdf"; break; 39 | 40 | default: contentType = "application/octet-stream"; break; 41 | } 42 | 43 | return contentType; 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /LunarServer/Core/Plugin.cs: -------------------------------------------------------------------------------- 1 | using LunarLabs.Parser; 2 | using System; 3 | using LunarLabs.WebServer.HTTP; 4 | using System.Linq; 5 | 6 | namespace LunarLabs.WebServer.Core 7 | { 8 | [AttributeUsage(AttributeTargets.Method)] 9 | public class EndPointAttribute: Attribute 10 | { 11 | public readonly string Path; 12 | public readonly HTTPRequest.Method Method; 13 | public readonly int Priority; 14 | 15 | public EndPointAttribute(HTTPRequest.Method method, string path, int priority = 0) 16 | { 17 | this.Method = method; 18 | this.Path = path; 19 | this.Priority = priority; 20 | } 21 | } 22 | 23 | public abstract class ServerPlugin 24 | { 25 | public readonly HTTPServer Server; 26 | public readonly string Path; 27 | 28 | public ServerPlugin(HTTPServer server, string path = null) 29 | { 30 | if (path == null) 31 | { 32 | path = "/"; 33 | } 34 | 35 | if (!path.EndsWith("/")) 36 | { 37 | path += "/"; 38 | } 39 | 40 | this.Server = server; 41 | this.Path = path; 42 | 43 | server.AddPlugin(this); 44 | } 45 | 46 | internal bool Install() 47 | { 48 | var type = this.GetType(); 49 | var methods = type.GetMethods().Where(m => m.GetCustomAttributes(typeof(EndPointAttribute), false).Length > 0).ToArray(); 50 | 51 | foreach (var method in methods) 52 | { 53 | var attr = (EndPointAttribute) method.GetCustomAttributes(typeof(EndPointAttribute), false).FirstOrDefault(); 54 | var fullPath = this.Path + attr.Path; 55 | 56 | var handler = (Func)Delegate.CreateDelegate(typeof(Func), this, method); 57 | 58 | Server.RegisterHandler(attr.Method, fullPath, attr.Priority, handler); 59 | } 60 | 61 | return OnInstall(); 62 | } 63 | 64 | protected virtual bool OnInstall() 65 | { 66 | return true; 67 | } 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /LunarServer/Templates/Engine/FormatNodes.cs: -------------------------------------------------------------------------------- 1 | using LunarLabs.Server.Utils; 2 | using System.Collections.Generic; 3 | 4 | namespace LunarLabs.Templates 5 | { 6 | public class NumericFormatNode : TemplateNode 7 | { 8 | internal static readonly Dictionary AmountFormatters = new Dictionary() 9 | { 10 | { "B", 1000000000}, 11 | { "M" , 1000000}, 12 | { "K", 1000}, 13 | }; 14 | 15 | internal static readonly Dictionary SizeFormatters = new Dictionary() 16 | { 17 | { "GB", 1000000000}, 18 | { "MB", 1000000}, 19 | { "KB", 1000}, 20 | }; 21 | 22 | private RenderingKey key; 23 | private Dictionary dictionary; 24 | 25 | public NumericFormatNode(Document document, string key, Dictionary dictionary) : base(document) 26 | { 27 | this.key = RenderingKey.Parse(key, RenderingType.Numeric); 28 | this.dictionary = dictionary; 29 | } 30 | 31 | public override void Execute(RenderingContext context) 32 | { 33 | var temp = context.EvaluateObject(key); 34 | 35 | if (temp != null) 36 | { 37 | decimal num; 38 | 39 | if (decimal.TryParse(temp.ToString(), out num)) 40 | { 41 | string key = null; 42 | foreach (var entry in dictionary) 43 | { 44 | if (num >= entry.Value || num <= -entry.Value) 45 | { 46 | key = entry.Key; 47 | } 48 | } 49 | 50 | if (key != null) 51 | { 52 | num /= dictionary[key]; 53 | num = num.TruncateEx(2); 54 | context.output.Append(num.ToString()); 55 | context.output.Append(key); 56 | } 57 | else 58 | { 59 | context.output.Append(num.ToString()); 60 | } 61 | } 62 | } 63 | } 64 | } 65 | 66 | } 67 | -------------------------------------------------------------------------------- /LunarServer/Templates/Engine/CaseNodes.cs: -------------------------------------------------------------------------------- 1 | using System.Linq; 2 | 3 | //http://wiki.c2.com/?CapitalizationRules 4 | 5 | namespace LunarLabs.Templates 6 | { 7 | public enum CaseKind 8 | { 9 | Lower, 10 | Upper, 11 | Pascal, 12 | Snake, 13 | Camel, 14 | Kebab 15 | } 16 | 17 | public class CaseNode : TemplateNode 18 | { 19 | private CaseKind kind; 20 | private RenderingKey key; 21 | 22 | public CaseNode(Document document, string key, CaseKind kind) : base(document) 23 | { 24 | this.key = RenderingKey.Parse(key, RenderingType.String); 25 | this.kind = kind; 26 | } 27 | 28 | private static string TitleCase(string s) 29 | { 30 | if (string.IsNullOrEmpty(s)) 31 | { 32 | return string.Empty; 33 | } 34 | 35 | char[] a = s.ToCharArray(); 36 | a[0] = char.ToUpperInvariant(a[0]); 37 | 38 | return new string(a); 39 | } 40 | 41 | private static string CamelCase(string s) 42 | { 43 | if (string.IsNullOrEmpty(s)) 44 | { 45 | return string.Empty; 46 | } 47 | 48 | char[] a = s.ToCharArray(); 49 | a[0] = char.ToLowerInvariant(a[0]); 50 | 51 | return new string(a); 52 | } 53 | 54 | public override void Execute(RenderingContext context) 55 | { 56 | var temp = context.EvaluateObject(key); 57 | 58 | if (temp != null) 59 | { 60 | var value = temp.ToString(); 61 | string result; 62 | switch (kind) 63 | { 64 | case CaseKind.Lower: result = value.ToLowerInvariant(); break; 65 | case CaseKind.Upper: result = value.ToUpperInvariant(); break; 66 | case CaseKind.Pascal: result = TitleCase(value); break; 67 | case CaseKind.Camel: result = CamelCase(value); break; 68 | case CaseKind.Snake: result = string.Concat(value.Select((x, i) => i > 0 && char.IsUpper(x) ? "_" + x.ToString() : x.ToString())); break; 69 | case CaseKind.Kebab: result = value.Replace(' ', '-').ToLowerInvariant(); break; 70 | default: result = value; break; 71 | } 72 | context.output.Append(result); 73 | } 74 | } 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /LunarServer/Websockets/WebSocketFrame.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace LunarLabs.WebSockets 4 | { 5 | internal class WebSocketFrame 6 | { 7 | public bool IsFinBitSet { get; private set; } 8 | 9 | public WebSocketOpCode OpCode { get; private set; } 10 | 11 | public int Count { get; private set; } 12 | 13 | public WebSocketCloseStatus CloseStatus { get; private set; } 14 | 15 | public string CloseStatusDescription { get; private set; } 16 | 17 | public WebSocketFrame(bool isFinBitSet, WebSocketOpCode webSocketOpCode, int count) 18 | { 19 | IsFinBitSet = isFinBitSet; 20 | OpCode = webSocketOpCode; 21 | Count = count; 22 | CloseStatus = WebSocketCloseStatus.None; 23 | CloseStatusDescription = null; 24 | } 25 | 26 | public WebSocketFrame(bool isFinBitSet, WebSocketOpCode webSocketOpCode, int count, WebSocketCloseStatus closeStatus, string closeStatusDescription) : this(isFinBitSet, webSocketOpCode, count) 27 | { 28 | CloseStatus = closeStatus; 29 | CloseStatusDescription = closeStatusDescription; 30 | } 31 | } 32 | 33 | internal static class WebSocketFrameExtensions 34 | { 35 | public const int MaskKeyLength = 4; 36 | 37 | /// 38 | /// Mutate payload with the mask key 39 | /// This is a reversible process 40 | /// If you apply this to masked data it will be unmasked and visa versa 41 | /// 42 | /// The 4 byte mask key 43 | /// The payload to mutate 44 | public static void ToggleMask(ArraySegment maskKey, ArraySegment payload) 45 | { 46 | if (maskKey.Count != MaskKeyLength) 47 | { 48 | throw new Exception($"MaskKey key must be {MaskKeyLength} bytes"); 49 | } 50 | 51 | byte[] buffer = payload.Array; 52 | byte[] maskKeyArray = maskKey.Array; 53 | int payloadOffset = payload.Offset; 54 | int payloadCount = payload.Count; 55 | int maskKeyOffset = maskKey.Offset; 56 | 57 | // apply the mask key (this is a reversible process so no need to copy the payload) 58 | // NOTE: this is a hot function 59 | // TODO: make this faster 60 | for (int i = payloadOffset; i < payloadCount; i++) 61 | { 62 | int payloadIndex = i - payloadOffset; // index should start at zero 63 | int maskKeyIndex = maskKeyOffset + (payloadIndex % MaskKeyLength); 64 | buffer[i] = (Byte)(buffer[i] ^ maskKeyArray[maskKeyIndex]); 65 | } 66 | } 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /LunarServer/Entity/Connector.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | 4 | namespace LunarLabs.WebServer.Entity 5 | { 6 | public abstract class EntityConnector 7 | { 8 | public Type objectType { get; private set; } 9 | public EntityStore Store { get; private set; } 10 | 11 | internal void Initialize(EntityStore store, Type objectType) 12 | { 13 | this.Store = store; 14 | this.objectType = objectType; 15 | LoadConnector(); 16 | } 17 | 18 | internal abstract void LoadConnector(); 19 | internal abstract void SaveConnector(); 20 | 21 | private Dictionary _objects = new Dictionary(); 22 | public IEnumerable Objects { get { return _objects.Values; } } 23 | 24 | protected Entity AllocObject() 25 | { 26 | lock (this) 27 | { 28 | var obj = (Entity)Activator.CreateInstance(objectType); 29 | obj.SetStore(this.Store); 30 | return obj; 31 | } 32 | } 33 | 34 | protected void AddObject(Entity obj) 35 | { 36 | _objects[obj.id] = obj; 37 | } 38 | 39 | internal Entity CreateObject() 40 | { 41 | var obj = AllocObject(); 42 | 43 | obj.id = Guid.NewGuid().ToString(); 44 | 45 | lock (this) 46 | { 47 | _objects[obj.id] = obj; 48 | } 49 | 50 | return obj; 51 | } 52 | 53 | internal Entity FindObject(string ID) 54 | { 55 | lock (this) 56 | { 57 | if (_objects.ContainsKey(ID)) 58 | { 59 | return _objects[ID]; 60 | } 61 | return null; 62 | } 63 | } 64 | 65 | internal void DeleteObject(string ID) 66 | { 67 | lock (this) 68 | { 69 | if (_objects.ContainsKey(ID)) 70 | { 71 | _objects.Remove(ID); 72 | } 73 | } 74 | } 75 | 76 | public void Save() 77 | { 78 | lock (this) 79 | { 80 | if (_shouldSave) 81 | { 82 | SaveConnector(); 83 | 84 | _shouldSave = false; 85 | //currentVersion++; 86 | } 87 | } 88 | } 89 | 90 | private bool _shouldSave = false; 91 | 92 | public void RequestSave() 93 | { 94 | lock (this) 95 | { 96 | _shouldSave = true; 97 | } 98 | } 99 | } 100 | 101 | } 102 | -------------------------------------------------------------------------------- /LunarServer/Plugins/Oauth/Profile.cs: -------------------------------------------------------------------------------- 1 | using LunarLabs.Parser; 2 | using LunarLabs.WebServer.Core; 3 | using LunarLabs.WebServer.HTTP; 4 | 5 | namespace LunarLabs.WebServer.Plugins.Oauth 6 | { 7 | public class Profile 8 | { 9 | public const string SessionPrefix = "profile_"; 10 | 11 | public string id; 12 | public string name; 13 | public string email; 14 | public string photo; 15 | public string birthday; 16 | 17 | public string token; 18 | public DataNode data; // TODO not serialized 19 | 20 | public void Save(Session session) 21 | { 22 | session.SetString(SessionPrefix + "id", id); 23 | session.SetString(SessionPrefix + "token", token); 24 | session.SetString(SessionPrefix + "name", name); 25 | session.SetString(SessionPrefix + "email", email); 26 | session.SetString(SessionPrefix + "photo", photo); 27 | session.SetString(SessionPrefix + "birthday", birthday); 28 | } 29 | 30 | public static void Remove(Session session) 31 | { 32 | session.Remove(SessionPrefix + "id"); 33 | session.Remove(SessionPrefix + "token"); 34 | session.Remove(SessionPrefix + "name"); 35 | session.Remove(SessionPrefix + "email"); 36 | session.Remove(SessionPrefix + "photo"); 37 | session.Remove(SessionPrefix + "birthday"); 38 | } 39 | 40 | public static Profile Load(Session session) 41 | { 42 | var id = session.GetString(SessionPrefix + "id", null); 43 | 44 | if (id == null) 45 | { 46 | return null; 47 | } 48 | 49 | var profile = new Profile(); 50 | profile.id = id; 51 | profile.token = session.GetString(SessionPrefix + "token"); 52 | profile.name = session.GetString(SessionPrefix + "name"); 53 | profile.email = session.GetString(SessionPrefix + "email"); 54 | profile.photo = session.GetString(SessionPrefix + "photo"); 55 | profile.birthday = session.GetString(SessionPrefix + "birthday"); 56 | return profile; 57 | } 58 | } 59 | 60 | public static class ProfileUtils 61 | { 62 | public static Profile GetProfile(this HTTPRequest request) 63 | { 64 | return Profile.Load(request.session); 65 | } 66 | 67 | public static bool IsAuthenticated(this HTTPRequest request) 68 | { 69 | return request.GetProfile() != null; 70 | } 71 | 72 | public static bool Logout(this HTTPRequest request) 73 | { 74 | if (request.IsAuthenticated()) 75 | { 76 | Profile.Remove(request.session); 77 | return true; 78 | } 79 | 80 | return false; 81 | } 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /SynkMVC/Model/Config.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | 4 | namespace LunarLabs.WebMVC.Model 5 | { 6 | public class Config: Entity 7 | { 8 | public override void InitFields() 9 | { 10 | this.RegisterField("logFile").asString(200).makeOptional(); 11 | 12 | this.RegisterField("database").asString(200); 13 | this.RegisterField("instanced").asBoolean(); 14 | 15 | this.RegisterField("sqlPlugin").asString(200).setDefaultValue("mysql"); 16 | this.RegisterField("sqlHost").asString(200).setDefaultValue("localhost"); 17 | this.RegisterField("sqlUser").asString(200).setDefaultValue("root"); 18 | this.RegisterField("sqlPass").asString(200).makeOptional(); 19 | 20 | this.RegisterField("defaultUser").asString(200).setDefaultValue("admin"); 21 | this.RegisterField("defaultPass").asString(200).setDefaultValue("test"); 22 | 23 | this.RegisterField("defaultLanguage").asString(200).setDefaultValue("en"); 24 | this.RegisterField("defaultModule").asString(200).setDefaultValue("home"); 25 | } 26 | 27 | public virtual void Load(MVC hostData) 28 | { 29 | var fileName = hostData.GetFullPath("config.txt"); 30 | var lines = System.IO.File.ReadAllLines(fileName); 31 | foreach (var temp in lines) 32 | { 33 | var line = temp; 34 | 35 | if (string.IsNullOrEmpty(line)) 36 | { 37 | continue; 38 | } 39 | 40 | if (line.Contains("#")) 41 | { 42 | line = line.Split('#')[0]; 43 | 44 | if (string.IsNullOrEmpty(line)) 45 | { 46 | continue; 47 | } 48 | } 49 | 50 | var s = line.Split('='); 51 | if (s.Length >= 2) 52 | { 53 | var fieldName = s[0]; 54 | var fieldValue = s[1]; 55 | this.SetFieldValue(fieldName, fieldValue); 56 | } 57 | } 58 | } 59 | 60 | public virtual void InitFirstUser(SynkContext context, User user) 61 | { 62 | user.SetFieldValue("username", this.GetFieldValue("defaultUser")); 63 | user.SetFieldValue("password", this.GetFieldValue("defaultPass")); 64 | user.SetFieldValue("permissions", 0xFFFFFFFF.ToString()); 65 | 66 | var dbName = this.GetFieldValue("database"); 67 | 68 | bool instanced = this.GetFieldBool("instanced"); 69 | if (instanced) 70 | { 71 | user.SetFieldValue("database", dbName + '_' + Utility.GetUniqID()); 72 | } 73 | else 74 | { 75 | user.SetFieldValue("database", dbName); 76 | } 77 | } 78 | 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /SynkMVC/Modules/Auth.cs: -------------------------------------------------------------------------------- 1 | using LunarLabs.WebMVC; 2 | using LunarLabs.WebMVC.Model; 3 | using System; 4 | using System.Collections.Generic; 5 | 6 | namespace LunarLabs.WebMVC.Modules 7 | { 8 | public class Auth: Module 9 | { 10 | public bool checkPassword(string password, string user_hash) 11 | { 12 | var password_md5 = password.MD5(); 13 | 14 | if (string.IsNullOrEmpty(user_hash)) 15 | { 16 | return false; 17 | } 18 | 19 | var temp_hash = Crypt.crypt(password_md5.ToLower(), user_hash); 20 | return temp_hash.Equals(user_hash); 21 | } 22 | 23 | public void OnDefault(SynkContext context) 24 | { 25 | context.PushTemplate("auth/default"); 26 | context.Render(); 27 | } 28 | 29 | private void ShowDefaultPage(SynkContext context) 30 | { 31 | context.ChangeModule(context.config.GetFieldValue("defaultModule")); 32 | context.ChangeAction("default"); 33 | context.Reload(); 34 | context.RunController(); 35 | } 36 | 37 | public void OnLogin(SynkContext context) 38 | { 39 | var username = context.loadVarFromRequest("username"); 40 | var password = context.loadVarFromRequest("password"); 41 | 42 | var dbName = context.config.GetFieldValue("database"); 43 | var cond = Condition.Equal("username", username); 44 | var user = context.database.FetchEntity(cond); 45 | 46 | string hash = null; 47 | 48 | if (user != null && user.exists) 49 | { 50 | hash = user.GetFieldValue("hash"); 51 | } 52 | 53 | if (context.database.failed) 54 | { 55 | context.warning = "Database error!"; 56 | context.PushTemplate("auth/default"); 57 | } 58 | else 59 | if (user != null && user.exists && (string.IsNullOrEmpty(hash) || this.checkPassword(password, hash))) 60 | { 61 | if (context.config.GetFieldBool("instanced")) { 62 | dbName = user.GetFieldValue("database"); 63 | } 64 | 65 | context.LogIn(user.id, dbName); 66 | 67 | ShowDefaultPage(context); 68 | return; 69 | } 70 | else 71 | { 72 | if (user.exists) 73 | { 74 | context.warning = "Dados de login invalidos!"; 75 | } 76 | else 77 | { 78 | context.warning = "Utilizador não existente!"; 79 | } 80 | 81 | context.PushTemplate("auth/default"); 82 | } 83 | 84 | context.Render(); 85 | } 86 | 87 | public void OnLogout(SynkContext context) 88 | { 89 | context.LogOut(); 90 | ShowDefaultPage(context); 91 | return; 92 | } 93 | 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /LunarServer/Plugins/Oauth/OauthPlugin.cs: -------------------------------------------------------------------------------- 1 | using LunarLabs.WebServer.Core; 2 | using LunarLabs.WebServer.HTTP; 3 | using System; 4 | using System.Collections.Generic; 5 | 6 | namespace LunarLabs.WebServer.Plugins.Oauth 7 | { 8 | public class OauthPlugin: ServerPlugin 9 | { 10 | private Dictionary _auths = new Dictionary(); 11 | public IEnumerable auths { get { return _auths.Values; } } 12 | 13 | private LoggerCallback logger { get { return this.Server.Logger; } } 14 | 15 | public Func OnLogin; 16 | public Func OnError; 17 | 18 | public OauthPlugin(HTTPServer server, string rootPath = null) : base(server, rootPath) 19 | { 20 | this.OnLogin = OnLoginException; 21 | this.OnError = OnErrorLog; 22 | } 23 | 24 | private object OnErrorLog(OauthKind kind, HTTPRequest request) 25 | { 26 | logger(LogLevel.Error, "Auth failed for " + kind); 27 | return null; 28 | } 29 | 30 | private object OnLoginException(OauthKind kind, HTTPRequest request) 31 | { 32 | throw new NotImplementedException(); 33 | } 34 | 35 | public void AddAuth(OauthKind kind, string client_id, string client_secret) 36 | { 37 | var redirect_uri = $"{kind.ToString().ToLowerInvariant()}_auth"; 38 | _auths[kind] = Create(kind, logger, client_id, client_secret, redirect_uri); 39 | } 40 | 41 | public OauthConnection Create(OauthKind kind, LoggerCallback logger, string client_id, string client_secret, string redirect_uri, string token = null) 42 | { 43 | var app_url = this.Server.Settings.Host; 44 | 45 | if (!app_url.StartsWith("http://")) 46 | { 47 | app_url = "http://" + app_url; 48 | } 49 | 50 | switch (kind) 51 | { 52 | case OauthKind.Facebook: return new FacebookAuth(logger, app_url, client_id, client_secret, redirect_uri); 53 | case OauthKind.LinkedIn: return new LinkedInAuth(logger, app_url, client_id, client_secret, redirect_uri); 54 | default: return null; 55 | } 56 | } 57 | 58 | protected override bool OnInstall() 59 | { 60 | foreach (var auth in _auths.Values) 61 | { 62 | var path = this.Path + auth.localPath; 63 | Server.Get(path, request => 64 | { 65 | var kind = auth.GetKind(); 66 | 67 | if (request.HasVariable("code")) 68 | { 69 | var profile = auth.Login(request.args["code"]); 70 | if (profile != null) 71 | { 72 | profile.Save(request.session); 73 | return OnLogin(kind, request); 74 | } 75 | } 76 | 77 | return OnError(kind, request); 78 | }); 79 | } 80 | 81 | return true; 82 | } 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /LunarServer/Utils/HTTP.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Net; 5 | using System.Text; 6 | 7 | namespace LunarLabs.WebServer.Utils 8 | { 9 | public static class HTTPUtils 10 | { 11 | private const string UA = "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.2; .NET CLR 1.0.3705;)"; 12 | 13 | public static string Get(string url, Dictionary headers = null) 14 | { 15 | using (var wc = new WebClient()) 16 | { 17 | wc.Encoding = System.Text.Encoding.UTF8; 18 | wc.Headers.Add("user-agent", UA); 19 | wc.Headers.Add("Content-Type", "application/json"); 20 | 21 | if (headers != null) 22 | { 23 | foreach (var entry in headers) 24 | { 25 | wc.Headers.Add(entry.Key, entry.Value); 26 | } 27 | } 28 | 29 | string contents = ""; 30 | try 31 | { 32 | contents = wc.DownloadString(url); 33 | } 34 | catch (WebException ex) 35 | { 36 | using (var stream = ex.Response.GetResponseStream()) 37 | using (var sr = new StreamReader(stream)) 38 | { 39 | contents = sr.ReadToEnd(); 40 | } 41 | } 42 | return contents; 43 | } 44 | } 45 | 46 | public static string Post(string url, Dictionary args = null, Dictionary headers = null) 47 | { 48 | string myParameters = ""; 49 | if (args != null) 50 | { 51 | foreach (var arg in args) 52 | { 53 | if (myParameters.Length > 0) { myParameters += "&"; } 54 | myParameters += arg.Key + "=" + arg.Value; 55 | } 56 | } 57 | 58 | using (WebClient wc = new WebClient()) 59 | { 60 | wc.Encoding = System.Text.Encoding.UTF8; 61 | wc.Headers.Add("user-agent", UA); 62 | wc.Headers.Add("Content-Type", "application/json"); 63 | 64 | wc.Headers[HttpRequestHeader.ContentType] = "application/x-www-form-urlencoded"; 65 | 66 | if (headers != null) 67 | { 68 | foreach (var entry in headers) 69 | { 70 | wc.Headers.Add(entry.Key, entry.Value); 71 | } 72 | } 73 | 74 | 75 | string contents = ""; 76 | try 77 | { 78 | contents = wc.UploadString(url, myParameters); 79 | } 80 | catch (WebException ex) 81 | { 82 | using (var stream = ex.Response.GetResponseStream()) 83 | using (var sr = new StreamReader(stream)) 84 | { 85 | contents = sr.ReadToEnd(); 86 | } 87 | } 88 | return contents; 89 | } 90 | } 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /LunarServer/Core/Settings.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace LunarLabs.WebServer.Core 4 | { 5 | public enum ServerEnvironment 6 | { 7 | Dev, 8 | Prod 9 | } 10 | 11 | public struct ServerSettings 12 | { 13 | public int Port; 14 | public string Path; 15 | public string Host; 16 | public bool Compression; 17 | public ServerEnvironment Environment; 18 | public int MaxPostSizeInBytes; 19 | public int MaxWebsocketFrameInBytes; 20 | public int CacheResponseTime; 21 | public string BindingHost; 22 | 23 | public static ServerSettings DefaultSettings() 24 | { 25 | var exePath = System.IO.Path.GetDirectoryName(System.Environment.GetCommandLineArgs()[0]); 26 | 27 | return new ServerSettings() 28 | { 29 | Port = 80, 30 | Compression = true, 31 | Path = exePath, 32 | BindingHost = "", 33 | Host = "localhost", 34 | MaxPostSizeInBytes = 1024 * 1024 * 8, 35 | MaxWebsocketFrameInBytes = 1024 * 8, 36 | CacheResponseTime = -1, 37 | Environment = ServerEnvironment.Dev 38 | }; 39 | } 40 | 41 | public static ServerSettings Parse(string[] args, string prefix = "--", Action customArgCallback = null) 42 | { 43 | var result = DefaultSettings(); 44 | 45 | foreach (var arg in args) 46 | { 47 | if (!arg.StartsWith(prefix)) 48 | { 49 | continue; 50 | } 51 | 52 | var temp = arg.Substring(prefix.Length).Split(new char[] { '=' }, 2); 53 | var key = temp[0].ToLower(); 54 | var val = temp.Length > 1 ? temp[1] : ""; 55 | 56 | switch (key) 57 | { 58 | case "host": result.Host = val; break; 59 | case "port": int.TryParse(val, out result.Port); break; 60 | case "postsize": int.TryParse(val, out result.MaxPostSizeInBytes); break; 61 | case "compression": bool.TryParse(val, out result.Compression); break; 62 | case "cachetime": int.TryParse(val, out result.CacheResponseTime); break; 63 | case "wsframes": int.TryParse(val, out result.MaxWebsocketFrameInBytes); break; 64 | case "path": 65 | { 66 | result.Path = System.IO.Path.GetFullPath(val); 67 | break; 68 | } 69 | case "env": Enum.TryParse(val, true, out result.Environment); break; 70 | case "binding": result.BindingHost = val; break; 71 | 72 | default: 73 | customArgCallback?.Invoke(key, val); 74 | break; 75 | } 76 | } 77 | 78 | if (result.CacheResponseTime < 0) 79 | { 80 | result.CacheResponseTime = result.Environment == ServerEnvironment.Dev ? 0 : 5; 81 | } 82 | 83 | result.Path = result.Path.Replace("\\", "/"); 84 | if (!result.Path.EndsWith("/")) 85 | { 86 | result.Path += "/"; 87 | } 88 | 89 | return result; 90 | } 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /LunarServer/Websockets/WebSocketFrameWriter.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | using System; 3 | 4 | namespace LunarLabs.WebSockets 5 | { 6 | // see http://tools.ietf.org/html/rfc6455 for specification 7 | // see fragmentation section for sending multi part messages 8 | // EXAMPLE: For a text message sent as three fragments, 9 | // the first fragment would have an opcode of TextFrame and isLastFrame false, 10 | // the second fragment would have an opcode of ContinuationFrame and isLastFrame false, 11 | // the third fragment would have an opcode of ContinuationFrame and isLastFrame true. 12 | internal static class WebSocketFrameWriter 13 | { 14 | /// 15 | /// This is used for data masking so that web proxies don't cache the data 16 | /// Therefore, there are no cryptographic concerns 17 | /// 18 | private static readonly Random _random; 19 | 20 | static WebSocketFrameWriter() 21 | { 22 | _random = new Random((int)DateTime.Now.Ticks); 23 | } 24 | 25 | /// 26 | /// No async await stuff here because we are dealing with a memory stream 27 | /// 28 | /// The web socket opcode 29 | /// Array segment to get payload data from 30 | /// Stream to write to 31 | /// True is this is the last frame in this message (usually true) 32 | public static void Write(WebSocketOpCode opCode, ArraySegment fromPayload, MemoryStream toStream, bool isLastFrame, bool isClient) 33 | { 34 | MemoryStream memoryStream = toStream; 35 | byte finBitSetAsByte = isLastFrame ? (byte)0x80 : (byte)0x00; 36 | byte byte1 = (byte)(finBitSetAsByte | (byte)opCode); 37 | memoryStream.WriteByte(byte1); 38 | 39 | // NB, set the mask flag if we are constructing a client frame 40 | byte maskBitSetAsByte = isClient ? (byte)0x80 : (byte)0x00; 41 | 42 | // depending on the size of the length we want to write it as a byte, ushort or ulong 43 | if (fromPayload.Count < 126) 44 | { 45 | byte byte2 = (byte)(maskBitSetAsByte | (byte)fromPayload.Count); 46 | memoryStream.WriteByte(byte2); 47 | } 48 | else if (fromPayload.Count <= ushort.MaxValue) 49 | { 50 | byte byte2 = (byte)(maskBitSetAsByte | 126); 51 | memoryStream.WriteByte(byte2); 52 | BinaryReaderWriter.WriteUShort((ushort)fromPayload.Count, memoryStream, false); 53 | } 54 | else 55 | { 56 | byte byte2 = (byte)(maskBitSetAsByte | 127); 57 | memoryStream.WriteByte(byte2); 58 | BinaryReaderWriter.WriteULong((ulong)fromPayload.Count, memoryStream, false); 59 | } 60 | 61 | // if we are creating a client frame then we MUST mack the payload as per the spec 62 | if (isClient) 63 | { 64 | byte[] maskKey = new byte[WebSocketFrameExtensions.MaskKeyLength]; 65 | _random.NextBytes(maskKey); 66 | memoryStream.Write(maskKey, 0, maskKey.Length); 67 | 68 | // mask the payload 69 | ArraySegment maskKeyArraySegment = new ArraySegment(maskKey, 0, maskKey.Length); 70 | WebSocketFrameExtensions.ToggleMask(maskKeyArraySegment, fromPayload); 71 | } 72 | 73 | memoryStream.Write(fromPayload.Array, fromPayload.Offset, fromPayload.Count); 74 | } 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /LunarServer/Plugins/Oauth/LinkedIn.cs: -------------------------------------------------------------------------------- 1 | using LunarLabs.Parser; 2 | using LunarLabs.Parser.JSON; 3 | using LunarLabs.WebServer.Core; 4 | using LunarLabs.WebServer.Utils; 5 | using System; 6 | using System.Collections.Generic; 7 | using System.Text; 8 | 9 | namespace LunarLabs.WebServer.Plugins.Oauth 10 | { 11 | public class LinkedInAuth : OauthConnection 12 | { 13 | public LinkedInAuth(LoggerCallback log, string app_url, string client_id, string client_secret, string redirect_uri) : base(log, app_url, client_id, client_secret, redirect_uri) 14 | { 15 | } 16 | 17 | public override OauthKind GetKind() 18 | { 19 | return OauthKind.LinkedIn; 20 | } 21 | 22 | public override string GetLoginURL() 23 | { 24 | return $"https://www.linkedin.com/oauth/v2/authorization?response_type=code&client_id={client_id}&redirect_uri={GetRedirectURL()}&state={OAUTH_ID}&scope=r_basicprofile%20r_emailaddress"; 25 | //r_fullprofile 26 | } 27 | 28 | private string Authorize(string code) 29 | { 30 | var url = $"https://www.linkedin.com/oauth/v2/accessToken"; 31 | 32 | var args = new Dictionary(); 33 | args["grant_type"] = "authorization_code"; 34 | args["code"] = code; 35 | args["redirect_uri"] = GetRedirectURL(); 36 | args["client_id"] = client_id; 37 | args["client_secret"] = client_secret; 38 | 39 | try 40 | { 41 | var json = HTTPUtils.Post(url, args); 42 | var root = JSONReader.ReadFromString(json); 43 | 44 | return root.GetString("access_token"); 45 | } 46 | catch (Exception e) 47 | { 48 | logger(LogLevel.Error, e.ToString()); 49 | return null; 50 | } 51 | } 52 | 53 | public override Profile Login(string code) 54 | { 55 | var token = Authorize(code); 56 | if (string.IsNullOrEmpty(token)) 57 | { 58 | return null; 59 | } 60 | 61 | var user = GetUser(null, token); 62 | if (user != null) 63 | { 64 | var profile = new Profile() 65 | { 66 | token = token, 67 | id = user.GetString("id"), 68 | name = user.GetString("formattedName"), 69 | email = user.GetString("emailAddress"), 70 | photo = user.GetString("pictureUrl"), 71 | birthday = user.GetString("date-of-birth"), 72 | data = user 73 | }; 74 | return profile; 75 | } 76 | 77 | return null; 78 | } 79 | 80 | public DataNode GetUser(string userid, string token) 81 | { 82 | try 83 | { 84 | if (userid == null) { userid = "~"; } 85 | var url = $"https://api.linkedin.com/v1/people/{userid}:(id,formatted-name,picture-url,email-address)?format=json"; 86 | var headers = new Dictionary(); 87 | headers["Authorization"] = "Bearer " + token; 88 | 89 | var json = HTTPUtils.Get(url, headers); 90 | var root = JSONReader.ReadFromString(json); 91 | 92 | return root; 93 | } 94 | catch (Exception e) 95 | { 96 | logger(LogLevel.Error, e.ToString()); 97 | return null; 98 | } 99 | } 100 | 101 | 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /LunarServer/Templates/AssetNodes.cs: -------------------------------------------------------------------------------- 1 | using LunarLabs.Templates; 2 | using LunarLabs.WebServer.Core; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.IO; 6 | 7 | namespace LunarLabs.WebServer.Templates 8 | { 9 | public class AssetNode : TemplateNode 10 | { 11 | public static Dictionary> assetList = new Dictionary>(); 12 | 13 | private string key; 14 | private string extension; 15 | 16 | private TemplateEngine engine; 17 | 18 | private bool skip; 19 | 20 | public AssetNode(Document document, string key, string extension, TemplateEngine engine) : base(document) 21 | { 22 | this.key = key; 23 | this.extension = extension; 24 | this.engine = engine; 25 | 26 | this.skip = false; 27 | 28 | bool found = false; 29 | 30 | List list; 31 | 32 | if (assetList.ContainsKey(extension)) 33 | { 34 | list = assetList[extension]; 35 | 36 | foreach (var entry in list) 37 | { 38 | if (entry == key) 39 | { 40 | found = true; 41 | break; 42 | } 43 | } 44 | } 45 | else 46 | { 47 | list = new List(); 48 | assetList[extension] = list; 49 | } 50 | 51 | if (!found) 52 | { 53 | list.Add(key); 54 | } 55 | 56 | foreach (var node in document.Nodes) 57 | { 58 | if (node == this) 59 | { 60 | break; 61 | } 62 | 63 | var an = node as AssetNode; 64 | if (an != null && an.extension == this.extension) 65 | { 66 | skip = true; 67 | break; 68 | } 69 | } 70 | } 71 | 72 | public override void Execute(RenderingContext context) 73 | { 74 | string fileName; 75 | 76 | if (this.engine.Server.Settings.Environment == Core.ServerEnvironment.Prod) 77 | { 78 | if (skip) 79 | { 80 | return; 81 | } 82 | 83 | fileName = AssetGroup.AssetFileName + extension; 84 | } 85 | else 86 | { 87 | fileName = key + "." + extension; 88 | 89 | var rootPath = this.engine.Server.Cache.filePath + extension + "/"; 90 | 91 | if (!File.Exists(rootPath + fileName)) 92 | { 93 | var minFile = fileName.Replace("."+extension, ".min."+ extension); 94 | if (File.Exists(rootPath + minFile)) 95 | { 96 | fileName = minFile; 97 | } 98 | else 99 | { 100 | throw new Exception("Could not find asset: " + fileName); 101 | } 102 | } 103 | } 104 | 105 | string html; 106 | 107 | switch (extension) 108 | { 109 | case "js": html = ""; break; 110 | case "css": html = ""; break; 111 | default: throw new Exception("Unsupport asset extension: " + extension); 112 | } 113 | 114 | context.output.Append(html); 115 | } 116 | } 117 | 118 | } 119 | -------------------------------------------------------------------------------- /LunarServer/Templates/Engine/Document.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using static LunarLabs.Templates.Compiler; 4 | 5 | namespace LunarLabs.Templates 6 | { 7 | public class Document 8 | { 9 | public TemplateNode Root { get; private set; } 10 | 11 | private List _nodes = new List(); 12 | public IEnumerable Nodes => _nodes; 13 | 14 | public void AddNode(TemplateNode node) 15 | { 16 | if (Root == null) 17 | { 18 | this.Root = node; 19 | } 20 | 21 | _nodes.Add(node); 22 | } 23 | 24 | public void Execute(RenderingContext context) 25 | { 26 | this.Root.Execute(context); 27 | } 28 | 29 | public TemplateNode CompileNode(ParseNode node, Compiler compiler) 30 | { 31 | switch (node.tag) 32 | { 33 | case "else": 34 | case "group": 35 | { 36 | if (node.nodes.Count == 1) 37 | { 38 | return CompileNode(node.nodes[0], compiler); 39 | } 40 | 41 | var result = new GroupNode(this); 42 | foreach (var child in node.nodes) 43 | { 44 | var temp = CompileNode(child, compiler); 45 | result.Nodes.Add(temp); 46 | } 47 | 48 | return result; 49 | } 50 | 51 | case "if": 52 | { 53 | var result = new IfNode(this, node.content); 54 | result.trueNode = CompileNode(node.nodes[0], compiler); 55 | result.falseNode = node.nodes.Count > 1 ? CompileNode(node.nodes[1], compiler) : null; 56 | return result; 57 | } 58 | 59 | case "text": 60 | return new TextNode(this, node.content); 61 | 62 | case "eval": 63 | { 64 | var key = node.content; 65 | var escape = !key.StartsWith("{"); 66 | if (!escape) 67 | { 68 | key = key.Substring(1); 69 | } 70 | return new EvalNode(this, key, escape); 71 | } 72 | 73 | case "each": 74 | { 75 | var result = new EachNode(this, node.content); 76 | result.inner = CompileNode(node.nodes[0], compiler); 77 | return result; 78 | } 79 | 80 | /*case "cache": 81 | { 82 | var inner = new GroupNode(this); 83 | foreach (var child in node.nodes) 84 | { 85 | var temp = CompileNode(child); 86 | inner.nodes.Add(temp); 87 | } 88 | 89 | return new CacheNode(this, node.content); 90 | }*/ 91 | 92 | default: 93 | { 94 | var customTag = compiler.GetCustomTag(node.tag); 95 | if (customTag != null) 96 | { 97 | var result = customTag(this, node.content); 98 | return result; 99 | } 100 | 101 | throw new Exception("Can not compile node of type " + node.tag); 102 | } 103 | } 104 | } 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /LunarServer/Entity/Entity.cs: -------------------------------------------------------------------------------- 1 | using LunarLabs.Parser; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | 6 | namespace LunarLabs.WebServer.Entity 7 | { 8 | public abstract class Entity 9 | { 10 | public string id; 11 | 12 | public EntityStore Store { get; private set; } 13 | 14 | internal void SetStore(EntityStore store) 15 | { 16 | this.Store = store; 17 | } 18 | 19 | public void Delete() 20 | { 21 | var type = this.GetType(); 22 | var collection = Store.GetCollection(type); 23 | collection.DeleteObject(this.id); 24 | Store.RequestBackgroundThread(); 25 | } 26 | 27 | public void Save() 28 | { 29 | var type = this.GetType(); 30 | var collection = Store.GetCollection(type); 31 | collection.RequestSave(); 32 | Store.RequestBackgroundThread(); 33 | } 34 | 35 | public static IEnumerable GetFields() where T: Entity 36 | { 37 | Type type = typeof(T); 38 | return type.GetFields().Where(f => f.IsPublic).Select(x => x.Name); 39 | } 40 | 41 | public DataNode Serialize() 42 | { 43 | Type type = this.GetType(); 44 | var result = DataNode.CreateObject(type.Name.ToLowerInvariant()); 45 | 46 | var fields = type.GetFields().Where(f => f.IsPublic); 47 | foreach (var field in fields) 48 | { 49 | var val = field.GetValue(this); 50 | if (val != null) { 51 | result.AddField(field.Name.ToLowerInvariant(), val); 52 | } 53 | } 54 | 55 | return result; 56 | } 57 | 58 | public void Deserialize(DataNode node) 59 | { 60 | Type type = this.GetType(); 61 | 62 | var fields = type.GetFields().Where(f => f.IsPublic); 63 | foreach (var field in fields) 64 | { 65 | if (!node.HasNode(field.Name)) 66 | { 67 | continue; 68 | } 69 | 70 | var fieldType = field.FieldType; 71 | if (fieldType == typeof(DateTime)) 72 | { 73 | var val = node.GetDateTime(field.Name); 74 | field.SetValue(this, val); 75 | } 76 | else 77 | if (fieldType == typeof(bool)) 78 | { 79 | var val = node.GetBool(field.Name); 80 | field.SetValue(this, val); 81 | } 82 | else 83 | if (fieldType == typeof(int)) 84 | { 85 | var val = node.GetInt32(field.Name); 86 | field.SetValue(this, val); 87 | } 88 | else 89 | if (fieldType.IsEnum) 90 | { 91 | var temp = node.GetString(field.Name); 92 | var values = Enum.GetValues(fieldType).Cast().ToArray(); 93 | var names = Enum.GetNames(fieldType).Cast().ToArray(); 94 | for (int i=0; i 15 | /// Http header too large to fit in buffer 16 | /// 17 | public EntityTooLargeException(string message) : base(message) 18 | { 19 | 20 | } 21 | 22 | public EntityTooLargeException(string message, Exception inner) : base(message, inner) 23 | { 24 | 25 | } 26 | } 27 | 28 | public class InvalidHttpResponseCodeException : Exception 29 | { 30 | public string ResponseCode { get; private set; } 31 | 32 | public string ResponseHeader { get; private set; } 33 | 34 | public string ResponseDetails { get; private set; } 35 | 36 | public InvalidHttpResponseCodeException() : base() 37 | { 38 | } 39 | 40 | public InvalidHttpResponseCodeException(string message) : base(message) 41 | { 42 | } 43 | 44 | public InvalidHttpResponseCodeException(string responseCode, string responseDetails, string responseHeader) : base(responseCode) 45 | { 46 | ResponseCode = responseCode; 47 | ResponseDetails = responseDetails; 48 | ResponseHeader = responseHeader; 49 | } 50 | 51 | public InvalidHttpResponseCodeException(string message, Exception inner) : base(message, inner) 52 | { 53 | } 54 | } 55 | 56 | public class SecWebSocketKeyMissingException : Exception 57 | { 58 | public SecWebSocketKeyMissingException() : base() 59 | { 60 | 61 | } 62 | 63 | public SecWebSocketKeyMissingException(string message) : base(message) 64 | { 65 | 66 | } 67 | 68 | public SecWebSocketKeyMissingException(string message, Exception inner) : base(message, inner) 69 | { 70 | 71 | } 72 | } 73 | 74 | public class ServerListenerSocketException : Exception 75 | { 76 | public ServerListenerSocketException() : base() 77 | { 78 | } 79 | 80 | public ServerListenerSocketException(string message) : base(message) 81 | { 82 | } 83 | 84 | public ServerListenerSocketException(string message, Exception inner) : base(message, inner) 85 | { 86 | } 87 | } 88 | 89 | public class WebSocketBufferOverflowException : Exception 90 | { 91 | public WebSocketBufferOverflowException() : base() 92 | { 93 | } 94 | 95 | public WebSocketBufferOverflowException(string message) : base(message) 96 | { 97 | } 98 | 99 | public WebSocketBufferOverflowException(string message, Exception inner) : base(message, inner) 100 | { 101 | } 102 | } 103 | 104 | public class WebSocketHandshakeFailedException : Exception 105 | { 106 | public WebSocketHandshakeFailedException() : base() 107 | { 108 | } 109 | 110 | public WebSocketHandshakeFailedException(string message) : base(message) 111 | { 112 | } 113 | 114 | public WebSocketHandshakeFailedException(string message, Exception inner) : base(message, inner) 115 | { 116 | } 117 | } 118 | 119 | public class WebSocketVersionNotSupportedException : Exception 120 | { 121 | public WebSocketVersionNotSupportedException() : base() 122 | { 123 | } 124 | 125 | public WebSocketVersionNotSupportedException(string message) : base(message) 126 | { 127 | } 128 | 129 | public WebSocketVersionNotSupportedException(string message, Exception inner) : base(message, inner) 130 | { 131 | } 132 | } 133 | } 134 | -------------------------------------------------------------------------------- /LunarServer/Templates/StoreNodes.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using System.Collections.Generic; 4 | using LunarLabs.Parser; 5 | using LunarLabs.Parser.XML; 6 | using LunarLabs.Parser.JSON; 7 | using LunarLabs.Parser.CSV; 8 | using LunarLabs.Parser.YAML; 9 | using LunarLabs.Templates; 10 | 11 | namespace LunarLabs.WebServer.Templates 12 | { 13 | public class StoreEntry 14 | { 15 | private string path; 16 | private string targetExtension; 17 | 18 | public DateTime lastTime { get; private set; } 19 | public DataNode content { get; private set; } 20 | 21 | public StoreEntry(string path) 22 | { 23 | this.path = path; 24 | this.targetExtension = Path.GetExtension(path); 25 | this.lastTime = DateTime.MinValue; 26 | } 27 | 28 | internal void Reload() 29 | { 30 | var diff = DateTime.UtcNow - lastTime; 31 | 32 | if (diff.TotalSeconds < 10) 33 | { 34 | return; 35 | } 36 | 37 | var lastWriteTime = File.GetLastWriteTime(path); 38 | if (lastWriteTime < lastTime) 39 | { 40 | return; 41 | } 42 | 43 | var contents = File.ReadAllText(path); 44 | 45 | switch (targetExtension) 46 | { 47 | case ".xml": content = XMLReader.ReadFromString(contents); break; 48 | case ".json": content = JSONReader.ReadFromString(contents); break; 49 | case ".csv": content = CSVReader.ReadFromString(contents); break; 50 | case ".yaml": content = YAMLReader.ReadFromString(contents); break; 51 | default: throw new TemplateException("Unsupported store extension: " + targetExtension); 52 | } 53 | 54 | if (content.Name == null) 55 | { 56 | content = content.GetNodeByIndex(0); 57 | } 58 | 59 | lastTime = DateTime.UtcNow; 60 | } 61 | } 62 | 63 | public class StoreNode : TemplateNode 64 | { 65 | public static Dictionary _storeMap = new Dictionary(); 66 | private string _key; 67 | 68 | private TemplateEngine engine; 69 | 70 | private static readonly string[] _extensions = new string[] { ".xml", ".json", ".csv", ".yaml" }; 71 | 72 | public StoreNode(Document document, string key, TemplateEngine engine) : base(document) 73 | { 74 | this._key = key; 75 | this.engine = engine; 76 | } 77 | 78 | private DataNode FetchStore() 79 | { 80 | StoreEntry entry; 81 | 82 | if (_storeMap.ContainsKey(_key)) 83 | { 84 | entry = _storeMap[_key]; 85 | entry.Reload(); 86 | 87 | return entry.content; 88 | } 89 | 90 | var basePath = this.engine.Server.Settings.Path + "store"; 91 | 92 | string fileName = null; 93 | 94 | foreach (var extension in _extensions) 95 | { 96 | var temp = basePath + "/" + _key + extension; 97 | if (File.Exists(temp)) 98 | { 99 | fileName = temp; 100 | break; 101 | } 102 | } 103 | 104 | if (fileName == null) 105 | { 106 | throw new TemplateException("Could not find any file for store: " + _key); 107 | } 108 | 109 | entry = new StoreEntry(fileName); 110 | 111 | _storeMap[_key] = entry; 112 | 113 | entry.Reload(); 114 | return entry.content; 115 | } 116 | 117 | public override void Execute(RenderingContext context) 118 | { 119 | var store = FetchStore(); 120 | context.Set(_key, store); 121 | } 122 | } 123 | 124 | } 125 | -------------------------------------------------------------------------------- /LunarServer/Utils/ArrayPointer.cs: -------------------------------------------------------------------------------- 1 | namespace LunarLabs.WebServer.Utils 2 | { 3 | public struct ArrayPointer 4 | { 5 | T[] array; 6 | long address; 7 | 8 | public ArrayPointer(T[] array) 9 | { 10 | this.array = array; 11 | this.address = 0; 12 | } 13 | 14 | public int Address 15 | { 16 | get 17 | { 18 | return (int)address; 19 | } 20 | 21 | set 22 | { 23 | address = value; 24 | } 25 | } 26 | 27 | public long LongAddress 28 | { 29 | get 30 | { 31 | return address; 32 | } 33 | 34 | set 35 | { 36 | address = value; 37 | } 38 | } 39 | 40 | public T Value 41 | { 42 | get 43 | { 44 | return this.array[this.address]; 45 | } 46 | 47 | set 48 | { 49 | this.array[this.address] = value; 50 | } 51 | } 52 | 53 | public int ArrayLength 54 | { 55 | get 56 | { 57 | return array.Length; 58 | } 59 | } 60 | 61 | public long ArrayLongLength 62 | { 63 | get 64 | { 65 | return array.LongLength; 66 | } 67 | } 68 | 69 | public T[] SourceArray 70 | { 71 | get 72 | { 73 | return this.array; 74 | } 75 | } 76 | 77 | public T this[int index] 78 | { 79 | get 80 | { 81 | return this.array[this.address + index]; 82 | } 83 | 84 | set 85 | { 86 | this.array[this.address + index] = value; 87 | } 88 | } 89 | 90 | public override bool Equals(object obj) 91 | { 92 | return obj is ArrayPointer && this == (ArrayPointer)obj; 93 | } 94 | 95 | public override int GetHashCode() 96 | { 97 | return (int)address; 98 | } 99 | 100 | public static ArrayPointer operator +(ArrayPointer ap, int offset) 101 | { 102 | ArrayPointer temp = new ArrayPointer(ap.array); 103 | temp.address = ap.address + offset; 104 | return temp; 105 | } 106 | 107 | public static ArrayPointer operator +(ArrayPointer ap, long offset) 108 | { 109 | ArrayPointer temp = new ArrayPointer(ap.array); 110 | temp.address = ap.address + offset; 111 | return temp; 112 | } 113 | 114 | public static ArrayPointer operator +(int offset, ArrayPointer ap) 115 | { 116 | ArrayPointer temp = new ArrayPointer(ap.array); 117 | temp.address = ap.address + offset; 118 | return temp; 119 | } 120 | 121 | public static ArrayPointer operator +(long offset, ArrayPointer ap) 122 | { 123 | ArrayPointer temp = new ArrayPointer(ap.array); 124 | temp.address = ap.address + offset; 125 | return temp; 126 | } 127 | 128 | public static ArrayPointer operator ++(ArrayPointer ap) 129 | { 130 | ArrayPointer temp = new ArrayPointer(ap.array); 131 | temp.address = ap.address + 1; 132 | return temp; 133 | } 134 | 135 | public static ArrayPointer operator --(ArrayPointer ap) 136 | { 137 | ArrayPointer temp = new ArrayPointer(ap.array); 138 | temp.address = ap.address - 1; 139 | return temp; 140 | } 141 | 142 | public static bool operator ==(ArrayPointer ap1, ArrayPointer ap2) 143 | { 144 | return ap1.array == ap2.array && ap1.address == ap2.address; 145 | } 146 | 147 | public static bool operator !=(ArrayPointer ap1, ArrayPointer ap2) 148 | { 149 | return ap1.array != ap2.array || ap1.address != ap2.address; 150 | } 151 | } 152 | } 153 | 154 | 155 | -------------------------------------------------------------------------------- /LunarServer/Websockets/BinaryReaderWriter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | 4 | namespace LunarLabs.WebSockets 5 | { 6 | internal class BinaryReaderWriter 7 | { 8 | public static void ReadExactly(int length, Stream stream, ArraySegment buffer) 9 | { 10 | if (length == 0) 11 | { 12 | return; 13 | } 14 | 15 | if (buffer.Count < length) 16 | { 17 | // This will happen if the calling function supplied a buffer that was too small to fit the payload of the websocket frame. 18 | // Note that this can happen on the close handshake where the message size can be larger than the regular payload 19 | throw new InternalBufferOverflowException($"Unable to read {length} bytes into buffer (offset: {buffer.Offset} size: {buffer.Count}). Use a larger read buffer"); 20 | } 21 | 22 | int offset = 0; 23 | do 24 | { 25 | int bytesRead = stream.Read(buffer.Array, buffer.Offset + offset, length - offset); 26 | if (bytesRead == 0) 27 | { 28 | throw new EndOfStreamException(string.Format("Unexpected end of stream encountered whilst attempting to read {0:#,##0} bytes", length)); 29 | } 30 | 31 | offset += bytesRead; 32 | } while (offset < length); 33 | 34 | return; 35 | } 36 | 37 | public static ushort ReadUShortExactly(Stream stream, bool isLittleEndian, ArraySegment buffer) 38 | { 39 | ReadExactly(2, stream, buffer); 40 | 41 | if (!isLittleEndian) 42 | { 43 | Array.Reverse(buffer.Array, buffer.Offset, 2); // big endian 44 | } 45 | 46 | return BitConverter.ToUInt16(buffer.Array, buffer.Offset); 47 | } 48 | 49 | public static ulong ReadULongExactly(Stream stream, bool isLittleEndian, ArraySegment buffer) 50 | { 51 | ReadExactly(8, stream, buffer); 52 | 53 | if (!isLittleEndian) 54 | { 55 | Array.Reverse(buffer.Array, buffer.Offset, 8); // big endian 56 | } 57 | 58 | return BitConverter.ToUInt64(buffer.Array, buffer.Offset); 59 | } 60 | 61 | public static long ReadLongExactly(Stream stream, bool isLittleEndian, ArraySegment buffer) 62 | { 63 | ReadExactly(8, stream, buffer); 64 | 65 | if (!isLittleEndian) 66 | { 67 | Array.Reverse(buffer.Array, buffer.Offset, 8); // big endian 68 | } 69 | 70 | return BitConverter.ToInt64(buffer.Array, buffer.Offset); 71 | } 72 | 73 | public static void WriteInt(int value, Stream stream, bool isLittleEndian) 74 | { 75 | byte[] buffer = BitConverter.GetBytes(value); 76 | if (BitConverter.IsLittleEndian && !isLittleEndian) 77 | { 78 | Array.Reverse(buffer); 79 | } 80 | 81 | stream.Write(buffer, 0, buffer.Length); 82 | } 83 | 84 | public static void WriteULong(ulong value, Stream stream, bool isLittleEndian) 85 | { 86 | byte[] buffer = BitConverter.GetBytes(value); 87 | if (BitConverter.IsLittleEndian && ! isLittleEndian) 88 | { 89 | Array.Reverse(buffer); 90 | } 91 | 92 | stream.Write(buffer, 0, buffer.Length); 93 | } 94 | 95 | public static void WriteLong(long value, Stream stream, bool isLittleEndian) 96 | { 97 | byte[] buffer = BitConverter.GetBytes(value); 98 | if (BitConverter.IsLittleEndian && !isLittleEndian) 99 | { 100 | Array.Reverse(buffer); 101 | } 102 | 103 | stream.Write(buffer, 0, buffer.Length); 104 | } 105 | 106 | public static void WriteUShort(ushort value, Stream stream, bool isLittleEndian) 107 | { 108 | byte[] buffer = BitConverter.GetBytes(value); 109 | if (BitConverter.IsLittleEndian && !isLittleEndian) 110 | { 111 | Array.Reverse(buffer); 112 | } 113 | 114 | stream.Write(buffer, 0, buffer.Length); 115 | } 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /SynkMVC/Utils/ArrayPointer.cs: -------------------------------------------------------------------------------- 1 | namespace LunarLabs.WebMVC 2 | { 3 | public struct ArrayPointer 4 | { 5 | T[] array; 6 | long address; 7 | 8 | public ArrayPointer(T[] array) 9 | { 10 | this.array = array; 11 | this.address = 0; 12 | } 13 | 14 | public int Address 15 | { 16 | get 17 | { 18 | return (int)address; 19 | } 20 | 21 | set 22 | { 23 | address = value; 24 | } 25 | } 26 | 27 | public long LongAddress 28 | { 29 | get 30 | { 31 | return address; 32 | } 33 | 34 | set 35 | { 36 | address = value; 37 | } 38 | } 39 | 40 | public T Value 41 | { 42 | get 43 | { 44 | return this.array[this.address]; 45 | } 46 | 47 | set 48 | { 49 | this.array[this.address] = value; 50 | } 51 | } 52 | 53 | public int ArrayLength 54 | { 55 | get 56 | { 57 | return array.Length; 58 | } 59 | } 60 | 61 | public long ArrayLongLength 62 | { 63 | get 64 | { 65 | return array.LongLength; 66 | } 67 | } 68 | 69 | public T[] SourceArray 70 | { 71 | get 72 | { 73 | return this.array; 74 | } 75 | } 76 | 77 | public T this[int index] 78 | { 79 | get 80 | { 81 | return this.array[this.address + index]; 82 | } 83 | 84 | set 85 | { 86 | this.array[this.address + index] = value; 87 | } 88 | } 89 | 90 | public override bool Equals(object obj) 91 | { 92 | return obj is ArrayPointer && this == (ArrayPointer)obj; 93 | } 94 | 95 | public override int GetHashCode() 96 | { 97 | return (int)address; 98 | } 99 | 100 | public static ArrayPointer operator +(ArrayPointer ap, int offset) 101 | { 102 | ArrayPointer temp = new ArrayPointer(ap.array); 103 | temp.address = ap.address + offset; 104 | return temp; 105 | } 106 | 107 | public static ArrayPointer operator +(ArrayPointer ap, long offset) 108 | { 109 | ArrayPointer temp = new ArrayPointer(ap.array); 110 | temp.address = ap.address + offset; 111 | return temp; 112 | } 113 | 114 | public static ArrayPointer operator +(int offset, ArrayPointer ap) 115 | { 116 | ArrayPointer temp = new ArrayPointer(ap.array); 117 | temp.address = ap.address + offset; 118 | return temp; 119 | } 120 | 121 | public static ArrayPointer operator +(long offset, ArrayPointer ap) 122 | { 123 | ArrayPointer temp = new ArrayPointer(ap.array); 124 | temp.address = ap.address + offset; 125 | return temp; 126 | } 127 | 128 | public static ArrayPointer operator ++(ArrayPointer ap) 129 | { 130 | ArrayPointer temp = new ArrayPointer(ap.array); 131 | temp.address = ap.address + 1; 132 | return temp; 133 | } 134 | 135 | public static ArrayPointer operator --(ArrayPointer ap) 136 | { 137 | ArrayPointer temp = new ArrayPointer(ap.array); 138 | temp.address = ap.address - 1; 139 | return temp; 140 | } 141 | 142 | public static bool operator ==(ArrayPointer ap1, ArrayPointer ap2) 143 | { 144 | return ap1.array == ap2.array && ap1.address == ap2.address; 145 | } 146 | 147 | public static bool operator !=(ArrayPointer ap1, ArrayPointer ap2) 148 | { 149 | return ap1.array != ap2.array || ap1.address != ap2.address; 150 | } 151 | } 152 | } 153 | 154 | 155 | -------------------------------------------------------------------------------- /LunarServer/Utils/DetectionUtils.cs: -------------------------------------------------------------------------------- 1 | using LunarLabs.WebServer.HTTP; 2 | using LunarLabs.WebServer.Templates; 3 | using System.Net; 4 | 5 | namespace LunarLabs.WebServer.Utils 6 | { 7 | public enum OSType 8 | { 9 | Unknown, 10 | Windows, 11 | Linux, 12 | OSX, 13 | Android, 14 | iOS, 15 | WindowsPhone, 16 | XboxOne, 17 | Playstation4, 18 | } 19 | 20 | public static class DetectionUtils 21 | { 22 | private static string[] uaHttpHeaders = { 23 | // The default User-Agent string. 24 | "User-Agent", 25 | // Header can occur on devices using Opera Mini. 26 | "X-OperaMini-Phone-UA", 27 | // Vodafone specific header: http://www.seoprinciple.com/mobile-web-community-still-angry-at-vodafone/24/ 28 | "X-Device-User-Agent", 29 | "X-Original-User-Agent", 30 | }; 31 | 32 | public static OSType DetectOS(this HTTPRequest request) 33 | { 34 | foreach (var ua in uaHttpHeaders) 35 | { 36 | if (!request.headers.ContainsKey(ua)) 37 | { 38 | continue; 39 | } 40 | 41 | var value = request.headers[ua]; 42 | if (value == null) 43 | { 44 | continue; 45 | } 46 | 47 | if (value.Contains("Windows Phone")) { return OSType.WindowsPhone; } 48 | 49 | if (value.Contains("Android")) { return OSType.Android; } 50 | 51 | if (value.Contains("AppleTV") || value.Contains("iPhone") || value.Contains("iPad")) { return OSType.iOS; } 52 | 53 | if (value.Contains("Xbox One")) { return OSType.XboxOne; } 54 | 55 | if (value.Contains("PlayStation 4")) { return OSType.Playstation4; } 56 | 57 | if (value.Contains("Win64") || value.Contains("WOW64")) { return OSType.Windows; } 58 | 59 | if (value.Contains("OS X")) { return OSType.OSX; } 60 | 61 | if (value.Contains("Linux")) { return OSType.Linux; } 62 | } 63 | 64 | return OSType.Unknown; 65 | } 66 | 67 | private static string[] ipHttpHeaders = { 68 | "Remote-Addr", 69 | "X_Forwarded_For", 70 | "Client-Ip", 71 | "X-Forwarded", 72 | "X-Cluster-Client-Ip", 73 | "Forwarded-For", 74 | "Forwarded" 75 | }; 76 | 77 | public static string DetectCountry(this HTTPRequest request) 78 | { 79 | foreach (var ipHeader in ipHttpHeaders) 80 | { 81 | if (!request.headers.ContainsKey(ipHeader)) 82 | { 83 | continue; 84 | } 85 | 86 | var value = request.headers[ipHeader]; 87 | if (value == null) 88 | { 89 | continue; 90 | } 91 | 92 | var ipAddress = IPAddress.Parse(value); 93 | var ipBytes = ipAddress.GetAddressBytes(); 94 | uint ip = (uint)ipBytes[0] << 24; 95 | ip += (uint)ipBytes[1] << 16; 96 | ip += (uint)ipBytes[2] << 8; 97 | ip += (uint)ipBytes[3]; 98 | 99 | return CountryUtils.IPToCountry(ip); 100 | } 101 | 102 | return ""; 103 | } 104 | 105 | const string LanguageHeader = "Accept-Language"; 106 | 107 | public static string DetectLanguage(this HTTPRequest request) 108 | { 109 | if (request.headers.ContainsKey(LanguageHeader)) 110 | { 111 | var languages = request.headers[LanguageHeader].Split(new char[] { ',', ';' }); 112 | foreach (var lang in languages) 113 | { 114 | string code; 115 | if (lang.Contains("-")) 116 | { 117 | code = lang.Split('-')[0]; 118 | } 119 | else 120 | { 121 | code = lang; 122 | } 123 | 124 | if (LocalizationManager.HasLanguage(code)) 125 | { 126 | return code; 127 | } 128 | } 129 | } 130 | 131 | return "en"; 132 | } 133 | 134 | } 135 | } 136 | -------------------------------------------------------------------------------- /LunarServer/Core/AssetCache.cs: -------------------------------------------------------------------------------- 1 | using LunarLabs.WebServer.Minifiers; 2 | using LunarLabs.WebServer.Templates; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.IO; 6 | using System.Text; 7 | 8 | namespace LunarLabs.WebServer.Core 9 | { 10 | public class AssetGroup 11 | { 12 | public static readonly string AssetFileName = "lunarpack."; 13 | 14 | public string extension; 15 | public string contentType; 16 | public Func minifier; 17 | 18 | private int fileCount; 19 | 20 | public string requestPath; 21 | 22 | public AssetGroup(string extension, string contentType, Func minifier) 23 | { 24 | this.extension = extension; 25 | this.contentType = contentType; 26 | this.minifier = minifier; 27 | this.fileCount = 0; 28 | } 29 | 30 | public CacheEntry Update(string filePath) 31 | { 32 | StringBuilder content; 33 | 34 | lock (AssetNode.assetList) 35 | { 36 | if (!AssetNode.assetList.ContainsKey(extension)) 37 | { 38 | return null; 39 | } 40 | 41 | var source = AssetNode.assetList[extension]; 42 | 43 | if (source.Count == this.fileCount) 44 | { 45 | return null; 46 | } 47 | 48 | var path = filePath + extension; 49 | 50 | content = new StringBuilder(); 51 | 52 | foreach (var file in source) 53 | { 54 | var srcName = path + "/" + file + "." + extension; 55 | if (!File.Exists(srcName)) 56 | { 57 | var minFile = srcName.Replace("." + extension, ".min." + extension); 58 | if (File.Exists(minFile)) 59 | { 60 | srcName = minFile; 61 | } 62 | else 63 | { 64 | throw new Exception("Could not find: " + srcName); 65 | } 66 | } 67 | var temp = File.ReadAllText(srcName); 68 | 69 | if (minifier != null) 70 | { 71 | temp = minifier(temp); 72 | } 73 | 74 | content.Append(temp); 75 | content.AppendLine(); 76 | } 77 | 78 | this.fileCount = source.Count; 79 | } 80 | 81 | var bytes = Encoding.UTF8.GetBytes(content.ToString()); 82 | 83 | var fileName = AssetFileName + extension; 84 | 85 | var entry = new CacheEntry(); 86 | entry.hash = StringUtils.MD5(bytes); 87 | entry.lastModified = DateTime.UtcNow; 88 | entry.path = null; 89 | entry.contentType = contentType; 90 | 91 | entry.isCompressed = true; 92 | entry.bytes = bytes.GZIPCompress(); 93 | 94 | this.requestPath = "/" + extension + "/" + fileName; 95 | 96 | return entry; 97 | } 98 | } 99 | 100 | public class AssetCache : FileCache 101 | { 102 | private List groups = new List(); 103 | 104 | public AssetCache(LoggerCallback logger, string filePath) : base(logger, filePath) 105 | { 106 | BuildAssetCache("js", "application/javascript", x => JSMinifier.Compress(x)); 107 | BuildAssetCache("css", "text/css", x => CSSMinifier.Compress(x)); 108 | } 109 | 110 | private void BuildAssetCache(string filter, string contentType, Func minifier = null) 111 | { 112 | groups.Add(new AssetGroup(filter, contentType, minifier)); 113 | } 114 | 115 | public void Update() 116 | { 117 | lock (groups) 118 | { 119 | foreach (var group in groups) 120 | { 121 | var entry = group.Update(this.filePath); 122 | if (entry != null) 123 | { 124 | lock (_files) 125 | { 126 | _files[group.requestPath] = entry; 127 | } 128 | } 129 | } 130 | } 131 | } 132 | } 133 | } 134 | -------------------------------------------------------------------------------- /LunarServer/HTTP/HTTPResponse.cs: -------------------------------------------------------------------------------- 1 | using LunarLabs.WebServer.Core; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.IO; 5 | using System.Linq; 6 | using System.Text; 7 | 8 | namespace LunarLabs.WebServer.HTTP 9 | { 10 | public enum HTTPCode 11 | { 12 | OK = 200, 13 | NoContent = 204, 14 | Redirect = 302, //https://en.wikipedia.org/wiki/HTTP_302 15 | NotModified = 304, 16 | BadRequest = 400, 17 | Unauthorized = 401, 18 | Forbidden = 403, 19 | NotFound = 404, 20 | InternalServerError = 500, 21 | ServiceUnavailable= 503, 22 | } 23 | 24 | public class HTTPResponse 25 | { 26 | public HTTPCode code; 27 | public Dictionary headers = new Dictionary(); 28 | public byte[] bytes; 29 | public DateTime date; 30 | public TimeSpan expiration = TimeSpan.FromSeconds(0); 31 | 32 | public HTTPResponse() 33 | { 34 | this.date = DateTime.UtcNow; 35 | 36 | headers["Date"] = date.ToString("r"); 37 | headers["Server"] = "LunarServer"; 38 | headers["Connection"] = "close"; 39 | headers["Access-Control-Allow-Origin"] = "*"; 40 | //headers["Content-Type"] = "text/html"; 41 | 42 | /* 43 | Transfer-Encoding: chunked 44 | Date: Sat, 28 Nov 2009 04:36:25 GMT 45 | Server: LiteSpeed 46 | Connection: close 47 | X-Powered-By: W3 Total Cache/0.8 48 | Pragma: public 49 | Etag: "pub1259380237;gz" 50 | Cache-Control: max-age=3600, public 51 | Content-Type: text/html; charset=UTF-8 52 | Last-Modified: Sat, 28 Nov 2009 03:50:37 GMT 53 | X-Pingback: http://net.tutsplus.com/xmlrpc.php 54 | Content-Encoding: gzip 55 | Vary: Accept-Encoding, Cookie, User-Agent 56 | */ 57 | } 58 | 59 | public static HTTPResponse Redirect(string url) 60 | { 61 | var result = new HTTPResponse(); 62 | result.code = HTTPCode.Redirect; 63 | result.bytes = new byte[0]; 64 | result.headers["Location"] = url; 65 | return result; 66 | } 67 | 68 | //https://stackoverflow.com/questions/1587667/should-http-304-not-modified-responses-contain-cache-control-headers 69 | public static HTTPResponse NotModified(string etag = null, int maxAge = 0) 70 | { 71 | var result = new HTTPResponse(); 72 | result.code = HTTPCode.NotModified; 73 | result.bytes = new byte[0]; 74 | 75 | if (maxAge>0) 76 | { 77 | result.headers["Cache-Control"] = "max-age=" + maxAge + ", public"; 78 | } 79 | 80 | if (etag != null) 81 | { 82 | result.headers["ETag"] = etag; 83 | } 84 | return result; 85 | } 86 | 87 | public static HTTPResponse Options(string allowedMethods = null) 88 | { 89 | var result = new HTTPResponse(); 90 | result.code = HTTPCode.NoContent; 91 | result.bytes = new byte[0]; 92 | 93 | if (string.IsNullOrEmpty(allowedMethods)) 94 | { 95 | allowedMethods = "OPTIONS, GET, POST"; 96 | } 97 | 98 | result.headers["Allow"] = allowedMethods; 99 | result.headers["Access-Control-Allow-Methods"] = allowedMethods; 100 | result.headers["Access-Control-Allow-Headers"] = "*"; 101 | return result; 102 | } 103 | 104 | public static HTTPResponse FromString(string content, HTTPCode code = HTTPCode.OK, bool compress = false, string contentType= "text/html") 105 | { 106 | var bytes = Encoding.UTF8.GetBytes(content); 107 | if (compress) 108 | { 109 | bytes = bytes.GZIPCompress(); 110 | } 111 | 112 | var result = new HTTPResponse(); 113 | result.code = code; 114 | result.bytes = bytes; 115 | 116 | result.headers["Content-Type"] = contentType; 117 | 118 | if (compress) 119 | { 120 | result.headers["Content-Encoding"] = "gzip"; 121 | } 122 | 123 | return result; 124 | } 125 | 126 | public static HTTPResponse FromBytes(byte[] bytes, string contentType = null) 127 | { 128 | if (string.IsNullOrEmpty(contentType)) 129 | { 130 | contentType = "application/octet-stream"; 131 | } 132 | 133 | var result = new HTTPResponse(); 134 | result.code = HTTPCode.OK; 135 | result.bytes = bytes; 136 | 137 | result.headers["Content-Type"] = contentType; 138 | 139 | return result; 140 | } 141 | 142 | } 143 | } 144 | -------------------------------------------------------------------------------- /LunarServer/Plugins/Oauth/Facebook.cs: -------------------------------------------------------------------------------- 1 | using LunarLabs.Parser; 2 | using LunarLabs.Parser.JSON; 3 | using LunarLabs.WebServer.Core; 4 | using LunarLabs.WebServer.Utils; 5 | using System; 6 | using System.Collections.Generic; 7 | 8 | namespace LunarLabs.WebServer.Plugins.Oauth 9 | { 10 | public enum FacebookField 11 | { 12 | Id, 13 | Name, 14 | Gender, 15 | Picture, 16 | Email 17 | } 18 | 19 | public class FacebookAuth: OauthConnection 20 | { 21 | private const string authorization_base_url = "https://www.facebook.com/dialog/oauth"; 22 | private const string token_url = "https://graph.facebook.com/oauth/access_token"; 23 | 24 | public FacebookAuth(LoggerCallback log, string app_url, string client_id, string client_secret, string localPath) : base(log, app_url, client_id, client_secret, localPath) 25 | { 26 | } 27 | 28 | public override OauthKind GetKind() 29 | { 30 | return OauthKind.Facebook; 31 | } 32 | 33 | public override string GetLoginURL() 34 | { 35 | var url = authorization_base_url + "?response_type=code&redirect_uri="+GetRedirectURL()+"&client_id=" + client_id; 36 | url += "&auth_type=rerequest"; 37 | url += "&scope=email,public_profile"; //,user_birthday"; 38 | return url; 39 | } 40 | 41 | private string Authorize(string code) 42 | { 43 | var url = $"{token_url}?client_id={client_id}&redirect_uri={GetRedirectURL()}&client_secret={client_secret}&code={code}"; 44 | 45 | try 46 | { 47 | var json = HTTPUtils.Get(url); 48 | var root = JSONReader.ReadFromString(json); 49 | 50 | return root.GetString("access_token"); 51 | } 52 | catch (Exception e) 53 | { 54 | logger(LogLevel.Error, e.ToString()); 55 | return null; 56 | } 57 | } 58 | 59 | 60 | public override Profile Login(string code) 61 | { 62 | var token = Authorize(code); 63 | if (string.IsNullOrEmpty(token)) 64 | { 65 | return null; 66 | } 67 | 68 | var user = GetUser(null, token, new FacebookField[] { FacebookField.Id, FacebookField.Name, FacebookField.Gender, FacebookField.Picture, FacebookField.Email } ); 69 | if (user != null) { 70 | var profile = new Profile() 71 | { 72 | token = token, 73 | id = user.GetString("id"), 74 | name = user.GetString("name"), 75 | email = user.GetString("email"), 76 | photo = user.GetNode("picture").GetNode("data").GetString("url"), 77 | birthday = "",//user.GetString("birthday"), 78 | data = user 79 | }; 80 | 81 | var likes = GetLikes(profile.id, token); 82 | 83 | return profile; 84 | } 85 | 86 | return null; 87 | } 88 | 89 | public DataNode GetUser(string userid, string token, IEnumerable fields) 90 | { 91 | try 92 | { 93 | string fieldStr = ""; 94 | foreach (var field in fields) 95 | { 96 | if (fieldStr.Length>0) { fieldStr += ","; } 97 | 98 | var fieldName = field.ToString(); 99 | fieldName = char.ToLowerInvariant(fieldName[0]) + fieldName.Substring(1); 100 | fieldStr += fieldName; 101 | } 102 | 103 | if (userid == null) { userid = "me"; } 104 | var url = $"https://graph.facebook.com/{userid}?access_token={token}&fields={fieldStr}"; 105 | var json = HTTPUtils.Get(url); 106 | var root = JSONReader.ReadFromString(json); 107 | 108 | return root; 109 | } 110 | catch (Exception e) 111 | { 112 | logger(LogLevel.Error, e.ToString()); 113 | return null; 114 | } 115 | } 116 | 117 | 118 | public DataNode GetLikes(string userid, string token) 119 | { 120 | try 121 | { 122 | var url = $"https://graph.facebook.com/{userid}/likes?access_token={token}"; 123 | var json = HTTPUtils.Get(url); 124 | var root = JSONReader.ReadFromString(json); 125 | 126 | return root; 127 | } 128 | catch (Exception e) 129 | { 130 | logger(LogLevel.Error, e.ToString()); 131 | return null; 132 | } 133 | } 134 | 135 | } 136 | } -------------------------------------------------------------------------------- /SynkMVC/Core/Module.cs: -------------------------------------------------------------------------------- 1 | using LunarLabs.WebMVC.Model; 2 | using LunarLabs.WebServer; 3 | using LunarLabs.WebServer.Core; 4 | using System; 5 | using System.Collections.Generic; 6 | using System.IO; 7 | using System.Linq; 8 | using System.Text; 9 | 10 | namespace LunarLabs.WebMVC 11 | { 12 | [AttributeUsage(AttributeTargets.Class, AllowMultiple = true, Inherited = true)] 13 | public class ModuleAttribute : Attribute 14 | { 15 | } 16 | 17 | [Module] 18 | public abstract class Module 19 | { 20 | public string name; 21 | public string title; 22 | 23 | public Module() 24 | { 25 | this.name = this.GetType().Name.ToLower(); 26 | this.title = name + "???"; 27 | } 28 | 29 | public virtual List Search(SynkContext context, string entityClass, string term) 30 | { 31 | List entities = null; 32 | var template = context.database.CreateEntity(entityClass); 33 | if (template != null) 34 | { 35 | var cond = template.GetSearch(term); 36 | 37 | if (cond != null) 38 | { 39 | entities = context.database.FetchAllEntities(entityClass, cond); 40 | } 41 | } 42 | return entities; 43 | } 44 | 45 | public virtual bool CheckPermissions(SynkContext context, User user, string action) 46 | { 47 | return true; 48 | } 49 | 50 | public string getLink() 51 | { 52 | return "synkNav().setModule('"+this.name+"').go();"; 53 | } 54 | 55 | public string getTitle(SynkContext context) 56 | { 57 | return context.Translate("module_" + this.name); 58 | } 59 | 60 | 61 | 62 | public void progress(SynkContext context) 63 | { 64 | /*$progressFile = "tmp/".session_id(). ".bar"; 65 | if (file_exists($progressFile)) 66 | { 67 | $progress = file_get_contents($progressFile); 68 | } 69 | else 70 | { 71 | $progress = '0'; 72 | } 73 | 74 | echo $progress;*/ 75 | } 76 | 77 | public virtual void beforeRender(SynkContext context) 78 | { 79 | 80 | } 81 | 82 | public bool render(SynkContext context) 83 | { 84 | var viewFile = context.targetView; 85 | var viewPath = context.currentModule.name + "/" + viewFile; 86 | 87 | if (!System.IO.File.Exists("views/" + viewPath + ".html")) 88 | { 89 | viewPath = "common/" + viewFile; 90 | 91 | 92 | if (!System.IO.File.Exists("views/" + viewPath + ".html")) 93 | { 94 | context.kill("Could not load view '" + context.targetView + "' for " + context.currentModule.name); 95 | return false; 96 | } 97 | } 98 | 99 | context.PushTemplate(viewPath); 100 | context.Render(); 101 | return true; 102 | } 103 | 104 | public void paginate(SynkContext context) 105 | { 106 | int page; 107 | int.TryParse(context.request.GetVariable("page"), out page); 108 | context.request.session.Set("page", page.ToString()); 109 | //context.page = page; 110 | 111 | this.render(context); 112 | } 113 | 114 | public virtual void afterRender(SynkContext context, string layoutTemplate) 115 | { 116 | } 117 | 118 | public virtual void OnInvalidAction(SynkContext context, string action) 119 | { 120 | context.kill("Invalid action " + action + " on module " + this.name); 121 | } 122 | 123 | public void InitRoutes(MVC mvc) 124 | { 125 | var type = this.GetType(); 126 | foreach (var method in type.GetMethods()) 127 | { 128 | if (method.Name.StartsWith("On")) 129 | { 130 | var action = method.Name.Substring(2).ToLower(); 131 | 132 | if (action.Equals("invalidaction")) 133 | { 134 | continue; 135 | } 136 | 137 | mvc.site.Get(this.name + "." + action, (req) => { 138 | var args = "module=" + this.name + "&action=" + action; 139 | return new SynkContext(mvc, req); 140 | }); 141 | } 142 | } 143 | } 144 | 145 | public string GetActionURL(string action) 146 | { 147 | return this.name + "." + action; 148 | } 149 | } 150 | 151 | 152 | } 153 | -------------------------------------------------------------------------------- /LunarServer/Templates/Engine/DateNodes.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Globalization; 3 | 4 | namespace LunarLabs.Templates 5 | { 6 | public class DateNode : TemplateNode 7 | { 8 | private static string[] monthNames = new string[] 9 | { 10 | "None", "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December" 11 | }; 12 | 13 | private RenderingKey key; 14 | private string format; 15 | 16 | public DateNode(Document document, string key, string format = "dd yyyy | hh:mm tt") : base(document) 17 | { 18 | this.key = RenderingKey.Parse(key, RenderingType.DateTime); 19 | this.format = " " + format; 20 | } 21 | 22 | private static DateTime ToDateTime(uint timestamp) 23 | { 24 | // Unix timestamp is seconds past epoch 25 | System.DateTime dtDateTime = new DateTime(1970, 1, 1, 0, 0, 0, 0, System.DateTimeKind.Utc); 26 | dtDateTime = dtDateTime.AddSeconds(timestamp).ToLocalTime(); 27 | return dtDateTime; 28 | } 29 | 30 | public override void Execute(RenderingContext context) 31 | { 32 | var temp = context.EvaluateObject(key); 33 | 34 | if (temp != null) 35 | { 36 | DateTime value; 37 | 38 | if (temp is DateTime) 39 | { 40 | value = (DateTime)temp; 41 | } 42 | else 43 | if (temp is uint) 44 | { 45 | value = ToDateTime((uint)temp); 46 | } 47 | else 48 | { 49 | throw new Exception("Invalid date for key: " + key); 50 | } 51 | 52 | var result = monthNames[value.Month] + value.ToString(format, CultureInfo.InvariantCulture); 53 | context.output.Append(result); 54 | } 55 | } 56 | } 57 | 58 | public class SpanNode : TemplateNode 59 | { 60 | private RenderingKey key; 61 | 62 | public SpanNode(Document document, string key) : base(document) 63 | { 64 | this.key = RenderingKey.Parse(key, RenderingType.DateTime); 65 | } 66 | 67 | private static string FetchTranslation(string key, object context) 68 | { 69 | /* try 70 | { 71 | var translation = (Dictionary)(((Dictionary)context)["translation"]); 72 | if (translation != null) 73 | { 74 | var obj = TemplateEngine.EvaluateObject(context, translation, "time_" + key); 75 | if (obj != null) 76 | { 77 | return (string)obj; 78 | } 79 | } 80 | 81 | return key; 82 | } 83 | catch 84 | { 85 | return key; 86 | }*/ 87 | 88 | return key; 89 | } 90 | 91 | public static string FormatTimeSpan(TimeSpan span, object context) 92 | { 93 | string result; 94 | 95 | if (span.TotalMinutes<=5) 96 | { 97 | result = FetchTranslation("now", context); 98 | } 99 | else 100 | if (span.TotalHours < 1) 101 | { 102 | int minutes = (int)span.TotalMinutes; 103 | result = minutes + " " + FetchTranslation("minutes", context); 104 | } 105 | else 106 | if (span.TotalDays < 1) 107 | { 108 | int hours = (int)span.TotalHours; 109 | result = hours + " " + FetchTranslation("hours", context); 110 | } 111 | else 112 | if (span.TotalDays < 365) 113 | { 114 | int days = (int)span.TotalDays; 115 | result = span.Days + " " + FetchTranslation("days", context); 116 | } 117 | else 118 | { 119 | var years = (int)(span.Days / 365); 120 | result = years + " " + FetchTranslation("years", context); 121 | } 122 | 123 | return result; 124 | } 125 | 126 | public override void Execute(RenderingContext context) 127 | { 128 | //var obj = TemplateEngine.EvaluateObject(context, context, "current_time"); 129 | //DateTime cur_time = (obj != null) ? (DateTime)obj : DateTime.Now; 130 | 131 | DateTime cur_time = DateTime.Now; 132 | 133 | var temp = context.EvaluateObject(key); 134 | if (temp != null) 135 | { 136 | DateTime time = (DateTime)temp; 137 | 138 | var diff = cur_time - time; 139 | var result = FormatTimeSpan(diff, context); 140 | context.output.Append(result); 141 | } 142 | } 143 | } 144 | } 145 | -------------------------------------------------------------------------------- /LunarServer/HTTP/HTTPRequest.cs: -------------------------------------------------------------------------------- 1 | using LunarLabs.WebServer.Core; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.IO; 5 | using System.Net.Sockets; 6 | using System.Text; 7 | 8 | namespace LunarLabs.WebServer.HTTP 9 | { 10 | public struct FileUpload 11 | { 12 | public string fileName; 13 | public string mimeType; 14 | public byte[] bytes; 15 | 16 | public FileUpload(string fileName, string mimeType, byte[] bytes) 17 | { 18 | this.fileName = fileName; 19 | this.mimeType = mimeType; 20 | this.bytes = bytes; 21 | } 22 | } 23 | 24 | public class HTTPRequest 25 | { 26 | public enum Method 27 | { 28 | Get, 29 | Post, 30 | Head, 31 | Put, 32 | Delete, 33 | Options, 34 | } 35 | 36 | public Method method; 37 | public string version; 38 | 39 | public byte[] bytes; 40 | 41 | public string postBody; 42 | 43 | public Session session; 44 | 45 | public Dictionary headers = new Dictionary(StringComparer.InvariantCultureIgnoreCase); 46 | 47 | public List uploads = new List(); 48 | 49 | public string url; 50 | public string path; 51 | public string IP; 52 | public Dictionary args = new Dictionary(StringComparer.InvariantCultureIgnoreCase); 53 | 54 | public bool HasVariable(string name) 55 | { 56 | return args != null && args.ContainsKey(name); 57 | } 58 | 59 | public string GetVariable(string name) 60 | { 61 | if (HasVariable(name)) 62 | { 63 | return args[name]; 64 | } 65 | 66 | return null; 67 | } 68 | 69 | public HTTPResponse Dispatch() 70 | { 71 | string host; 72 | int port; 73 | 74 | if (url.Contains(":")) 75 | { 76 | var temp = url.Split(':'); 77 | host = temp[0]; 78 | port = int.Parse(temp[1]); 79 | } 80 | else 81 | { 82 | host = url; 83 | port = 80; 84 | } 85 | 86 | AddDefaultHeader("User-Agent", "User-Agent: Mozilla/4.0 (compatible; MSIE5.01; Windows NT)"); 87 | AddDefaultHeader("Host", host); 88 | AddDefaultHeader("Accept-Language", "en-us"); 89 | AddDefaultHeader("Accept-Encoding", "gzip"); 90 | 91 | var sb = new StringBuilder(); 92 | 93 | var version = "HTTP/1.1"; 94 | sb.Append($"{method} {path} {version}\r\n\r\nHost: {host}\r\n"); 95 | 96 | foreach (var entry in headers) 97 | { 98 | sb.Append($"{entry.Key}: {entry.Value}\r\n"); 99 | } 100 | sb.Append("\r\n"); 101 | 102 | var requestBody = sb.ToString(); 103 | 104 | using (var client = new TcpClient(host, port)) 105 | { 106 | var response = new HTTPResponse(); 107 | using (NetworkStream nwStream = client.GetStream()) 108 | { 109 | byte[] bytesToSend = Encoding.ASCII.GetBytes(requestBody); 110 | 111 | nwStream.Write(bytesToSend, 0, bytesToSend.Length); 112 | 113 | using (var reader = new StreamReader(nwStream)) 114 | { 115 | var status = reader.ReadLine(); 116 | if (status.StartsWith(version)) 117 | { 118 | var code = status.Replace(version, "").TrimStart(); 119 | response.code = DecodeStatusCode(code); 120 | } 121 | else 122 | { 123 | throw new Exception("Invalid HTTP response"); 124 | } 125 | 126 | while (true) 127 | { 128 | var header = reader.ReadLine(); 129 | if (string.IsNullOrEmpty(header)) 130 | { 131 | break; 132 | } 133 | 134 | var temp = header.Split(':'); 135 | var key = temp[0].TrimEnd(); 136 | var val = temp[1].TrimStart(); 137 | response.headers[key] = val; 138 | } 139 | } 140 | 141 | } 142 | 143 | client.Close(); 144 | return response; 145 | } 146 | } 147 | 148 | private HTTPCode DecodeStatusCode(string code) 149 | { 150 | throw new NotImplementedException(); 151 | } 152 | 153 | private void AddDefaultHeader(string key, string val) 154 | { 155 | if (headers.ContainsKey(key)) 156 | { 157 | return; 158 | } 159 | 160 | headers[key] = val; 161 | } 162 | } 163 | } 164 | -------------------------------------------------------------------------------- /LunarServer/Core/MultipartParser.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Text.RegularExpressions; 7 | 8 | namespace LunarLabs.WebServer.Core 9 | { 10 | public class MultipartParser 11 | { 12 | public MultipartParser(Stream stream, Action onKeyFound) 13 | { 14 | this.Parse(stream, Encoding.UTF8, onKeyFound); 15 | } 16 | 17 | public MultipartParser(Stream stream, Encoding encoding, Action onKeyFound) 18 | { 19 | this.Parse(stream, encoding, onKeyFound); 20 | } 21 | 22 | private void Parse(Stream stream, Encoding encoding, Action onKeyFound) 23 | { 24 | this.Success = false; 25 | 26 | // Read the stream into a byte array 27 | byte[] data = ToByteArray(stream); 28 | 29 | // Copy to a string for header parsing 30 | string content = encoding.GetString(data); 31 | 32 | // The first line should contain the delimiter 33 | int delimiterEndIndex = content.IndexOf("\r\n"); 34 | 35 | if (delimiterEndIndex > -1) 36 | { 37 | string delimiter = content.Substring(0, content.IndexOf("\r\n")); 38 | 39 | var re = new Regex("Content-Disposition:.*?name=\"(\\w*)\"*\r\n\r\n(.*)\r\n"); 40 | var matches = re.Matches(content); 41 | foreach (Match match in matches) 42 | { 43 | var key = match.Groups[1].Value; 44 | var val = match.Groups[2].Value; 45 | onKeyFound(key, val); 46 | } 47 | 48 | // Look for Content-Type 49 | re = new Regex(@"(?<=Content\-Type:)(.*?)(?=\r\n\r\n)"); 50 | Match contentTypeMatch = re.Match(content); 51 | 52 | // Look for filename 53 | re = new Regex(@"(?<=filename\=\"")(.*?)(?=\"")"); 54 | Match filenameMatch = re.Match(content); 55 | 56 | // Did we find the required values? 57 | if (contentTypeMatch.Success && filenameMatch.Success) 58 | { 59 | // Set properties 60 | this.ContentType = contentTypeMatch.Value.Trim(); 61 | this.Filename = filenameMatch.Value.Trim(); 62 | 63 | // Get the start & end indexes of the file contents 64 | int startIndex = contentTypeMatch.Index + contentTypeMatch.Length + "\r\n\r\n".Length; 65 | 66 | byte[] delimiterBytes = encoding.GetBytes("\r\n" + delimiter); 67 | int endIndex = IndexOf(data, delimiterBytes, startIndex); 68 | 69 | int contentLength = endIndex - startIndex; 70 | 71 | // Extract the file contents from the byte array 72 | byte[] fileData = new byte[contentLength]; 73 | 74 | Buffer.BlockCopy(data, startIndex, fileData, 0, contentLength); 75 | 76 | this.FileContents = fileData; 77 | this.Success = true; 78 | } 79 | } 80 | } 81 | 82 | private int IndexOf(byte[] searchWithin, byte[] serachFor, int startIndex) 83 | { 84 | int index = 0; 85 | int startPos = Array.IndexOf(searchWithin, serachFor[0], startIndex); 86 | 87 | if (startPos != -1) 88 | { 89 | while ((startPos + index) < searchWithin.Length) 90 | { 91 | if (searchWithin[startPos + index] == serachFor[index]) 92 | { 93 | index++; 94 | if (index == serachFor.Length) 95 | { 96 | return startPos; 97 | } 98 | } 99 | else 100 | { 101 | startPos = Array.IndexOf(searchWithin, serachFor[0], startPos + index); 102 | if (startPos == -1) 103 | { 104 | return -1; 105 | } 106 | index = 0; 107 | } 108 | } 109 | } 110 | 111 | return -1; 112 | } 113 | 114 | private byte[] ToByteArray(Stream stream) 115 | { 116 | byte[] buffer = new byte[32768]; 117 | using (MemoryStream ms = new MemoryStream()) 118 | { 119 | while (true) 120 | { 121 | int read = stream.Read(buffer, 0, buffer.Length); 122 | if (read <= 0) 123 | return ms.ToArray(); 124 | ms.Write(buffer, 0, read); 125 | } 126 | } 127 | } 128 | 129 | public bool Success 130 | { 131 | get; 132 | private set; 133 | } 134 | 135 | public string ContentType 136 | { 137 | get; 138 | private set; 139 | } 140 | 141 | public string Filename 142 | { 143 | get; 144 | private set; 145 | } 146 | 147 | public byte[] FileContents 148 | { 149 | get; 150 | private set; 151 | } 152 | } 153 | } 154 | -------------------------------------------------------------------------------- /LunarServer/Plugins/RPC.cs: -------------------------------------------------------------------------------- 1 | using LunarLabs.WebServer.Core; 2 | using LunarLabs.WebServer.HTTP; 3 | using LunarLabs.Parser; 4 | using LunarLabs.Parser.JSON; 5 | using System; 6 | using System.Collections.Generic; 7 | 8 | namespace LunarLabs.WebServer.Plugins 9 | { 10 | public class RPCException: Exception 11 | { 12 | public RPCException(string msg) : base(msg) 13 | { 14 | 15 | } 16 | } 17 | 18 | public class RPCPlugin : ServerPlugin 19 | { 20 | private Dictionary> _handlers = new Dictionary>(); 21 | 22 | public RPCPlugin(HTTPServer server, string path = null) : base(server, path) 23 | { 24 | } 25 | 26 | public void RegisterHandler(string methodName, Func handler) 27 | { 28 | _handlers[methodName] = handler; 29 | } 30 | 31 | private object HandleGet(HTTPRequest request) 32 | { 33 | var version = request.GetVariable("jsonrpc"); 34 | if (version != "2" && version != "2.0") 35 | { 36 | return GenerateRPCError("Invalid jsonrpc version", -32602); 37 | } 38 | 39 | var method = request.GetVariable("method"); 40 | var id = request.GetVariable("id"); 41 | if (string.IsNullOrEmpty(id)) 42 | { 43 | id = "0"; 44 | } 45 | 46 | var encodedParams = request.GetVariable("params"); 47 | 48 | var decodedParams = encodedParams.UrlDecode(); 49 | 50 | DataNode paramNode; 51 | try 52 | { 53 | paramNode = JSONReader.ReadFromString(decodedParams); 54 | } 55 | catch 56 | { 57 | return GenerateRPCError("Parsing error", -32700); 58 | } 59 | 60 | return HandleRPCRequest(id, method, paramNode); 61 | } 62 | 63 | protected override bool OnInstall() 64 | { 65 | Server.Get(this.Path, HandleGet); 66 | 67 | Server.Post(this.Path, (request) => 68 | { 69 | if (string.IsNullOrEmpty(request.postBody)) 70 | { 71 | if (request.args.Count > 0) 72 | { 73 | return HandleGet(request); 74 | } 75 | 76 | return GenerateRPCError("Invalid request", -32600); 77 | } 78 | else 79 | { 80 | DataNode root; 81 | try 82 | { 83 | root = JSONReader.ReadFromString(request.postBody); 84 | } 85 | catch 86 | { 87 | return GenerateRPCError("Parsing error", -32700); 88 | } 89 | 90 | var version = root.GetString("jsonrpc"); 91 | if (version != "2" && version != "2.0") 92 | { 93 | return GenerateRPCError("Invalid jsonrpc version", -32602); 94 | } 95 | 96 | var method = root.GetString("method"); 97 | var id = root.GetString("id", "0"); 98 | 99 | var paramNode = root.GetNode("params"); 100 | 101 | return HandleRPCRequest(id, method, paramNode); 102 | } 103 | 104 | }); 105 | 106 | return true; 107 | } 108 | 109 | private object HandleRPCRequest(string id, string method, DataNode paramNode) 110 | { 111 | object result = null; 112 | if (_handlers.ContainsKey(method)) 113 | { 114 | if (paramNode == null) 115 | { 116 | return GenerateRPCError("Invalid params", -32602); 117 | } 118 | 119 | try 120 | { 121 | var handler = _handlers[method]; 122 | result = handler(paramNode); 123 | } 124 | catch (RPCException e) 125 | { 126 | return GenerateRPCError(e.Message, -32603); 127 | } 128 | catch 129 | { 130 | return GenerateRPCError("Internal error", -32603); 131 | } 132 | 133 | } 134 | else 135 | { 136 | return GenerateRPCError("Method not found", -32601); 137 | } 138 | 139 | if (result == null) 140 | { 141 | return GenerateRPCError("Missing result", -32603); 142 | } 143 | 144 | string content; 145 | 146 | if (result is DataNode) 147 | { 148 | content = JSONWriter.WriteToString((DataNode)result); 149 | } 150 | else 151 | if (result is string) 152 | { 153 | content = (string)result; 154 | } 155 | else 156 | { 157 | return GenerateRPCError("Not implemented", -32603); 158 | } 159 | 160 | return HTTPResponse.FromString("{\"jsonrpc\": \"2.0\", \"result\": " + content + ", \"id\": " + id + "}", HTTPCode.OK, Server.Settings.Compression, "application/json"); 161 | } 162 | 163 | private HTTPResponse GenerateRPCError(string msg, int code = -32000, int id = 0) 164 | { 165 | return HTTPResponse.FromString("{\"jsonrpc\": \"2.0\", \"error\": {\"code\": " + code + ", \"message\": \"" + msg + "\"}, \"id\": " + id + "}", HTTPCode.OK, Server.Settings.Compression, "application/json"); 166 | } 167 | } 168 | } 169 | -------------------------------------------------------------------------------- /LunarServer/Templates/Engine/RenderingKey.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | 4 | namespace LunarLabs.Templates 5 | { 6 | public enum RenderingType 7 | { 8 | Any, 9 | String, 10 | Bool, 11 | Numeric, 12 | Collection, 13 | DateTime, 14 | } 15 | 16 | public enum KeyOperator 17 | { 18 | Equal, 19 | Different, 20 | Greater, 21 | Less, 22 | GreaterOrEqual, 23 | LessOrEqual, 24 | Assignment, 25 | Plus, 26 | Multiply, 27 | Contains, 28 | Begins, 29 | Ends, 30 | Or, 31 | And, 32 | Function 33 | } 34 | 35 | public abstract class RenderingKey 36 | { 37 | public abstract RenderingType RenderingType { get;} 38 | 39 | // the order is important, larger strings first 40 | private static readonly Dictionary operatorSymbols = new Dictionary() 41 | { 42 | { "&&" , KeyOperator.And}, 43 | { "||" , KeyOperator.Or}, 44 | { "==" , KeyOperator.Equal}, 45 | { "!=" , KeyOperator.Different}, 46 | { ">=" , KeyOperator.GreaterOrEqual}, 47 | { "<=" , KeyOperator.LessOrEqual}, 48 | { ":=" , KeyOperator.Assignment}, 49 | { "*?" , KeyOperator.Begins}, 50 | { "?*" , KeyOperator.Ends}, 51 | { ">" , KeyOperator.Greater}, 52 | { "<", KeyOperator.Less }, 53 | { "+" , KeyOperator.Plus}, 54 | { "*" , KeyOperator.Multiply}, 55 | { "?" , KeyOperator.Contains}, 56 | { "::" , KeyOperator.Function}, 57 | }; 58 | 59 | private static int GetOperatorPriority(string op) 60 | { 61 | switch(op) 62 | { 63 | case "&&": return 3; 64 | case "||": return 2; 65 | default: return 0; 66 | } 67 | } 68 | 69 | public static RenderingKey Parse(string key, RenderingType expectedType) 70 | { 71 | if (key.StartsWith("!")) 72 | { 73 | if (expectedType != RenderingType.Bool && expectedType != RenderingType.Any) 74 | { 75 | throw new Exception("expected bool"); 76 | } 77 | 78 | key = key.Substring(1); 79 | var body = Parse(key, expectedType); 80 | return new NegationKey(body); 81 | } 82 | 83 | string operatorMatch = null; 84 | int operatorIndex = -1; 85 | 86 | foreach (var symbol in operatorSymbols.Keys) 87 | { 88 | var index = key.IndexOf(symbol); 89 | 90 | if (index >= 0) 91 | { 92 | if (operatorMatch != null && GetOperatorPriority(symbol) <= GetOperatorPriority(operatorMatch)) 93 | { 94 | continue; 95 | } 96 | 97 | operatorIndex = index; 98 | operatorMatch = symbol; 99 | } 100 | } 101 | 102 | if (operatorMatch != null) 103 | { 104 | var leftText = key.Substring(0, operatorIndex).Trim(); 105 | var righText = key.Substring(operatorIndex + operatorMatch.Length).Trim(); 106 | 107 | var op = operatorSymbols[operatorMatch]; 108 | return new CompositeRenderingKey(op, leftText, righText); 109 | } 110 | 111 | if (key.StartsWith("@")) 112 | { 113 | return new GlobalKey(key.Substring(1)); 114 | } 115 | 116 | switch (key) 117 | { 118 | case "true": 119 | if (expectedType != RenderingType.Bool && expectedType != RenderingType.Any) 120 | { 121 | throw new Exception("expected bool"); 122 | } 123 | 124 | return new LiteralKey(true, RenderingType.Bool); 125 | 126 | case "false": 127 | if (expectedType != RenderingType.Bool && expectedType != RenderingType.Any) 128 | { 129 | throw new Exception("expected bool"); 130 | } 131 | 132 | return new LiteralKey(false, RenderingType.Bool); 133 | 134 | case "this": 135 | return new SelfKey(); 136 | } 137 | 138 | if (key.StartsWith("'") && key.EndsWith("'")) 139 | { 140 | if (expectedType != RenderingType.String && expectedType != RenderingType.Any) 141 | { 142 | throw new Exception("unexpected string"); 143 | } 144 | 145 | var str = key.Substring(1, key.Length - 2); 146 | return new LiteralKey(str, RenderingType.String); 147 | } 148 | 149 | decimal number; 150 | if (decimal.TryParse(key, out number)) 151 | { 152 | if (expectedType != RenderingType.Numeric && expectedType != RenderingType.Any) 153 | { 154 | throw new Exception("expected number"); 155 | } 156 | 157 | return new LiteralKey(number, RenderingType.Numeric); 158 | } 159 | 160 | if (key.StartsWith("this.")) 161 | { 162 | key = key.Substring(5); 163 | return RenderingKey.Parse(key, expectedType); 164 | } 165 | 166 | return new PathRenderingKey(key); 167 | } 168 | 169 | public abstract object Evaluate(RenderingContext context); 170 | } 171 | } 172 | -------------------------------------------------------------------------------- /LunarServer/Core/Session.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | 5 | namespace LunarLabs.WebServer.Core 6 | { 7 | public class Session 8 | { 9 | private Dictionary _data = new Dictionary(); 10 | 11 | public IEnumerable> Data { get { return _data; } } 12 | 13 | public string ID; 14 | public DateTime lastActivity; 15 | 16 | public bool IsEmpty => _data.Count == 0; 17 | public int Size => _data.Count; 18 | 19 | public Session(string ID = null) 20 | { 21 | this.ID = ID != null ? ID : Convert.ToBase64String(System.Text.Encoding.UTF8.GetBytes(Guid.NewGuid().ToString() + DateTime.Now.Millisecond.ToString() + DateTime.Now.Ticks.ToString())); 22 | this.lastActivity = DateTime.Now; 23 | } 24 | 25 | public bool Contains(string name) 26 | { 27 | if (name.EndsWith("*")) 28 | { 29 | name = name.Substring(0, name.Length - 1); 30 | foreach (var entry in _data) 31 | { 32 | if (entry.Key.StartsWith(name)) 33 | { 34 | return true; 35 | } 36 | } 37 | return false; 38 | } 39 | 40 | if (name.StartsWith("*")) 41 | { 42 | name = name.Substring(1); 43 | foreach (var entry in _data) 44 | { 45 | if (entry.Key.EndsWith(name)) 46 | { 47 | return true; 48 | } 49 | } 50 | return false; 51 | } 52 | 53 | return _data.ContainsKey(name); 54 | } 55 | 56 | private T Get(string name, T defaultValue = default(T)) 57 | { 58 | if (_data.ContainsKey(name)) 59 | { 60 | try 61 | { 62 | var temp = (T)_data[name]; 63 | return temp; 64 | } 65 | catch (Exception e) 66 | { 67 | return defaultValue; 68 | } 69 | } 70 | 71 | return defaultValue; 72 | } 73 | 74 | private void Set(string name, object value) 75 | { 76 | _data[name] = value; 77 | } 78 | 79 | public void SetString(string name, string value) 80 | { 81 | Set(name, value); 82 | } 83 | 84 | public void SetBool(string name, bool value) 85 | { 86 | Set(name, value); 87 | } 88 | 89 | public void SetInt(string name, int value) 90 | { 91 | Set(name, value); 92 | } 93 | 94 | public void SetStruct(string name, object obj) where T : struct 95 | { 96 | var type = typeof(T); 97 | var fields = type.GetFields(); 98 | foreach (var field in fields) 99 | { 100 | if (field.FieldType == typeof(string)) 101 | { 102 | var val = (string)field.GetValue(obj); 103 | SetString(name + "." + field.Name, val); 104 | } 105 | else 106 | if (field.FieldType == typeof(bool)) 107 | { 108 | var val = (bool)field.GetValue(obj); 109 | SetBool(name + "." + field.Name, val); 110 | } 111 | else 112 | if (field.FieldType == typeof(int)) 113 | { 114 | var val = (int)field.GetValue(obj); 115 | SetInt(name + "." + field.Name, val); 116 | } 117 | else 118 | { 119 | throw new Exception("Unsupport field type for session storage: " + field.FieldType.Name); 120 | } 121 | } 122 | } 123 | 124 | public string GetString(string name, string defaultVal = null) 125 | { 126 | return Get(name, defaultVal); 127 | } 128 | 129 | public bool GetBool(string name, bool defaultVal = false) 130 | { 131 | return Get(name, defaultVal); 132 | } 133 | 134 | public int GetInt(string name, int defaultVal = 0) 135 | { 136 | return Get(name, defaultVal); 137 | } 138 | 139 | public T GetStruct(string name) where T : struct 140 | { 141 | var type = typeof(T); 142 | var fields = type.GetFields(); 143 | T obj = default(T); 144 | var ptr = __makeref(obj); 145 | foreach (var field in fields) 146 | { 147 | if (field.FieldType == typeof(string)) 148 | { 149 | var val = GetString(name + "." + field.Name); 150 | field.SetValueDirect(ptr, val); 151 | } 152 | else 153 | if (field.FieldType == typeof(bool)) 154 | { 155 | var val = GetBool(name + "." + field.Name); 156 | field.SetValueDirect(ptr, val); 157 | } 158 | else 159 | if (field.FieldType == typeof(int)) 160 | { 161 | var val = GetInt(name + "." + field.Name); 162 | field.SetValueDirect(ptr, val); 163 | } 164 | else 165 | { 166 | throw new Exception("Unsupport field type for session storage: " + field.FieldType.Name); 167 | } 168 | } 169 | return obj; 170 | } 171 | 172 | public void Remove(string name) 173 | { 174 | if (_data.ContainsKey(name)) 175 | { 176 | _data.Remove(name); 177 | } 178 | } 179 | 180 | public void Destroy() 181 | { 182 | _data.Clear(); 183 | } 184 | } 185 | 186 | } 187 | -------------------------------------------------------------------------------- /LunarServer/Websockets/WebSocketFrameReader.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using System.Text; 4 | 5 | namespace LunarLabs.WebSockets 6 | { 7 | /// 8 | /// Reads a WebSocket frame 9 | /// see http://tools.ietf.org/html/rfc6455 for specification 10 | /// 11 | internal static class WebSocketFrameReader 12 | { 13 | /// 14 | /// Read a WebSocket frame from the stream 15 | /// 16 | /// The stream to read from 17 | /// The buffer to read into 18 | /// the cancellation token 19 | /// A websocket frame 20 | public static WebSocketFrame Read(Stream fromStream, ArraySegment intoBuffer) 21 | { 22 | // allocate a small buffer to read small chunks of data from the stream 23 | var smallBuffer = new ArraySegment(new byte[8]); 24 | 25 | BinaryReaderWriter.ReadExactly(2, fromStream, smallBuffer); 26 | byte byte1 = smallBuffer.Array[0]; 27 | byte byte2 = smallBuffer.Array[1]; 28 | 29 | // process first byte 30 | byte finBitFlag = 0x80; 31 | byte opCodeFlag = 0x0F; 32 | bool isFinBitSet = (byte1 & finBitFlag) == finBitFlag; 33 | WebSocketOpCode opCode = (WebSocketOpCode) (byte1 & opCodeFlag); 34 | 35 | // read and process second byte 36 | byte maskFlag = 0x80; 37 | bool isMaskBitSet = (byte2 & maskFlag) == maskFlag; 38 | uint len = ReadLength(byte2, smallBuffer, fromStream); 39 | int count = (int)len; 40 | 41 | try 42 | { 43 | // use the masking key to decode the data if needed 44 | if (isMaskBitSet) 45 | { 46 | ArraySegment maskKey = new ArraySegment(smallBuffer.Array, 0, WebSocketFrameExtensions.MaskKeyLength); 47 | BinaryReaderWriter.ReadExactly(maskKey.Count, fromStream, maskKey); 48 | BinaryReaderWriter.ReadExactly(count, fromStream, intoBuffer); 49 | ArraySegment payloadToMask = new ArraySegment(intoBuffer.Array, intoBuffer.Offset, count); 50 | WebSocketFrameExtensions.ToggleMask(maskKey, payloadToMask); 51 | } 52 | else 53 | { 54 | BinaryReaderWriter.ReadExactly(count, fromStream, intoBuffer); 55 | } 56 | } 57 | catch (InternalBufferOverflowException e) 58 | { 59 | throw new InternalBufferOverflowException($"Supplied buffer too small to read {0} bytes from {Enum.GetName(typeof(WebSocketOpCode), opCode)} frame", e); 60 | } 61 | 62 | if (opCode == WebSocketOpCode.ConnectionClose) 63 | { 64 | return DecodeCloseFrame(isFinBitSet, opCode, count, intoBuffer); 65 | } 66 | else 67 | { 68 | // note that by this point the payload will be populated 69 | return new WebSocketFrame(isFinBitSet, opCode, count); 70 | } 71 | } 72 | 73 | /// 74 | /// Extracts close status and close description information from the web socket frame 75 | /// 76 | private static WebSocketFrame DecodeCloseFrame(bool isFinBitSet, WebSocketOpCode opCode, int count, ArraySegment buffer) 77 | { 78 | WebSocketCloseStatus closeStatus; 79 | string closeStatusDescription; 80 | 81 | if (count >= 2) 82 | { 83 | Array.Reverse(buffer.Array, buffer.Offset, 2); // network byte order 84 | int closeStatusCode = (int)BitConverter.ToUInt16(buffer.Array, buffer.Offset); 85 | if (Enum.IsDefined(typeof(WebSocketCloseStatus), closeStatusCode)) 86 | { 87 | closeStatus = (WebSocketCloseStatus)closeStatusCode; 88 | } 89 | else 90 | { 91 | closeStatus = WebSocketCloseStatus.Empty; 92 | } 93 | 94 | int offset = buffer.Offset + 2; 95 | int descCount = count - 2; 96 | 97 | if (descCount > 0) 98 | { 99 | closeStatusDescription = Encoding.UTF8.GetString(buffer.Array, offset, descCount); 100 | } 101 | else 102 | { 103 | closeStatusDescription = null; 104 | } 105 | } 106 | else 107 | { 108 | closeStatus = WebSocketCloseStatus.Empty; 109 | closeStatusDescription = null; 110 | } 111 | 112 | return new WebSocketFrame(isFinBitSet, opCode, count, closeStatus, closeStatusDescription); 113 | } 114 | 115 | /// 116 | /// Reads the length of the payload according to the contents of byte2 117 | /// 118 | private static uint ReadLength(byte byte2, ArraySegment smallBuffer, Stream fromStream) 119 | { 120 | byte payloadLenFlag = 0x7F; 121 | uint len = (uint) (byte2 & payloadLenFlag); 122 | 123 | // read a short length or a long length depending on the value of len 124 | if (len == 126) 125 | { 126 | len = BinaryReaderWriter.ReadUShortExactly(fromStream, false, smallBuffer); 127 | } 128 | else if (len == 127) 129 | { 130 | len = (uint) BinaryReaderWriter.ReadULongExactly(fromStream, false, smallBuffer); 131 | const uint maxLen = 2147483648; // 2GB - not part of the spec but just a precaution. Send large volumes of data in smaller frames. 132 | 133 | // protect ourselves against bad data 134 | if (len > maxLen || len < 0) 135 | { 136 | throw new ArgumentOutOfRangeException($"Payload length out of range. Min 0 max 2GB. Actual {len:#,##0} bytes."); 137 | } 138 | } 139 | 140 | return len; 141 | } 142 | } 143 | } 144 | -------------------------------------------------------------------------------- /LunarServer/Entity/API.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using LunarLabs.Parser; 3 | using LunarLabs.WebServer.Core; 4 | using LunarLabs.WebServer.HTTP; 5 | using System.Collections.Generic; 6 | using System.Linq; 7 | 8 | namespace LunarLabs.WebServer.Entity 9 | { 10 | public class APIRequestException : Exception 11 | { 12 | public APIRequestException(string message) : base(message) 13 | { 14 | 15 | } 16 | } 17 | 18 | public class EntityAPI: ServerPlugin 19 | { 20 | private DataFormat format; 21 | private string mimeType; 22 | 23 | public EntityStore Store { get; private set; } 24 | 25 | public EntityAPI(HTTPServer server, EntityStore store, string rootPath = null, DataFormat format = DataFormat.JSON) : base(server, rootPath) 26 | { 27 | this.format = format; 28 | this.Store = store; 29 | this.mimeType = "application/" + format.ToString().ToLower(); 30 | } 31 | 32 | protected HTTPResponse HandleRequest(HTTPRequest request) 33 | { 34 | var url = StringUtils.FixUrl(request.path); 35 | 36 | if (url.Equals("/favicon.ico")) 37 | { 38 | return null; 39 | } 40 | 41 | DataNode result; 42 | var query = request.args; 43 | 44 | RouteEntry route = null;// router.Find(url, query); 45 | 46 | if (route != null) 47 | { 48 | var handler = route.Handlers.First().Handler; // TODO fixme 49 | 50 | try 51 | { 52 | result = (DataNode) handler(request); 53 | } 54 | catch (Exception ex) 55 | { 56 | result = OperationResult(ex.Message); 57 | } 58 | 59 | } 60 | else 61 | { 62 | result = DataNode.CreateObject("result"); 63 | result.AddField("error", "invalid route"); 64 | } 65 | 66 | var response = new HTTPResponse(); 67 | 68 | 69 | var content = DataFormats.SaveToString(format, result); 70 | response.bytes = System.Text.Encoding.UTF8.GetBytes(content); 71 | 72 | response.headers["Content-Type"] = mimeType; 73 | 74 | return response; 75 | 76 | } 77 | 78 | public static DataNode OperationResult(string errorMsg = null) 79 | { 80 | var result = DataNode.CreateObject("operation"); 81 | result.AddField("result", string.IsNullOrEmpty(errorMsg) ? "success" : "error"); 82 | if (!string.IsNullOrEmpty(errorMsg)) 83 | { 84 | result.AddField("error", errorMsg); 85 | } 86 | return result; 87 | } 88 | 89 | public void GenerateAPI() where T : Entity 90 | { 91 | var name = typeof(T).Name.ToLower() + "s"; 92 | var baseURL = $"{this.Path}api /{name}"; 93 | Server.Get(baseURL, (request) => ListEntities()); 94 | Server.Get(baseURL + "/new", (request) => NewEntity(request)); 95 | Server.Get(baseURL + "/{id}/show", (request) => ShowEntity(request)); 96 | Server.Get(baseURL + "/{id}/edit", (request) => EditEntity(request)); 97 | Server.Get(baseURL + "/{id}/delete", (request) => DeleteEntity(request)); 98 | } 99 | 100 | private static void Check(Dictionary dic, IEnumerable args) 101 | { 102 | foreach (var arg in args) 103 | { 104 | if (!dic.ContainsKey(arg)) 105 | { 106 | throw new APIRequestException($"argument '{arg}' is required"); 107 | } 108 | } 109 | } 110 | 111 | private static string Check(Dictionary dic, string name) 112 | { 113 | if (dic.ContainsKey(name)) 114 | { 115 | return dic[name]; 116 | } 117 | 118 | throw new APIRequestException($"argument '{name}' is required"); 119 | } 120 | 121 | private T Check(string id) where T : Entity 122 | { 123 | var entity = Store.FindById(id); 124 | 125 | if (entity != null) 126 | { 127 | return entity; 128 | } 129 | 130 | throw new APIRequestException($"{typeof(T).Name} with '{id}' does not exist"); 131 | } 132 | 133 | #region UTILS 134 | private object ListEntities() where T : Entity 135 | { 136 | var result = DataNode.CreateObject(typeof(T).Name.ToLower() + "s"); 137 | 138 | foreach (var user in Store.Every()) 139 | { 140 | var data = user.Serialize(); 141 | result.AddNode(data); 142 | } 143 | 144 | return result; 145 | } 146 | 147 | private object ShowEntity(HTTPRequest requset) where T : Entity 148 | { 149 | var id = requset.args["id"]; 150 | var entity = Store.FindById(id); 151 | var data = entity.Serialize(); 152 | return data; 153 | } 154 | 155 | private object NewEntity(HTTPRequest request) where T : Entity 156 | { 157 | var entity = Store.Create(); 158 | var node = request.args.ToDataNode(typeof(T).Name.ToLower()); 159 | Check(request.args, Entity.GetFields()); 160 | entity.Deserialize(node); 161 | entity.Save(); 162 | var data = entity.Serialize(); 163 | return data; 164 | } 165 | 166 | private object EditEntity(HTTPRequest request) where T : Entity 167 | { 168 | var node = request.args.ToDataNode(typeof(T).Name.ToLower()); 169 | var id = Check(request.args, "id"); 170 | var entity = Check(id); 171 | entity.Deserialize(node); 172 | entity.Save(); 173 | var data = entity.Serialize(); 174 | return data; 175 | } 176 | 177 | private object DeleteEntity(HTTPRequest request) where T : Entity 178 | { 179 | var node = request.args.ToDataNode(typeof(T).Name.ToLower()); 180 | var id = Check(request.args, "id"); 181 | var entity = Check(id); 182 | entity.Delete(); 183 | return OperationResult(); 184 | } 185 | 186 | #endregion 187 | 188 | } 189 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Lunar Server 2 | HTTP server with minimal dependencies 3 | 4 | # Why Lunar Server? 5 | Most of web development written in C# is done with ASP.NET, which is too high-level for my taste. 6 | Also, many of the Web/HTTP classes from .NET / ASP.NET don't play well with Mono / Linux. 7 | 8 | 9 | So I decided to write something that would allow me to code websites in C# with minimal dependencies and easy to use. 10 | 11 | ## Installation 12 | 13 | PM> Install-Package LunarServer 14 | 15 | Since this is a .NET standard package, to use with .NET framework projects please set the target to .NET Framework 4.5 or higher, otherwise Nuget will give you installation errors. 16 | 17 | ## Supported platforms 18 | 19 | - .NET Core 20 | - .NET Framework 3.5 and above 21 | - Mono & Xamarin 22 | - UWP 23 | 24 | ## Supported features 25 | 26 | - HTTP methods (GET/POST/PUT/DELETE) 27 | - Cookies / Sessions 28 | - File caching / ETAG / GZip compression 29 | - Forms / File uploads 30 | - Websockets 31 | 32 | # Usage 33 | 34 | Import the package: 35 | 36 | ```c# 37 | using LunarLabs.WebServer.Core; 38 | using LunarLabs.WebServer.HTTP; 39 | ``` 40 | 41 | Instantiate the necessary classes: 42 | 43 | ```c# 44 | // either parse the settings from the program args or initialize them manually 45 | var settings = ServerSettings.Parse(args); 46 | 47 | var server = new HTTPServer(settings, ConsoleLogger.Write); 48 | ``` 49 | 50 | Add some routes to the site. 51 | 52 | ```c# 53 | server.Get("/", (request) => 54 | { 55 | return HTTPResponse.FromString("Hello world!"); 56 | }); 57 | 58 | ``` 59 | 60 | Finally add code to start the server. 61 | ```c# 62 | server.Run(); 63 | 64 | bool running = true; 65 | 66 | Console.CancelKeyPress += delegate { 67 | server.Stop(); 68 | running = false; 69 | }; 70 | 71 | server.WaitForFinish(); 72 | ``` 73 | 74 | You can now open "http://localhost" in your browser and see "Hello World! appear. 75 | 76 | Here's how to support POST requests (from HTML forms, etc) 77 | ```c# 78 | server.Post("/myform", (request) => 79 | { 80 | var username = request.args["username"]; 81 | var password = request.args["password"]; 82 | 83 | if (password == "hello") { 84 | return HTTPResponse.FromString("Login ok!"); 85 | } 86 | else { 87 | return HTTPResponse.FromString("Invalid login details!"); 88 | } 89 | }); 90 | ``` 91 | 92 | Here's how to do dynamic routes (aka pretty URLs) 93 | ```c# 94 | server.Get("/user/{id}", (request) => 95 | { 96 | var user_id = request.args["id"]; 97 | return HTTPResponse.FromString($"Hello user with ID = {user_id}!"); 98 | }); 99 | ``` 100 | 101 | You can also do more complex routes using wildcards (although this way you will have to process manually the request) 102 | ```c# 103 | server.Get("/user/*", (request) => 104 | { 105 | var user_id = request.path.Replace("/user/", ""); 106 | return HTTPResponse.FromString($"Hello user with ID = {user_id}!"); 107 | }); 108 | ``` 109 | 110 | 111 | Here is how you redirect the user browser to another URL. 112 | ```c# 113 | server.Get("/secret", (request) => 114 | { 115 | return HTTPResponse.Redirect("/login"); 116 | }); 117 | ``` 118 | 119 | Here is how you handle websockets. 120 | ```c# 121 | server.WebSocket("/chat", (socket) => 122 | { 123 | while (socket.IsOpen) 124 | { 125 | var msg = socket.Receive(); 126 | 127 | if (msg.CloseStatus == WebSocketCloseStatus.None) 128 | { 129 | var str = Encoding.UTF8.GetString(msg.Bytes); 130 | Console.WriteLine(str); 131 | 132 | socket.Send("Thanks man!"); 133 | } 134 | } 135 | }); 136 | ``` 137 | 138 | There's also builtin support for Mustache templates. 139 | First create a folder (eg: "views") with your template files. 140 | 141 | ```html 142 |

Hello, {{username}}!

143 | ``` 144 | 145 | Then instantiate a template engine and add the necessary routes. 146 | ```c# 147 | var templateEngine = new TemplateEngine(server, "views"); 148 | 149 | server.Get("/hello", (request) => 150 | { 151 | var context = new Dictionary(); 152 | context["username"] = "Admin"; 153 | return templateEngine.Render(context, "main"); 154 | }); 155 | ``` 156 | 157 | For custom 404 not found pages use the OnNotFound handler. 158 | 159 | ```c# 160 | var templateEngine = new TemplateEngine(server, "views"); 161 | 162 | server.OnNotFound = (request) => 163 | { 164 | var context = new Dictionary(); 165 | context["error"] = "Something funny"; 166 | return templateEngine.Render(context, "404"); 167 | }; 168 | ``` 169 | 170 | # Template Examples 171 | 172 | Passing arguments to nested templates (via globals). 173 | 174 | ```c# 175 | {{#set FARM:=Farm}} 176 | {{#include farm}} 177 | ``` 178 | Then inside the other template: 179 | 180 | ```c# 181 | {{#if @FARM.Pair == 'LOL'}} 182 | Hello World 183 | {{/if}} 184 | ``` 185 | 186 | # Recommended directory structure 187 | Create a main directory for your site (eg: a folder called www), then inside create two sub-directories public and views. 188 | Public directory is where you are going to add javascript, css, images, etc and views directory is where you put the htlm templates. 189 | 190 | You can run then your server with something like: 191 | --path=C:\code\my_server\www 192 | 193 | # Settings 194 | 195 | Those settings can be either configured in-code or passed as arguments to the server. 196 | 197 | | Setting | Values | Description | 198 | | ------------- | ------------- | ------------- | 199 | | --host | An IP or hostname | Specify the host where the server will be running, without this, localhost is assumed. | 200 | | --port | A valid port number | Specify the port where the server will be listening | 201 | | --postsize | Number | Specify the maximum allowed number of bytes that is accepted via a POST request, pass 0 to disable POST | 202 | | --wsframes | Number | Specify the maximum allowed of bytes for a websocket frame | 203 | | --compression | true/false | Enable or disable HTML and JS compression | 204 | | --cachetime | Seconds | Specify the duration of server caching | 205 | | --path | A valid file path | Specify the path from where the server will be serving files | 206 | | --binding | IP address | Specify the IP address that the server will accept connections from, if not specified it will accept every ip address | 207 | | --env | dev or prod | Specify the enviroment where the server will run | 208 | 209 | 210 | # FAQ 211 | 212 | **Q:** How to disable access to file system / static content? 213 | 214 | **A:** Currently the way to do this is to set the server settings Path to null. 215 | ```c# 216 | // either parse the settings from the program args or initialize them manually 217 | var settings = ServerSettings.Parse(args); 218 | settings.Path = null; 219 | 220 | var server = new HTTPServer(settings, ConsoleLogger.Write); 221 | ``` 222 | 223 | # Contact 224 | 225 | Let me know if you find bugs or if you have suggestions to improve the project. 226 | 227 | And maybe follow me [@onihunters](https://twitter.com/onihunters) :) -------------------------------------------------------------------------------- /LunarServer/Core/Router.cs: -------------------------------------------------------------------------------- 1 | using LunarLabs.WebServer.HTTP; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Text; 5 | using System.Text.RegularExpressions; 6 | using System.Linq; 7 | 8 | namespace LunarLabs.WebServer.Core 9 | { 10 | public struct RouteEndPoint 11 | { 12 | public readonly Func Handler; 13 | public readonly int Priority; 14 | 15 | public RouteEndPoint(Func handler, int priority) 16 | { 17 | Handler = handler; 18 | Priority = priority; 19 | } 20 | } 21 | 22 | public sealed class RouteEntry 23 | { 24 | public readonly string Route; 25 | public readonly List Handlers = new List(); 26 | 27 | public RouteEntry(string route) 28 | { 29 | this.Route = route; 30 | } 31 | } 32 | public sealed class Router 33 | { 34 | private Dictionary> _routes = new Dictionary>(); 35 | private Dictionary> _wildRoutes = new Dictionary>(); 36 | public Router() 37 | { 38 | var methods = Enum.GetValues(typeof(HTTPRequest.Method)).Cast().ToArray(); 39 | 40 | foreach (var method in methods) 41 | { 42 | _routes[method] = new Dictionary(); 43 | _wildRoutes[method] = new List(); 44 | } 45 | } 46 | 47 | /* 48 | string[] s = path.Split('/'); 49 | var regex = new Regex(@"{([A-z]\w+)}"); 50 | 51 | var sb = new StringBuilder(); 52 | names = new Dictionary(); 53 | 54 | for (int i = 0; i < s.Length; i++) 55 | { 56 | if (i > 0) 57 | { 58 | sb.Append('/'); 59 | } 60 | 61 | var match = regex.Match(s[i]); 62 | if (match.Success) 63 | { 64 | sb.Append("*"); 65 | names[i] = match.Groups[1].Value; 66 | } 67 | else 68 | { 69 | sb.Append(s[i]); 70 | } 71 | } 72 | 73 | path = "/" + sb.ToString(); 74 | */ 75 | 76 | public void Register(HTTPRequest.Method method, string path, int priority, Func handler) 77 | { 78 | //path = StringUtils.FixUrl(path); 79 | 80 | RouteEntry entry; 81 | 82 | if (path.Contains("*")) 83 | { 84 | entry = new RouteEntry(path); 85 | var list = _wildRoutes[method]; 86 | 87 | if (list.Any(x => x.Route.Equals(path, StringComparison.OrdinalIgnoreCase))) 88 | { 89 | throw new ArgumentException($"Duplicated {method} path: " + path); 90 | } 91 | 92 | list.Add(entry); 93 | } 94 | else 95 | { 96 | var dic = _routes[method]; 97 | 98 | if (dic.ContainsKey(path)) 99 | { 100 | entry = dic[path]; 101 | } 102 | else 103 | { 104 | entry = new RouteEntry(path); 105 | dic[path] = entry; 106 | } 107 | } 108 | 109 | entry.Handlers.Add(new RouteEndPoint(handler, priority)); 110 | entry.Handlers.Sort((x, y) => y.Priority.CompareTo(x.Priority)); 111 | } 112 | 113 | public RouteEntry Find(HTTPRequest.Method method, string url, Dictionary query) 114 | { 115 | if (string.IsNullOrEmpty(url)) 116 | { 117 | return null; 118 | } 119 | 120 | var table = _routes[method]; 121 | 122 | if (!table.ContainsKey(url)) 123 | { 124 | // first try wildcards (if any available) 125 | var list = _wildRoutes[method]; 126 | 127 | foreach (var entry in list) 128 | { 129 | if (StringUtils.MatchWildCard(url, entry.Route)) 130 | { 131 | return entry; 132 | } 133 | } 134 | 135 | // if still nothing found, try routes with args 136 | url = FindRouteWithArgs(method, url, query); 137 | 138 | if (url == null) 139 | { 140 | return null; 141 | } 142 | } 143 | 144 | if (table.ContainsKey(url)) 145 | { 146 | return table[url]; 147 | } 148 | 149 | return null; 150 | } 151 | 152 | private readonly static char[] splitter = new char[] { '/' }; 153 | 154 | private string FindRouteWithArgs(HTTPRequest.Method method, string url, Dictionary query) 155 | { 156 | //bool hasSlash = urlPath.Contains("/"); 157 | 158 | string[] urlComponents = url.Split(splitter, StringSplitOptions.RemoveEmptyEntries); 159 | var sb = new StringBuilder(); 160 | 161 | var table = _routes[method]; 162 | 163 | foreach (var routePath in table) 164 | { 165 | var entryPath = routePath.Key.Split(splitter, StringSplitOptions.RemoveEmptyEntries); 166 | 167 | if (entryPath.Length != urlComponents.Length) 168 | { 169 | continue; 170 | } 171 | 172 | var found = true; 173 | 174 | for (int i = 0; i < entryPath.Length; i++) 175 | { 176 | var other = entryPath[i]; 177 | 178 | if (other.StartsWith("{")) 179 | { 180 | continue; 181 | } 182 | 183 | var component = urlComponents[i]; 184 | if (!component.Equals(other)) 185 | { 186 | found = false; 187 | break; 188 | } 189 | } 190 | 191 | if (found) 192 | { 193 | var route = routePath.Value; 194 | 195 | for (int i = 0; i < entryPath.Length; i++) 196 | { 197 | var other = entryPath[i]; 198 | 199 | if (other.StartsWith("{")) 200 | { 201 | var name = other.Substring(1, other.Length - 2); 202 | 203 | var component = urlComponents[i]; 204 | query[name] = component; 205 | } 206 | } 207 | 208 | return routePath.Key; 209 | } 210 | } 211 | 212 | return null; 213 | } 214 | 215 | } 216 | 217 | } 218 | -------------------------------------------------------------------------------- /LunarServer/Entity/Store.cs: -------------------------------------------------------------------------------- 1 | using LunarLabs.Parser; 2 | using LunarLabs.Parser.CSV; 3 | using LunarLabs.Parser.JSON; 4 | using LunarLabs.Parser.XML; 5 | using LunarLabs.WebServer.Core; 6 | using System; 7 | using System.Collections.Generic; 8 | using System.IO; 9 | using System.Threading; 10 | 11 | namespace LunarLabs.WebServer.Entity 12 | { 13 | public class EntityStore 14 | { 15 | private Type CollectionType; 16 | private Dictionary _collections = new Dictionary(); 17 | 18 | private Thread saveThread = null; 19 | internal ServerSettings settings; 20 | 21 | public EntityStore(ServerSettings settings, Type collectionType) 22 | { 23 | this.settings = settings; 24 | this.CollectionType = collectionType; 25 | 26 | if (collectionType.BaseType.IsAssignableFrom(typeof(EntityConnector))) 27 | { 28 | throw new ArgumentException("Collection type must be valid"); 29 | } 30 | } 31 | 32 | public static EntityStore Create(ServerSettings settings) where T : EntityConnector 33 | { 34 | return new EntityStore(settings, typeof(T)); 35 | } 36 | 37 | public T Create() where T : Entity 38 | { 39 | var type = typeof(T); 40 | var collection = GetCollection(type); 41 | return (T)collection.CreateObject(); 42 | } 43 | 44 | internal void RequestBackgroundThread() 45 | { 46 | throw new Exception("Store thread needs changes, currently it does CPU spinnning!"); 47 | if (saveThread == null) 48 | { 49 | saveThread = new Thread(() => 50 | { 51 | Thread.CurrentThread.IsBackground = true; 52 | 53 | do 54 | { 55 | Thread.Sleep(500); 56 | foreach (var collection in _collections.Values) 57 | { 58 | collection.Save(); 59 | } 60 | } while (true); 61 | }); 62 | 63 | saveThread.Start(); 64 | } 65 | } 66 | 67 | internal EntityConnector GetCollection(Type type) 68 | { 69 | EntityConnector collection; 70 | 71 | lock (_collections) 72 | { 73 | if (_collections.ContainsKey(type)) 74 | { 75 | collection = _collections[type]; 76 | } 77 | else 78 | { 79 | collection = (EntityConnector)Activator.CreateInstance(CollectionType); 80 | collection.Initialize(this, type); 81 | _collections[type] = collection; 82 | } 83 | } 84 | 85 | return collection; 86 | } 87 | 88 | public T FindById(string ID) where T : Entity 89 | { 90 | var type = typeof(T); 91 | 92 | var collection = GetCollection(type); 93 | return (T)collection.FindObject(ID); 94 | } 95 | 96 | public T FindOne(Predicate pred) where T : Entity 97 | { 98 | var type = typeof(T); 99 | 100 | var collection = GetCollection(type); 101 | 102 | foreach (T item in collection.Objects) 103 | { 104 | if (pred(item)) 105 | { 106 | return item; 107 | } 108 | } 109 | 110 | return default(T); 111 | } 112 | 113 | public List FindAll(Predicate pred) where T : Entity 114 | { 115 | var type = typeof(T); 116 | 117 | var collection = GetCollection(type); 118 | 119 | var result = new List(); 120 | 121 | foreach (T item in collection.Objects) 122 | { 123 | if (pred(item)) 124 | { 125 | result.Add(item); 126 | } 127 | } 128 | 129 | return result; 130 | } 131 | 132 | public IEnumerable Every() where T : Entity 133 | { 134 | var type = typeof(T); 135 | var result = new List(); 136 | 137 | var collection = GetCollection(type); 138 | 139 | foreach (var obj in collection.Objects) 140 | { 141 | result.Add((T)obj); 142 | } 143 | 144 | return result; 145 | } 146 | } 147 | 148 | 149 | public abstract class LunarStore : EntityConnector 150 | { 151 | public abstract DataNode ReadStore(string content); 152 | public abstract string SaveStore(DataNode root); 153 | 154 | internal override void LoadConnector() 155 | { 156 | var fileName = GetFileName(); 157 | if (File.Exists(fileName)) 158 | { 159 | var contents = File.ReadAllText(fileName); 160 | var root = ReadStore(contents); 161 | var name = objectType.Name.ToLower(); 162 | 163 | root = root["collection"]; 164 | 165 | foreach (var child in root.Children) 166 | { 167 | if (!child.Name.Equals(name)) 168 | { 169 | continue; 170 | } 171 | 172 | var obj = AllocObject(); 173 | obj.Deserialize(child); 174 | AddObject(obj); 175 | } 176 | } 177 | } 178 | 179 | internal override void SaveConnector() 180 | { 181 | var result = DataNode.CreateObject("collection"); 182 | 183 | foreach (var obj in this.Objects) 184 | { 185 | var node = obj.Serialize(); 186 | result.AddNode(node); 187 | } 188 | 189 | var contents = SaveStore(result); 190 | File.WriteAllText(GetFileName(), contents); 191 | } 192 | 193 | private string GetFileName() 194 | { 195 | return Store.settings.Path + "store/" + objectType.Name.ToLower() + ".xml"; 196 | } 197 | } 198 | 199 | public class XMLStore : LunarStore 200 | { 201 | public override DataNode ReadStore(string content) 202 | { 203 | return XMLReader.ReadFromString(content); 204 | } 205 | 206 | public override string SaveStore(DataNode root) 207 | { 208 | return XMLWriter.WriteToString(root); 209 | } 210 | } 211 | 212 | public class JSONStore : LunarStore 213 | { 214 | public override DataNode ReadStore(string content) 215 | { 216 | return JSONReader.ReadFromString(content); 217 | } 218 | 219 | public override string SaveStore(DataNode root) 220 | { 221 | return JSONWriter.WriteToString(root); 222 | } 223 | } 224 | 225 | public class CSVStore : LunarStore 226 | { 227 | public override DataNode ReadStore(string content) 228 | { 229 | return CSVReader.ReadFromString(content); 230 | } 231 | 232 | public override string SaveStore(DataNode root) 233 | { 234 | return CSVWriter.WriteToString(root); 235 | } 236 | } 237 | 238 | } 239 | -------------------------------------------------------------------------------- /LunarServer/Analytics/AnalyticsCollection.cs: -------------------------------------------------------------------------------- 1 | using LunarLabs.WebServer.Core; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.IO; 5 | 6 | namespace LunarLabs.WebServer.Analytics 7 | { 8 | public enum AnalyticsDataType 9 | { 10 | Invalid, 11 | None, 12 | String, 13 | SByte, 14 | Short, 15 | Int, 16 | Long, 17 | Byte, 18 | UShort, 19 | UInt, 20 | ULong, 21 | Float, 22 | Double, 23 | Decimal, 24 | Bool 25 | } 26 | 27 | public class AnalyticsCollection 28 | { 29 | public SortedDictionary values = new SortedDictionary(); 30 | 31 | public Dictionary dayAggregate = new Dictionary(); 32 | 33 | public Dictionary monthAggregate = new Dictionary(); 34 | 35 | public Dictionary yearAggregate = new Dictionary(); 36 | 37 | public string name 38 | { 39 | get; 40 | private set; 41 | } 42 | 43 | public AnalyticsDataType dataType 44 | { 45 | get; 46 | private set; 47 | } 48 | 49 | public AnalyticsCollection(string name, AnalyticsDataType dataType) 50 | { 51 | this.name = name; 52 | this.dataType = dataType; 53 | } 54 | 55 | public void Add(long timestamp, object obj) 56 | { 57 | DateTime val = timestamp.ToDateTime(); 58 | this.values[timestamp] = obj; 59 | DateTime val2 = val.FixedDay(); 60 | this.Increase(this.dayAggregate, val2); 61 | DateTime val3 = val.FixedMonth(); 62 | this.Increase(this.monthAggregate, val3); 63 | DateTime val4 = val.FixedYear(); 64 | this.Increase(this.yearAggregate, val4); 65 | } 66 | 67 | private void Increase(Dictionary dic, DateTime val) 68 | { 69 | long num = val.ToTimestamp(); 70 | if (dic.ContainsKey(num)) 71 | { 72 | long key = num; 73 | dic[key]++; 74 | } 75 | else 76 | { 77 | dic[num] = 1; 78 | } 79 | } 80 | 81 | public void Load(BinaryReader reader) 82 | { 83 | for (long num = reader.ReadInt64(); num > 0; num--) 84 | { 85 | long timestamp = reader.ReadInt64(); 86 | object obj; 87 | switch (this.dataType) 88 | { 89 | case AnalyticsDataType.None: 90 | obj = null; 91 | break; 92 | case AnalyticsDataType.String: 93 | obj = reader.ReadString(); 94 | break; 95 | case AnalyticsDataType.SByte: 96 | obj = reader.ReadSByte(); 97 | break; 98 | case AnalyticsDataType.Short: 99 | obj = reader.ReadInt16(); 100 | break; 101 | case AnalyticsDataType.Int: 102 | obj = reader.ReadInt32(); 103 | break; 104 | case AnalyticsDataType.Long: 105 | obj = reader.ReadInt64(); 106 | break; 107 | case AnalyticsDataType.Byte: 108 | obj = reader.ReadByte(); 109 | break; 110 | case AnalyticsDataType.UShort: 111 | obj = reader.ReadUInt16(); 112 | break; 113 | case AnalyticsDataType.UInt: 114 | obj = (obj = reader.ReadUInt32()); 115 | break; 116 | case AnalyticsDataType.ULong: 117 | obj = reader.ReadUInt64(); 118 | break; 119 | case AnalyticsDataType.Float: 120 | obj = reader.ReadSingle(); 121 | break; 122 | case AnalyticsDataType.Double: 123 | obj = reader.ReadDouble(); 124 | break; 125 | case AnalyticsDataType.Decimal: 126 | obj = reader.ReadDecimal(); 127 | break; 128 | case AnalyticsDataType.Bool: 129 | obj = reader.ReadBoolean(); 130 | break; 131 | default: 132 | throw new Exception("Invalid analytics type"); 133 | } 134 | this.Add(timestamp, obj); 135 | } 136 | } 137 | 138 | public void Save(BinaryWriter writer) 139 | { 140 | long value = this.values.Count; 141 | writer.Write(value); 142 | foreach (KeyValuePair value3 in this.values) 143 | { 144 | long key = value3.Key; 145 | object value2 = value3.Value; 146 | writer.Write(key); 147 | switch (this.dataType) 148 | { 149 | case AnalyticsDataType.String: 150 | writer.Write((string)value2); 151 | break; 152 | case AnalyticsDataType.SByte: 153 | writer.Write((sbyte)value2); 154 | break; 155 | case AnalyticsDataType.Short: 156 | writer.Write((short)value2); 157 | break; 158 | case AnalyticsDataType.Int: 159 | writer.Write((int)value2); 160 | break; 161 | case AnalyticsDataType.Long: 162 | writer.Write((long)value2); 163 | break; 164 | case AnalyticsDataType.Byte: 165 | writer.Write((byte)value2); 166 | break; 167 | case AnalyticsDataType.UShort: 168 | writer.Write((ushort)value2); 169 | break; 170 | case AnalyticsDataType.UInt: 171 | writer.Write((uint)value2); 172 | break; 173 | case AnalyticsDataType.ULong: 174 | writer.Write((ulong)value2); 175 | break; 176 | case AnalyticsDataType.Float: 177 | writer.Write((float)value2); 178 | break; 179 | case AnalyticsDataType.Double: 180 | writer.Write((double)value2); 181 | break; 182 | case AnalyticsDataType.Decimal: 183 | writer.Write((decimal)value2); 184 | break; 185 | case AnalyticsDataType.Bool: 186 | writer.Write((bool)value2); 187 | break; 188 | default: 189 | throw new Exception("Invalid analytics type"); 190 | case AnalyticsDataType.None: 191 | break; 192 | } 193 | } 194 | } 195 | } 196 | 197 | } 198 | -------------------------------------------------------------------------------- /SynkMVC/Modules/API.cs: -------------------------------------------------------------------------------- 1 | using LunarParser; 2 | using LunarParser.JSON; 3 | using LunarLabs.WebMVC; 4 | using LunarLabs.WebMVC.Model; 5 | using LunarLabs.WebMVC.Utils; 6 | using System; 7 | using System.Collections.Generic; 8 | using System.Text; 9 | 10 | namespace LunarLabs.WebMVC.Modules 11 | { 12 | public class API : Module 13 | { 14 | public override bool CheckPermissions(SynkContext context, User user, string action) 15 | { 16 | return true; 17 | } 18 | 19 | public override void OnInvalidAction(SynkContext context, string action) 20 | { 21 | var content = DataNode.CreateObject(); 22 | var error = true; 23 | content.AddField("error", "method "+action+" not supported"); 24 | APIResult(context, content, error); 25 | } 26 | 27 | public void OnDelete(SynkContext context) 28 | { 29 | var error = false; 30 | var content = DataNode.CreateObject(); 31 | 32 | if (context.request.HasVariable("entity")) 33 | { 34 | var entityClass = context.request.GetVariable("entity"); 35 | 36 | if (context.request.HasVariable("id")) 37 | { 38 | long entityID; 39 | long.TryParse(context.request.GetVariable("id"), out entityID); 40 | var entity = context.database.FetchEntityByID(entityClass, entityID); 41 | if (entity.exists) 42 | { 43 | content.AddField("id", entityID.ToString()); 44 | entity.Remove(context); 45 | } 46 | else 47 | { 48 | error = true; 49 | content.AddField("error", "ID does not exist"); 50 | } 51 | } 52 | else 53 | { 54 | error = true; 55 | content.AddField("error", "ID is required"); 56 | } 57 | } 58 | else 59 | { 60 | error = true; 61 | content.AddField("error", "Entity type not specified"); 62 | } 63 | 64 | APIResult(context, content, error); 65 | } 66 | 67 | public void OnInsert(SynkContext context) 68 | { 69 | var error = false; 70 | var content = DataNode.CreateObject(); 71 | 72 | if (context.request.HasVariable("entity")) 73 | { 74 | var entityClass = context.request.GetVariable("entity"); 75 | var entity = context.database.CreateEntity(entityClass); 76 | 77 | foreach (var field in entity.fields) 78 | { 79 | var fieldName = field.name; 80 | 81 | if (context.request.HasVariable(fieldName)) 82 | { 83 | entity.SetFieldValue(fieldName, context.request.GetVariable(fieldName)); 84 | } 85 | else 86 | if (!field.required) 87 | { 88 | entity.SetFieldValue(fieldName, field.GetDefaultValue(context)); 89 | } 90 | else 91 | { 92 | error = true; 93 | content.AddField("error", fieldName + " is required"); 94 | break; 95 | } 96 | } 97 | 98 | if (!error) 99 | { 100 | entity.Save(context); 101 | content.AddField("id", entity.id.ToString()); 102 | } 103 | } 104 | else 105 | { 106 | error = true; 107 | content.AddField("error", "Entity type not specified"); 108 | } 109 | 110 | APIResult(context, content, error); 111 | } 112 | 113 | public void OnGet(SynkContext context) 114 | { 115 | var error = false; 116 | var content = DataNode.CreateObject(); 117 | 118 | if (context.request.HasVariable("entity")) 119 | { 120 | var entityClass = context.request.GetVariable("entity"); 121 | if (context.request.HasVariable("id")) 122 | { 123 | long entityID; 124 | long.TryParse(context.request.GetVariable("id"), out entityID); 125 | 126 | var obj = DataNode.CreateObject("content"); 127 | content.AddNode(obj); 128 | 129 | var entity = context.database.FetchEntityByID(entityClass, entityID); 130 | if (entity.exists) 131 | { 132 | obj.AddField("id", entity.id.ToString()); 133 | var fields = entity.GetFields(); 134 | obj.AddNode(fields.ToDataSource()); 135 | } 136 | else 137 | { 138 | error = true; 139 | content.AddField("error", "ID does not exist"); 140 | } 141 | } 142 | else 143 | { 144 | error = true; 145 | content.AddField("error", "id is required for"); 146 | } 147 | } 148 | else 149 | { 150 | error = true; 151 | content.AddField("error", "Entity type not specified"); 152 | } 153 | 154 | APIResult(context, content, error); 155 | } 156 | 157 | public void OnList(SynkContext context) 158 | { 159 | var error = false; 160 | var content = DataNode.CreateObject(); 161 | 162 | if (context.request.HasVariable("entity")) 163 | { 164 | var entityClass = context.request.GetVariable("entity"); 165 | var entities = context.database.FetchAllEntities(entityClass); 166 | 167 | var obj = DataNode.CreateObject("content"); 168 | content.AddNode(obj); 169 | 170 | foreach (var entity in entities) 171 | { 172 | var fields = entity.GetFields(); 173 | var child = DataNode.CreateObject("entity"); 174 | obj.AddNode(child); 175 | child.AddField("id", entity.id.ToString()); 176 | child.AddNode(fields.ToDataSource()); 177 | } 178 | } 179 | else 180 | { 181 | error = true; 182 | content.AddField("error", "Entity type not specified"); 183 | } 184 | 185 | APIResult(context, content, error); 186 | } 187 | 188 | private void APIResult(SynkContext context, DataNode content, bool error) 189 | { 190 | var result = DataNode.CreateObject(); 191 | result.AddField("result", error ? "error" : "ok"); 192 | result.AddNode(content); 193 | 194 | var json = JSONWriter.WriteToString(result); 195 | context.Echo(json); 196 | } 197 | 198 | } 199 | } --------------------------------------------------------------------------------