initial commits

master
Charles Iliya Krempeaux 2023-09-22 18:25:02 +09:00
commit fa167b5757
7 changed files with 760 additions and 0 deletions

19
LICENSE 100644
View File

@ -0,0 +1,19 @@
Copyright (c) 2023 Charles Iliya Krempeaux :: http://changelog.ca/
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

37
README.md 100644
View File

@ -0,0 +1,37 @@
# go-httpbearer
Package **httpbearer** provides tool to deal with HTTP bearer tokens, for the Go programming language.
## Online Documention
Online documentation, which includes examples, can be found at: http://godoc.org/sourcecode.social/reiver/go-httpbearer
[![GoDoc](https://godoc.org/sourcecode.social/reiver/go-httpbearer?status.svg)](https://godoc.org/sourcecode.social/reiver/go-httpbearer)
## Example
Here is an example:
```go
import "sourcecode.social/reiver/go-httpbearer"
value := req.Header.Get("Authorization")
// ...
bearerToken, successful := httpbearer.Parse(value)
if !successful {
//@TODO: the value of the Authorization header was not a bearer token.
return ErrNotBearerToken
}
```
If the Authorization header was:
```
Authorization: Bearer WW91IGFyZSBub3QgYSBkcm9wIGluIHRoZSBvY2Vhbi4gWW91IGFyZSB0aGUgZW50aXJlIG9jZWFuLCBpbiBhIGRyb3Au
```
Then the value of `bearerToken` would be:
```go
"WW91IGFyZSBub3QgYSBkcm9wIGluIHRoZSBvY2Vhbi4gWW91IGFyZSB0aGUgZW50aXJlIG9jZWFuLCBpbiBhIGRyb3Au"
```

3
go.mod 100644
View File

@ -0,0 +1,3 @@
module sourcecode.social/reiver/go-httpbearer
go 1.20

180
parse.go 100644
View File

@ -0,0 +1,180 @@
package httpbearer
import (
"strings"
)
// Parse parses the value of an HTTP "Authorization" header and returns a bearer token, if there was one.
// If there was no bearer token in the HTTP "Authorization" header, then the returned value of successful will be false.
//
// The full HTTP request header will look something like this:
//
// "Authorization: Bearer WW91IGFyZSBub3QgYSBkcm9wIGluIHRoZSBvY2Vhbi4gWW91IGFyZSB0aGUgZW50aXJlIG9jZWFuLCBpbiBhIGRyb3Au\r\n"
//
// This function expected to receive just the value. So, with our previous example, that would be:
//
// "Bearer WW91IGFyZSBub3QgYSBkcm9wIGluIHRoZSBvY2Vhbi4gWW91IGFyZSB0aGUgZW50aXJlIG9jZWFuLCBpbiBhIGRyb3Au"
//
// Note that HTTP headers allow for extra "\t" and " " to be put in any place where a "\t" or " " already is.
//
// So, for example, this:
//
// "Authorization: Bearer abcde12345\r\n"
//
// And these:
//
// "Authorization: Bearer abcde12345\r\n"
//
// "Authorization: Bearer abcde12345\r\n"
//
// "Authorization: Bearer abcde12345\r\n"
//
// "Authorization: Bearer abcde12345\r\n"
//
// "Authorization: Bearer abcde12345\r\n"
//
// "Authorization: Bearer\tabcde12345\r\n"
//
// "Authorization: Bearer\t\tabcde12345\r\n"
//
// "Authorization: Bearer\t\t\tabcde12345\r\n"
//
// "Authorization: Bearer\t\t\t\tabcde12345\r\n"
//
// "Authorization: Bearer\t\t\t\t\tabcde12345\r\n"
//
// "Authorization: Bearer\t\t\t\t\t\tabcde12345\r\n"
//
// "Authorization: Bearer \t \t abcde12345\r\n"
//
// Are all equivalent.
//
// Also note that HTTP headers also allow for the values to be broken up in multiple lines.
// The rule is that if a "\r\n" is followed by a " " or "\t" the that next line is part of the previous line.
//
// So, for example, this:
//
// "Authorization: Bearer abcde12345\r\n"
//
// And these:
//
// "Authorization:\r\n Bearer abcde12345\r\n"
//
// "Authorization:\r\n\tBearer abcde12345\r\n"
//
// "Authorization: Bearer\r\n abcde12345\r\n"
//
// "Authorization: Bearer\r\n\tabcde12345\r\n"
//
// "Authorization:\r\n \r\n\t Bearer \r\n\t \t\t\t abcde12345\r\n"
//
// Are all equivalent.
//
// Parse deals with all of these, too.
func Parse(value string) (bearerToken string, successful bool) {
// Although the first important thing we expected it the string "Bearer",
// there could be zero or more of these characters.
//
// • "\t" i.e,. horizontal tab (␉)
// • " " i.e., space (␠)
// • "\r\n" i.e., carriage return (␍), line feed (␊)
//
// In IETF RFC822 LWSP-char is defined as a horizontal tab (␉) or space (␠).
//
// Technically "\r\n" should always be followed by a " " or a "\t".
// But we don't have to worry about that here. As the parser for the request
// already dealt with that.
value = trimleft(value)
// The first important thing we should see it "Bearer".
//
//@TODO: should this be case insensitive?
{
const expected string = "Bearer"
if !strings.HasPrefix(value, expected) {
return "", false
}
value = value[len(expected):]
}
// The next thing we should see is one of these 3:
//
// • "\t" i.e,. horizontal tab (␉)
// • " " i.e., space (␠)
// • "\r\n" i.e., carriage return (␍), line feed (␊)
//
// In IETF RFC822 LWSP-char is defined as a horizontal tab (␉) or space (␠).
//
// Technically "\r\n" should always be followed by a " " or a "\t".
// But we don't have to worry about that here. As the parser for the request
// already dealt with that.
//
// Note that what we are doing here is safe, even if we are dealing with the UTF-8 Unicode encoding.
{
if len(value) <= 0 {
return "", false
}
c0, value := value[0], value[1:]
switch c0 {
case ' ','\t':
// Nothing here. We got LWSP-char. So we will continue.
case '\r':
if len(value) <= 0 {
return "", false
}
c1, value := value[0], value[1:]
value = value[1:]
if '\n' != c1 {
return "", false
}
// Nothing else here. We got "\r\n". So we will continue.
default:
return "", false
}
}
// There could be more of these characters:
//
// • "\t" i.e,. horizontal tab (␉)
// • " " i.e., space (␠)
// • "\r\n" i.e., carriage return (␍), line feed (␊)
//
// We will consume them (and ignore them) if they are there.
value = trimleft(value)
// What should be left is the bearer token (with possibly some LWSP-chars or "\r\n" after it).
//
// Note that what we are doing here is safe, even if we are dealing with the UTF-8 Unicode encoding.
{
if len(value) <= 0 {
return "", true
}
for i,c := range value {
switch c {
case ' ','\t':
return value[:i], true
case '\r':
if len(value) < i+2 {
return value, true
}
next := value[i+1]
if '\n' != next {
return value[:i+1], true
}
return value[:i], true
}
}
return value, true
}
}

300
parse_test.go 100644
View File

@ -0,0 +1,300 @@
package httpbearer
import (
"testing"
)
func TestParse(t *testing.T) {
tests := []struct{
Value string
Expected string
}{
{
Value: "Bearer ",
Expected: "",
},
{
Value: "Bearer 0",
Expected: "0",
},
{
Value: "Bearer 1",
Expected: "1",
},
{
Value: "Bearer a",
Expected: "a",
},
{
Value: "Bearer x",
Expected: "x",
},
{
Value: "Bearer B",
Expected: "B",
},
{
Value: "Bearer Z",
Expected: "Z",
},
{
Value: "Bearer ۴",
Expected: "۴",
},
{
Value: "Bearer ۵",
Expected: "۵",
},
{
Value: "Bearer 🙂",
Expected: "🙂",
},
{
Value: "Bearer 😈",
Expected: "😈",
},
{
Value: "Bearer a",
Expected: "a",
},
{
Value: "Bearer ab",
Expected: "ab",
},
{
Value: "Bearer abc",
Expected: "abc",
},
{
Value: "Bearer abcd",
Expected: "abcd",
},
{
Value: "Bearer abcde",
Expected: "abcde",
},
{
Value: "Bearer a ",
Expected: "a",
},
{
Value: "Bearer ab ",
Expected: "ab",
},
{
Value: "Bearer abc ",
Expected: "abc",
},
{
Value: "Bearer abcd ",
Expected: "abcd",
},
{
Value: "Bearer abcde ",
Expected: "abcde",
},
{
Value: "Bearer a\t",
Expected: "a",
},
{
Value: "Bearer ab\t",
Expected: "ab",
},
{
Value: "Bearer abc\t",
Expected: "abc",
},
{
Value: "Bearer abcd\t",
Expected: "abcd",
},
{
Value: "Bearer abcde\t",
Expected: "abcde",
},
{
Value: "Bearer a\r\n ",
Expected: "a",
},
{
Value: "Bearer ab\r\n ",
Expected: "ab",
},
{
Value: "Bearer abc\r\n ",
Expected: "abc",
},
{
Value: "Bearer abcd\r\n ",
Expected: "abcd",
},
{
Value: "Bearer abcde\r\n ",
Expected: "abcde",
},
{
Value: "Bearer a\r\n\t",
Expected: "a",
},
{
Value: "Bearer ab\r\n\t",
Expected: "ab",
},
{
Value: "Bearer abc\r\n\t",
Expected: "abc",
},
{
Value: "Bearer abcd\r\n\t",
Expected: "abcd",
},
{
Value: "Bearer abcde\r\n\t",
Expected: "abcde",
},
{
Value: "Bearer XYZ123\r",
Expected: "XYZ123\r",
},
{
Value: "Bearer XYZ123\r ",
Expected: "XYZ123\r",
},
{
Value: "Bearer XYZ123\r\t",
Expected: "XYZ123\r",
},
{
Value: "Bearer XYZ123\r\r\n ",
Expected: "XYZ123\r",
},
{
Value: "Bearer XYZ123\r\r\n\t",
Expected: "XYZ123\r",
},
{
Value: "Bearer awcQ.j/YQLCF-yhC0Dah@wCn_NJe3e[VwKv1gzj!!SP3PHtj",
Expected: "awcQ.j/YQLCF-yhC0Dah@wCn_NJe3e[VwKv1gzj!!SP3PHtj",
},
{
Value: "Bearer\tawcQ.j/YQLCF-yhC0Dah@wCn_NJe3e[VwKv1gzj!!SP3PHtj",
Expected: "awcQ.j/YQLCF-yhC0Dah@wCn_NJe3e[VwKv1gzj!!SP3PHtj",
},
{
Value: "Bearer \tawcQ.j/YQLCF-yhC0Dah@wCn_NJe3e[VwKv1gzj!!SP3PHtj",
Expected: "awcQ.j/YQLCF-yhC0Dah@wCn_NJe3e[VwKv1gzj!!SP3PHtj",
},
{
Value: "Bearer\t awcQ.j/YQLCF-yhC0Dah@wCn_NJe3e[VwKv1gzj!!SP3PHtj",
Expected: "awcQ.j/YQLCF-yhC0Dah@wCn_NJe3e[VwKv1gzj!!SP3PHtj",
},
{
Value: "Bearer\r\n\tawcQ.j/YQLCF-yhC0Dah@wCn_NJe3e[VwKv1gzj!!SP3PHtj",
Expected: "awcQ.j/YQLCF-yhC0Dah@wCn_NJe3e[VwKv1gzj!!SP3PHtj",
},
{
Value: "Bearer \r\n\t awcQ.j/YQLCF-yhC0Dah@wCn_NJe3e[VwKv1gzj!!SP3PHtj",
Expected: "awcQ.j/YQLCF-yhC0Dah@wCn_NJe3e[VwKv1gzj!!SP3PHtj",
},
{
Value: "Bearer awcQ.j/YQLCF-yhC0Dah@wCn_NJe3e[VwKv1gzj!!SP3PHtj ",
Expected: "awcQ.j/YQLCF-yhC0Dah@wCn_NJe3e[VwKv1gzj!!SP3PHtj",
},
{
Value: "Bearer awcQ.j/YQLCF-yhC0Dah@wCn_NJe3e[VwKv1gzj!!SP3PHtj\t",
Expected: "awcQ.j/YQLCF-yhC0Dah@wCn_NJe3e[VwKv1gzj!!SP3PHtj",
},
{
Value: "Bearer awcQ.j/YQLCF-yhC0Dah@wCn_NJe3e[VwKv1gzj!!SP3PHtj\r\n\t ",
Expected: "awcQ.j/YQLCF-yhC0Dah@wCn_NJe3e[VwKv1gzj!!SP3PHtj",
},
}
for testNumber, test := range tests {
actual, successful := Parse(test.Value)
expected := test.Expected
if !successful {
t.Errorf("For test #%d, expected to be able to parse HTTP bearer token successfully but actually didn't.", testNumber)
t.Logf("VALUE: %q", test.Value)
continue
}
if expected != actual {
t.Errorf("For test #%d, the actual bearer token is not what was expected.", testNumber)
t.Logf("EXPECTED: %q", expected)
t.Logf("ACTUAL: %q", actual)
t.Logf("VALUE: %q", test.Value)
continue
}
}
}
func TestParse_fail(t *testing.T) {
tests := []struct{
Value string
}{
{
Value: "",
},
{
Value: "bearer awcQ.j/YQLCF-yhC0Dah@wCn_NJe3e[VwKv1gzj!!SP3PHtj",
},
{
Value: "Token awcQ.j/YQLCF-yhC0Dah@wCn_NJe3e[VwKv1gzj!!SP3PHtj",
},
{
Value: "Bearer",
},
}
for testNumber, test := range tests {
actual, successful := Parse(test.Value)
if successful {
t.Errorf("For test #%d, expected parseing of HTTP bearer token to be unsuccessfully but actually was.", testNumber)
t.Logf("VALUE: %q", test.Value)
continue
}
if expected := ""; expected != actual {
t.Errorf("For test #%d, expected result to be empty string but actually wasn't.", testNumber)
t.Logf("EXPECTED: %q", expected)
t.Logf("ACTUAL: %q", actual)
t.Logf("VALUE: %q", test.Value)
continue
}
}
}

37
trimleft.go 100644
View File

@ -0,0 +1,37 @@
package httpbearer
// There could be more of these characters:
//
// • "\t" i.e,. horizontal tab (␉)
// • " " i.e., space (␠)
// • "\r\n" i.e., carriage return (␍), line feed (␊)
//
// We will consume them (and ignore them) if they are there.
func trimleft(value string) string {
for {
if len(value) <= 0 {
return value
}
c0 := value[0]
switch c0 {
case ' ','\t':
value = value[1:]
// Nothing else here. We got LWSP-char. So we will continue.
case '\r':
if len(value) < 2 {
return value
}
c1 := value[1]
if '\n' != c1 {
return value
}
value = value[2:]
// Nothing else here. We got "\r\n". So we will continue.
default:
return value
}
}
}

184
trimleft_test.go 100644
View File

@ -0,0 +1,184 @@
package httpbearer
import (
"testing"
)
func TestTrimLeft(t *testing.T) {
tests := []struct{
Value string
Expected string
}{
{
Value: "",
Expected: "",
},
{
Value: " ",
Expected: "",
},
{
Value: " ",
Expected: "",
},
{
Value: " ",
Expected: "",
},
{
Value: " ",
Expected: "",
},
{
Value: " ",
Expected: "",
},
{
Value: "\t",
Expected: "",
},
{
Value: "\t\t",
Expected: "",
},
{
Value: "\t\t\t",
Expected: "",
},
{
Value: "\t\t\t\t",
Expected: "",
},
{
Value: "\t\t\t\t\t",
Expected: "",
},
{
Value: "\r\n",
Expected: "",
},
{
Value: "\r\n\r\n",
Expected: "",
},
{
Value: "\r\n\r\n\r\n",
Expected: "",
},
{
Value: "\r\n\r\n\r\n\r\n",
Expected: "",
},
{
Value: "\r\n\r\n\r\n\r\n\r\n",
Expected: "",
},
{
Value: "\t \r\n \r\n\t \t\r\n \r\n\r\n",
Expected: "",
},
{
Value: " ToK3n]]*i4HD2@)",
Expected: "ToK3n]]*i4HD2@)",
},
{
Value: " ToK3n]]*i4HD2@)",
Expected: "ToK3n]]*i4HD2@)",
},
{
Value: " ToK3n]]*i4HD2@)",
Expected: "ToK3n]]*i4HD2@)",
},
{
Value: " ToK3n]]*i4HD2@)",
Expected: "ToK3n]]*i4HD2@)",
},
{
Value: " ToK3n]]*i4HD2@)",
Expected: "ToK3n]]*i4HD2@)",
},
{
Value: "\tToK3n]]*i4HD2@)",
Expected: "ToK3n]]*i4HD2@)",
},
{
Value: "\t\tToK3n]]*i4HD2@)",
Expected: "ToK3n]]*i4HD2@)",
},
{
Value: "\t\t\tToK3n]]*i4HD2@)",
Expected: "ToK3n]]*i4HD2@)",
},
{
Value: "\t\t\t\tToK3n]]*i4HD2@)",
Expected: "ToK3n]]*i4HD2@)",
},
{
Value: "\t\t\t\t\tToK3n]]*i4HD2@)",
Expected: "ToK3n]]*i4HD2@)",
},
{
Value: "\r\nToK3n]]*i4HD2@)",
Expected: "ToK3n]]*i4HD2@)",
},
{
Value: "\r\n\r\nToK3n]]*i4HD2@)",
Expected: "ToK3n]]*i4HD2@)",
},
{
Value: "\r\n\r\n\r\nToK3n]]*i4HD2@)",
Expected: "ToK3n]]*i4HD2@)",
},
{
Value: "\r\n\r\n\r\n\r\nToK3n]]*i4HD2@)",
Expected: "ToK3n]]*i4HD2@)",
},
{
Value: "\r\n\r\n\r\n\r\n\r\nToK3n]]*i4HD2@)",
Expected: "ToK3n]]*i4HD2@)",
},
{
Value: "\t \r\n \r\n\t \t\r\n \r\n\r\nToK3n]]*i4HD2@",
Expected: "ToK3n]]*i4HD2@",
},
}
for testNumber, test := range tests {
actual := trimleft(test.Value)
expected := test.Expected
if expected != actual {
t.Errorf("For test #%d, the actual left-trimmed value is not what was expected.", testNumber)
t.Logf("EXPECTED: %q", expected)
t.Logf("ACTUAL: %q", actual)
t.Logf("VALUE: %q", test.Value)
continue
}
}
}