func (t *threadCGI) printPostForm(ca *thread.Cache) {
	mimes := []string{
		".css", ".gif", ".htm", ".html", ".jpg", ".js", ".pdf", ".png", ".svg",
		".txt", ".xml",
	}
	s := struct {
		Cache    *thread.Cache
		Suffixes []string
		Limit    int
		cgi.Defaults
	}{
		ca,
		mimes,
		cfg.RecordLimit * 3 >> 2,
		*t.Defaults(),
	}
	cgi.RenderTemplate("post_form", s, t.WR)
}
printPostForm renders post_form.txt,page for posting attached file.
package thread
import (
	"encoding/base64"
	"errors"
	"fmt"
	"html"
	"html/template"
	"log"
	"math"
	"math/rand"
	"mime"
	"mime/multipart"
	"net/http"
	"net/url"
	"path"
	"regexp"
	"strconv"
	"strings"
	"time"
	"github.com/gorilla/handlers"
	"github.com/gorilla/mux"
	"github.com/shingetsu-gou/shingetsu-gou/cfg"
	"github.com/shingetsu-gou/shingetsu-gou/cgi"
	"github.com/shingetsu-gou/shingetsu-gou/record"
	"github.com/shingetsu-gou/shingetsu-gou/tag/user"
	"github.com/shingetsu-gou/shingetsu-gou/thread"
	"github.com/shingetsu-gou/shingetsu-gou/thread/download"
	"github.com/shingetsu-gou/shingetsu-gou/updateque"
	"github.com/shingetsu-gou/shingetsu-gou/util"
)
func Setup(s *cgi.LoggingServeMux) {
	rtr := mux.NewRouter()
	cgi.RegistToRouter(rtr, cfg.ThreadURL+"/", printThreadIndex)
	reg := cfg.ThreadURL + "/{datfile:thread_[0-9A-F]+}/{id:[0-9a-f]{32}}/s{stamp:\\d+}.{thumbnailSize:\\d+x\\d+}.{suffix:.*}"
	cgi.RegistToRouter(rtr, reg, printAttach)
	reg = cfg.ThreadURL + "/{datfile:thread_[0-9A-F]+}/{id:[0-9a-f]{32}}/{stamp:\\d+}.{suffix:.*}"
	cgi.RegistToRouter(rtr, reg, printAttach)
	reg = cfg.ThreadURL + "/{path:[^/]+}{end:/?$}"
	cgi.RegistToRouter(rtr, reg, printThread)
	reg = cfg.ThreadURL + "/{path:[^/]+}/{id:[0-9a-f]{8}}{end:$}"
	cgi.RegistToRouter(rtr, reg, printThread)
	reg = cfg.ThreadURL + "/{path:[^/]+}/p{page:[0-9]+}{end:$}"
	cgi.RegistToRouter(rtr, reg, printThread)
	s.Handle(cfg.ThreadURL+"/", handlers.CompressHandler(rtr))
}
func printThreadIndex(w http.ResponseWriter, r *http.Request) {
	if a, err := new(w, r); err == nil {
		a.printThreadIndex()
	}
}
func printAttach(w http.ResponseWriter, r *http.Request) {
	a, err := new(w, r)
	if err != nil {
		return
	}
	m := mux.Vars(r)
	var stamp int64
	if m["stamp"] != "" {
		var err error
		stamp, err = strconv.ParseInt(m["stamp"], 10, 64)
		if err != nil {
			log.Println(err)
			return
		}
	}
	a.printAttach(m["datfile"], m["id"], stamp, m["thumbnailSize"], m["suffix"])
}
func printThread(w http.ResponseWriter, r *http.Request) {
	a, errr := new(w, r)
	if errr != nil {
		return
	}
	m := mux.Vars(r)
	var page int
	if m["page"] != "" {
		var err error
		page, err = strconv.Atoi(m["page"])
		if err != nil {
			return
		}
	}
	path, err := url.QueryUnescape(m["path"])
	if err != nil {
		log.Print(err)
		return
	}
	a.printThread(path, m["id"], page)
}
type threadCGI struct {
	*cgi.CGI
}
func new(w http.ResponseWriter, r *http.Request) (*threadCGI, error) {
	c, err := cgi.NewCGI(w, r)
	if err != nil {
		c.Print403()
		return nil, err
	}
	if !c.CheckVisitor() {
		c.Print403()
		return nil, errors.New("visitor now allowed")
	}
	t := threadCGI{
		CGI: c,
	}
	t.IsThread = true
	return &t, nil
}
func (t *threadCGI) printThreadIndex() {
	err := t.Req.ParseMultipartForm(int64(cfg.RecordLimit) << 10)
	if err != nil {
		t.Print404(nil, "")
		return
	}
	if t.Req.FormValue("cmd") != "post" || !strings.HasPrefix(t.Req.FormValue("file"), "thread_") {
		t.Print404(nil, "")
		return
	}
	id := t.doPost()
	if id == "" {
		t.Print404(nil, "")
		return
	}
	datfile := t.Req.FormValue("file")
	title := util.StrEncode(util.FileDecode(datfile))
	t.Print302(cfg.ThreadURL + "/" + title + "#r" + id)
}
func (t *threadCGI) setCookie(ca *thread.Cache, access string) []*http.Cookie {
	const (
		saveCookie = 7 * 24 * time.Hour  
	)
	c := http.Cookie{}
	c.Expires = time.Now().Add(saveCookie)
	c.Path = cfg.ThreadURL + "/" + util.StrEncode(util.FileDecode(ca.Datfile))
	c.Name = "access"
	c.Value = strconv.FormatInt(time.Now().Unix(), 10)
	if access == "" {
		return []*http.Cookie{&c}
	}
	cc := http.Cookie{}
	cc.Name = "tmpaccess"
	cc.Value = access
	return []*http.Cookie{&c, &cc}
}
func (t *threadCGI) printPageNavi(path string, page int, ca *thread.Cache, id string) {
	len := ca.Len(record.Alive)
	first := len / cfg.ThreadPageSize
	if len%cfg.ThreadPageSize == 0 {
		first++
	}
	pages := make([]int, first+1)
	for i := 0; i <= first; i++ {
		pages[i] = i
	}
	s := struct {
		Page           int
		CacheLen       int
		Path           string
		ID             string
		First          int
		ThreadCGI      string
		Message        cgi.Message
		ThreadPageSize int
		Pages          []int
	}{
		page,
		len,
		path,
		id,
		first,
		cfg.ThreadURL,
		t.M,
		cfg.ThreadPageSize,
		pages,
	}
	cgi.RenderTemplate("page_navi", s, t.WR)
}
func (t *threadCGI) printTag(ca *thread.Cache) {
	s := struct {
		Datfile   string
		Tags      []string
		Classname string
		Target    string
		cgi.Defaults
	}{
		ca.Datfile,
		user.GetStrings(ca.Datfile),
		"tags",
		"changes",
		*t.Defaults(),
	}
	cgi.RenderTemplate("thread_tags", s, t.WR)
}
func (t *threadCGI) printThreadHead(path, id string, page int, ca *thread.Cache, rss string) error {
	switch {
	case ca.HasRecord():
		if !t.IsBot() {
			download.GetCache(true, ca)
		} else {
			log.Println("bot detected, not get cache")
		}
	case t.CheckGetCache():
		ca.Subscribe()
		if t.Req.FormValue("search_new_file") == "" {
			download.GetCache(true, ca)
		}
	default:
		t.Print404(nil, id)
		return errors.New("no records")
	}
	var access string
	var newcookie []*http.Cookie
	if ca.HasRecord() && id == "" && page == 0 {
		cookie, err := t.Req.Cookie("access")
		if err == nil {
			access = cookie.Value
		} else {
			log.Println(err)
		}
		newcookie = t.setCookie(ca, access)
	}
	t.Header(path, rss, newcookie, false)
	return nil
}
func (t *threadCGI) printThreadTop(path, id string, nPage int, ca *thread.Cache) {
	var lastrec *record.Record
	var resAnchor string
	recs := ca.LoadRecords(record.Alive)
	ids := recs.Keys()
	if ca.HasRecord() && nPage == 0 && id == "" && len(ids) > 0 {
		lastrec = recs[ids[len(ids)-1]]
		resAnchor = t.ResAnchor(lastrec.ID[:8], cfg.ThreadURL, t.Path(), false)
	}
	s := struct {
		Path      string
		Cache     *thread.Cache
		Lastrec   *record.Record
		ResAnchor template.HTML
		cgi.Defaults
	}{
		path,
		ca,
		lastrec,
		template.HTML(resAnchor),
		*t.Defaults(),
	}
	cgi.RenderTemplate("thread_top", s, t.WR)
}
func (t *threadCGI) printThreadBody(id string, nPage int, ca *thread.Cache) {
	recs := ca.LoadRecords(record.Alive)
	ids := recs.Keys()
	fmt.Fprintln(t.WR, "</p>\n<dl id=\"records\">")
	from := len(ids) - cfg.ThreadPageSize*(nPage+1)
	to := len(ids) - cfg.ThreadPageSize*(nPage)
	if from < 0 {
		from = 0
	}
	if to < 0 {
		to = 0
	}
	var inrange []string
	switch {
	case id != "":
		inrange = ids
	case nPage > 0:
		inrange = ids[from:to]
	default:
		inrange = ids[from:]
	}
	for _, k := range inrange {
		rec := recs.Get(k, nil)
		if (id == "" || rec.ID[:8] == id) && rec.Load() == nil {
			t.printRecord(ca, rec)
		}
	}
	fmt.Fprintln(t.WR, "</dl>")
}
func (t *threadCGI) printThread(path, id string, nPage int) {
	if id != "" && t.Req.FormValue("ajax") != "" {
		t.printThreadAjax(id)
		return
	}
	filePath := util.FileEncode("thread", path)
	ca := thread.NewCache(filePath)
	rss := cfg.GatewayURL + "/rss"
	if t.printThreadHead(path, id, nPage, ca, rss) != nil {
		return
	}
	tags := strings.Fields(strings.TrimSpace(t.Req.FormValue("tag")))
	if t.IsAdmin() && len(tags) > 0 {
		user.Add(ca.Datfile, tags)
	}
	t.printTag(ca)
	t.printThreadTop(path, id, nPage, ca)
	t.printPageNavi(path, nPage, ca, id)
	t.printThreadBody(id, nPage, ca)
	escapedPath := html.EscapeString(path)
	escapedPath = strings.Replace(escapedPath, "  ", "&nbsp;&nbsp;", -1)
	ss := struct {
		Cache   *thread.Cache
		Message cgi.Message
	}{
		ca,
		t.M,
	}
	cgi.RenderTemplate("thread_bottom", ss, t.WR)
	if ca.HasRecord() {
		t.printPageNavi(path, nPage, ca, id)
		fmt.Fprintf(t.WR, "</p>")
	}
	t.printPostForm(ca)
	t.printTag(ca)
	t.RemoveFileForm(ca, escapedPath)
	t.Footer(t.MakeMenubar("bottom", rss))
}
func (t *threadCGI) printThreadAjax(id string) {
	th := strings.Split(t.Path(), "/")[0]
	filePath := util.FileEncode("thread", th)
	ca := thread.NewCache(filePath)
	if !ca.HasRecord() {
		log.Println(filePath, "not found")
		return
	}
	fmt.Fprintln(t.WR, "<dl>")
	recs := ca.LoadRecords(record.Alive)
	for _, rec := range recs {
		if id == "" || rec.ID[:8] == id && rec.Load() == nil {
			t.printRecord(ca, rec)
		}
	}
	fmt.Fprintln(t.WR, "</dl>")
}
func (t *threadCGI) printRecord(ca *thread.Cache, rec *record.Record) {
	thumbnailSize := ""
	var suffix string
	var attachSize int64
	if at := rec.GetBodyValue("attach", ""); at != "" {
		suffix = rec.GetBodyValue("suffix", "")
		attachFile := rec.AttachPath("")
		attachSize = int64(len(at)*57/78) + 1000
		reg := regexp.MustCompile("^[0-9A-Za-z]+")
		if !reg.MatchString(suffix) {
			suffix = cfg.SuffixTXT
		}
		typ := mime.TypeByExtension("." + suffix)
		if typ == "" {
			typ = "text/plain"
		}
		if util.IsValidImage(typ, attachFile) {
			thumbnailSize = cfg.DefaultThumbnailSize
		}
	}
	body := rec.GetBodyValue("body", "")
	body = t.HTMLFormat(body, cfg.ThreadURL, t.Path(), false)
	removeID := rec.GetBodyValue("remove_id", "")
	if len(removeID) > 8 {
		removeID = removeID[:8]
	}
	resAnchor := t.ResAnchor(removeID, cfg.ThreadURL, t.Path(), false)
	id8 := rec.ID
	if len(id8) > 8 {
		id8 = id8[:8]
	}
	s := struct {
		Datfile    string
		Rec        *record.Record
		RecHead    record.Head
		Sid        string
		AttachSize int64
		Suffix     string
		Body       template.HTML
		Thumbnail  string
		RemoveID   string
		ResAnchor  string
		cgi.Defaults
	}{
		ca.Datfile,
		rec,
		rec.CopyHead(),
		id8,
		attachSize,
		suffix,
		template.HTML(body),
		thumbnailSize,
		removeID,
		resAnchor,
		*t.Defaults(),
	}
	cgi.RenderTemplate("record", s, t.WR)
}