-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathshrt.go
157 lines (144 loc) · 4.62 KB
/
shrt.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
// See LICENSE file for copyright and license details
// Package shrt implements a simple (perhaps simplistic) URL
// shortener. It also handles go-get requests.
//
// Shortlinks are recorded in the database, and any request path not
// matching a shortlink is assumed to be a go-get request. This is by
// design, but can result in specious redirects. Additionally,
// subdirectory paths are not allowed.
//
// Shortlinks generate an HTTP 301 response. Go-get requests generate
// an HTTP 200 response. If configured, requests to the base path
// (i.e., "/") generate an HTTP 302 response.
//
// The database file is human-readable. See [Shrtfile] for the full
// specification.
package shrt
import (
"fmt"
"html/template"
"io/fs"
"log"
"net/http"
"strings"
)
var robotstxt = `# Welcome to Shrt
User-Agent: *
Disallow:
`
var shrtrsp = `<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>"
<meta name="go-import" content="{{ .SrvName }}/{{ .Repo }} {{ .ScmType }} {{ .URL }}">{{ if ne .GoSourceDir "" }}
<meta name="go-source" content="{{ .SrvName }}/{{ .Repo }} {{ .URL }} {{ .URL }}/{{ .GoSourceDir }} {{ .URL }}/{{ .GoSourceFile }}">
<meta content="{{ .ScmType }}" name="vcs">
<meta content="{{ .URL }}" name="vcs:clone">
<meta content="{{ .URL }}" name="forge:summary">
<meta content="{{ .URL }}/-/tree/{ref}/{path}" name="forge:dir">
<meta content="{{ .URL }}/-/blob/{ref}/{path}" name="forge:file">
<meta content="{{ .URL }}/-/raw/{ref}/{path}" name="forge:rawfile">
<meta content="{{ .URL }}/-/blob/{ref}/{path}#L{line}" name="forge:line">{{ end }}
<meta http-equiv="refresh" content="0; url=https://pkg.go.dev/{{ .SrvName }}/{{ .DocPath }}">
</head>
<body>
Redirecting to docs at <a href="https://pkg.go.dev/{{ .SrvName }}/{{ .DocPath }}">pkg.go.dev/{{ .SrvName }}/{{ .DocPath }}</a>...
</body>
</html>
`
type shrtRequest struct {
SrvName string
Repo string
ScmType string
URL string
DocPath string
GoSourceDir string
GoSourceFile string
}
// Config contains all of the global configuration for Shrt. All
// values except BareRdr and DbPath are used in the go-import meta tag
// values for go-get requests.
type Config struct {
// Server name of the Shrt host
SrvName string
// SCM (or VCS) type
ScmType string
// SCM repository suffix, if required by repository host
Suffix string
// The server name of the repository host
RdrName string
// Where requests with an empty path should redirect
BareRdr string
// The path to the [ShrtFile]-formatted database file.
DbPath string
// The string to append to the URL for go-get redirects to
// form the directory entry in the go-source meta tag. This
// key is experimental and may be removed in a future release.
GoSourceDir string
// The string to append to the URL for go-get redirects to
// form the file entry in the go-source meta tag. This
// key is experimental and may be removed in a future release.
GoSourceFile string
}
// ShrtHandler is the core [http.Handler] for go-shrt.
type ShrtHandler struct {
ShrtFile *ShrtFile
Config Config
FS fs.FS
}
// Handle implements the http.Handler interface.
func (s *ShrtHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
if req.Method != http.MethodGet {
w.WriteHeader(http.StatusMethodNotAllowed)
fmt.Fprintf(w, "Method not allowed")
return
}
p := req.URL.Path
p = strings.TrimPrefix(p, "/")
if p == "robots.txt" {
log.Println("incoming robot")
fmt.Fprint(w, robotstxt)
return
}
if p == "" && s.Config.BareRdr != "" {
log.Println("shortlink request for /")
w.Header().Add("Location", s.Config.BareRdr)
w.WriteHeader(http.StatusFound)
fmt.Fprintln(w, "Redirecting")
return
}
key := strings.SplitN(p, "/", 2)[0]
val, err := s.ShrtFile.Get(key)
if err != nil {
log.Println("not found:", key)
http.Error(w, "Not found", http.StatusNotFound)
return
}
switch val.Type {
case ShortLink:
if key != p {
log.Println("path elements following shortlink:", p)
http.Error(w, "Not found", http.StatusNotFound)
return
}
log.Println("shortlink request for", key)
w.Header().Add("Location", val.URL)
w.WriteHeader(http.StatusMovedPermanently)
fmt.Fprintln(w, "Redirecting")
case GoGet:
log.Println("go-get request for", key)
t := template.Must(template.New("shrt").Parse(shrtrsp))
sReq := shrtRequest{
SrvName: s.Config.SrvName,
Repo: key,
ScmType: s.Config.ScmType,
URL: val.URL,
DocPath: p,
GoSourceDir: s.Config.GoSourceDir,
GoSourceFile: s.Config.GoSourceFile,
}
if err := t.Execute(w, sReq); err != nil {
log.Println("error executing template:", err)
}
}
}