...

Source file src/codeberg.org/tslocum/sriracha/internal/server/server_banner.go

Documentation: codeberg.org/tslocum/sriracha/internal/server

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

View as plain text