perkeep/website/camweb.go

228 lines
5.3 KiB
Go

package main
import (
"bytes"
"flag"
"fmt"
"http"
"io"
"io/ioutil"
"log"
"os"
"path"
"strings"
"template"
)
const defaultAddr = ":31798" // default webserver address
var (
httpAddr = flag.String("http", defaultAddr, "HTTP service address (e.g., '"+defaultAddr+"')")
root = flag.String("root", "", "Website root (parent of 'static', 'content', and 'tmpl")
gitwebScript = flag.String("gitwebscript", "/usr/lib/cgi-bin/gitweb.cgi", "Path to gitweb.cgi, or blank to disable.")
gitwebFiles = flag.String("gitwebfiles", "/usr/share/gitweb", "Path to gitweb's static files.")
pageHtml, errorHtml *template.Template
)
var fmap = template.FormatterMap{
"": textFmt,
"html": htmlFmt,
"html-esc": htmlEscFmt,
}
// Template formatter for "" (default) format.
func textFmt(w io.Writer, format string, x ...interface{}) {
writeAny(w, false, x[0])
}
// Template formatter for "html" format.
func htmlFmt(w io.Writer, format string, x ...interface{}) {
writeAny(w, true, x[0])
}
// Template formatter for "html-esc" format.
func htmlEscFmt(w io.Writer, format string, x ...interface{}) {
var buf bytes.Buffer
writeAny(&buf, false, x[0])
template.HTMLEscape(w, buf.Bytes())
}
// Write anything to w; optionally html-escaped.
func writeAny(w io.Writer, html bool, x interface{}) {
switch v := x.(type) {
case []byte:
writeText(w, v, html)
case string:
writeText(w, []byte(v), html)
default:
if html {
var buf bytes.Buffer
fmt.Fprint(&buf, x)
writeText(w, buf.Bytes(), true)
} else {
fmt.Fprint(w, x)
}
}
}
// Write text to w; optionally html-escaped.
func writeText(w io.Writer, text []byte, html bool) {
if html {
template.HTMLEscape(w, text)
return
}
w.Write(text)
}
func applyTemplate(t *template.Template, name string, data interface{}) []byte {
var buf bytes.Buffer
if err := t.Execute(data, &buf); err != nil {
log.Printf("%s.Execute: %s", name, err)
}
return buf.Bytes()
}
func servePage(w http.ResponseWriter, title, subtitle string, content []byte) {
d := struct {
Title string
Subtitle string
Content []byte
}{
title,
subtitle,
content,
}
if err := pageHtml.Execute(&d, w); err != nil {
log.Printf("godocHTML.Execute: %s", err)
}
}
func readTemplate(name string) *template.Template {
fileName := path.Join(*root, "tmpl", name)
data, err := ioutil.ReadFile(fileName)
if err != nil {
log.Exitf("ReadFile %s: %v", fileName, err)
}
t, err := template.Parse(string(data), fmap)
if err != nil {
log.Exitf("%s: %v", fileName, err)
}
return t
}
func readTemplates() {
pageHtml = readTemplate("page.html")
errorHtml = readTemplate("error.html")
}
func serveError(w http.ResponseWriter, r *http.Request, relpath string, err os.Error) {
contents := applyTemplate(errorHtml, "errorHtml", err) // err may contain an absolute path!
w.WriteHeader(http.StatusNotFound)
servePage(w, "File "+relpath, "", contents)
}
func mainHandler(rw http.ResponseWriter, req *http.Request) {
relPath := req.URL.Path[1:] // serveFile URL paths start with '/'
if strings.Contains(relPath, "..") {
return
}
if relPath == "" {
relPath = "index.html"
}
absPath := path.Join(*root, "content", relPath)
fi, err := os.Lstat(absPath)
if err != nil {
log.Print(err)
serveError(rw, req, relPath, err)
return
}
switch {
case fi.IsRegular():
serveFile(rw, req, relPath, absPath)
}
}
func serveFile(rw http.ResponseWriter, req *http.Request, relPath, absPath string) {
data, err := ioutil.ReadFile(absPath)
if err != nil {
serveError(rw, req, absPath, err)
return
}
servePage(rw, "", "", []byte(data))
}
type gitwebHandler struct {
Cgi http.Handler
Static http.Handler
}
func (h *gitwebHandler) ServeHTTP(rw http.ResponseWriter, r *http.Request) {
if r.URL.RawPath == "/code/" ||
strings.HasPrefix(r.URL.RawPath, "/code/?") {
h.Cgi.ServeHTTP(rw, r)
} else {
h.Static.ServeHTTP(rw, r)
}
}
type noWwwHandler struct {
Handler http.Handler
}
func (h *noWwwHandler) ServeHTTP(rw http.ResponseWriter, r *http.Request) {
host := strings.ToLower(r.Host)
if host == "www.camlistore.org" {
http.Redirect(rw, r, "http://camlistore.org" + r.URL.RawPath, http.StatusFound)
return
}
h.Handler.ServeHTTP(rw, r)
}
func main() {
flag.Parse()
readTemplates()
if *root == "" {
var err os.Error
*root, err = os.Getwd()
if err != nil {
log.Exitf("Failed to getwd: %v", err)
}
}
mux := http.DefaultServeMux
mux.Handle("/favicon.ico", http.FileServer(path.Join(*root, "static"), "/"))
mux.Handle("/static/", http.FileServer(path.Join(*root, "static"), "/static/"))
testCgi := &CgiHandler{ExecutablePath: path.Join(*root, "test.cgi"),
Root: "/test.cgi",
}
mux.Handle("/test.cgi", testCgi)
mux.Handle("/test.cgi/foo", testCgi)
mux.Handle("/code", http.RedirectHandler("/code/", http.StatusFound))
if *gitwebScript != "" {
env := os.Environ()
env = append(env, "GITWEB_CONFIG="+path.Join(*root, "gitweb-camli.conf"))
env = append(env, "CAMWEB_ROOT="+path.Join(*root))
mux.Handle("/code/", &gitwebHandler{
Cgi: &CgiHandler{
ExecutablePath: *gitwebScript,
Root: "/code/",
Environ: env,
},
Static: http.FileServer(*gitwebFiles, "/code/"),
})
}
mux.HandleFunc("/", mainHandler)
if err := http.ListenAndServe(*httpAddr, &noWwwHandler{Handler: mux}); err != nil {
log.Exitf("ListenAndServe %s: %v", *httpAddr, err)
}
}