Index: trunk/morty.go
===================================================================
--- trunk/morty.go	(revision 66)
+++ trunk/morty.go	(revision 67)
@@ -5,4 +5,5 @@
 	"crypto/hmac"
 	"crypto/sha256"
+	"encoding/base64"
 	"encoding/hex"
 	"errors"
@@ -11,7 +12,5 @@
 	"io"
 	"log"
-	"mime"
 	"net/url"
-	"path/filepath"
 	"regexp"
 	"strings"
@@ -23,6 +22,4 @@
 	"golang.org/x/net/html/charset"
 	"golang.org/x/text/encoding"
-
-	"github.com/asciimoo/morty/contenttype"
 )
 
@@ -38,55 +35,4 @@
 
 var CSS_URL_REGEXP *regexp.Regexp = regexp.MustCompile("url\\((['\"]?)[ \\t\\f]*([\u0009\u0021\u0023-\u0026\u0028\u002a-\u007E]+)(['\"]?)\\)?")
-
-var ALLOWED_CONTENTTYPE_FILTER contenttype.Filter = contenttype.NewFilterOr([]contenttype.Filter{
-	// html
-	contenttype.NewFilterEquals("text", "html", ""),
-	contenttype.NewFilterEquals("application", "xhtml", "xml"),
-	// css
-	contenttype.NewFilterEquals("text", "css", ""),
-	// images
-	contenttype.NewFilterEquals("image", "gif", ""),
-	contenttype.NewFilterEquals("image", "png", ""),
-	contenttype.NewFilterEquals("image", "jpeg", ""),
-	contenttype.NewFilterEquals("image", "pjpeg", ""),
-	contenttype.NewFilterEquals("image", "webp", ""),
-	contenttype.NewFilterEquals("image", "tiff", ""),
-	contenttype.NewFilterEquals("image", "vnd.microsoft.icon", ""),
-	contenttype.NewFilterEquals("image", "bmp", ""),
-	contenttype.NewFilterEquals("image", "x-ms-bmp", ""),
-	// fonts
-	contenttype.NewFilterEquals("application", "font-otf", ""),
-	contenttype.NewFilterEquals("application", "font-ttf", ""),
-	contenttype.NewFilterEquals("application", "font-woff", ""),
-	contenttype.NewFilterEquals("application", "vnd.ms-fontobject", ""),
-})
-
-var ALLOWED_CONTENTTYPE_ATTACHMENT_FILTER contenttype.Filter = contenttype.NewFilterOr([]contenttype.Filter{
-	// texts
-	contenttype.NewFilterEquals("text", "csv", ""),
-	contenttype.NewFilterEquals("text", "tab-separated-value", ""),
-	contenttype.NewFilterEquals("text", "plain", ""),
-	// API
-	contenttype.NewFilterEquals("application", "json", ""),
-	// Documents
-	contenttype.NewFilterEquals("application", "x-latex", ""),
-	contenttype.NewFilterEquals("application", "pdf", ""),
-	contenttype.NewFilterEquals("application", "vnd.oasis.opendocument.text", ""),
-	contenttype.NewFilterEquals("application", "vnd.oasis.opendocument.spreadsheet", ""),
-	contenttype.NewFilterEquals("application", "vnd.oasis.opendocument.presentation", ""),
-	contenttype.NewFilterEquals("application", "vnd.oasis.opendocument.graphics", ""),
-	// Compressed archives
-	contenttype.NewFilterEquals("application", "zip", ""),
-	contenttype.NewFilterEquals("application", "gzip", ""),
-	contenttype.NewFilterEquals("application", "x-compressed", ""),
-	contenttype.NewFilterEquals("application", "x-gtar", ""),
-	contenttype.NewFilterEquals("application", "x-compress", ""),
-	// Generic binary
-	contenttype.NewFilterEquals("application", "octet-stream", ""),
-})
-
-var ALLOWED_CONTENTTYPE_PARAMETERS map[string]bool = map[string]bool{
-	"charset": true,
-}
 
 var UNSAFE_ELEMENTS [][]byte = [][]byte{
@@ -201,12 +147,12 @@
 <div id="mortyheader">
   <input type="checkbox" id="mortytoggle" autocomplete="off" />
-  <div><p>This is a proxified and sanitized view of the page,<br />visit <a href="%s" rel="noreferrer">original site</a>.</p><p><label for="mortytoggle">hide</label></p></div>
+  <p>This is a proxified and sanitized view of the page,<br />visit <a href="%s" rel="noreferrer">original site</a>.</p><p><label for="mortytoggle">hide</label></p>
 </div>
 <style>
-#mortyheader { position: fixed; padding: 12px 12px 12px 0; margin: 0; box-sizing: content-box; top: 15%%; left: 0; max-width: 140px; color: #444; overflow: hidden; z-index: 110000; font-size: 12px; line-height: normal; }
-#mortyheader a { color: #3498db; font-weight: bold; }
-#mortyheader p { padding: 0 0 0.7em 0; margin: 0; }
-#mortyheader > div { padding: 8px; font-size: 12px !important; font-family: sans !important; border-width: 4px 4px 4px 0; border-style: solid; border-color: #1abc9c; background: #FFF; line-height: 1em; }
-#mortyheader label { text-align: right; cursor: pointer; display: block; color: #444; padding: 0; margin: 0; }
+#mortyheader { position: fixed; margin: 0; box-sizing: border-box; -webkit-box-sizing: border-box; top: 15%%; left: 0; max-width: 140px; overflow: hidden; z-index: 2147483647 !important; font-size: 12px; line-height: normal; border-width: 4px 4px 4px 0; border-style: solid; border-color: #1abc9c; background: #FFF; padding: 12px 12px 8px 8px; color: #444; }
+#mortyheader * { box-sizing: content-box; margin: 0; border: none; padding: 0; overflow: hidden; z-index: 2147483647 !important; line-height: 1em; font-size: 12px !important; font-family: sans !important; font-weight: normal; text-align: left; text-decoration: none; }
+#mortyheader p { padding: 0 0 0.7em 0; display: block; }
+#mortyheader a { color: #3498db; font-weight: bold; display: inline; }
+#mortyheader label { text-align: right; cursor: pointer; display: block; color: #444; }
 input[type=checkbox]#mortytoggle { display: none; }
 input[type=checkbox]#mortytoggle:checked ~ div { display: none; }
@@ -216,5 +162,14 @@
 var HTML_HEAD_CONTENT_TYPE string = `<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
 <meta http-equiv="X-UA-Compatible" content="IE=edge">
+<meta name="referrer" content="no-referrer">
 `
+
+var FAVICON_BYTES []byte
+
+func init() {
+	FaviconBase64 := "iVBORw0KGgoAAAANSUhEUgAAABAAAAAQEAYAAABPYyMiAAAABmJLR0T///////8JWPfcAAAACXBIWXMAAABIAAAASABGyWs+AAAAF0lEQVRIx2NgGAWjYBSMglEwCkbBSAcACBAAAeaR9cIAAAAASUVORK5CYII"
+
+	FAVICON_BYTES, _ = base64.StdEncoding.DecodeString(FaviconBase64)
+}
 
 func (p *Proxy) RequestHandler(ctx *fasthttp.RequestCtx) {
@@ -307,7 +262,7 @@
 	}
 
-	contentTypeBytes := resp.Header.Peek("Content-Type")
-
-	if contentTypeBytes == nil {
+	contentType := resp.Header.Peek("Content-Type")
+
+	if contentType == nil {
 		// HTTP status code 503 : Service Unavailable
 		p.serveMainPage(ctx, 503, errors.New("invalid content type"))
@@ -315,43 +270,16 @@
 	}
 
-	contentTypeString := string(contentTypeBytes)
-
-	// decode Content-Type header
-	contentType, error := contenttype.ParseContentType(contentTypeString)
-	if error != nil {
-		// HTTP status code 503 : Service Unavailable
-		p.serveMainPage(ctx, 503, errors.New("invalid content type"))
-		return
-	}
-
-	// content-disposition
-	contentDispositionBytes := ctx.Request.Header.Peek("Content-Disposition")
-
-	// check content type
-	if !ALLOWED_CONTENTTYPE_FILTER(contentType) {
-		// it is not a usual content type
-		if ALLOWED_CONTENTTYPE_ATTACHMENT_FILTER(contentType) {
-			// force attachment for allowed content type
-			contentDispositionBytes = contentDispositionForceAttachment(contentDispositionBytes, parsedURI)
-		} else {
-			// deny access to forbidden content type
-			// HTTP status code 403 : Forbidden
-			p.serveMainPage(ctx, 403, errors.New("forbidden content type"))
-			return
-		}
-	}
-
-	// HACK : replace */xhtml by text/html
-	if contentType.SubType == "xhtml" {
-		contentType.TopLevelType = "text"
-		contentType.SubType = "html"
-		contentType.Suffix = ""
-	}
-
-	// conversion to UTF-8
+	if bytes.Contains(bytes.ToLower(contentType), []byte("javascript")) {
+		// HTTP status code 403 : Forbidden
+		p.serveMainPage(ctx, 403, errors.New("forbidden content type"))
+		return
+	}
+
+	contentInfo := bytes.SplitN(contentType, []byte(";"), 2)
+
 	var responseBody []byte
 
-	if contentType.TopLevelType == "text" {
-		e, ename, _ := charset.DetermineEncoding(resp.Body(), contentTypeString)
+	if len(contentInfo) == 2 && bytes.Contains(contentInfo[0], []byte("text")) {
+		e, ename, _ := charset.DetermineEncoding(resp.Body(), string(contentType))
 		if (e != encoding.Nop) && (!strings.EqualFold("utf-8", ename)) {
 			responseBody, err = e.NewDecoder().Bytes(resp.Body())
@@ -364,51 +292,25 @@
 			responseBody = resp.Body()
 		}
-		// update the charset or specify it
-		contentType.Parameters["charset"] = "UTF-8"
 	} else {
 		responseBody = resp.Body()
 	}
 
-	//
-	contentType.FilterParameters(ALLOWED_CONTENTTYPE_PARAMETERS)
-
-	// set the content type
-	ctx.SetContentType(contentType.String())
-
-	// output according to MIME type
+	if bytes.Contains(contentType, []byte("xhtml")) {
+		ctx.SetContentType("text/html; charset=UTF-8")
+	} else {
+		ctx.SetContentType(fmt.Sprintf("%s; charset=UTF-8", contentInfo[0]))
+	}
+
 	switch {
-	case contentType.SubType == "css" && contentType.Suffix == "":
+	case bytes.Contains(contentType, []byte("css")):
 		sanitizeCSS(&RequestConfig{Key: p.Key, BaseURL: parsedURI}, ctx, responseBody)
-	case contentType.SubType == "html" && contentType.Suffix == "":
+	case bytes.Contains(contentType, []byte("html")):
 		sanitizeHTML(&RequestConfig{Key: p.Key, BaseURL: parsedURI}, ctx, responseBody)
 	default:
-		if contentDispositionBytes != nil {
-			ctx.Response.Header.AddBytesV("Content-Disposition", contentDispositionBytes)
+		if ctx.Request.Header.Peek("Content-Disposition") != nil {
+			ctx.Response.Header.AddBytesV("Content-Disposition", ctx.Request.Header.Peek("Content-Disposition"))
 		}
 		ctx.Write(responseBody)
 	}
-}
-
-// force content-disposition to attachment
-func contentDispositionForceAttachment(contentDispositionBytes []byte, url *url.URL) []byte {
-	var contentDispositionParams map[string]string
-
-	if contentDispositionBytes != nil {
-		var err error
-		_, contentDispositionParams, err = mime.ParseMediaType(string(contentDispositionBytes))
-		if err != nil {
-			contentDispositionParams = make(map[string]string)
-		}
-	} else {
-		contentDispositionParams = make(map[string]string)
-	}
-
-	_, fileNameDefined := contentDispositionParams["filename"]
-	if !fileNameDefined {
-		// TODO : sanitize filename
-		contentDispositionParams["fileName"] = filepath.Base(url.Path)
-	}
-
-	return []byte(mime.FormatMediaType("attachment", contentDispositionParams))
 }
 
@@ -418,4 +320,11 @@
 		ctx.SetContentType("text/plain")
 		ctx.Write([]byte("User-Agent: *\nDisallow: /\n"))
+		return true
+	}
+
+	// server favicon.ico
+	if bytes.Equal(ctx.Path(), []byte("/favicon.ico")) {
+		ctx.SetContentType("image/png")
+		ctx.Write(FAVICON_BYTES)
 		return true
 	}
@@ -878,7 +787,8 @@
 
 func (p *Proxy) serveMainPage(ctx *fasthttp.RequestCtx, statusCode int, err error) {
-	ctx.SetContentType("text/html")
+	ctx.SetContentType("text/html; charset=UTF-8")
 	ctx.SetStatusCode(statusCode)
 	ctx.Write([]byte(`<!doctype html>
+<html>
 <head>
 <title>MortyProxy</title>
