2012-04-03 21:39:49 +00:00
|
|
|
/*
|
|
|
|
Copyright 2012 Google Inc.
|
|
|
|
|
|
|
|
Licensed under the Apache License, Version 2.0 (the "License");
|
|
|
|
you may not use this file except in compliance with the License.
|
|
|
|
You may obtain a copy of the License at
|
|
|
|
|
|
|
|
http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
|
|
|
|
Unless required by applicable law or agreed to in writing, software
|
|
|
|
distributed under the License is distributed on an "AS IS" BASIS,
|
|
|
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
|
|
See the License for the specific language governing permissions and
|
|
|
|
limitations under the License.
|
|
|
|
*/
|
|
|
|
|
|
|
|
package server
|
|
|
|
|
|
|
|
import (
|
|
|
|
"encoding/json"
|
2012-04-15 18:09:51 +00:00
|
|
|
"fmt"
|
2012-04-03 21:39:49 +00:00
|
|
|
"html/template"
|
|
|
|
"net/http"
|
|
|
|
"os"
|
|
|
|
"reflect"
|
|
|
|
"strconv"
|
2012-04-21 20:41:40 +00:00
|
|
|
"strings"
|
2012-04-03 21:39:49 +00:00
|
|
|
|
2012-04-15 18:09:51 +00:00
|
|
|
"camlistore.org/pkg/auth"
|
2012-04-03 21:39:49 +00:00
|
|
|
"camlistore.org/pkg/blobserver"
|
|
|
|
"camlistore.org/pkg/httputil"
|
|
|
|
"camlistore.org/pkg/jsonconfig"
|
|
|
|
"camlistore.org/pkg/osutil"
|
|
|
|
)
|
|
|
|
|
|
|
|
// SetupHandler handles serving the wizard setup page.
|
|
|
|
type SetupHandler struct {
|
|
|
|
config jsonconfig.Obj
|
|
|
|
}
|
|
|
|
|
|
|
|
func init() {
|
|
|
|
blobserver.RegisterHandlerConstructor("setup", newSetupFromConfig)
|
|
|
|
}
|
|
|
|
|
|
|
|
func newSetupFromConfig(ld blobserver.Loader, conf jsonconfig.Obj) (h http.Handler, err error) {
|
|
|
|
wizard := &SetupHandler{config: conf}
|
|
|
|
return wizard, nil
|
|
|
|
}
|
|
|
|
|
2012-04-22 15:33:22 +00:00
|
|
|
func printWizard(i interface{}) (s string) {
|
2012-04-21 20:41:40 +00:00
|
|
|
switch ei := i.(type) {
|
2012-04-22 15:33:22 +00:00
|
|
|
case []string:
|
|
|
|
for _, v := range ei {
|
|
|
|
s += printWizard(v) + ","
|
|
|
|
}
|
|
|
|
s = strings.TrimRight(s, ",")
|
2012-04-21 20:41:40 +00:00
|
|
|
case []interface{}:
|
|
|
|
for _, v := range ei {
|
2012-04-22 15:33:22 +00:00
|
|
|
s += printWizard(v) + ","
|
2012-04-21 20:41:40 +00:00
|
|
|
}
|
|
|
|
s = strings.TrimRight(s, ",")
|
|
|
|
default:
|
|
|
|
return fmt.Sprintf("%v", i)
|
|
|
|
}
|
|
|
|
return s
|
|
|
|
}
|
|
|
|
|
2012-04-22 15:33:22 +00:00
|
|
|
// Flatten all published entities as lists and move them at the root
|
|
|
|
// of the conf, to have them displayed individually by the template
|
|
|
|
func flattenPublish(config jsonconfig.Obj) error {
|
|
|
|
gallery := []string{}
|
|
|
|
blog := []string{}
|
|
|
|
config["gallery"] = gallery
|
|
|
|
config["blog"] = blog
|
|
|
|
published, ok := config["publish"]
|
|
|
|
if !ok {
|
|
|
|
delete(config, "publish")
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
pubObj, ok := published.(map[string]interface{})
|
|
|
|
if !ok {
|
|
|
|
return fmt.Errorf("Was expecting a map[string]interface{} for \"publish\", got %T", published)
|
|
|
|
}
|
|
|
|
for k, v := range pubObj {
|
|
|
|
pub, ok := v.(map[string]interface{})
|
|
|
|
if !ok {
|
|
|
|
return fmt.Errorf("Was expecting a map[string]interface{} for %s, got %T", k, pub)
|
|
|
|
}
|
|
|
|
template, rootPermanode, style := "", "", ""
|
|
|
|
for pk, pv := range pub {
|
|
|
|
val, ok := pv.(string)
|
|
|
|
if !ok {
|
|
|
|
return fmt.Errorf("Was expecting a string for %s, got %T", pk, pv)
|
|
|
|
}
|
|
|
|
switch pk {
|
|
|
|
case "template":
|
|
|
|
template = val
|
|
|
|
case "rootPermanode":
|
|
|
|
rootPermanode = val
|
|
|
|
case "style":
|
|
|
|
style = val
|
|
|
|
default:
|
|
|
|
return fmt.Errorf("Unknown key %q in %s", pk, k)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if template == "" || rootPermanode == "" {
|
2012-04-27 00:04:20 +00:00
|
|
|
return fmt.Errorf("missing \"template\" key or \"rootPermanode\" key in %s", k)
|
2012-04-22 15:33:22 +00:00
|
|
|
}
|
|
|
|
obj := []string{k, rootPermanode, style}
|
|
|
|
config[template] = obj
|
|
|
|
}
|
|
|
|
|
|
|
|
delete(config, "publish")
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2012-04-03 21:39:49 +00:00
|
|
|
func sendWizard(req *http.Request, rw http.ResponseWriter, hasChanged bool) {
|
|
|
|
config, err := jsonconfig.ReadFile(osutil.UserServerConfigPath())
|
|
|
|
if err != nil {
|
|
|
|
httputil.ServerError(rw, err)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2012-04-22 15:33:22 +00:00
|
|
|
err = flattenPublish(config)
|
|
|
|
if err != nil {
|
|
|
|
httputil.ServerError(rw, err)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2012-04-21 20:41:40 +00:00
|
|
|
funcMap := template.FuncMap{
|
2012-04-22 15:33:22 +00:00
|
|
|
"printWizard": printWizard,
|
2012-04-21 20:41:40 +00:00
|
|
|
}
|
|
|
|
|
2012-04-03 21:39:49 +00:00
|
|
|
body := `<form id="WizardForm" action="setup" method="post" enctype="multipart/form-data">`
|
2012-04-22 15:33:22 +00:00
|
|
|
body += `{{range $k,$v := .}}{{printf "%v" $k}} <input type="text" size="30" name ="{{printf "%v" $k}}" value="{{printWizard $v}}"><br />{{end}}`
|
2012-04-03 21:39:49 +00:00
|
|
|
body += `<input type="submit" form="WizardForm" value="Save"></form>`
|
|
|
|
|
|
|
|
if hasChanged {
|
|
|
|
body += `<p> Configuration succesfully rewritten </p>`
|
|
|
|
}
|
|
|
|
|
2012-04-21 20:41:40 +00:00
|
|
|
tmpl, err := template.New("wizard").Funcs(funcMap).Parse(topWizard + body + bottomWizard)
|
2012-04-03 21:39:49 +00:00
|
|
|
if err != nil {
|
|
|
|
httputil.ServerError(rw, err)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
err = tmpl.Execute(rw, config)
|
|
|
|
if err != nil {
|
|
|
|
httputil.ServerError(rw, err)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func rewriteConfig(config *jsonconfig.Obj, configfile string) error {
|
|
|
|
b, err := json.MarshalIndent(*config, "", " ")
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
s := string(b)
|
|
|
|
f, err := os.Create(configfile)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
defer f.Close()
|
|
|
|
_, err = f.WriteString(s)
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
func handleSetupChange(req *http.Request, rw http.ResponseWriter) {
|
|
|
|
err := req.ParseMultipartForm(10e6)
|
|
|
|
if err != nil {
|
|
|
|
httputil.ServerError(rw, err)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
hilevelConf, err := jsonconfig.ReadFile(osutil.UserServerConfigPath())
|
|
|
|
if err != nil {
|
|
|
|
httputil.ServerError(rw, err)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
hasChanged := false
|
2012-04-21 20:41:40 +00:00
|
|
|
var el interface{}
|
2012-04-22 15:33:22 +00:00
|
|
|
publish := jsonconfig.Obj{}
|
2012-04-03 21:39:49 +00:00
|
|
|
for k, v := range req.Form {
|
|
|
|
if _, ok := hilevelConf[k]; !ok {
|
2012-04-22 15:33:22 +00:00
|
|
|
if k != "gallery" && k != "blog" {
|
|
|
|
continue
|
|
|
|
}
|
2012-04-03 21:39:49 +00:00
|
|
|
}
|
|
|
|
|
2012-04-21 20:41:40 +00:00
|
|
|
switch k {
|
|
|
|
case "TLS":
|
|
|
|
b, err := strconv.ParseBool(v[0])
|
|
|
|
if err != nil {
|
|
|
|
httputil.ServerError(rw, fmt.Errorf("TLS field expects a boolean value"))
|
|
|
|
}
|
2012-04-03 21:39:49 +00:00
|
|
|
el = b
|
2012-04-21 20:41:40 +00:00
|
|
|
case "replicateTo":
|
2012-04-22 15:33:22 +00:00
|
|
|
// TODO(mpl): figure out why it is always seen as different from the conf
|
|
|
|
el = []interface{}{}
|
2012-04-21 20:41:40 +00:00
|
|
|
if len(v[0]) > 0 {
|
2012-04-22 15:33:22 +00:00
|
|
|
els := []string{}
|
2012-04-21 20:41:40 +00:00
|
|
|
vals := strings.Split(v[0], ",")
|
|
|
|
els = append(els, vals...)
|
2012-04-22 15:33:22 +00:00
|
|
|
el = els
|
|
|
|
}
|
|
|
|
// TODO(mpl): "handler,rootPermanode[,style]" for each published entity for now.
|
|
|
|
// we will need something more readable later probably
|
|
|
|
case "gallery", "blog":
|
|
|
|
if len(v[0]) > 0 {
|
|
|
|
pub := strings.Split(v[0], ",")
|
|
|
|
if len(pub) < 2 || len(pub) > 3 {
|
|
|
|
// no need to fail loudly for now as we'll probably change this format
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
handler := jsonconfig.Obj{}
|
|
|
|
handler["template"] = k
|
|
|
|
handler["rootPermanode"] = pub[1]
|
|
|
|
if len(pub) > 2 {
|
|
|
|
handler["style"] = pub[2]
|
|
|
|
}
|
|
|
|
publish[pub[0]] = handler
|
2012-04-03 21:39:49 +00:00
|
|
|
}
|
2012-04-22 15:33:22 +00:00
|
|
|
continue
|
2012-04-21 20:41:40 +00:00
|
|
|
default:
|
|
|
|
el = v[0]
|
2012-04-03 21:39:49 +00:00
|
|
|
}
|
|
|
|
if reflect.DeepEqual(hilevelConf[k], el) {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
hasChanged = true
|
|
|
|
hilevelConf[k] = el
|
|
|
|
}
|
2012-04-22 15:33:22 +00:00
|
|
|
// "publish" wasn't checked yet
|
|
|
|
if !reflect.DeepEqual(hilevelConf["publish"], publish) {
|
|
|
|
hilevelConf["publish"] = publish
|
|
|
|
hasChanged = true
|
|
|
|
}
|
2012-04-03 21:39:49 +00:00
|
|
|
|
|
|
|
if hasChanged {
|
|
|
|
err = rewriteConfig(&hilevelConf, osutil.UserServerConfigPath())
|
|
|
|
if err != nil {
|
|
|
|
httputil.ServerError(rw, err)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
sendWizard(req, rw, hasChanged)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
func (sh *SetupHandler) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
|
2012-04-15 18:09:51 +00:00
|
|
|
if !auth.LocalhostAuthorized(req) {
|
|
|
|
fmt.Fprintf(rw,
|
|
|
|
"<html><body>Setup only allowed from localhost"+
|
|
|
|
"<p><a href='/'>Back</a></p>"+
|
|
|
|
"</body></html>\n")
|
|
|
|
return
|
|
|
|
}
|
2012-04-03 21:39:49 +00:00
|
|
|
if req.Method == "POST" {
|
|
|
|
handleSetupChange(req, rw)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
sendWizard(req, rw, false)
|
|
|
|
}
|