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/version"
  19. "github.com/drakkan/sftpgo/vfs"
  20. )
  21. const (
  22. templateBase = "base.html"
  23. templateUsers = "users.html"
  24. templateUser = "user.html"
  25. templateConnections = "connections.html"
  26. templateFolders = "folders.html"
  27. templateFolder = "folder.html"
  28. templateMessage = "message.html"
  29. pageUsersTitle = "Users"
  30. pageConnectionsTitle = "Connections"
  31. pageFoldersTitle = "Folders"
  32. page400Title = "Bad request"
  33. page404Title = "Not found"
  34. page404Body = "The page you are looking for does not exist."
  35. page500Title = "Internal Server Error"
  36. page500Body = "The server is unable to fulfill your request."
  37. defaultQueryLimit = 500
  38. webDateTimeFormat = "2006-01-02 15:04:05" // YYYY-MM-DD HH:MM:SS
  39. )
  40. var (
  41. templates = make(map[string]*template.Template)
  42. )
  43. type basePage struct {
  44. Title string
  45. CurrentURL string
  46. UsersURL string
  47. UserURL string
  48. APIUserURL string
  49. APIConnectionsURL string
  50. APIQuotaScanURL string
  51. ConnectionsURL string
  52. FoldersURL string
  53. FolderURL string
  54. APIFoldersURL string
  55. APIFolderQuotaScanURL string
  56. UsersTitle string
  57. ConnectionsTitle string
  58. FoldersTitle string
  59. Version string
  60. }
  61. type usersPage struct {
  62. basePage
  63. Users []dataprovider.User
  64. }
  65. type foldersPage struct {
  66. basePage
  67. Folders []vfs.BaseVirtualFolder
  68. }
  69. type connectionsPage struct {
  70. basePage
  71. Connections []sftpd.ConnectionStatus
  72. }
  73. type userPage struct {
  74. basePage
  75. IsAdd bool
  76. User dataprovider.User
  77. RootPerms []string
  78. Error string
  79. ValidPerms []string
  80. ValidSSHLoginMethods []string
  81. RootDirPerms []string
  82. }
  83. type folderPage struct {
  84. basePage
  85. Folder vfs.BaseVirtualFolder
  86. Error string
  87. }
  88. type messagePage struct {
  89. basePage
  90. Error string
  91. Success string
  92. }
  93. func loadTemplates(templatesPath string) {
  94. usersPaths := []string{
  95. filepath.Join(templatesPath, templateBase),
  96. filepath.Join(templatesPath, templateUsers),
  97. }
  98. userPaths := []string{
  99. filepath.Join(templatesPath, templateBase),
  100. filepath.Join(templatesPath, templateUser),
  101. }
  102. connectionsPaths := []string{
  103. filepath.Join(templatesPath, templateBase),
  104. filepath.Join(templatesPath, templateConnections),
  105. }
  106. messagePath := []string{
  107. filepath.Join(templatesPath, templateBase),
  108. filepath.Join(templatesPath, templateMessage),
  109. }
  110. foldersPath := []string{
  111. filepath.Join(templatesPath, templateBase),
  112. filepath.Join(templatesPath, templateFolders),
  113. }
  114. folderPath := []string{
  115. filepath.Join(templatesPath, templateBase),
  116. filepath.Join(templatesPath, templateFolder),
  117. }
  118. usersTmpl := utils.LoadTemplate(template.ParseFiles(usersPaths...))
  119. userTmpl := utils.LoadTemplate(template.ParseFiles(userPaths...))
  120. connectionsTmpl := utils.LoadTemplate(template.ParseFiles(connectionsPaths...))
  121. messageTmpl := utils.LoadTemplate(template.ParseFiles(messagePath...))
  122. foldersTmpl := utils.LoadTemplate(template.ParseFiles(foldersPath...))
  123. folderTmpl := utils.LoadTemplate(template.ParseFiles(folderPath...))
  124. templates[templateUsers] = usersTmpl
  125. templates[templateUser] = userTmpl
  126. templates[templateConnections] = connectionsTmpl
  127. templates[templateMessage] = messageTmpl
  128. templates[templateFolders] = foldersTmpl
  129. templates[templateFolder] = folderTmpl
  130. }
  131. func getBasePageData(title, currentURL string) basePage {
  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.GetAsString(),
  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. }