Skip to content

Commit 93f4ee8

Browse files
author
Aytackydln
committed
allow OPTIONS method on all paths
1 parent 8241837 commit 93f4ee8

7 files changed

Lines changed: 237 additions & 48 deletions

File tree

Project-Aurora/Project-Aurora/Modules/GameStateListen/AuroraHttpListener.cs

Lines changed: 41 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -30,23 +30,24 @@ public sealed class AuroraHttpListener
3030
private bool _isRunning;
3131
private readonly CancellationTokenSource _cancellationTokenSource;
3232
private readonly CancellationToken _cancellationToken;
33-
private IGameState _currentGameState = new NewtonsoftGameState("{}");
3433
private readonly HttpListener _netListener;
3534
private readonly int _port;
3635
private readonly SingleConcurrentThread _readThread;
3736
private readonly SingleConcurrentThread _readThread2;
37+
3838
private readonly FrozenDictionary<string, AuroraEndpoint> _endpoints;
3939
private readonly FrozenDictionary<Regex, AuroraRegexEndpoint> _regexEndpoints;
40+
private readonly HashSet<string> _allowedMethods;
4041

4142
public IGameState CurrentGameState
4243
{
43-
get => _currentGameState;
44+
get;
4445
internal set
4546
{
46-
_currentGameState = value;
47+
field = value;
4748
NewGameState?.Invoke(this, value);
4849
}
49-
}
50+
} = new NewtonsoftGameState("{}");
5051

5152
/// <summary>
5253
/// Event for handing a newly received game state
@@ -55,7 +56,7 @@ internal set
5556

5657
public event EventHandler<JsonGameStateEventArgs>? NewJsonGameState;
5758

58-
/// <summary>
59+
/// <summary>
5960
/// A GameStateListener that listens for connections on http://127.0.0.1:port/
6061
/// </summary>
6162
public AuroraHttpListener(int port, IEnumerable<string> listenIps)
@@ -76,6 +77,10 @@ public AuroraHttpListener(int port, IEnumerable<string> listenIps)
7677

7778
_endpoints = HttpEndpointFactory.CreateEndpoints(this);
7879
_regexEndpoints = HttpEndpointFactory.CreateRegexEndpoints(this);
80+
_allowedMethods = _endpoints.Values
81+
.Union<IAuroraEndpoint>(_regexEndpoints.Values)
82+
.SelectMany(endpoint => endpoint.AvailableMethods)
83+
.ToHashSet();
7984
}
8085

8186
private static void ExceptionCallback(object? sender, SingleThreadExceptionEventArgs eventArgs)
@@ -98,15 +103,16 @@ public bool Start()
98103
catch (HttpListenerException exc)
99104
{
100105
Global.logger.Error(exc, "Could not start HttpListener");
101-
102-
if (exc.ErrorCode == 5)//Access Denied
106+
107+
if (exc.ErrorCode == 5) //Access Denied
103108
MessageBox.Show("Access error during start of network listener.\r\n\r\n" +
104109
"To fix this issue, please run the following commands as admin in Command Prompt:r\n" +
105-
$" netsh http add urlacl url=http://127.0.0.1:{_port}/ user=Everyone listen=yes",
110+
$" netsh http add urlacl url=http://127.0.0.1:{_port}/ user=Everyone listen=yes",
106111
"Aurora - Error");
107112

108113
return false;
109114
}
115+
110116
_isRunning = true;
111117

112118
_readThread.Trigger();
@@ -122,6 +128,7 @@ private async Task AsyncRead1()
122128
{
123129
_readThread2.Trigger();
124130
}
131+
125132
ProcessContext(context);
126133
}
127134
catch (TaskCanceledException)
@@ -139,6 +146,7 @@ private async Task AsyncRead2()
139146
{
140147
_readThread.Trigger();
141148
}
149+
142150
ProcessContext(context);
143151
}
144152
catch (TaskCanceledException)
@@ -163,34 +171,43 @@ public Task Stop()
163171
private void ProcessContext(HttpListenerContext context)
164172
{
165173
var path = context.Request.Url!.LocalPath;
166-
167-
// find exact path match
168-
if (_endpoints.TryGetValue(path, out var endpoint))
169-
{
170-
endpoint.HandleRequest(context);
174+
175+
// find the exact path match
176+
if (_endpoints.TryGetValue(path, out var endpoint) && endpoint.HandleRequest(context))
171177
return;
172-
}
173178

174-
// find regex path match
179+
// find a regex path match
175180
try
176181
{
177-
var (match, regexEndpoint) = _regexEndpoints
182+
var regexHandled = _regexEndpoints
178183
.Select(kv => (kv.Key.Match(path), kv.Value))
179-
.First(kv => kv.Item1.Success);
180-
regexEndpoint.HandleRequest(context, match);
184+
.Where(kv => kv.Item1.Success)
185+
.Select(kv => kv.Value.HandleRequest(context, kv.Item1))
186+
.FirstOrDefault(k => k);
187+
188+
if (regexHandled) return;
189+
var ctxResponse = context.Response;
190+
ctxResponse.StatusCode = (int)HttpStatusCode.MethodNotAllowed;
191+
ctxResponse.Close();
181192
return;
182-
}catch(InvalidOperationException)
193+
}
194+
catch (InvalidOperationException)
183195
{
184196
// no match
185197
}
186198

199+
var method = context.Request.HttpMethod;
200+
if (!_allowedMethods.Contains(method))
201+
{
202+
var ctxResponse = context.Response;
203+
ctxResponse.StatusCode = (int)HttpStatusCode.MethodNotAllowed;
204+
ctxResponse.Close();
205+
return;
206+
}
207+
187208
// no match
188209
var response = context.Response;
189-
response.StatusCode = context.Request.HttpMethod switch
190-
{
191-
"OPTIONS" => (int)HttpStatusCode.OK,
192-
_ => (int)HttpStatusCode.NotFound
193-
};
210+
response.StatusCode = (int)HttpStatusCode.NotFound;
194211
response.Headers = WebHeaderCollection;
195212
response.Close([], true);
196213
}
Lines changed: 7 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,23 @@
11
using System;
22
using System.Collections.Frozen;
33
using System.Collections.Generic;
4+
using System.Linq;
45
using System.Net;
56

67
namespace AuroraRgb.Modules.GameStateListen.Http;
78

8-
public class AuroraEndpoint(Dictionary<string, Action<HttpListenerContext>> methods, string path)
9+
public class AuroraEndpoint(Dictionary<string, Action<HttpListenerContext>> methods, string path) : IAuroraEndpoint
910
{
1011
public string Path { get; } = path;
12+
public string[] AvailableMethods { get; } = methods.Keys.ToArray();
1113
private readonly FrozenDictionary<string, Action<HttpListenerContext>> _methods = methods.ToFrozenDictionary();
1214

13-
public void HandleRequest(HttpListenerContext context)
15+
public bool HandleRequest(HttpListenerContext context)
1416
{
1517
var request = context.Request;
16-
var response = context.Response;
1718

18-
if (_methods.TryGetValue(request.HttpMethod, out var handler))
19-
{
20-
handler(context);
21-
}
22-
else
23-
{
24-
response.StatusCode = (int)HttpStatusCode.MethodNotAllowed;
25-
response.Close();
26-
}
19+
if (!_methods.TryGetValue(request.HttpMethod, out var handler)) return false;
20+
handler(context);
21+
return true;
2722
}
2823
}
Lines changed: 13 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,30 @@
11
using System;
22
using System.Collections.Frozen;
33
using System.Collections.Generic;
4+
using System.Linq;
45
using System.Net;
56
using System.Text.RegularExpressions;
67

78
namespace AuroraRgb.Modules.GameStateListen.Http;
89

9-
public class AuroraRegexEndpoint(Dictionary<string, Action<HttpListenerContext, Match>> methods, Regex path)
10+
public class AuroraRegexEndpoint(Dictionary<string, Action<HttpListenerContext, Match>> methods, Regex path) : IAuroraEndpoint
1011
{
1112
public Regex Path { get; } = path;
13+
public string[] AvailableMethods { get; } = methods.Keys.ToArray();
1214
private readonly FrozenDictionary<string, Action<HttpListenerContext, Match>> _methods = methods.ToFrozenDictionary();
1315

14-
public void HandleRequest(HttpListenerContext context, Match regexMatch)
16+
/**
17+
* Processes the request if the method is supported,
18+
* otherwise returns false to indicate that the request should be handled by another endpoint
19+
*/
20+
public bool HandleRequest(HttpListenerContext context, Match regexMatch)
1521
{
1622
var request = context.Request;
17-
var response = context.Response;
1823

19-
if (_methods.TryGetValue(request.HttpMethod, out var handler))
20-
{
21-
handler(context, regexMatch);
22-
}
23-
else
24-
{
25-
response.StatusCode = (int)HttpStatusCode.MethodNotAllowed;
26-
response.Close();
27-
}
24+
if (!_methods.TryGetValue(request.HttpMethod, out var handler))
25+
return false;
26+
27+
handler(context, regexMatch);
28+
return true;
2829
}
2930
}
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Net;
4+
using System.Text.RegularExpressions;
5+
using AuroraRgb.Profiles;
6+
7+
namespace AuroraRgb.Modules.GameStateListen.Http;
8+
9+
public static partial class HttpEndpointFactory
10+
{
11+
private static readonly WebHeaderCollection OptionsHeaderCollection = new()
12+
{
13+
["Access-Control-Allow-Origin"] = "*",
14+
["Access-Control-Allow-Methods"] = "GET, POST, OPTIONS",
15+
["Access-Control-Allow-Headers"] = "Content-Type",
16+
["Access-Control-Allow-Private-Network"] = "true",
17+
};
18+
19+
private static AuroraRegexEndpoint OptionsMethod()
20+
{
21+
var methods = new Dictionary<string, Action<HttpListenerContext, Match>>
22+
{
23+
["OPTIONS"] = ProcessOptions
24+
};
25+
return new AuroraRegexEndpoint(methods, AnyPathRegex());
26+
27+
void ProcessOptions(HttpListenerContext context, Match match)
28+
{
29+
try
30+
{
31+
context.Response.StatusCode = (int)HttpStatusCode.OK;
32+
context.Response.Headers = OptionsHeaderCollection;
33+
context.Response.Close([], true);
34+
}
35+
catch (Exception e)
36+
{
37+
Global.logger.Error(e, "[NetworkListener] Options error on: {Path}", context.Request.Url?.LocalPath);
38+
}
39+
}
40+
}
41+
42+
[GeneratedRegex(@".*")]
43+
private static partial Regex AnyPathRegex();
44+
}

Project-Aurora/Project-Aurora/Modules/GameStateListen/Http/HttpEndpointFactory.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ public static FrozenDictionary<Regex, AuroraRegexEndpoint> CreateRegexEndpoints(
2929
return ((AuroraRegexEndpoint[])
3030
[
3131
GsiEndpoint(listener),
32+
OptionsMethod(),
3233
])
3334
.ToFrozenDictionary(endpoint => endpoint.Path, endpoint => endpoint);
3435
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
namespace AuroraRgb.Modules.GameStateListen.Http;
2+
3+
public interface IAuroraEndpoint
4+
{
5+
string[] AvailableMethods { get; }
6+
}

0 commit comments

Comments
 (0)