1 package server
2
3 import (
4 "fmt"
5 "io"
6 "log"
7 "net/http"
8 "os"
9 "path/filepath"
10 "strconv"
11 "strings"
12 "time"
13
14 "codeberg.org/tslocum/sriracha/internal/database"
15 . "codeberg.org/tslocum/sriracha/model"
16 . "codeberg.org/tslocum/sriracha/util"
17 "github.com/gabriel-vasile/mimetype"
18 )
19
20 func (s *Server) loadBannerFormFile(db *database.DB, r *http.Request, b *Banner) ([]byte, error) {
21 if r.PostForm == nil {
22 const maxMemory = 32 << 20
23 err := r.ParseMultipartForm(maxMemory)
24 if err != nil {
25 return nil, err
26 }
27 }
28 if r.MultipartForm == nil || r.MultipartForm.File == nil {
29 return nil, nil
30 }
31 files := r.MultipartForm.File["file"]
32 if len(files) == 0 {
33 return nil, nil
34 } else if len(files) > 1 {
35 return nil, fmt.Errorf("too many files: upload a single file")
36 }
37 fileHeader := files[0]
38
39 formFile, err := fileHeader.Open()
40 if err != nil {
41 return nil, err
42 }
43 defer formFile.Close()
44
45 buf, err := io.ReadAll(formFile)
46 if err != nil {
47 return nil, err
48 }
49
50 if b.Name == "" {
51 b.Name = strings.TrimSpace(fileHeader.Filename)
52 if b.Name == "" {
53 b.Name = fmt.Sprintf("%d", time.Now().Unix())
54 }
55 }
56
57 mimeType := mimetype.Detect(buf).String()
58 ext := MIMEToExt(mimeType)
59 if ext == "" {
60 return nil, fmt.Errorf("unknown file type: upload a JPG, PNG or GIF image")
61 }
62 fileExt := "." + ext
63 if !strings.HasSuffix(b.Name, fileExt) {
64 b.Name = strings.TrimSuffix(b.Name, filepath.Ext(b.Name)) + fileExt
65 }
66
67 imgWidth, imgHeight := s.imageDimensions(buf)
68 if imgWidth == 0 || imgHeight == 0 {
69 return nil, fmt.Errorf("invalid image")
70 }
71 b.Width, b.Height = imgWidth, imgHeight
72
73 return buf, nil
74 }
75
76 func (s *Server) loadBannerForm(db *database.DB, r *http.Request, b *Banner) {
77 b.Name = FormString(r, "name")
78 b.Overboard = FormBool(r, "overboard")
79 b.News = FormBool(r, "news")
80 b.Pages = FormBool(r, "pages")
81
82 b.Boards = nil
83 boards := r.Form["boards"]
84 for _, board := range boards {
85 boardID, err := strconv.Atoi(board)
86 if err != nil || boardID <= 0 {
87 continue
88 }
89 board := db.BoardByID(boardID)
90 if board == nil {
91 continue
92 }
93 b.Boards = append(b.Boards, board)
94 }
95 }
96
97 func (s *Server) serveBanner(data *templateData, db *database.DB, w http.ResponseWriter, r *http.Request) {
98 var err error
99 data.Template = "manage_banner"
100 data.Boards = db.AllBoards()
101 data.Manage.Banner = &Banner{}
102
103 deleteBannerID := PathInt(r, "/sriracha/banner/delete/")
104 if deleteBannerID > 0 {
105 if s.forbidden(w, data, "banner.delete") {
106 return
107 }
108 b := db.BannerByID(deleteBannerID)
109 if b == nil {
110 data.ManageError("Invalid banner.")
111 return
112 }
113
114 bannerPath := filepath.Join(s.config.Root, "banner", b.Name)
115 os.Remove(bannerPath)
116
117 db.DeleteBanner(b.ID)
118 s.refreshBannerCache(db)
119 s.rebuildAll(db)
120
121 s.log(db, data.Account, nil, fmt.Sprintf("Deleted banner #%d", b.ID), "")
122
123 http.Redirect(w, r, "/sriracha/banner/", http.StatusFound)
124 return
125 }
126
127 bannerID, err := strconv.Atoi(strings.TrimPrefix(r.URL.Path, "/sriracha/banner/"))
128 if err == nil && bannerID > 0 {
129 if s.forbidden(w, data, "banner.update") {
130 return
131 }
132 data.Manage.Banner = db.BannerByID(bannerID)
133
134 if data.Manage.Banner != nil && r.Method == http.MethodPost {
135 oldName := data.Manage.Banner.Name
136 s.loadBannerForm(db, r, data.Manage.Banner)
137 buf, err := s.loadBannerFormFile(db, r, data.Manage.Banner)
138 if err != nil {
139 data.ManageError(err.Error())
140 return
141 }
142
143 err = data.Manage.Banner.Validate()
144 if err != nil {
145 data.ManageError(err.Error())
146 return
147 }
148
149 if data.Manage.Banner.Name != oldName {
150 if buf == nil && filepath.Ext(data.Manage.Banner.Name) != filepath.Ext(oldName) {
151 data.ManageError("File extension was modified in banner name: to change a banner's file extension, upload a new file")
152 return
153 }
154 match := db.BannerByName(data.Manage.Banner.Name)
155 if match != nil {
156 data.ManageError("Banner with that name already exists")
157 return
158 }
159 oldPath := filepath.Join(s.config.Root, "banner", oldName)
160 newPath := filepath.Join(s.config.Root, "banner", data.Manage.Banner.Name)
161 err = os.Rename(oldPath, newPath)
162 if err != nil {
163 log.Fatalf("failed to rename banner from %s to %s: %s", oldPath, newPath, err)
164 }
165 }
166
167 if buf != nil {
168 bannerPath := filepath.Join(s.config.Root, "banner", data.Manage.Banner.Name)
169 err = os.WriteFile(bannerPath, buf, NewFilePermission)
170 if err != nil {
171 log.Fatalf("failed to write banner image at %s: %s", bannerPath, err)
172 }
173 }
174
175 db.UpdateBanner(data.Manage.Banner)
176 s.refreshBannerCache(db)
177 s.rebuildAll(db)
178
179 s.log(db, data.Account, nil, fmt.Sprintf("Updated >>/banner/%d", data.Manage.Banner.ID), "")
180
181 http.Redirect(w, r, "/sriracha/banner/", http.StatusFound)
182 return
183 }
184 return
185 }
186
187 if r.Method == http.MethodPost {
188 if s.forbidden(w, data, "banner.add") {
189 return
190 }
191 b := &Banner{}
192 s.loadBannerForm(db, r, b)
193 buf, err := s.loadBannerFormFile(db, r, b)
194 if err != nil {
195 data.ManageError(err.Error())
196 return
197 } else if buf == nil {
198 data.ManageError("upload a file to add a banner")
199 return
200 }
201
202 err = b.Validate()
203 if err != nil {
204 data.ManageError(err.Error())
205 return
206 }
207
208 match := db.BannerByName(b.Name)
209 if match != nil {
210 data.ManageError("Banner with that name already exists")
211 return
212 }
213
214 bannerPath := filepath.Join(s.config.Root, "banner", b.Name)
215 err = os.WriteFile(bannerPath, buf, NewFilePermission)
216 if err != nil {
217 log.Fatalf("failed to write banner image at %s: %s", bannerPath, err)
218 }
219
220 db.AddBanner(b)
221 s.refreshBannerCache(db)
222 s.rebuildAll(db)
223
224 s.log(db, data.Account, nil, fmt.Sprintf("Added >>/banner/%d", b.ID), "")
225
226 http.Redirect(w, r, "/sriracha/banner/", http.StatusFound)
227 return
228 }
229
230 data.Manage.Banners = db.AllBanners()
231 }
232
View as plain text