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"
2012-06-16 17:20:32 +00:00
"html/template"
2011-01-03 23:49:19 +00:00
"io"
"io/ioutil"
"log"
2012-02-21 10:11:28 +00:00
"net/http"
"net/http/cgi"
"net/http/httputil"
"net/url"
2011-01-03 23:49:19 +00:00
"os"
2012-02-21 10:11:28 +00:00
"os/exec"
2011-06-22 00:07:59 +00:00
"path/filepath"
2011-01-28 19:17:08 +00:00
"regexp"
2011-01-03 23:49:19 +00:00
"strings"
2013-03-15 23:26:21 +00:00
txttemplate "text/template"
2011-06-22 00:07:59 +00:00
"time"
2011-01-03 23:49:19 +00:00
)
const defaultAddr = ":31798" // default webserver address
2011-07-07 00:01:35 +00:00
var h1TitlePattern = regexp . MustCompile ( ` <h1>([^<]+)</h1> ` )
2011-01-28 19:17:08 +00:00
2011-01-03 23:49:19 +00:00
var (
2013-06-24 22:50:49 +00:00
httpAddr = flag . String ( "http" , defaultAddr , "HTTP service address (e.g., '" + defaultAddr + "')" )
httpsAddr = flag . String ( "https" , "" , "HTTPS service address" )
root = flag . String ( "root" , "" , "Website root (parent of 'static', 'content', and 'tmpl" )
logDir = flag . String ( "logdir" , "" , "Directory to write log files to (one per hour), or empty to not log." )
logStdout = flag . Bool ( "logstdout" , true , "Write to stdout?" )
tlsCertFile = flag . String ( "tlscert" , "" , "TLS cert file" )
tlsKeyFile = flag . String ( "tlskey" , "" , "TLS private key file" )
gerritUser = flag . String ( "gerrituser" , "ubuntu" , "Gerrit host's username" )
gerritHost = flag . String ( "gerrithost" , "" , "Gerrit host, or empty." )
buildbotBackend = flag . String ( "buildbot_backend" , "" , "Build bot status backend URL" )
buildbotHost = flag . String ( "buildbot_host" , "" , "Hostname to map to the buildbot_backend. If an HTTP request with this hostname is received, it proxies to buildbot_backend." )
alsoRun = flag . String ( "also_run" , "" , "Optional path to run as a child process. (Used to run camlistore.org's ./scripts/run-blob-server)" )
2011-01-03 23:49:19 +00:00
pageHtml , errorHtml * template . Template
2013-03-15 23:26:21 +00:00
packageHTML * txttemplate . Template
2011-01-03 23:49:19 +00:00
)
2012-06-16 17:20:32 +00:00
var fmap = template . FuncMap {
2013-01-14 22:33:49 +00:00
"" : textFmt ,
"html" : htmlFmt ,
2012-06-16 17:20:32 +00:00
"htmlesc" : htmlEscFmt ,
2011-01-03 23:49:19 +00:00
}
// Template formatter for "" (default) format.
2012-06-16 17:20:32 +00:00
func textFmt ( w io . Writer , format string , x ... interface { } ) string {
2011-01-03 23:49:19 +00:00
writeAny ( w , false , x [ 0 ] )
2012-06-16 17:20:32 +00:00
return ""
2011-01-03 23:49:19 +00:00
}
// Template formatter for "html" format.
2012-06-16 17:20:32 +00:00
func htmlFmt ( w io . Writer , format string , x ... interface { } ) string {
2011-01-03 23:49:19 +00:00
writeAny ( w , true , x [ 0 ] )
2012-06-16 17:20:32 +00:00
return ""
2011-01-03 23:49:19 +00:00
}
2012-06-16 17:20:32 +00:00
// Template formatter for "htmlesc" format.
func htmlEscFmt ( w io . Writer , format string , x ... interface { } ) string {
2011-01-03 23:49:19 +00:00
var buf bytes . Buffer
writeAny ( & buf , false , x [ 0 ] )
template . HTMLEscape ( w , buf . Bytes ( ) )
2012-06-16 17:20:32 +00:00
return ""
2011-01-03 23:49:19 +00:00
}
// 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
2011-02-21 20:12:28 +00:00
if err := t . Execute ( & buf , data ) ; err != nil {
2011-01-03 23:49:19 +00:00
log . Printf ( "%s.Execute: %s" , name , err )
}
return buf . Bytes ( )
}
func servePage ( w http . ResponseWriter , title , subtitle string , content [ ] byte ) {
2013-03-15 18:26:24 +00:00
// insert an "install command" if it applies
if strings . Contains ( title , cmdPattern ) && subtitle != cmdPattern {
toInsert := `
< h3 > Installation < / h3 >
< pre > go get camlistore . org / cmd / ` + subtitle + ` < / pre >
< h3 > Overview < / h3 > < p > `
content = bytes . Replace ( content , [ ] byte ( "<p>" ) , [ ] byte ( toInsert ) , 1 )
}
2011-01-03 23:49:19 +00:00
d := struct {
Title string
Subtitle string
2012-06-16 17:20:32 +00:00
Content template . HTML
2011-01-03 23:49:19 +00:00
} {
title ,
subtitle ,
2012-06-16 17:20:32 +00:00
template . HTML ( content ) ,
2011-01-03 23:49:19 +00:00
}
2011-02-21 20:12:28 +00:00
if err := pageHtml . Execute ( w , & d ) ; err != nil {
2011-01-03 23:49:19 +00:00
log . Printf ( "godocHTML.Execute: %s" , err )
}
}
func readTemplate ( name string ) * template . Template {
2011-06-22 00:07:59 +00:00
fileName := filepath . Join ( * root , "tmpl" , name )
2011-01-03 23:49:19 +00:00
data , err := ioutil . ReadFile ( fileName )
if err != nil {
2011-02-02 20:27:30 +00:00
log . Fatalf ( "ReadFile %s: %v" , fileName , err )
2011-01-03 23:49:19 +00:00
}
2012-06-16 17:20:32 +00:00
t , err := template . New ( name ) . Funcs ( fmap ) . Parse ( string ( data ) )
2011-01-03 23:49:19 +00:00
if err != nil {
2011-02-02 20:27:30 +00:00
log . Fatalf ( "%s: %v" , fileName , err )
2011-01-03 23:49:19 +00:00
}
return t
}
func readTemplates ( ) {
pageHtml = readTemplate ( "page.html" )
errorHtml = readTemplate ( "error.html" )
2013-03-15 23:26:21 +00:00
// TODO(mpl): see about not using text template anymore?
packageHTML = readTextTemplate ( "package.html" )
2011-01-03 23:49:19 +00:00
}
2012-02-21 10:11:28 +00:00
func serveError ( w http . ResponseWriter , r * http . Request , relpath string , err error ) {
2011-01-03 23:49:19 +00:00
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 : ]
2013-02-02 18:05:09 +00:00
http . Redirect ( rw , req , "http://camlistore.org/code/?p=camlistore.git;f=" + path + ";hb=master" , http . StatusFound )
2011-01-28 19:17:08 +00:00
return
}
2011-06-22 00:07:59 +00:00
absPath := filepath . 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
}
2012-02-21 10:11:28 +00:00
if fi . IsDir ( ) {
2011-01-28 07:47:44 +00:00
relPath += "/index.html"
2011-06-22 00:07:59 +00:00
absPath = filepath . Join ( * root , "content" , relPath )
2011-01-28 07:47:44 +00:00
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 {
2012-02-21 10:11:28 +00:00
case ! fi . IsDir ( ) :
2011-01-03 23:49:19 +00:00
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-11-30 21:08:01 +00:00
func isBot ( r * http . Request ) bool {
agent := r . Header . Get ( "User-Agent" )
return strings . Contains ( agent , "Baidu" ) || strings . Contains ( agent , "bingbot" ) ||
strings . Contains ( agent , "Ezooms" ) || strings . Contains ( agent , "Googlebot" )
}
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-11-30 21:08:01 +00:00
// Some bots (especially Baidu) don't seem to respect robots.txt and swamp gitweb.cgi,
// so explicitly protect it from bots.
2012-02-21 10:11:28 +00:00
if ru := r . URL . RequestURI ( ) ; strings . Contains ( ru , "/code/" ) && strings . Contains ( ru , "?" ) && isBot ( r ) {
2011-11-30 21:08:01 +00:00
http . Error ( rw , "bye" , http . StatusUnauthorized )
log . Printf ( "bot denied" )
return
}
2011-01-28 05:12:14 +00:00
host := strings . ToLower ( r . Host )
if host == "www.camlistore.org" {
2012-02-21 10:11:28 +00:00
http . Redirect ( rw , r , "http://camlistore.org" + r . URL . RequestURI ( ) , http . StatusFound )
2011-01-28 05:12:14 +00:00
return
}
2011-01-26 06:48:15 +00:00
h . Handler . ServeHTTP ( rw , r )
}
2013-06-24 22:50:49 +00:00
// runAsChild runs res as a child process and
// does not wait for it to finish.
func runAsChild ( res string ) {
cmdName , err := exec . LookPath ( res )
if err != nil {
log . Fatalf ( "Could not find %v in $PATH: %v" , res , err )
}
cmd := exec . Command ( cmdName )
2013-07-02 05:26:44 +00:00
cmd . Stderr = os . Stderr
cmd . Stdout = os . Stdout
2013-06-24 22:50:49 +00:00
log . Printf ( "Running %v" , res )
if err := cmd . Start ( ) ; err != nil {
log . Fatal ( "Program %v failed to start: %v" , res , err )
}
go func ( ) {
if err := cmd . Wait ( ) ; err != nil {
log . Fatalf ( "Program %s did not end successfully: %v" , res , err )
}
} ( )
}
2011-01-03 23:49:19 +00:00
func main ( ) {
flag . Parse ( )
2011-01-25 20:43:43 +00:00
if * root == "" {
2012-02-21 10:11:28 +00:00
var err error
2011-01-25 20:43:43 +00:00
* root , err = os . Getwd ( )
if err != nil {
2011-02-02 20:27:30 +00:00
log . Fatalf ( "Failed to getwd: %v" , err )
2011-01-25 20:43:43 +00:00
}
}
2013-03-15 23:26:21 +00:00
readTemplates ( )
2011-06-22 00:07:59 +00:00
2011-01-03 23:49:19 +00:00
mux := http . DefaultServeMux
2011-06-30 04:13:03 +00:00
mux . Handle ( "/favicon.ico" , http . FileServer ( http . Dir ( filepath . Join ( * root , "static" ) ) ) )
mux . Handle ( "/robots.txt" , http . FileServer ( http . Dir ( filepath . Join ( * root , "static" ) ) ) )
mux . Handle ( "/static/" , http . StripPrefix ( "/static/" , http . FileServer ( http . Dir ( filepath . Join ( * root , "static" ) ) ) ) )
mux . Handle ( "/talks/" , http . StripPrefix ( "/talks/" , http . FileServer ( http . Dir ( filepath . Join ( * root , "talks" ) ) ) ) )
2013-03-14 00:42:56 +00:00
mux . Handle ( pkgPattern , godocHandler { } )
mux . Handle ( cmdPattern , godocHandler { } )
2011-01-25 20:43:43 +00:00
2013-08-14 16:59:31 +00:00
mux . HandleFunc ( "/r/" , gerritRedirect )
2011-11-28 22:52:58 +00:00
mux . HandleFunc ( "/debugz/ip" , ipHandler )
2011-06-13 21:24:51 +00:00
2011-06-22 00:07:59 +00:00
testCgi := & cgi . Handler { Path : filepath . Join ( * root , "test.cgi" ) ,
2011-01-25 20:43:43 +00:00
Root : "/test.cgi" ,
}
mux . Handle ( "/test.cgi" , testCgi )
mux . Handle ( "/test.cgi/foo" , testCgi )
2013-08-14 16:59:31 +00:00
2013-02-18 05:40:57 +00:00
mux . HandleFunc ( "/issue/" , issueRedirect )
2011-01-03 23:49:19 +00:00
mux . HandleFunc ( "/" , mainHandler )
2013-01-14 22:33:49 +00:00
if * buildbotHost != "" && * buildbotBackend != "" {
buildbotUrl , err := url . Parse ( * buildbotBackend )
if err != nil {
log . Fatalf ( "Failed to parse %v as a URL: %v" , * buildbotBackend , err )
}
buildbotHandler := httputil . NewSingleHostReverseProxy ( buildbotUrl )
bbhpattern := strings . TrimRight ( * buildbotHost , "/" ) + "/"
mux . Handle ( bbhpattern , buildbotHandler )
}
2011-01-29 19:53:22 +00:00
var handler http . Handler = & noWwwHandler { Handler : mux }
2011-01-29 20:31:35 +00:00
if * logDir != "" || * logStdout {
handler = NewLoggingHandler ( handler , * logDir , * logStdout )
2011-01-29 19:53:22 +00:00
}
2011-01-31 23:04:16 +00:00
2013-06-24 22:50:49 +00:00
if * alsoRun != "" {
runAsChild ( * alsoRun )
}
2012-02-21 10:11:28 +00:00
errch := make ( chan error )
2011-01-31 23:04:16 +00:00
2011-06-21 17:27:11 +00:00
httpServer := & http . Server {
Addr : * httpAddr ,
Handler : handler ,
2013-02-02 17:42:20 +00:00
ReadTimeout : 5 * time . Minute ,
WriteTimeout : 30 * time . Minute ,
2011-06-21 17:27:11 +00:00
}
go func ( ) {
errch <- httpServer . ListenAndServe ( )
} ( )
if * httpsAddr != "" {
2011-07-07 00:16:47 +00:00
log . Printf ( "Starting TLS server on %s" , * httpsAddr )
2011-06-21 17:27:11 +00:00
httpsServer := new ( http . Server )
* httpsServer = * httpServer
httpsServer . Addr = * httpsAddr
go func ( ) {
errch <- httpsServer . ListenAndServeTLS ( * tlsCertFile , * tlsKeyFile )
} ( )
2011-01-03 23:49:19 +00:00
}
2011-01-31 23:04:16 +00:00
2011-06-21 17:27:11 +00:00
log . Fatalf ( "Serve error: %v" , <- errch )
2011-01-31 23:04:16 +00:00
}
2013-02-18 05:40:57 +00:00
var issueNum = regexp . MustCompile ( ` ^/issue/(\d+)$ ` )
func issueRedirect ( w http . ResponseWriter , r * http . Request ) {
m := issueNum . FindStringSubmatch ( r . URL . Path )
if m == nil {
http . Error ( w , "Bad request" , 400 )
return
}
2013-02-23 21:38:58 +00:00
http . Redirect ( w , r , "https://code.google.com/p/camlistore/issues/detail?id=" + m [ 1 ] , http . StatusFound )
2013-02-18 05:40:57 +00:00
}
2013-08-14 16:59:31 +00:00
func gerritRedirect ( w http . ResponseWriter , r * http . Request ) {
dest := "https://camlistore-review.googlesource.com/"
if len ( r . URL . Path ) > len ( "/r/" ) {
dest += r . URL . Path [ 1 : ]
}
http . Redirect ( w , r , dest , http . StatusFound )
2011-02-02 21:00:32 +00:00
}
// Not sure what's making these broken URLs like:
//
// http://localhost:8080/code/?p=camlistore.git%3Bf=doc/json-signing/json-signing.txt%3Bhb=master
//
// ... but something is. Maybe Buzz? For now just re-write them
// . Doesn't seem to be a bug in the CGI implementation, though, which
// is what I'd originally suspected.
2013-08-14 16:59:31 +00:00
/ *
2011-02-02 21:00:32 +00:00
func ( fu * fixUpGitwebUrls ) ServeHTTP ( rw http . ResponseWriter , req * http . Request ) {
2012-02-21 10:11:28 +00:00
oldUrl := req . URL . String ( )
2011-02-02 21:00:32 +00:00
newUrl := strings . Replace ( oldUrl , "%3B" , ";" , - 1 )
if newUrl == oldUrl {
fu . handler . ServeHTTP ( rw , req )
return
}
http . Redirect ( rw , req , newUrl , http . StatusFound )
}
2013-08-14 16:59:31 +00:00
* /
2011-11-28 22:52:58 +00:00
func ipHandler ( w http . ResponseWriter , r * http . Request ) {
out , _ := exec . Command ( "ip" , "-f" , "inet" , "addr" , "show" , "dev" , "eth0" ) . Output ( )
str := string ( out )
pos := strings . Index ( str , "inet " )
if pos == - 1 {
return
}
2011-11-30 21:08:01 +00:00
str = str [ pos + 5 : ]
2011-11-28 22:52:58 +00:00
pos = strings . Index ( str , "/" )
if pos == - 1 {
return
}
str = str [ : pos ]
w . Write ( [ ] byte ( str ) )
}