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/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" )
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
}
2013-10-11 16:33:43 +00:00
const gerritURLPrefix = "https://camlistore.googlesource.com/camlistore/+/"
2013-08-18 15:08:18 +00:00
var commitHash = regexp . MustCompile ( ` ^p=camlistore.git;a=commit;h=([0-9a-f]+)$ ` )
2013-08-28 20:09:01 +00:00
// empty return value means don't redirect.
func redirectPath ( u * url . URL ) string {
2013-08-18 15:08:18 +00:00
// Example:
// /code/?p=camlistore.git;a=commit;h=b0d2a8f0e5f27bbfc025a96ec3c7896b42d198ed
2013-08-28 20:09:01 +00:00
if strings . HasPrefix ( u . Path , "/code/" ) {
m := commitHash . FindStringSubmatch ( u . RawQuery )
2013-08-18 15:08:18 +00:00
if len ( m ) == 2 {
2013-10-11 16:33:43 +00:00
return gerritURLPrefix + m [ 1 ]
2013-08-18 15:08:18 +00:00
}
}
2013-08-28 20:09:01 +00:00
if strings . HasPrefix ( u . Path , "/gw/" ) {
path := strings . TrimPrefix ( u . Path , "/gw/" )
if strings . HasPrefix ( path , "doc" ) || strings . HasPrefix ( path , "clients" ) {
2013-10-11 16:33:43 +00:00
return gerritURLPrefix + "master/" + path
2013-08-28 20:09:01 +00:00
}
// Assume it's a commit
2013-10-11 16:33:43 +00:00
return gerritURLPrefix + path
2013-08-28 20:09:01 +00:00
}
return ""
}
func mainHandler ( rw http . ResponseWriter , req * http . Request ) {
if target := redirectPath ( req . URL ) ; target != "" {
http . Redirect ( rw , req , target , http . StatusFound )
return
}
2013-11-21 23:04:54 +00:00
if dest , ok := issueRedirect ( req . URL . Path ) ; ok {
http . Redirect ( rw , req , dest , http . StatusFound )
return
}
2013-08-28 20:09:01 +00:00
relPath := req . URL . Path [ 1 : ] // serveFile URL paths start with '/'
if strings . Contains ( relPath , ".." ) {
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
2013-08-23 04:53:49 +00:00
if ! fi . IsDir ( ) {
if checkLastModified ( rw , req , fi . ModTime ( ) ) {
return
}
2011-01-03 23:49:19 +00:00
serveFile ( rw , req , relPath , absPath )
}
}
2013-08-23 04:53:49 +00:00
// modtime is the modification time of the resource to be served, or IsZero().
// return value is whether this request is now complete.
func checkLastModified ( w http . ResponseWriter , r * http . Request , modtime time . Time ) bool {
if modtime . IsZero ( ) {
return false
}
// The Date-Modified header truncates sub-second precision, so
// use mtime < t+1s instead of mtime <= t to check for unmodified.
if t , err := time . Parse ( http . TimeFormat , r . Header . Get ( "If-Modified-Since" ) ) ; err == nil && modtime . Before ( t . Add ( 1 * time . Second ) ) {
h := w . Header ( )
delete ( h , "Content-Type" )
delete ( h , "Content-Length" )
w . WriteHeader ( http . StatusNotModified )
return true
}
w . Header ( ) . Set ( "Last-Modified" , modtime . UTC ( ) . Format ( http . TimeFormat ) )
return false
}
2011-01-03 23:49:19 +00:00
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 {
2013-10-08 18:46:20 +00:00
log . Fatalf ( "Program %v failed to start: %v" , res , err )
2013-06-24 22:50:49 +00:00
}
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-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-08-23 04:33:23 +00:00
errc := make ( chan error )
startEmailCommitLoop ( errc )
2013-06-24 22:50:49 +00:00
if * alsoRun != "" {
runAsChild ( * alsoRun )
}
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 ( ) {
2013-08-23 04:33:23 +00:00
errc <- httpServer . ListenAndServe ( )
2011-06-21 17:27:11 +00:00
} ( )
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 ( ) {
2013-08-23 04:33:23 +00:00
errc <- httpsServer . ListenAndServeTLS ( * tlsCertFile , * tlsKeyFile )
2011-06-21 17:27:11 +00:00
} ( )
2011-01-03 23:49:19 +00:00
}
2011-01-31 23:04:16 +00:00
2013-08-23 04:33:23 +00:00
log . Fatalf ( "Serve error: %v" , <- errc )
2011-01-31 23:04:16 +00:00
}
2013-11-21 23:04:54 +00:00
var issueNum = regexp . MustCompile ( ` ^/(?:issue(?:s)?|bugs)(/\d*)?$ ` )
2013-02-18 05:40:57 +00:00
2013-11-21 23:04:54 +00:00
// issueRedirect returns whether the request should be redirected to the
// issues tracker, and the url for that redirection if yes, the empty
// string otherwise.
func issueRedirect ( urlPath string ) ( string , bool ) {
m := issueNum . FindStringSubmatch ( urlPath )
2013-02-18 05:40:57 +00:00
if m == nil {
2013-11-21 23:04:54 +00:00
return "" , false
}
issueNumber := strings . TrimPrefix ( m [ 1 ] , "/" )
suffix := "list"
if issueNumber != "" {
suffix = "detail?id=" + issueNumber
2013-02-18 05:40:57 +00:00
}
2013-11-21 23:04:54 +00:00
return "https://code.google.com/p/camlistore/issues/" + suffix , true
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 ) )
}