text)
17 | {
18 | this.Text = string.Join("\r\n", text);
19 | }
20 |
21 | public string Text { get; set; }
22 |
23 | public override void WriteResult(Stream stream)
24 | {
25 | using (var streamWriter = new StreamWriter(stream, Encoding.ASCII))
26 | {
27 | streamWriter.Write(this.Text);
28 | }
29 | }
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/GopherServer.Core/Results/UrlResult.cs:
--------------------------------------------------------------------------------
1 | namespace GopherServer.Core.Results
2 | {
3 | ///
4 | /// Returns a HTML page with a link to the request url
5 | ///
6 | public class UrlResult : TextResult
7 | {
8 | public UrlResult(string url)
9 | {
10 | this.Text = string.Format("Follow {0}", url);
11 | }
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/GopherServer.Core/Routes/NamedGroupRoute.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text.RegularExpressions;
5 | using GopherServer.Core.Helpers;
6 | using GopherServer.Core.Results;
7 |
8 | namespace GopherServer.Core.Routes
9 | {
10 | public class NamedGroupRoute : Route
11 | {
12 | public NamedGroupRoute(string name, string pattern, Delegate resultMethod)
13 | {
14 | this.Name = name;
15 | this.RegexString = pattern;
16 | this.Delegate = resultMethod;
17 | this.BuildRegex(pattern);
18 | }
19 |
20 | protected Delegate Delegate { get; set; }
21 |
22 | ///
23 | /// Returns a dictionary of groups and their values for the route regex on the specified selector.
24 | ///
25 | ///
26 | ///
27 | private Dictionary GetValues(string selector)
28 | {
29 | var groupValues = new Dictionary();
30 | // Get the groupnames
31 | var groups = Regex.Match(selector).Groups;
32 | foreach (var groupName in Regex.GetGroupNames())
33 | groupValues.Add(groupName, groups[groupName].Value);
34 |
35 | return groupValues;
36 | }
37 |
38 | public override BaseResult Execute(string selector)
39 | {
40 | var methodParams = this.Delegate.Method.GetParameters();
41 |
42 | // Build our arguments
43 | var selectorValues = this.GetValues(selector);
44 |
45 | var args = methodParams.Select(p => selectorValues[p.Name].ToType(p.ParameterType)).ToArray();
46 |
47 | return (BaseResult)this.Delegate.DynamicInvoke(args);
48 | }
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/GopherServer.Core/Routes/PrebuiltRoutes.cs:
--------------------------------------------------------------------------------
1 | using GopherServer.Core.Results;
2 |
3 | namespace GopherServer.Core.Routes
4 | {
5 | ///
6 | /// Contains pre-built routes you can use directly in your Provider
7 | ///
8 | public static class PrebuiltRoutes
9 | {
10 | ///
11 | /// Returns the client a link to the specfied URL
12 | ///
13 | ///
14 | ///
15 | public static TypedRoute UrlResult() => new TypedRoute("Url", @"URL:(.+)", url => new UrlResult(url));
16 |
17 | public static TypedRoute GifRoute() => new TypedRoute("Gif", @"GIF:(.+)", url => new GifResult(url));
18 |
19 | public static TypedRoute HtmlProxy() => new TypedRoute("Html", @"HTML:(.+)", url => new HtmlResult(url));
20 |
21 | public static TypedRoute ProxyRoute() => new TypedRoute("Proxy", @"PROXY:(.+)", url => new ProxyResult(url));
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/GopherServer.Core/Routes/Route.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Text.RegularExpressions;
3 | using GopherServer.Core.Results;
4 |
5 | namespace GopherServer.Core.Routes
6 | {
7 | ///
8 | /// Helper class to assist with providers handling selectors
9 | ///
10 | public class Route
11 | {
12 | public Route() { }
13 |
14 | ///
15 | /// Create a route
16 | ///
17 | /// Name of route
18 | /// Regex partern for matching selectors
19 | /// Action to perform if this route is found
20 | public Route(string name, string pattern, Func action)
21 | {
22 | this.Name = name;
23 | this.RegexString = pattern;
24 | this.Action = action;
25 | this.BuildRegex(pattern);
26 | }
27 |
28 | internal Regex Regex { get; set; }
29 | public string Name { get; set; }
30 | public string RegexString { get; set; }
31 | public Func Action { get; set; }
32 |
33 | internal void BuildRegex(string pattern) => this.Regex = new Regex(pattern);
34 | public bool IsMatch(string selector) => Regex.IsMatch(selector);
35 | public virtual BaseResult Execute(string selector) => Action();
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/GopherServer.Core/Routes/TypedRoute.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using GopherServer.Core.Results;
3 |
4 | namespace GopherServer.Core.Routes
5 | {
6 | ///
7 | /// For Regex patterns which returns a *single* group
8 | ///
9 | ///
10 | public class TypedRoute : Route
11 | {
12 | public TypedRoute(string name, string pattern, Func action)
13 | {
14 | this.Name = name;
15 | this.RegexString = pattern;
16 | this.TypedAction = action;
17 | this.BuildRegex(pattern);
18 | }
19 |
20 | public Func TypedAction { get; set; }
21 |
22 | public T GetValue(string selector)
23 | {
24 | var v = Regex.Match(selector).Groups[1].Value;
25 | return (T)Convert.ChangeType(v, typeof(T));
26 | }
27 |
28 | public override BaseResult Execute(string selector)
29 | {
30 | var value = this.GetValue(selector);
31 | return TypedAction(value);
32 | }
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/GopherServer.Providers.FileProvider/FileProvider.cs:
--------------------------------------------------------------------------------
1 | using System.IO;
2 | using System.Linq;
3 | using GopherServer.Core.Providers;
4 | using GopherServer.Core.Results;
5 |
6 | namespace GopherServer.Providers.FileProvider
7 | {
8 | public class FileProvider : ServerProviderBase
9 | {
10 | public FileProvider(string hostname, int port) : base(hostname, port)
11 | { }
12 |
13 | public string BaseDirectory { get; set; }
14 |
15 | public override void Init() => this.BaseDirectory = Settings.RootDirectory;
16 |
17 | public override BaseResult GetResult(string selector)
18 | {
19 | if (string.IsNullOrEmpty(selector))
20 | return new DirectoryListingResult(BaseDirectory, BaseDirectory);
21 |
22 | if (selector.Contains(".."))
23 | return new ErrorResult("Invalid Path");
24 |
25 | selector = selector.TrimStart(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar);
26 |
27 | var path = Path.Combine(BaseDirectory, selector);
28 |
29 | var indexPath = Path.Combine(path, "index.gopher");
30 |
31 | if (File.Exists(indexPath))
32 | return new TextResult(File.ReadAllLines(indexPath).ToList());
33 |
34 | if (File.Exists(path))
35 | {
36 | return new FileResult(path, BaseDirectory);
37 | }
38 |
39 | if (Directory.Exists(path))
40 | return new DirectoryListingResult(path, BaseDirectory);
41 |
42 | return new ErrorResult("Invalid Path");
43 | }
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/GopherServer.Providers.FileProvider/GopherServer.Providers.FileProvider.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | netstandard2.0
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/GopherServer.Providers.FileProvider/Results/DirectoryInfoResult.cs:
--------------------------------------------------------------------------------
1 | using System.IO;
2 | using System.Linq;
3 | using GopherServer.Core.Models;
4 | using GopherServer.Core.Results;
5 |
6 | namespace GopherServer.Providers.FileProvider
7 | {
8 | public class DirectoryListingResult : DirectoryResult
9 | {
10 | public DirectoryListingResult(string path, string baseDirectory)
11 | {
12 | var dir = new DirectoryInfo(path);
13 | var directories = dir.GetDirectories();
14 | var files = dir.GetFiles();
15 |
16 | // List Directories first
17 | Items.AddRange(directories.Select(d => new DirectoryItem(d.Name, d.FullName.Replace(baseDirectory, string.Empty))));
18 | Items.AddRange(files.Select(f => new DirectoryItem(GetItemType(f.FullName), f.Name, f.FullName.Replace(baseDirectory, string.Empty))));
19 | }
20 |
21 | private ItemType GetItemType(string fullName)
22 | {
23 | // TODO: Handle mapping these
24 | //return ItemType.FILE;
25 | return Core.Helpers.FileTypeHelpers.GetItemTypeFromFileName(fullName);
26 | }
27 | }
28 | }
--------------------------------------------------------------------------------
/GopherServer.Providers.FileProvider/Results/FileResult.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.IO;
3 | using GopherServer.Core.Results;
4 |
5 | namespace GopherServer.Providers.FileProvider
6 | {
7 | internal class FileResult : BaseResult
8 | {
9 | private string baseDirectory;
10 | private string path;
11 |
12 | public FileResult(string path, string baseDirectory)
13 | {
14 | this.path = path;
15 | this.baseDirectory = baseDirectory;
16 | }
17 |
18 | public override void WriteResult(Stream stream) => File.OpenRead(this.path).CopyTo(stream);
19 | }
20 | }
--------------------------------------------------------------------------------
/GopherServer.Providers.FileProvider/Settings.cs:
--------------------------------------------------------------------------------
1 | using System.Configuration;
2 |
3 | namespace GopherServer.Providers.FileProvider
4 | {
5 | public static class Settings
6 | {
7 | public static string RootDirectory => ConfigurationManager.AppSettings["FileProvider.RootDirectory"];
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/GopherServer.Providers.MacintoshGarden/Extensions/AngleSharpExtensions.cs:
--------------------------------------------------------------------------------
1 | using AngleSharp.Dom;
2 | using AngleSharp.Html.Dom;
3 |
4 | namespace GopherServer.Providers.MacintoshGarden.Extensions
5 | {
6 | public static class AngleSharpExtensions
7 | {
8 | public static string TryGetContent(this IElement element, string defaultText = "")
9 | {
10 | if (element == null)
11 | return defaultText;
12 |
13 | return element.TextContent ?? defaultText;
14 | }
15 |
16 | public static string TryGetHref(this IElement element, string defaultText = "")
17 | {
18 | var href = element as IHtmlAnchorElement;
19 | if (href == null)
20 | return defaultText;
21 |
22 | return href.Href ?? defaultText;
23 | }
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/GopherServer.Providers.MacintoshGarden/GopherServer.Providers.MacintoshGarden.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 | netstandard2.0
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/GopherServer.Providers.MacintoshGarden/MacGardenController.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Linq;
3 | using System.Net;
4 | using GopherServer.Core.Models;
5 | using GopherServer.Core.Results;
6 | using GopherServer.Providers.MacintoshGarden.Results;
7 | using GopherServer.Providers.MacintoshGarden.Models;
8 |
9 | namespace GopherServer.Providers.MacintoshGarden
10 | {
11 | public class MacGardenController
12 | {
13 | private string[] validHosts = new string[] { "www.macintoshgarden.org", "macintoshgarden.org", "mirror.macintoshgarden.org" };
14 |
15 | public BaseResult ShowApp(string url) => new SoftwareResult(new SoftwareItem(url));
16 |
17 | internal DirectoryResult Search(string search)
18 | {
19 | var searchUrl = "http://macintoshgarden.org/search/node/" + WebUtility.UrlEncode(search + " type:app,game");
20 | var searchResults = new Models.SearchResults(searchUrl);
21 | return new Results.SearchResult(searchResults);
22 | }
23 |
24 | internal DirectoryResult SearchPage(string url)
25 | {
26 | var searchResults = new Models.SearchResults(url);
27 | return new Results.SearchResult(searchResults);
28 | }
29 |
30 | public BaseResult DoDownload(string url)
31 | {
32 | var uri = new Uri(url);
33 |
34 | if (!validHosts.Contains(uri.Host))
35 | return new ErrorResult("Invalid host");
36 |
37 | return new ProxyResult(url, "http://macintoshgarden.org");
38 | }
39 |
40 | public DirectoryResult ShowHome()
41 | {
42 | var result = new DirectoryResult();
43 | result.Items.Add(new DirectoryItem("Macintosh Garden - Gopher Edition"));
44 | result.Items.Add(new DirectoryItem("================================="));
45 | result.Items.Add(new DirectoryItem(""));
46 | result.Items.Add(new DirectoryItem(ItemType.INDEXSEARCH, "Search the Garden", "/search/"));
47 |
48 | return result;
49 | }
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/GopherServer.Providers.MacintoshGarden/MacintoshGardenProvider.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using GopherServer.Core.Providers;
5 | using GopherServer.Core.Results;
6 | using GopherServer.Core.Routes;
7 |
8 | namespace GopherServer.Providers.MacintoshGarden
9 | {
10 | public class MacintoshGardenProvider : ServerProviderBase
11 | {
12 | public List Routes { get; private set; }
13 | public MacGardenController Controller { get; set; }
14 |
15 | public MacintoshGardenProvider(string hostname, int port) : base(hostname, port)
16 | { }
17 |
18 | public override void Init()
19 | {
20 | Controller = new MacintoshGarden.MacGardenController();
21 |
22 | this.Routes = new List() {
23 | new TypedRoute("Bin", "BIN:(.+)", Controller.DoDownload), // Proxy Download
24 | new TypedRoute("Search", @"\/search\/*\t(.+)", Controller.Search), // Search
25 | new TypedRoute("Search", @"\/search\/(http://macintoshgarden.org\/.+)", Controller.SearchPage),
26 | new TypedRoute("App", @"\/app\/(.+)", Controller.ShowApp), // App Result
27 | PrebuiltRoutes.GifRoute(), // Screenshot
28 | };
29 | }
30 |
31 | ///
32 | /// Processes the selector and performs the appropriate action
33 | ///
34 | ///
35 | ///
36 | public override BaseResult GetResult(string selector)
37 | {
38 | // This is where we read our selectors...
39 | // it's a shame we can't reuse the route code out of MVC (or can we ?)
40 |
41 | try
42 | {
43 | if (string.IsNullOrEmpty(selector) || selector == "1") // some clients seem to use 1
44 | return Controller.ShowHome();
45 |
46 | // Check our routes
47 | var route = Routes.FirstOrDefault(r => r.IsMatch(selector));
48 |
49 | if (route == null)
50 | return new ErrorResult("Selector '" + selector + "' was not found/is not supported.");
51 | else
52 | return route.Execute(selector);
53 | }
54 | catch (Exception ex)
55 | {
56 | // TODO: Some kind of common logging?
57 | Console.WriteLine(ex);
58 | return new ErrorResult("Error occurred processing your request.");
59 | }
60 | }
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/GopherServer.Providers.MacintoshGarden/Models/DownloadDetails.cs:
--------------------------------------------------------------------------------
1 | namespace GopherServer.Providers.MacintoshGarden.Models
2 | {
3 | public class DownloadDetails
4 | {
5 | public string Title { get; set; }
6 | public string Size { get; set; }
7 | public string Os { get; set; }
8 | public DownloadLink[] Links { get;set;}
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/GopherServer.Providers.MacintoshGarden/Models/DownloadLink.cs:
--------------------------------------------------------------------------------
1 | namespace GopherServer.Providers.MacintoshGarden.Models
2 | {
3 | public class DownloadLink
4 | {
5 | public string Text { get; set; }
6 | public string Url { get; set; }
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/GopherServer.Providers.MacintoshGarden/Models/SearchResult.cs:
--------------------------------------------------------------------------------
1 | namespace GopherServer.Providers.MacintoshGarden.Models
2 | {
3 | public class SearchResult
4 | {
5 | public string Name { get; set; }
6 | public string Url { get; set; }
7 | public string Selector { get; set; }
8 | public string SearchSnippet { get; set; }
9 | public string Category { get; set; }
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/GopherServer.Providers.MacintoshGarden/Models/SearchResults.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 | using System.Linq;
3 | using AngleSharp;
4 | using AngleSharp.Html.Dom;
5 |
6 | namespace GopherServer.Providers.MacintoshGarden.Models
7 | {
8 | public class SearchResults
9 | {
10 | public string Url { get; set; }
11 | public string Search { get; set; }
12 | public string NextPageLink { get; set; }
13 | public string PreviousPageLink { get; set; }
14 | public string PageNumber { get; set; }
15 | public List Results { get; set; }
16 |
17 | public SearchResults(string url)
18 | {
19 | this.Url = url;
20 | this.Parse(url);
21 | }
22 |
23 | private void Parse(string url)
24 | {
25 | var config = Configuration.Default.WithDefaultLoader();
26 | using (var doc = BrowsingContext.New(config).OpenAsync(url).Result)
27 | {
28 |
29 | var searchText = doc.QuerySelector("#edit-keys").NodeValue;
30 | var currentPageNode = doc.QuerySelector("#paper > div.box > div > div > ul > li.pager-current");
31 | var currentPage = currentPageNode == null ? "1" : currentPageNode.TextContent;
32 |
33 | var nextPageNode = doc.QuerySelector("#paper > div.box > div > div > ul > li.pager-next > a") as IHtmlAnchorElement;
34 | var previousPageNode = doc.QuerySelector("#paper > div.box > div > div > ul > li.pager-previous > a") as IHtmlAnchorElement;
35 |
36 | this.Search = searchText;
37 | if (nextPageNode != null)
38 | this.NextPageLink = nextPageNode.Href;
39 | if (previousPageNode != null)
40 | this.PreviousPageLink = previousPageNode.Href;
41 | this.PageNumber = currentPage;
42 |
43 | var searchItemsNodes = doc.QuerySelectorAll("#paper > div.box > div > dl > dt.title a, dd .search-snippet");
44 |
45 | var results = new List();
46 | if (searchItemsNodes.Any())
47 | {
48 | // The first node should be the title href
49 | // the second the search snippet
50 | // eg node[0] == title
51 | // node[1] == snippet
52 | var itemCount = searchItemsNodes.Count();
53 | for (int i = 0; i < itemCount; i = i + 2)
54 | {
55 | var titleNode = ((IHtmlAnchorElement)searchItemsNodes[i]);
56 | var searchSnippet = searchItemsNodes[i + 1];
57 |
58 | var result = new SearchResult
59 | {
60 | Name = titleNode.Text,
61 | Url = titleNode.Href,
62 | SearchSnippet = searchSnippet.TextContent,
63 | Selector = "/app/" + titleNode.Href
64 | };
65 |
66 | results.Add(result);
67 | }
68 | }
69 |
70 | this.Results = results;
71 | }
72 | }
73 | }
74 | }
75 |
--------------------------------------------------------------------------------
/GopherServer.Providers.MacintoshGarden/Models/SoftwareItem.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 | using System.Linq;
3 | using AngleSharp;
4 | using AngleSharp.Html.Dom;
5 | using GopherServer.Core.Helpers;
6 | using GopherServer.Providers.MacintoshGarden.Extensions;
7 |
8 | namespace GopherServer.Providers.MacintoshGarden.Models
9 | {
10 | public class SoftwareItem
11 | {
12 | public string Title { get; set; }
13 | public string Url { get; set; }
14 | public string Rating { get; set; }
15 | public string Category { get; set; }
16 | public string CategoryLink { get; set; }
17 | public string YearReleased { get; set; }
18 | public string YearReleasedLink { get; set; }
19 | public string Author { get; set; }
20 | public string AuthorLink { get; set; }
21 | public string Publisher { get; set; }
22 | public string PublisherLink { get; set; }
23 | public string[] Screenshots { get; set; }
24 | public string Description { get; set; }
25 | public string ManualUrl { get; set; }
26 | public DownloadDetails[] Downloads { get; set; }
27 |
28 | public SoftwareItem(string url)
29 | {
30 | var html = HttpHelpers.GetUrl(url);
31 | this.Url = url;
32 | this.Parse(url);
33 | }
34 |
35 | public void Parse(string url)
36 | {
37 | var config = Configuration.Default.WithDefaultLoader();
38 | using (var doc = BrowsingContext.New(config).OpenAsync(url).Result)
39 | {
40 | this.Title = doc.QuerySelector("#paper > h1").TextContent.Trim();
41 | this.Rating = doc.QuerySelector("#edit-vote-wrapper > div.description > div > span.average-rating > span").TryGetContent("No rating").CleanString();
42 |
43 | this.Category = doc.QuerySelector("#paper > div.game-preview > div.descr > table > tbody > tr:nth-child(2) > td:nth-child(2) > ul > li > a").TryGetContent("No Category").CleanString();
44 | this.CategoryLink = doc.QuerySelector("#paper > div.game-preview > div.descr > table > tbody > tr:nth-child(2) > td:nth-child(2) > ul > li > a").TryGetHref();
45 |
46 | // For the screenshots we just want to link to full images
47 | var screenNodes = doc.QuerySelectorAll("#paper > div.game-preview > div.images a.thickbox");
48 |
49 | if (screenNodes.Any())
50 | this.Screenshots = screenNodes.OfType().Select(n => n.Href).ToArray();
51 |
52 | this.Description = string.Join("\r\n", doc.QuerySelectorAll("#paper > p").Select(n => n.TextContent));
53 | //this.ManualUrl = doc.QuerySelector("#paper .note.manual a").GetAttribute("href");
54 |
55 | var downloadNodes = doc.QuerySelectorAll("#paper > div.game-preview > div.descr .note.download");
56 |
57 | List downloads = new List();
58 |
59 | foreach (var downloadElement in downloadNodes)
60 | {
61 | // Skip Purchase links
62 | if (downloadElement.QuerySelector("a").TextContent == "Purchase")
63 | continue;
64 |
65 | // Grab the filename > div:nth-child(2) > small
66 | var fileName = downloadElement.QuerySelector("br + small").FirstChild.TextContent.CleanString();
67 | var fileSize = downloadElement.QuerySelector("br + small > i").TryGetContent().CleanString().TrimStart('(');
68 | var os = downloadElement.LastChild.TextContent.CleanString();
69 |
70 | // Grab the links with in each element
71 | //var links = downloadElement.QuerySelectorAll("a");
72 | var links = downloadElement.QuerySelectorAll("a").OfType();
73 |
74 | downloads.Add(new DownloadDetails()
75 | {
76 | Title = fileName,
77 | Size = fileSize,
78 | Os = os,
79 | Links = links.Select(l => new DownloadLink() { Text = l.Text, Url = l.Href }).ToArray()
80 | });
81 | }
82 |
83 | this.Downloads = downloads.ToArray();
84 | }
85 | }
86 | }
87 | }
88 |
--------------------------------------------------------------------------------
/GopherServer.Providers.MacintoshGarden/Results/SearchResults.cs:
--------------------------------------------------------------------------------
1 | using GopherServer.Core.Helpers;
2 | using GopherServer.Core.Models;
3 | using GopherServer.Core.Results;
4 |
5 | namespace GopherServer.Providers.MacintoshGarden.Results
6 | {
7 | public class SearchResult : DirectoryResult
8 | {
9 | public SearchResult(Models.SearchResults results)
10 | {
11 | this.Items.Add(new DirectoryItem("Search Results"));
12 | this.Items.Add(new DirectoryItem("--------------"));
13 |
14 | this.Items.Add(new DirectoryItem(Core.Models.ItemType.INDEXSEARCH, "New Search", "/search/"));
15 |
16 | foreach (var result in results.Results)
17 | {
18 | this.Items.Add(new DirectoryItem(result.Name, result.Selector));
19 | this.Items.AddRange(result.SearchSnippet.CleanString().WrapToDirectoryItems());
20 | this.Items.Add(new DirectoryItem("----"));
21 | }
22 |
23 | this.Items.Add(new DirectoryItem("Current Page: " + results.PageNumber));
24 |
25 | if (!string.IsNullOrEmpty(results.PreviousPageLink))
26 | this.Items.Add(new DirectoryItem("Previous Page", "/search/" + results.PreviousPageLink));
27 | if (!string.IsNullOrEmpty(results.NextPageLink))
28 | this.Items.Add(new DirectoryItem("Next Page", "/search/" + results.NextPageLink));
29 | }
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/GopherServer.Providers.MacintoshGarden/Results/SoftwareResult.cs:
--------------------------------------------------------------------------------
1 | using GopherServer.Core.Helpers;
2 | using GopherServer.Core.Models;
3 | using GopherServer.Core.Results;
4 | using GopherServer.Providers.MacintoshGarden.Models;
5 |
6 | namespace GopherServer.Providers.MacintoshGarden.Results
7 | {
8 | public class SoftwareResult : DirectoryResult
9 | {
10 | public SoftwareResult(SoftwareItem item) : base()
11 | {
12 | Items.Add(new DirectoryItem(new string('*', item.Title.Length + 4)));
13 | Items.Add(new DirectoryItem("* " + item.Title + " *"));
14 | Items.Add(new DirectoryItem(new string('*', item.Title.Length + 4)));
15 |
16 | if (item.Screenshots != null)
17 | {
18 | foreach (var screenshot in item.Screenshots) Items.Add(new DirectoryItem(ItemType.GIF, "Screenshot", "GIF:" + screenshot));
19 | }
20 |
21 | Items.AddRange(item.Description.WrapToDirectoryItems());
22 | Items.Add(new DirectoryItem(""));
23 |
24 | Items.Add(new DirectoryItem("Downloads"));
25 | Items.Add(new DirectoryItem("========="));
26 | foreach (var download in item.Downloads)
27 | {
28 | //Items.Add(new DirectoryItem(new string('-', download.Title.Length)));
29 | //Items.Add(new DirectoryItem(download.Title));
30 | //Items.Add(new DirectoryItem(new string('-', download.Title.Length)));
31 | foreach (var link in download.Links)
32 | {
33 | Items.Add(new DirectoryItem(link.Text + ":"));
34 | Items.Add(new DirectoryItem(Core.Helpers.FileTypeHelpers.GetItemTypeFromFileName(link.Url), download.Title, "BIN:" + link.Url));
35 | }
36 | Items.Add(new DirectoryItem("Filesize: " + download.Size));
37 | Items.Add(new DirectoryItem("OS: " + download.Os));
38 | Items.Add(new DirectoryItem(""));
39 | }
40 | }
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/GopherServer.Providers.Rss/Data/Db.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using SQLite;
5 | using SQLiteNetExtensions.Extensions;
6 | using GopherServer.Core.Rss.Syndication;
7 |
8 | namespace GopherServer.Core.Rss.Data
9 | {
10 | public class Db
11 | {
12 | SQLiteConnection connection;
13 |
14 | public Db(string dbPath)
15 | {
16 | connection = new SQLiteConnection(dbPath);
17 | CreateDb();
18 | }
19 |
20 | public void CreateDb()
21 | {
22 | connection.CreateTable();
23 | connection.CreateTable();
24 | connection.CreateTable();
25 | connection.CreateTable();
26 | }
27 |
28 | public User GetUser(string nickname)
29 | {
30 | var user = connection.Table().FirstOrDefault(u => u.NickName == nickname);
31 | if (user == null)
32 | throw new Exception("No user found for nickanme '" + nickname + "'");
33 |
34 | return user;
35 | }
36 |
37 | public Feed GetFeed(int feedId)
38 | {
39 | return connection.Find(feedId);
40 | }
41 |
42 | public string GetFeedCache(string nickname, int feedId)
43 | {
44 | var user = this.GetUser(nickname);
45 | // Make sure the user has a feed
46 | if (!connection.Table().Any(f => f.FeedId == feedId && f.UserId == user.Id))
47 | throw new Exception("Invalid Feed Id for this user.");
48 |
49 | return connection.Table().FirstOrDefault(f => f.FeedId == feedId).CacheData;
50 | }
51 |
52 | internal IEnumerable GetFeeds()
53 | {
54 | return connection.Table();
55 | }
56 |
57 | public IEnumerable GetUsers()
58 | {
59 | return connection.Table().AsEnumerable();
60 | }
61 |
62 | public IEnumerable GetUserFeeds(string nickName)
63 | {
64 | var userId = this.GetUser(nickName).Id;
65 | var user = connection.GetWithChildren(userId);
66 | return user.Feeds;
67 | }
68 |
69 | public void AddUser(string nickName)
70 | {
71 | connection.Insert(new User()
72 | {
73 | NickName = nickName,
74 | Created = DateTime.Now,
75 | LastLogin = DateTime.Now,
76 | });
77 | }
78 |
79 | public int AddFeed(string nickName, string url)
80 | {
81 | // Get the User
82 | var user = GetUser(nickName);
83 |
84 | // Check if the URL already exists
85 | var feed = connection.Table().FirstOrDefault(f => f.Url == url);
86 | if (feed == null)
87 | {
88 | // Create the feed
89 | feed = new Feed();
90 | feed.Feedname = "TBA";
91 | feed.Url = url;
92 |
93 | connection.Insert(feed);
94 | }
95 | // Check if the user has the feed
96 | if (connection.GetAllWithChildren().Any(f => f.FeedId == feed.Id && f.User.NickName == nickName))
97 | return feed.Id; // user already has feed
98 |
99 |
100 | UserFeed userFeed = new UserFeed()
101 | {
102 | FeedId = feed.Id,
103 | UserId = user.Id
104 | };
105 |
106 | connection.Insert(userFeed);
107 |
108 | return feed.Id;
109 | }
110 |
111 | public void UpdateCache(int feedId, string cacheData)
112 | {
113 | // grab the existing cache (if it exists)
114 | var cache = connection.Table().FirstOrDefault(c => c.FeedId == feedId);
115 | if (cache == null)
116 | {
117 | cache = new FeedCache();
118 | cache.FeedId = feedId;
119 | cache.CacheData = cacheData;
120 | cache.LastRefreshed = DateTime.Now;
121 | connection.Insert(cache);
122 | return;
123 | }
124 | cache.CacheData = cacheData;
125 | cache.LastRefreshed = DateTime.Now;
126 | connection.Update(cache);
127 | }
128 |
129 | public void UpdateFeed(int feedId, FeedDetails detail)
130 | {
131 | var feed = connection.Find(feedId);
132 | feed.Feedname = detail.Title;
133 | connection.Update(feed);
134 | this.UpdateCache(feedId, detail.FeedXml);
135 | }
136 |
137 | public void UpdateUserLogin(string nickName)
138 | {
139 | var user = GetUser(nickName);
140 | user.LastLogin = DateTime.Now;
141 | connection.Update(user);
142 | }
143 |
144 | public List CachedFeedDataForUser(string nickName)
145 | {
146 | var user = GetUser(nickName);
147 | return user.Feeds.Select(f => f.FeedCache.CacheData).ToList();
148 | }
149 |
150 | }
151 | }
152 |
--------------------------------------------------------------------------------
/GopherServer.Providers.Rss/Data/Feed.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 | using SQLite;
3 | using SQLiteNetExtensions.Attributes;
4 |
5 | namespace GopherServer.Core.Rss.Data
6 | {
7 | public class Feed
8 | {
9 | [PrimaryKey, AutoIncrement]
10 | public int Id { get; set; }
11 |
12 | [MaxLength(100)]
13 | public string Feedname { get; set; }
14 |
15 | [Indexed]
16 | [MaxLength(255)]
17 | public string Url { get; set; }
18 |
19 | [OneToOne]
20 | public FeedCache FeedCache { get; set; }
21 |
22 | [ManyToMany(typeof(User))]
23 | public List Users { get; set; }
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/GopherServer.Providers.Rss/Data/FeedCache.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using SQLite;
3 | using SQLiteNetExtensions.Attributes;
4 |
5 | namespace GopherServer.Core.Rss.Data
6 | {
7 | public class FeedCache
8 | {
9 | [PrimaryKey, AutoIncrement]
10 | public int Id { get; set; }
11 |
12 | public string CacheData { get; set; }
13 |
14 | [ForeignKey(typeof(Feed))]
15 | public int FeedId { get; set; }
16 |
17 | public DateTime LastRefreshed { get; set; }
18 |
19 | [OneToOne]
20 | public Feed Feed { get; set; }
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/GopherServer.Providers.Rss/Data/User.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using SQLite;
4 | using SQLiteNetExtensions.Attributes;
5 |
6 | namespace GopherServer.Core.Rss.Data
7 | {
8 | public class User
9 | {
10 | [PrimaryKey, AutoIncrement]
11 | public int Id { get; set; }
12 |
13 | [Indexed]
14 | [MaxLength(16)]
15 | public string NickName { get; set; }
16 |
17 | public DateTime Created { get; set; }
18 |
19 | public DateTime LastLogin { get; set; }
20 |
21 | [ManyToMany(typeof(UserFeed))]
22 | public List Feeds { get; set; }
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/GopherServer.Providers.Rss/Data/UserFeed.cs:
--------------------------------------------------------------------------------
1 | using SQLite;
2 | using SQLiteNetExtensions.Attributes;
3 |
4 | namespace GopherServer.Core.Rss.Data
5 | {
6 | public class UserFeed
7 | {
8 | [PrimaryKey, AutoIncrement]
9 | public int Id { get; set; }
10 |
11 | [ForeignKey(typeof(User))]
12 | public int UserId { get; set; }
13 |
14 | [ForeignKey(typeof(Feed))]
15 | public int FeedId { get; set; }
16 |
17 | [ManyToOne]
18 | public Feed Feed { get; set; }
19 |
20 | [ManyToOne]
21 | public User User { get; set; }
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/GopherServer.Providers.Rss/GopherResults/FeedItemResult.cs:
--------------------------------------------------------------------------------
1 | using System.Linq;
2 | using System.ServiceModel.Syndication;
3 | using GopherServer.Core.Helpers;
4 | using GopherServer.Core.Models;
5 | using GopherServer.Core.Results;
6 |
7 | namespace GopherServer.Core.Rss.GopherResults
8 | {
9 | public class FeedItemResult : DirectoryResult
10 | {
11 | public FeedItemResult(string nickname, int feedId, string xml, string itemId) : base()
12 | {
13 | var feed = new Syndication.FeedDetails(xml);
14 | // Find the item
15 | var item = feed.Feed.Items.FirstOrDefault(i => i.Id == itemId);
16 | var content = item.Content as TextSyndicationContent;
17 | var text = content != null ? content.Text : item.Summary.Text;
18 |
19 | this.Items.Add(new DirectoryItem(item.Title.Text));
20 | this.Items.Add(new DirectoryItem("------------"));
21 | this.Items.Add(new DirectoryItem(item.PublishDate.UtcDateTime.ToString()));
22 | this.Items.AddRange(text.HtmlToText().WrapToDirectoryItems());
23 |
24 | if (item.Links.Any())
25 | this.Items.Add(new ExternalUrlItem("Read More...", item.Links.First().Uri.ToString()));
26 |
27 | this.Items.Add(new DirectoryItem("---"));
28 | this.Items.Add(new DirectoryItem("Return to '" + feed.Title + "'...", string.Format("/feeds/{0}/{1}/", nickname, feedId)));
29 | this.Items.Add(new DirectoryItem("Return to Feed List...", string.Format("/feeds/{0}/", nickname)));
30 | }
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/GopherServer.Providers.Rss/GopherResults/FeedListingResult.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 | using System.Linq;
3 | using GopherServer.Core.Models;
4 | using GopherServer.Core.Results;
5 | using GopherServer.Core.Rss.Data;
6 |
7 | namespace GopherServer.Core.Rss.GopherResults
8 | {
9 | public class FeedListingResult : DirectoryResult
10 | {
11 | public FeedListingResult(string nickname, IEnumerable feeds) : base()
12 | {
13 | this.Items.Add(new DirectoryItem("Feeds for '" + nickname + "'"));
14 | this.Items.Add(new DirectoryItem(""));
15 | this.Items.Add(new DirectoryItem(ItemType.INDEXSEARCH, "Add Feed", string.Format("/user/{0}/add/", nickname)));
16 | this.Items.Add(new DirectoryItem(" Enter the URL of the feed when prompted."));
17 | this.Items.Add(new DirectoryItem(""));
18 |
19 | if (feeds == null || feeds.Count() == 0)
20 | {
21 | this.Items.Add(new DirectoryItem("No feeds found."));
22 | return;
23 | }
24 |
25 | this.Items.Add(new DirectoryItem("Combined View", "/feeds/" + nickname + "/all/"));
26 |
27 | foreach (var feed in feeds)
28 | {
29 | this.Items.Add(new DirectoryItem(feed.Feedname, string.Format("/feeds/{0}/{1}/", nickname, feed.Id)));
30 | this.Items.Add(new DirectoryItem(feed.Url));
31 | }
32 | }
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/GopherServer.Providers.Rss/GopherResults/FeedResult.cs:
--------------------------------------------------------------------------------
1 | using GopherServer.Core.Results;
2 | using GopherServer.Core.Helpers;
3 | using System;
4 | using System.Collections.Generic;
5 | using System.Linq;
6 | using System.Text;
7 | using System.Threading.Tasks;
8 | using GopherServer.Core.Models;
9 |
10 | namespace GopherServer.Core.Rss.GopherResults
11 | {
12 | public class FeedResult : DirectoryResult
13 | {
14 | public FeedResult(string nickname, int feedId, string xml) : base()
15 | {
16 | try
17 | {
18 | var feed = new Syndication.FeedDetails(xml);
19 |
20 |
21 |
22 | this.Items.Add(new DirectoryItem(feed.Title));
23 | this.Items.Add(new DirectoryItem("---------------"));
24 | this.Items.Add(new DirectoryItem("Last Updated: " + feed.LastUpdated.ToString()));
25 | this.Items.Add(new DirectoryItem("Delete Feed", string.Format("/user/{0}/delete/{1}/", nickname, feedId)));
26 | this.Items.Add(new DirectoryItem(""));
27 | this.Items.Add(new DirectoryItem("Return to Feed List", string.Format("/feeds/{0}/", nickname)));
28 | this.Items.Add(new DirectoryItem(""));
29 |
30 | foreach (var item in feed.Feed.Items)
31 | {
32 | this.Items.Add(new DirectoryItem(item.Title.Text, string.Format("/feeds/{0}/{1}/{2}", nickname, feedId, item.Id)));
33 | //this.Items.Add(new DirectoryItem("Author(s): " + string.Join(", ", item.Authors.Select(a => a.Name))));
34 | this.Items.Add(new DirectoryItem("Published: " + item.PublishDate.UtcDateTime.ToString()));
35 | this.Items.AddRange(item.Summary.Text.HtmlToText().WrapToDirectoryItems());
36 |
37 | this.Items.Add(new DirectoryItem("---"));
38 | }
39 | }
40 | catch (Exception)
41 | {
42 | this.Items.Add(new DirectoryItem("Error Processing Feed."));
43 | }
44 |
45 | this.Items.Add(new DirectoryItem("Return to Feed List", string.Format("/feeds/{0}/", nickname)));
46 | }
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/GopherServer.Providers.Rss/GopherServer.Providers.Rss.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 | netstandard2.0
4 |
5 |
6 |
7 |
8 |
9 |
10 | Always
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
--------------------------------------------------------------------------------
/GopherServer.Providers.Rss/RssController.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using GopherServer.Core.Models;
4 | using GopherServer.Core.Results;
5 | using GopherServer.Core.Rss.Data;
6 | using GopherServer.Core.Rss.GopherResults;
7 |
8 | namespace GopherServer.Core.Rss
9 | {
10 | public class RssController
11 | {
12 | public Db Db { get; private set; }
13 |
14 | public RssController(Db db) => this.Db = db;
15 |
16 | public FeedListingResult GetUserFeeds(string nickname)
17 | {
18 | Db.UpdateUserLogin(nickname);
19 | var feeds = Db.GetUserFeeds(nickname);
20 | return new FeedListingResult(nickname, feeds);
21 | }
22 |
23 | public DirectoryResult GetCombinedFeeds(string nickname)
24 | {
25 | throw new NotImplementedException();
26 | }
27 |
28 | public DirectoryResult GetFeed(string nickname, int feedId)
29 | {
30 | var feed = Db.GetFeedCache(nickname, feedId);
31 | return new FeedResult(nickname, feedId, feed);
32 | }
33 |
34 | public BaseResult RegisterUser(string nickname)
35 | {
36 | try
37 | {
38 | Db.AddUser(nickname);
39 | return GetUserFeeds(nickname);
40 | }
41 | catch (Exception)
42 | {
43 | // User probably already exists
44 | return new ErrorResult("Unable to register '" + nickname + '"');
45 | }
46 | }
47 |
48 | public BaseResult AddFeed(string nickname, string feedUrl)
49 | {
50 | if (Syndication.Syndication.TestValidFeed(feedUrl))
51 | {
52 | var id = Db.AddFeed(nickname, feedUrl);
53 | Syndication.Syndication.UpdateFeed(Db, id);
54 | return GetUserFeeds(nickname);
55 | }
56 |
57 | return new ErrorResult("Invalid Feed");
58 | }
59 |
60 | internal BaseResult DeleteFeed(string nickname, int feedId)
61 | {
62 | // TODO remove feed code
63 | return GetUserFeeds(nickname);
64 | }
65 |
66 | internal DirectoryResult GetHomePage()
67 | {
68 | return new DirectoryResult(new List()
69 | {
70 | new DirectoryItem("Welcome to Gopher RSS"),
71 | new DirectoryItem("---------------------"),
72 | new DirectoryItem(ItemType.INDEXSEARCH, "Register", "/register/"),
73 | new DirectoryItem("Enter a nickname to register."),
74 | new DirectoryItem(" Note there is no security on this. If someone"),
75 | new DirectoryItem(" can guess your nickname then they can edit"),
76 | new DirectoryItem(" your feeds."),
77 | new DirectoryItem(""),
78 | new DirectoryItem(ItemType.INDEXSEARCH, "Login", "/feeds/"),
79 | new DirectoryItem("Enter your nickname to retrieve your feeds."),
80 | new DirectoryItem(""),
81 | new DirectoryItem("Powered by GopherServer - https://github.com/pgodwin/GopherServer/"),
82 | });
83 | }
84 |
85 | public BaseResult GetFeedItem(string nickname, int feedId, string itemId)
86 | {
87 | var xml = Db.GetFeedCache(nickname, feedId);
88 | return new FeedItemResult(nickname, feedId, xml, itemId);
89 | }
90 | }
91 | }
92 |
--------------------------------------------------------------------------------
/GopherServer.Providers.Rss/RssProvider.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using GopherServer.Core.Providers;
5 | using GopherServer.Core.Results;
6 | using GopherServer.Core.Routes;
7 | using GopherServer.Core.Rss.Data;
8 |
9 | namespace GopherServer.Core.Rss
10 | {
11 | ///
12 | /// Rss Provider
13 | ///
14 | public class RssProvider : ServerProviderBase
15 | {
16 | Db db;
17 |
18 | public RssProvider(string hostname, int port) : base(hostname, port)
19 | { }
20 |
21 | public List Routes { get; set; }
22 |
23 | public RssController Controller { get; private set; }
24 |
25 | public override void Init()
26 | {
27 | db = new Db("rss.db");
28 | this.Controller = new RssController(db);
29 | this.Routes = BuildRoutes();
30 |
31 | var feedDownloader = new System.Threading.Timer(e => { Syndication.Syndication.UpdateFeeds(db); }, null, 0, (int)TimeSpan.FromMinutes(5).TotalMilliseconds);
32 | }
33 |
34 | private List BuildRoutes()
35 | {
36 | var routes = new List()
37 | {
38 | // Named groups require that the parameter name matches the group name
39 | // User Feed Listing
40 | new NamedGroupRoute("UserFeeds", @"\/feeds\/(?\w+)\/$", new Func(this.Controller.GetUserFeeds)),
41 |
42 | // User Feed Listing (ie 'login')
43 | new NamedGroupRoute("UserFeedsQuery", @"\/feeds\/\t(?.*)", new Func(this.Controller.GetUserFeeds)),
44 |
45 | // Combined view of the user's feeds
46 | new NamedGroupRoute("CombinedUserFeeds", @"\/feeds\/(?\w+)\/all\/$", new Func(this.Controller.GetCombinedFeeds)),
47 |
48 | // View of selected Feed
49 | new NamedGroupRoute("SpecificUserFeed", @"\/feeds\/(?\w+)\/(?\d+)\/$", new Func(this.Controller.GetFeed)),
50 |
51 | // View Feed Item
52 | new NamedGroupRoute("FeedItem", @"\/feeds\/(?\w+)\/(?\d+)\/(?.+)", new Func(this.Controller.GetFeedItem)),
53 |
54 | // Registration
55 | new NamedGroupRoute("Registration", @"\/register\/*\t(?\w+)", new Func(this.Controller.RegisterUser)),
56 |
57 | // Add Feed
58 | new NamedGroupRoute("AddFeed", @"\/user\/(?\w+)\/add\/\t(?.+)", new Func(this.Controller.AddFeed)),
59 |
60 | // Delete User Feed
61 | new NamedGroupRoute("DeleteFeed", @"\/user\/(?.\w+)\/delete\/(?\d+)\/$", new Func(this.Controller.DeleteFeed)),
62 | };
63 |
64 | return routes;
65 | }
66 |
67 | public override BaseResult GetResult(string selector)
68 | {
69 | try
70 | {
71 | if (string.IsNullOrEmpty(selector) || selector == "1") // some clients seem to use 1
72 | return this.Controller.GetHomePage();
73 |
74 | // Check our routes
75 | var route = this.Routes.FirstOrDefault(r => r.IsMatch(selector));
76 |
77 | if (route == null)
78 | return new ErrorResult("Selector '" + selector + "' was not found/is not supported.");
79 | else
80 | {
81 | Console.WriteLine("Matched Route: {0}", route.Name);
82 | return route.Execute(selector);
83 | }
84 | }
85 | catch (Exception ex)
86 | {
87 | // TODO: Some kind of common logging?
88 | Console.WriteLine(ex);
89 | return new ErrorResult("Error occurred processing your request.");
90 | }
91 | }
92 | }
93 | }
94 |
--------------------------------------------------------------------------------
/GopherServer.Providers.Rss/Syndication/FeedDetails.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.IO;
3 | using System.ServiceModel.Syndication;
4 | using System.Xml;
5 | using GopherServer.Core.Helpers;
6 |
7 | namespace GopherServer.Core.Rss.Syndication
8 | {
9 | public class FeedDetails
10 | {
11 | public string Title { get; set; }
12 | public string FeedXml { get; set; }
13 | public DateTime LastUpdated { get; set; }
14 | public SyndicationFeed Feed { get; set; }
15 |
16 | public FeedDetails(string xml)
17 | {
18 | this.FeedXml = xml;
19 |
20 | using (XmlReader reader = XmlReader.Create(new StringReader(xml)))
21 | {
22 | this.Feed = SyndicationFeed.Load(reader);
23 | }
24 |
25 | this.Title = this.Feed.Title.Text;
26 | this.LastUpdated = this.Feed.LastUpdatedTime.UtcDateTime;
27 | }
28 |
29 | public static FeedDetails FromUrl(string url)
30 | {
31 | return new FeedDetails(HttpHelpers.GetUrl(url));
32 | }
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/GopherServer.Providers.Rss/Syndication/Syndication.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using GopherServer.Core.Rss.Data;
3 |
4 | namespace GopherServer.Core.Rss.Syndication
5 | {
6 | public static class Syndication
7 | {
8 | public static void UpdateFeeds(Db db)
9 | {
10 | foreach (var feed in db.GetFeeds())
11 | {
12 | try
13 | {
14 | var detail = FeedDetails.FromUrl(feed.Url);
15 | db.UpdateFeed(feed.Id, detail);
16 | }
17 | catch (Exception)
18 | {
19 | // ahh logging where are you!
20 | }
21 | }
22 | }
23 |
24 | public static void UpdateFeed(Db db, int feedId)
25 | {
26 | var feed = db.GetFeed(feedId);
27 | var detail = FeedDetails.FromUrl(feed.Url);
28 | db.UpdateFeed(feed.Id, detail);
29 | }
30 |
31 | public static bool TestValidFeed(string url)
32 | {
33 | try
34 | {
35 | var detail = FeedDetails.FromUrl(url);
36 | return true;
37 | }
38 | catch (Exception)
39 | {
40 | return false;
41 | }
42 | }
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/GopherServer.Providers.Rss/sqlite3.dll:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pgodwin/GopherServer/5cf9346efbd498b31dffede16cfa8ff081c8ee62/GopherServer.Providers.Rss/sqlite3.dll
--------------------------------------------------------------------------------
/GopherServer.Providers.WpJson/Extensions/WordPressExtensions.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 | using WordPressRestApiStandard.Models;
3 | using GopherServer.Core.Helpers;
4 | using GopherServer.Core.Models;
5 |
6 | namespace GopherServer.Core.WpJson.Extensions
7 | {
8 | public static class WordPressExtensions
9 | {
10 | ///
11 | /// Converts the specified posts to Directory Items
12 | ///
13 | ///
14 | ///
15 | public static List ToDirectoryItems(this List posts)
16 | {
17 | //return posts.Select(p => new DirectoryItem
18 | //{
19 | // Description = p.Title.Rendered,
20 | // ItemType = ItemType.DIRECTORY,
21 | // Selector = "/posts/" + p.Id
22 | //}).ToList();
23 | var items = new List();
24 |
25 | foreach (var p in posts)
26 | {
27 | // Add a directory for the title
28 | items.Add(new DirectoryItem(ItemType.DIRECTORY, p.Title.Rendered.CleanHtml(), "/posts/" + p.Id));
29 |
30 | // Add the blurb
31 | items.AddRange(p.Excerpt.Rendered.CleanHtml().HtmlToText().WrapToDirectoryItems(80));
32 | }
33 |
34 | return items;
35 | }
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/GopherServer.Providers.WpJson/GopherServer.Providers.WpJson.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 | netstandard2.0
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/GopherServer.Providers.WpJson/WordPressClient.cs:
--------------------------------------------------------------------------------
1 |
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using HtmlAgilityPack;
5 | using WordPressRestApiStandard;
6 | using WordPressRestApiStandard.Models;
7 | using WordPressRestApiStandard.QueryModel;
8 | using GopherServer.Core.Helpers;
9 | using GopherServer.Core.Models;
10 | using GopherServer.Core.Results;
11 | using GopherServer.Core.WpJson.Extensions;
12 |
13 | namespace GopherServer.Core.WpJson
14 | {
15 | public class WordPressClient
16 | {
17 | public static WordPressApiClient client;
18 |
19 | public WordPressClient(string url)
20 | {
21 | client = new WordPressApiClient(url);
22 | }
23 |
24 | ///
25 | /// Produces a basic Homepage for the blog
26 | ///
27 | ///
28 | public DirectoryResult GetHomePage()
29 | {
30 | var result = new DirectoryResult();
31 | result.Items.Add(new DirectoryItem("Welcome"));
32 | result.Items.Add(new DirectoryItem("-------"));
33 |
34 | result.Items.Add(new DirectoryItem("Latest Posts"));
35 |
36 | var latestPosts = client.GetPosts(new PostsQuery { PerPage = 10 }).Result;
37 | result.Items.AddRange(latestPosts.ToDirectoryItems());
38 |
39 | result.Items.Add(new DirectoryItem("---"));
40 | result.Items.Add(new DirectoryItem(ItemType.DIRECTORY, "Categories", "/categories/"));
41 | result.Items.Add(new DirectoryItem(ItemType.INDEXSEARCH, "Search", "/search/"));
42 |
43 | // TODO: Add Tags and Pages
44 |
45 | return result;
46 | }
47 |
48 | ///
49 | /// Peforms a search of the blog
50 | ///
51 | ///
52 | ///
53 | public DirectoryResult Search(string q)
54 | {
55 | var posts = client.GetPosts(new PostsQuery { Search = q, PerPage = 100, OrderBy = OrderBy.title.ToString() }).Result;
56 | var directory = new DirectoryResult();
57 |
58 | directory.Items.Add(new DirectoryItem("Search for '" + q + "'"));
59 |
60 | if (posts.Count > 0)
61 | directory.Items.AddRange(posts.ToDirectoryItems());
62 | else
63 | directory.Items.Add(new DirectoryItem("No results found."));
64 |
65 | // TODO - add paging!
66 |
67 | return directory;
68 | }
69 |
70 | ///
71 | /// Gets the first 100 categories from the blog ordered by their use.
72 | ///
73 | ///
74 | public DirectoryResult GetCategories()
75 | {
76 | List result = client.GetCategories(new CategoriesQuery() { HideEmpty = true, OrderBy = "count", Order="desc", PerPage = 100 }).Result;
77 |
78 | var directory = new DirectoryResult();
79 | directory.Items.Add(new DirectoryItem("Categories"));
80 |
81 | directory.Items.AddRange(result.Select(c => new DirectoryItem()
82 | {
83 | Description = string.Format("{0} ({1})", c.Name, c.Count),
84 | ItemType = ItemType.DIRECTORY,
85 | Selector = "/category/" + c.Id
86 | }));
87 |
88 | return directory;
89 |
90 | }
91 |
92 | ///
93 | /// Returns the post as a DirectoryListing for the spcified ID
94 | ///
95 | ///
96 | ///
97 | public DirectoryResult GetPost(int id)
98 | {
99 | var result = new DirectoryResult();
100 |
101 | var post = client.GetPost(new PostQuery { }, id).Result;
102 | result.Items.Add(new DirectoryItem(post.Title.Rendered.CleanHtml()));
103 | result.Items.Add(new DirectoryItem("---"));
104 | result.Items.Add(new DirectoryItem("Author: " + post.Author));
105 | result.Items.Add(new DirectoryItem("Date Posted: " + post.DateGmt.ToString()));
106 | result.Items.Add(new DirectoryItem(" "));
107 | result.Items.Add(new DirectoryItem(ItemType.DOC, "Text Version", "/posts/text/" + id));
108 | result.Items.Add(new DirectoryItem(ItemType.HTML, "Web Link", "URL:" + post.Link));
109 | result.Items.Add(new DirectoryItem("---"));
110 |
111 | result.Items.AddRange(post.Content.Rendered.ToDirectoryItems());
112 |
113 | result.Items.Add(new DirectoryItem("----------------------------"));
114 |
115 | return result;
116 | }
117 |
118 | public TextResult GetPostText(int id)
119 | {
120 | var post = client.GetPost(new PostQuery { }, id).Result;
121 | return new TextResult(post.Content.Rendered.CleanHtml().HtmlToText().WrapText(80));
122 | }
123 |
124 |
125 | ///
126 | /// Gets the posts for the specified category.
127 | ///
128 | ///
129 | ///
130 | public DirectoryResult GetCategoryPosts(int category)
131 | {
132 | var posts = client.GetPosts(new PostsQuery { Categories = new List() { category }, PerPage = 100 }).Result;
133 | return new DirectoryResult(posts.ToDirectoryItems());
134 |
135 | }
136 |
137 | ///
138 | /// Converts the specified URL to a GIF
139 | ///
140 | ///
141 | ///
142 | public ByteResult GetGif(string url)
143 | {
144 | return new ByteResult(ImageToGif.ConvertImageToGif(url), ItemType.GIF);
145 | }
146 |
147 | ///
148 | /// Returns a HTML page to redirect to the propper address
149 | ///
150 | ///
151 | ///
152 | public UrlResult Redirect(string url)
153 | {
154 | return new UrlResult(url);
155 | }
156 |
157 | public DirectoryResult ProxyPage(string url)
158 | {
159 | var result = new DirectoryResult();
160 |
161 | var html = HttpHelpers.GetUrl(url);
162 |
163 | HtmlDocument doc = new HtmlDocument();
164 | doc.LoadHtml(html);
165 | var title = doc.DocumentNode.Descendants("title").SingleOrDefault();
166 |
167 | result.Items.Add(new DirectoryItem("Title: " + title));
168 | result.Items.Add(new DirectoryItem("Url: " + url));
169 | result.Items.Add(new DirectoryItem("---------------"));
170 | result.Items.Add(new DirectoryItem(""));
171 | result.Items.AddRange(html.ToDirectoryItems());
172 |
173 | return result;
174 | }
175 | }
176 | }
177 |
--------------------------------------------------------------------------------
/GopherServer.Providers.WpJson/WordPressProvider.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Configuration;
4 | using System.Linq;
5 | using GopherServer.Core.Providers;
6 | using GopherServer.Core.Results;
7 | using GopherServer.Core.Routes;
8 |
9 | namespace GopherServer.Core.WpJson
10 | {
11 | ///
12 | /// Provides a Gopher Provider to the Wordpress REST API
13 | ///
14 | public class WordPressProvider : ServerProviderBase
15 | {
16 | internal WordPressClient client;
17 |
18 | // The routes we're going to use later to perform actions
19 | private List routes;
20 |
21 | public string Hostname { get; private set; }
22 | public int Port { get; private set; }
23 | public string WordPressUrl { get; private set; }
24 |
25 | public WordPressProvider(string hostname, int port) : base(hostname, port)
26 | {
27 | Hostname = hostname;
28 | Port = port;
29 | }
30 |
31 | public override void Init()
32 | {
33 | WordPressUrl = ConfigurationManager.AppSettings[GetType().Name + ".Url"];
34 |
35 | // TODO Read in Config
36 | client = new WordPressClient(WordPressUrl);
37 |
38 | // Build our route list for teh selector
39 | routes = new List()
40 | {
41 | // Get Post
42 | new TypedRoute("Posts", @"\/posts\/(\d+)", client.GetPost),
43 |
44 | // Get Post as Text
45 | new TypedRoute("Posts", @"\/posts\/text\/(\d+)", client.GetPostText),
46 |
47 | // Categories List
48 | new Route("Categories", @"\/categories\/", client.GetCategories),
49 |
50 | // Posts by Category
51 | new TypedRoute("CategoryPosts", @"\/category\/(\d+)", client.GetCategoryPosts),
52 |
53 | // Search
54 | new TypedRoute("Search", @"\/search\/*\t(.+)", client.Search),
55 |
56 | // Get Image
57 | //new TypedRoute("Gif", @"\/gif\/(.+)", client.GetGif),
58 | PrebuiltRoutes.GifRoute(),
59 |
60 | // External Link
61 | //new TypedRoute("Url", @"URL:(.+)", client.Redirect),
62 | PrebuiltRoutes.UrlResult(),
63 |
64 | // Proxy Link
65 | //new TypedRoute("Proxy", @"\/proxy\/(.+)", client.ProxyPage),
66 | PrebuiltRoutes.HtmlProxy(),
67 |
68 | };
69 | }
70 |
71 |
72 | ///
73 | /// Processes the selector and performs the appropriate action
74 | ///
75 | ///
76 | ///
77 | public override BaseResult GetResult(string selector)
78 | {
79 | // This is where we read our selectors...
80 | // it's a shame we can't reuse the route code out of MVC (or can we ?)
81 | try
82 | {
83 | if (string.IsNullOrEmpty(selector) || selector == "1") // some clients seem to use 1
84 | return client.GetHomePage();
85 |
86 | // Check our routes
87 | var route = routes.FirstOrDefault(r => r.IsMatch(selector));
88 |
89 | if (route == null)
90 | return new ErrorResult("Selector '" + selector + "' was not found/is not supported.");
91 | else
92 | return route.Execute(selector);
93 | }
94 | catch (Exception ex)
95 | {
96 | // TODO: Some kind of common logging?
97 | Console.WriteLine(ex);
98 | return new ErrorResult("Error occurred processing your request.");
99 | }
100 | }
101 | }
102 | }
103 |
--------------------------------------------------------------------------------
/GopherServer.sln:
--------------------------------------------------------------------------------
1 |
2 | Microsoft Visual Studio Solution File, Format Version 12.00
3 | # Visual Studio 15
4 | VisualStudioVersion = 15.0.26124.0
5 | MinimumVisualStudioVersion = 15.0.26124.0
6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GopherServer", "GopherServer\GopherServer.csproj", "{9C8F5CF8-7267-4653-A8FE-F6902345814F}"
7 | EndProject
8 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GopherServer.Core", "GopherServer.Core\GopherServer.Core.csproj", "{7735DC4B-E628-4D5C-8A3A-BDB19FD11131}"
9 | EndProject
10 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GopherServer.Providers.MacintoshGarden", "GopherServer.Providers.MacintoshGarden\GopherServer.Providers.MacintoshGarden.csproj", "{80A9B15D-DB4D-4EA0-A701-3E0E5CFF14F2}"
11 | EndProject
12 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GopherServer.Providers.Rss", "GopherServer.Providers.Rss\GopherServer.Providers.Rss.csproj", "{6CCAB066-D03C-437A-9EB7-6810C77CDE89}"
13 | EndProject
14 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GopherServer.Providers.WpJson", "GopherServer.Providers.WpJson\GopherServer.Providers.WpJson.csproj", "{BE800704-34D3-4A1A-8BB0-39DCAE7A135D}"
15 | EndProject
16 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GopherServer.Providers.FileProvider", "GopherServer.Providers.FileProvider\GopherServer.Providers.FileProvider.csproj", "{BA99491D-F890-4697-94C8-75B18FE22455}"
17 | EndProject
18 | Global
19 | GlobalSection(SolutionConfigurationPlatforms) = preSolution
20 | Debug|Any CPU = Debug|Any CPU
21 | Debug|x64 = Debug|x64
22 | Debug|x86 = Debug|x86
23 | Release|Any CPU = Release|Any CPU
24 | Release|x64 = Release|x64
25 | Release|x86 = Release|x86
26 | EndGlobalSection
27 | GlobalSection(SolutionProperties) = preSolution
28 | HideSolutionNode = FALSE
29 | EndGlobalSection
30 | GlobalSection(ProjectConfigurationPlatforms) = postSolution
31 | {9C8F5CF8-7267-4653-A8FE-F6902345814F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
32 | {9C8F5CF8-7267-4653-A8FE-F6902345814F}.Debug|Any CPU.Build.0 = Debug|Any CPU
33 | {9C8F5CF8-7267-4653-A8FE-F6902345814F}.Debug|x64.ActiveCfg = Debug|Any CPU
34 | {9C8F5CF8-7267-4653-A8FE-F6902345814F}.Debug|x64.Build.0 = Debug|Any CPU
35 | {9C8F5CF8-7267-4653-A8FE-F6902345814F}.Debug|x86.ActiveCfg = Debug|Any CPU
36 | {9C8F5CF8-7267-4653-A8FE-F6902345814F}.Debug|x86.Build.0 = Debug|Any CPU
37 | {9C8F5CF8-7267-4653-A8FE-F6902345814F}.Release|Any CPU.ActiveCfg = Release|Any CPU
38 | {9C8F5CF8-7267-4653-A8FE-F6902345814F}.Release|Any CPU.Build.0 = Release|Any CPU
39 | {9C8F5CF8-7267-4653-A8FE-F6902345814F}.Release|x64.ActiveCfg = Release|Any CPU
40 | {9C8F5CF8-7267-4653-A8FE-F6902345814F}.Release|x64.Build.0 = Release|Any CPU
41 | {9C8F5CF8-7267-4653-A8FE-F6902345814F}.Release|x86.ActiveCfg = Release|Any CPU
42 | {9C8F5CF8-7267-4653-A8FE-F6902345814F}.Release|x86.Build.0 = Release|Any CPU
43 | {7735DC4B-E628-4D5C-8A3A-BDB19FD11131}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
44 | {7735DC4B-E628-4D5C-8A3A-BDB19FD11131}.Debug|Any CPU.Build.0 = Debug|Any CPU
45 | {7735DC4B-E628-4D5C-8A3A-BDB19FD11131}.Debug|x64.ActiveCfg = Debug|Any CPU
46 | {7735DC4B-E628-4D5C-8A3A-BDB19FD11131}.Debug|x64.Build.0 = Debug|Any CPU
47 | {7735DC4B-E628-4D5C-8A3A-BDB19FD11131}.Debug|x86.ActiveCfg = Debug|Any CPU
48 | {7735DC4B-E628-4D5C-8A3A-BDB19FD11131}.Debug|x86.Build.0 = Debug|Any CPU
49 | {7735DC4B-E628-4D5C-8A3A-BDB19FD11131}.Release|Any CPU.ActiveCfg = Release|Any CPU
50 | {7735DC4B-E628-4D5C-8A3A-BDB19FD11131}.Release|Any CPU.Build.0 = Release|Any CPU
51 | {7735DC4B-E628-4D5C-8A3A-BDB19FD11131}.Release|x64.ActiveCfg = Release|Any CPU
52 | {7735DC4B-E628-4D5C-8A3A-BDB19FD11131}.Release|x64.Build.0 = Release|Any CPU
53 | {7735DC4B-E628-4D5C-8A3A-BDB19FD11131}.Release|x86.ActiveCfg = Release|Any CPU
54 | {7735DC4B-E628-4D5C-8A3A-BDB19FD11131}.Release|x86.Build.0 = Release|Any CPU
55 | {80A9B15D-DB4D-4EA0-A701-3E0E5CFF14F2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
56 | {80A9B15D-DB4D-4EA0-A701-3E0E5CFF14F2}.Debug|Any CPU.Build.0 = Debug|Any CPU
57 | {80A9B15D-DB4D-4EA0-A701-3E0E5CFF14F2}.Debug|x64.ActiveCfg = Debug|Any CPU
58 | {80A9B15D-DB4D-4EA0-A701-3E0E5CFF14F2}.Debug|x64.Build.0 = Debug|Any CPU
59 | {80A9B15D-DB4D-4EA0-A701-3E0E5CFF14F2}.Debug|x86.ActiveCfg = Debug|Any CPU
60 | {80A9B15D-DB4D-4EA0-A701-3E0E5CFF14F2}.Debug|x86.Build.0 = Debug|Any CPU
61 | {80A9B15D-DB4D-4EA0-A701-3E0E5CFF14F2}.Release|Any CPU.ActiveCfg = Release|Any CPU
62 | {80A9B15D-DB4D-4EA0-A701-3E0E5CFF14F2}.Release|Any CPU.Build.0 = Release|Any CPU
63 | {80A9B15D-DB4D-4EA0-A701-3E0E5CFF14F2}.Release|x64.ActiveCfg = Release|Any CPU
64 | {80A9B15D-DB4D-4EA0-A701-3E0E5CFF14F2}.Release|x64.Build.0 = Release|Any CPU
65 | {80A9B15D-DB4D-4EA0-A701-3E0E5CFF14F2}.Release|x86.ActiveCfg = Release|Any CPU
66 | {80A9B15D-DB4D-4EA0-A701-3E0E5CFF14F2}.Release|x86.Build.0 = Release|Any CPU
67 | {6CCAB066-D03C-437A-9EB7-6810C77CDE89}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
68 | {6CCAB066-D03C-437A-9EB7-6810C77CDE89}.Debug|Any CPU.Build.0 = Debug|Any CPU
69 | {6CCAB066-D03C-437A-9EB7-6810C77CDE89}.Debug|x64.ActiveCfg = Debug|Any CPU
70 | {6CCAB066-D03C-437A-9EB7-6810C77CDE89}.Debug|x64.Build.0 = Debug|Any CPU
71 | {6CCAB066-D03C-437A-9EB7-6810C77CDE89}.Debug|x86.ActiveCfg = Debug|Any CPU
72 | {6CCAB066-D03C-437A-9EB7-6810C77CDE89}.Debug|x86.Build.0 = Debug|Any CPU
73 | {6CCAB066-D03C-437A-9EB7-6810C77CDE89}.Release|Any CPU.ActiveCfg = Release|Any CPU
74 | {6CCAB066-D03C-437A-9EB7-6810C77CDE89}.Release|Any CPU.Build.0 = Release|Any CPU
75 | {6CCAB066-D03C-437A-9EB7-6810C77CDE89}.Release|x64.ActiveCfg = Release|Any CPU
76 | {6CCAB066-D03C-437A-9EB7-6810C77CDE89}.Release|x64.Build.0 = Release|Any CPU
77 | {6CCAB066-D03C-437A-9EB7-6810C77CDE89}.Release|x86.ActiveCfg = Release|Any CPU
78 | {6CCAB066-D03C-437A-9EB7-6810C77CDE89}.Release|x86.Build.0 = Release|Any CPU
79 | {BE800704-34D3-4A1A-8BB0-39DCAE7A135D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
80 | {BE800704-34D3-4A1A-8BB0-39DCAE7A135D}.Debug|Any CPU.Build.0 = Debug|Any CPU
81 | {BE800704-34D3-4A1A-8BB0-39DCAE7A135D}.Debug|x64.ActiveCfg = Debug|Any CPU
82 | {BE800704-34D3-4A1A-8BB0-39DCAE7A135D}.Debug|x64.Build.0 = Debug|Any CPU
83 | {BE800704-34D3-4A1A-8BB0-39DCAE7A135D}.Debug|x86.ActiveCfg = Debug|Any CPU
84 | {BE800704-34D3-4A1A-8BB0-39DCAE7A135D}.Debug|x86.Build.0 = Debug|Any CPU
85 | {BE800704-34D3-4A1A-8BB0-39DCAE7A135D}.Release|Any CPU.ActiveCfg = Release|Any CPU
86 | {BE800704-34D3-4A1A-8BB0-39DCAE7A135D}.Release|Any CPU.Build.0 = Release|Any CPU
87 | {BE800704-34D3-4A1A-8BB0-39DCAE7A135D}.Release|x64.ActiveCfg = Release|Any CPU
88 | {BE800704-34D3-4A1A-8BB0-39DCAE7A135D}.Release|x64.Build.0 = Release|Any CPU
89 | {BE800704-34D3-4A1A-8BB0-39DCAE7A135D}.Release|x86.ActiveCfg = Release|Any CPU
90 | {BE800704-34D3-4A1A-8BB0-39DCAE7A135D}.Release|x86.Build.0 = Release|Any CPU
91 | {BA99491D-F890-4697-94C8-75B18FE22455}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
92 | {BA99491D-F890-4697-94C8-75B18FE22455}.Debug|Any CPU.Build.0 = Debug|Any CPU
93 | {BA99491D-F890-4697-94C8-75B18FE22455}.Debug|x64.ActiveCfg = Debug|Any CPU
94 | {BA99491D-F890-4697-94C8-75B18FE22455}.Debug|x64.Build.0 = Debug|Any CPU
95 | {BA99491D-F890-4697-94C8-75B18FE22455}.Debug|x86.ActiveCfg = Debug|Any CPU
96 | {BA99491D-F890-4697-94C8-75B18FE22455}.Debug|x86.Build.0 = Debug|Any CPU
97 | {BA99491D-F890-4697-94C8-75B18FE22455}.Release|Any CPU.ActiveCfg = Release|Any CPU
98 | {BA99491D-F890-4697-94C8-75B18FE22455}.Release|Any CPU.Build.0 = Release|Any CPU
99 | {BA99491D-F890-4697-94C8-75B18FE22455}.Release|x64.ActiveCfg = Release|Any CPU
100 | {BA99491D-F890-4697-94C8-75B18FE22455}.Release|x64.Build.0 = Release|Any CPU
101 | {BA99491D-F890-4697-94C8-75B18FE22455}.Release|x86.ActiveCfg = Release|Any CPU
102 | {BA99491D-F890-4697-94C8-75B18FE22455}.Release|x86.Build.0 = Release|Any CPU
103 | EndGlobalSection
104 | EndGlobal
105 |
--------------------------------------------------------------------------------
/GopherServer/GopherServer.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Exe
5 | netcoreapp2.2
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/GopherServer/Program.cs:
--------------------------------------------------------------------------------
1 | namespace GopherServer
2 | {
3 | public class Program
4 | {
5 | public static void Main(string[] args)
6 | {
7 | var server = new Server();
8 | server.StartListening();
9 | }
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/GopherServer/Server.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Net;
3 | using System.Net.Sockets;
4 | using System.Text;
5 | using System.Threading;
6 | using GopherServer.Core.Configuration;
7 | using GopherServer.Core.Results;
8 | using GopherServer.Core.Providers;
9 |
10 | namespace GopherServer
11 | {
12 | public class StateObject
13 | {
14 | // Client socket.
15 | public Socket workSocket = null;
16 | // Size of receive buffer.
17 | public const int BufferSize = 1024;
18 | // Receive buffer.
19 | public byte[] buffer = new byte[BufferSize];
20 | // Received data string.
21 | public StringBuilder sb = new StringBuilder();
22 | }
23 |
24 | public class Server
25 | {
26 | // Thread signal.
27 | public static ManualResetEvent allDone = new ManualResetEvent(false);
28 |
29 | public IServerProvider provider;
30 |
31 | public IPAddress IPAddress { get; private set; }
32 | public int Port { get; private set; }
33 | public string ExternalHostname { get; set; }
34 | public int ExternalPort { get; set; }
35 |
36 | public Server()
37 | {
38 | IPAddress = IPAddress.Parse(ServerSettings.BoundIP);
39 | Port = ServerSettings.BoundPort;
40 | ExternalHostname = ServerSettings.PublicHostname;
41 | ExternalPort = ServerSettings.PublicPort;
42 | provider = ProviderFactory.GetProvider(this.ExternalHostname, this.ExternalPort);
43 | provider.Init();
44 | }
45 |
46 | public void StartListening()
47 | {
48 | // Data buffer for incoming data.
49 | var bytes = new byte[1024];
50 |
51 | // Establish the local endpoint for the socket.
52 | // The DNS name of the computer
53 | // running the listener is "host.contoso.com".
54 | //IPHostEntry ipHostInfo = Dns.Resolve(Dns.GetHostName());
55 | var ipAddress = IPAddress;
56 | var localEndPoint = new IPEndPoint(ipAddress, Port);
57 |
58 | Console.WriteLine($"Listening on {ipAddress}:{Port}");
59 |
60 | // Create a TCP/IP socket.
61 | var listener = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
62 |
63 | // Bind the socket to the local endpoint and listen for incoming connections.
64 | try
65 | {
66 | listener.Bind(localEndPoint);
67 | listener.Listen(100);
68 |
69 | while (true)
70 | {
71 | allDone.Reset(); // Set the event to nonsignaled state
72 | Console.WriteLine("Waiting for a connection...");
73 | listener.BeginAccept(new AsyncCallback(AcceptCallback), listener); // Start an asynchronous socket to listen for connections
74 | allDone.WaitOne(); // Wait until a connection is made before continuing
75 | }
76 | }
77 | catch (Exception e)
78 | {
79 | Console.WriteLine(e.ToString());
80 | }
81 |
82 | Console.WriteLine("\nPress ENTER to continue...");
83 | Console.Read();
84 | }
85 |
86 | public void AcceptCallback(IAsyncResult ar)
87 | {
88 | // Signal the main thread to continue.
89 | allDone.Set();
90 |
91 | // Get the socket that handles the client request.
92 | var listener = (Socket)ar.AsyncState;
93 | var handler = listener.EndAccept(ar);
94 |
95 | // Create the state object.
96 | var state = new StateObject();
97 | state.workSocket = handler;
98 | handler.BeginReceive(state.buffer, 0, StateObject.BufferSize, 0, new AsyncCallback(ReadCallback), state);
99 | }
100 |
101 | public void ReadCallback(IAsyncResult ar)
102 | {
103 | var selector = string.Empty;
104 |
105 | // Retrieve the state object and the handler socket
106 | // from the asynchronous state object.
107 | var state = (StateObject)ar.AsyncState;
108 | var handler = state.workSocket;
109 |
110 | // Read data from the client socket.
111 | var bytesRead = handler.EndReceive(ar);
112 |
113 | if (bytesRead > 0)
114 | {
115 | // There might be more data, so store the data received so far.
116 | state.sb.Append(Encoding.ASCII.GetString(state.buffer, 0, bytesRead));
117 |
118 | // Check for end-of-file tag. If it is not there, read more data.
119 | selector = state.sb.ToString();
120 |
121 | if (selector.IndexOf("\r\n") > -1)
122 | {
123 | // All the data has been read from the client. Display it on the console.
124 | Console.WriteLine($"Read {selector.Length} bytes from socket.\nData: {selector}");
125 |
126 | // Trim the trailng CRLF
127 | selector = selector.TrimEnd('\r', '\n');
128 |
129 | // a whole bunch of legacy clients seem to be doing might strip that too
130 | selector = selector.TrimStart('\r', '\n');
131 |
132 | if (selector == ".") selector = "";
133 |
134 | // Tell the provider to return a result for the selector
135 | var result = provider.GetResult(selector);
136 |
137 | // This is a bit of a hack - I need a better way of doing this (push it back to the provider I think)
138 | if (result is DirectoryResult)
139 | {
140 | var dir = result as DirectoryResult;
141 | foreach (var item in dir.Items)
142 | {
143 | if (string.IsNullOrEmpty(item.Host))
144 | {
145 | item.Host = this.ExternalHostname;
146 | item.Port = this.Port;
147 | }
148 | }
149 | }
150 |
151 | //using (var stream = new MemoryStream())
152 | //{
153 | // result.WriteResult(stream);
154 | // Send(handler, stream.ToArray());
155 | //}
156 |
157 | WriteResult(handler, result);
158 | }
159 | else
160 | {
161 | // Not all data received. Get more.
162 | handler.BeginReceive(state.buffer, 0, StateObject.BufferSize, 0, new AsyncCallback(ReadCallback), state);
163 | }
164 | }
165 | }
166 |
167 | private static void Send(Socket handler, String data)
168 | {
169 | // Convert the string data to byte data using ASCII encoding.
170 | var byteData = Encoding.ASCII.GetBytes(data);
171 |
172 | // Begin sending the data to the remote device.
173 | handler.BeginSend(byteData, 0, byteData.Length, 0, new AsyncCallback(SendCallback), handler);
174 | }
175 |
176 | private static void Send(Socket handler, byte[] data)
177 | {
178 | // Begin sending the data to the remote device.
179 | handler.BeginSend(data, 0, data.Length, 0, new AsyncCallback(SendCallback), handler);
180 | }
181 |
182 | private static void WriteResult(Socket handler, BaseResult result)
183 | {
184 | try
185 | {
186 | using (var netStream = new NetworkStream(handler))
187 | result.WriteResult(netStream);
188 | }
189 | catch (Exception ex)
190 | {
191 | Console.WriteLine("Error writing result. {0}", ex);
192 | }
193 | finally
194 | {
195 | try
196 | {
197 | handler.Shutdown(SocketShutdown.Both);
198 | handler.Close();
199 | }
200 | catch (Exception handlerException)
201 | {
202 | Console.WriteLine("Error attempting to close the handler: {0}", handlerException);
203 | }
204 | }
205 | }
206 |
207 | private static void SendCallback(IAsyncResult ar)
208 | {
209 | try
210 | {
211 | // Retrieve the socket from the state object.
212 | var handler = (Socket)ar.AsyncState;
213 |
214 | // Complete sending the data to the remote device.
215 | var bytesSent = handler.EndSend(ar);
216 | Console.WriteLine("Sent {0} bytes to client.", bytesSent);
217 |
218 | handler.Shutdown(SocketShutdown.Both);
219 | handler.Close();
220 | }
221 | catch (Exception e)
222 | {
223 | Console.WriteLine(e.ToString());
224 | }
225 | }
226 | }
227 | }
228 |
--------------------------------------------------------------------------------
/GopherServer/app.sample.config:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 |
110 |
111 |
112 |
113 |
114 |
115 |
116 |
117 |
118 |
119 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2017 pgodwin
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 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # GopherServer
2 | A Gopher Server implemented in C#.
3 |
4 | ## What is in here
5 | - Simple Socket Server implemented in GopherServer
6 | - Concept of "Providers' which return a Result for the selector.
7 | - Providers only have two methods to implement: `void Init()` and `BaseResult GetResult(string selector);`
8 | - Models for results, item types, etc and helpers in GopherServer.Core
9 | - Providers configured in app.config
10 | - A few sample providers have been written which demonstrate different examples of Providers.
11 |
12 | Pull requests very welcome!
13 |
14 | # Config Options
15 | All configuration options are currently via the GopherServer.exe.config file. Further work will be undertaken to improve
16 | the way the server and the providers are configured.
17 |
18 | - boundIP: The IP Address the SocketServer will listen on
19 | - boundPort: The port the SocketServer will listen on
20 | - publicHostname: The address to use on for DirectoryResults
21 | - publicPort: The port to use for DirectoryResults
22 | - resizeImages: true|false - whether or not images should be resized (ie when proxying images to gif).
23 | This is useful for low-memory targets where large images will consume too much memory.
24 | - maximumWidth: int - the maximum width a resized image should be
25 | - maximumHeight: int - the maximum height a resized image should be.
26 | - resampleImages: true|false - whether or not images should be resampled to a lower colour depth.
27 | As we're outputting gif images, the bit-depth is always a maximum of 8 (256 colours).
28 | - maximumBitDepth: 1, 4, 8: the bit-depth images should be resampled to. Will depend on your target platform.
29 | Eg Early Macintosh machines it might be best to keep things a 1 or 4bit (16 colours).
30 | - providerName: The GopherServerProvider to use with this server (see below).
31 |
32 | # Providers
33 |
34 | ## WordPressProvider
35 | The WordPressProvider consumes the WordPress REST API and allows a blog to be browsed with a Gopher client(!).
36 | To use it:
37 | - Set ``
38 | - and adjust `` to your URL
39 |
40 | The provider has support for proxying GIF and Webpages over gopher.
41 | Give it a go!
42 |
43 | ## RSS Provider
44 | The RSS provider is an advanced example showing how to bulid a database backed Gopher Provider.
45 | Users can "Register" a nickname on the site and add RSS feeds. The feeds are periodically downloaded
46 | and cached in the DB. The user is then able to view the RSS feed items. All from their Gopher Client.
47 |
48 | ## Macintosh Garden Provider
49 | This provider proxies search results, applications pages, screenshots and downloads. It's a good example
50 | of building a Gopher Proxy against a public website.
51 |
52 | To use it:
53 | - Set `` in your GopherServer.exe.config file.
54 |
55 | ## FileProvider
56 | The FileProvider allows GopherServer to run like a more traditional Gopher server, serving directories and files.
57 | It's still in progress and is currently hardcoded to a specific directory (as of 3/6/2017). This will be developed further.
58 |
59 | To use it:
60 | Make sure you have Sqlite available for your platform (Windows included).
61 | - Set `` in GopherServer.exe.config
62 |
63 | # Mono Support
64 | I've been able to successfully run this under Mono on Ubuntu 16 LTS. This, along with the low-memory footprint of
65 | the providers, means you can host GopherServer on cheap linux hosting such as Amazon Lightsail or other low-end hosts.
66 |
67 | # Client Support
68 | I've successfully tested this server with the following clients:
69 |
70 | ## Win32
71 | - SeaMonkey 1.1.19 (Win32) - https://www.seamonkey-project.org/releases/seamonkey1.1.19/
72 | - Digger Dwarf (Win32) - http://slugmax.tx0.org/gopher.viste.fr/projects/ddwarf/
73 |
74 | ## Classic Macintosh
75 | - Netscape 3 (Almost certainly early and later versions)
76 | Netscape is great as it supports images out of the box and already has good mime-mappings.
77 | Screenshot - http://imgur.com/oSTpWSI
78 | - GopherApp (Macintosh System 6+) - http://iubio.bio.indiana.edu/soft/util/gopher/gopherapp/
79 | You need to edit one of bookmark files in BBEdit or similar. It doesn't seem to support INFO types,
80 | but SOURCE (MacApp Pascal i think) is available!
81 | - GopherApp++ (Macintosh System 6+) - http://iubio.bio.indiana.edu/soft/util/gopher/gopherapp/
82 | - GopherPup (Macintosh System 7+ I think) - http://iubio.bio.indiana.edu/soft/util/gopher/gopherpup/
83 | This was cross-platform, but I'm not sure if source was ever released. Available also for Win16, Win32, SGI Mips and Sparc
84 | - TurboGopher (System 6+)
85 | I had some issues with this one. It would download the initial directory, but wouldn't connect again past there.
86 | Screenshot - http://i.imgur.com/obYrUcZ.png
87 |
88 | There's lots of clients out there, if you've had any luck with any of them let me know!
89 |
90 | # TODO
91 | - Write Much better documentation
92 | - Improve the HTML to Text rendering
93 | - Improve the socket server (it's pretty much microsoft's example async socket server as is, lots of room for improvement)
94 | - Improve the configuration and loading of providers
95 | - Write a client
96 | - Replace hacky code
97 |
98 |
99 |
100 | License / Credits:
101 | ==================
102 | This software would not be possible without the hardware of contributors to many open source projects.
103 | Many of these have been released under an MIT license, and this software has also been released under the same license (see LICENSE).
104 |
105 | AngleSharp
106 | ----------
107 |
108 | The MIT License (MIT)
109 |
110 | Copyright (c) 2013 - 2017 AngleSharp
111 |
112 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
113 |
114 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
115 |
116 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
117 |
118 | Html Agility Pack
119 | -----------------
120 |
121 | The MIT License (MIT)
122 | Permission is hereby granted, free of charge, to any person obtaining a copy
123 | of this software and associated documentation files (the "Software"), to deal
124 | in the Software without restriction, including without limitation the rights
125 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
126 | copies of the Software, and to permit persons to whom the Software is
127 | furnished to do so, subject to the following conditions:
128 |
129 | The above copyright notice and this permission notice shall be included in all
130 | copies or substantial portions of the Software.
131 |
132 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
133 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
134 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
135 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
136 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
137 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
138 | SOFTWARE.
139 |
140 | NewtonSoft.Json
141 | ---------------
142 |
143 | The MIT License (MIT)
144 |
145 | Copyright (c) 2007 James Newton-King
146 |
147 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
148 |
149 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
150 |
151 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
152 |
153 | SQLite.Net-PCL
154 | --------------
155 |
156 | // Copyright (c) 2012 Krueger Systems, Inc.
157 | // Copyright (c) 2013 Øystein Krog (oystein.krog@gmail.com)
158 | //
159 | // Permission is hereby granted, free of charge, to any person obtaining a copy
160 | // of this software and associated documentation files (the "Software"), to deal
161 | // in the Software without restriction, including without limitation the rights
162 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
163 | // copies of the Software, and to permit persons to whom the Software is
164 | // furnished to do so, subject to the following conditions:
165 | //
166 | // The above copyright notice and this permission notice shall be included in
167 | // all copies or substantial portions of the Software.
168 | //
169 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
170 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
171 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
172 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
173 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
174 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
175 | // THE SOFTWARE.
176 |
177 | sqlite-net-extensions
178 | ---------------------
179 |
180 | Copyright (C) 2013 TwinCoders S.L.
181 |
182 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
183 |
184 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
185 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
186 |
187 | WordPressRestAPI
188 | ------------------
189 | Apache License
190 | Version 2.0, January 2004
191 | http://www.apache.org/licenses/
192 |
193 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
194 |
195 | 1. Definitions.
196 |
197 | "License" shall mean the terms and conditions for use, reproduction,
198 | and distribution as defined by Sections 1 through 9 of this document.
199 |
200 | "Licensor" shall mean the copyright owner or entity authorized by
201 | the copyright owner that is granting the License.
202 |
203 | "Legal Entity" shall mean the union of the acting entity and all
204 | other entities that control, are controlled by, or are under common
205 | control with that entity. For the purposes of this definition,
206 | "control" means (i) the power, direct or indirect, to cause the
207 | direction or management of such entity, whether by contract or
208 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
209 | outstanding shares, or (iii) beneficial ownership of such entity.
210 |
211 | "You" (or "Your") shall mean an individual or Legal Entity
212 | exercising permissions granted by this License.
213 |
214 | "Source" form shall mean the preferred form for making modifications,
215 | including but not limited to software source code, documentation
216 | source, and configuration files.
217 |
218 | "Object" form shall mean any form resulting from mechanical
219 | transformation or translation of a Source form, including but
220 | not limited to compiled object code, generated documentation,
221 | and conversions to other media types.
222 |
223 | "Work" shall mean the work of authorship, whether in Source or
224 | Object form, made available under the License, as indicated by a
225 | copyright notice that is included in or attached to the work
226 | (an example is provided in the Appendix below).
227 |
228 | "Derivative Works" shall mean any work, whether in Source or Object
229 | form, that is based on (or derived from) the Work and for which the
230 | editorial revisions, annotations, elaborations, or other modifications
231 | represent, as a whole, an original work of authorship. For the purposes
232 | of this License, Derivative Works shall not include works that remain
233 | separable from, or merely link (or bind by name) to the interfaces of,
234 | the Work and Derivative Works thereof.
235 |
236 | "Contribution" shall mean any work of authorship, including
237 | the original version of the Work and any modifications or additions
238 | to that Work or Derivative Works thereof, that is intentionally
239 | submitted to Licensor for inclusion in the Work by the copyright owner
240 | or by an individual or Legal Entity authorized to submit on behalf of
241 | the copyright owner. For the purposes of this definition, "submitted"
242 | means any form of electronic, verbal, or written communication sent
243 | to the Licensor or its representatives, including but not limited to
244 | communication on electronic mailing lists, source code control systems,
245 | and issue tracking systems that are managed by, or on behalf of, the
246 | Licensor for the purpose of discussing and improving the Work, but
247 | excluding communication that is conspicuously marked or otherwise
248 | designated in writing by the copyright owner as "Not a Contribution."
249 |
250 | "Contributor" shall mean Licensor and any individual or Legal Entity
251 | on behalf of whom a Contribution has been received by Licensor and
252 | subsequently incorporated within the Work.
253 |
254 | 2. Grant of Copyright License. Subject to the terms and conditions of
255 | this License, each Contributor hereby grants to You a perpetual,
256 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
257 | copyright license to reproduce, prepare Derivative Works of,
258 | publicly display, publicly perform, sublicense, and distribute the
259 | Work and such Derivative Works in Source or Object form.
260 |
261 | 3. Grant of Patent License. Subject to the terms and conditions of
262 | this License, each Contributor hereby grants to You a perpetual,
263 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
264 | (except as stated in this section) patent license to make, have made,
265 | use, offer to sell, sell, import, and otherwise transfer the Work,
266 | where such license applies only to those patent claims licensable
267 | by such Contributor that are necessarily infringed by their
268 | Contribution(s) alone or by combination of their Contribution(s)
269 | with the Work to which such Contribution(s) was submitted. If You
270 | institute patent litigation against any entity (including a
271 | cross-claim or counterclaim in a lawsuit) alleging that the Work
272 | or a Contribution incorporated within the Work constitutes direct
273 | or contributory patent infringement, then any patent licenses
274 | granted to You under this License for that Work shall terminate
275 | as of the date such litigation is filed.
276 |
277 | 4. Redistribution. You may reproduce and distribute copies of the
278 | Work or Derivative Works thereof in any medium, with or without
279 | modifications, and in Source or Object form, provided that You
280 | meet the following conditions:
281 |
282 | (a) You must give any other recipients of the Work or
283 | Derivative Works a copy of this License; and
284 |
285 | (b) You must cause any modified files to carry prominent notices
286 | stating that You changed the files; and
287 |
288 | (c) You must retain, in the Source form of any Derivative Works
289 | that You distribute, all copyright, patent, trademark, and
290 | attribution notices from the Source form of the Work,
291 | excluding those notices that do not pertain to any part of
292 | the Derivative Works; and
293 |
294 | (d) If the Work includes a "NOTICE" text file as part of its
295 | distribution, then any Derivative Works that You distribute must
296 | include a readable copy of the attribution notices contained
297 | within such NOTICE file, excluding those notices that do not
298 | pertain to any part of the Derivative Works, in at least one
299 | of the following places: within a NOTICE text file distributed
300 | as part of the Derivative Works; within the Source form or
301 | documentation, if provided along with the Derivative Works; or,
302 | within a display generated by the Derivative Works, if and
303 | wherever such third-party notices normally appear. The contents
304 | of the NOTICE file are for informational purposes only and
305 | do not modify the License. You may add Your own attribution
306 | notices within Derivative Works that You distribute, alongside
307 | or as an addendum to the NOTICE text from the Work, provided
308 | that such additional attribution notices cannot be construed
309 | as modifying the License.
310 |
311 | You may add Your own copyright statement to Your modifications and
312 | may provide additional or different license terms and conditions
313 | for use, reproduction, or distribution of Your modifications, or
314 | for any such Derivative Works as a whole, provided Your use,
315 | reproduction, and distribution of the Work otherwise complies with
316 | the conditions stated in this License.
317 |
318 | 5. Submission of Contributions. Unless You explicitly state otherwise,
319 | any Contribution intentionally submitted for inclusion in the Work
320 | by You to the Licensor shall be under the terms and conditions of
321 | this License, without any additional terms or conditions.
322 | Notwithstanding the above, nothing herein shall supersede or modify
323 | the terms of any separate license agreement you may have executed
324 | with Licensor regarding such Contributions.
325 |
326 | 6. Trademarks. This License does not grant permission to use the trade
327 | names, trademarks, service marks, or product names of the Licensor,
328 | except as required for reasonable and customary use in describing the
329 | origin of the Work and reproducing the content of the NOTICE file.
330 |
331 | 7. Disclaimer of Warranty. Unless required by applicable law or
332 | agreed to in writing, Licensor provides the Work (and each
333 | Contributor provides its Contributions) on an "AS IS" BASIS,
334 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
335 | implied, including, without limitation, any warranties or conditions
336 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
337 | PARTICULAR PURPOSE. You are solely responsible for determining the
338 | appropriateness of using or redistributing the Work and assume any
339 | risks associated with Your exercise of permissions under this License.
340 |
341 | 8. Limitation of Liability. In no event and under no legal theory,
342 | whether in tort (including negligence), contract, or otherwise,
343 | unless required by applicable law (such as deliberate and grossly
344 | negligent acts) or agreed to in writing, shall any Contributor be
345 | liable to You for damages, including any direct, indirect, special,
346 | incidental, or consequential damages of any character arising as a
347 | result of this License or out of the use or inability to use the
348 | Work (including but not limited to damages for loss of goodwill,
349 | work stoppage, computer failure or malfunction, or any and all
350 | other commercial damages or losses), even if such Contributor
351 | has been advised of the possibility of such damages.
352 |
353 | 9. Accepting Warranty or Additional Liability. While redistributing
354 | the Work or Derivative Works thereof, You may choose to offer,
355 | and charge a fee for, acceptance of support, warranty, indemnity,
356 | or other liability obligations and/or rights consistent with this
357 | License. However, in accepting such obligations, You may act only
358 | on Your own behalf and on Your sole responsibility, not on behalf
359 | of any other Contributor, and only if You agree to indemnify,
360 | defend, and hold each Contributor harmless for any liability
361 | incurred by, or claims asserted against, such Contributor by reason
362 | of your accepting any such warranty or additional liability.
363 |
364 | END OF TERMS AND CONDITIONS
365 |
366 | APPENDIX: How to apply the Apache License to your work.
367 |
368 | To apply the Apache License to your work, attach the following
369 | boilerplate notice, with the fields enclosed by brackets "{}"
370 | replaced with your own identifying information. (Don't include
371 | the brackets!) The text should be enclosed in the appropriate
372 | comment syntax for the file format. We also recommend that a
373 | file or class name and description of purpose be included on the
374 | same "printed page" as the copyright notice for easier
375 | identification within third-party archives.
376 |
377 | Copyright {yyyy} {name of copyright owner}
378 |
379 | Licensed under the Apache License, Version 2.0 (the "License");
380 | you may not use this file except in compliance with the License.
381 | You may obtain a copy of the License at
382 |
383 | http://www.apache.org/licenses/LICENSE-2.0
384 |
385 | Unless required by applicable law or agreed to in writing, software
386 | distributed under the License is distributed on an "AS IS" BASIS,
387 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
388 | See the License for the specific language governing permissions and
389 | limitations under the License.
390 |
--------------------------------------------------------------------------------