package kyra import ( "surdeus.su/util/kyra/mdx" httpx "surdeus.su/core/xgo/v2/stdlib/http" "surdeus.su/core/xgo/v2/stdlib" "surdeus.su/util/gopp" "bytes" "path/filepath" "net/http" "context" "path" "mime" "log" "os" "io" ) var _ = http.Handler(&Handler{}) // The type describes behaviour // of handling stuff like in PHP. type Handler struct { // THe field represents // where we store site's // source files and request handlers. sourcePath string // Preprocessor must be set by user // to be able to bring custom features in. pp *gopp.Preprocessor // Aditional extension. ".kr" by default. // For example "file.html.kr" will be // first preprocessed by gopp and sent back as simple HTML. ext string // The global map accessable from all the // request so you can keep the states // between requests. global any md *mdx.Markdown indexSuffix string } // Returns the new Handler with // specified preprocessor, source path, // preprocessor extension and the global value. func NewHandler( pp *gopp.Preprocessor, src, ext, index string, global any, ) *Handler { ret := &Handler{} ret.sourcePath = src ret.ext = ext ret.pp = pp ret.global = global ret.indexSuffix = index return ret } // Returns the default gopp.Preprocessor suitable for // the most needs. func DefaultPP(mod string) *gopp.Preprocessor { t := gopp.NewXGo().SetPreCompile(func( ctx context.Context, s *gopp.Script, ) { s.SetImportDir(mod) modules := stdlib.GetModuleMap(stdlib.AllModuleNames()...) modules.Add("markdown", mdx.GetModule()) s.SetImports(modules) s.EnableFileImport(true) vals := ctx.Value(KeyValues).(*ContextValues) s.Add("__http_request__", vals.Request) s.Add("__global__", vals.Global) s.Add("__markdown__", vals.Markdown) s.Add("__is_index__", vals.IsIndex) s.Add("__index_suffix__", vals.IndexSuffix) s.Add("__pp_ext__", vals.PPExt) s.Add("__source_path__", vals.SourcePath) s.Add("__fext__", vals.FExt) s.Add("__fpath__", vals.FPath) }).SetPreCode(func(ctx context.Context) []byte { return []byte(` __os__ := import("os") markdown := func(...args) { return pp.write_raw(__markdown__(args...)) } __http__ := immutable({ request : __http_request__ }) context.http = __http__ context.pp = pp context.global = __global__ context.is_index = __is_index__ context.index_suffix = __index_suffix__ context.pp_ext = __pp_ext__ context.source_path = __source_path__ context.fext = __fext__ context.fpath = __fpath__ import("./pre")(context) `) }).SetPostCode(func(ctx context.Context) []byte { return []byte(` import("./post")(context) `) }) return gopp.New(t) } func (h *Handler) SetMD(md *mdx.Markdown) *Handler { h.md = md return h } func (h *Handler) ServeHTTP( w http.ResponseWriter, r *http.Request, ) { isIndex := false shouldProcess := true urlPath := r.URL.Path // Cleaning URL path to prevent injections. urlPath = path.Clean(urlPath) urlExt := path.Ext(urlPath) if urlExt == h.ext { http.NotFound(w, r) return } filePath := filepath.Join( filepath.FromSlash(h.sourcePath), filepath.FromSlash(urlPath), ) fileStat, err := os.Stat(filePath) if err == nil { if fileStat.IsDir() { filePath = filepath.Join( filePath, h.indexSuffix, ) isIndex = true } } filePathTpp := filePath + h.ext //log.Println("pth:", urlPath, filePathTpp) file, err := os.Open(filePathTpp) if err != nil { shouldProcess = false file, err = os.Open(filePath) if err != nil { http.NotFound(w, r) return } } fext := filepath.Ext(filePath) fpath := filePath ctx := context.WithValue( r.Context(), KeyValues, &ContextValues{ Request: &httpx.Request{ Request: r, }, Global: h.global, Markdown: h.md, IsIndex: isIndex, IndexSuffix: h.indexSuffix, PPExt: h.ext, SourcePath: h.sourcePath, FPath: fpath, FExt: fext, }, ) // Setting before the code to let it change own // content type? fileExt := filepath.Ext(filePath) contentType := mime.TypeByExtension(fileExt) w.Header().Set("Content-Type", contentType) var buf bytes.Buffer var reader io.Reader if shouldProcess { compiled, err := h.pp.Compile( ctx, file, ) if err != nil { log.Printf("gopp.Compile(...): %s\n", err) http.Error(w, err.Error(), 500) return } err = h.pp.XGo().RunContext( ctx, compiled, filePath, &buf, ) if err != nil { log.Printf("gopp.RunContext(...): %s\n", err) http.Error(w, err.Error(), 500) return } reader = &buf } else { reader = file } io.Copy(w, reader) }