api_shares.go 8.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290
  1. package httpd
  2. import (
  3. "context"
  4. "errors"
  5. "fmt"
  6. "net/http"
  7. "os"
  8. "path"
  9. "github.com/go-chi/render"
  10. "github.com/rs/xid"
  11. "github.com/drakkan/sftpgo/v2/common"
  12. "github.com/drakkan/sftpgo/v2/dataprovider"
  13. "github.com/drakkan/sftpgo/v2/util"
  14. )
  15. func getShares(w http.ResponseWriter, r *http.Request) {
  16. r.Body = http.MaxBytesReader(w, r.Body, maxRequestSize)
  17. claims, err := getTokenClaims(r)
  18. if err != nil || claims.Username == "" {
  19. sendAPIResponse(w, r, err, "Invalid token claims", http.StatusBadRequest)
  20. return
  21. }
  22. limit, offset, order, err := getSearchFilters(w, r)
  23. if err != nil {
  24. return
  25. }
  26. shares, err := dataprovider.GetShares(limit, offset, order, claims.Username)
  27. if err != nil {
  28. sendAPIResponse(w, r, err, "", getRespStatus(err))
  29. return
  30. }
  31. render.JSON(w, r, shares)
  32. }
  33. func getShareByID(w http.ResponseWriter, r *http.Request) {
  34. r.Body = http.MaxBytesReader(w, r.Body, maxRequestSize)
  35. claims, err := getTokenClaims(r)
  36. if err != nil || claims.Username == "" {
  37. sendAPIResponse(w, r, err, "Invalid token claims", http.StatusBadRequest)
  38. return
  39. }
  40. shareID := getURLParam(r, "id")
  41. share, err := dataprovider.ShareExists(shareID, claims.Username)
  42. if err != nil {
  43. sendAPIResponse(w, r, err, "", getRespStatus(err))
  44. return
  45. }
  46. share.HideConfidentialData()
  47. render.JSON(w, r, share)
  48. }
  49. func addShare(w http.ResponseWriter, r *http.Request) {
  50. r.Body = http.MaxBytesReader(w, r.Body, maxRequestSize)
  51. claims, err := getTokenClaims(r)
  52. if err != nil || claims.Username == "" {
  53. sendAPIResponse(w, r, err, "Invalid token claims", http.StatusBadRequest)
  54. return
  55. }
  56. var share dataprovider.Share
  57. err = render.DecodeJSON(r.Body, &share)
  58. if err != nil {
  59. sendAPIResponse(w, r, err, "", http.StatusBadRequest)
  60. return
  61. }
  62. share.ID = 0
  63. share.ShareID = util.GenerateUniqueID()
  64. share.LastUseAt = 0
  65. share.Username = claims.Username
  66. if share.Name == "" {
  67. share.Name = share.ShareID
  68. }
  69. err = dataprovider.AddShare(&share, claims.Username, util.GetIPFromRemoteAddress(r.RemoteAddr))
  70. if err != nil {
  71. sendAPIResponse(w, r, err, "", getRespStatus(err))
  72. return
  73. }
  74. w.Header().Add("Location", fmt.Sprintf("%v/%v", userSharesPath, share.ShareID))
  75. w.Header().Add("X-Object-ID", share.ShareID)
  76. sendAPIResponse(w, r, nil, "Share created", http.StatusCreated)
  77. }
  78. func updateShare(w http.ResponseWriter, r *http.Request) {
  79. r.Body = http.MaxBytesReader(w, r.Body, maxRequestSize)
  80. claims, err := getTokenClaims(r)
  81. if err != nil || claims.Username == "" {
  82. sendAPIResponse(w, r, err, "Invalid token claims", http.StatusBadRequest)
  83. return
  84. }
  85. shareID := getURLParam(r, "id")
  86. share, err := dataprovider.ShareExists(shareID, claims.Username)
  87. if err != nil {
  88. sendAPIResponse(w, r, err, "", getRespStatus(err))
  89. return
  90. }
  91. oldPassword := share.Password
  92. err = render.DecodeJSON(r.Body, &share)
  93. if err != nil {
  94. sendAPIResponse(w, r, err, "", http.StatusBadRequest)
  95. return
  96. }
  97. share.ShareID = shareID
  98. share.Username = claims.Username
  99. if share.Password == redactedSecret {
  100. share.Password = oldPassword
  101. }
  102. if err := dataprovider.UpdateShare(&share, claims.Username, util.GetIPFromRemoteAddress(r.RemoteAddr)); err != nil {
  103. sendAPIResponse(w, r, err, "", getRespStatus(err))
  104. return
  105. }
  106. sendAPIResponse(w, r, nil, "Share updated", http.StatusOK)
  107. }
  108. func deleteShare(w http.ResponseWriter, r *http.Request) {
  109. r.Body = http.MaxBytesReader(w, r.Body, maxRequestSize)
  110. shareID := getURLParam(r, "id")
  111. claims, err := getTokenClaims(r)
  112. if err != nil || claims.Username == "" {
  113. sendAPIResponse(w, r, err, "Invalid token claims", http.StatusBadRequest)
  114. return
  115. }
  116. err = dataprovider.DeleteShare(shareID, claims.Username, util.GetIPFromRemoteAddress(r.RemoteAddr))
  117. if err != nil {
  118. sendAPIResponse(w, r, err, "", getRespStatus(err))
  119. return
  120. }
  121. sendAPIResponse(w, r, err, "Share deleted", http.StatusOK)
  122. }
  123. func downloadFromShare(w http.ResponseWriter, r *http.Request) {
  124. r.Body = http.MaxBytesReader(w, r.Body, maxRequestSize)
  125. share, connection, err := checkPublicShare(w, r, dataprovider.ShareScopeRead)
  126. if err != nil {
  127. return
  128. }
  129. common.Connections.Add(connection)
  130. defer common.Connections.Remove(connection.GetID())
  131. compress := true
  132. var info os.FileInfo
  133. if len(share.Paths) > 0 && r.URL.Query().Get("compress") == "false" {
  134. info, err = connection.Stat(share.Paths[0], 0)
  135. if err != nil {
  136. sendAPIResponse(w, r, err, "", getRespStatus(err))
  137. return
  138. }
  139. if !info.IsDir() {
  140. compress = false
  141. }
  142. }
  143. dataprovider.UpdateShareLastUse(&share, 1) //nolint:errcheck
  144. if compress {
  145. w.Header().Set("Content-Disposition", fmt.Sprintf("attachment; filename=\"share-%v.zip\"", share.ShareID))
  146. renderCompressedFiles(w, connection, "/", share.Paths, &share)
  147. return
  148. }
  149. if status, err := downloadFile(w, r, connection, share.Paths[0], info, false); err != nil {
  150. dataprovider.UpdateShareLastUse(&share, -1) //nolint:errcheck
  151. resp := apiResponse{
  152. Error: err.Error(),
  153. Message: http.StatusText(status),
  154. }
  155. ctx := r.Context()
  156. if status != 0 {
  157. ctx = context.WithValue(ctx, render.StatusCtxKey, status)
  158. }
  159. render.JSON(w, r.WithContext(ctx), resp)
  160. }
  161. }
  162. func uploadFileToShare(w http.ResponseWriter, r *http.Request) {
  163. if maxUploadFileSize > 0 {
  164. r.Body = http.MaxBytesReader(w, r.Body, maxUploadFileSize)
  165. }
  166. name := getURLParam(r, "name")
  167. share, connection, err := checkPublicShare(w, r, dataprovider.ShareScopeWrite)
  168. if err != nil {
  169. return
  170. }
  171. filePath := path.Join(share.Paths[0], name)
  172. if path.Dir(filePath) != share.Paths[0] {
  173. sendAPIResponse(w, r, err, "Uploading outside the share is not allowed", http.StatusForbidden)
  174. return
  175. }
  176. dataprovider.UpdateShareLastUse(&share, 1) //nolint:errcheck
  177. common.Connections.Add(connection)
  178. defer common.Connections.Remove(connection.GetID())
  179. if err := doUploadFile(w, r, connection, filePath); err != nil {
  180. dataprovider.UpdateShareLastUse(&share, -1) //nolint:errcheck
  181. }
  182. }
  183. func uploadFilesToShare(w http.ResponseWriter, r *http.Request) {
  184. if maxUploadFileSize > 0 {
  185. r.Body = http.MaxBytesReader(w, r.Body, maxUploadFileSize)
  186. }
  187. share, connection, err := checkPublicShare(w, r, dataprovider.ShareScopeWrite)
  188. if err != nil {
  189. return
  190. }
  191. common.Connections.Add(connection)
  192. defer common.Connections.Remove(connection.GetID())
  193. t := newThrottledReader(r.Body, connection.User.UploadBandwidth, connection)
  194. r.Body = t
  195. err = r.ParseMultipartForm(maxMultipartMem)
  196. if err != nil {
  197. connection.RemoveTransfer(t)
  198. sendAPIResponse(w, r, err, "Unable to parse multipart form", http.StatusBadRequest)
  199. return
  200. }
  201. connection.RemoveTransfer(t)
  202. defer r.MultipartForm.RemoveAll() //nolint:errcheck
  203. files := r.MultipartForm.File["filenames"]
  204. if len(files) == 0 {
  205. sendAPIResponse(w, r, nil, "No files uploaded!", http.StatusBadRequest)
  206. return
  207. }
  208. if share.MaxTokens > 0 {
  209. if len(files) > (share.MaxTokens - share.UsedTokens) {
  210. sendAPIResponse(w, r, nil, "Allowed usage exceeded", http.StatusBadRequest)
  211. return
  212. }
  213. }
  214. dataprovider.UpdateShareLastUse(&share, len(files)) //nolint:errcheck
  215. numUploads := doUploadFiles(w, r, connection, share.Paths[0], files)
  216. if numUploads != len(files) {
  217. dataprovider.UpdateShareLastUse(&share, numUploads-len(files)) //nolint:errcheck
  218. }
  219. }
  220. func checkPublicShare(w http.ResponseWriter, r *http.Request, shareShope dataprovider.ShareScope,
  221. ) (dataprovider.Share, *Connection, error) {
  222. shareID := getURLParam(r, "id")
  223. share, err := dataprovider.ShareExists(shareID, "")
  224. if err != nil {
  225. sendAPIResponse(w, r, err, "", getRespStatus(err))
  226. return share, nil, err
  227. }
  228. if share.Scope != shareShope {
  229. sendAPIResponse(w, r, nil, "Invalid share scope", http.StatusForbidden)
  230. return share, nil, errors.New("invalid share scope")
  231. }
  232. ipAddr := util.GetIPFromRemoteAddress(r.RemoteAddr)
  233. ok, err := share.IsUsable(ipAddr)
  234. if !ok || err != nil {
  235. sendAPIResponse(w, r, err, "", getRespStatus(err))
  236. return share, nil, errors.New("login not allowed")
  237. }
  238. if share.Password != "" {
  239. _, password, ok := r.BasicAuth()
  240. if !ok {
  241. w.Header().Set(common.HTTPAuthenticationHeader, basicRealm)
  242. sendAPIResponse(w, r, nil, http.StatusText(http.StatusUnauthorized), http.StatusUnauthorized)
  243. return share, nil, dataprovider.ErrInvalidCredentials
  244. }
  245. match, err := share.CheckPassword(password)
  246. if !match || err != nil {
  247. w.Header().Set(common.HTTPAuthenticationHeader, basicRealm)
  248. sendAPIResponse(w, r, nil, http.StatusText(http.StatusUnauthorized), http.StatusUnauthorized)
  249. return share, nil, dataprovider.ErrInvalidCredentials
  250. }
  251. }
  252. user, err := dataprovider.UserExists(share.Username)
  253. if err != nil {
  254. sendAPIResponse(w, r, err, "", getRespStatus(err))
  255. return share, nil, err
  256. }
  257. connID := xid.New().String()
  258. connection := &Connection{
  259. BaseConnection: common.NewBaseConnection(connID, common.ProtocolHTTPShare, util.GetHTTPLocalAddress(r),
  260. r.RemoteAddr, user),
  261. request: r,
  262. }
  263. return share, connection, nil
  264. }