blob: 938a4262b0ba8bbae4b0faa4085f431c9d5cf8ab [file] [log] [blame]
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)
}