Skip to content

Commit a9990aa

Browse files
committed
First commit
0 parents  commit a9990aa

10 files changed

+390
-0
lines changed

Dockerfile

+10
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
FROM golang AS build
2+
WORKDIR /src
3+
COPY . /src
4+
RUN CGO_ENABLED=0 go build -o curl-paste curl-paste.go config.go
5+
6+
FROM gcr.io/distroless/static-debian11
7+
WORKDIR /
8+
COPY --from=build /src/curl-paste /src/curl-paste.conf /
9+
COPY --from=build /src/index.html /
10+
ENTRYPOINT ["/curl-paste"]

Makefile

+23
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
SHELL=/bin/bash
2+
IMAGE=localhost/kevydotvinu/curl-paste
3+
ENGINE?=podman
4+
NAME=curl-paste
5+
6+
.PHONY: all
7+
all: build kill run logs
8+
9+
.PHONY: build
10+
build: curl-paste.go
11+
${ENGINE} build . --tag ${IMAGE}
12+
13+
.PHONY: run
14+
run: curl-paste.go
15+
${ENGINE} run --detach --name ${NAME} --rm --net host ${IMAGE}
16+
17+
.PHONY: logs
18+
logs:
19+
${ENGINE} logs --follow ${NAME}
20+
21+
.PHONY: kill
22+
kill:
23+
-${ENGINE} kill ${NAME}

README.md

+22
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
# Golang powered cURL paste
2+
3+
The `HTTP` `POST` request will send the form data along and respond with a link to the paste. The `HTTP` `GET` will retrieve the paste with the given ID as plain-text.
4+
5+
### Usage
6+
Paste a file named `file.txt` using cURL.
7+
```bash
8+
$ curl -F 'title=Request from cURL' -F 'paste=<file.txt' http://curl-paste.local/
9+
```
10+
Paste from stdin using cURL.
11+
```bash
12+
echo "Hello, world." | curl -F 'title=Request from cURL' -F 'paste=<-' http://curl-paste.local/
13+
```
14+
A shell function that can be added to `.bashrc` or `.bash_profle` for quick pasting from the CLI. The command reads from stdin and outputs the URL of the paste to stdout.
15+
```bash
16+
function curl-paste() {
17+
curl -F 'title=Request from cURL' -F 'paste=<-' https://curl-paste.local
18+
}
19+
```
20+
```bash
21+
echo "hi" | curl-paste
22+
```

config.go

+78
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
package main
2+
3+
import (
4+
"bufio"
5+
"fmt"
6+
"os"
7+
"regexp"
8+
"strings"
9+
)
10+
11+
type Options struct {
12+
confFile string
13+
}
14+
15+
type Config struct {
16+
server string
17+
address string
18+
port string
19+
directory string
20+
size uint32
21+
logfile string
22+
}
23+
24+
func (c Config) String() string {
25+
26+
var s string
27+
28+
s += "Server name: " + c.server + "\n"
29+
s += "Listening on: " + c.address + ":" + c.port + "\n"
30+
s += "Paste directory: " + c.directory + "\n"
31+
s += "Max size: " + string(c.size) + "\n"
32+
s += "Log file: " + c.logfile + "\n"
33+
34+
return s
35+
36+
}
37+
38+
func parseConfig(fName string, c *Config) error {
39+
40+
f, err := os.Open(fName)
41+
if err != nil {
42+
return err
43+
}
44+
45+
r := bufio.NewScanner(f)
46+
47+
line := 0
48+
for r.Scan() {
49+
s := r.Text()
50+
line += 1
51+
if matched, _ := regexp.MatchString("^([ \t]*)$", s); matched != true {
52+
if matched, _ := regexp.MatchString("^#", s); matched != true {
53+
if matched, _ := regexp.MatchString("^([a-zA-Z]+)=.*", s); matched == true {
54+
fields := strings.Split(s, "=")
55+
switch strings.Trim(fields[0], " \t\"") {
56+
case "server":
57+
c.server = strings.Trim(fields[1], " \t\"")
58+
case "address":
59+
c.address = strings.Trim(fields[1], " \t\"")
60+
case "port":
61+
c.port = strings.Trim(fields[1], " \t\"")
62+
case "directory":
63+
c.directory = strings.Trim(fields[1], " \t\"")
64+
case "logfile":
65+
c.logfile = strings.Trim(fields[1], " \t\"")
66+
default:
67+
fmt.Fprintf(os.Stderr, "Error reading config file %s at line %d: unknown variable '%s'\n",
68+
fName, line, fields[0])
69+
}
70+
} else {
71+
fmt.Fprintf(os.Stderr, "Error reading config file %s at line %d: unknown statement '%s'\n",
72+
fName, line, s)
73+
}
74+
}
75+
}
76+
}
77+
return nil
78+
}

curl-paste.conf

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
server=localhost
2+
address=0.0.0.0
3+
port=9990

curl-paste.func

+4
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
URL="http://localhost:9990"
2+
function curl-paste() {
3+
curl -F 'title=Post request from a Curl command' -F 'paste=<-' ${URL}
4+
}

curl-paste.go

+139
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,139 @@
1+
package main
2+
3+
import (
4+
"flag"
5+
"fmt"
6+
"io"
7+
"log"
8+
"net/http"
9+
"os"
10+
"path/filepath"
11+
"time"
12+
13+
"github.com/kevydotvinu/curl-paste/paste"
14+
)
15+
16+
var configFile = flag.String("c", "./curl-paste.conf", "Configuration file for curl-paste")
17+
18+
var pConf = Config{
19+
server: "localhost",
20+
address: "0.0.0.0",
21+
port: "9990",
22+
directory: "/",
23+
size: 4294967295,
24+
logfile: "/curl-paste.log",
25+
}
26+
27+
func min(a, b int) int {
28+
29+
if a > b {
30+
return b
31+
} else {
32+
return a
33+
}
34+
35+
}
36+
37+
func handleGetPaste(w http.ResponseWriter, r *http.Request) {
38+
39+
var pasteName, origName string
40+
41+
origName = filepath.Clean(r.URL.Path)
42+
pasteName = pConf.directory + "/" + origName
43+
44+
origIP := r.RemoteAddr
45+
46+
log.Printf("Received GET from %s for '%s'\n", origIP, origName)
47+
48+
if (origName == "/") || (origName == "/index.html") {
49+
http.ServeFile(w, r, pConf.directory+"/index.html")
50+
} else {
51+
52+
_, _, content, err := paste.Retrieve(pasteName)
53+
54+
if err == nil {
55+
fmt.Fprintf(w, "%s", content)
56+
return
57+
} else {
58+
fmt.Fprintf(w, "%s\n", err)
59+
return
60+
}
61+
}
62+
}
63+
64+
func handlePutPaste(w http.ResponseWriter, r *http.Request) {
65+
66+
err1 := r.ParseForm()
67+
err2 := r.ParseMultipartForm(int64(2 * pConf.size))
68+
69+
if err1 != nil && err2 != nil {
70+
http.ServeFile(w, r, pConf.directory+"/index.html")
71+
} else {
72+
reqBody := r.PostForm
73+
74+
origIP := r.RemoteAddr
75+
76+
log.Printf("Received new POST from %s\n", origIP)
77+
78+
title := reqBody.Get("title")
79+
date := time.Now().String()
80+
content := reqBody.Get("paste")
81+
82+
content = content[0:min(len(content), int(pConf.size))]
83+
84+
ID, err := paste.Store(title, date, content, pConf.directory)
85+
86+
log.Printf(" Title: %s\n", title)
87+
log.Printf(" ID: %s\n", ID)
88+
89+
if err == nil {
90+
hostname := pConf.server
91+
port := pConf.port
92+
fmt.Fprintf(w, "http://%s:%s/%s\n", hostname, port, ID)
93+
return
94+
} else {
95+
fmt.Fprintf(w, "%s\n", err)
96+
}
97+
}
98+
}
99+
100+
func reqHandler(w http.ResponseWriter, r *http.Request) {
101+
102+
switch r.Method {
103+
case "GET":
104+
handleGetPaste(w, r)
105+
case "POST":
106+
handlePutPaste(w, r)
107+
default:
108+
http.NotFound(w, r)
109+
}
110+
}
111+
112+
func main() {
113+
114+
flag.Parse()
115+
116+
parseConfig(*configFile, &pConf)
117+
118+
f, err := os.OpenFile(pConf.logfile, os.O_APPEND|os.O_CREATE|os.O_RDWR, 0600)
119+
if err != nil {
120+
fmt.Fprintf(os.Stderr, "Error opening log file: %s. Exiting\n", pConf.logfile)
121+
os.Exit(1)
122+
}
123+
defer f.Close()
124+
125+
mw := io.MultiWriter(os.Stdout, f)
126+
log.SetOutput(mw)
127+
log.SetPrefix("[curl-paste]: ")
128+
log.SetFlags(log.Ldate | log.Ltime | log.Lmicroseconds)
129+
130+
log.Println("curl-paste version 0.0.1 -- Starting ")
131+
log.Printf(" + Config file: %s\n", *configFile)
132+
log.Printf(" + Serving pastes on: %s:%s\n", pConf.server, pConf.port)
133+
log.Printf(" + Listening on: %s:%s\n", pConf.address, pConf.port)
134+
log.Printf(" + Paste directory: %s\n", pConf.directory)
135+
log.Printf(" + Max size: %d\n", pConf.size)
136+
137+
http.HandleFunc("/", reqHandler)
138+
log.Fatal(http.ListenAndServe(pConf.address+":"+pConf.port, nil))
139+
}

go.mod

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
module github.com/kevydotvinu/curl-paste
2+
3+
go 1.18

index.html

+46
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
<!DOCTYPE html>
2+
<html lang="en"><head>
3+
<meta http-equiv="content-type" content="text/html; charset=UTF-8">
4+
<meta charset="utf-8">
5+
<title>curl-paste.local - Golang Powered Curl Pastebin</title>
6+
<meta name="author" content="Vinu K">
7+
<meta name="description" content="curl-paste.local is a simple
8+
CLI curl pastebin service powered by Golang">
9+
<link href="http://curl-paste.local/" rel="canonical">
10+
</head>
11+
<body data-new-gr-c-s-check-loaded="8.904.0" data-gr-ext-installed=""><pre> BROWSER USAGE
12+
13+
<a href="http://curl-paste.local" alt="curl-paste.local web page">http://curl-paste.local/</a>
14+
15+
API USAGE
16+
17+
POST http://curl-paste.local/
18+
19+
Send the form data along. Will respond with a link to the paste.
20+
21+
GET https://curl-paste.local/&lt;id&gt;
22+
23+
Retrieve the paste with the given id as plain-text.
24+
25+
EXAMPLES
26+
27+
Paste a file named 'file.txt' using cURL:
28+
29+
curl -F 'title=Request from cURL' -F 'paste=&lt;file.txt' http://curl-paste.local/
30+
31+
Paste from stdin using cURL:
32+
33+
echo "Hello, world." | curl -F 'title=Request from cURL' -F 'paste=&lt;-' http://curl-paste.local/
34+
35+
A shell function that can be added to `.bashrc` or `.bash_profle` for
36+
quick pasting from the command line. The command takes reads from stdin
37+
if none was supplied and outputs the URL of the paste to stdout:
38+
`echo "hi" | curl-paste`.
39+
40+
function curl-paste() {
41+
curl -F 'title=Request from cURL' -F 'paste=<-' https://curl-paste.local
42+
}
43+
</pre>
44+
45+
</body>
46+
</html>

paste/paste.go

+62
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
package paste
2+
3+
import (
4+
"bufio"
5+
"crypto/sha256"
6+
"errors"
7+
"fmt"
8+
"io/ioutil"
9+
"log"
10+
"os"
11+
"strings"
12+
)
13+
14+
func Store(title, date, content, destDir string) (string, error) {
15+
16+
h := sha256.New()
17+
18+
h.Write([]byte(title))
19+
h.Write([]byte(date))
20+
h.Write([]byte(content))
21+
22+
paste := fmt.Sprintf("# Title: %s\n# Date: %s\n%s", title, date, content)
23+
24+
pasteHash := fmt.Sprintf("%x", h.Sum(nil))
25+
log.Printf(" -- hash: %s\n", pasteHash)
26+
directory := destDir
27+
28+
for i := 0; i < len(pasteHash)-16; i++ {
29+
pasteName := pasteHash[i : i+16]
30+
if _, err := os.Stat(directory + pasteName); os.IsNotExist(err) {
31+
if err := ioutil.WriteFile(directory+pasteName, []byte(paste), 0644); err == nil {
32+
log.Printf(" -- saving new paste to : %s", directory+pasteName)
33+
return pasteName, nil
34+
} else {
35+
log.Printf("Cannot create the paste: %s!\n", directory+pasteName)
36+
}
37+
}
38+
}
39+
return "", errors.New("Could not store the paste!")
40+
}
41+
42+
func Retrieve(URI string) (title, date, content string, err error) {
43+
44+
fCont, err := os.Open(URI)
45+
defer fCont.Close()
46+
47+
if err == nil {
48+
stuff := bufio.NewScanner(fCont)
49+
stuff.Scan()
50+
title = strings.Trim(strings.Split(stuff.Text(), ":")[1], " ")
51+
stuff.Scan()
52+
date = strings.Trim(strings.Join(strings.Split(stuff.Text(), ":")[1:], ":"), " ")
53+
for stuff.Scan() {
54+
content += stuff.Text() + "\n"
55+
}
56+
} else {
57+
58+
return "", "", "", errors.New("No data to retrieve with that ID!")
59+
}
60+
61+
return title, date, content, nil
62+
}

0 commit comments

Comments
 (0)