forked from hswaw/hscloud
179 lines
4.5 KiB
Go
179 lines
4.5 KiB
Go
package main
|
|
|
|
import (
|
|
"flag"
|
|
"fmt"
|
|
"net/http"
|
|
"path/filepath"
|
|
"regexp"
|
|
"strings"
|
|
|
|
"github.com/golang/glog"
|
|
|
|
"code.hackerspace.pl/hscloud/devtools/hackdoc/config"
|
|
"code.hackerspace.pl/hscloud/devtools/hackdoc/source"
|
|
)
|
|
|
|
var (
|
|
flagListen = "127.0.0.1:8080"
|
|
flagDocRoot = "./docroot"
|
|
flagHackdocURL = ""
|
|
flagGitwebURLPattern = "https://gerrit.hackerspace.pl/plugins/gitiles/hscloud/+/%s/%s"
|
|
flagGitwebDefaultBranch = "master"
|
|
|
|
rePagePath = regexp.MustCompile(`^/([A-Za-z0-9_\-/\. ]*)$`)
|
|
)
|
|
|
|
func init() {
|
|
flag.Set("logtostderr", "true")
|
|
}
|
|
|
|
func main() {
|
|
flag.StringVar(&flagListen, "listen", flagListen, "Address to listen on for HTTP traffic")
|
|
flag.StringVar(&flagDocRoot, "docroot", flagDocRoot, "Path from which to serve documents")
|
|
flag.StringVar(&flagHackdocURL, "hackdoc_url", flagHackdocURL, "Public URL of hackdoc. If not given, autogenerate from listen path for dev purposes")
|
|
flag.StringVar(&flagGitwebURLPattern, "gitweb_url_pattern", flagGitwebURLPattern, "Pattern to sprintf to for URL for viewing a file in Git. First string is ref/rev, second is bare file path (sans //)")
|
|
flag.StringVar(&flagGitwebDefaultBranch, "gitweb_default_rev", flagGitwebDefaultBranch, "Default Git rev to render/link to")
|
|
flag.Parse()
|
|
|
|
if flagHackdocURL == "" {
|
|
flagHackdocURL = fmt.Sprintf("http://%s", flagListen)
|
|
}
|
|
|
|
path, err := filepath.Abs(flagDocRoot)
|
|
if err != nil {
|
|
glog.Fatalf("Could not dereference path %q: %w", path, err)
|
|
}
|
|
|
|
s := &service{
|
|
source: source.NewLocal(path),
|
|
}
|
|
|
|
http.HandleFunc("/", s.handler)
|
|
|
|
glog.Infof("Listening on %q...", flagListen)
|
|
if err := http.ListenAndServe(flagListen, nil); err != nil {
|
|
glog.Fatal(err)
|
|
}
|
|
}
|
|
|
|
type service struct {
|
|
source source.Source
|
|
}
|
|
|
|
func (s *service) handler(w http.ResponseWriter, r *http.Request) {
|
|
if r.Method != "GET" && r.Method != "HEAD" {
|
|
w.WriteHeader(http.StatusMethodNotAllowed)
|
|
fmt.Fprintf(w, "method not allowed")
|
|
return
|
|
}
|
|
|
|
glog.Infof("%+v", r.URL.Query())
|
|
rev := r.URL.Query().Get("rev")
|
|
if rev == "" {
|
|
rev = flagGitwebDefaultBranch
|
|
}
|
|
|
|
path := r.URL.Path
|
|
|
|
if match := rePagePath.FindStringSubmatch(path); match != nil {
|
|
s.handlePage(w, r, rev, match[1])
|
|
return
|
|
}
|
|
handle404(w, r)
|
|
}
|
|
|
|
func logRequest(w http.ResponseWriter, r *http.Request, format string, args ...interface{}) {
|
|
result := fmt.Sprintf(format, args...)
|
|
glog.Infof("result: %s, remote: %q, ua: %q, referrer: %q, host: %q path: %q", result, r.RemoteAddr, r.Header.Get("User-Agent"), r.Header.Get("Referrer"), r.Host, r.URL.Path)
|
|
}
|
|
|
|
func urlPathToDepotPath(url string) string {
|
|
// Sanitize request.
|
|
parts := strings.Split(url, "/")
|
|
for i, p := range parts {
|
|
// Allow last part to be "", ie, for a path to end in /
|
|
if p == "" {
|
|
if i != len(parts)-1 {
|
|
return ""
|
|
}
|
|
}
|
|
|
|
// net/http sanitizes this anyway, but we better be sure.
|
|
if p == "." || p == ".." {
|
|
return ""
|
|
}
|
|
}
|
|
path := "//" + strings.Join(parts, "/")
|
|
|
|
return path
|
|
}
|
|
|
|
func (s *service) handlePageAuto(w http.ResponseWriter, r *http.Request, rev, rpath, dirpath string) {
|
|
cfg, err := config.ForPath(s.source, dirpath)
|
|
if err != nil {
|
|
glog.Errorf("could not get config for path %q: %w", dirpath, err)
|
|
handle500(w, r)
|
|
return
|
|
}
|
|
for _, f := range cfg.DefaultIndex {
|
|
fpath := dirpath + f
|
|
file, err := s.source.IsFile(fpath)
|
|
if err != nil {
|
|
glog.Errorf("IsFile(%q): %w", fpath, err)
|
|
handle500(w, r)
|
|
return
|
|
}
|
|
|
|
if file {
|
|
s.handleMarkdown(w, r, s.source, rev, fpath, cfg)
|
|
return
|
|
}
|
|
}
|
|
|
|
handle404(w, r)
|
|
}
|
|
|
|
func (s *service) handlePage(w http.ResponseWriter, r *http.Request, rev, page string) {
|
|
path := urlPathToDepotPath(page)
|
|
|
|
if strings.HasSuffix(path, "/") {
|
|
// Directory path given, autoresolve.
|
|
dirpath := path
|
|
if path != "//" {
|
|
dirpath = strings.TrimSuffix(path, "/") + "/"
|
|
}
|
|
s.handlePageAuto(w, r, rev, path, dirpath)
|
|
return
|
|
}
|
|
|
|
// Otherwise, try loading the file.
|
|
file, err := s.source.IsFile(path)
|
|
if err != nil {
|
|
glog.Errorf("IsFile(%q): %w", path, err)
|
|
handle500(w, r)
|
|
return
|
|
}
|
|
|
|
// File exists, render that.
|
|
if file {
|
|
parts := strings.Split(path, "/")
|
|
dirpath := strings.Join(parts[:(len(parts)-1)], "/")
|
|
cfg, err := config.ForPath(s.source, dirpath)
|
|
if err != nil {
|
|
glog.Errorf("could not get config for path %q: %w", dirpath, err)
|
|
handle500(w, r)
|
|
return
|
|
}
|
|
s.handleMarkdown(w, r, s.source, rev, path, cfg)
|
|
return
|
|
}
|
|
|
|
// Otherwise assume directory, try all posibilities.
|
|
dirpath := path
|
|
if path != "//" {
|
|
dirpath = strings.TrimSuffix(path, "/") + "/"
|
|
}
|
|
s.handlePageAuto(w, r, rev, path, dirpath)
|
|
}
|