all repos — honk @ e732691828c92d4255c5ef9e9155d909c79cc26b

my fork of honk

web.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	"database/sql"
  21	"fmt"
  22	"html"
  23	"html/template"
  24	"io"
  25	"log"
  26	notrand "math/rand"
  27	"net/http"
  28	"net/url"
  29	"os"
  30	"sort"
  31	"strconv"
  32	"strings"
  33	"time"
  34
  35	"github.com/gorilla/mux"
  36	"humungus.tedunangst.com/r/webs/css"
  37	"humungus.tedunangst.com/r/webs/htfilter"
  38	"humungus.tedunangst.com/r/webs/httpsig"
  39	"humungus.tedunangst.com/r/webs/image"
  40	"humungus.tedunangst.com/r/webs/junk"
  41	"humungus.tedunangst.com/r/webs/login"
  42	"humungus.tedunangst.com/r/webs/rss"
  43	"humungus.tedunangst.com/r/webs/templates"
  44)
  45
  46var readviews *templates.Template
  47
  48var userSep = "u"
  49var honkSep = "h"
  50
  51func getuserstyle(u *login.UserInfo) template.CSS {
  52	if u == nil {
  53		return ""
  54	}
  55	user, _ := butwhatabout(u.Username)
  56	if user.SkinnyCSS {
  57		return "main { max-width: 700px; }"
  58	}
  59	return ""
  60}
  61
  62func getInfo(r *http.Request) map[string]interface{} {
  63	u := login.GetUserInfo(r)
  64	templinfo := make(map[string]interface{})
  65	templinfo["StyleParam"] = getassetparam("views/style.css")
  66	templinfo["LocalStyleParam"] = getassetparam("views/local.css")
  67	templinfo["JSParam"] = getassetparam("views/honkpage.js")
  68	templinfo["UserStyle"] = getuserstyle(u)
  69	templinfo["ServerName"] = serverName
  70	templinfo["IconName"] = iconName
  71	templinfo["UserInfo"] = u
  72	templinfo["UserSep"] = userSep
  73	if u != nil {
  74		var combos []string
  75		combocache.Get(u.UserID, &combos)
  76		templinfo["Combos"] = combos
  77	}
  78	return templinfo
  79}
  80
  81func homepage(w http.ResponseWriter, r *http.Request) {
  82	templinfo := getInfo(r)
  83	u := login.GetUserInfo(r)
  84	var honks []*Honk
  85	var userid int64 = -1
  86	if r.URL.Path == "/front" || u == nil {
  87		honks = getpublichonks()
  88	} else {
  89		userid = u.UserID
  90		if r.URL.Path == "/atme" {
  91			templinfo["PageName"] = "atme"
  92			honks = gethonksforme(userid)
  93		} else if r.URL.Path == "/first" {
  94			templinfo["PageName"] = "first"
  95			honks = gethonksforuser(userid)
  96			honks = osmosis(honks, userid)
  97		} else {
  98			templinfo["PageName"] = "home"
  99			honks = gethonksforuser(userid)
 100			honks = osmosis(honks, userid)
 101		}
 102		templinfo["HonkCSRF"] = login.GetCSRF("honkhonk", r)
 103	}
 104
 105	templinfo["ShowRSS"] = true
 106	templinfo["ServerMessage"] = serverMsg
 107	honkpage(w, u, honks, templinfo)
 108}
 109
 110func showfunzone(w http.ResponseWriter, r *http.Request) {
 111	var emunames, memenames []string
 112	dir, err := os.Open("emus")
 113	if err == nil {
 114		emunames, _ = dir.Readdirnames(0)
 115		dir.Close()
 116	}
 117	for i, e := range emunames {
 118		if len(e) > 4 {
 119			emunames[i] = e[:len(e)-4]
 120		}
 121	}
 122	dir, err = os.Open("memes")
 123	if err == nil {
 124		memenames, _ = dir.Readdirnames(0)
 125		dir.Close()
 126	}
 127	templinfo := getInfo(r)
 128	templinfo["Emus"] = emunames
 129	templinfo["Memes"] = memenames
 130	err = readviews.Execute(w, "funzone.html", templinfo)
 131	if err != nil {
 132		log.Print(err)
 133	}
 134}
 135
 136func showrss(w http.ResponseWriter, r *http.Request) {
 137	name := mux.Vars(r)["name"]
 138
 139	var honks []*Honk
 140	if name != "" {
 141		honks = gethonksbyuser(name, false)
 142	} else {
 143		honks = getpublichonks()
 144	}
 145	if len(honks) > 20 {
 146		honks = honks[0:20]
 147	}
 148	reverbolate(-1, honks)
 149
 150	home := fmt.Sprintf("https://%s/", serverName)
 151	base := home
 152	if name != "" {
 153		home += "u/" + name
 154		name += " "
 155	}
 156	feed := rss.Feed{
 157		Title:       name + "honk",
 158		Link:        home,
 159		Description: name + "honk rss",
 160		Image: &rss.Image{
 161			URL:   base + "icon.png",
 162			Title: name + "honk rss",
 163			Link:  home,
 164		},
 165	}
 166	var modtime time.Time
 167	for _, honk := range honks {
 168		if !firstclass(honk) {
 169			continue
 170		}
 171		desc := string(honk.HTML)
 172		if t := honk.Time; t != nil {
 173			desc += fmt.Sprintf(`<p>Time: %s`, t.StartTime.Local().Format("03:04PM EDT Mon Jan 02"))
 174			if t.Duration != 0 {
 175				desc += fmt.Sprintf(`<br>Duration: %s`, t.Duration)
 176			}
 177		}
 178		if p := honk.Place; p != nil {
 179			desc += fmt.Sprintf(`<p>Location: <a href="%s">%s</a> %f %f`,
 180				html.EscapeString(p.Url), html.EscapeString(p.Name), p.Latitude, p.Longitude)
 181		}
 182		for _, d := range honk.Donks {
 183			desc += fmt.Sprintf(`<p><a href="%s">Attachment: %s</a>`,
 184				d.URL, html.EscapeString(d.Name))
 185		}
 186
 187		feed.Items = append(feed.Items, &rss.Item{
 188			Title:       fmt.Sprintf("%s %s %s", honk.Username, honk.What, honk.XID),
 189			Description: rss.CData{Data: desc},
 190			Link:        honk.URL,
 191			PubDate:     honk.Date.Format(time.RFC1123),
 192			Guid:        &rss.Guid{IsPermaLink: true, Value: honk.URL},
 193		})
 194		if honk.Date.After(modtime) {
 195			modtime = honk.Date
 196		}
 197	}
 198	w.Header().Set("Cache-Control", "max-age=300")
 199	w.Header().Set("Last-Modified", modtime.Format(http.TimeFormat))
 200
 201	err := feed.Write(w)
 202	if err != nil {
 203		log.Printf("error writing rss: %s", err)
 204	}
 205}
 206
 207func crappola(j junk.Junk) bool {
 208	t, _ := j.GetString("type")
 209	a, _ := j.GetString("actor")
 210	o, _ := j.GetString("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	var box *Box
 220	ok := boxofboxes.Get(who, &box)
 221	if !ok {
 222		log.Printf("no inbox to ping %s", who)
 223		return
 224	}
 225	j := junk.New()
 226	j["@context"] = itiswhatitis
 227	j["type"] = "Ping"
 228	j["id"] = user.URL + "/ping/" + xfiltrate()
 229	j["actor"] = user.URL
 230	j["to"] = who
 231	keyname, key := ziggy(user.Name)
 232	err := PostJunk(keyname, key, box.In, j)
 233	if err != nil {
 234		log.Printf("can't send ping: %s", err)
 235		return
 236	}
 237	log.Printf("sent ping to %s: %s", who, j["id"])
 238}
 239
 240func pong(user *WhatAbout, who string, obj string) {
 241	var box *Box
 242	ok := boxofboxes.Get(who, &box)
 243	if !ok {
 244		log.Printf("no inbox to pong %s", who)
 245		return
 246	}
 247	j := junk.New()
 248	j["@context"] = itiswhatitis
 249	j["type"] = "Pong"
 250	j["id"] = user.URL + "/pong/" + xfiltrate()
 251	j["actor"] = user.URL
 252	j["to"] = who
 253	j["object"] = obj
 254	keyname, key := ziggy(user.Name)
 255	err := PostJunk(keyname, key, box.In, j)
 256	if err != nil {
 257		log.Printf("can't send pong: %s", err)
 258		return
 259	}
 260}
 261
 262func inbox(w http.ResponseWriter, r *http.Request) {
 263	name := mux.Vars(r)["name"]
 264	user, err := butwhatabout(name)
 265	if err != nil {
 266		http.NotFound(w, r)
 267		return
 268	}
 269	if stealthmode(user.ID, r) {
 270		http.NotFound(w, r)
 271		return
 272	}
 273	var buf bytes.Buffer
 274	io.Copy(&buf, r.Body)
 275	payload := buf.Bytes()
 276	j, err := junk.Read(bytes.NewReader(payload))
 277	if err != nil {
 278		log.Printf("bad payload: %s", err)
 279		io.WriteString(os.Stdout, "bad payload\n")
 280		os.Stdout.Write(payload)
 281		io.WriteString(os.Stdout, "\n")
 282		return
 283	}
 284	if crappola(j) {
 285		return
 286	}
 287	keyname, err := httpsig.VerifyRequest(r, payload, zaggy)
 288	if err != nil {
 289		log.Printf("inbox message failed signature for %s from %s", keyname, r.Header.Get("X-Forwarded-For"))
 290		if keyname != "" {
 291			log.Printf("bad signature from %s", keyname)
 292			io.WriteString(os.Stdout, "bad payload\n")
 293			os.Stdout.Write(payload)
 294			io.WriteString(os.Stdout, "\n")
 295		}
 296		http.Error(w, "what did you call me?", http.StatusTeapot)
 297		return
 298	}
 299	what, _ := j.GetString("type")
 300	if what == "Like" {
 301		return
 302	}
 303	who, _ := j.GetString("actor")
 304	origin := keymatch(keyname, who)
 305	if origin == "" {
 306		log.Printf("keyname actor mismatch: %s <> %s", keyname, who)
 307		return
 308	}
 309	if rejectactor(user.ID, who) {
 310		return
 311	}
 312	switch what {
 313	case "Ping":
 314		obj, _ := j.GetString("id")
 315		log.Printf("ping from %s: %s", who, obj)
 316		pong(user, who, obj)
 317	case "Pong":
 318		obj, _ := j.GetString("object")
 319		log.Printf("pong from %s: %s", who, obj)
 320	case "Follow":
 321		obj, _ := j.GetString("object")
 322		if obj == user.URL {
 323			log.Printf("updating honker follow: %s", who)
 324
 325			db := opendatabase()
 326			row := db.QueryRow("select xid from honkers where xid = ? and userid = ? and flavor in ('dub', 'undub')", who, user.ID)
 327			var x string
 328			err = row.Scan(&x)
 329			if err != sql.ErrNoRows {
 330				log.Printf("duplicate follow request: %s", who)
 331				_, err = stmtUpdateFlavor.Exec("dub", user.ID, who, "undub")
 332				if err != nil {
 333					log.Printf("error updating honker: %s", err)
 334				}
 335			} else {
 336				stmtSaveDub.Exec(user.ID, who, who, "dub")
 337			}
 338			go rubadubdub(user, j)
 339		} else {
 340			log.Printf("can't follow %s", obj)
 341		}
 342	case "Accept":
 343		log.Printf("updating honker accept: %s", who)
 344		_, err = stmtUpdateFlavor.Exec("sub", user.ID, who, "presub")
 345		if err != nil {
 346			log.Printf("error updating honker: %s", err)
 347			return
 348		}
 349	case "Update":
 350		obj, ok := j.GetMap("object")
 351		if ok {
 352			what, _ := obj.GetString("type")
 353			switch what {
 354			case "Person":
 355				return
 356			case "Question":
 357				return
 358			case "Note":
 359				go xonksaver(user, j, origin)
 360				return
 361			}
 362		}
 363		log.Printf("unknown Update activity")
 364		fd, _ := os.OpenFile("savedinbox.json", os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666)
 365		j.Write(fd)
 366		io.WriteString(fd, "\n")
 367		fd.Close()
 368
 369	case "Undo":
 370		obj, ok := j.GetMap("object")
 371		if !ok {
 372			log.Printf("unknown undo no object")
 373		} else {
 374			what, _ := obj.GetString("type")
 375			switch what {
 376			case "Follow":
 377				log.Printf("updating honker undo: %s", who)
 378				_, err = stmtUpdateFlavor.Exec("undub", user.ID, who, "dub")
 379				if err != nil {
 380					log.Printf("error updating honker: %s", err)
 381					return
 382				}
 383			case "Announce":
 384				xid, _ := obj.GetString("object")
 385				log.Printf("undo announce: %s", xid)
 386			case "Like":
 387			default:
 388				log.Printf("unknown undo: %s", what)
 389			}
 390		}
 391	default:
 392		go xonksaver(user, j, origin)
 393	}
 394}
 395
 396func ximport(w http.ResponseWriter, r *http.Request) {
 397	u := login.GetUserInfo(r)
 398	xid := r.FormValue("xid")
 399	xonk := getxonk(u.UserID, xid)
 400	if xonk == nil {
 401		p, _ := investigate(xid)
 402		if p != nil {
 403			xid = p.XID
 404		}
 405		j, err := GetJunk(xid)
 406		if err != nil {
 407			http.Error(w, "error getting external object", http.StatusInternalServerError)
 408			log.Printf("error getting external object: %s", err)
 409			return
 410		}
 411		log.Printf("importing %s", xid)
 412		user, _ := butwhatabout(u.Username)
 413
 414		what, _ := j.GetString("type")
 415		if isactor(what) {
 416			outbox, _ := j.GetString("outbox")
 417			gimmexonks(user, outbox)
 418			http.Redirect(w, r, "/h?xid="+url.QueryEscape(xid), http.StatusSeeOther)
 419			return
 420		}
 421		xonk = xonksaver(user, j, originate(xid))
 422	}
 423	convoy := ""
 424	if xonk != nil {
 425		convoy = xonk.Convoy
 426	}
 427	http.Redirect(w, r, "/t?c="+url.QueryEscape(convoy), http.StatusSeeOther)
 428}
 429
 430func xzone(w http.ResponseWriter, r *http.Request) {
 431	u := login.GetUserInfo(r)
 432	rows, err := stmtRecentHonkers.Query(u.UserID, u.UserID)
 433	if err != nil {
 434		log.Printf("query err: %s", err)
 435		return
 436	}
 437	defer rows.Close()
 438	var honkers []Honker
 439	for rows.Next() {
 440		var xid string
 441		rows.Scan(&xid)
 442		honkers = append(honkers, Honker{XID: xid})
 443	}
 444	rows.Close()
 445	for i, _ := range honkers {
 446		_, honkers[i].Handle = handles(honkers[i].XID)
 447	}
 448	templinfo := getInfo(r)
 449	templinfo["XCSRF"] = login.GetCSRF("ximport", r)
 450	templinfo["Honkers"] = honkers
 451	err = readviews.Execute(w, "xzone.html", templinfo)
 452	if err != nil {
 453		log.Print(err)
 454	}
 455}
 456
 457var oldoutbox = cacheNew(cacheOptions{Filler: func(name string) ([]byte, bool) {
 458	user, err := butwhatabout(name)
 459	if err != nil {
 460		return nil, false
 461	}
 462	honks := gethonksbyuser(name, false)
 463	if len(honks) > 20 {
 464		honks = honks[0:20]
 465	}
 466
 467	var jonks []junk.Junk
 468	for _, h := range honks {
 469		j, _ := jonkjonk(user, h)
 470		jonks = append(jonks, j)
 471	}
 472
 473	j := junk.New()
 474	j["@context"] = itiswhatitis
 475	j["id"] = user.URL + "/outbox"
 476	j["type"] = "OrderedCollection"
 477	j["totalItems"] = len(jonks)
 478	j["orderedItems"] = jonks
 479
 480	var buf bytes.Buffer
 481	j.Write(&buf)
 482	return buf.Bytes(), true
 483}, Duration: 1 * time.Minute})
 484
 485func outbox(w http.ResponseWriter, r *http.Request) {
 486	name := mux.Vars(r)["name"]
 487	user, err := butwhatabout(name)
 488	if err != nil {
 489		http.NotFound(w, r)
 490		return
 491	}
 492	if stealthmode(user.ID, r) {
 493		http.NotFound(w, r)
 494		return
 495	}
 496	var j []byte
 497	ok := oldoutbox.Get(name, &j)
 498	if ok {
 499		w.Header().Set("Content-Type", theonetruename)
 500		w.Write(j)
 501	} else {
 502		http.NotFound(w, r)
 503	}
 504}
 505
 506func emptiness(w http.ResponseWriter, r *http.Request) {
 507	name := mux.Vars(r)["name"]
 508	user, err := butwhatabout(name)
 509	if err != nil {
 510		http.NotFound(w, r)
 511		return
 512	}
 513	if stealthmode(user.ID, r) {
 514		http.NotFound(w, r)
 515		return
 516	}
 517	colname := "/followers"
 518	if strings.HasSuffix(r.URL.Path, "/following") {
 519		colname = "/following"
 520	}
 521	j := junk.New()
 522	j["@context"] = itiswhatitis
 523	j["id"] = user.URL + colname
 524	j["type"] = "OrderedCollection"
 525	j["totalItems"] = 0
 526	j["orderedItems"] = []junk.Junk{}
 527
 528	w.Header().Set("Content-Type", theonetruename)
 529	j.Write(w)
 530}
 531
 532func showuser(w http.ResponseWriter, r *http.Request) {
 533	name := mux.Vars(r)["name"]
 534	user, err := butwhatabout(name)
 535	if err != nil {
 536		log.Printf("user not found %s: %s", name, err)
 537		http.NotFound(w, r)
 538		return
 539	}
 540	if stealthmode(user.ID, r) {
 541		http.NotFound(w, r)
 542		return
 543	}
 544	if friendorfoe(r.Header.Get("Accept")) {
 545		j, ok := asjonker(name)
 546		if ok {
 547			w.Header().Set("Content-Type", theonetruename)
 548			w.Write(j)
 549		} else {
 550			http.NotFound(w, r)
 551		}
 552		return
 553	}
 554	u := login.GetUserInfo(r)
 555	honks := gethonksbyuser(name, u != nil && u.Username == name)
 556	templinfo := getInfo(r)
 557	filt := htfilter.New()
 558	templinfo["Name"] = user.Name
 559	whatabout := user.About
 560	whatabout = markitzero(user.About)
 561	templinfo["WhatAbout"], _ = filt.String(whatabout)
 562	templinfo["ServerMessage"] = ""
 563	templinfo["HonkCSRF"] = login.GetCSRF("honkhonk", r)
 564	honkpage(w, u, honks, templinfo)
 565}
 566
 567func showhonker(w http.ResponseWriter, r *http.Request) {
 568	u := login.GetUserInfo(r)
 569	name := mux.Vars(r)["name"]
 570	var honks []*Honk
 571	if name == "" {
 572		name = r.FormValue("xid")
 573		honks = gethonksbyxonker(u.UserID, name)
 574	} else {
 575		honks = gethonksbyhonker(u.UserID, name)
 576	}
 577	name = html.EscapeString(name)
 578	msg := fmt.Sprintf(`honks by honker: <a href="%s" ref="noreferrer">%s</a>`, name, name)
 579	templinfo := getInfo(r)
 580	templinfo["PageName"] = "honker"
 581	templinfo["PageArg"] = name
 582	templinfo["ServerMessage"] = template.HTML(msg)
 583	templinfo["HonkCSRF"] = login.GetCSRF("honkhonk", r)
 584	honkpage(w, u, honks, templinfo)
 585}
 586
 587func showcombo(w http.ResponseWriter, r *http.Request) {
 588	name := mux.Vars(r)["name"]
 589	u := login.GetUserInfo(r)
 590	honks := gethonksbycombo(u.UserID, name)
 591	honks = osmosis(honks, u.UserID)
 592	templinfo := getInfo(r)
 593	templinfo["PageName"] = "combo"
 594	templinfo["PageArg"] = name
 595	templinfo["ServerMessage"] = "honks by combo: " + name
 596	templinfo["HonkCSRF"] = login.GetCSRF("honkhonk", r)
 597	honkpage(w, u, honks, templinfo)
 598}
 599func showconvoy(w http.ResponseWriter, r *http.Request) {
 600	c := r.FormValue("c")
 601	u := login.GetUserInfo(r)
 602	honks := gethonksbyconvoy(u.UserID, c)
 603	templinfo := getInfo(r)
 604	if len(honks) > 0 {
 605		templinfo["TopXID"] = honks[0].XID
 606	}
 607	reversehonks(honks)
 608	templinfo["PageName"] = "convoy"
 609	templinfo["PageArg"] = c
 610	templinfo["ServerMessage"] = "honks in convoy: " + c
 611	templinfo["HonkCSRF"] = login.GetCSRF("honkhonk", r)
 612	honkpage(w, u, honks, templinfo)
 613}
 614func showsearch(w http.ResponseWriter, r *http.Request) {
 615	q := r.FormValue("q")
 616	u := login.GetUserInfo(r)
 617	honks := gethonksbysearch(u.UserID, q)
 618	templinfo := getInfo(r)
 619	templinfo["PageName"] = "search"
 620	templinfo["PageArg"] = q
 621	templinfo["ServerMessage"] = "honks for search: " + q
 622	templinfo["HonkCSRF"] = login.GetCSRF("honkhonk", r)
 623	honkpage(w, u, honks, templinfo)
 624}
 625func showontology(w http.ResponseWriter, r *http.Request) {
 626	name := mux.Vars(r)["name"]
 627	u := login.GetUserInfo(r)
 628	var userid int64 = -1
 629	if u != nil {
 630		userid = u.UserID
 631	}
 632	honks := gethonksbyontology(userid, "#"+name)
 633	templinfo := getInfo(r)
 634	templinfo["ServerMessage"] = "honks by ontology: " + name
 635	templinfo["HonkCSRF"] = login.GetCSRF("honkhonk", r)
 636	honkpage(w, u, honks, templinfo)
 637}
 638
 639func thelistingoftheontologies(w http.ResponseWriter, r *http.Request) {
 640	u := login.GetUserInfo(r)
 641	var userid int64 = -1
 642	if u != nil {
 643		userid = u.UserID
 644	}
 645	rows, err := stmtSelectOnts.Query(userid)
 646	if err != nil {
 647		log.Printf("selection error: %s", err)
 648		return
 649	}
 650	defer rows.Close()
 651	var onts [][]string
 652	for rows.Next() {
 653		var o string
 654		err := rows.Scan(&o)
 655		if err != nil {
 656			log.Printf("error scanning ont: %s", err)
 657			continue
 658		}
 659		onts = append(onts, []string{o, o[1:]})
 660	}
 661	if u == nil {
 662		w.Header().Set("Cache-Control", "max-age=300")
 663	}
 664	templinfo := getInfo(r)
 665	templinfo["Onts"] = onts
 666	err = readviews.Execute(w, "onts.html", templinfo)
 667	if err != nil {
 668		log.Print(err)
 669	}
 670}
 671
 672func showhonk(w http.ResponseWriter, r *http.Request) {
 673	name := mux.Vars(r)["name"]
 674	user, err := butwhatabout(name)
 675	if err != nil {
 676		http.NotFound(w, r)
 677		return
 678	}
 679	if stealthmode(user.ID, r) {
 680		http.NotFound(w, r)
 681		return
 682	}
 683	xid := fmt.Sprintf("https://%s%s", serverName, r.URL.Path)
 684
 685	if friendorfoe(r.Header.Get("Accept")) {
 686		j, ok := gimmejonk(xid)
 687		if ok {
 688			w.Header().Set("Content-Type", theonetruename)
 689			w.Write(j)
 690		} else {
 691			http.NotFound(w, r)
 692		}
 693		return
 694	}
 695	honk := getxonk(user.ID, xid)
 696	if honk == nil {
 697		http.NotFound(w, r)
 698		return
 699	}
 700	u := login.GetUserInfo(r)
 701	if u != nil && u.UserID != user.ID {
 702		u = nil
 703	}
 704	if !honk.Public {
 705		if u == nil {
 706			http.NotFound(w, r)
 707			return
 708
 709		}
 710		templinfo := getInfo(r)
 711		templinfo["ServerMessage"] = "one honk maybe more"
 712		templinfo["HonkCSRF"] = login.GetCSRF("honkhonk", r)
 713		honkpage(w, u, []*Honk{honk}, templinfo)
 714		return
 715	}
 716	rawhonks := gethonksbyconvoy(honk.UserID, honk.Convoy)
 717	reversehonks(rawhonks)
 718	var honks []*Honk
 719	for _, h := range rawhonks {
 720		if h.Public && (h.Whofore == 2 || h.IsAcked()) {
 721			honks = append(honks, h)
 722		}
 723	}
 724
 725	templinfo := getInfo(r)
 726	templinfo["ServerMessage"] = "one honk maybe more"
 727	templinfo["HonkCSRF"] = login.GetCSRF("honkhonk", r)
 728	honkpage(w, u, honks, templinfo)
 729}
 730
 731func honkpage(w http.ResponseWriter, u *login.UserInfo, honks []*Honk, templinfo map[string]interface{}) {
 732	var userid int64 = -1
 733	if u != nil {
 734		userid = u.UserID
 735	}
 736	if u == nil {
 737		w.Header().Set("Cache-Control", "max-age=60")
 738	}
 739	reverbolate(userid, honks)
 740	templinfo["Honks"] = honks
 741	if templinfo["TopXID"] == nil && len(honks) > 0 {
 742		templinfo["TopXID"] = honks[0].XID
 743	}
 744	err := readviews.Execute(w, "honkpage.html", templinfo)
 745	if err != nil {
 746		log.Print(err)
 747	}
 748}
 749
 750func saveuser(w http.ResponseWriter, r *http.Request) {
 751	whatabout := r.FormValue("whatabout")
 752	u := login.GetUserInfo(r)
 753	db := opendatabase()
 754	options := ""
 755	if r.FormValue("skinny") == "skinny" {
 756		options += " skinny "
 757	}
 758	_, err := db.Exec("update users set about = ?, options = ? where username = ?", whatabout, options, u.Username)
 759	if err != nil {
 760		log.Printf("error bouting what: %s", err)
 761	}
 762	someusers.Clear(u.Username)
 763
 764	http.Redirect(w, r, "/account", http.StatusSeeOther)
 765}
 766
 767func submitbonk(w http.ResponseWriter, r *http.Request) {
 768	xid := r.FormValue("xid")
 769	userinfo := login.GetUserInfo(r)
 770	user, _ := butwhatabout(userinfo.Username)
 771
 772	log.Printf("bonking %s", xid)
 773
 774	xonk := getxonk(userinfo.UserID, xid)
 775	if xonk == nil {
 776		return
 777	}
 778	if !xonk.Public {
 779		return
 780	}
 781	donksforhonks([]*Honk{xonk})
 782
 783	_, err := stmtUpdateFlags.Exec(flagIsBonked, xonk.ID)
 784	if err != nil {
 785		log.Printf("error acking bonk: %s", err)
 786	}
 787
 788	oonker := xonk.Oonker
 789	if oonker == "" {
 790		oonker = xonk.Honker
 791	}
 792	dt := time.Now().UTC()
 793	bonk := Honk{
 794		UserID:   userinfo.UserID,
 795		Username: userinfo.Username,
 796		What:     "bonk",
 797		Honker:   user.URL,
 798		Oonker:   oonker,
 799		XID:      xonk.XID,
 800		RID:      xonk.RID,
 801		Noise:    xonk.Noise,
 802		Precis:   xonk.Precis,
 803		URL:      xonk.URL,
 804		Date:     dt,
 805		Donks:    xonk.Donks,
 806		Whofore:  2,
 807		Convoy:   xonk.Convoy,
 808		Audience: []string{thewholeworld, oonker},
 809		Public:   true,
 810	}
 811
 812	bonk.Format = "html"
 813
 814	err = savehonk(&bonk)
 815	if err != nil {
 816		log.Printf("uh oh")
 817		return
 818	}
 819
 820	go honkworldwide(user, &bonk)
 821}
 822
 823func sendzonkofsorts(xonk *Honk, user *WhatAbout, what string) {
 824	zonk := Honk{
 825		What:     what,
 826		XID:      xonk.XID,
 827		Date:     time.Now().UTC(),
 828		Audience: oneofakind(xonk.Audience),
 829	}
 830	zonk.Public = !keepitquiet(zonk.Audience)
 831
 832	log.Printf("announcing %sed honk: %s", what, xonk.XID)
 833	go honkworldwide(user, &zonk)
 834}
 835
 836func zonkit(w http.ResponseWriter, r *http.Request) {
 837	wherefore := r.FormValue("wherefore")
 838	what := r.FormValue("what")
 839	userinfo := login.GetUserInfo(r)
 840	user, _ := butwhatabout(userinfo.Username)
 841
 842	// my hammer is too big, oh well
 843	defer oldjonks.Flush()
 844
 845	if wherefore == "ack" {
 846		xonk := getxonk(userinfo.UserID, what)
 847		if xonk != nil {
 848			_, err := stmtUpdateFlags.Exec(flagIsAcked, xonk.ID)
 849			if err != nil {
 850				log.Printf("error acking: %s", err)
 851			}
 852			sendzonkofsorts(xonk, user, "ack")
 853		}
 854		return
 855	}
 856
 857	if wherefore == "deack" {
 858		xonk := getxonk(userinfo.UserID, what)
 859		if xonk != nil {
 860			_, err := stmtClearFlags.Exec(flagIsAcked, xonk.ID)
 861			if err != nil {
 862				log.Printf("error deacking: %s", err)
 863			}
 864			sendzonkofsorts(xonk, user, "deack")
 865		}
 866		return
 867	}
 868
 869	if wherefore == "unbonk" {
 870		xonk := getbonk(userinfo.UserID, what)
 871		if xonk != nil {
 872			deletehonk(xonk.ID)
 873			xonk = getxonk(userinfo.UserID, what)
 874			_, err := stmtClearFlags.Exec(flagIsBonked, xonk.ID)
 875			if err != nil {
 876				log.Printf("error unbonking: %s", err)
 877			}
 878			sendzonkofsorts(xonk, user, "unbonk")
 879		}
 880		return
 881	}
 882
 883	log.Printf("zonking %s %s", wherefore, what)
 884	if wherefore == "zonk" {
 885		xonk := getxonk(userinfo.UserID, what)
 886		if xonk != nil {
 887			deletehonk(xonk.ID)
 888			if xonk.Whofore == 2 || xonk.Whofore == 3 {
 889				sendzonkofsorts(xonk, user, "zonk")
 890			}
 891		}
 892	}
 893	_, err := stmtSaveZonker.Exec(userinfo.UserID, what, wherefore)
 894	if err != nil {
 895		log.Printf("error saving zonker: %s", err)
 896		return
 897	}
 898}
 899
 900func edithonkpage(w http.ResponseWriter, r *http.Request) {
 901	u := login.GetUserInfo(r)
 902	user, _ := butwhatabout(u.Username)
 903	xid := r.FormValue("xid")
 904	honk := getxonk(u.UserID, xid)
 905	if honk == nil || honk.Honker != user.URL || honk.What != "honk" {
 906		log.Printf("no edit")
 907		return
 908	}
 909
 910	noise := honk.Noise
 911	if honk.Precis != "" {
 912		noise = honk.Precis + "\n\n" + noise
 913	}
 914
 915	honks := []*Honk{honk}
 916	donksforhonks(honks)
 917	reverbolate(u.UserID, honks)
 918	templinfo := getInfo(r)
 919	templinfo["HonkCSRF"] = login.GetCSRF("honkhonk", r)
 920	templinfo["Honks"] = honks
 921	templinfo["Noise"] = noise
 922	templinfo["ServerMessage"] = "honk edit"
 923	templinfo["UpdateXID"] = honk.XID
 924	if len(honk.Donks) > 0 {
 925		templinfo["SavedFile"] = honk.Donks[0].XID
 926	}
 927	err := readviews.Execute(w, "honkpage.html", templinfo)
 928	if err != nil {
 929		log.Print(err)
 930	}
 931}
 932
 933// what a hot mess this function is
 934func submithonk(w http.ResponseWriter, r *http.Request) {
 935	rid := r.FormValue("rid")
 936	noise := r.FormValue("noise")
 937
 938	userinfo := login.GetUserInfo(r)
 939	user, _ := butwhatabout(userinfo.Username)
 940
 941	dt := time.Now().UTC()
 942	updatexid := r.FormValue("updatexid")
 943	var honk *Honk
 944	if updatexid != "" {
 945		honk = getxonk(userinfo.UserID, updatexid)
 946		if honk == nil || honk.Honker != user.URL || honk.What != "honk" {
 947			log.Printf("not saving edit")
 948			return
 949		}
 950		honk.Date = dt
 951		honk.What = "update"
 952		honk.Format = "markdown"
 953	} else {
 954		xid := fmt.Sprintf("%s/%s/%s", user.URL, honkSep, xfiltrate())
 955		what := "honk"
 956		if rid != "" {
 957			what = "tonk"
 958		}
 959		honk = &Honk{
 960			UserID:   userinfo.UserID,
 961			Username: userinfo.Username,
 962			What:     what,
 963			Honker:   user.URL,
 964			XID:      xid,
 965			Date:     dt,
 966			Format:   "markdown",
 967		}
 968	}
 969
 970	noise = hooterize(noise)
 971	honk.Noise = noise
 972	translate(honk)
 973
 974	var convoy string
 975	if rid != "" {
 976		xonk := getxonk(userinfo.UserID, rid)
 977		if xonk != nil {
 978			if xonk.Public {
 979				honk.Audience = append(honk.Audience, xonk.Audience...)
 980			}
 981			convoy = xonk.Convoy
 982		} else {
 983			xonkaud, c := whosthere(rid)
 984			honk.Audience = append(honk.Audience, xonkaud...)
 985			convoy = c
 986		}
 987		for i, a := range honk.Audience {
 988			if a == thewholeworld {
 989				honk.Audience[0], honk.Audience[i] = honk.Audience[i], honk.Audience[0]
 990				break
 991			}
 992		}
 993		honk.RID = rid
 994	} else {
 995		honk.Audience = []string{thewholeworld}
 996	}
 997	if honk.Noise != "" && honk.Noise[0] == '@' {
 998		honk.Audience = append(grapevine(honk.Noise), honk.Audience...)
 999	} else {
1000		honk.Audience = append(honk.Audience, grapevine(honk.Noise)...)
1001	}
1002
1003	if convoy == "" {
1004		convoy = "data:,electrichonkytonk-" + xfiltrate()
1005	}
1006	butnottooloud(honk.Audience)
1007	honk.Audience = oneofakind(honk.Audience)
1008	if len(honk.Audience) == 0 {
1009		log.Printf("honk to nowhere")
1010		http.Error(w, "honk to nowhere...", http.StatusNotFound)
1011		return
1012	}
1013	honk.Public = !keepitquiet(honk.Audience)
1014	honk.Convoy = convoy
1015
1016	donkxid := r.FormValue("donkxid")
1017	if donkxid == "" {
1018		file, filehdr, err := r.FormFile("donk")
1019		if err == nil {
1020			var buf bytes.Buffer
1021			io.Copy(&buf, file)
1022			file.Close()
1023			data := buf.Bytes()
1024			xid := xfiltrate()
1025			var media, name string
1026			img, err := image.Vacuum(&buf, image.Params{MaxWidth: 2048, MaxHeight: 2048})
1027			if err == nil {
1028				data = img.Data
1029				format := img.Format
1030				media = "image/" + format
1031				if format == "jpeg" {
1032					format = "jpg"
1033				}
1034				name = xid + "." + format
1035				xid = name
1036			} else {
1037				maxsize := 100000
1038				if len(data) > maxsize {
1039					log.Printf("bad image: %s too much text: %d", err, len(data))
1040					http.Error(w, "didn't like your attachment", http.StatusUnsupportedMediaType)
1041					return
1042				}
1043				for i := 0; i < len(data); i++ {
1044					if data[i] < 32 && data[i] != '\t' && data[i] != '\r' && data[i] != '\n' {
1045						log.Printf("bad image: %s not text: %d", err, data[i])
1046						http.Error(w, "didn't like your attachment", http.StatusUnsupportedMediaType)
1047						return
1048					}
1049				}
1050				media = "text/plain"
1051				name = filehdr.Filename
1052				if name == "" {
1053					name = xid + ".txt"
1054				}
1055				xid += ".txt"
1056			}
1057			desc := r.FormValue("donkdesc")
1058			if desc == "" {
1059				desc = name
1060			}
1061			url := fmt.Sprintf("https://%s/d/%s", serverName, xid)
1062			fileid, err := savefile(xid, name, desc, url, media, true, data)
1063			if err != nil {
1064				log.Printf("unable to save image: %s", err)
1065				return
1066			}
1067			var d Donk
1068			d.FileID = fileid
1069			honk.Donks = append(honk.Donks, &d)
1070			donkxid = d.XID
1071		}
1072	} else {
1073		xid := donkxid
1074		url := fmt.Sprintf("https://%s/d/%s", serverName, xid)
1075		donk := finddonk(url)
1076		if donk != nil {
1077			honk.Donks = append(honk.Donks, donk)
1078		} else {
1079			log.Printf("can't find file: %s", xid)
1080		}
1081	}
1082	herd := herdofemus(honk.Noise)
1083	for _, e := range herd {
1084		donk := savedonk(e.ID, e.Name, e.Name, "image/png", true)
1085		if donk != nil {
1086			donk.Name = e.Name
1087			honk.Donks = append(honk.Donks, donk)
1088		}
1089	}
1090	memetize(honk)
1091
1092	placename := r.FormValue("placename")
1093	placelat := r.FormValue("placelat")
1094	placelong := r.FormValue("placelong")
1095	if placename != "" || placelat != "" || placelong != "" {
1096		p := new(Place)
1097		p.Name = placename
1098		p.Latitude, _ = strconv.ParseFloat(placelat, 64)
1099		p.Longitude, _ = strconv.ParseFloat(placelong, 64)
1100		p.Url = r.FormValue("placeurl")
1101		honk.Place = p
1102	}
1103	timestart := r.FormValue("timestart")
1104	if timestart != "" {
1105		t := new(Time)
1106		now := time.Now().Local()
1107		for _, layout := range []string{"2006-01-02 3:04pm", "2006-01-02 15:04", "3:04pm", "15:04"} {
1108			start, err := time.ParseInLocation(layout, timestart, now.Location())
1109			if err == nil {
1110				if start.Year() == 0 {
1111					start = time.Date(now.Year(), now.Month(), now.Day(), start.Hour(), start.Minute(), 0, 0, now.Location())
1112				}
1113				t.StartTime = start
1114				break
1115			}
1116		}
1117		timeend := r.FormValue("timeend")
1118		dur, err := time.ParseDuration(timeend)
1119		if err == nil {
1120			t.Duration = Duration(dur)
1121		}
1122		if !t.StartTime.IsZero() {
1123			honk.What = "event"
1124			honk.Time = t
1125		}
1126	}
1127
1128	if honk.Public {
1129		honk.Whofore = 2
1130	} else {
1131		honk.Whofore = 3
1132	}
1133
1134	// back to markdown
1135	honk.Noise = noise
1136
1137	if r.FormValue("preview") == "preview" {
1138		honks := []*Honk{honk}
1139		reverbolate(userinfo.UserID, honks)
1140		templinfo := getInfo(r)
1141		templinfo["HonkCSRF"] = login.GetCSRF("honkhonk", r)
1142		templinfo["Honks"] = honks
1143		templinfo["InReplyTo"] = r.FormValue("rid")
1144		templinfo["Noise"] = r.FormValue("noise")
1145		templinfo["SavedFile"] = donkxid
1146		templinfo["ServerMessage"] = "honk preview"
1147		err := readviews.Execute(w, "honkpage.html", templinfo)
1148		if err != nil {
1149			log.Print(err)
1150		}
1151		return
1152	}
1153
1154	if updatexid != "" {
1155		updatehonk(honk)
1156		oldjonks.Clear(honk.XID)
1157	} else {
1158		err := savehonk(honk)
1159		if err != nil {
1160			log.Printf("uh oh")
1161			return
1162		}
1163	}
1164
1165	// reload for consistency
1166	honk.Donks = nil
1167	donksforhonks([]*Honk{honk})
1168
1169	go honkworldwide(user, honk)
1170
1171	http.Redirect(w, r, honk.XID, http.StatusSeeOther)
1172}
1173
1174func showhonkers(w http.ResponseWriter, r *http.Request) {
1175	userinfo := login.GetUserInfo(r)
1176	templinfo := getInfo(r)
1177	templinfo["Honkers"] = gethonkers(userinfo.UserID)
1178	templinfo["HonkerCSRF"] = login.GetCSRF("submithonker", r)
1179	err := readviews.Execute(w, "honkers.html", templinfo)
1180	if err != nil {
1181		log.Print(err)
1182	}
1183}
1184
1185var combocache = cacheNew(cacheOptions{Filler: func(userid int64) ([]string, bool) {
1186	honkers := gethonkers(userid)
1187	var combos []string
1188	for _, h := range honkers {
1189		combos = append(combos, h.Combos...)
1190	}
1191	for i, c := range combos {
1192		if c == "-" {
1193			combos[i] = ""
1194		}
1195	}
1196	combos = oneofakind(combos)
1197	sort.Strings(combos)
1198	return combos, true
1199}})
1200
1201func showcombos(w http.ResponseWriter, r *http.Request) {
1202	userinfo := login.GetUserInfo(r)
1203	var combos []string
1204	combocache.Get(userinfo.UserID, &combos)
1205	templinfo := getInfo(r)
1206	err := readviews.Execute(w, "combos.html", templinfo)
1207	if err != nil {
1208		log.Print(err)
1209	}
1210}
1211
1212func submithonker(w http.ResponseWriter, r *http.Request) {
1213	u := login.GetUserInfo(r)
1214	name := r.FormValue("name")
1215	url := r.FormValue("url")
1216	peep := r.FormValue("peep")
1217	combos := r.FormValue("combos")
1218	combos = " " + strings.TrimSpace(combos) + " "
1219	honkerid, _ := strconv.ParseInt(r.FormValue("honkerid"), 10, 0)
1220
1221	defer combocache.Clear(u.UserID)
1222
1223	if honkerid > 0 {
1224		goodbye := r.FormValue("goodbye")
1225		if goodbye == "F" {
1226			db := opendatabase()
1227			row := db.QueryRow("select xid from honkers where honkerid = ? and userid = ? and flavor in ('sub')",
1228				honkerid, u.UserID)
1229			err := row.Scan(&url)
1230			if err != nil {
1231				log.Printf("can't get honker xid: %s", err)
1232				return
1233			}
1234			log.Printf("unsubscribing from %s", url)
1235			user, _ := butwhatabout(u.Username)
1236			_, err = stmtUpdateFlavor.Exec("unsub", u.UserID, url, "sub")
1237			if err != nil {
1238				log.Printf("error updating honker: %s", err)
1239				return
1240			}
1241			go itakeitallback(user, url)
1242
1243			http.Redirect(w, r, "/honkers", http.StatusSeeOther)
1244			return
1245		}
1246		if goodbye == "X" {
1247			db := opendatabase()
1248			row := db.QueryRow("select xid from honkers where honkerid = ? and userid = ? and flavor in ('unsub', 'peep')",
1249				honkerid, u.UserID)
1250			err := row.Scan(&url)
1251			if err != nil {
1252				log.Printf("can't get honker xid: %s", err)
1253				return
1254			}
1255			log.Printf("resubscribing to %s", url)
1256			user, _ := butwhatabout(u.Username)
1257			_, err = stmtUpdateFlavor.Exec("presub", u.UserID, url, "unsub")
1258			if err == nil {
1259				_, err = stmtUpdateFlavor.Exec("presub", u.UserID, url, "peep")
1260			}
1261			if err != nil {
1262				log.Printf("error updating honker: %s", err)
1263				return
1264			}
1265			go subsub(user, url)
1266
1267			http.Redirect(w, r, "/honkers", http.StatusSeeOther)
1268			return
1269		}
1270		_, err := stmtUpdateHonker.Exec(name, combos, honkerid, u.UserID)
1271		if err != nil {
1272			log.Printf("update honker err: %s", err)
1273			return
1274		}
1275		http.Redirect(w, r, "/honkers", http.StatusSeeOther)
1276		return
1277	}
1278
1279	flavor := "presub"
1280	if peep == "peep" {
1281		flavor = "peep"
1282	}
1283	p, err := investigate(url)
1284	if err != nil {
1285		http.Error(w, "error investigating: "+err.Error(), http.StatusInternalServerError)
1286		log.Printf("failed to investigate honker: %s", err)
1287		return
1288	}
1289	url = p.XID
1290
1291	db := opendatabase()
1292	row := db.QueryRow("select xid from honkers where xid = ? and userid = ? and flavor in ('sub', 'unsub', 'peep')", url, u.UserID)
1293	var x string
1294	err = row.Scan(&x)
1295	if err != sql.ErrNoRows {
1296		http.Error(w, "it seems you are already subscribed to them", http.StatusInternalServerError)
1297		if err != nil {
1298			log.Printf("honker scan err: %s", err)
1299		}
1300		return
1301	}
1302
1303	if name == "" {
1304		name = p.Handle
1305	}
1306	_, err = stmtSaveHonker.Exec(u.UserID, name, url, flavor, combos)
1307	if err != nil {
1308		log.Print(err)
1309		return
1310	}
1311	if flavor == "presub" {
1312		user, _ := butwhatabout(u.Username)
1313		go subsub(user, url)
1314	}
1315	http.Redirect(w, r, "/honkers", http.StatusSeeOther)
1316}
1317
1318func hfcspage(w http.ResponseWriter, r *http.Request) {
1319	userinfo := login.GetUserInfo(r)
1320
1321	filters := getfilters(userinfo.UserID, filtAny)
1322
1323	templinfo := getInfo(r)
1324	templinfo["Filters"] = filters
1325	templinfo["FilterCSRF"] = login.GetCSRF("filter", r)
1326	err := readviews.Execute(w, "hfcs.html", templinfo)
1327	if err != nil {
1328		log.Print(err)
1329	}
1330}
1331
1332func savehfcs(w http.ResponseWriter, r *http.Request) {
1333	userinfo := login.GetUserInfo(r)
1334	itsok := r.FormValue("itsok")
1335	if itsok == "iforgiveyou" {
1336		hfcsid, _ := strconv.ParseInt(r.FormValue("hfcsid"), 10, 0)
1337		_, err := stmtDeleteFilter.Exec(userinfo.UserID, hfcsid)
1338		if err != nil {
1339			log.Printf("error deleting filter: %s", err)
1340		}
1341		filtcache.Clear(userinfo.UserID)
1342		http.Redirect(w, r, "/hfcs", http.StatusSeeOther)
1343		return
1344	}
1345
1346	filt := new(Filter)
1347	filt.Name = r.FormValue("name")
1348	filt.Date = time.Now().UTC()
1349	filt.Actor = r.FormValue("actor")
1350	filt.IncludeAudience = r.FormValue("incaud") == "yes"
1351	filt.Text = r.FormValue("filttext")
1352	filt.IsAnnounce = r.FormValue("isannounce") == "yes"
1353	filt.AnnounceOf = r.FormValue("announceof")
1354	filt.Reject = r.FormValue("doreject") == "yes"
1355	filt.SkipMedia = r.FormValue("doskipmedia") == "yes"
1356	filt.Hide = r.FormValue("dohide") == "yes"
1357	filt.Collapse = r.FormValue("docollapse") == "yes"
1358	filt.Rewrite = r.FormValue("filtrewrite")
1359	filt.Replace = r.FormValue("filtreplace")
1360
1361	if filt.Actor == "" && filt.Text == "" {
1362		log.Printf("blank filter")
1363		return
1364	}
1365
1366	j, err := jsonify(filt)
1367	if err == nil {
1368		_, err = stmtSaveFilter.Exec(userinfo.UserID, j)
1369	}
1370	if err != nil {
1371		log.Printf("error saving filter: %s", err)
1372	}
1373
1374	filtcache.Clear(userinfo.UserID)
1375	http.Redirect(w, r, "/hfcs", http.StatusSeeOther)
1376}
1377
1378func accountpage(w http.ResponseWriter, r *http.Request) {
1379	u := login.GetUserInfo(r)
1380	user, _ := butwhatabout(u.Username)
1381	templinfo := getInfo(r)
1382	templinfo["UserCSRF"] = login.GetCSRF("saveuser", r)
1383	templinfo["LogoutCSRF"] = login.GetCSRF("logout", r)
1384	templinfo["User"] = user
1385	err := readviews.Execute(w, "account.html", templinfo)
1386	if err != nil {
1387		log.Print(err)
1388	}
1389}
1390
1391func dochpass(w http.ResponseWriter, r *http.Request) {
1392	err := login.ChangePassword(w, r)
1393	if err != nil {
1394		log.Printf("error changing password: %s", err)
1395	}
1396	http.Redirect(w, r, "/account", http.StatusSeeOther)
1397}
1398
1399func fingerlicker(w http.ResponseWriter, r *http.Request) {
1400	orig := r.FormValue("resource")
1401
1402	log.Printf("finger lick: %s", orig)
1403
1404	if strings.HasPrefix(orig, "acct:") {
1405		orig = orig[5:]
1406	}
1407
1408	name := orig
1409	idx := strings.LastIndexByte(name, '/')
1410	if idx != -1 {
1411		name = name[idx+1:]
1412		if fmt.Sprintf("https://%s/%s/%s", serverName, userSep, name) != orig {
1413			log.Printf("foreign request rejected")
1414			name = ""
1415		}
1416	} else {
1417		idx = strings.IndexByte(name, '@')
1418		if idx != -1 {
1419			name = name[:idx]
1420			if name+"@"+serverName != orig {
1421				log.Printf("foreign request rejected")
1422				name = ""
1423			}
1424		}
1425	}
1426	user, err := butwhatabout(name)
1427	if err != nil {
1428		http.NotFound(w, r)
1429		return
1430	}
1431	if stealthmode(user.ID, r) {
1432		http.NotFound(w, r)
1433		return
1434	}
1435
1436	j := junk.New()
1437	j["subject"] = fmt.Sprintf("acct:%s@%s", user.Name, serverName)
1438	j["aliases"] = []string{user.URL}
1439	var links []junk.Junk
1440	l := junk.New()
1441	l["rel"] = "self"
1442	l["type"] = `application/activity+json`
1443	l["href"] = user.URL
1444	links = append(links, l)
1445	j["links"] = links
1446
1447	w.Header().Set("Cache-Control", "max-age=3600")
1448	w.Header().Set("Content-Type", "application/jrd+json")
1449	j.Write(w)
1450}
1451
1452func somedays() string {
1453	secs := 432000 + notrand.Int63n(432000)
1454	return fmt.Sprintf("%d", secs)
1455}
1456
1457func avatate(w http.ResponseWriter, r *http.Request) {
1458	n := r.FormValue("a")
1459	a := avatar(n)
1460	w.Header().Set("Cache-Control", "max-age="+somedays())
1461	w.Write(a)
1462}
1463
1464func servecss(w http.ResponseWriter, r *http.Request) {
1465	fd, err := os.Open("views" + r.URL.Path)
1466	if err != nil {
1467		http.NotFound(w, r)
1468		return
1469	}
1470	w.Header().Set("Cache-Control", "max-age=0")
1471	w.Header().Set("Content-Type", "text/css; charset=utf-8")
1472	err = css.Filter(fd, w)
1473	if err != nil {
1474		log.Printf("error filtering css: %s", err)
1475	}
1476}
1477func serveasset(w http.ResponseWriter, r *http.Request) {
1478	w.Header().Set("Cache-Control", "max-age=7776000")
1479	http.ServeFile(w, r, "views"+r.URL.Path)
1480}
1481func servehelp(w http.ResponseWriter, r *http.Request) {
1482	name := mux.Vars(r)["name"]
1483	w.Header().Set("Cache-Control", "max-age=600")
1484	http.ServeFile(w, r, "docs/"+name)
1485}
1486func servehtml(w http.ResponseWriter, r *http.Request) {
1487	templinfo := getInfo(r)
1488	err := readviews.Execute(w, r.URL.Path[1:]+".html", templinfo)
1489	if err != nil {
1490		log.Print(err)
1491	}
1492}
1493func serveemu(w http.ResponseWriter, r *http.Request) {
1494	xid := mux.Vars(r)["xid"]
1495	w.Header().Set("Cache-Control", "max-age="+somedays())
1496	http.ServeFile(w, r, "emus/"+xid)
1497}
1498func servememe(w http.ResponseWriter, r *http.Request) {
1499	xid := mux.Vars(r)["xid"]
1500	w.Header().Set("Cache-Control", "max-age="+somedays())
1501	http.ServeFile(w, r, "memes/"+xid)
1502}
1503
1504func servefile(w http.ResponseWriter, r *http.Request) {
1505	xid := mux.Vars(r)["xid"]
1506	row := stmtGetFileData.QueryRow(xid)
1507	var media string
1508	var data []byte
1509	err := row.Scan(&media, &data)
1510	if err != nil {
1511		log.Printf("error loading file: %s", err)
1512		http.NotFound(w, r)
1513		return
1514	}
1515	w.Header().Set("Content-Type", media)
1516	w.Header().Set("X-Content-Type-Options", "nosniff")
1517	w.Header().Set("Cache-Control", "max-age="+somedays())
1518	w.Write(data)
1519}
1520
1521func nomoroboto(w http.ResponseWriter, r *http.Request) {
1522	io.WriteString(w, "User-agent: *\n")
1523	io.WriteString(w, "Disallow: /a\n")
1524	io.WriteString(w, "Disallow: /d\n")
1525	io.WriteString(w, "Disallow: /meme\n")
1526	for _, u := range allusers() {
1527		fmt.Fprintf(w, "Disallow: /%s/%s/%s/\n", userSep, u.Username, honkSep)
1528	}
1529}
1530
1531func webhydra(w http.ResponseWriter, r *http.Request) {
1532	u := login.GetUserInfo(r)
1533	userid := u.UserID
1534	templinfo := getInfo(r)
1535	templinfo["HonkCSRF"] = login.GetCSRF("honkhonk", r)
1536	page := r.FormValue("page")
1537	var honks []*Honk
1538	switch page {
1539	case "atme":
1540		honks = gethonksforme(userid)
1541		templinfo["ServerMessage"] = "at me!"
1542	case "home":
1543		honks = gethonksforuser(userid)
1544		honks = osmosis(honks, userid)
1545		templinfo["ServerMessage"] = serverMsg
1546	case "first":
1547		honks = gethonksforuserfirstclass(userid)
1548		honks = osmosis(honks, userid)
1549		templinfo["ServerMessage"] = "first class only"
1550	case "combo":
1551		c := r.FormValue("c")
1552		honks = gethonksbycombo(userid, c)
1553		honks = osmosis(honks, userid)
1554		templinfo["ServerMessage"] = "honks by combo: " + c
1555	case "convoy":
1556		c := r.FormValue("c")
1557		honks = gethonksbyconvoy(userid, c)
1558		templinfo["ServerMessage"] = "honks in convoy: " + c
1559	case "honker":
1560		xid := r.FormValue("xid")
1561		if strings.IndexByte(xid, '@') != -1 {
1562			xid = gofish(xid)
1563		}
1564		honks = gethonksbyxonker(userid, xid)
1565		xid = html.EscapeString(xid)
1566		msg := fmt.Sprintf(`honks by honker: <a href="%s" ref="noreferrer">%s</a>`, xid, xid)
1567		templinfo["ServerMessage"] = template.HTML(msg)
1568	default:
1569		http.NotFound(w, r)
1570	}
1571	if len(honks) > 0 {
1572		templinfo["TopXID"] = honks[0].XID
1573	}
1574	if topxid := r.FormValue("topxid"); topxid != "" {
1575		for i, h := range honks {
1576			if h.XID == topxid {
1577				honks = honks[0:i]
1578				break
1579			}
1580		}
1581		log.Printf("topxid %d frags", len(honks))
1582	}
1583	reverbolate(userid, honks)
1584	templinfo["Honks"] = honks
1585	w.Header().Set("Content-Type", "text/html; charset=utf-8")
1586	err := readviews.Execute(w, "honkfrags.html", templinfo)
1587	if err != nil {
1588		log.Printf("frag error: %s", err)
1589	}
1590}
1591
1592func serve() {
1593	db := opendatabase()
1594	login.Init(db)
1595
1596	listener, err := openListener()
1597	if err != nil {
1598		log.Fatal(err)
1599	}
1600	go redeliverator()
1601
1602	debug := false
1603	getconfig("debug", &debug)
1604	readviews = templates.Load(debug,
1605		"views/honkpage.html",
1606		"views/honkfrags.html",
1607		"views/honkers.html",
1608		"views/hfcs.html",
1609		"views/combos.html",
1610		"views/honkform.html",
1611		"views/honk.html",
1612		"views/account.html",
1613		"views/about.html",
1614		"views/funzone.html",
1615		"views/login.html",
1616		"views/xzone.html",
1617		"views/header.html",
1618		"views/onts.html",
1619		"views/honkpage.js",
1620	)
1621	if !debug {
1622		assets := []string{"views/style.css", "views/local.css", "views/honkpage.js"}
1623		for _, s := range assets {
1624			savedassetparams[s] = getassetparam(s)
1625		}
1626	}
1627
1628	mux := mux.NewRouter()
1629	mux.Use(login.Checker)
1630
1631	posters := mux.Methods("POST").Subrouter()
1632	getters := mux.Methods("GET").Subrouter()
1633
1634	getters.HandleFunc("/", homepage)
1635	getters.HandleFunc("/home", homepage)
1636	getters.HandleFunc("/front", homepage)
1637	getters.HandleFunc("/robots.txt", nomoroboto)
1638	getters.HandleFunc("/rss", showrss)
1639	getters.HandleFunc("/"+userSep+"/{name:[[:alnum:]]+}", showuser)
1640	getters.HandleFunc("/"+userSep+"/{name:[[:alnum:]]+}/"+honkSep+"/{xid:[[:alnum:]]+}", showhonk)
1641	getters.HandleFunc("/"+userSep+"/{name:[[:alnum:]]+}/rss", showrss)
1642	posters.HandleFunc("/"+userSep+"/{name:[[:alnum:]]+}/inbox", inbox)
1643	getters.HandleFunc("/"+userSep+"/{name:[[:alnum:]]+}/outbox", outbox)
1644	getters.HandleFunc("/"+userSep+"/{name:[[:alnum:]]+}/followers", emptiness)
1645	getters.HandleFunc("/"+userSep+"/{name:[[:alnum:]]+}/following", emptiness)
1646	getters.HandleFunc("/a", avatate)
1647	getters.HandleFunc("/o", thelistingoftheontologies)
1648	getters.HandleFunc("/o/{name:.+}", showontology)
1649	getters.HandleFunc("/d/{xid:[[:alnum:].]+}", servefile)
1650	getters.HandleFunc("/emu/{xid:[[:alnum:]_.-]+}", serveemu)
1651	getters.HandleFunc("/meme/{xid:[[:alnum:]_.-]+}", servememe)
1652	getters.HandleFunc("/.well-known/webfinger", fingerlicker)
1653
1654	getters.HandleFunc("/style.css", servecss)
1655	getters.HandleFunc("/local.css", servecss)
1656	getters.HandleFunc("/honkpage.js", serveasset)
1657	getters.HandleFunc("/about", servehtml)
1658	getters.HandleFunc("/login", servehtml)
1659	posters.HandleFunc("/dologin", login.LoginFunc)
1660	getters.HandleFunc("/logout", login.LogoutFunc)
1661	getters.HandleFunc("/help/{name:[[:alnum:]_.-]+}", servehelp)
1662
1663	loggedin := mux.NewRoute().Subrouter()
1664	loggedin.Use(login.Required)
1665	loggedin.HandleFunc("/account", accountpage)
1666	loggedin.HandleFunc("/funzone", showfunzone)
1667	loggedin.HandleFunc("/chpass", dochpass)
1668	loggedin.HandleFunc("/atme", homepage)
1669	loggedin.HandleFunc("/hfcs", hfcspage)
1670	loggedin.HandleFunc("/xzone", xzone)
1671	loggedin.HandleFunc("/edit", edithonkpage)
1672	loggedin.Handle("/honk", login.CSRFWrap("honkhonk", http.HandlerFunc(submithonk)))
1673	loggedin.Handle("/bonk", login.CSRFWrap("honkhonk", http.HandlerFunc(submitbonk)))
1674	loggedin.Handle("/zonkit", login.CSRFWrap("honkhonk", http.HandlerFunc(zonkit)))
1675	loggedin.Handle("/savehfcs", login.CSRFWrap("filter", http.HandlerFunc(savehfcs)))
1676	loggedin.Handle("/saveuser", login.CSRFWrap("saveuser", http.HandlerFunc(saveuser)))
1677	loggedin.Handle("/ximport", login.CSRFWrap("ximport", http.HandlerFunc(ximport)))
1678	loggedin.HandleFunc("/honkers", showhonkers)
1679	loggedin.HandleFunc("/h/{name:[[:alnum:]_.-]+}", showhonker)
1680	loggedin.HandleFunc("/h", showhonker)
1681	loggedin.HandleFunc("/c/{name:[[:alnum:]_.-]+}", showcombo)
1682	loggedin.HandleFunc("/c", showcombos)
1683	loggedin.HandleFunc("/t", showconvoy)
1684	loggedin.HandleFunc("/q", showsearch)
1685	loggedin.HandleFunc("/hydra", webhydra)
1686	loggedin.Handle("/submithonker", login.CSRFWrap("submithonker", http.HandlerFunc(submithonker)))
1687
1688	err = http.Serve(listener, mux)
1689	if err != nil {
1690		log.Fatal(err)
1691	}
1692}