fix: make the parser more robust and no longer accept invalid input silently.

This commit is contained in:
redxef 2025-03-02 18:33:25 +01:00
parent 1d472ed61f
commit 2624b83a79
Signed by: redxef
GPG key ID: 7DAC3AA211CBD921
3 changed files with 25 additions and 4 deletions

View file

@ -3,7 +3,7 @@ using Jellyfin.Plugin.SmartPlaylist.Lisp;
namespace Jellyfin.Plugin.SmartPlaylist.Lisp.Compiler { namespace Jellyfin.Plugin.SmartPlaylist.Lisp.Compiler {
public class Parser { public class Parser {
private StringTokenStream _sts; internal StringTokenStream _sts;
public Parser(StringTokenStream tokens) { public Parser(StringTokenStream tokens) {
_sts = tokens; _sts = tokens;
} }
@ -29,13 +29,18 @@ namespace Jellyfin.Plugin.SmartPlaylist.Lisp.Compiler {
Debug.Assert(start.value == end.value); Debug.Assert(start.value == end.value);
Debug.Assert("\"".Contains(start.value)); Debug.Assert("\"".Contains(start.value));
string r = ""; string r = "";
bool exit_ok = false;
while (_sts.Available() > 0) { while (_sts.Available() > 0) {
Token<string> t = _sts.Get(); Token<string> t = _sts.Get();
if (t.value == end.value) { if (t.value == end.value) {
exit_ok = true;
break; break;
} }
r += t.value; r += t.value;
} }
if (!exit_ok) {
throw new ApplicationException($"Failed to parse string, are you missing the closing quotes? String is: {r}");
}
_sts.Commit(); _sts.Commit();
return new String(r); return new String(r);
} }
@ -69,11 +74,16 @@ namespace Jellyfin.Plugin.SmartPlaylist.Lisp.Compiler {
if (";".Contains(start.value)) { if (";".Contains(start.value)) {
return parse_comment(start, end); return parse_comment(start, end);
} }
Debug.Assert(end != null); if (end == null) {
throw new ApplicationException($"Don't know how to parse grouping starting with token '{start.value}'");
}
IList<Expression> expressions = new List<Expression>(); IList<Expression> expressions = new List<Expression>();
bool exit_ok = false;
while (_sts.Available() > 0) { while (_sts.Available() > 0) {
Token<string> t = _sts.Get(); Token<string> t = _sts.Get();
if (t.value == end.value) { if (t.value == end.value) {
exit_ok = true;
_sts.Commit(); _sts.Commit();
break; break;
} }
@ -87,7 +97,11 @@ namespace Jellyfin.Plugin.SmartPlaylist.Lisp.Compiler {
_sts.Rewind(1); _sts.Rewind(1);
expressions.Add(parse()); expressions.Add(parse());
} }
return Cons.FromList(expressions); var r = Cons.FromList(expressions);
if (!exit_ok) {
throw new ApplicationException($"Failed to parse grouping, are you missing some closing braces? Parsed expressions: {r}");
}
return r;
} }
Expression parse_atom(AtomToken at) { Expression parse_atom(AtomToken at) {

View file

@ -601,7 +601,11 @@ namespace Jellyfin.Plugin.SmartPlaylist.Lisp {
throw new ApplicationException($"Not handled case '{expression}'"); throw new ApplicationException($"Not handled case '{expression}'");
} }
public Expression eval(Parser p) { public Expression eval(Parser p) {
return eval(p.parse()); Expression r = eval(p.parse());
if (p._sts.Available() > 0) {
throw new ApplicationException($"Did not consume all tokens, remaining program is {string.Join(" ", p._sts.Remainder())}");
}
return r;
} }
public Expression eval(StringTokenStream sts) { public Expression eval(StringTokenStream sts) {
return eval(new Parser(sts)); return eval(new Parser(sts));

View file

@ -56,6 +56,9 @@ namespace Jellyfin.Plugin.SmartPlaylist.Util {
public IStream<T> Copy() { public IStream<T> Copy() {
return new Stream<T>(_items, _cursor, _ephemeralCursor); return new Stream<T>(_items, _cursor, _ephemeralCursor);
} }
public IList<T> Remainder() {
return _items.Skip(_cursor).ToList();
}
} }
} }