Skip to content
This repository has been archived by the owner on Jan 20, 2022. It is now read-only.

Commit

Permalink
Add attachment manipulation UI
Browse files Browse the repository at this point in the history
Fixes #13
  • Loading branch information
zombiezen committed Jul 2, 2019
1 parent 89ba062 commit 83557e0
Show file tree
Hide file tree
Showing 5 changed files with 140 additions and 1 deletion.
2 changes: 1 addition & 1 deletion .vscode/tasks.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
{
"label": "docker-spk build",
"type": "shell",
"command": "docker-spk build",
"command": "docker-spk build -appkey=ztxr53r3uh7fuh6y8td7rjztyzf5d3c8sq9jhkjmr6wfvy41xnqh",
"group": {
"kind": "build",
"isDefault": true
Expand Down
95 changes: 95 additions & 0 deletions sandpass.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,11 @@ import (
"io"
"io/ioutil"
"log"
"mime"
"net/http"
"net/url"
"os"
slashpath "path"
"path/filepath"
"sort"
"strconv"
Expand Down Expand Up @@ -128,6 +130,10 @@ func initHandlers() {
rEntry.Handle("/edit", appHandler{f: postEntryForm, perm: "write"}).Methods("GET").Name("editEntryForm")
rEntry.Handle("/delete", appHandler{f: confirmDeleteEntry, perm: "write"}).Methods("GET").Name("confirmDeleteEntry")
rEntry.Handle("/delete", appHandler{f: deleteEntry, perm: "write"}).Methods("POST").Name("deleteEntry")
rEntry.Handle("/attachment", appHandler{f: downloadAttachment}).Methods("GET", "HEAD").Name("downloadAttachment")
rEntry.Handle("/attachment", appHandler{f: deleteAttachment, perm: "write"}).Methods("DELETE")
rEntry.Handle("/attachment/delete", appHandler{f: confirmDeleteAttachment, perm: "write"}).Methods("GET", "HEAD").Name("confirmDeleteAttachment")
rEntry.Handle("/attachment/delete", appHandler{f: deleteAttachment, perm: "write"}).Methods("POST").Name("deleteAttachment")

meta := r.PathPrefix("/_").Subrouter()
meta.Handle("/newdb", appHandler{f: newDB, perm: "init"}).Methods("POST").Name("newDB")
Expand Down Expand Up @@ -290,6 +296,32 @@ func viewEntry(w http.ResponseWriter, r *http.Request) error {
})
}

func downloadAttachment(w http.ResponseWriter, r *http.Request) error {
mu.Lock()
defer mu.Unlock()
db, err := sessions.dbFromRequest(w, r)
if err != nil {
return err
}
e, err := requestEntry(db, mux.Vars(r))
if err != nil {
return err
}
if !e.HasAttachment() {
return notFoundError{}
}
w.Header().Set("Content-Disposition", fmt.Sprintf("attachment; filename=%q", e.Attachment.Name))
w.Header().Set("Content-Length", strconv.Itoa(len(e.Attachment.Data)))
contentType := mime.TypeByExtension(slashpath.Ext(e.Attachment.Name))
if contentType == "" {
// http.DetectContentType always returns a valid MIME type.
contentType = http.DetectContentType(e.Attachment.Data)
}
w.Header().Set("Content-Type", contentType)
_, err = w.Write(e.Attachment.Data)
return err
}

func postEntryForm(w http.ResponseWriter, r *http.Request) error {
xtok, err := xsrfToken(w, r)
if err != nil {
Expand Down Expand Up @@ -333,6 +365,9 @@ func postEntryForm(w http.ResponseWriter, r *http.Request) error {

func postEntry(w http.ResponseWriter, r *http.Request) error {
now := time.Now()
if err := parseMultipartForm(r); err != nil {
return err
}
mu.Lock()
defer mu.Unlock()
var e *keepass.Entry
Expand Down Expand Up @@ -364,6 +399,15 @@ func postEntry(w http.ResponseWriter, r *http.Request) error {
e.Username = r.FormValue("username")
e.Password = r.FormValue("password")
e.URL = r.FormValue("url")
if f, header, err := r.FormFile("attachment"); err == nil {
e.Attachment.Name = header.Filename
e.Attachment.Data, err = ioutil.ReadAll(f)
if err != nil {
return err
}
} else if err != http.ErrMissingFile {
return err
}
e.Notes = r.FormValue("notes")
e.LastModificationTime = now
return nil
Expand All @@ -374,6 +418,57 @@ func postEntry(w http.ResponseWriter, r *http.Request) error {
return redirectRoute(w, r, "viewEntry", "uuid", e.UUID.String())
}

func confirmDeleteAttachment(w http.ResponseWriter, r *http.Request) error {
xtok, err := xsrfToken(w, r)
if err != nil {
return err
}
mu.Lock()
defer mu.Unlock()
db, err := sessions.dbFromRequest(w, r)
if err != nil {
return err
}
e, err := requestEntry(db, mux.Vars(r))
if err != nil {
return err
}
return tmpl.ExecuteTemplate(w, "deleteattachment.html", struct {
Entry *keepass.Entry
Group *keepass.Group
XSRFToken string
}{
Entry: e,
Group: e.Parent(),
XSRFToken: xtok,
})
}

func deleteAttachment(w http.ResponseWriter, r *http.Request) error {
now := time.Now()
mu.Lock()
defer mu.Unlock()
var e *keepass.Entry
err := transaction(w, r, func(db *keepass.Database) error {
var err error
e, err = requestEntry(db, mux.Vars(r))
if err != nil {
return err
}
if !e.HasAttachment() {
return notFoundError{}
}
e.Attachment.Name = ""
e.Attachment.Data = nil
e.LastModificationTime = now
return nil
})
if err != nil {
return err
}
return redirectRoute(w, r, "viewEntry", "uuid", e.UUID.String())
}

func confirmDeleteEntry(w http.ResponseWriter, r *http.Request) error {
xtok, err := xsrfToken(w, r)
if err != nil {
Expand Down
26 changes: 26 additions & 0 deletions templates/deleteattachment.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
<!DOCTYPE html>
<title>Delete {{.Entry.Title}} - Sandpass</title>
<link rel="stylesheet" href="/style.css">
<div class="pageContainer">
<header class="pageHeader">
<nav class="breadcrumb">
<a href="{{route "listGroups"}}" class="breadcrumbLink">Groups</a> &#x279C;
{{with .Group}}
<a href="{{route "viewGroup" "gid" .ID}}" class="breadcrumbLink">{{.Name}}</a> &#x279C;
{{end}}
</nav>
<h1 class="pageTitle">Delete {{.Entry.Title}} attachment</h1>
</header>
<main class="pageBody">
<p>Are you sure you want to delete "{{.Entry.Attachment.Name}}"? This action cannot be undone.</p>
<div>
<form method="POST" action="{{route "deleteAttachment" "uuid" .Entry.UUID}}">
{{template "xsrfinput.html" .XSRFToken}}
<button type="button" data-href="{{route "viewEntry" "uuid" .Entry.UUID}}">Cancel</button>
<input type="submit" value="Delete">
</form>
</div>
</main>
</div>
{{template "footer.html"}}
<script src="/js/init.js"></script>
7 changes: 7 additions & 0 deletions templates/editentry.html
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ <h1 class="pageTitle">
action="
{{- if .Entry.UUID.IsZero}}{{route "newEntry" -}}
{{- else}}{{route "editEntry" "uuid" .Entry.UUID}}{{end}}"
enctype="multipart/form-data"
autocomplete="off">
{{template "xsrfinput.html" .XSRFToken}}
<div class="sandpassFields">
Expand Down Expand Up @@ -93,6 +94,12 @@ <h1 class="pageTitle">
autocomplete="off">
</div>
</div>
<div class="sandpassFieldGroup">
<label for="entryAttachment" class="sandpassFieldName">Attachment</label>
<div class="sandpassField">
<input type="file" name="attachment" id="entryAttachment">
</div>
</div>
<div class="sandpassFieldGroup">
<label for="entryNotes" class="sandpassFieldName">Notes</label>
<div class="sandpassField">
Expand Down
11 changes: 11 additions & 0 deletions templates/entry.html
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,17 @@ <h1 class="pageTitle">{{.Entry.Title}}</h1>
<div class="sandpassField sandpassFieldValue"><a href="{{.}}" target="_blank">{{.}}</a></div>
</div>
{{end}}
{{if .HasAttachment}}
<div class="sandpassFieldGroup">
<div class="sandpassFieldName">Attachment</div>
<div class="sandpassField sandpassFieldValue">
{{- with .Attachment -}}
<a href="{{route "downloadAttachment" "uuid" $.Entry.UUID}}">{{.Name}}</a>
{{- end -}}
<button type="button" data-href="{{route "confirmDeleteAttachment" "uuid" $.Entry.UUID}}">Delete</button>
</div>
</div>
{{end}}
{{with .Notes}}
<div class="sandpassFieldGroup">
<div class="sandpassFieldName">Notes</div>
Expand Down

0 comments on commit 83557e0

Please sign in to comment.