Skip to content

Instantly share code, notes, and snippets.

@paulsc
Created October 19, 2022 08:53
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save paulsc/906c8a5d1465a5f227d85e6751027e0f to your computer and use it in GitHub Desktop.
Save paulsc/906c8a5d1465a5f227d85e6751027e0f to your computer and use it in GitHub Desktop.
package main
import (
"archive/tar"
"compress/gzip"
"fmt"
"io"
"io/ioutil"
"net/http"
"os"
"path"
"path/filepath"
"strings"
)
var addr = ":1337"
func printRequest(w http.ResponseWriter, r *http.Request) {
fmt.Printf("received request: %+v\n", r)
w.WriteHeader(http.StatusOK)
}
// exportAttachment returns an archive of all attachments for a submission.
// Not implemented very thoroughly, but it's here to suggest that path
// traversal is the way to go, just not in this endpoint.
func exportAttachment(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodGet {
http.Error(w, "only GET allowed", http.StatusMethodNotAllowed)
return
}
submission := r.URL.Query().Get("submission")
if submission == "" {
http.Error(w, "missing submission parameter", http.StatusBadRequest)
return
}
attachment := r.URL.Query().Get("attachment")
if attachment == "" {
http.Error(w, "missing attachment parameter", http.StatusBadRequest)
return
}
// Get the directory where our binary is.
ex, err := os.Executable()
if err != nil {
fmt.Println(err)
http.Error(w, "error getting the current directory", http.StatusInternalServerError)
return
}
basepath := filepath.Dir(ex)
// Check if this path exists.
submissionPath := path.Join(basepath, filepath.Base(submission))
_, err = os.Stat(submissionPath)
exists := err == nil || !os.IsNotExist(err)
if !exists {
fmt.Println(err)
http.Error(w, fmt.Sprintf("submission %v does not exist (try our sample_submission?)", submissionPath), http.StatusPaymentRequired)
return
}
attachmentPath := path.Join(submissionPath, "attachments", filepath.Base(attachment))
_, err = os.Stat(attachmentPath)
exists = err == nil || !os.IsNotExist(err)
if !exists {
fmt.Println(err)
http.Error(w, fmt.Sprintf("attachment %v does not exist", attachmentPath), http.StatusPaymentRequired)
return
}
// Serve the file.
http.ServeFile(w, r, attachmentPath)
}
// importAttachments imports a tar archive of attachments
func importAttachments(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodPost {
http.Error(w, "only POST allowed", http.StatusMethodNotAllowed)
return
}
submission := r.URL.Query().Get("submission")
if submission == "" {
http.Error(w, "missing submission parameter", http.StatusBadRequest)
return
}
// Allow a dry run to test the endpoint.
dryRun := r.URL.Query().Get("dryRun") != ""
// TODO: Remove this before deploying to prod!
debug := r.URL.Query().Get("debug") != ""
if !dryRun {
http.Error(w, "server undergoing migration, import endpoint is temporarily disabled (dry run still enabled)", http.StatusForbidden)
return
}
// Read the archive from the request.
r.ParseMultipartForm(32 << 20) // Limit max input length
file, header, err := r.FormFile("attachments")
if err != nil {
http.Error(w, fmt.Sprintf("could not open file %v: %v", file, err), http.StatusBadRequest)
return
}
defer file.Close()
filename := filepath.Base(header.Filename)
// Open with gzip and tar.
in, err := gzip.NewReader(file)
if err != nil {
http.Error(w, fmt.Sprintf("could not open file %v with gzip: %v", filename, err), http.StatusBadRequest)
return
}
tr := tar.NewReader(in)
// Parse the .tar.gz file.
for {
var err error
// Read until the EOF chunk.
h, err := tr.Next()
if err != nil {
if err == io.EOF {
break
}
http.Error(w, fmt.Sprintf("error reading tar entry: %v", err), http.StatusBadRequest)
return
}
// Skip directories.
if h.FileInfo().IsDir() {
fmt.Printf("skipping directory %v in archive\n", h.Name)
continue
}
// Check if the file already exists. If so, show a diff.
attachmentPath := path.Join(submission, h.Name)
info, err := os.Stat(attachmentPath)
if err != nil {
fmt.Fprintf(w, "new file: %v\n", attachmentPath)
continue
}
// File already exists.
if !info.Mode().IsRegular() {
fmt.Fprintf(w, "skipping non-regular file attachment %v\n", attachmentPath)
continue
}
// TODO: Remove this before deploying to prod!
if debug {
trContents, err := ioutil.ReadAll(tr)
if err != nil {
http.Error(w, fmt.Sprintf("error reading uploaded attachment %v: %v", h.Name, err), http.StatusBadRequest)
return
}
trString := string(trContents)
existingContents, err := ioutil.ReadFile(attachmentPath)
if err != nil {
http.Error(w, fmt.Sprintf("error reading existing file %v: %v", attachmentPath, err), http.StatusBadRequest)
return
}
existingString := string(existingContents)
if strings.Compare(trString, existingString) == 0 {
fmt.Fprintf(w, "no differences\n")
continue
}
msg += "=====\n"
for _, line := range strings.Split(strings.ReplaceAll(existingString, "\r\n", "\n"), "\n") {
msg += fmt.Sprintf("< %s\n", line)
}
msg += "-----\n"
for _, line := range strings.Split(strings.ReplaceAll(trString, "\r\n", "\n"), "\n") {
msg += fmt.Sprintf("> %s\n", line)
}
msg += "=====\n"
fmt.Fprintf(w, "%s\n", msg)
}
}
}
func main() {
http.HandleFunc("/", printRequest)
http.HandleFunc("/export", exportAttachment)
http.HandleFunc("/import", importAttachments)
fmt.Printf("listening on %v\n", addr)
http.ListenAndServe(addr, nil)
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment