webclient.go 38 KB

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