go-shrt

Shortlinks and go-get redirects
git clone git://git.danielmoch.com/go-shrt.git
Log | Files | Refs | README | LICENSE

commit 415f91390f5fba22aa9b2d9d43bf35bfc50ae81c
parent b350c5923b76b1f73ac5d5d4876e8cb7f0b86d8f
Author: Daniel Moch <daniel@danielmoch.com>
Date:   Sat,  5 Dec 2020 19:32:40 -0500

Add init argument; move main into cmd subdir

Diffstat:
M.gitignore | 2+-
AMakefile | 16++++++++++++++++
Acmd/shrt/main.go | 229+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Dmain.go | 143-------------------------------------------------------------------------------
Mshrtfile.go | 65++++++++++++++++++++++++++++++++++++++++++++++++++++-------------
5 files changed, 298 insertions(+), 157 deletions(-)

diff --git a/.gitignore b/.gitignore @@ -1,3 +1,3 @@ -go-shrt +shrt shrt.conf shrt.db diff --git a/Makefile b/Makefile @@ -0,0 +1,15 @@ +VERSION := 0.1.0-dev0 + +all: shrt + +go.mod: cmd/shrt/main.go *.go + go mod tidy + touch go.mod + +shrt: go.mod + go build -ldflags "-X main.version=${VERSION}" ./cmd/shrt + +clean: + rm -f shrt + +.PHONY: all clean+ \ No newline at end of file diff --git a/cmd/shrt/main.go b/cmd/shrt/main.go @@ -0,0 +1,229 @@ +package main + +import ( + "bufio" + "fmt" + "log" + "net/http" + "os" + "path" + "strings" + + goshrt "djmo.ch/go-shrt" +) + +const ( + errNone = iota + errArgNum + errDatabase + errShrtFile + errRepeatToken + errInit +) + +var ( + arg0 = path.Base(os.Args[0]) + + shrt, cfg *goshrt.ShrtFile + version string +) + +func usage(r int) { + fmt.Printf("usage: %s [-hv] [-d dbpath] [-c cfgpath] [-l listenaddr] [init]\n", arg0) + os.Exit(r) +} + +func print_version() { + fmt.Printf("%s version %s\n", arg0, version) + os.Exit(errNone) +} + +func handl(w http.ResponseWriter, req *http.Request) { + if req.Method != http.MethodGet { + w.WriteHeader(http.StatusMethodNotAllowed) + w.Write([]byte("Method not allowed\n")) + return + } + + key := req.URL.Path + if strings.HasPrefix(key, "/") { + key = key[1:] + } + + if strings.Contains(key, "/") { + log.Println("bad request: " + key) + w.WriteHeader(http.StatusForbidden) + w.Write([]byte("Request path not allowed\n")) + return + } + if strings.Contains(key, "/") { + log.Println("bad request: " + key) + w.WriteHeader(http.StatusForbidden) + w.Write([]byte("Request path not allowed\n")) + return + } + + if key == "robots.txt" { + log.Println("incoming robot") + resp := "# Welcome to Shrt\n" + resp += "User-Agent: *\n" + resp += "Disallow:\n" + w.Write([]byte(resp)) + return + } + if key == "robots.txt" { + log.Println("incoming robot") + resp := "# Welcome to Shrt\n" + resp += "User-Agent: *\n" + resp += "Disallow:\n" + w.Write([]byte(resp)) + return + } + + if key == "" && cfg.Get("barerdr") != "" { + log.Println("shortlink request for /") + w.Header().Add("Location", cfg.Get("barerdr")) + w.WriteHeader(http.StatusFound) + w.Write([]byte("Redirecting\n")) + return + } + + if val := shrt.Get(key); val != "" { + log.Println("shortlink request for", key) + w.Header().Add("Location", val) + w.WriteHeader(http.StatusMovedPermanently) + w.Write([]byte("Redirecting\n")) + return + } + + repo := key + log.Println("go-get request for", repo) + resp := "<!DOCTYPE html>\n" + resp += "<html>\n" + resp += "<head>\n" + resp += `<meta http-equiv="Content-Type" ` + resp += "content=\"text/html; charset=utf-8\"/>\n" + resp += "<meta name=\"go-import\" " + resp += fmt.Sprintf("content=\"%s/%s %s %s/%s%s\">\n", + cfg.Get("srvname"), repo, cfg.Get("scmtype"), + cfg.Get("rdrname"), repo, cfg.Get("suffix")) + resp += `<meta http-equiv="refresh" content="0; ` + resp += fmt.Sprintf("url=https://godoc.org/%s/%s\">\n", + cfg.Get("srvname"), repo) + resp += "</head>\n" + resp += "<body>\n" + resp += `Redirecting to docs at <a href="https://godoc.org/` + resp += fmt.Sprintf("%s/%s\">godoc.org/%s/%s</a>...\n", + cfg.Get("srvname"), repo, cfg.Get("srvname"), repo) + resp += "</body>\n" + resp += "</html>\n" + w.Write([]byte(resp)) + return +} + +func doInit(path string) { + r := bufio.NewReader(os.Stdin) + m, err := goshrt.NewShrtFile(path) + if err != nil { + fmt.Fprintf(os.Stderr, "%s: error creating config -- %s\n", arg0, err.Error()) + os.Exit(errInit) + } + + fmt.Printf("server name: ") + srvname, err := r.ReadString('\n') + if err != nil { + fmt.Fprintf(os.Stderr, "%s: %s\n", arg0, err.Error()) + os.Exit(errInit) + } + m.Put("srvname", srvname) + + fmt.Printf("SCM type: ") + scmtype, err := r.ReadString('\n') + if err != nil { + fmt.Fprintf(os.Stderr, "%s: %s\n", arg0, err.Error()) + os.Exit(errInit) + } + m.Put("scmtype", scmtype) + + fmt.Printf("repo suffix (blank for none): ") + suffix, err := r.ReadString('\n') + if err != nil { + fmt.Fprintf(os.Stderr, "%s: %s\n", arg0, err.Error()) + os.Exit(errInit) + } + m.Put("suffix", suffix) + + fmt.Printf("redirect base url: ") + rdrname, err := r.ReadString('\n') + if err != nil { + fmt.Fprintf(os.Stderr, "%s: %s\n", arg0, err.Error()) + os.Exit(errInit) + } + m.Put("rdrname", rdrname) + + fmt.Printf("bare redirect url: ") + barerdr, err := r.ReadString('\n') + if err != nil { + fmt.Fprintf(os.Stderr, "%s: %s\n", arg0, err.Error()) + os.Exit(errInit) + } + m.Put("barerdr", barerdr) + m.Write() +} + +func main() { + var err error + if len(os.Args) > 4 { + usage(errArgNum) + } + + dbpath := "shrt.db" + cfgpath := "shrt.conf" + listenaddr := ":8080" + doinit := false + for i := 1; i < len(os.Args); i++ { + switch os.Args[i] { + case "-h": + usage(errNone) + case "-v": + print_version() + case "-d": + i += 1 + dbpath = os.Args[i] + case "-c": + i += 1 + cfgpath = os.Args[i] + case "-l": + i += 1 + listenaddr = os.Args[i] + case "init": + doinit = true + break + default: + fmt.Fprintf(os.Stderr, "%s: unknown option -- %s\n", + arg0, os.Args[i]) + + } + } + + if doinit { + doInit(cfgpath) + os.Exit(errNone) + } + + cfg, err = goshrt.ReadShrtFile(cfgpath) + if err != nil { + fmt.Fprintf(os.Stderr, "%s: config error -- %s\n", arg0, err.Error()) + os.Exit(errShrtFile) + } + + shrt, err = goshrt.ReadShrtFile(dbpath) + if err != nil { + fmt.Fprintf(os.Stderr, "%s: db error -- %s\n", arg0, err.Error()) + os.Exit(errShrtFile) + } + + http.Handle("/", http.HandlerFunc(handl)) + log.Println("listening on", listenaddr) + log.Fatal(http.ListenAndServe(listenaddr, nil)) +} diff --git a/main.go b/main.go @@ -1,143 +0,0 @@ -package main - -import ( - "fmt" - "log" - "net/http" - "os" - "path" - "strings" -) - -const ( - errNone = iota - errArgNum - errDatabase - errNoFile - errRepeatToken -) - -var ( - arg0 = path.Base(os.Args[0]) - - shrt, cfg *shrtFile -) - -func usage(r int) { - fmt.Printf("usage: %s [-d dbpath] [-c cfgpath] [-l listenaddr] [init]\n", arg0) - os.Exit(r) -} - -func handl(w http.ResponseWriter, req *http.Request) { - if req.Method != http.MethodGet { - w.WriteHeader(http.StatusMethodNotAllowed) - w.Write([]byte("Method not allowed\n")) - return - } - - key := req.URL.Path - if strings.HasPrefix(key, "/") { - key = key[1:] - } - - if strings.Contains(key, "/") { - log.Println("bad request: " + key) - w.WriteHeader(http.StatusForbidden) - w.Write([]byte("Request path not allowed\n")) - return - } - - if key == "" && (*cfg)["barerdr"] != "" { - log.Println("shortlink request for /") - w.Header().Add("Location", (*cfg)["barerdr"]) - w.WriteHeader(http.StatusFound) - w.Write([]byte("Redirecting\n")) - return - } - - if key == "robots.txt" { - log.Println("incoming robot") - resp := "# Welcome to Shrt\n" - resp += "User-Agent: *\n" - resp += "Disallow:\n" - w.Write([]byte(resp)) - return - } - - if val, ok := (*shrt)[key]; ok { - log.Println("shortlink request for", key) - w.Header().Add("Location", val) - w.WriteHeader(http.StatusMovedPermanently) - w.Write([]byte("Redirecting\n")) - return - } - - repo := key - log.Println("go-get request for", repo) - resp := "<!DOCTYPE html>\n" - resp += "<html>\n" - resp += "<head>\n" - resp += `<meta http-equiv="Content-Type" ` - resp += "content=\"text/html; charset=utf-8\"/>\n" - resp += "<meta name=\"go-import\" " - resp += fmt.Sprintf("content=\"%s/%s %s %s/%s%s\">\n", - (*cfg)["srvname"], repo, (*cfg)["scmtype"], - (*cfg)["rdrname"], repo, (*cfg)["suffix"]) - resp += `<meta http-equiv="refresh" content="0; ` - resp += fmt.Sprintf("url=https://godoc.org/%s/%s\">\n", - (*cfg)["srvname"], repo) - resp += "</head>\n" - resp += "<body>\n" - resp += `Redirecting to docs at <a href="https://godoc.org/` - resp += fmt.Sprintf("%s/%s\">godoc.org/%s/%s</a>...\n", - (*cfg)["srvname"], repo, (*cfg)["srvname"], repo) - resp += "</body>\n" - resp += "</html>\n" - w.Write([]byte(resp)) - return -} - -func main() { - if len(os.Args) > 4 { - usage(errArgNum) - } - - dbpath := "shrt.db" - cfgpath := "shrt.conf" - listenaddr := ":8080" - //doinit := false - for i := 1; i < len(os.Args); i++ { - switch os.Args[i] { - case "-h": - usage(errNone) - case "-d": - i += 1 - dbpath = os.Args[i] - case "-c": - i += 1 - cfgpath = os.Args[i] - case "-l": - i += 1 - listenaddr = os.Args[i] - case "init": - //doinit = true - break - default: - fmt.Fprintf(os.Stderr, "%s: unknown option -- %s\n", - arg0, os.Args[i]) - - } - } - - // if doinit { - // doInit(dbpath) - // os.Exit(errNone) - // } - - cfg = readShrtFile(cfgpath) - shrt = readShrtFile(dbpath) - - http.Handle("/", http.HandlerFunc(handl)) - log.Println("listening on", listenaddr) - log.Fatal(http.ListenAndServe(listenaddr, nil)) -} diff --git a/shrtfile.go b/shrtfile.go @@ -1,4 +1,4 @@ -package main +package shrt import ( "bufio" @@ -7,16 +7,30 @@ import ( "strings" ) -type shrtFile map[string]string +type ShrtFile struct { + m map[string]string + path string +} + +func NewShrtFile(path string) (*ShrtFile, error) { + // we fail if the file already exists, so the logic is reversed + // from the usual here + f, err := os.Open(path) + if err == nil { + f.Close() + return nil, + fmt.Errorf("File already exists. Please delete and try again.") + } + + return &ShrtFile{m: make(map[string]string), path: path}, nil +} -func readShrtFile(db string) *shrtFile { - var newFile shrtFile - newFile = make(map[string]string) +func ReadShrtFile(db string) (*ShrtFile, error) { + var newFile ShrtFile + newFile.m = make(map[string]string) f, err := os.Open(db) if err != nil { - fmt.Fprintf(os.Stderr, "%s: error opening shrtfile -- %s\n", - arg0, err.Error()) - os.Exit(errNoFile) + return nil, fmt.Errorf("error opening shrtfile -- %s", err.Error()) } defer f.Close() @@ -25,11 +39,36 @@ func readShrtFile(db string) *shrtFile { for scnr.Scan() { tok := strings.SplitN(scnr.Text(), "=", 2) - if _, ok := newFile[strings.Trim(tok[0], " ")]; ok { - fmt.Fprintf(os.Stderr, "%s: repeat token -- %s\n", arg0, tok[0]) - os.Exit(errRepeatToken) + if _, ok := newFile.m[strings.Trim(tok[0], " ")]; ok { + return nil, fmt.Errorf("repeat token -- %s", tok[0]) + } + newFile.m[strings.Trim(tok[0], " ")] = strings.Trim(tok[1], " ") + } + return &newFile, nil +} + +func (s *ShrtFile) Get(key string) string { + return s.m[key] +} + +func (s *ShrtFile) Put(key, value string) { + s.m[key] = value +} + +func (s *ShrtFile) Write() error { + f, err := os.Create(s.path) + if err != nil { + return fmt.Errorf("error opening ShrtFile: %s", err.Error()) + } + defer f.Close() + + w := bufio.NewWriter(f) + for k, v := range s.m { + _, err = w.WriteString(fmt.Sprintf("%s = %s", k, v)) + if err != nil { + return fmt.Errorf("error writing ShrtFile: %s", err.Error()) } - newFile[strings.Trim(tok[0], " ")] = strings.Trim(tok[1], " ") } - return &newFile + w.Flush() + return nil }