webclient.go 34 KB

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