2019-03-03 17:43:29 +00:00
package main
import (
// "fmt"
"golang.org/x/net/html"
"mime"
"net/http"
"os"
"path/filepath"
"strings"
"time"
)
func LoggingWrapper ( log * os . File , handler http . HandlerFunc ) http . HandlerFunc {
return func ( w http . ResponseWriter , r * http . Request ) {
handler ( w , r )
t := time . Now ( )
log . WriteString ( r . RemoteAddr + " " + t . Format ( time . UnixDate ) + " " + r . RequestURI + "\n" )
}
}
func GetHandler ( config Config ) http . HandlerFunc {
return func ( w http . ResponseWriter , r * http . Request ) {
// Security first!
w . Header ( ) . Set ( "Strict-Transport-Security" , "max-age=31536000" )
// Default to text/plain, as used for errors
w . Header ( ) . Set ( "Content-Type" , "text/plain" )
// Fail if request method is not GET or HEAD
if r . Method != "GET" && r . Method != "HEAD" {
http . Error ( w , "Unimplemented HTTP method: use HEAD or GET." , http . StatusNotImplemented )
return
}
// Fail if there are dots in the path
if strings . Contains ( r . URL . Path , ".." ) {
http . Error ( w , "Your directory traversal technique has been defeated!" , http . StatusForbidden )
return
}
// Resolve URI path to actual filesystem path
path := r . URL . Path
2019-04-06 18:01:42 +00:00
if strings . HasPrefix ( path , "/~" ) {
bits := strings . Split ( r . URL . Path , "/" )
username := bits [ 1 ] [ 1 : ]
//strings.Replace(bits[1], "~", "", 1)
new_prefix := filepath . Join ( "home" , username , config . HomeDocBase )
path = strings . Replace ( path , bits [ 1 ] , new_prefix , 1 )
2019-03-03 17:43:29 +00:00
} else {
path = filepath . Join ( config . DocBase , r . URL . Path )
}
2019-04-06 18:01:42 +00:00
//fmt.Println(path)
2019-03-03 17:43:29 +00:00
// Fail if file does not exist
info , err := os . Stat ( path )
if os . IsNotExist ( err ) {
http . Error ( w , "Not found." , http . StatusNotFound )
return
}
// Fail if file is not world readable
if uint64 ( info . Mode ( ) . Perm ( ) ) & 0444 != 0444 {
http . Error ( w , "File not world-readable." , http . StatusForbidden )
return
}
// Get MIME type
ext := filepath . Ext ( path )
mime := mime . TypeByExtension ( ext )
// Fail if MIME type is forbidden
_ , ok := config . BadMimesMap [ mime ]
if ok {
http . Error ( w , "Forbidden MIME type: " + mime , http . StatusForbidden )
return
}
// Fail if file is too large
if ! strings . HasPrefix ( mime , "text/plain" ) && ( ( strings . HasPrefix ( mime , "text/" ) && info . Size ( ) > 1024 * 1024 ) ||
info . Size ( ) > 32 * 1024 ) {
http . Error ( w , "File too big: text/plain files have no limit, other text/* files may be up to 1 MiB, non-text files may be up to 32 KiB." , http . StatusForbidden )
return
}
// Fail if a HTML file doesn't measure up
if strings . HasPrefix ( mime , "text/html" ) {
reader , err := os . Open ( path )
if err != nil {
http . Error ( w , "Some kind of file error!" , http . StatusInternalServerError )
return
}
doc , err := html . Parse ( reader )
if err != nil {
http . Error ( w , "Some kind of HTML parsing error!" , http . StatusInternalServerError )
return
}
var imgcount int
ok , msg := ValidateHtml ( config , doc , 0 , & imgcount )
if ok == false {
http . Error ( w , "HTML document contains forbidden tag: " + msg , http . StatusForbidden )
return
}
}
// At this point everything is fine and we'll actually try to serve
// the file.
w . Header ( ) . Set ( "Content-Type" , mime )
http . ServeFile ( w , r , path )
}
}
func redirectTLS ( w http . ResponseWriter , r * http . Request ) {
http . Redirect ( w , r , "https://" + r . Host + r . RequestURI , http . StatusMovedPermanently )
}