webclient.go 44 KB

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