webclient.go 44 KB

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