package contented import ( "bytes" "encoding/json" "log" "net/http" "os" "regexp" "strings" "sync" "time" "github.com/boltdb/bolt" "github.com/mxk/go-flowrate/flowrate" ) var SERVER_HEADER string = `contented/0.0.0-dev` type ServerPublicProperties struct { AppTitle string MaxUploadBytes int64 CanonicalBaseURL string } type ServerOptions struct { DataDirectory string DBPath string DiskFilesWorldReadable bool BandwidthLimit int64 TrustXForwardedFor bool EnableHomepage bool ServerPublicProperties } func (this *ServerOptions) FileMode() os.FileMode { if this.DiskFilesWorldReadable { return 0644 } else { return 0600 } } type Server struct { opts ServerOptions db *bolt.DB startTime time.Time thumbnailSem sync.Mutex metadataBucket []byte } func NewServer(opts *ServerOptions) (*Server, error) { s := &Server{ opts: *opts, metadataBucket: []byte(`METADATA`), startTime: time.Now(), } b, err := bolt.Open(opts.DBPath, 0644, bolt.DefaultOptions) if err != nil { return nil, err } err = b.Update(func(tx *bolt.Tx) error { _, err := tx.CreateBucketIfNotExists(s.metadataBucket) return err }) if err != nil { return nil, err } s.db = b return s, nil } func (this *Server) serveJsonObject(w http.ResponseWriter, o interface{}) { jb, err := json.Marshal(o) if err != nil { log.Println(err.Error()) http.Error(w, "Internal error", 500) return } w.Header().Set(`Content-Type`, `application/json`) w.WriteHeader(200) w.Write(jb) } func (this *Server) handleAbout(w http.ResponseWriter) { this.serveJsonObject(w, this.opts.ServerPublicProperties) } func (this *Server) remoteIP(r *http.Request) string { if this.opts.TrustXForwardedFor { if xff := r.Header.Get("X-Forwarded-For"); len(xff) > 0 { return xff } } return strings.TrimRight(strings.TrimRight(r.RemoteAddr, "0123456789"), ":") } const ( downloadUrlPrefix = `/get/` metadataUrlPrefix = `/info/` previewUrlPrefix = `/p/` ) var rxThumbUrl = regexp.MustCompile(`^/thumb/(.)/(.*)$`) func (this *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) { w.Header().Set(`Server`, SERVER_HEADER) w.Header().Set(`Access-Control-Allow-Origin`, `*`) // Blanket allow CORS if this.opts.MaxUploadBytes > 0 { r.Body = http.MaxBytesReader(w, r.Body, this.opts.MaxUploadBytes) } if this.opts.BandwidthLimit > 0 { r.Body = flowrate.NewReader(r.Body, this.opts.BandwidthLimit) } // if r.Method == "GET" && strings.HasPrefix(r.URL.Path, downloadUrlPrefix) { this.handleView(w, r, r.URL.Path[len(downloadUrlPrefix):]) } else if r.Method == "GET" && strings.HasPrefix(r.URL.Path, metadataUrlPrefix) { this.handleInformation(w, r.URL.Path[len(metadataUrlPrefix):]) } else if r.Method == "GET" && strings.HasPrefix(r.URL.Path, previewUrlPrefix) { this.handlePreview(w, r.URL.Path[len(previewUrlPrefix):]) } else if r.Method == "GET" && rxThumbUrl.MatchString(r.URL.Path) { parts := rxThumbUrl.FindStringSubmatch(r.URL.Path) this.handleThumb(w, r, parts[1][0], parts[2]) } else if r.Method == "GET" && r.URL.Path == `/about` { this.handleAbout(w) } else if r.Method == "POST" && r.URL.Path == `/upload` { this.handleUpload(w, r) } else if r.Method == "OPTIONS" { // Blanket allow (headers already set) w.WriteHeader(200) } else if r.Method == "GET" && r.URL.Path == `/` && this.opts.EnableHomepage { http.Redirect(w, r, `/index.html`, http.StatusFound) } else if static, err := Asset(r.URL.Path[1:]); err == nil && r.Method == "GET" && (this.opts.EnableHomepage || r.URL.Path != `/index.html`) { http.ServeContent(w, r, r.URL.Path[1:], this.startTime, bytes.NewReader(static)) } else { http.Error(w, "Not found", 404) } }