예제 #1
0
        public static NpgsqlConnection OpenNewConnection()
        {
            CommonController.LogDebug("Opening new SQL connection");
            var conn = new NpgsqlConnection(GetPgsqlConfig());

            conn.Open();
            return(conn);
        }
        private static async Task InstallGet(HttpContext context)
        {
            CommonController.SetHTMLContentType(context);
            var playerURL = GetLatestPlayerInstallerURL();
            var editorURL = GetLatestEditorInstallerURL();

            var templateContext = new TemplateContext(new { playerURL, editorURL });
            await context.Response.WriteAsync(await HTMLRenderer.Render(context, "Templates\\install.liquid", templateContext));
        }
예제 #3
0
 private static void OnCreate(object sender, FileSystemEventArgs e)
 {
     CommonController.LogDebug($"File Created: {e.FullPath}");
     try
     {
         templateCache.TryAdd(e.FullPath, parser.Parse(File.ReadAllText(e.FullPath)));
     }
     catch { Debug.WriteLine("Error updating template: " + e); }
 }
예제 #4
0
        public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
        {
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
                CommonController.baseURL = "https://localhost:5001";
            }
            else
            {
                app.UseHsts();
                CommonController.baseURL = "https://vivista.net";
            }

            CommonController.wwwroot = env.WebRootPath;
            app.UseStaticFiles();
            app.UseTus(context =>
            {
                if (!context.Request.Path.StartsWithSegments(new PathString("/api/file")) || context.Request.Method == "GET")
                {
                    return(null);
                }
                var guid    = new Guid(context.Request.Headers["guid"]);
                string path = Path.Combine(VideoController.baseFilePath, guid.ToString());
                return(new DefaultTusConfiguration
                {
                    Store = new TusDiskStore(path),
                    UrlPath = "/api/file",
                    Events = new Events
                    {
                        OnAuthorizeAsync = VideoController.AuthorizeUploadTus,
                        OnBeforeCreateAsync = async createContext => Directory.CreateDirectory(path),
                        //NOTE(Simon): Do not allow deleting by someone trying to exploit the protocol
                        OnBeforeDeleteAsync = async deleteContext => deleteContext.FailRequest(""),
                        OnFileCompleteAsync = VideoController.ProcessUploadTus,
                    }
                });
            });

            //Task.Run(PeriodicFunction);

            app.Run(async(context) =>
            {
                var watch            = Stopwatch.StartNew();
                var authenticateTask = UserSessions.GetLoggedInUser(context);

                PrintDebugData(context);

                SetJSONContentType(context);

                await authenticateTask;
                watch.Stop();
                CommonController.LogDebug($"request preamble: {watch.Elapsed.TotalMilliseconds} ms");

                await router.RouteAsync(context.Request, context);
            });
        }
예제 #5
0
        private static async Task VideosGetApi(HttpContext context)
        {
            var args = context.Request.Query;

            string   author = args["author"].ToString().ToLowerInvariant().Trim();
            int?     userid = null;
            DateTime?uploadDate;

            if (!Int32.TryParse(args["offset"].ToString(), out int offset) || offset < 0)
            {
                offset = 0;
            }

            if (!Int32.TryParse(args["count"].ToString(), out int count) || count > 100 || count < 0)
            {
                count = indexCountDefault;
            }

            using var connection = Database.OpenNewConnection();

            //TODO(Simon): Fuzzy search for username
            if (!String.IsNullOrEmpty(author))
            {
                userid = await UserController.UserIdFromUsername(author, connection);
            }

            if (!Int32.TryParse(args["agedays"].ToString(), out int daysOld) || daysOld < 0)
            {
                uploadDate = null;
            }
            else
            {
                uploadDate = DateTime.UtcNow.AddDays(-daysOld);
            }

            var videos = new VideoResponse();

            try
            {
                //TODO(Simon): There might be a faster way to get the count, while also executing just 1 query: add "count(*) OVER() AS total_count" to query
                videos.totalcount = await connection.QuerySingleAsync <int>(@"select count(*) from videos v
								inner join users u on v.userid = u.userid
								where (@userid::int is NULL or v.userid=@userid)
								and (@uploadDate::timestamp is NULL or v.timestamp>=@uploadDate)"                                , new { userid, uploadDate });

                videos.videos = await GetIndexVideos(IndexTab.MostWatched, count, offset, connection);

                videos.count = videos.videos.AsList().Count;
                videos.page  = videos.totalcount > 0 ? offset / videos.totalcount + 1 : 1;
                await context.Response.Body.WriteAsync(Utf8Json.JsonSerializer.SerializeUnsafe(videos));
            }
            catch (Exception e)
            {
                await CommonController.WriteError(context, "Something went wrong while processing this request", StatusCodes.Status500InternalServerError, e);
            }
        }
예제 #6
0
 private static void OnChange(object sender, FileSystemEventArgs e)
 {
     CommonController.LogDebug($"File Change: {e.FullPath}");
     try
     {
         string raw = File.ReadAllText(e.FullPath);
         templateCache[e.FullPath] = parser.Parse(raw);
     }
     catch { Debug.WriteLine("Error updating template: " + e); }
 }
예제 #7
0
 private static void OnRename(object sender, RenamedEventArgs e)
 {
     CommonController.LogDebug($"File Renamed: {e.OldFullPath} => {e.FullPath}");
     templateCache.Remove(e.OldFullPath);
     try
     {
         templateCache.TryAdd(e.FullPath, parser.Parse(File.ReadAllText(e.FullPath)));
     }
     catch { Debug.WriteLine("Error updating template: " + e); }
 }
예제 #8
0
        public static async Task ProcessUploadTus(FileCompleteContext arg)
        {
            CommonController.LogDebug("Begin processing upload");
            var context = arg.HttpContext;
            var headers = context.Request.Headers;

            if (Enum.TryParse <UploadFileType>(headers["type"].ToString(), out var type))
            {
                if (Guid.TryParse(headers["guid"], out var guid))
                {
                    var newFilename = headers["filename"];

                    //NOTE(Simon): Check if provided filename is a guid
                    if (!String.IsNullOrEmpty(newFilename))
                    {
                        CommonController.LogDebug("Upload has correct headers");
                        var    path        = Path.Combine(baseFilePath, guid.ToString());
                        var    tusFilePath = Path.Combine(path, arg.FileId);
                        string newFilePath;

                        switch (type)
                        {
                        case UploadFileType.Video:
                        case UploadFileType.Meta:
                        case UploadFileType.Tags:
                        case UploadFileType.Chapters:
                            newFilePath = Path.Combine(path, newFilename);
                            break;

                        case UploadFileType.Extra:
                            newFilePath = Path.Combine(path, "extra", newFilename);
                            break;

                        case UploadFileType.Miniature:
                            newFilePath = Path.Combine(path, "areaMiniatures", newFilename);
                            break;

                        default:
                            await CommonController.WriteError(context, "Unknown file type", StatusCodes.Status400BadRequest);

                            return;
                        }

                        CommonController.LogDebug($"Creating directory for {newFilePath} and moving uploaded file there");
                        Directory.CreateDirectory(Path.GetDirectoryName(newFilePath));
                        File.Move(tusFilePath, newFilePath, true);
                        await((ITusTerminationStore)arg.Store).DeleteFileAsync(arg.FileId, arg.CancellationToken);
                    }
                    else
                    {
                        await CommonController.WriteError(arg.HttpContext, "The project being uploaded is corrupted", StatusCodes.Status400BadRequest);
                    }
                }
            }
        }
예제 #9
0
        public static async Task <string> Render(HttpContext httpContext, string templateName, TemplateContext context, BaseLayout layout = BaseLayout.Web)
        {
            CommonController.LogDebug($"Begin Rendering {templateName}");
            var watch = Stopwatch.StartNew();

            if (templateCache.TryGetValue(templateName, out var template))
            {
                CommonController.LogDebug("Template found");
                var user = await UserSessions.GetLoggedInUser(httpContext);

                string result;

                if (context == null)
                {
                    context = new TemplateContext();
                    context.Options.MemberAccessStrategy = defaultAccessStrategy;
                    context.Options.Scope.SetValue("User", FluidValue.Create(user, context.Options));
                    AddDefaultFilters(context);

                    result = await template.RenderAsync();
                }
                else
                {
                    //NOTE(Simon): This lib requires users to register all used models by their type name.
                    //NOTE(cont.): This block prevents having to do it manually.
                    context.Options.MemberAccessStrategy = defaultAccessStrategy;
                    context.Options.Scope.SetValue("User", FluidValue.Create(user, context.Options));
                    AddDefaultFilters(context);

                    result = await template.RenderAsync(context);
                }

                if (layout != BaseLayout.None)
                {
                    var content = new TemplateContext(new { content = result });
                    context.Options.MemberAccessStrategy = defaultAccessStrategy;
                    content.Options.Scope.SetValue("User", FluidValue.Create(user, context.Options));
                    AddDefaultFilters(context);

                    result = await layoutCache[layout].RenderAsync(content);
                }

                watch.Stop();
                CommonController.LogDebug($"rendering: {watch.Elapsed.TotalMilliseconds} ms");

                return(result);
            }
            else
            {
                Console.WriteLine("Template not found");
                throw new Exception($"Something went wrong while rendering {templateName}");
            }
        }
예제 #10
0
        private static async Task ThumbnailGetApi(HttpContext context)
        {
            var    args = context.Request.Query;
            string id   = args["id"];

            if (Guid.TryParse(id, out _))
            {
                string filename = Path.Combine(baseFilePath, id, "thumb.jpg");

                context.Response.Headers.Add(HeaderNames.CacheControl, $"max-age={24 * 60 * 60}");
                await CommonController.WriteFile(context, filename, "image/jpg", "thumb.jpg");
            }
            else
            {
                await CommonController.Write404(context);
            }
        }
예제 #11
0
        private static async Task FilesGetApi(HttpContext context)
        {
            var    args    = context.Request.Query;
            string videoid = args["videoid"];

            if (String.IsNullOrEmpty(videoid))
            {
                await CommonController.Write404(context);

                return;
            }

            if (Guid.TryParse(videoid, out var guid))
            {
                using var connection = Database.OpenNewConnection();

                var video = await GetVideo(guid, connection);

                if (video.isPublic)
                {
                    try
                    {
                        string path      = Path.Combine(baseFilePath, guid.ToString());
                        var    di        = new DirectoryInfo(path);
                        var    files     = di.GetFiles("", SearchOption.AllDirectories);
                        var    filenames = files.Select(x => x.FullName.Substring(path.Length + 1));
                        await context.Response.Body.WriteAsync(Utf8Json.JsonSerializer.SerializeUnsafe(new { array = filenames }));
                        await AddVideoDownload(guid, connection);
                    }
                    catch (Exception e)
                    {
                        await CommonController.WriteError(context, "Something went wrong while processing this request", StatusCodes.Status500InternalServerError, e);
                    }
                }
                else
                {
                    await CommonController.Write404(context);
                }
            }
            else
            {
                await CommonController.Write404(context);
            }
        }
예제 #12
0
        private static async Task EditVideoPost(HttpContext context)
        {
            CommonController.SetHTMLContentType(context);

            var userTask = UserSessions.GetLoggedInUser(context);

            using var connection = Database.OpenNewConnection();
            var user = await userTask;

            if (user != null && GuidHelpers.TryDecode(context.Request.Query["id"], out var videoId))
            {
                var video = await GetVideo(videoId, connection);

                if (UserOwnsVideo(video, user.userid))
                {
                    var form = context.Request.Form;
                    video.title       = form["title"];
                    video.description = form["description"];

                    //TODO(Simon): Deduplicate tags. Should be cleaned by frontend, but may be malicious data.
                    string[] tags             = form["tags"].ToString().Split(',');
                    var      deduplicatedTags = new HashSet <string>(tags);
                    video.tags = deduplicatedTags.ToList();
                    if (Int32.TryParse(form["privacy"], out var privacyInt))
                    {
                        video.privacy = (VideoPrivacy)privacyInt;
                    }

                    await AddOrUpdateVideo(video, connection);

                    context.Response.Redirect("/my_videos");
                }
                else
                {
                    await CommonController.Write404(context);
                }
            }
            else
            {
                await CommonController.Write404(context);
            }
        }
예제 #13
0
        private static async Task UserGet(HttpContext context)
        {
            CommonController.SetHTMLContentType(context);

            var username = context.Request.Query["name"].ToString();

            if (!String.IsNullOrEmpty(username))
            {
                using var connection = Database.OpenNewConnection();
                var user = await UserController.UserFromUsername(username, connection);

                if (user != null)
                {
                    const int countPerPage = 20;
                    int       page         = 1;
                    if (context.Request.Query.ContainsKey("page"))
                    {
                        Int32.TryParse(context.Request.Query["page"], out page);
                    }

                    int offset = (page - 1) * countPerPage;

                    var numVideos = await NumPublicVideosForUser(user.userid, connection);

                    var VideosTask = PublicVideosForUser(user.userid, countPerPage, offset, connection);

                    var pagination = new Pagination(numVideos, countPerPage, offset);

                    var templateContext = new TemplateContext(new { videos = await VideosTask, user, pagination });

                    await context.Response.WriteAsync(await HTMLRenderer.Render(context, "Templates\\user.liquid", templateContext));
                }
                else
                {
                    await CommonController.Write404(context);
                }
            }
            else
            {
                await CommonController.Write404(context);
            }
        }
예제 #14
0
        private static async Task ReportBugPostApi(HttpContext context)
        {
            var form    = context.Request.Form;
            var problem = form["problem"];
            var repro   = form["repro"];
            var email   = form["email"];

            if (!String.IsNullOrEmpty(problem) && !String.IsNullOrEmpty(repro))
            {
                using var connection = Database.OpenNewConnection();

                bool success = await AddBugReport(problem, repro, email, connection);

                await context.Response.Body.WriteAsync(Utf8Json.JsonSerializer.SerializeUnsafe(new { success }));
            }
            else
            {
                await CommonController.WriteError(context, "This request is missing data", StatusCodes.Status400BadRequest);
            }
        }
예제 #15
0
        public async Task RouteAsync(HttpRequest request, HttpContext context)
        {
            CommonController.LogDebug("Attempt routing");
            if (routes.TryGetValue(new Route(request.Method, request.Path), out var del))
            {
                CommonController.LogDebug("Route found");
                var watch = Stopwatch.StartNew();

                await del.Invoke(context);

                CommonController.LogDebug("Route function invocation succesful");

                watch.Stop();
                CommonController.LogDebug($"controller: {watch.Elapsed.TotalMilliseconds} ms");
            }
            else
            {
                await routes[new Route("", "404")].Invoke(context);
            }
        }
예제 #16
0
        static HTMLRenderer()
        {
            var files = Directory.EnumerateFiles(templateDirectory, "*.liquid", SearchOption.AllDirectories);

            foreach (var file in files)
            {
                var normalizedFile = file.Replace('/', '\\');
                var rawTemplate    = File.ReadAllText(file);
                var template       = parser.Parse(rawTemplate);
                templateCache[normalizedFile] = template;
            }

            CommonController.LogDebug("Templates found:");
            foreach (var file in templateCache)
            {
                CommonController.LogDebug($"\t{file.Key}");
            }

            InitWatcher();
        }
예제 #17
0
        public static async Task <User> GetLoggedInUser(HttpContext context)
        {
            CommonController.LogDebug("Begin searching for logged in user");

            string sessionToken = context.Request.Cookies["session"];

            if (String.IsNullOrEmpty(sessionToken))
            {
                CommonController.LogDebug("No session cookie");
                sessionToken = context.Request.Query["token"].ToString();
                if (String.IsNullOrEmpty(sessionToken))
                {
                    CommonController.LogDebug("No session in query either. No user found");
                    return(null);
                }
            }

            if (cache[sessionToken] != null)
            {
                CommonController.LogDebug("Session in cache");
                return((User)cache[sessionToken]);
            }
            else
            {
                CommonController.LogDebug("Session not in cache. Retrieving from db");
                var user = await GetLoggedInUserSkipCache(sessionToken);

                if (user == null)
                {
                    CommonController.LogDebug("Bad token. No user found.");
                    return(null);
                }
                else
                {
                    cache.Set(sessionToken, user, defaultPolicy);
                    return(user);
                }
            }
        }
예제 #18
0
        private static async Task IndexGet(HttpContext context)
        {
            CommonController.SetHTMLContentType(context);
            var tabString = context.Request.Query["tab"].ToString();
            var tab       = tabString switch
            {
                "new" => IndexTab.New,
                "popular" => IndexTab.Popular,
                _ => IndexTab.MostWatched
            };

            int count  = 20;
            int offset = 0;

            using var connection = Database.OpenNewConnection();

            var videos = await GetIndexVideos(tab, count, offset, connection);

            var templateContext = new TemplateContext(new { videos, tab = tab.ToString() });

            await context.Response.WriteAsync(await HTMLRenderer.Render(context, "Templates\\index.liquid", templateContext));
        }
예제 #19
0
        private static async Task UpdateVideoPrivacyPost(HttpContext context)
        {
            CommonController.SetHTMLContentType(context);

            var userTask = UserSessions.GetLoggedInUser(context);

            using var connection = Database.OpenNewConnection();
            var user = await userTask;

            if (user != null && GuidHelpers.TryDecode(context.Request.Query["id"], out var videoid))
            {
                var video = await GetVideo(videoid, connection);

                if (UserOwnsVideo(video, user.userid))
                {
                    if (Int32.TryParse(context.Request.Form["video-privacy"], out int privacy))
                    {
                        await SetVideoPrivacy(video.id, (VideoPrivacy)privacy, connection);
                    }
                }
            }

            context.Response.Redirect("/my_videos");
        }
예제 #20
0
        private static async Task FinishUploadApi(HttpContext context)
        {
            var form = context.Request.Form;

            if (Guid.TryParse(form["id"], out var guid))
            {
                var userTask = UserSessions.GetLoggedInUser(context);

                using var connection = Database.OpenNewConnection();

                var user  = await userTask;
                var video = await GetVideo(guid, connection);

                if (user != null && await UserOwnsVideo(guid, user.userid, connection))
                {
                    var directoryPath = Path.Combine(baseFilePath, guid.ToString());
                    var videoPath     = Path.Combine(directoryPath, "main.mp4");
                    var thumbPath     = Path.Combine(directoryPath, "thumb.jpg");

                    var process = new Process();
                    process.StartInfo.UseShellExecute        = false;
                    process.StartInfo.RedirectStandardOutput = true;
                    process.StartInfo.RedirectStandardError  = true;

                    string ffmpegArgs = $"-hide_banner -loglevel error -y -ss 00:00:05 -i {videoPath} -frames:v 1 -q:v 3 -s 720x360 {thumbPath}";

                    if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
                    {
                        //process.StartInfo.FileName = @"\bin\ffmpeg.exe";
                        process.StartInfo.FileName  = "/usr/bin/ffmpeg";
                        process.StartInfo.Arguments = $"{ffmpegArgs}";
                        //process.StartInfo.Arguments = $"-c ffmpeg {ffmpegArgs}";
                        CommonController.LogDebug($"Running following command: {process.StartInfo.Arguments}");
                    }
                    else if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
                    {
                        process.StartInfo.FileName  = "cmd.exe";
                        process.StartInfo.Arguments = $"/C ffmpeg.exe {ffmpegArgs}";
                        CommonController.LogDebug($"Running following command: {process.StartInfo.Arguments}");
                    }

                    process.Start();

                    await process.WaitForExitAsync();

                    CommonController.LogDebug($"Ffmpeg exit code: {process.ExitCode}");
                    if (process.ExitCode == 0)
                    {
                        CommonController.LogDebug("Thumbnail generated succesfully");
                        video.privacy      = VideoPrivacy.Unlisted;
                        video.downloadsize = (int)GetDirectorySize(new DirectoryInfo(directoryPath));
                        video.length       = await FfmpegGetVideoLength(videoPath);

                        var meta = ReadMetaFile(Path.Combine(directoryPath, "meta.json"));

                        video.title       = meta.title;
                        video.description = meta.description;

                        CommonController.LogDebug($"Video Privacy: {video.privacy}");
                        CommonController.LogDebug($"Video Size: {video.downloadsize}");
                        CommonController.LogDebug($"Video length: {video.length}");

                        if (await AddOrUpdateVideo(video, connection))
                        {
                            CommonController.LogDebug("Database row updated succesfully");
                            await context.Response.WriteAsJsonAsync(new { success = true });

                            CleanPartialUploads(video.id);
                        }
                        else
                        {
                            Console.WriteLine("Error while updating database row for video");
                            await CommonController.WriteError(context, "{}", StatusCodes.Status500InternalServerError);
                        }
                    }
                    else
                    {
                        Console.WriteLine("Error while generating thumbnail");
                        Console.WriteLine($"\t {process.StandardOutput.ReadToEnd()}");
                        Console.WriteLine($"\t {process.StandardError.ReadToEnd()}");
                        await CommonController.WriteError(context, "{}", StatusCodes.Status500InternalServerError);
                    }
                }
                else
                {
                    CommonController.LogDebug("User does not own video");
                    await CommonController.WriteError(context, "{}", StatusCodes.Status401Unauthorized);
                }
            }
            else
            {
                await CommonController.WriteError(context, "{}", StatusCodes.Status400BadRequest);
            }
        }