mirror of
https://github.com/AlexxIT/go2rtc.git
synced 2026-04-22 23:57:20 +08:00
Initial commit
This commit is contained in:
+104
@@ -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)
|
||||
}
|
||||
@@ -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()
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
Reference in New Issue
Block a user