//Make makes and returns record from Recstr func Make(line string) (*Record, error) { line = strings.TrimRight(line, "\r\n") buf := strings.Split(line, "<>") if len(buf) <= 2 || buf[0] == "" || buf[1] == "" || buf[2] == "" { err := errors.New("illegal format") return nil, err } idstr := buf[0] + "_" + buf[1] dec := util.FileDecode(buf[2]) if dec == "" || !strings.HasPrefix(buf[2], "thread_") { err := errors.New("illegal format " + buf[2]) // log.Println(err) return nil, err } buf[2] = util.FileEncode("thread", dec) vr, err := NewIDstr(buf[2], idstr) if err != nil { return nil, err } if err := vr.Parse(line); err != nil { log.Println(err) return nil, err } return vr, nil }
//appendRSS appends cache ca to rss with contents,url to records,stamp,attached file. func (g *gatewayCGI) appendRSS(rsss *cgi.RSS, ca *thread.Cache) { now := time.Now().Unix() if ca.Stamp()+cfg.RSSRange < now { return } title := util.Escape(util.FileDecode(ca.Datfile)) path := cfg.ThreadURL + "/" + util.StrEncode(title) recs := ca.LoadRecords(record.Alive) for _, r := range recs { if r.Stamp+cfg.RSSRange < now { continue } if err := r.Load(); err != nil { log.Println(err) continue } desc := cgi.RSSTextFormat(r.GetBodyValue("body", "")) content := g.rssHTMLFormat(r.GetBodyValue("body", ""), cfg.ThreadURL, title) if attach := r.GetBodyValue("attach", ""); attach != "" { suffix := r.GetBodyValue("suffix", "") if reg := regexp.MustCompile("^[0-9A-Za-z]+$"); !reg.MatchString(suffix) { suffix = cfg.SuffixTXT } content += fmt.Sprintf("\n <p><a href=\"http://%s%s%s%s/%s/%d.%s\">%d.%s</a></p>", g.Host(), cfg.ThreadURL, "/", ca.Datfile, r.ID, r.Stamp, suffix, r.Stamp, suffix) } permpath := fmt.Sprintf("%s/%s", path[1:], r.ID[:8]) rsss.Append(permpath, title, cgi.RSSTextFormat(r.GetBodyValue("name", "")), desc, content, user.GetStrings(ca.Datfile), r.Stamp, false) } }
//MakeDat makes dat lines of 2ch from cache. func MakeDat(ca *thread.Cache, board, host string) []string { recs := ca.LoadRecords(record.Alive) dat := make([]string, len(recs)) table := mch.NewResTable(ca) i := 0 for _, datfile := range recs.Keys() { rec := recs[datfile] err := rec.Load() if err != nil { log.Println(err) continue } name := rec.GetBodyValue("name", "") if name == "" { name = "名無しさん" } if rec.GetBodyValue("pubkey", "") != "" { name += "◆" + rec.GetBodyValue("pubkey", "")[:10] } comment := fmt.Sprintf("%s<>%s<>%s<>%s<>", name, rec.GetBodyValue("main", ""), util.Datestr2ch(rec.Stamp), MakeBody(rec, host, board, table)) if i == 0 { comment += util.FileDecode(ca.Datfile) } dat[i] = comment i++ } return dat }
//makeSubject makes subject.txt(list of records title) from thread.Caches with tag=board. func (m *mchCGI) makeSubject(board string) ([]string, int64) { loadFromNet := m.CheckGetCache() var subjects []string cl := m.makeSubjectCachelist(board) var lastStamp int64 for _, c := range cl { if !loadFromNet && c.Len() == 0 { continue } if lastStamp < c.Stamp() { lastStamp = c.Stamp() } key, err := keylib.GetDatkey(c.Datfile) if err != nil { log.Println(err) continue } titleStr := util.FileDecode(c.Datfile) if titleStr == "" { continue } titleStr = strings.Trim(titleStr, "\r\n") subjects = append(subjects, fmt.Sprintf("%d.dat<>%s (%d)", key, titleStr, c.Len())) } return subjects, lastStamp }
//subjectApp makes list of records title from thread.Caches whose tag is same as one stripped from url. func (m *mchCGI) subjectApp(board string) { var boardEncoded, boardName string if board != "" { boardEncoded = util.StrDecode(board) } if boardEncoded != "" { boardName = util.FileDecode("dummy_" + boardEncoded) } subject, lastStamp := m.makeSubject(boardName) m.WR.Header().Set("Content-Type", "text/plain; charset=Shift_JIS") m.serveContent("a.txt", time.Unix(lastStamp, 0), strings.Join(subject, "\n")+"\n") }
//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>`)) }
//checkCache checks cache ca has specified tag and datfile doesn't contains filterd string. func checkCache(ca *thread.Cache, target, filter, tag string) (string, bool) { x := util.FileDecode(ca.Datfile) if x == "" { return "", false } if filter != "" && !strings.Contains(strings.ToLower(x), filter) { return "", false } if tag != "" { switch { case user.Has(ca.Datfile, strings.ToLower(tag)): case target == "recent" && suggest.HasTagstr(ca.Datfile, strings.ToLower(tag)): default: return "", false } } return x, true }
//setCookie set cookie access=now time,tmpaccess=access var. func (t *threadCGI) setCookie(ca *thread.Cache, access string) []*http.Cookie { const ( saveCookie = 7 * 24 * time.Hour // Seconds ) 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} }
//printThreadIndex adds records in multiform and redirect to its thread page. 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) }
//renderCSV renders CSV including key string of caches in disk. //key is specified in url query. func (g *gatewayCGI) renderCSV(cl thread.Caches) { g.WR.Header().Set("Content-Type", "text/comma-separated-values;charset=UTF-8") p := strings.Split(g.Path(), "/") if len(p) < 3 { g.Print404(nil, "") return } cols := strings.Split(p[2], ",") cwr := csv.NewWriter(g.WR) for _, ca := range cl { title := util.FileDecode(ca.Datfile) p := cfg.ThreadURL + "/" + util.StrEncode(title) row := make([]string, len(cols)) for i, c := range cols { row[i] = g.makeOneRow(c, ca, p, title) } err := cwr.Write(row) if err != nil { log.Println(err) } } cwr.Flush() }
//printRecentRSS renders rss of caches which are written recently(are updated remotely). //including title,tags,last-modified. func printRecentRSS(w http.ResponseWriter, r *http.Request) { g, err := new(w, r) if err != nil { log.Println(err) return } rsss := cgi.NewRSS("UTF-8", "", fmt.Sprintf("%s - %s", g.M["recent"], g.M["logo"]), "http://"+g.Host(), "", "http://"+g.Host()+cfg.GatewayURL+"/"+"recent_rss", g.M["description"], xslURL) cl := thread.MakeRecentCachelist() for _, ca := range cl { title := util.Escape(util.FileDecode(ca.Datfile)) tags := suggest.Get(ca.Datfile, nil) tags = append(tags, user.GetByThread(ca.Datfile)...) rsss.Append(cfg.ThreadURL[1:]+"/"+util.StrEncode(title), title, "", "", html.EscapeString(title), tags.GetTagstrSlice(), ca.RecentStamp(), false) } g.WR.Header().Set("Content-Type", "text/xml; charset=UTF-8") if rsss.Len() != 0 { g.WR.Header().Set("Last-Modified", g.RFC822Time(rsss.Feeds[0].Date)) } rsss.MakeRSS1(g.WR) }
//Gettitle returns title part if *_*. //returns ca.datfile if not. //used in templates func (c *Cache) Gettitle() string { if strings.HasPrefix(c.Datfile, "thread_") { return util.FileDecode(c.Datfile) } return c.Datfile }