Page Menu
Home
Musing Studio
Search
Configure Global Search
Log In
Files
F10669840
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Award Token
Flag For Later
Size
12 KB
Subscribers
None
View Options
diff --git a/pad.go b/pad.go
index f065cfb..348467b 100644
--- a/pad.go
+++ b/pad.go
@@ -1,397 +1,426 @@
/*
* Copyright © 2018-2021 Musing Studio LLC.
*
* This file is part of WriteFreely.
*
* WriteFreely is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License, included
* in the LICENSE file in this source code package.
*/
package writefreely
import (
"os"
"io"
"fmt"
"strconv"
"io/ioutil"
"path/filepath"
"encoding/json"
"net/http"
"strings"
uuid "github.com/nu7hatch/gouuid"
"github.com/gorilla/mux"
"github.com/writeas/impart"
"github.com/writeas/web-core/log"
"github.com/writefreely/writefreely/page"
)
func handleViewPad(app *App, w http.ResponseWriter, r *http.Request) error {
vars := mux.Vars(r)
action := vars["action"]
slug := vars["slug"]
collAlias := vars["collection"]
if app.cfg.App.SingleUser {
// TODO: refactor all of this, especially for single-user blogs
c, err := app.db.GetCollectionByID(1)
if err != nil {
return err
}
collAlias = c.Alias
}
appData := &struct {
page.StaticPage
Post *RawPost
User *User
Blogs *[]Collection
Silenced bool
Editing bool // True if we're modifying an existing post
EditCollection *Collection // Collection of the post we're editing, if any
}{
StaticPage: pageForReq(app, r),
Post: &RawPost{Font: "norm"},
User: getUserSession(app, r),
}
var err error
if appData.User != nil {
appData.Blogs, err = app.db.GetPublishableCollections(appData.User, app.cfg.App.Host)
if err != nil {
log.Error("Unable to get user's blogs for Pad: %v", err)
}
appData.Silenced, err = app.db.IsUserSilenced(appData.User.ID)
if err != nil {
if err == ErrUserNotFound {
return err
}
log.Error("Unable to get user status for Pad: %v", err)
}
}
padTmpl := app.cfg.App.Editor
if templates[padTmpl] == nil {
if padTmpl != "" {
log.Info("No template '%s' found. Falling back to default 'pad' template.", padTmpl)
}
padTmpl = "pad"
}
if action == "" && slug == "" {
// Not editing any post; simply render the Pad
if err = templates[padTmpl].ExecuteTemplate(w, "pad", appData); err != nil {
log.Error("Unable to execute template: %v", err)
}
return nil
}
// Retrieve post information for editing
appData.Editing = true
// Make sure this isn't cached, so user doesn't accidentally lose data
w.Header().Set("Cache-Control", "no-cache, no-store, must-revalidate")
w.Header().Set("Expires", "Thu, 04 Oct 1990 20:00:00 GMT")
if slug != "" {
// TODO: refactor all of this, especially for single-user blogs
appData.Post = getRawCollectionPost(app, slug, collAlias)
if appData.Post.OwnerID != appData.User.ID {
// TODO: add ErrForbiddenEditPost message to flashes
return impart.HTTPError{http.StatusFound, r.URL.Path[:strings.LastIndex(r.URL.Path, "/edit")]}
}
appData.EditCollection, err = app.db.GetCollectionForPad(collAlias)
if err != nil {
log.Error("Unable to GetCollectionForPad: %s", err)
return err
}
appData.EditCollection.hostName = app.cfg.App.Host
} else {
// Editing a floating article
appData.Post = getRawPost(app, action)
appData.Post.Id = action
}
if appData.Post.Gone {
return ErrPostUnpublished
} else if appData.Post.Found && (appData.Post.Title != "" || appData.Post.Content != "") {
// Got the post
} else if appData.Post.Found {
log.Error("Found post, but other conditions failed.")
return ErrPostFetchError
} else {
return ErrPostNotFound
}
if err = templates[padTmpl].ExecuteTemplate(w, "pad", appData); err != nil {
log.Error("Unable to execute template: %v", err)
}
return nil
}
func okToEdit(app *App, w http.ResponseWriter, r *http.Request) error {
vars := mux.Vars(r)
action := vars["action"]
slug := vars["slug"]
collAlias := vars["collection"]
appData := &struct {
page.StaticPage
Post *RawPost
User *User
EditCollection *Collection // Collection of the post we're editing, if any
Flashes []string
NeedsToken bool
Silenced bool
}{
StaticPage: pageForReq(app, r),
Post: &RawPost{Font: "norm"},
User: getUserSession(app, r),
}
var err error
appData.Silenced, err = app.db.IsUserSilenced(appData.User.ID)
if err != nil {
log.Error("view meta: get user status: %v", err)
return ErrInternalGeneral
}
if action == "" && slug == "" {
return ErrPostNotFound
}
// Make sure this isn't cached, so user doesn't accidentally lose data
w.Header().Set("Cache-Control", "no-cache, no-store, must-revalidate")
w.Header().Set("Expires", "Thu, 28 Jul 1989 12:00:00 GMT")
if slug != "" {
appData.Post = getRawCollectionPost(app, slug, collAlias)
if appData.Post.OwnerID != appData.User.ID {
// TODO: add ErrForbiddenEditPost message to flashes
return impart.HTTPError{
http.StatusFound, r.URL.Path[:strings.LastIndex(r.URL.Path, "/meta")]}
}
if app.cfg.App.SingleUser {
// TODO: optimize this query just like we do in GetCollectionForPad (?)
appData.EditCollection, err = app.db.GetCollectionByID(1)
} else {
appData.EditCollection, err = app.db.GetCollectionForPad(collAlias)
}
if err != nil {
return err
}
appData.EditCollection.hostName = app.cfg.App.Host
} else {
// Editing a floating article
appData.Post = getRawPost(app, action)
appData.Post.Id = action
}
appData.NeedsToken = appData.User == nil || appData.User.ID != appData.Post.OwnerID
if appData.Post.Gone {
return ErrPostUnpublished
} else if appData.Post.Found && appData.Post.Content != "" {
// Got the post
} else if appData.Post.Found {
return ErrPostFetchError
} else {
return ErrPostNotFound
}
return nil
}
func handleDeleteFile(app *App, w http.ResponseWriter, r *http.Request) error {
vars := mux.Vars(r)
fileName := vars["filename"]
slug := vars["slug"]
user := getUserSession(app, r)
filePath := filepath.Join(app.cfg.Server.MediaParentDir, mediaDir, user.Username, slug) + "/" + fileName
err := os.Remove(filePath)
if err != nil {
w.WriteHeader(http.StatusNotFound)
return ErrFileNotFound
}
w.WriteHeader(http.StatusOK)
return nil
}
func handleViewMeta(app *App, w http.ResponseWriter, r *http.Request) error {
vars := mux.Vars(r)
action := vars["action"]
slug := vars["slug"]
collAlias := vars["collection"]
appData := &struct {
page.StaticPage
Post *RawPost
User *User
EditCollection *Collection // Collection of the post we're editing, if any
Flashes []string
NeedsToken bool
Silenced bool
}{
StaticPage: pageForReq(app, r),
Post: &RawPost{Font: "norm"},
User: getUserSession(app, r),
}
var err error
appData.Silenced, err = app.db.IsUserSilenced(appData.User.ID)
if err != nil {
log.Error("view meta: get user status: %v", err)
return ErrInternalGeneral
}
if action == "" && slug == "" {
return ErrPostNotFound
}
// Make sure this isn't cached, so user doesn't accidentally lose data
w.Header().Set("Cache-Control", "no-cache, no-store, must-revalidate")
w.Header().Set("Expires", "Thu, 28 Jul 1989 12:00:00 GMT")
if slug != "" {
appData.Post = getRawCollectionPost(app, slug, collAlias)
if appData.Post.OwnerID != appData.User.ID {
// TODO: add ErrForbiddenEditPost message to flashes
return impart.HTTPError{http.StatusFound, r.URL.Path[:strings.LastIndex(r.URL.Path, "/meta")]}
}
if app.cfg.App.SingleUser {
// TODO: optimize this query just like we do in GetCollectionForPad (?)
appData.EditCollection, err = app.db.GetCollectionByID(1)
} else {
appData.EditCollection, err = app.db.GetCollectionForPad(collAlias)
}
if err != nil {
return err
}
appData.EditCollection.hostName = app.cfg.App.Host
} else {
// Editing a floating article
appData.Post = getRawPost(app, action)
appData.Post.Id = action
}
appData.NeedsToken = appData.User == nil || appData.User.ID != appData.Post.OwnerID
if appData.Post.Gone {
return ErrPostUnpublished
} else if appData.Post.Found && appData.Post.Content != "" {
// Got the post
} else if appData.Post.Found {
return ErrPostFetchError
} else {
return ErrPostNotFound
}
appData.Flashes, _ = getSessionFlashes(app, w, r, nil)
user := getUserSession(app, r)
mediaDirectoryPath := filepath.Join(app.cfg.Server.MediaParentDir, mediaDir,
user.Username, slug)
appData.Post.MediaFilesList, _ = getFilesListInPath(mediaDirectoryPath)
if err = templates["edit-meta"].ExecuteTemplate(w, "edit-meta", appData); err != nil {
log.Error("Unable to execute template: %v", err)
}
return nil
}
func getNewFileName(path string, originalFieName string) (string, error) {
u, err := uuid.NewV4()
if err != nil {
log.Error("Unable to generate uuid: %v", err)
return "", err
}
extension := filepath.Ext(originalFieName)
return u.String() + extension, nil
}
func getFilesListInPath(path string) ([]string, error) {
var files []string
entries, err := ioutil.ReadDir(path)
if err != nil {
return nil, err
}
for _, entry := range entries {
if !entry.IsDir() {
files = append(files, "/" + path + "/" + entry.Name())
}
}
return files, nil
}
func handleGetFile(app *App, w http.ResponseWriter, r *http.Request) error {
vars := mux.Vars(r)
slug := vars["slug"]
author := vars["author"]
filename := vars["filename"]
mediaDirectoryPath := filepath.Join(app.cfg.Server.MediaParentDir, mediaDir,
author, slug, filename)
filePath := mediaDirectoryPath
file, err := http.Dir("").Open(filePath)
if err != nil {
http.Error(w, "File not found", http.StatusNotFound)
return nil
}
defer file.Close()
fileInfo, err := file.Stat()
if err != nil {
http.Error(w, "Failed to get file information", http.StatusInternalServerError)
return nil
}
w.Header().Set("Content-Disposition", "attachment; filename="+fileInfo.Name())
w.Header().Set("Content-Type", "application/octet-stream")
w.Header().Set("Content-Length", strconv.FormatInt(fileInfo.Size(), 10))
http.ServeContent(w, r, fileInfo.Name(), fileInfo.ModTime(), file)
return nil
}
+func calculateDirectoryTotalSize(dirPath string) (int64, error) {
+ var totalSize int64
+
+ err := filepath.Walk(dirPath, func(path string, info os.FileInfo, err error) error {
+ if err != nil {
+ return err
+ }
+
+ if !info.IsDir() {
+ totalSize += info.Size()
+ }
+
+ return nil
+ })
+
+ return totalSize, err
+}
+
func handleUploadMedia(app *App, w http.ResponseWriter, r *http.Request) error {
maxUploadSize := app.cfg.App.MediaMaxSize * 1024 * 1024
r.Body = http.MaxBytesReader(w, r.Body, maxUploadSize)
if err := r.ParseMultipartForm(maxUploadSize); err != nil {
errMsg := fmt.Sprintf("File size limit exceeded. The limit is: %d MB",
app.cfg.App.MediaMaxSize)
http.Error(w, errMsg, http.StatusBadRequest)
return nil
}
+ fileSize := r.ContentLength
+
if err := okToEdit(app, w, r); err != nil {
return err
}
vars := mux.Vars(r)
slug := vars["slug"]
user := getUserSession(app, r)
if slug == "" {
return ErrPostNotFound
}
if r.Method != http.MethodPost {
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
return nil
}
file, handler, err := r.FormFile("file")
if err != nil {
http.Error(w, "Error retrieving the file", http.StatusInternalServerError)
return nil
}
defer file.Close()
mediaDirectoryPath := filepath.Join(app.cfg.Server.MediaParentDir, mediaDir,
user.Username, slug)
err = os.MkdirAll(mediaDirectoryPath, 0755)
if err != nil {
return err
- } else {
}
+
+ totalSize, err := calculateDirectoryTotalSize(mediaDirectoryPath)
+ totalMediaSpace := app.cfg.App.TotalMediaSpace * 1024 * 1024
+ if totalSize + fileSize > totalMediaSpace {
+ errMsg := fmt.Sprintf("Your upload space limit has been exceeded. Your limit is: %d MB",
+ app.cfg.App.TotalMediaSpace)
+ http.Error(w, errMsg, http.StatusBadRequest)
+ return nil
+ }
+
newFileName, _ := getNewFileName(mediaDirectoryPath, handler.Filename)
newFilePath := filepath.Join(mediaDirectoryPath, newFileName)
dst, err := os.Create(newFilePath)
if err != nil {
http.Error(w, "Error saving the file", http.StatusInternalServerError)
return nil
}
defer dst.Close()
_, err = io.Copy(dst, file)
if err != nil {
http.Error(w, "Error copying the file", http.StatusInternalServerError)
return nil
}
response := map[string]string{
"message": "File uploaded successfully!",
"path": newFilePath,
}
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(response)
return nil
}
File Metadata
Details
Attached
Mime Type
text/x-diff
Expires
Fri, May 16, 5:54 PM (1 d, 6 h)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
3240293
Attached To
rWF WriteFreely
Event Timeline
Log In to Comment