123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460 |
- /*
- * 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"
- "fmt"
- "io"
- "math"
- "net"
- "regexp"
- "strconv"
- "strings"
- "devt.de/krotik/common/datautil"
- )
- /*
- MaxRequestSize is the maximum size for a request
- */
- const MaxRequestSize = 1024
- /*
- MetaDataInterval is the data interval in which meta data is send
- */
- var MetaDataInterval uint64 = 65536
- /*
- peerNoAuthTimeout is the time in seconds a peer can open new connections without
- sending new authentication information.
- */
- const peerNoAuthTimeout = 10
- /*
- MaxMetaDataSize is the maximum size for meta data (everything over is truncated)
- Must be a multiple of 16 which fits into one byte. Maximum: 16 * 255 = 4080
- */
- var MaxMetaDataSize = 4080
- /*
- requestPathPattern is the pattern which is used to extract the requested path
- (i case-insensitive / m multi-line mode: ^ and $ match begin/end line)
- */
- var requestPathPattern = regexp.MustCompile("(?im)get\\s+([^\\s]+).*")
- /*
- requestOffsetPattern is the pattern which is used to extract the requested offset
- (i case-insensitive / m multi-line mode: ^ and $ match begin/end line)
- */
- var requestOffsetPattern = regexp.MustCompile("(?im)^Range: bytes=([0-9]+)-.*$")
- /*
- DefaultRequestHandler data structure
- */
- type DefaultRequestHandler struct {
- PlaylistFactory PlaylistFactory // Factory for playlists
- ServeRequest func(c net.Conn, path string,
- metaDataSupport bool, offset int, auth string) // Function to serve requests
- loop bool // Flag if the playlist should be looped
- LoopTimes int // Number of loops -1 loops forever
- shuffle bool // Flag if the playlist should be shuffled
- auth string // Required (basic) authentication string - may be empty
- authPeers *datautil.MapCache // Peers which have been authenticated
- logger DebugLogger // Logger for debug output
- }
- /*
- NewDefaultRequestHandler creates a new default request handler object.
- */
- func NewDefaultRequestHandler(pf PlaylistFactory, loop bool,
- shuffle bool, auth string) *DefaultRequestHandler {
- drh := &DefaultRequestHandler{
- PlaylistFactory: pf,
- loop: loop,
- LoopTimes: -1,
- shuffle: shuffle,
- auth: auth,
- authPeers: datautil.NewMapCache(0, peerNoAuthTimeout),
- logger: nil,
- }
- drh.ServeRequest = drh.defaultServeRequest
- return drh
- }
- /*
- SetDebugLogger sets the debug logger for this request handler.
- */
- func (drh *DefaultRequestHandler) SetDebugLogger(logger DebugLogger) {
- drh.logger = logger
- }
- /*
- HandleRequest handles requests from streaming clients. It tries to extract
- the path and if meta data is supported. Once a request has been successfully
- decoded ServeRequest is called. The connection is closed once HandleRequest
- finishes.
- */
- func (drh *DefaultRequestHandler) HandleRequest(c net.Conn, nerr net.Error) {
- drh.logger.PrintDebug("Handling request from: ", c.RemoteAddr())
- defer func() {
- c.Close()
- }()
- // Check if there was an error
- if nerr != nil {
- drh.logger.PrintDebug(nerr)
- return
- }
- buf, err := drh.decodeRequestHeader(c)
- if err != nil {
- drh.logger.PrintDebug(err)
- return
- }
- // Add ending sequence in case the client "forgets"
- bufStr := buf.String() + "\r\n\r\n"
- // Determine the remote string
- clientString := "-"
- if c.RemoteAddr() != nil {
- clientString, _, _ = net.SplitHostPort(c.RemoteAddr().String())
- }
- drh.logger.PrintDebug("Client:", c.RemoteAddr(), " Request:", bufStr)
- if i := strings.Index(bufStr, "\r\n\r\n"); i >= 0 {
- var auth string
- var ok bool
- bufStr = strings.TrimSpace(bufStr[:i])
- // Check authentication
- if auth, bufStr, ok = drh.checkAuth(bufStr, clientString); !ok {
- drh.writeUnauthorized(c)
- return
- }
- // Check if the client supports meta data
- metaDataSupport := false
- if strings.Contains(strings.ToLower(bufStr), "icy-metadata: 1") {
- metaDataSupport = true
- }
- // Extract offset
- offset := 0
- res := requestOffsetPattern.FindStringSubmatch(bufStr)
- if len(res) > 1 {
- if o, err := strconv.Atoi(res[1]); err == nil {
- offset = o
- }
- }
- // Extract the path
- res = requestPathPattern.FindStringSubmatch(bufStr)
- if len(res) > 1 {
- // Now serve the request
- drh.ServeRequest(c, res[1], metaDataSupport, offset, auth)
- return
- }
- }
- drh.logger.PrintDebug("Invalid request: ", bufStr)
- }
- /*
- decodeRequestHeader decodes the header of an incoming request.
- */
- func (drh *DefaultRequestHandler) decodeRequestHeader(c net.Conn) (*bytes.Buffer, error) {
- var buf bytes.Buffer
- rbuf := make([]byte, 512, 512)
- // Decode request
- n, err := c.Read(rbuf)
- for n > 0 || err != nil && err != io.EOF {
- // Do some error checking
- if err != nil {
- return nil, err
- } else if buf.Len() > MaxRequestSize {
- return nil, fmt.Errorf("Illegal request: Request is too long")
- }
- buf.Write(rbuf[:n])
- if strings.Contains(string(rbuf), "\r\n\r\n") {
- break
- }
- n, err = c.Read(rbuf)
- }
- return &buf, nil
- }
- /*
- defaultServeRequest is called once a request was successfully decoded.
- */
- func (drh *DefaultRequestHandler) defaultServeRequest(c net.Conn, path string, metaDataSupport bool, offset int, auth string) {
- var writtenBytes uint64
- var currentPlaying string
- var err error
- drh.logger.PrintDebug("Serve request path:", path, " Metadata support:", metaDataSupport, " Offset:", offset)
- pl := drh.PlaylistFactory.Playlist(path, drh.shuffle)
- if pl == nil {
- // Stream was not found - no error checking here (don't care)
- drh.writeStreamNotFoundResponse(c)
- return
- }
- err = drh.writeStreamStartResponse(c, pl.Name(), pl.ContentType(), metaDataSupport)
- frameOffset := offset
- for {
- for !pl.Finished() {
- if drh.logger.IsDebugOutputEnabled() {
- playingString := fmt.Sprintf("%v - %v", pl.Title(), pl.Artist())
- if playingString != currentPlaying {
- currentPlaying = playingString
- drh.logger.PrintDebug("Written bytes: ", writtenBytes)
- drh.logger.PrintDebug("Sending: ", currentPlaying)
- }
- }
- // Check if there were any errors
- if err != nil {
- drh.logger.PrintDebug(err)
- return
- }
- frameOffset, writtenBytes, err = drh.writeFrame(c, pl, frameOffset,
- writtenBytes, metaDataSupport)
- }
- // Handle looping - do not loop if close returns an error
- if pl.Close() != nil || !drh.loop {
- break
- } else if drh.LoopTimes != -1 {
- drh.LoopTimes--
- if drh.LoopTimes == 0 {
- break
- }
- }
- }
- drh.logger.PrintDebug("Serve request path:", path, " complete")
- }
- /*
- prepareFrame prepares a frame before it can be written to a client.
- */
- func (drh *DefaultRequestHandler) prepareFrame(c net.Conn, pl Playlist, frameOffset int,
- writtenBytes uint64, metaDataSupport bool) ([]byte, int, error) {
- frame, err := pl.Frame()
- // Handle offsets
- if frameOffset > 0 && err == nil {
- for frameOffset > len(frame) && err == nil {
- frameOffset -= len(frame)
- frame, err = pl.Frame()
- }
- if err == nil {
- frame = frame[frameOffset:]
- frameOffset = 0
- if len(frame) == 0 {
- frame, err = pl.Frame()
- }
- }
- }
- if frame == nil {
- if !pl.Finished() {
- drh.logger.PrintDebug(fmt.Sprintf("Empty frame for: %v - %v (Error: %v)", pl.Title(), pl.Artist(), err))
- }
- } else if err != nil {
- if err != ErrPlaylistEnd {
- drh.logger.PrintDebug(fmt.Sprintf("Error while retrieving playlist data: %v", err))
- }
- err = nil
- }
- return frame, frameOffset, err
- }
- /*
- writeFrame writes a frame to a client.
- */
- func (drh *DefaultRequestHandler) writeFrame(c net.Conn, pl Playlist, frameOffset int,
- writtenBytes uint64, metaDataSupport bool) (int, uint64, error) {
- frame, frameOffset, err := drh.prepareFrame(c, pl, frameOffset, writtenBytes, metaDataSupport)
- if frame == nil {
- return frameOffset, writtenBytes, err
- }
- // Check if meta data should be send
- if metaDataSupport && writtenBytes+uint64(len(frame)) >= MetaDataInterval {
- // Write rest data before sending meta data
- if preMetaDataLength := MetaDataInterval - writtenBytes; preMetaDataLength > 0 {
- if err == nil {
- _, err = c.Write(frame[:preMetaDataLength])
- frame = frame[preMetaDataLength:]
- writtenBytes += preMetaDataLength
- }
- }
- if err == nil {
- // Write meta data - no error checking (next write should fail)
- drh.writeStreamMetaData(c, pl)
- // Write rest of the frame
- c.Write(frame)
- writtenBytes += uint64(len(frame))
- }
- writtenBytes -= MetaDataInterval
- } else {
- // Just write the frame to the client
- if err == nil {
- clientWritten, _ := c.Write(frame)
- // Abort if the client does not accept more data
- if clientWritten == 0 && len(frame) > 0 {
- return frameOffset, writtenBytes,
- fmt.Errorf("Could not write to client - closing connection")
- }
- }
- pl.ReleaseFrame(frame)
- writtenBytes += uint64(len(frame))
- }
- return frameOffset, writtenBytes, err
- }
- /*
- writeStreamMetaData writes meta data information into the stream.
- */
- func (drh *DefaultRequestHandler) writeStreamMetaData(c net.Conn, playlist Playlist) {
- streamTitle := fmt.Sprintf("StreamTitle='%v - %v';", playlist.Title(), playlist.Artist())
- // Truncate stream title if necessary
- if len(streamTitle) > MaxMetaDataSize {
- streamTitle = streamTitle[:MaxMetaDataSize-2] + "';"
- }
- // Calculate the meta data frame size as a multiple of 16
- metaDataFrameSize := byte(math.Ceil(float64(len(streamTitle)) / 16.0))
- // Write meta data to the client
- metaData := make([]byte, 16.0*metaDataFrameSize+1, 16.0*metaDataFrameSize+1)
- metaData[0] = metaDataFrameSize
- copy(metaData[1:], streamTitle)
- c.Write(metaData)
- }
- /*
- writeStreamStartResponse writes the start response to the client.
- */
- func (drh *DefaultRequestHandler) writeStreamStartResponse(c net.Conn,
- name, contentType string, metaDataSupport bool) error {
- c.Write([]byte("ICY 200 OK\r\n"))
- c.Write([]byte(fmt.Sprintf("Content-Type: %v\r\n", contentType)))
- c.Write([]byte(fmt.Sprintf("icy-name: %v\r\n", name)))
- if metaDataSupport {
- c.Write([]byte("icy-metadata: 1\r\n"))
- c.Write([]byte(fmt.Sprintf("icy-metaint: %v\r\n", MetaDataInterval)))
- }
- _, err := c.Write([]byte("\r\n"))
- return err
- }
- /*
- writeStreamNotFoundResponse writes the not found response to the client.
- */
- func (drh *DefaultRequestHandler) writeStreamNotFoundResponse(c net.Conn) error {
- _, err := c.Write([]byte("HTTP/1.1 404 Not found\r\n\r\n"))
- return err
- }
- /*
- writeUnauthorized writes the Unauthorized response to the client.
- */
- func (drh *DefaultRequestHandler) writeUnauthorized(c net.Conn) error {
- _, err := c.Write([]byte("HTTP/1.1 401 Authorization Required\r\nWWW-Authenticate: Basic realm=\"DudelDu Streaming Server\"\r\n\r\n"))
- return err
- }
|