webclient.go 66 KB

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