webclient.go 51 KB

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