Changeset 67 in code


Ignore:
Timestamp:
Feb 11, 2024, 2:08:18 AM (16 months ago)
Author:
yakumo.izuru
Message:

リファクタリングと再設計

Signed-off-by: Izuru Yakumo <yakumo.izuru@…>

Location:
trunk
Files:
13 added
2 deleted
4 edited

Legend:

Unmodified
Added
Removed
  • trunk/cmd/marisa/main.go

    r65 r67  
    22
    33import (
    4         "encoding/json"
    54        "flag"
    6         "fmt"
    7         "html/template"
    8         "io"
    9         "io/ioutil"
    105        "log"
    116        "net"
     
    149        "os"
    1510        "os/signal"
    16         "os/user"
    17         "path"
    18         "path/filepath"
    19         "strconv"
    2011        "syscall"
    21         "time"
    2212
    23         "github.com/dustin/go-humanize"
    24         "gopkg.in/ini.v1"
    2513        "marisa.chaotic.ninja/marisa"
    2614)
     
    5442
    5543var verbose bool
    56 
    57 func writefile(f *os.File, s io.ReadCloser, contentlength int64) error {
    58         buffer := make([]byte, 4096)
    59         eof := false
    60         sz := int64(0)
    61 
    62         defer f.Sync()
    63 
    64         for !eof {
    65                 n, err := s.Read(buffer)
    66                 if err != nil && err != io.EOF {
    67                         return err
    68                 } else if err == io.EOF {
    69                         eof = true
    70                 }
    71 
    72                 /* ensure we don't write more than expected */
    73                 r := int64(n)
    74                 if sz+r > contentlength {
    75                         r = contentlength - sz
    76                         eof = true
    77                 }
    78 
    79                 _, err = f.Write(buffer[:r])
    80                 if err != nil {
    81                         return err
    82                 }
    83                 sz += r
    84         }
    85 
    86         return nil
    87 }
    88 
    89 func writemeta(filename string, expiry int64) error {
    90 
    91         f, _ := os.Open(filename)
    92         stat, _ := f.Stat()
    93         size := stat.Size()
    94         f.Close()
    95 
    96         if expiry < 0 {
    97                 expiry = conf.expiry
    98         }
    99 
    100         meta := metadata{
    101                 Filename: filepath.Base(filename),
    102                 Size:     size,
    103                 Expiry:   time.Now().Unix() + expiry,
    104         }
    105 
    106         if verbose {
    107                 log.Printf("Saving metadata for %s in %s", meta.Filename, conf.metapath+"/"+meta.Filename+".json")
    108         }
    109 
    110         f, err := os.Create(conf.metapath + "/" + meta.Filename + ".json")
    111         if err != nil {
    112                 return err
    113         }
    114         defer f.Close()
    115 
    116         j, err := json.Marshal(meta)
    117         if err != nil {
    118                 return err
    119         }
    120 
    121         _, err = f.Write(j)
    122 
    123         return err
    124 }
    125 
    126 func servetemplate(w http.ResponseWriter, f string, d templatedata) {
    127         t, err := template.ParseFiles(conf.tmplpath + "/" + f)
    128         if err != nil {
    129                 http.Error(w, "Internal error", http.StatusInternalServerError)
    130                 return
    131         }
    132 
    133         if verbose {
    134                 log.Printf("Serving template %s", t.Name())
    135         }
    136 
    137         err = t.Execute(w, d)
    138         if err != nil {
    139                 fmt.Println(err)
    140         }
    141 }
    142 
    143 func uploaderPut(w http.ResponseWriter, r *http.Request) {
    144         /* limit upload size */
    145         if r.ContentLength > conf.maxsize {
    146                 http.Error(w, "File is too big", http.StatusRequestEntityTooLarge)
    147         }
    148 
    149         tmp, _ := ioutil.TempFile(conf.filepath, "*"+path.Ext(r.URL.Path))
    150         f, err := os.Create(tmp.Name())
    151         if err != nil {
    152                 fmt.Println(err)
    153                 return
    154         }
    155         defer f.Close()
    156 
    157         if verbose {
    158                 log.Printf("Writing %d bytes to %s", r.ContentLength, tmp.Name())
    159         }
    160 
    161         if err = writefile(f, r.Body, r.ContentLength); err != nil {
    162                 http.Error(w, "Internal error", http.StatusInternalServerError)
    163                 defer os.Remove(tmp.Name())
    164                 return
    165         }
    166         writemeta(tmp.Name(), conf.expiry)
    167 
    168         resp := conf.baseuri + conf.filectx + filepath.Base(tmp.Name())
    169         w.Write([]byte(resp + "\r\n"))
    170 }
    171 
    172 func uploaderPost(w http.ResponseWriter, r *http.Request) {
    173         /* read 32Mb at a time */
    174         r.ParseMultipartForm(32 << 20)
    175 
    176         links := []string{}
    177         for _, h := range r.MultipartForm.File["file"] {
    178                 if h.Size > conf.maxsize {
    179                         http.Error(w, "File is too big", http.StatusRequestEntityTooLarge)
    180                         return
    181                 }
    182 
    183                 post, err := h.Open()
    184                 if err != nil {
    185                         http.Error(w, "Internal error", http.StatusInternalServerError)
    186                         return
    187                 }
    188                 defer post.Close()
    189 
    190                 tmp, _ := ioutil.TempFile(conf.filepath, "*"+path.Ext(h.Filename))
    191                 f, err := os.Create(tmp.Name())
    192                 if err != nil {
    193                         http.Error(w, "Internal error", http.StatusInternalServerError)
    194                         return
    195                 }
    196                 defer f.Close()
    197 
    198                 if err = writefile(f, post, h.Size); err != nil {
    199                         http.Error(w, "Internal error", http.StatusInternalServerError)
    200                         defer os.Remove(tmp.Name())
    201                         return
    202                 }
    203 
    204                 expiry, err := strconv.Atoi(r.PostFormValue("expiry"))
    205                 if err != nil || expiry < 0 {
    206                         expiry = int(conf.expiry)
    207                 }
    208                 writemeta(tmp.Name(), int64(expiry))
    209 
    210                 link := conf.baseuri + conf.filectx + filepath.Base(tmp.Name())
    211                 links = append(links, link)
    212         }
    213 
    214         switch r.PostFormValue("output") {
    215         case "html":
    216                 data := templatedata{
    217                         Maxsize: humanize.IBytes(uint64(conf.maxsize)),
    218                         Links:   links,
    219                 }
    220                 servetemplate(w, "/index.html", data)
    221         case "json":
    222                 data, _ := json.Marshal(links)
    223                 w.Write(data)
    224         default:
    225                 for _, link := range links {
    226                         w.Write([]byte(link + "\r\n"))
    227                 }
    228         }
    229 }
    230 
    231 func uploaderGet(w http.ResponseWriter, r *http.Request) {
    232         // r.URL.Path is sanitized regarding "." and ".."
    233         filename := r.URL.Path
    234         if r.URL.Path == "/" || r.URL.Path == "/index.html" {
    235                 data := templatedata{Maxsize: humanize.IBytes(uint64(conf.maxsize))}
    236                 servetemplate(w, "/index.html", data)
    237                 return
    238         }
    239 
    240         if verbose {
    241                 log.Printf("Serving file %s", conf.rootdir+filename)
    242         }
    243 
    244         http.ServeFile(w, r, conf.rootdir+filename)
    245 }
    246 
    247 func uploaderDelete(w http.ResponseWriter, r *http.Request) {
    248         // r.URL.Path is sanitized regarding "." and ".."
    249         filename := r.URL.Path
    250         filepath := conf.filepath + filename
    251 
    252         if verbose {
    253                 log.Printf("Deleting file %s", filepath)
    254         }
    255 
    256         f, err := os.Open(filepath)
    257         if err != nil {
    258                 http.NotFound(w, r)
    259                 return
    260         }
    261         f.Close()
    262 
    263         // Force file expiration
    264         writemeta(filepath, 0)
    265         w.WriteHeader(http.StatusNoContent)
    266 }
    267 
    268 func uploader(w http.ResponseWriter, r *http.Request) {
    269         if verbose {
    270                 log.Printf("%s: <%s> %s %s %s", r.Host, r.RemoteAddr, r.Method, r.RequestURI, r.Proto)
    271         }
    272 
    273         switch r.Method {
    274         case "DELETE":
    275                 uploaderDelete(w, r)
    276         case "POST":
    277                 uploaderPost(w, r)
    278         case "PUT":
    279                 uploaderPut(w, r)
    280         case "GET":
    281                 uploaderGet(w, r)
    282         }
    283 }
    284 
    285 func parseconfig(file string) error {
    286         cfg, err := ini.Load(file)
    287         if err != nil {
    288                 return err
    289         }
    290 
    291         conf.listen = cfg.Section("").Key("listen").String()
    292         conf.user = cfg.Section("").Key("user").String()
    293         conf.group = cfg.Section("").Key("group").String()
    294         conf.baseuri = cfg.Section("").Key("baseuri").String()
    295         conf.filepath = cfg.Section("").Key("filepath").String()
    296         conf.metapath = cfg.Section("").Key("metapath").String()
    297         conf.filectx = cfg.Section("").Key("filectx").String()
    298         conf.rootdir = cfg.Section("").Key("rootdir").String()
    299         conf.chroot = cfg.Section("").Key("chroot").String()
    300         conf.tmplpath = cfg.Section("").Key("tmplpath").String()
    301         conf.maxsize, _ = cfg.Section("").Key("maxsize").Int64()
    302         conf.expiry, _ = cfg.Section("").Key("expiry").Int64()
    303 
    304         return nil
    305 }
    306 
    307 func usergroupids(username string, groupname string) (int, int, error) {
    308         u, err := user.Lookup(username)
    309         if err != nil {
    310                 return -1, -1, err
    311         }
    312 
    313         uid, _ := strconv.Atoi(u.Uid)
    314         gid, _ := strconv.Atoi(u.Gid)
    315 
    316         if conf.group != "" {
    317                 g, err := user.LookupGroup(groupname)
    318                 if err != nil {
    319                         return uid, -1, err
    320                 }
    321                 gid, _ = strconv.Atoi(g.Gid)
    322         }
    323 
    324         return uid, gid, nil
    325 }
    32644
    32745func main() {
  • trunk/example/marisa.conf

    r65 r67  
    1 # TCP or unix Socket to listen on.
    2 # When unix sockets are used, the content will be served over FastCGI.
    3 #listen = /var/run/marisa-fcgi.sock
    4 listen = 127.0.0.1:9000
    5 
    6 # Base to use when constructing URI to files uploaded.
    7 # The full URI must be specified, in the form SCHEME://HOST[:PORT]
    8 baseuri = http://127.0.0.1:9000
     1[marisa]
     2# TCP or Unix socket to listen on.
     3# When the Unix socket is used, the content will be served through FastCGI
     4# listen = /var/run/marisa.sock
     5# listen = 127.0.0.1:9000
    96
    107# Drop privilege to the user and group specified.
    11 # When only the user is specified, the default group of the user will
    12 # be used.
    13 #user = www
    14 #group = daemon
     8# When only the user is specified, the default group of the user
     9# will be used.
     10# user = www
     11# group = www
    1512
    1613# Change the root directory to the following directory.
    17 # When a chroot is set, all path must be given according to the chroot.
    18 # Note: the configuration file is read before chrooting.
    19 #chroot = /var/www
     14# When a chroot(2) is set, all paths must be given according to it.
     15# Note: the configuration file is read before it happens
     16# chroot =
     17[www]
     18# baseuri = http://127.0.0.1:9000
    2019
    21 # Path to the different path used by the server. Must take into account
    22 # the chroot if set.
    23 rootdir = static
    24 tmplpath = templates
    25 filepath = files
    26 metapath = meta
     20# Path to the resources used by the server, must take into account
     21# the chroot is set
     22# rootdir = ./static
     23# tmplpath = ./templates
     24# filepath = ./files
     25# metapath = ./meta
    2726
    2827# URI context that files will be served on
    29 filectx = /f/
     28# filectx = /f/
    3029
    3130# Maximum per-file upload size (in bytes)
    32 maxsize = 536870912 # 512Mib
     31# maxsize = 536870912 # 512 MiB
    3332
    34 # Default expiration time (in seconds). An expiration time of 0 seconds
    35 # means no expiration.
    36 expiry = 86400 # 24 hours
     33# Default expiration time (in seconds).
     34# An expiration time of 0 seconds means no expiration.
     35# expiry = 86400 # 24 hours
  • trunk/example/templates/index.html

    r65 r67  
    1 <!DOCTYPE HTML>
     1<!DOCTYPE HTML PUBLIC "//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
    22<html>
    33  <head>
    4     <meta charset="utf-8">
    5     <meta name="author" content="z3bra, Izuru Yakumo">
    6     <meta name="robots" content="noindex,nofollow" >
     4    <link rel="icon" href="/favicon.ico">
     5    <link rel="stylesheet" href="/marisa.css">
     6    <meta http-equiv="Content-type" content="text/html; charset=utf-8">
     7    <meta name="author" content="Izuru Yakumo">
    78    <meta name="viewport" content="width=device-width">
    8     <link rel="stylesheet" type="text/css" href="/marisa_98.css" >
    9     <link rel="icon" href="/marisa.png">
    109    <title>Marisa</title>
    1110  </head>
    1211  <body>
    13     <header>
    14       <img id="logo" src="/marisa.png" >
    15       <h1>marisa</h1>
    16     </header>
    17     <form enctype="multipart/form-data" method="post">
    18       <div id="dropzone"></div>
    19       <div id="fallbackform" class="dropzone">
    20         <input id="filebox" name="file" type="file" multiple>
    21         <input id="output" name="output" type="hidden" value='html' >
    22         <input type="submit" value="Upload">
    23       </div>
    24       <section id="formsettings">
    25         <label for="expiry"> Destroy after </label>
    26         <select id="expiry" name="expiry">
    27           <option value="900"> 15 minutes </option>
    28           <option value="3600"> 1 hour </option>
    29           <option value="28800"> 8 hours </option>
    30           <option value="86400" selected> 1 day </option>
    31           <option value="604800"> 1 week </option>
    32         </select>
    33       </section>
    34     </form>
    35     <p>File size limited to {{.Maxsize}}.</p>
    36     <div id="uploads">{{if .Links}}
    37       <ul>
    38         {{range .Links}}<li><a href="{{.}}">{{.}}</a></li>{{end}}
    39       </ul>
    40       {{end}}</div>
     12    <table border="1" align="center">
     13      <thead>
     14        <img class="logo" src="/marisa.png">
     15        <br>
     16        <h1>Marisa</h1>
     17      </thead>
     18      <tbody>
     19        <form enctype="multipart/form-data" method="POST">
     20          <input class="file" name="file" type="file"><br>
     21          <input name="output" type="hidden" value="html"><br>
     22          <input type="submit"><br>
     23          <label for="expiry">Destroy after</label>
     24          <select name="expiry">
     25            <option value="900">15 minutes</option>
     26            <option value="3600">1 hour</option>
     27            <option value="28800">8 hours</option>
     28            <option value="86400">1 day</option>
     29            <option value="604800">1 week</option>
     30          </select>
     31        </form>
     32        <p>
     33          File size limited to {{.Maxsize}}.
     34        </p>
     35      </tbody>
     36      <tfoot>
     37        {{if .Links}}
     38        <tr>
     39          {{range .Links}}<td><a href="{{.}}">{{.}}</a></td>{{end}}
     40        </tr>
     41        {{end}}
     42        <br><hr>
     43        <p>&copy; 2024 Izuru Yakumo</p>
     44      </tfoot>
     45    </table>
    4146  </body>
    4247</html>
Note: See TracChangeset for help on using the changeset viewer.