web.go 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663
  1. package httpd
  2. import (
  3. "encoding/base64"
  4. "errors"
  5. "fmt"
  6. "html/template"
  7. "io/ioutil"
  8. "net/http"
  9. "path"
  10. "path/filepath"
  11. "strconv"
  12. "strings"
  13. "time"
  14. "github.com/go-chi/chi"
  15. "github.com/drakkan/sftpgo/dataprovider"
  16. "github.com/drakkan/sftpgo/sftpd"
  17. "github.com/drakkan/sftpgo/utils"
  18. "github.com/drakkan/sftpgo/vfs"
  19. )
  20. const (
  21. templateBase = "base.html"
  22. templateUsers = "users.html"
  23. templateUser = "user.html"
  24. templateConnections = "connections.html"
  25. templateFolders = "folders.html"
  26. templateFolder = "folder.html"
  27. templateMessage = "message.html"
  28. pageUsersTitle = "Users"
  29. pageConnectionsTitle = "Connections"
  30. pageFoldersTitle = "Folders"
  31. page400Title = "Bad request"
  32. page404Title = "Not found"
  33. page404Body = "The page you are looking for does not exist."
  34. page500Title = "Internal Server Error"
  35. page500Body = "The server is unable to fulfill your request."
  36. defaultQueryLimit = 500
  37. webDateTimeFormat = "2006-01-02 15:04:05" // YYYY-MM-DD HH:MM:SS
  38. )
  39. var (
  40. templates = make(map[string]*template.Template)
  41. )
  42. type basePage struct {
  43. Title string
  44. CurrentURL string
  45. UsersURL string
  46. UserURL string
  47. APIUserURL string
  48. APIConnectionsURL string
  49. APIQuotaScanURL string
  50. ConnectionsURL string
  51. FoldersURL string
  52. FolderURL string
  53. APIFoldersURL string
  54. APIFolderQuotaScanURL string
  55. UsersTitle string
  56. ConnectionsTitle string
  57. FoldersTitle string
  58. Version string
  59. }
  60. type usersPage struct {
  61. basePage
  62. Users []dataprovider.User
  63. }
  64. type foldersPage struct {
  65. basePage
  66. Folders []vfs.BaseVirtualFolder
  67. }
  68. type connectionsPage struct {
  69. basePage
  70. Connections []sftpd.ConnectionStatus
  71. }
  72. type userPage struct {
  73. basePage
  74. IsAdd bool
  75. User dataprovider.User
  76. RootPerms []string
  77. Error string
  78. ValidPerms []string
  79. ValidSSHLoginMethods []string
  80. RootDirPerms []string
  81. }
  82. type folderPage struct {
  83. basePage
  84. Folder vfs.BaseVirtualFolder
  85. Error string
  86. }
  87. type messagePage struct {
  88. basePage
  89. Error string
  90. Success string
  91. }
  92. func loadTemplates(templatesPath string) {
  93. usersPaths := []string{
  94. filepath.Join(templatesPath, templateBase),
  95. filepath.Join(templatesPath, templateUsers),
  96. }
  97. userPaths := []string{
  98. filepath.Join(templatesPath, templateBase),
  99. filepath.Join(templatesPath, templateUser),
  100. }
  101. connectionsPaths := []string{
  102. filepath.Join(templatesPath, templateBase),
  103. filepath.Join(templatesPath, templateConnections),
  104. }
  105. messagePath := []string{
  106. filepath.Join(templatesPath, templateBase),
  107. filepath.Join(templatesPath, templateMessage),
  108. }
  109. foldersPath := []string{
  110. filepath.Join(templatesPath, templateBase),
  111. filepath.Join(templatesPath, templateFolders),
  112. }
  113. folderPath := []string{
  114. filepath.Join(templatesPath, templateBase),
  115. filepath.Join(templatesPath, templateFolder),
  116. }
  117. usersTmpl := utils.LoadTemplate(template.ParseFiles(usersPaths...))
  118. userTmpl := utils.LoadTemplate(template.ParseFiles(userPaths...))
  119. connectionsTmpl := utils.LoadTemplate(template.ParseFiles(connectionsPaths...))
  120. messageTmpl := utils.LoadTemplate(template.ParseFiles(messagePath...))
  121. foldersTmpl := utils.LoadTemplate(template.ParseFiles(foldersPath...))
  122. folderTmpl := utils.LoadTemplate(template.ParseFiles(folderPath...))
  123. templates[templateUsers] = usersTmpl
  124. templates[templateUser] = userTmpl
  125. templates[templateConnections] = connectionsTmpl
  126. templates[templateMessage] = messageTmpl
  127. templates[templateFolders] = foldersTmpl
  128. templates[templateFolder] = folderTmpl
  129. }
  130. func getBasePageData(title, currentURL string) basePage {
  131. version := utils.GetAppVersion()
  132. return basePage{
  133. Title: title,
  134. CurrentURL: currentURL,
  135. UsersURL: webUsersPath,
  136. UserURL: webUserPath,
  137. FoldersURL: webFoldersPath,
  138. FolderURL: webFolderPath,
  139. APIUserURL: userPath,
  140. APIConnectionsURL: activeConnectionsPath,
  141. APIQuotaScanURL: quotaScanPath,
  142. APIFoldersURL: folderPath,
  143. APIFolderQuotaScanURL: quotaScanVFolderPath,
  144. ConnectionsURL: webConnectionsPath,
  145. UsersTitle: pageUsersTitle,
  146. ConnectionsTitle: pageConnectionsTitle,
  147. FoldersTitle: pageFoldersTitle,
  148. Version: version.GetVersionAsString(),
  149. }
  150. }
  151. func renderTemplate(w http.ResponseWriter, tmplName string, data interface{}) {
  152. err := templates[tmplName].ExecuteTemplate(w, tmplName, data)
  153. if err != nil {
  154. http.Error(w, err.Error(), http.StatusInternalServerError)
  155. }
  156. }
  157. func renderMessagePage(w http.ResponseWriter, title, body string, statusCode int, err error, message string) {
  158. var errorString string
  159. if len(body) > 0 {
  160. errorString = body + " "
  161. }
  162. if err != nil {
  163. errorString += err.Error()
  164. }
  165. data := messagePage{
  166. basePage: getBasePageData(title, ""),
  167. Error: errorString,
  168. Success: message,
  169. }
  170. w.WriteHeader(statusCode)
  171. renderTemplate(w, templateMessage, data)
  172. }
  173. func renderInternalServerErrorPage(w http.ResponseWriter, err error) {
  174. renderMessagePage(w, page500Title, page500Body, http.StatusInternalServerError, err, "")
  175. }
  176. func renderBadRequestPage(w http.ResponseWriter, err error) {
  177. renderMessagePage(w, page400Title, "", http.StatusBadRequest, err, "")
  178. }
  179. func renderNotFoundPage(w http.ResponseWriter, err error) {
  180. renderMessagePage(w, page404Title, page404Body, http.StatusNotFound, err, "")
  181. }
  182. func renderAddUserPage(w http.ResponseWriter, user dataprovider.User, error string) {
  183. data := userPage{
  184. basePage: getBasePageData("Add a new user", webUserPath),
  185. IsAdd: true,
  186. Error: error,
  187. User: user,
  188. ValidPerms: dataprovider.ValidPerms,
  189. ValidSSHLoginMethods: dataprovider.ValidSSHLoginMethods,
  190. RootDirPerms: user.GetPermissionsForPath("/"),
  191. }
  192. renderTemplate(w, templateUser, data)
  193. }
  194. func renderUpdateUserPage(w http.ResponseWriter, user dataprovider.User, error string) {
  195. data := userPage{
  196. basePage: getBasePageData("Update user", fmt.Sprintf("%v/%v", webUserPath, user.ID)),
  197. IsAdd: false,
  198. Error: error,
  199. User: user,
  200. ValidPerms: dataprovider.ValidPerms,
  201. ValidSSHLoginMethods: dataprovider.ValidSSHLoginMethods,
  202. RootDirPerms: user.GetPermissionsForPath("/"),
  203. }
  204. renderTemplate(w, templateUser, data)
  205. }
  206. func renderAddFolderPage(w http.ResponseWriter, folder vfs.BaseVirtualFolder, error string) {
  207. data := folderPage{
  208. basePage: getBasePageData("Add a new folder", webFolderPath),
  209. Error: error,
  210. Folder: folder,
  211. }
  212. renderTemplate(w, templateFolder, data)
  213. }
  214. func getVirtualFoldersFromPostFields(r *http.Request) []vfs.VirtualFolder {
  215. var virtualFolders []vfs.VirtualFolder
  216. formValue := r.Form.Get("virtual_folders")
  217. for _, cleaned := range getSliceFromDelimitedValues(formValue, "\n") {
  218. if strings.Contains(cleaned, "::") {
  219. mapping := strings.Split(cleaned, "::")
  220. if len(mapping) > 1 {
  221. vfolder := vfs.VirtualFolder{
  222. BaseVirtualFolder: vfs.BaseVirtualFolder{
  223. MappedPath: strings.TrimSpace(mapping[1]),
  224. },
  225. VirtualPath: strings.TrimSpace(mapping[0]),
  226. QuotaFiles: -1,
  227. QuotaSize: -1,
  228. }
  229. if len(mapping) > 2 {
  230. quotaFiles, err := strconv.Atoi(strings.TrimSpace(mapping[2]))
  231. if err == nil {
  232. vfolder.QuotaFiles = quotaFiles
  233. }
  234. }
  235. if len(mapping) > 3 {
  236. quotaSize, err := strconv.ParseInt(strings.TrimSpace(mapping[3]), 10, 64)
  237. if err == nil {
  238. vfolder.QuotaSize = quotaSize
  239. }
  240. }
  241. virtualFolders = append(virtualFolders, vfolder)
  242. }
  243. }
  244. }
  245. return virtualFolders
  246. }
  247. func getUserPermissionsFromPostFields(r *http.Request) map[string][]string {
  248. permissions := make(map[string][]string)
  249. permissions["/"] = r.Form["permissions"]
  250. subDirsPermsValue := r.Form.Get("sub_dirs_permissions")
  251. for _, cleaned := range getSliceFromDelimitedValues(subDirsPermsValue, "\n") {
  252. if strings.Contains(cleaned, "::") {
  253. dirPerms := strings.Split(cleaned, "::")
  254. if len(dirPerms) > 1 {
  255. dir := dirPerms[0]
  256. dir = strings.TrimSpace(dir)
  257. perms := []string{}
  258. for _, p := range strings.Split(dirPerms[1], ",") {
  259. cleanedPerm := strings.TrimSpace(p)
  260. if len(cleanedPerm) > 0 {
  261. perms = append(perms, cleanedPerm)
  262. }
  263. }
  264. if len(dir) > 0 {
  265. permissions[dir] = perms
  266. }
  267. }
  268. }
  269. }
  270. return permissions
  271. }
  272. func getSliceFromDelimitedValues(values, delimiter string) []string {
  273. result := []string{}
  274. for _, v := range strings.Split(values, delimiter) {
  275. cleaned := strings.TrimSpace(v)
  276. if len(cleaned) > 0 {
  277. result = append(result, cleaned)
  278. }
  279. }
  280. return result
  281. }
  282. func getFileExtensionsFromPostField(value string, extesionsType int) []dataprovider.ExtensionsFilter {
  283. var result []dataprovider.ExtensionsFilter
  284. for _, cleaned := range getSliceFromDelimitedValues(value, "\n") {
  285. if strings.Contains(cleaned, "::") {
  286. dirExts := strings.Split(cleaned, "::")
  287. if len(dirExts) > 1 {
  288. dir := dirExts[0]
  289. dir = strings.TrimSpace(dir)
  290. exts := []string{}
  291. for _, e := range strings.Split(dirExts[1], ",") {
  292. cleanedExt := strings.TrimSpace(e)
  293. if len(cleanedExt) > 0 {
  294. exts = append(exts, cleanedExt)
  295. }
  296. }
  297. if len(dir) > 0 {
  298. filter := dataprovider.ExtensionsFilter{
  299. Path: dir,
  300. }
  301. if extesionsType == 1 {
  302. filter.AllowedExtensions = exts
  303. filter.DeniedExtensions = []string{}
  304. } else {
  305. filter.DeniedExtensions = exts
  306. filter.AllowedExtensions = []string{}
  307. }
  308. result = append(result, filter)
  309. }
  310. }
  311. }
  312. }
  313. return result
  314. }
  315. func getFiltersFromUserPostFields(r *http.Request) dataprovider.UserFilters {
  316. var filters dataprovider.UserFilters
  317. filters.AllowedIP = getSliceFromDelimitedValues(r.Form.Get("allowed_ip"), ",")
  318. filters.DeniedIP = getSliceFromDelimitedValues(r.Form.Get("denied_ip"), ",")
  319. filters.DeniedLoginMethods = r.Form["ssh_login_methods"]
  320. allowedExtensions := getFileExtensionsFromPostField(r.Form.Get("allowed_extensions"), 1)
  321. deniedExtensions := getFileExtensionsFromPostField(r.Form.Get("denied_extensions"), 2)
  322. extensions := []dataprovider.ExtensionsFilter{}
  323. if len(allowedExtensions) > 0 && len(deniedExtensions) > 0 {
  324. for _, allowed := range allowedExtensions {
  325. for _, denied := range deniedExtensions {
  326. if path.Clean(allowed.Path) == path.Clean(denied.Path) {
  327. allowed.DeniedExtensions = append(allowed.DeniedExtensions, denied.DeniedExtensions...)
  328. }
  329. }
  330. extensions = append(extensions, allowed)
  331. }
  332. for _, denied := range deniedExtensions {
  333. found := false
  334. for _, allowed := range allowedExtensions {
  335. if path.Clean(denied.Path) == path.Clean(allowed.Path) {
  336. found = true
  337. break
  338. }
  339. }
  340. if !found {
  341. extensions = append(extensions, denied)
  342. }
  343. }
  344. } else if len(allowedExtensions) > 0 {
  345. extensions = append(extensions, allowedExtensions...)
  346. } else if len(deniedExtensions) > 0 {
  347. extensions = append(extensions, deniedExtensions...)
  348. }
  349. filters.FileExtensions = extensions
  350. return filters
  351. }
  352. func getFsConfigFromUserPostFields(r *http.Request) (dataprovider.Filesystem, error) {
  353. var fs dataprovider.Filesystem
  354. provider, err := strconv.Atoi(r.Form.Get("fs_provider"))
  355. if err != nil {
  356. provider = 0
  357. }
  358. fs.Provider = provider
  359. if fs.Provider == 1 {
  360. fs.S3Config.Bucket = r.Form.Get("s3_bucket")
  361. fs.S3Config.Region = r.Form.Get("s3_region")
  362. fs.S3Config.AccessKey = r.Form.Get("s3_access_key")
  363. fs.S3Config.AccessSecret = r.Form.Get("s3_access_secret")
  364. fs.S3Config.Endpoint = r.Form.Get("s3_endpoint")
  365. fs.S3Config.StorageClass = r.Form.Get("s3_storage_class")
  366. fs.S3Config.KeyPrefix = r.Form.Get("s3_key_prefix")
  367. fs.S3Config.UploadPartSize, err = strconv.ParseInt(r.Form.Get("s3_upload_part_size"), 10, 64)
  368. if err != nil {
  369. return fs, err
  370. }
  371. fs.S3Config.UploadConcurrency, err = strconv.Atoi(r.Form.Get("s3_upload_concurrency"))
  372. if err != nil {
  373. return fs, err
  374. }
  375. } else if fs.Provider == 2 {
  376. fs.GCSConfig.Bucket = r.Form.Get("gcs_bucket")
  377. fs.GCSConfig.StorageClass = r.Form.Get("gcs_storage_class")
  378. fs.GCSConfig.KeyPrefix = r.Form.Get("gcs_key_prefix")
  379. autoCredentials := r.Form.Get("gcs_auto_credentials")
  380. if len(autoCredentials) > 0 {
  381. fs.GCSConfig.AutomaticCredentials = 1
  382. } else {
  383. fs.GCSConfig.AutomaticCredentials = 0
  384. }
  385. credentials, _, err := r.FormFile("gcs_credential_file")
  386. if err == http.ErrMissingFile {
  387. return fs, nil
  388. }
  389. if err != nil {
  390. return fs, err
  391. }
  392. defer credentials.Close()
  393. fileBytes, err := ioutil.ReadAll(credentials)
  394. if err != nil || len(fileBytes) == 0 {
  395. if len(fileBytes) == 0 {
  396. err = errors.New("credentials file size must be greater than 0")
  397. }
  398. return fs, err
  399. }
  400. fs.GCSConfig.Credentials = base64.StdEncoding.EncodeToString(fileBytes)
  401. fs.GCSConfig.AutomaticCredentials = 0
  402. }
  403. return fs, nil
  404. }
  405. func getUserFromPostFields(r *http.Request) (dataprovider.User, error) {
  406. var user dataprovider.User
  407. err := r.ParseMultipartForm(maxRequestSize)
  408. if err != nil {
  409. return user, err
  410. }
  411. publicKeysFormValue := r.Form.Get("public_keys")
  412. publicKeys := getSliceFromDelimitedValues(publicKeysFormValue, "\n")
  413. uid, err := strconv.Atoi(r.Form.Get("uid"))
  414. if err != nil {
  415. return user, err
  416. }
  417. gid, err := strconv.Atoi(r.Form.Get("gid"))
  418. if err != nil {
  419. return user, err
  420. }
  421. maxSessions, err := strconv.Atoi(r.Form.Get("max_sessions"))
  422. if err != nil {
  423. return user, err
  424. }
  425. quotaSize, err := strconv.ParseInt(r.Form.Get("quota_size"), 10, 64)
  426. if err != nil {
  427. return user, err
  428. }
  429. quotaFiles, err := strconv.Atoi(r.Form.Get("quota_files"))
  430. if err != nil {
  431. return user, err
  432. }
  433. bandwidthUL, err := strconv.ParseInt(r.Form.Get("upload_bandwidth"), 10, 64)
  434. if err != nil {
  435. return user, err
  436. }
  437. bandwidthDL, err := strconv.ParseInt(r.Form.Get("download_bandwidth"), 10, 64)
  438. if err != nil {
  439. return user, err
  440. }
  441. status, err := strconv.Atoi(r.Form.Get("status"))
  442. if err != nil {
  443. return user, err
  444. }
  445. expirationDateMillis := int64(0)
  446. expirationDateString := r.Form.Get("expiration_date")
  447. if len(strings.TrimSpace(expirationDateString)) > 0 {
  448. expirationDate, err := time.Parse(webDateTimeFormat, expirationDateString)
  449. if err != nil {
  450. return user, err
  451. }
  452. expirationDateMillis = utils.GetTimeAsMsSinceEpoch(expirationDate)
  453. }
  454. fsConfig, err := getFsConfigFromUserPostFields(r)
  455. if err != nil {
  456. return user, err
  457. }
  458. user = dataprovider.User{
  459. Username: r.Form.Get("username"),
  460. Password: r.Form.Get("password"),
  461. PublicKeys: publicKeys,
  462. HomeDir: r.Form.Get("home_dir"),
  463. VirtualFolders: getVirtualFoldersFromPostFields(r),
  464. UID: uid,
  465. GID: gid,
  466. Permissions: getUserPermissionsFromPostFields(r),
  467. MaxSessions: maxSessions,
  468. QuotaSize: quotaSize,
  469. QuotaFiles: quotaFiles,
  470. UploadBandwidth: bandwidthUL,
  471. DownloadBandwidth: bandwidthDL,
  472. Status: status,
  473. ExpirationDate: expirationDateMillis,
  474. Filters: getFiltersFromUserPostFields(r),
  475. FsConfig: fsConfig,
  476. }
  477. return user, err
  478. }
  479. func handleGetWebUsers(w http.ResponseWriter, r *http.Request) {
  480. limit := defaultQueryLimit
  481. if _, ok := r.URL.Query()["qlimit"]; ok {
  482. var err error
  483. limit, err = strconv.Atoi(r.URL.Query().Get("qlimit"))
  484. if err != nil {
  485. limit = defaultQueryLimit
  486. }
  487. }
  488. users := make([]dataprovider.User, 0, limit)
  489. for {
  490. u, err := dataprovider.GetUsers(dataProvider, limit, len(users), dataprovider.OrderASC, "")
  491. if err != nil {
  492. renderInternalServerErrorPage(w, err)
  493. return
  494. }
  495. users = append(users, u...)
  496. if len(u) < limit {
  497. break
  498. }
  499. }
  500. data := usersPage{
  501. basePage: getBasePageData(pageUsersTitle, webUsersPath),
  502. Users: users,
  503. }
  504. renderTemplate(w, templateUsers, data)
  505. }
  506. func handleWebAddUserGet(w http.ResponseWriter, r *http.Request) {
  507. renderAddUserPage(w, dataprovider.User{Status: 1}, "")
  508. }
  509. func handleWebUpdateUserGet(w http.ResponseWriter, r *http.Request) {
  510. id, err := strconv.ParseInt(chi.URLParam(r, "userID"), 10, 64)
  511. if err != nil {
  512. renderBadRequestPage(w, err)
  513. return
  514. }
  515. user, err := dataprovider.GetUserByID(dataProvider, id)
  516. if err == nil {
  517. renderUpdateUserPage(w, user, "")
  518. } else if _, ok := err.(*dataprovider.RecordNotFoundError); ok {
  519. renderNotFoundPage(w, err)
  520. } else {
  521. renderInternalServerErrorPage(w, err)
  522. }
  523. }
  524. func handleWebAddUserPost(w http.ResponseWriter, r *http.Request) {
  525. r.Body = http.MaxBytesReader(w, r.Body, maxRequestSize)
  526. user, err := getUserFromPostFields(r)
  527. if err != nil {
  528. renderAddUserPage(w, user, err.Error())
  529. return
  530. }
  531. err = dataprovider.AddUser(dataProvider, user)
  532. if err == nil {
  533. http.Redirect(w, r, webUsersPath, http.StatusSeeOther)
  534. } else {
  535. renderAddUserPage(w, user, err.Error())
  536. }
  537. }
  538. func handleWebUpdateUserPost(w http.ResponseWriter, r *http.Request) {
  539. r.Body = http.MaxBytesReader(w, r.Body, maxRequestSize)
  540. id, err := strconv.ParseInt(chi.URLParam(r, "userID"), 10, 64)
  541. if err != nil {
  542. renderBadRequestPage(w, err)
  543. return
  544. }
  545. user, err := dataprovider.GetUserByID(dataProvider, id)
  546. if _, ok := err.(*dataprovider.RecordNotFoundError); ok {
  547. renderNotFoundPage(w, err)
  548. return
  549. } else if err != nil {
  550. renderInternalServerErrorPage(w, err)
  551. return
  552. }
  553. updatedUser, err := getUserFromPostFields(r)
  554. if err != nil {
  555. renderUpdateUserPage(w, user, err.Error())
  556. return
  557. }
  558. updatedUser.ID = user.ID
  559. if len(updatedUser.Password) == 0 {
  560. updatedUser.Password = user.Password
  561. }
  562. err = dataprovider.UpdateUser(dataProvider, updatedUser)
  563. if err == nil {
  564. http.Redirect(w, r, webUsersPath, http.StatusSeeOther)
  565. } else {
  566. renderUpdateUserPage(w, user, err.Error())
  567. }
  568. }
  569. func handleWebGetConnections(w http.ResponseWriter, r *http.Request) {
  570. connectionStats := sftpd.GetConnectionsStats()
  571. data := connectionsPage{
  572. basePage: getBasePageData(pageConnectionsTitle, webConnectionsPath),
  573. Connections: connectionStats,
  574. }
  575. renderTemplate(w, templateConnections, data)
  576. }
  577. func handleWebAddFolderGet(w http.ResponseWriter, r *http.Request) {
  578. renderAddFolderPage(w, vfs.BaseVirtualFolder{}, "")
  579. }
  580. func handleWebAddFolderPost(w http.ResponseWriter, r *http.Request) {
  581. r.Body = http.MaxBytesReader(w, r.Body, maxRequestSize)
  582. folder := vfs.BaseVirtualFolder{}
  583. err := r.ParseForm()
  584. if err != nil {
  585. renderAddFolderPage(w, folder, err.Error())
  586. return
  587. }
  588. folder.MappedPath = r.Form.Get("mapped_path")
  589. err = dataprovider.AddFolder(dataProvider, folder)
  590. if err == nil {
  591. http.Redirect(w, r, webFoldersPath, http.StatusSeeOther)
  592. } else {
  593. renderAddFolderPage(w, folder, err.Error())
  594. }
  595. }
  596. func handleWebGetFolders(w http.ResponseWriter, r *http.Request) {
  597. limit := defaultQueryLimit
  598. if _, ok := r.URL.Query()["qlimit"]; ok {
  599. var err error
  600. limit, err = strconv.Atoi(r.URL.Query().Get("qlimit"))
  601. if err != nil {
  602. limit = defaultQueryLimit
  603. }
  604. }
  605. folders := make([]vfs.BaseVirtualFolder, 0, limit)
  606. for {
  607. f, err := dataprovider.GetFolders(dataProvider, limit, len(folders), dataprovider.OrderASC, "")
  608. if err != nil {
  609. renderInternalServerErrorPage(w, err)
  610. return
  611. }
  612. folders = append(folders, f...)
  613. if len(f) < limit {
  614. break
  615. }
  616. }
  617. data := foldersPage{
  618. basePage: getBasePageData(pageFoldersTitle, webFoldersPath),
  619. Folders: folders,
  620. }
  621. renderTemplate(w, templateFolders, data)
  622. }