Create a rendering stub
This commit is contained in:
parent
b7a4b1fee8
commit
ddd674e88a
10
assets/templates/base.html.tmpl
Normal file
10
assets/templates/base.html.tmpl
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<title>{{block "title" .}}{{end}}</title>{{block "head-append" .}}{{end}}
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
{{block "body" .}}{{end}}
|
||||||
|
</body>
|
||||||
|
</html>
|
0
assets/templates/base.txt.tmpl
Normal file
0
assets/templates/base.txt.tmpl
Normal file
14
assets/templates/index.html.tmpl
Normal file
14
assets/templates/index.html.tmpl
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
{{define "body"}}
|
||||||
|
<pre>
|
||||||
|
#RU URL SHORTENER
|
||||||
|
=================
|
||||||
|
|
||||||
|
Based on https://0x0.st/, this site allows you to easily shorten URLs using
|
||||||
|
the command line.
|
||||||
|
|
||||||
|
## USAGE
|
||||||
|
|
||||||
|
# Shorten a URL
|
||||||
|
curl -F'shorten=http://example.com/some/long/url' <a href="https://hashru.link">https://hashru.link</a>
|
||||||
|
</pre>
|
||||||
|
{{end}}
|
@ -1,9 +1,5 @@
|
|||||||
package handlers
|
package handlers
|
||||||
|
|
||||||
//go:generate go get github.com/go-bindata/go-bindata
|
|
||||||
//go:generate go get -u github.com/go-bindata/go-bindata/...
|
|
||||||
//go:generate go-bindata -pkg $GOPACKAGE -prefix ../assets ../assets/...
|
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"crypto/rand"
|
"crypto/rand"
|
||||||
@ -15,7 +11,6 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
"strings"
|
"strings"
|
||||||
"text/template"
|
|
||||||
"time"
|
"time"
|
||||||
"unicode"
|
"unicode"
|
||||||
|
|
||||||
@ -58,10 +53,6 @@ var ReservedPasteKeys [][]byte = [][]byte{[]byte("xd42"), []byte("example")}
|
|||||||
var base64Alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_"
|
var base64Alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_"
|
||||||
var base64Encoder = base64.RawURLEncoding.WithPadding(base64.NoPadding)
|
var base64Encoder = base64.RawURLEncoding.WithPadding(base64.NoPadding)
|
||||||
|
|
||||||
// Page contents
|
|
||||||
var baseTemplate = template.New("empty")
|
|
||||||
var indexTemplate = template.Must(baseTemplate.Parse(string(MustAsset("text/index.txt"))))
|
|
||||||
|
|
||||||
func (t PasteType) String() (string, error) {
|
func (t PasteType) String() (string, error) {
|
||||||
switch t {
|
switch t {
|
||||||
case TypePaste:
|
case TypePaste:
|
||||||
@ -85,9 +76,7 @@ func (t PasteState) String() (string, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func IndexGetHandler(w http.ResponseWriter, r *http.Request) {
|
func IndexGetHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
if err := indexTemplate.Execute(w, nil); err != nil {
|
Render(w, r, "index", nil)
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func IndexPostHandler(w http.ResponseWriter, r *http.Request) {
|
func IndexPostHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
201
handlers/views.go
Normal file
201
handlers/views.go
Normal file
@ -0,0 +1,201 @@
|
|||||||
|
package handlers
|
||||||
|
|
||||||
|
//go:generate go get github.com/go-bindata/go-bindata
|
||||||
|
//go:generate go get -u github.com/go-bindata/go-bindata/...
|
||||||
|
//go:generate go-bindata -pkg $GOPACKAGE -prefix ../assets ../assets/...
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
html "html/template"
|
||||||
|
"io"
|
||||||
|
"log"
|
||||||
|
"net/http"
|
||||||
|
"path/filepath"
|
||||||
|
"regexp"
|
||||||
|
"runtime/debug"
|
||||||
|
"sort"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
text "text/template"
|
||||||
|
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Plain text templates
|
||||||
|
var TextBaseTemplate *text.Template = text.Must(text.New("Empty").Parse(string(MustAsset("templates/base.txt.tmpl"))))
|
||||||
|
var HTMLBaseTemplate *html.Template = html.Must(html.New("Empty").Parse(string(MustAsset("templates/base.html.tmpl"))))
|
||||||
|
|
||||||
|
// Template collections
|
||||||
|
var TextTemplates = make(map[string]*text.Template, 0)
|
||||||
|
var HTMLTemplates = make(map[string]*html.Template, 0)
|
||||||
|
|
||||||
|
// Used by resolveResponseContentType
|
||||||
|
var acceptHeaderMediaRangeRegex = regexp.MustCompile(`^\s*([^()<>@,;:\\"/\[\]?.=]+)/([^()<>@,;:\\"/\[\]?.=]+)\s*$`)
|
||||||
|
var acceptHeaderAcceptParamsRegex = regexp.MustCompile(`^\s*(\w+)=([A-Za-z0-9.-])\s*$`)
|
||||||
|
var acceptHeaderWeight = regexp.MustCompile(`^\s*q=0(?:\.([0-9]{0,3}))|1(?:\.0{0,3})\s*$`)
|
||||||
|
|
||||||
|
// HTML templates
|
||||||
|
func init() {
|
||||||
|
for _, tmplPath := range AssetNames() {
|
||||||
|
if mustMatch("templates/*.txt.tmpl", tmplPath) {
|
||||||
|
tmpl := text.Must(TextBaseTemplate.Parse(string(MustAsset(tmplPath))))
|
||||||
|
tmplName := strings.TrimSuffix(filepath.Base(tmplPath), ".txt.tmpl")
|
||||||
|
TextTemplates[tmplName] = tmpl
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if mustMatch("templates/*.html.tmpl", tmplPath) {
|
||||||
|
tmpl := html.Must(HTMLBaseTemplate.Parse(string(MustAsset(tmplPath))))
|
||||||
|
tmplName := strings.TrimSuffix(filepath.Base(tmplPath), ".html.tmpl")
|
||||||
|
HTMLTemplates[tmplName] = tmpl
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func mustMatch(pattern, name string) bool {
|
||||||
|
m, err := filepath.Match(pattern, name)
|
||||||
|
if err != nil {
|
||||||
|
panic("%v error in call to mustMatch")
|
||||||
|
}
|
||||||
|
return m
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseFail(tmplName string, err error) {
|
||||||
|
err = errors.Wrapf(err, "parsing of %v failed", tmplName)
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Render(w http.ResponseWriter, r *http.Request, tmplName string, data interface{}) {
|
||||||
|
contentType, err := resolveResponseContentType(r, []string{"text/plain", "text/html"})
|
||||||
|
if err != nil {
|
||||||
|
w.WriteHeader(http.StatusNotAcceptable)
|
||||||
|
fmt.Fprintf(w, "error parsing Accept header: %v\n", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
switch contentType {
|
||||||
|
case "text/plain":
|
||||||
|
w.Header().Set("Content-Type", "text/plain")
|
||||||
|
tmpl := TextTemplates[tmplName]
|
||||||
|
if tmpl == nil {
|
||||||
|
err = fmt.Errorf("'%v' not in TextTemplates", tmplName)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
err = tmpl.Execute(w, data)
|
||||||
|
case "text/html":
|
||||||
|
w.Header().Set("Content-Type", "text/html")
|
||||||
|
tmpl := HTMLTemplates[tmplName]
|
||||||
|
if tmpl == nil {
|
||||||
|
err = fmt.Errorf("'%v' not in HTMLTemplates", tmplName)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
err = tmpl.Execute(w, data)
|
||||||
|
default:
|
||||||
|
w.WriteHeader(http.StatusNotAcceptable)
|
||||||
|
io.WriteString(w, "could not resolve an acceptable content-type\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Try to resolve the preferred content-type for the response to this request.
|
||||||
|
//
|
||||||
|
// This is done by reading from the `types` argument. If one of them matches
|
||||||
|
// the preferences supplied by the client in their Accept header, we will
|
||||||
|
// return that one. We will take the clients preferences into account.
|
||||||
|
//
|
||||||
|
// Iff no match could be found, this function will return an empty string, and
|
||||||
|
// the caller should probably respond with a 406 Not Acceptable status code.
|
||||||
|
// Iff the Accept header was invalid, we will return an error. In this case,
|
||||||
|
// the situation calls for a 400 Bad Request.
|
||||||
|
func resolveResponseContentType(r *http.Request, types []string) (string, error) {
|
||||||
|
// Ref: https://tools.ietf.org/html/rfc7231#section-5.3.2
|
||||||
|
if len(types) == 0 {
|
||||||
|
return "", nil
|
||||||
|
}
|
||||||
|
acceptHeader := r.Header.Get("Accept")
|
||||||
|
if acceptHeader == "" {
|
||||||
|
return types[0], nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type AcceptValue struct {
|
||||||
|
Type string
|
||||||
|
Subtype string
|
||||||
|
Weight int
|
||||||
|
}
|
||||||
|
|
||||||
|
avStrings := strings.Split(acceptHeader, ",")
|
||||||
|
avs := make([]AcceptValue, len(avStrings))
|
||||||
|
for i, avString := range avStrings {
|
||||||
|
av := AcceptValue{Weight: 1000}
|
||||||
|
choiceParts := strings.Split(avString, ";")
|
||||||
|
mediaRange := acceptHeaderMediaRangeRegex.FindStringSubmatch(choiceParts[0])
|
||||||
|
if mediaRange == nil {
|
||||||
|
return "", fmt.Errorf("bad media-range (\"%v\")", choiceParts[0])
|
||||||
|
}
|
||||||
|
av.Type = mediaRange[1]
|
||||||
|
av.Subtype = mediaRange[2]
|
||||||
|
// Go through the rest to see if there is a q=... parameter
|
||||||
|
for choiceParts = choiceParts[1:]; len(choiceParts) > 0; choiceParts = choiceParts[1:] {
|
||||||
|
// Try to parse the weight param
|
||||||
|
weight := acceptHeaderWeight.FindStringSubmatch(choiceParts[0])
|
||||||
|
if weight != nil {
|
||||||
|
if weight[1] == "" {
|
||||||
|
av.Weight = 0
|
||||||
|
} else {
|
||||||
|
var err error
|
||||||
|
av.Weight, err = strconv.Atoi((weight[1] + "000")[:3])
|
||||||
|
if err != nil {
|
||||||
|
log.Println("error: unreachable statement")
|
||||||
|
debug.PrintStack()
|
||||||
|
av.Weight = 1000 // Reset to default value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
// Check if this parameter is still invalid in any case
|
||||||
|
acceptParams := acceptHeaderAcceptParamsRegex.FindStringSubmatchIndex(choiceParts[0])
|
||||||
|
if acceptParams == nil {
|
||||||
|
return "", fmt.Errorf("bad accept-params (\"%v\")", choiceParts[0])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
avs[i] = av
|
||||||
|
}
|
||||||
|
|
||||||
|
sort.SliceStable(avs, func(i, j int) bool {
|
||||||
|
if avs[i].Weight > avs[j].Weight {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if avs[i].Type != "*" && avs[j].Type == "*" {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if avs[i].Subtype != "*" && avs[j].Subtype == "*" {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
})
|
||||||
|
|
||||||
|
avArgs := make([]AcceptValue, len(types))
|
||||||
|
for i, fulltype := range types {
|
||||||
|
split := strings.Split(fulltype, "/")
|
||||||
|
if len(split) == 1 {
|
||||||
|
avArgs[i] = AcceptValue{Type: split[0]}
|
||||||
|
} else {
|
||||||
|
avArgs[i] = AcceptValue{Type: split[0], Subtype: split[1]}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, av := range avs {
|
||||||
|
for j, avArg := range avArgs {
|
||||||
|
if !(av.Type == avArg.Type || av.Type == "*" || avArg.Type == "*") {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if !(av.Subtype == avArg.Subtype || av.Subtype == "*" || avArg.Subtype == "*") {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
return types[j], nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return "", nil
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user