github.com/aarzilli/tools@v0.0.0-20151123112009-0d27094f75e0/net/http/fileserver/1_fileserver.go (about)

     1  // Package fileserver replaces http.Fileserver
     2  package fileserver
     3  
     4  import (
     5  	"bytes"
     6  	"encoding/json"
     7  	"fmt"
     8  	"io"
     9  	"io/ioutil"
    10  	"mime"
    11  	"net/http"
    12  	"net/url"
    13  	"path"
    14  	"strings"
    15  	"time"
    16  
    17  	"github.com/golang/snappy"
    18  	"github.com/pbberlin/tools/net/http/htmlfrag"
    19  	"github.com/pbberlin/tools/net/http/loghttp"
    20  	"github.com/pbberlin/tools/net/http/tplx"
    21  	"github.com/pbberlin/tools/os/fsi"
    22  	"github.com/pbberlin/tools/os/fsi/common"
    23  	"github.com/pbberlin/tools/stringspb"
    24  )
    25  
    26  var wpf = func(w io.Writer, format string, a ...interface{}) (int, error) {
    27  	fmt.Fprintf(w, format, a...)
    28  	fmt.Fprintf(w, "\n")
    29  	return 0, nil
    30  }
    31  
    32  var spf = fmt.Sprintf
    33  
    34  type Options struct {
    35  	FS           fsi.FileSystem
    36  	Prefix       string
    37  	Replacements map[string][]byte
    38  	Cutout       bool
    39  }
    40  
    41  // We cannot use http.FileServer(http.Dir("./css/")
    42  // to dispatch our dsfs files.
    43  // We need the appengine context to initialize dsfs.
    44  // Thus we have to re-implement a serveFile method:
    45  func FsiFileServer(w http.ResponseWriter, r *http.Request, opt Options) {
    46  
    47  	r.Header.Set("X-Custom-Header-Counter", "nocounter")
    48  
    49  	lg, b1 := loghttp.BuffLoggerUniversal(w, r)
    50  
    51  	fclose := func() {
    52  		// Only upon error.
    53  		// If everything is fine, we reset fclose at the end.
    54  		w.Write(b1.Bytes())
    55  	}
    56  	defer fclose()
    57  
    58  	wpf(b1, tplx.ExecTplHelper(tplx.Head, map[string]interface{}{"HtmlTitle": "Half-Static-File-Server"}))
    59  	wpf(b1, "<pre>")
    60  
    61  	err := r.ParseForm()
    62  	if err != nil {
    63  		wpf(b1, "err parsing request (ParseForm)%v", err)
    64  	}
    65  
    66  	p := r.URL.Path
    67  
    68  	if strings.HasPrefix(p, opt.Prefix) {
    69  		// p = p[len(prefix):]
    70  		p = strings.TrimPrefix(p, opt.Prefix)
    71  	} else {
    72  		wpf(b1, "route must start with prefix %v - but is %v", opt.Prefix, p)
    73  	}
    74  
    75  	if strings.HasPrefix(p, "/") {
    76  		p = p[1:]
    77  	}
    78  	wpf(b1, "effective path = %q", p)
    79  
    80  	// fullP := path.Join(docRootDataStore, p)
    81  	fullP := p
    82  
    83  	f, err := opt.FS.Open(fullP)
    84  	if err != nil {
    85  		wpf(b1, "err opening file %v - %v", fullP, err)
    86  		return
    87  	}
    88  	defer f.Close()
    89  
    90  	inf, err := f.Stat()
    91  	if err != nil {
    92  		wpf(b1, "err opening fileinfo %v - %v", fullP, err)
    93  		return
    94  	}
    95  
    96  	if inf.IsDir() {
    97  
    98  		wpf(b1, "%v is a directory - trying index.html...", fullP)
    99  
   100  		fullP += "/index.html"
   101  
   102  		fIndex, err := opt.FS.Open(fullP)
   103  		if err == nil {
   104  			defer fIndex.Close()
   105  			inf, err = fIndex.Stat()
   106  			if err != nil {
   107  				wpf(b1, "err opening index fileinfo %v - %v", fullP, err)
   108  				return
   109  			}
   110  
   111  			f = fIndex
   112  		} else {
   113  
   114  			wpf(b1, "err opening index file %v - %v", fullP, err)
   115  
   116  			if r.FormValue("fmt") == "html" {
   117  				dirListHtml(w, r, f)
   118  			} else {
   119  				dirListJson(w, r, f)
   120  			}
   121  
   122  			b1 = new(bytes.Buffer) // success => reset the message log => dumps an empty buffer
   123  			return
   124  		}
   125  
   126  	}
   127  
   128  	wpf(b1, "opened file %v - %v -  %v", f.Name(), inf.Size(), err)
   129  
   130  	bts1, err := ioutil.ReadAll(f)
   131  	if err != nil {
   132  		wpf(b1, "err with ReadAll %v - %v", fullP, err)
   133  		return
   134  	}
   135  
   136  	ext := path.Ext(fullP)
   137  	ext = strings.ToLower(ext)
   138  	if ext == ".snappy" {
   139  		btsDec, err := snappy.Decode(nil, bts1)
   140  		if err != nil {
   141  			wpf(b1, "err decoding snappy: "+err.Error())
   142  		} else {
   143  			lg("decoded from %vkB to %vkB", len(bts1)/1024, len(btsDec)/1024)
   144  			bts1 = btsDec
   145  		}
   146  		fullP = strings.TrimSuffix(fullP, path.Ext(fullP))
   147  		ext = path.Ext(fullP)
   148  		ext = strings.ToLower(ext)
   149  		lg("new extension is %v", ext)
   150  	}
   151  
   152  	tp := mime.TypeByExtension(ext)
   153  
   154  	w.Header().Set("Content-Type", tp)
   155  
   156  	//
   157  	// caching
   158  	// either explicitly discourage
   159  	// or     explicitly  encourage
   160  	if false ||
   161  		ext == ".css" || ext == ".js" ||
   162  		ext == "css" || ext == "js" ||
   163  		ext == ".jpg" || ext == ".gif" ||
   164  		ext == "jpg" || ext == "gif" ||
   165  		false {
   166  
   167  		if strings.Contains(fullP, "tamper-monkey") {
   168  			htmlfrag.SetNocacheHeaders(w)
   169  		} else {
   170  			htmlfrag.CacheHeaders(w)
   171  		}
   172  	} else {
   173  		htmlfrag.SetNocacheHeaders(w)
   174  	}
   175  
   176  	for k, v := range opt.Replacements {
   177  		bts1 = bytes.Replace(bts1, []byte(k), v, -1)
   178  	}
   179  	if opt.Cutout {
   180  		sep := []byte("<span id='CUTOUT'></span>")
   181  		spl := bytes.Split(bts1, sep)
   182  		if len(spl) > 1 {
   183  			bts2 := []byte{}
   184  			for i, part := range spl {
   185  				if i%2 == 0 {
   186  					bts2 = append(bts2, part...)
   187  				}
   188  			}
   189  			bts1 = bts2
   190  		}
   191  	}
   192  
   193  	w.Write(bts1)
   194  
   195  	b1 = new(bytes.Buffer) // success => reset the message log => dumps an empty buffer
   196  
   197  }
   198  
   199  // inspired by https://golang.org/src/net/http/fs.go
   200  //
   201  // name may contain '?' or '#', which must be escaped to remain
   202  // part of the URL path, and not indicate the start of a query
   203  // string or fragment.
   204  var htmlReplacer = strings.NewReplacer(
   205  	"&", "&amp;",
   206  	"<", "&lt;",
   207  	">", "&gt;",
   208  
   209  	`"`, "&#34;",
   210  
   211  	"'", "&#39;",
   212  )
   213  
   214  func dirListJson(w http.ResponseWriter, r *http.Request, f fsi.File) {
   215  
   216  	r.Header.Set("Content-Type", "application/json")
   217  
   218  	mp := []map[string]string{}
   219  
   220  	for {
   221  		dirs, err := f.Readdir(100)
   222  		if err != nil || len(dirs) == 0 {
   223  			break
   224  		}
   225  		for _, d := range dirs {
   226  			name := d.Name()
   227  			if d.IsDir() {
   228  				name = common.Directorify(name)
   229  			}
   230  			name = htmlReplacer.Replace(name)
   231  
   232  			url := url.URL{Path: name}
   233  
   234  			mpl := map[string]string{
   235  				"path": url.String(),
   236  				"mod":  d.ModTime().Format(time.RFC1123Z),
   237  			}
   238  
   239  			mp = append(mp, mpl)
   240  		}
   241  	}
   242  
   243  	bdirListHtml, err := json.MarshalIndent(mp, "", "\t")
   244  	if err != nil {
   245  		wpf(w, "marshalling to []byte failed - mp was %v", mp)
   246  		return
   247  	}
   248  	w.Write(bdirListHtml)
   249  
   250  }
   251  
   252  func dirListHtml(w http.ResponseWriter, r *http.Request, f fsi.File) {
   253  
   254  	w.Header().Set("Content-Type", "text/html; charset=utf-8")
   255  
   256  	for {
   257  		dirs, err := f.Readdir(100)
   258  		if err != nil || len(dirs) == 0 {
   259  			break
   260  		}
   261  		for _, d := range dirs {
   262  			name := d.Name()
   263  
   264  			suffix := ""
   265  			if d.IsDir() {
   266  				suffix = "/"
   267  			}
   268  
   269  			linktitle := htmlReplacer.Replace(name)
   270  			linktitle = stringspb.Ellipsoider(linktitle, 40)
   271  			if d.IsDir() {
   272  				linktitle = common.Directorify(linktitle)
   273  			}
   274  
   275  			surl := path.Join(r.URL.Path, name) + suffix + "?fmt=html"
   276  
   277  			oneLine := spf("<a  style='display:inline-block;min-width:600px;' href=\"%s\">%s</a>", surl, linktitle)
   278  			// wpf(w, " %v", d.ModTime().Format("2006-01-02 15:04:05 MST"))
   279  			oneLine += spf(" %v<br>", d.ModTime().Format(time.RFC1123Z))
   280  			wpf(w, oneLine)
   281  		}
   282  	}
   283  
   284  }