Initial commit

This commit is contained in:
Alexey Khit
2022-08-18 09:19:00 +03:00
commit 3e77835583
65 changed files with 6372 additions and 0 deletions
+104
View File
@@ -0,0 +1,104 @@
package tcp
import (
"crypto/md5"
"encoding/base64"
"encoding/hex"
"fmt"
"net/url"
"strings"
)
type Auth struct {
Method byte
user string
pass string
header string
h1nonce string
}
const (
AuthNone byte = iota
AuthUnknown
AuthBasic
AuthDigest
)
func NewAuth(user *url.Userinfo) *Auth {
a := new(Auth)
a.user = user.Username()
a.pass, _ = user.Password()
if a.user != "" {
a.Method = AuthUnknown
}
return a
}
func (a *Auth) Read(res *Response) bool {
auth := res.Header.Get("WWW-Authenticate")
if len(auth) < 6 {
return false
}
switch auth[:6] {
case "Basic ":
a.header = "Basic " + B64(a.user, a.pass)
a.Method = AuthBasic
return true
case "Digest":
realm := Between(auth, `realm="`, `"`)
nonce := Between(auth, `nonce="`, `"`)
a.h1nonce = HexMD5(a.user, realm, a.pass) + ":" + nonce
a.header = fmt.Sprintf(
`Digest username="%s", realm="%s", nonce="%s"`,
a.user, realm, nonce,
)
a.Method = AuthDigest
return true
default:
return false
}
}
func (a *Auth) Write(req *Request) {
if a == nil {
return
}
switch a.Method {
case AuthBasic:
req.Header.Set("Authorization", a.header)
case AuthDigest:
uri := req.URL.RequestURI()
h2 := HexMD5(req.Method, uri)
response := HexMD5(a.h1nonce, h2)
header := a.header + fmt.Sprintf(
`, uri="%s", response="%s"`, uri, response,
)
req.Header.Set("Authorization", header)
}
}
func Between(s, sub1, sub2 string) string {
i := strings.Index(s, sub1)
if i < 0 {
return ""
}
s = s[i+len(sub1):]
i = strings.Index(s, sub2)
if i < 0 {
return ""
}
return s[:i]
}
func HexMD5(s ...string) string {
b := md5.Sum([]byte(strings.Join(s, ":")))
return hex.EncodeToString(b[:])
}
func B64(s ...string) string {
b := []byte(strings.Join(s, ":"))
return base64.StdEncoding.EncodeToString(b)
}
+36
View File
@@ -0,0 +1,36 @@
package tcp
import (
"github.com/AlexxIT/go2rtc/pkg/streamer"
"net"
)
type Server struct {
streamer.Element
listener net.Listener
closed bool
}
func NewServer(address string) (srv *Server, err error) {
srv = &Server{}
srv.listener, err = net.Listen("tcp", address)
return
}
func (s *Server) Serve() {
for {
conn, err := s.listener.Accept()
if err != nil {
return
}
go func() {
s.Fire(conn)
_ = conn.Close()
}()
}
}
func (s *Server) Close() error {
return s.listener.Close()
}
+147
View File
@@ -0,0 +1,147 @@
package tcp
import (
"bufio"
"errors"
"fmt"
"io"
"net/textproto"
"net/url"
"strconv"
"strings"
)
const EndLine = "\r\n"
// Response like http.Response, but with any proto
type Response struct {
Status string
StatusCode int
Proto string
Header textproto.MIMEHeader
Body []byte
Request *Request
}
func (r Response) String() string {
s := r.Proto + " " + r.Status + EndLine
for k, v := range r.Header {
s += k + ": " + v[0] + EndLine
}
s += EndLine
if r.Body != nil {
s += string(r.Body)
}
return s
}
func (r *Response) Write(w io.Writer) (err error) {
_, err = w.Write([]byte(r.String()))
return
}
func ReadResponse(r *bufio.Reader) (*Response, error) {
tp := textproto.NewReader(r)
line, err := tp.ReadLine()
if err != nil {
return nil, err
}
ss := strings.SplitN(line, " ", 3)
if len(ss) != 3 {
return nil, errors.New("malformed response")
}
res := &Response{
Status: ss[1] + " " + ss[2],
Proto: ss[0],
}
res.StatusCode, err = strconv.Atoi(ss[1])
if err != nil {
return nil, err
}
res.Header, err = tp.ReadMIMEHeader()
if err != nil {
return nil, err
}
if val := res.Header.Get("Content-Length"); val != "" {
var i int
i, err = strconv.Atoi(val)
res.Body = make([]byte, i)
if _, err = io.ReadAtLeast(r, res.Body, i); err != nil {
return nil, err
}
}
return res, nil
}
// Request like http.Request, but with any proto
type Request struct {
Method string
URL *url.URL
Proto string
Header textproto.MIMEHeader
Body []byte
}
func (r *Request) String() string {
s := r.Method + " " + r.URL.String() + " " + r.Proto + EndLine
for k, v := range r.Header {
s += k + ": " + v[0] + EndLine
}
s += EndLine
if r.Body != nil {
s += string(r.Body)
}
return s
}
func (r *Request) Write(w io.Writer) (err error) {
_, err = w.Write([]byte(r.String()))
return
}
func ReadRequest(r *bufio.Reader) (*Request, error) {
tp := textproto.NewReader(r)
line, err := tp.ReadLine()
if err != nil {
return nil, err
}
ss := strings.SplitN(line, " ", 3)
if len(ss) != 3 {
return nil, fmt.Errorf("wrong request: %s", line)
}
req := &Request{
Method: ss[0],
Proto: ss[2],
}
req.URL, err = url.Parse(ss[1])
if err != nil {
return nil, err
}
req.Header, err = tp.ReadMIMEHeader()
if err != nil {
return nil, err
}
if val := req.Header.Get("Content-Length"); val != "" {
var i int
i, err = strconv.Atoi(val)
req.Body = make([]byte, i)
if _, err = io.ReadAtLeast(r, req.Body, i); err != nil {
return nil, err
}
}
return req, nil
}
+30
View File
@@ -0,0 +1,30 @@
package tcp
import (
"bufio"
"bytes"
"net/http"
"testing"
)
func assert(t *testing.T, one, two interface{}) {
if one != two {
t.FailNow()
}
}
func TestName(t *testing.T) {
data := []byte(`RTSP/1.0 401 Unauthorized
WWW-Authenticate: Digest realm="testrealm@host.com",
nonce="dcd98b7102dd2f0e8b11d0f600bfb0c093",
`)
buf := bytes.NewBuffer(data)
r := bufio.NewReader(buf)
res, err := ReadResponse(r)
assert(t, err, nil)
assert(t, res.StatusCode, http.StatusUnauthorized)
}