all repos — honk @ 63ffd1f12a8b0af3193666d013c2fcc344001621

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