Example #1
0
// ServeHTTP implements the middleware.Handler interface.
func (t Templates) ServeHTTP(w http.ResponseWriter, r *http.Request) (int, error) {
	for _, rule := range t.Rules {
		if !middleware.Path(r.URL.Path).Matches(rule.Path) {
			continue
		}

		// Check for index files
		fpath := r.URL.Path
		if idx, ok := middleware.IndexFile(t.FileSys, fpath, rule.IndexFiles); ok {
			fpath = idx
		}

		// Check the extension
		reqExt := path.Ext(fpath)

		for _, ext := range rule.Extensions {
			if reqExt == ext {
				// Create execution context
				ctx := context{root: t.FileSys, req: r, URL: r.URL}

				// Build the template
				tpl, err := template.ParseFiles(filepath.Join(t.Root, fpath))
				if err != nil {
					if os.IsNotExist(err) {
						return http.StatusNotFound, nil
					} else if os.IsPermission(err) {
						return http.StatusForbidden, nil
					}
					return http.StatusInternalServerError, err
				}

				// Execute it
				var buf bytes.Buffer
				err = tpl.Execute(&buf, ctx)
				if err != nil {
					return http.StatusInternalServerError, err
				}
				buf.WriteTo(w)

				return http.StatusOK, nil
			}
		}
	}

	return t.Next.ServeHTTP(w, r)
}
Example #2
0
func (h Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) (int, error) {
	for _, rule := range h.Rules {
		if !middleware.Path(r.URL.Path).Matches(rule.BasePath) {
			continue
		}

		// Check for index file
		fpath := r.URL.Path
		if idx, ok := middleware.IndexFile(h.FileSys, fpath, browse.IndexPages); ok {
			fpath = idx
		}

		// TODO: Check extension. If .lua, assume whole file is Lua script.

		file, err := h.FileSys.Open(filepath.Join(h.Root, fpath))
		if err != nil {
			if os.IsNotExist(err) {
				return http.StatusNotFound, nil
			} else if os.IsPermission(err) {
				return http.StatusForbidden, nil
			}
			return http.StatusInternalServerError, err
		}
		defer file.Close()

		contents, err := ioutil.ReadAll(file)
		if err != nil {
			return http.StatusInternalServerError, err
		}

		var out bytes.Buffer
		if err := Interpret(&out, contents); err != nil {
			return http.StatusInternalServerError, err
		}

		// Write the combined text to the http.ResponseWriter
		w.Write(out.Bytes())

		return http.StatusOK, nil
	}

	return h.Next.ServeHTTP(w, r)
}
Example #3
0
// ServeHTTP implements the http.Handler interface.
func (md Markdown) ServeHTTP(w http.ResponseWriter, r *http.Request) (int, error) {
	for _, cfg := range md.Configs {
		if !middleware.Path(r.URL.Path).Matches(cfg.PathScope) {
			continue
		}

		fpath := r.URL.Path
		if idx, ok := middleware.IndexFile(md.FileSys, fpath, md.IndexFiles); ok {
			fpath = idx
		}

		for _, ext := range cfg.Extensions {
			if strings.HasSuffix(fpath, ext) {
				f, err := md.FileSys.Open(fpath)
				if err != nil {
					if os.IsPermission(err) {
						return http.StatusForbidden, err
					}
					return http.StatusNotFound, nil
				}

				fs, err := f.Stat()
				if err != nil {
					return http.StatusNotFound, nil
				}

				// if development is set, scan directory for file changes for links.
				if cfg.Development {
					if err := GenerateStatic(md, cfg); err != nil {
						log.Println("On-demand generation error (markdown):", err)
					}
				}

				cfg.RLock()
				filepath, ok := cfg.StaticFiles[fpath]
				cfg.RUnlock()
				// if static site is generated, attempt to use it
				if ok {
					if fs1, err := os.Stat(filepath); err == nil {
						// if markdown has not been modified since static page
						// generation, serve the static page
						if fs.ModTime().Before(fs1.ModTime()) {
							if html, err := ioutil.ReadFile(filepath); err == nil {
								w.Write(html)
								return http.StatusOK, nil
							}
							if os.IsPermission(err) {
								return http.StatusForbidden, err
							}
							return http.StatusNotFound, nil
						}
					}
				}

				body, err := ioutil.ReadAll(f)
				if err != nil {
					return http.StatusInternalServerError, err
				}

				ctx := middleware.Context{
					Root: md.FileSys,
					Req:  r,
					URL:  r.URL,
				}
				html, err := md.Process(cfg, fpath, body, ctx)
				if err != nil {
					return http.StatusInternalServerError, err
				}

				w.Write(html)
				return http.StatusOK, nil
			}
		}
	}

	// Didn't qualify to serve as markdown; pass-thru
	return md.Next.ServeHTTP(w, r)
}
Example #4
0
// ServeHTTP satisfies the middleware.Handler interface.
func (h Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) (int, error) {
	for _, rule := range h.Rules {

		// First requirement: Base path must match
		if !middleware.Path(r.URL.Path).Matches(rule.Path) {
			continue
		}

		// In addition to matching the path, a request must meet some
		// other criteria before being proxied as FastCGI. For example,
		// we probably want to exclude static assets (CSS, JS, images...)
		// but we also want to be flexible for the script we proxy to.

		fpath := r.URL.Path
		if idx, ok := middleware.IndexFile(h.FileSys, fpath, rule.IndexFiles); ok {
			fpath = idx
		}

		// These criteria work well in this order for PHP sites
		if fpath[len(fpath)-1] == '/' || strings.HasSuffix(fpath, rule.Ext) || !h.exists(fpath) {

			// Create environment for CGI script
			env, err := h.buildEnv(r, rule, fpath)
			if err != nil {
				return http.StatusInternalServerError, err
			}

			// Connect to FastCGI gateway
			var fcgi *FCGIClient

			// check if unix socket or tcp
			if strings.HasPrefix(rule.Address, "/") || strings.HasPrefix(rule.Address, "unix:") {
				if strings.HasPrefix(rule.Address, "unix:") {
					rule.Address = rule.Address[len("unix:"):]
				}
				fcgi, err = Dial("unix", rule.Address)
			} else {
				fcgi, err = Dial("tcp", rule.Address)
			}
			if err != nil {
				return http.StatusBadGateway, err
			}

			var resp *http.Response
			contentLength, _ := strconv.Atoi(r.Header.Get("Content-Length"))
			switch r.Method {
			case "HEAD":
				resp, err = fcgi.Head(env)
			case "GET":
				resp, err = fcgi.Get(env)
			case "OPTIONS":
				resp, err = fcgi.Options(env)
			case "POST":
				resp, err = fcgi.Post(env, r.Header.Get("Content-Type"), r.Body, contentLength)
			case "PUT":
				resp, err = fcgi.Put(env, r.Header.Get("Content-Type"), r.Body, contentLength)
			case "PATCH":
				resp, err = fcgi.Patch(env, r.Header.Get("Content-Type"), r.Body, contentLength)
			case "DELETE":
				resp, err = fcgi.Delete(env, r.Header.Get("Content-Type"), r.Body, contentLength)
			default:
				return http.StatusMethodNotAllowed, nil
			}

			if resp.Body != nil {
				defer resp.Body.Close()
			}

			if err != nil && err != io.EOF {
				return http.StatusBadGateway, err
			}

			// Write the response header
			for key, vals := range resp.Header {
				for _, val := range vals {
					w.Header().Add(key, val)
				}
			}
			w.WriteHeader(resp.StatusCode)

			// Write the response body
			// TODO: If this has an error, the response will already be
			// partly written. We should copy out of resp.Body into a buffer
			// first, then write it to the response...
			_, err = io.Copy(w, resp.Body)
			if err != nil {
				return http.StatusBadGateway, err
			}

			return 0, nil
		}
	}

	return h.Next.ServeHTTP(w, r)
}
Example #5
0
// ServeHTTP satisfies the middleware.Handler interface.
func (h Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) (int, error) {
	for _, rule := range h.Rules {

		// First requirement: Base path must match and the path must be allowed.
		if !middleware.Path(r.URL.Path).Matches(rule.Path) || !rule.AllowedPath(r.URL.Path) {
			continue
		}

		// In addition to matching the path, a request must meet some
		// other criteria before being proxied as FastCGI. For example,
		// we probably want to exclude static assets (CSS, JS, images...)
		// but we also want to be flexible for the script we proxy to.

		fpath := r.URL.Path

		if idx, ok := middleware.IndexFile(h.FileSys, fpath, rule.IndexFiles); ok {
			fpath = idx
			// Index file present.
			// If request path cannot be split, return error.
			if !rule.canSplit(fpath) {
				return http.StatusInternalServerError, ErrIndexMissingSplit
			}
		} else {
			// No index file present.
			// If request path cannot be split, ignore request.
			if !rule.canSplit(fpath) {
				continue
			}
		}

		// These criteria work well in this order for PHP sites
		if !h.exists(fpath) || fpath[len(fpath)-1] == '/' || strings.HasSuffix(fpath, rule.Ext) {

			// Create environment for CGI script
			env, err := h.buildEnv(r, rule, fpath)
			if err != nil {
				return http.StatusInternalServerError, err
			}

			// Connect to FastCGI gateway
			network, address := rule.parseAddress()
			fcgiBackend, err := Dial(network, address)
			if err != nil {
				return http.StatusBadGateway, err
			}

			var resp *http.Response
			contentLength, _ := strconv.Atoi(r.Header.Get("Content-Length"))
			switch r.Method {
			case "HEAD":
				resp, err = fcgiBackend.Head(env)
			case "GET":
				resp, err = fcgiBackend.Get(env)
			case "OPTIONS":
				resp, err = fcgiBackend.Options(env)
			default:
				resp, err = fcgiBackend.Post(env, r.Method, r.Header.Get("Content-Type"), r.Body, contentLength)
			}

			if resp.Body != nil {
				defer resp.Body.Close()
			}

			if err != nil && err != io.EOF {
				return http.StatusBadGateway, err
			}

			// Write response header
			writeHeader(w, resp)

			// Write the response body
			_, err = io.Copy(w, resp.Body)
			if err != nil {
				return http.StatusBadGateway, err
			}

			// Log any stderr output from upstream
			if fcgiBackend.stderr.Len() != 0 {
				// Remove trailing newline, error logger already does this.
				err = LogError(strings.TrimSuffix(fcgiBackend.stderr.String(), "\n"))
			}

			// Normally we would return the status code if it is an error status (>= 400),
			// however, upstream FastCGI apps don't know about our contract and have
			// probably already written an error page. So we just return 0, indicating
			// that the response body is already written. However, we do return any
			// error value so it can be logged.
			// Note that the proxy middleware works the same way, returning status=0.
			return 0, err
		}
	}

	return h.Next.ServeHTTP(w, r)
}
Example #6
0
// ServeHTTP implements the http.Handler interface.
func (md Markdown) ServeHTTP(w http.ResponseWriter, r *http.Request) (int, error) {
	for _, m := range md.Configs {
		if !middleware.Path(r.URL.Path).Matches(m.PathScope) {
			continue
		}

		fpath := r.URL.Path
		if idx, ok := middleware.IndexFile(md.FileSys, fpath, md.IndexFiles); ok {
			fpath = idx
		}

		for _, ext := range m.Extensions {
			if strings.HasSuffix(fpath, ext) {
				f, err := md.FileSys.Open(fpath)
				if err != nil {
					if os.IsPermission(err) {
						return http.StatusForbidden, err
					}
					return http.StatusNotFound, nil
				}

				fs, err := f.Stat()
				if err != nil {
					return http.StatusNotFound, nil
				}

				// if static site is generated, attempt to use it
				if filepath, ok := m.StaticFiles[fpath]; ok {
					if fs1, err := os.Stat(filepath); err == nil {
						// if markdown has not been modified
						// since static page generation,
						// serve the static page
						if fs.ModTime().UnixNano() < fs1.ModTime().UnixNano() {
							if html, err := ioutil.ReadFile(filepath); err == nil {
								w.Write(html)
								return http.StatusOK, nil
							}
							if os.IsPermission(err) {
								return http.StatusForbidden, err
							}
							return http.StatusNotFound, nil
						}
					}
				}

				body, err := ioutil.ReadAll(f)
				if err != nil {
					return http.StatusInternalServerError, err
				}

				html, err := md.Process(m, fpath, body)
				if err != nil {
					return http.StatusInternalServerError, err
				}

				w.Write(html)
				return http.StatusOK, nil
			}
		}
	}

	// Didn't qualify to serve as markdown; pass-thru
	return md.Next.ServeHTTP(w, r)
}
Example #7
0
// ServeHTTP implements the middleware.Handler interface.
func (t Templates) ServeHTTP(w http.ResponseWriter, r *http.Request) (int, error) {
	for _, rule := range t.Rules {
		if !middleware.Path(r.URL.Path).Matches(rule.Path) {
			continue
		}

		// Check for index files
		fpath := r.URL.Path
		if idx, ok := middleware.IndexFile(t.FileSys, fpath, rule.IndexFiles); ok {
			fpath = idx
		}

		// Check the extension
		reqExt := path.Ext(fpath)

		for _, ext := range rule.Extensions {
			if reqExt == ext {
				// Create execution context
				ctx := middleware.Context{Root: t.FileSys, Req: r, URL: r.URL}

				// New template
				templateName := filepath.Base(fpath)
				tpl := template.New(templateName)

				// Set delims
				if rule.Delims != [2]string{} {
					tpl.Delims(rule.Delims[0], rule.Delims[1])
				}

				// Build the template
				templatePath := filepath.Join(t.Root, fpath)
				tpl, err := tpl.ParseFiles(templatePath)
				if err != nil {
					if os.IsNotExist(err) {
						return http.StatusNotFound, nil
					} else if os.IsPermission(err) {
						return http.StatusForbidden, nil
					}
					return http.StatusInternalServerError, err
				}

				// Execute it
				var buf bytes.Buffer
				err = tpl.Execute(&buf, ctx)
				if err != nil {
					return http.StatusInternalServerError, err
				}

				templateInfo, err := os.Stat(templatePath)
				if err == nil {
					// add the Last-Modified header if we were able to read the stamp
					middleware.SetLastModifiedHeader(w, templateInfo.ModTime())
				}
				buf.WriteTo(w)

				return http.StatusOK, nil
			}
		}
	}

	return t.Next.ServeHTTP(w, r)
}
Example #8
0
// ServeHTTP satisfies the middleware.Handler interface.
func (h Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) (int, error) {
	for _, rule := range h.Rules {

		// First requirement: Base path must match
		if !middleware.Path(r.URL.Path).Matches(rule.Path) {
			continue
		}

		// In addition to matching the path, a request must meet some
		// other criteria before being proxied as FastCGI. For example,
		// we probably want to exclude static assets (CSS, JS, images...)
		// but we also want to be flexible for the script we proxy to.

		fpath := r.URL.Path
		if idx, ok := middleware.IndexFile(h.FileSys, fpath, rule.IndexFiles); ok {
			fpath = idx
			// Index file present.
			// If request path cannot be split, return error.
			if !h.canSplit(fpath, rule) {
				return http.StatusInternalServerError, ErrIndexMissingSplit
			}
		} else {
			// No index file present.
			// If request path cannot be split, ignore request.
			if !h.canSplit(fpath, rule) {
				continue
			}
		}

		// These criteria work well in this order for PHP sites
		if !h.exists(fpath) || fpath[len(fpath)-1] == '/' || strings.HasSuffix(fpath, rule.Ext) {

			// Create environment for CGI script
			env, err := h.buildEnv(r, rule, fpath)
			if err != nil {
				return http.StatusInternalServerError, err
			}

			// Connect to FastCGI gateway
			fcgi, err := getClient(&rule)
			if err != nil {
				return http.StatusBadGateway, err
			}

			var resp *http.Response
			contentLength, _ := strconv.Atoi(r.Header.Get("Content-Length"))
			switch r.Method {
			case "HEAD":
				resp, err = fcgi.Head(env)
			case "GET":
				resp, err = fcgi.Get(env)
			case "OPTIONS":
				resp, err = fcgi.Options(env)
			case "POST":
				resp, err = fcgi.Post(env, r.Header.Get("Content-Type"), r.Body, contentLength)
			case "PUT":
				resp, err = fcgi.Put(env, r.Header.Get("Content-Type"), r.Body, contentLength)
			case "PATCH":
				resp, err = fcgi.Patch(env, r.Header.Get("Content-Type"), r.Body, contentLength)
			case "DELETE":
				resp, err = fcgi.Delete(env, r.Header.Get("Content-Type"), r.Body, contentLength)
			default:
				return http.StatusMethodNotAllowed, nil
			}

			if resp.Body != nil {
				defer resp.Body.Close()
			}

			if err != nil && err != io.EOF {
				return http.StatusBadGateway, err
			}

			writeHeader(w, resp)

			// Write the response body
			// TODO: If this has an error, the response will already be
			// partly written. We should copy out of resp.Body into a buffer
			// first, then write it to the response...
			_, err = io.Copy(w, resp.Body)
			if err != nil {
				return http.StatusBadGateway, err
			}

			return 0, nil
		}
	}

	return h.Next.ServeHTTP(w, r)
}
Example #9
0
// ServeHTTP implements the http.Handler interface.
func (md Markdown) ServeHTTP(w http.ResponseWriter, r *http.Request) (int, error) {
	var cfg *Config
	for _, c := range md.Configs {
		if middleware.Path(r.URL.Path).Matches(c.PathScope) { // not negated
			cfg = c
			break // or goto
		}
	}
	if cfg == nil {
		return md.Next.ServeHTTP(w, r) // exit early
	}

	// We only deal with HEAD/GET
	switch r.Method {
	case http.MethodGet, http.MethodHead:
	default:
		return http.StatusMethodNotAllowed, nil
	}

	var dirents []os.FileInfo
	var lastModTime time.Time
	fpath := r.URL.Path
	if idx, ok := middleware.IndexFile(md.FileSys, fpath, md.IndexFiles); ok {
		// We're serving a directory index file, which may be a markdown
		// file with a template.  Let's grab a list of files this directory
		// URL points to, and pass that in to any possible template invocations,
		// so that templates can customize the look and feel of a directory.
		fdp, err := md.FileSys.Open(fpath)
		switch {
		case err == nil: // nop
		case os.IsPermission(err):
			return http.StatusForbidden, err
		case os.IsExist(err):
			return http.StatusNotFound, nil
		default: // did we run out of FD?
			return http.StatusInternalServerError, err
		}
		defer fdp.Close()

		// Grab a possible set of directory entries.  Note, we do not check
		// for errors here (unreadable directory, for example).  It may
		// still be useful to have a directory template file, without the
		// directory contents being present.  Note, the directory's last
		// modification is also present here (entry ".").
		dirents, _ = fdp.Readdir(-1)
		for _, d := range dirents {
			lastModTime = latest(lastModTime, d.ModTime())
		}

		// Set path to found index file
		fpath = idx
	}

	// If not supported extension, pass on it
	if _, ok := cfg.Extensions[path.Ext(fpath)]; !ok {
		return md.Next.ServeHTTP(w, r)
	}

	// At this point we have a supported extension/markdown
	f, err := md.FileSys.Open(fpath)
	switch {
	case err == nil: // nop
	case os.IsPermission(err):
		return http.StatusForbidden, err
	case os.IsExist(err):
		return http.StatusNotFound, nil
	default: // did we run out of FD?
		return http.StatusInternalServerError, err
	}
	defer f.Close()

	if fs, err := f.Stat(); err != nil {
		return http.StatusGone, nil
	} else {
		lastModTime = latest(lastModTime, fs.ModTime())
	}

	ctx := middleware.Context{
		Root: md.FileSys,
		Req:  r,
		URL:  r.URL,
	}
	html, err := cfg.Markdown(title(fpath), f, dirents, ctx)
	if err != nil {
		return http.StatusInternalServerError, err
	}

	w.Header().Set("Content-Type", "text/html; charset=utf-8")
	w.Header().Set("Content-Length", strconv.FormatInt(int64(len(html)), 10))
	middleware.SetLastModifiedHeader(w, lastModTime)
	if r.Method == http.MethodGet {
		w.Write(html)
	}
	return http.StatusOK, nil
}
Example #10
0
func (h Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) (int, error) {
	for _, rule := range h.Rules {
		if !middleware.Path(r.URL.Path).Matches(rule.BasePath) {
			continue
		}

		// Check for index file
		fpath := r.URL.Path
		if idx, ok := middleware.IndexFile(h.FileSys, fpath, middleware.IndexPages); ok {
			fpath = idx
		}

		// TODO: Check extension. If .lua, assume whole file is Lua script.
		fileName := filepath.Join(h.Root, fpath)
		file, err := h.FileSys.Open(fileName)
		if err != nil {
			if os.IsNotExist(err) {
				return http.StatusNotFound, nil
			} else if os.IsPermission(err) {
				return http.StatusForbidden, nil
			}
			return http.StatusInternalServerError, err
		}
		defer file.Close()

		input, err := ioutil.ReadAll(file)
		if err != nil {
			return http.StatusInternalServerError, err
		}

		L := lua.NewState()
		defer L.Close()
		ctx := interpreter.NewContext(L, w)

		if err := interpreter.Interpret(L, input, &ctx.Out); err != nil {
			var errReport error

			ierr := err.(interpreter.InterpretationError)
			if lerr, ok := ierr.Err.(*lua.ApiError); ok {
				switch cause := lerr.Cause.(type) {
				case *parse.Error:
					errReport = fmt.Errorf("%s:%d (col %d): Syntax error near '%s'", fileName,
						cause.Pos.Line+ierr.LineOffset, cause.Pos.Column, cause.Token)
				case *lua.CompileError:
					errReport = fmt.Errorf("%s:%d: %s", fileName,
						cause.Line+ierr.LineOffset, cause.Message)
				default:
					errReport = fmt.Errorf("%s: %s", fileName, cause.Error())
				}
			}

			return http.StatusInternalServerError, errReport
		}

		for _, f := range ctx.Callbacks {
			err := f()
			if err != nil {
				// TODO
				fmt.Println(err)
			}
		}

		// Write the combined text to the http.ResponseWriter
		w.Write(ctx.Out.Bytes())

		return http.StatusOK, nil
	}

	return h.Next.ServeHTTP(w, r)
}