Exemplo n.º 1
0
// GetRenderer returns the current raw renderer from the context.
func GetRenderer(c context.Context) renderer.Renderer {
	if val, ok := c.GetGlobal(context.BaseCtxKey("renderer")); ok {
		return val.(renderer.Renderer)
	}

	return renderer.NewRenderer("template", "base.tmpl")
}
Exemplo n.º 2
0
func GetUser(c context.Context, r *http.Request) content.User {
	if v, ok := c.Get(r, context.BaseCtxKey("user")); ok {
		return v.(content.User)
	}

	return nil
}
Exemplo n.º 3
0
// GetLogger returns the error logger, to be used if an error occurs during
// a request.
func GetLogger(c context.Context) Logger {
	if val, ok := c.GetGlobal(context.BaseCtxKey("logger")); ok {
		return val.(Logger)
	}

	return NewStandardLogger(os.Stderr, "", 0)
}
Exemplo n.º 4
0
// GetParams returns the current request path parameters from the context.
func GetParams(c context.Context, r *http.Request) RouteParams {
	if val, ok := c.Get(r, context.BaseCtxKey("params")); ok {
		return val.(RouteParams)
	}

	return RouteParams{}
}
Exemplo n.º 5
0
// GetLanguage returns the current request language, such as "en", or "bg-BG"
// from the context, if the I18N middleware is in use.
func GetLanguage(c context.Context, r *http.Request) string {
	if val, ok := c.Get(r, context.BaseCtxKey("lang")); ok {
		return val.(string)
	}

	return GetFallbackLanguage(c, r)
}
Exemplo n.º 6
0
// GetConfig is a helper function for getting the current config
// from the request context.
func GetConfig(c context.Context) Config {
	if val, ok := c.GetGlobal(context.BaseCtxKey("config")); ok {
		return val.(Config)
	}

	return Config{}
}
Exemplo n.º 7
0
func RegisterControllers(config readeef.Config, dispatcher *webfw.Dispatcher, apiPattern string) {
	dispatcher.Renderer = renderer.NewRenderer(dispatcher.Config.Renderer.Dir,
		dispatcher.Config.Renderer.Base)

	dispatcher.Renderer.Delims("{%", "%}")
	dispatcher.Context.SetGlobal(context.BaseCtxKey("readeefConfig"), config)

	middleware.InitializeDefault(dispatcher)

	dispatcher.Handle(NewApp())
	dispatcher.Handle(NewComponent(dispatcher, apiPattern))
}
Exemplo n.º 8
0
func RegisterControllers(config readeef.Config, dispatcher *webfw.Dispatcher, apiPattern string) {
	dispatcher.Renderer = renderer.NewRenderer(dispatcher.Config.Renderer.Dir,
		dispatcher.Config.Renderer.Base)

	hasProxy := false
	for _, p := range config.FeedParser.Processors {
		if p == "proxy-http" {
			hasProxy = true
			break
		}
	}

	if !hasProxy {
		for _, p := range config.Content.ArticleProcessors {
			if p == "proxy-http" {
				hasProxy = true
				break
			}
		}
	}

	mw := make([]string, 0, len(dispatcher.Config.Dispatcher.Middleware))
	for _, m := range dispatcher.Config.Dispatcher.Middleware {
		switch m {
		case "Session":
			if hasProxy {
				mw = append(mw, m)
			}
		default:
			mw = append(mw, m)
		}
	}

	dispatcher.Config.Dispatcher.Middleware = mw

	dispatcher.Renderer.Delims("{%", "%}")
	dispatcher.Context.SetGlobal(readeef.CtxKey("config"), config)
	dispatcher.Context.SetGlobal(context.BaseCtxKey("readeefConfig"), config)

	middleware.InitializeDefault(dispatcher)

	dispatcher.Handle(NewApp())
	dispatcher.Handle(NewComponent(dispatcher, apiPattern))

	if hasProxy {
		dispatcher.Handle(NewProxy())
	}

}
Exemplo n.º 9
0
Arquivo: url.go Projeto: urandom/webfw
// The URL function provides the functionality of the url template functions
// for use outside of the template context. The dispatcherPattern is the
// pattern used by the dispatcher responsible for handling the resulting url.
// In most cases it will probably be "/".
func URL(c context.Context, r *http.Request, dispatcherPattern string, parts []string) (string, error) {
	base, err := unlocalizedUrl(r, parts)

	if err != nil {
		return "", err
	}

	if lang, ok := c.Get(r, context.BaseCtxKey("lang")); ok && len(lang.(string)) > 0 {
		base = "/" + lang.(string) + base
	}
	if len(dispatcherPattern) > 1 {
		base = dispatcherPattern[:len(dispatcherPattern)-1] + base
	}

	if len(base) > 1 && base[len(base)-1] == '/' {
		base = base[:len(base)-1]
	}
	return base, nil
}
Exemplo n.º 10
0
// GetSession returns the current session from the context,
// if the Session middleware is in use.
func GetSession(c context.Context, r *http.Request) context.Session {
	if val, ok := c.Get(r, context.BaseCtxKey("session")); ok {
		return val.(context.Session)
	}

	conf := GetConfig(c)
	var abspath string

	if filepath.IsAbs(conf.Session.Dir) {
		abspath = conf.Session.Dir
	} else {
		var err error
		abspath, err = filepath.Abs(path.Join(filepath.Dir(os.Args[0]), conf.Session.Dir))

		if err != nil {
			abspath = os.TempDir()
		}
	}

	sess := context.NewSession([]byte(conf.Session.Secret), []byte(conf.Session.Cipher), abspath)
	sess.SetName(util.UUID())
	return sess
}
Exemplo n.º 11
0
// GetFallbackLanguage tries to obtain a requested language via the session,
// or the Accept-Language request header, or the LANG or LC_MESSAGES
// environment variables
func GetFallbackLanguage(c context.Context, r *http.Request, fallback ...string) string {
	if val, ok := c.Get(r, context.BaseCtxKey("session")); ok {
		sess := val.(context.Session)

		if language, ok := sess.Get("language"); ok {
			return language.(string)
		}
	}

	langs := lng.Parse(r.Header.Get("Accept-Language"))

	if len(langs) > 0 {
		return langs[0].String()
	}

	language := os.Getenv("LANG")

	if language == "" {
		language = os.Getenv("LC_MESSAGES")
		language = localeRegexp.ReplaceAllLiteralString(language, "")
	}

	if language == "" {
		if len(fallback) > 0 {
			language = fallback[0]
		} else {
			language = "en"
		}
	} else {
		langs := lng.Parse(language)
		if len(langs) > 0 {
			return langs[0].String()
		}
	}

	return language
}
Exemplo n.º 12
0
// GetNamedForward returns a name, used by the dispatcher to lookup a route to
// forward to.
func GetNamedForward(c context.Context, r *http.Request) string {
	if val, ok := c.Get(r, context.BaseCtxKey("named-forward")); ok {
		return val.(string)
	}
	return ""
}
Exemplo n.º 13
0
func TestRenderer(t *testing.T) {
	r := NewRenderer("testdata", "test.tmpl")
	cd := context.ContextData{}

	buf := new(bytes.Buffer)

	if err := r.Render(buf, nil, cd); err != nil {
		t.Fatal(err)
	}

	res := strings.TrimSpace(buf.String())
	expected := ""
	if res != expected {
		t.Fatalf("Expected result to be '%s', got '%s'\n", expected, res)
	}

	buf.Reset()
	if err := r.Render(buf, nil, cd, "test_normal.tmpl"); err != nil {
		t.Fatal(err)
	}

	res = strings.TrimSpace(buf.String())

	expected = `[content: test1]

[ctx: ]
[base: ]`
	if res != expected {
		t.Fatalf("Expected '%s', got '%s'\n", expected, res)
	}

	buf.Reset()
	if err := r.Render(buf, nil, cd, "test_inner.tmpl", "test_normal.tmpl"); err != nil {
		t.Fatal(err)
	}

	res = strings.TrimSpace(buf.String())

	expected = `[content: test1]

[inner: test3]

[ctx: ]
[base: ]`
	if res != expected {
		t.Fatalf("Expected '%s', got '%s'\n", expected, res)
	}

	buf.Reset()
	cd[context.BaseCtxKey("test")] = "foo"
	cd["test"] = "bar"
	if err := r.Render(buf, nil, cd, "test_inner.tmpl", "test_normal.tmpl"); err != nil {
		t.Fatal(err)
	}

	res = strings.TrimSpace(buf.String())

	expected = `[content: test1]

[inner: test3]

[ctx: bar]
[base: foo]`
	if res != expected {
		t.Fatalf("Expected '%s', got '%s'\n", expected, res)
	}

	r = NewRenderer("testdata", "test.tmpl")
	r.Funcs(template.FuncMap{
		"foo": func(dot string) string {
			return strings.ToUpper(dot)
		},
	})

	buf.Reset()
	data := RenderData{"test": "stuff"}
	if err := r.Render(buf, data, cd, "test_inner.tmpl", "test_func.tmpl"); err != nil {
		t.Fatal(err)
	}

	res = strings.TrimSpace(buf.String())

	expected = `STUFF`
	if res != expected {
		t.Fatalf("Expected '%s', got '%s'\n", expected, res)
	}
}
Exemplo n.º 14
0
func TestSessionHandler(t *testing.T) {
	c := context.NewContext()
	mw := Session{
		Path:            path.Join(os.TempDir(), "session"),
		Secret:          secret,
		MaxAge:          "1s",
		CleanupInterval: "1s",
		CleanupMaxAge:   "1s",
	}

	h := mw.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
	}), c)

	r, _ := http.NewRequest("GET", "http://localhost:8080/some/url", nil)
	rec := httptest.NewRecorder()
	c.Set(r, context.BaseCtxKey("r"), r)
	c.Set(r, context.BaseCtxKey("lang"), "en")

	h.ServeHTTP(rec, r)

	var cookie string
	if s, ok := c.Get(r, context.BaseCtxKey("session")); ok {
		sess := s.(context.Session)
		if sess.MaxAge() != time.Second {
			t.Fatalf("Expected Session.MaxAge to be '%s', got '%s'\n", time.Second, sess.MaxAge())
		}

		if err := sess.Write(rec); err != nil {
			t.Fatal(err)
		}
		sess.Set("foo", "bar")
		cookie = rec.Header().Get("Set-Cookie")
	} else {
		t.Fatalf("Expected a new session")
	}

	if ft, ok := c.Get(r, context.BaseCtxKey("firstTimer")); ok {
		if !ft.(bool) {
			t.Fatalf("Expected a true first-timer flag")
		}
	} else {
		t.Fatalf("Expected a first-timer flag")
	}

	time.Sleep(2 * time.Second)

	r, _ = http.NewRequest("GET", "http://localhost:8080/some/url", nil)
	rec = httptest.NewRecorder()
	r.Header.Set("Cookie", cookie[:strings.Index(cookie, ";")])

	h.ServeHTTP(rec, r)

	if ft, ok := c.Get(r, context.BaseCtxKey("firstTimer")); ok {
		if ft.(bool) {
			t.Fatalf("Expected a false first-timer flag")
		}
	} else {
		t.Fatalf("Expected a first-timer flag")
	}

	sess, _ := c.Get(r, context.BaseCtxKey("session"))
	if _, ok := sess.(context.Session).Get("foo"); ok {
		t.Fatalf("Expected the session to be empty")
	}

}
Exemplo n.º 15
0
func (mw Auth) Handler(ph http.Handler, c context.Context) http.Handler {
	logger := webfw.GetLogger(c)
	handler := func(w http.ResponseWriter, r *http.Request) {
		for _, prefix := range mw.IgnoreURLPrefix {
			if prefix[0] == '/' {
				prefix = prefix[1:]
			}

			if strings.HasPrefix(r.URL.Path, mw.Pattern+prefix+"/") {
				ph.ServeHTTP(w, r)
				return
			}
		}

		route, _, ok := webfw.GetDispatcher(c).RequestRoute(r)
		if !ok {
			ph.ServeHTTP(w, r)
			return
		}

		repo := GetRepo(c)

		switch ac := route.Controller.(type) {
		case AuthController:
			if !ac.LoginRequired(c, r) {
				ph.ServeHTTP(w, r)
				return
			}

			sess := webfw.GetSession(c, r)

			var u content.User
			validUser := false
			if uv, ok := sess.Get(AuthUserKey); ok {
				if u, ok = uv.(content.User); ok {
					validUser = true
				}
			}

			if !validUser {
				if uv, ok := sess.Get(AuthNameKey); ok {
					if n, ok := uv.(data.Login); ok {
						u = repo.UserByLogin(n)

						if u.HasErr() {
							logger.Print(u.Err())
						} else {
							validUser = true
							sess.Set(AuthUserKey, u)
						}
					}
				}
			}

			if validUser && !u.Data().Active {
				logger.Infoln("User " + u.Data().Login + " is inactive")
				validUser = false
			}

			if !validUser {
				d := webfw.GetDispatcher(c)
				sess.SetFlash(CtxKey("return-to"), r.URL.Path)
				path := d.NameToPath("auth-login", webfw.MethodGet)

				if path == "" {
					path = "/"
				}

				http.Redirect(w, r, path, http.StatusMovedPermanently)
				return
			}

		case ApiAuthController:
			if !ac.AuthRequired(c, r) {
				ph.ServeHTTP(w, r)
				return
			}

			url, login, signature, nonce, date, t := authData(r)

			validUser := false

			var u content.User

			if login != "" && signature != "" && !t.IsZero() {
				switch {
				default:
					u = repo.UserByLogin(data.Login(login))
					if u.HasErr() {
						logger.Printf("Error getting db user '%s': %v\n", login, u.Err())
						break
					}

					decoded, err := base64.StdEncoding.DecodeString(signature)
					if err != nil {
						logger.Printf("Error decoding auth header: %v\n", err)
						break
					}

					if t.Add(30 * time.Second).Before(time.Now()) {
						break
					}

					if !mw.Nonce.Check(nonce) {
						break
					}
					mw.Nonce.Remove(nonce)

					buf := util.BufferPool.GetBuffer()
					defer util.BufferPool.Put(buf)

					buf.ReadFrom(r.Body)
					r.Body = ioutil.NopCloser(buf)

					bodyHash := md5.New()
					if _, err := bodyHash.Write(buf.Bytes()); err != nil {
						logger.Printf("Error generating the hash for the request body: %v\n", err)
						break
					}

					contentMD5 := base64.StdEncoding.EncodeToString(bodyHash.Sum(nil))

					message := fmt.Sprintf("%s\n%s\n%s\n%s\n%s\n%s\n",
						url, r.Method, contentMD5, r.Header.Get("Content-Type"),
						date, nonce)

					b := make([]byte, base64.StdEncoding.EncodedLen(len(u.Data().MD5API)))
					base64.StdEncoding.Encode(b, u.Data().MD5API)

					hm := hmac.New(sha256.New, b)
					if _, err := hm.Write([]byte(message)); err != nil {
						logger.Printf("Error generating the hashed message: %v\n", err)
						break
					}

					if !hmac.Equal(hm.Sum(nil), decoded) {
						logger.Printf("Error matching the supplied auth message to the generated one.\n")
						break
					}

					if !u.Data().Active {
						logger.Println("User " + u.Data().Login + " is inactive")
						break
					}

					validUser = true
				}
			}

			if validUser {
				c.Set(r, context.BaseCtxKey("user"), u)
			} else {
				if rej, ok := ac.(AuthRejectHandler); ok {
					rej.AuthReject(c, r)
				} else {
					w.WriteHeader(http.StatusUnauthorized)
					return
				}
			}
		}

		ph.ServeHTTP(w, r)
	}

	return http.HandlerFunc(handler)
}
Exemplo n.º 16
0
func (imw I18N) Handler(ph http.Handler, c context.Context) http.Handler {
	for _, l := range imw.Languages {
		file, err := fs.DefaultFS.OpenRoot(imw.Dir, l+".all.json")
		if err == nil {
			var b []byte

			if b, err = ioutil.ReadAll(file); err == nil {
				err = i18n.ParseTranslationFileBytes(l+".all.json", b)
			}
		}

		if err != nil {
			panic(fmt.Sprintf("Error opening locale file '%s.all.json': %v\n", l, err))
		}
	}

	renderer := webfw.GetRenderer(c)
	renderer.Funcs(imw.TemplateFuncMap())

	handler := func(w http.ResponseWriter, r *http.Request) {
		c.Set(r, context.BaseCtxKey("langs"), imw.Languages)

		if len(imw.Languages) == 0 {
			c.Set(r, context.BaseCtxKey("lang"), "")
			ph.ServeHTTP(w, r)
			return
		}

		found := false

		uriParts := strings.SplitN(r.RequestURI, "?", 2)
		if uriParts[0] == "" {
			uriParts[0] = r.URL.Path
		}
		for _, prefix := range imw.IgnoreURLPrefix {
			if prefix[0] == '/' {
				prefix = prefix[1:]
			}

			if strings.HasPrefix(uriParts[0], imw.Pattern+prefix+"/") {
				found = true
				break
			}

			if uriParts[0] == imw.Pattern+prefix {
				found = true
				break
			}
		}

		if !found {
			for _, language := range imw.Languages {
				if uriParts[0] == imw.Pattern+language {
					url := uriParts[0] + "/"
					if len(uriParts) > 1 && uriParts[1] != "" {
						url += "?" + uriParts[1]
					}

					http.Redirect(w, r, url, http.StatusFound)

					return
				}

				if strings.HasPrefix(uriParts[0], imw.Pattern+language+"/") {
					r.URL.Path = imw.Pattern + r.URL.Path[len(imw.Pattern+language+"/"):]

					uriParts[0] = imw.Pattern + uriParts[0][len(imw.Pattern+language+"/"):]

					r.RequestURI = strings.Join(uriParts, "?")

					c.Set(r, context.BaseCtxKey("lang"), language)
					found = true

					s := webfw.GetSession(c, r)
					s.Set("language", language)

					break
				}
			}
		}

		if !found {
			fallback := webfw.GetFallbackLanguage(c, r, imw.FallbackLanguage)
			index := strings.Index(fallback, "-")
			short := fallback
			if index > -1 {
				short = fallback[:index]
			}
			foundShort := false

			for _, language := range imw.Languages {
				if language == fallback {
					found = true
					break
				}

				if language == short {
					foundShort = true
				}
			}

			var language string
			if found {
				language = fallback
			} else if foundShort {
				language = short
			} else {
				language = imw.FallbackLanguage
			}

			url := imw.Pattern + language + uriParts[0][len(imw.Pattern)-1:]
			if len(uriParts) > 1 && uriParts[1] != "" {
				url += "?" + uriParts[1]
			}

			http.Redirect(w, r, url, http.StatusFound)

			return
		}

		ph.ServeHTTP(w, r)
	}

	return http.HandlerFunc(handler)
}
Exemplo n.º 17
0
func TestContextUtil(t *testing.T) {
	c := context.NewContext()
	r, _ := http.NewRequest("GET", "http://localhost:8080", nil)

	conf := GetConfig(c)
	if fmt.Sprintf("%v", conf) != fmt.Sprintf("%v", Config{}) {
		t.Fatalf("Expected a an empty Config, got %v\n", conf)
	}

	conf.Server.Address = "127.0.0.1"
	c.SetGlobal(context.BaseCtxKey("config"), conf)

	conf = GetConfig(c)
	if conf.Server.Address != "127.0.0.1" {
		t.Fatalf("Expected Server.Address to be '127.0.0.1', got '%s'\n", conf.Server.Address)
	}

	params := GetParams(c, r)
	if len(params) != 0 {
		t.Fatalf("Expected empty params, got %v\n", params)
	}

	params["foo"] = "bar"
	c.Set(r, context.BaseCtxKey("params"), params)

	params = GetParams(c, r)
	if len(params) != 1 {
		t.Fatalf("Expected params with 1 entry, got %v\n", params)
	}

	if v := params["foo"]; v != "bar" {
		t.Fatalf("Expected param value for 'foo' to be 'bar', got %s\n", v)
	}

	sess := GetSession(c, r)
	if sess.Name() == "" {
		t.Fatalf("Expected a non-empty session name\n")
	}

	if len(sess.GetAll()) != 0 {
		t.Fatalf("Expected an empty session, got %v\n", sess.GetAll())
	}

	sess.Set("foo", "bar")
	uuid := sess.Name()
	c.Set(r, context.BaseCtxKey("session"), sess)

	sess = GetSession(c, r)
	if sess.Name() != uuid {
		t.Fatalf("Expected Session.Name '%s', got '%s'\n", uuid, sess.Name())
	}

	if v, ok := sess.Get("foo"); ok {
		if v != "bar" {
			t.Fatalf("Expected the value for session key 'foo' to be 'bar', got '%v'\n", v)
		}
	} else {
		t.Fatalf("Expected the session to have a value for key 'foo'\n")
	}

	lang := GetLanguage(c, r)

	if lang != GetFallbackLanguage(c, r) {
		t.Fatalf("Expected lang to be '%s', got '%s'\n", GetFallbackLanguage(c, r), lang)
	}

	c.Set(r, context.BaseCtxKey("lang"), "ZZ")

	lang = GetLanguage(c, r)
	if lang != "ZZ" {
		t.Fatalf("Expected lang to be 'ZZ', got '%s'\n", lang)
	}

	ren := GetRenderer(c)
	if ren == nil {
		t.Fatalf("Expected a non-nil renderer\n")
	}

	log := GetLogger(c)
	if log == nil {
		t.Fatalf("Expected a non-nill logger\n")
	}

	r, _ = http.NewRequest("GET", "http://localhost:8080", nil)
	conf = GetConfig(c)
	if conf.Server.Address != "127.0.0.1" {
		t.Fatalf("Expected Server.Address to be '127.0.0.1', got '%s'\n", conf.Server.Address)
	}

	params = GetParams(c, r)
	if len(params) != 0 {
		t.Fatalf("Expected empty params, got %v\n", params)
	}
}
Exemplo n.º 18
0
func (smw Session) Handler(ph http.Handler, c context.Context) http.Handler {
	var abspath string
	var maxAge, cleanupInterval, cleanupMaxAge time.Duration

	if filepath.IsAbs(smw.Path) {
		abspath = smw.Path
	} else {
		var err error
		abspath, err = filepath.Abs(path.Join(filepath.Dir(os.Args[0]), smw.Path))

		if err != nil {
			panic(err)
		}
	}

	if smw.MaxAge != "" {
		var err error
		maxAge, err = time.ParseDuration(smw.MaxAge)

		if err != nil {
			panic(err)
		}
	}

	logger := webfw.GetLogger(c)

	if smw.CleanupInterval != "" {
		var err error
		cleanupInterval, err = time.ParseDuration(smw.CleanupInterval)

		if err != nil {
			panic(err)
		}

		cleanupMaxAge, err = time.ParseDuration(smw.CleanupMaxAge)

		if err != nil {
			panic(err)
		}

		go func() {
			for _ = range time.Tick(cleanupInterval) {
				logger.Print("Cleaning up old sessions")

				if err := context.CleanupSessions(abspath, cleanupMaxAge); err != nil {
					logger.Printf("Failed to clean up sessions: %v", err)
				}
			}
		}()
	}

	handler := func(w http.ResponseWriter, r *http.Request) {
		uriParts := strings.SplitN(r.RequestURI, "?", 2)
		if uriParts[0] == "" {
			uriParts[0] = r.URL.Path
		}

		ignore := false
		for _, prefix := range smw.IgnoreURLPrefix {
			if prefix[0] == '/' {
				prefix = prefix[1:]
			}

			if strings.HasPrefix(uriParts[0], smw.Pattern+prefix+"/") {
				ignore = true
				break
			}

			if uriParts[0] == smw.Pattern+prefix {
				ignore = true
				break
			}
		}

		if ignore {
			ph.ServeHTTP(w, r)
			return
		}

		firstTimer := false
		var sess context.Session

		if smw.SessionGenerator == nil {
			sess = context.NewSession(smw.Secret, smw.Cipher, abspath)
		} else {
			sess = smw.SessionGenerator(smw.Secret, smw.Cipher, abspath)
		}
		sess.SetMaxAge(maxAge)

		err := sess.Read(r, c)

		if err != nil && err != context.ErrExpired && err != context.ErrNotExist {
			sess.SetName(util.UUID())
			firstTimer = true

			if err != context.ErrCookieNotExist {
				logger.Printf("Error reading session: %v", err)
			}
		}

		c.Set(r, context.BaseCtxKey("session"), sess)
		c.Set(r, context.BaseCtxKey("firstTimer"), firstTimer)

		rec := util.NewRecorderHijacker(w)

		ph.ServeHTTP(rec, r)

		for k, v := range rec.Header() {
			w.Header()[k] = v
		}

		if sess != nil {
			if err := sess.Write(w); err != nil {
				logger.Printf("Unable to write session: %v", err)
			}
		}

		w.WriteHeader(rec.GetCode())
		w.Write(rec.GetBody().Bytes())
	}

	return http.HandlerFunc(handler)
}
Exemplo n.º 19
0
func RegisterControllers(config readeef.Config, dispatcher *webfw.Dispatcher, logger webfw.Logger) error {
	repo, err := repo.New(config.DB.Driver, config.DB.Connect, logger)
	if err != nil {
		return err
	}

	if err := initAdminUser(repo, []byte(config.Auth.Secret)); err != nil {
		return err
	}

	dispatcher.Context.SetGlobal(readeef.CtxKey("config"), config)
	dispatcher.Context.SetGlobal(context.BaseCtxKey("readeefConfig"), config)
	dispatcher.Context.SetGlobal(readeef.CtxKey("repo"), repo)

	um := &readeef.UpdateFeedReceiverManager{}

	fm := readeef.NewFeedManager(repo, config, logger, um)

	if config.Hubbub.CallbackURL != "" {
		hubbub := readeef.NewHubbub(repo, config, logger, dispatcher.Pattern, fm.RemoveFeedChannel(), fm.AddFeedChannel(), um)
		if err := hubbub.InitSubscriptions(); err != nil {
			return fmt.Errorf("Error initializing hubbub subscriptions: %v", err)
		}

		fm.SetHubbub(hubbub)
		dispatcher.Handle(readeef.NewHubbubController(hubbub))
	}

	var si readeef.SearchIndex
	if config.SearchIndex.BlevePath != "" {
		var err error
		si, err = readeef.NewSearchIndex(repo, config, logger)
		if err != nil {
			return fmt.Errorf("Error initializing bleve search: %v", err)
		}

		if si.IsNewIndex() {
			go func() {
				si.IndexAllArticles()
			}()
		}

		fm.SetSearchIndex(si)
	}

	fm.Start()

	nonce := readeef.NewNonce()

	var patternController webfw.PatternController
	var multiPatternController webfw.MultiPatternController

	patternController = NewAuth()
	dispatcher.Handle(patternController)

	multiPatternController = NewFeed(fm)
	dispatcher.Handle(multiPatternController)

	multiPatternController = NewArticle(config)
	dispatcher.Handle(multiPatternController)

	if config.SearchIndex.BlevePath != "" {
		patternController = NewSearch(si)
		dispatcher.Handle(patternController)
	}

	multiPatternController = NewUser()
	dispatcher.Handle(multiPatternController)

	patternController = NewUserSettings()
	dispatcher.Handle(patternController)

	patternController = NewNonce(nonce)
	dispatcher.Handle(patternController)

	if config.API.Fever {
		patternController = NewFever(fm)
		dispatcher.Handle(patternController)
	}

	webSocket := NewWebSocket(fm, si)
	dispatcher.Handle(webSocket)
	um.AddUpdateReceiver(webSocket)

	middleware.InitializeDefault(dispatcher)
	dispatcher.RegisterMiddleware(readeef.Auth{Pattern: dispatcher.Pattern, Nonce: nonce, IgnoreURLPrefix: config.Auth.IgnoreURLPrefix})

	dispatcher.Renderer = renderer.NewRenderer(dispatcher.Config.Renderer.Dir,
		dispatcher.Config.Renderer.Base)

	dispatcher.Renderer.Delims("{%", "%}")

	go func() {
		for {
			select {
			case <-time.After(5 * time.Minute):
				nonce.Clean(45 * time.Second)
			}
		}
	}()

	return nil
}
Exemplo n.º 20
0
// Initialize creates all configured middleware handlers, producing a chain of
// functions to be called on each request. Initializes and registers all
// handled controllers. This function is called automatically by the Server
// object, when its ListenAndServe method is called. It should be called
// directly before calling http.Handle() using the dispatcher when the Server
// object is not used.
func (d *Dispatcher) Initialize() {
	if d.Renderer == nil {
		d.Renderer = renderer.NewRenderer(d.Config.Renderer.Dir, d.Config.Renderer.Base)
	}

	d.Context.SetGlobal(context.BaseCtxKey("renderer"), d.Renderer)
	d.Context.SetGlobal(context.BaseCtxKey("logger"), d.Logger)
	d.Context.SetGlobal(context.BaseCtxKey("dispatcher"), d)
	d.Context.SetGlobal(context.BaseCtxKey("config"), d.Config)

	var mw []Middleware
	order := []string{}
	middlewareInserted := make(map[string]bool)

	for _, m := range d.Config.Dispatcher.Middleware {
		if custom, ok := d.middleware[m]; ok {
			d.Logger.Debugf("Queueing middleware %s to the chain.\n", m)
			mw = append(mw, custom)
			order = append(order, m)

			middlewareInserted[m] = true
		}
	}

	reverseOrder := d.middlewareOrder[:]
	sort.Sort(sort.Reverse(sort.StringSlice(reverseOrder)))
	for _, name := range reverseOrder {
		if !middlewareInserted[name] {
			if custom, ok := d.middleware[name]; ok {
				d.Logger.Debugf("Queueing middleware %s to the chain.\n", name)
				mw = append([]Middleware{custom}, mw...)
				order = append([]string{name}, order...)
			}
		}
	}

	handler := d.handlerFunc()

	for _, m := range mw {
		handler = m.Handler(handler, d.Context)
	}

	d.handler = handler
	d.middlewareOrder = order

	for i := range d.Controllers {
		var routes []Route

		switch c := d.Controllers[i].(type) {
		case PatternController:
			r := Route{
				c.Pattern(), c.Method(), c.Handler(d.Context), c.Name(), c,
			}
			routes = append(routes, r)
		case MultiPatternController:
			for _, tuple := range c.Patterns() {
				r := Route{
					tuple.Pattern, tuple.Method, c.Handler(d.Context), "", c,
				}

				routes = append(routes, r)
			}
		default:
			panic(fmt.Sprintf("Controllers of type '%T' are not supported\n", c))
		}

		for _, r := range routes {
			d.Logger.Debugf("Adding route to %s with method %d and controller %T to %s.\n",
				r.Pattern, r.Method, r.Controller, d.Pattern)
			if err := d.trie.AddRoute(r); err != nil {
				panic(fmt.Sprintf("Error adding route for %s to the dispatcher: %v\n", r.Pattern, err))
			}
		}
	}
}
Exemplo n.º 21
0
func (d Dispatcher) handlerFunc() http.Handler {
	var handler func(w http.ResponseWriter, r *http.Request)

	handler = func(w http.ResponseWriter, r *http.Request) {
		var route Route
		routeFound := false

		method := ReverseMethodNames[r.Method]
		if GetNamedForward(d.Context, r) != "" {
			match, ok := d.trie.LookupNamed(GetNamedForward(d.Context, r), method)
			d.Context.Delete(r, context.BaseCtxKey("named-forward"))

			if ok {
				route, routeFound = match.RouteMap[method]
			}
		} else if GetForward(d.Context, r) != "" {
			path := GetForward(d.Context, r)

			d.Context.Delete(r, context.BaseCtxKey("forward"))

			if d.Pattern != "/" {
				path = path[len(d.Pattern)-1:]
			}
			d.Context.Delete(r, context.BaseCtxKey("params"))
			if match, ok := d.trie.Lookup(path, method); ok {
				route, routeFound = match.RouteMap[method]
				d.Context.Set(r, context.BaseCtxKey("params"), match.Params)
			}
		} else {
			var params RouteParams
			route, params, routeFound = d.RequestRoute(r)

			if routeFound {
				d.Context.Set(r, context.BaseCtxKey("params"), params)

				switch tc := route.Controller.(type) {
				case MultiPatternController:
					for _, tuple := range tc.Patterns() {
						if tuple.Pattern == route.Pattern && tuple.Method&method > 0 {
							d.Context.Set(r, context.BaseCtxKey("multi-pattern-identifier"), tuple.Identifier)
						}
					}
				}
			}
		}

		d.Context.Set(r, context.BaseCtxKey("r"), r)

		if routeFound {
			d.Context.Set(r, context.BaseCtxKey("route-name"), route.Name)
			route.Handler.ServeHTTP(w, r)

			if GetForward(d.Context, r) != "" || GetNamedForward(d.Context, r) != "" {
				handler(w, r)
			}
		} else {
			w.WriteHeader(http.StatusNotFound)

			err := GetRenderCtx(d.Context, r)(w, nil, "404.tmpl")
			if err != nil {
				fmt.Print(err)
			}
		}
	}

	return http.HandlerFunc(handler)
}
Exemplo n.º 22
0
// GetMultiPatternIdentifier returns the identifier for the current
// multi-pattern route.
func GetMultiPatternIdentifier(c context.Context, r *http.Request) string {
	if val, ok := c.Get(r, context.BaseCtxKey("multi-pattern-identifier")); ok {
		return val.(string)
	}
	return ""
}
Exemplo n.º 23
0
func (mw Auth) Handler(ph http.Handler, c context.Context, l *log.Logger) http.Handler {
	handler := func(w http.ResponseWriter, r *http.Request) {
		for _, prefix := range mw.IgnoreURLPrefix {
			if prefix[0] == '/' {
				prefix = prefix[1:]
			}

			if strings.HasPrefix(r.URL.Path, mw.Pattern+prefix+"/") {
				ph.ServeHTTP(w, r)
				return
			}
		}

		route, _, ok := webfw.GetDispatcher(c).RequestRoute(r)
		if !ok {
			ph.ServeHTTP(w, r)
			return
		}

		switch ac := route.Controller.(type) {
		case AuthController:
			if !ac.LoginRequired(c, r) {
				ph.ServeHTTP(w, r)
				return
			}

			sess := webfw.GetSession(c, r)

			var u User
			validUser := false
			if uv, ok := sess.Get(authkey); ok {
				if u, ok = uv.(User); ok {
					validUser = true
				}
			}

			if !validUser {
				if uv, ok := sess.Get(namekey); ok {
					if n, ok := uv.(string); ok {
						var err error
						u, err = mw.DB.GetUser(n)

						if err == nil {
							validUser = true
							sess.Set(authkey, u)
						} else if _, ok := err.(ValidationError); !ok {
							l.Print(err)
						}
					}
				}
			}

			if validUser && !u.Active {
				Debug.Println("User " + u.Login + " is inactive")
				validUser = false
			}

			if !validUser {
				d := webfw.GetDispatcher(c)
				sess.SetFlash(CtxKey("return-to"), r.URL.Path)
				path := d.NameToPath("auth-login", webfw.MethodGet)

				if path == "" {
					path = "/"
				}

				http.Redirect(w, r, path, http.StatusMovedPermanently)
				return
			}

		case ApiAuthController:
			if !ac.AuthRequired(c, r) {
				ph.ServeHTTP(w, r)
				return
			}

			var u User
			var err error

			auth := r.Header.Get("Authorization")
			validUser := false

			if auth != "" {
				switch {
				default:
					if !strings.HasPrefix(auth, "Readeef ") {
						break
					}
					auth = auth[len("Readeef "):]

					parts := strings.SplitN(auth, ":", 2)
					login := parts[0]

					u, err = mw.DB.GetUser(login)
					if err != nil {
						l.Printf("Error getting db user '%s': %v\n", login, err)
						break
					}

					var decoded []byte
					decoded, err = base64.StdEncoding.DecodeString(parts[1])
					if err != nil {
						l.Printf("Error decoding auth header: %v\n", err)
						break
					}

					date := r.Header.Get("X-Date")
					t, err := time.Parse(http.TimeFormat, date)
					if err != nil || t.Add(30*time.Second).Before(time.Now()) {
						break
					}

					nonce := r.Header.Get("X-Nonce")
					if !mw.Nonce.Check(nonce) {
						break
					}
					mw.Nonce.Remove(nonce)

					buf := util.BufferPool.GetBuffer()
					defer util.BufferPool.Put(buf)

					buf.ReadFrom(r.Body)
					r.Body = ioutil.NopCloser(buf)

					bodyHash := md5.New()
					if _, err := bodyHash.Write(buf.Bytes()); err != nil {
						l.Printf("Error generating the hash for the request body: %v\n", err)
						break
					}

					contentMD5 := base64.StdEncoding.EncodeToString(bodyHash.Sum(nil))

					message := fmt.Sprintf("%s\n%s\n%s\n%s\n%s\n%s\n",
						r.RequestURI, r.Method, contentMD5, r.Header.Get("Content-Type"),
						date, nonce)

					b := make([]byte, base64.StdEncoding.EncodedLen(len(u.MD5API)))
					base64.StdEncoding.Encode(b, u.MD5API)

					hm := hmac.New(sha256.New, b)
					if _, err := hm.Write([]byte(message)); err != nil {
						l.Printf("Error generating the hashed message: %v\n", err)
						break
					}

					if !hmac.Equal(hm.Sum(nil), decoded) {
						l.Printf("Error matching the supplied auth message to the generated one.\n")
						break
					}

					if !u.Active {
						Debug.Println("User " + u.Login + " is inactive")
						break
					}

					validUser = true
				}
			}

			if !validUser {
				w.WriteHeader(http.StatusForbidden)
				return
			}

			c.Set(r, context.BaseCtxKey("user"), u)
		}

		ph.ServeHTTP(w, r)
	}

	return http.HandlerFunc(handler)
}
Exemplo n.º 24
0
// GetDispatcher returns the request dispatcher.
func GetDispatcher(c context.Context) *Dispatcher {
	if val, ok := c.GetGlobal(context.BaseCtxKey("dispatcher")); ok {
		return val.(*Dispatcher)
	}
	return &Dispatcher{}
}
Exemplo n.º 25
0
func RegisterControllers(config readeef.Config, dispatcher *webfw.Dispatcher, logger webfw.Logger) error {
	repo, err := repo.New(config.DB.Driver, config.DB.Connect, logger)
	if err != nil {
		return err
	}

	capabilities := capabilities{
		I18N:       len(dispatcher.Config.I18n.Languages) > 1,
		Popularity: len(config.Popularity.Providers) > 0,
	}

	var ap []content.ArticleProcessor
	for _, p := range config.Content.ArticleProcessors {
		switch p {
		case "relative-url":
			ap = append(ap, contentProcessor.NewRelativeUrl(logger))
		case "proxy-http":
			template := config.Content.ProxyHTTPURLTemplate

			if template != "" {
				p, err := contentProcessor.NewProxyHTTP(logger, template)
				if err != nil {
					return fmt.Errorf("Error initializing Proxy HTTP article processor: %v", err)
				}
				ap = append(ap, p)
				capabilities.ProxyHTTP = true
			}
		case "insert-thumbnail-target":
			ap = append(ap, contentProcessor.NewInsertThumbnailTarget(logger))
		}
	}

	repo.ArticleProcessors(ap)

	if err := initAdminUser(repo, []byte(config.Auth.Secret)); err != nil {
		return err
	}

	mw := make([]string, 0, len(dispatcher.Config.Dispatcher.Middleware))
	for _, m := range dispatcher.Config.Dispatcher.Middleware {
		switch m {
		case "I18N", "Static", "Url", "Sitemap":
		case "Session":
			if capabilities.ProxyHTTP {
				mw = append(mw, m)
			}
		default:
			mw = append(mw, m)
		}
	}

	dispatcher.Config.Dispatcher.Middleware = mw

	dispatcher.Context.SetGlobal(readeef.CtxKey("config"), config)
	dispatcher.Context.SetGlobal(context.BaseCtxKey("readeefConfig"), config)
	dispatcher.Context.SetGlobal(readeef.CtxKey("repo"), repo)

	fm := readeef.NewFeedManager(repo, config, logger)

	var processors []parser.Processor
	for _, p := range config.FeedParser.Processors {
		switch p {
		case "relative-url":
			processors = append(processors, processor.NewRelativeUrl(logger))
		case "proxy-http":
			template := config.FeedParser.ProxyHTTPURLTemplate

			if template != "" {
				p, err := processor.NewProxyHTTP(logger, template)
				if err != nil {
					return fmt.Errorf("Error initializing Proxy HTTP processor: %v", err)
				}
				processors = append(processors, p)
				capabilities.ProxyHTTP = true
			}
		case "cleanup":
			processors = append(processors, processor.NewCleanup(logger))
		case "top-image-marker":
			processors = append(processors, processor.NewTopImageMarker(logger))
		}
	}

	fm.ParserProcessors(processors)

	var sp content.SearchProvider

	switch config.Content.SearchProvider {
	case "elastic":
		if sp, err = search.NewElastic(config.Content.ElasticURL, config.Content.SearchBatchSize, logger); err != nil {
			logger.Printf("Error initializing Elastic search: %v\n", err)
		}
	case "bleve":
		fallthrough
	default:
		if sp, err = search.NewBleve(config.Content.BlevePath, config.Content.SearchBatchSize, logger); err != nil {
			logger.Printf("Error initializing Bleve search: %v\n", err)
		}
	}

	if sp != nil {
		if sp.IsNewIndex() {
			go func() {
				sp.IndexAllFeeds(repo)
			}()
		}
	}

	var ce content.Extractor

	switch config.Content.Extractor {
	case "readability":
		if ce, err = extractor.NewReadability(config.Content.ReadabilityKey); err != nil {
			return fmt.Errorf("Error initializing Readability extractor: %v\n", err)
		}
	case "goose":
		fallthrough
	default:
		if ce, err = extractor.NewGoose(dispatcher.Config.Renderer.Dir); err != nil {
			return fmt.Errorf("Error initializing Goose extractor: %v\n", err)
		}
	}

	if ce != nil {
		capabilities.Extractor = true
	}

	var t content.Thumbnailer
	switch config.Content.Thumbnailer {
	case "extract":
		if t, err = thumbnailer.NewExtract(ce, logger); err != nil {
			return fmt.Errorf("Error initializing Extract thumbnailer: %v\n", err)
		}
	case "description":
		fallthrough
	default:
		t = thumbnailer.NewDescription(logger)
	}

	monitors := []content.FeedMonitor{monitor.NewUnread(repo, logger)}
	for _, m := range config.FeedManager.Monitors {
		switch m {
		case "index":
			if sp != nil {
				monitors = append(monitors, monitor.NewIndex(sp, logger))
				capabilities.Search = true
			}
		case "thumbnailer":
			if t != nil {
				monitors = append(monitors, monitor.NewThumbnailer(t, logger))
			}
		}
	}

	webSocket := NewWebSocket(fm, sp, ce, capabilities)
	dispatcher.Handle(webSocket)

	monitors = append(monitors, webSocket)

	if config.Hubbub.CallbackURL != "" {
		hubbub := readeef.NewHubbub(repo, config, logger, dispatcher.Pattern,
			fm.RemoveFeedChannel())
		if err := hubbub.InitSubscriptions(); err != nil {
			return fmt.Errorf("Error initializing hubbub subscriptions: %v", err)
		}

		hubbub.FeedMonitors(monitors)
		fm.Hubbub(hubbub)
	}

	fm.FeedMonitors(monitors)

	fm.Start()

	nonce := readeef.NewNonce()

	controllers := []webfw.Controller{
		NewAuth(capabilities),
		NewFeed(fm, sp),
		NewArticle(config, ce),
		NewUser(),
		NewUserSettings(),
		NewNonce(nonce),
	}

	if fm.Hubbub() != nil {
		controllers = append(controllers, NewHubbubController(fm.Hubbub(), config.Hubbub.RelativePath,
			fm.AddFeedChannel(), fm.RemoveFeedChannel()))
	}

	for _, e := range config.API.Emulators {
		switch e {
		case "tt-rss":
			controllers = append(controllers, NewTtRss(fm, sp))
		case "fever":
			controllers = append(controllers, NewFever())
		}
	}

	for _, c := range controllers {
		dispatcher.Handle(c)
	}

	middleware.InitializeDefault(dispatcher)
	dispatcher.RegisterMiddleware(readeef.Auth{Pattern: dispatcher.Pattern, Nonce: nonce, IgnoreURLPrefix: config.Auth.IgnoreURLPrefix})

	dispatcher.Renderer = renderer.NewRenderer(dispatcher.Config.Renderer.Dir,
		dispatcher.Config.Renderer.Base)

	dispatcher.Renderer.Delims("{%", "%}")

	go func() {
		for {
			select {
			case <-time.After(5 * time.Minute):
				nonce.Clean(45 * time.Second)
			}
		}
	}()

	return nil
}
Exemplo n.º 26
0
func TestI18NHandler(t *testing.T) {
	c := context.NewContext()
	ren := renderer.NewRenderer("testdata", "test.tmpl")
	c.SetGlobal(context.BaseCtxKey("renderer"), ren)
	mw := I18N{
		Languages:       []string{"en", "bg"},
		Pattern:         "/",
		Dir:             "testdata",
		IgnoreURLPrefix: []string{"/css", "/js"},
	}

	h := mw.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		if err := ren.Render(w, nil, c.GetAll(r), "test_i18n.tmpl"); err != nil {
			t.Fatal(err)
		}
	}), c)

	r, _ := http.NewRequest("GET", "http://localhost:8080", nil)
	rec := httptest.NewRecorder()

	h.ServeHTTP(rec, r)

	if rec.Code != http.StatusFound {
		t.Fatalf("Expected code %d, got %d\n", http.StatusFound, rec.Code)
	}

	r, _ = http.NewRequest("GET", "http://localhost:8080/bg/", nil)
	r.RequestURI = "/bg/"
	rec = httptest.NewRecorder()

	h.ServeHTTP(rec, r)

	if rec.Code != http.StatusOK {
		t.Fatalf("Expected code %d, got %d\n", http.StatusOK, rec.Code)
	}

	expected := "test data bg"
	if !strings.Contains(rec.Body.String(), expected) {
		t.Fatalf("Expected body '%s' to contain '%s'\n", rec.Body.String(), expected)
	}

	r, _ = http.NewRequest("GET", "http://localhost:8080/en/", nil)
	r.RequestURI = "/en/"
	rec = httptest.NewRecorder()

	s := context.NewSession([]byte(""), nil, os.TempDir())
	s.SetName("test1")
	c.Set(r, context.BaseCtxKey("session"), s)
	h.ServeHTTP(rec, r)

	if rec.Code != http.StatusOK {
		t.Fatalf("Expected code %d, got %d\n", http.StatusOK, rec.Code)
	}

	expected = "test data en"
	if !strings.Contains(rec.Body.String(), expected) {
		t.Fatalf("Expected body '%s' to contain '%s'\n", rec.Body.String(), expected)
	}

	if s, ok := s.Get("language"); ok {
		if s.(string) != "en" {
			t.Fatalf("Expected session.language to be '%s', got '%s'\n", "en", s.(string))
		}
	} else {
		t.Fatalf("Expected the session to have a language key\n")
	}

	r, _ = http.NewRequest("GET", "http://localhost:8080/en", nil)
	r.RequestURI = "/en"
	rec = httptest.NewRecorder()

	h.ServeHTTP(rec, r)

	if rec.Code != http.StatusFound {
		t.Fatalf("Expected code %d, got %d\n", http.StatusFound, rec.Code)
	}

	expected = "/en/"
	if rec.Header().Get("Location") != expected {
		t.Fatalf("Expected a redirect to '%s', got '%s'\n", expected, rec.Header().Get("Location"))
	}

	r, _ = http.NewRequest("GET", "http://localhost:8080/foo/bar/baz", nil)
	r.RequestURI = "/foo/bar/baz"
	rec = httptest.NewRecorder()

	h.ServeHTTP(rec, r)

	if rec.Code != http.StatusFound {
		t.Fatalf("Expected code %d, got %d\n", http.StatusFound, rec.Code)
	}

	expected = "/en/foo/bar/baz"
	if rec.Header().Get("Location") != expected {
		t.Fatalf("Expected a redirect to '%s', got '%s'\n", expected, rec.Header().Get("Location"))
	}

	r, _ = http.NewRequest("GET", "http://localhost:8080/foo/bar/baz?alpha=beta&gamma=delta", nil)
	r.RequestURI = "/foo/bar/baz?alpha=beta&gamma=delta"
	rec = httptest.NewRecorder()

	h.ServeHTTP(rec, r)

	if rec.Code != http.StatusFound {
		t.Fatalf("Expected code %d, got %d\n", http.StatusFound, rec.Code)
	}

	expected = "/en/foo/bar/baz?alpha=beta&gamma=delta"
	if rec.Header().Get("Location") != expected {
		t.Fatalf("Expected a redirect to '%s', got '%s'\n", expected, rec.Header().Get("Location"))
	}

	r, _ = http.NewRequest("GET", "http://localhost:8080/foo/bar/baz?alpha=beta&gamma=delta#test", nil)
	r.RequestURI = "/foo/bar/baz?alpha=beta&gamma=delta#test"
	rec = httptest.NewRecorder()

	h.ServeHTTP(rec, r)

	if rec.Code != http.StatusFound {
		t.Fatalf("Expected code %d, got %d\n", http.StatusFound, rec.Code)
	}

	expected = "/en/foo/bar/baz?alpha=beta&gamma=delta#test"
	if rec.Header().Get("Location") != expected {
		t.Fatalf("Expected a redirect to '%s', got '%s'\n", expected, rec.Header().Get("Location"))
	}

	r, _ = http.NewRequest("GET", "http://localhost:8080/css/foo", nil)
	r.RequestURI = "/css/foo"
	rec = httptest.NewRecorder()

	h = mw.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
	}), c)

	h.ServeHTTP(rec, r)

	if rec.Code != http.StatusOK {
		t.Fatalf("Expected code %d, got %d\n", http.StatusOK, rec.Code)
	}

	r, _ = http.NewRequest("GET", "http://localhost:8080/js/foo", nil)
	r.RequestURI = "/js/foo"
	rec = httptest.NewRecorder()

	h.ServeHTTP(rec, r)

	if rec.Code != http.StatusOK {
		t.Fatalf("Expected code %d, got %d\n", http.StatusOK, rec.Code)
	}
}