webclient.go 56 KB

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