-
Notifications
You must be signed in to change notification settings - Fork 34
Expand file tree
/
Copy pathOpenGraph.cs
More file actions
108 lines (90 loc) · 4.42 KB
/
OpenGraph.cs
File metadata and controls
108 lines (90 loc) · 4.42 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
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
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using RT.Servers;
using RT.Util.ExtensionMethods;
namespace KtaneWeb
{
partial class KtanePropellerModule
{
private Dictionary<string, string> getOpenGraphData(KtaneModuleInfo info)
{
return new()
{
["title"] = info.Name,
["type"] = "website",
["image"] = $"https://ktane.timwi.de/Icons/{(info.HasIcon(_config) ? info.FileName ?? info.Name : "blank")}.png",
["url"] = $"https://ktane.timwi.de/HTML/{info.FileName ?? info.Name}.html",
["description"] = info.Descriptions.FirstOrDefault(d => d.Language == "English")?.Description ?? "Error: indescribable item"
};
}
private HttpResponse injectOpenGraphData(HttpRequest req, HttpResponse resp)
{
if (!resp.Headers.ContentType?.EqualsIgnoreCase("text/html; charset=utf-8") ?? true
|| resp.Status != HttpStatusCode._200_OK
|| req.Method != HttpMethod.Get)
return resp;
// Only modify manual page HTML
if (!req.Url.Path.UrlUnescape().RegexMatch(@"^/HTML/([^/]+\.html)$", out var match))
return resp;
if (!_moduleInfoCache.SheetToJsonLookup.TryGetValue(match.Groups[1].Value, out var info))
return resp;
var openGraphData = getOpenGraphData(info);
if (openGraphData.Count == 0)
return resp;
return new HttpResponseContent(HttpStatusCode._200_OK, resp.Headers, () => createOpenGraphStream(resp, openGraphData));
}
private Stream createOpenGraphStream(HttpResponse resp, Dictionary<string, string> data)
{
var innerStream = resp.GetContentStream();
// The opening <head> tag should be within the leading 64 bytes of the file.
var header = innerStream.Read(64);
var ix = header.IndexOfSubarray("<head>".ToUtf8());
if (ix == -1)
return new HeaderStream(header, innerStream);
var ogBlock = data.Select(kvp => $"\n <meta property=\"og:{kvp.Key}\" content=\"{kvp.Value.HtmlEscape()}\">").JoinString().ToUtf8();
var fullHeader = new byte[header.Length + ogBlock.Length];
Array.Copy(header, fullHeader, ix + 6);
Array.Copy(ogBlock, 0, fullHeader, ix + 6, ogBlock.Length);
Array.Copy(header, ix + 6, fullHeader, ix + 6 + ogBlock.Length, header.Length - ix - 6);
return new HeaderStream(fullHeader, innerStream);
}
private sealed class HeaderStream(byte[] head, Stream tail) : Stream
{
private int _bytesRead = 0;
public override bool CanRead => true;
public override bool CanSeek => false;
public override bool CanWrite => false;
public override long Length => head.Length + tail.Length;
public override long Position
{
get => throw new NotSupportedException();
set => throw new NotSupportedException();
}
public override void Flush() => tail.Flush();
public override int Read(byte[] buffer, int offset, int count)
{
if (_bytesRead == -1)
return tail.Read(buffer, offset, count);
if (_bytesRead + count > head.Length)
{
Array.Copy(head, _bytesRead, buffer, offset, head.Length - _bytesRead);
var moved = tail.Read(buffer, offset + head.Length - _bytesRead, count - (head.Length - _bytesRead));
var ret = moved + head.Length - _bytesRead;
_bytesRead = -1;
return ret;
}
Array.Copy(head, _bytesRead, buffer, offset, count);
_bytesRead += count;
if (_bytesRead == head.Length)
_bytesRead = -1;
return count;
}
public override long Seek(long offset, SeekOrigin origin) => throw new NotSupportedException();
public override void SetLength(long value) => throw new NotSupportedException();
public override void Write(byte[] buffer, int offset, int count) => throw new NotSupportedException();
public override void Close() => tail.Close();
}
}
}