package caddyhugo
import (
"encoding/base64"
"errors"
"fmt"
"io"
"net/http"
"os"
"path"
"path/filepath"
"strings"
"git.stephensearles.com/stephen/caddy-hugo2/assets"
"github.com/caddyserver/caddy/v2/modules/caddyhttp"
"github.com/spf13/afero"
)
func ( ch * CaddyHugo ) ServeHTTP ( w http . ResponseWriter , r * http . Request , next caddyhttp . Handler ) error {
if ! ch . Match ( r ) {
p := path . Join ( ch . Dir , "public" , r . URL . Path )
http . ServeFile ( w , r , p )
return nil
}
if ch . Comments != nil && strings . HasSuffix ( r . URL . Path , "/comments" ) {
docName := docNameFromCommentRequest ( r )
err := ch . Comments . ServeComments ( docName , w , r )
if err != nil {
return fmt . Errorf ( "couldn't load comments:" , err )
}
return nil
}
if r . URL . Path == "/login" {
return ch . commentsLogin ( r , w )
}
if strings . HasPrefix ( r . URL . Path , "/hugo/publish" ) {
err := ch . Publish ( )
if err != nil {
return err
}
http . Redirect ( w , r , "/" , http . StatusFound )
return nil
}
if strings . HasPrefix ( r . URL . Path , "/hugo/simplemde.css" ) {
w . Write ( assets . MustAsset ( "simplemde/dist/simplemde.min.css" ) )
return nil
}
if strings . HasPrefix ( r . URL . Path , "/hugo/simplemde.js" ) {
w . Write ( assets . MustAsset ( "simplemde/debug/simplemde.js" ) )
return nil
}
if strings . HasPrefix ( r . URL . Path , "/hugo/vue.js" ) {
w . Write ( assets . MustAsset ( "js/vue.js" ) )
return nil
}
if strings . HasPrefix ( r . URL . Path , "/hugo/moment.js" ) {
w . Write ( assets . MustAsset ( "js/moment.js" ) )
return nil
}
if strings . HasPrefix ( r . URL . Path , "/hugo/font-awesome.css" ) {
w . Write ( assets . MustAsset ( "css/font-awesome.min.css" ) )
return nil
}
if strings . HasPrefix ( r . URL . Path , "/hugo/admin" ) {
return ch . Admin ( ) . ServeHTTP ( w , r )
}
if strings . HasPrefix ( r . URL . Path , "/hugo/author" ) {
return ch . AuthorHome ( ) . ServeHTTP ( w , r )
}
if strings . HasPrefix ( r . URL . Path , "/hugo/edit/" ) {
return ch . Edit ( ) . ServeHTTP ( w , r )
}
if strings . HasPrefix ( r . URL . Path , "/hugo/draft/" ) {
return ch . serveDraft ( w , r )
}
if strings . HasPrefix ( r . URL . Path , "/hugo/media" ) {
return ch . serveMediaPage ( w , r )
}
if strings . HasPrefix ( r . URL . Path , "/hugo/upload" ) {
return ch . uploadMedia ( w , r )
}
if strings . HasPrefix ( r . URL . Path , "/media/" ) {
return ch . serveMedia ( w , r )
}
if strings . HasPrefix ( r . URL . Path , "/hugo/fs/" ) {
printTree ( afero . NewOsFs ( ) , w , ch . Dir )
return nil
}
return next . ServeHTTP ( w , r )
}
func ( ch * CaddyHugo ) ServeNewContent ( w http . ResponseWriter , r * http . Request ) error {
name := r . FormValue ( "name" )
ctype := r . FormValue ( "type" )
filename , err := ch . NewContent ( name , ctype )
if err != nil {
fmt . Println ( "error creating new content:" , err )
return err
}
// serve redirect
http . Redirect ( w , r , filepath . Join ( "/hugo/edit/" , "content" , filename ) , http . StatusFound )
return nil
}
func ( ch * CaddyHugo ) Auth ( r * http . Request ) bool {
// this is handled upstream by the caddy configuration
return true
}
func ( ch * CaddyHugo ) Match ( r * http . Request ) bool {
if strings . HasPrefix ( r . URL . Path , "/media/" ) {
return true
}
if strings . HasSuffix ( r . URL . Path , "/comments" ) {
return true
}
if r . URL . Path == "/login" {
return true
}
if r . URL . Path == "/hugo" {
return true
}
return strings . HasPrefix ( r . URL . Path , "/hugo/" )
}
func ( ch * CaddyHugo ) Admin ( ) caddyhttp . Handler {
return caddyhttp . HandlerFunc ( func ( w http . ResponseWriter , r * http . Request ) error {
err := ch . adminTmpl . Execute ( w , ch . TmplData ( r , nil ) )
if err != nil {
fmt . Println ( err )
return err
}
return nil
} )
}
func ( ch * CaddyHugo ) AuthorHome ( ) caddyhttp . Handler {
return caddyhttp . HandlerFunc ( func ( w http . ResponseWriter , r * http . Request ) error {
td := ch . TmplData ( r , nil )
err := ch . authorTmpl . Execute ( w , td )
if err != nil {
fmt . Println ( err )
return err
}
return nil
} )
}
func ( ch * CaddyHugo ) Edit ( ) caddyhttp . Handler {
return caddyhttp . HandlerFunc ( func ( w http . ResponseWriter , r * http . Request ) error {
if r . URL . Path == "/hugo/edit/new" {
return ch . ServeNewContent ( w , r )
}
if r . Header . Get ( "Upgrade" ) == "websocket" {
return ch . DeltaWebsocket ( w , r )
}
doc , err := ch . editSession ( docNameFromEditRequest ( r ) )
if err != nil {
fmt . Println ( err )
http . Error ( w , err . Error ( ) , http . StatusNotFound )
return err
}
err = ch . editTmpl . Execute ( w , ch . TmplData ( r , doc ) )
if err != nil {
fmt . Println ( err )
return err
}
return nil
} )
}
func ( ch * CaddyHugo ) serveDraft ( w http . ResponseWriter , r * http . Request ) error {
pathSegments := strings . SplitN ( r . URL . Path , "/" , 5 )
if len ( pathSegments ) < 4 {
return errors . New ( "not found" )
}
encoded := pathSegments [ 3 ]
nameBytes , err := base64 . RawURLEncoding . DecodeString ( encoded )
if err != nil {
return errors . New ( "not found" )
}
name := string ( nameBytes )
ch . mtx . Lock ( )
defer ch . mtx . Unlock ( )
docref , ok := ch . docs [ ch . docFilename ( name ) ]
if ! ok {
return fmt . Errorf ( "draft not found" )
}
r . URL . Path = strings . ToLower ( r . URL . Path )
prefix := "/hugo/draft/" + encoded
r . URL . Path = path . Join ( "public" , r . URL . Path [ len ( prefix ) : ] )
page := ch . HugoSites . GetContentPage ( ch . docFilename ( name ) )
if page == nil {
fmt . Fprintf ( w , "can't find %q to display a draft" , name )
return fmt . Errorf ( "draft not found" )
}
r . URL . Path = page . RelPermalink ( )
http . FileServer ( aferoHTTP { afero . NewBasePathFs ( docref . tmpfs , path . Join ( ch . Dir , "public" ) ) } ) . ServeHTTP ( w , r )
return nil
}
func printTree ( fs afero . Fs , w io . Writer , dir string ) {
const (
Line = " │ "
Tab = " "
Elbow = " └─"
Tee = " ├─"
)
wd , _ := os . Getwd ( )
fmt . Fprintln ( w , wd )
if dir == "" {
dir = "/"
}
openDirs := map [ string ] bool { }
lastFiles := map [ string ] string { }
afero . Walk ( fs , dir , filepath . WalkFunc ( func ( p string , info os . FileInfo , err error ) error {
if strings . HasPrefix ( p , "./" ) {
p = p [ 2 : ]
}
openDirs [ filepath . Dir ( p ) ] = true
lastFiles [ filepath . Dir ( p ) ] = filepath . Base ( p )
return nil
} ) )
afero . Walk ( fs , dir , filepath . WalkFunc ( func ( p string , info os . FileInfo , err error ) error {
if err != nil {
return err
}
if strings . HasPrefix ( p , "./" ) {
p = p [ 2 : ]
}
if filepath . Base ( p ) [ 0 ] == '.' && info . IsDir ( ) {
return filepath . SkipDir
}
entry := Tee
if lastFiles [ filepath . Dir ( p ) ] == filepath . Base ( p ) {
openDirs [ filepath . Dir ( p ) ] = false
entry = Elbow
}
indent := ""
dirs := strings . Split ( p , string ( filepath . Separator ) )
dirs = dirs [ : len ( dirs ) - 1 ]
for i := range dirs {
if openDirs [ filepath . Join ( dirs [ : i ] ... ) ] {
indent += Line
} else {
indent += Tab
}
}
fmt . Fprintf ( w , "%s%s %s (%s)\n" , indent , entry , filepath . Base ( p ) , p )
return nil
} ) )
}
type aferoHTTP struct {
afero . Fs
}
func ( a aferoHTTP ) Open ( name string ) ( http . File , error ) {
af , err := a . Fs . Open ( name )
if os . IsExist ( err ) {
err = os . ErrExist
}
if os . IsNotExist ( err ) {
err = os . ErrNotExist
}
return af , err
}
func ( ch * CaddyHugo ) commentsLogin ( r * http . Request , w http . ResponseWriter ) error {
if ch . Comments == nil {
return nil
}
_ , ok := ch . Comments . User ( r )
if ! ok {
w . Header ( ) . Set ( "WWW-Authenticate" , ` Basic realm="Log in with your name and the password. Ask Dan or Stephen for the password." ` )
w . WriteHeader ( 401 )
fmt . Fprintf ( w , "<html><body>Log in with your name and the password. Ask Dan or Stephen for the password. <a href=%q>go back</a></body></html>" , r . Referer ( ) )
return nil
}
http . Redirect ( w , r , r . Referer ( ) , http . StatusFound )
return nil
}