PageRenderTime 104ms CodeModel.GetById 14ms RepoModel.GetById 0ms app.codeStats 0ms

/gobits.go

https://gitlab.com/magan/gobits
Go | 188 lines | 124 code | 31 blank | 33 comment | 34 complexity | 8f5909e073f9fc63b0bb843660cd4fc6 MD5 | raw file
  1. /*
  2. GoBITS - A server implementation of Microsoft BITS (Background Intelligent Transfer Service) written in go.
  3. Copyright (C) 2017 Magnus Andersson
  4. */
  5. package gobits
  6. import (
  7. "crypto/rand"
  8. "errors"
  9. "fmt"
  10. "io"
  11. "net/http"
  12. "os"
  13. "path"
  14. "regexp"
  15. "strconv"
  16. "strings"
  17. )
  18. // Event if the type of the event for the callback
  19. type Event int
  20. // Events that is sent to the callback
  21. const (
  22. EventCreateSession Event = 0 // A new session is created
  23. EventRecieveFile Event = 1 // a file is recieved
  24. EventCloseSession Event = 2 // a session is closed
  25. EventCancelSession Event = 3 // a session is canceled
  26. )
  27. // CallbackFunc is the function that is called when an event occurs
  28. type CallbackFunc func(event Event, Session, Path string)
  29. // Config contains configuration information
  30. type Config struct {
  31. TempDir string // Directory to store unfinished files in
  32. AllowedMethod string // Allowed method name
  33. Protocol string // Protocol to use
  34. MaxSize uint64 // Max size of uploaded file
  35. Allowed []string // Whitelisted filter
  36. Disallowed []string // Blacklisted filter
  37. }
  38. // Handler contains the config and the callback
  39. type Handler struct {
  40. cfg Config
  41. callback CallbackFunc
  42. }
  43. // ErrorContext is the type of the event for the callback
  44. type ErrorContext int
  45. // BITS error constants
  46. // https://msdn.microsoft.com/en-us/library/aa362798(v=vs.85).aspx
  47. const (
  48. ErrorContextNone ErrorContext = 0 // An error has not occurred
  49. ErrorContextUnknown ErrorContext = 1 // The error context is unknown
  50. ErrorContextGeneralQueueManager ErrorContext = 2 // The transfer queue manager generated the error
  51. ErrorContextQueueManagerNotification ErrorContext = 3 // The error was generated while the queue manager was notifying the client of an event
  52. ErrorContextLocalFile ErrorContext = 4 // The error was related to the specified local file. For example, permission was denied or the volume was unavailable
  53. ErrorContextRemoteFile ErrorContext = 5 // The error was related to the specified remote file. For example, the URL was not accessible
  54. ErrorContextGeneralTransport ErrorContext = 6 // The transport layer generated the error. These errors are general transport failures (these errors are not specific to the remote file)
  55. ErrorContextRemoteApplication ErrorContext = 7 // The server application that BITS passed the upload file to generated an error while processing the upload file
  56. )
  57. // NewHandler return a new Handler with sane defaults
  58. func NewHandler(cfg Config, cb CallbackFunc) (b *Handler, err error) {
  59. b = &Handler{
  60. cfg: cfg,
  61. callback: cb,
  62. }
  63. // make sure we have a method
  64. if b.cfg.AllowedMethod == "" {
  65. b.cfg.AllowedMethod = "BITS_POST"
  66. }
  67. // this will probably never change, unless a very custom server is made
  68. if b.cfg.Protocol == "" {
  69. // https://msdn.microsoft.com/en-us/library/aa362833(v=vs.85).aspx
  70. b.cfg.Protocol = "{7df0354d-249b-430f-820d-3d2a9bef4931}" // BITS 1.5 Upload Protocol
  71. }
  72. // setup the temporary directory
  73. if b.cfg.TempDir == "" {
  74. b.cfg.TempDir = path.Join(os.TempDir(), "gobits")
  75. }
  76. // if the allowed filter isn't specified, allow everything
  77. if len(b.cfg.Allowed) == 0 {
  78. b.cfg.Allowed = []string{".*"}
  79. }
  80. // Make sure all regexp compiles
  81. for _, n := range b.cfg.Allowed {
  82. _, err = regexp.Compile(n)
  83. if err != nil {
  84. return nil, fmt.Errorf("failed to compile regexp '%s': %v", n, err)
  85. }
  86. }
  87. for _, n := range b.cfg.Disallowed {
  88. _, err = regexp.Compile(n)
  89. if err != nil {
  90. return nil, fmt.Errorf("failed to compile regexp '%s': %v", n, err)
  91. }
  92. }
  93. return
  94. }
  95. // returns a BITS error
  96. func bitsError(w http.ResponseWriter, uuid string, status, code int, context ErrorContext) {
  97. w.Header().Add("BITS-Packet-Type", "Ack")
  98. if uuid != "" {
  99. w.Header().Add("BITS-Session-Id", uuid)
  100. }
  101. w.Header().Add("BITS-Error-Code", strconv.FormatInt(int64(code), 16))
  102. w.Header().Add("BITS-Error-Context", strconv.FormatInt(int64(context), 16))
  103. w.WriteHeader(status)
  104. w.Write(nil)
  105. }
  106. // generate a new UUID
  107. func newUUID() (string, error) {
  108. // Stolen from http://play.golang.org/p/4FkNSiUDMg
  109. uuid := make([]byte, 16)
  110. if n, err := io.ReadFull(rand.Reader, uuid); n != len(uuid) || err != nil {
  111. return "", err
  112. }
  113. // https://tools.ietf.org/html/rfc4122#section-4.1.1
  114. uuid[8] = uuid[8]&^0xc0 | 0x80
  115. // https://tools.ietf.org/html/rfc4122#section-4.1.3
  116. uuid[6] = uuid[6]&^0xf0 | 0x40
  117. return fmt.Sprintf("%x-%x-%x-%x-%x", uuid[0:4], uuid[4:6], uuid[6:8], uuid[8:10], uuid[10:]), nil
  118. }
  119. // check if file exists
  120. func exists(path string) (bool, error) {
  121. var err error
  122. if _, err = os.Stat(path); err != nil && os.IsNotExist(err) {
  123. return false, nil
  124. }
  125. return true, err
  126. }
  127. // parse a HTTP range header
  128. func parseRange(rangeString string) (rangeStart, rangeEnd, fileLength uint64, err error) {
  129. // We only support "range #-#/#" syntax
  130. if !strings.HasPrefix(rangeString, "bytes ") {
  131. return 0, 0, 0, errors.New("invalid range syntax")
  132. }
  133. // Remove leading 6 characters
  134. rangeArray := strings.Split(rangeString[6:], "/")
  135. if len(rangeArray) != 2 {
  136. return 0, 0, 0, errors.New("invalid range syntax")
  137. }
  138. // Parse total length
  139. if fileLength, err = strconv.ParseUint(rangeArray[1], 10, 64); err != nil {
  140. return 0, 0, 0, err
  141. }
  142. // Get start and end of range
  143. rangeArray = strings.Split(rangeArray[0], "-")
  144. if len(rangeArray) != 2 {
  145. return 0, 0, 0, errors.New("invalid range syntax")
  146. }
  147. // Parse start value
  148. if rangeStart, err = strconv.ParseUint(rangeArray[0], 10, 64); err != nil {
  149. return 0, 0, 0, err
  150. }
  151. // Parse end value
  152. if rangeEnd, err = strconv.ParseUint(rangeArray[1], 10, 64); err != nil {
  153. return 0, 0, 0, err
  154. }
  155. // Return values
  156. return rangeStart, rangeEnd, fileLength, nil
  157. }