// 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") }
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 }
// 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) }
// 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{} }
// 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) }
// 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{} }
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)) }
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()) } }
// 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 }
// 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 }
// 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 }
// 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 "" }
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) } }
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") } }
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) }
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) }
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) } }
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) }
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 }
// 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)) } } } }
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) }
// 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 "" }
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) }
// 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{} }
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 }
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) } }