Changeset 67 in code for trunk/morty.go
- Timestamp:
- Dec 23, 2016, 5:58:04 PM (8 years ago)
- File:
-
- 1 edited
Legend:
- Unmodified
- Added
- Removed
-
trunk/morty.go
r66 r67 5 5 "crypto/hmac" 6 6 "crypto/sha256" 7 "encoding/base64" 7 8 "encoding/hex" 8 9 "errors" … … 11 12 "io" 12 13 "log" 13 "mime"14 14 "net/url" 15 "path/filepath"16 15 "regexp" 17 16 "strings" … … 23 22 "golang.org/x/net/html/charset" 24 23 "golang.org/x/text/encoding" 25 26 "github.com/asciimoo/morty/contenttype"27 24 ) 28 25 … … 38 35 39 36 var CSS_URL_REGEXP *regexp.Regexp = regexp.MustCompile("url\\((['\"]?)[ \\t\\f]*([\u0009\u0021\u0023-\u0026\u0028\u002a-\u007E]+)(['\"]?)\\)?") 40 41 var ALLOWED_CONTENTTYPE_FILTER contenttype.Filter = contenttype.NewFilterOr([]contenttype.Filter{42 // html43 contenttype.NewFilterEquals("text", "html", ""),44 contenttype.NewFilterEquals("application", "xhtml", "xml"),45 // css46 contenttype.NewFilterEquals("text", "css", ""),47 // images48 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 // fonts58 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 64 var ALLOWED_CONTENTTYPE_ATTACHMENT_FILTER contenttype.Filter = contenttype.NewFilterOr([]contenttype.Filter{65 // texts66 contenttype.NewFilterEquals("text", "csv", ""),67 contenttype.NewFilterEquals("text", "tab-separated-value", ""),68 contenttype.NewFilterEquals("text", "plain", ""),69 // API70 contenttype.NewFilterEquals("application", "json", ""),71 // Documents72 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 archives79 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 binary85 contenttype.NewFilterEquals("application", "octet-stream", ""),86 })87 88 var ALLOWED_CONTENTTYPE_PARAMETERS map[string]bool = map[string]bool{89 "charset": true,90 }91 37 92 38 var UNSAFE_ELEMENTS [][]byte = [][]byte{ … … 201 147 <div id="mortyheader"> 202 148 <input type="checkbox" id="mortytoggle" autocomplete="off" /> 203 < 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>149 <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> 204 150 </div> 205 151 <style> 206 #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; }207 #mortyheader a { color: #3498db; font-weight: bold; }208 #mortyheader p { padding: 0 0 0.7em 0; margin: 0; }209 #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; }210 #mortyheader label { text-align: right; cursor: pointer; display: block; color: #444; padding: 0; margin: 0;}152 #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; } 153 #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; } 154 #mortyheader p { padding: 0 0 0.7em 0; display: block; } 155 #mortyheader a { color: #3498db; font-weight: bold; display: inline; } 156 #mortyheader label { text-align: right; cursor: pointer; display: block; color: #444; } 211 157 input[type=checkbox]#mortytoggle { display: none; } 212 158 input[type=checkbox]#mortytoggle:checked ~ div { display: none; } … … 216 162 var HTML_HEAD_CONTENT_TYPE string = `<meta http-equiv="Content-Type" content="text/html; charset=utf-8"> 217 163 <meta http-equiv="X-UA-Compatible" content="IE=edge"> 164 <meta name="referrer" content="no-referrer"> 218 165 ` 166 167 var FAVICON_BYTES []byte 168 169 func init() { 170 FaviconBase64 := "iVBORw0KGgoAAAANSUhEUgAAABAAAAAQEAYAAABPYyMiAAAABmJLR0T///////8JWPfcAAAACXBIWXMAAABIAAAASABGyWs+AAAAF0lEQVRIx2NgGAWjYBSMglEwCkbBSAcACBAAAeaR9cIAAAAASUVORK5CYII" 171 172 FAVICON_BYTES, _ = base64.StdEncoding.DecodeString(FaviconBase64) 173 } 219 174 220 175 func (p *Proxy) RequestHandler(ctx *fasthttp.RequestCtx) { … … 307 262 } 308 263 309 contentType Bytes:= resp.Header.Peek("Content-Type")310 311 if contentType Bytes== nil {264 contentType := resp.Header.Peek("Content-Type") 265 266 if contentType == nil { 312 267 // HTTP status code 503 : Service Unavailable 313 268 p.serveMainPage(ctx, 503, errors.New("invalid content type")) … … 315 270 } 316 271 317 contentTypeString := string(contentTypeBytes) 318 319 // decode Content-Type header 320 contentType, error := contenttype.ParseContentType(contentTypeString) 321 if error != nil { 322 // HTTP status code 503 : Service Unavailable 323 p.serveMainPage(ctx, 503, errors.New("invalid content type")) 324 return 325 } 326 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 } 342 } 343 344 // HACK : replace */xhtml by text/html 345 if contentType.SubType == "xhtml" { 346 contentType.TopLevelType = "text" 347 contentType.SubType = "html" 348 contentType.Suffix = "" 349 } 350 351 // conversion to UTF-8 272 if bytes.Contains(bytes.ToLower(contentType), []byte("javascript")) { 273 // HTTP status code 403 : Forbidden 274 p.serveMainPage(ctx, 403, errors.New("forbidden content type")) 275 return 276 } 277 278 contentInfo := bytes.SplitN(contentType, []byte(";"), 2) 279 352 280 var responseBody []byte 353 281 354 if contentType.TopLevelType == "text"{355 e, ename, _ := charset.DetermineEncoding(resp.Body(), contentTypeString)282 if len(contentInfo) == 2 && bytes.Contains(contentInfo[0], []byte("text")) { 283 e, ename, _ := charset.DetermineEncoding(resp.Body(), string(contentType)) 356 284 if (e != encoding.Nop) && (!strings.EqualFold("utf-8", ename)) { 357 285 responseBody, err = e.NewDecoder().Bytes(resp.Body()) … … 364 292 responseBody = resp.Body() 365 293 } 366 // update the charset or specify it367 contentType.Parameters["charset"] = "UTF-8"368 294 } else { 369 295 responseBody = resp.Body() 370 296 } 371 297 372 // 373 contentType.FilterParameters(ALLOWED_CONTENTTYPE_PARAMETERS) 374 375 // set the content type 376 ctx.SetContentType(contentType.String()) 377 378 // output according to MIME type 298 if bytes.Contains(contentType, []byte("xhtml")) { 299 ctx.SetContentType("text/html; charset=UTF-8") 300 } else { 301 ctx.SetContentType(fmt.Sprintf("%s; charset=UTF-8", contentInfo[0])) 302 } 303 379 304 switch { 380 case contentType.SubType == "css" && contentType.Suffix == "":305 case bytes.Contains(contentType, []byte("css")): 381 306 sanitizeCSS(&RequestConfig{Key: p.Key, BaseURL: parsedURI}, ctx, responseBody) 382 case contentType.SubType == "html" && contentType.Suffix == "":307 case bytes.Contains(contentType, []byte("html")): 383 308 sanitizeHTML(&RequestConfig{Key: p.Key, BaseURL: parsedURI}, ctx, responseBody) 384 309 default: 385 if c ontentDispositionBytes!= nil {386 ctx.Response.Header.AddBytesV("Content-Disposition", c ontentDispositionBytes)310 if ctx.Request.Header.Peek("Content-Disposition") != nil { 311 ctx.Response.Header.AddBytesV("Content-Disposition", ctx.Request.Header.Peek("Content-Disposition")) 387 312 } 388 313 ctx.Write(responseBody) 389 314 } 390 }391 392 // force content-disposition to attachment393 func contentDispositionForceAttachment(contentDispositionBytes []byte, url *url.URL) []byte {394 var contentDispositionParams map[string]string395 396 if contentDispositionBytes != nil {397 var err error398 _, 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 filename409 contentDispositionParams["fileName"] = filepath.Base(url.Path)410 }411 412 return []byte(mime.FormatMediaType("attachment", contentDispositionParams))413 315 } 414 316 … … 418 320 ctx.SetContentType("text/plain") 419 321 ctx.Write([]byte("User-Agent: *\nDisallow: /\n")) 322 return true 323 } 324 325 // server favicon.ico 326 if bytes.Equal(ctx.Path(), []byte("/favicon.ico")) { 327 ctx.SetContentType("image/png") 328 ctx.Write(FAVICON_BYTES) 420 329 return true 421 330 } … … 878 787 879 788 func (p *Proxy) serveMainPage(ctx *fasthttp.RequestCtx, statusCode int, err error) { 880 ctx.SetContentType("text/html ")789 ctx.SetContentType("text/html; charset=UTF-8") 881 790 ctx.SetStatusCode(statusCode) 882 791 ctx.Write([]byte(`<!doctype html> 792 <html> 883 793 <head> 884 794 <title>MortyProxy</title>
Note:
See TracChangeset
for help on using the changeset viewer.