webclient.go 42 KB

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