nat/pastebin.go
patchon 5a0b1f5d4d Initial commit of Pastebin fork
This commit is quite big rewrite and individual commits for each feature is
unfortunately not available. This fork introduces quite a lot of new features,
and possibly while doing so, some bugs as well. Nonetheless the features
introduced are listed below.

- Support for multiple database backends (sqlite3, postgresql and mysql are now
  supported). Configurations options like dbname/tablename/ports/adresses/etc.
  are supported as well.

- Support for dynamically adding lexers (languages) and styles (themes) from
  pygments. This means no manual configuration depending on where the
  installation is done. There is however a way of adding "prioritized" lexers.
  This feature can be nice to use sine pygments now days support hundreds of
  languages, some more common than others. Simply add the "display name" for
  each lexer you want to prioritize in the file 'assets/prio-lexers' and they
  will show up first in the list when the user selects languages.

- Support for changing styles and lexers directly from the webgui on the fly
  (no reload of the page, just content update).

- Support for row-highlightning (on/off) and rownumbers (show/hide).

- Support for showing information about when paste expires.

- Support for goo.gl-shortener.

- Extreme debugging.
2016-12-19 19:43:37 +01:00

924 lines
26 KiB
Go

// Package pastebin is a simple modern and powerful pastebin service
package main
import (
"bufio"
"bytes"
"crypto/sha1"
"encoding/base64"
"encoding/json"
"fmt"
"html"
"html/template"
"io"
"log"
"net/http"
"os"
"os/exec"
"strconv"
"strings"
"time"
// Random string generation,
"github.com/dchest/uniuri"
// Database drivers,
"database/sql"
_ "github.com/go-sql-driver/mysql"
_ "github.com/lib/pq"
_ "github.com/mattn/go-sqlite3"
// For url routing
"github.com/gorilla/mux"
)
// Configuration struct,
type Configuration struct {
Address string `json:"address"` // Url to to the pastebin
DBHost string `json:"dbhost"` // Name of your database host
DBName string `json:"dbname"` // Name of your database
DBPassword string `json:"dbpassword"` // The password for the database user
DBPlaceHolder [6]string // ? / $[i] Depending on db driver.
DBPort string `json:"dbport"` // Port of the database
DBTable string `json:"dbtable"` // Name of the table in the database
DBType string `json:"dbtype"` // Type of database
DBUser string `json:"dbuser"` // The database user
DisplayName string `json:"displayname"` // Name of your pastebin
GoogleAPIKey string `json:"googleapikey"` // Your google api key
Highlighter string `json:"highlighter"` // The name of the highlighter.
ListenAddress string `json:"listenaddress"` // Address that pastebin will bind on
ListenPort string `json:"listenport"` // Port that pastebin will listen on
ShortUrlLength int `json:"shorturllength,string"` // Length of the generated short urls
}
// This struct is used for responses.
// A request to the pastebin will always this json struct.
type Response struct {
DelKey string `json:"delkey"` // The id to use when delete a paste
Expiry string `json:"expiry"` // The date when post expires
Extra string `json:"extra"` // Extra output from the highlight-wrapper
Id string `json:"id"` // The id of the paste
Lang string `json:"lang"` // Specified language
Paste string `json:"paste"` // The eactual paste data
Sha1 string `json:"sha1"` // The sha1 of the paste
Size int `json:"size"` // The length of the paste
Status string `json:"status"` // A custom status message
Style string `json:"style"` // Specified style
Title string `json:"title"` // The title of the paste
Url string `json:"url"` // The url of the paste
}
// This struct is used for indata when a request is being made to the pastebin.
type Request struct {
DelKey string `json:"delkey"` // The delkey that is used to delete paste
Expiry int64 `json:"expiry,string"` // An expiry date
Id string `json:"id"` // The id of the paste
Lang string `json:"lang"` // The language of the paste
Paste string `json:"paste"` // The actual pase
Style string `json:"style"` // The style of the paste
Title string `json:"title"` // The title of the paste
WebReq bool `json:"webreq"` // If its a webrequest or not
}
// This struct is used for generating pages.
type Page struct {
Body template.HTML
Expiry string
GoogleAPIKey string
Lang string
LangsFirst map[string]string
LangsLast map[string]string
PasteTitle string
Style string
SupportedStyles map[string]string
Title string
UrlAddress string
UrlClone string
UrlDownload string
UrlHome string
UrlRaw string
WrapperErr string
}
// Template pages,
var templates = template.Must(template.ParseFiles("assets/index.html",
"assets/syntax.html"))
// Global variables, *shrug*
var configuration Configuration
var dbHandle *sql.DB
var debug bool
var debugLogger *log.Logger
var listOfLangsFirst map[string]string
var listOfLangsLast map[string]string
var listOfStyles map[string]string
//
// Functions below,
//
// loggy prints a message if the debug flag is turned.
func loggy(str string) {
if debug {
debugLogger.Println(" " + str)
}
}
// checkErr simply checks if passed error is anything but nil.
// If an error exists it will be printed and the program terminates.
func checkErr(err error) {
if err != nil {
debugLogger.Println(" " + err.Error())
os.Exit(1)
}
}
// getSupportedStyless reads supported styles from the highlighter-wrapper
// (which in turn gets available styles from pygments). It then puts them into
// an array which is used by the html-template. The function doesn't return
// anything since the array is defined globally (shrug).
func getSupportedStyles() {
listOfStyles = make(map[string]string)
arg := "getstyles"
out, err := exec.Command(configuration.Highlighter, arg).Output()
if err != nil {
log.Fatal(err)
}
// Loop lexers and add them to respectively map,
for _, line := range strings.Split(string(out), "\n") {
if line == "" {
continue
}
loggy(fmt.Sprintf("Populating supported styles map with %s", line))
listOfStyles[line] = strings.Title(line)
}
}
// getSupportedLangs reads supported lexers from the highlighter-wrapper (which
// in turn gets available lexers from pygments). It then puts them into two
// maps, depending on if it's a "prioritized" lexers. If it's prioritized or not
// is determined by if its listed in the assets/prio-lexers. The description is
// the key and the actual lexer is the value. The maps are used by the
// html-template. The function doesn't return anything since the maps are
// defined globally (shrug).
func getSupportedLangs() {
var prioLexers map[string]string
// Initialize maps,
prioLexers = make(map[string]string)
listOfLangsFirst = make(map[string]string)
listOfLangsLast = make(map[string]string)
// Get prioritized lexers and put them in a separate map,
file, err := os.Open("assets/prio-lexers")
checkErr(err)
scanner := bufio.NewScanner(file)
for scanner.Scan() {
prioLexers[scanner.Text()] = "1"
}
if err := scanner.Err(); err != nil {
log.Fatal(err)
}
file.Close()
arg := "getlexers"
out, err := exec.Command(configuration.Highlighter, arg).Output()
if err != nil {
log.Fatal(err)
}
// Loop lexers and add them to respectively map,
for _, line := range strings.Split(string(out), "\n") {
if line == "" {
continue
}
s := strings.Split(line, ";")
if len(s) != 2 {
loggy(fmt.Sprintf("Could not split '%v' from %s (fields should be seperated by ;)",
s, configuration.Highlighter))
os.Exit(1)
}
s[0] = strings.Title(s[0])
if prioLexers[s[0]] == "1" {
loggy(fmt.Sprintf("Populating first languages map with %s - %s",
s[0], s[1]))
listOfLangsFirst[s[0]] = s[1]
} else {
loggy(fmt.Sprintf("Populating second languages map with %s - %s",
s[0], s[1]))
listOfLangsLast[s[0]] = s[1]
}
}
}
// printHelp prints a description of the program.
// Exit code will depend on how the function is called.
func printHelp(err int) {
fmt.Printf("\n Description, \n")
fmt.Printf(" - This is a small (< 600 line of go) pastebing with")
fmt.Printf(" support for syntax highlightnig (trough python-pygments).\n")
fmt.Printf(" No more no less.\n\n")
fmt.Printf(" Usage, \n")
fmt.Printf(" - %s [--help] [--debug]\n\n", os.Args[0])
fmt.Printf(" Where, \n")
fmt.Printf(" - help shows this incredibly useful help.\n")
fmt.Printf(" - debug shows quite detailed information about whats")
fmt.Printf(" going on.\n\n")
os.Exit(err)
}
// checkArgs parses the command line in a very simple manner.
func checkArgs() {
if len(os.Args[1:]) >= 1 {
for _, arg := range os.Args[1:] {
switch arg {
case "-h", "--help":
printHelp(0)
case "-d", "--debug":
debug = true
default:
printHelp(1)
}
}
}
}
// getDbHandle opens a connection to database.
// Returns the dbhandle if the open was successful
func getDBHandle() *sql.DB {
var dbinfo string
for i := 0; i < 6; i++ {
configuration.DBPlaceHolder[i] = "?"
}
switch configuration.DBType {
case "sqlite3":
dbinfo = configuration.DBName
loggy("Specified databasetype : " + configuration.DBType)
loggy(fmt.Sprintf("Trying to open %s (%s)",
configuration.DBName, configuration.DBType))
case "postgres":
dbinfo = fmt.Sprintf("host=%s port=%s user=%s password=%s dbname=%s sslmode=disable",
configuration.DBHost,
configuration.DBPort,
configuration.DBUser,
configuration.DBPassword,
configuration.DBName)
for i := 0; i < 6; i++ {
configuration.DBPlaceHolder[i] = "$" + strconv.Itoa(i+1)
}
case "mysql":
dbinfo = configuration.DBUser + ":" + configuration.DBPassword + "@tcp(" + configuration.DBHost + ":" + configuration.DBPort + ")/" + configuration.DBName
case "":
debugLogger.Println(" Database error : dbtype not specified in configuration.")
os.Exit(1)
default:
debugLogger.Println(" Database error : Specified dbtype (" +
configuration.DBType + ") not supported.")
os.Exit(1)
}
db, err := sql.Open(configuration.DBType, dbinfo)
checkErr(err)
// Just create a dummy query to really verify that the database is working as
// expected,
var dummy string
err = db.QueryRow("select id from " + configuration.DBTable + " where id='dummyid'").Scan(&dummy)
switch {
case err == sql.ErrNoRows:
loggy("Successfully connected and found table " + configuration.DBTable)
case err != nil:
debugLogger.Println(" Database error : " + err.Error())
os.Exit(1)
}
return db
}
// generateName generates a short url with the length defined in main config
// The function calls itself recursively until an id that doesn't exist is found
// Returns the id
func generateName() string {
// Use uniuri to generate random string
id := uniuri.NewLen(configuration.ShortUrlLength)
loggy(fmt.Sprintf("Generated id is '%s', checking if it's already taken in the database",
id))
// Query database if id exists and if it does call generateName again
var id_taken string
err := dbHandle.QueryRow("select id from "+configuration.DBTable+
" where id="+configuration.DBPlaceHolder[0], id).
Scan(&id_taken)
switch {
case err == sql.ErrNoRows:
loggy(fmt.Sprintf("Id '%s' is not taken, will use it.", id))
case err != nil:
debugLogger.Println(" Database error : " + err.Error())
os.Exit(1)
default:
loggy(fmt.Sprintf("Id '%s' is taken, generating new id.", id_taken))
generateName()
}
return id
}
// shaPaste hashes the paste data into a sha1 hash which will be used to
// determine if the pasted data already exists in the database
// Returns the hash
func shaPaste(paste string) string {
hasher := sha1.New()
hasher.Write([]byte(paste))
sha := base64.URLEncoding.EncodeToString(hasher.Sum(nil))
loggy(fmt.Sprintf("Generated sha for paste is '%s'", sha))
return sha
}
// savePaste handles the saving for each paste.
// Takes the arguments,
// title, title of the paste as string,
// paste, the actual paste data as a string,
// expiry, the epxpiry date in epoch time as an int64
// Returns the Response struct
func savePaste(title string, paste string, expiry int64) Response {
var id, hash, delkey, url string
// Escape user input,
paste = html.EscapeString(paste)
title = html.EscapeString(title)
// Hash paste data and query database to see if paste exists
sha := shaPaste(paste)
loggy("Checking if pasted data is already in the database.")
err := dbHandle.QueryRow("select id, title, hash, data, delkey from "+
configuration.DBTable+" where hash="+
configuration.DBPlaceHolder[0], sha).Scan(&id,
&title, &hash, &paste, &delkey)
switch {
case err == sql.ErrNoRows:
loggy("Pasted data is not in the database, will insert it.")
case err != nil:
debugLogger.Println(" Database error : " + err.Error())
os.Exit(1)
default:
loggy(fmt.Sprintf("Pasted data already exists at id '%s' with title '%s'.",
id, html.UnescapeString(title)))
url = configuration.Address + "/p/" + id
return Response{
Status: "Paste data already exists ...",
Id: id,
Title: title,
Sha1: hash,
Url: url,
Size: len(paste)}
}
// Generate id,
id = generateName()
url = configuration.Address + "/p/" + id
// Set expiry if it's specified,
if expiry != 0 {
expiry += time.Now().Unix()
}
// Set the generated id as title if not given,
if title == "" {
title = id
}
delKey := uniuri.NewLen(40)
// This is needed since mysql/postgres uses different placeholders,
var dbQuery string
for i := 0; i < 6; i++ {
dbQuery += configuration.DBPlaceHolder[i] + ","
}
dbQuery = dbQuery[:len(dbQuery)-1]
stmt, err := dbHandle.Prepare("INSERT INTO " + configuration.DBTable + " (id,title,hash,data,delkey,expiry)values(" + dbQuery + ")")
checkErr(err)
_, err = stmt.Exec(id, title, sha, paste, delKey, expiry)
checkErr(err)
loggy(fmt.Sprintf("Sucessfully inserted data at id '%s', title '%s', expiry '%v' and data \n \n* * * *\n\n%s\n\n* * * *\n",
id,
html.UnescapeString(title),
expiry,
html.UnescapeString(paste)))
stmt.Close()
checkErr(err)
return Response{
Status: "Successfully saved paste.",
Id: id,
Title: title,
Sha1: hash,
Url: url,
Size: len(paste),
DelKey: delKey}
}
// DelHandler handles the deletion of pastes.
// If pasteId and DelKey consist the paste will be removed.
func DelHandler(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
var inData Request
inData.Id = vars["pasteId"]
// Escape user input,
inData.DelKey = html.EscapeString(inData.DelKey)
inData.Id = html.EscapeString(inData.Id)
fmt.Printf("Trying to delete paste with id '%s' and delkey '%s'\n",
inData.Id, inData.DelKey)
stmt, err := dbHandle.Prepare("delete from pastebin where delkey=" +
configuration.DBPlaceHolder[0] + " and id=" +
configuration.DBPlaceHolder[1])
checkErr(err)
res, err := stmt.Exec(inData.DelKey, inData.Id)
checkErr(err)
_, err = res.RowsAffected()
if err != sql.ErrNoRows {
w.Header().Set("Content-Type", "application/json")
b := Response{Status: "Deleted paste " + inData.Id}
err := json.NewEncoder(w).Encode(b)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
}
}
// SaveHandler will handle the actual save of each paste.
// Returns with a Response struct.
func SaveHandler(w http.ResponseWriter, r *http.Request) {
var inData Request
loggy(fmt.Sprintf("Recieving request to save new paste, trying to parse indata."))
decoder := json.NewDecoder(r.Body)
err := decoder.Decode(&inData)
// Return error if we can't decode the json-data,
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
d, _ := json.MarshalIndent(inData, "DEBUG : ", " ")
loggy(fmt.Sprintf("Successfully parsed json indata into struct \nDEBUG : %s", d))
// Return error if we don't have any data at all
if inData.Paste == "" {
loggy("Empty paste received, returning 500.")
http.Error(w, "Empty paste.", 500)
return
}
// Return error if title is to long
// TODO add check of paste size.
if len(inData.Title) > 50 {
loggy(fmt.Sprintf("Paste title to long (%v).", len(inData.Title)))
http.Error(w, "Title to long.", 500)
return
}
p := savePaste(inData.Title, inData.Paste, inData.Expiry)
d, _ = json.MarshalIndent(p, "DEBUG : ", " ")
loggy(fmt.Sprintf("Returning json data to requester \nDEBUG : %s", d))
w.Header().Set("Content-Type", "application/json")
err = json.NewEncoder(w).Encode(p)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
}
// high calls the highlighter-wrapper and runs the paste through it.
// Takes the arguments,
// paste, the actual paste data as a string,
// lang, the pygments lexer to use as a string,
// style, the pygments style to use as a string
// Returns two strings, first is the output from the pygments html-formatter,
// the second is a custom message
func high(paste string, lang string, style string) (string, string, string, string) {
// Lets loop through the supported languages to catch if the user is doing
// something fishy. We do this to be extra safe since we are making an
// an external call with user input.
var supported_lang, supported_styles bool
supported_lang = false
supported_styles = false
for _, v1 := range listOfLangsFirst {
if lang == v1 {
supported_lang = true
}
}
for _, v2 := range listOfLangsLast {
if lang == v2 {
supported_lang = true
}
}
if lang == "" {
lang = "autodetect"
}
if !supported_lang && lang != "autodetect" {
lang = "text"
loggy(fmt.Sprintf("Given language ('%s') not supported, using 'text'", lang))
}
for _, s := range listOfStyles {
if style == strings.ToLower(s) {
supported_styles = true
}
}
// Same with the styles,
if !supported_styles {
style = "manni"
loggy(fmt.Sprintf("Given style ('%s') not supported, using ", style))
}
if _, err := os.Stat(configuration.Highlighter); os.IsNotExist(err) {
log.Fatal(err)
}
loggy(fmt.Sprintf("Executing command : %s %s %s", configuration.Highlighter,
lang, style))
cmd := exec.Command(configuration.Highlighter, lang, style)
cmd.Stdin = strings.NewReader(paste)
var stdout bytes.Buffer
cmd.Stdout = &stdout
var stderr bytes.Buffer
cmd.Stderr = &stderr
err := cmd.Run()
if err != nil {
loggy(fmt.Sprintf("The highlightning feature failed, returning text. Error : %s", stderr.String()))
return paste, "Internal Error, returning plain text.", lang, style
}
loggy(fmt.Sprintf("The wrapper returned the requested language (%s)", lang))
return stdout.String(), stderr.String(), lang, style
}
// checkPasteExpiry checks if a paste is overdue.
// It takes the pasteId as sting and the expiry date as an int64 as arguments.
// If the paste is overdue it gets deleted and false is returned.
func checkPasteExpiry(pasteId string, expiry int64) bool {
loggy("Checking if paste is overdue.")
if expiry == 0 {
loggy("Paste doesn't have a duedate.")
} else {
// Current time,
now := time.Now().Unix()
// Human friendly strings for logging,
nowStr := time.Unix(now, 0).Format("2006-01-02 15:04:05")
expiryStr := time.Unix(expiry, 0).Format("2006-01-02 15:04:05")
loggy(fmt.Sprintf("Checking if paste is overdue (is %s later than %s).",
nowStr, expiryStr))
// If expiry is greater than current time, delete paste,
if now >= expiry {
loggy("User requested a paste that is overdue, deleting it.")
delPaste(pasteId)
return false
}
}
return true
}
// delPaste deletes the actual paste.
// It takes the pasteId as sting as argument.
func delPaste(pasteId string) {
// Prepare statement,
stmt, err := dbHandle.Prepare("delete from pastebin where id=" +
configuration.DBPlaceHolder[0])
checkErr(err)
// Execute it,
_, err = stmt.Exec(pasteId)
checkErr(err)
stmt.Close()
loggy("Successfully deleted paste.")
}
// getPaste gets the paste from the database.
// Takes the pasteid as a string argument.
// Returns the Response struct.
func getPaste(pasteId string) Response {
var title, paste string
var expiry int64
err := dbHandle.QueryRow("select title, data, expiry from "+
configuration.DBTable+" where id="+configuration.DBPlaceHolder[0],
pasteId).Scan(&title, &paste, &expiry)
switch {
case err == sql.ErrNoRows:
loggy("Requested paste doesn't exist.")
return Response{Status: "Requested paste doesn't exist."}
case err != nil:
debugLogger.Println(" Database error : " + err.Error())
os.Exit(1)
}
// Check if paste is overdue,
if !checkPasteExpiry(pasteId, expiry) {
return Response{Status: "Requested paste doesn't exist."}
}
// Unescape the saved data,
paste = html.UnescapeString(paste)
title = html.UnescapeString(title)
expiryS := "Never"
if expiry != 0 {
expiryS = time.Unix(expiry, 0).Format("2006-01-02 15:04:05")
}
r := Response{
Status: "Success",
Id: pasteId,
Title: title,
Paste: paste,
Size: len(paste),
Expiry: expiryS}
d, _ := json.MarshalIndent(r, "DEBUG : ", " ")
loggy(fmt.Sprintf("Returning data from getPaste \nDEBUG : %s", d))
return r
}
// APIHandler handles all
func APIHandler(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
pasteId := vars["pasteId"]
var inData Request
decoder := json.NewDecoder(r.Body)
err := decoder.Decode(&inData)
//if err != nil {
// http.Error(w, err.Error(), http.StatusInternalServerError)
// return
//}
loggy(fmt.Sprintf("Getting paste with id '%s' and lang '%s' and style '%s'.",
pasteId, inData.Lang, inData.Style))
// Get the actual paste data,
p := getPaste(pasteId)
if inData.WebReq {
// If no style is given, use default style,
if inData.Style == "" {
inData.Style = "manni"
p.Url += "/" + inData.Style
}
// If no lang is given, use autodetect
if inData.Lang == "" {
inData.Lang = "autodetect"
p.Url += "/" + inData.Lang
}
// Run it through the highgligther.,
p.Paste, p.Extra, p.Lang, p.Style = high(p.Paste, inData.Lang, inData.Style)
}
d, _ := json.MarshalIndent(p, "DEBUG : ", " ")
loggy(fmt.Sprintf("Returning json data to requester \nDEBUG : %s", d))
w.Header().Set("Content-Type", "application/json")
err = json.NewEncoder(w).Encode(p)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
}
// pasteHandler generates the html paste pages
func pasteHandler(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
pasteId := vars["pasteId"]
lang := vars["lang"]
style := vars["style"]
loggy(fmt.Sprintf("Getting paste with id '%s' and lang '%s' and style '%s'.", pasteId, lang, style))
// Get the actual paste data,
p := getPaste(pasteId)
// Run it through the highgligther.,
p.Paste, p.Extra, p.Lang, p.Style = high(p.Paste, lang, style)
// Construct page struct
page := &Page{
Body: template.HTML(p.Paste),
Expiry: p.Expiry,
Lang: p.Lang,
LangsFirst: listOfLangsFirst,
LangsLast: listOfLangsLast,
Style: p.Style,
SupportedStyles: listOfStyles,
Title: p.Title,
GoogleAPIKey: configuration.GoogleAPIKey,
UrlClone: configuration.Address + "/clone/" + pasteId,
UrlDownload: configuration.Address + "/download/" + pasteId,
UrlHome: configuration.Address,
UrlRaw: configuration.Address + "/raw/" + pasteId,
WrapperErr: p.Extra,
}
err := templates.ExecuteTemplate(w, "syntax.html", page)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
}
}
// CloneHandler handles generating the clone pages
func CloneHandler(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
paste := vars["pasteId"]
p := getPaste(paste)
loggy(p.Paste)
// Clone page struct
page := &Page{
Body: template.HTML(p.Paste),
PasteTitle: "Copy of " + p.Title,
Title: "Copy of " + p.Title,
}
err := templates.ExecuteTemplate(w, "index.html", page)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
}
}
// DownloadHandler forces downloads of selected pastes
func DownloadHandler(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
pasteId := vars["pasteId"]
p := getPaste(pasteId)
// Set header to an attachment so browser will automatically download it
w.Header().Set("Content-Disposition", "attachment; filename="+p.Paste)
w.Header().Set("Content-Type", r.Header.Get("Content-Type"))
io.WriteString(w, p.Paste)
}
// RawHandler displays the pastes in text/plain format
func RawHandler(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
pasteId := vars["pasteId"]
p := getPaste(pasteId)
w.Header().Set("Content-Type", "text/plain; charset=UTF-8; imeanit=yes")
// Simply write string to browser
io.WriteString(w, p.Paste)
}
// RootHandler handles generating the root page
func RootHandler(w http.ResponseWriter, r *http.Request) {
p := &Page{
LangsFirst: listOfLangsFirst,
LangsLast: listOfLangsLast,
Title: configuration.DisplayName,
UrlAddress: configuration.Address,
}
err := templates.ExecuteTemplate(w, "index.html", p)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
}
}
func serveCss(w http.ResponseWriter, r *http.Request) {
http.ServeFile(w, r, "assets/pastebin.css")
}
func main() {
// Set up new logger,
debugLogger = log.New(os.Stderr, "DEBUG : ", log.Ldate|log.Ltime)
// Check args,
checkArgs()
// Load config,
file, err := os.Open("config.json")
if err != nil {
loggy(fmt.Sprintf("Error opening config.json (%s)", err))
os.Exit(1)
}
loggy(fmt.Sprintf("Successfully opened %s", "config.json"))
// Try to parse json,
decoder := json.NewDecoder(file)
err = decoder.Decode(&configuration)
if err != nil {
loggy(fmt.Sprintf("Error parsing json data from %s : %s", "config.json", err))
os.Exit(1)
}
d, _ := json.MarshalIndent(configuration, "DEBUG : ", " ")
loggy(fmt.Sprintf("Successfully parsed json data into struct \nDEBUG : %s", d))
// Get languages and styles,
getSupportedLangs()
getSupportedStyles()
// Get the database handle
dbHandle = getDBHandle()
// Router object,
router := mux.NewRouter()
// Routes,
router.HandleFunc("/", RootHandler)
router.HandleFunc("/p/{pasteId}", pasteHandler).Methods("GET")
router.HandleFunc("/p/{pasteId}/{lang}", pasteHandler).Methods("GET")
router.HandleFunc("/p/{pasteId}/{lang}/{style}", pasteHandler).Methods("GET")
// Api
router.HandleFunc("/api", SaveHandler).Methods("POST")
router.HandleFunc("/api/{pasteId}", APIHandler).Methods("POST")
router.HandleFunc("/api/{pasteId}", APIHandler).Methods("GET")
router.HandleFunc("/api/{pasteId}", DelHandler).Methods("DELETE")
router.HandleFunc("/raw/{pasteId}", RawHandler).Methods("GET")
router.HandleFunc("/clone/{pasteId}", CloneHandler).Methods("GET")
router.HandleFunc("/download/{pasteId}", DownloadHandler).Methods("GET")
router.HandleFunc("/assets/pastebin.css", serveCss).Methods("GET")
// Set up server,
srv := &http.Server{
Handler: router,
Addr: configuration.ListenAddress + ":" + configuration.ListenPort,
WriteTimeout: 15 * time.Second,
ReadTimeout: 15 * time.Second,
}
err = srv.ListenAndServe()
checkErr(err)
}