//printAttach renders the content of attach file and makes thumnail if needed and possible. func (t *threadCGI) printAttach(datfile, id string, stamp int64, thumbnailSize, suffix string) { ca := thread.NewCache(datfile) switch { case ca.HasRecord(): case t.CheckGetCache(): download.GetCache(true, ca) default: t.Print404(ca, "") return } rec := record.New(ca.Datfile, id, stamp) if !rec.Exists() { t.Print404(ca, "") return } if err := rec.Load(); err != nil { t.Print404(ca, "") return } if rec.GetBodyValue("suffix", "") != suffix { t.Print404(ca, "") return } t.renderAttach(rec, suffix, stamp, thumbnailSize) }
//doRecent renders records whose timestamp is in range of one specified in url. func doRecent(w http.ResponseWriter, r *http.Request) { s, err := new(w, r) if err != nil { log.Println(err) return } reg := regexp.MustCompile("^recent/?([-0-9A-Za-z/]*)$") m := reg.FindStringSubmatch(s.Path()) if m == nil { log.Println("illegal url") return } stamp := m[1] last := time.Now().Unix() + cfg.RecentRange begin, end, _ := s.parseStamp(stamp, last) for _, i := range recentlist.GetRecords() { if begin > i.Stamp || i.Stamp > end { continue } ca := thread.NewCache(i.Datfile) cont := fmt.Sprintf("%d<>%s<>%s", i.Stamp, i.ID, i.Datfile) if user.Len(ca.Datfile) > 0 { cont += "<>tag:" + user.String(ca.Datfile) } _, err := fmt.Fprintf(w, "%s\n", cont) if err != nil { log.Println(err) } } }
//checkInfo checks posted info and returs thread name. //if ok. func (m *mchCGI) checkInfo(info map[string]string) string { var key string if info["subject"] != "" { key = util.FileEncode("thread", info["subject"]) } else { n, err := strconv.ParseInt(info["key"], 10, 64) if err != nil { m.errorResp(err.Error(), info) return "" } key = keylib.GetFilekey(n) } switch { case info["body"] == "": m.errorResp("本文がありません.", info) return "" case thread.NewCache(key).Exists(), m.HasAuth(): case info["subject"] != "": m.errorResp("掲示版を作る権限がありません", info) return "" default: m.errorResp("掲示版がありません", info) return "" } if info["subject"] == "" && key == "" { m.errorResp("フォームが変です.", info) return "" } return key }
//threadApp load thread.Cache specified in the url and returns dat file //listing records. if thread.Cache len=0 or for each refering the thread.Cache 4 times //reloads thread.Cache fron network. func (m *mchCGI) threadApp(board, datkey string) { m.WR.Header().Set("Content-Type", "text/plain; charset=Shift_JIS") n, err := strconv.ParseInt(datkey, 10, 64) if err != nil { log.Println(err) return } key := keylib.GetFilekey(n) if err != nil { m.WR.WriteHeader(404) fmt.Fprintf(m.WR, "404 Not Found") return } data := thread.NewCache(key) if !data.Exists() { m.WR.WriteHeader(404) fmt.Fprintf(m.WR, "404 Not Found") return } if m.CheckGetCache() { download.GetCache(true, data) } thread := keylib.MakeDat(data, board, m.Req.Host) str := strings.Join(thread, "\n") + "\n" m.serveContent("a.txt", time.Unix(data.Stamp(), 0), str) }
//UpdateNodes do doUpdateNode for each records using related nodes. //if success to doUpdateNode, add node to updatelist and recentlist and //removes the record from queue. func UpdateNodes(rec *record.Record, n *node.Node) { if !doUpdateNode(rec, n) { return } recentlist.Append(rec.Head) if !cfg.HeavyMoon { return } if ca := thread.NewCache(rec.Datfile); !ca.Exists() { ca.Subscribe() } }
//postCommentApp checks posted data and replaces >> links to html links, //and saves it as record. func (m *mchCGI) postCommentApp() { if m.Req.Method != http.MethodPost { m.WR.Header().Set("Content-Type", "text/plain") m.WR.WriteHeader(404) fmt.Fprintf(m.WR, "404 Not Found") return } info := m.getCommentData() info["host"] = m.Req.Host key := m.checkInfo(info) if key == "" { return } referer := m.getCP932("Referer") reg := regexp.MustCompile("/2ch_([^/]+)/") var tag string if ma := reg.FindStringSubmatch(referer); ma != nil && m.HasAuth() { tag = util.FileDecode("dummy_" + ma[1]) } table := mch.NewResTable(thread.NewCache(key)) reg = regexp.MustCompile(">>([1-9][0-9]*)") body := reg.ReplaceAllStringFunc(info["body"], func(str string) string { noStr := reg.FindStringSubmatch(str)[1] no, err := strconv.Atoi(noStr) if err != nil { log.Fatal(err) } return ">>" + table.Num2id[no] }) name := info["name"] var passwd string if strings.ContainsRune(name, '#') { ary := strings.Split(name, "#") name = ary[0] passwd = ary[1] } if passwd != "" && !m.IsAdmin() { m.errorResp("自ノード以外で署名機能は使えません", info) } err := m.postComment(key, name, info["mail"], body, passwd, tag) if err == errSpamM { m.errorResp("スパムとみなされました", info) } m.WR.Header().Set("Content-Type", "text/html; charset=Shift_JIS") fmt.Fprintln(m.WR, util.ToSJIS(`<html lang="ja"><head><meta http-equiv="Content-Type" content="text/html"><title>書きこみました。</title></head><body>書きこみが終わりました。<br><br></body></html>`)) }
//GetDatkey returns stamp from filekey. //if not found, tries to read from cache. func GetDatkey(filekey string) (int64, error) { var v int64 err := db.DB.Update(func(tx *bolt.Tx) error { var errr error v, errr = getTime(tx, filekey) if errr == nil { return nil } c := thread.NewCache(filekey) setFromCache(tx, c) v, errr = getTime(tx, filekey) return errr }) return v, err }
//Load loads from the file, adds stamps/datfile pairs from cachelist and recentlist. //and saves to file. func Load() { allCaches := thread.AllCaches() allRecs := recentlist.GetRecords() err := db.DB.Update(func(tx *bolt.Tx) error { for _, c := range allCaches { setFromCache(tx, c) } for _, rec := range allRecs { c := thread.NewCache(rec.Datfile) setFromCache(tx, c) } return nil }) if err != nil { log.Println(err) } }
//printThreadAjax renders records in cache id for ajax. 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>") }
//doUpdateNode broadcast and get data for each new records. //if can get data (even if spam) return true, if fails to get, return false. //if no fail, broadcast updates to node in cache and added n to nodelist and searchlist. func doUpdateNode(rec *record.Record, n *node.Node) bool { mutex.Lock() deleteOldUpdated() if _, exist := updated[rec.Hash()]; exist { log.Println("already broadcasted", rec.ID) mutex.Unlock() return true } updated[rec.Hash()] = time.Now() mutex.Unlock() ca := thread.NewCache(rec.Datfile) var err error if !ca.Exists() || n == nil { log.Println("no cache or updates by myself, broadcast updates.") UpdatedRecord.register(rec.Head) manager.TellUpdate(ca.Datfile, rec.Stamp, rec.ID, n) if UpdatedRecord.wait() || n != nil { log.Println(rec.ID, "was gotten or don't have the record") } else { log.Println(rec.ID, "was NOT gotten, will call updates later") go func() { time.Sleep(10 * time.Minute) UpdateNodes(rec, n) }() } return true } log.Println("cache exists. get record from node n.") err = rec.GetData(n) switch err { case cfg.ErrGet: log.Println("could not get") return false case cfg.ErrSpam: log.Println("marked spam") return true default: log.Println("telling update") manager.TellUpdate(ca.Datfile, rec.Stamp, rec.ID, nil) manager.Join(n) return true } }
//MakeBracketLink changes str in brackets to the html links format. func MakeBracketLink(body, datHost, board string, table *mch.ResTable) string { regs := []*regexp.Regexp{ regexp.MustCompile("^(?P<title>[^/]+)$"), regexp.MustCompile("^/(?P<type>[a-z]+)/(?P<title>[^/]+)$"), regexp.MustCompile("^(?P<title>[^/]+)/(?P<id>[0-9a-f]{8})$"), regexp.MustCompile("^/(?P<type>[a-z]+)/(?P<title>[^/]+)/(?P<id>[0-9a-f]{8})$"), } reg := regexp.MustCompile(`\[\[([^<>]+?)\]\]`) return reg.ReplaceAllStringFunc(body, func(str string) string { link := reg.FindStringSubmatch(str)[1] result := make(map[string]string) for _, r := range regs { if match := r.FindStringSubmatch(link); match != nil { for i, name := range r.SubexpNames() { result[name] = match[i] } break } } if result["title"] == "" { return result["body"] } if result["type"] == "" { result["type"] = "thread" } file := util.FileEncode(result["type"], result["title"]) datkey, err := GetDatkey(file) if err != nil { log.Println(err) return body } if result["id"] == "" { url := fmt.Sprintf("http://%s/test/read.cgi/%s/%d/", datHost, board, datkey) return fmt.Sprintf("[[%s(%s)]]", result["title"], url) } ca := thread.NewCache(file) table = mch.NewResTable(ca) no := table.ID2num[result["id"]] url := fmt.Sprintf("http://%s/test/read.cgi/%s/%d/%d", datHost, board, datkey, no) return fmt.Sprintf("[[%s(>>%d %s)]]", result["title"], no, url) }) }
//doHave checks existance of cache whose name is specified in url. func doHave(w http.ResponseWriter, r *http.Request) { s, err := new(w, r) if err != nil { log.Println(err) return } reg := regexp.MustCompile("^have/([0-9A-Za-z_]+)$") m := reg.FindStringSubmatch(s.Path()) if m == nil { fmt.Fprintln(w, "NO") log.Println("illegal url") return } ca := thread.NewCache(m[1]) if ca.HasRecord() { fmt.Fprintln(w, "YES") } else { fmt.Fprintln(w, "NO") } }
//postComment creates a record from args and adds it to thread.Cache. //also adds tag if not tag!="" func (m *mchCGI) postComment(threadKey, name, mail, body, passwd, tag string) error { stamp := time.Now().Unix() recbody := make(map[string]string) recbody["body"] = html.EscapeString(body) recbody["name"] = html.EscapeString(name) recbody["mail"] = html.EscapeString(mail) c := thread.NewCache(threadKey) rec := record.New(c.Datfile, "", 0) rec.Build(stamp, recbody, passwd) if rec.IsSpam() { return errSpamM } rec.Sync() if tag != "" { user.Set(c.Datfile, []string{tag}) } go updateque.UpdateNodes(rec, nil) return nil }
//doPost parses multipart form ,makes record of it and adds to cache. //if form dopost=yes broadcasts it. func (t *threadCGI) doPost() string { attached, attachedErr := t.parseAttached() if attachedErr != nil { log.Println(attachedErr) } suffix := t.guessSuffix(attached) ca := thread.NewCache(t.Req.FormValue("file")) rec, err := t.makeRecord(attached, suffix, ca) if err != nil { return "" } proxyClient := t.Req.Header.Get("X_FORWARDED_FOR") log.Printf("post %s/%d_%s from %s/%s\n", ca.Datfile, ca.Stamp(), rec.ID, t.Req.RemoteAddr, proxyClient) if len(rec.Recstr()) > cfg.RecordLimit<<10 { t.Header(t.M["big_file"], "", nil, true) t.Footer(nil) return "" } if rec.IsSpam() { t.Header(t.M["spam"], "", nil, true) t.Footer(nil) return "" } if ca.Exists() { rec.Sync() } else { t.Print404(nil, "") return "" } if t.Req.FormValue("dopost") != "" { log.Println(rec.Datfile, rec.ID, "is queued") go updateque.UpdateNodes(rec, nil) } return rec.ID[:8] }
//printThread renders whole thread list page. 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, " ", " ", -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)) }
//doGetHead renders records contents(get) or id+timestamp(head) who has id and // whose stamp is in range of one specified by url. func doGetHead(w http.ResponseWriter, r *http.Request) { s, err := new(w, r) if err != nil { log.Println(err) return } reg := regexp.MustCompile("^(get|head|removed)/([0-9A-Za-z_]+)/?([-0-9A-Za-z/]*)$") m := reg.FindStringSubmatch(s.Path()) if m == nil { log.Println("illegal url", s.Path()) return } method, datfile, stamp := m[1], m[2], m[3] ca := thread.NewCache(datfile) begin, end, id := s.parseStamp(stamp, math.MaxInt32) var recs record.Map if method == "removed" { recs = ca.LoadRecords(record.Removed) } else { recs = ca.LoadRecords(record.Alive) } for _, r := range recs { if r.InRange(begin, end, id) { if method == "get" { if err := r.Load(); err != nil { log.Println(err) continue } fmt.Fprintln(s.WR, r.Recstr()) continue } fmt.Fprintln(s.WR, strings.Replace(r.Idstr(), "_", "<>", -1)) } } if method == "get" { updateque.UpdatedRecord.Inform(datfile, id, begin, end) } }