2011-01-28 07:07:18 +00:00
|
|
|
/*
|
|
|
|
Copyright 2011 Google Inc.
|
|
|
|
|
|
|
|
Licensed under the Apache License, Version 2.0 (the "License");
|
|
|
|
you may not use this file except in compliance with the License.
|
|
|
|
You may obtain a copy of the License at
|
|
|
|
|
|
|
|
http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
|
|
|
|
Unless required by applicable law or agreed to in writing, software
|
|
|
|
distributed under the License is distributed on an "AS IS" BASIS,
|
|
|
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
|
|
See the License for the specific language governing permissions and
|
|
|
|
limitations under the License.
|
|
|
|
*/
|
|
|
|
|
2011-01-03 23:49:19 +00:00
|
|
|
package main
|
|
|
|
|
|
|
|
import (
|
|
|
|
"bytes"
|
|
|
|
"flag"
|
|
|
|
"fmt"
|
|
|
|
"http"
|
|
|
|
"io"
|
|
|
|
"io/ioutil"
|
|
|
|
"log"
|
|
|
|
"os"
|
|
|
|
"path"
|
2011-01-28 19:17:08 +00:00
|
|
|
"regexp"
|
2011-01-03 23:49:19 +00:00
|
|
|
"strings"
|
|
|
|
"template"
|
|
|
|
)
|
|
|
|
|
|
|
|
const defaultAddr = ":31798" // default webserver address
|
|
|
|
|
2011-01-28 19:17:08 +00:00
|
|
|
var h1TitlePattern = regexp.MustCompile(`<h1>(.+)</h1>`)
|
|
|
|
|
2011-01-03 23:49:19 +00:00
|
|
|
var (
|
2011-01-25 20:43:43 +00:00
|
|
|
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.")
|
2011-01-03 23:49:19 +00:00
|
|
|
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)
|
2011-01-25 20:43:43 +00:00
|
|
|
servePage(w, "File "+relpath, "", contents)
|
2011-01-03 23:49:19 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func mainHandler(rw http.ResponseWriter, req *http.Request) {
|
|
|
|
relPath := req.URL.Path[1:] // serveFile URL paths start with '/'
|
|
|
|
if strings.Contains(relPath, "..") {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2011-01-28 19:17:08 +00:00
|
|
|
if strings.HasPrefix(relPath, "gw/") {
|
|
|
|
path := relPath[3:]
|
|
|
|
http.Redirect(rw, req, "/code/?p=camlistore.git;f=" + path + ";hb=master", http.StatusFound)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2011-01-28 07:47:44 +00:00
|
|
|
absPath := path.Join(*root, "content", relPath)
|
2011-01-03 23:49:19 +00:00
|
|
|
fi, err := os.Lstat(absPath)
|
|
|
|
if err != nil {
|
|
|
|
log.Print(err)
|
|
|
|
serveError(rw, req, relPath, err)
|
|
|
|
return
|
|
|
|
}
|
2011-01-28 07:47:44 +00:00
|
|
|
if fi.IsDirectory() {
|
|
|
|
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
|
|
|
|
}
|
|
|
|
}
|
2011-01-03 23:49:19 +00:00
|
|
|
|
|
|
|
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)
|
2011-01-25 20:43:43 +00:00
|
|
|
return
|
2011-01-03 23:49:19 +00:00
|
|
|
}
|
2011-01-28 19:17:08 +00:00
|
|
|
|
|
|
|
title := ""
|
|
|
|
if m := h1TitlePattern.FindSubmatch(data); len(m) > 1 {
|
|
|
|
title = string(m[1])
|
|
|
|
}
|
|
|
|
|
|
|
|
servePage(rw, title, "", data)
|
2011-01-03 23:49:19 +00:00
|
|
|
}
|
|
|
|
|
2011-01-25 20:43:43 +00:00
|
|
|
type gitwebHandler struct {
|
|
|
|
Cgi http.Handler
|
|
|
|
Static http.Handler
|
2011-01-25 18:38:36 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
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)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2011-01-26 06:48:15 +00:00
|
|
|
type noWwwHandler struct {
|
|
|
|
Handler http.Handler
|
|
|
|
}
|
|
|
|
|
|
|
|
func (h *noWwwHandler) ServeHTTP(rw http.ResponseWriter, r *http.Request) {
|
2011-01-28 05:12:14 +00:00
|
|
|
host := strings.ToLower(r.Host)
|
|
|
|
if host == "www.camlistore.org" {
|
|
|
|
http.Redirect(rw, r, "http://camlistore.org" + r.URL.RawPath, http.StatusFound)
|
|
|
|
return
|
|
|
|
}
|
2011-01-26 06:48:15 +00:00
|
|
|
h.Handler.ServeHTTP(rw, r)
|
|
|
|
}
|
|
|
|
|
2011-01-03 23:49:19 +00:00
|
|
|
func main() {
|
|
|
|
flag.Parse()
|
|
|
|
readTemplates()
|
|
|
|
|
2011-01-25 20:43:43 +00:00
|
|
|
if *root == "" {
|
|
|
|
var err os.Error
|
|
|
|
*root, err = os.Getwd()
|
|
|
|
if err != nil {
|
|
|
|
log.Exitf("Failed to getwd: %v", err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2011-01-03 23:49:19 +00:00
|
|
|
mux := http.DefaultServeMux
|
2011-01-24 05:52:14 +00:00
|
|
|
mux.Handle("/favicon.ico", http.FileServer(path.Join(*root, "static"), "/"))
|
2011-01-28 05:18:11 +00:00
|
|
|
mux.Handle("/robots.txt", http.FileServer(path.Join(*root, "static"), "/"))
|
2011-01-03 23:49:19 +00:00
|
|
|
mux.Handle("/static/", http.FileServer(path.Join(*root, "static"), "/static/"))
|
2011-01-25 20:43:43 +00:00
|
|
|
|
|
|
|
testCgi := &CgiHandler{ExecutablePath: path.Join(*root, "test.cgi"),
|
|
|
|
Root: "/test.cgi",
|
|
|
|
}
|
|
|
|
mux.Handle("/test.cgi", testCgi)
|
|
|
|
mux.Handle("/test.cgi/foo", testCgi)
|
|
|
|
|
2011-01-25 18:38:36 +00:00
|
|
|
mux.Handle("/code", http.RedirectHandler("/code/", http.StatusFound))
|
|
|
|
if *gitwebScript != "" {
|
2011-01-25 20:43:43 +00:00
|
|
|
env := os.Environ()
|
|
|
|
env = append(env, "GITWEB_CONFIG="+path.Join(*root, "gitweb-camli.conf"))
|
2011-01-25 20:54:12 +00:00
|
|
|
env = append(env, "CAMWEB_ROOT="+path.Join(*root))
|
2011-01-25 18:38:36 +00:00
|
|
|
mux.Handle("/code/", &gitwebHandler{
|
2011-01-25 20:43:43 +00:00
|
|
|
Cgi: &CgiHandler{
|
|
|
|
ExecutablePath: *gitwebScript,
|
|
|
|
Root: "/code/",
|
|
|
|
Environ: env,
|
|
|
|
},
|
|
|
|
Static: http.FileServer(*gitwebFiles, "/code/"),
|
2011-01-25 18:38:36 +00:00
|
|
|
})
|
|
|
|
}
|
2011-01-03 23:49:19 +00:00
|
|
|
mux.HandleFunc("/", mainHandler)
|
|
|
|
|
2011-01-26 06:48:15 +00:00
|
|
|
if err := http.ListenAndServe(*httpAddr, &noWwwHandler{Handler: mux}); err != nil {
|
2011-01-03 23:49:19 +00:00
|
|
|
log.Exitf("ListenAndServe %s: %v", *httpAddr, err)
|
|
|
|
}
|
|
|
|
}
|