Beispiel #1
0
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())
	}
}
Beispiel #2
0
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))
		}
	}
}