--- /dev/null
+#
+# Tests for parsing conditions in the new xlat_expr framework.
+#
+# $Id$
+#
+
+proto-dictionary radius
+#tmpl-rules allow_unresolved=yes allow_unknown=yes
+
+# All IP address literals should be parsed as prefixes
+xlat_purify ("foo\
+match ERROR offset 2: Unterminated string
+
+xlat_purify ("foo
+match ERROR offset 2: Unterminated string
+
+xlat_purify ()
+match ERROR offset 2: No operand found. Expected &ref, literal, 'quoted literal', "%{expansion}", or enum value
+
+xlat_purify (!)
+match ERROR offset 3: No operand found. Expected &ref, literal, 'quoted literal', "%{expansion}", or enum value
+
+xlat_purify (|| b)
+match ERROR offset 2: No operand found. Expected &ref, literal, 'quoted literal', "%{expansion}", or enum value
+
+xlat_purify ((ok || handled) foo)
+match ERROR offset 17: Invalid operator
+
+# escapes in names are illegal
+xlat_purify (ok\ foo || handled)
+match ERROR offset 4: Unexpected text after enum value. Expected operator
+
+#
+# @todo - this is an error somehow?
+#
+xlat_purify (Service-Type == 000-111)
+match ERROR offset 1: No IPv6 component separator: Failed resolving attribute in expansion: Service-Type
+#match (&Service-Type == (0 - 111))
+
+xlat_purify (ok FOO handled)
+match ERROR offset 4: Invalid operator
+
+xlat_purify (ok !x handled)
+match ERROR offset 4: Invalid operator
+
+xlat_purify (ok =x handled)
+match ERROR offset 4: Invalid operator
+
+#
+# Re-enable when we have proper bareword xlat tokenization
+#
+#xlat_purify (ok == handled"foo")
+#match ERROR offset 14 Unexpected text after condition
+
+# And now we have a bunch of VALID conditions we want to parse.
+
+# sillyness is OK, but cleaned up.
+#
+# We should really allow parsing of bare words, too?
+#
+#xlat_purify ((((((ok))))))
+#match ok
+
+#
+# Extra braces get squashed
+#
+#xlat_purify (&User-Name == &User-Password)
+#match (&User-Name == &User-Password)
+
+#xlat_purify (!ok)
+#match !ok
+
+#xlat_purify !(ok)
+#match !ok
+
+xlat_purify !!true
+match ERROR offset 1: Double operator is invalid
+
+#xlat_purify !(!ok)
+#match ok
+
+#
+# These next two are identical after normalization
+#
+xlat_purify (&User-Name == &User-Password || &Filter-Id == &Reply-Message)
+match ((&User-Name == &User-Password) || (&Filter-Id == &Reply-Message))
+
+xlat_purify ((&User-Name == &User-Password) || (&Filter-Id == &Reply-Message))
+match ((&User-Name == &User-Password) || (&Filter-Id == &Reply-Message))
+
+xlat_purify (!(&User-Name == &User-Password) || (&Filter-Id == &Reply-Message))
+match (!(&User-Name == &User-Password) || (&Filter-Id == &Reply-Message))
+
+# different from the previous ones.
+xlat_purify (!((&User-Name == &User-Password) || (&Filter-Id == &Reply-Message)))
+match !((&User-Name == &User-Password) || (&Filter-Id == &Reply-Message))
+
+xlat_purify (!(&User-Name == &User-Password) || (&Filter-Id == &Reply-Message))
+match (!(&User-Name == &User-Password) || (&Filter-Id == &Reply-Message))
+
+#
+# @todo - add a flag which says to parse the FULL thing, or only parse part of it?
+#
+xlat_purify ((&User-Name == &Filter-Id) || (&Reply-Message == &User-Password)))
+match Passed in 67 characters, but only parsed 66 characters: Unknown data type
+
+#
+# @todo - the first argument is truthy, so we should replace the
+# "&&" node with just the RHS.
+#
+xlat_purify ('handled' && (&Packet-Type == Access-Challenge))
+match ('handled' && (&Packet-Type == Access-Challenge))
+
+# This is OK, without the braces
+xlat_purify 'handled' && &Packet-Type == Access-Challenge
+match ('handled' && (&Packet-Type == Access-Challenge))
+
+# and this, though it's not a good idea.
+xlat_purify 'handled' &&&Packet-Type == Access-Challenge
+match ('handled' && (&Packet-Type == Access-Challenge))
+
+#
+# @todo - fix list comparisons
+#
+#xlat_purify &reply == &request
+#match ERROR offset 0: Cannot use list references in condition
+
+#xlat_purify &reply == "hello"
+#match ERROR offset 0: Cannot use list references in condition
+
+#xlat_purify "hello" == &reply
+#match ERROR offset 11: Cannot use list references in condition
+
+
+#
+# Convert != to !(COND) for normal checks
+#
+# @todo - xlat doesn't do this, maybe that needs fixing?
+#
+xlat_purify &User-Name == &User-Password
+match (&User-Name == &User-Password)
+
+xlat_purify &User-Name != &User-Password
+match (&User-Name != &User-Password)
+#match !&User-Name == &User-Password
+
+xlat_purify !&User-Name != &User-Password
+match (!&User-Name != &User-Password)
+#match &User-Name == &User-Password
+
+#
+# We allow a cast for the existence check?
+#
+xlat_purify <ipv6addr>::1
+match ::1
+
+# new casts are allowed, too.
+xlat_purify (ipv6addr)::1
+match ::1
+
+xlat_purify (ipv6addr)"xxx"
+match ERROR offset 12: Failed to parse IPv6 address string "xxx"
+
+#
+# Various casts
+#
+xlat_purify <ipaddr>&Filter-Id == &Framed-IP-Address
+match ((ipaddr)&Filter-Id == &Framed-IP-Address)
+
+#
+# We can automatically promote things as needed. But if the
+# user forces incompatible types, then that's an error.
+#
+# @todo - perhaps better errors for casts?
+#
+xlat_purify <ipaddr>&Filter-Id == <blerg>&Framed-IP-Address
+match ERROR offset 22: No operand found. Expected &ref, literal, 'quoted literal', "%{expansion}", or enum value
+
+xlat_purify <blerg>&Filter-Id == "foo"
+match ERROR offset 1: No operand found. Expected &ref, literal, 'quoted literal', "%{expansion}", or enum value
+
+#
+# Don't normalize things
+#
+xlat_purify <ipaddr>127.0.0.1 < &Framed-IP-Address
+match (127.0.0.1 < &Framed-IP-Address)
+
+# redundant casts get squashed
+xlat_purify <ipaddr>&Framed-IP-Address == 127.0.0.1
+match (&Framed-IP-Address == 127.0.0.1)
+
+xlat_purify <cidr>&Framed-IP-Address <= 192.168.0.0/16
+match ((ipv4prefix)&Framed-IP-Address <= 192.168.0.0/16)
+
+# All IP address literals should be parsed as prefixes
+xlat_purify &Framed-IP-Address <= 192.168.0.0/16
+match (&Framed-IP-Address <= 192.168.0.0/16)
+
+# string attributes must be string
+xlat_purify &User-Name == "bob"
+match (&User-Name == "bob")
+
+xlat_purify &User-Name == `bob`
+match (&User-Name == `bob`)
+
+xlat_purify &User-Name == 'bob'
+match (&User-Name == 'bob')
+
+xlat_purify &User-Name == bob
+match (&User-Name == 'bob')
+
+# Integer (etc.) types must be "bare"
+xlat_purify &Session-Timeout == 10
+match (&Session-Timeout == 10)
+
+# Automatic type inference means this is fine
+xlat_purify &Session-Timeout == '10'
+match (&Session-Timeout == 10)
+
+# Except for dates, which can be humanly readable!
+# This one is be an expansion, so it's left as-is.
+#
+# @todo - yuck. Suppress full path?
+#
+xlat_purify &Event-Timestamp == "January 1, 2012 %{User-Name}"
+match (&Event-Timestamp == "January 1, 2012 %{Request[0].User-Name}")
+
+# This one is NOT an expansion, so it's parsed into normal form
+#
+# @todo - can't parse years?
+#
+xlat_purify &Event-Timestamp == 'January 1, 2012'
+match ERROR offset 1: Invalid year string
+#match (&Event-Timestamp == 'Jan 1 2012 00:00:00 EST')
+
+# literals are parsed when the conditions are parsed
+xlat_purify <integer>X == 1
+match ERROR offset 10: Failed parsing string as type 'uint32'
+
+#
+# @todo - resolution is delayed, so we don't know where in the input
+# string the RHS is.
+#
+xlat_purify &NAS-Port == X
+match ERROR offset 1: Failed parsing string as type 'uint32'
+#match ERROR offset 13: Failed parsing string as type 'uint32'
+
+#
+# The RHS is a static string, so this gets mashed to a literal,
+# and then statically evaluated.
+#
+xlat_purify <ipaddr>127.0.0.1 == "127.0.0.1"
+match yes
+
+# @todo - why isn't the RHS being purified?
+xlat_purify <ipaddr>127.0.0.1 == "%{md4: 127.0.0.1}"
+match (127.0.0.1 == "%{md4: 127.0.0.1}")
+
+#
+# Bare %{...} is allowed.
+#
+
+# @todo - why isn't the RHS being purified?
+xlat_purify <ipaddr>127.0.0.1 == %{md4:127.0.0.1}
+match (127.0.0.1 == %{md4:127.0.0.1})
+
+# @todo - yuck, don't print full path?
+xlat_purify <ipaddr>127.0.0.1 == %{md4: SELECT user FROM table WHERE user='%{User-Name}'}
+match (127.0.0.1 == %{md4: SELECT user FROM table WHERE user='%{Request[0].User-Name}'})
+
+xlat_purify <ether> 00:11:22:33:44:55 == "00:11:22:33:44:55"
+match yes
+
+# @todo - why isn't the RHS being purified?
+xlat_purify <ether>00:11:22:33:44:55 == "%{md4:00:11:22:33:44:55}"
+match (00:11:22:33:44:55 == "%{md4:00:11:22:33:44:55}")
+
+xlat_purify <ether> 00:XX:22:33:44:55 == 00:11:22:33:44:55
+match ERROR offset 12: Missing separator, expected ':'
+
+#
+# Tests for boolean data types.
+#
+xlat_purify true
+match yes
+
+# @todo - for conditions, this should evaluate to "yes"
+xlat_purify 1
+match 1
+
+xlat_purify false
+match no
+
+xlat_purify 0
+match 0
+
+#
+# @todo - purify logical operators. The instantiation function should update the "can_purify" flags.
+#
+xlat_purify true && (&User-Name == "bob")
+match (yes && (&User-Name == "bob"))
+#match &User-Name == "bob"
+
+xlat_purify false && (&User-Name == "bob")
+match (no && (&User-Name == "bob"))
+
+xlat_purify false || (&User-Name == "bob")
+match (no || (&User-Name == "bob"))
+
+xlat_purify true || (&User-Name == "bob")
+match (yes || (&User-Name == "bob"))
+
+#
+# Both sides static data with a cast: evaluate at parse time.
+#
+xlat_purify <integer>20 < 100
+match yes
+
+#
+# Both sides literal: evaluate at parse time
+#
+xlat_purify ('foo' == 'bar')
+match no
+
+xlat_purify ('foo' < 'bar')
+match no
+
+xlat_purify ('foo' > 'bar')
+match yes
+
+xlat_purify ('foo' == 'foo')
+match yes
+
+#
+# @todo - why isn't the RHS being purified?
+#
+# @todo - why isn't the RHS being purified?
+xlat_purify ("foo" == "%{md4: foo}")
+match ("foo" == "%{md4: foo}")
+
+#
+# @todo - why isn't the RHS being purified?
+#
+# @todo - why isn't the RHS being purified?
+xlat_purify ("foo bar" == "%{md4: foo}")
+match ("foo bar" == "%{md4: foo}")
+
+xlat_purify ("foo" == "bar")
+match no
+
+xlat_purify ("foo" == 'bar')
+match no
+
+#
+# The RHS gets parsed as a VPT_TYPE_DATA, which is
+# a double-quoted string. Except that there's no '%'
+# in it, so it reverts back to a literal.
+#
+xlat_purify (&User-Name == "bob")
+match (&User-Name == "bob")
+
+xlat_purify (&User-Name == "%{md4: blah}")
+match (&User-Name == "0x544924d05ec4481925ba3749a096a0a7")
+
+xlat_purify <ipaddr>127.0.0.1 == 2130706433
+match yes
+
+# /32 suffix should be trimmed for this type
+xlat_purify <ipaddr>127.0.0.1/32 == 127.0.0.1
+match yes
+
+xlat_purify <ipaddr>127.0.0.1/327 == 127.0.0.1
+match ERROR offset 9: Invalid IPv4 mask length "/327". Should be between 0-32
+
+xlat_purify <ipaddr>127.0.0.1/32 == 127.0.0.1
+match yes
+
+xlat_purify (/foo/)
+match ERROR offset 1: Unexpected regular expression
+
+#
+# Tests for (FOO).
+#
+xlat_purify (1)
+match 1
+
+xlat_purify (0)
+match 0
+
+xlat_purify (true)
+match yes
+
+xlat_purify (false)
+match no
+
+xlat_purify ('')
+match ''
+
+xlat_purify ("")
+match ""
+
+#
+# Integers are true, as are non-zero strings
+#
+xlat_purify (4)
+match 4
+
+xlat_purify ('a')
+match 'a'
+
+#
+# @todo - modules && return codes.
+#
+
+#xlat_purify (a)
+#match ERROR offset 1: Expected a module return code
+
+#
+# Module return codes are OK
+#
+#xlat_purify (ok)
+#match ok
+
+#xlat_purify (handled)
+#match handled
+
+#xlat_purify (fail)
+#match fail
+
+xlat_purify ("a")
+match "a"
+
+xlat_purify (`a`)
+match `a`
+
+xlat_purify (&User-Name)
+match &User-Name
+
+#
+# Forbidden data types in cast
+#
+# @todo - this should arguably be allowed?
+xlat_purify (<vsa>"foo" == &User-Name)
+match ERROR offset 2: No operand found. Expected &ref, literal, 'quoted literal', "%{expansion}", or enum value
+
+#
+# If the LHS is a cast to a type, and the RHS is an attribute
+# of the same type, then re-write it so that the attribute
+# is on the LHS of the condition.
+#
+xlat_purify <string>"foo" == &User-Name
+match ("foo" == &User-Name)
+
+# This used to be expr, but expr isn't a builtin, so it failed...
+
+xlat_purify <integer>"%{md4: 1 + 1}" < &NAS-Port
+match ("0x002ade8665c69219ca16bd108d92c8d5" < &NAS-Port)
+
+#
+# The string gets parsed as an IP address.
+#
+# @todo - cast strings to the other data type? Or just rely on the comparisons to do the right thing?
+xlat_purify &Filter-Id == &Framed-IP-Address
+match (&Filter-Id == &Framed-IP-Address)
+#match (ipv4addr)&Filter-Id == &Framed-IP-Address
+
+xlat_purify <ipaddr>127.0.0.1 == &Filter-Id
+match (127.0.0.1 == &Filter-Id)
+
+xlat_purify &Tmp-uint64-0 == &request.Tmp-String-0
+match (&Tmp-uint64-0 == &request.Tmp-String-0)
+
+xlat_purify &Tmp-uint64-0 == &reply.Tmp-String-0
+match (&Tmp-uint64-0 == &reply.Tmp-String-0)
+
+#
+# Casting attributes of different size
+#
+xlat_purify <ipaddr>&Tmp-uint64-0 == &Framed-IP-Address
+match ERROR offset 8: Cannot cast type 'uint64' to 'ipaddr'
+
+#
+# LHS is a prefix, which _might_ be castable to an address
+# if the prefix is /32. We don't know enough at compile time,
+# so this may be a run-time failure.
+#
+xlat_purify <ipaddr>&PMIP6-Home-IPv4-HoA == &Framed-IP-Address
+match ((ipaddr)&PMIP6-Home-IPv4-HoA == &Framed-IP-Address)
+
+# but these are allowed
+xlat_purify <ether>&Tmp-uint64-0 == "%{module: foo}"
+match ((ether)&Tmp-uint64-0 == "%{module: foo}")
+
+xlat_purify <ipaddr>&Filter-Id == &Framed-IP-Address
+match ((ipaddr)&Filter-Id == &Framed-IP-Address)
+
+xlat_purify <ipaddr>&Class == &Framed-IP-Address
+match ((ipaddr)&Class == &Framed-IP-Address)
+
+#
+# zero offset into arrays get parsed and ignored
+#
+xlat_purify &User-Name[0] == "bob"
+match (&User-Name[0] == "bob")
+
+xlat_purify &User-Name[1] == "bob"
+match (&User-Name[1] == "bob")
+
+xlat_purify &User-Name[n] == "bob"
+match (&User-Name[n] == "bob")
+
+#
+# This is allowed for pass2-fixups. Foo-Bar MAY be an attribute.
+# If so allow it so that pass2 can fix it up. Until then,
+# it's an unknown attribute
+#
+#xlat_purify &Foo-Bar
+#match &Foo-Bar
+
+# Same types are optimized
+#
+# @todo- what does this mean? Check for later...
+#
+# FIXME: the tests don't currently run the "pass2" checks.
+# This test should really be:
+#
+# xlat_purify &Acct-Input-Octets > &Session-Timeout
+#
+xlat_purify &Acct-Input-Octets > "%{Session-Timeout}"
+match (&Acct-Input-Octets > "%{Request[0].Session-Timeout}")
+
+xlat_purify &Acct-Input-Octets > &Session-Timeout
+match (&Acct-Input-Octets > &Session-Timeout)
+
+# Separate types aren't optimized
+xlat_purify &Tmp-uint64-0 > &Session-Timeout
+match (&Tmp-uint64-0 > &Session-Timeout)
+
+#
+# Parse OIDs into known attributes, where possible.
+#
+# @todo - whoops, resolve it
+xlat_purify &26.24757.84.9.5.4 == 0x1a99
+match (&26.24757.84.9.5.4 == 0x1a99)
+#match &Vendor-Specific.WiMAX.Packet-Flow-Descriptor-v2.Classifier.Src-Spec.Port == 6809
+
+#
+# This OID is known, but the data is malformed.
+# Allow it so that we can look for malformed attributes
+# in packets.
+#
+xlat_purify &raw.26.24757.84.9.5.7 == 0x1a99
+match (&raw.26.24757.84.9.5.7 == 0x1a99)
+#match &raw.Vendor-Specific.WiMAX.Packet-Flow-Descriptor-v2.Classifier.Src-Spec.Assigned == 0x1a99
+
+# This one is really unknown
+xlat_purify &26.24757.84.9.5.15 == 0x1a99
+match ERROR offset 17: Unknown attributes not allowed here
+#match &Vendor-Specific.WiMAX.Packet-Flow-Descriptor-v2.Classifier.Src-Spec.15 == 0x1a99
+
+#
+# Invalid array references.
+#
+xlat_purify &User-Name[a] == 'bob'
+match ERROR offset 11: Invalid array index
+
+# @todo - really 25?
+xlat_purify &User-Name == &Filter-Id[a]
+match ERROR offset 24: Invalid array index
+
+#
+# Bounds checks...
+#
+xlat_purify &User-Name[1001] == 'bob'
+match ERROR offset 11: Invalid array index '1001' (should be between 0-1000)
+
+xlat_purify &User-Name[-1] == 'bob'
+match ERROR offset 11: Invalid array index '-1' (should be between 0-1000)
+
+#
+# The attribute/xlat_purify parser does not fall back to bare words
+#
+xlat_purify request.Foo == 'request.Foo'
+match ERROR offset 1: Invalid attribute reference, missing '&' prefix: Failed resolving attribute in expansion: request.Foo
+
+xlat_purify ¬-a-list.User-Name == ¬-a-list.User-Name
+match ERROR offset 1: Attribute 'not' not found. Searched in: RADIUS, internal: Unresolved attributes are not allowed here
+
+# . is a valid dictionarxy name attribute, so we can't error out in pass1
+xlat_purify ¬-a-packet.User-Name == ¬-a-packet.User-Name
+match ERROR offset 1: Attribute 'not' not found. Searched in: RADIUS, internal: Unresolved attributes are not allowed here
+
+#
+# The LHS is a string with ASCII 5C 30 30 30 inside of it vs the RHS which should contain ASCII 0.
+#
+xlat_purify ('i have scary embedded things\000 inside me' == "i have scary embedded things\000 inside me")
+match no
+
+#
+# 'Unknown' attributes which are defined in the main dictionary
+# should be resolved to their real names.
+# @todo - resolve!
+xlat_purify &1 == 0x616263
+match (&1 == 0x616263)
+#match (&User-Name == 'abc')
+
+# @todo - resolve!
+#xlat_purify &26.11344.1 == 0x7f000001
+#match &Vendor-Specific.FreeRADIUS.Proxied-To == 127.0.0.1
+
+#
+# Escape the backslashes correctly
+# And print them correctly
+#
+xlat_purify &User-Name == '\\'
+match (&User-Name == '\\')
+
+xlat_purify &User-Name == "@|\\"
+match (&User-Name == "@|\\")
+
+xlat_purify &User-Name != "foo\nbar"
+match (&User-Name != "foo\nbar")
+
+#
+# We infer that the LHS is a prefix and the RHS is
+# and ipaddr without requiring an explicit cast.
+#
+# @todo - fix up cast!
+xlat_purify 192.168.0.0/16 > 192.168.1.2
+match (192.168.0.0/16 > 192.168.1.2)
+#match true
+
+# @todo - fix up cast!
+#xlat_purify <ipv4prefix>192.168.0.0/16 > 192.168.1.2
+#match true
+
+xlat_purify <ipv4prefix>&NAS-IP-Address == 192.168.0.0/24
+match ((ipv4prefix)&NAS-IP-Address == 192.168.0.0/24)
+
+#
+# Don't rewrite so that the attribute is on the LHS
+# and, move the cast to the attribute, as the RHS
+# is parsed as ipv4prefix
+#
+xlat_purify <ipv4prefix>192.168.0.0/24 > &NAS-IP-Address
+match (192.168.0.0/24 > &NAS-IP-Address)
+
+#
+# This is allowed and means "the list is not empty"
+#
+# @todo - we need a "parse as condition" flag!
+xlat_purify (&reply)
+match &reply
+
+#
+# Expansions of environment variables
+# and empty strings
+#
+xlat_purify ("$ENV{SOMETHING_OR_OTHER}" == '')
+match no
+
+#
+# Attributes with a protocol namespace
+#
+# @todo - if the explicit namespace is the same as the implicit one, we can omit
+# the explicit one? But this is largely due to the printing fixes, where we just
+# print the tmpl name as-is.
+xlat_purify &radius.User-Name == 'bob'
+match (&radius.User-Name == 'bob')
+#match &User-Name == 'bob'
+
+xlat_purify !(!(0))
+match no
+
+xlat_purify (true) && (false)
+match no
+
+#
+# More short-circuit evaluations
+#
+xlat_purify (&User-Name == "bob") && (false)
+match ((&User-Name == "bob") && no)
+
+xlat_purify (&User-Name == "bob") || (true)
+match ((&User-Name == "bob") || yes)
+
+#
+# A && (B || C) is not the same as (A && B) || C, for 0/1/1
+#
+# 0 && (1 || 1) = 0 && 1 == 0
+# (0 && 1) || 1 = 0 || 1 == 1
+#
+xlat_purify (&User-Name == "bob") && ((&User-Password == "bob") || &EAP-Message)
+match ((&User-Name == "bob") && ((&User-Password == "bob") || &EAP-Message))
+
+count
+match 261