webclient.go 37 KB

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