webclient.go 51 KB

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