123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659 |
- /*
- * DudelDu
- *
- * Copyright 2016 Matthias Ladkau. All rights reserved.
- *
- * This Source Code Form is subject to the terms of the MIT
- * License, If a copy of the MIT License was not distributed with this
- * file, You can obtain one at https://opensource.org/licenses/MIT.
- */
- package dudeldu
- import (
- "bytes"
- "errors"
- "fmt"
- "net"
- "strings"
- "sync"
- "testing"
- "devt.de/krotik/common/testutil"
- )
- const testRequest = `
- GET /mylist HTTP/1.1
- Host: localhost:9091
- User-Agent: VLC/2.2.1 LibVLC/2.2.1
- Range: bytes=0-
- Connection: close
- Icy-MetaData: 1` +
- "\r\n\r\n"
- const testRequest2 = `
- GET /mylist2 HTTP/1.1
- Host: localhost:9091
- User-Agent: VLC/2.2.1 LibVLC/2.2.1
- Range: bytes=656-
- Connection: close
- Icy-MetaData: 1` +
- "\r\n\r\n"
- const testRequest3 = `
- GET /bach/cello_suite1 HTTP/1.1
- Host: localhost:9091
- User-Agent: Mozilla/5.0 (Windows NT 6.3; WOW64; rv:48.0) Gecko/20100101 Firefox/99.0
- Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
- Accept-Language: en-US,en;q=0.5
- Accept-Encoding: gzip, deflate
- Authorization: Basic d2ViOndlYg==
- Connection: keep-alive
- Upgrade-Insecure-Requests: 1
- Cache-Control: max-age=0
- `
- const testRequest4 = "GET /bach/cello_suite1 HTTP/1.1\r\nHost: localhost:9091\r\n" +
- "User-Agent: Mozilla/5.0 (Windows NT 6.3; WOW64; rv:48.0) Gecko/20100101 Firefox/48.0\r\n" +
- "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8\r\nAccept-Language: en-US,en;q=0.5\r\n" +
- "Accept-Encoding: gzip, deflate\r\n" +
- "Authorization: Basic d2ViOndlYg==\r\n" +
- "Connection: keep-alive\r\nUpgrade-Insecure-Requests: 1\r\nCache-Control: max-age=0"
- const testRequest5 = `
- GET /mylist2 HTTP/1.1
- Host: localhost:9091
- User-Agent: VLC/2.2.1 LibVLC/2.2.1
- Range: bytes=656-
- Authorization: Basic erghb4
- Connection: close
- Icy-MetaData: 1` +
- "\r\n\r\n"
- /*
- testNetError is am error for testing
- */
- type testNetError struct {
- }
- func (t *testNetError) Error() string {
- return "TestNetError"
- }
- func (t *testNetError) Timeout() bool {
- return false
- }
- func (t *testNetError) Temporary() bool {
- return false
- }
- type testPlaylistFactory struct {
- RetPlaylist Playlist
- }
- func (tp *testPlaylistFactory) Playlist(path string, shuffle bool) Playlist {
- if path == "/testpath" {
- return tp.RetPlaylist
- }
- return nil
- }
- var testTitle = "Test Title"
- /*
- testPlaylist is a playlist for testing
- */
- type testPlaylist struct {
- Frames [][]byte
- Errors []error
- fp int
- }
- func (tp *testPlaylist) Name() string {
- return "TestPlaylist"
- }
- func (tp *testPlaylist) ContentType() string {
- return "Test/Content"
- }
- func (tp *testPlaylist) Artist() string {
- return "Test Artist"
- }
- func (tp *testPlaylist) Title() string {
- return testTitle
- }
- func (tp *testPlaylist) Frame() ([]byte, error) {
- var err error
- f := tp.Frames[tp.fp]
- if tp.Errors != nil {
- err = tp.Errors[tp.fp]
- }
- tp.fp++
- return f, err
- }
- func (tp *testPlaylist) ReleaseFrame([]byte) {
- }
- func (tp *testPlaylist) Finished() bool {
- return tp.fp == len(tp.Frames)
- }
- func (tp *testPlaylist) Close() error {
- tp.fp = 0
- return nil
- }
- func TestRequestServing(t *testing.T) {
- // Collect the print output
- var out bytes.Buffer
- debugLogger := &TestDebugLogger{true, func(v ...interface{}) {
- out.WriteString(fmt.Sprint(v...))
- out.WriteString("\n")
- }}
- drh := NewDefaultRequestHandler(&testPlaylistFactory{}, false, false, "")
- drh.SetDebugLogger(debugLogger)
- testConn := &testutil.ErrorTestingConnection{}
- // Test a path not found
- drh.defaultServeRequest(testConn, "tester", false, 0, "")
- if testConn.Out.String() != "HTTP/1.1 404 Not found\r\n\r\n" {
- t.Error("Unexpected response:", testConn.Out.String())
- return
- }
- // Test straight forward case - serving a stream without meta data
- drh = NewDefaultRequestHandler(&testPlaylistFactory{&testPlaylist{
- [][]byte{[]byte("12"), nil, []byte("3")},
- []error{nil, nil, errors.New("TestError")},
- 0}}, false, false, "")
- drh.SetDebugLogger(debugLogger)
- testConn = &testutil.ErrorTestingConnection{}
- out.Reset()
- drh.defaultServeRequest(testConn, "/testpath", false, 0, "")
- if testConn.Out.String() != "ICY 200 OK\r\n"+
- "Content-Type: Test/Content\r\n"+
- "icy-name: TestPlaylist\r\n"+
- "\r\n"+
- "123" {
- t.Error("Unexpected response:", testConn.Out.String())
- return
- }
- if out.String() != "Serve request path:/testpath Metadata support:false Offset:0\n"+
- "Written bytes: 0\n"+
- "Sending: Test Title - Test Artist\n"+
- "Empty frame for: Test Title - Test Artist (Error: <nil>)\n"+
- "Error while retrieving playlist data: TestError\n"+
- "Serve request path:/testpath complete\n" {
- t.Error("Unexpected out string:", out.String())
- return
- }
- // Test case when sending meta data
- oldMetaDataInterval := MetaDataInterval
- MetaDataInterval = 5
- defer func() {
- MetaDataInterval = oldMetaDataInterval
- }()
- tpl := &testPlaylist{[][]byte{[]byte("123"), []byte("4567"), []byte("0123"), []byte("456789")}, nil, 0}
- drh = NewDefaultRequestHandler(&testPlaylistFactory{tpl}, false, false, "")
- drh.SetDebugLogger(debugLogger)
- testConn = &testutil.ErrorTestingConnection{}
- drh.defaultServeRequest(testConn, "/testpath", true, 0, "")
- // Meta data is 3*16=48 bytes - text is 39 bytes, padding is 9 bytes
- if testConn.Out.String() != ("ICY 200 OK\r\n" +
- "Content-Type: Test/Content\r\n" +
- "icy-name: TestPlaylist\r\n" +
- "icy-metadata: 1\r\n" +
- "icy-metaint: 5\r\n" +
- "\r\n" +
- `12345` + string(0x03) + `StreamTitle='Test Title - Test Artist';` + string([]byte{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}) +
- `67012` + string(0x03) + `StreamTitle='Test Title - Test Artist';` + string([]byte{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}) +
- `34567` + string(0x03) + `StreamTitle='Test Title - Test Artist';` + string([]byte{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}) +
- `89`) {
- t.Error("Unexpected response:", testConn.Out.String())
- return
- }
- tpl.fp = 0
- drh = NewDefaultRequestHandler(&testPlaylistFactory{tpl}, false, false, "")
- drh.SetDebugLogger(debugLogger)
- testConn = &testutil.ErrorTestingConnection{}
- testConn.OutErr = 5
- out.Reset()
- drh.defaultServeRequest(testConn, "/testpath", true, 0, "")
- if out.String() != "Serve request path:/testpath Metadata support:true Offset:0\n"+
- "Written bytes: 0\n"+
- "Sending: Test Title - Test Artist\n"+
- "Test writing error\n" {
- t.Error("Unexpected output:", out.String())
- return
- }
- oldTestTitle := testTitle
- testTitle = "A very long title name which should be truncated"
- defer func() {
- testTitle = oldTestTitle
- }()
- oldMaxMetaDataSize := MaxMetaDataSize
- MaxMetaDataSize = 40
- defer func() {
- MaxMetaDataSize = oldMaxMetaDataSize
- }()
- tpl.fp = 0
- drh = NewDefaultRequestHandler(&testPlaylistFactory{tpl}, false, false, "")
- drh.SetDebugLogger(debugLogger)
- testConn = &testutil.ErrorTestingConnection{}
- drh.defaultServeRequest(testConn, "/testpath", true, 0, "")
- // Meta data is 3*16=48 bytes - text is 40 bytes, padding is 8 bytes
- if testConn.Out.String() != ("ICY 200 OK\r\n" +
- "Content-Type: Test/Content\r\n" +
- "icy-name: TestPlaylist\r\n" +
- "icy-metadata: 1\r\n" +
- "icy-metaint: 5\r\n" +
- "\r\n" +
- `12345` + string(0x03) + `StreamTitle='A very long title name wh';` + string([]byte{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}) +
- `67012` + string(0x03) + `StreamTitle='A very long title name wh';` + string([]byte{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}) +
- `34567` + string(0x03) + `StreamTitle='A very long title name wh';` + string([]byte{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}) +
- `89`) {
- t.Error("Unexpected response:", testConn.Out.String())
- return
- }
- // Test offsets
- tpl.fp = 0
- drh = NewDefaultRequestHandler(&testPlaylistFactory{tpl}, false, false, "")
- drh.SetDebugLogger(debugLogger)
- testConn = &testutil.ErrorTestingConnection{}
- drh.defaultServeRequest(testConn, "/testpath", true, 7, "")
- // Meta data is 3*16=48 bytes - text is 40 bytes, padding is 8 bytes
- if testConn.Out.String() != ("ICY 200 OK\r\n" +
- "Content-Type: Test/Content\r\n" +
- "icy-name: TestPlaylist\r\n" +
- "icy-metadata: 1\r\n" +
- "icy-metaint: 5\r\n" +
- "\r\n" +
- `01234` + string(0x03) + `StreamTitle='A very long title name wh';` + string([]byte{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}) +
- `56789`) {
- t.Error("Unexpected response:", testConn.Out.String())
- return
- }
- tpl.fp = 0
- drh = NewDefaultRequestHandler(&testPlaylistFactory{tpl}, false, false, "")
- drh.SetDebugLogger(debugLogger)
- testConn = &testutil.ErrorTestingConnection{}
- drh.defaultServeRequest(testConn, "/testpath", true, 2, "")
- // Meta data is 3*16=48 bytes - text is 40 bytes, padding is 8 bytes
- if testConn.Out.String() != ("ICY 200 OK\r\n" +
- "Content-Type: Test/Content\r\n" +
- "icy-name: TestPlaylist\r\n" +
- "icy-metadata: 1\r\n" +
- "icy-metaint: 5\r\n" +
- "\r\n" +
- `34567` + string(0x03) + `StreamTitle='A very long title name wh';` + string([]byte{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}) +
- `01234` + string(0x03) + `StreamTitle='A very long title name wh';` + string([]byte{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}) +
- `56789`) {
- t.Error("Unexpected response:", testConn.Out.String())
- return
- }
- // Test offset and loops
- tpl.fp = 0
- drh = NewDefaultRequestHandler(&testPlaylistFactory{tpl}, true, false, "")
- drh.SetDebugLogger(debugLogger)
- testConn = &testutil.ErrorTestingConnection{}
- drh.LoopTimes = 3
- drh.defaultServeRequest(testConn, "/testpath", true, 4, "")
- // Meta data is 3*16=48 bytes - text is 40 bytes, padding is 8 bytes
- if testConn.Out.String() != ("ICY 200 OK\r\n" +
- "Content-Type: Test/Content\r\n" +
- "icy-name: TestPlaylist\r\n" +
- "icy-metadata: 1\r\n" +
- "icy-metaint: 5\r\n" +
- "\r\n" +
- `56701` + string(0x03) + `StreamTitle='A very long title name wh';` + string([]byte{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}) +
- `23456` + string(0x03) + `StreamTitle='A very long title name wh';` + string([]byte{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}) +
- `78912` + string(0x03) + `StreamTitle='A very long title name wh';` + string([]byte{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}) +
- `34567` + string(0x03) + `StreamTitle='A very long title name wh';` + string([]byte{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}) +
- `01234` + string(0x03) + `StreamTitle='A very long title name wh';` + string([]byte{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}) +
- `56789` + string(0x03) + `StreamTitle='A very long title name wh';` + string([]byte{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}) +
- `12345` + string(0x03) + `StreamTitle='A very long title name wh';` + string([]byte{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}) +
- `67012` + string(0x03) + `StreamTitle='A very long title name wh';` + string([]byte{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}) +
- `34567` + string(0x03) + `StreamTitle='A very long title name wh';` + string([]byte{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}) +
- `89`) {
- t.Error("Unexpected response:", testConn.Out.String())
- return
- }
- // Test client close connection
- tpl.fp = 0
- drh = NewDefaultRequestHandler(&testPlaylistFactory{tpl}, false, false, "")
- drh.SetDebugLogger(debugLogger)
- testConn = &testutil.ErrorTestingConnection{}
- testConn.OutClose = true
- out.Reset()
- drh.defaultServeRequest(testConn, "/testpath", true, 0, "")
- if out.String() != "Serve request path:/testpath Metadata support:true Offset:0\n"+
- "Written bytes: 0\n"+
- "Sending: A very long title name which should be truncated - Test Artist\n"+
- "Could not write to client - closing connection\n" {
- t.Error("Unexpected output:", out.String())
- return
- }
- }
- func TestRequestHandling(t *testing.T) {
- // Collect the print output
- var out bytes.Buffer
- debugLogger := &TestDebugLogger{true, func(v ...interface{}) {
- out.WriteString(fmt.Sprint(v...))
- out.WriteString("\n")
- }}
- drh := NewDefaultRequestHandler(nil, false, false, "")
- drh.SetDebugLogger(debugLogger)
- testConn := &testutil.ErrorTestingConnection{}
- // Check normal error return
- drh.HandleRequest(testConn, &testNetError{})
- if out.String() != "Handling request from: <nil>\n"+
- "TestNetError\n" {
- t.Error("Unexpected output:", out.String())
- return
- }
- out.Reset()
- // Test connection writing errors
- testConn = &testutil.ErrorTestingConnection{}
- for i := 0; i < 1600; i++ {
- testConn.In.WriteString("0123456789")
- }
- testConn.InErr = 530
- drh.HandleRequest(testConn, nil)
- if out.String() != "Handling request from: <nil>\n"+
- "Test reading error\n" {
- t.Error("Unexpected output:", out.String())
- return
- }
- out.Reset()
- testConn.In.Reset()
- for i := 0; i < 1600; i++ {
- testConn.In.WriteString("0123456789")
- }
- testConn.InErr = 0
- drh.HandleRequest(testConn, nil)
- if out.String() != "Handling request from: <nil>\n"+
- "Illegal request: Request is too long\n" {
- t.Error("Unexpected output:", out.String())
- return
- }
- out.Reset()
- testConn.In.Reset()
- testConn.In.WriteString("123")
- testConn.InErr = 0
- drh.HandleRequest(testConn, nil)
- if out.String() != "Handling request from: <nil>\n"+
- "Client:<nil> Request:123\r\n\r\n\n"+
- "Invalid request: 123\n" {
- t.Error("Unexpected output:", out.String())
- return
- }
- // Test auth
- drh = NewDefaultRequestHandler(nil, false, false, "web:web")
- drh.SetDebugLogger(debugLogger)
- testConn = &testutil.ErrorTestingConnection{}
- testConn.In.Reset()
- testConn.In.WriteString(testRequest5)
- // Check normal error return
- drh.HandleRequest(testConn, nil)
- if !strings.Contains(out.String(), "Invalid request (cannot decode authentication)") {
- t.Error("Unexpected output:", out.String())
- return
- }
- out.Reset()
- testConn.In.Reset()
- testConn.In.WriteString(testRequest2)
- // Check normal error return
- drh.HandleRequest(testConn, nil)
- if !strings.Contains(out.String(), "No authentication found") {
- t.Error("Unexpected output:", out.String())
- return
- }
- out.Reset()
- drh = NewDefaultRequestHandler(nil, false, false, "web:web2")
- drh.SetDebugLogger(debugLogger)
- testConn = &testutil.ErrorTestingConnection{}
- testConn.In.Reset()
- testConn.In.WriteString(testRequest3)
- // Check normal error return
- drh.HandleRequest(testConn, nil)
- if !strings.Contains(out.String(), "Wrong authentication:web:web") {
- t.Error("Unexpected output:", out.String())
- return
- }
- out.Reset()
- }
- func TestRequestHandler(t *testing.T) {
- // Collect the print output
- var out bytes.Buffer
- debugLogger := &TestDebugLogger{true, func(v ...interface{}) {
- out.WriteString(fmt.Sprint(v...))
- out.WriteString("\n")
- }}
- drh := NewDefaultRequestHandler(nil, false, false, "")
- drh.SetDebugLogger(debugLogger)
- dds := NewServer(drh.HandleRequest)
- var wg sync.WaitGroup
- wg.Add(1)
- go func() {
- err := dds.Run(testport, &wg)
- if err != nil {
- t.Error(err)
- return
- }
- }()
- wg.Wait()
- rpath := ""
- rmetaDataSupport := false
- roffset := -1
- rauth := ""
- errorChan := make(chan error)
- drh.ServeRequest = func(c net.Conn, path string, metaDataSupport bool, offset int, auth string) {
- rpath = path
- rmetaDataSupport = metaDataSupport
- roffset = offset
- rauth = auth
- errorChan <- nil
- }
- defer func() {
- drh.ServeRequest = drh.defaultServeRequest
- }()
- // Server is now running
- if err := writeSocket([]byte(testRequest)); err != nil {
- t.Error(err)
- return
- }
- <-errorChan
- if rpath != "/mylist" || rmetaDataSupport != true || roffset != 0 || rauth != "" {
- t.Error("Unexpected request decoding result:", rpath, rmetaDataSupport, roffset)
- return
- }
- if err := writeSocket([]byte(testRequest2)); err != nil {
- t.Error(err)
- return
- }
- <-errorChan
- if rpath != "/mylist2" || rmetaDataSupport != true || roffset != 656 || rauth != "" {
- t.Error("Unexpected request decoding result:", rpath, rmetaDataSupport, roffset)
- return
- }
- if err := writeSocket([]byte(testRequest3)); err != nil {
- t.Error(err)
- return
- }
- <-errorChan
- if rpath != "/bach/cello_suite1" || rmetaDataSupport != false || roffset != 0 || rauth != "web:web" {
- t.Error("Unexpected request decoding result:", rpath, rmetaDataSupport, roffset, rauth)
- return
- }
- if err := writeSocket([]byte(testRequest4)); err != nil {
- t.Error(err)
- return
- }
- <-errorChan
- if rpath != "/bach/cello_suite1" || rmetaDataSupport != false || roffset != 0 || rauth != "web:web" {
- t.Error("Unexpected request decoding result:", rpath, rmetaDataSupport, roffset, rauth)
- fmt.Println(testRequest4)
- return
- }
- if err := writeSocket([]byte("\r\n")); err != nil {
- t.Error(err)
- return
- }
- <-errorChan
- if rpath != "/bach/cello_suite1" || rmetaDataSupport != false || roffset != 0 || rauth != "web:web" {
- t.Error("Unexpected request decoding result:", rpath, rmetaDataSupport, roffset, rauth)
- fmt.Println(testRequest4)
- return
- }
- // Shutdown server
- wg.Add(1)
- dds.Shutdown()
- wg.Wait()
- }
- func writeSocket(req []byte) error {
- conn, err := net.Dial("tcp", testport)
- if err != nil {
- return err
- }
- defer conn.Close()
- conn.Write(req)
- return nil
- }
|