Changeset 68 in code for trunk/morty.go
- Timestamp:
- Dec 23, 2016, 7:25:28 PM (8 years ago)
- File:
-
- 1 edited
Legend:
- Unmodified
- Added
- Removed
-
trunk/morty.go
r67 r68 12 12 "io" 13 13 "log" 14 "mime" 14 15 "net/url" 16 "path/filepath" 15 17 "regexp" 16 18 "strings" … … 22 24 "golang.org/x/net/html/charset" 23 25 "golang.org/x/text/encoding" 26 27 "github.com/asciimoo/morty/contenttype" 24 28 ) 25 29 … … 35 39 36 40 var CSS_URL_REGEXP *regexp.Regexp = regexp.MustCompile("url\\((['\"]?)[ \\t\\f]*([\u0009\u0021\u0023-\u0026\u0028\u002a-\u007E]+)(['\"]?)\\)?") 41 42 var ALLOWED_CONTENTTYPE_FILTER contenttype.Filter = contenttype.NewFilterOr([]contenttype.Filter{ 43 // html 44 contenttype.NewFilterEquals("text", "html", ""), 45 contenttype.NewFilterEquals("application", "xhtml", "xml"), 46 // css 47 contenttype.NewFilterEquals("text", "css", ""), 48 // images 49 contenttype.NewFilterEquals("image", "gif", ""), 50 contenttype.NewFilterEquals("image", "png", ""), 51 contenttype.NewFilterEquals("image", "jpeg", ""), 52 contenttype.NewFilterEquals("image", "pjpeg", ""), 53 contenttype.NewFilterEquals("image", "webp", ""), 54 contenttype.NewFilterEquals("image", "tiff", ""), 55 contenttype.NewFilterEquals("image", "vnd.microsoft.icon", ""), 56 contenttype.NewFilterEquals("image", "bmp", ""), 57 contenttype.NewFilterEquals("image", "x-ms-bmp", ""), 58 // fonts 59 contenttype.NewFilterEquals("application", "font-otf", ""), 60 contenttype.NewFilterEquals("application", "font-ttf", ""), 61 contenttype.NewFilterEquals("application", "font-woff", ""), 62 contenttype.NewFilterEquals("application", "vnd.ms-fontobject", ""), 63 }) 64 65 var ALLOWED_CONTENTTYPE_ATTACHMENT_FILTER contenttype.Filter = contenttype.NewFilterOr([]contenttype.Filter{ 66 // texts 67 contenttype.NewFilterEquals("text", "csv", ""), 68 contenttype.NewFilterEquals("text", "tab-separated-value", ""), 69 contenttype.NewFilterEquals("text", "plain", ""), 70 // API 71 contenttype.NewFilterEquals("application", "json", ""), 72 // Documents 73 contenttype.NewFilterEquals("application", "x-latex", ""), 74 contenttype.NewFilterEquals("application", "pdf", ""), 75 contenttype.NewFilterEquals("application", "vnd.oasis.opendocument.text", ""), 76 contenttype.NewFilterEquals("application", "vnd.oasis.opendocument.spreadsheet", ""), 77 contenttype.NewFilterEquals("application", "vnd.oasis.opendocument.presentation", ""), 78 contenttype.NewFilterEquals("application", "vnd.oasis.opendocument.graphics", ""), 79 // Compressed archives 80 contenttype.NewFilterEquals("application", "zip", ""), 81 contenttype.NewFilterEquals("application", "gzip", ""), 82 contenttype.NewFilterEquals("application", "x-compressed", ""), 83 contenttype.NewFilterEquals("application", "x-gtar", ""), 84 contenttype.NewFilterEquals("application", "x-compress", ""), 85 // Generic binary 86 contenttype.NewFilterEquals("application", "octet-stream", ""), 87 }) 88 89 var ALLOWED_CONTENTTYPE_PARAMETERS map[string]bool = map[string]bool{ 90 "charset": true, 91 } 37 92 38 93 var UNSAFE_ELEMENTS [][]byte = [][]byte{ … … 262 317 } 263 318 264 contentType := resp.Header.Peek("Content-Type")265 266 if contentType == nil {319 contentTypeBytes := resp.Header.Peek("Content-Type") 320 321 if contentTypeBytes == nil { 267 322 // HTTP status code 503 : Service Unavailable 268 323 p.serveMainPage(ctx, 503, errors.New("invalid content type")) … … 270 325 } 271 326 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")) 327 contentTypeString := string(contentTypeBytes) 328 329 // decode Content-Type header 330 contentType, error := contenttype.ParseContentType(contentTypeString) 331 if error != nil { 332 // HTTP status code 503 : Service Unavailable 333 p.serveMainPage(ctx, 503, errors.New("invalid content type")) 275 334 return 276 335 } 277 336 278 contentInfo := bytes.SplitN(contentType, []byte(";"), 2) 279 337 // content-disposition 338 contentDispositionBytes := ctx.Request.Header.Peek("Content-Disposition") 339 340 // check content type 341 if !ALLOWED_CONTENTTYPE_FILTER(contentType) { 342 // it is not a usual content type 343 if ALLOWED_CONTENTTYPE_ATTACHMENT_FILTER(contentType) { 344 // force attachment for allowed content type 345 contentDispositionBytes = contentDispositionForceAttachment(contentDispositionBytes, parsedURI) 346 } else { 347 // deny access to forbidden content type 348 // HTTP status code 403 : Forbidden 349 p.serveMainPage(ctx, 403, errors.New("forbidden content type")) 350 return 351 } 352 } 353 354 // HACK : replace */xhtml by text/html 355 if contentType.SubType == "xhtml" { 356 contentType.TopLevelType = "text" 357 contentType.SubType = "html" 358 contentType.Suffix = "" 359 } 360 361 // conversion to UTF-8 280 362 var responseBody []byte 281 363 282 if len(contentInfo) == 2 && bytes.Contains(contentInfo[0], []byte("text")){283 e, ename, _ := charset.DetermineEncoding(resp.Body(), string(contentType))364 if contentType.TopLevelType == "text" { 365 e, ename, _ := charset.DetermineEncoding(resp.Body(), contentTypeString) 284 366 if (e != encoding.Nop) && (!strings.EqualFold("utf-8", ename)) { 285 367 responseBody, err = e.NewDecoder().Bytes(resp.Body()) … … 292 374 responseBody = resp.Body() 293 375 } 376 // update the charset or specify it 377 contentType.Parameters["charset"] = "UTF-8" 294 378 } else { 295 379 responseBody = resp.Body() 296 380 } 297 381 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 382 // 383 contentType.FilterParameters(ALLOWED_CONTENTTYPE_PARAMETERS) 384 385 // set the content type 386 ctx.SetContentType(contentType.String()) 387 388 // output according to MIME type 304 389 switch { 305 case bytes.Contains(contentType, []byte("css")):390 case contentType.SubType == "css" && contentType.Suffix == "": 306 391 sanitizeCSS(&RequestConfig{Key: p.Key, BaseURL: parsedURI}, ctx, responseBody) 307 case bytes.Contains(contentType, []byte("html")):392 case contentType.SubType == "html" && contentType.Suffix == "": 308 393 sanitizeHTML(&RequestConfig{Key: p.Key, BaseURL: parsedURI}, ctx, responseBody) 309 394 default: 310 if c tx.Request.Header.Peek("Content-Disposition")!= nil {311 ctx.Response.Header.AddBytesV("Content-Disposition", c tx.Request.Header.Peek("Content-Disposition"))395 if contentDispositionBytes != nil { 396 ctx.Response.Header.AddBytesV("Content-Disposition", contentDispositionBytes) 312 397 } 313 398 ctx.Write(responseBody) 314 399 } 400 } 401 402 // force content-disposition to attachment 403 func contentDispositionForceAttachment(contentDispositionBytes []byte, url *url.URL) []byte { 404 var contentDispositionParams map[string]string 405 406 if contentDispositionBytes != nil { 407 var err error 408 _, contentDispositionParams, err = mime.ParseMediaType(string(contentDispositionBytes)) 409 if err != nil { 410 contentDispositionParams = make(map[string]string) 411 } 412 } else { 413 contentDispositionParams = make(map[string]string) 414 } 415 416 _, fileNameDefined := contentDispositionParams["filename"] 417 if !fileNameDefined { 418 // TODO : sanitize filename 419 contentDispositionParams["fileName"] = filepath.Base(url.Path) 420 } 421 422 return []byte(mime.FormatMediaType("attachment", contentDispositionParams)) 315 423 } 316 424
Note:
See TracChangeset
for help on using the changeset viewer.