webclient.go 29 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959
  1. package httpd
  2. import (
  3. "bytes"
  4. "encoding/json"
  5. "fmt"
  6. "html/template"
  7. "io"
  8. "net/http"
  9. "net/url"
  10. "os"
  11. "path"
  12. "path/filepath"
  13. "strconv"
  14. "strings"
  15. "time"
  16. "github.com/go-chi/render"
  17. "github.com/rs/xid"
  18. "github.com/drakkan/sftpgo/v2/common"
  19. "github.com/drakkan/sftpgo/v2/dataprovider"
  20. "github.com/drakkan/sftpgo/v2/mfa"
  21. "github.com/drakkan/sftpgo/v2/sdk"
  22. "github.com/drakkan/sftpgo/v2/util"
  23. "github.com/drakkan/sftpgo/v2/version"
  24. "github.com/drakkan/sftpgo/v2/vfs"
  25. )
  26. const (
  27. templateClientDir = "webclient"
  28. templateClientBase = "base.html"
  29. templateClientBaseLogin = "baselogin.html"
  30. templateClientLogin = "login.html"
  31. templateClientFiles = "files.html"
  32. templateClientMessage = "message.html"
  33. templateClientProfile = "profile.html"
  34. templateClientChangePwd = "changepassword.html"
  35. templateClientTwoFactor = "twofactor.html"
  36. templateClientTwoFactorRecovery = "twofactor-recovery.html"
  37. templateClientMFA = "mfa.html"
  38. templateClientEditFile = "editfile.html"
  39. templateClientShare = "share.html"
  40. templateClientShares = "shares.html"
  41. pageClientFilesTitle = "My Files"
  42. pageClientSharesTitle = "Shares"
  43. pageClientProfileTitle = "My Profile"
  44. pageClientChangePwdTitle = "Change password"
  45. pageClient2FATitle = "Two-factor auth"
  46. pageClientEditFileTitle = "Edit file"
  47. )
  48. // condResult is the result of an HTTP request precondition check.
  49. // See https://tools.ietf.org/html/rfc7232 section 3.
  50. type condResult int
  51. const (
  52. condNone condResult = iota
  53. condTrue
  54. condFalse
  55. )
  56. var (
  57. clientTemplates = make(map[string]*template.Template)
  58. unixEpochTime = time.Unix(0, 0)
  59. )
  60. // isZeroTime reports whether t is obviously unspecified (either zero or Unix()=0).
  61. func isZeroTime(t time.Time) bool {
  62. return t.IsZero() || t.Equal(unixEpochTime)
  63. }
  64. type baseClientPage struct {
  65. Title string
  66. CurrentURL string
  67. FilesURL string
  68. SharesURL string
  69. ShareURL string
  70. ProfileURL string
  71. ChangePwdURL string
  72. StaticURL string
  73. LogoutURL string
  74. MFAURL string
  75. MFATitle string
  76. FilesTitle string
  77. SharesTitle string
  78. ProfileTitle string
  79. Version string
  80. CSRFToken string
  81. LoggedUser *dataprovider.User
  82. }
  83. type dirMapping struct {
  84. DirName string
  85. Href string
  86. }
  87. type editFilePage struct {
  88. baseClientPage
  89. CurrentDir string
  90. Path string
  91. Name string
  92. Data string
  93. }
  94. type filesPage struct {
  95. baseClientPage
  96. CurrentDir string
  97. DirsURL string
  98. DownloadURL string
  99. CanAddFiles bool
  100. CanCreateDirs bool
  101. CanRename bool
  102. CanDelete bool
  103. CanDownload bool
  104. CanShare bool
  105. Error string
  106. Paths []dirMapping
  107. }
  108. type clientMessagePage struct {
  109. baseClientPage
  110. Error string
  111. Success string
  112. }
  113. type clientProfilePage struct {
  114. baseClientPage
  115. PublicKeys []string
  116. CanSubmit bool
  117. AllowAPIKeyAuth bool
  118. Email string
  119. Description string
  120. Error string
  121. }
  122. type changeClientPasswordPage struct {
  123. baseClientPage
  124. Error string
  125. }
  126. type clientMFAPage struct {
  127. baseClientPage
  128. TOTPConfigs []string
  129. TOTPConfig sdk.TOTPConfig
  130. GenerateTOTPURL string
  131. ValidateTOTPURL string
  132. SaveTOTPURL string
  133. RecCodesURL string
  134. Protocols []string
  135. }
  136. type clientSharesPage struct {
  137. baseClientPage
  138. Shares []dataprovider.Share
  139. BasePublicSharesURL string
  140. }
  141. type clientSharePage struct {
  142. baseClientPage
  143. Share *dataprovider.Share
  144. Error string
  145. IsAdd bool
  146. }
  147. func getFileObjectURL(baseDir, name string) string {
  148. return fmt.Sprintf("%v?path=%v&_=%v", webClientFilesPath, url.QueryEscape(path.Join(baseDir, name)), time.Now().UTC().Unix())
  149. }
  150. func getFileObjectModTime(t time.Time) string {
  151. if isZeroTime(t) {
  152. return ""
  153. }
  154. return t.Format("2006-01-02 15:04")
  155. }
  156. func loadClientTemplates(templatesPath string) {
  157. filesPaths := []string{
  158. filepath.Join(templatesPath, templateClientDir, templateClientBase),
  159. filepath.Join(templatesPath, templateClientDir, templateClientFiles),
  160. }
  161. editFilePath := []string{
  162. filepath.Join(templatesPath, templateClientDir, templateClientBase),
  163. filepath.Join(templatesPath, templateClientDir, templateClientEditFile),
  164. }
  165. sharesPaths := []string{
  166. filepath.Join(templatesPath, templateClientDir, templateClientBase),
  167. filepath.Join(templatesPath, templateClientDir, templateClientShares),
  168. }
  169. sharePaths := []string{
  170. filepath.Join(templatesPath, templateClientDir, templateClientBase),
  171. filepath.Join(templatesPath, templateClientDir, templateClientShare),
  172. }
  173. profilePaths := []string{
  174. filepath.Join(templatesPath, templateClientDir, templateClientBase),
  175. filepath.Join(templatesPath, templateClientDir, templateClientProfile),
  176. }
  177. changePwdPaths := []string{
  178. filepath.Join(templatesPath, templateClientDir, templateClientBase),
  179. filepath.Join(templatesPath, templateClientDir, templateClientChangePwd),
  180. }
  181. loginPath := []string{
  182. filepath.Join(templatesPath, templateClientDir, templateClientBaseLogin),
  183. filepath.Join(templatesPath, templateClientDir, templateClientLogin),
  184. }
  185. messagePath := []string{
  186. filepath.Join(templatesPath, templateClientDir, templateClientBase),
  187. filepath.Join(templatesPath, templateClientDir, templateClientMessage),
  188. }
  189. mfaPath := []string{
  190. filepath.Join(templatesPath, templateClientDir, templateClientBase),
  191. filepath.Join(templatesPath, templateClientDir, templateClientMFA),
  192. }
  193. twoFactorPath := []string{
  194. filepath.Join(templatesPath, templateClientDir, templateClientBaseLogin),
  195. filepath.Join(templatesPath, templateClientDir, templateClientTwoFactor),
  196. }
  197. twoFactorRecoveryPath := []string{
  198. filepath.Join(templatesPath, templateClientDir, templateClientBaseLogin),
  199. filepath.Join(templatesPath, templateClientDir, templateClientTwoFactorRecovery),
  200. }
  201. filesTmpl := util.LoadTemplate(nil, filesPaths...)
  202. profileTmpl := util.LoadTemplate(nil, profilePaths...)
  203. changePwdTmpl := util.LoadTemplate(nil, changePwdPaths...)
  204. loginTmpl := util.LoadTemplate(nil, loginPath...)
  205. messageTmpl := util.LoadTemplate(nil, messagePath...)
  206. mfaTmpl := util.LoadTemplate(nil, mfaPath...)
  207. twoFactorTmpl := util.LoadTemplate(nil, twoFactorPath...)
  208. twoFactorRecoveryTmpl := util.LoadTemplate(nil, twoFactorRecoveryPath...)
  209. editFileTmpl := util.LoadTemplate(nil, editFilePath...)
  210. sharesTmpl := util.LoadTemplate(nil, sharesPaths...)
  211. shareTmpl := util.LoadTemplate(nil, sharePaths...)
  212. clientTemplates[templateClientFiles] = filesTmpl
  213. clientTemplates[templateClientProfile] = profileTmpl
  214. clientTemplates[templateClientChangePwd] = changePwdTmpl
  215. clientTemplates[templateClientLogin] = loginTmpl
  216. clientTemplates[templateClientMessage] = messageTmpl
  217. clientTemplates[templateClientMFA] = mfaTmpl
  218. clientTemplates[templateClientTwoFactor] = twoFactorTmpl
  219. clientTemplates[templateClientTwoFactorRecovery] = twoFactorRecoveryTmpl
  220. clientTemplates[templateClientEditFile] = editFileTmpl
  221. clientTemplates[templateClientShares] = sharesTmpl
  222. clientTemplates[templateClientShare] = shareTmpl
  223. }
  224. func getBaseClientPageData(title, currentURL string, r *http.Request) baseClientPage {
  225. var csrfToken string
  226. if currentURL != "" {
  227. csrfToken = createCSRFToken()
  228. }
  229. v := version.Get()
  230. return baseClientPage{
  231. Title: title,
  232. CurrentURL: currentURL,
  233. FilesURL: webClientFilesPath,
  234. SharesURL: webClientSharesPath,
  235. ShareURL: webClientSharePath,
  236. ProfileURL: webClientProfilePath,
  237. ChangePwdURL: webChangeClientPwdPath,
  238. StaticURL: webStaticFilesPath,
  239. LogoutURL: webClientLogoutPath,
  240. MFAURL: webClientMFAPath,
  241. MFATitle: pageClient2FATitle,
  242. FilesTitle: pageClientFilesTitle,
  243. SharesTitle: pageClientSharesTitle,
  244. ProfileTitle: pageClientProfileTitle,
  245. Version: fmt.Sprintf("%v-%v", v.Version, v.CommitHash),
  246. CSRFToken: csrfToken,
  247. LoggedUser: getUserFromToken(r),
  248. }
  249. }
  250. func renderClientTemplate(w http.ResponseWriter, tmplName string, data interface{}) {
  251. err := clientTemplates[tmplName].ExecuteTemplate(w, tmplName, data)
  252. if err != nil {
  253. http.Error(w, err.Error(), http.StatusInternalServerError)
  254. }
  255. }
  256. func renderClientMessagePage(w http.ResponseWriter, r *http.Request, title, body string, statusCode int, err error, message string) {
  257. var errorString string
  258. if body != "" {
  259. errorString = body + " "
  260. }
  261. if err != nil {
  262. errorString += err.Error()
  263. }
  264. data := clientMessagePage{
  265. baseClientPage: getBaseClientPageData(title, "", r),
  266. Error: errorString,
  267. Success: message,
  268. }
  269. w.WriteHeader(statusCode)
  270. renderClientTemplate(w, templateClientMessage, data)
  271. }
  272. func renderClientInternalServerErrorPage(w http.ResponseWriter, r *http.Request, err error) {
  273. renderClientMessagePage(w, r, page500Title, page500Body, http.StatusInternalServerError, err, "")
  274. }
  275. func renderClientBadRequestPage(w http.ResponseWriter, r *http.Request, err error) {
  276. renderClientMessagePage(w, r, page400Title, "", http.StatusBadRequest, err, "")
  277. }
  278. func renderClientForbiddenPage(w http.ResponseWriter, r *http.Request, body string) {
  279. renderClientMessagePage(w, r, page403Title, "", http.StatusForbidden, nil, body)
  280. }
  281. func renderClientNotFoundPage(w http.ResponseWriter, r *http.Request, err error) {
  282. renderClientMessagePage(w, r, page404Title, page404Body, http.StatusNotFound, err, "")
  283. }
  284. func renderClientTwoFactorPage(w http.ResponseWriter, error string) {
  285. data := twoFactorPage{
  286. CurrentURL: webClientTwoFactorPath,
  287. Version: version.Get().Version,
  288. Error: error,
  289. CSRFToken: createCSRFToken(),
  290. StaticURL: webStaticFilesPath,
  291. RecoveryURL: webClientTwoFactorRecoveryPath,
  292. }
  293. renderClientTemplate(w, templateTwoFactor, data)
  294. }
  295. func renderClientTwoFactorRecoveryPage(w http.ResponseWriter, error string) {
  296. data := twoFactorPage{
  297. CurrentURL: webClientTwoFactorRecoveryPath,
  298. Version: version.Get().Version,
  299. Error: error,
  300. CSRFToken: createCSRFToken(),
  301. StaticURL: webStaticFilesPath,
  302. }
  303. renderClientTemplate(w, templateTwoFactorRecovery, data)
  304. }
  305. func renderClientMFAPage(w http.ResponseWriter, r *http.Request) {
  306. data := clientMFAPage{
  307. baseClientPage: getBaseClientPageData(pageMFATitle, webClientMFAPath, r),
  308. TOTPConfigs: mfa.GetAvailableTOTPConfigNames(),
  309. GenerateTOTPURL: webClientTOTPGeneratePath,
  310. ValidateTOTPURL: webClientTOTPValidatePath,
  311. SaveTOTPURL: webClientTOTPSavePath,
  312. RecCodesURL: webClientRecoveryCodesPath,
  313. Protocols: dataprovider.MFAProtocols,
  314. }
  315. user, err := dataprovider.UserExists(data.LoggedUser.Username)
  316. if err != nil {
  317. renderInternalServerErrorPage(w, r, err)
  318. return
  319. }
  320. data.TOTPConfig = user.Filters.TOTPConfig
  321. renderClientTemplate(w, templateClientMFA, data)
  322. }
  323. func renderEditFilePage(w http.ResponseWriter, r *http.Request, fileName, fileData string) {
  324. data := editFilePage{
  325. baseClientPage: getBaseClientPageData(pageClientEditFileTitle, webClientEditFilePath, r),
  326. Path: fileName,
  327. Name: path.Base(fileName),
  328. CurrentDir: path.Dir(fileName),
  329. Data: fileData,
  330. }
  331. renderClientTemplate(w, templateClientEditFile, data)
  332. }
  333. func renderAddUpdateSharePage(w http.ResponseWriter, r *http.Request, share *dataprovider.Share,
  334. error string, isAdd bool) {
  335. currentURL := webClientSharePath
  336. title := "Add a new share"
  337. if !isAdd {
  338. currentURL = fmt.Sprintf("%v/%v", webClientSharePath, url.PathEscape(share.ShareID))
  339. title = "Update share"
  340. }
  341. data := clientSharePage{
  342. baseClientPage: getBaseClientPageData(title, currentURL, r),
  343. Share: share,
  344. Error: error,
  345. IsAdd: isAdd,
  346. }
  347. renderClientTemplate(w, templateClientShare, data)
  348. }
  349. func renderFilesPage(w http.ResponseWriter, r *http.Request, dirName, error string, user dataprovider.User) {
  350. data := filesPage{
  351. baseClientPage: getBaseClientPageData(pageClientFilesTitle, webClientFilesPath, r),
  352. Error: error,
  353. CurrentDir: url.QueryEscape(dirName),
  354. DownloadURL: webClientDownloadZipPath,
  355. DirsURL: webClientDirsPath,
  356. CanAddFiles: user.CanAddFilesFromWeb(dirName),
  357. CanCreateDirs: user.CanAddDirsFromWeb(dirName),
  358. CanRename: user.CanRenameFromWeb(dirName, dirName),
  359. CanDelete: user.CanDeleteFromWeb(dirName),
  360. CanDownload: user.HasPerm(dataprovider.PermDownload, dirName),
  361. CanShare: user.CanManageShares(),
  362. }
  363. paths := []dirMapping{}
  364. if dirName != "/" {
  365. paths = append(paths, dirMapping{
  366. DirName: path.Base(dirName),
  367. Href: "",
  368. })
  369. for {
  370. dirName = path.Dir(dirName)
  371. if dirName == "/" || dirName == "." {
  372. break
  373. }
  374. paths = append([]dirMapping{{
  375. DirName: path.Base(dirName),
  376. Href: getFileObjectURL("/", dirName)},
  377. }, paths...)
  378. }
  379. }
  380. data.Paths = paths
  381. renderClientTemplate(w, templateClientFiles, data)
  382. }
  383. func renderClientProfilePage(w http.ResponseWriter, r *http.Request, error string) {
  384. data := clientProfilePage{
  385. baseClientPage: getBaseClientPageData(pageClientProfileTitle, webClientProfilePath, r),
  386. Error: error,
  387. }
  388. user, err := dataprovider.UserExists(data.LoggedUser.Username)
  389. if err != nil {
  390. renderClientInternalServerErrorPage(w, r, err)
  391. return
  392. }
  393. data.PublicKeys = user.PublicKeys
  394. data.AllowAPIKeyAuth = user.Filters.AllowAPIKeyAuth
  395. data.Email = user.Email
  396. data.Description = user.Description
  397. data.CanSubmit = user.CanChangeAPIKeyAuth() || user.CanManagePublicKeys() || user.CanChangeInfo()
  398. renderClientTemplate(w, templateClientProfile, data)
  399. }
  400. func renderClientChangePasswordPage(w http.ResponseWriter, r *http.Request, error string) {
  401. data := changeClientPasswordPage{
  402. baseClientPage: getBaseClientPageData(pageClientChangePwdTitle, webChangeClientPwdPath, r),
  403. Error: error,
  404. }
  405. renderClientTemplate(w, templateClientChangePwd, data)
  406. }
  407. func handleWebClientLogout(w http.ResponseWriter, r *http.Request) {
  408. r.Body = http.MaxBytesReader(w, r.Body, maxLoginBodySize)
  409. c := jwtTokenClaims{}
  410. c.removeCookie(w, r, webBaseClientPath)
  411. http.Redirect(w, r, webClientLoginPath, http.StatusFound)
  412. }
  413. func handleWebClientDownloadZip(w http.ResponseWriter, r *http.Request) {
  414. r.Body = http.MaxBytesReader(w, r.Body, maxRequestSize)
  415. claims, err := getTokenClaims(r)
  416. if err != nil || claims.Username == "" {
  417. renderClientMessagePage(w, r, "Invalid token claims", "", http.StatusForbidden, nil, "")
  418. return
  419. }
  420. user, err := dataprovider.UserExists(claims.Username)
  421. if err != nil {
  422. renderClientMessagePage(w, r, "Unable to retrieve your user", "", getRespStatus(err), nil, "")
  423. return
  424. }
  425. connID := xid.New().String()
  426. connectionID := fmt.Sprintf("%v_%v", common.ProtocolHTTP, connID)
  427. if err := checkHTTPClientUser(&user, r, connectionID); err != nil {
  428. renderClientForbiddenPage(w, r, err.Error())
  429. return
  430. }
  431. connection := &Connection{
  432. BaseConnection: common.NewBaseConnection(connID, common.ProtocolHTTP, util.GetHTTPLocalAddress(r),
  433. r.RemoteAddr, user),
  434. request: r,
  435. }
  436. common.Connections.Add(connection)
  437. defer common.Connections.Remove(connection.GetID())
  438. name := "/"
  439. if _, ok := r.URL.Query()["path"]; ok {
  440. name = util.CleanPath(r.URL.Query().Get("path"))
  441. }
  442. files := r.URL.Query().Get("files")
  443. var filesList []string
  444. err = json.Unmarshal([]byte(files), &filesList)
  445. if err != nil {
  446. renderClientMessagePage(w, r, "Unable to get files list", "", http.StatusInternalServerError, err, "")
  447. return
  448. }
  449. w.Header().Set("Content-Disposition", "attachment; filename=\"sftpgo-download.zip\"")
  450. renderCompressedFiles(w, connection, name, filesList, nil)
  451. }
  452. func handleClientGetDirContents(w http.ResponseWriter, r *http.Request) {
  453. r.Body = http.MaxBytesReader(w, r.Body, maxRequestSize)
  454. claims, err := getTokenClaims(r)
  455. if err != nil || claims.Username == "" {
  456. sendAPIResponse(w, r, nil, "invalid token claims", http.StatusForbidden)
  457. return
  458. }
  459. user, err := dataprovider.UserExists(claims.Username)
  460. if err != nil {
  461. sendAPIResponse(w, r, nil, "Unable to retrieve your user", getRespStatus(err))
  462. return
  463. }
  464. connID := xid.New().String()
  465. connectionID := fmt.Sprintf("%v_%v", common.ProtocolHTTP, connID)
  466. if err := checkHTTPClientUser(&user, r, connectionID); err != nil {
  467. sendAPIResponse(w, r, err, http.StatusText(http.StatusForbidden), http.StatusForbidden)
  468. return
  469. }
  470. connection := &Connection{
  471. BaseConnection: common.NewBaseConnection(connID, common.ProtocolHTTP, util.GetHTTPLocalAddress(r),
  472. r.RemoteAddr, user),
  473. request: r,
  474. }
  475. common.Connections.Add(connection)
  476. defer common.Connections.Remove(connection.GetID())
  477. name := "/"
  478. if _, ok := r.URL.Query()["path"]; ok {
  479. name = util.CleanPath(r.URL.Query().Get("path"))
  480. }
  481. contents, err := connection.ReadDir(name)
  482. if err != nil {
  483. sendAPIResponse(w, r, err, "Unable to get directory contents", getMappedStatusCode(err))
  484. return
  485. }
  486. results := make([]map[string]string, 0, len(contents))
  487. for _, info := range contents {
  488. res := make(map[string]string)
  489. res["url"] = getFileObjectURL(name, info.Name())
  490. editURL := ""
  491. if info.IsDir() {
  492. res["type"] = "1"
  493. res["size"] = ""
  494. } else {
  495. res["type"] = "2"
  496. if info.Mode()&os.ModeSymlink != 0 {
  497. res["size"] = ""
  498. } else {
  499. res["size"] = util.ByteCountIEC(info.Size())
  500. if info.Size() < httpdMaxEditFileSize {
  501. editURL = strings.Replace(res["url"], webClientFilesPath, webClientEditFilePath, 1)
  502. }
  503. }
  504. }
  505. res["meta"] = fmt.Sprintf("%v_%v", res["type"], info.Name())
  506. res["name"] = info.Name()
  507. res["last_modified"] = getFileObjectModTime(info.ModTime())
  508. res["edit_url"] = editURL
  509. results = append(results, res)
  510. }
  511. render.JSON(w, r, results)
  512. }
  513. func handleClientGetFiles(w http.ResponseWriter, r *http.Request) {
  514. r.Body = http.MaxBytesReader(w, r.Body, maxRequestSize)
  515. claims, err := getTokenClaims(r)
  516. if err != nil || claims.Username == "" {
  517. renderClientForbiddenPage(w, r, "Invalid token claims")
  518. return
  519. }
  520. user, err := dataprovider.UserExists(claims.Username)
  521. if err != nil {
  522. renderClientMessagePage(w, r, "Unable to retrieve your user", "", getRespStatus(err), nil, "")
  523. return
  524. }
  525. connID := xid.New().String()
  526. connectionID := fmt.Sprintf("%v_%v", common.ProtocolHTTP, connID)
  527. if err := checkHTTPClientUser(&user, r, connectionID); err != nil {
  528. renderClientForbiddenPage(w, r, err.Error())
  529. return
  530. }
  531. connection := &Connection{
  532. BaseConnection: common.NewBaseConnection(connID, common.ProtocolHTTP, util.GetHTTPLocalAddress(r),
  533. r.RemoteAddr, user),
  534. request: r,
  535. }
  536. common.Connections.Add(connection)
  537. defer common.Connections.Remove(connection.GetID())
  538. name := "/"
  539. if _, ok := r.URL.Query()["path"]; ok {
  540. name = util.CleanPath(r.URL.Query().Get("path"))
  541. }
  542. var info os.FileInfo
  543. if name == "/" {
  544. info = vfs.NewFileInfo(name, true, 0, time.Now(), false)
  545. } else {
  546. info, err = connection.Stat(name, 0)
  547. }
  548. if err != nil {
  549. renderFilesPage(w, r, path.Dir(name), fmt.Sprintf("unable to stat file %#v: %v", name, err), user)
  550. return
  551. }
  552. if info.IsDir() {
  553. renderFilesPage(w, r, name, "", user)
  554. return
  555. }
  556. if status, err := downloadFile(w, r, connection, name, info); err != nil && status != 0 {
  557. if status > 0 {
  558. if status == http.StatusRequestedRangeNotSatisfiable {
  559. renderClientMessagePage(w, r, http.StatusText(status), "", status, err, "")
  560. return
  561. }
  562. renderFilesPage(w, r, path.Dir(name), err.Error(), user)
  563. }
  564. }
  565. }
  566. func handleClientEditFile(w http.ResponseWriter, r *http.Request) {
  567. r.Body = http.MaxBytesReader(w, r.Body, maxRequestSize)
  568. claims, err := getTokenClaims(r)
  569. if err != nil || claims.Username == "" {
  570. renderClientForbiddenPage(w, r, "Invalid token claims")
  571. return
  572. }
  573. user, err := dataprovider.UserExists(claims.Username)
  574. if err != nil {
  575. renderClientMessagePage(w, r, "Unable to retrieve your user", "", getRespStatus(err), nil, "")
  576. return
  577. }
  578. connID := xid.New().String()
  579. connectionID := fmt.Sprintf("%v_%v", common.ProtocolHTTP, connID)
  580. if err := checkHTTPClientUser(&user, r, connectionID); err != nil {
  581. renderClientForbiddenPage(w, r, err.Error())
  582. return
  583. }
  584. connection := &Connection{
  585. BaseConnection: common.NewBaseConnection(connID, common.ProtocolHTTP, util.GetHTTPLocalAddress(r),
  586. r.RemoteAddr, user),
  587. request: r,
  588. }
  589. common.Connections.Add(connection)
  590. defer common.Connections.Remove(connection.GetID())
  591. name := util.CleanPath(r.URL.Query().Get("path"))
  592. info, err := connection.Stat(name, 0)
  593. if err != nil {
  594. renderClientMessagePage(w, r, fmt.Sprintf("Unable to stat file %#v", name), "",
  595. getRespStatus(err), nil, "")
  596. return
  597. }
  598. if info.IsDir() {
  599. renderClientMessagePage(w, r, fmt.Sprintf("The path %#v does not point to a file", name), "",
  600. http.StatusBadRequest, nil, "")
  601. return
  602. }
  603. if info.Size() > httpdMaxEditFileSize {
  604. renderClientMessagePage(w, r, fmt.Sprintf("The file size %v for %#v exceeds the maximum allowed size",
  605. util.ByteCountIEC(info.Size()), name), "", http.StatusBadRequest, nil, "")
  606. return
  607. }
  608. reader, err := connection.getFileReader(name, 0, r.Method)
  609. if err != nil {
  610. renderClientMessagePage(w, r, fmt.Sprintf("Unable to get a reader for the file %#v", name), "",
  611. getRespStatus(err), nil, "")
  612. return
  613. }
  614. defer reader.Close()
  615. var b bytes.Buffer
  616. _, err = io.Copy(&b, reader)
  617. if err != nil {
  618. renderClientMessagePage(w, r, fmt.Sprintf("Unable to read the file %#v", name), "", http.StatusInternalServerError,
  619. nil, "")
  620. return
  621. }
  622. renderEditFilePage(w, r, name, b.String())
  623. }
  624. func handleClientAddShareGet(w http.ResponseWriter, r *http.Request) {
  625. r.Body = http.MaxBytesReader(w, r.Body, maxRequestSize)
  626. share := &dataprovider.Share{Scope: dataprovider.ShareScopeRead}
  627. dirName := "/"
  628. if _, ok := r.URL.Query()["path"]; ok {
  629. dirName = util.CleanPath(r.URL.Query().Get("path"))
  630. }
  631. if _, ok := r.URL.Query()["files"]; ok {
  632. files := r.URL.Query().Get("files")
  633. var filesList []string
  634. err := json.Unmarshal([]byte(files), &filesList)
  635. if err != nil {
  636. renderClientMessagePage(w, r, "Invalid share list", "", http.StatusBadRequest, err, "")
  637. return
  638. }
  639. for _, f := range filesList {
  640. if f != "" {
  641. share.Paths = append(share.Paths, path.Join(dirName, f))
  642. }
  643. }
  644. }
  645. renderAddUpdateSharePage(w, r, share, "", true)
  646. }
  647. func handleClientUpdateShareGet(w http.ResponseWriter, r *http.Request) {
  648. r.Body = http.MaxBytesReader(w, r.Body, maxRequestSize)
  649. claims, err := getTokenClaims(r)
  650. if err != nil || claims.Username == "" {
  651. renderClientForbiddenPage(w, r, "Invalid token claims")
  652. return
  653. }
  654. shareID := getURLParam(r, "id")
  655. share, err := dataprovider.ShareExists(shareID, claims.Username)
  656. if err == nil {
  657. share.HideConfidentialData()
  658. renderAddUpdateSharePage(w, r, &share, "", false)
  659. } else if _, ok := err.(*util.RecordNotFoundError); ok {
  660. renderClientNotFoundPage(w, r, err)
  661. } else {
  662. renderClientInternalServerErrorPage(w, r, err)
  663. }
  664. }
  665. func handleClientAddSharePost(w http.ResponseWriter, r *http.Request) {
  666. r.Body = http.MaxBytesReader(w, r.Body, maxRequestSize)
  667. claims, err := getTokenClaims(r)
  668. if err != nil || claims.Username == "" {
  669. renderClientForbiddenPage(w, r, "Invalid token claims")
  670. return
  671. }
  672. share, err := getShareFromPostFields(r)
  673. if err != nil {
  674. renderAddUpdateSharePage(w, r, share, err.Error(), true)
  675. return
  676. }
  677. if err := verifyCSRFToken(r.Form.Get(csrfFormToken)); err != nil {
  678. renderClientForbiddenPage(w, r, err.Error())
  679. return
  680. }
  681. share.ID = 0
  682. share.ShareID = util.GenerateUniqueID()
  683. share.LastUseAt = 0
  684. share.Username = claims.Username
  685. err = dataprovider.AddShare(share, claims.Username, util.GetIPFromRemoteAddress(r.RemoteAddr))
  686. if err == nil {
  687. http.Redirect(w, r, webClientSharesPath, http.StatusSeeOther)
  688. } else {
  689. renderAddUpdateSharePage(w, r, share, err.Error(), true)
  690. }
  691. }
  692. func handleClientUpdateSharePost(w http.ResponseWriter, r *http.Request) {
  693. r.Body = http.MaxBytesReader(w, r.Body, maxRequestSize)
  694. claims, err := getTokenClaims(r)
  695. if err != nil || claims.Username == "" {
  696. renderClientForbiddenPage(w, r, "Invalid token claims")
  697. return
  698. }
  699. shareID := getURLParam(r, "id")
  700. share, err := dataprovider.ShareExists(shareID, claims.Username)
  701. if _, ok := err.(*util.RecordNotFoundError); ok {
  702. renderClientNotFoundPage(w, r, err)
  703. return
  704. } else if err != nil {
  705. renderClientInternalServerErrorPage(w, r, err)
  706. return
  707. }
  708. updatedShare, err := getShareFromPostFields(r)
  709. if err != nil {
  710. renderAddUpdateSharePage(w, r, updatedShare, err.Error(), false)
  711. return
  712. }
  713. if err := verifyCSRFToken(r.Form.Get(csrfFormToken)); err != nil {
  714. renderClientForbiddenPage(w, r, err.Error())
  715. return
  716. }
  717. updatedShare.ShareID = shareID
  718. updatedShare.Username = claims.Username
  719. if updatedShare.Password == redactedSecret {
  720. updatedShare.Password = share.Password
  721. }
  722. err = dataprovider.UpdateShare(updatedShare, claims.Username, util.GetIPFromRemoteAddress(r.RemoteAddr))
  723. if err == nil {
  724. http.Redirect(w, r, webClientSharesPath, http.StatusSeeOther)
  725. } else {
  726. renderAddUpdateSharePage(w, r, updatedShare, err.Error(), false)
  727. }
  728. }
  729. func handleClientGetShares(w http.ResponseWriter, r *http.Request) {
  730. r.Body = http.MaxBytesReader(w, r.Body, maxRequestSize)
  731. claims, err := getTokenClaims(r)
  732. if err != nil || claims.Username == "" {
  733. renderClientForbiddenPage(w, r, "Invalid token claims")
  734. return
  735. }
  736. limit := defaultQueryLimit
  737. if _, ok := r.URL.Query()["qlimit"]; ok {
  738. var err error
  739. limit, err = strconv.Atoi(r.URL.Query().Get("qlimit"))
  740. if err != nil {
  741. limit = defaultQueryLimit
  742. }
  743. }
  744. shares := make([]dataprovider.Share, 0, limit)
  745. for {
  746. s, err := dataprovider.GetShares(limit, len(shares), dataprovider.OrderASC, claims.Username)
  747. if err != nil {
  748. renderInternalServerErrorPage(w, r, err)
  749. return
  750. }
  751. shares = append(shares, s...)
  752. if len(s) < limit {
  753. break
  754. }
  755. }
  756. data := clientSharesPage{
  757. baseClientPage: getBaseClientPageData(pageClientSharesTitle, webClientSharesPath, r),
  758. Shares: shares,
  759. BasePublicSharesURL: webClientPubSharesPath,
  760. }
  761. renderClientTemplate(w, templateClientShares, data)
  762. }
  763. func handleClientGetProfile(w http.ResponseWriter, r *http.Request) {
  764. r.Body = http.MaxBytesReader(w, r.Body, maxRequestSize)
  765. renderClientProfilePage(w, r, "")
  766. }
  767. func handleWebClientChangePwd(w http.ResponseWriter, r *http.Request) {
  768. r.Body = http.MaxBytesReader(w, r.Body, maxRequestSize)
  769. renderClientChangePasswordPage(w, r, "")
  770. }
  771. func handleWebClientChangePwdPost(w http.ResponseWriter, r *http.Request) {
  772. r.Body = http.MaxBytesReader(w, r.Body, maxRequestSize)
  773. err := r.ParseForm()
  774. if err != nil {
  775. renderClientChangePasswordPage(w, r, err.Error())
  776. return
  777. }
  778. if err := verifyCSRFToken(r.Form.Get(csrfFormToken)); err != nil {
  779. renderClientForbiddenPage(w, r, err.Error())
  780. return
  781. }
  782. err = doChangeUserPassword(r, r.Form.Get("current_password"), r.Form.Get("new_password1"),
  783. r.Form.Get("new_password2"))
  784. if err != nil {
  785. renderClientChangePasswordPage(w, r, err.Error())
  786. return
  787. }
  788. handleWebClientLogout(w, r)
  789. }
  790. func handleWebClientProfilePost(w http.ResponseWriter, r *http.Request) {
  791. r.Body = http.MaxBytesReader(w, r.Body, maxRequestSize)
  792. err := r.ParseForm()
  793. if err != nil {
  794. renderClientProfilePage(w, r, err.Error())
  795. return
  796. }
  797. if err := verifyCSRFToken(r.Form.Get(csrfFormToken)); err != nil {
  798. renderClientForbiddenPage(w, r, err.Error())
  799. return
  800. }
  801. claims, err := getTokenClaims(r)
  802. if err != nil || claims.Username == "" {
  803. renderClientForbiddenPage(w, r, "Invalid token claims")
  804. return
  805. }
  806. user, err := dataprovider.UserExists(claims.Username)
  807. if err != nil {
  808. renderClientProfilePage(w, r, err.Error())
  809. return
  810. }
  811. if !user.CanManagePublicKeys() && !user.CanChangeAPIKeyAuth() && !user.CanChangeInfo() {
  812. renderClientForbiddenPage(w, r, "You are not allowed to change anything")
  813. return
  814. }
  815. if user.CanManagePublicKeys() {
  816. user.PublicKeys = r.Form["public_keys"]
  817. }
  818. if user.CanChangeAPIKeyAuth() {
  819. user.Filters.AllowAPIKeyAuth = len(r.Form.Get("allow_api_key_auth")) > 0
  820. }
  821. if user.CanChangeInfo() {
  822. user.Email = r.Form.Get("email")
  823. user.Description = r.Form.Get("description")
  824. }
  825. err = dataprovider.UpdateUser(&user, dataprovider.ActionExecutorSelf, util.GetIPFromRemoteAddress(r.RemoteAddr))
  826. if err != nil {
  827. renderClientProfilePage(w, r, err.Error())
  828. return
  829. }
  830. renderClientMessagePage(w, r, "Profile updated", "", http.StatusOK, nil,
  831. "Your profile has been successfully updated")
  832. }
  833. func handleWebClientMFA(w http.ResponseWriter, r *http.Request) {
  834. r.Body = http.MaxBytesReader(w, r.Body, maxRequestSize)
  835. renderClientMFAPage(w, r)
  836. }
  837. func handleWebClientTwoFactor(w http.ResponseWriter, r *http.Request) {
  838. r.Body = http.MaxBytesReader(w, r.Body, maxRequestSize)
  839. renderClientTwoFactorPage(w, "")
  840. }
  841. func handleWebClientTwoFactorRecovery(w http.ResponseWriter, r *http.Request) {
  842. r.Body = http.MaxBytesReader(w, r.Body, maxRequestSize)
  843. renderClientTwoFactorRecoveryPage(w, "")
  844. }
  845. func getShareFromPostFields(r *http.Request) (*dataprovider.Share, error) {
  846. share := &dataprovider.Share{}
  847. if err := r.ParseForm(); err != nil {
  848. return share, err
  849. }
  850. share.Name = r.Form.Get("name")
  851. share.Description = r.Form.Get("description")
  852. share.Paths = r.Form["paths"]
  853. share.Password = r.Form.Get("password")
  854. share.AllowFrom = getSliceFromDelimitedValues(r.Form.Get("allowed_ip"), ",")
  855. scope, err := strconv.Atoi(r.Form.Get("scope"))
  856. if err != nil {
  857. return share, err
  858. }
  859. share.Scope = dataprovider.ShareScope(scope)
  860. maxTokens, err := strconv.Atoi(r.Form.Get("max_tokens"))
  861. if err != nil {
  862. return share, err
  863. }
  864. share.MaxTokens = maxTokens
  865. expirationDateMillis := int64(0)
  866. expirationDateString := r.Form.Get("expiration_date")
  867. if strings.TrimSpace(expirationDateString) != "" {
  868. expirationDate, err := time.Parse(webDateTimeFormat, expirationDateString)
  869. if err != nil {
  870. return share, err
  871. }
  872. expirationDateMillis = util.GetTimeAsMsSinceEpoch(expirationDate)
  873. }
  874. share.ExpiresAt = expirationDateMillis
  875. return share, nil
  876. }