api_shares.go 9.3 KB

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