Changeset 64 in code


Ignore:
Timestamp:
Dec 21, 2016, 5:10:56 PM (8 years ago)
Author:
alex
Message:

[mod] allow different content types according to two while lists.
The first white list doesn't modified the Content-Disposition header.
The second white list forces "attachment"

File:
1 edited

Legend:

Unmodified
Added
Removed
  • trunk/morty.go

    r63 r64  
    1111        "io"
    1212        "log"
     13        "mime"
    1314        "net/url"
     15        "path/filepath"
    1416        "regexp"
    1517        "strings"
     
    3739var CSS_URL_REGEXP *regexp.Regexp = regexp.MustCompile("url\\((['\"]?)[ \\t\\f]*([\u0009\u0021\u0023-\u0026\u0028\u002a-\u007E]+)(['\"]?)\\)?")
    3840
    39 // https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/MIME_types/Complete_list_of_MIME_types
    40 // https://www.w3.org/TR/2009/WD-MathML3-20090604/mathml.pdf
    41 // http://planetsvg.com/tools/mime.php
    42 var FORBIDDEN_CONTENTTYPE_FILTER contenttype.Filter = contenttype.NewFilterOr([]contenttype.Filter{
    43         // javascript
    44         contenttype.NewFilterContains("javascript"),
    45         contenttype.NewFilterContains("ecmascript"),
    46         contenttype.NewFilterEquals("application", "js", "*"),
    47         // no xml (can contain xhtml or css)
    48         contenttype.NewFilterEquals("text", "xml", "*"),
    49         contenttype.NewFilterEquals("text", "xml-external-parsed-entity", "*"),
    50         contenttype.NewFilterEquals("application", "xml", "*"),
    51         contenttype.NewFilterEquals("application", "xml-external-parsed-entity", "*"),
    52         contenttype.NewFilterEquals("application", "xslt", "xml"),
    53         // no mathml
    54         contenttype.NewFilterEquals("application", "mathml", "xml"),
    55         contenttype.NewFilterEquals("application", "mathml-presentation", "xml"),
    56         contenttype.NewFilterEquals("application", "mathml-content", "xml"),
    57         // no svg
    58         contenttype.NewFilterEquals("image", "svg", "xml"),
    59         contenttype.NewFilterEquals("image", "svg-xml", "*"),
    60         // no cache
    61         contenttype.NewFilterEquals("text", "cache-manifest", "*"),
    62         // no multipart
    63         contenttype.NewFilterEquals("multipart", "*", "*"),
    64         // no xul
    65         contenttype.NewFilterEquals("application", "vnd.mozilla.xul", "xml"),
    66         // no htc
    67         contenttype.NewFilterEquals("text", "x-component", "*"),
    68         // no flash
    69         contenttype.NewFilterEquals("application", "x-shockwave-flash", "*"),
    70         contenttype.NewFilterEquals("video", "x-flv", ""),
    71         contenttype.NewFilterEquals("video", "vnd.sealed-swf", ""),
    72         // no know format to have issues
    73         contenttype.NewFilterEquals("image", "wmf", "*"),
    74         contenttype.NewFilterEquals("image", "emf", "*"),
    75         // some of the microsoft and IE mime types
    76         contenttype.NewFilterEquals("text", "vbs", "*"),
    77         contenttype.NewFilterEquals("text", "vbscript", "*"),
    78         contenttype.NewFilterEquals("text", "scriptlet", "*"),
    79         contenttype.NewFilterEquals("application", "x-vbs", "*"),
    80         contenttype.NewFilterEquals("application", "olescript", "*"),
    81         contenttype.NewFilterEquals("application", "x-msmetafile", "*"),
    82         // no css (sometime, rendering depend on the browser)
    83         contenttype.NewFilterEquals("application", "x-pointplus", "*"),
     41var ALLOWED_CONTENTTYPE_FILTER contenttype.Filter = contenttype.NewFilterOr([]contenttype.Filter{
     42        // html
     43        contenttype.NewFilterEquals("text", "html", ""),
     44        contenttype.NewFilterEquals("application", "xhtml", "xml"),
     45        // css
     46        contenttype.NewFilterEquals("text", "css", ""),
     47        // images
     48        contenttype.NewFilterEquals("image", "gif", ""),
     49        contenttype.NewFilterEquals("image", "png", ""),
     50        contenttype.NewFilterEquals("image", "jpeg", ""),
     51        contenttype.NewFilterEquals("image", "pjpeg", ""),
     52        contenttype.NewFilterEquals("image", "webp", ""),
     53        contenttype.NewFilterEquals("image", "tiff", ""),
     54        contenttype.NewFilterEquals("image", "vnd.microsoft.icon", ""),
     55        contenttype.NewFilterEquals("image", "bmp", ""),
     56        contenttype.NewFilterEquals("image", "x-ms-bmp", ""),
     57        // fonts
     58        contenttype.NewFilterEquals("application", "font-otf", ""),
     59        contenttype.NewFilterEquals("application", "font-ttf", ""),
     60        contenttype.NewFilterEquals("application", "font-woff", ""),
     61        contenttype.NewFilterEquals("application", "vnd.ms-fontobject", ""),
     62})
     63
     64var ALLOWED_CONTENTTYPE_ATTACHMENT_FILTER contenttype.Filter = contenttype.NewFilterOr([]contenttype.Filter{
     65        // texts
     66        contenttype.NewFilterEquals("text", "csv", ""),
     67        contenttype.NewFilterEquals("text", "tab-separated-value", ""),
     68        contenttype.NewFilterEquals("text", "plain", ""),
     69        // API
     70        contenttype.NewFilterEquals("application", "json", ""),
     71        // Documents
     72        contenttype.NewFilterEquals("application", "x-latex", ""),
     73        contenttype.NewFilterEquals("application", "pdf", ""),
     74        contenttype.NewFilterEquals("application", "vnd.oasis.opendocument.text", ""),
     75        contenttype.NewFilterEquals("application", "vnd.oasis.opendocument.spreadsheet", ""),
     76        contenttype.NewFilterEquals("application", "vnd.oasis.opendocument.presentation", ""),
     77        contenttype.NewFilterEquals("application", "vnd.oasis.opendocument.graphics", ""),
     78        // Compressed archives
     79        contenttype.NewFilterEquals("application", "zip", ""),
     80        contenttype.NewFilterEquals("application", "gzip", ""),
     81        contenttype.NewFilterEquals("application", "x-compressed", ""),
     82        contenttype.NewFilterEquals("application", "x-gtar", ""),
     83        contenttype.NewFilterEquals("application", "x-compress", ""),
     84        // Generic binary
     85        contenttype.NewFilterEquals("application", "octet-stream", ""),
    8486})
    8587
     
    323325        }
    324326
    325         // deny access to forbidden content type
    326         if FORBIDDEN_CONTENTTYPE_FILTER(contentType) {
    327                 // HTTP status code 403 : Forbidden
    328                 p.serveMainPage(ctx, 403, errors.New("forbidden content type"))
    329                 return
     327        // content-disposition
     328        contentDispositionBytes := ctx.Request.Header.Peek("Content-Disposition")
     329
     330        // check content type
     331        if !ALLOWED_CONTENTTYPE_FILTER(contentType) {
     332                // it is not a usual content type
     333                if ALLOWED_CONTENTTYPE_ATTACHMENT_FILTER(contentType) {
     334                        // force attachment for allowed content type
     335                        contentDispositionBytes = contentDispositionForceAttachment(contentDispositionBytes, parsedURI)
     336                } else {
     337                        // deny access to forbidden content type
     338                        // HTTP status code 403 : Forbidden
     339                        p.serveMainPage(ctx, 403, errors.New("forbidden content type"))
     340                        return
     341                }
    330342        }
    331343
     
    364376        ctx.SetContentType(contentType.String())
    365377
     378        // output according to MIME type
    366379        switch {
    367380        case contentType.SubType == "css" && contentType.Suffix == "":
     
    370383                sanitizeHTML(&RequestConfig{Key: p.Key, BaseURL: parsedURI}, ctx, responseBody)
    371384        default:
    372                 if ctx.Request.Header.Peek("Content-Disposition") != nil {
    373                         ctx.Response.Header.AddBytesV("Content-Disposition", ctx.Request.Header.Peek("Content-Disposition"))
     385                if contentDispositionBytes != nil {
     386                        ctx.Response.Header.AddBytesV("Content-Disposition", contentDispositionBytes)
    374387                }
    375388                ctx.Write(responseBody)
    376389        }
     390}
     391
     392// force content-disposition to attachment
     393func contentDispositionForceAttachment(contentDispositionBytes []byte, url *url.URL) []byte {
     394        var contentDispositionParams map[string]string
     395
     396        if contentDispositionBytes != nil {
     397                var err error
     398                _, contentDispositionParams, err = mime.ParseMediaType(string(contentDispositionBytes))
     399                if err != nil {
     400                        contentDispositionParams = make(map[string]string)
     401                }
     402        } else {
     403                contentDispositionParams = make(map[string]string)
     404        }
     405
     406        _, fileNameDefined := contentDispositionParams["filename"]
     407        if !fileNameDefined {
     408                // TODO : sanitize filename
     409                contentDispositionParams["fileName"] = filepath.Base(url.Path)
     410        }
     411
     412        return []byte(mime.FormatMediaType("attachment", contentDispositionParams))
    377413}
    378414
Note: See TracChangeset for help on using the changeset viewer.