Skip to content

Commit 8c8d191

Browse files
* clean up and optimize actor name validation. (#6919)
* * clean up and optimize actor name validation. * Change ValidSymbols const to public (referenced in docs) and add unit test * Fix API Approval list --------- Co-authored-by: Gregorius Soedharmo <[email protected]>
1 parent 15bb5ca commit 8c8d191

File tree

6 files changed

+62
-25
lines changed

6 files changed

+62
-25
lines changed

src/core/Akka.API.Tests/verify/CoreAPISpec.ApproveCore.DotNet.verified.txt

+1
Original file line numberDiff line numberDiff line change
@@ -178,6 +178,7 @@ namespace Akka.Actor
178178
}
179179
public abstract class ActorPath : Akka.Util.ISurrogated, System.IComparable<Akka.Actor.ActorPath>, System.IEquatable<Akka.Actor.ActorPath>
180180
{
181+
public const string ValidSymbols = "\"-_.*$+:@&=,!~\';()";
181182
protected ActorPath(Akka.Actor.Address address, string name) { }
182183
protected ActorPath(Akka.Actor.ActorPath parentPath, string name, long uid) { }
183184
public Akka.Actor.Address Address { get; }

src/core/Akka.API.Tests/verify/CoreAPISpec.ApproveCore.Net.verified.txt

+1
Original file line numberDiff line numberDiff line change
@@ -178,6 +178,7 @@ namespace Akka.Actor
178178
}
179179
public abstract class ActorPath : Akka.Util.ISurrogated, System.IComparable<Akka.Actor.ActorPath>, System.IEquatable<Akka.Actor.ActorPath>
180180
{
181+
public const string ValidSymbols = "\"-_.*$+:@&=,!~\';()";
181182
protected ActorPath(Akka.Actor.Address address, string name) { }
182183
protected ActorPath(Akka.Actor.ActorPath parentPath, string name, long uid) { }
183184
public Akka.Actor.Address Address { get; }

src/core/Akka.Tests/Actor/ActorPathSpec.cs

+28
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
using Akka.Actor;
1212
using Akka.TestKit;
1313
using Xunit;
14+
using FluentAssertions;
1415

1516
namespace Akka.Tests.Actor
1617
{
@@ -274,6 +275,33 @@ public void Validate_element_parts(string element, bool matches)
274275
ActorPath.IsValidPathElement(element).ShouldBe(matches);
275276
}
276277

278+
[Fact]
279+
public void ValidateElementPartsComprehensive()
280+
{
281+
// NOTE: $ is not a valid starting character
282+
var valid = Enumerable.Range(0, 128).Select(i => (char)i).Where(c =>
283+
c is >= 'a' and <= 'z' ||
284+
c is >= 'A' and <= 'Z' ||
285+
c is >= '0' and <= '9' ||
286+
ActorPath.ValidSymbols.Contains(c)
287+
&& c is not '$'
288+
).ToArray();
289+
290+
foreach (var c in Enumerable.Range(0, 2048).Select(i => (char)i))
291+
{
292+
if(valid.Contains(c))
293+
ActorPath.IsValidPathElement(new string(new[] { c })).Should().BeTrue();
294+
else
295+
ActorPath.IsValidPathElement(new string(new[] { c })).Should().BeFalse();
296+
}
297+
298+
// $ after a valid character should be valid
299+
foreach (var c in valid)
300+
{
301+
ActorPath.IsValidPathElement(new string(new[] { c, '$' })).Should().BeTrue();
302+
}
303+
}
304+
277305
[Theory]
278306
[InlineData("$NamesMayNotNormallyStartWithDollarButShouldBeOkWHenEncoded")]
279307
[InlineData("NamesMayContain$Dollar")]

src/core/Akka/Actor/ActorCell.Children.cs

+8-12
Original file line numberDiff line numberDiff line change
@@ -8,15 +8,11 @@
88
using System;
99
using System.Collections.Generic;
1010
using System.Collections.Immutable;
11-
using System.Text;
1211
using System.Runtime.CompilerServices;
1312
using System.Runtime.Serialization;
1413
using System.Threading;
1514
using Akka.Actor.Internal;
16-
using Akka.Dispatch.SysMsg;
17-
using Akka.Serialization;
1815
using Akka.Util;
19-
using Akka.Util.Internal;
2016

2117
namespace Akka.Actor
2218
{
@@ -80,12 +76,12 @@ protected void StopFunctionRefs()
8076

8177
/// <summary>
8278
/// Attaches a child to the current <see cref="ActorCell"/>.
83-
///
79+
///
8480
/// This method is used in the process of starting actors.
8581
/// </summary>
8682
/// <param name="props">The <see cref="Props"/> this child actor will use.</param>
8783
/// <param name="isSystemService">If <c>true</c>, then this actor is a system actor and activates a special initialization path.</param>
88-
/// <param name="name">The name of the actor being started. Can be <c>null</c>, and if it is we will automatically
84+
/// <param name="name">The name of the actor being started. Can be <c>null</c>, and if it is we will automatically
8985
/// generate a random name for this actor.</param>
9086
/// <exception cref="InvalidActorNameException">
9187
/// This exception is thrown if the given <paramref name="name"/> is an invalid actor name.
@@ -199,7 +195,7 @@ protected void UnreserveChild(string name)
199195
}
200196

201197
/// <summary>
202-
/// This should only be used privately or when creating the root actor.
198+
/// This should only be used privately or when creating the root actor.
203199
/// </summary>
204200
/// <param name="actor">TBD</param>
205201
/// <returns>TBD</returns>
@@ -307,8 +303,8 @@ private void ResumeChildren(Exception causedByFailure, IActorRef perpetrator)
307303
}
308304

309305
/// <summary>
310-
/// Tries to get the stats for the child with the specified name. The stats can be either <see cref="ChildNameReserved"/>
311-
/// indicating that only a name has been reserved for the child, or a <see cref="ChildRestartStats"/> for a child that
306+
/// Tries to get the stats for the child with the specified name. The stats can be either <see cref="ChildNameReserved"/>
307+
/// indicating that only a name has been reserved for the child, or a <see cref="ChildRestartStats"/> for a child that
312308
/// has been initialized/created.
313309
/// </summary>
314310
/// <param name="name">TBD</param>
@@ -383,7 +379,7 @@ public IInternalActorRef GetSingleChild(string name)
383379
}
384380
return ActorRefs.Nobody;
385381
}
386-
382+
387383
/// <summary>
388384
/// TBD
389385
/// </summary>
@@ -421,7 +417,7 @@ private static string CheckName(string name)
421417
if (name.Length == 0) throw new InvalidActorNameException("Actor name must not be empty.");
422418
if (!ActorPath.IsValidPathElement(name))
423419
{
424-
throw new InvalidActorNameException($"Illegal actor name [{name}]. Actor paths MUST: not start with `$`, include only ASCII letters and can only contain these special characters: ${new string(ActorPath.ValidSymbols)}.");
420+
throw new InvalidActorNameException($"Illegal actor name [{name}]. {ActorPath.ValidActorNameDescription}");
425421
}
426422
return name;
427423
}
@@ -437,7 +433,7 @@ private IInternalActorRef MakeChild(Props props, string name, bool async, bool s
437433
if (oldInfo == null)
438434
Serialization.Serialization.CurrentTransportInformation =
439435
SystemImpl.Provider.SerializationInformation;
440-
436+
441437
var ser = _systemImpl.Serialization;
442438
if (props.Arguments != null)
443439
{

src/core/Akka/Actor/ActorPath.cs

+23-12
Original file line numberDiff line numberDiff line change
@@ -92,25 +92,36 @@ public override int GetHashCode()
9292
#endregion
9393
}
9494

95+
public const string ValidSymbols = "\"-_.*$+:@&=,!~';()";
96+
9597
/// <summary>
96-
/// INTERNAL API
98+
/// A small bool array, indexed by the ASCII code (0..127), containing <c>true</c> for valid chars
99+
/// and <c>false</c> for invalid characters.
97100
/// </summary>
98-
internal static readonly char[] ValidSymbols = @"""-_.*$+:@&=,!~';""()".ToCharArray();
101+
private static readonly bool[] ValidAscii = Enumerable.Range(0, 128).Select(c => (c >= 'a' && c <= 'z')
102+
|| (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9') || ValidSymbols.Contains((char)c))
103+
.ToArray();
104+
105+
/// <summary>
106+
/// A human readable description of a valid actor name. This is used in several places to throw
107+
/// exceptions when the caller supplies invalid actor names.
108+
/// </summary>
109+
internal const string ValidActorNameDescription=
110+
$"Actor paths MUST: not start with `$`, not be empty, and only contain ASCII letters, digits and these special characters: `{ValidSymbols}`";
99111

100112
/// <summary>
101113
/// Method that checks if actor name conforms to RFC 2396, http://www.ietf.org/rfc/rfc2396.txt
102114
/// Note that AKKA JVM does not allow parenthesis ( ) but, according to RFC 2396 those are allowed, and
103115
/// since we use URL Encode to create valid actor names, we must allow them.
104116
/// </summary>
105-
/// <param name="s">TBD</param>
106-
/// <returns>TBD</returns>
117+
/// <param name="s">The string to verify for conformity</param>
118+
/// <returns>True if the path element is valid</returns>
107119
public static bool IsValidPathElement(string s)
108120
{
109121
return !string.IsNullOrEmpty(s) && !s.StartsWith('$') && Validate(s);
110122
}
111123

112-
private static bool IsValidChar(char c) => (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') ||
113-
(c >= '0' && c <= '9') || ValidSymbols.Contains(c);
124+
private static bool IsValidChar(char c) => c < 128 && ValidAscii[c];
114125

115126
private static bool IsHexChar(char c) => (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F') ||
116127
(c >= '0' && c <= '9');
@@ -584,24 +595,24 @@ void AppendUidSpan(ref Span<char> writeable, int startPos, int sizeHint)
584595
totalLength += p._name.Length + 1;
585596
p = p._parent;
586597
}
587-
598+
588599
// UID calculation
589600
var uidSizeHint = 0;
590601
if (uid != null)
591602
{
592603
// 1 extra character for the '#'
593604
uidSizeHint = SpanHacks.Int64SizeInCharacters(uid.Value) + 1;
594-
totalLength += uidSizeHint;
605+
totalLength += uidSizeHint;
595606
}
596607

597-
// Concatenate segments (in reverse order) into buffer with '/' prefixes
608+
// Concatenate segments (in reverse order) into buffer with '/' prefixes
598609
Span<char> buffer = totalLength < 1024 ? stackalloc char[totalLength] : new char[totalLength];
599610
prefix.CopyTo(buffer);
600611

601612
var offset = buffer.Length - uidSizeHint;
602613
// append UID span first
603614
AppendUidSpan(ref buffer, offset, uidSizeHint-1); // -1 for the '#'
604-
615+
605616
p = this;
606617
while (p._depth > 0)
607618
{
@@ -752,11 +763,11 @@ private string ToStringWithAddress(Address address, bool includeUid)
752763
// we never change address for IgnoreActorRef
753764
return ToString();
754765
}
755-
766+
756767
long? uid = null;
757768
if (includeUid && _uid != ActorCell.UndefinedUid)
758769
uid = _uid;
759-
770+
760771
if (_address.Host != null && _address.Port.HasValue)
761772
return Join(_address.ToString().AsSpan(), uid);
762773

src/core/Akka/Actor/Deployer.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,7 @@ void Add(IList<string> path, Deploy d)
9797
if (!ActorPath.IsValidPathElement(t))
9898
{
9999
throw new IllegalActorNameException(
100-
$"Illegal actor name [{t}] in deployment [${d.Path}]. Actor paths MUST: not start with `$`, include only ASCII letters and can only contain these special characters: ${new string(ActorPath.ValidSymbols)}.");
100+
$"Illegal actor name [{t}] in deployment [${d.Path}]. {ActorPath.ValidActorNameDescription}");
101101
}
102102
}
103103
if (!_deployments.CompareAndSet(w, w.Insert(path, d))) Add(path, d);

0 commit comments

Comments
 (0)