Ejemplo n.º 1
0
func main() {

	opts := optparse.New("Usage: dynamodb-marshal file1.go [file2.go ...]",
		"dynamodb-marshal 0.0.1")

	force := opts.Bool([]string{"-f", "--force"},
		"overwrite existing marshal files")

	os.Args[0] = "dynamodb-marshal"
	files := opts.Parse(os.Args)

	if len(files) == 0 {
		opts.PrintUsage()
		runtime.Exit(0)
	}

	log.AddConsoleLogger()
	for _, file := range files {
		path, err := filepath.Abs(file)
		if err != nil {
			runtime.StandardError(err)
		}
		parseFile(path, *force)
	}

	log.Wait()

}
Ejemplo n.º 2
0
func main() {

	// Define the options for the command line and config file options parser.
	opts := optparse.New(
		"Usage: planfile <config.yaml> [options]\n",
		"planfile 0.0.1")

	cookieKeyFile := opts.StringConfig("cookie-key-file", "cookie.key",
		"the file containing the key to sign cookie values [cookie.key]")

	gaHost := opts.StringConfig("ga-host", "",
		"the google analytics hostname to use")

	gaID := opts.StringConfig("ga-id", "",
		"the google analytics id to use")

	httpAddr := opts.StringConfig("http-addr", ":8888",
		"the address to bind the http server [:8888]")

	oauthID := opts.Required().StringConfig("oauth-id", "",
		"the oauth client id for github")

	oauthSecret := opts.Required().StringConfig("oauth-secret", "",
		"the oauth client secret for github")

	redirectURL := opts.StringConfig("redirect-url", "/.oauth",
		"the redirect url for handling oauth [/.oauth]")

	repository := opts.Required().StringConfig("repository", "",
		"the username/repository on github")

	secureMode := opts.BoolConfig("secure-mode",
		"enable hsts and secure cookies [false]")

	title := opts.StringConfig("title", "Planfile",
		"the title for the web app [Planfile]")

	refreshKey := opts.StringConfig("refresh-key", "",
		"key for anonymously calling refresh at /.refresh?key=<refresh-key>")

	refreshOpt := opts.IntConfig("refresh-interval", 1,
		"the number of through-the-web edits before a full refresh [1]")

	debug, instanceDirectory, _, logPath, _ = runtime.DefaultOpts("planfile", opts, os.Args, true)

	service := &oauth.OAuthService{
		ClientID:     *oauthID,
		ClientSecret: *oauthSecret,
		Scope:        "public_repo",
		AuthURL:      "https://github.com/login/oauth/authorize",
		TokenURL:     "https://github.com/login/oauth/access_token",
		RedirectURL:  *redirectURL,
		AcceptHeader: "application/json",
	}

	assets := map[string]string{}
	json.Unmarshal(readFile("assets.json"), &assets)
	setupPygments()

	mutex := sync.RWMutex{}
	repo := &Repo{Path: *repository}

	err := repo.Load(callGithubAnon)
	if err != nil {
		runtime.Exit(1)
	}

	repo.Title = *title
	repo.Updated = time.Now().UTC()
	repoJSON, err := json.Marshal(repo)
	if err != nil {
		runtime.StandardError(err)
	}

	refreshCount := 0
	refreshInterval := *refreshOpt
	refreshKeySet := *refreshKey != ""
	refreshKeyBytes := []byte(*refreshKey)

	secret := readFile(*cookieKeyFile)
	newContext := func(w http.ResponseWriter, r *http.Request) *Context {
		return &Context{
			r:      r,
			w:      w,
			secret: secret,
			secure: *secureMode,
		}
	}

	register := func(path string, handler func(*Context), usegzip ...bool) {
		gzippable := len(usegzip) > 0 && usegzip[0]
		http.HandleFunc(path, func(w http.ResponseWriter, r *http.Request) {
			log.Info("serving %s", r.URL)
			w.Header().Set("Content-Type", "text/html; charset=utf-8")
			if gzippable && httputil.Parse(r, "Accept-Encoding").Accepts("gzip") {
				buf := &bytes.Buffer{}
				enc := gzip.NewWriter(buf)
				handler(newContext(GzipWriter{enc, w}, r))
				enc.Close()
				w.Header().Set("Content-Encoding", "gzip")
				w.Header().Set("Content-Length", strconv.Itoa(buf.Len()))
				buf.WriteTo(w)
			} else {
				handler(newContext(w, r))
			}
		})
	}

	anon := []byte(", null, null, '', false")
	authFalse := []byte("', false")
	authTrue := []byte("', true")

	header := []byte(`<!doctype html>
<meta charset=utf-8>
<title>` + html.EscapeString(*title) + `</title>
<link href="//fonts.googleapis.com/css?family=Abel|Coustard:400" rel=stylesheet>
<link href=/.static/` + assets["planfile.css"] + ` rel=stylesheet>
<body><script>DATA = ['` + *gaHost + `', '` + *gaID + `', `)

	footer := []byte(`];</script>
<script src=/.static/` + assets["planfile.js"] + `></script>
<noscript>Sorry, your browser needs <a href=http://enable-javascript.com>JavaScript enabled</a>.</noscript>
`)

	register("/", func(ctx *Context) {
		mutex.RLock()
		defer mutex.RUnlock()
		ctx.Write(header)
		ctx.Write(repoJSON)
		avatar := ctx.GetCookie("avatar")
		user := ctx.GetCookie("user")
		if avatar != "" && user != "" {
			ctx.Write([]byte(", '" + user + "', '" + avatar + "', '" + ctx.GetCookie("xsrf")))
			if ctx.IsAuthorised(repo) {
				ctx.Write(authTrue)
			} else {
				ctx.Write(authFalse)
			}
		} else {
			ctx.Write(anon)
		}
		ctx.Write(footer)
	}, true)

	register("/.api", func(ctx *Context) {
		mutex.RLock()
		defer mutex.RUnlock()
		if cb := ctx.FormValue("callback"); cb != "" {
			ctx.Write([]byte(cb))
			ctx.Write([]byte{'('})
			ctx.Write(repoJSON)
			ctx.Write([]byte{')', ';'})
		} else {
			ctx.Write(repoJSON)
		}
	}, true)

	register("/.login", func(ctx *Context) {
		b := make([]byte, 20)
		if n, err := rand.Read(b); err != nil || n != 20 {
			ctx.Error("Couldn't access cryptographic device", err)
			return
		}
		s := hex.EncodeToString(b)
		ctx.SetCookie("xsrf", s)
		ctx.Redirect(service.AuthCodeURL(s))
	})

	register("/.logout", func(ctx *Context) {
		ctx.ExpireCookie("auth")
		ctx.ExpireCookie("avatar")
		ctx.ExpireCookie("token")
		ctx.ExpireCookie("user")
		ctx.ExpireCookie("xsrf")
		ctx.Redirect("/")
	})

	notAuthorised := []byte("ERROR: Not Authorised!")

	savedHeader := []byte(`<!doctype html>
<meta charset=utf-8>
<title>` + html.EscapeString(*title) + `</title>
<body><script>SAVED="`)

	savedFooter := []byte(`"</script><script src=/.static/` + assets["planfile.js"] + `></script>`)

	exportRepo := func(ctx *Context) bool {
		repo.Updated = time.Now().UTC()
		repoJSON, err = json.Marshal(repo)
		if err != nil {
			ctx.Error("Couldn't encode repo data during refresh", err)
			return false
		}
		return true
	}

	refresh := func(ctx *Context) {
		err := repo.Load(ctx.CreateCallGithub())
		if err != nil {
			log.Error("couldn't rebuild planfile info: %s", err)
			ctx.Write([]byte("ERROR: " + err.Error()))
			return
		}
		exportRepo(ctx)
	}

	saveItem := func(ctx *Context, update bool) {
		mutex.Lock()
		defer mutex.Unlock()
		if !ctx.IsAuthorised(repo) {
			ctx.Write(notAuthorised)
			return
		}
		if !isEqual([]byte(ctx.FormValue("xsrf")), []byte(ctx.GetCookie("xsrf"))) {
			ctx.Write(notAuthorised)
			return
		}
		callGithub := ctx.CreateCallGithub()
		err := repo.UpdateInfo(callGithub)
		if err != nil {
			ctx.Error("Couldn't update repo info", err)
			return
		}
		var id, path, message string
		if update {
			id = ctx.FormValue("id")
			path = ctx.FormValue("path")
		} else {
			baseID := ctx.FormValue("id")
			id = baseID
			count := 0
			for repo.Exists(id + ".md") {
				count += 1
				id = fmt.Sprintf("%s%d", baseID, count)
			}
			path = id + ".md"
		}
		content := strings.Replace(ctx.FormValue("content"), "\r\n", "\n", -1)
		tags := ctx.FormValue("tags")
		title := ctx.FormValue("title")
		redir := "/"
		if ctx.FormValue("summary") == "yes" {
			if id != "/" {
				content = fmt.Sprintf(`---
title: %s
---

%s`, title, content)
				if strings.HasPrefix(id, "summary.") {
					redir = "/" + id[8:]
				} else {
					// Shouldn't ever happen. But just in case...
					redir = "/" + id
				}
			}
		} else {
			redir = "/.item." + id
			content = fmt.Sprintf(`---
id: %s
tags: %s
title: %s
---

%s`, id, tags, title, content)
		}
		if title == "" {
			title = id
		}
		if update {
			message = "update: " + title + "."
		} else {
			message = "add: " + title + "."
		}
		log.Info("SAVE PATH: %q for %q", path, title)
		err = repo.Modify(ctx, path, content, message)
		if err != nil {
			if update {
				ctx.Error("<a href='/.refresh'>Try refreshing.</a> Couldn't update item", err)
			} else {
				ctx.Error("<a href='/.refresh'>Try refreshing.</a> Couldn't save new item", err)
			}
			return
		}
		refreshCount++
		if refreshCount%refreshInterval == 0 {
			refresh(ctx)
		} else {
			repo.AddPlanfile(path, []byte(content), callGithub)
			if !exportRepo(ctx) {
				return
			}
		}
		ctx.Write(savedHeader)
		ctx.Write([]byte(html.EscapeString(redir)))
		ctx.Write(savedFooter)
	}

	register("/.modify", func(ctx *Context) {
		saveItem(ctx, true)
	})

	register("/.new", func(ctx *Context) {
		saveItem(ctx, false)
	})

	register("/.oauth", func(ctx *Context) {
		s := ctx.FormValue("state")
		if s == "" {
			ctx.Redirect("/.login")
			return
		}
		if !isEqual([]byte(s), []byte(ctx.GetCookie("xsrf"))) {
			ctx.ExpireCookie("xsrf")
			ctx.Redirect("/.login")
			return
		}
		t := &oauth.Transport{OAuthService: service}
		tok, err := t.ExchangeAuthorizationCode(ctx.FormValue("code"))
		if err != nil {
			ctx.Error("Auth Exchange Error", err)
			return
		}
		jtok, err := json.Marshal(tok)
		if err != nil {
			ctx.Error("Couldn't encode token", err)
			return
		}
		ctx.SetCookie("token", hex.EncodeToString(jtok))
		ctx.token = tok
		user := &User{}
		err = ctx.Call("/user", user, nil, false)
		if err != nil {
			ctx.Error("Couldn't load user info", err)
			return
		}
		ctx.SetCookie("avatar", user.AvatarURL)
		ctx.SetCookie("user", user.Login)
		ctx.Redirect("/")
	})

	register("/.preview", func(ctx *Context) {
		rendered, err := renderMarkdown([]byte(ctx.FormValue("content")))
		if err != nil {
			ctx.Error("Couldn't render Markdown", err)
			return
		}
		ctx.Write(rendered)
	}, true)

	register("/.refresh", func(ctx *Context) {
		if !ctx.IsAuthorised(repo) {
			if !(refreshKeySet && isEqual(refreshKeyBytes, []byte(ctx.FormValue("key")))) {
				ctx.Write(notAuthorised)
				return
			}
		}
		mutex.Lock()
		defer mutex.Unlock()
		refresh(ctx)
		ctx.Redirect("/")
	})

	mimetypes := map[string]string{
		"css":  "text/css",
		"gif":  "image/gif",
		"ico":  "image/x-icon",
		"jpeg": "image/jpeg",
		"jpg":  "image/jpeg",
		"js":   "text/javascript",
		"png":  "image/png",
		"swf":  "application/x-shockwave-flash",
		"txt":  "text/plain",
	}

	registerStatic := func(filepath, urlpath string) {
		_, ext := rsplit(filepath, ".")
		ctype, ok := mimetypes[ext]
		if !ok {
			ctype = "application/octet-stream"
		}
		if debug {
			register(urlpath, func(ctx *Context) {
				ctx.SetHeader("Content-Type", ctype)
				ctx.Write(readFile(filepath))
			}, strings.HasPrefix(ctype, "text/"))
		} else {
			content := readFile(filepath)
			register(urlpath, func(ctx *Context) {
				ctx.SetHeader("Cache-Control", "public, max-age=86400")
				ctx.SetHeader("Content-Type", ctype)
				ctx.Write(content)
			}, strings.HasPrefix(ctype, "text/"))
		}
	}

	for _, path := range assets {
		registerStatic(filepath.Join(instanceDirectory, "static", path), "/.static/"+path)
	}

	wwwPath := filepath.Join(instanceDirectory, "www")
	if files, err := ioutil.ReadDir(wwwPath); err == nil {
		for _, file := range files {
			if !file.IsDir() {
				registerStatic(filepath.Join(wwwPath, file.Name()), "/"+file.Name())
			}
		}
	}

	log.Info("Listening on %s", *httpAddr)
	server := &http.Server{
		Addr:         *httpAddr,
		ReadTimeout:  30 * time.Second,
		WriteTimeout: 30 * time.Second,
	}
	err = server.ListenAndServe()
	if err != nil {
		runtime.Error("couldn't bind to tcp socket: %s", err)
	}

}