Cameradar 3.0.0: Uses ullaakut/nmap, runs faster, removed legacy code (#188)

Unit tests functional and coverage back to 100%

Add more routes to dictionary, add more credentials, add default port 5554, rename cameradar logs ENV variable, improve unit test readability, remove tmp file
This commit is contained in:
Brendan LE GLAUNEC
2019-01-22 21:16:16 +01:00
committed by GitHub
parent 878ca9f032
commit 5849898283
973 changed files with 401790 additions and 1002 deletions
+9
View File
@@ -0,0 +1,9 @@
*.exe
*.dll
*.so
*.dylib
*.test
*.out
*.txt
+15
View File
@@ -0,0 +1,15 @@
language: go
go:
- 1.9.x
- 1.10.x
- tip
before_install:
- go get -t -v ./...
script:
- go test -race -coverprofile=coverage.txt -covermode=atomic
after_success:
- bash <(curl -s https://codecov.io/bash)
+55
View File
@@ -0,0 +1,55 @@
[![Build](https://img.shields.io/travis/leodido/go-urn/master.svg?style=for-the-badge)](https://travis-ci.org/leodido/go-urn) [![Coverage](https://img.shields.io/codecov/c/github/leodido/go-urn.svg?style=for-the-badge)](https://codecov.io/gh/leodido/go-urn) [![Documentation](https://img.shields.io/badge/godoc-reference-blue.svg?style=for-the-badge)](https://godoc.org/github.com/leodido/go-urn)
**A parser for URNs**.
> As seen on [RFC 2141](https://tools.ietf.org/html/rfc2141#ref-1).
[API documentation](https://godoc.org/github.com/leodido/go-urn).
## Installation
```
go get github.com/leodido/go-urn
```
## Performances
This implementation results to be really fast.
Usually below ½ microsecond on my machine<sup>[1](#mymachine)</sup>.
Notice it also performs, while parsing:
1. fine-grained and informative erroring
2. specific-string normalization
```
ok/00/urn:a:b______________________________________/-4 20000000 265 ns/op 182 B/op 6 allocs/op
ok/01/URN:foo:a123,456_____________________________/-4 30000000 296 ns/op 200 B/op 6 allocs/op
ok/02/urn:foo:a123%2c456___________________________/-4 20000000 331 ns/op 208 B/op 6 allocs/op
ok/03/urn:ietf:params:scim:schemas:core:2.0:User___/-4 20000000 430 ns/op 280 B/op 6 allocs/op
ok/04/urn:ietf:params:scim:schemas:extension:enterp/-4 20000000 411 ns/op 312 B/op 6 allocs/op
ok/05/urn:ietf:params:scim:schemas:extension:enterp/-4 20000000 472 ns/op 344 B/op 6 allocs/op
ok/06/urn:burnout:nss______________________________/-4 30000000 257 ns/op 192 B/op 6 allocs/op
ok/07/urn:abcdefghilmnopqrstuvzabcdefghilm:x_______/-4 20000000 375 ns/op 213 B/op 6 allocs/op
ok/08/urn:urnurnurn:urn____________________________/-4 30000000 265 ns/op 197 B/op 6 allocs/op
ok/09/urn:ciao:@!=%2c(xyz)+a,b.*@g=$_'_____________/-4 20000000 307 ns/op 248 B/op 6 allocs/op
ok/10/URN:x:abc%1dz%2f%3az_________________________/-4 30000000 259 ns/op 212 B/op 6 allocs/op
no/11/URN:-xxx:x___________________________________/-4 20000000 445 ns/op 320 B/op 6 allocs/op
no/12/urn::colon:nss_______________________________/-4 20000000 461 ns/op 320 B/op 6 allocs/op
no/13/urn:abcdefghilmnopqrstuvzabcdefghilmn:specifi/-4 10000000 660 ns/op 320 B/op 6 allocs/op
no/14/URN:a!?:x____________________________________/-4 20000000 507 ns/op 320 B/op 6 allocs/op
no/15/urn:urn:NSS__________________________________/-4 20000000 429 ns/op 288 B/op 6 allocs/op
no/16/urn:white_space:NSS__________________________/-4 20000000 482 ns/op 320 B/op 6 allocs/op
no/17/urn:concat:no_spaces_________________________/-4 20000000 539 ns/op 328 B/op 7 allocs/op
no/18/urn:a:/______________________________________/-4 20000000 470 ns/op 320 B/op 7 allocs/op
no/19/urn:UrN:NSS__________________________________/-4 20000000 399 ns/op 288 B/op 6 allocs/op
```
---
* <a name="mymachine">[1]</a>: Intel Core i7-7600U CPU @ 2.80GHz
---
[![Analytics](https://ga-beacon.appspot.com/UA-49657176-1/go-urn?flat)](https://github.com/igrigorik/ga-beacon)
+1670
View File
File diff suppressed because it is too large Load Diff
+159
View File
@@ -0,0 +1,159 @@
package urn
import (
"fmt"
)
var (
errPrefix = "expecting the prefix to be the \"urn\" string (whatever case) [col %d]"
errIdentifier = "expecting the identifier to be string (1..31 alnum chars, also containing dashes but not at its start) [col %d]"
errSpecificString = "expecting the specific string to be a string containing alnum, hex, or others ([()+,-.:=@;$_!*']) chars [col %d]"
errNoUrnWithinID = "expecting the identifier to not contain the \"urn\" reserved string [col %d]"
errHex = "expecting the specific string hex chars to be well-formed (%%alnum{2}) [col %d]"
errParse = "parsing error [col %d]"
)
%%{
machine urn;
# unsigned alphabet
alphtype uint8;
action mark {
m.pb = m.p
}
action tolower {
m.tolower = append(m.tolower, m.p - m.pb)
}
action set_pre {
output.prefix = string(m.text())
}
action set_nid {
output.ID = string(m.text())
}
action set_nss {
raw := m.text()
output.SS = string(raw)
// Iterate upper letters lowering them
for _, i := range m.tolower {
raw[i] = raw[i] + 32
}
output.norm = string(raw)
}
action err_pre {
m.err = fmt.Errorf(errPrefix, m.p)
fhold;
fgoto fail;
}
action err_nid {
m.err = fmt.Errorf(errIdentifier, m.p)
fhold;
fgoto fail;
}
action err_nss {
m.err = fmt.Errorf(errSpecificString, m.p)
fhold;
fgoto fail;
}
action err_urn {
m.err = fmt.Errorf(errNoUrnWithinID, m.p)
fhold;
fgoto fail;
}
action err_hex {
m.err = fmt.Errorf(errHex, m.p)
fhold;
fgoto fail;
}
action err_parse {
m.err = fmt.Errorf(errParse, m.p)
fhold;
fgoto fail;
}
pre = ([uU][rR][nN] @err(err_pre)) >mark %set_pre;
nid = (alnum >mark (alnum | '-'){0,31}) %set_nid;
hex = '%' (digit | lower | upper >tolower){2} $err(err_hex);
sss = (alnum | [()+,\-.:=@;$_!*']);
nss = (sss | hex)+ $err(err_nss);
fail := (any - [\n\r])* @err{ fgoto main; };
main := (pre ':' (nid - pre %err(err_urn)) $err(err_nid) ':' nss >mark %set_nss) $err(err_parse);
}%%
%% write data noerror noprefix;
// Machine is the interface representing the FSM
type Machine interface {
Error() error
Parse(input []byte) (*URN, error)
}
type machine struct {
data []byte
cs int
p, pe, eof, pb int
err error
tolower []int
}
// NewMachine creates a new FSM able to parse RFC 2141 strings.
func NewMachine() Machine {
m := &machine{}
%% access m.;
%% variable p m.p;
%% variable pe m.pe;
%% variable eof m.eof;
%% variable data m.data;
return m
}
// Err returns the error that occurred on the last call to Parse.
//
// If the result is nil, then the line was parsed successfully.
func (m *machine) Error() error {
return m.err
}
func (m *machine) text() []byte {
return m.data[m.pb:m.p]
}
// Parse parses the input byte array as a RFC 2141 string.
func (m *machine) Parse(input []byte) (*URN, error) {
m.data = input
m.p = 0
m.pb = 0
m.pe = len(input)
m.eof = len(input)
m.err = nil
m.tolower = []int{}
output := &URN{}
%% write init;
%% write exec;
if m.cs < first_final || m.cs == en_fail {
return nil, m.err
}
return output, nil
}
+27
View File
@@ -0,0 +1,27 @@
package urn
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestParse(t *testing.T) {
for ii, tt := range tests {
urn, err := NewMachine().Parse([]byte(tt.in))
ok := err == nil
if ok {
assert.True(t, tt.ok, herror(ii, tt))
assert.Equal(t, tt.obj.prefix, urn.prefix, herror(ii, tt))
assert.Equal(t, tt.obj.ID, urn.ID, herror(ii, tt))
assert.Equal(t, tt.obj.SS, urn.SS, herror(ii, tt))
assert.Equal(t, tt.str, urn.String(), herror(ii, tt))
assert.Equal(t, tt.norm, urn.Normalize().String(), herror(ii, tt))
} else {
assert.False(t, tt.ok, herror(ii, tt))
assert.Empty(t, urn, herror(ii, tt))
assert.Equal(t, tt.estr, err.Error())
}
}
}
+17
View File
@@ -0,0 +1,17 @@
SHELL := /bin/bash
machine.go: machine.go.rl
ragel -Z -G2 -e -o $@ $<
@gofmt -w -s $@
@sed -i '/^\/\/line/d' $@
.PHONY: build
build: machine.go
.PHONY: bench
bench: *_test.go machine.go
go test -bench=. -benchmem -benchtime=5s ./...
.PHONY: tests
tests: *_test.go machine.go
go test -race -timeout 10s -coverprofile=coverage.out -covermode=atomic -v ./...
+49
View File
@@ -0,0 +1,49 @@
package urn
import (
"fmt"
"testing"
)
var benchs = []testCase{
tests[14],
tests[2],
tests[6],
tests[10],
tests[11],
tests[13],
tests[20],
tests[23],
tests[33],
tests[45],
tests[47],
tests[48],
tests[50],
tests[52],
tests[53],
tests[57],
tests[62],
tests[63],
tests[67],
tests[60],
}
// This is here to avoid compiler optimizations that
// could remove the actual call we are benchmarking
// during benchmarks
var benchParseResult *URN
func BenchmarkParse(b *testing.B) {
for ii, tt := range benchs {
tt := tt
outcome := (map[bool]string{true: "ok", false: "no"})[tt.ok]
b.Run(
fmt.Sprintf("%s/%02d/%s/", outcome, ii, rxpad(string(tt.in), 45)),
func(b *testing.B) {
for i := 0; i < b.N; i++ {
benchParseResult, _ = Parse(tt.in)
}
},
)
}
}
+899
View File
@@ -0,0 +1,899 @@
package urn
import (
"strconv"
"strings"
)
func ierror(index int) string {
return "Test case num. " + strconv.Itoa(index+1)
}
func herror(index int, test testCase) string {
return ierror(index) + ", input \"" + string(test.in) + "\""
}
func rxpad(str string, lim int) string {
str = str + strings.Repeat(" ", lim)
return str[:lim]
}
type testCase struct {
in []byte // the input
ok bool // whether it is valid or not
obj *URN // a pointer to the resulting urn.URN instance
str string // string representation
norm string // norm string representation
estr string // error string
}
var tests = []testCase{
// ok
{
[]byte("urn:simple:simple"),
true,
&URN{
prefix: "urn",
ID: "simple",
SS: "simple",
},
"urn:simple:simple",
"urn:simple:simple",
"",
},
{
[]byte("urn:ciao:%5D"),
true,
&URN{
prefix: "urn",
ID: "ciao",
SS: "%5D",
},
"urn:ciao:%5D",
"urn:ciao:%5d",
"",
},
// ok - RFC examples
{
[]byte("URN:foo:a123,456"),
true,
&URN{
prefix: "URN",
ID: "foo",
SS: "a123,456",
},
"URN:foo:a123,456",
"urn:foo:a123,456",
"",
},
{
[]byte("urn:foo:a123,456"),
true,
&URN{
prefix: "urn",
ID: "foo",
SS: "a123,456",
},
"urn:foo:a123,456",
"urn:foo:a123,456",
"",
},
{
[]byte("urn:FOO:a123,456"),
true,
&URN{
prefix: "urn",
ID: "FOO",
SS: "a123,456",
},
"urn:FOO:a123,456",
"urn:foo:a123,456",
"",
},
{
[]byte("urn:foo:A123,456"),
true,
&URN{
prefix: "urn",
ID: "foo",
SS: "A123,456",
},
"urn:foo:A123,456",
"urn:foo:A123,456",
"",
},
{
[]byte("urn:foo:a123%2C456"),
true,
&URN{
prefix: "urn",
ID: "foo",
SS: "a123%2C456",
},
"urn:foo:a123%2C456",
"urn:foo:a123%2c456",
"",
},
{
[]byte("URN:FOO:a123%2c456"),
true,
&URN{
prefix: "URN",
ID: "FOO",
SS: "a123%2c456",
},
"URN:FOO:a123%2c456",
"urn:foo:a123%2c456",
"",
},
{
[]byte("URN:FOO:ABC%FFabc123%2c456"),
true,
&URN{
prefix: "URN",
ID: "FOO",
SS: "ABC%FFabc123%2c456",
},
"URN:FOO:ABC%FFabc123%2c456",
"urn:foo:ABC%ffabc123%2c456",
"",
},
{
[]byte("URN:FOO:ABC%FFabc123%2C456%9A"),
true,
&URN{
prefix: "URN",
ID: "FOO",
SS: "ABC%FFabc123%2C456%9A",
},
"URN:FOO:ABC%FFabc123%2C456%9A",
"urn:foo:ABC%ffabc123%2c456%9a",
"",
},
// ok - SCIM v2
{
[]byte("urn:ietf:params:scim:schemas:core:2.0:User"),
true,
&URN{
prefix: "urn",
ID: "ietf",
SS: "params:scim:schemas:core:2.0:User",
},
"urn:ietf:params:scim:schemas:core:2.0:User",
"urn:ietf:params:scim:schemas:core:2.0:User",
"",
},
{
[]byte("urn:ietf:params:scim:schemas:extension:enterprise:2.0:User"),
true,
&URN{
prefix: "urn",
ID: "ietf",
SS: "params:scim:schemas:extension:enterprise:2.0:User",
},
"urn:ietf:params:scim:schemas:extension:enterprise:2.0:User",
"urn:ietf:params:scim:schemas:extension:enterprise:2.0:User",
"",
},
{
[]byte("urn:ietf:params:scim:schemas:extension:enterprise:2.0:User:userName"),
true,
&URN{
prefix: "urn",
ID: "ietf",
SS: "params:scim:schemas:extension:enterprise:2.0:User:userName",
},
"urn:ietf:params:scim:schemas:extension:enterprise:2.0:User:userName",
"urn:ietf:params:scim:schemas:extension:enterprise:2.0:User:userName",
"",
},
{
[]byte("urn:ietf:params:scim:schemas:extension:enterprise:2.0:User:meta.lastModified"),
true,
&URN{
prefix: "urn",
ID: "ietf",
SS: "params:scim:schemas:extension:enterprise:2.0:User:meta.lastModified",
},
"urn:ietf:params:scim:schemas:extension:enterprise:2.0:User:meta.lastModified",
"urn:ietf:params:scim:schemas:extension:enterprise:2.0:User:meta.lastModified",
"",
},
// ok - minimum urn
{
[]byte("urn:a:b"),
true,
&URN{
prefix: "urn",
ID: "a",
SS: "b",
},
"urn:a:b",
"urn:a:b",
"",
},
{
[]byte("urn:a::"),
true,
&URN{
prefix: "urn",
ID: "a",
SS: ":",
},
"urn:a::",
"urn:a::",
"",
},
{
[]byte("urn:a:-"),
true,
&URN{
prefix: "urn",
ID: "a",
SS: "-",
},
"urn:a:-",
"urn:a:-",
"",
},
// ok - URN prefix is case-insensitive
{
[]byte("URN:simple:simple"),
true,
&URN{
prefix: "URN",
ID: "simple",
SS: "simple",
},
"URN:simple:simple",
"urn:simple:simple",
"",
},
{
[]byte("Urn:simple:simple"),
true,
&URN{
prefix: "Urn",
ID: "simple",
SS: "simple",
},
"Urn:simple:simple",
"urn:simple:simple",
"",
},
// ok - ID can contain the "urn" string but it can not be exactly equal to it
{
[]byte("urn:urna:simple"),
true,
&URN{
prefix: "urn",
ID: "urna",
SS: "simple",
},
"urn:urna:simple",
"urn:urna:simple",
"",
},
{
[]byte("urn:burnout:nss"),
true,
&URN{
prefix: "urn",
ID: "burnout",
SS: "nss",
},
"urn:burnout:nss",
"urn:burnout:nss",
"",
},
{
[]byte("urn:burn:nss"),
true,
&URN{
prefix: "urn",
ID: "burn",
SS: "nss",
},
"urn:burn:nss",
"urn:burn:nss",
"",
},
{
[]byte("urn:urnurnurn:x"),
true,
&URN{
prefix: "urn",
ID: "urnurnurn",
SS: "x",
},
"urn:urnurnurn:x",
"urn:urnurnurn:x",
"",
},
// ok - ID can contains maximum 32 characters
{
[]byte("urn:abcdefghilmnopqrstuvzabcdefghilm:x"),
true,
&URN{
prefix: "urn",
ID: "abcdefghilmnopqrstuvzabcdefghilm",
SS: "x",
},
"urn:abcdefghilmnopqrstuvzabcdefghilm:x",
"urn:abcdefghilmnopqrstuvzabcdefghilm:x",
"",
},
// ok - ID can be alpha numeric
{
[]byte("URN:123:x"),
true,
&URN{
prefix: "URN",
ID: "123",
SS: "x",
},
"URN:123:x",
"urn:123:x",
"",
},
{
[]byte("URN:1ab:x"),
true,
&URN{
prefix: "URN",
ID: "1ab",
SS: "x",
},
"URN:1ab:x",
"urn:1ab:x",
"",
},
{
[]byte("URN:a1b:x"),
true,
&URN{
prefix: "URN",
ID: "a1b",
SS: "x",
},
"URN:a1b:x",
"urn:a1b:x",
"",
},
{
[]byte("URN:a12:x"),
true,
&URN{
prefix: "URN",
ID: "a12",
SS: "x",
},
"URN:a12:x",
"urn:a12:x",
"",
},
{
[]byte("URN:cd2:x"),
true,
&URN{
prefix: "URN",
ID: "cd2",
SS: "x",
},
"URN:cd2:x",
"urn:cd2:x",
"",
},
// ok - ID can contain an hyphen (not in its first position, see below)
{
[]byte("URN:abcd-:x"),
true,
&URN{
prefix: "URN",
ID: "abcd-",
SS: "x",
},
"URN:abcd-:x",
"urn:abcd-:x",
"",
},
{
[]byte("URN:abcd-abcd:x"),
true,
&URN{
prefix: "URN",
ID: "abcd-abcd",
SS: "x",
},
"URN:abcd-abcd:x",
"urn:abcd-abcd:x",
"",
},
{
[]byte("URN:a123-456z:x"),
true,
&URN{
prefix: "URN",
ID: "a123-456z",
SS: "x",
},
"URN:a123-456z:x",
"urn:a123-456z:x",
"",
},
// ok - SS can contain the "urn" string, also be exactly equal to it
{
[]byte("urn:urnx:urn"),
true,
&URN{
prefix: "urn",
ID: "urnx",
SS: "urn",
},
"urn:urnx:urn",
"urn:urnx:urn",
"",
},
{
[]byte("urn:urnurnurn:urn"),
true,
&URN{
prefix: "urn",
ID: "urnurnurn",
SS: "urn",
},
"urn:urnurnurn:urn",
"urn:urnurnurn:urn",
"",
},
{
[]byte("urn:hey:urnurnurn"),
true,
&URN{
prefix: "urn",
ID: "hey",
SS: "urnurnurn",
},
"urn:hey:urnurnurn",
"urn:hey:urnurnurn",
"",
},
// ok - SS can contains and discerns multiple colons, also at the end
{
[]byte("urn:ciao:a:b:c"),
true,
&URN{
prefix: "urn",
ID: "ciao",
SS: "a:b:c",
},
"urn:ciao:a:b:c",
"urn:ciao:a:b:c",
"",
},
{
[]byte("urn:aaa:x:y:"),
true,
&URN{
prefix: "urn",
ID: "aaa",
SS: "x:y:",
},
"urn:aaa:x:y:",
"urn:aaa:x:y:",
"",
},
{
[]byte("urn:aaa:x:y:"),
true,
&URN{
prefix: "urn",
ID: "aaa",
SS: "x:y:",
},
"urn:aaa:x:y:",
"urn:aaa:x:y:",
"",
},
// ok - SS can contain (and also start with) some non-alphabetical (ie., OTHER) characters
{
[]byte("urn:ciao:-"),
true,
&URN{
prefix: "urn",
ID: "ciao",
SS: "-",
},
"urn:ciao:-",
"urn:ciao:-",
"",
},
{
[]byte("urn:ciao::"),
true,
&URN{
prefix: "urn",
ID: "ciao",
SS: ":",
},
"urn:ciao::",
"urn:ciao::",
"",
},
{
[]byte("urn:colon:::::nss"),
true,
&URN{
prefix: "urn",
ID: "colon",
SS: "::::nss",
},
"urn:colon:::::nss",
"urn:colon:::::nss",
"",
},
{
[]byte("urn:ciao:!"),
true,
&URN{
prefix: "urn",
ID: "ciao",
SS: "!",
},
"urn:ciao:!",
"urn:ciao:!",
"",
},
{
[]byte("urn:ciao:!!*"),
true,
&URN{
prefix: "urn",
ID: "ciao",
SS: "!!*",
},
"urn:ciao:!!*",
"urn:ciao:!!*",
"",
},
{
[]byte("urn:ciao:-!:-,:x"),
true,
&URN{
prefix: "urn",
ID: "ciao",
SS: "-!:-,:x",
},
"urn:ciao:-!:-,:x",
"urn:ciao:-!:-,:x",
"",
},
{
[]byte("urn:ciao:=@"),
true,
&URN{
prefix: "urn",
ID: "ciao",
SS: "=@",
},
"urn:ciao:=@",
"urn:ciao:=@",
"",
},
{
[]byte("urn:ciao:@!=%2C(xyz)+a,b.*@g=$_'"),
true,
&URN{
prefix: "urn",
ID: "ciao",
SS: "@!=%2C(xyz)+a,b.*@g=$_'",
},
"urn:ciao:@!=%2C(xyz)+a,b.*@g=$_'",
"urn:ciao:@!=%2c(xyz)+a,b.*@g=$_'",
"",
},
// ok - SS can contain (and also start with) hexadecimal representation of octets
{
[]byte("URN:hexes:%25"),
true,
&URN{
prefix: "URN",
ID: "hexes",
SS: "%25",
},
"URN:hexes:%25",
"urn:hexes:%25",
"",
}, // Literal use of the "%" character in a namespace must be encoded using "%25"
{
[]byte("URN:x:abc%1Dz%2F%3az"),
true,
&URN{
prefix: "URN",
ID: "x",
SS: "abc%1Dz%2F%3az",
},
"URN:x:abc%1Dz%2F%3az",
"urn:x:abc%1dz%2f%3az",
"",
}, // Literal use of the "%" character in a namespace must be encoded using "%25"
// no - ID can not start with an hyphen
{
[]byte("URN:-xxx:x"),
false,
nil,
"",
"",
`expecting the identifier to be string (1..31 alnum chars, also containing dashes but not at its start) [col 4]`,
},
{
[]byte("URN:---xxx:x"),
false,
nil,
"",
"",
`expecting the identifier to be string (1..31 alnum chars, also containing dashes but not at its start) [col 4]`,
},
// no - ID can not start with a colon
{
[]byte("urn::colon:nss"),
false,
nil,
"",
"",
`expecting the identifier to be string (1..31 alnum chars, also containing dashes but not at its start) [col 4]`,
},
{
[]byte("urn::::nss"),
false,
nil,
"",
"",
`expecting the identifier to be string (1..31 alnum chars, also containing dashes but not at its start) [col 4]`,
},
// no - ID can not contains more than 32 characters
{
[]byte("urn:abcdefghilmnopqrstuvzabcdefghilmn:specificstring"),
false,
nil,
"",
"",
`expecting the identifier to be string (1..31 alnum chars, also containing dashes but not at its start) [col 36]`,
},
// no - ID can not contain special characters
{
[]byte("URN:a!?:x"),
false,
nil,
"",
"",
`expecting the identifier to be string (1..31 alnum chars, also containing dashes but not at its start) [col 5]`,
},
{
[]byte("URN:@,:x"),
false,
nil,
"",
"",
`expecting the identifier to be string (1..31 alnum chars, also containing dashes but not at its start) [col 4]`,
},
{
[]byte("URN:#,:x"),
false,
nil,
"",
"",
`expecting the identifier to be string (1..31 alnum chars, also containing dashes but not at its start) [col 4]`,
},
{
[]byte("URN:bc'.@:x"),
false,
nil,
"",
"",
`expecting the identifier to be string (1..31 alnum chars, also containing dashes but not at its start) [col 6]`,
},
// no - ID can not be equal to "urn"
{
[]byte("urn:urn:NSS"),
false,
nil,
"",
"",
`expecting the identifier to not contain the "urn" reserved string [col 7]`,
},
{
[]byte("urn:URN:NSS"),
false,
nil,
"",
"",
`expecting the identifier to not contain the "urn" reserved string [col 7]`,
},
{
[]byte("URN:URN:NSS"),
false,
nil,
"",
"",
`expecting the identifier to not contain the "urn" reserved string [col 7]`,
},
{
[]byte("urn:UrN:NSS"),
false,
nil,
"",
"",
`expecting the identifier to not contain the "urn" reserved string [col 7]`,
},
{
[]byte("urn:Urn:NSS"),
false,
nil,
"",
"",
`expecting the identifier to not contain the "urn" reserved string [col 7]`,
},
// no - ID can not contain spaces
{
[]byte("urn:white space:NSS"),
false,
nil,
"",
"",
`expecting the identifier to be string (1..31 alnum chars, also containing dashes but not at its start) [col 9]`,
},
// no - SS can not contain spaces
{
[]byte("urn:concat:no spaces"),
false,
nil,
"",
"",
`expecting the specific string to be a string containing alnum, hex, or others ([()+,-.:=@;$_!*']) chars [col 13]`,
},
// no - SS can not contain reserved characters (can accept them only if %-escaped)
{
[]byte("urn:a:%"), // the presence of an "%" character in an URN MUST be followed by two characters from the <hex> character set
false,
nil,
"",
"",
`expecting the specific string hex chars to be well-formed (%alnum{2}) [col 7]`,
},
{
[]byte("urn:a:?"),
false,
nil,
"",
"",
`expecting the specific string to be a string containing alnum, hex, or others ([()+,-.:=@;$_!*']) chars [col 6]`,
},
{
[]byte("urn:a:#"),
false,
nil,
"",
"",
`expecting the specific string to be a string containing alnum, hex, or others ([()+,-.:=@;$_!*']) chars [col 6]`,
},
{
[]byte("urn:a:/"),
false,
nil,
"",
"",
`expecting the specific string to be a string containing alnum, hex, or others ([()+,-.:=@;$_!*']) chars [col 6]`,
},
// no - Incomplete URNs
{
[]byte("urn:"),
false,
nil,
"",
"",
`expecting the identifier to be string (1..31 alnum chars, also containing dashes but not at its start) [col 4]`,
},
{
[]byte("urn::"),
false,
nil,
"",
"",
`expecting the identifier to be string (1..31 alnum chars, also containing dashes but not at its start) [col 4]`,
},
{
[]byte("urn:a:"),
false,
nil,
"",
"",
`expecting the specific string to be a string containing alnum, hex, or others ([()+,-.:=@;$_!*']) chars [col 6]`,
},
// {
// "urn:a",
// false,
// nil,
// "",
// "",
// "",
// },
}
var equivalenceTests = []struct {
eq bool
lx []byte
rx []byte
}{
{
true,
[]byte("urn:foo:a123%2C456"),
[]byte("URN:FOO:a123%2c456"),
},
{
true,
[]byte("urn:foo:AbC123%2C456"),
[]byte("URN:FOO:AbC123%2c456"),
},
{
true,
[]byte("urn:foo:AbC123%2C456%1f"),
[]byte("URN:FOO:AbC123%2c456%1f"),
},
{
true,
[]byte("URN:foo:a123,456"),
[]byte("urn:foo:a123,456"),
},
{
true,
[]byte("URN:foo:a123,456"),
[]byte("urn:FOO:a123,456"),
},
{
true,
[]byte("urn:foo:a123,456"),
[]byte("urn:FOO:a123,456"),
},
{
true,
[]byte("urn:ciao:%2E"),
[]byte("urn:ciao:%2e"),
},
{
false,
[]byte("urn:foo:A123,456"),
[]byte("URN:foo:a123,456"),
},
{
false,
[]byte("urn:foo:A123,456"),
[]byte("urn:foo:a123,456"),
},
{
false,
[]byte("urn:foo:A123,456"),
[]byte("urn:FOO:a123,456"),
},
}
+63
View File
@@ -0,0 +1,63 @@
package urn
import (
"strings"
)
// URN represents an Uniform Resource Name.
//
// The general form represented is:
//
// urn:<id>:<ss>
//
// Details at https://tools.ietf.org/html/rfc2141.
type URN struct {
prefix string // Static prefix. Equal to "urn" when empty.
ID string // Namespace identifier
SS string // Namespace specific string
norm string // Normalized namespace specific string
}
// Normalize turns the receiving URN into its norm version.
//
// Which means: lowercase prefix, lowercase namespace identifier, and immutate namespace specific string chars (except <hex> tokens which are lowercased).
func (u *URN) Normalize() *URN {
return &URN{
prefix: "urn",
ID: strings.ToLower(u.ID),
SS: u.norm,
}
}
// Equal checks the lexical equivalence of the current URN with another one.
func (u *URN) Equal(x *URN) bool {
return *u.Normalize() == *x.Normalize()
}
// String reassembles the URN into a valid URN string.
//
// This requires both ID and SS fields to be non-empty.
// Otherwise it returns an empty string.
//
// Default URN prefix is "urn".
func (u *URN) String() string {
var res string
if u.ID != "" && u.SS != "" {
if u.prefix == "" {
res += "urn"
}
res += u.prefix + ":" + u.ID + ":" + u.SS
}
return res
}
// Parse is responsible to create an URN instance from a byte array matching the correct URN syntax.
func Parse(u []byte) (*URN, bool) {
urn, err := NewMachine().Parse(u)
if err != nil {
return nil, false
}
return urn, true
}
+45
View File
@@ -0,0 +1,45 @@
package urn
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestDefaultPrefixWhenString(t *testing.T) {
u := &URN{
ID: "pippo",
SS: "pluto",
}
assert.Equal(t, "urn:pippo:pluto", u.String())
}
func TestParseSignature(t *testing.T) {
urn, ok := Parse([]byte(``))
assert.Nil(t, urn)
assert.False(t, ok)
}
func TestLexicalEquivalence(t *testing.T) {
for ii, tt := range equivalenceTests {
urnlx, oklx := Parse(tt.lx)
urnrx, okrx := Parse(tt.rx)
if oklx && okrx {
assert.True(t, urnlx.Equal(urnlx))
assert.True(t, urnrx.Equal(urnrx))
if tt.eq {
assert.True(t, urnlx.Equal(urnrx), ierror(ii))
assert.True(t, urnrx.Equal(urnlx), ierror(ii))
} else {
assert.False(t, urnlx.Equal(urnrx), ierror(ii))
assert.False(t, urnrx.Equal(urnlx), ierror(ii))
}
} else {
t.Log("Something wrong in the testing table ...")
}
}
}