diff options
author | Steve <nilslice@gmail.com> | 2016-12-28 16:23:44 -0800 |
---|---|---|
committer | GitHub <noreply@github.com> | 2016-12-28 16:23:44 -0800 |
commit | 806fdbe1e8839feb1bcc4e5e07aa7c144a429901 (patch) | |
tree | dacaed91abb57bc6891c1b2cb58a82fc0c9610fc | |
parent | 1f7a5b46b906da00ceccb03e7ff26627bb29a3dd (diff) | |
parent | 5c340ca57e876a556a5b57e5a7dd32b0ae288440 (diff) |
Merge pull request #26 from ponzu-cms/ponzu-dev
[tooling] Add Go command flag, auto-generate self-signed SSL for development
-rw-r--r-- | cmd/ponzu/main.go | 162 | ||||
-rw-r--r-- | cmd/ponzu/options.go | 15 | ||||
-rw-r--r-- | cmd/ponzu/usage.go | 129 | ||||
-rw-r--r-- | cmd/ponzu/vendor/golang.org/x/net/context/context_test.go | 40 | ||||
-rw-r--r-- | management/editor/dom.go | 258 | ||||
-rw-r--r-- | management/editor/editor.go | 54 | ||||
-rw-r--r-- | management/editor/elements.go | 34 | ||||
-rw-r--r-- | management/editor/repeaters.go | 94 | ||||
-rw-r--r-- | system/api/handlers.go | 14 | ||||
-rw-r--r-- | system/db/config.go | 5 | ||||
-rw-r--r-- | system/tls/devcerts.go | 148 | ||||
-rw-r--r-- | system/tls/enable.go | 6 | ||||
-rw-r--r-- | system/tls/enabledev.go | 29 |
13 files changed, 722 insertions, 266 deletions
diff --git a/cmd/ponzu/main.go b/cmd/ponzu/main.go index d2e8b97..440ce70 100644 --- a/cmd/ponzu/main.go +++ b/cmd/ponzu/main.go @@ -8,7 +8,6 @@ import ( "os" "os/exec" "strings" - "time" "github.com/ponzu-cms/ponzu/system/admin" "github.com/ponzu-cms/ponzu/system/api" @@ -20,150 +19,29 @@ import ( _ "github.com/ponzu-cms/ponzu/content" ) -var year = fmt.Sprintf("%d", time.Now().Year()) - -var usageHeader = ` -$ ponzu [flags] command <params> - -Ponzu is a powerful and efficient open-source "Content-as-a-Service" system -framework and CMS. It provides automatic, free, and secure HTTP/2 over TLS -(certificates obtained via Let's Encrypt - https://letsencrypt.org), a useful -CMS and scaffolding to generate content editors, and a fast HTTP API on which -to build modern applications. - -Ponzu is released under the BSD-3-Clause license (see LICENSE). -(c) ` + year + ` Boss Sauce Creative, LLC - -COMMANDS - -` - -var usageHelp = ` -help, h (command): - - Help command will print the usage for Ponzu, or if a command is entered, it - will show only the usage for that specific command. - - Example: - $ ponzu help generate - - -` - -var usageNew = ` -new <directory>: - - Creates a 'ponzu' directory, or one by the name supplied as a parameter - immediately following the 'new' option in the $GOPATH/src directory. Note: - 'new' depends on the program 'git' and possibly a network connection. If - there is no local repository to clone from at the local machine's $GOPATH, - 'new' will attempt to clone the 'github.com/ponzu-cms/ponzu' package from - over the network. - - Example: - $ ponzu new myProject - > New ponzu project created at $GOPATH/src/myProject - - Errors will be reported, but successful commands retrun nothing. - - -` - -var usageGenerate = ` -generate, gen, g <type (,...fields)>: - - Generate a content type file with boilerplate code to implement - the editor.Editable interface. Must be given one (1) parameter of - the name of the type for the new content. The fields following a - type determine the field names and types of the content struct to - be generated. These must be in the following format: - fieldName:"T" - - Example: - $ ponzu gen review title:"string" body:"string" rating:"int" tags:"[]string" - - The command above will generate a file 'content/review.go' with boilerplate - methods, as well as struct definition, and cooresponding field tags like: - - type Review struct { - Title string ` + "`json:" + `"title"` + "`" + ` - Body string ` + "`json:" + `"body"` + "`" + ` - Rating int ` + "`json:" + `"rating"` + "`" + ` - Tags []string ` + "`json:" + `"tags"` + "`" + ` - } - - The generate command will intelligently parse more sophisticated field names - such as 'field_name' and convert it to 'FieldName' and vice versa, only where - appropriate as per common Go idioms. Errors will be reported, but successful - generate commands retrun nothing. - - -` - -var usageBuild = ` -build - - From within your Ponzu project directory, running build will copy and move - the necessary files from your workspace into the vendored directory, and - will build/compile the project to then be run. - - Example: - $ ponzu build - - Errors will be reported, but successful build commands return nothing. - -` - -var usageRun = ` -[[--port=8080] [--https]] run <service(,service)>: - - Starts the 'ponzu' HTTP server for the JSON API, Admin System, or both. - The segments, separated by a comma, describe which services to start, either - 'admin' (Admin System / CMS backend) or 'api' (JSON API), and, optionally, - if the server should utilize TLS encryption - served over HTTPS, which is - automatically managed using Let's Encrypt (https://letsencrypt.org) - - Example: - $ ponzu run - (or) - $ ponzu --port=8080 --https run admin,api - (or) - $ ponzu run admin - (or) - $ ponzu --port=8888 run api - - Defaults to '--port=8080 run admin,api' (running Admin & API on port 8080, without TLS) - - Note: - Admin and API cannot run on separate processes unless you use a copy of the - database, since the first process to open it receives a lock. If you intend - to run the Admin and API on separate processes, you must call them with the - 'ponzu' command independently. - - -` - var ( - usage = usageHeader + usageNew + usageGenerate + usageBuild + usageRun - port int - https bool + usage = usageHeader + usageNew + usageGenerate + usageBuild + usageRun + port int + https bool + devhttps bool // for ponzu internal / core development - dev bool - fork string + dev bool + fork string + gocmd string ) -func init() { +func main() { flag.Usage = func() { fmt.Println(usage) } -} -func main() { flag.IntVar(&port, "port", 8080, "port for ponzu to bind its listener") flag.BoolVar(&https, "https", false, "enable automatic TLS/SSL certificate management") + flag.BoolVar(&devhttps, "devhttps", false, "[dev environment] enable automatic TLS/SSL certificate management") flag.BoolVar(&dev, "dev", false, "modify environment for Ponzu core development") flag.StringVar(&fork, "fork", "", "modify repo source for Ponzu core development") + flag.StringVar(&gocmd, "gocmd", "go", "custom go command if using beta or new release of Go") flag.Parse() args := flag.Args() @@ -238,6 +116,10 @@ func main() { addTLS = "--https=false" } + if devhttps { + addTLS = "--devhttps" + } + var services string if len(args) > 1 { services = args[1] @@ -289,9 +171,21 @@ func main() { } } - if https { + // cannot run production HTTPS and development HTTPS together + if devhttps { + fmt.Println("Enabling self-signed HTTPS... [DEV]") + + go tls.EnableDev() + fmt.Println("Server listening on https://localhost:10443 for requests... [DEV]") + fmt.Println("----") + fmt.Println("If your browser rejects HTTPS requests, try allowing insecure connections on localhost.") + fmt.Println("on Chrome, visit chrome://flags/#allow-insecure-localhost") + + } else if https { fmt.Println("Enabling HTTPS...") - tls.Enable() + + go tls.Enable() + fmt.Println("Server listening on :443 for HTTPS requests...") } // save the port the system is listening on so internal system can make diff --git a/cmd/ponzu/options.go b/cmd/ponzu/options.go index 7e37255..1316a67 100644 --- a/cmd/ponzu/options.go +++ b/cmd/ponzu/options.go @@ -271,10 +271,17 @@ func buildPonzuServer(args []string) error { } // execute go build -o ponzu-cms cmd/ponzu/*.go - mainPath := filepath.Join(pwd, "cmd", "ponzu", "main.go") - optsPath := filepath.Join(pwd, "cmd", "ponzu", "options.go") - genPath := filepath.Join(pwd, "cmd", "ponzu", "generate.go") - build := exec.Command("go", "build", "-o", "ponzu-server", mainPath, optsPath, genPath) + buildOptions := []string{"build", "-o", "ponzu-server"} + cmdBuildFiles := []string{"main.go", "options.go", "generate.go", "usage.go"} + var cmdBuildFilePaths []string + for _, file := range cmdBuildFiles { + p := filepath.Join(pwd, "cmd", "ponzu", file) + cmdBuildFilePaths = append(cmdBuildFilePaths, p) + } + // mainPath := filepath.Join(pwd, "cmd", "ponzu", "main.go") + // optsPath := filepath.Join(pwd, "cmd", "ponzu", "options.go") + // genPath := filepath.Join(pwd, "cmd", "ponzu", "generate.go") + build := exec.Command(gocmd, append(buildOptions, cmdBuildFilePaths...)...) build.Stderr = os.Stderr build.Stdout = os.Stdout diff --git a/cmd/ponzu/usage.go b/cmd/ponzu/usage.go new file mode 100644 index 0000000..2dca46d --- /dev/null +++ b/cmd/ponzu/usage.go @@ -0,0 +1,129 @@ +package main + +import ( + "fmt" + "time" +) + +var year = fmt.Sprintf("%d", time.Now().Year()) + +var usageHeader = ` +$ ponzu [flags] command <params> + +Ponzu is a powerful and efficient open-source "Content-as-a-Service" system +framework and CMS. It provides automatic, free, and secure HTTP/2 over TLS +(certificates obtained via Let's Encrypt - https://letsencrypt.org), a useful +CMS and scaffolding to generate content editors, and a fast HTTP API on which +to build modern applications. + +Ponzu is released under the BSD-3-Clause license (see LICENSE). +(c) ` + year + ` Boss Sauce Creative, LLC + +COMMANDS + +` + +var usageHelp = ` +help, h (command): + + Help command will print the usage for Ponzu, or if a command is entered, it + will show only the usage for that specific command. + + Example: + $ ponzu help generate + + +` + +var usageNew = ` +new <directory>: + + Creates a 'ponzu' directory, or one by the name supplied as a parameter + immediately following the 'new' option in the $GOPATH/src directory. Note: + 'new' depends on the program 'git' and possibly a network connection. If + there is no local repository to clone from at the local machine's $GOPATH, + 'new' will attempt to clone the 'github.com/ponzu-cms/ponzu' package from + over the network. + + Example: + $ ponzu new myProject + > New ponzu project created at $GOPATH/src/myProject + + Errors will be reported, but successful commands retrun nothing. + + +` + +var usageGenerate = ` +generate, gen, g <type (,...fields)>: + + Generate a content type file with boilerplate code to implement + the editor.Editable interface. Must be given one (1) parameter of + the name of the type for the new content. The fields following a + type determine the field names and types of the content struct to + be generated. These must be in the following format: + fieldName:"T" + + Example: + $ ponzu gen review title:"string" body:"string" rating:"int" tags:"[]string" + + The command above will generate a file 'content/review.go' with boilerplate + methods, as well as struct definition, and cooresponding field tags like: + + type Review struct { + Title string ` + "`json:" + `"title"` + "`" + ` + Body string ` + "`json:" + `"body"` + "`" + ` + Rating int ` + "`json:" + `"rating"` + "`" + ` + Tags []string ` + "`json:" + `"tags"` + "`" + ` + } + + The generate command will intelligently parse more sophisticated field names + such as 'field_name' and convert it to 'FieldName' and vice versa, only where + appropriate as per common Go idioms. Errors will be reported, but successful + generate commands retrun nothing. + + +` + +var usageBuild = ` +build + + From within your Ponzu project directory, running build will copy and move + the necessary files from your workspace into the vendored directory, and + will build/compile the project to then be run. + + Example: + $ ponzu build + + Errors will be reported, but successful build commands return nothing. + +` + +var usageRun = ` +[[--port=8080] [--https]] run <service(,service)>: + + Starts the 'ponzu' HTTP server for the JSON API, Admin System, or both. + The segments, separated by a comma, describe which services to start, either + 'admin' (Admin System / CMS backend) or 'api' (JSON API), and, optionally, + if the server should utilize TLS encryption - served over HTTPS, which is + automatically managed using Let's Encrypt (https://letsencrypt.org) + + Example: + $ ponzu run + (or) + $ ponzu --port=8080 --https run admin,api + (or) + $ ponzu run admin + (or) + $ ponzu --port=8888 run api + + Defaults to '--port=8080 run admin,api' (running Admin & API on port 8080, without TLS) + + Note: + Admin and API cannot run on separate processes unless you use a copy of the + database, since the first process to open it receives a lock. If you intend + to run the Admin and API on separate processes, you must call them with the + 'ponzu' command independently. + + +` diff --git a/cmd/ponzu/vendor/golang.org/x/net/context/context_test.go b/cmd/ponzu/vendor/golang.org/x/net/context/context_test.go index 9554dcf..6284413 100644 --- a/cmd/ponzu/vendor/golang.org/x/net/context/context_test.go +++ b/cmd/ponzu/vendor/golang.org/x/net/context/context_test.go @@ -243,45 +243,51 @@ func testDeadline(c Context, wait time.Duration, t *testing.T) { } func TestDeadline(t *testing.T) { - c, _ := WithDeadline(Background(), time.Now().Add(100*time.Millisecond)) + t.Parallel() + const timeUnit = 500 * time.Millisecond + c, _ := WithDeadline(Background(), time.Now().Add(1*timeUnit)) if got, prefix := fmt.Sprint(c), "context.Background.WithDeadline("; !strings.HasPrefix(got, prefix) { t.Errorf("c.String() = %q want prefix %q", got, prefix) } - testDeadline(c, 200*time.Millisecond, t) + testDeadline(c, 2*timeUnit, t) - c, _ = WithDeadline(Background(), time.Now().Add(100*time.Millisecond)) + c, _ = WithDeadline(Background(), time.Now().Add(1*timeUnit)) o := otherContext{c} - testDeadline(o, 200*time.Millisecond, t) + testDeadline(o, 2*timeUnit, t) - c, _ = WithDeadline(Background(), time.Now().Add(100*time.Millisecond)) + c, _ = WithDeadline(Background(), time.Now().Add(1*timeUnit)) o = otherContext{c} - c, _ = WithDeadline(o, time.Now().Add(300*time.Millisecond)) - testDeadline(c, 200*time.Millisecond, t) + c, _ = WithDeadline(o, time.Now().Add(3*timeUnit)) + testDeadline(c, 2*timeUnit, t) } func TestTimeout(t *testing.T) { - c, _ := WithTimeout(Background(), 100*time.Millisecond) + t.Parallel() + const timeUnit = 500 * time.Millisecond + c, _ := WithTimeout(Background(), 1*timeUnit) if got, prefix := fmt.Sprint(c), "context.Background.WithDeadline("; !strings.HasPrefix(got, prefix) { t.Errorf("c.String() = %q want prefix %q", got, prefix) } - testDeadline(c, 200*time.Millisecond, t) + testDeadline(c, 2*timeUnit, t) - c, _ = WithTimeout(Background(), 100*time.Millisecond) + c, _ = WithTimeout(Background(), 1*timeUnit) o := otherContext{c} - testDeadline(o, 200*time.Millisecond, t) + testDeadline(o, 2*timeUnit, t) - c, _ = WithTimeout(Background(), 100*time.Millisecond) + c, _ = WithTimeout(Background(), 1*timeUnit) o = otherContext{c} - c, _ = WithTimeout(o, 300*time.Millisecond) - testDeadline(c, 200*time.Millisecond, t) + c, _ = WithTimeout(o, 3*timeUnit) + testDeadline(c, 2*timeUnit, t) } func TestCanceledTimeout(t *testing.T) { - c, _ := WithTimeout(Background(), 200*time.Millisecond) + t.Parallel() + const timeUnit = 500 * time.Millisecond + c, _ := WithTimeout(Background(), 2*timeUnit) o := otherContext{c} - c, cancel := WithTimeout(o, 400*time.Millisecond) + c, cancel := WithTimeout(o, 4*timeUnit) cancel() - time.Sleep(100 * time.Millisecond) // let cancelation propagate + time.Sleep(1 * timeUnit) // let cancelation propagate select { case <-c.Done(): default: diff --git a/management/editor/dom.go b/management/editor/dom.go index cf36ad9..41aafa7 100644 --- a/management/editor/dom.go +++ b/management/editor/dom.go @@ -3,13 +3,14 @@ package editor import ( "bytes" "html" + "log" "strings" ) type element struct { - TagName string - Attrs map[string]string - Name string + tagName string + attrs map[string]string + name string label string data string viewBuf *bytes.Buffer @@ -17,9 +18,9 @@ type element struct { func newElement(tagName, label, fieldName string, p interface{}, attrs map[string]string) *element { return &element{ - TagName: tagName, - Attrs: attrs, - Name: tagNameFromStructField(fieldName, p), + tagName: tagName, + attrs: attrs, + name: tagNameFromStructField(fieldName, p), label: label, data: valueFromStructField(fieldName, p), viewBuf: &bytes.Buffer{}, @@ -29,108 +30,265 @@ func newElement(tagName, label, fieldName string, p interface{}, attrs map[strin // domElementSelfClose is a special DOM element which is parsed as a // self-closing tag and thus needs to be created differently func domElementSelfClose(e *element) []byte { - e.viewBuf.Write([]byte(`<div class="input-field col s12">`)) + _, err := e.viewBuf.WriteString(`<div class="input-field col s12">`) + if err != nil { + log.Println("Error writing HTML string to buffer: domElementSelfClose") + return nil + } + if e.label != "" { - e.viewBuf.Write([]byte(`<label class="active" for="` + strings.Join(strings.Split(e.label, " "), "-") + `">` + e.label + `</label>`)) + _, err = e.viewBuf.WriteString( + `<label class="active" for="` + + strings.Join(strings.Split(e.label, " "), "-") + `">` + e.label + + `</label>`) + if err != nil { + log.Println("Error writing HTML string to buffer: domElementSelfClose") + return nil + } + } + + _, err = e.viewBuf.WriteString(`<` + e.tagName + ` value="`) + if err != nil { + log.Println("Error writing HTML string to buffer: domElementSelfClose") + return nil + } + + _, err = e.viewBuf.WriteString(html.EscapeString(e.data) + `" `) + if err != nil { + log.Println("Error writing HTML string to buffer: domElementSelfClose") + return nil + } + + for attr, value := range e.attrs { + _, err := e.viewBuf.WriteString(attr + `="` + value + `" `) + if err != nil { + log.Println("Error writing HTML string to buffer: domElementSelfClose") + return nil + } + } + _, err = e.viewBuf.WriteString(` name="` + e.name + `" />`) + if err != nil { + log.Println("Error writing HTML string to buffer: domElementSelfClose") + return nil } - e.viewBuf.Write([]byte(`<` + e.TagName + ` value="`)) - e.viewBuf.Write([]byte(html.EscapeString(e.data) + `" `)) - for attr, value := range e.Attrs { - e.viewBuf.Write([]byte(attr + `="` + value + `" `)) + _, err = e.viewBuf.WriteString(`</div>`) + if err != nil { + log.Println("Error writing HTML string to buffer: domElementSelfClose") + return nil } - e.viewBuf.Write([]byte(` name="` + e.Name + `"`)) - e.viewBuf.Write([]byte(` />`)) - e.viewBuf.Write([]byte(`</div>`)) return e.viewBuf.Bytes() } // domElementCheckbox is a special DOM element which is parsed as a // checkbox input tag and thus needs to be created differently func domElementCheckbox(e *element) []byte { - e.viewBuf.Write([]byte(`<p class="col s6">`)) - e.viewBuf.Write([]byte(`<` + e.TagName + ` `)) + _, err := e.viewBuf.WriteString(`<p class="col s6">`) + if err != nil { + log.Println("Error writing HTML string to buffer: domElementCheckbox") + return nil + } + + _, err = e.viewBuf.WriteString(`<` + e.tagName + ` `) + if err != nil { + log.Println("Error writing HTML string to buffer: domElementCheckbox") + return nil + } - for attr, value := range e.Attrs { - e.viewBuf.Write([]byte(attr + `="` + value + `" `)) + for attr, value := range e.attrs { + _, err := e.viewBuf.WriteString(attr + `="` + value + `" `) + if err != nil { + log.Println("Error writing HTML string to buffer: domElementCheckbox") + return nil + } } - e.viewBuf.Write([]byte(` name="` + e.Name + `"`)) - e.viewBuf.Write([]byte(` /> `)) + _, err = e.viewBuf.WriteString(` name="` + e.name + `" />`) + if err != nil { + log.Println("Error writing HTML string to buffer: domElementCheckbox") + return nil + } + if e.label != "" { - e.viewBuf.Write([]byte(`<label for="` + strings.Join(strings.Split(e.label, " "), "-") + `">` + e.label + `</label>`)) + _, err = e.viewBuf.WriteString( + `<label for="` + + strings.Join(strings.Split(e.label, " "), "-") + `">` + + e.label + `</label>`) + if err != nil { + log.Println("Error writing HTML string to buffer: domElementCheckbox") + return nil + } } - e.viewBuf.Write([]byte(`</p>`)) + + _, err = e.viewBuf.WriteString(`</p>`) + if err != nil { + log.Println("Error writing HTML string to buffer: domElementCheckbox") + return nil + } + return e.viewBuf.Bytes() } // domElement creates a DOM element func domElement(e *element) []byte { - e.viewBuf.Write([]byte(`<div class="input-field col s12">`)) + _, err := e.viewBuf.WriteString(`<div class="input-field col s12">`) + if err != nil { + log.Println("Error writing HTML string to buffer: domElement") + return nil + } if e.label != "" { - e.viewBuf.Write([]byte(`<label class="active" for="` + strings.Join(strings.Split(e.label, " "), "-") + `">` + e.label + `</label>`)) + _, err = e.viewBuf.WriteString( + `<label class="active" for="` + + strings.Join(strings.Split(e.label, " "), "-") + `">` + e.label + + `</label>`) + if err != nil { + log.Println("Error writing HTML string to buffer: domElement") + return nil + } } - e.viewBuf.Write([]byte(`<` + e.TagName + ` `)) - for attr, value := range e.Attrs { - e.viewBuf.Write([]byte(attr + `="` + string(value) + `" `)) + _, err = e.viewBuf.WriteString(`<` + e.tagName + ` `) + if err != nil { + log.Println("Error writing HTML string to buffer: domElement") + return nil } - e.viewBuf.Write([]byte(` name="` + e.Name + `"`)) - e.viewBuf.Write([]byte(` >`)) - e.viewBuf.Write([]byte(html.EscapeString(e.data))) - e.viewBuf.Write([]byte(`</` + e.TagName + `>`)) + for attr, value := range e.attrs { + _, err = e.viewBuf.WriteString(attr + `="` + string(value) + `" `) + if err != nil { + log.Println("Error writing HTML string to buffer: domElement") + return nil + } + } + _, err = e.viewBuf.WriteString(` name="` + e.name + `" > `) + if err != nil { + log.Println("Error writing HTML string to buffer: domElement") + return nil + } + + _, err = e.viewBuf.WriteString(html.EscapeString(e.data)) + if err != nil { + log.Println("Error writing HTML string to buffer: domElement") + return nil + } + + _, err = e.viewBuf.WriteString(`</` + e.tagName + `>`) + if err != nil { + log.Println("Error writing HTML string to buffer: domElement") + return nil + } + + _, err = e.viewBuf.WriteString(`</div>`) + if err != nil { + log.Println("Error writing HTML string to buffer: domElement") + return nil + } - e.viewBuf.Write([]byte(`</div>`)) return e.viewBuf.Bytes() } func domElementWithChildrenSelect(e *element, children []*element) []byte { - e.viewBuf.Write([]byte(`<div class="input-field col s6">`)) + _, err := e.viewBuf.WriteString(`<div class="input-field col s6">`) + if err != nil { + log.Println("Error writing HTML string to buffer: domElementWithChildrenSelect") + return nil + } - e.viewBuf.Write([]byte(`<` + e.TagName + ` `)) + _, err = e.viewBuf.WriteString(`<` + e.tagName + ` `) + if err != nil { + log.Println("Error writing HTML string to buffer: domElementWithChildrenSelect") + return nil + } - for attr, value := range e.Attrs { - e.viewBuf.Write([]byte(attr + `="` + string(value) + `" `)) + for attr, value := range e.attrs { + _, err = e.viewBuf.WriteString(attr + `="` + value + `" `) + if err != nil { + log.Println("Error writing HTML string to buffer: domElementWithChildrenSelect") + return nil + } + } + _, err = e.viewBuf.WriteString(` name="` + e.name + `" >`) + if err != nil { + log.Println("Error writing HTML string to buffer: domElementWithChildrenSelect") + return nil } - e.viewBuf.Write([]byte(` name="` + e.Name + `"`)) - e.viewBuf.Write([]byte(` >`)) // loop over children and create domElement for each child for _, child := range children { - e.viewBuf.Write(domElement(child)) + _, err = e.viewBuf.Write(domElement(child)) + if err != nil { + log.Println("Error writing HTML domElement to buffer: domElementWithChildrenSelect") + return nil + } } - e.viewBuf.Write([]byte(`</` + e.TagName + `>`)) + _, err = e.viewBuf.WriteString(`</` + e.tagName + `>`) + if err != nil { + log.Println("Error writing HTML string to buffer: domElementWithChildrenSelect") + return nil + } if e.label != "" { - e.viewBuf.Write([]byte(`<label class="active">` + e.label + `</label>`)) + _, err = e.viewBuf.WriteString(`<label class="active">` + e.label + `</label>`) + if err != nil { + log.Println("Error writing HTML string to buffer: domElementWithChildrenSelect") + return nil + } + } + + _, err = e.viewBuf.WriteString(`</div>`) + if err != nil { + log.Println("Error writing HTML string to buffer: domElementWithChildrenSelect") + return nil } - e.viewBuf.Write([]byte(`</div>`)) return e.viewBuf.Bytes() } func domElementWithChildrenCheckbox(e *element, children []*element) []byte { - e.viewBuf.Write([]byte(`<` + e.TagName + ` `)) + _, err := e.viewBuf.WriteString(`<` + e.tagName + ` `) + if err != nil { + log.Println("Error writing HTML string to buffer: domElementWithChildrenCheckbox") + return nil + } - for attr, value := range e.Attrs { - e.viewBuf.Write([]byte(attr + `="` + value + `" `)) + for attr, value := range e.attrs { + _, err = e.viewBuf.WriteString(attr + `="` + value + `" `) + if err != nil { + log.Println("Error writing HTML string to buffer: domElementWithChildrenCheckbox") + return nil + } } - e.viewBuf.Write([]byte(` >`)) + _, err = e.viewBuf.WriteString(` >`) + if err != nil { + log.Println("Error writing HTML string to buffer: domElementWithChildrenCheckbox") + return nil + } if e.label != "" { - e.viewBuf.Write([]byte(`<label class="active">` + e.label + `</label>`)) + _, err = e.viewBuf.WriteString(`<label class="active">` + e.label + `</label>`) + if err != nil { + log.Println("Error writing HTML string to buffer: domElementWithChildrenCheckbox") + return nil + } } // loop over children and create domElement for each child for _, child := range children { - e.viewBuf.Write(domElementCheckbox(child)) + _, err = e.viewBuf.Write(domElementCheckbox(child)) + if err != nil { + log.Println("Error writing HTML domElementCheckbox to buffer: domElementWithChildrenCheckbox") + return nil + } } - e.viewBuf.Write([]byte(`</` + e.TagName + `><div class="clear padding"> </div>`)) + _, err = e.viewBuf.WriteString(`</` + e.tagName + `><div class="clear padding"> </div>`) + if err != nil { + log.Println("Error writing HTML string to buffer: domElementWithChildrenCheckbox") + return nil + } return e.viewBuf.Bytes() } diff --git a/management/editor/editor.go b/management/editor/editor.go index 7194c27..511edb2 100644 --- a/management/editor/editor.go +++ b/management/editor/editor.go @@ -4,6 +4,7 @@ package editor import ( "bytes" + "log" "net/http" ) @@ -38,16 +39,28 @@ func Form(post Editable, fields ...Field) ([]byte, error) { editor := post.Editor() editor.ViewBuf = &bytes.Buffer{} - editor.ViewBuf.Write([]byte(`<table><tbody class="row"><tr class="col s8"><td>`)) + _, err := editor.ViewBuf.WriteString(`<table><tbody class="row"><tr class="col s8"><td>`) + if err != nil { + log.Println("Error writing HTML string to editor Form buffer") + return nil, err + } for _, f := range fields { addFieldToEditorView(editor, f) } - editor.ViewBuf.Write([]byte(`</td></tr>`)) + _, err = editor.ViewBuf.WriteString(`</td></tr>`) + if err != nil { + log.Println("Error writing HTML string to editor Form buffer") + return nil, err + } // content items with Item embedded have some default fields we need to render - editor.ViewBuf.Write([]byte(`<tr class="col s4 default-fields"><td>`)) + _, err = editor.ViewBuf.WriteString(`<tr class="col s4 default-fields"><td>`) + if err != nil { + log.Println("Error writing HTML string to editor Form buffer") + return nil, err + } publishTime := ` <div class="row content-only __ponzu"> @@ -98,9 +111,16 @@ func Form(post Editable, fields ...Field) ([]byte, error) { </div> ` - editor.ViewBuf.Write([]byte(publishTime)) + _, err = editor.ViewBuf.WriteString(publishTime) + if err != nil { + log.Println("Error writing HTML string to editor Form buffer") + return nil, err + } - addPostDefaultFieldsToEditorView(post, editor) + err = addPostDefaultFieldsToEditorView(post, editor) + if err != nil { + return nil, err + } submit := ` <div class="input-field post-controls"> @@ -186,16 +206,26 @@ func Form(post Editable, fields ...Field) ([]byte, error) { }); </script> ` - editor.ViewBuf.Write([]byte(submit + script + `</td></tr></tbody></table>`)) + _, err = editor.ViewBuf.WriteString(submit + script + `</td></tr></tbody></table>`) + if err != nil { + log.Println("Error writing HTML string to editor Form buffer") + return nil, err + } return editor.ViewBuf.Bytes(), nil } -func addFieldToEditorView(e *Editor, f Field) { - e.ViewBuf.Write(f.View) +func addFieldToEditorView(e *Editor, f Field) error { + _, err := e.ViewBuf.Write(f.View) + if err != nil { + log.Println("Error writing field view to editor view buffer") + return err + } + + return nil } -func addPostDefaultFieldsToEditorView(p Editable, e *Editor) { +func addPostDefaultFieldsToEditorView(p Editable, e *Editor) error { defaults := []Field{ Field{ View: Input("Slug", p, map[string]string{ @@ -220,7 +250,11 @@ func addPostDefaultFieldsToEditorView(p Editable, e *Editor) { } for _, f := range defaults { - addFieldToEditorView(e, f) + err := addFieldToEditorView(e, f) + if err != nil { + return err + } } + return nil } diff --git a/management/editor/elements.go b/management/editor/elements.go index 873e81c..b82b220 100644 --- a/management/editor/elements.go +++ b/management/editor/elements.go @@ -11,7 +11,11 @@ import ( // The `fieldName` argument will cause a panic if it is not exactly the string // form of the struct field that this editor input is representing // type Person struct { +// item.Item +// editor editor.Editor +// // Name string `json:"name"` +// //... // } // // func (p *Person) MarshalEditor() ([]byte, error) { @@ -64,9 +68,9 @@ func Timestamp(fieldName string, p interface{}, attrs map[string]string) []byte } e := &element{ - TagName: "input", - Attrs: attrs, - Name: tagNameFromStructField(fieldName, p), + tagName: "input", + attrs: attrs, + name: tagNameFromStructField(fieldName, p), label: attrs["label"], data: data, viewBuf: &bytes.Buffer{}, @@ -161,9 +165,9 @@ func Richtext(fieldName string, p interface{}, attrs map[string]string) []byte { attrs["class"] = "richtext " + fieldName attrs["id"] = "richtext-" + fieldName div := &element{ - TagName: "div", - Attrs: attrs, - Name: "", + tagName: "div", + attrs: attrs, + name: "", label: "", data: "", viewBuf: &bytes.Buffer{}, @@ -261,16 +265,16 @@ func Select(fieldName string, p interface{}, attrs, options map[string]string) [ // provide a call to action for the select element cta := &element{ - TagName: "option", - Attrs: map[string]string{"disabled": "true", "selected": "true"}, + tagName: "option", + attrs: map[string]string{"disabled": "true", "selected": "true"}, data: "Select an option...", viewBuf: &bytes.Buffer{}, } // provide a selection reset (will store empty string in db) reset := &element{ - TagName: "option", - Attrs: map[string]string{"value": ""}, + tagName: "option", + attrs: map[string]string{"value": ""}, data: "None", viewBuf: &bytes.Buffer{}, } @@ -283,8 +287,8 @@ func Select(fieldName string, p interface{}, attrs, options map[string]string) [ optAttrs["selected"] = "true" } opt := &element{ - TagName: "option", - Attrs: optAttrs, + tagName: "option", + attrs: optAttrs, data: v, viewBuf: &bytes.Buffer{}, } @@ -328,9 +332,9 @@ func Checkbox(fieldName string, p interface{}, attrs, options map[string]string) // create a *element manually using the modified tagNameFromStructFieldMulti // func since this is for a multi-value name input := &element{ - TagName: "input", - Attrs: inputAttrs, - Name: tagNameFromStructFieldMulti(fieldName, i, p), + tagName: "input", + attrs: inputAttrs, + name: tagNameFromStructFieldMulti(fieldName, i, p), label: v, data: "", viewBuf: &bytes.Buffer{}, diff --git a/management/editor/repeaters.go b/management/editor/repeaters.go index 37fb982..617caee 100644 --- a/management/editor/repeaters.go +++ b/management/editor/repeaters.go @@ -3,6 +3,7 @@ package editor import ( "bytes" "fmt" + "log" "strings" ) @@ -13,7 +14,11 @@ import ( // The `fieldName` argument will cause a panic if it is not exactly the string // form of the struct field that this editor input is representing // type Person struct { +// item.Item +// editor editor.Editor +// // Names []string `json:"names"` +// //... // } // // func (p *Person) MarshalEditor() ([]byte, error) { @@ -35,12 +40,17 @@ func InputRepeater(fieldName string, p interface{}, attrs map[string]string) []b scope := tagNameFromStructField(fieldName, p) html := bytes.Buffer{} - html.WriteString(`<span class="__ponzu-repeat ` + scope + `">`) + _, err := html.WriteString(`<span class="__ponzu-repeat ` + scope + `">`) + if err != nil { + log.Println("Error writing HTML string to InputRepeater buffer") + return nil + } + for i, val := range vals { el := &element{ - TagName: "input", - Attrs: attrs, - Name: tagNameFromStructFieldMulti(fieldName, i, p), + tagName: "input", + attrs: attrs, + name: tagNameFromStructFieldMulti(fieldName, i, p), data: val, viewBuf: &bytes.Buffer{}, } @@ -50,9 +60,17 @@ func InputRepeater(fieldName string, p interface{}, attrs map[string]string) []b el.label = attrs["label"] } - html.Write(domElementSelfClose(el)) + _, err := html.Write(domElementSelfClose(el)) + if err != nil { + log.Println("Error writing domElementSelfClose to InputRepeater buffer") + return nil + } + } + _, err = html.WriteString(`</span>`) + if err != nil { + log.Println("Error writing HTML string to InputRepeater buffer") + return nil } - html.WriteString(`</span>`) return append(html.Bytes(), RepeatController(fieldName, p, "input", ".input-field")...) } @@ -68,7 +86,11 @@ func SelectRepeater(fieldName string, p interface{}, attrs, options map[string]s // <option value="{map key}">{map value}</option> scope := tagNameFromStructField(fieldName, p) html := bytes.Buffer{} - html.WriteString(`<span class="__ponzu-repeat ` + scope + `">`) + _, err := html.WriteString(`<span class="__ponzu-repeat ` + scope + `">`) + if err != nil { + log.Println("Error writing HTML string to SelectRepeater buffer") + return nil + } // find the field values in p to determine if an option is pre-selected fieldVals := valueFromStructField(fieldName, p) @@ -80,9 +102,9 @@ func SelectRepeater(fieldName string, p interface{}, attrs, options map[string]s if len(vals) > 0 { for i, val := range vals { sel := &element{ - TagName: "select", - Attrs: attrs, - Name: tagNameFromStructFieldMulti(fieldName, i, p), + tagName: "select", + attrs: attrs, + name: tagNameFromStructFieldMulti(fieldName, i, p), viewBuf: &bytes.Buffer{}, } @@ -96,16 +118,16 @@ func SelectRepeater(fieldName string, p interface{}, attrs, options map[string]s // provide a call to action for the select element cta := &element{ - TagName: "option", - Attrs: map[string]string{"disabled": "true", "selected": "true"}, + tagName: "option", + attrs: map[string]string{"disabled": "true", "selected": "true"}, data: "Select an option...", viewBuf: &bytes.Buffer{}, } // provide a selection reset (will store empty string in db) reset := &element{ - TagName: "option", - Attrs: map[string]string{"value": ""}, + tagName: "option", + attrs: map[string]string{"value": ""}, data: "None", viewBuf: &bytes.Buffer{}, } @@ -118,8 +140,8 @@ func SelectRepeater(fieldName string, p interface{}, attrs, options map[string]s optAttrs["selected"] = "true" } opt := &element{ - TagName: "option", - Attrs: optAttrs, + tagName: "option", + attrs: optAttrs, data: v, viewBuf: &bytes.Buffer{}, } @@ -127,11 +149,20 @@ func SelectRepeater(fieldName string, p interface{}, attrs, options map[string]s opts = append(opts, opt) } - html.Write(domElementWithChildrenSelect(sel, opts)) + _, err := html.Write(domElementWithChildrenSelect(sel, opts)) + if err != nil { + log.Println("Error writing domElementWithChildrenSelect to SelectRepeater buffer") + return nil + } } } - html.WriteString(`</span>`) + _, err = html.WriteString(`</span>`) + if err != nil { + log.Println("Error writing HTML string to SelectRepeater buffer") + return nil + } + return append(html.Bytes(), RepeatController(fieldName, p, "select", ".input-field")...) } @@ -223,14 +254,33 @@ func FileRepeater(fieldName string, p interface{}, attrs map[string]string) []by name := tagNameFromStructField(fieldName, p) html := bytes.Buffer{} - html.WriteString(`<span class="__ponzu-repeat ` + name + `">`) + _, err := html.WriteString(`<span class="__ponzu-repeat ` + name + `">`) + if err != nil { + log.Println("Error writing HTML string to FileRepeater buffer") + return nil + } + for i, val := range vals { className := fmt.Sprintf("%s-%d", name, i) nameidx := tagNameFromStructFieldMulti(fieldName, i, p) - html.WriteString(fmt.Sprintf(tmpl, nameidx, addLabelFirst(i, attrs["label"]), val, className, fieldName)) - html.WriteString(fmt.Sprintf(script, nameidx, className)) + + _, err := html.WriteString(fmt.Sprintf(tmpl, nameidx, addLabelFirst(i, attrs["label"]), val, className, fieldName)) + if err != nil { + log.Println("Error writing HTML string to FileRepeater buffer") + return nil + } + + _, err = html.WriteString(fmt.Sprintf(script, nameidx, className)) + if err != nil { + log.Println("Error writing HTML string to FileRepeater buffer") + return nil + } + } + _, err = html.WriteString(`</span>`) + if err != nil { + log.Println("Error writing HTML string to FileRepeater buffer") + return nil } - html.WriteString(`</span>`) return append(html.Bytes(), RepeatController(fieldName, p, "input.upload", "div.file-input."+fieldName)...) } diff --git a/system/api/handlers.go b/system/api/handlers.go index 788b2a0..7b59dbd 100644 --- a/system/api/handlers.go +++ b/system/api/handlers.go @@ -180,15 +180,6 @@ func toJSON(data []string) ([]byte, error) { return buf.Bytes(), nil } -func wrapJSON(json []byte) []byte { - var buf = &bytes.Buffer{} - buf.Write([]byte(`{"data":`)) - buf.Write(json) - buf.Write([]byte(`}`)) - - return buf.Bytes() -} - // sendData() should be used any time you want to communicate // data back to a foreign client func sendData(res http.ResponseWriter, data []byte, code int) { @@ -196,7 +187,10 @@ func sendData(res http.ResponseWriter, data []byte, code int) { res.Header().Set("Access-Control-Allow-Origin", "*") res.Header().Set("Content-Type", "application/json") res.WriteHeader(code) - res.Write(data) + _, err := res.Write(data) + if err != nil { + log.Println("Error writing to response in sendData") + } } // SendPreflight is used to respond to a cross-origin "OPTIONS" request diff --git a/system/db/config.go b/system/db/config.go index ce76021..45b3952 100644 --- a/system/db/config.go +++ b/system/db/config.go @@ -108,7 +108,10 @@ func ConfigAll() ([]byte, error) { val := &bytes.Buffer{} err := store.View(func(tx *bolt.Tx) error { b := tx.Bucket([]byte("__config")) - val.Write(b.Get([]byte("settings"))) + _, err := val.Write(b.Get([]byte("settings"))) + if err != nil { + return err + } return nil }) diff --git a/system/tls/devcerts.go b/system/tls/devcerts.go new file mode 100644 index 0000000..b41f099 --- /dev/null +++ b/system/tls/devcerts.go @@ -0,0 +1,148 @@ +// Copyright 2009 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Modified 2016 by Steve Manuel, Boss Sauce Creative, LLC +// All modifications are relicensed under the same BSD license +// found in the LICENSE file. + +// Generate a self-signed X.509 certificate for a TLS server. Outputs to +// 'devcerts/cert.pem' and 'devcerts/key.pem' and will overwrite existing files. + +package tls + +import ( + "crypto/ecdsa" + "crypto/rand" + "crypto/rsa" + "crypto/x509" + "crypto/x509/pkix" + "encoding/pem" + "fmt" + "log" + "math/big" + "net" + "os" + "path/filepath" + "time" + + "github.com/ponzu-cms/ponzu/system/db" +) + +func publicKey(priv interface{}) interface{} { + switch k := priv.(type) { + case *rsa.PrivateKey: + return &k.PublicKey + case *ecdsa.PrivateKey: + return &k.PublicKey + default: + return nil + } +} + +func pemBlockForKey(priv interface{}) *pem.Block { + switch k := priv.(type) { + case *rsa.PrivateKey: + return &pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(k)} + case *ecdsa.PrivateKey: + b, err := x509.MarshalECPrivateKey(k) + if err != nil { + fmt.Fprintf(os.Stderr, "Unable to marshal ECDSA private key: %v", err) + os.Exit(2) + } + return &pem.Block{Type: "EC PRIVATE KEY", Bytes: b} + default: + return nil + } +} + +func setupDev() { + var priv interface{} + var err error + + priv, err = rsa.GenerateKey(rand.Reader, 2048) + + if err != nil { + log.Fatalf("failed to generate private key: %s", err) + } + + notBefore := time.Now() + notAfter := notBefore.Add(time.Hour * 24 * 30) // valid for 30 days + + serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 128) + serialNumber, err := rand.Int(rand.Reader, serialNumberLimit) + if err != nil { + log.Fatalf("failed to generate serial number: %s", err) + } + + template := x509.Certificate{ + SerialNumber: serialNumber, + Subject: pkix.Name{ + Organization: []string{"Ponzu Dev Server"}, + }, + NotBefore: notBefore, + NotAfter: notAfter, + + KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature, + ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth}, + BasicConstraintsValid: true, + } + + hosts := []string{"localhost", "0.0.0.0"} + domain := db.ConfigCache("domain") + if domain != "" { + hosts = append(hosts, domain) + } + + for _, h := range hosts { + if ip := net.ParseIP(h); ip != nil { + template.IPAddresses = append(template.IPAddresses, ip) + } else { + template.DNSNames = append(template.DNSNames, h) + } + } + + // make all certs CA + template.IsCA = true + template.KeyUsage |= x509.KeyUsageCertSign + + derBytes, err := x509.CreateCertificate(rand.Reader, &template, &template, publicKey(priv), priv) + if err != nil { + log.Fatalln("Failed to create certificate:", err) + } + + // overwrite/create directory for devcerts + pwd, err := os.Getwd() + if err != nil { + log.Fatalln("Couldn't find working directory to locate or save dev certificates:", err) + } + + vendorTLSPath := filepath.Join(pwd, "cmd", "ponzu", "vendor", "github.com", "ponzu-cms", "ponzu", "system", "tls") + devcertsPath := filepath.Join(vendorTLSPath, "devcerts") + + // clear all old certs if found + err = os.RemoveAll(devcertsPath) + if err != nil { + log.Fatalln("Failed to remove old files from dev certificate directory:", err) + } + + err = os.Mkdir(devcertsPath, os.ModePerm|os.ModePerm) + if err != nil { + log.Fatalln("Failed to create directory to locate or save dev certificates:", err) + } + + certOut, err := os.Create(filepath.Join(devcertsPath, "cert.pem")) + if err != nil { + log.Fatalln("Failed to open devcerts/cert.pem for writing:", err) + } + pem.Encode(certOut, &pem.Block{Type: "CERTIFICATE", Bytes: derBytes}) + certOut.Close() + + keyOut, err := os.OpenFile(filepath.Join(devcertsPath, "key.pem"), os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600) + if err != nil { + log.Fatalln("Failed to open devcerts/key.pem for writing:", err) + return + } + pem.Encode(keyOut, pemBlockForKey(priv)) + keyOut.Close() +} diff --git a/system/tls/enable.go b/system/tls/enable.go index 04f032a..c6f65b3 100644 --- a/system/tls/enable.go +++ b/system/tls/enable.go @@ -65,7 +65,8 @@ func setup() { } -// Enable runs the setup for creating or locating certificates and starts the TLS server +// Enable runs the setup for creating or locating production certificates and +// starts the TLS server func Enable() { setup() @@ -74,6 +75,5 @@ func Enable() { TLSConfig: &tls.Config{GetCertificate: m.GetCertificate}, } - go log.Fatalln(server.ListenAndServeTLS("", "")) - fmt.Println("Server listening for HTTPS requests...") + log.Fatalln(server.ListenAndServeTLS("", "")) } diff --git a/system/tls/enabledev.go b/system/tls/enabledev.go new file mode 100644 index 0000000..3550fc0 --- /dev/null +++ b/system/tls/enabledev.go @@ -0,0 +1,29 @@ +package tls + +import ( + "log" + "net/http" + "os" + "path/filepath" +) + +// EnableDev generates self-signed SSL certificates to use HTTPS & HTTP/2 while +// working in a development environment. The certs are saved in a different +// directory than the production certs (from Let's Encrypt), so that the +// acme/autocert package doesn't mistake them for it's own. +// Additionally, a TLS server is started using the default http mux. +func EnableDev() { + setupDev() + + pwd, err := os.Getwd() + if err != nil { + log.Fatalln("Couldn't find working directory to activate dev certificates:", err) + } + + vendorPath := filepath.Join(pwd, "cmd", "ponzu", "vendor", "github.com", "ponzu-cms", "ponzu", "system", "tls") + + cert := filepath.Join(vendorPath, "devcerts", "cert.pem") + key := filepath.Join(vendorPath, "devcerts", "key.pem") + + log.Fatalln(http.ListenAndServeTLS(":10443", cert, key, nil)) +} |