commit 3c42d7d87800ca5264b80f0451a104dd30ff1a2b Author: James Mills Date: Sun Jul 2 23:42:34 2017 -0700 Initial Commit diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..ef4147d --- /dev/null +++ b/Dockerfile @@ -0,0 +1,17 @@ +FROM golang:alpine + +EXPOSE 8000/tcp + +ENTRYPOINT ["pastebin"] + +RUN \ + apk add --update git && \ + rm -rf /var/cache/apk/* + +RUN mkdir -p /go/src/pastebin +WORKDIR /go/src/pastebin + +COPY . /go/src/pastebin + +RUN go get -v -d +RUN go install -v diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..32275b5 --- /dev/null +++ b/LICENSE @@ -0,0 +1,22 @@ +Copyright (C) 2017 James Mills + +httpfs is covered by the MIT license:: + +Permission is hereby granted, free of charge, to any person +obtaining a copy of this software and associated documentation +files (the "Software"), to deal in the Software without restriction, +including without limitation the rights to use, copy, modify, merge, +publish, distribute, sublicense, and/or sell copies of the Software, +and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included +in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE +OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..9d9707d --- /dev/null +++ b/Makefile @@ -0,0 +1,13 @@ +.PHONY: dev build clean + +all: dev + +dev: build + ./pastebin -bind 127.0.0.1:8000 + +build: clean + go get ./... + go build -o ./pastebin . + +clean: + rm -rf bin pastebin diff --git a/README.md b/README.md new file mode 100644 index 0000000..9efc116 --- /dev/null +++ b/README.md @@ -0,0 +1,49 @@ +# pastebin +[![Build Status](https://travis-ci.org/prologic/pastebin.svg)](https://travis-ci.org/prologic/pastebin) +[![GoDoc](https://godoc.org/github.com/prologic/pastebin?status.svg)](https://godoc.org/github.com/prologic/pastebin) +[![Wiki](https://img.shields.io/badge/docs-wiki-blue.svg)](https://github.com/prologic/pastebin/wiki) +[![Go Report Card](https://goreportcard.com/badge/github.com/prologic/pastebin)](https://goreportcard.com/report/github.com/prologic/pastebin) +[![Coverage](https://coveralls.io/repos/prologic/pastebin/badge.svg)](https://coveralls.io/r/prologic/pastebin) + +pastebin is a web app that allows you to create smart bookmarks, commands and aliases by pointing your web browser's default search engine at a running instance. Similar to bunny1 or yubnub. + +## Installation + +### Source + +```#!bash +$ go install github.com/prologic/pastebin/... +``` + +### OS X Homebrew + +There is a formula provided that you can tap and install from +[prologic/homebrew-pastebin](https://github.com/prologic/homebrew-pastebin): + +```#!bash +$ brew tap prologic/pastebin +$ brew install pastebin +``` + +**NB:** This installs the latest released binary; so if you want a more +recent unreleased version from master you'll have to clone the repository +and build yourself. + +pastebin is still early days so contributions, ideas and expertise are +much appreciated and highly welcome! + +## Usage + +Run pastebin: + +```#!bash +$ pastebin -bind 127.0.0.1:8000 +``` + +Set your browser's default pastebin engine to http://localhost:8000/?q=%s + +Then type `help` to view the main help page, `g foo bar` to perform a [Google](https://google.com) search for "foo bar" or `list` to list all available commands. + +## License + +MIT diff --git a/config.go b/config.go new file mode 100644 index 0000000..2cb4796 --- /dev/null +++ b/config.go @@ -0,0 +1,7 @@ +package main + +// Config ... +type Config struct { + expiry int + fqdn string +} diff --git a/config_test.go b/config_test.go new file mode 100644 index 0000000..aa628d8 --- /dev/null +++ b/config_test.go @@ -0,0 +1,23 @@ +package main + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestZeroConfig(t *testing.T) { + assert := assert.New(t) + + cfg := Config{} + assert.Equal(cfg.expiry, 0) + assert.Equal(cfg.fqdn, "") +} + +func TestConfig(t *testing.T) { + assert := assert.New(t) + + cfg := Config{expiry: 1800, fqdn: "https://localhost"} + assert.Equal(cfg.expiry, 1800) + assert.Equal(cfg.fqdn, "https://localhost") +} diff --git a/main.go b/main.go new file mode 100644 index 0000000..bc2a6a5 --- /dev/null +++ b/main.go @@ -0,0 +1,43 @@ +package main + +import ( + "log" + + "github.com/boltdb/bolt" + "github.com/namsral/flag" +) + +var ( + db *bolt.DB + cfg Config +) + +func main() { + var ( + config string + dbpath string + bind string + fqdn string + expiry int + ) + + flag.StringVar(&config, "config", "", "config file") + flag.StringVar(&dbpath, "dbpth", "urls.db", "Database path") + flag.StringVar(&bind, "bind", "0.0.0.0:8000", "[int]: to bind to") + flag.StringVar(&fqdn, "fqdn", "localhost", "FQDN for public access") + flag.IntVar(&expiry, "expiery", 5, "expiry time (mins) for passte") + flag.Parse() + + // TODO: Abstract the Config and Handlers better + cfg.fqdn = fqdn + cfg.expiry = expiry + + var err error + db, err = bolt.Open(dbpath, 0600, nil) + if err != nil { + log.Fatal(err) + } + defer db.Close() + + NewServer(bind, cfg).ListenAndServe() +} diff --git a/scripts/release.sh b/scripts/release.sh new file mode 100755 index 0000000..e6f128e --- /dev/null +++ b/scripts/release.sh @@ -0,0 +1,43 @@ +#!/bin/bash + +echo -n "Version to tag: " +read TAG + +echo -n "Name of release: " +read NAME + +echo -n "Desc of release: " +read DESC + +git tag ${TAG} +git push --tags + +if [ ! -d ./bin ]; then + mkdir bin +else + rm -rf ./bin/* +fi + +echo -n "Building binaries ... " + +rice embed-go + +GOOS=linux GOARCH=amd64 go build -o ./bin/pastebin-Linux-x86_64 . +GOOS=linux GOARCH=arm64 go build -o ./bin/pastebin-Linux-x86_64 . +GOOS=darwin GOARCH=amd64 go build -o ./bin/pastebin-Darwin-x86_64 . +GOOS=windows GOARCH=amd64 go build -o ./bin/pastebin-Windows-x86_64.exe . + +echo "DONE" + +echo -n "Uploading binaries ... " + +github-release release \ + -u prologic -p -r pastebin \ + -t ${TAG} -n "${NAME}" -d "${DESC}" + +for file in bin/*; do + name="$(echo $file | sed -e 's|bin/||g')" + github-release upload -u prologic -r pastebin -t ${TAG} -n $name -f $file +done + +echo "DONE" diff --git a/server.go b/server.go new file mode 100644 index 0000000..e9414ba --- /dev/null +++ b/server.go @@ -0,0 +1,109 @@ +package main + +import ( + "fmt" + "log" + "net/http" + "net/url" + "time" + + "github.com/GeertJohan/go.rice" + "github.com/julienschmidt/httprouter" + "github.com/patrickmn/go-cache" + "github.com/renstrom/shortuuid" + "go.iondynamics.net/templice" +) + +// Server ... +type Server struct { + bind string + config Config + store *cache.Cache + templates *templice.Template + router *httprouter.Router +} + +func (s *Server) render(w http.ResponseWriter, tmpl string, data interface{}) { + err := s.templates.ExecuteTemplate(w, tmpl+".html", data) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + } +} + +// IndexHandler ... +func (s *Server) IndexHandler() httprouter.Handle { + return func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) { + s.render(w, "index", nil) + } +} + +// PasteHandler ... +func (s *Server) PasteHandler() httprouter.Handle { + return func(w http.ResponseWriter, r *http.Request, p httprouter.Params) { + err := r.ParseForm() + if err != nil { + http.Error(w, "Internal Error", http.StatusInternalServerError) + return + } + + uuid := shortuuid.NewWithNamespace(s.config.fqdn) + blob := r.Form.Get("blob") + s.store.Set(uuid, blob, cache.DefaultExpiration) + + u, err := url.Parse(fmt.Sprintf("./%s", uuid)) + if err != nil { + http.Error(w, "Internal Error", http.StatusInternalServerError) + } + http.Redirect(w, r, r.URL.ResolveReference(u).String(), http.StatusFound) + } +} + +// ViewHandler ... +func (s *Server) ViewHandler() httprouter.Handle { + return func(w http.ResponseWriter, r *http.Request, p httprouter.Params) { + uuid := p.ByName("uuid") + if uuid == "" { + http.Error(w, "Bad Request", http.StatusBadRequest) + return + } + + blob, ok := s.store.Get(uuid) + if !ok { + http.Error(w, "Not Found", http.StatusNotFound) + return + } + + s.render(w, "view", struct{ Blob string }{Blob: blob.(string)}) + } +} + +// ListenAndServe ... +func (s *Server) ListenAndServe() { + log.Fatal(http.ListenAndServe(s.bind, s.router)) +} + +func (s *Server) initRoutes() { + s.router.GET("/", s.IndexHandler()) + s.router.POST("/", s.PasteHandler()) + s.router.GET("/:uuid", s.ViewHandler()) +} + +// NewServer ... +func NewServer(bind string, config Config) *Server { + server := &Server{ + bind: bind, + config: config, + router: httprouter.New(), + store: cache.New(5*time.Minute, 10*time.Minute), + templates: templice.New(rice.MustFindBox("templates")), + } + + err := server.templates.Load() + if err != nil { + log.Panicf("error loading templates: %s", err) + } + + server.initRoutes() + + return server +} diff --git a/templates/index.html b/templates/index.html new file mode 100644 index 0000000..5447c5b --- /dev/null +++ b/templates/index.html @@ -0,0 +1,14 @@ + + + pastebin + + + +

Enter your text here:

+
+ + +
+ + + diff --git a/templates/view.html b/templates/view.html new file mode 100644 index 0000000..0e8381e --- /dev/null +++ b/templates/view.html @@ -0,0 +1,10 @@ + + + pastebin + + + +
{{.Blob}}
+ + + diff --git a/urls.db b/urls.db new file mode 100644 index 0000000..e449c28 Binary files /dev/null and b/urls.db differ diff --git a/utils.go b/utils.go new file mode 100644 index 0000000..d5fb2b8 --- /dev/null +++ b/utils.go @@ -0,0 +1,19 @@ +package main + +import ( + "crypto/rand" +) + +const ( + alphabet = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz" +) + +// RandomString ... +func RandomString(length int) string { + bytes := make([]byte, length) + rand.Read(bytes) + for i, b := range bytes { + bytes[i] = alphabet[b%byte(len(alphabet))] + } + return string(bytes) +}