webclient.go 49 KB

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