func main() { flag.Parse() if *vFlag { fmt.Println(ProgName, ProgVersion) os.Exit(0) } // Build font inventory. fontInventory := inventory.New() if err := fontInventory.Build(*lFlag); err != nil { PrintErrorExit(err.Error()) } if fontInventory.Len() == 0 { PrintErrorExit(fmt.Sprintf("%s: empty font library", *lFlag)) } // Read whitelist. whitelist := whitelist.New() if err := whitelist.Read(*wFlag); err != nil { PrintErrorExit(err.Error()) } if whitelist.Size() == 0 { PrintErrorExit(fmt.Sprintf("%s: empty whitelist", *wFlag)) } // Parse templates. templatesPath := filepath.FromSlash(*tFlag) eot := filepath.Join(templatesPath, "eot.css.tmpl") woff := filepath.Join(templatesPath, "woff.css.tmpl") var templates *template.Template var err error if templates, err = template.ParseFiles(eot, woff); err != nil { PrintErrorExit(err.Error()) } // Create CSS handler function. var cssHandler http.HandlerFunc ctx := ihttp.HandlerContext{ Flags: ihttp.Flags{ AcAllowOrigin: *oFlag, CcMaxAge: *mFlag, Etag: *eFlag, Gzip: *gFlag, Version: ProgName + "/" + ProgVersion, }, Inventory: *fontInventory, Templates: *templates, Whitelist: *whitelist, } cssHandler = ihttp.MakeHandler(ihttp.CssHandler, ctx) if *gFlag { // Enable response gzip compression. cssHandler = ihttp.MakeGzipHandler(cssHandler) } // Register CSS HTTP handler. http.HandleFunc("/css/", cssHandler) // Start HTTP server. if err := http.ListenAndServe(*bFlag, nil); err != nil { PrintErrorExit(err.Error()) } }
func TestCssHandler(t *testing.T) { type Request struct { Method string URL string } // Build an inventory. // Used in cases 6-7. inv := inventory.New() err := inv.Build(fl) test.VerifyFatal(t, 1, 0, true, err == nil) // Build an "allow all" whitelist. // Use this whitelist to allow all // referrers to fetch the resource. // Used in cases 3-7. aawl := whitelist.New() aawl.Domains = append(aawl.Domains, "") // Parse templates. // Used in cases 7-9. eot := filepath.Join(tp, "eot.css.tmpl") woff := filepath.Join(tp, "woff.css.tmpl") tmpl, err := template.ParseFiles(eot, woff) test.VerifyFatal(t, 2, 0, true, err == nil) // Execute template containing Amaranth Regular. // Used in case 7. ar := &font.Font{ Family: "Amaranth", Format: font.WOFF, Path: filepath.Join(amf, "amaranth-regular.woff"), Style: "normal", Weight: 400, } var tmplData []*FontFace data, err := ar.Contents() test.VerifyFatal(t, 3, 0, true, err == nil) mimeType := ar.MimeType() test.VerifyFatal(t, 4, 0, false, "" == mimeType) arff := &FontFace{ Base64Data: util.Base64(data), Family: ar.Family, Format: ar.Format.String(), MimeType: mimeType, Style: ar.Style, Weight: ar.Weight, } buf := new(bytes.Buffer) tmplName := ar.Format.String() + ".css.tmpl" tmplData = append(tmplData, arff) err = tmpl.ExecuteTemplate(buf, tmplName, tmplData) test.VerifyFatal(t, 5, 0, true, nil == err) arBody := buf.Bytes() // Generate the entity tag corresponding to Amaranth Regular. // Uncompressed response. // Used in case 7. hash := md5.New() modtime, err := ar.ModTime() test.VerifyFatal(t, 6, 0, true, nil == err) io.WriteString(hash, modtime.String()) arEtag := fmt.Sprintf("%x", hash.Sum(nil)) // Gzip compressed response. // Used in cases 8-9. arEtagGzip := arEtag + "+gzip" var cases = []struct { Body []byte Context HandlerContext Header map[string]string IfNoneMatch string // If-None-Match client request header. Request *Request StatusCode int }{ // Case 1 { Header: map[string]string{ "Content-Type": "text/plain; charset=utf-8", }, Request: &Request{ Method: "POST", URL: "", }, StatusCode: http.StatusNotImplemented, }, // Case 2 { Header: map[string]string{ "Content-Type": "text/plain; charset=utf-8", }, Request: &Request{ Method: "GET", URL: "", }, StatusCode: http.StatusForbidden, }, // Case 3 { Context: HandlerContext{ Whitelist: *aawl, }, Header: map[string]string{ "Content-Type": "text/plain; charset=utf-8", }, Request: &Request{ Method: "GET", URL: "?family=", }, StatusCode: http.StatusBadRequest, }, // Case 4 { Context: HandlerContext{ Whitelist: *aawl, }, Header: map[string]string{ "Content-Type": "text/plain; charset=utf-8", }, Request: &Request{ Method: "GET", URL: "?family=Amaranth&format=nonexistent", }, StatusCode: http.StatusBadRequest, }, // Case 5 { Context: HandlerContext{ Whitelist: *aawl, }, Header: map[string]string{ "Content-Type": "text/plain; charset=utf-8", }, Request: &Request{ Method: "GET", URL: "?family=|Amaranth", }, StatusCode: http.StatusBadRequest, }, // Case 6 { Context: HandlerContext{ Flags: Flags{ Etag: true, }, Inventory: *inv, Whitelist: *aawl, }, Header: map[string]string{ "Content-Type": "text/plain; charset=utf-8", "Etag": "", }, Request: &Request{ Method: "GET", URL: "?family=Nonexistent", }, StatusCode: http.StatusBadRequest, }, // Case 7 { Body: arBody, Context: HandlerContext{ Flags: Flags{ CcMaxAge: 2592000, Etag: true, }, Inventory: *inv, Templates: *tmpl, Whitelist: *aawl, }, Header: map[string]string{ "Cache-Control": "max-age=2592000", "Content-Type": "text/css; charset=utf-8", "Etag": arEtag, }, Request: &Request{ Method: "GET", URL: "?family=Amaranth", }, StatusCode: http.StatusOK, }, // Case 8 { Body: arBody, Context: HandlerContext{ Flags: Flags{ Etag: true, Gzip: true, }, Inventory: *inv, Templates: *tmpl, Whitelist: *aawl, }, Header: map[string]string{ "Cache-Control": "max-age=0", "Content-Type": "text/css; charset=utf-8", "Etag": arEtagGzip, }, Request: &Request{ Method: "GET", URL: "?family=Amaranth", }, StatusCode: http.StatusOK, }, // Case 9 { Context: HandlerContext{ Flags: Flags{ Etag: true, Gzip: true, }, Inventory: *inv, Templates: *tmpl, Whitelist: *aawl, }, Header: map[string]string{ "Cache-Control": "max-age=0", "Etag": arEtagGzip, }, IfNoneMatch: arEtagGzip, Request: &Request{ Method: "GET", URL: "?family=Amaranth", }, StatusCode: http.StatusNotModified, }, } for i, c := range cases { j := i + 1 handler := MakeHandler(CssHandler, c.Context) server := httptest.NewServer(handler) defer server.Close() client := http.Client{} reqURL := server.URL + c.Request.URL req, err := http.NewRequest(c.Request.Method, reqURL, nil) test.VerifyFatal(t, 6, j, true, nil == err) if c.IfNoneMatch != "" { req.Header.Add("If-None-Match", c.IfNoneMatch) } resp, err := client.Do(req) defer resp.Body.Close() test.VerifyFatal(t, 7, j, true, nil == err) test.Verify(t, 8, j, c.StatusCode, resp.StatusCode) wContentType := c.Header["Content-Type"] gContentType := resp.Header.Get("Content-Type") test.Verify(t, 9, j, wContentType, gContentType) wCacheControl := c.Header["Cache-Control"] gCacheControl := resp.Header.Get("Cache-Control") test.Verify(t, 10, j, wCacheControl, gCacheControl) wEtag := c.Header["Etag"] gEtag := resp.Header.Get("Etag") test.Verify(t, 11, j, wEtag, gEtag) if (c.StatusCode == http.StatusOK) && (resp.StatusCode == http.StatusOK) { gbody, err := ioutil.ReadAll(resp.Body) test.VerifyFatal(t, 13, j, true, nil == err) wbody := c.Body test.Verify(t, 14, j, true, bytes.Equal(wbody, gbody)) } } }