<commit_before>// This file is part of Monsti, a web content management system.
// Copyright 2012-2013 Christian Neumann
//
// Monsti is free software: you can redistribute it and/or modify it under the
// terms of the GNU Affero General Public License as published by the Free
// Software Foundation, either version 3 of the License, or (at your option) any
// later version.
//
// Monsti is distributed in the hope that it will be useful, but WITHOUT ANY
// WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
// A PARTICULAR PURPOSE.  See the GNU Affero General Public License for more
// details.
//
// You should have received a copy of the GNU Affero General Public License
// along with Monsti.  If not, see <http://www.gnu.org/licenses/>.

/*
 Monsti is a simple and resource efficient CMS.

 This package implements the image node type.
*/
package main

import (
	"flag"
	"fmt"
	"github.com/monsti/form"
	"github.com/monsti/service"
	"github.com/monsti/util"
	"github.com/monsti/util/l10n"
	"github.com/monsti/util/template"
	"log"
	"os"
)

var settings struct {
	Monsti util.MonstiSettings
}

var logger *log.Logger
var renderer template.Renderer

type editFormData struct {
	Title string
	Image string
}

func edit(req service.Request, res *service.Response, s *service.Session) {
	G := l10n.UseCatalog(req.Session.Locale)
	data := editFormData{}
	form := form.NewForm(&data, form.Fields{
		"Title": form.Field{G("Title"), "", form.Required(G("Required.")), nil},
		"Image": form.Field{G("Image"), "", nil, new(form.FileWidget)}})
	switch req.Method {
	case "GET":
		data.Title = req.Node.Title
	case "POST":
		var imageData []byte
		var err error
		if len(req.Files["Image"]) == 1 {
			imageData, err = req.Files["Image"][0].ReadFile()
			if err != nil {
				panic("Could not read image data: " + err.Error())
			}
		}
		if form.Fill(req.FormData) {
			if len(imageData) > 0 {
				dataC := s.Data()
				node := req.Node
				node.Title = data.Title
				node.Hide = true
				if err := dataC.UpdateNode(req.Site, node); err != nil {
					panic("Could not update node: " + err.Error())
				}
				if err := dataC.WriteNodeData(req.Site, req.Node.Path,
					"image.data", string(imageData)); err != nil {
					panic("Could not write image data: " + err.Error())
				}
				res.Redirect = req.Node.Path
				return
			}
			form.AddError("Image", G("There was a problem with your image upload."))
		}
	default:
		panic("Request method not supported: " + req.Method)
	}
	fmt.Fprint(res, renderer.Render("image/edit",
		template.Context{"Form": form.RenderData()},
		req.Session.Locale, ""))
}

func view(req service.Request, res *service.Response, s *service.Session) {
	body, err := s.Data().GetNodeData(req.Site, req.Node.Path,
		"image.data")
	if err != nil {
		panic("Could not get image data: " + err.Error())
	}
	res.Raw = true
	res.Write(body)
}

func main() {
	logger = log.New(os.Stderr, "image ", log.LstdFlags)
	// Load configuration
	flag.Parse()
	if flag.NArg() != 1 {
		logger.Fatal("Expecting configuration path.")
	}
	cfgPath := util.GetConfigPath(flag.Arg(0))
	if err := util.LoadModuleSettings("image", cfgPath, &settings); err != nil {
		logger.Fatal("Could not load settings: ", err)
	}

	infoPath := settings.Monsti.GetServicePath(service.Info.String())

	l10n.Setup("monsti-image", settings.Monsti.GetLocalePath())
	renderer.Root = settings.Monsti.GetTemplatesPath()

	provider := service.NewNodeProvider(logger, infoPath)
	image := service.NodeTypeHandler{
		Name:       "Image",
		ViewAction: view,
		EditAction: edit,
	}
	provider.AddNodeType(&image)
	if err := provider.Serve(settings.Monsti.GetServicePath(
		service.Node.String() + "_image")); err != nil {
		panic("Could not setup node provider: " + err.Error())
	}
}
<commit_msg>Use new import paths.<commit_after>// This file is part of Monsti, a web content management system.
// Copyright 2012-2013 Christian Neumann
//
// Monsti is free software: you can redistribute it and/or modify it under the
// terms of the GNU Affero General Public License as published by the Free
// Software Foundation, either version 3 of the License, or (at your option) any
// later version.
//
// Monsti is distributed in the hope that it will be useful, but WITHOUT ANY
// WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
// A PARTICULAR PURPOSE.  See the GNU Affero General Public License for more
// details.
//
// You should have received a copy of the GNU Affero General Public License
// along with Monsti.  If not, see <http://www.gnu.org/licenses/>.

/*
 Monsti is a simple and resource efficient CMS.

 This package implements the image node type.
*/
package main

import (
	"flag"
	"fmt"
	"pkg.monsti.org/form"
	"pkg.monsti.org/service"
	"pkg.monsti.org/util"
	"pkg.monsti.org/util/l10n"
	"pkg.monsti.org/util/template"
	"log"
	"os"
)

var settings struct {
	Monsti util.MonstiSettings
}

var logger *log.Logger
var renderer template.Renderer

type editFormData struct {
	Title string
	Image string
}

func edit(req service.Request, res *service.Response, s *service.Session) {
	G := l10n.UseCatalog(req.Session.Locale)
	data := editFormData{}
	form := form.NewForm(&data, form.Fields{
		"Title": form.Field{G("Title"), "", form.Required(G("Required.")), nil},
		"Image": form.Field{G("Image"), "", nil, new(form.FileWidget)}})
	switch req.Method {
	case "GET":
		data.Title = req.Node.Title
	case "POST":
		var imageData []byte
		var err error
		if len(req.Files["Image"]) == 1 {
			imageData, err = req.Files["Image"][0].ReadFile()
			if err != nil {
				panic("Could not read image data: " + err.Error())
			}
		}
		if form.Fill(req.FormData) {
			if len(imageData) > 0 {
				dataC := s.Data()
				node := req.Node
				node.Title = data.Title
				node.Hide = true
				if err := dataC.UpdateNode(req.Site, node); err != nil {
					panic("Could not update node: " + err.Error())
				}
				if err := dataC.WriteNodeData(req.Site, req.Node.Path,
					"image.data", string(imageData)); err != nil {
					panic("Could not write image data: " + err.Error())
				}
				res.Redirect = req.Node.Path
				return
			}
			form.AddError("Image", G("There was a problem with your image upload."))
		}
	default:
		panic("Request method not supported: " + req.Method)
	}
	fmt.Fprint(res, renderer.Render("image/edit",
		template.Context{"Form": form.RenderData()},
		req.Session.Locale, ""))
}

func view(req service.Request, res *service.Response, s *service.Session) {
	body, err := s.Data().GetNodeData(req.Site, req.Node.Path,
		"image.data")
	if err != nil {
		panic("Could not get image data: " + err.Error())
	}
	res.Raw = true
	res.Write(body)
}

func main() {
	logger = log.New(os.Stderr, "image ", log.LstdFlags)
	// Load configuration
	flag.Parse()
	if flag.NArg() != 1 {
		logger.Fatal("Expecting configuration path.")
	}
	cfgPath := util.GetConfigPath(flag.Arg(0))
	if err := util.LoadModuleSettings("image", cfgPath, &settings); err != nil {
		logger.Fatal("Could not load settings: ", err)
	}

	infoPath := settings.Monsti.GetServicePath(service.Info.String())

	l10n.Setup("monsti-image", settings.Monsti.GetLocalePath())
	renderer.Root = settings.Monsti.GetTemplatesPath()

	provider := service.NewNodeProvider(logger, infoPath)
	image := service.NodeTypeHandler{
		Name:       "Image",
		ViewAction: view,
		EditAction: edit,
	}
	provider.AddNodeType(&image)
	if err := provider.Serve(settings.Monsti.GetServicePath(
		service.Node.String() + "_image")); err != nil {
		panic("Could not setup node provider: " + err.Error())
	}
}
<|endoftext|>


<commit_before>// This file is part of Monsti, a web content management system.
// Copyright 2012-2013 Christian Neumann
//
// Monsti is free software: you can redistribute it and/or modify it under the
// terms of the GNU Affero General Public License as published by the Free
// Software Foundation, either version 3 of the License, or (at your option) any
// later version.
//
// Monsti is distributed in the hope that it will be useful, but WITHOUT ANY
// WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
// A PARTICULAR PURPOSE.  See the GNU Affero General Public License for more
// details.
//
// You should have received a copy of the GNU Affero General Public License
// along with Monsti.  If not, see <http://www.gnu.org/licenses/>.

/*
 Monsti is a simple and resource efficient CMS.

 This package implements the image node type.
*/
package main

import (
	"flag"
	"fmt"
	"log"
	"os"

	"github.com/quirkey/magick"
	"pkg.monsti.org/form"
	"pkg.monsti.org/service"
	"pkg.monsti.org/util"
	"pkg.monsti.org/util/l10n"
	"pkg.monsti.org/util/template"
)

var settings struct {
	Monsti util.MonstiSettings
}

var logger *log.Logger
var renderer template.Renderer

type editFormData struct {
	Title string
	Image string
}

func edit(req service.Request, res *service.Response, s *service.Session) {
	G := l10n.UseCatalog(req.Session.Locale)
	data := editFormData{}
	form := form.NewForm(&data, form.Fields{
		"Title": form.Field{G("Title"), "", form.Required(G("Required.")), nil},
		"Image": form.Field{G("Image"), "", nil, new(form.FileWidget)}})
	switch req.Method {
	case service.GetRequest:
		data.Title = req.Node.Title
	case service.PostRequest:
		var imageData []byte
		var err error
		if len(req.Files["Image"]) == 1 {
			imageData, err = req.Files["Image"][0].ReadFile()
			if err != nil {
				panic("Could not read image data: " + err.Error())
			}
		}
		if form.Fill(req.FormData) {
			if len(imageData) > 0 {
				dataC := s.Data()
				node := req.Node
				node.Title = data.Title
				node.Hide = true
				if err := dataC.UpdateNode(req.Site, node); err != nil {
					panic("Could not update node: " + err.Error())
				}
				if err := dataC.WriteNodeData(req.Site, req.Node.Path,
					"image.data", string(imageData)); err != nil {
					panic("Could not write image data: " + err.Error())
				}
				res.Redirect = req.Node.Path
				return
			}
			form.AddError("Image", G("There was a problem with your image upload."))
		}
	default:
		panic(fmt.Sprintf("Request method not supported: %v", req.Method))
	}
	rendered, err := renderer.Render("image/edit",
		template.Context{"Form": form.RenderData()},
		req.Session.Locale, "")
	if err != nil {
		panic("Could not render template: " + err.Error())
	}
	fmt.Fprint(res, rendered)
}

type size struct{ Width, Height int }

func (s size) String() string {
	return fmt.Sprintf("%vx%v", s.Width, s.Height)
}

func view(req service.Request, res *service.Response, s *service.Session) {
	sizeName := req.Query.Get("size")
	var size size
	var body []byte
	var err error
	if sizeName != "" {
		err = s.Data().GetConfig(req.Site, "image", "sizes."+sizeName, &size)
		if err != nil || size.Width == 0 {
			logger.Printf("Could not find size %q for site %q: %v", sizeName, req.Site,
				err)
		} else {
			sizePath := "image_" + size.String() + ".data"
			body, err = s.Data().GetNodeData(req.Site, req.Node.Path, sizePath)
			if err != nil || body == nil {
				body, err = s.Data().GetNodeData(req.Site, req.Node.Path, "image.data")
				if err != nil {
					panic("Could not get image data: " + err.Error())
				}
				image, err := magick.NewFromBlob(body, "jpg")
				if err != nil {
					panic("Could not open image data with magick: " + err.Error())
				}
				defer image.Destroy()
				err = image.Resize(size.String())
				if err != nil {
					panic("Could not resize image: " + err.Error())
				}
				body, err = image.ToBlob("jpg")
				if err != nil {
					panic("Could not dump image: " + err.Error())
				}
				if err := s.Data().WriteNodeData(req.Site, req.Node.Path,
					sizePath, string(body)); err != nil {
					panic("Could not write resized image data: " + err.Error())
				}
			}
		}
	}
	if body == nil {
		body, err = s.Data().GetNodeData(req.Site, req.Node.Path, "image.data")
		if err != nil {
			panic("Could not get image data: " + err.Error())
		}
	}
	if req.Query.Get("raw") == "1" {
		res.Raw = true
		res.Write(body)
	} else {
		rendered, err := renderer.Render("image/view",
			template.Context{"Image": req.Node},
			req.Session.Locale, "")
		if err != nil {
			panic("Could not render template: " + err.Error())
		}
		fmt.Fprint(res, rendered)
	}
}

func main() {
	logger = log.New(os.Stderr, "image ", log.LstdFlags)
	// Load configuration
	flag.Parse()
	if flag.NArg() != 1 {
		logger.Fatal("Expecting configuration path.")
	}
	cfgPath := util.GetConfigPath(flag.Arg(0))
	if err := util.LoadModuleSettings("image", cfgPath, &settings); err != nil {
		logger.Fatal("Could not load settings: ", err)
	}

	infoPath := settings.Monsti.GetServicePath(service.Info.String())

	l10n.Setup("monsti-image", settings.Monsti.GetLocalePath())
	renderer.Root = settings.Monsti.GetTemplatesPath()

	provider := service.NewNodeProvider(logger, infoPath)
	image := service.NodeTypeHandler{
		Name:       "Image",
		ViewAction: view,
		EditAction: edit,
	}
	provider.AddNodeType(&image)
	if err := provider.Serve(settings.Monsti.GetServicePath(
		service.Node.String() + "_image")); err != nil {
		panic("Could not setup node provider: " + err.Error())
	}
}
<commit_msg>Use new API.<commit_after>// This file is part of Monsti, a web content management system.
// Copyright 2012-2013 Christian Neumann
//
// Monsti is free software: you can redistribute it and/or modify it under the
// terms of the GNU Affero General Public License as published by the Free
// Software Foundation, either version 3 of the License, or (at your option) any
// later version.
//
// Monsti is distributed in the hope that it will be useful, but WITHOUT ANY
// WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
// A PARTICULAR PURPOSE.  See the GNU Affero General Public License for more
// details.
//
// You should have received a copy of the GNU Affero General Public License
// along with Monsti.  If not, see <http://www.gnu.org/licenses/>.

/*
 Monsti is a simple and resource efficient CMS.

 This package implements the image node type.
*/
package main

import (
	"flag"
	"fmt"
	"log"
	"os"

	"github.com/quirkey/magick"
	"pkg.monsti.org/form"
	"pkg.monsti.org/service"
	"pkg.monsti.org/util"
	"pkg.monsti.org/util/l10n"
	"pkg.monsti.org/util/template"
)

var settings struct {
	Monsti util.MonstiSettings
}

var logger *log.Logger
var renderer template.Renderer

type editFormData struct {
	Title string
	Image string
}

func edit(req service.Request, res *service.Response, s *service.Session) error {
	G := l10n.UseCatalog(req.Session.Locale)
	data := editFormData{}
	form := form.NewForm(&data, form.Fields{
		"Title": form.Field{G("Title"), "", form.Required(G("Required.")), nil},
		"Image": form.Field{G("Image"), "", nil, new(form.FileWidget)}})
	switch req.Method {
	case service.GetRequest:
		data.Title = req.Node.Title
	case service.PostRequest:
		var imageData []byte
		var err error
		if len(req.Files["Image"]) == 1 {
			imageData, err = req.Files["Image"][0].ReadFile()
			if err != nil {
				return fmt.Errorf("Could not read image data: %v", err)
			}
		}
		if form.Fill(req.FormData) {
			if len(imageData) > 0 {
				dataC := s.Data()
				node := req.Node
				node.Title = data.Title
				node.Hide = true
				if err := dataC.UpdateNode(req.Site, node); err != nil {
					return fmt.Errorf("Could not update node: %v", err)
				}
				if err := dataC.WriteNodeData(req.Site, req.Node.Path,
					"image.data", string(imageData)); err != nil {
					return fmt.Errorf("Could not write image data: %v", err)
				}
				res.Redirect = req.Node.Path
				return nil
			}
			form.AddError("Image", G("There was a problem with your image upload."))
		}
	default:
		return fmt.Errorf("Request method not supported: %v", req.Method)
	}
	rendered, err := renderer.Render("image/edit",
		template.Context{"Form": form.RenderData()},
		req.Session.Locale, "")
	if err != nil {
		return fmt.Errorf("Could not render template: %v", err)
	}
	fmt.Fprint(res, rendered)
	return nil
}

type size struct{ Width, Height int }

func (s size) String() string {
	return fmt.Sprintf("%vx%v", s.Width, s.Height)
}

func view(req service.Request, res *service.Response, s *service.Session) error {
	sizeName := req.Query.Get("size")
	var size size
	var body []byte
	var err error
	if sizeName != "" {
		err = s.Data().GetConfig(req.Site, "image", "sizes."+sizeName, &size)
		if err != nil || size.Width == 0 {
			logger.Printf("Could not find size %q for site %q: %v", sizeName, req.Site,
				err)
		} else {
			sizePath := "image_" + size.String() + ".data"
			body, err = s.Data().GetNodeData(req.Site, req.Node.Path, sizePath)
			if err != nil || body == nil {
				body, err = s.Data().GetNodeData(req.Site, req.Node.Path, "image.data")
				if err != nil {
					return fmt.Errorf("Could not get image data: %v", err)
				}
				image, err := magick.NewFromBlob(body, "jpg")
				if err != nil {
					return fmt.Errorf("Could not open image data with magick: %v", err)
				}
				defer image.Destroy()
				err = image.Resize(size.String())
				if err != nil {
					return fmt.Errorf("Could not resize image: %v", err)
				}
				body, err = image.ToBlob("jpg")
				if err != nil {
					return fmt.Errorf("Could not dump image: %v", err)
				}
				if err := s.Data().WriteNodeData(req.Site, req.Node.Path,
					sizePath, string(body)); err != nil {
					return fmt.Errorf("Could not write resized image data: %v", err)
				}
			}
		}
	}
	if body == nil {
		body, err = s.Data().GetNodeData(req.Site, req.Node.Path, "image.data")
		if err != nil {
			return fmt.Errorf("Could not get image data: %v", err)
		}
	}
	if req.Query.Get("raw") == "1" {
		res.Raw = true
		res.Write(body)
	} else {
		rendered, err := renderer.Render("image/view",
			template.Context{"Image": req.Node},
			req.Session.Locale, "")
		if err != nil {
			return fmt.Errorf("Could not render template: %v", err)
		}
		fmt.Fprint(res, rendered)
	}
	return nil
}

func main() {
	logger = log.New(os.Stderr, "image ", log.LstdFlags)
	// Load configuration
	flag.Parse()
	if flag.NArg() != 1 {
		logger.Fatal("Expecting configuration path.")
	}
	cfgPath := util.GetConfigPath(flag.Arg(0))
	if err := util.LoadModuleSettings("image", cfgPath, &settings); err != nil {
		logger.Fatal("Could not load settings: ", err)
	}

	infoPath := settings.Monsti.GetServicePath(service.Info.String())

	l10n.Setup("monsti-image", settings.Monsti.GetLocalePath())
	renderer.Root = settings.Monsti.GetTemplatesPath()

	provider := service.NewNodeProvider(logger, infoPath)
	image := service.NodeTypeHandler{
		Name:       "Image",
		ViewAction: view,
		EditAction: edit,
	}
	provider.AddNodeType(&image)
	if err := provider.Serve(settings.Monsti.GetServicePath(
		service.Node.String() + "_image")); err != nil {
		panic("Could not setup node provider: " + err.Error())
	}
}
<|endoftext|>


<commit_before>package main

import (
	"fmt"
	"io"
	"text/template"

	"github.com/vbatts/imgsrv/types"
)

var emptyInterface interface{}

var headTemplate = template.Must(template.New("head").Parse(headTemplateHTML))
var headTemplateHTML = `

  <link href="/assets/bootstrap.css" media="screen" rel="stylesheet" type="text/css" />

  <title>{{.title}}</title>

`

var navbarTemplate = template.Must(template.New("navbar").Parse(navbarTemplateHTML))
var navbarTemplateHTML = `

        filesrv

Home

Upload

URLie

All

            Other

Keywords

File ext

MD5s

`
var containerBeginTemplate = template.Must(template.New("containerBegin").Parse(containerBeginTemplateHTML))
var containerBeginTemplateHTML = `

`

var tailTemplate = template.Must(template.New("tail").Parse(tailTemplateHTML))
var tailTemplateHTML = `

{{if .}}
{{.footer}}
{{end}}

`

var formDeleteFileTemplate = template.Must(template.New("formDeleteFile").Parse(formDeleteFileTemplateHTML))
var formDeleteFileTemplateHTML = `

Get file from URL

{{if .}}

Are you sure?

no!

yes! delete!

{{else}}

ERROR: No File provided!

{{end}}

{{/* hero-unit */}}

{{/* span9 */}}
`

var formGetUrlTemplate = template.Must(template.New("formGetUrl").Parse(formGetUrlTemplateHTML))
var formGetUrlTemplateHTML = `

Get file from URL

<form enctype="multipart/form-data" action="/urlie" method="POST">

      <input type="text" name="url" placeholder="file URL">

      <input type="text" name="keywords" placeholder="keywords">(comma seperatated, no spaces)

      <input type="checkbox" name="rand" value="true">Randomize filename

      <input type="submit" value="Fetch File">

</form>

{{/* hero-unit */}}

{{/* span9 */}}
`

var formFileUploadTemplate = template.Must(template.New("formFileUpload").Parse(formFileUploadTemplateHTML))
var formFileUploadTemplateHTML = `

Upload a File

<form enctype="multipart/form-data" action="/upload" method="POST">

      <input type="file" name="filename" placeholder="filename">

      <input type="text" name="keywords" placeholder="keywords">(comma seperatated, no spaces)

      <input type="checkbox" name="rand" value="true">Randomize filename

      <input type="submit" value="Upload File">

</form>

{{/* hero-unit */}}

{{/* span9 */}}
`

var listTemplate = template.Must(template.New("list").Parse(listTemplateHTML))
var listTemplateHTML = `
{{if .}}

{{range .}}

{{.Filename}}
[keywords:{{range $key := .Metadata.Keywords}} {{$key}}{{end}}]
[md5: {{.Md5 | printf "%8.8s"}}...]

{{end}}

{{end}}
`

var tagcloudTemplate = template.Must(template.New("tagcloud").Parse(tagcloudTemplateHTML))
var tagcloudTemplateHTML = `
{{if .}}

{{range .}}
{{.Id}}
{{end}}

{{end}}

`

var fileViewImageTemplate = template.Must(template.New("file").Parse(fileViewImageTemplateHTML))
var fileViewImageTemplateHTML = `
{{if .}}

{{end}}
`

var fileViewAudioTemplate = template.Must(template.New("file").Parse(fileViewAudioTemplateHTML))
var fileViewAudioTemplateHTML = `
{{if .}}

<audio controls>
  <source src="/f/{{.Filename}}" type="{{.ContentType}}">
  Your browser does not support the video tag.
</audio>

{{end}}
`

var fileViewVideoTemplate = template.Must(template.New("file").Parse(fileViewVideoTemplateHTML))
var fileViewVideoTemplateHTML = `
{{if .}}

<video width="320" height="240" controls>
  <source src="/f/{{.Filename}}" type="{{.ContentType}}">
  Your browser does not support the video tag.
</video>

{{end}}
`

var fileViewTemplate = template.Must(template.New("file").Parse(fileViewTemplateHTML))
var fileViewTemplateHTML = `
{{if .}}
{{.Filename}}
{{end}}
`

var fileViewInfoTemplate = template.Must(template.New("file").Parse(fileViewInfoTemplateHTML))
var fileViewInfoTemplateHTML = `
{{if .}}

[keywords:{{range $key := .Metadata.Keywords}} {{$key}}{{end}}]

[md5: {{.Md5}}]

[size: {{.Length}}]

[UploadDate: {{.Metadata.TimeStamp}}]

[Delete]
{{end}}
`

func DeleteFilePage(w io.Writer, filename string) (err error) {
	err = headTemplate.Execute(w, map[string]string{"title": "FileSrv :: delete"})
	if err != nil {
		return err
	}
	err = navbarTemplate.Execute(w, nil)
	if err != nil {
		return err
	}
	err = containerBeginTemplate.Execute(w, nil)
	if err != nil {
		return err
	}

	err = formDeleteFileTemplate.Execute(w, &filename)
	if err != nil {
		return err
	}

	err = tailTemplate.Execute(w, map[string]string{"footer": fmt.Sprintf("Version: %s", VERSION)})
	if err != nil {
		return err
	}
	return
}

func UrliePage(w io.Writer) (err error) {
	err = headTemplate.Execute(w, map[string]string{"title": "FileSrv :: URLie"})
	if err != nil {
		return err
	}
	err = navbarTemplate.Execute(w, nil)
	if err != nil {
		return err
	}
	err = containerBeginTemplate.Execute(w, nil)
	if err != nil {
		return err
	}
	err = formGetUrlTemplate.Execute(w, &emptyInterface)
	if err != nil {
		return err
	}
	err = tailTemplate.Execute(w, map[string]string{"footer": fmt.Sprintf("Version: %s", VERSION)})
	if err != nil {
		return err
	}
	return
}

func UploadPage(w io.Writer) (err error) {
	err = headTemplate.Execute(w, map[string]string{"title": "FileSrv :: Upload"})
	if err != nil {
		return err
	}
	err = navbarTemplate.Execute(w, nil)
	if err != nil {
		return err
	}
	err = containerBeginTemplate.Execute(w, nil)
	if err != nil {
		return err
	}

	// main context of this page
	err = formFileUploadTemplate.Execute(w, &emptyInterface)
	if err != nil {
		return err
	}

	err = tailTemplate.Execute(w, map[string]string{"footer": fmt.Sprintf("Version: %s", VERSION)})
	if err != nil {
		return err
	}
	return
}

func ImageViewPage(w io.Writer, file types.File) (err error) {
	err = headTemplate.Execute(w, map[string]string{"title": "FileSrv"})
	if err != nil {
		return err
	}
	err = navbarTemplate.Execute(w, nil)
	if err != nil {
		return err
	}
	err = containerBeginTemplate.Execute(w, nil)
	if err != nil {
		return err
	}

	if file.IsImage() {
		err = fileViewImageTemplate.Execute(w, file)
	} else if file.IsAudio() {
		err = fileViewAudioTemplate.Execute(w, file)
	} else if file.IsVideo() {
		err = fileViewVideoTemplate.Execute(w, file)
	} else {
		err = fileViewTemplate.Execute(w, file)
	}
	if err != nil {
		return err
	}
	err = fileViewInfoTemplate.Execute(w, file)
	if err != nil {
		return err
	}

	err = tailTemplate.Execute(w, map[string]string{"footer": fmt.Sprintf("Version: %s", VERSION)})
	if err != nil {
		return err
	}
	return
}

func ListFilesPage(w io.Writer, files []types.File) (err error) {
	err = headTemplate.Execute(w, map[string]string{"title": "FileSrv"})
	if err != nil {
		return err
	}
	err = navbarTemplate.Execute(w, nil)
	if err != nil {
		return err
	}
	err = containerBeginTemplate.Execute(w, nil)
	if err != nil {
		return err
	}

	// main context of this page
	err = listTemplate.Execute(w, files)
	if err != nil {
		return err
	}

	err = tailTemplate.Execute(w, map[string]string{"footer": fmt.Sprintf("Version: %s", VERSION)})
	if err != nil {
		return err
	}
	return
}

func ListTagCloudPage(w io.Writer, ic []types.IdCount) (err error) {
	err = headTemplate.Execute(w, map[string]string{"title": "FileSrv"})
	if err != nil {
		return err
	}
	err = navbarTemplate.Execute(w, nil)
	if err != nil {
		return err
	}
	err = containerBeginTemplate.Execute(w, nil)
	if err != nil {
		return err
	}

	// main context of this page
	err = tagcloudTemplate.Execute(w, ic)
	if err != nil {
		return err
	}

	err = tailTemplate.Execute(w, map[string]string{"footer": fmt.Sprintf("Version: %s", VERSION)})
	if err != nil {
		return err
	}
	return
}
<commit_msg>i broke the tag cloud js :-\<commit_after>package main

import (
	"fmt"
	"io"
	"text/template"

	"github.com/vbatts/imgsrv/types"
)

var emptyInterface interface{}

var headTemplate = template.Must(template.New("head").Parse(headTemplateHTML))
var headTemplateHTML = `

  <link href="/assets/bootstrap.css" media="screen" rel="stylesheet" type="text/css" />

  <title>{{.title}}</title>

`

var navbarTemplate = template.Must(template.New("navbar").Parse(navbarTemplateHTML))
var navbarTemplateHTML = `

        filesrv

Home

Upload

URLie

All

            Other

Keywords

File ext

MD5s

`
var containerBeginTemplate = template.Must(template.New("containerBegin").Parse(containerBeginTemplateHTML))
var containerBeginTemplateHTML = `

`

var tailTemplate = template.Must(template.New("tail").Parse(tailTemplateHTML))
var tailTemplateHTML = `

{{if .}}
{{.footer}}
{{end}}

`

var formDeleteFileTemplate = template.Must(template.New("formDeleteFile").Parse(formDeleteFileTemplateHTML))
var formDeleteFileTemplateHTML = `

Get file from URL

{{if .}}

Are you sure?

no!

yes! delete!

{{else}}

ERROR: No File provided!

{{end}}

{{/* hero-unit */}}

{{/* span9 */}}
`

var formGetUrlTemplate = template.Must(template.New("formGetUrl").Parse(formGetUrlTemplateHTML))
var formGetUrlTemplateHTML = `

Get file from URL

<form enctype="multipart/form-data" action="/urlie" method="POST">

      <input type="text" name="url" placeholder="file URL">

      <input type="text" name="keywords" placeholder="keywords">(comma seperatated, no spaces)

      <input type="checkbox" name="rand" value="true">Randomize filename

      <input type="submit" value="Fetch File">

</form>

{{/* hero-unit */}}

{{/* span9 */}}
`

var formFileUploadTemplate = template.Must(template.New("formFileUpload").Parse(formFileUploadTemplateHTML))
var formFileUploadTemplateHTML = `

Upload a File

<form enctype="multipart/form-data" action="/upload" method="POST">

      <input type="file" name="filename" placeholder="filename">

      <input type="text" name="keywords" placeholder="keywords">(comma seperatated, no spaces)

      <input type="checkbox" name="rand" value="true">Randomize filename

      <input type="submit" value="Upload File">

</form>

{{/* hero-unit */}}

{{/* span9 */}}
`

var listTemplate = template.Must(template.New("list").Parse(listTemplateHTML))
var listTemplateHTML = `
{{if .}}

{{range .}}

{{.Filename}}
[keywords:{{range $key := .Metadata.Keywords}} {{$key}}{{end}}]
[md5: {{.Md5 | printf "%8.8s"}}...]

{{end}}

{{end}}
`

var tagcloudTemplate = template.Must(template.New("tagcloud").Parse(tagcloudTemplateHTML))
var tagcloudTemplateHTML = `
{{if .}}

{{range .}}
{{.Id}}
{{end}}

{{end}}

`

var fileViewImageTemplate = template.Must(template.New("file").Parse(fileViewImageTemplateHTML))
var fileViewImageTemplateHTML = `
{{if .}}

{{end}}
`

var fileViewAudioTemplate = template.Must(template.New("file").Parse(fileViewAudioTemplateHTML))
var fileViewAudioTemplateHTML = `
{{if .}}

<audio controls>
  <source src="/f/{{.Filename}}" type="{{.ContentType}}">
  Your browser does not support the video tag.
</audio>

{{end}}
`

var fileViewVideoTemplate = template.Must(template.New("file").Parse(fileViewVideoTemplateHTML))
var fileViewVideoTemplateHTML = `
{{if .}}

<video width="320" height="240" controls>
  <source src="/f/{{.Filename}}" type="{{.ContentType}}">
  Your browser does not support the video tag.
</video>

{{end}}
`

var fileViewTemplate = template.Must(template.New("file").Parse(fileViewTemplateHTML))
var fileViewTemplateHTML = `
{{if .}}
{{.Filename}}
{{end}}
`

var fileViewInfoTemplate = template.Must(template.New("file").Parse(fileViewInfoTemplateHTML))
var fileViewInfoTemplateHTML = `
{{if .}}

[keywords:{{range $key := .Metadata.Keywords}} {{$key}}{{end}}]

[md5: {{.Md5}}]

[size: {{.Length}}]

[UploadDate: {{.Metadata.TimeStamp}}]

[Delete]
{{end}}
`

func DeleteFilePage(w io.Writer, filename string) (err error) {
	err = headTemplate.Execute(w, map[string]string{"title": "FileSrv :: delete"})
	if err != nil {
		return err
	}
	err = navbarTemplate.Execute(w, nil)
	if err != nil {
		return err
	}
	err = containerBeginTemplate.Execute(w, nil)
	if err != nil {
		return err
	}

	err = formDeleteFileTemplate.Execute(w, &filename)
	if err != nil {
		return err
	}

	err = tailTemplate.Execute(w, map[string]string{"footer": fmt.Sprintf("Version: %s", VERSION)})
	if err != nil {
		return err
	}
	return
}

func UrliePage(w io.Writer) (err error) {
	err = headTemplate.Execute(w, map[string]string{"title": "FileSrv :: URLie"})
	if err != nil {
		return err
	}
	err = navbarTemplate.Execute(w, nil)
	if err != nil {
		return err
	}
	err = containerBeginTemplate.Execute(w, nil)
	if err != nil {
		return err
	}
	err = formGetUrlTemplate.Execute(w, &emptyInterface)
	if err != nil {
		return err
	}
	err = tailTemplate.Execute(w, map[string]string{"footer": fmt.Sprintf("Version: %s", VERSION)})
	if err != nil {
		return err
	}
	return
}

func UploadPage(w io.Writer) (err error) {
	err = headTemplate.Execute(w, map[string]string{"title": "FileSrv :: Upload"})
	if err != nil {
		return err
	}
	err = navbarTemplate.Execute(w, nil)
	if err != nil {
		return err
	}
	err = containerBeginTemplate.Execute(w, nil)
	if err != nil {
		return err
	}

	// main context of this page
	err = formFileUploadTemplate.Execute(w, &emptyInterface)
	if err != nil {
		return err
	}

	err = tailTemplate.Execute(w, map[string]string{"footer": fmt.Sprintf("Version: %s", VERSION)})
	if err != nil {
		return err
	}
	return
}

func ImageViewPage(w io.Writer, file types.File) (err error) {
	err = headTemplate.Execute(w, map[string]string{"title": "FileSrv"})
	if err != nil {
		return err
	}
	err = navbarTemplate.Execute(w, nil)
	if err != nil {
		return err
	}
	err = containerBeginTemplate.Execute(w, nil)
	if err != nil {
		return err
	}

	if file.IsImage() {
		err = fileViewImageTemplate.Execute(w, file)
	} else if file.IsAudio() {
		err = fileViewAudioTemplate.Execute(w, file)
	} else if file.IsVideo() {
		err = fileViewVideoTemplate.Execute(w, file)
	} else {
		err = fileViewTemplate.Execute(w, file)
	}
	if err != nil {
		return err
	}
	err = fileViewInfoTemplate.Execute(w, file)
	if err != nil {
		return err
	}

	err = tailTemplate.Execute(w, map[string]string{"footer": fmt.Sprintf("Version: %s", VERSION)})
	if err != nil {
		return err
	}
	return
}

func ListFilesPage(w io.Writer, files []types.File) (err error) {
	err = headTemplate.Execute(w, map[string]string{"title": "FileSrv"})
	if err != nil {
		return err
	}
	err = navbarTemplate.Execute(w, nil)
	if err != nil {
		return err
	}
	err = containerBeginTemplate.Execute(w, nil)
	if err != nil {
		return err
	}

	// main context of this page
	err = listTemplate.Execute(w, files)
	if err != nil {
		return err
	}

	err = tailTemplate.Execute(w, map[string]string{"footer": fmt.Sprintf("Version: %s", VERSION)})
	if err != nil {
		return err
	}
	return
}

func ListTagCloudPage(w io.Writer, ic []types.IdCount) (err error) {
	err = headTemplate.Execute(w, map[string]string{"title": "FileSrv"})
	if err != nil {
		return err
	}
	err = navbarTemplate.Execute(w, nil)
	if err != nil {
		return err
	}
	err = containerBeginTemplate.Execute(w, nil)
	if err != nil {
		return err
	}

	// main context of this page
	err = tagcloudTemplate.Execute(w, ic)
	if err != nil {
		return err
	}

	err = tailTemplate.Execute(w, map[string]string{"footer": fmt.Sprintf("Version: %s", VERSION)})
	if err != nil {
		return err
	}
	return
}
<|endoftext|>


<commit_before>package ircbot

import (
	"strings"
)

type ActionFunc func(b *IrcBot, m *IrcMsg)

type Action struct {
	command string
	do      ActionFunc
}

func Pong(b *IrcBot, m *IrcMsg) {
	b.writer.PrintfLine("PONG %s", strings.Join(m.args, " "))
}
<commit_msg>add actionFunc<commit_after>package ircbot

import (
	"fmt"
	"math/rand"
	"strings"
)

type ActionFunc func(b *IrcBot, m *IrcMsg)

type Action struct {
	command string
	do      ActionFunc
}

func Pong(b *IrcBot, m *IrcMsg) {

	b.Out <- &IrcMsg{
		command: "PONG",
		args:    m.args,
	}
}

func Join(b *IrcBot, m *IrcMsg) {
	if m.nick == b.Nick {
		b.joined = true
		return
	}

	s := fmt.Sprintf("%s :Salut %s", b.Channel[0], m.nick)
	b.Out <- &IrcMsg{
		command: "PRIVMSG",
		args:    []string{s},
	}
}

func Respond(b *IrcBot, m *IrcMsg) {
	response := []string{
		"oui ?",
		"on parle de moi ?",
		"Je suis pas là",
	}

	s := strings.Join(m.args, " ")

	if strings.Contains(s, b.Nick) {
		nbr := rand.Intn(len(response))
		line := fmt.Sprintf("%s :%s", b.Channel[0], response[nbr])
		b.Out <- &IrcMsg{
			command: "PRIVMSG",
			args:    []string{line},
		}
	}
}
<|endoftext|>


<commit_before>// Copyright 2015 Aaron Jacobs. All Rights Reserved.
// Author: aaronjjacobs@gmail.com (Aaron Jacobs)
//
// 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 verify_test

import (
	"errors"
	"fmt"
	"testing"

	"golang.org/x/net/context"

	"github.com/jacobsa/comeback/blob"
	"github.com/jacobsa/comeback/blob/mock"
	"github.com/jacobsa/comeback/fs"
	"github.com/jacobsa/comeback/graph"
	"github.com/jacobsa/comeback/repr"
	"github.com/jacobsa/comeback/verify"
	. "github.com/jacobsa/oglematchers"
	. "github.com/jacobsa/oglemock"
	. "github.com/jacobsa/ogletest"
)

func TestVisitor(t *testing.T) { RunTests(t) }

////////////////////////////////////////////////////////////////////////
// Boilerplate
////////////////////////////////////////////////////////////////////////

const namePrefix = "blobs/"

type superTest struct {
	ctx       context.Context
	blobStore mock_blob.MockStore
	visitor   graph.Visitor
}

func (t *superTest) setUp(
	ti *TestInfo,
	readFiles bool,
	allScores []blob.Score) {
	t.ctx = ti.Ctx
	t.blobStore = mock_blob.NewMockStore(ti.MockController, "blobStore")
	t.visitor = verify.NewVisitor(readFiles, allScores, t.blobStore)
}

////////////////////////////////////////////////////////////////////////
// Common
////////////////////////////////////////////////////////////////////////

type CommonTest struct {
	superTest
}

var _ SetUpInterface = &CommonTest{}

func init() { RegisterTestSuite(&CommonTest{}) }

func (t *CommonTest) SetUp(ti *TestInfo) {
	t.superTest.setUp(ti, false, nil)
}

func (t *CommonTest) InvalidNodeName() {
	_, err := t.visitor.Visit(t.ctx, "taco")

	ExpectThat(err, Error(HasSubstr("ParseNodeName")))
	ExpectThat(err, Error(HasSubstr("taco")))
}

////////////////////////////////////////////////////////////////////////
// Directories
////////////////////////////////////////////////////////////////////////

type DirsTest struct {
	superTest

	listing  []*fs.DirectoryEntry
	contents []byte
	score    blob.Score
	node     string
}

var _ SetUpInterface = &DirsTest{}

func init() { RegisterTestSuite(&DirsTest{}) }

func (t *DirsTest) SetUp(ti *TestInfo) {
	t.superTest.setUp(ti, false, nil)

	// Set up canned data for a valid listing.
	t.listing = []*fs.DirectoryEntry{
		&fs.DirectoryEntry{
			Type: fs.TypeFile,
			Name: "foo",
			Scores: []blob.Score{
				blob.ComputeScore([]byte("0")),
				blob.ComputeScore([]byte("1")),
			},
		},

		&fs.DirectoryEntry{
			Type: fs.TypeDirectory,
			Name: "bar",
			Scores: []blob.Score{
				blob.ComputeScore([]byte("2")),
			},
		},
	}

	var err error
	t.contents, err = repr.MarshalDir(t.listing)
	AssertEq(nil, err)

	t.score = blob.ComputeScore(t.contents)
	t.node = verify.FormatNodeName(true, t.score)
}

func (t *DirsTest) CallsBlobStore() {
	// Load
	ExpectCall(t.blobStore, "Load")(t.score).
		WillOnce(Return(nil, errors.New("")))

	// Call
	t.visitor.Visit(t.ctx, t.node)
}

func (t *DirsTest) BlobStoreReturnsError() {
	// Load
	ExpectCall(t.blobStore, "Load")(Any()).
		WillOnce(Return(nil, errors.New("taco")))

	// Call
	_, err := t.visitor.Visit(t.ctx, t.node)

	ExpectThat(err, Error(HasSubstr("Load")))
	ExpectThat(err, Error(HasSubstr("taco")))
}

func (t *DirsTest) BlobIsJunk() {
	// Set up junk contents.
	t.contents = append(t.contents, 'a')
	t.score = blob.ComputeScore(t.contents)
	t.node = verify.FormatNodeName(true, t.score)

	// Load
	ExpectCall(t.blobStore, "Load")(Any()).
		WillOnce(Return(t.contents, nil))

	// Call
	_, err := t.visitor.Visit(t.ctx, t.node)

	ExpectThat(err, Error(HasSubstr(t.score.Hex())))
	ExpectThat(err, Error(HasSubstr("UnmarshalDir")))
}

func (t *DirsTest) UnknownEntryType() {
	// Set up a listing with a junk entry type.
	t.listing = []*fs.DirectoryEntry{
		&fs.DirectoryEntry{
			Type: fs.TypeSymlink,
			Name: "foo",
			Scores: []blob.Score{
				blob.ComputeScore([]byte("0")),
			},
		},
	}

	var err error
	t.contents, err = repr.MarshalDir(t.listing)
	AssertEq(nil, err)

	t.score = blob.ComputeScore(t.contents)
	t.node = verify.FormatNodeName(true, t.score)

	// Load
	ExpectCall(t.blobStore, "Load")(Any()).
		WillOnce(Return(t.contents, nil))

	// Call
	_, err = t.visitor.Visit(t.ctx, t.node)

	ExpectThat(err, Error(HasSubstr("entry type")))
	ExpectThat(err, Error(HasSubstr(fmt.Sprintf("%d", fs.TypeSymlink))))
}

func (t *DirsTest) ReturnsAppropriateAdjacentNodes() {
	// Load
	ExpectCall(t.blobStore, "Load")(Any()).
		WillOnce(Return(t.contents, nil))

	// Call
	adjacent, err := t.visitor.Visit(t.ctx, t.node)
	AssertEq(nil, err)

	var expected []interface{}
	for _, entry := range t.listing {
		dir := entry.Type == fs.TypeDirectory
		for _, score := range entry.Scores {
			expected = append(expected, verify.FormatNodeName(dir, score))
		}
	}

	ExpectThat(adjacent, ElementsAre(expected...))
}

////////////////////////////////////////////////////////////////////////
// Files, no read
////////////////////////////////////////////////////////////////////////

type filesCommonTest struct {
	superTest

	contents []byte

	knownScore blob.Score
	knownNode  string

	unknownScore blob.Score
	unknownNode  string
}

func (t *filesCommonTest) setUp(ti *TestInfo, readFiles bool) {
	t.contents = []byte("foobarbaz")

	t.knownScore = blob.ComputeScore(t.contents)
	t.knownNode = verify.FormatNodeName(false, t.knownScore)

	t.unknownScore = blob.ComputeScore(append(t.contents, 'a'))
	t.unknownNode = verify.FormatNodeName(false, t.unknownScore)

	t.superTest.setUp(ti, readFiles, []blob.Score{t.knownScore})
}

type FilesLiteTest struct {
	filesCommonTest

	contents []byte

	knownScore blob.Score
	knownNode  string

	unknownScore blob.Score
	unknownNode  string
}

var _ SetUpInterface = &FilesLiteTest{}

func init() { RegisterTestSuite(&FilesLiteTest{}) }

func (t *FilesLiteTest) SetUp(ti *TestInfo) {
	t.filesCommonTest.setUp(ti, false)
}

func (t *FilesLiteTest) ScoreNotInList() {
	// Call
	_, err := t.visitor.Visit(t.ctx, t.unknownNode)

	ExpectThat(err, Error(HasSubstr("Unknown score")))
	ExpectThat(err, Error(HasSubstr(t.unknownScore.Hex())))
}

func (t *FilesLiteTest) ScoreIsInList() {
	// Call
	adjacent, err := t.visitor.Visit(t.ctx, t.knownNode)

	AssertEq(nil, err)
	ExpectThat(adjacent, ElementsAre())
}

////////////////////////////////////////////////////////////////////////
// Files, read
////////////////////////////////////////////////////////////////////////

type FilesFullTest struct {
	filesCommonTest
}

var _ SetUpInterface = &FilesFullTest{}

func init() { RegisterTestSuite(&FilesFullTest{}) }

func (t *FilesFullTest) SetUp(ti *TestInfo) {
	t.filesCommonTest.setUp(ti, true)
}

func (t *FilesFullTest) CallsBlobStore() {
	// Load
	ExpectCall(t.blobStore, "Load")(t.knownScore).
		WillOnce(Return(nil, errors.New("")))

	// Call
	t.visitor.Visit(t.ctx, t.knownNode)
}

func (t *FilesFullTest) BlobStoreReturnsError() {
	AssertFalse(true, "TODO")
}

func (t *FilesFullTest) BlobStoreSucceeds() {
	AssertFalse(true, "TODO")
}
<commit_msg>FilesFullTest.BlobStoreReturnsError<commit_after>// Copyright 2015 Aaron Jacobs. All Rights Reserved.
// Author: aaronjjacobs@gmail.com (Aaron Jacobs)
//
// 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 verify_test

import (
	"errors"
	"fmt"
	"testing"

	"golang.org/x/net/context"

	"github.com/jacobsa/comeback/blob"
	"github.com/jacobsa/comeback/blob/mock"
	"github.com/jacobsa/comeback/fs"
	"github.com/jacobsa/comeback/graph"
	"github.com/jacobsa/comeback/repr"
	"github.com/jacobsa/comeback/verify"
	. "github.com/jacobsa/oglematchers"
	. "github.com/jacobsa/oglemock"
	. "github.com/jacobsa/ogletest"
)

func TestVisitor(t *testing.T) { RunTests(t) }

////////////////////////////////////////////////////////////////////////
// Boilerplate
////////////////////////////////////////////////////////////////////////

const namePrefix = "blobs/"

type superTest struct {
	ctx       context.Context
	blobStore mock_blob.MockStore
	visitor   graph.Visitor
}

func (t *superTest) setUp(
	ti *TestInfo,
	readFiles bool,
	allScores []blob.Score) {
	t.ctx = ti.Ctx
	t.blobStore = mock_blob.NewMockStore(ti.MockController, "blobStore")
	t.visitor = verify.NewVisitor(readFiles, allScores, t.blobStore)
}

////////////////////////////////////////////////////////////////////////
// Common
////////////////////////////////////////////////////////////////////////

type CommonTest struct {
	superTest
}

var _ SetUpInterface = &CommonTest{}

func init() { RegisterTestSuite(&CommonTest{}) }

func (t *CommonTest) SetUp(ti *TestInfo) {
	t.superTest.setUp(ti, false, nil)
}

func (t *CommonTest) InvalidNodeName() {
	_, err := t.visitor.Visit(t.ctx, "taco")

	ExpectThat(err, Error(HasSubstr("ParseNodeName")))
	ExpectThat(err, Error(HasSubstr("taco")))
}

////////////////////////////////////////////////////////////////////////
// Directories
////////////////////////////////////////////////////////////////////////

type DirsTest struct {
	superTest

	listing  []*fs.DirectoryEntry
	contents []byte
	score    blob.Score
	node     string
}

var _ SetUpInterface = &DirsTest{}

func init() { RegisterTestSuite(&DirsTest{}) }

func (t *DirsTest) SetUp(ti *TestInfo) {
	t.superTest.setUp(ti, false, nil)

	// Set up canned data for a valid listing.
	t.listing = []*fs.DirectoryEntry{
		&fs.DirectoryEntry{
			Type: fs.TypeFile,
			Name: "foo",
			Scores: []blob.Score{
				blob.ComputeScore([]byte("0")),
				blob.ComputeScore([]byte("1")),
			},
		},

		&fs.DirectoryEntry{
			Type: fs.TypeDirectory,
			Name: "bar",
			Scores: []blob.Score{
				blob.ComputeScore([]byte("2")),
			},
		},
	}

	var err error
	t.contents, err = repr.MarshalDir(t.listing)
	AssertEq(nil, err)

	t.score = blob.ComputeScore(t.contents)
	t.node = verify.FormatNodeName(true, t.score)
}

func (t *DirsTest) CallsBlobStore() {
	// Load
	ExpectCall(t.blobStore, "Load")(t.score).
		WillOnce(Return(nil, errors.New("")))

	// Call
	t.visitor.Visit(t.ctx, t.node)
}

func (t *DirsTest) BlobStoreReturnsError() {
	// Load
	ExpectCall(t.blobStore, "Load")(Any()).
		WillOnce(Return(nil, errors.New("taco")))

	// Call
	_, err := t.visitor.Visit(t.ctx, t.node)

	ExpectThat(err, Error(HasSubstr("Load")))
	ExpectThat(err, Error(HasSubstr("taco")))
}

func (t *DirsTest) BlobIsJunk() {
	// Set up junk contents.
	t.contents = append(t.contents, 'a')
	t.score = blob.ComputeScore(t.contents)
	t.node = verify.FormatNodeName(true, t.score)

	// Load
	ExpectCall(t.blobStore, "Load")(Any()).
		WillOnce(Return(t.contents, nil))

	// Call
	_, err := t.visitor.Visit(t.ctx, t.node)

	ExpectThat(err, Error(HasSubstr(t.score.Hex())))
	ExpectThat(err, Error(HasSubstr("UnmarshalDir")))
}

func (t *DirsTest) UnknownEntryType() {
	// Set up a listing with a junk entry type.
	t.listing = []*fs.DirectoryEntry{
		&fs.DirectoryEntry{
			Type: fs.TypeSymlink,
			Name: "foo",
			Scores: []blob.Score{
				blob.ComputeScore([]byte("0")),
			},
		},
	}

	var err error
	t.contents, err = repr.MarshalDir(t.listing)
	AssertEq(nil, err)

	t.score = blob.ComputeScore(t.contents)
	t.node = verify.FormatNodeName(true, t.score)

	// Load
	ExpectCall(t.blobStore, "Load")(Any()).
		WillOnce(Return(t.contents, nil))

	// Call
	_, err = t.visitor.Visit(t.ctx, t.node)

	ExpectThat(err, Error(HasSubstr("entry type")))
	ExpectThat(err, Error(HasSubstr(fmt.Sprintf("%d", fs.TypeSymlink))))
}

func (t *DirsTest) ReturnsAppropriateAdjacentNodes() {
	// Load
	ExpectCall(t.blobStore, "Load")(Any()).
		WillOnce(Return(t.contents, nil))

	// Call
	adjacent, err := t.visitor.Visit(t.ctx, t.node)
	AssertEq(nil, err)

	var expected []interface{}
	for _, entry := range t.listing {
		dir := entry.Type == fs.TypeDirectory
		for _, score := range entry.Scores {
			expected = append(expected, verify.FormatNodeName(dir, score))
		}
	}

	ExpectThat(adjacent, ElementsAre(expected...))
}

////////////////////////////////////////////////////////////////////////
// Files, no read
////////////////////////////////////////////////////////////////////////

type filesCommonTest struct {
	superTest

	contents []byte

	knownScore blob.Score
	knownNode  string

	unknownScore blob.Score
	unknownNode  string
}

func (t *filesCommonTest) setUp(ti *TestInfo, readFiles bool) {
	t.contents = []byte("foobarbaz")

	t.knownScore = blob.ComputeScore(t.contents)
	t.knownNode = verify.FormatNodeName(false, t.knownScore)

	t.unknownScore = blob.ComputeScore(append(t.contents, 'a'))
	t.unknownNode = verify.FormatNodeName(false, t.unknownScore)

	t.superTest.setUp(ti, readFiles, []blob.Score{t.knownScore})
}

type FilesLiteTest struct {
	filesCommonTest

	contents []byte

	knownScore blob.Score
	knownNode  string

	unknownScore blob.Score
	unknownNode  string
}

var _ SetUpInterface = &FilesLiteTest{}

func init() { RegisterTestSuite(&FilesLiteTest{}) }

func (t *FilesLiteTest) SetUp(ti *TestInfo) {
	t.filesCommonTest.setUp(ti, false)
}

func (t *FilesLiteTest) ScoreNotInList() {
	// Call
	_, err := t.visitor.Visit(t.ctx, t.unknownNode)

	ExpectThat(err, Error(HasSubstr("Unknown score")))
	ExpectThat(err, Error(HasSubstr(t.unknownScore.Hex())))
}

func (t *FilesLiteTest) ScoreIsInList() {
	// Call
	adjacent, err := t.visitor.Visit(t.ctx, t.knownNode)

	AssertEq(nil, err)
	ExpectThat(adjacent, ElementsAre())
}

////////////////////////////////////////////////////////////////////////
// Files, read
////////////////////////////////////////////////////////////////////////

type FilesFullTest struct {
	filesCommonTest
}

var _ SetUpInterface = &FilesFullTest{}

func init() { RegisterTestSuite(&FilesFullTest{}) }

func (t *FilesFullTest) SetUp(ti *TestInfo) {
	t.filesCommonTest.setUp(ti, true)
}

func (t *FilesFullTest) CallsBlobStore() {
	// Load
	ExpectCall(t.blobStore, "Load")(t.knownScore).
		WillOnce(Return(nil, errors.New("")))

	// Call
	t.visitor.Visit(t.ctx, t.knownNode)
}

func (t *FilesFullTest) BlobStoreReturnsError() {
	// Load
	ExpectCall(t.blobStore, "Load")(Any()).
		WillOnce(Return(nil, errors.New("taco")))

	// Call
	_, err := t.visitor.Visit(t.ctx, t.knownNode)

	ExpectThat(err, Error(HasSubstr("Load")))
	ExpectThat(err, Error(HasSubstr("taco")))
}

func (t *FilesFullTest) BlobStoreSucceeds() {
	AssertFalse(true, "TODO")
}
<|endoftext|>


<commit_before>// Copyright ©2015 The gonum Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package network

import (
	"math"
	"math/rand"

	"github.com/gonum/floats"
	"github.com/gonum/graph"
	"github.com/gonum/matrix/mat64"
)

// PageRank returns the PageRank weights for nodes of the directed graph g
// using the given damping factor and terminating when the 2-norm of the
// vector difference between iterations is below tol. The returned map is
// keyed on the graph node IDs.
func PageRank(g graph.Directed, damp, tol float64) map[int]float64 {
	// PageRank is implemented according to "How Google Finds Your Needle
	// in the Web's Haystack".
	//
	// G.I^k = alpha.S.I^k + (1-alpha).1/n.1.I^k
	//
	// http://www.ams.org/samplings/feature-column/fcarc-pagerank

	nodes := g.Nodes()
	indexOf := make(map[int]int, len(nodes))
	for i, n := range nodes {
		indexOf[n.ID()] = i
	}

	m := mat64.NewDense(len(nodes), len(nodes), nil)
	dangling := damp / float64(len(nodes))
	for j, u := range nodes {
		to := g.From(u)
		f := damp / float64(len(to))
		for _, v := range to {
			m.Set(indexOf[v.ID()], j, f)
		}
		if len(to) == 0 {
			for i := range nodes {
				m.Set(i, j, dangling)
			}
		}
	}
	mat := m.RawMatrix().Data
	dt := (1 - damp) / float64(len(nodes))
	for i := range mat {
		mat[i] += dt
	}

	last := make([]float64, len(nodes))
	for i := range last {
		last[i] = 1
	}
	lastV := mat64.NewVector(len(nodes), last)

	vec := make([]float64, len(nodes))
	var sum float64
	for i := range vec {
		r := rand.NormFloat64()
		sum += r
		vec[i] = r
	}
	f := 1 / sum
	for i := range vec {
		vec[i] *= f
	}
	v := mat64.NewVector(len(nodes), vec)

	for {
		lastV, v = v, lastV
		v.MulVec(m, false, lastV)
		if normDiff(vec, last) < tol {
			break
		}
	}

	ranks := make(map[int]float64, len(nodes))
	for i, r := range v.RawVector().Data {
		ranks[nodes[i].ID()] = r
	}

	return ranks
}

// PageRankSparse returns the PageRank weights for nodes of the sparse directed
// graph g using the given damping factor and terminating when the 2-norm of the
// vector difference between iterations is below tol. The returned map is
// keyed on the graph node IDs.
func PageRankSparse(g graph.Directed, damp, tol float64) map[int]float64 {
	// PageRankSparse is implemented according to "How Google Finds Your Needle
	// in the Web's Haystack".
	//
	// G.I^k = alpha.H.I^k + alpha.A.I^k + (1-alpha).1/n.1.I^k
	//
	// http://www.ams.org/samplings/feature-column/fcarc-pagerank

	nodes := g.Nodes()
	indexOf := make(map[int]int, len(nodes))
	for i, n := range nodes {
		indexOf[n.ID()] = i
	}

	m := make(rowCompressedMatrix, len(nodes))
	var dangling compressedRow
	df := damp / float64(len(nodes))
	for j, u := range nodes {
		to := g.From(u)
		f := damp / float64(len(to))
		for _, v := range to {
			m.addTo(indexOf[v.ID()], j, f)
		}
		if len(to) == 0 {
			dangling.addTo(j, df)
		}
	}

	last := make([]float64, len(nodes))
	for i := range last {
		last[i] = 1
	}
	lastV := mat64.NewVector(len(nodes), last)

	vec := make([]float64, len(nodes))
	var sum float64
	for i := range vec {
		r := rand.NormFloat64()
		sum += r
		vec[i] = r
	}
	f := 1 / sum
	for i := range vec {
		vec[i] *= f
	}
	v := mat64.NewVector(len(nodes), vec)

	dt := (1 - damp) / float64(len(nodes))
	for {
		lastV, v = v, lastV

		m.mulVecUnitary(v, lastV)          // First term of the G matrix equation;
		with := dangling.dotUnitary(lastV) // Second term;
		away := onesDotUnitary(dt, lastV)  // Last term.

		floats.AddConst(with+away, v.RawVector().Data)
		if normDiff(vec, last) < tol {
			break
		}
	}

	ranks := make(map[int]float64, len(nodes))
	for i, r := range v.RawVector().Data {
		ranks[nodes[i].ID()] = r
	}

	return ranks
}

// rowCompressedMatrix implements row-compressed
// matrix/vector multiplication.
type rowCompressedMatrix []compressedRow

// addTo adds the value v to the matrix element at (i,j). Repeated
// calls to addTo with the same column index will result in
// non-unique element representation.
func (m rowCompressedMatrix) addTo(i, j int, v float64) { m[i].addTo(j, v) }

// mulVecUnitary multiplies the receiver by the src vector, storing
// the result in dst. It assumes src and dst are the same length as m
// and that both have unitary vector increments.
func (m rowCompressedMatrix) mulVecUnitary(dst, src *mat64.Vector) {
	dMat := dst.RawVector().Data
	for i, r := range m {
		dMat[i] = r.dotUnitary(src)
	}
}

// compressedRow implements a simplified scatter-based Ddot.
type compressedRow []sparseElement

// addTo adds the value v to the vector element at j. Repeated
// calls to addTo with the same vector index will result in
// non-unique element representation.
func (r *compressedRow) addTo(j int, v float64) {
	*r = append(*r, sparseElement{index: j, value: v})
}

// dotUnitary performs a simplified scatter-based Ddot operations on
// v and the receiver. v must have have a unitary vector increment.
func (r compressedRow) dotUnitary(v *mat64.Vector) float64 {
	var sum float64
	vec := v.RawVector().Data
	for _, e := range r {
		sum += vec[e.index] * e.value
	}
	return sum
}

// sparseElement is a sparse vector or matrix element.
type sparseElement struct {
	index int
	value float64
}

// onesDotUnitary performs the equivalent of a Ddot of v with
// a ones vector of equal length. v must have have a unitary
// vector increment.
func onesDotUnitary(alpha float64, v *mat64.Vector) float64 {
	var sum float64
	for _, f := range v.RawVector().Data {
		sum += alpha * f
	}
	return sum
}

// normDiff returns the 2-norm of the difference between x and y.
// This is a cut down version of gonum/floats.Distance.
func normDiff(x, y []float64) float64 {
	var sum float64
	for i, v := range x {
		d := v - y[i]
		sum += d * d
	}
	return math.Sqrt(sum)
}
<commit_msg>network: use new MulVec signature<commit_after>// Copyright ©2015 The gonum Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package network

import (
	"math"
	"math/rand"

	"github.com/gonum/floats"
	"github.com/gonum/graph"
	"github.com/gonum/matrix/mat64"
)

// PageRank returns the PageRank weights for nodes of the directed graph g
// using the given damping factor and terminating when the 2-norm of the
// vector difference between iterations is below tol. The returned map is
// keyed on the graph node IDs.
func PageRank(g graph.Directed, damp, tol float64) map[int]float64 {
	// PageRank is implemented according to "How Google Finds Your Needle
	// in the Web's Haystack".
	//
	// G.I^k = alpha.S.I^k + (1-alpha).1/n.1.I^k
	//
	// http://www.ams.org/samplings/feature-column/fcarc-pagerank

	nodes := g.Nodes()
	indexOf := make(map[int]int, len(nodes))
	for i, n := range nodes {
		indexOf[n.ID()] = i
	}

	m := mat64.NewDense(len(nodes), len(nodes), nil)
	dangling := damp / float64(len(nodes))
	for j, u := range nodes {
		to := g.From(u)
		f := damp / float64(len(to))
		for _, v := range to {
			m.Set(indexOf[v.ID()], j, f)
		}
		if len(to) == 0 {
			for i := range nodes {
				m.Set(i, j, dangling)
			}
		}
	}
	mat := m.RawMatrix().Data
	dt := (1 - damp) / float64(len(nodes))
	for i := range mat {
		mat[i] += dt
	}

	last := make([]float64, len(nodes))
	for i := range last {
		last[i] = 1
	}
	lastV := mat64.NewVector(len(nodes), last)

	vec := make([]float64, len(nodes))
	var sum float64
	for i := range vec {
		r := rand.NormFloat64()
		sum += r
		vec[i] = r
	}
	f := 1 / sum
	for i := range vec {
		vec[i] *= f
	}
	v := mat64.NewVector(len(nodes), vec)

	for {
		lastV, v = v, lastV
		v.MulVec(m, lastV)
		if normDiff(vec, last) < tol {
			break
		}
	}

	ranks := make(map[int]float64, len(nodes))
	for i, r := range v.RawVector().Data {
		ranks[nodes[i].ID()] = r
	}

	return ranks
}

// PageRankSparse returns the PageRank weights for nodes of the sparse directed
// graph g using the given damping factor and terminating when the 2-norm of the
// vector difference between iterations is below tol. The returned map is
// keyed on the graph node IDs.
func PageRankSparse(g graph.Directed, damp, tol float64) map[int]float64 {
	// PageRankSparse is implemented according to "How Google Finds Your Needle
	// in the Web's Haystack".
	//
	// G.I^k = alpha.H.I^k + alpha.A.I^k + (1-alpha).1/n.1.I^k
	//
	// http://www.ams.org/samplings/feature-column/fcarc-pagerank

	nodes := g.Nodes()
	indexOf := make(map[int]int, len(nodes))
	for i, n := range nodes {
		indexOf[n.ID()] = i
	}

	m := make(rowCompressedMatrix, len(nodes))
	var dangling compressedRow
	df := damp / float64(len(nodes))
	for j, u := range nodes {
		to := g.From(u)
		f := damp / float64(len(to))
		for _, v := range to {
			m.addTo(indexOf[v.ID()], j, f)
		}
		if len(to) == 0 {
			dangling.addTo(j, df)
		}
	}

	last := make([]float64, len(nodes))
	for i := range last {
		last[i] = 1
	}
	lastV := mat64.NewVector(len(nodes), last)

	vec := make([]float64, len(nodes))
	var sum float64
	for i := range vec {
		r := rand.NormFloat64()
		sum += r
		vec[i] = r
	}
	f := 1 / sum
	for i := range vec {
		vec[i] *= f
	}
	v := mat64.NewVector(len(nodes), vec)

	dt := (1 - damp) / float64(len(nodes))
	for {
		lastV, v = v, lastV

		m.mulVecUnitary(v, lastV)          // First term of the G matrix equation;
		with := dangling.dotUnitary(lastV) // Second term;
		away := onesDotUnitary(dt, lastV)  // Last term.

		floats.AddConst(with+away, v.RawVector().Data)
		if normDiff(vec, last) < tol {
			break
		}
	}

	ranks := make(map[int]float64, len(nodes))
	for i, r := range v.RawVector().Data {
		ranks[nodes[i].ID()] = r
	}

	return ranks
}

// rowCompressedMatrix implements row-compressed
// matrix/vector multiplication.
type rowCompressedMatrix []compressedRow

// addTo adds the value v to the matrix element at (i,j). Repeated
// calls to addTo with the same column index will result in
// non-unique element representation.
func (m rowCompressedMatrix) addTo(i, j int, v float64) { m[i].addTo(j, v) }

// mulVecUnitary multiplies the receiver by the src vector, storing
// the result in dst. It assumes src and dst are the same length as m
// and that both have unitary vector increments.
func (m rowCompressedMatrix) mulVecUnitary(dst, src *mat64.Vector) {
	dMat := dst.RawVector().Data
	for i, r := range m {
		dMat[i] = r.dotUnitary(src)
	}
}

// compressedRow implements a simplified scatter-based Ddot.
type compressedRow []sparseElement

// addTo adds the value v to the vector element at j. Repeated
// calls to addTo with the same vector index will result in
// non-unique element representation.
func (r *compressedRow) addTo(j int, v float64) {
	*r = append(*r, sparseElement{index: j, value: v})
}

// dotUnitary performs a simplified scatter-based Ddot operations on
// v and the receiver. v must have have a unitary vector increment.
func (r compressedRow) dotUnitary(v *mat64.Vector) float64 {
	var sum float64
	vec := v.RawVector().Data
	for _, e := range r {
		sum += vec[e.index] * e.value
	}
	return sum
}

// sparseElement is a sparse vector or matrix element.
type sparseElement struct {
	index int
	value float64
}

// onesDotUnitary performs the equivalent of a Ddot of v with
// a ones vector of equal length. v must have have a unitary
// vector increment.
func onesDotUnitary(alpha float64, v *mat64.Vector) float64 {
	var sum float64
	for _, f := range v.RawVector().Data {
		sum += alpha * f
	}
	return sum
}

// normDiff returns the 2-norm of the difference between x and y.
// This is a cut down version of gonum/floats.Distance.
func normDiff(x, y []float64) float64 {
	var sum float64
	for i, v := range x {
		d := v - y[i]
		sum += d * d
	}
	return math.Sqrt(sum)
}
<|endoftext|>


<commit_before>/*
 * Send mail messages through SMTP server
 * ======================================
 *
 * - The connections to the service can be either plain (port 25)
 *   or SSL/TLS (port 465)
 * - If the server supports STARTTLS and the channel is not already
 *   encrypted (via SSL), the application will use the "STLS" command
 *   to initiate a channel encryption.
 * - Connections can be tunneled through any SOCKS5 proxy (like Tor)
 *
 * (c) 2013-2014 Bernd Fix    >Y<
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or (at
 * your option) any later version.
 *
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program. If not, see <http://www.gnu.org/licenses/>.
 */

package network

///////////////////////////////////////////////////////////////////////
// Import external declarations.

import (
	"bytes"
	"code.google.com/p/go.crypto/openpgp"
	"code.google.com/p/go.crypto/openpgp/armor"
	"crypto/rand"
	"crypto/tls"
	"errors"
	"fmt"
	"github.com/bfix/gospel/logger"
	"io"
	"mime/multipart"
	"net"
	"net/smtp"
	"net/textproto"
	"net/url"
)

///////////////////////////////////////////////////////////////////////
/*
 * Handle outgoing message to SMTP server.
 * @param host string - SMTP URL of service
 * @param proxy string - proxy service
 * @param fromAddr string - sender address
 * @param toAddr string - receiver address
 * @param body []byte - mail body
 * @return error - error instance or nil
 */
func SendMailMessage(host, proxy, fromAddr, toAddr string, body []byte) error {
	var (
		c0  net.Conn
		c1  *tls.Conn
		cli *smtp.Client
	)
	defer func() {
		if cli != nil {
			cli.Close()
		}
		if c1 != nil {
			c1.Close()
		}
		if c0 != nil {
			c0.Close()
		}
	}()

	uSrv, err := url.Parse(host)
	if err != nil {
		return err
	}
	if proxy == "" {
		c0, err = net.Dial("tcp", uSrv.Host)
	} else {
		host, port, err := SplitHost(uSrv.Host)
		if err != nil {
			return err
		}
		c0, err = Socks5Connect("tcp", host, port, proxy)
	}
	if err != nil {
		return err
	}
	if c0 == nil {
		return errors.New("Can't estabish connection to " + uSrv.Host)
	}

	sslConfig := &tls.Config{InsecureSkipVerify: true}
	if uSrv.Scheme == "smtps" {
		c1 = tls.Client(c0, sslConfig)
		if err = c1.Handshake(); err != nil {
			return err
		}
		cli, err = smtp.NewClient(c1, uSrv.Host)
	} else {
		cli, err = smtp.NewClient(c0, uSrv.Host)
		if err == nil {
			if ok, _ := cli.Extension("STLS"); ok {
				err = cli.StartTLS(sslConfig)
			}
		}
	}
	if err != nil {
		return err
	}
	pw, _ := uSrv.User.Password()
	auth := smtp.PlainAuth("", uSrv.User.Username(), pw, uSrv.Host)
	if err = cli.Auth(auth); err != nil {
		return err
	}
	if err = cli.Mail(fromAddr); err != nil {
		return err
	}
	if err = cli.Rcpt(toAddr); err != nil {
		return err
	}
	wrt, err := cli.Data()
	if err != nil {
		return err
	}
	wrt.Write(body)
	wrt.Close()
	if err = cli.Quit(); err != nil {
		return err
	}
	return nil
}

///////////////////////////////////////////////////////////////////////
// Message creation functions

type MailAttachment struct {
	Header textproto.MIMEHeader
	Data   []byte
}

//---------------------------------------------------------------------
/*
 * Create (plain) SMTP email with body and optional attachments.
 * @param body []byte - mail body (UTF-8 encoded)
 * @param att []MailAttachment - list of attachments
 * @return []byte - data to be send to SMTP server (or encryption)
 * @return error - error instance or nil
 */
func CreateMailMessage(body []byte, att []*MailAttachment) ([]byte, error) {
	buf := new(bytes.Buffer)
	wrt := multipart.NewWriter(buf)
	buf.WriteString(
		"MIME-Version: 1.0\n" +
			"Content-Type: multipart/mixed;\n" +
			" boundary=\"" + wrt.Boundary() + "\"\n\n" +
			"This is a multi-part message in MIME format.\n")
	hdr := textproto.MIMEHeader{}
	hdr.Set("Content-Type", "text/plain; charset=ISO-8859-15")
	hdr.Set("Content-Transfer-Encoding", "utf-8")
	pw, err := wrt.CreatePart(hdr)
	if err != nil {
		return nil, err
	}
	pw.Write(body)

	for _, a := range att {
		pw, err = wrt.CreatePart(a.Header)
		if err != nil {
			return nil, err
		}
		pw.Write(a.Data)
	}
	wrt.Close()
	return buf.Bytes(), nil
}

//---------------------------------------------------------------------
/*
 * Encrypt mail with given public key.
 * @param key []byte - public OpenPGP key used for encryption
 * @param body []byte - mail body
 * @return []byte - data to be send to SMTP server
 * @return error - error instance or nil
 */
func EncryptMailMessage(key, body []byte) ([]byte, error) {
	rdr := bytes.NewBuffer(key)
	keyring, err := openpgp.ReadArmoredKeyRing(rdr)
	if err != nil {
		logger.Println(logger.ERROR, err.Error())
		return nil, err
	}

	out := new(bytes.Buffer)
	ct, err := armor.Encode(out, "PGP MESSAGE", nil)
	if err != nil {
		logger.Println(logger.ERROR, "Can't create armorer: "+err.Error())
		return nil, err
	}
	wrt, err := openpgp.Encrypt(ct, []*openpgp.Entity{keyring[0]}, nil, &openpgp.FileHints{IsBinary: true}, nil)
	if err != nil {
		logger.Println(logger.ERROR, err.Error())
		return nil, err
	}
	wrt.Write(body)
	wrt.Close()
	ct.Close()

	tmp := make([]byte, 30)
	_, err = io.ReadFull(rand.Reader, tmp)
	if err != nil {
		logger.Println(logger.ERROR, err.Error())
		return nil, err
	}
	bndry := fmt.Sprintf("%x", tmp)
	msg := new(bytes.Buffer)
	msg.WriteString(
		"MIME-Version: 1.0\n" +
			"Content-Type: multipart/encrypted;\n" +
			" protocol=\"application/pgp-encrypted\";\n" +
			" boundary=\"" + bndry + "\"\n\n" +
			"This is an OpenPGP/MIME encrypted message (RFC 4880 and 3156)\n" +
			"--" + bndry + "\n" +
			"Content-Type: application/pgp-encrypted\n" +
			"Content-Description: PGP/MIME version identification\n\n" +
			"Version: 1\n\n" +
			"--" + bndry + "\n" +
			"Content-Type: application/octet-stream;\n name=\"encrypted.asc\"\n" +
			"Content-Description: OpenPGP encrypted message\n" +
			"Content-Disposition: inline;\n filename=\"encrypted.asc\"\n\n" +
			string(out.Bytes()) + "\n--" + bndry + "--")
	return msg.Bytes(), nil
}
<commit_msg>Parser/handler for mail messages (plain, encrypted, signed).<commit_after>/*
 * Send mail messages through SMTP server
 * ======================================
 *
 * - The connections to the service can be either plain (port 25)
 *   or SSL/TLS (port 465)
 * - If the server supports STARTTLS and the channel is not already
 *   encrypted (via SSL), the application will use the "STLS" command
 *   to initiate a channel encryption.
 * - Connections can be tunneled through any SOCKS5 proxy (like Tor)
 *
 * (c) 2013-2014 Bernd Fix    >Y<
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or (at
 * your option) any later version.
 *
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program. If not, see <http://www.gnu.org/licenses/>.
 */

package network

///////////////////////////////////////////////////////////////////////
// Import external declarations.

import (
	"bytes"
	"code.google.com/p/go.crypto/openpgp"
	"code.google.com/p/go.crypto/openpgp/armor"
	"crypto/rand"
	"crypto/tls"
	"errors"
	"fmt"
	"github.com/bfix/gospel/crypto"
	"github.com/bfix/gospel/logger"
	"io"
	"io/ioutil"
	"mime/multipart"
	"net"
	"net/mail"
	"net/smtp"
	"net/textproto"
	"net/url"
	"strings"
)

///////////////////////////////////////////////////////////////////////
/*
 * Handle outgoing message to SMTP server.
 * @param host string - SMTP URL of service
 * @param proxy string - proxy service
 * @param fromAddr string - sender address
 * @param toAddr string - receiver address
 * @param body []byte - mail body
 * @return error - error instance or nil
 */
func SendMailMessage(host, proxy, fromAddr, toAddr string, body []byte) error {
	var (
		c0  net.Conn
		c1  *tls.Conn
		cli *smtp.Client
	)
	defer func() {
		if cli != nil {
			cli.Close()
		}
		if c1 != nil {
			c1.Close()
		}
		if c0 != nil {
			c0.Close()
		}
	}()

	uSrv, err := url.Parse(host)
	if err != nil {
		return err
	}
	if proxy == "" {
		c0, err = net.Dial("tcp", uSrv.Host)
	} else {
		host, port, err := SplitHost(uSrv.Host)
		if err != nil {
			return err
		}
		c0, err = Socks5Connect("tcp", host, port, proxy)
	}
	if err != nil {
		return err
	}
	if c0 == nil {
		return errors.New("Can't estabish connection to " + uSrv.Host)
	}

	sslConfig := &tls.Config{InsecureSkipVerify: true}
	if uSrv.Scheme == "smtps" {
		c1 = tls.Client(c0, sslConfig)
		if err = c1.Handshake(); err != nil {
			return err
		}
		cli, err = smtp.NewClient(c1, uSrv.Host)
	} else {
		cli, err = smtp.NewClient(c0, uSrv.Host)
		if err == nil {
			if ok, _ := cli.Extension("STLS"); ok {
				err = cli.StartTLS(sslConfig)
			}
		}
	}
	if err != nil {
		return err
	}
	pw, _ := uSrv.User.Password()
	auth := smtp.PlainAuth("", uSrv.User.Username(), pw, uSrv.Host)
	if err = cli.Auth(auth); err != nil {
		return err
	}
	if err = cli.Mail(fromAddr); err != nil {
		return err
	}
	if err = cli.Rcpt(toAddr); err != nil {
		return err
	}
	wrt, err := cli.Data()
	if err != nil {
		return err
	}
	wrt.Write(body)
	wrt.Close()
	if err = cli.Quit(); err != nil {
		return err
	}
	return nil
}

///////////////////////////////////////////////////////////////////////
// Message creation functions

type MailAttachment struct {
	Header textproto.MIMEHeader
	Data   []byte
}

//---------------------------------------------------------------------
/*
 * Create (plain) SMTP email with body and optional attachments.
 * @param body []byte - mail body (UTF-8 encoded)
 * @param att []MailAttachment - list of attachments
 * @return []byte - data to be send to SMTP server (or encryption)
 * @return error - error instance or nil
 */
func CreateMailMessage(body []byte, att []*MailAttachment) ([]byte, error) {
	buf := new(bytes.Buffer)
	wrt := multipart.NewWriter(buf)
	buf.WriteString(
		"MIME-Version: 1.0\n" +
			"Content-Type: multipart/mixed;\n" +
			" boundary=\"" + wrt.Boundary() + "\"\n\n" +
			"This is a multi-part message in MIME format.\n")
	hdr := textproto.MIMEHeader{}
	hdr.Set("Content-Type", "text/plain; charset=ISO-8859-15")
	hdr.Set("Content-Transfer-Encoding", "utf-8")
	pw, err := wrt.CreatePart(hdr)
	if err != nil {
		return nil, err
	}
	pw.Write(body)

	for _, a := range att {
		pw, err = wrt.CreatePart(a.Header)
		if err != nil {
			return nil, err
		}
		pw.Write(a.Data)
	}
	wrt.Close()
	return buf.Bytes(), nil
}

//---------------------------------------------------------------------
/*
 * Encrypt mail with given public key.
 * @param key []byte - public OpenPGP key used for encryption
 * @param body []byte - mail body
 * @return []byte - data to be send to SMTP server
 * @return error - error instance or nil
 */
func EncryptMailMessage(key, body []byte) ([]byte, error) {
	rdr := bytes.NewBuffer(key)
	keyring, err := openpgp.ReadArmoredKeyRing(rdr)
	if err != nil {
		logger.Println(logger.ERROR, err.Error())
		return nil, err
	}

	out := new(bytes.Buffer)
	ct, err := armor.Encode(out, "PGP MESSAGE", nil)
	if err != nil {
		logger.Println(logger.ERROR, "Can't create armorer: "+err.Error())
		return nil, err
	}
	wrt, err := openpgp.Encrypt(ct, []*openpgp.Entity{keyring[0]}, nil, &openpgp.FileHints{IsBinary: true}, nil)
	if err != nil {
		logger.Println(logger.ERROR, err.Error())
		return nil, err
	}
	wrt.Write(body)
	wrt.Close()
	ct.Close()

	tmp := make([]byte, 30)
	_, err = io.ReadFull(rand.Reader, tmp)
	if err != nil {
		logger.Println(logger.ERROR, err.Error())
		return nil, err
	}
	bndry := fmt.Sprintf("%x", tmp)
	msg := new(bytes.Buffer)
	msg.WriteString(
		"MIME-Version: 1.0\n" +
			"Content-Type: multipart/encrypted;\n" +
			" protocol=\"application/pgp-encrypted\";\n" +
			" boundary=\"" + bndry + "\"\n\n" +
			"This is an OpenPGP/MIME encrypted message (RFC 4880 and 3156)\n" +
			"--" + bndry + "\n" +
			"Content-Type: application/pgp-encrypted\n" +
			"Content-Description: PGP/MIME version identification\n\n" +
			"Version: 1\n\n" +
			"--" + bndry + "\n" +
			"Content-Type: application/octet-stream;\n name=\"encrypted.asc\"\n" +
			"Content-Description: OpenPGP encrypted message\n" +
			"Content-Disposition: inline;\n filename=\"encrypted.asc\"\n\n" +
			string(out.Bytes()) + "\n--" + bndry + "--")
	return msg.Bytes(), nil
}

//---------------------------------------------------------------------
/*
 * Result type for parsing mail messages
 */
type MailContent struct {
	Mode int    // message type (MDOE_XXX)
	From string // sender email address
	Body string // message body
	Key  []byte // attached key or signing key (public)
}

/*
 * Callback function to request user information:
 * @param key int - info type requested (INFO_XXX)
 * @param data string - additional data
 * @return interface{} - return object
 */
type MailUserInfo func(key int, data string) interface{}

//---------------------------------------------------------------------
/*
 * Parsing-related constants
 */
const (
	ct_PLAIN   = "text/plain;"
	ct_MP_MIX  = "multipart/mixed;"
	ct_MP_ENC  = "multipart/encrypted;"
	ct_MP_SIGN = "multipart/signed;"

	MODE_PLAIN = iota
	MODE_SIGN
	MODE_ENC
	MODE_SIGN_ENC
	MODE_USIGN_ENC // unverified signature (key missing)

	INFO_IDENTITY = iota
	INFO_PASSPHRASE
	INFO_SENDER
)

//---------------------------------------------------------------------
/*
 * Parse mail message:
 * @param msg io.Reader - message reader
 * @param getInfo MailUserInfo - callback for info retrieval
 * @return error - error instance or nil
 */
func ParseMailMessage(msg io.Reader, getInfo MailUserInfo) (*MailContent, error) {
	m, err := mail.ReadMessage(msg)
	if err != nil {
		return nil, err
	}
	addr, err := mail.ParseAddress(m.Header.Get("From"))
	if err != nil {
		return nil, err
	}
	ct := m.Header.Get("Content-Type")
	var mc *MailContent = nil
	if strings.HasPrefix(ct, ct_PLAIN) {
		mc = new(MailContent)
		mc.Mode = MODE_PLAIN
		mc.Key = nil
		data, err := ioutil.ReadAll(m.Body)
		if err != nil {
			return nil, err
		}
		mc.Body = string(data)
	} else if strings.HasPrefix(ct, ct_MP_MIX) {
		mc, err = ParsePlain(ct, m.Body)
	} else if strings.HasPrefix(ct, ct_MP_ENC) {
		mc, err = ParseEncrypted(ct, addr.Address, getInfo, m.Body)
	} else if strings.HasPrefix(ct, ct_MP_SIGN) {
		mc, err = ParseSigned(ct, addr.Address, m.Body)
	}
	if err != nil {
		return nil, err
	}
	if mc == nil {
		return nil, errors.New("Unparsed mail message")
	}
	mc.From = addr.Address
	return mc, nil
}

//---------------------------------------------------------------------
/*
 * Parse plain text message.
 * @param ct string - content type string
 * @param body io.Reader - content reader
 * @return *MailContent - parse result
 * @return error - error instance or nil
 */
func ParsePlain(ct string, body io.Reader) (*MailContent, error) {
	mc := new(MailContent)
	mc.Mode = MODE_PLAIN
	boundary := extractValue(ct, "boundary")
	rdr := multipart.NewReader(body, boundary)
	for {
		if part, err := rdr.NextPart(); err == nil {
			ct = part.Header.Get("Content-Type")
			switch {
			case strings.HasPrefix(ct, "text/plain;"):
				data, err := ioutil.ReadAll(part)
				if err != nil {
					return nil, err
				}
				mc.Body = string(data)
			case strings.HasPrefix(ct, "application/pgp-keys;"):
				mc.Key, err = ioutil.ReadAll(part)
				if err != nil {
					return nil, err
				}
			default:
				return nil, errors.New("Unhandled MIME part: " + ct)
			}
		} else if err == io.EOF {
			break
		} else {
			return nil, err
		}
	}
	return mc, nil
}

//---------------------------------------------------------------------
/*
 * Parse encrypted (and possibly signed) message.
 * @param ct string - content type string
 * @param addr string - sender address
 * @param getInfo MailUserInfo - callback for info retrieval
 * @param body io.Reader - content reader
 * @return *MailContent - parse result
 * @return error - error instance or nil
 */
func ParseEncrypted(ct, addr string, getInfo MailUserInfo, body io.Reader) (*MailContent, error) {
	mc := new(MailContent)
	mc.Mode = MODE_ENC
	boundary := extractValue(ct, "boundary")
	rdr := multipart.NewReader(body, boundary)
	for {
		if part, err := rdr.NextPart(); err == nil {
			ct = part.Header.Get("Content-Type")
			switch {
			case strings.HasPrefix(ct, "application/pgp-encrypted"):
				buf, err := ioutil.ReadAll(part)
				if err != nil {
					return nil, err
				}
				logger.Printf(logger.DBG, "application/pgp-encrypted: '%s'\n", strings.TrimSpace(string(buf)))
				continue
			case strings.HasPrefix(ct, "application/octet-stream;"):
				rdr, err := armor.Decode(part)
				if err != nil {
					return nil, err
				}
				pw := getInfo(INFO_PASSPHRASE, "").(string)
				prompt := func(keys []openpgp.Key, symmetric bool) ([]byte, error) {
					priv := keys[0].PrivateKey
					if priv.Encrypted {
						priv.Decrypt([]byte(pw))
					}
					buf := new(bytes.Buffer)
					priv.Serialize(buf)
					return buf.Bytes(), nil
				}
				identity := getInfo(INFO_IDENTITY, "").(*openpgp.Entity)
				md, err := openpgp.ReadMessage(rdr.Body, openpgp.EntityList{identity}, prompt, nil)
				if err != nil {
					return nil, err
				}
				if md.IsSigned {
					mc.Mode = MODE_SIGN_ENC
					id := getInfo(INFO_SENDER, addr).(*openpgp.Entity)
					if id == nil {
						mc.Mode = MODE_USIGN_ENC
						content, err := ioutil.ReadAll(md.UnverifiedBody)
						if err != nil {
							return nil, err
						}
						mc.Body = string(content)
						continue
					}
					md.SignedBy = crypto.GetKeyFromIdentity(id, crypto.KEY_SIGN)
					md.SignedByKeyId = md.SignedBy.PublicKey.KeyId
					mc.Key, err = crypto.GetArmoredPublicKey(id)
					if err != nil {
						return nil, err
					}
					content, err := ioutil.ReadAll(md.UnverifiedBody)
					if err != nil {
						return nil, err
					}
					if md.SignatureError != nil {
						return nil, md.SignatureError
					}
					logger.Println(logger.INFO, "Signature verified OK")

					m, err := mail.ReadMessage(bytes.NewBuffer(content))
					if err != nil {
						return nil, err
					}
					ct = m.Header.Get("Content-Type")
					mc2, err := ParsePlain(ct, m.Body)
					if err != nil {
						return nil, err
					}
					mc.Body = mc2.Body
				}
			default:
				return nil, errors.New("Unhandled MIME part: " + ct)
			}
		} else if err == io.EOF {
			break
		} else {
			return nil, err
		}
	}
	return mc, nil
}

//---------------------------------------------------------------------
/*
 * Parse signed unencrypted message.
 * @param ct string - content type string
 * @param addr string - sender address
 * @param body io.Reader - content reader
 * @return *MailContent - parse result
 * @return error - error instance or nil
 */
func ParseSigned(ct, addr string, body io.Reader) (*MailContent, error) {
	mc := new(MailContent)
	mc.Mode = MODE_ENC
	boundary := extractValue(ct, "boundary")
	rdr := multipart.NewReader(body, boundary)
	for {
		if part, err := rdr.NextPart(); err == nil {
			ct = part.Header.Get("Content-Type")
			switch {
			case strings.HasPrefix(ct, "text/plain;"):
				data, err := ioutil.ReadAll(part)
				if err != nil {
					return nil, err
				}
				mc.Body = string(data)
			case strings.HasPrefix(ct, "application/pgp-signature;"):
				data, err := ioutil.ReadAll(part)
				if err != nil {
					return nil, err
				}
				fmt.Println("Signature: " + string(data))
			}
		} else if err == io.EOF {
			break
		} else {
			return nil, err
		}
	}
	return mc, nil
}

//---------------------------------------------------------------------
/*
 * Extract value from string ('... key="value" ...')
 * @param s string - input string
 * @param key string - name of key
 * @param string - value string (or empty)
 */
func extractValue(s, key string) string {
	idx := strings.Index(s, key)
	skip := idx + len(key) + 2
	if idx < 0 || len(s) < skip {
		return ""
	}
	s = s[skip:]
	idx = strings.IndexRune(s, '"')
	if idx < 0 {
		return ""
	}
	return s[:idx]
}
<|endoftext|>