Exemple #1
0
// Efficient favicon handler, mostly a port of node's Connect library implementation
// of the favicon middleware.
// https://github.com/senchalabs/connect
func FaviconHandler(h http.Handler, path string, maxAge time.Duration) http.HandlerFunc {
	var buf []byte
	var hash string

	return func(w http.ResponseWriter, r *http.Request) {
		var err error
		if r.URL.Path == "/favicon.ico" {
			if buf == nil {
				// Read from file and cache
				ghost.LogFn("ghost.favicon : serving from %s", path)
				buf, err = ioutil.ReadFile(path)
				if err != nil {
					ghost.LogFn("ghost.favicon : error reading file : %s", err)
					http.NotFound(w, r)
					return
				}
				hash = hashContent(buf)
			}
			writeHeaders(w.Header(), buf, maxAge, hash)
			writeBody(w, r, buf)
		} else {
			h.ServeHTTP(w, r)
		}
	}
}
Exemple #2
0
// Write the content of the favicon, or respond with a 404 not found
// in case of error (hardly a critical error).
func writeBody(w http.ResponseWriter, r *http.Request, buf []byte) {
	_, err := w.Write(buf)
	if err != nil {
		ghost.LogFn("ghost.favicon : error writing response : %s", err)
		http.NotFound(w, r)
	}
}
Exemple #3
0
// Compute a CRC32 hash of the session's JSON-encoded contents.
func hash(s *Session) uint32 {
	data, err := json.Marshal(s)
	if err != nil {
		ghost.LogFn("ghost.session : error hash : %s", err)
		return 0 // 0 is always treated as "modified" session content
	}
	return crc32.ChecksumIEEE(data)
}
Exemple #4
0
// Compile the specified template file if there is a matching compiler.
func compileTemplate(p, base string) error {
	ext := path.Ext(p)
	c, ok := compilers[ext]
	// Ignore file if no template compiler exist for this extension
	if ok {
		t, err := c.Compile(p)
		if err != nil {
			return err
		}
		key, err := filepath.Rel(base, p)
		if err != nil {
			return err
		}
		ghost.LogFn("ghost.templates : storing template for file %s", key)
		templaters[key] = t
	}
	return nil
}
Exemple #5
0
// Compile all templates that have a matching compiler (based on their extension) in the
// specified directory.
func CompileDir(dir string) error {
	mu.Lock()
	defer mu.Unlock()

	return filepath.Walk(dir, func(path string, fi os.FileInfo, err error) error {
		if fi == nil {
			return ErrDirNotExist
		}
		if !fi.IsDir() {
			err = compileTemplate(path, dir)
			if err != nil {
				ghost.LogFn("ghost.templates : error compiling template %s : %s", path, err)
				return err
			}
		}
		return nil
	})
}
Exemple #6
0
// Create a Session handler to offer the Session behaviour to the specified handler.
func SessionHandler(h http.Handler, opts *SessionOptions) http.HandlerFunc {
	// Make sure the required cookie fields are set
	if opts.CookieTemplate.Name == "" {
		opts.CookieTemplate.Name = defaultCookieName
	}
	if opts.CookieTemplate.Path == "" {
		opts.CookieTemplate.Path = "/"
	}
	// Secret is required
	if opts.Secret == "" {
		panic(ErrSessionSecretMissing)
	}

	// Return the actual handler
	return func(w http.ResponseWriter, r *http.Request) {
		if _, ok := getSessionWriter(w); ok {
			// Self-awareness
			h.ServeHTTP(w, r)
			return
		}

		if strings.Index(r.URL.Path, opts.CookieTemplate.Path) != 0 {
			// Session does not apply to this path
			h.ServeHTTP(w, r)
			return
		}

		// Create a new Session or retrieve the existing session based on the
		// session cookie received.
		var sess *Session
		var ckSessId string
		exCk, err := r.Cookie(opts.CookieTemplate.Name)
		if err != nil {
			sess = newSession(opts.CookieTemplate.MaxAge)
			ghost.LogFn("ghost.session : error getting session cookie : %s", err)
		} else {
			ckSessId, err = parseSignedCookie(exCk, opts.Secret)
			if err != nil {
				sess = newSession(opts.CookieTemplate.MaxAge)
				ghost.LogFn("ghost.session : error parsing signed cookie : %s", err)
			} else if ckSessId == "" {
				sess = newSession(opts.CookieTemplate.MaxAge)
				ghost.LogFn("ghost.session : no existing session ID")
			} else {
				// Get the session
				sess, err = opts.Store.Get(ckSessId)
				if err != nil {
					sess = newSession(opts.CookieTemplate.MaxAge)
					ghost.LogFn("ghost.session : error getting session from store : %s", err)
				} else if sess == nil {
					sess = newSession(opts.CookieTemplate.MaxAge)
					ghost.LogFn("ghost.session : nil session")
				}
			}
		}
		// Save the original hash of the session, used to compare if the contents
		// have changed during the handling of the request, so that it has to be
		// saved to the stored.
		oriHash := hash(sess)

		// Create the augmented ResponseWriter.
		srw := &sessResponseWriter{w, sess, opts.Store, false, func() {
			// This function is called when the header is about to be written, so that
			// the session cookie is correctly set.

			// Check if the connection is secure
			proto := strings.Trim(strings.ToLower(r.Header.Get("X-Forwarded-Proto")), " ")
			tls := r.TLS != nil || (strings.HasPrefix(proto, "https") && opts.TrustProxy)
			if opts.CookieTemplate.Secure && !tls {
				ghost.LogFn("ghost.session : secure cookie on a non-secure connection, cookie not sent")
				return
			}
			if !sess.IsNew() {
				// If this is not a new session, no need to send back the cookie
				// TODO : Handle expires?
				return
			}

			// Send the session cookie
			ck := opts.CookieTemplate
			ck.Value = sess.ID()
			err := signCookie(&ck, opts.Secret)
			if err != nil {
				ghost.LogFn("ghost.session : error signing cookie : %s", err)
				return
			}
			http.SetCookie(w, &ck)
		}}

		// Call wrapped handler
		h.ServeHTTP(srw, r)

		// TODO : Expiration management? srw.sess.resetMaxAge()
		// Do not save if content is the same, unless session is new (to avoid
		// creating a new session and sending a cookie on each successive request).
		if newHash := hash(sess); !sess.IsNew() && oriHash == newHash && newHash != 0 {
			// No changes to the session, no need to save
			ghost.LogFn("ghost.session : no changes to save to store")
			return
		}
		err = opts.Store.Set(sess)
		if err != nil {
			ghost.LogFn("ghost.session : error saving session to store : %s", err)
		}
	}
}