webclient.go 48 KB

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