all repos — honk @ 8f4ed81333e2e8c1ac2bcef39c0b61f1f6cd33f7

my fork of honk

honk.go (view raw)

   1//
   2// Copyright (c) 2019 Ted Unangst <tedu@tedunangst.com>
   3//
   4// Permission to use, copy, modify, and distribute this software for any
   5// purpose with or without fee is hereby granted, provided that the above
   6// copyright notice and this permission notice appear in all copies.
   7//
   8// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
   9// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
  10// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
  11// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
  12// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
  13// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
  14// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
  15
  16package main
  17
  18import (
  19	"bytes"
  20	"crypto/rand"
  21	"crypto/rsa"
  22	"database/sql"
  23	"fmt"
  24	"html"
  25	"html/template"
  26	"image"
  27	_ "image/gif"
  28	_ "image/jpeg"
  29	_ "image/png"
  30	"io"
  31	"log"
  32	"net/http"
  33	"os"
  34	"sort"
  35	"strings"
  36	"sync"
  37	"time"
  38
  39	"github.com/gorilla/mux"
  40)
  41
  42type UserInfo struct {
  43	UserID   int64
  44	Username string
  45}
  46
  47type WhatAbout struct {
  48	ID      int64
  49	Name    string
  50	Display string
  51	About   string
  52	Key     string
  53	URL     string
  54}
  55
  56type Honk struct {
  57	ID       int64
  58	UserID   int64
  59	Username string
  60	What     string
  61	Honker   string
  62	XID      string
  63	RID      string
  64	Date     time.Time
  65	URL      string
  66	Noise    string
  67	Audience []string
  68	HTML     template.HTML
  69	Donks    []*Donk
  70}
  71
  72type Donk struct {
  73	FileID  int64
  74	XID     string
  75	Name    string
  76	URL     string
  77	Media   string
  78	Content []byte
  79}
  80
  81type Honker struct {
  82	ID     int64
  83	UserID int64
  84	Name   string
  85	XID    string
  86	Flavor string
  87}
  88
  89var serverName string
  90var iconName = "icon.png"
  91
  92var readviews *Template
  93
  94func getInfo(r *http.Request) map[string]interface{} {
  95	templinfo := make(map[string]interface{})
  96	templinfo["StyleParam"] = getstyleparam("views/style.css")
  97	templinfo["LocalStyleParam"] = getstyleparam("views/local.css")
  98	templinfo["ServerName"] = serverName
  99	templinfo["IconName"] = iconName
 100	templinfo["UserInfo"] = GetUserInfo(r)
 101	templinfo["LogoutCSRF"] = GetCSRF("logout", r)
 102	return templinfo
 103}
 104
 105func homepage(w http.ResponseWriter, r *http.Request) {
 106	templinfo := getInfo(r)
 107	u := GetUserInfo(r)
 108	var honks []*Honk
 109	if u != nil {
 110		honks = gethonksforuser(u.UserID)
 111		templinfo["HonkCSRF"] = GetCSRF("honkhonk", r)
 112	} else {
 113		honks = gethonks()
 114	}
 115	sort.Slice(honks, func(i, j int) bool {
 116		return honks[i].Date.After(honks[j].Date)
 117	})
 118	reverbolate(honks)
 119
 120	var modtime time.Time
 121	if len(honks) > 0 {
 122		modtime = honks[0].Date
 123	}
 124	debug := false
 125	getconfig("debug", &debug)
 126	imh := r.Header.Get("If-Modified-Since")
 127	if !debug && imh != "" && !modtime.IsZero() {
 128		ifmod, err := time.Parse(http.TimeFormat, imh)
 129		if err == nil && !modtime.After(ifmod) {
 130			w.WriteHeader(http.StatusNotModified)
 131			return
 132		}
 133	}
 134
 135	msg := "Things happen."
 136	getconfig("servermsg", &msg)
 137	templinfo["Honks"] = honks
 138	templinfo["ShowRSS"] = true
 139	templinfo["ServerMessage"] = msg
 140	w.Header().Set("Cache-Control", "max-age=0")
 141	w.Header().Set("Last-Modified", modtime.Format(http.TimeFormat))
 142	err := readviews.ExecuteTemplate(w, "homepage.html", templinfo)
 143	if err != nil {
 144		log.Print(err)
 145	}
 146}
 147
 148func showrss(w http.ResponseWriter, r *http.Request) {
 149	name := mux.Vars(r)["name"]
 150
 151	var honks []*Honk
 152	if name != "" {
 153		honks = gethonksbyuser(name)
 154	} else {
 155		honks = gethonks()
 156	}
 157	sort.Slice(honks, func(i, j int) bool {
 158		return honks[i].Date.After(honks[j].Date)
 159	})
 160	reverbolate(honks)
 161
 162	home := fmt.Sprintf("https://%s/", serverName)
 163	base := home
 164	if name != "" {
 165		home += "u/" + name
 166		name += " "
 167	}
 168	feed := RssFeed{
 169		Title:       name + "honk",
 170		Link:        home,
 171		Description: name + "honk rss",
 172		FeedImage: &RssFeedImage{
 173			URL:   base + "icon.png",
 174			Title: name + "honk rss",
 175			Link:  home,
 176		},
 177	}
 178	var modtime time.Time
 179	past := time.Now().UTC().Add(-3 * 24 * time.Hour)
 180	for _, honk := range honks {
 181		if honk.Date.Before(past) {
 182			break
 183		}
 184		desc := string(honk.HTML)
 185		for _, d := range honk.Donks {
 186			desc += fmt.Sprintf(`<p><a href="%sd/%s">Attachment: %s</a>`,
 187				base, d.XID, html.EscapeString(d.Name))
 188		}
 189
 190		feed.Items = append(feed.Items, &RssItem{
 191			Title:       fmt.Sprintf("%s %s %s", honk.Username, honk.What, honk.XID),
 192			Description: RssCData{desc},
 193			Link:        honk.URL,
 194			PubDate:     honk.Date.Format(time.RFC1123),
 195		})
 196		if honk.Date.After(modtime) {
 197			modtime = honk.Date
 198		}
 199	}
 200	w.Header().Set("Cache-Control", "max-age=300")
 201	w.Header().Set("Last-Modified", modtime.Format(http.TimeFormat))
 202
 203	err := feed.Write(w)
 204	if err != nil {
 205		log.Printf("error writing rss: %s", err)
 206	}
 207}
 208
 209func butwhatabout(name string) (*WhatAbout, error) {
 210	row := stmtWhatAbout.QueryRow(name)
 211	var user WhatAbout
 212	err := row.Scan(&user.ID, &user.Name, &user.Display, &user.About, &user.Key)
 213	user.URL = fmt.Sprintf("https://%s/u/%s", serverName, user.Name)
 214	return &user, err
 215}
 216
 217func crappola(j map[string]interface{}) bool {
 218	t, _ := jsongetstring(j, "type")
 219	a, _ := jsongetstring(j, "actor")
 220	o, _ := jsongetstring(j, "object")
 221	if t == "Delete" && a == o {
 222		log.Printf("crappola from %s", a)
 223		return true
 224	}
 225	return false
 226}
 227
 228func ping(user *WhatAbout, who string) {
 229	inbox, _, err := getboxes(who)
 230	if err != nil {
 231		log.Printf("no inbox for ping: %s", err)
 232		return
 233	}
 234	j := NewJunk()
 235	j["@context"] = itiswhatitis
 236	j["type"] = "Ping"
 237	j["id"] = user.URL + "/ping/" + xfiltrate()
 238	j["actor"] = user.URL
 239	j["to"] = who
 240	keyname, key := ziggy(user.Name)
 241	err = PostJunk(keyname, key, inbox, j)
 242	if err != nil {
 243		log.Printf("can't send ping: %s", err)
 244		return
 245	}
 246	log.Printf("sent ping to %s: %s", who, j["id"])
 247}
 248
 249func pong(user *WhatAbout, who string, obj string) {
 250	inbox, _, err := getboxes(who)
 251	if err != nil {
 252		log.Printf("no inbox for pong %s : %s", who, err)
 253		return
 254	}
 255	j := NewJunk()
 256	j["@context"] = itiswhatitis
 257	j["type"] = "Pong"
 258	j["id"] = user.URL + "/pong/" + xfiltrate()
 259	j["actor"] = user.URL
 260	j["to"] = who
 261	j["object"] = obj
 262	keyname, key := ziggy(user.Name)
 263	err = PostJunk(keyname, key, inbox, j)
 264	if err != nil {
 265		log.Printf("can't send pong: %s", err)
 266		return
 267	}
 268}
 269
 270func inbox(w http.ResponseWriter, r *http.Request) {
 271	name := mux.Vars(r)["name"]
 272	user, err := butwhatabout(name)
 273	if err != nil {
 274		http.NotFound(w, r)
 275		return
 276	}
 277	var buf bytes.Buffer
 278	io.Copy(&buf, r.Body)
 279	payload := buf.Bytes()
 280	j, err := ReadJunk(bytes.NewReader(payload))
 281	if err != nil {
 282		log.Printf("bad payload: %s", err)
 283		io.WriteString(os.Stdout, "bad payload\n")
 284		os.Stdout.Write(payload)
 285		io.WriteString(os.Stdout, "\n")
 286		return
 287	}
 288	if crappola(j) {
 289		return
 290	}
 291	keyname, err := zag(r, payload)
 292	if err != nil {
 293		log.Printf("inbox message failed signature: %s", err)
 294		fd, _ := os.OpenFile("savedinbox.json", os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666)
 295		io.WriteString(fd, "bad signature:\n")
 296		WriteJunk(fd, j)
 297		io.WriteString(fd, "\n")
 298		fd.Close()
 299		return
 300	}
 301	fd, _ := os.OpenFile("savedinbox.json", os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666)
 302	WriteJunk(fd, j)
 303	io.WriteString(fd, "\n")
 304	fd.Close()
 305	who, _ := jsongetstring(j, "actor")
 306	if !keymatch(keyname, who) {
 307		log.Printf("keyname actor mismatch: %s <> %s", keyname, who)
 308		return
 309	}
 310	what, _ := jsongetstring(j, "type")
 311	switch what {
 312	case "Ping":
 313		obj, _ := jsongetstring(j, "id")
 314		log.Printf("ping from %s: %s", who, obj)
 315		pong(user, who, obj)
 316	case "Pong":
 317		obj, _ := jsongetstring(j, "object")
 318		log.Printf("pong from %s: %s", who, obj)
 319	case "Follow":
 320		log.Printf("updating honker follow: %s", who)
 321		rubadubdub(user, j)
 322	case "Accept":
 323		db := opendatabase()
 324		log.Printf("updating honker accept: %s", who)
 325		db.Exec("update honkers set flavor = 'sub' where xid = ? and flavor = 'presub'", who)
 326	case "Undo":
 327		obj, ok := jsongetmap(j, "object")
 328		if !ok {
 329			log.Printf("unknown undo no object")
 330		} else {
 331			what, _ := jsongetstring(obj, "type")
 332			if what != "Follow" {
 333				log.Printf("unknown undo: %s", what)
 334			} else {
 335				log.Printf("updating honker undo: %s", who)
 336				db := opendatabase()
 337				db.Exec("update honkers set flavor = 'undub' where xid = ? and flavor = 'dub'", who)
 338			}
 339		}
 340	case "Like":
 341		break
 342	default:
 343		xonk := xonkxonk(j)
 344		if xonk != nil && needxonk(user, xonk) {
 345			xonk.UserID = user.ID
 346			savexonk(xonk)
 347		}
 348	}
 349}
 350
 351func outbox(w http.ResponseWriter, r *http.Request) {
 352	name := mux.Vars(r)["name"]
 353	user, err := butwhatabout(name)
 354	if err != nil {
 355		http.NotFound(w, r)
 356		return
 357	}
 358	honks := gethonksbyuser(name)
 359
 360	var jonks []map[string]interface{}
 361	for _, h := range honks {
 362		j, _ := jonkjonk(user, h)
 363		jonks = append(jonks, j)
 364	}
 365
 366	j := NewJunk()
 367	j["@context"] = itiswhatitis
 368	j["id"] = user.URL + "/outbox"
 369	j["type"] = "OrderedCollection"
 370	j["totalItems"] = len(jonks)
 371	j["orderedItems"] = jonks
 372
 373	w.Header().Set("Content-Type", theonetruename)
 374	WriteJunk(w, j)
 375}
 376
 377func viewuser(w http.ResponseWriter, r *http.Request) {
 378	name := mux.Vars(r)["name"]
 379	user, err := butwhatabout(name)
 380	if err != nil {
 381		http.NotFound(w, r)
 382		return
 383	}
 384	if friendorfoe(r.Header.Get("Accept")) {
 385		j := asjonker(user)
 386		w.Header().Set("Content-Type", theonetruename)
 387		WriteJunk(w, j)
 388		return
 389	}
 390	honks := gethonksbyuser(name)
 391	u := GetUserInfo(r)
 392	honkpage(w, r, u, user, honks)
 393}
 394
 395func viewhonker(w http.ResponseWriter, r *http.Request) {
 396	name := mux.Vars(r)["name"]
 397	u := GetUserInfo(r)
 398	honks := gethonksbyhonker(u.UserID, name)
 399	honkpage(w, r, nil, nil, honks)
 400}
 401
 402func fingerlicker(w http.ResponseWriter, r *http.Request) {
 403	orig := r.FormValue("resource")
 404
 405	log.Printf("finger lick: %s", orig)
 406
 407	if strings.HasPrefix(orig, "acct:") {
 408		orig = orig[5:]
 409	}
 410
 411	name := orig
 412	idx := strings.LastIndexByte(name, '/')
 413	if idx != -1 {
 414		name = name[idx+1:]
 415		if "https://"+serverName+"/u/"+name != orig {
 416			log.Printf("foreign request rejected")
 417			name = ""
 418		}
 419	} else {
 420		idx = strings.IndexByte(name, '@')
 421		if idx != -1 {
 422			name = name[:idx]
 423			if name+"@"+serverName != orig {
 424				log.Printf("foreign request rejected")
 425				name = ""
 426			}
 427		}
 428	}
 429	user, err := butwhatabout(name)
 430	if err != nil {
 431		http.NotFound(w, r)
 432		return
 433	}
 434
 435	j := NewJunk()
 436	j["subject"] = fmt.Sprintf("acct:%s@%s", user.Name, serverName)
 437	j["aliases"] = []string{user.URL}
 438	var links []map[string]interface{}
 439	l := NewJunk()
 440	l["rel"] = "self"
 441	l["type"] = `application/activity+json`
 442	l["href"] = user.URL
 443	links = append(links, l)
 444	j["links"] = links
 445
 446	w.Header().Set("Content-Type", "application/jrd+json")
 447	WriteJunk(w, j)
 448}
 449
 450func viewhonk(w http.ResponseWriter, r *http.Request) {
 451	name := mux.Vars(r)["name"]
 452	xid := mux.Vars(r)["xid"]
 453	user, err := butwhatabout(name)
 454	if err != nil {
 455		http.NotFound(w, r)
 456		return
 457	}
 458	h := getxonk(name, xid)
 459	if h == nil {
 460		http.NotFound(w, r)
 461		return
 462	}
 463	if friendorfoe(r.Header.Get("Accept")) {
 464		_, j := jonkjonk(user, h)
 465		j["@context"] = itiswhatitis
 466		w.Header().Set("Content-Type", theonetruename)
 467		WriteJunk(w, j)
 468		return
 469	}
 470	honkpage(w, r, nil, nil, []*Honk{h})
 471}
 472
 473func honkpage(w http.ResponseWriter, r *http.Request, u *UserInfo, user *WhatAbout, honks []*Honk) {
 474	reverbolate(honks)
 475	templinfo := getInfo(r)
 476	if u != nil && u.Username == user.Name {
 477		templinfo["UserCSRF"] = GetCSRF("saveuser", r)
 478		templinfo["HonkCSRF"] = GetCSRF("honkhonk", r)
 479	}
 480	if user != nil {
 481		templinfo["Name"] = user.Name
 482		whatabout := user.About
 483		templinfo["RawWhatAbout"] = whatabout
 484		whatabout = obfusbreak(whatabout)
 485		templinfo["WhatAbout"] = cleanstring(whatabout)
 486	}
 487	templinfo["Honks"] = honks
 488	err := readviews.ExecuteTemplate(w, "honkpage.html", templinfo)
 489	if err != nil {
 490		log.Print(err)
 491	}
 492}
 493
 494func saveuser(w http.ResponseWriter, r *http.Request) {
 495	whatabout := r.FormValue("whatabout")
 496	u := GetUserInfo(r)
 497	db := opendatabase()
 498	_, err := db.Exec("update users set about = ? where username = ?", whatabout, u.Username)
 499	if err != nil {
 500		log.Printf("error bouting what: %s", err)
 501	}
 502
 503	http.Redirect(w, r, "/u/"+u.Username, http.StatusSeeOther)
 504}
 505
 506func gethonkers(userid int64) []*Honker {
 507	rows, err := stmtHonkers.Query(userid)
 508	if err != nil {
 509		log.Printf("error querying honkers: %s", err)
 510		return nil
 511	}
 512	defer rows.Close()
 513	var honkers []*Honker
 514	for rows.Next() {
 515		var f Honker
 516		err = rows.Scan(&f.ID, &f.UserID, &f.Name, &f.XID, &f.Flavor)
 517		if err != nil {
 518			log.Printf("error scanning honker: %s", err)
 519			return nil
 520		}
 521		honkers = append(honkers, &f)
 522	}
 523	return honkers
 524}
 525
 526func getdubs(userid int64) []*Honker {
 527	rows, err := stmtDubbers.Query(userid)
 528	if err != nil {
 529		log.Printf("error querying dubs: %s", err)
 530		return nil
 531	}
 532	defer rows.Close()
 533	var honkers []*Honker
 534	for rows.Next() {
 535		var f Honker
 536		err = rows.Scan(&f.ID, &f.UserID, &f.Name, &f.XID, &f.Flavor)
 537		if err != nil {
 538			log.Printf("error scanning honker: %s", err)
 539			return nil
 540		}
 541		honkers = append(honkers, &f)
 542	}
 543	return honkers
 544}
 545
 546func getxonk(name, xid string) *Honk {
 547	var h Honk
 548	var dt, aud string
 549	row := stmtOneXonk.QueryRow(xid)
 550	err := row.Scan(&h.ID, &h.UserID, &h.Username, &h.What, &h.Honker, &h.XID, &h.RID,
 551		&dt, &h.URL, &aud, &h.Noise)
 552	if err != nil {
 553		log.Printf("error scanning xonk: %s", err)
 554		return nil
 555	}
 556	if name != "" && h.Username != name {
 557		log.Printf("user xonk mismatch")
 558		return nil
 559	}
 560	h.Date, _ = time.Parse(dbtimeformat, dt)
 561	h.Audience = strings.Split(aud, " ")
 562	donksforhonks([]*Honk{&h})
 563	return &h
 564}
 565
 566func gethonks() []*Honk {
 567	rows, err := stmtHonks.Query()
 568	return getsomehonks(rows, err)
 569}
 570func gethonksbyuser(name string) []*Honk {
 571	rows, err := stmtUserHonks.Query(name)
 572	return getsomehonks(rows, err)
 573}
 574func gethonksforuser(userid int64) []*Honk {
 575	dt := time.Now().UTC().Add(-2 * 24 * time.Hour)
 576	rows, err := stmtHonksForUser.Query(userid, dt.Format(dbtimeformat))
 577	return getsomehonks(rows, err)
 578}
 579func gethonksbyhonker(userid int64, honker string) []*Honk {
 580	rows, err := stmtHonksByHonker.Query(userid, honker)
 581	return getsomehonks(rows, err)
 582}
 583
 584func getsomehonks(rows *sql.Rows, err error) []*Honk {
 585	if err != nil {
 586		log.Printf("error querying honks: %s", err)
 587		return nil
 588	}
 589	defer rows.Close()
 590	var honks []*Honk
 591	for rows.Next() {
 592		var h Honk
 593		var dt, aud string
 594		err = rows.Scan(&h.ID, &h.UserID, &h.Username, &h.What, &h.Honker, &h.XID, &h.RID,
 595			&dt, &h.URL, &aud, &h.Noise)
 596		if err != nil {
 597			log.Printf("error scanning honks: %s", err)
 598			return nil
 599		}
 600		h.Date, _ = time.Parse(dbtimeformat, dt)
 601		h.Audience = strings.Split(aud, " ")
 602		honks = append(honks, &h)
 603	}
 604	rows.Close()
 605	donksforhonks(honks)
 606	return honks
 607}
 608
 609func donksforhonks(honks []*Honk) {
 610	db := opendatabase()
 611	var ids []string
 612	hmap := make(map[int64]*Honk)
 613	for _, h := range honks {
 614		if h.What == "zonk" {
 615			continue
 616		}
 617		ids = append(ids, fmt.Sprintf("%d", h.ID))
 618		hmap[h.ID] = h
 619	}
 620	q := fmt.Sprintf("select honkid, donks.fileid, xid, name, url, media from donks join files on donks.fileid = files.fileid where honkid in (%s)", strings.Join(ids, ","))
 621	rows, err := db.Query(q)
 622	if err != nil {
 623		log.Printf("error querying donks: %s", err)
 624		return
 625	}
 626	defer rows.Close()
 627	for rows.Next() {
 628		var hid int64
 629		var d Donk
 630		err = rows.Scan(&hid, &d.FileID, &d.XID, &d.Name, &d.URL, &d.Media)
 631		if err != nil {
 632			log.Printf("error scanning donk: %s", err)
 633			continue
 634		}
 635		h := hmap[hid]
 636		h.Donks = append(h.Donks, &d)
 637	}
 638}
 639
 640func savebonk(w http.ResponseWriter, r *http.Request) {
 641	xid := r.FormValue("xid")
 642
 643	log.Printf("bonking %s", xid)
 644
 645	xonk := getxonk("", xid)
 646	if xonk == nil {
 647		return
 648	}
 649	if xonk.Honker == "" {
 650		xonk.XID = fmt.Sprintf("https://%s/u/%s/h/%s", serverName, xonk.Username, xonk.XID)
 651	}
 652
 653	userinfo := GetUserInfo(r)
 654
 655	dt := time.Now().UTC()
 656	bonk := Honk{
 657		UserID:   userinfo.UserID,
 658		Username: userinfo.Username,
 659		Honker:   xonk.Honker,
 660		What:     "bonk",
 661		XID:      xonk.XID,
 662		Date:     dt,
 663		Noise:    xonk.Noise,
 664		Donks:    xonk.Donks,
 665		Audience: oneofakind(prepend(thewholeworld, xonk.Audience)),
 666	}
 667
 668	aud := strings.Join(bonk.Audience, " ")
 669	res, err := stmtSaveHonk.Exec(userinfo.UserID, "bonk", "", xid, "",
 670		dt.Format(dbtimeformat), "", aud, bonk.Noise)
 671	if err != nil {
 672		log.Printf("error saving bonk: %s", err)
 673		return
 674	}
 675	bonk.ID, _ = res.LastInsertId()
 676	for _, d := range bonk.Donks {
 677		_, err = stmtSaveDonk.Exec(bonk.ID, d.FileID)
 678		if err != nil {
 679			log.Printf("err saving donk: %s", err)
 680			return
 681		}
 682	}
 683
 684	user, _ := butwhatabout(userinfo.Username)
 685
 686	go honkworldwide(user, &bonk)
 687
 688}
 689
 690func zonkit(w http.ResponseWriter, r *http.Request) {
 691	xid := r.FormValue("xid")
 692
 693	log.Printf("zonking %s", xid)
 694	userinfo := GetUserInfo(r)
 695	stmtZonkIt.Exec(userinfo.UserID, xid)
 696}
 697
 698func savehonk(w http.ResponseWriter, r *http.Request) {
 699	rid := r.FormValue("rid")
 700	noise := r.FormValue("noise")
 701
 702	userinfo := GetUserInfo(r)
 703
 704	dt := time.Now().UTC()
 705	xid := xfiltrate()
 706	what := "honk"
 707	if rid != "" {
 708		what = "tonk"
 709	}
 710	honk := Honk{
 711		UserID:   userinfo.UserID,
 712		Username: userinfo.Username,
 713		What:     "honk",
 714		XID:      xid,
 715		RID:      rid,
 716		Date:     dt,
 717	}
 718	if noise[0] == '@' {
 719		honk.Audience = append(grapevine(noise), thewholeworld)
 720	} else {
 721		honk.Audience = prepend(thewholeworld, grapevine(noise))
 722	}
 723	if rid != "" {
 724		xonk := getxonk("", rid)
 725		if xonk != nil {
 726			honk.Audience = append(honk.Audience, xonk.Audience...)
 727		} else {
 728			xonkaud := whosthere(rid)
 729			honk.Audience = append(honk.Audience, xonkaud...)
 730		}
 731	}
 732	honk.Audience = oneofakind(honk.Audience)
 733	noise = obfusbreak(noise)
 734	honk.Noise = noise
 735
 736	file, filehdr, err := r.FormFile("donk")
 737	if err == nil {
 738		var buf bytes.Buffer
 739		io.Copy(&buf, file)
 740		file.Close()
 741		data := buf.Bytes()
 742		xid := xfiltrate()
 743		var media, name string
 744		img, format, err := image.Decode(&buf)
 745		if err == nil {
 746			data, format, err = vacuumwrap(img, format)
 747			if err != nil {
 748				log.Printf("can't vacuum image: %s", err)
 749				return
 750			}
 751			media = "image/" + format
 752			if format == "jpeg" {
 753				format = "jpg"
 754			}
 755			name = xid + "." + format
 756			xid = name
 757		} else {
 758			maxsize := 100000
 759			if len(data) > maxsize {
 760				log.Printf("bad image: %s too much text: %d", err, len(data))
 761				http.Error(w, "didn't like your attachment", http.StatusUnsupportedMediaType)
 762				return
 763			}
 764			for i := 0; i < len(data); i++ {
 765				if data[i] < 32 && data[i] != '\t' && data[i] != '\r' && data[i] != '\n' {
 766					log.Printf("bad image: %s not text: %d", err, data[i])
 767					http.Error(w, "didn't like your attachment", http.StatusUnsupportedMediaType)
 768					return
 769				}
 770			}
 771			media = "text/plain"
 772			name = filehdr.Filename
 773			if name == "" {
 774				name = xid + ".txt"
 775			}
 776			xid += ".txt"
 777		}
 778		url := fmt.Sprintf("https://%s/d/%s", serverName, xid)
 779		res, err := stmtSaveFile.Exec(xid, name, url, media, data)
 780		if err != nil {
 781			log.Printf("unable to save image: %s", err)
 782			return
 783		}
 784		var d Donk
 785		d.FileID, _ = res.LastInsertId()
 786		d.XID = name
 787		d.Name = name
 788		d.Media = media
 789		d.URL = url
 790		honk.Donks = append(honk.Donks, &d)
 791	}
 792	herd := herdofemus(honk.Noise)
 793	for _, e := range herd {
 794		donk := savedonk(e.ID, e.Name, "image/png")
 795		if donk != nil {
 796			honk.Donks = append(honk.Donks, donk)
 797		}
 798	}
 799
 800	aud := strings.Join(honk.Audience, " ")
 801	res, err := stmtSaveHonk.Exec(userinfo.UserID, what, "", xid, rid,
 802		dt.Format(dbtimeformat), "", aud, noise)
 803	if err != nil {
 804		log.Printf("error saving honk: %s", err)
 805		return
 806	}
 807	honk.ID, _ = res.LastInsertId()
 808	for _, d := range honk.Donks {
 809		_, err = stmtSaveDonk.Exec(honk.ID, d.FileID)
 810		if err != nil {
 811			log.Printf("err saving donk: %s", err)
 812			return
 813		}
 814	}
 815
 816	user, _ := butwhatabout(userinfo.Username)
 817
 818	go honkworldwide(user, &honk)
 819
 820	http.Redirect(w, r, "/", http.StatusSeeOther)
 821}
 822
 823func showhonkers(w http.ResponseWriter, r *http.Request) {
 824	userinfo := GetUserInfo(r)
 825	templinfo := getInfo(r)
 826	templinfo["Honkers"] = gethonkers(userinfo.UserID)
 827	templinfo["HonkerCSRF"] = GetCSRF("savehonker", r)
 828	err := readviews.ExecuteTemplate(w, "honkers.html", templinfo)
 829	if err != nil {
 830		log.Print(err)
 831	}
 832}
 833
 834var handfull = make(map[string]string)
 835var handlock sync.Mutex
 836
 837func gofish(name string) string {
 838	if name[0] == '@' {
 839		name = name[1:]
 840	}
 841	m := strings.Split(name, "@")
 842	if len(m) != 2 {
 843		log.Printf("bad fish name: %s", name)
 844		return ""
 845	}
 846	handlock.Lock()
 847	defer handlock.Unlock()
 848	ref, ok := handfull[name]
 849	if ok {
 850		return ref
 851	}
 852	j, err := GetJunk(fmt.Sprintf("https://%s/.well-known/webfinger?resource=acct:%s", m[1], name))
 853	if err != nil {
 854		log.Printf("failed to go fish %s: %s", name, err)
 855		handfull[name] = ""
 856		return ""
 857	}
 858	links, _ := jsongetarray(j, "links")
 859	for _, l := range links {
 860		href, _ := jsongetstring(l, "href")
 861		rel, _ := jsongetstring(l, "rel")
 862		t, _ := jsongetstring(l, "type")
 863		if rel == "self" && friendorfoe(t) {
 864			handfull[name] = href
 865			return href
 866		}
 867	}
 868	handfull[name] = ""
 869	return ""
 870}
 871
 872func savehonker(w http.ResponseWriter, r *http.Request) {
 873	name := r.FormValue("name")
 874	url := r.FormValue("url")
 875	peep := r.FormValue("peep")
 876	flavor := "presub"
 877	if peep == "peep" {
 878		flavor = "peep"
 879	}
 880
 881	if url == "" {
 882		return
 883	}
 884	if url[0] == '@' {
 885		url = gofish(url)
 886	}
 887	if url == "" {
 888		return
 889	}
 890
 891	u := GetUserInfo(r)
 892	db := opendatabase()
 893	_, err := db.Exec("insert into honkers (userid, name, xid, flavor) values (?, ?, ?, ?)",
 894		u.UserID, name, url, flavor)
 895	if err != nil {
 896		log.Print(err)
 897	}
 898	if flavor == "presub" {
 899		user, _ := butwhatabout(u.Username)
 900		go subsub(user, url)
 901	}
 902	http.Redirect(w, r, "/honkers", http.StatusSeeOther)
 903}
 904
 905func avatate(w http.ResponseWriter, r *http.Request) {
 906	n := r.FormValue("a")
 907	a := avatar(n)
 908	w.Header().Set("Cache-Control", "max-age=432000")
 909	w.Write(a)
 910}
 911
 912func servecss(w http.ResponseWriter, r *http.Request) {
 913	w.Header().Set("Cache-Control", "max-age=7776000")
 914	http.ServeFile(w, r, "views"+r.URL.Path)
 915}
 916func servehtml(w http.ResponseWriter, r *http.Request) {
 917	templinfo := getInfo(r)
 918	err := readviews.ExecuteTemplate(w, r.URL.Path[1:]+".html", templinfo)
 919	if err != nil {
 920		log.Print(err)
 921	}
 922}
 923func serveemu(w http.ResponseWriter, r *http.Request) {
 924	xid := mux.Vars(r)["xid"]
 925	w.Header().Set("Cache-Control", "max-age=432000")
 926	http.ServeFile(w, r, "emus/"+xid)
 927}
 928
 929func servefile(w http.ResponseWriter, r *http.Request) {
 930	xid := mux.Vars(r)["xid"]
 931	row := stmtFileData.QueryRow(xid)
 932	var media string
 933	var data []byte
 934	err := row.Scan(&media, &data)
 935	if err != nil {
 936		log.Printf("error loading file: %s", err)
 937		http.NotFound(w, r)
 938		return
 939	}
 940	w.Header().Set("Content-Type", media)
 941	w.Header().Set("Cache-Control", "max-age=432000")
 942	w.Write(data)
 943}
 944
 945func serve() {
 946	db := opendatabase()
 947	LoginInit(db)
 948
 949	listener, err := openListener()
 950	if err != nil {
 951		log.Fatal(err)
 952	}
 953	go redeliverator()
 954
 955	debug := false
 956	getconfig("debug", &debug)
 957	readviews = ParseTemplates(debug,
 958		"views/homepage.html",
 959		"views/honkpage.html",
 960		"views/honkers.html",
 961		"views/honkform.html",
 962		"views/honk.html",
 963		"views/login.html",
 964		"views/header.html",
 965	)
 966	if !debug {
 967		s := "views/style.css"
 968		savedstyleparams[s] = getstyleparam(s)
 969		s = "views/local.css"
 970		savedstyleparams[s] = getstyleparam(s)
 971	}
 972
 973	mux := mux.NewRouter()
 974	mux.Use(LoginChecker)
 975
 976	posters := mux.Methods("POST").Subrouter()
 977	getters := mux.Methods("GET").Subrouter()
 978
 979	getters.HandleFunc("/", homepage)
 980	getters.HandleFunc("/rss", showrss)
 981	getters.HandleFunc("/u/{name:[[:alnum:]]+}", viewuser)
 982	getters.HandleFunc("/u/{name:[[:alnum:]]+}/h/{xid:[[:alnum:]]+}", viewhonk)
 983	getters.HandleFunc("/u/{name:[[:alnum:]]+}/rss", showrss)
 984	posters.HandleFunc("/u/{name:[[:alnum:]]+}/inbox", inbox)
 985	getters.HandleFunc("/u/{name:[[:alnum:]]+}/outbox", outbox)
 986	getters.HandleFunc("/a", avatate)
 987	getters.HandleFunc("/d/{xid:[[:alnum:].]+}", servefile)
 988	getters.HandleFunc("/emu/{xid:[[:alnum:]_.]+}", serveemu)
 989	getters.HandleFunc("/h/{name:[[:alnum:]]+}", viewhonker)
 990	getters.HandleFunc("/.well-known/webfinger", fingerlicker)
 991
 992	getters.HandleFunc("/style.css", servecss)
 993	getters.HandleFunc("/local.css", servecss)
 994	getters.HandleFunc("/login", servehtml)
 995	posters.HandleFunc("/dologin", dologin)
 996	getters.HandleFunc("/logout", dologout)
 997
 998	loggedin := mux.NewRoute().Subrouter()
 999	loggedin.Use(LoginRequired)
1000	loggedin.Handle("/honk", CSRFWrap("honkhonk", http.HandlerFunc(savehonk)))
1001	loggedin.Handle("/bonk", CSRFWrap("honkhonk", http.HandlerFunc(savebonk)))
1002	loggedin.Handle("/zonkit", CSRFWrap("honkhonk", http.HandlerFunc(zonkit)))
1003	loggedin.Handle("/saveuser", CSRFWrap("saveuser", http.HandlerFunc(saveuser)))
1004	loggedin.HandleFunc("/honkers", showhonkers)
1005	loggedin.Handle("/savehonker", CSRFWrap("savehonker", http.HandlerFunc(savehonker)))
1006
1007	err = http.Serve(listener, mux)
1008	if err != nil {
1009		log.Fatal(err)
1010	}
1011}
1012
1013var stmtHonkers, stmtDubbers, stmtOneXonk, stmtHonks, stmtUserHonks *sql.Stmt
1014var stmtHonksForUser, stmtDeleteHonk, stmtSaveDub *sql.Stmt
1015var stmtHonksByHonker, stmtSaveHonk, stmtFileData, stmtWhatAbout *sql.Stmt
1016var stmtFindXonk, stmtSaveDonk, stmtFindFile, stmtSaveFile *sql.Stmt
1017var stmtAddDoover, stmtGetDoovers, stmtLoadDoover, stmtZapDoover *sql.Stmt
1018var stmtZonkIt *sql.Stmt
1019
1020func preparetodie(db *sql.DB, s string) *sql.Stmt {
1021	stmt, err := db.Prepare(s)
1022	if err != nil {
1023		log.Fatal(err)
1024	}
1025	return stmt
1026}
1027
1028func prepareStatements(db *sql.DB) {
1029	stmtHonkers = preparetodie(db, "select honkerid, userid, name, xid, flavor from honkers where userid = ? and flavor = 'sub' or flavor = 'peep'")
1030	stmtDubbers = preparetodie(db, "select honkerid, userid, name, xid, flavor from honkers where userid = ? and flavor = 'dub'")
1031	stmtOneXonk = preparetodie(db, "select honkid, honks.userid, users.username, what, honker, xid, rid, dt, url, audience, noise from honks join users on honks.userid = users.userid where xid = ?")
1032	stmtHonks = preparetodie(db, "select honkid, honks.userid, users.username, what, honker, xid, rid, dt, url, audience, noise from honks join users on honks.userid = users.userid where honker = '' order by honkid desc limit 50")
1033	stmtUserHonks = preparetodie(db, "select honkid, honks.userid, username, what, honker, xid, rid, dt, url, audience, noise from honks join users on honks.userid = users.userid where honker = '' and username = ? order by honkid desc limit 50")
1034	stmtHonksForUser = preparetodie(db, "select honkid, honks.userid, users.username, what, honker, xid, rid, dt, url, audience, noise from honks join users on honks.userid = users.userid where honks.userid = ? and dt > ? order by honkid desc limit 250")
1035	stmtHonksByHonker = preparetodie(db, "select honkid, honks.userid, users.username, what, honker, honks.xid, rid, dt, url, audience, noise from honks join users on honks.userid = users.userid join honkers on honkers.xid = honks.honker where honks.userid = ? and honkers.name = ? order by honkid desc limit 50")
1036	stmtSaveHonk = preparetodie(db, "insert into honks (userid, what, honker, xid, rid, dt, url, audience, noise) values (?, ?, ?, ?, ?, ?, ?, ?, ?)")
1037	stmtFileData = preparetodie(db, "select media, content from files where xid = ?")
1038	stmtFindXonk = preparetodie(db, "select honkid from honks where userid = ? and xid = ?")
1039	stmtSaveDonk = preparetodie(db, "insert into donks (honkid, fileid) values (?, ?)")
1040	stmtDeleteHonk = preparetodie(db, "update honks set what = 'zonk' where xid = ? and honker = ?")
1041	stmtFindFile = preparetodie(db, "select fileid from files where url = ?")
1042	stmtSaveFile = preparetodie(db, "insert into files (xid, name, url, media, content) values (?, ?, ?, ?, ?)")
1043	stmtWhatAbout = preparetodie(db, "select userid, username, displayname, about, pubkey from users where username = ?")
1044	stmtSaveDub = preparetodie(db, "insert into honkers (userid, name, xid, flavor) values (?, ?, ?, ?)")
1045	stmtAddDoover = preparetodie(db, "insert into doovers (dt, tries, username, rcpt, msg) values (?, ?, ?, ?, ?)")
1046	stmtGetDoovers = preparetodie(db, "select dooverid, dt from doovers")
1047	stmtLoadDoover = preparetodie(db, "select tries, username, rcpt, msg from doovers where dooverid = ?")
1048	stmtZapDoover = preparetodie(db, "delete from doovers where dooverid = ?")
1049	stmtZonkIt = preparetodie(db, "update honks set what = 'zonk' where userid = ? and xid = ?")
1050}
1051
1052func ElaborateUnitTests() {
1053}
1054
1055func finishusersetup() error {
1056	db := opendatabase()
1057	k, err := rsa.GenerateKey(rand.Reader, 2048)
1058	if err != nil {
1059		return err
1060	}
1061	pubkey, err := zem(&k.PublicKey)
1062	if err != nil {
1063		return err
1064	}
1065	seckey, err := zem(k)
1066	if err != nil {
1067		return err
1068	}
1069	_, err = db.Exec("update users set displayname = username, about = ?, pubkey = ?, seckey = ? where userid = 1", "what about me?", pubkey, seckey)
1070	if err != nil {
1071		return err
1072	}
1073	return nil
1074}
1075
1076func main() {
1077	cmd := "run"
1078	if len(os.Args) > 1 {
1079		cmd = os.Args[1]
1080	}
1081	switch cmd {
1082	case "init":
1083		initdb()
1084	case "upgrade":
1085		upgradedb()
1086	}
1087	db := opendatabase()
1088	dbversion := 0
1089	getconfig("dbversion", &dbversion)
1090	if dbversion != myVersion {
1091		log.Fatal("incorrect database version. run upgrade.")
1092	}
1093	getconfig("servername", &serverName)
1094	prepareStatements(db)
1095	switch cmd {
1096	case "ping":
1097		if len(os.Args) < 4 {
1098			fmt.Printf("usage: honk ping from to\n")
1099			return
1100		}
1101		name := os.Args[2]
1102		targ := os.Args[3]
1103		user, err := butwhatabout(name)
1104		if err != nil {
1105			log.Printf("unknown user")
1106			return
1107		}
1108		ping(user, targ)
1109	case "peep":
1110		peeppeep()
1111	case "run":
1112		serve()
1113	case "test":
1114		ElaborateUnitTests()
1115	default:
1116		log.Fatal("unknown command")
1117	}
1118}