@@ -1,12 +1,13 @@
/* Tiny web server in Golang for sharing a folder
Copyright (c) 2010 Alexis ROBERT <[email protected] >
Copyright (c) 2010-2014 Alexis ROBERT <[email protected] >
Contains some code from Golang's http.ServeFile method, and
uses lighttpd's directory listing HTML template. */
package main
import "http"
import "net/http"
import "net/url"
import "io"
import "os"
import "mime"
@@ -15,7 +16,7 @@ import "fmt"
import "flag"
import "strings"
import "strconv"
import "template"
import "text/ template"
import "container/list"
import "compress/gzip"
import "compress/zlib"
@@ -24,7 +25,7 @@ import "time"
var root_folder * string // TODO: Find a way to be cleaner !
var uses_gzip * bool
const serverUA = "Alexis/0.1 "
const serverUA = "Alexis/0.2 "
const fs_maxbufsize = 4096 // 4096 bits = default page size on OSX
/* Go is the first programming language with a templating engine embeddeed
@@ -82,7 +83,7 @@ func handleDirectory(f *os.File, w http.ResponseWriter, req *http.Request) {
// First, check if there is any index in this folder.
for _ , val := range names {
if val .Name == "index.html" {
if val .Name () == "index.html" {
serveFile (path .Join (f .Name (), "index.html" ), w , req )
return
}
@@ -93,33 +94,35 @@ func handleDirectory(f *os.File, w http.ResponseWriter, req *http.Request) {
children_files_tmp := list .New ()
for _ , val := range names {
if val .Name [0 ] == '.' {
if val .Name () [0 ] == '.' {
continue
} // Remove hidden files from listing
if val .IsDirectory () {
children_dir_tmp .PushBack (val .Name )
if val .IsDir () {
children_dir_tmp .PushBack (val .Name () )
} else {
children_files_tmp .PushBack (val .Name )
children_files_tmp .PushBack (val .Name () )
}
}
// And transfer the content to the final array structure
children_dir := copyToArray (children_dir_tmp )
children_files := copyToArray (children_files_tmp )
tpl := template .New (nil )
tpl .SetDelims ("[" , "]" )
err := tpl .Parse (dirlisting_tpl )
tpl , err := template .New ("tpl" ).Parse (dirlisting_tpl )
if err != nil {
http .Error (w , "500 Internal Error : Error while generating directory listing." , 500 )
fmt .Println (err )
return
}
data := dirlisting {Name : req .URL .Path , ServerUA : serverUA ,
Children_dir : children_dir , Children_files : children_files }
tpl .Execute (w , data )
err = tpl .Execute (w , data )
if err != nil {
fmt .Println (err )
}
}
func serveFile (filepath string , w http.ResponseWriter , req * http.Request ) {
@@ -139,25 +142,25 @@ func serveFile(filepath string, w http.ResponseWriter, req *http.Request) {
return
}
if statinfo .IsDirectory () { // If it's a directory, open it !
if statinfo .IsDir () { // If it's a directory, open it !
handleDirectory (f , w , req )
return
}
if statinfo .IsSocket () { // If it's a socket, forbid it !
if ( statinfo .Mode () &^ 07777 ) == os . ModeSocket { // If it's a socket, forbid it !
http .Error (w , "403 Forbidden : you can't access this resource." , 403 )
return
}
// Manages If-Modified-Since and add Last-Modified (taken from Golang code)
if t , _ := time .Parse (http .TimeFormat , req .Header .Get ("If-Modified-Since" )); t != nil && statinfo .Mtime_ns / 1e9 <= t .Seconds () {
if t , err := time .Parse (http .TimeFormat , req .Header .Get ("If-Modified-Since" )); err == nil && statinfo .ModTime (). Unix () <= t .Unix () {
w .WriteHeader (http .StatusNotModified )
return
}
w .Header ().Set ("Last-Modified" , time . SecondsToUTC ( statinfo .Mtime_ns / 1e9 ).Format (http .TimeFormat ))
w .Header ().Set ("Last-Modified" , statinfo .ModTime ( ).Format (http .TimeFormat ))
// Content-Type handling
query , err := http .ParseQuery (req .URL .RawQuery )
query , err := url .ParseQuery (req .URL .RawQuery )
if err == nil && len (query ["dl" ]) > 0 { // The user explicitedly wanted to download the file (Dropbox style!)
w .Header ().Set ("Content-Type" , "application/octet-stream" )
@@ -170,59 +173,68 @@ func serveFile(filepath string, w http.ResponseWriter, req *http.Request) {
}
}
// Add Content-Length
w .Header ().Set ("Content-Length" , strconv .Itoa64 (statinfo .Size ))
// Manage Content-Range (TODO: Manage end byte and multiple Content-Range)
if req .Header .Get ("Range" ) != "" {
start_byte := parseRange (req .Header .Get ("Range" ))
if start_byte < statinfo .Size {
if start_byte < statinfo .Size () {
f .Seek (start_byte , 0 )
} else {
start_byte = 0
}
w .Header ().Set ("Content-Range" ,
fmt .Sprintf ("bytes %d-%d/%d" , start_byte , statinfo .Size - 1 , statinfo .Size ))
fmt .Sprintf ("bytes %d-%d/%d" , start_byte , statinfo .Size () - 1 , statinfo .Size () ))
}
// Manage gzip/zlib compression
output_writer := w .(io.Writer )
is_compressed_reply := false
if (* uses_gzip ) == true && req .Header .Get ("Accept-Encoding" ) != "" {
encodings := parseCSV (req .Header .Get ("Accept-Encoding" ))
for _ , val := range ( encodings ) {
for _ , val := range encodings {
if val == "gzip" {
w .Header ().Set ("Accept-Encoding" , "gzip" )
output_writer ,_ = gzip .NewWriterLevel (w , gzip .BestSpeed )
w .Header ().Set ("Content-Encoding" , "gzip" )
output_writer = gzip .NewWriter (w )
is_compressed_reply = true
break
} else if val == "deflate" {
w .Header ().Set ("Accept-Encoding" , "deflate" )
output_writer ,_ = zlib .NewWriterLevel (w , zlib .BestSpeed )
w .Header ().Set ("Content-Encoding" , "deflate" )
output_writer = zlib .NewWriter (w )
is_compressed_reply = true
break
}
}
}
if ! is_compressed_reply {
// Add Content-Length
w .Header ().Set ("Content-Length" , strconv .FormatInt (statinfo .Size (), 10 ))
}
// Stream data out !
buf := make ([]byte , min (fs_maxbufsize , statinfo .Size ))
buf := make ([]byte , min (fs_maxbufsize , statinfo .Size () ))
n := 0
for err == nil {
n , err = f .Read (buf )
output_writer .Write (buf [0 :n ])
}
// Closes current compressors
switch t := output_writer .(type ) {
case * gzip.Compressor : output_writer .(* gzip.Compressor ).Close ()
case io.WriteCloser : output_writer .(io.WriteCloser ).Close ()
switch output_writer .(type ) {
case * gzip.Writer :
output_writer .(* gzip.Writer ).Close ()
case * zlib.Writer :
output_writer .(* zlib.Writer ).Close ()
}
//w.Flush()
f .Close ()
}
@@ -234,18 +246,18 @@ func handleFile(w http.ResponseWriter, req *http.Request) {
fmt .Printf ("\" %s %s %s\" \" %s\" \" %s\" \n " ,
req .Method ,
req .URL .Raw ,
req .URL .String () ,
req .Proto ,
req .Referer ,
req .UserAgent ) // TODO: Improve this crappy logging
req .Referer () ,
req .UserAgent () ) // TODO: Improve this crappy logging
}
func parseCSV (data string ) []string {
splitted := strings .Split (data , "," , - 1 )
splitted := strings .SplitN (data , "," , - 1 )
data_tmp := make ([]string , len (splitted ))
for i , val := range ( splitted ) {
for i , val := range splitted {
data_tmp [i ] = strings .TrimSpace (val )
}
@@ -284,9 +296,9 @@ func parseRange(data string) int64 {
const dirlisting_tpl = `<?xml version="1.0" encoding="iso-8859-1"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">
<!-- Modified from lighttpd directory listing --! >
<!-- Modified from lighttpd directory listing -->
<head>
<title>Index of [ Name] </title>
<title>Index of {{. Name}} </title>
<style type="text/css">
a, a:active {text-decoration: none; color: blue;}
a:visited {color: #48468F;}
@@ -303,21 +315,21 @@ div.foot { font: 90% monospace; color: #787878; padding-top: 4px;}
</style>
</head>
<body>
<h2>Index of [ Name] </h2>
<h2>Index of {{. Name}} </h2>
<div class="list">
<table summary="Directory Listing" cellpadding="0" cellspacing="0">
<thead><tr><th class="n">Name</th><th class="t">Type</th><th class="dl">Options</th></tr></thead>
<tbody>
<tr><td class="n"><a href="../">Parent Directory</a>/</td><td class="t">Directory</td><td class="dl"></td></tr>
[.repeated section Children_dir]
<tr><td class="n"><a href="[@] /">[@] /</a></td><td class="t">Directory</td><td class="dl"></td></tr>
[. end]
[.repeated section Children_files]
<tr><td class="n"><a href="[@]">[@] </a></td><td class="t"> </td><td class="dl"><a href="[@] ?dl">Download</a></td></tr>
[. end]
{{range . Children_dir}}
<tr><td class="n"><a href="{{.}} /">{{.}} /</a></td><td class="t">Directory</td><td class="dl"></td></tr>
{{ end}}
{{range . Children_files}}
<tr><td class="n"><a href="{{.}}">{{.}} </a></td><td class="t"> </td><td class="dl"><a href="{{.}} ?dl">Download</a></td></tr>
{{ end}}
</tbody>
</table>
</div>
<div class="foot">[ ServerUA] </div>
<div class="foot">{{. ServerUA}} </div>
</body>
</html>`
</html>`